SpringBoot: Aspect AOP to achieve permission verification: example demonstration and full explanation of annotations

Click on the blue font above and select "Star Official Account"

High-quality articles, delivered immediately

Follow the official account backstage to reply to pay or mall to obtain actual project information + video

Author: i do not know at Cloudland

blog.csdn.net/mu_wind/article/details/102758005

table of Contents

  • Understand AOP

    • What is AOP

    • AOP system and concept

  • AOP example

    • First instance

    • Second instance

  • AOP phase attention solution

    • @Pointcut

    • @Around

    • @Before

    • @After

    • @AfterReturning

    • @AfterThrowing

1 Understand AOP

1.1 What is AOP

AOP (Aspect Oriented Programming), an aspect-oriented idea, is one of Spring's three core ideas (two outside two: IOC-inversion of control, DI-dependency injection).

So why is AOP so important? In our programs, there are often some systemic requirements, such as permission verification, logging, statistics, etc. These codes will be scattered and interspersed in various business logics, which are very redundant and not conducive to maintenance. For example, the following diagram:

how many business operations there are, how many repeated checksum logging codes need to be written, which is obviously unacceptable. Of course, using object-oriented thinking, we can extract these repetitive codes and write them as public methods, as follows:


In this way, the problem of code redundancy and maintainability has been solved, but in each business method, it is still necessary to manually call these public methods in turn, which is also slightly cumbersome. Is there a better way? Yes, that is AOP. AOP completely extracts non-business codes such as permission verification and log records, separates them from business codes, and finds nodes to cut into business codes:

1.2 AOP system and concept

Simply understand, in fact, AOP has to do three types of things:

  • Where to cut in, that is, in which business code the non-business operations such as permission verification are executed.

  • When to cut in, before or after the execution of the business code.

  • What to do after switching in, such as permission verification, logging, etc.

Therefore, the AOP system can be sorted out as the following figure:

Some concepts are explained in detail:

  • Pointcut: Cut point, decide where to cut into the business code (ie weave into the facet) for processing such as permission verification, log records, etc. The cut point is divided into executionway and annotationway. The former can use path expressions to specify which classes are woven into the aspect, and the latter can specify which annotations modify the code to be woven into the aspect.

  • Advice: Processing, including processing timing and processing content. Processing content is what to do, such as verifying permissions and recording logs. Processing timing is when the processing content is executed, which is divided into pre-processing (that is, before the execution of the business code), post-processing (after the execution of the business code), and so on.

  • Aspect: Cut surface, that is, Pointcutand Advice.

  • Joint point: Connection point is a point of program execution. For example, the execution of a method or the handling of an exception. In Spring AOP, a connection point always represents a method execution.

  • Weaving: Weaving is the process of executing content processing in target object methods through dynamic agents.

There is a picture on the Internet, I think it is very vivid, post it here for everyone to see:

2 AOP example

Real knowledge is gained from practice, and then we will use code to implement AOP. The completed project has been uploaded to:

https://github.com/ThinkMugz/aopDemo

To use AOP, you first need to introduce AOP dependencies. Parameter verification: Write the parameter verification (validator) so that you will not be dissuaded~

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

2.1 The first example

Next, let's look at a minimalist example: getbefore all requests are called, a sentence "advice of get request triggered" is output on the console.

The specific implementation is as follows:

  1. To create an AOP aspect class, just add an @Aspect annotation to the class  . @Aspect Annotation is used to describe an aspect class, and this annotation needs to be marked when defining an aspect class. @Component The annotations will be handed over 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 {
    // 定义一个切点:所有被GetMapping注解修饰的方法会织入advice
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    private void logAdvicePointcut() {}

 // Before表示logAdvice将在目标方法执行前执行
    @Before("logAdvicePointcut()")
    public void logAdvice(){
     // 这里只是一个示例,你可以写任何处理逻辑
        System.out.println("get请求的advice触发了");
    }
}
  1. Create an interface class and create a get request internally:

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}");
    }
}

After the project is launched, request the http://localhost:8085/aop/getTestinterface:

Insert picture description here

