Spring AOP is a must for Java interviews, and we need to understand the basic concepts and principles of AOP. So what exactly is Spring AOP, and why do interviewers love to ask about it? This article first introduces the basic concept of AOP, and then according to the principle of AOP, the implementation of an interface to return a unified format of the small example, convenient for everyone to understand how to use Spring AOP!

Why use AOP?

During actual development, our application will be divided into many layers. Generally speaking, a Java Web application has the following layers:

  • Web layer: Mainly exposes some Restful apis for the front end to call.
  • Business layer: Mainly deals with the concrete business logic.
  • Data persistence layer: mainly responsible for database related operations (add, delete, change and check).

While it may seem like each layer is doing something completely different, there is always similar code, such as log printing and exception handling. If we choose to write this part of the code separately in each layer, the code will become difficult to maintain over time. So we offer another solution: AOP. This ensures that common code is maintained together and that we have the flexibility to choose where to use it.

What is AOP?

AOP (Aspect Oriented Programming, section Oriented Programming), can be said to be OOP (Object Oriented Programing, object-oriented Programming) complement and perfect. OOP introduces concepts such as encapsulation, inheritance, and polymorphism to create an object hierarchy that simulates a collection of common behaviors. OOP is helpless when we need to introduce common behavior to discrete objects. That is, OOP allows you to define top-to-bottom relationships, but not left-to-right relationships. Logging, for example, tends to be distributed horizontally across all object hierarchies, regardless of the core functionality of the object to which it is distributed. The same is true for other types of code, such as permission management, exception handling, and so on. This scattering of extraneous code is called cross-cutting code, and in OOP design it leads to a lot of code duplication, which is not conducive to reuse of modules.

AOP, on the other hand, uses a technique called “crosscutting” to rip open the interior of a wrapped object and encapsulate common behavior that affects multiple classes into a reusable module called “Aspect,” or section. The so-called “section”, in a simple way, is to extract and encapsulate permissions, transactions, logs, exceptions and other functions that are relatively independent from business logic, so as to reduce the repetitive code of the system, reduce the coupling degree between modules and increase the maintainability of the code. AOP represents a horizontal relationship, if the “object” is a hollow cylinder, which encapsulates the properties and behavior of the object; Programming to sections, then, is like cutting open these hollow cylinders to get information from them, and then restoring them with a masterly hand without leaving a trace.

Section understanding: with a knife will be divided into two watermelon, cut the incision is the section; Cooking, pot and stove together to complete cooking, pot and stove is the noodles. In Web hierarchy design, the Controller layer, Service layer, Dao layer, each layer is also a section. In programming, object to object, method to method, module to module are all facets.

Recommend an easy to understand AOP on the web:.

Three, AOP usage scenarios

  • Access control
  • The logging stored
  • Unified Exception Handling
  • Cache handling
  • Transaction processing

AOP terminology

AOP has a lot of professional terms, at first look at so many terms, may not understand, read a few times, I believe that will soon understand.

1. Advice

  • Before advice: Perform advice Before the target method invocation
  • Around advice: Custom logic can be executed both before and after the target method invocation
  • After Returning Advice: Calls advice After successful execution of the target method
  • After Throwing advice: After the target method throws an exception, the advice is executed
  • After advice: Execute advice After the target method completes, whether it throws an exception or executes successfully

2. JoinPoint

This is where Spring allows you to put Advice, a lot. Basically every method can be join points before, after, or when it throws an exception. Spring only supports method join points, and all before and after methods.

Tips: You can use join points to get the class name, method name, parameter name, and so on for execution.

3, Pointcut

Pointcuts are defined on the basis of join points. For example, if you have 15 methods in a class, there are dozens of join points, but you only want a few of them to do something before, after, or when you throw an exception. Pointcuts define these methods and let pointcuts filter join points.

4) Aspect

Advice is a combination of Advice and Pointcut, which specifies what to do and when (defined by @before, @around, @after, @afterreturning, @afterthrowing), A Pointcut tells you where to do it, which is a complete aspect definition.

5. AOP proxy

AOP Proxy: An object created by an AOP framework, and a Proxy is an enhancement of the target object. AOP’s clever examples use dynamic proxies to elegantly solve problems beyond OOP’s power. AOP proxies in Spring can be either JDK dynamic proxies or Cglib dynamic proxies. The former is based on interfaces, while the latter is based on subclasses.

Five, AOP example: implement Spring interface return uniform (normal/abnormal) format

After reading so many of the abstractions above, the absorption or depth of understanding might not be that good without an AOP concrete example. So, read on:

