Spring AOP practice, use of custom annotations

The interview said that AOP will say that the function is to record logs for each layer. But never really practiced it.

Scenario : In the written business, each function, especially the Controller and Dao layer, wants to record the parameters of each function in the log, so as to facilitate troubleshooting of production problems later.

Solution : Use AOP technology. The specific practice is as follows:

File directory structure:

 Step 1: Write Annotations

Remember to introduce the dependency of aspect, which is posted at the bottom of the article.

package com.example.demo.anno;

import java.lang.annotation.*;

/*创建日志注解*/
@Retention(RetentionPolicy.RUNTIME) //指定注解的生命周期 在运行时
@Target(ElementType.METHOD)  // 指定注解只作用在 方法(函数)上
@Documented
@Inherited //父类写了该注解 则子类默认继承的话
public @interface Logs {   //@interface 注解的格式
    String action() default ""; //注解 可以接受的参数。
}

Step 2: Write the logic of the pointcut

package com.example.demo.anno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/*切入每个函数的的入口且有@log注解标记的函数 调用该部分:功能打印函数参数*/
@Aspect
@Component
public class AOPOfLog {

    @Around(value = "within(com.example.demo.anno..*) && @annotation(logs)")
    public Object doLogs(ProceedingJoinPoint joinPoint,Logs logs) throws Throwable {

        System.out.println("打印注解的参数:"+logs.action());

        Object[] args = joinPoint.getArgs();//函数有几个参数,agrs这个数组就会有几个元素

        System.out.println("打印切入点的函数,传入的参数是:");
        for (int i = 0; i < args.length; i++) {
            //如果是字符串或者数字则直接打印,否则打印 对象.toStirng()
            if(args[i] instanceof String || args[i] instanceof Integer) {
                System.out.print(" " + args[i]);
            }else {
                System.out.print(" "+args[i].toString());
            }
        }

        //调用切入点 的函数  即:放行拦截
        Object obj = joinPoint.proceed();
        return obj; //如果返回void 则相当于没放行

    }

}

Step 3: Use Annotations

package com.example.demo.anno;


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/")
@ResponseBody
public class TestController {

    // post请求测试 http://localhost:8080/test1?parm1=1&parm2=2
    @Logs(action = "我是test1的注解")
    @RequestMapping("/test1")
    public  Object test1(String parm1,String parm2){
        return "成功了";
    }

    //post请求测试 http://localhost:8080/test2?name=王建伟&age=18
    @Logs(action = "我是test2的注解")
    @RequestMapping("/test2")
    public  Object  test2(User user){
        return "成功了";
    }
}

class  User{
    private String name;
    private int age;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Phenomenon result: All functions marked with @Logs in com.example.demo.anno under the access package will print the parameter values ​​passed into the function.

front end:

Background printing:

 Summary: AOP means that you can "insert a certain code segment" before, after, and during the execution of each function, which is often said in interviews, extracting the common parts of each same business for unified processing, which not only improves development efficiency , and also improve the maintainability of the code in the future (implement one modification and global modification)

The complete pom dependencies of this project are as follows:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.mybatis</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>



    <repositories>
        <repository>
            <id>com.e-iceblue</id>
            <name>e-iceblue</name>
            <url>http://repo.e-iceblue.com/nexus/content/groups/public/</url>
        </repository>
    </repositories>

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

        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.12</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13</version>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>com.itextpdf</groupId>-->
<!--            <artifactId>itext-asian</artifactId>-->
<!--            <version>5.2.0</version>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.7.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>1.9.5</version>
        </dependency>

        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.0</version>
        </dependency>

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext7-core</artifactId>
            <version>7.1.12</version>
            <type>pom</type>
        </dependency>

        <dependency>
            <groupId> e-iceblue </groupId>
            <artifactId>spire.pdf</artifactId>
            <version>3.11.6</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>


            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>

            </plugin>
        </plugins>
    </build>

</project>

Another version of log printing (using: joinPoint.proceed() has a bug. It will be called once more.)

With the help of: swagger annotations, add a title to the method header. Shaped like

Log section:

package com.XXX.product.aspect;

import com.patpat.product.helpers.LogAspectHelper;
import lombok.extern.slf4j.Slf4j;
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.stereotype.Component;
import org.springframework.util.StopWatch;


@Slf4j
@Aspect
@Component
public class LogAspect {