The http://localhost:8085/aop/postTestinterface is requested , and there is no output from the console, which proves that the cutpoint is indeed only for the GetMappingmodified method.

2.2 The second example

Let's complicate the problem a bit. The scenario of this example is:

  1. Customize an annotationPermissionsAnnotation

  2. Create an aspect class, set the point of cut to intercept all marked PermissionsAnnotationmethods, intercept the parameters of the interface, and perform simple permission verification

  3. The PermissionsAnnotationmarked test interface test interface class teston

Specific implementation steps:

  1. Use @Target、@Retention、@Documenteda custom annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionAnnotation{
}
  1. To create the first AOP aspect class, just add an @Aspect annotation to the class  . @Aspect Annotation is used to describe an aspect class, and this annotation needs to be marked when defining an aspect class. @Component The annotations will be handed over to Spring to manage. Implement the first step 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 {

 // 定义一个切面,括号内写入第1步中自定义注解的路径
    @Pointcut("@annotation(com.mu.demo.annotation.PermissionAnnotation)")
    private void permissionCheck() {
    }

    @Around("permissionCheck()")
    public Object permissionCheckFirst(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("===================第一个切面===================:" + 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);

        // id小于0则抛出非法id的异常
        if (id < 0) {
            return JSON.parseObject("{\"message\":\"illegal id\",\"code\":403}");
        }
        return joinPoint.proceed();
    }
}
  1. Create an interface class and mark a custom annotation on the target method  PermissionsAnnotation:

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}");
    }
}

Here, we first conduct a test. First, fill in the request address and header:

Second, construct normal parameters:

You can get the normal response result:

Then, construct an abnormal parameter and request again:

The response result shows that the aspect class has been judged and the corresponding result is returned:

Some people may ask, what if I want to set up multiple aspect classes for verification in one interface? How to manage the execution order of these aspects?

It's very simple. A custom AOPannotation can correspond to multiple aspect classes. The execution order of these aspect classes is managed by the @Orderannotation. The smaller the number after the annotation, the more the aspect class is executed first.

The following is demonstrated in an example:

Create a second AOP aspect class, and implement the second step of permission verification 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(0)
public class PermissionSecondAdvice {

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

   @Around("permissionCheck()")
   public Object permissionCheckSecond(ProceedingJoinPoint joinPoint) throws Throwable {
       System.out.println("===================第二个切面===================:" + 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);

       // name不是管理员则抛出异常
       if (!name.equals("admin")) {
           return JSON.parseObject("{\"message\":\"not admin\",\"code\":403}");
       }
       return joinPoint.proceed();
   }
}

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

 

In response to the results, the execution order of the second aspect class appears to be higher:

 

3 AOP phase attention solution

In the above case, a lot of annotations are used, and these annotations are explained in detail below.

3.1 @Pointcut

@Pointcut Annotation is used to define an aspect, that is, the entry point of something that is concerned above, and the point of entry defines the timing of event triggering.

@Aspect
@Component
public class LogAspectHandler {

    /**
     * 定义一个切面,拦截 com.itcodai.course09.controller 包和子包下的所有方法
     */
    @Pointcut("execution(* com.mutest.controller..*.*(..))")
    public void pointCut() {}
}

The @Pointcut annotation specifies an aspect and defines what needs to be intercepted. Here are two commonly used expressions: one is to use  execution()and the other is to use  annotation().

More reference: SpringBoot content aggregation

execution expression:

Take the  execution(* * com.mutest.controller..*.*(..))) expression as an example:

  • The position of the first *: indicates the return value type, and * indicates all types.

  • Package name: indicates the name of the package that needs to be intercepted. The two periods behind indicate the current package and all subpackages of the current package. In this example, it refers to the com.mutest.controller package and the methods of all classes under the subpackage.

  • The position of the second * sign: indicates the class name, * indicates all classes.

  • *(..): This asterisk represents the method name, * represents all methods, and the parentheses indicate the method parameters, and the two periods represent any parameters.

annotation() expression:

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

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

