zoukankan      html  css  js  c++  java
  • Spring boot中使用aop详解

    aop是spring的两大功能模块之一,功能非常强大,为解耦提供了非常优秀的解决方案。

    现在就以springboot中aop的使用来了解一下aop。

    一:使用aop来完成全局请求日志处理

    创建一个springboot的web项目,勾选aop,pom如下:

    <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
        <modelVersion>4.0.0</modelVersion>  
      
        <groupId>com.example</groupId>  
        <artifactId>testaop</artifactId>  
        <version>0.0.1-SNAPSHOT</version>  
        <packaging>jar</packaging>  
      
        <name>testaop</name>  
        <description>Demo project for Spring Boot</description>  
      
        <parent>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-parent</artifactId>  
            <version>1.5.3.RELEASE</version>  
            <relativePath/> <!-- lookup parent from repository -->  
        </parent>  
      
        <properties>  
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>  
            <java.version>1.8</java.version>  
        </properties>  
      
        <dependencies>  
            <dependency>  
                <groupId>org.springframework.boot</groupId>  
                <artifactId>spring-boot-starter-aop</artifactId>  
            </dependency>  
            <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>  
        </dependencies>  
      
        <build>  
            <plugins>  
                <plugin>  
                    <groupId>org.springframework.boot</groupId>  
                    <artifactId>spring-boot-maven-plugin</artifactId>  
                </plugin>  
            </plugins>  
        </build>  
    </project>  

    创建个controller

    package com.example.controller;  
      
    import org.springframework.web.bind.annotation.RequestMapping;  
    import org.springframework.web.bind.annotation.RestController;  
      
    /** 
     * Created by wuwf on 17/4/27. 
     * 
     */  
    @RestController  
    public class FirstController {  
      
        @RequestMapping("/first")  
        public Object first() {  
            return "first controller";  
        }  
      
        @RequestMapping("/doError")  
        public Object error() {  
            return 1 / 0;  
        }  
    }  

    创建一个aspect切面类

    package com.example.aop;  
      
    import org.aspectj.lang.JoinPoint;  
    import org.aspectj.lang.ProceedingJoinPoint;  
    import org.aspectj.lang.annotation.*;  
    import org.springframework.stereotype.Component;  
    import org.springframework.web.context.request.RequestContextHolder;  
    import org.springframework.web.context.request.ServletRequestAttributes;  
      
    import javax.servlet.http.HttpServletRequest;  
    import java.util.Arrays;  
      
    /** 
     * Created by wuwf on 17/4/27. 
     * 日志切面 
     */  
    @Aspect  
    @Component  
    public class LogAspect {  
        @Pointcut("execution(public * com.example.controller.*.*(..))")  
        public void webLog(){}  
      
        @Before("webLog()")  
        public void deBefore(JoinPoint joinPoint) throws Throwable {  
            // 接收到请求,记录请求内容  
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();  
            HttpServletRequest request = attributes.getRequest();  
            // 记录下请求内容  
            System.out.println("URL : " + request.getRequestURL().toString());  
            System.out.println("HTTP_METHOD : " + request.getMethod());  
            System.out.println("IP : " + request.getRemoteAddr());  
            System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());  
            System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));  
      
        }  
      
        @AfterReturning(returning = "ret", pointcut = "webLog()")  
        public void doAfterReturning(Object ret) throws Throwable {  
            // 处理完请求,返回内容  
            System.out.println("方法的返回值 : " + ret);  
        }  
      
        //后置异常通知  
        @AfterThrowing("webLog()")  
        public void throwss(JoinPoint jp){  
            System.out.println("方法异常时执行.....");  
        }  
      
        //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行  
        @After("webLog()")  
        public void after(JoinPoint jp){  
            System.out.println("方法最后执行.....");  
        }  
      
        //环绕通知,环绕增强,相当于MethodInterceptor  
        @Around("webLog()")  
        public Object arround(ProceedingJoinPoint pjp) {  
            System.out.println("方法环绕start.....");  
            try {  
                Object o =  pjp.proceed();  
                System.out.println("方法环绕proceed,结果是 :" + o);  
                return o;  
            } catch (Throwable e) {  
                e.printStackTrace();  
                return null;  
            }  
        }  
    }  

    启动项目

    模拟正常执行的情况,访问http://localhost:8080/first,看控制台结果:

    URL : http://localhost:8080/first
    HTTP_METHOD : GET
    IP : 0:0:0:0:0:0:0:1
    CLASS_METHOD : com.example.controller.FirstController.first
    ARGS : []
    方法环绕proceed,结果是 :first controller
    方法最后执行.....
    方法的返回值 : first controller

    /****************************分割线****************************/

    模拟出现异常时的情况,访问http://localhost:8080/doError,看控制台结果:
    方法环绕start.....
    URL : http://localhost:8080/doError
    HTTP_METHOD : GET
    IP : 0:0:0:0:0:0:0:1
    CLASS_METHOD : com.example.controller.FirstController.error
    ARGS : []
    java.lang.ArithmeticException: / by zero

    ......

    方法最后执行.....
    方法的返回值 : null

    /****************************分割线****************************/

    通过上面的简单的例子,可以看到aop的执行顺序。知道了顺序后,就可以在相应的位置做切面处理了。

    二: 切面方法说明

    @Aspect

    作用是把当前类标识为一个切面供容器读取

    @Before
    标识一个前置增强方法,相当于BeforeAdvice的功能

    @AfterReturning

    后置增强,相当于AfterReturningAdvice,方法退出时执行

    @AfterThrowing

    异常抛出增强,相当于ThrowsAdvice

    @After

    final增强,不管是抛出异常或者正常退出都会执行

    @Around

    环绕增强,相当于MethodInterceptor

    /****************************分割线****************************/

    各方法参数说明:

    除了@Around外,每个方法里都可以加或者不加参数JoinPoint,如果有用JoinPoint的地方就加,不加也可以,JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。@Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。@AfterReturning方法里,可以加returning = “XXX”,XXX即为在controller里方法的返回值,本例中的返回值是“first controller”。@AfterThrowing方法里,可以加throwing = "XXX",供读取异常信息,如本例中可以改为:

        //后置异常通知  
        @AfterThrowing(throwing = "ex", pointcut = "webLog()")  
        public void throwss(JoinPoint jp, Exception ex){  
            System.out.println("方法异常时执行.....");  
        }  

    一般常用的有before和afterReturn组合,或者单独使用Around,即可获取方法开始前和结束后的切面。

    三:关于切面PointCut的切入点

    execution切点函数

    execution函数用于匹配方法执行的连接点,语法为:

    execution(方法修饰符(可选)  返回类型  方法名  参数  异常模式(可选)) 

    参数部分允许使用通配符:

    *  匹配任意字符,但只能匹配一个元素

    .. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用

    +  必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类

    参考:http://blog.csdn.net/autfish/article/details/51184405

    除了execution(),Spring中还支持其他多个函数,这里列出名称和简单介绍,以方便根据需要进行更详细的查询

     @annotation()

    表示标注了指定注解的目标类方法

    例如 @annotation(org.springframework.transaction.annotation.Transactional) 表示标注了@Transactional的方法

    args()

    通过目标类方法的参数类型指定切点

    例如 args(String) 表示有且仅有一个String型参数的方法

    @args()

    通过目标类参数的对象类型是否标注了指定注解指定切点

    如 @args(org.springframework.stereotype.Service) 表示有且仅有一个标注了@Service的类参数的方法

    within()

    通过类名指定切点

    如 with(examples.chap03.Horseman) 表示Horseman的所有方法

    target()

    通过类名指定,同时包含所有子类

    如 target(examples.chap03.Horseman)  且Elephantman extends Horseman,则两个类的所有方法都匹配

    @within()

    匹配标注了指定注解的类及其所有子类

    如 @within(org.springframework.stereotype.Service) 给Horseman加上@Service标注,则Horseman和Elephantman 的所有方法都匹配

    @target()

    所有标注了指定注解的类

    如 @target(org.springframework.stereotype.Service) 表示所有标注了@Service的类的所有方法

     this()

    大部分时候和target()相同,区别是this是在运行时生成代理类后,才判断代理类与指定的对象类型是否匹配

    /****************************分割线****************************/

     

    逻辑运算符

    表达式可由多个切点函数通过逻辑运算组成

     &&

    与操作,求交集,也可以写成and

    例如 execution(* chop(..)) && target(Horseman)  表示Horseman及其子类的chop方法

     ||

    或操作,求并集,也可以写成or

    例如 execution(* chop(..)) || args(String)  表示名称为chop的方法或者有一个String型参数的方法

    !

    非操作,求反集,也可以写成not

    例如 execution(* chop(..)) and !args(String)  表示名称为chop的方法但是不能是只有一个String型参数的方法

    execution常用于匹配特定的方法,如update时怎么处理,或者匹配某些类,如所有的controller类,是一种范围较大的切面方式,多用于日志或者事务处理等。

    其他的几个用法各有千秋,视情况而选择。

    以上标红的比较常用。下面来看annotation的。

    四:自定义注解

    一般多用于某些特定的功能,比较零散的切面,譬如特定的某些方法需要处理,就可以单独在方法上加注解切面。

    我们来自定义一个注解:

    package com.example.aop;  
      
    import java.lang.annotation.ElementType;  
    import java.lang.annotation.Retention;  
    import java.lang.annotation.RetentionPolicy;  
    import java.lang.annotation.Target;  
      
    /** 
     * Created by wuwf on 17/4/27. 
     */  
    @Target({ElementType.METHOD, ElementType.TYPE})  
    @Retention(RetentionPolicy.RUNTIME)  
    public @interface UserAccess {  
        String desc() default "无信息";  
    }  

    注解里提供了一个desc的方法,供被切面的地方传参,如果不需要传参可以不写。

    在Controller里加个方法

        @RequestMapping("/second")  
        @UserAccess(desc = "second")  
        public Object second() {  
            return "second controller";  
        }  

    切面类:

    package com.example.aop;  
      
    import org.aspectj.lang.JoinPoint;  
    import org.aspectj.lang.ProceedingJoinPoint;  
    import org.aspectj.lang.annotation.*;  
    import org.springframework.stereotype.Component;  
      
    /** 
     * Created by wuwf on 17/4/27. 
     */  
    @Component  
    @Aspect  
    public class UserAccessAspect {  
      
        @Pointcut(value = "@annotation(com.example.aop.UserAccess)")  
        public void access() {  
      
        }  
      
        @Before("access()")  
        public void deBefore(JoinPoint joinPoint) throws Throwable {  
            System.out.println("second before");  
        }  
      
        @Around("@annotation(userAccess)")  
        public Object around(ProceedingJoinPoint pjp, UserAccess userAccess) {  
            //获取注解里的值  
            System.out.println("second around:" + userAccess.desc());  
            try {  
                return pjp.proceed();  
            } catch (Throwable throwable) {  
                throwable.printStackTrace();  
                return null;  
            }  
        }  
    }  

    主要看一下@Around注解这里,如果需要获取在controller注解中赋给UserAccess的desc里的值,就需要这种写法,这样UserAccess参数就有值了。

    /****************************分割线****************************/

    启动项目,访问http://localhost:8080/second,看控制台:

    方法环绕start.....
    URL : http://localhost:8080/second
    HTTP_METHOD : GET
    IP : 0:0:0:0:0:0:0:1
    CLASS_METHOD : com.example.controller.FirstController.second
    ARGS : []
    second around:second
    second before
    方法环绕proceed,结果是 :second controller
    方法最后执行.....
    方法的返回值 : second controller

    /****************************分割线****************************/

    通知结果可以看到,两个aop切面类都工作了,顺序呢就是下面的

    spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。
    对于上面的例子就是,先外层的就是对所有controller的切面,内层就是自定义注解的。
    那不同的切面,顺序怎么决定呢,尤其是同格式的切面处理,譬如两个execution的情况,那spring就是随机决定哪个在外哪个在内了。
    所以大部分情况下,我们需要指定顺序,最简单的方式就是在Aspect切面类上加上@Order(1)注解即可,order越小最先执行,也就是位于最外层。像一些全局处理的就可以把order设小一点,具体到某个细节的就设大一点。

  • 相关阅读:
    【POJ 3162】 Walking Race (树形DP-求树上最长路径问题,+单调队列)
    【POJ 2152】 Fire (树形DP)
    【POJ 1741】 Tree (树的点分治)
    【POJ 2486】 Apple Tree (树形DP)
    【HDU 3810】 Magina (01背包,优先队列优化,并查集)
    【SGU 390】Tickets (数位DP)
    【SPOJ 2319】 BIGSEQ
    【SPOJ 1182】 SORTBIT
    【HDU 5456】 Matches Puzzle Game (数位DP)
    【HDU 3652】 B-number (数位DP)
  • 原文地址:https://www.cnblogs.com/zhujiabin/p/9844585.html
Copyright © 2011-2022 走看看