org.springframework.boot spring-boot-starter-aop org.springframework spring-aspects
Next, we'll create a new package called aspect and a new class called ControllerAspect. The ControllerAspect will be our class where we can put our global try/catch block where will we log out the input and output parameters of the method being called and also print any exception messages that might occur. I'll just be using standard output for now and leave setting up log4j for a future post or leave it for you to setup.
package com.herzog.boot.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Configurable; /** * @author dherzog * * Controller Aspect for all public controller methods. */ @Aspect @Configurable public class ControllerAspect { /** * Proxy method to log before and after service method calls. * * @param joinPoint ProceedingJoinPoint * @return object * @throws Throwable throw exception */ @Around("execution(public * com.herzog.boot.controller.*ControllerImpl.*(..))") public final Object logAround(final ProceedingJoinPoint joinPoint) throws Throwable { Object request = null; Object response = null; try { Object[] params = joinPoint.getArgs(); request = params[0]; response = joinPoint.proceed(params); System.out.println("\n\n\t\t" + "The class " + joinPoint.getSignature().getDeclaringTypeName() + " with the method " + joinPoint.getSignature().getName() + "()" + " \n\t\t\t begins with " + params[0] + " \n\t\t\t ends with " + response + "\n"); } catch (Exception e) { Object[] params = joinPoint.getArgs(); System.out.println("\n\n\t\t" + "Class " + joinPoint.getSignature().getDeclaringTypeName() + " with the method " + joinPoint.getSignature().getName() + "()" + " \n\t\t\t begins with " + params[0] + " \n\t\t\t and failed with the exception " + e + "\n"); throw e; } return response; } }
In this class we defined the AspectJ @Around annotation. The code here will proxied around all the RESTful services. The code before the joinPoint.proceed will be invoked before the service and the code after joinPoint.proceed will be invoked after the service. The execution in the the @Around annotation proxies the code around all public methods that accept all parameters and return anything for classes that have the naming pattern of *ControllerImpl in the package com.herzog.boot.controller. You'll want to make sure that all your request and response objects have toString's defined so that useful information may be logged about your services. For example, the toString for AlbumListRequest looks like the following:
@Override public String toString() { return String.format("AlbumListRequest [artist=%s]", artist); }
Now we will add some server side validation to our services. We'll use the java validation framework. In the AlbumListRequest class we will add two annotations to the getArtist method. First, we will add the @NotNull annotation to make sure that the artist value in the request object is not null. Next we will add the @Size annotation to make sure the artist value passed has a size of 1 to 100 characters. If the validation fails, Spring will through a MethodArgumentNotValidException exception.
@NotNull @Size(min = 1, max = 100) public String getArtist() { return artist; }For Spring to perform the validation, you'll need to add the @Valid annotation to the request of the controller method. So in AlbumControllerImpl, we will add @Valid to the beginning of the request parameter in the list method.
public AlbumListResponse list(@Valid @RequestBody final AlbumListRequest request) throws Exception {
Lastly, we are going to create a new class called ErrorHandlerAspect in the aspect package. This class will take advantage of the @ControllerAdvice feature that was introduced around Spring 3.2.5. You can setup different exception handlers for different types of exceptions. This gives you the flexibility to return different HTTP responses based on the different types of errors that might be thrown. As I mentioned before, Spring will throw a MethodArgumentNotValidException exception when server side validation fails. Using the @ConrollerAdvice, you can catch that exception, return a specific type of error message (in this case, a simple class called ErrorResponse) and set the HTTP status to 400 for a bad request. In other cases, you can create a catch all method that returns an error 500 for unexpected errors. In my ControllerAspect, I always re-throw the errors I have caught so that I can send them through the @ControllerAdvice mechanism.
package com.herzog.boot.aspect; import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import com.herzog.boot.dtos.ErrorResponse; /** * @author dherzog * * Error Handler Aspect */ @ControllerAdvice public class ErrorHandlerAspect { /** * Method that catches java validation errors * * @param ve - Method Argument Not Valid Exception being thrown * @return - ErrorResponse */ @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(value = HttpStatus.BAD_REQUEST) @ResponseBody public ErrorResponse validationError(final MethodArgumentNotValidException ve) { ErrorResponse response = new ErrorResponse(); BindingResult result = ve.getBindingResult(); ListobjectErrors = result.getAllErrors(); ObjectError objectError = objectErrors.get(0); response.setMessage(objectError.getDefaultMessage()); return response; } /** * Method that catches non application errors * * @param e - Exception being thrown * @return - ErrorResponse */ @ExceptionHandler(Throwable.class) @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) @ResponseBody public ErrorResponse serverError(final Throwable e) { e.printStackTrace(); ErrorResponse response = new ErrorResponse(); response.setMessage("General Service exception - please contact customer support"); return response; } }
import java.io.Serializable; /** * @author dherzog * * Error Response */ public class ErrorResponse implements Serializable { private static final long serialVersionUID = 1L; private String message; public String getMessage() { return message; } public void setMessage(final String message) { this.message = message; } @Override public String toString() { return String.format("ErrorResponse [message=%s]", message); } }
You can find the existing source code for part 3 at https://bitbucket.org/davidjherzog/spring-boot-angular. The code is under branch 0.0.3-SNAPSHOT.