Then, if you use this aspect, @PostMapping all the methods of the annotation will be cut into  . This method is very suitable for processing  @GetMapping、@PostMapping、@DeleteMappingscenarios where different annotations have various specific processing logic.

There is also the definition of aspects for custom annotations as shown in the above case.

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

3.2 @Around

@AroundAnnotations are used to modify and Aroundenhance processing, which Aroundis very powerful, manifested in:

  1. @AroundThe execution order of the enhancement action and the target method can be freely selected, which means that the target method can be executed before and after the enhancement action, or even during the process. The realization of this feature is that ProceedingJoinPointthe procedd()method that calls the parameter will execute the target method.

  2. @AroundYou can change the parameter value of the execution target method, and you can also change the return value after the execution of the target method.

AroundEnhanced processing has the following characteristics:

  1. When defining an Aroundenhanced processing method, the first parameter of the method must be a  ProceedingJoinPoint type (at least one parameter). In vivo enhancement processing, call ProceedingJoinPointthe proceedmethod will execute the target method: This is the @Aroundenhanced processing method can completely control the target execution timing, how to perform key; if the program does not call ProceedingJoinPointthe proceedmethod, the target method does.

  2. When calling ProceedingJoinPointthe proceedmethod, you can also pass in an Object[ ]object, and the value in the array will be passed to the target method as an actual parameter-this is the key to the Aroundenhanced processing method that can change the parameter value of the target method. This is if the Object[ ]length of the array passed in is not equal to the number of parameters required by the target method, or the Object[ ]array elements do not match the types of the parameters required by the target method, the program will be abnormal.

@AroundAlthough powerful, it usually needs to be used in a thread-safe environment. Therefore, if you use common Before, AfterReturningwill be able to solve the problem, there is no need to use Aroundup. If you need to share some state data before and after the target method is executed, you should consider using it Around. Especially when you need to use enhanced processing to prevent the execution of the target, or need to change the return value of the target method, you can only use Aroundenhanced processing.

Next, make some modifications on the previous example to observe @Aroundthe characteristics.

The custom annotation class remains 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 + "}");
    }
}

The only facet type (there are two facet types in the previous case, only need to 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.out.println("===================开始增强处理===================");

        //获取请求参数,详见接口类
        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);

  // 修改入参
        JSONObject object = new JSONObject();
        object.put("id", 8);
        object.put("name", "lisi");
        objects[0] = object;
  
  // 将修改后的参数传入
        return joinPoint.proceed(objects);
    }
}

Also use JMeter to call the interface, pass in the parameter:, {"id":-5,"name":"admin"}and the response result shows that: the @Aroundinput parameters of the interface are intercepted, and the interface returns the result in the aspect class.

3.3 @Before

@Before The method specified by the annotation is executed before the aspect cuts into the target method. It can do some  Log processing or statistics of information, such as obtaining the user's request  URL and the user's  IP address, etc. This can be used when making a personal site. It is a commonly used method. For example, the following code:

@Aspect
@Component
@Slf4j
public class LogAspectHandler {
    /**
     * 在上面定义的切面方法之前执行该方法
     * @param joinPoint jointPoint
     */
    @Before("pointCut()")
    public void doBefore(JoinPoint joinPoint) {
        log.info("====doBefore方法进入了====");

        // 获取签名
        Signature signature = joinPoint.getSignature();
        // 获取切入的包名
        String declaringTypeName = signature.getDeclaringTypeName();
        // 获取即将执行的方法名
        String funcName = signature.getName();
        log.info("即将执行方法为: {},属于{}包", funcName, declaringTypeName);

        // 也可以用来记录一些信息,比如获取请求的 URL 和 IP
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 获取请求 URL
        String url = request.getRequestURL().toString();
        // 获取请求 IP
        String ip = request.getRemoteAddr();
        log.info("用户请求的url为:{},ip地址为:{}", url, ip);
    }
}

JointPoint The object is very useful, you can use it to get a signature, and use the signature to get the requested package name, method name, including parameters (by  joinPoint.getArgs() obtaining), etc.

3.4 @After

@After Annotation  @Before corresponds to an annotation. The specified method is executed after the aspect cuts into the target method, or some Log processing after a certain method is completed.

