1 understand AOP

1.1 What is AOP

AOP (Aspect Oriented Programming), section-oriented thought, is one of the three core ideas of Spring (IOC- inversion of control, DI- dependency injection).

So why is AOP so important? In our programs, there are often some systematic requirements, such as permission verification, logging, statistics, etc., which are scattered in various business logic, very redundant and not conducive to maintenance. Here’s an example:

It is clearly unacceptable to write repeated checksum logging code for as many business operations as possible. Of course, with object-oriented thinking, we can strip out the repetitive code and write it as a public method, like this:

This solves the problem of code redundancy and maintainability, but it is still a bit cumbersome to manually call these public methods in turn in each business method. Is there a better way? Yes, that is AOP. AOP completely extracts non-business code such as permission verification and logging, separates it from business code, and looks for nodes to cut into business code:

1.2 AOP architecture and concepts

To put it simply, AOP does three things:

  • Where to cut in, that is, in what business code non-business operations such as permission verification are performed.
  • When to cut in, before or after business code execution.
  • What to do after access, such as checking permissions, logging, etc.

Thus, the AOP architecture can be summarized as follows:

Some concepts:

  • Pointcut: cut point, which determines where processes such as permissions verification, logging, and so on are cut into the business code (i.e. woven into the cut plane). Tangent point is divided intoexecutionWay andannotationWay. The former can use a path expression to specify which classes are woven into the aspect, while the latter can specify which code is modified by which annotations are woven into the aspect.
  • Advice: processing, including the processing time and processing content. Processing content is doing things, such as verifying permissions and logging. The processing time is the time when the processing content is executed, which can be divided into pre-processing (that is, before the business code is executed) and post-processing (after the business code is executed).
  • Aspect: Section, i.ePointcutandAdvice.
  • Joint point: join point, a point at which a program executes. For example, the execution of a method or handling of an exception. In Spring AOP, a join point always represents a method execution.
  • WeavingWeaving is the process of processing content in a target object method through a dynamic proxy.

There is a picture on the Internet that I think is very vivid and I put it up here for you to see:

Two examples of AOP

The proof is in the pudding, so let’s roll out some code to implement AOP. Completed project has been uploaded to:

Github.com/ThinkMugz/a…

With AOP, you first need to introduce AOP dependencies. Parameter validation: Write parameter validators so that they are not discouraged

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Copy the code

2.1 First example

Next, let’s take a look at a minimalist example: all GET requests are invoked before printing “ADVICE for get requests was triggered” on the console.

The concrete implementation is as follows:

  1. Create an AOP facet class by simply adding an@AspectA note will do.@AspectAn annotation is used to describe an aspect class and is required when defining an aspect class.@ComponentThe annotations leave the class to Spring to manage. Implement advice in this class:
package com.mu.demo.advice; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @aspect @Component public class LogAdvice {// Define a pointcut: All GetMapping annotation modified method can weave advice the @pointcut (" @ the annotation (org. Springframework. Web. Bind. The annotation. GetMapping) ") private void LogAdvicePointcut (){} @before ("logAdvicePointcut()") public void logAdvice(){// Here is just an example, you can write any processing logic system.out.println ("get request advice triggered "); }}Copy the code

  1. Create an interface class that internally creates a GET request:
package com.mu.demo.controller; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/aop") public class AopController { @GetMapping(value = "/getTest") public JSONObject aopTest() { return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}"); } @PostMapping(value = "/postTest") public JSONObject aopTest2(@RequestParam("id") String id) { return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}"); }}Copy the code

After launch, request http://localhost:8085/aop/getTest interface:

Request http://localhost:8085/aop/postTest interface, the console output, prove tangent point is only to be GetMapping modification method.

2.2 Second Example

To make things more complicated, the scenario for this example is:

  1. Define a custom annotationPermissionsAnnotation
  2. Create a section class with the pointcut set to intercept all annotationsPermissionsAnnotationTo intercept the parameters of the interface and perform simple permission verification
  3. willPermissionsAnnotationAnnotate the test interface in the test interface classteston

Specific implementation steps:

  1. use@Target, @Retention, @documentedCustomize a comment:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionAnnotation{
}
Copy the code

  1. Create the first AOP facet class, simply add a@AspectA note will do.@AspectAn annotation is used to describe an aspect class and is required when defining an aspect class.@ComponentThe annotations leave the class to Spring to manage. Implement the first permission verification logic in this class:
