Create custom annotation with Spring and Aspectj
In this blog we will create the custom annotation using spring and aspectj lib. So let’s assume that we have a rest API and we want to validate the request object for post requests.
Let’s create the 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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.springboot.annotation</groupId> <artifactId>example</artifactId> <version>1.0-SNAPSHOT</version> <!-- Spring Boot dependency management--> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>1.4.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- AspectJ --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <showDeprecation>true</showDeprecation> <showWarnings>true</showWarnings> <fork>true</fork> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project> [addToAppearHere]
So let’s create following model classes (AbstractRequestModel.java, ResultModel.java, CreateUserRequestModel.java)
package com.spring.annotation.example;
import java.util.List;
public abstract class AbstractRequestModel {
/**
* All Request models should override this abstract method
* @return the List of errors
*/
public abstract List validateFields();
}
package com.spring.annotation.example; import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.List; public class CreateUserRequestModel extends AbstractRequestModel { @JsonProperty("first_name") private String firstName; @JsonProperty("last_name") private String lastName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } @Override public List<String> validateFields() { final List<String> errors = new ArrayList<>(); if (StringUtils.isEmpty(firstName)) { errors.add("First name can't be empty"); } if (StringUtils.isEmpty(lastName)) { errors.add("Last name can't be empty"); } return errors; } } [addToAppearHere]
package com.spring.annotation.example; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; import java.util.List; public class ResultModel<T> { @JsonProperty private T result; @JsonProperty(value = "errors", required = false) private List<String> errors; public ResultModel(final T result) { this(); this.result = result; } public ResultModel(final List<String> errors) { this.errors = errors; } public ResultModel() { this.errors = new ArrayList<>(); } public List<String> getErrors() { return errors; } public void setErrors(List<String> errors) { this.errors = errors; } public T getResult() { return result; } public void setResult(T result) { this.result = result; } } [addToAppearHere]
After this we need to create the ValidateRequest.java annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
public @interface ValidateRequest {
}
We need to create the ValidateRequestAspect.java handler for this annotation
package com.spring.annotation.example; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; @Aspect @Component public class ValidateRequestAspect { @Around("@annotation(com.spring.annotation.example.ValidateRequest)") public Object validateActionRequest(final ProceedingJoinPoint joinPoint) throws Throwable { Object joinPointResult; try { /*Check if method has argument with type AbstractRequestModel*/ final AbstractRequestModel facadeActionModel = getActionRequest(joinPoint.getArgs()); if (facadeActionModel != null) { /*Validate request model*/ final List<String> errors = validateRequiredFields(facadeActionModel); if (errors.size() != 0) { return new ResultModel<>(errors); } } joinPointResult = joinPoint.proceed(); } catch (final Exception ex) { System.out.println(ex); throw ex; } return joinPointResult; } /** * @param args the args * @return the AbstractRequestModel */ private static AbstractRequestModel getActionRequest(final Object[] args) { for (final Object arg : args) { if (arg instanceof AbstractRequestModel) { return (AbstractRequestModel) arg; } } return null; } /** * @param requestModel the request model * @return the list of error messages */ private List<String> validateRequiredFields(final AbstractRequestModel requestModel) { final List<String> errors = new ArrayList<>(); errors.addAll(requestModel.validateFields()); return errors; } } [addToAppearHere]
And the last think we need to do create UserController.java, UserService.java and the Application.java to start the spring boot application.
package com.spring.annotation.example; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @Autowired private UserService userService; @RequestMapping(value = "/", method = RequestMethod.POST) public ResponseEntity<ResultModel<String>> create(@RequestBody final CreateUserRequestModel createUserRequestModel) { ResultModel<String> resultModel = userService.create(createUserRequestModel); return ResponseEntity.ok(resultModel); } }
package com.spring.annotation.example; import org.springframework.stereotype.Service; @Service public class UserService { @ValidateRequest public ResultModel<String> create(final CreateUserRequestModel userRequestModel) { //Returning result model object return new ResultModel<>(userRequestModel.getFirstName() + ", " + userRequestModel.getLastName()); } }
package com.spring.annotation.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @ComponentScan({"com.spring.annotation.example"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
So let’s run the Application.java, by default it will use 8080 port. Now we just need to do a following post request:
curl -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{
"first_name": "test1",
"last_name": "test2"
}' "http://localhost:8080/"
The result will be following:
{ "result": "test1, test2", "errors": [] }
Let’s try following post request (with empty firstName and lastName)
curl -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{
"first_name": "",
"last_name": ""
}' "http://localhost:8080/"
And the response will be:
{
"result": null,
"errors": [
"First name can't be empty",
"Last name can't be empty"
]
}
You can download the source codes from the github.