annotation

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 to RetentionPolicy.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";
//	}

}
  1. If we keep the method commented, then the Object.toString() method is executed, and the result will be something like jcg.zheng.demo.builtinannotation.OverrideExp@48cf768c
  2. If we uncomment the method, then the OverrideExp.toString() method is executed, and the resutls will be “Annotations Example“.
  3. 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.
Java Annotations - Error when @Override on a non-exist method
Figure 1 Eclipse IDE shows an Error when @Override on a non-exist method

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.

Java Annotations - warning marks
Figure 2 Eclipse IDE shows warning marks

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());
	}
}
Java Annotations - Eclipse shows a warning mark
Figure 3 Eclipse shows a warning mark

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);

}
Java Annotations - error due to @FunctionalInterface
Figure 4 Eclipse IDE shows an error due to @FunctionalInterface

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. The name 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 or MessageBodyReader 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 a Service 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 the main 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.

Java Annotations - Get Users
Figure 5 Get Users

Demo the POST createUser method with the following screenshot.

The following screenshot shows the validation annotation works in the run-time.

Java Annotations - Create with Validation
Figure 6 Create with Validation

The following screenshot shows a new user is created if the data is valid.

Figure 7 Create a new User

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 RequiresPermissionFilterwhich 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.

Figure 8 401 Unauthorized Error

Alter the postman to enter the valid header – requestUserId, then the service returns a 200 response as the following images shown.

Java Annotations - Secured Service
Figure 9 Secured Service

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.

Download
You can download the full source code of this example here: Java Annotations Example

Last updated on Sept. 20, 2019

Mary Zheng

Mary has graduated from Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She works as a senior Software Engineer in the telecommunications sector where she acts as a leader and works with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button