Site icon Java blog

Java #50: własne adnotacje – zastosowanie

java annotation

Znasz już kilka adnotacji (np. @Override). Język Java umożliwia tworzenie ich dowolnie dużej ilości tzw. customowych (od ang. custom) adnotacji. Jednak zanim zaprojektujesz własne adnotacje, czas przyjrzeć im się z bliska. Nazwy adnotacji zaczynają się od znaku małpy (@).

Podstawowe właściwości

Adnotacje, w zależności od jej funkcji, z reguły stawiasz przed nazwą:

Adnotacja może zawierać składowe, takie jak:

Żadna ze składowych nie może być nullem, chociaż może mieć domyślne wartości. Wszystkie wartości w adnotacji są traktowane jako stałe. Dzieje się tak dlatego, że adnotacje są interpretowane przez kompilator przed wykonanie danego kodu.

Możesz stawiać ich dowolnie dużo, w zależności od potrzeb.

Własne adnotacje

Możesz też stworzyć własne adnotacje i wykorzystywać je w kodzie.

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonField {
    String value() default "empty";
}

Target i Retention

Powyższy kod przypomina zwykły interfejs z jedną sygnaturą. Na pierwszy rzut oka ma on kilka różnic. Po pierwsze przed słowem kluczowym interface postawiłem znak małpy. Jest to obowiązkowa konwencja, która czyni interfejs adnotacją. Wszelkie inne zmiany są już nieobligatoryjne. Drugą wyróżniającą się rzeczą są adnotacje Target i Retention. Pierwsza z nich określa do jakiego elementu kodu będzie można stosować adnotację (w moim przypadku jest to pole) oraz w jakim momencie przetwarzania kodu będzie ona użyta (runtime*). Ostatnią dodatkową kwestią są sygnatury, które będziesz wykorzystywać w trakcie użycia adnotacji. Tutaj wykorzystałem tylko jedną opcję z możliwością domyślnej wartości (default „empty”).

Teraz wykorzystam stworzoną adnotację w zwykłej klasie typu POJO.

public class InternalApi {
	@JsonField("ver")
	private String version;
	@JsonField("snap")
	private String snapshot;
	@JsonField("dependency")
	private String dependency;
	
	public InternalApi() {
	}
	
	public InternalApi(String version, String snapshot, String dependency) {
		this.version = version;
		this.snapshot = snapshot;
		this.dependency = dependency;
	}
	public String getVersion() {
		return version;
	}
	public void setVersion(String version) {
		this.version = version;
	}
	public String getSnapshot() {
		return snapshot;
	}
	public void setSnapshot(String snapshot) {
		this.snapshot = snapshot;
	}
	public String getDependency() {
		return dependency;
	}
	public void setDependency(String dependency) {
		this.dependency = dependency;
	}
	@Override
	public String toString() {
		return "InternalApi [version=" + version + ", snapshot=" + snapshot + ", dependency=" + dependency + "]";
	}
	
}

Adnotacja @JsonField mówi programiście: „zastosuj mnie na jakimś polu, a nadpiszę jego nazwę”. Jeśli nie wypiszesz nowej nazwy w ciele adnotacji, zostanie użyta nazwa domyślna zadeklarowana w jej implementacji. Mając na myśli 'nazwa’ nie myl jej z wartością zmiennej. Ta będzie ustalana w trakcie użycia klasy InternalApi.

Własne adnotacje w użyciu

Teraz, aby wykorzystać moją adnotację, muszę stworzyć odpowiedni kod, który korzystając z mechanizmu refleksji „wydobędzie” z wartości wpisanej do adnotacji nazwę pola i wpisze go do nowo powstałego tekstu typu JSON**. Metody serialize i getSerializedKey nie posiadają niczego nowego w stosunku do lekcji odnośnie „refleksji”. Jest to tworzona mapa, w której kluczem będzie nazwa wpisana w adnotacji (a nie nazwa pola) a jej wartością tekst wpisany w konstruktorze klasy InternalApi. Ciekawą metodą jest natomiast toJsonString, która to parsuje mapę do postaci typu JSON.

public class JsonSerializer {
	public static String serialize(Object object) throws IllegalAccessException {
		Class<?> objectClass = object.getClass();
		Map<String, String> jsonElements = new HashMap<>();
		for (Field field : objectClass.getDeclaredFields()) {
			field.setAccessible(true);
			if (field.isAnnotationPresent(JsonField.class)) {
				jsonElements.put(getSerializedKey(field), (String) field.get(object));
			}
		}
		return toJsonString(jsonElements);
	}
	private static String toJsonString(Map<String, String> jsonMap) { 
		StringBuilder elementsStringBuilder = new StringBuilder("");
		
		for (Map.Entry<String, String> element : jsonMap.entrySet()) {
			elementsStringBuilder.append("\"," + element.getKey() + "\":\"" + element.getValue());
		}
		String elementsString = elementsStringBuilder.toString().replaceFirst(",", "");
	
		return "{" + elementsString + "}";
	}
	private static String getSerializedKey(Field field) {
		String annotationValue = field.getAnnotation(JsonField.class).value();
		if (annotationValue.isEmpty()) {
			return field.getName();
		} else {
			return annotationValue;
		}
	}
}

Praktyczne użycie adnotacji:

public class ApiMain {
	public static void main(String[] args) throws IllegalAccessException {
		InternalApi api = new InternalApi("18.1.1", "RC", "Spring core");
		System.out.println(api);
		System.out.println(JsonSerializer.serialize(api));
	}
}

Na konsoli powinno się wyświetlić:

InternalApi [version=18.1.1, snapshot=RC, dependency=Spring core]
{"ver":"18.1.1","dependency":"Spring core","snap":"RC"}

Inne standardowe adnotacje używane w Javie

* Różnica pomiędzy run time a compile time na przykładzie języka C: https://www.javatpoint.com/compile-time-vs-runtime

** Czym jest JSON: https://www.w3schools.com/whatis/whatis_json.asp

Link do kodu: https://github.com/developeronthego/java-advanced/tree/master/src/main/java/advanced/lesson6

Powtórka odnośnie działania adnotacji override: Java #20: adnotacja override, przesłanianie metod

Exit mobile version