@Aspect
@Component
@Slf4j
public class LogAspectHandler {
    /**
     * 定义一个切面,拦截 com.mutest.controller 包下的所有方法
     */
    @Pointcut("execution(* com.mutest.controller..*.*(..))")
    public void pointCut() {}

    /**
     * 在上面定义的切面方法之后执行该方法
     * @param joinPoint jointPoint
     */
    @After("pointCut()")
    public void doAfter(JoinPoint joinPoint) {

        log.info("==== doAfter 方法进入了====");
        Signature signature = joinPoint.getSignature();
        String method = signature.getName();
        log.info("方法{}已经执行完", method);
    }
}

At this point, let's write a Controller to test the execution results, and create a new AopController as follows:

@RestController
@RequestMapping("/aop")
public class AopController {

    @GetMapping("/{name}")
    public String testAop(@PathVariable String name) {
        return "Hello " + name;
    }
}

Start the project, enter: localhost:8080/aop/csdn in the browser, and observe the output information of the console:

====doBefore 方法进入了====  
即将执行方法为: testAop,属于com.itcodai.mutest.AopController包  
用户请求的 url 为:http://localhost:8080/aop/name,ip地址为:0:0:0:0:0:0:0:1  
==== doAfter 方法进入了====  
方法 testAop 已经执行完

From the print out  Log can be seen with the logical sequence of program execution, control can be very intuitive  @Before and  @After practical effect of two annotations.

3.5 @AfterReturning

@AfterReturning Annotations  @After are somewhat similar, the difference is that  @AfterReturning annotations can be used to capture the return value after the cut-in method is executed, and perform business logic enhancement processing on the return value, for example:

@Aspect
@Component
@Slf4j
public class LogAspectHandler {
    /**
     * 在上面定义的切面方法返回后执行该方法,可以捕获返回对象或者对返回对象进行增强
     * @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("方法{}执行完毕,返回参数为:{}", classMethod, result);
        // 实际项目中可以根据业务做具体的返回值增强
        log.info("对返回参数进行业务上的增强:{}", result + "增强版");
    }
}

It should be noted that in the  @AfterReturning annotation, returning the value of the attribute  must be consistent with the parameter, otherwise it will not be detected. The second input parameter in this method is the return value of the cut method. The return value doAfterReturning can be enhanced in the  method, and the corresponding encapsulation can be done according to business needs. Let's restart the service and test it again:

方法 testAop 执行完毕,返回参数为:Hello CSDN  
对返回参数进行业务上的增强:Hello CSDN 增强版

3.6 @AfterThrowing

When an exception is thrown during the execution of the cut method, it will be  @AfterThrowing executed in the annotated method, and some exception handling logic can be done in this method. It should be noted that the throwing value of the  attribute must be consistent with the parameter, otherwise an error will be reported. The second input parameter in this method is the exception thrown.

@Aspect
@Component
@Slf4j
public class LogAspectHandler {
    /**
     * 在上面定义的切面方法执行抛异常时,执行该方法
     * @param joinPoint 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("执行方法{}出错,异常为:{}", method, ex);
    }
}

The above is the whole content of AOP. Through a few examples, you can feel the convenience of AOP.


There are hot recommendations???

Our company has used 7 years of code execution time-consuming statistical functions, so elegant! !

19 pictures overview Spring Cloud

The new CTO prohibits the separation of front and back ends, and also said a lot of advantages and disadvantages!

It is not difficult to realize SpringBoot's simple read-write separation from scratch!

Use VS Code to develop Spring Boot, Zhenni Ma show!

Stupidly indistinguishable Cookie, Session, Token, JWT

Dry goods|SpringBoot integrated Jiguang push complete implementation code (recommended collection)

Zhihu Gaozan: Which one to choose between Pinduoduo and State Grid Offer?

Don't use SELECT *

In-depth comparison of Jackson and Fastjson, in the end I still chose...

Click to read the original text, go to learn SpringCloud actual combat project

Guess you like

Origin blog.csdn.net/qq_17231297/article/details/115327980