1. Define the return format

import lombok.Data; @data public class Result<T> {// code Status value: 0 indicates success, and other values indicate failure. Private Integer code; MSG returns the message. If code is 0, MSG is success; If code is 1, MSG is error private String MSG; // data returns a result set, using generics compatible with different types private T data; }Copy the code

2. Define some known exceptions

import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; Error (" UNKNOW_ERROR "); error (" UNKNOW_ERROR "); error (" UNKNOW_ERROR "); error (" UNKNOW_ERROR ") NullPointerException "), INVALID_EXCEPTION (1146, "invalid data access resources using exceptions: InvalidDataAccessResourceUsageException"); public Integer code; public String msg; }Copy the code

3. Exception classes are caught and returned

//@ControllerAdvice @Component @Slf4j public class ExceptionHandle { // @ExceptionHandler(value = Exception.class) // @responseBody public Result exceptionGet(Throwable t) {log.error(" exceptionGet: ", t); if (t instanceof InvalidDataAccessResourceUsageException) { return ResultUtil.error(ExceptionEnum.INVALID_EXCEPTION); } else if (t instanceof NullPointerException) { return ResultUtil.error(ExceptionEnum.NULL_EXCEPTION); } return ResultUtil.error(ExceptionEnum.UNKNOW_ERROR); }}Copy the code

Make a result return utility class:

Public class ResultUtil {/ * * * @ return com. Study. Spring. The entity. The Result * @ the data format of the description interface call successful return * @ param: object */ public static Result success(Object object) { Result result = new Result(); result.setCode(0); result.setMsg("success"); result.setData(object); return result; } / * * * @ return com. Study. Spring. The entity. The Result * @ the data format of the description interface call failure return * @ param: code * @ param: msg */ public static Result error(Integer code, String msg) { Result result = new Result(); result.setCode(code); result.setMsg(msg); result.setData(null); return result; } /** * return an exception message, * * @param exceptionEnum * @return */ public static Result Error (exceptionEnum exceptionEnum) {Result Result = new Result(); result.setCode(exceptionEnum.code); result.setMsg(exceptionEnum.msg); result.setData(null); return result; }}Copy the code

4. Pom dependency

Dependencies like Spring AOP must be added:

<! -- Web dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <! GroupId > <artifactId> Spring-boot-starter-AOP </artifactId> </dependency> <! -- For log sections, <dependency> <groupId>com.google.code.gson</groupId> <artifactId> The < version > 2.8.5 < / version > < / dependency > <! --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>Copy the code

5. Custom annotations

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface HandleResult {

    String desc() default "create17";

}Copy the code

Some of the concepts in the above code need to be explained:

  • @Retention: Defines Retention policies for annotations
    • @Retention(retentionPolicy.source) : Annotations remain in SOURCE; they are discarded when Java files are compiled into class bytecode files.
    • @Retention(retentionPolicy. CLASS: default Retention policy in which annotations are retained in CLASS bytecode files but discarded when run (JVM loads CLASS bytecode files).
    • @Retention(retentionPolicy.runtime) : Annotations are retained in class bytecode files and can also be retrieved by reflection at RUNTIME.
  • @target: Defines the Target of the annotation. Multiple annotations can be separated by commas.
    • @target (elementType.type) : applies to interfaces, classes, enumerations, annotations
    • @target (elementType. FIELD) : constants for fields and enumerations
    • @target (ElementType.METHOD) : Applies to methods, excluding constructors
    • @target (elementtype.parameter) : the PARAMETER applied to the method
    • @target (elementtype.constructor) : Applies to the CONSTRUCTOR
    • @target (ElementType.LOCAL_VARIABLE) : applies to local variables
    • @target (ElementType.ANNOTATION_TYPE) : applies to annotations
    • @target (ElementType.PACKAGE) : applies to packages
  • @document: indicates that the annotation will be included in javadoc.
  • @Inherited: Indicates that a child class can inherit the annotations in its parent class.
  • @interface: declare custom annotations.
  • Desc () : defines a property, default create17. @handleresult (desc = “Describe content…”) )

At this point, a complete custom annotation definition is complete.

6. Section realization

1)First we define a section class HandleResultAspect

  • Using the @aspect annotation to define aspects is essential to identify the current class as an Aspect for container management.
  • It is also necessary to use the @Component annotation to define components and to identify the current class as a Component for container management.
  • Use the @slf4j annotation to print the log;
  • Use the @order (I) annotation to indicate the Order of the cuts, more on that later.
