美文网首页
Spring Aop

Spring Aop

作者: lclandld | 来源:发表于2021-03-23 10:21 被阅读0次

    因为项目中有使用AOP,所有就整理一个出来,相当于自己也再学习一遍。
    Controller上的一个切面:是否需要记录日志

    项目中的具体需求是,对每个接口发生异常的情况的数据,要详细的记录到数据库中的日志表中,日志表中的字段如下

    • description 接口功能描述
    • actionArgs 方法参数
    • className类名称
    • methodName方法名称
    • ip ip地址
    • modelName 模块名称
    • action操作
    • succeed 是否成功 1:成功 2异常
    • message 异常堆栈信息

    1、pom.xml文件引入

    实际项目中是多模块的,所有将关于aop的引入,放到到对应的模块中

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

    1、pom.xml文件引入

    <?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.4.3</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>demo1</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>demo1</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>1.8</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
           <!-- 要用Aop必须加上这个依赖包-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    

    2、写一个Controller

    @RestController
    @RequestMapping("/course")
    public class TestController {
        @GetMapping("/findById")
        public String findById() {
            return "调用接口成功";
        }
    }
    
    

    运行起来之后,浏览器调用http://localhost:8080/course/findById能打印出“调用接口成功”即可

    3、定义一个日志annotation

    
    /**
     *  在Controller方法上加入改注解会自动记录日志
     */
    @Target( { ElementType.METHOD } )
    @Retention( RetentionPolicy.RUNTIME )
    @Documented
    public @interface Log {
        /**
         * 模块名称
         */
        String modelName() default "";
    
        /**
         * 操作
         */
        String action()default "";
        /**
         * 描述.
         */
        String description() default "";
        /**
         * 是否保存返回参数
         */
        boolean saveResult() default false;
    }
    
    

    4、定义一个ControllerAspect

    /**
     * @author lichunlan
     * @description 控制器的切面:记录log
     * @since 2021-03-22
     */
    @Aspect
    @Configuration
    public class ControllerAspect {
        /**
         * 连接点(Joinpoint) 程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。简单来说,就是在哪加入你的逻辑增强
         * 连接点表示具体要拦截的方法,上面切点是定义一个范围,而连接点是具体到某个方法
         */
    
        /**
         * 切点(PointCut) 每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。比如,连接点--数据库的记录,切点--查询条件
         * 切点用于来限定Spring-AOP启动的范围,通常我们采用表达式的方式来设置,所以关键词是范围
         */
        @Pointcut("execution(* com.example.demo1.controller..*(..))  ")
        public void aspect() {
        }
        @Around(value = "aspect()")
        public Object validationPoint(ProceedingJoinPoint pjp)throws Throwable{
            Method method = currentMethod(pjp,pjp.getSignature().getName());
            if (method != null && method.isAnnotationPresent(Log.class)){
                return new RecordLogAspect().doHandlerAspect(pjp,method);
            }
            return  pjp.proceed(pjp.getArgs());
        }
    
        /**
         * 获取目标类的所有方法,找到当前要执行的方法
         */
        private Method currentMethod (ProceedingJoinPoint joinPoint , String methodName ) {
            Method[] methods      = joinPoint.getTarget().getClass().getMethods();
            Method   resultMethod = null;
            for ( Method method : methods ) {
                if ( method.getName().equals( methodName ) ) {
                    resultMethod = method;
                    break;
                }
            }
            return resultMethod;
        }
    }
    
    

    这里的主要能执行到日志记录的切面方法是RecordLogAspect

      if (method != null && method.isAnnotationPresent(Log.class)){
                return new RecordLogAspect().doHandlerAspect(pjp,method);
        }
    

    5、定义一个RecordLogAspect

    /**
     * @author lichunlan
     * @description 日志处理切面
     * @since 2021-03-22
     */
    public class RecordLogAspect {
        public Object doHandlerAspect(ProceedingJoinPoint pjp, Method method) throws Throwable {
            return execute(pjp, method);
        }
        @Async
        protected Object execute(ProceedingJoinPoint pjp, Method method) throws Throwable {
            try{
                Object result = null;
                result = pjp.proceed(pjp.getArgs());
                return result;
            }catch (Throwable throwable){
                return throwable;
            }finally {
                Log log = method.getAnnotation(Log.class);
                if (log != null) {
                    pjp.getTarget().getClass().getName();
    
                    System.out.println("开始处理切面日志信息 "+log.modelName()+"\n"+log.action()+"\n"+log.description());
                }
            }
        }
    }
    
    
    /**
     * @author lichunlan
     * @description 测试另外一个Controller
     * @since 2021-03-22
     */
    @RestController
    @RequestMapping("/info")
    public class Test2Controller {
        @Log(action = "add", modelName = "Test2Controller", description = "添加用户信息")
        @GetMapping("/add")
        public String add() {
            return "调用添加用户信息接口成功";
        }
    }
    
    

    运行代码并访问http://localhost:8080/info/add 可以看到

    image.png

    至此我的AOP切面模块就能跑起来了,至于对日志做哪些处理就在打印日志这里做详细的逻辑处理


    image.png

    6、在我们项目中的一些其他切面(这个后续还会整理一遍)

    • 限流、拦截xss攻击


      image.png

      可以看到ParamXssPass AccessLimit这两个注解,其实和Log是一样的,所有这里面又用到了一个设计模式,装饰者模式。

    • 对某个service中的方法执行完之后的一切处理


      image.png

    相关文章

      网友评论

          本文标题:Spring Aop

          本文链接:https://www.haomeiwen.com/subject/vuqyjhtx.html