Kayıt sınıfları, bileşen alanlarını otomatik olarak final olarak ilan eder, bu da inşaattan sonra değiştirilmelerini yasaklar. Bir kompakt yapıcı kullanıldığında—resmi parametre listesi hariç—Java derleyicisi, this.component = ... gibi açık alan atamalarını yasaklar çünkü yapıcı gövdesinin yürütülmesinin hemen ardından otomatik olarak atama bytecode'u enjekte eder. Bu tasarım, geliştiricilerin alanları doğrudan değil, kendilerinin parametre değişkenlerini yeniden atamalarını zorunlu kılar (örn. component = Objects.requireNonNull(component)). Sonuç olarak, değişken bileşenler için savunma kopyalamak zorunlu hale gelir; çünkü kayıt referansları depoladığından, kompakt yapıcı içinde değişken argümanların çoğaltılmaması, dışsal değişikliklerin kaydın değişmezlik garantisini ihlal etmesine neden olur.
Bir yüksek frekanslı ticaret platformu geliştirilirken, mimari ekip, BigDecimal fiyatı ve java.util.Date zaman damgası içeren değişmez piyasa verisi tıklarını temsil etmek için Kayıt sınıflarını benimsedi. Date'in değişkenliği, zaman damgası nesnesinin kayıt oluşturulmasından sonra bir üretici iş parçacığı tarafından değiştirilmesiyle bir yarış koşulu oluşturabileceği için kritik bir zafiyet teşkil etti ve denetim izini bozdu.
Bu açığı azaltmak için üç yaklaşım değerlendirildi. İlk strateji, değişmez bir zaman türü olan java.time.Instant'a geçiş yapmayı içeriyordu. Bu, savunma kopyalama aşamasını ortadan kaldırdı ve modern Java zaman API'leriyle uyumlu hale gelse de, Date nesnelerini serileştiren eski ara yazılım bileşenlerinin kapsamlı şekilde yeniden yapılandırılmasını gerektiriyordu ve bu da kabul edilemeyecek bir teslimat riski oluşturuyordu.
İkinci seçenek, savunma kopyalı bir statik fabrika yöntemi kullanarak kurucuya yönlendirmeden önce kopyalama yapmayı içeriyordu. Bu yaklaşım kapsüllemeyi korudu ancak kayıtların içsel faydaları olan kısa sözdizimi ve otomatik yapısal eşitlik avantajlarından feragat etti ve ayrıca kanonik yapıcı kalıplarını bekleyen serileştirme çerçevelerini karmaşıklaştırdı.
Son çözüm, girdi doğrulamayı ve savunma kopyalarını oluşturmak için bir kompakt yapıcı kullandı: timestamp = (Date) timestamp.clone();. Bu, kopyayı orijinal referans yerine saklamak için derleyicinin örtük alan atamasını kullandı ve kayıt anlamını bozmadan iş parçacığı güvenliğini sağladı.
Uygulama, zaman manipülasyonu saldırılarını başarılı bir şekilde engelledi ve milyonlarca eşzamanlı işlem içeren sonraki stres testlerinde veri bozulması olaylarını sıfıra indirdi.
Neden derleyici kompakt bir yapıcı içinde açık this.field atamasını reddeder fakat bunun normal yapıcılarda yapılmasına izin verir?
Java Dil Spesifikasyonu, kompakt yapıcıları, derleyicinin parametre listesini sentezlediği ve alan atamalarını eklediği kanonik yapıcılara genişletilmiş olarak tanımlamaktadır. Kayıt bileşenleri otomatik olarak final olduğundan, kompakt yapıcı gövdesi, alanların "kesin şekilde atanmadığı" bir ön-atama durumunda yürütülmektedir. Herhangi bir açık this.field ataması, final bir değişkene yapılan ikinci bir atamayı teşkil eder ve kesin atama kurallarını ihlal ederken, parametre değişkenini yeniden atamak, yalnızca takip eden örtük atamayı gölgede bırakır.
Kayıtların kompakt yapıcılarında savunma kopyası kullanımı, ObjectInputStream ile serileştirme saldırılarına karşı nasıl koruma sağlar?
Geleneksel Serializable sınıflarının, JVM tarafından Unsafe tahsisi ile başlatıldığını ve yansıma veya readObject yöntemleri ile doldurulduğunu unutmayın; serileştirilmiş kayıtlar her zaman akışla sağlanan argümanlarla kanonik yapıcı çağrılarak yeniden oluşturulur. Bu nedenle, kompakt yapıcı içinde yürütülen savunma kopyalama mantığı, daha sonra değiştirilmek üzere değişken nesneleri enjekte etmeye çalışan kötü niyetli veya bozulmuş giriş akışlarını otomatik olarak sterilize eder. Geliştiriciler bu mekanizmayı sıkça göz ardı eder ve standart serileştirme sırasında ne gerekli ne de çağrılan kayıtlarda readObject veya readResolve yöntemlerini yanış bir şekilde uygularlar.
Bir kompakt yapıcı ile açıkça tanımlanmış bir kanonik yapıcı arasında hangi bytecode ayrımı vardır?
Kompakt bir yapıcı, invokespecial (Object‘in yapıcısını çağırma) ile izlenen ve ardından yapıcının mantığının gelmesi, ardından her bir bileşen için derleyici tarafından oluşturulan putfield talimatlarıyla derlenir. Tersine, açık bir kanonik yapıcı, geliştirici tarafından yazılmış putfield işlemlerini içerir. Bu ayrım, kompakt yapıcıların alan başlatma işlemlerinden sonra doğrulama veya mantıksal işlemler gerçekleştirmelerini engeller, bu da başlangıç sırasını temel olarak kısıtlar ve tüm savunma dönüşümlerinin örtük atamalar yürütülmeden önce parametre değişkenleri üzerinde gerçekleşmesini gerektirir.