    @Pointcut("execution(public * com.XX.product.controller..*.*(..)) " +
            "&& !execution(public * com.XX.product.controller.XXController.*(..))")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object handle(ProceedingJoinPoint joinPoint) throws Throwable {
        String title = LogAspectHelper.getMethodTitle(joinPoint);
        String param = LogAspectHelper.getMethodParam(joinPoint);
        String paramReturn = LogAspectHelper.getReturnResult(joinPoint);
        log.info("[Controller start], {}, param->{},", title, param);
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        try {
            return joinPoint.proceed();
        } finally {
            stopWatch.stop();
            log.info("[Controller end], {}, elapsed time->{}ms,resultParam->{}", title, stopWatch.getTotalTimeMillis(),paramReturn);
        }
    }

}

//Work done after aspect interception:

package com.XX.product.helpers;

import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.json.JsonMapper;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;


public class LogAspectHelper {

    private LogAspectHelper() {
    }

    public static String getMethodParam(ProceedingJoinPoint joinPoint) {
        try {
            JsonMapper jsonMapper = new JsonMapper();
            return jsonMapper.writeValueAsString(Arrays.stream(joinPoint.getArgs())
                    .filter(param -> !(param instanceof HttpServletRequest)
                            && !(param instanceof HttpServletResponse)
                            && !(param instanceof MultipartFile)
                            && !(param instanceof MultipartFile[])
                    ).collect(Collectors.toList()));

        } catch (Exception e) {
            return "";
        }
    }

    public static String getMethodTitle(ProceedingJoinPoint joinPoint) {
        String title = "";
        try {
            Method[] methods = joinPoint.getSignature().getDeclaringType().getMethods();
            for (Method method : methods) {
                if (StringUtils.equalsIgnoreCase(method.getName(), joinPoint.getSignature().getName())) {
                    ApiOperation annotation = method.getAnnotation(ApiOperation.class);
                    if (ObjectUtils.isNotEmpty(annotation)) {
                        title = annotation.value();
                        break;
                    }
                }
            }
            if (StringUtils.isBlank(title)) {
                title = getMethodName(joinPoint);
            }
        } catch (Exception e) {
            title = "";
        }
        return title;
    }

    private static String getMethodName(ProceedingJoinPoint joinPoint) {
        return joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
    }

    public static String getReturnResult(ProceedingJoinPoint joinPoint) {
        Object proceed = null;
        try {
            proceed = joinPoint.proceed();//会导致 方法多调用一次
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        return JSONUtil.toJsonStr(proceed);
    }
}

How to use:

 @ApiOperation("搜索-list")
    @PostMapping("/list")
    public ResponseDTO<OpenSearchQueryResult> XX(@RequestBody XXXhReq req){
        return openSearchService.XXX(req);
    }

Each call to /list will print:

[Controller start], openSearch搜索-list, param->[{XXX}]
。。。。。。其他业务日志
[Controller end], openSearch搜索-list, elapsed time->818ms,resultParam->{XXX}

You can refer to: (118 messages) The parameters and return values ​​in the springboot project acquisition request_Xiaoxiaobitang's Blog-CSDN Blog

5 annotations of AOP:

Essence: Using the principle of AOP: 5 ways

  1. Execute @Before before the method
  2. Execute @AfterReturning after the method
  3. Method execution before and after @Around
  4. Execute @AfterThrowing when the method execution throws an exception
  5. After the method is executed, it must be executed @After

Among them, @AfterReturning means that the method will be executed after the normal execution, and will not be executed if the method throws an exception

@After is similar to try{}finally{} finally will be executed

The above annotations are all for "cutting facets", so we still need to know how to find the cut facets. Here we find the cut faces similar to regular expressions. Only the matched packages, classes, methods, and permission modifiers are counted as cut points.

How to write pointcut expressions? Use @PointCut("exexcution(XXXX)")

And use the above 5 annotations? The basic template is as follows:

@Aspect
@Component
public class logUtils {
      @Pointcut("execution(public int caculator.Interface.MyCaculator.*(int,int))")
      public void test(){}


    @Before("test()")
    public static void logStart(JoinPoint joinPoint){
        System.out.println("logUtil前置方法【"+joinPoint.getSignature().getName()+"】开始执行" + "调用的参数是:" + Arrays.asList(joinPoint.getArgs()));
    }


    @AfterReturning(value = "test()",returning = "result")
    public void logReturn(JoinPoint joinPoint,Object result){
        System.out.println("logUtil返回方法【"+joinPoint.getSignature().getName()+"】执行完毕" + "返回的结果是:" + result);
    }

    @AfterThrowing(value= "test()",throwing = "exception")
    public void logException(JoinPoint joinPoint,Exception exception){
        System.out.println("logUtil异常方法【"+joinPoint.getSignature().getName()+"】出现异常" + "异常的原因是:" + exception);
    }

    @After("test()")
    public static void logAfter(JoinPoint joinPoint){
          System.out.println("logUtil结束方法【"+joinPoint.getSignature().getName()+"】结束执行");
      }
}      

Guess you like

Origin blog.csdn.net/u013372493/article/details/121340300