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.