@Aspect
@Component
@Slf4j
@Order(100)
public class HandleResultAspect {
    ...
}        Copy the code

2)Next, we define a pointcut.

Use @pointcut to define a Pointcut.

@Pointcut("@annotation(com.study.spring.annotation.HandleResult)") // @Pointcut("execution(* com.study.spring.controller.. *. * (..) )") public void HandleResult() { }Copy the code

An execution expression is an execution expression.

* Execution (< modifier mode >? < return type mode >< method name mode >(< parameter mode >)< exception mode >? *

All items are optional except return type mode, method name mode, and parameter mode. This explanation may be a little difficult to understand, but let’s look at a concrete example. In HandleResultAspect we define a tangent point, its execution expression is: * com. Study. Spring. The controller.. *. * (..) ), the following table shows the popular parsing of this expression:

identifier

meaning

Execution ()

Body of an expression

The first one*symbol

Represents the type of the return value,*Represents all return types

com.study.spring.controller The package name of the service that AOP cuts, that is, the business class that needs to be crosscut

After the package name.

Represents the current package and its subpackages

The second*

Represents the class name,*Represents all classes

The last of the. * (..)

The first one.Represents any method name, with parameter types in parentheses,.Represents any type parameter

The execution of the expression is the com. Study. Spring. All methods under the controller as a point of contact. In addition to using execution expressions, @pointcut can also use @annotation to specify annotation cuts. For example, @handleresult, a custom annotation created above, can be specified. Where @Handleresult is used is a Pointcut.

3)Say something about the Advice section notes

  • @before: The decorated method is executed Before entering the pointcut. In this section, we need to print a log of the start of execution, such as type, method name, parameter name, etc.
