Java Annotations Example
1. Introduction
Java Annotations are special metadata instructions included in the source code of a program which starts with the @
symbol and can be applied to a class, method, field, parameter, variable, constructor, and package. They were introduced in Java 5 under JSR-175 to reduce the XML usage for metadata, which tends to become complicated and cumbersome. It was enhanced in Java 6 under JSR-269 to formalize and integrate with the Javac compiler. So in this article, we talk about Java Annotations and we create some Java annotations examples.
1.1 Annotation Categories
There are three categories of annotations in Java:
- Marker – It is a declaration to check if it is available or missing. e.g.
@Override
and@Deprecated
. - Single Value – It is an annotation which has only one member and specifies the member name as the value. e.g.
@SuppressWarnings
. - Full – It is an annotation which has multiple data members as name-value pair.
Note: annotation member can only be primitive type, Enum
, or String
.
1.2 Annotations to Define an Annotation
Java provides four built-in annotations to define an annotation:
@Retention
– defines where the annotation is retained, defaults toRetentionPolicy.CLASS
@Documented
– documents the annotation in Javadoc@Target
– defines the ElementType that the annotation is applied to. If defines as@Target(ElementType.ANNOTATION_TYPE)
, then it declared type is a meta-annotation type.@Inherited
– indicates an annotation type is automatically inherited by sub-classes
1.3 Annotation Steps
There are three steps required for a Java annotation to work:
- Define – Creates an annotation type.
- Apply – Applies annotation on the elements.
- Consume – Processes the annotation. It can be done by an IDE, Java compiler annotation processor, or frameworks.
In this example I will demonstrate:
- How to use built-in annotations and demonstrate the difference during development cycle within Eclipse IDE
- How to use annotations from JPA, Spring frameworks and validate the results during the compile-time and runtime
- How to create a custom annotation –
@Employee
and its consumer - How to create a custom annotation –
@RequiresPermission
and integrate it in an existing web application.
2. Technologies Used
The example code in this article was built and run using:
- Java 1.11
- Maven 3.3.9
- Spring boot 2.1.8-RELEASE
- Eclipse Oxygen
- H2 Database
3. Basic Java Annotations
Java has several built-in annotations that are used as compiler instructions. When using an IDE these annotations are extremely useful as they provide essential information, or allow you to examine your code more closely.
3.1 @Override
@Override
: This annotation indicates that a method declaration is intended to override a method declaration in a super-class (sometimes the Object
class, or another). The main point of this annotation is to show explicitly that this method is to be overridden, so as to avoid making a mistake in the method implementation and get weird results. Here is the definition:
@Target(METHOD) @Retention(SOURCE) public @interface Override
This is used heavily in inheritance and in the very essential toString()
method. In this step, I will create OverrideExp
to show what happens in three different cases.
OverrideExp.java
package jcg.zheng.demo.builtinannotation; public class OverrideExp { public static void main(String[] args) { OverrideExp ex = new OverrideExp(); System.out.println(ex.toString()); } // @Override // public String toString() { // return "Annotations Example"; // } }
- If we keep the method commented, then the
Object.toString()
method is executed, and the result will be something likejcg.zheng.demo.builtinannotation.OverrideExp@48cf768c
- If we uncomment the method, then the
OverrideExp.toString()
method is executed, and the resutls will be “Annotations Example“. - In case we refactor the method name to something like
tostr()
by accident, then the@Override
annotation tries to find the method to override, which does not exist, Eclipse IDE will show as an error as the following image.
3.2 @Deprecated
@Deprecated
: It marks a method so user should avoid to use. It appears as a compiler warning when you try to use a deprecated method that still exists in the code. Here is the definition:
@Documented @Retention(RUNTIME) @Target({CONSTRUCTOR,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,MODULE,PARAMETER,TYPE}) public @interface Deprecated
In this step, I will create a DeprecatedExp
which marks foo
as a deprecated method.
DeprecatedExp.java
package jcg.zheng.demo.builtinannotation; public class DeprecatedExp { @Deprecated public void foo() { System.out.println("foo! stop using it"); } }
3.3 @SuppressWarnings
@SuppressWarnings
: This annotation makes the compiler to stop showing any warnings for a given method or class. Here is the definition:
@Target({TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,MODULE}) @Retention(SOURCE) public @interface SuppressWarnings
In this step, I will create a SuppressWarningsExp
class to show the difference in Eclipse IDE when using it.
SuppressWarningsExp.java
package jcg.zheng.demo.builtinannotation; import java.util.Arrays; import java.util.List; public class SuppressWarningsExp { public static void main(String[] args) { SuppressWarningsExp notesStr = new SuppressWarningsExp(); System.out.println(notesStr.convert()); } @SuppressWarnings("unchecked") public List<String> convert() { return (List<String>) createDummyList(); } @SuppressWarnings("rawtypes") private List createDummyList() { return Arrays.asList("Test"); } @SuppressWarnings("deprecation") public void callingDeprecated() { DeprecatedExp notesStr = new DeprecatedExp(); notesStr.foo(); } }
Here is the screenshot of Eclipse IDE shows the warnings.
3.4 @SafeVarargs
@SafeVarargs
– it asserts that program does not perform potentially unsafe operations on its varargs
parameter. Here is the definition:
@Documented @Retention(RUNTIME) @Target({CONSTRUCTOR,METHOD}) public @interface SafeVarargs
In this step, I will create a SafeVarargsExp
class to show the difference in Eclipse IDE.
SafeVarargsExp.java
package jcg.zheng.demo.builtinannotation; import java.util.ArrayList; import java.util.List; public class SafeVarargsExp<T> { private List<T> notes = new ArrayList<>(); public List<T> getNotes() { return notes; } @SafeVarargs public final void safe(T... toAdd) { for (T version : toAdd) { notes.add(version); } } public static void main(String[] args) { SafeVarargsExp<String> notesStr = new SafeVarargsExp<>(); notesStr.safe("Hello", "World!"); System.out.println(notesStr.getNotes()); } }
3.5 @FunctionalInterface
@FunctionalInterface
defines a functional interface which has exactly one abstract method. Here is the definition:
@Documented @Retention(RUNTIME) @Target(TYPE) public @interface FunctionalInterface
In this step, I will create a FunctionalInterfaceExp
and shows the Eclipse IDE error if it has more than one abstract method.
FunctionalInterfaceExp.java
package jcg.zheng.demo.builtinannotation; @FunctionalInterface public interface FunctionalInterfaceExp { String foo(String msg); //String foo2(String msg); }
4. Spring Boot Web Application
Java Annotations are widely adapted since it’s released. In this step, I will demonstrate several annotations from Java validation API, Java Persistence API, Web Service, and Spring framework.
I will create a Spring boot web application. You can click my other article for step-by-step details. I will explain the annotations used for validating, configuring, and data mapping.
4.1 Dependency
Maven pom.xml
manages the project libraries.
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.8.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>jcg.zheng.demo</groupId> <artifactId>java-annotations-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>java-annotations-demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>11</java.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jersey</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
4.2 Validation Annotations
Java validation library provides validation annotations. Spring boot includes the hibernate-validator. The validation annotations will be validated during the runtime.
In this step, I will create a User
which has the @NotNull
, @Size
, @Min
, and @Max
annotations. Here are the annotation definitions:
@Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER}) @Retention(value=RUNTIME) @Documented @Constraint(validatedBy={}) public @interface NotNull @Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER}) @Retention(value=RUNTIME) @Documented @Constraint(validatedBy={}) public @interface Min @Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER}) @Retention(value=RUNTIME) @Documented @Constraint(validatedBy={}) public @interface Max @Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER}) @Retention(value=RUNTIME) @Documented @Constraint(validatedBy={}) public @interface Size
User.java
package jcg.zheng.demo.web.data; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.springframework.beans.BeanUtils; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_EMPTY) public class User { @NotNull @Size(max = 20, min = 3) private String firstName; private int id; @NotNull @Size(max = 50, min = 3) private String lastName; @Min(value = 0) @Max(value = 10) private int role; public User() { super(); } public User(Person person) { BeanUtils.copyProperties(person, this); } public String getFirstName() { return firstName; } public Integer getId() { return id; } public String getLastName() { return lastName; } public int getRole() { return role; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setId(Integer id) { this.id = id; } public void setLastName(String lastName) { this.lastName = lastName; } public void setRole(int role) { this.role = role; } }
4.3 JPA Annotations
The Java Persistence API (JPA) provides a list of annotations to map Java objects to relational database tables.
In this step, I will demonstrate the most commonly used annotations:
@Entity
– designates a plain old Java object class as an entity and make it eligible for JPA services@Id
– unique key for the@Entity
@GeneratedValue
– JPA generates entity identifiers@Column
– JPA assumes that each of an entity’s persistent attributes is stored in a database table column whose name matches that of the persistent field or property. This is a full annotation with several name-value pair. e.g. Thename
member overrides the table column name.
Person.java
package jcg.zheng.demo.web.data; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import org.springframework.beans.BeanUtils; @Entity public class Person { private String firstName; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column(name = "LAST_NAME") private String lastName; private int role; public Person() { super(); } public Person(User user) { BeanUtils.copyProperties(user, this); } public String getFirstName() { return firstName; } public Integer getId() { return id; } public String getLastName() { return lastName; } public int getRole() { return role; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setId(Integer id) { this.id = id; } public void setLastName(String lastName) { this.lastName = lastName; } public void setRole(int role) { this.role = role; } }
4.4 Web Service Annotations
Java web Service provides a list of annotations to ease the web application development.
In this step, I will create a UserResource
which utilizes the following annotations:
@Path
– identifies the URI path template to which the resource responds.@GET
– specifies method responds to GET request.@POST
– specifies method responds to POST request.@Produces
– defines media type for the response such as XML, PLAIN, JSON etc.@Consumes
– defines the media type that the methods of a resource class orMessageBodyReader
can consume.
UserResource.java
package jcg.zheng.demo.web.api; import java.util.List; import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import jcg.zheng.demo.web.data.User; @Path("/v1/SecuredService") @Produces({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON }) public interface UserResource { @POST User createUser(@Valid User user); @GET @Path(value = "/users") List getUsers();// http://localhost:8080/v1/SecuredService/users }
4.5 Spring Annotations
Spring framework provides a list of annotations to simplify software development. In this step, I will create a Jersey RESTful resource which creates a User
and lists all the users.
4.5.1 @Repository
Here is the definition of @Repository
annotation, The class with it will be auto-detected through classpath scanning.
@Target(value=TYPE) @Retention(value=RUNTIME) @Documented @Component public @interface Repository
In this step, I will create a PersonRepostitory
which annotates with @Repository
. The @EnableJpaRepositories
annotation enables it.
PersonRepository.java
package jcg.zheng.demo.web.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import jcg.zheng.demo.web.data.Person; @Repository public interface PersonRepository extends JpaRepository<Person, Integer> { }
4.5.2 @Component
Here is the @Component
definition, It marks the class for auto-detection when using annotation-based configuration and classpath scanning. The @EnableAutoConfiguration
annotation enables the auto-scanning.
@Target(value=TYPE) @Retention(value=RUNTIME) @Documented @Indexed public @interface Component
In this step, I will create a UserResourceImpl
which annotates with @Component
. It uses @Autowired
to inject a UserService
. The @RequiresPermission
annotation is commented and will be uncomment at step 6.3
@Autowired
– Spring will manage a bean on the setter method, constructor, a property or methods with arbitrary names and/or multiple arguments.
UserResourceImpl.java
package jcg.zheng.demo.web.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import jcg.zheng.demo.web.api.UserResource; import jcg.zheng.demo.web.data.User; @Component public class UserResourceImpl implements UserResource { @Autowired private UserService userSrv; @Override // @RequiresPermissions(type = "role", value = "10") public User createUser(User user) { return userSrv.saveUser(user); } @Override // @RequiresPermissions(type = "role", value = "1") public List<User> getUsers() { return userSrv.getUsers(); } }
4.5.3 @Transactional
The @Transctional
annotation can apply to both class and method. When applies to the class level, then all methods will have it as the default.
@Target(value={TYPE,METHOD}) @Retention(value=RUNTIME) @Inherited @Documented public @interface Transactional
In this step, I will create a UserService
which annotates with @Transactional
and @Service
. It also uses @Autowired
to inject a PersonRepository
. Please note that saveUser
method annotates with @Transactional
which overrides the class level @Transactional
.
@Service
– Indicates annotated class is aService
component in the business layer.
UserService.java
package jcg.zheng.demo.web.service; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import jcg.zheng.demo.web.data.Person; import jcg.zheng.demo.web.data.User; import jcg.zheng.demo.web.repository.PersonRepository; @Service @Transactional(isolation = Isolation.READ_UNCOMMITTED) public class UserService { @Autowired private PersonRepository personRepo; public User getUser(Integer userId) { Optional<Person> p = personRepo.findById(userId); if (p.isEmpty()) { return null; } return new User(p.get()); } public List<User> getUsers() { List<Person> persons = personRepo.findAll(); if (CollectionUtils.isEmpty(persons)) { return Collections.emptyList(); } return persons.stream().map(p -> new User(p)).collect(Collectors.toList()); } @Transactional(isolation = Isolation.READ_COMMITTED) public User saveUser(User user) { Person p = new Person(user); Person saved = personRepo.save(p); return new User(saved); } }
4.5.4 @SpringBootApplication
Spring boot provides a convenient way to configure an application. @SpringBootApplication
is a combination of three Spring annotations: @SpringBootConfiguration
, @EnableAutoConfiguration
, and @ComponentScan
.
@Target(value=TYPE) @Retention(value=RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters={@ComponentScan.Filter(type=CUSTOM,classes=TypeExcludeFilter.class),}) public @interface SpringBootApplication
In this step, I will create an ApplicationConfig
class which extends from SpringBootServletInitializer
and annotates with @SpringBootApplication
, @Import
, and @EnableJpaRepositories
.
@SpringBootApplication
– marks themain
class of a Spring Boot application.@EnableAutoConfiguration
– enables auto-configuration. It means that Spring Boot looks for auto-configuration beans on its classpath and automatically applies them.@Configuration
– marks class containing bean definitions.@Import
– imports@Configuration
classes.@EnableJpaRepositories
– enable JPA repositories. It will scan the package of the annotated configuration class for Spring Data repositories by default.
ApplicationConfig.java
package jcg.zheng.demo.web; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.annotation.Import; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootApplication @Import({ JerseyConfiguration.class }) @EnableJpaRepositories(basePackages = "jcg.zheng.demo.web") public class ApplicationConfig extends SpringBootServletInitializer { public static void main(String[] args) { new ApplicationConfig().configure(new SpringApplicationBuilder(ApplicationConfig.class)).run(args); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(ApplicationConfig.class); } }
I will create a JerseryConfiguration
which extends from org.glassfish.jersey.server.ResourceConfig and registers a UserResourceImpl
.
JerseryConfiguration.java
package jcg.zheng.demo.web; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.ServerProperties; import jcg.zheng.demo.web.security.RequiresPermissionsFilter; import jcg.zheng.demo.web.service.UserResourceImpl; public class JerseyConfiguration extends ResourceConfig { public JerseyConfiguration() { property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true); property(ServerProperties.BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK, true); register(UserResourceImpl.class); register(RequiresPermissionsFilter.class); } }
4.6 Demo
We will demonstrate via Postman after starting the spring boot application. Here is the server log output:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.1.8.RELEASE) 2019-09-13 21:50:05.573 INFO 42808 --- [ main] jcg.zheng.demo.web.ApplicationConfig : Starting ApplicationConfig on S443831 with PID 42808 (C:\MaryZheng\Workspaces\jdk12\string-annotations-demo\target\classes started by aa00765 in C:\MaryZheng\Workspaces\jdk12\string-annotations-demo) 2019-09-13 21:50:05.594 INFO 42808 --- [ main] jcg.zheng.demo.web.ApplicationConfig : No active profile set, falling back to default profiles: default 2019-09-13 21:50:07.989 INFO 42808 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode. 2019-09-13 21:50:08.327 INFO 42808 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 296ms. Found 1 repository interfaces. 2019-09-13 21:50:10.582 INFO 42808 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2019-09-13 21:50:10.741 INFO 42808 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2019-09-13 21:50:10.744 INFO 42808 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.24] 2019-09-13 21:50:11.417 INFO 42808 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2019-09-13 21:50:11.418 INFO 42808 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 5532 ms 2019-09-13 21:50:12.855 INFO 42808 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2019-09-13 21:50:13.647 INFO 42808 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2019-09-13 21:50:13.908 INFO 42808 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [ name: default ...] 2019-09-13 21:50:14.212 INFO 42808 --- [ main] org.hibernate.Version : HHH000412: Hibernate Core {5.3.11.Final} 2019-09-13 21:50:14.217 INFO 42808 --- [ main] org.hibernate.cfg.Environment : HHH000206: hibernate.properties not found 2019-09-13 21:50:14.820 INFO 42808 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.0.4.Final} 2019-09-13 21:50:15.404 INFO 42808 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect 2019-09-13 21:50:17.471 INFO 42808 --- [ main] o.h.t.schema.internal.SchemaCreatorImpl : HHH000476: Executing import script 'org.hibernate.tool.schema.internal.exec.ScriptSourceInputNonExistentImpl@24018c8b' 2019-09-13 21:50:17.479 INFO 42808 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2019-09-13 21:50:18.754 INFO 42808 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2019-09-13 21:50:18.761 INFO 42808 --- [ main] jcg.zheng.demo.web.ApplicationConfig : Started ApplicationConfig in 14.676 seconds (JVM running for 16.105)
I will use PostMan tool to show both getUsers
and createUser
methods.
Demo the GET users method return ok status with the following screenshot.
Demo the POST createUser
method with the following screenshot.
The following screenshot shows the validation annotation works in the run-time.
The following screenshot shows a new user is created if the data is valid.
5. Custom Annotation Example
5.1 Employee Annotation
In this step, I will create an EmployeeAnnotation
type which has three members. This annotation is retained at run-time and applies to a method only.
EmployeeAnnotation.java
package jcg.zheng.demo.customannotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // Retained at runtime (so we can use them with Reflection). // Applied to a method. @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface EmployeeAnnotation { int age(); String job(); String name(); }
5.2 Mark Employee Annotations
In this step, I will create a MarkEmployyAnnotation
which marks two methods with @EmployeeAnnotation
.
MarkEmployeeAnnotation.java
package jcg.zheng.demo.customannotation; public class MarkEmployeeAnnotation { @EmployeeAnnotation(age = 23, job = "Developer", name = "John") public void printEmployeeInfo1() { System.out.println("printEmployeeInfo1"); } @EmployeeAnnotation(age = 30, job = "Writer", name = "Luke") public void printEmployeeInfo2() { System.out.println("printEmployeeInfo2"); } public void printEmployeeInfo3() { System.out.println("printEmployeeInfo3"); } public static void main(String[] args) { MarkEmployeeAnnotation ex = new MarkEmployeeAnnotation(); ex.printEmployeeInfo1(); ex.printEmployeeInfo2(); ex.printEmployeeInfo3(); } }
Annotations does not change the method behavior. We can demonstrate this by executing it as a Java application. You will get the following output:
printEmployeeInfo1 printEmployeeInfo2 printEmployeeInfo3
5.3 Consume Employee Annotation
In this step, I will create a ConsumeEmployeeAnnotation
which use the Reflection
to extract information through the annotations of each method. As you can see by using Reflection
, we don’t get only the methods we created, but the methods inherited from the Object
class as well.
ConsumeEmployeeAnnotation.java
package jcg.zheng.demo.customannotation; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ConsumeEmployeeAnnotation { public static void main(String[] args) { MarkEmployeeAnnotation ex = new MarkEmployeeAnnotation(); System.out.println("Checking class methods for annotation...\n"); // get all the methods of this class for (Method method : ex.getClass().getMethods()) { // Check if a method has the @Employee annotation if (method.isAnnotationPresent(EmployeeAnnotation.class)) { if (method.getAnnotation(EmployeeAnnotation.class) != null) { System.out.println(method.getName() + " has the @Employee annotation."); for (Annotation an : method.getAnnotations()) { System.out.println("\t" + an.toString()); processAnnotation((EmployeeAnnotation) an); } try { method.invoke(ex); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); } } } else { System.out.println(method.getName() + " does not have specific annotation."); } } } private static void processAnnotation(EmployeeAnnotation emp) { System.out.println("Can do cross-cutting for all the @Employee here"); System.out.println(emp.job()); System.out.println(emp.age()); System.out.println(emp.name()); } }
As you seen the annotation is processed via Java reflection. We will demonstrate it by executing it as a Java application. You will get the following output:
Checking class methods for annotation... main does not have specific annotation. printEmployeeInfo3 does not have specific annotation. printEmployeeInfo1 has the @Employee annotation. @jcg.zheng.demo.customannotation.EmployeeAnnotation(age=23, job="Developer", name="John") Can do cross-cutting for all the @Employee here Developer 23 John printEmployeeInfo1 printEmployeeInfo2 has the @Employee annotation. @jcg.zheng.demo.customannotation.EmployeeAnnotation(age=30, job="Writer", name="Luke") Can do cross-cutting for all the @Employee here Writer 30 Luke printEmployeeInfo2 wait does not have specific annotation. wait does not have specific annotation. wait does not have specific annotation. equals does not have specific annotation. toString does not have specific annotation. hashCode does not have specific annotation. getClass does not have specific annotation. notify does not have specific annotation. notifyAll does not have specific annotation.
6. Add Security to Web Application
As we seen earlier, the web application is working without any security check. Now we can enable permission check with a customized @RequiresPermission
annotation.
6.1 RequiresPermission
In this step, I will create a customized annotation type – @RequiresPermission
.
RequiresPermission.java
package jcg.zheng.demo.customannotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //Retained at runtime (so we can use them with Reflection). //Applied to a method @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD ) public @interface RequiresPermissions { String type() default ""; String value() default ""; }
6.2 RequiresPermissionFilter
In this step, I will create a RequiresPermissionFilter
which implements ContainerRequestFilter
. I will add a logic in the override method filter()
to handle the security check based on the header information from the client requests.
RequiresPermissionFilter.java
package jcg.zheng.demo.web.security; import java.io.IOException; import java.util.List; import javax.ws.rs.NotAuthorizedException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ResourceInfo; import javax.ws.rs.core.Context; import javax.ws.rs.core.MultivaluedMap; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.CollectionUtils; import jcg.zheng.demo.customannotation.RequiresPermissions; import jcg.zheng.demo.web.data.User; import jcg.zheng.demo.web.service.UserService; public class RequiresPermissionsFilter implements ContainerRequestFilter { private static final String SUPER_USER = "MZheng"; @Context private ApplicationContext applicationContext; @Context private ResourceInfo resourceInfo; @Autowired private UserService userSrv; @Override public void filter(ContainerRequestContext requestContext) throws IOException { RequiresPermissions annotation = AnnotationUtils.findAnnotation(resourceInfo.getResourceMethod(), RequiresPermissions.class); if (annotation != null) { MultivaluedMap<String, String> headers = requestContext.getHeaders(); processPermission(headers, annotation); } } private void processPermission(MultivaluedMap<String, String> headers, RequiresPermissions permission) { String permissionValue = permission.value(); String permissionType = permission.type(); if ("role".equalsIgnoreCase(permissionType)) { // need to check the header user id's role match to the permission role List<String> requestUserId = headers.get("requestUserId"); if (CollectionUtils.isEmpty(requestUserId)) { throw new NotAuthorizedException("Missing security header"); } if (!requestUserId.get(0).equalsIgnoreCase(SUPER_USER)) { Integer requestUserNum = Integer.valueOf(requestUserId.get(0)); User requestUser = userSrv.getUser(requestUserNum); if (requestUser == null) { throw new NotAuthorizedException("Invalid requestUserId"); } Integer userRoleInt = Integer.valueOf(requestUser.getRole()); Integer permissionRoleInt = Integer.valueOf(permissionValue); if (userRoleInt < permissionRoleInt) { throw new NotAuthorizedException( "Not Authorized for the method, request user must have a role=" + permissionValue); } } } } }
6.3 Update UserResource with @RequiresPermission
In this step, I will annotate these methods requires permission check with @RequiresPermission
. No any other changes to the web application, but now the application is secured with the permission check.
package jcg.zheng.demo.web.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import jcg.zheng.demo.customannotation.RequiresPermissions; import jcg.zheng.demo.web.api.UserResource; import jcg.zheng.demo.web.data.User; @Component public class UserResourceImpl implements UserResource { @Autowired private UserService userSrv; @Override @RequiresPermissions(type = "role", value = "10") public User createUser(User user) { return userSrv.saveUser(user); } @Override @RequiresPermissions(type = "role", value = "1") public List<User> getUsers() { return userSrv.getUsers(); } }
6.4 Demo
We will do the same steps as we did at step 4.6. We should get a 401 error as the service requires a valid header now.
Alter the postman to enter the valid header – requestUserId
, then the service returns a 200 response as the following images shown.
7. Summary
Java Annotation providea a standard way of defining metadata about a program. It gains acceptance since it’s released. In this example, I demonstrated how to use the built-in annotations from Java, as well as the annotations from JPA, Validation, Web Service, and Spring framework. I also demonstrated how to create our own annotations and integrate with existing web application with minimum changes.
8. Download the Source Code
This was an example about built-in and custom Java annotations.
You can download the full source code of this example here: Java Annotations Example
Last updated on Sept. 20, 2019