Custom Annotation ve Annotation Processing
Java kodunda her yerde @Override, @Deprecated, @SuppressWarnings gibi annotation'lar görürsün. Bunlar sadece derleyiciye veya araçlara bilgi veren etiketler gibi görünür — ama arkasında çok güçlü bir mekanizma yatar. Kendi annotation'ını tanımlayabilir, runtime'da reflection ile okuyabilir, hatta compile-time'da kod üretebilirsin.
Lombok'un @Getter/@Setter ile getter-setter yazdığını, Spring'in @Autowired ile dependency injection yaptığını biliyorsun. Ama bunlar nasıl çalışıyor? Bu derste annotation'ların iç yüzünü öğrenecek, kendi annotation'larını oluşturacak ve Java'nın annotation processing mekanizmasını keşfedeceğiz.
1. Annotation Nedir?
Annotation, Java koduna metadata (üstveri) ekler. Kodun davranışını doğrudan değiştirmez ama derleyiciye, framework'lere veya çalışma zamanı araçlarına bilgi verir.
🎯 Analoji — Etiket Sistemi:
>
Annotation'ları bir etiketleme sistemi gibi düşün. Bir depoda kutular var — her kutunun üstüne "Kırılacak", "Bu Taraf Yukarı", "Soğukta Sakla" gibi etiketler yapıştırırsın. Kutu hâlâ aynı kutudur, içindeki ürün değişmez. Ama kargo şirketi bu etiketleri okuyup davranışını değiştirir: kırılacak olanı dikkatli taşır, soğukta saklanması gerekeni soğuk zincire alır.
>
Java'da
@Override, metoda yapıştırılmış bir "Bu bir override — kontrol et" etiketidir.@Deprecated, "Bu artık kullanılmasın" etiketidir. Sen kendi etiketlerini de tanımlayabilirsin:@Cacheable,@RateLimit,@Audited... Derleyici veya framework bu etiketleri okuyup gerekli işlemi yapar.
Yerleşik Annotation'lar (Hızlı Hatırlatma)
Bunları zaten biliyorsun, hızlıca üzerinden geçelim:
class Animal {
@Deprecated(since = "2.0", forRemoval = true)
void makeSound() {
System.out.println("...");
}
}
class Dog extends Animal {
@Override // Derleyici kontrol eder: üst sınıfta var mı?
void makeSound() {
System.out.println("Hav hav!");
}
@SuppressWarnings("unchecked") // Uyarıyı bastır
void processRawList() {
// ...
}
}@Override yanlış yazımları yakalar, @Deprecated kullanıcıyı uyarır, @SuppressWarnings gereksiz uyarıları susturur. Peki bunların arkasında ne var? Nasıl kendi annotation'ımızı yazabiliriz?
2. Custom Annotation Tanımlama: @interface
Bir annotation tanımlamak @interface anahtar kelimesiyle yapılır. Görünüşte bir interface'e benzer ama farklıdır.
En Basit Annotation
// Hiçbir parametre almayan, sadece işaretleme amaçlı annotation
@interface Important {
}
// Kullanım
@Important
class PaymentService {
// ...
}Bu kadar! @interface ile bir annotation tanımladın. Ama şu an hiçbir şey yapmıyor — sadece bir etiket. Bir şey yapması için birinin bu etiketi okuması gerekir (reflection veya annotation processor).
Parametreli Annotation
Annotation'lara parametre ekleyerek daha zengin metadata taşıyabilirsin.
@interface Author {
String name();
String date();
int version() default 1; // varsayılan değer — opsiyonel
}
// Kullanım
@Author(name = "Tolga", date = "2024-01-15", version = 2)
class OrderService {
// ...
}
// version varsayılan olduğundan yazılmayabilir
@Author(name = "Ahmet", date = "2024-03-20")
class UserService {
// ...
}Annotation parametreleri aslında soyut metod gibi tanımlanır. default ile varsayılan değer verebilirsin. Parametre tipleri sınırlıdır: primitifler, String, Class, enum, annotation ve bunların dizileri.
value() Kısayolu
Eğer annotation'ın tek bir parametresi varsa ve adı value ise, kullanırken parametre adını yazmana gerek kalmaz.
@interface Priority {
String value();
}
// Her ikisi de aynı şeyi yapar
@Priority(value = "HIGH")
class CriticalTask { }
@Priority("HIGH") // value() olduğu için kısa yol
class AnotherTask { }3. Meta-Annotations: Annotation'ları Kontrol Etmek
Custom annotation'ını tanımlarken iki kritik soruyu yanıtlamalısın: Nerede kullanılabilir? ve Ne zaman erişilebilir? Bu soruları meta-annotation'lar yanıtlar — yani annotation'lara uygulanan annotation'lar.
@Retention — Ne Kadar Yaşar?
Annotation'ın ne zaman mevcut olacağını belirler.
import java.lang.annotation.*;
// SOURCE: Sadece kaynak kodda yaşar, .class dosyasına yazılmaz
// Örnek: @Override, @SuppressWarnings
@Retention(RetentionPolicy.SOURCE)
@interface Todo {
String value();
}
// CLASS: .class dosyasına yazılır ama runtime'da okunamaz (varsayılan)
// Örnek: Bytecode analiz araçları için
@Retention(RetentionPolicy.CLASS)
@interface GeneratedBy {
String tool();
}
// RUNTIME: Runtime'da reflection ile okunabilir — en sık kullanılan
// Örnek: Spring @Component, JUnit @Test
@Retention(RetentionPolicy.RUNTIME)
@interface Cacheable {
int ttlSeconds() default 300;
}Pratikte neredeyse her zaman RUNTIME kullanırsın. Çünkü annotation'ı okumanın en yaygın yolu reflection'dır ve reflection runtime'da çalışır. SOURCE, Lombok gibi compile-time araçlar ve IDE'ler için kullanılır.
@Target — Nereye Yapıştırılabilir?
Annotation'ın hangi Java öğelerine uygulanabileceğini kısıtlar.
import java.lang.annotation.*;
@Target(ElementType.METHOD) // Sadece metodlara
@Retention(RetentionPolicy.RUNTIME)
@interface Timed {
}
@Target({ElementType.FIELD, ElementType.PARAMETER}) // Alanlara ve parametrelere
@Retention(RetentionPolicy.RUNTIME)
@interface NotNull {
}
@Target(ElementType.TYPE) // Sınıf, interface, enum
@Retention(RetentionPolicy.RUNTIME)
@interface Entity {
}Yaygın ElementType değerleri:
| ElementType | Uygulanabileceği Yer |
|---|---|
TYPE | Sınıf, interface, enum, record |
METHOD | Metod |
FIELD | Alan (instance variable) |
PARAMETER | Metod parametresi |
CONSTRUCTOR | Constructor |
LOCAL_VARIABLE | Yerel değişken |
ANNOTATION_TYPE | Başka bir annotation (meta-annotation) |
TYPE_USE | Her tür kullanımı (Java 8+, generics dahil) |
Diğer Meta-Annotations
// @Documented — Javadoc'ta görünsün
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ApiEndpoint {
String path();
}
// @Inherited — Alt sınıflara miras geçsin
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Auditable {
}
@Auditable
class BaseService { }
class OrderService extends BaseService { }
// OrderService da @Auditable'dır — miras geçti
// @Repeatable — Aynı öğeye birden fazla kez uygulanabilsin
@Repeatable(Roles.class) // konteyner annotation'ı belirt
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Role {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Roles {
Role[] value(); // konteyner
}
@Role("ADMIN")
@Role("MANAGER")
class AdminService { }@Inherited sadece sınıflarda çalışır (interface'lerde miras geçmez). @Repeatable Java 8 ile geldi ve bir konteyner annotation gerektirir.
4. Runtime Annotation Okuma (Reflection İle)
Annotation tanımladık, şimdi çalışma zamanında okuyalım. Bu iş reflection API ile yapılır.
Basit Örnek: @NotNull Validator
Alanların null olup olmadığını annotation'a bakarak kontrol eden bir validator yazalım.
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface NotNull {
String message() default "bu alan null olamaz";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface MinLength {
int value();
String message() default "minimum uzunluk sağlanmadı";
}
class User {
@NotNull
String name;
@NotNull(message = "Email zorunludur")
@MinLength(5)
String email;
int age; // annotation yok — kontrol edilmez
User(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
}
class Validator {
static void validate(Object obj) throws Exception {
Class<?> clazz = obj.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true); // private alanlara erişim
Object value = field.get(obj);
// @NotNull kontrolü
if (field.isAnnotationPresent(NotNull.class)) {
NotNull notNull = field.getAnnotation(NotNull.class);
if (value == null) {
throw new IllegalArgumentException(
field.getName() + ": " + notNull.message()
);
}
}
// @MinLength kontrolü
if (field.isAnnotationPresent(MinLength.class)) {
MinLength minLen = field.getAnnotation(MinLength.class);
if (value instanceof String s && s.length() < minLen.value()) {
throw new IllegalArgumentException(
field.getName() + ": " + minLen.message() +
" (min: " + minLen.value() + ", actual: " + s.length() + ")"
);
}
}
}
}
}
class Main {
public static void main(String[] args) {
// Geçerli kullanıcı
try {
User validUser = new User("Tolga", "tolga@mail.com", 30);
Validator.validate(validUser);
System.out.println("✓ Geçerli kullanıcı");
} catch (Exception e) {
System.out.println("✗ " + e.getMessage());
}
// Null isim
try {
User nullName = new User(null, "test@mail.com", 25);
Validator.validate(nullName);
} catch (Exception e) {
System.out.println("✗ " + e.getMessage());
// ✗ name: bu alan null olamaz
}
// Kısa email
try {
User shortEmail = new User("Ali", "a@b", 20);
Validator.validate(shortEmail);
} catch (Exception e) {
System.out.println("✗ " + e.getMessage());
// ✗ email: minimum uzunluk sağlanmadı (min: 5, actual: 3)
}
}
}Bu örnek basit ama güçlü bir pattern'dir. Spring Validation (@NotNull, @Size, @Email gibi annotation'lar) temelde aynı mantığı kullanır — reflection ile alanları tarar ve annotation'lara göre kuralları uygular.
@Timed Method Profiler
Metodların çalışma süresini otomatik ölçen bir profiler yazalım. Burada dynamic proxy kullanarak annotation'ı okuyan bir mekanizma kuracağız.
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Timed {
String label() default "";
}
interface OrderService {
String processOrder(String orderId);
void cancelOrder(String orderId);
}
class OrderServiceImpl implements OrderService {
@Timed(label = "sipariş-işleme")
public String processOrder(String orderId) {
sleep(150); // Veritabanı işlemi simülasyonu
return "Order " + orderId + " processed";
}
@Timed
public void cancelOrder(String orderId) {
sleep(80);
System.out.println("Order " + orderId + " cancelled");
}
private static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}
class TimingProxy implements InvocationHandler {
private final Object target;
TimingProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Gerçek sınıftaki metodu bul (interface'deki değil)
Method targetMethod = target.getClass().getMethod(
method.getName(), method.getParameterTypes()
);
if (targetMethod.isAnnotationPresent(Timed.class)) {
Timed timed = targetMethod.getAnnotation(Timed.class);
String label = timed.label().isEmpty() ? method.getName() : timed.label();
long start = System.nanoTime();
Object result = method.invoke(target, args);
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.printf("[TIMER] %s: %dms%n", label, elapsed);
return result;
}
return method.invoke(target, args);
}
@SuppressWarnings("unchecked")
static <T> T create(T target, Class<T> iface) {
return (T) Proxy.newProxyInstance(
iface.getClassLoader(),
new Class[]{iface},
new TimingProxy(target)
);
}
}
class Main {
public static void main(String[] args) {
OrderService service = TimingProxy.create(new OrderServiceImpl(), OrderService.class);
String result = service.processOrder("ORD-001");
System.out.println(result);
// [TIMER] sipariş-işleme: 150ms
// Order ORD-001 processed
service.cancelOrder("ORD-002");
// Order ORD-002 cancelled
// [TIMER] cancelOrder: 80ms
}
}Bu örnekte @Timed annotation'ı tanımladık, TimingProxy ise dynamic proxy kullanarak her metod çağrısında annotation'ı kontrol eder ve süreyi ölçer. Spring AOP ve Micrometer gibi framework'ler de temelde bu pattern'i kullanır.
5. Compile-Time Annotation Processing
Runtime reflection güçlüdür ama bir maliyeti vardır: her çağrıda reflection API kullanılır, bu da performans etkisi yaratır. Alternatif olarak annotation'ları derleme zamanında işleyebilirsin. Java'nın javax.annotation.processing paketi bunu sağlar.
Nasıl Çalışır?
Javac (Java derleyicisi) kodu derlerken, kayıtlı annotation processor'ları çağırır. Processor'lar kaynak kodu okur, annotation'ları bulur ve:
Yeni Java dosyaları üretebilir (Lombok, MapStruct gibi)
Derleme hatası veya uyarısı verebilir (Google AutoValue gibi)
Doğrulama yapabilir (Checker Framework gibi)
AbstractProcessor Yapısı
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
import java.util.Set;
@SupportedAnnotationTypes("com.example.Immutable") // İşlenecek annotation
@SupportedSourceVersion(SourceVersion.RELEASE_21)
public class ImmutableProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(Immutable.class)) {
if (element.getKind() != ElementKind.CLASS) {
processingEnv.getMessager().printMessage(
Diagnostic.Kind.ERROR,
"@Immutable sadece sınıflara uygulanabilir",
element
);
continue;
}
TypeElement classElement = (TypeElement) element;
// Tüm alanları kontrol et — final olmalı
for (Element enclosed : classElement.getEnclosedElements()) {
if (enclosed.getKind() == ElementKind.FIELD) {
if (!enclosed.getModifiers().contains(Modifier.FINAL)) {
processingEnv.getMessager().printMessage(
Diagnostic.Kind.ERROR,
"@Immutable sınıfında tüm alanlar final olmalı: " +
enclosed.getSimpleName(),
enclosed
);
}
}
}
}
return true; // true: bu annotation başka processor'lara gitmez
}
}Bu processor, @Immutable ile işaretlenmiş sınıflardaki tüm alanların final olup olmadığını kontrol eder. Değilse derleme hatası verir — runtime'a bile geçemezsin.
Processor Kayıt Etme
Annotation processor'ın javac tarafından bulunması için kayıt edilmesi gerekir:
src/main/resources/META-INF/services/javax.annotation.processing.ProcessorBu dosyanın içine processor sınıfının tam adını yaz:
com.example.ImmutableProcessorVeya Google AutoService kullanarak otomatik kayıt yapabilirsin:
@AutoService(Processor.class) // META-INF/services dosyasını otomatik üretir
public class ImmutableProcessor extends AbstractProcessor {
// ...
}Processor'lar ayrıca processingEnv.getFiler().createSourceFile() ile yeni Java dosyaları da üretebilir. Derleme sırasında üretilen .java dosyaları bir sonraki derleme turunda derlenir. MapStruct ve Dagger gibi kütüphaneler bu yöntemle kod üretir.
⚠️ Dikkat: Annotation processor'lar mevcut kodu değiştiremez — sadece yeni dosyalar üretebilir. Lombok bu kuralı "hack" ederek AST manipülasyonu yapar, bu yüzden tartışmalı bir araçtır. Standart annotation processing sadece yeni dosya üretir.
6. Lombok Nasıl Çalışır?
Lombok Java dünyasının en popüler ve en tartışmalı kütüphanesidir. @Getter, @Setter, @ToString, @Builder gibi annotation'larla boilerplate kodu otomatik üretir.
// Lombok ile — 6 satır
@Getter @Setter @ToString @EqualsAndHashCode
@AllArgsConstructor @NoArgsConstructor
class User {
private String name;
private String email;
private int age;
}
// Lombok olmadan — 80+ satır:
// constructor, getter×3, setter×3, toString, equals, hashCode...Lombok'un Mekanizması
Lombok standart annotation processing API'sini kullanır ama bir adım ötesine geçer:
Compile-time annotation processor olarak javac'a kaydolur
Abstract Syntax Tree (AST) — derleyicinin kod temsilini — doğrudan manipüle eder
Getter, setter, constructor gibi metodları AST'ye ekler
Javac bu değiştirilmiş AST'yi derler — sonuçta
.classdosyasında tüm metodlar mevcuttur
Bu yaklaşım resmi API'nin dışındadır. Standart annotation processing sadece yeni dosya üretir, mevcut kodu değiştirmez. Lombok ise javac'ın iç API'lerini kullanarak mevcut sınıfa metod ekler.
Lombok vs Java Records
Java 16+ ile gelen record türü, birçok Lombok kullanım senaryosunu doğrudan dil seviyesinde çözer:
// Record — immutable veri taşıyıcı
record User(String name, String email, int age) { }
// Otomatik: constructor, getter (name(), email(), age()),
// toString, equals, hashCode
// Lombok — mutable, daha esnek
@Data // @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor
class User {
private String name;
private String email;
private int age;
}Record'lar immutable, Lombok mutable. Basit veri taşıyıcılar için record tercih et. Setter gereken, karmaşık builder pattern'li sınıflar için Lombok hâlâ değerli.
7. Gerçek Dünya: Mini Validation Framework
Şimdiye kadar öğrendiğimiz her şeyi birleştirip küçük ama işlevsel bir doğrulama framework'ü yazalım.
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
// ====== ANNOTATION TANIMLARI ======
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface NotEmpty {
String message() default "boş olamaz";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Range {
int min() default 0;
int max() default Integer.MAX_VALUE;
String message() default "aralık dışında";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Pattern {
String regex();
String message() default "format geçersiz";
}
// ====== MODEL ======
class Employee {
@NotEmpty(message = "İsim zorunludur")
String name;
@NotEmpty
@Pattern(regex = "^[\\w.]+@[\\w.]+\\.[a-z]{2,}$", message = "Geçersiz email formatı")
String email;
@Range(min = 18, max = 65, message = "Yaş 18-65 arasında olmalı")
int age;
@Range(min = 0, message = "Maaş negatif olamaz")
double salary;
Employee(String name, String email, int age, double salary) {
this.name = name;
this.email = email;
this.age = age;
this.salary = salary;
}
}
// ====== VALIDATION ENGINE ======
class ValidationResult {
private final List<String> errors = new ArrayList<>();
void addError(String fieldName, String message) {
errors.add(fieldName + ": " + message);
}
boolean isValid() { return errors.isEmpty(); }
List<String> getErrors() { return Collections.unmodifiableList(errors); }
@Override
public String toString() {
return isValid() ? "✓ Geçerli" :
"✗ " + errors.size() + " hata:\n - " + String.join("\n - ", errors);
}
}
class ValidationEngine {
static ValidationResult validate(Object obj) {
ValidationResult result = new ValidationResult();
for (Field field : obj.getClass().getDeclaredFields()) {
field.setAccessible(true);
Object value;
try {
value = field.get(obj);
} catch (IllegalAccessException e) {
continue;
}
// @NotEmpty
if (field.isAnnotationPresent(NotEmpty.class)) {
NotEmpty ann = field.getAnnotation(NotEmpty.class);
if (value == null || (value instanceof String s && s.isBlank())) {
result.addError(field.getName(), ann.message());
}
}
// @Range
if (field.isAnnotationPresent(Range.class)) {
Range ann = field.getAnnotation(Range.class);
if (value instanceof Number num) {
double val = num.doubleValue();
if (val < ann.min() || val > ann.max()) {
result.addError(field.getName(),
ann.message() + " (min: " + ann.min() + ", max: " + ann.max() +
", değer: " + num + ")");
}
}
}
// @Pattern
if (field.isAnnotationPresent(Pattern.class)) {
Pattern ann = field.getAnnotation(Pattern.class);
if (value instanceof String s && !s.matches(ann.regex())) {
result.addError(field.getName(), ann.message());
}
}
}
return result;
}
}
// ====== KULLANIM ======
class Main {
public static void main(String[] args) {
// Geçerli çalışan
Employee valid = new Employee("Tolga", "tolga@company.com", 30, 50000);
System.out.println("Test 1: " + ValidationEngine.validate(valid));
// ✓ Geçerli
// Boş isim + geçersiz email
Employee invalid1 = new Employee("", "not-an-email", 30, 50000);
System.out.println("\nTest 2: " + ValidationEngine.validate(invalid1));
// ✗ 2 hata:
// - name: İsim zorunludur
// - email: Geçersiz email formatı
// Yaş aralık dışı + negatif maaş
Employee invalid2 = new Employee("Ali", "ali@mail.com", 15, -1000);
System.out.println("\nTest 3: " + ValidationEngine.validate(invalid2));
// ✗ 2 hata:
// - age: Yaş 18-65 arasında olmalı (min: 18, max: 65, değer: 15)
// - salary: Maaş negatif olamaz (min: 0, max: 2147483647, değer: -1000.0)
// Null email
Employee invalid3 = new Employee("Zeynep", null, 40, 75000);
System.out.println("\nTest 4: " + ValidationEngine.validate(invalid3));
// ✗ 1 hata:
// - email: boş olamaz
}
}Bu framework küçük ama gerçek dünya pattern'lerini gösteriyor: annotation tanımlama, retention/target ayarlama, reflection ile okuma, birden fazla annotation'ı birlikte işleme. Spring Validation veya Jakarta Bean Validation da temelde aynı yaklaşımı kullanır — çok daha fazla annotation ve edge case desteğiyle.
8. Best Practices: Ne Zaman Custom Annotation Yazmalı?
Custom annotation güçlü bir araçtır ama her yerde kullanılmamalıdır.
✅ Annotation Yaz — Bu Durumlar İçin
Cross-cutting concern'ler: Loglama, caching, güvenlik, metrik toplama gibi birçok sınıfta tekrarlanan işler.
@Cached(ttl = 60)
Product getProductById(Long id) { ... }
@RateLimit(maxRequests = 100, windowSeconds = 60)
Response handleRequest(Request req) { ... }
@Audited
void transferMoney(Account from, Account to, BigDecimal amount) { ... }Declarative konfigürasyon: Framework'e "ne istediğini" söyleyip "nasıl yapılacağını" framework'e bırakmak.
@Entity
@Table(name = "users")
class User {
@Id @GeneratedValue
Long id;
@Column(nullable = false, length = 100)
String name;
}Validation kuralları: Veri doğrulama işlemlerini deklaratif yapmak.
@NotNull @Email
String email;
@Min(0) @Max(150)
int age;❌ Annotation Yazma — Bu Durumlar İçin
Basit bir metod çağrısı yeterliyse: Annotation, basit bir if kontrolünden daha karmaşık olmamalı.
// ❌ Overengineering
@ShouldLog
void process() { }
// ✅ Basit çözüm
void process() {
logger.info("Processing...");
}Akış kontrolü için: Annotation'lar iş mantığı akışını yönetmek için uygun değil.
Tek bir yerde kullanılıyorsa: Annotation, tekrarlanan pattern'ler için anlamlıdır. Tek bir sınıfta tek bir metod için annotation yazmak gereksiz karmaşıklıktır.
Karar Çerçevesi
Annotation yazmadan önce şu soruları sor:
Tekrarlanan bir pattern mi? 3+ yerde aynı mantık varsa annotation düşün.
Framework veya kütüphanede mevcut var mı? Spring, Jakarta, Lombok zaten zengin annotation seti sunar — tekerleği yeniden icat etme.
Declarative yaklaşım imperative'den daha temiz mi? Annotation kodun okunabilirliğini artırıyor mu yoksa gizliyor mu?
Runtime mı compile-time mı? Reflection maliyetini tolere edebilir misin? Performans kritikse compile-time processing düşün.
💡 İpucu: Annotation magic'e dikkat et. Bir sınıfın davranışını anlamak için 5 farklı annotation'ın ne yaptığını bilmek gerekiyorsa, kod daha az okunabilir hale gelmiştir. Annotation'lar basit ve öngörülebilir davranmalı — sürpriz yapmamalı.
9. Özet
Annotation, Java koduna metadata ekleyen etiketlerdir.
@interfaceile tanımlanır. Parametreleri olabilir vedefaultile varsayılan değer alır.Meta-annotations ile annotation'ların davranışını kontrol edersin:
@Retention(ne zaman erişilebilir),@Target(nereye uygulanabilir),@Inherited(miras),@Repeatable(tekrar).Runtime annotation okuma reflection ile yapılır:
isAnnotationPresent(),getAnnotation(),getDeclaredAnnotations(). Validation, profiling, ORM mapping gibi senaryolarda kullanılır.Compile-time annotation processing ile derleme sırasında kod üretebilir veya derleme hatası verebilirsin.
AbstractProcessorsınıfını extend ederek yazılır.Lombok, annotation processing ile AST manipülasyonu yaparak boilerplate kodu otomatik üretir. Java 16+ record'lar bazı Lombok kullanımlarını doğrudan dil seviyesinde çözer.
Custom annotation yaz tekrarlanan cross-cutting concern'ler (caching, loglama, validation) ve declarative konfigürasyon için. Tek kullanımlık veya basit bir metod çağrısıyla çözülebilecek durumlar için annotation yazmaktan kaçın.
AI Asistan
Sorularını yanıtlamaya hazır