According to Wikipedia, an annotation is a form of syntactic metadata that can be added to Java source code. Classes, methods, variables, parameters and Java packages may be annotated.
I think following explanation more clear. An annotation is a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.
Annotations generally use in 3 specific situatios.
- Information for the compiler
- Compile-time and deployment-time processing
- Runtime processing
The format of an Annotation is look like following form :
@NotNull
“@” symbol indicates an annotation and NotNull is annotation name. Now we should look inside “@NotNull” structure
@Documented
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
public @interface NotNull {
}
Firstly If you create an annotation you gotta use to “@interface” structure. And a top level annotation might be public or package private access modifiers. If you define private or protected a top level annotation, you’re gonna take compile error.
Anyway let’s take a look the other structures in the “@NotNull” structure. “@Documented” — To ensure that our custom annotations are shown in the Java Doc documentation.
“@Retention” — Specifies how the marked annotation is stored. There are three option. these are :
- RetentionPolicy.SOURCE : The marked annotation is retained only in the source level and is ignored by the compiler.
- RetentionPolicy.CLASS : The marked annotation is retained by the compiler at compile time, but is ignored by the Java Virtual Machine (JVM).
- RetentionPolicy.RUNTIME : The marked annotation is retained by the JVM so it can be used by the runtime environment.
Lastly structure “@Target”, this annotation restrict what kind of Java elements the annotation can be applied to. There are eight options. these are :
ElementType.ANNOTATION_TYPE can be applied to an annotation type.
ElementType.CONSTRUCTOR can be applied to a constructor.
ElementType.FIELD can be applied to a field or property.
ElementType.LOCAL_VARIABLE can be applied to a local variable.
ElementType.METHOD can be applied to a method-level annotation.
ElementType.PACKAGE can be applied to a package declaration.
ElementType.PARAMETER can be applied to the parameters of a method.
ElementType.TYPE can be applied to any element of a class.
let’s we define own annotation and learn other fascinating things. Now I wanna define my own entity structure similar to the in JPA.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Entity {
String name();
}
Did you realise that String name(). String name() is a element. A Java annotation can have elements for which you can set values. An element is like an attribute or parameter. We can set default value to elements
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Entity {
String name() default "";
}
Now let’s check out what we did. We defined an annotation and this annotation uses only in class because we marked TYPE and defined RUNTIME because we wanna use in RUNTIME, and lastly we defined a element which has default value.
Let’s use we defined annotation.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Entity {
String name();
}
Secondly we define our Id and Column annotations.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Id {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
String value() default "";
}
Now let’s use them. I wanna create my entity in the following part.
@Entity(name = "role")
public class Role {
@Id
private Integer roleId;
@Column(value = "role_name")
private String roleName;
@Column(value = "role_priority")
private Integer rolePriority;
@Column(value = "max_number")
private Integer maxNumber; // from 0 to maxNumber
@Column(value = "created_time")
private ZonedDateTime createdTime;
@Column(value = "updated_time")
private ZonedDateTime updatedTime;
public Role() {
}
public Role(Integer roleId, String roleName, Integer rolePriority, Integer maxNumber, ZonedDateTime createdTime, ZonedDateTime updatedTime) {
this.roleId = roleId;
this.roleName = roleName;
this.rolePriority = rolePriority;
this.maxNumber = maxNumber;
this.createdTime = createdTime;
this.updatedTime = updatedTime;
}
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public Integer getRolePriority() {
return rolePriority;
}
public void setRolePriority(Integer rolePriority) {
this.rolePriority = rolePriority;
}
public Integer getMaxNumber() {
return maxNumber;
}
public void setMaxNumber(Integer maxNumber) {
this.maxNumber = maxNumber;
}
public ZonedDateTime getCreatedTime() {
return createdTime;
}
public void setCreatedTime(ZonedDateTime createdTime) {
this.createdTime = createdTime;
}
public ZonedDateTime getUpdatedTime() {
return updatedTime;
}
public void setUpdatedTime(ZonedDateTime updatedTime) {
this.updatedTime = updatedTime;
}
}
Let’s check out what we did. We created our annotations and entity. And we marked with “@Retention(RetentionPolicy.RUNTIME)” because we can use in RunTime. If we didn’t marked with RunTime, We can’t take them values. And we marked “@Target(ElementType.XYZ)” by our need. Now we should analyze the following code for understand new structures and check the structures described.
/**
* It's used for Role Entity's Database Operations
*
* @author Hikmet
* @since 17-04-2022+03:00.
*/
public class RoleRepository <T> {
// It's used for get properties of given class
Class<?> incomingClass;
/**
* It's used for create table by Role class
*
* @param t: the object is t that wanted save to the Database
*/
public void create(T t) {
incomingClass = t.getClass();
Entity entity = incomingClass.getAnnotation(Entity.class);
String tableName = entity.name();
System.out.println("Table Name=" + tableName);
System.out.println("\n");
Map<String, String> tableColumnsWithoutPrimaryKey = findTableColumnsWithoutPrimaryKey(t);
System.out.println(tableColumnsWithoutPrimaryKey);
System.out.println("\n");
String[] primaryKeyProperties = findPrimaryKeyProperties(t);
System.out.println("Primary Key=" + primaryKeyProperties[0]);
// TODO: Write SQL Operations
// TODO: FOR INSTANCE:
// String sql = "CREATE TABLE REGISTRATION " +
// "(id INTEGER not NULL, " +
// " first VARCHAR(255), " +
// " last VARCHAR(255), " +
// " age INTEGER, " +
// " PRIMARY KEY ( id ))";
}
/**
* It's used for find marked column annotation of given entity
*
* @param t: It's used for find marked Column annotation values of given class
* @return returns the values and types of fields marked with Column annotation
* Map's Key : Column Annotation's value; The Value that the Map Key points : Column Annotation's value is type
*/
public Map<String, String> findTableColumnsWithoutPrimaryKey(T t) {
Map<String, String> databaseTableValuesAndTypes = new HashMap<>();
Field[] declaredFields = incomingClass.getDeclaredFields();
Arrays.stream(declaredFields)
.filter(field -> field.isAnnotationPresent(Column.class))
.forEach(field -> {
Column column = field.getAnnotation(Column.class);
Optional<String> type = findType(field);
if(!type.isPresent()) {
throw new RuntimeException("Type Was Not Found");
}
databaseTableValuesAndTypes.put(column.value(), type.get());
});
return databaseTableValuesAndTypes;
}
/**
* It's used for find primary key properties of given class
*
* @param t: incomingClass whose primary key properties are desired
* @return return the primary key's properties (Name and Type respectively)
*/
public String[] findPrimaryKeyProperties(T t) {
incomingClass = t.getClass();
Field[] fields = incomingClass.getDeclaredFields();
Optional<Field> foundedIdField = Arrays.stream(fields)
.filter(field -> field.isAnnotationPresent(Id.class))
.findFirst();
if(!foundedIdField.isPresent()) {
throw new RuntimeException("Primary Key Value Not Found!");
}
Id idAnnotation = foundedIdField.get().getAnnotation(Id.class);
Optional<String> type = findType(foundedIdField.get());
if(!type.isPresent()) {
throw new RuntimeException("Primary Key Type Not Found!");
}
return new String[] {idAnnotation.value(), type.get()};
}
/**
*
* This method should be used for find given field's type.
* This is supported only 3 type for basically
*
* @param field
* @return Found Approriate Type
*/
private Optional<String> findType(Field field) {
if(field.getType().isAssignableFrom(Integer.class)) {
return Optional.of("INTEGER");
}
if(field.getType().isAssignableFrom(String.class)) {
return Optional.of("VARCHAR");
}
if(field.getType().isAssignableFrom(ZonedDateTime.class)) {
return Optional.of("TIMESTAMP");
}
return Optional.empty();
}
}
RoleRepository contains four methods. These are :
create(T):void
findTableColumnsWithoutPrimaryKey(T):Map<String, String>
findPrimaryKeyProperties(T):String[]
findType(Field):Optional<String>
- create(T):void — this method is used for create table in database. I didn’t add db create operation codes because this db operation’s part unrelated our topic but you can add easily (for instance you can use JDBC API which can do easily db operations)
- findTableColumnsWithoutPrimaryKey(T):Map<String, String> — this method is used find to values and them types marked with “@Column” annotation
- findPrimaryKeyProperties(T):String[] — this method is used to find primary key and it’s type
- findType(Field):Optional<String> — this method is used for find given field’s type. It was supported three type by this method (INTEGER, VARCHAR, TIMESTAMP)
I hope everything is clear for you. If you don’t understand any part, Please feel free, ask me.
I used various resource for prepare this essay. I indicated in following. You can check out.
— https://docs.oracle.com/javase/tutorial/java/annotations/