@Before(value = "HandleResult() && @annotation(t)", argNames = "joinPoint,t") public void doBefore(JoinPoint joinPoint, HandleResult t) throws Exception {// className String className = joinpoint.gettarget ().getclass ().getname (); String methodName = joinPoint.getSignature().getName(); // Object[] args = joinPoint.getargs (); StringBuilder sb = new StringBuilder(); if (args ! = null && args.length > 0) { for (Object arg : args) { sb.append(arg).append(", "); Interface {}}} the info (" began to be called, the name of the class: {}, the method name: {}, parameter called: {}. ", t.d esc (), the className, methodName, sb. The toString ()); }Copy the code

  • @around: Decorated methods wrap Around the pointcut, weave code before and after the pointcut, and have the freedom to control when the pointcut is executed. Popular point is: before entering the tangent point to perform part of logic, and then enter the tangent point to perform business logic (ProceedingJoinPoint. Proceed () method can be used to return information of receiving business logic), and finally the tangent point to perform another part of the logic.
@Around("HandleResult()") public Result doAround(ProceedingJoinPoint point) { long startTime = System.currentTimeMillis(); The info (" -- -- -- HandleResultAspect - Around the first half of the -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "); Object result; Try {// execute the pointcut. Point.proceed is the method return value result = point.proceed(); // Print out the log.info(" interface original output content: {}", new Gson().tojson (result)); Log.info (" Execution time: {} ms", system.currentTimemillis () -startTime); return ResultUtil.success(result); } catch (Throwable throwable) { return exceptionHandle.exceptionGet(throwable); }}Copy the code

  • @after: The decorated method corresponds to @before and is executed whether the program executes normally or abnormally.
@After("HandleResult()") public void doAfter() { log.info("doAfter..." ); }Copy the code

  • @afterRETURNING: This method is executed after normal pointcuts. It is used in scenarios where some processing is done on returned values.

Returning Information is ultimately returned by a receiving interface.

@AfterReturning(pointcut = "@annotation(t)", returning = "res") public void afterReturn(HandleResult t, Object res) {log.info(" interface {} has been called, the final result is: {}.", t.esc (), new Gson().tojson (res)); }Copy the code

  • AfterThrowing: This method is executed after the pointcut throws an exception.

Throwing is used to get exception information.

@AfterThrowing(throwing = "throwable", pointcut = "HandleResult()") public void afterThrowing(Throwable throwable) { log.info("After throwing..." , throwable); }Copy the code

The order of execution of these notifications is shown below:

The following is the full code of the section implementation:

@Aspect @Component @Slf4j @Order(100) public class HandleResultAspect { @Autowired private ExceptionHandle exceptionHandle; / * * * @ return void * @ description defined tangent point * / the @pointcut (" @ the annotation (com) study. Spring. The annotation. HandleResult) ") / / @Pointcut("execution(* com.study.spring.controller.. *. * (..) )") public void HandleResult() {} /** * @return void * @description print interface name, class name, method name, parameter name * @param: joinPoint * @param: t */ @Before(value = "@annotation(t)", argNames = "joinPoint,t") public void doBefore(JoinPoint joinPoint, HandleResult t) throws Exception {// className String className = joinpoint.gettarget ().getclass ().getname (); String methodName = joinPoint.getSignature().getName(); // Object[] args = joinPoint.getargs (); StringBuilder sb = new StringBuilder(); if (args ! = null && args.length > 0) { for (Object arg : args) { sb.append(arg).append(", "); Interface {}}} the info (" began to be called, the name of the class: {}, the method name: {}, parameter called: {}. ", t.d esc (), the className, methodName, sb. The toString ()); } /** * @return java.lang.Object * @description defines the @around surround for when to execute the pointcut * @param: proceedingJoinPoint */ @Around("HandleResult()") public Result doAround(ProceedingJoinPoint point) { long startTime = System.currentTimeMillis(); The info (" -- -- -- HandleResultAspect - Around the first half of the -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "); Object result; Try {// execute the pointcut. Point.proceed is the method return value result = point.proceed(); // Print out the log.info(" interface original output content: {}", new Gson().tojson (result)); Log.info (" Execution time: {} ms", system.currentTimemillis () -startTime); return ResultUtil.success(result); } catch (Throwable throwable) { return exceptionHandle.exceptionGet(throwable); }} /** * @return void * @description, whether normal or abnormal, the method is executed * @param: */ @After("HandleResult()") public void doAfter() { log.info("doAfter..." ); } /** * @return void * @description When the program is running properly, the method is executed * @param: t * @param: res */ @AfterReturning(pointcut = "@annotation(t)", returning = "res") public void afterReturn(HandleResult t, Object res) {log.info(" interface {} was called, interface final return result: {}.", t.esc (), new Gson().tojson (res)); } /** * @return void * @description when an exception occurs, the method is used to print the exception * @param: throwable */ @AfterThrowing(throwing = "throwable", pointcut = "HandleResult()") public void afterThrowing(Throwable throwable) { log.info("After throwing..." , throwable); }}Copy the code

Six, the order of execution of the multi-section

In production, our project may have more than one facet, so how do we assign priority to facets when there are multiple facets?

We can use the @Order(I) annotation to define the priority of the slice. The lower the I value, the higher the priority.

For example, if we create another section, the code example is as follows:

@Aspect @Component @Order(50) @Slf4j public class TestAspect2 { @Pointcut("@annotation(com.study.spring.annotation.HandleResult)") public void aa(){ } @Before("aa()") public void Bb (JoinPoint JoinPoint){log.info(" I am Before method of TestAspect2...") ); } @around ("aa()") public Object cc(ProceedingJoinPoint Point){log.info(" I am TestAspect2. ); Object result = null; try { result = point.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } log.info(" I'm the second half of TestAspect2's Around method..." ); return result; } @after ("aa()") public void doAfter() {log.info(" I am TestAspect2 After method... ); } @afterreturning (" AA ()") public void afterReturn() {log.info(" I am AfterReturning TestAspect2... ); } @afterthrowing ("aa()") public void AfterThrowing() {log.info(" I am the AfterThrowing method of TestAspect2... ); }}Copy the code

The TestAspect2 aspect is @Order(50), and the previous aspect HandleResultAspect is Order(100). The log returned by the test interface is as follows:

To sum up the rules:

  • Before executing the pointcut, @order is executed from small to large, that is, the smaller the Order, the higher the priority;
  • After executing the pointcut, @order is executed from large to small, i.e., the larger the Order, the higher the priority;

Namely: first in, last out principle. To facilitate our understanding, I drew a diagram, as shown below:

How to set up AOP in a specific environment

Generally, in project development, there are three environments: development, test, and production. So what if I only want to use a facet in a development and test environment? We just need to annotate @profile above the specified section class, as follows:

This specifies that HandleResultAspect is valid only in dev and test environments, but not in proD. Yml file and specify spring.profiles.active as dev or test in the application.yml file.

Eight, summary

This article is long, but it gives you a brief overview of Spring AOP. From the origin of AOP to the concept, usage scenarios, and then in-depth understanding of its terminology, using AOP ideas to implement examples for our own understanding. After reading this article, I believe that you can basically not fear the assessment of the interviewer on this knowledge point!

The code in this article has been uploaded to Github:

References:

——