package com.example.demo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @aspect @Component @order (1) public class PermissionFirstAdvice {// Define an Aspect, Brackets to the path of the custom annotations in step 1 the @pointcut (" @ the annotation (com) mu. The demo. The annotation. PermissionAnnotation) ") private void permissionCheck () {  } @Around("permissionCheck()") public Object permissionCheckFirst(ProceedingJoinPoint joinPoint) throws Throwable { System. The out. Println (" = = = = = = = = = = = = = = = = = = = the first section of = = = = = = = = = = = = = = = = = = = : "+ System. CurrentTimeMillis ()); Object[] objects = joinPoint.getargs (); Long id = ((JSONObject) objects[0]).getLong("id"); String name = ((JSONObject) objects[0]).getString("name"); System.out.println("id1->>>>>>>>>>>>>>>>>>>>>>" + id); System.out.println("name1->>>>>>>>>>>>>>>>>>>>>>" + name); If (id < 0) {return JSON. ParseObject ("{\"message\":\"illegal id\",\"code\":403}"); } return joinPoint.proceed(); }}Copy the code

  1. Create an interface class and annotate custom annotations on the target methodPermissionsAnnotation:
package com.example.demo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/permission") public class TestController { @RequestMapping(value = "/check", Method = requestMethod.post) @permissionSannotation () public JSONObject getGroupList(@requestBody JSONObject request) { return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}"); }}Copy the code

So let’s do a test here. First, fill in the request address and header:

Secondly, construct normal parameters:

You can get the normal response result:

Then, construct an exception parameter and request again:

The response result shows that the section class makes a judgment and returns the corresponding result:

One might ask, what if I want to set up multiple aspect classes for an interface? How is the order of execution of these facets managed?

Quite simply, a custom AOP annotation can correspond to multiple aspect classes, the Order of execution of which is managed by the @Order annotation. The smaller the number after the annotation, the earlier the aspect class is executed.

This is demonstrated in an example:

Create a second AOP aspect class that implements the second permission verification step:

package com.example.demo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Aspect @Component @Order(0) public class PermissionSecondAdvice { @Pointcut("@annotation(com.example.demo.PermissionsAnnotation)") private void permissionCheck() { } @Around("permissionCheck()") public Object permissionCheckSecond(ProceedingJoinPoint joinPoint) throws Throwable { System. The out. Println (" = = = = = = = = = = = = = = = = = = = the second section = = = = = = = = = = = = = = = = = = = : "+ System. CurrentTimeMillis ()); Object[] objects = joinPoint.getargs (); Long id = ((JSONObject) objects[0]).getLong("id"); String name = ((JSONObject) objects[0]).getString("name"); System.out.println("id->>>>>>>>>>>>>>>>>>>>>>" + id); System.out.println("name->>>>>>>>>>>>>>>>>>>>>>" + name); // if (! name.equals("admin")) { return JSON.parseObject("{\"message\":\"not admin\",\"code\":403}"); } return joinPoint.proceed(); }}Copy the code

Restart the project, continue testing, and construct the case where both parameters are abnormal:

As a result, the second section class of the surface is executed in a higher order:

AOP related annotations

In the above example, there are many annotations, which are explained in detail below.

3.1 the @pointcut

The @pointcut annotation is used to define an aspect, the entry point to something that was concerned with above. A Pointcut defines when an event is triggered.

@aspect @Component public class LogAspectHandler {/** * define an Aspect, Intercept com. Itcodai. Course09. Controller under the package and the package of all the methods * / the @pointcut (" execution (* com. Mutest. Controller.. *. * (..) )") public void pointCut() {} }Copy the code

The @pointcut annotation specifies a facet that defines what needs to be intercepted. Here are two common expressions: one using execution() and the other using annotation().

Execution expression:

In execution (* * com. Mutest. Controller.. *. * (..) For example:

  • The position of the first * : indicates the return value type, and * indicates all types.
  • Package name: indicates the name of the package to be intercepted. The following two periods indicate the current package and all subpackages of the current package, in this case the com.mutest. Controller package and methods of all classes under the subpackage.
  • The position of the second * : indicates the class name, and * indicates all classes.
  • (..) : The asterisk indicates the method name,All methods, parentheses for method parameters, and two periods for any parameters.

Annotation () expression:

The annotation() method is to define an aspect for an annotation. For example, if we do an aspect for a method with the @postMapping annotation, we can define an aspect as follows:

@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
public void annotationPointcut() {}
Copy the code

And then using that slice, you’re going to cut in annotation is all the way to @postMapping. This approach works well for scenarios where @getMapping, @postMapping, and @deletemapping annotations have specific processing logic.

There is also the ability to define facets for custom annotations, as shown in the example above.

@Pointcut("@annotation(com.example.demo.PermissionsAnnotation)")
private void permissionCheck() {}
Copy the code

3.2 @ Around

The @around annotation is used to modify Around enhancements, which are very powerful in the following ways:

  1. @AroundYou can choose the order in which the enhanced action and the target method are executed, that is, before, after, or even during the enhanced action. The implementation of this feature is calledProceedingJoinPointParameters of theprocedd()Method executes the target method.
  2. @AroundYou can change the parameter value of executing the target method or the return value after executing the target method.

Around enhancement processing has the following characteristics:

  1. When you define aAroundWhen enhancing a method, the first parameter of the method must beProceedingJoinPointType (at least one parameter). In the enhanced handler body, callProceedingJoinPointtheproceedMethod executes target method: here it is@AroundEnhancement processing can completely control the timing and execution of the target method. If the program doesn’t callProceedingJoinPointtheproceedMethod, the target method does not execute.
  2. callProceedingJoinPointtheproceedMethod, you can also pass in oneObject[ ]Object, the values of which are passed to the target method as arguments — that’s itAroundEnhancing the processing method can change the key value of the target method parameter. That’s what if you pass inObject[ ]The array length is not equal to the number of parameters required by the target method, orObject[ ]An exception occurs when the array elements do not match the types of the parameters required by the target method.

The @around feature, while powerful, usually needs to be used in a thread-safe environment. Therefore, there is no need to use Around for problems that can be solved using normal Before and AfterReturning. If you need to share some state data before and after the target method executes, you should consider using Around. In particular, if you need to prevent the execution of a target with enhancement processing, or if you need to change the return value of the target method, then Around enhancement processing is the only way to use it.

Now, let’s take a look at some of the characteristics of @around by modifying the previous example.

Custom annotation classes remain unchanged. First, define the interface class:

package com.example.demo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/permission") public class TestController { @RequestMapping(value = "/check", method = RequestMethod.POST) @PermissionsAnnotation() public JSONObject getGroupList(@RequestBody JSONObject request) { return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200,\"data\":" + request + "}"); }}Copy the code

Unique aspect class (there were two aspect classes in the previous example, just keep one here) :

package com.example.demo; import com.alibaba.fastjson.JSONObject; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Aspect @Component @Order(1) public class PermissionAdvice { @Pointcut("@annotation(com.example.demo.PermissionsAnnotation)") private void permissionCheck() { } @Around("permissionCheck()") public Object permissionCheck(ProceedingJoinPoint joinPoint) throws Throwable { System. The out. Println (" = = = = = = = = = = = = = = = = = = = began to enhance processing = = = = = = = = = = = = = = = = = = = "); Object[] objects = joinPoint.getargs (); Long id = ((JSONObject) objects[0]).getLong("id"); String name = ((JSONObject) objects[0]).getString("name"); System.out.println("id1->>>>>>>>>>>>>>>>>>>>>>" + id); System.out.println("name1->>>>>>>>>>>>>>>>>>>>>>" + name); // Modify the entry parameter JSONObject object = new JSONObject(); object.put("id", 8); object.put("name", "lisi"); objects[0] = object; // Pass the modified argument to return joinPoint.proceed(objects); }}Copy the code

{” ID “:-5,”name”:”admin”}, @around intercepts the input parameter of the interface, and causes the interface to return the result of the section class.

3.3 the @ Before

The @before annotation specifies the method to be executed Before cutting into the target method. It can do some Log processing, and it can also do some statistics, such as obtaining the user’s request URL and user’s IP address, etc. This can be used when doing personal sites, are common methods. For example:

@aspect @Component @slf4j public class LogAspectHandler {/** * execute the method before the section method defined above * @param joinPoint jointPoint */ @before ("pointCut()") public void doBefore(JoinPoint JoinPoint) {log.info("====doBefore method entered ===="); Signature Signature = joinPoint.getSignature(); / / to get into the package name String declaringTypeName = signature. GetDeclaringTypeName (); // Get the name of the method to be executed String funcName = signature.getName(); Log.info (" declaringTypeName = {}, part of {} package ", funcName); // Can also be used to record information, For example, get the URL and IP of the request. ServletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); URL = request.getrequestURL ().toString(); String IP = request.getremoteaddr (); Log.info (" user requested URL: {}, IP address: {}", URL, IP); }}Copy the code

The JointPoint object is useful for getting a signature that can be used to get the package name, method name, and parameters of the request (obtained in JoinPoint.getargs ()).

Search the programmer Maidong public account, reply “888”, send you a 2020 latest Java interview questions manual. PDF

@ 3.4 After

The @after annotation, as opposed to the @before annotation, executes After the specified method is cut into the target method, and can also do some Log processing After the method completes.

@aspect @component @slf4j public class LogAspectHandler {/** * define an Aspect, */ @pointcut ("execution(* com.mutest. Controller.. *. * (..) )") public void pointCut() {} @param joinPoint jointPoint */ @after ("pointCut()") public void pointCut() { DoAfter (JoinPoint JoinPoint) {log.info("==== doAfter method entered ===="); Signature signature = joinPoint.getSignature(); String method = signature.getName(); Log.info (" method {} has been executed ", method); }}Copy the code

Here, let’s write a Controller to test the result. Create a new AopController like this:

@RestController @RequestMapping("/aop") public class AopController { @GetMapping("/{name}") public String testAop(@PathVariable String name) { return "Hello " + name; }}Copy the code

Start the project, type localhost:8080/aop/ CSDN in your browser, and observe the console output:

The ====doBefore method enters ====. TestAop, belongs to the com. Itcodai. Mutest. AopController package user requested url is: http://localhost:8080/aop/name, the IP address is: 0:0:0:0:0:0:0:1 ==== doAfter method entered ==== method testAop has been executedCopy the code

From the printed Log, we can see the logic and order of program execution, and we can intuitively grasp the actual function of @before and @after annotations.

Search the programmer Maidong public account, reply “888”, send you a 2020 latest Java interview questions manual. PDF

3.5 @ AfterReturning

The @AfterRETURNING annotation is similar to the @After annotation, except that it can be used to capture the return value of a cut method After it has been executed, and to enhance the return value with business logic, for example:

@aspect @Component @slf4j public class LogAspectHandler {/** * execute the section method defined above after it returns, @param joinPoint joinPoint * @param result result */ @afterreturning (pointcut = "pointcut ()", returning = "result") public void doAfterReturning(JoinPoint joinPoint, Object result) { Signature signature = joinPoint.getSignature(); String classMethod = signature.getName(); Log.info (" method {} completed, return: {}", classMethod, result); Log.info (" business enhancements to return parameters: {}", result + "enhancements "); }}Copy the code

It is important to note that in the @afterRETURNING annotation, the values of the attribute RETURNING must match the parameters to avoid detection. The second entry in the method is the return value of the sliced method, which can be enhanced in doAfterReturning and encapsulated according to business needs. Let’s restart the service and test again:

After the testAop method is executed, the return parameter is: Hello CSDN Service enhancement: Hello CSDN enhanced versionCopy the code

3.6 @ AfterThrowing

When an exception is thrown during the execution of the cut method, it is executed in the @AfterThrowing annotation method, where you can do some exception handling logic. It is important to note that the value of the Throwing attribute must be the same as the throwing parameter, otherwise an error will be reported. The second input parameter in this method is the exception thrown.

@aspect @Component @slf4j public class LogAspectHandler {/** * when the section method defined above executes an exception, @param jointPoint jointPoint * @param ex ex */ @afterthrowing (pointcut = "pointcut ()", throwing = "ex") public void afterThrowing(JoinPoint joinPoint, Throwable ex) { Signature signature = joinPoint.getSignature(); String method = signature.getName(); // Log.info (" execution method {} error, exception: {}", method, ex); }}Copy the code

The last

Thank you for reading here, the article is inadequate, welcome to point out; If you think it’s good, give me a thumbs up.

Also welcome to pay attention to my public number: programmer Maidong, Maidong will share Java related technical articles or industry information every day, welcome to pay attention to and forward the article!