• springboot-aop


    AOP(面向切面编程)是Spring的两大核心功能之一,功能非常强大,为解耦提供了非常优秀的解决方案。

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

    写几个简单的Spring RESTful服务接口方法,实现方法前面或后面打印日志。

    AOP术语定义

    Spring的AOP中有几个重要概念搞清楚就行

    • 执行点(Executepoint) - 类初始化,方法调用。
    • 连接点(Joinpoint) - 执行点+方位的组合,可确定Joinpoint,比如类开始初始化前,类初始化后,方法调用前,方法调用后。
    • 切点(Pointcut) - 在众多执行点中,定位感兴趣的执行点。Executepoint相当于数据库表中的记录,而Pointcut相当于查询条件。
    • 增强(Advice) - 织入到目标类连接点上的一段程序代码。除了一段程序代码外,还拥有执行点的方位信息。
    • 目标对象(Target) - 增强逻辑的织入目标类
    • 引介(Introduction) - 一种特殊的增强(advice),它为类添加一些额外的属性和方法,动态为业务类添加其他接口的实现逻辑,让业务类成为这个接口的实现类。
    • 代理(Proxy) - 一个类被AOP织入后,产生一个结果类,它便是融合了原类和增强逻辑的代理类。
    • 切面(Aspect) - 切面由切点(Pointcut)和增强(Advice/Introduction)组成,既包括横切逻辑定义,也包括连接点定义。

    AOP工作重点:

    1. 如何通过切点(Pointcut)和增强(Advice)定位到连接点(Jointpoint)上;
    2. 如何在增强(Advice)中编写切面的代码。

    添加maven依赖

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     4     <modelVersion>4.0.0</modelVersion>
     5     <parent>
     6         <groupId>org.springframework.boot</groupId>
     7         <artifactId>spring-boot-starter-parent</artifactId>
     8         <version>2.1.3.RELEASE</version>
     9         <relativePath/> <!-- lookup parent from repository -->
    10     </parent>
    11     <groupId>com.code</groupId>
    12     <artifactId>springboot-aop</artifactId>
    13     <version>0.0.1-SNAPSHOT</version>
    14     <name>springboot-aop</name>
    15     <description>springboot-aop</description>
    16 
    17     <properties>
    18         <java.version>1.8</java.version>
    19     </properties>
    20 
    21     <dependencies>
    22         <dependency>
    23             <groupId>org.springframework.boot</groupId>
    24             <artifactId>spring-boot-starter</artifactId>
    25         </dependency>
    26 
    27         <dependency>
    28             <groupId>org.springframework.boot</groupId>
    29             <artifactId>spring-boot-starter-test</artifactId>
    30             <scope>test</scope>
    31         </dependency>
    32         <dependency>
    33             <groupId>org.springframework.boot</groupId>
    34             <artifactId>spring-boot-starter-web</artifactId>
    35             <exclusions>
    36                 <exclusion>
    37                     <groupId>org.springframework.boot</groupId>
    38                     <artifactId>spring-boot-starter-tomcat</artifactId>
    39                 </exclusion>
    40             </exclusions>
    41         </dependency>
    42         <dependency>
    43             <groupId>org.springframework.boot</groupId>
    44             <artifactId>spring-boot-starter-jetty</artifactId>
    45         </dependency>
    46         <dependency>
    47             <groupId>org.springframework.boot</groupId>
    48             <artifactId>spring-boot-starter-aop</artifactId>
    49         </dependency>
    50     </dependencies>
    51 
    52     <build>
    53         <plugins>
    54             <plugin>
    55                 <groupId>org.springframework.boot</groupId>
    56                 <artifactId>spring-boot-maven-plugin</artifactId>
    57             </plugin>
    58         </plugins>
    59     </build>
    60 
    61 </project>

    配置application.yml 

    server:
      port: 8092

    创建Controller

    没有结果返回的示例:

    @RestController
    public class UserController {
        @RequestMapping("/first")
        public Object first() {
            return "first controller";
        }
    
        @RequestMapping("/doError")
        public Object error() {
            return 1 / 0;
        }
    }

    创建切面类

     1 /**
     2  * 日志切面
     3  */
     4 @Aspect
     5 @Component
     6 public class LogAspect {
     7 
     8     @Pointcut("execution(public * com.code.aop.controller.*.*(..))")
     9     public void webLog(){}
    10 
    11     @Before("webLog()")
    12     public void deBefore(JoinPoint joinPoint) throws Throwable {
    13         // 接收到请求,记录请求内容
    14         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    15         HttpServletRequest request = attributes.getRequest();
    16         // 记录下请求内容
    17         System.out.println("URL : " + request.getRequestURL().toString());
    18         System.out.println("HTTP_METHOD : " + request.getMethod());
    19         System.out.println("IP : " + request.getRemoteAddr());
    20         System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
    21         System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
    22 
    23     }
    24 
    25     @AfterReturning(returning = "ret", pointcut = "webLog()")
    26     public void doAfterReturning(Object ret) throws Throwable {
    27         // 处理完请求,返回内容
    28         System.out.println("方法的返回值 : " + ret);
    29     }
    30 
    31     //后置异常通知
    32     @AfterThrowing("webLog()")
    33     public void throwss(JoinPoint jp){
    34         System.out.println("方法异常时执行.....");
    35     }
    36 
    37     //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
    38     @After("webLog()")
    39     public void after(JoinPoint jp){
    40         System.out.println("方法最后执行.....");
    41     }
    42 
    43     //环绕通知,环绕增强,相当于MethodInterceptor
    44     @Around("webLog()")
    45     public Object arround(ProceedingJoinPoint pjp) {
    46         System.out.println("方法环绕start.....");
    47         try {
    48             Object o =  pjp.proceed();
    49             System.out.println("方法环绕proceed,结果是 :" + o);
    50             return o;
    51         } catch (Throwable e) {
    52             e.printStackTrace();
    53             return null;
    54         }
    55     }
    56 }

    测试效果

    启动项目,执行如下mvn命令:

    mvn spring-boot:run

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

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

    模拟出现异常时的情况,访问http://localhost:8092/doError,看控制台结果:

    java.lang.ArithmeticException: / by zero
        at com.code.aop.controller.UserController.error(UserController.java:17)
        at com.code.aop.controller.UserController$$FastClassBySpringCGLIB$$db286cd9.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:56)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
        at com.code.aop.aspect.LogAspect.arround(LogAspect.java:60)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
        at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
        at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:47)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:55)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
        at com.code.aop.controller.UserController$$EnhancerBySpringCGLIB$$b85974ee.error(<generated>)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:645)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)
        at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:867)
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1623)
        at org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:214)
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
        at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
        at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:540)
        at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:146)
        at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548)
        at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
        at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:257)
        at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1588)
        at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:255)
        at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1345)
        at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:203)
        at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:480)
        at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1557)
        at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:201)
        at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1247)
        at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144)
        at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
        at org.eclipse.jetty.server.Server.handle(Server.java:502)
        at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:364)
        at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260)
        at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:305)
        at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
        at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
        at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:765)
        at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:683)
        at java.lang.Thread.run(Thread.java:748)
    方法环绕start.....
    URL : http://localhost:8092/doError
    HTTP_METHOD : GET
    IP : 0:0:0:0:0:0:0:1
    CLASS_METHOD : com.code.aop.controller.UserController.error
    ARGS : []
    方法最后执行.....
    方法的返回值 : null

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

    切面注解说明

    • @Aspect 作用是把当前类标识为一个切面供容器读取
    • @Pointcut 定义切点,切点方法不用任何代码,返回值是void,重要的是条件表达式
    • @Before 标识一个前置增强方法,相当于BeforeAdvice的功能
    • @AfterReturning 后置增强,相当于AfterReturningAdvice,方法退出时执行
    • @AfterThrowing 异常抛出增强,相当于ThrowsAdvice
    • @After final增强,不管是抛出异常或者正常退出都会执行
    • @Around 环绕增强,相当于MethodInterceptor

    方法参数说明:

    除了@Around外,每个方法里都可以加或者不加参数JoinPoint。

    JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。

    @Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。

    @AfterReturning方法里,可以加returning = “xxx”,xxx即为在controller里方法的返回值,本例中的返回值是“first controller”。

    @AfterThrowing方法里,可以加throwing = “XXX”,读取异常信息,如本例中可以改为:

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

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

    关于切点PointCut

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

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

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

      • 匹配任意字符,但只能匹配一个元素
    1. .. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用
      • 必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类

    除了execution(),Spring中还支持其他多个函数,这里列出名称和简单介绍

    1. @annotation() 表示标注了指定注解的目标类方法
    2. args() 通过目标类方法的参数类型指定切点
    3. @args() 通过目标类参数的对象类型是否标注了指定注解指定切点
    4. within() 通过类名指定,它将匹配指定类型
    5. target() 通过类名指定,同时包含所有子类
    6. @within() 匹配标注了指定注解的类及其子类,必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
    7. @target() 匹配标注了指定注解的类,必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

    逻辑运算符

    表达式可由多个切点函数通过逻辑运算组成,与(&&)、 或(||)、 非(!)

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

    自定义注解

    一般多用于某些特定的功能,我们来自定义一个注解:

    1 @Target({ElementType.METHOD, ElementType.TYPE})
    2 @Retention(RetentionPolicy.RUNTIME)
    3 public @interface UserAccess {
    4 
    5     String desc() default "无信息";
    6 
    7 }

    在Controller里加个方法:

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

    创建一个新的切面类:

     1 @Component
     2 @Aspect
     3 public class UserAccessAspect {
     4 
     5     @Pointcut(value = "@annotation(com.code.aop.annotation.UserAccess)")
     6     public void access() {
     7 
     8     }
     9 
    10     @Before("access()")
    11     public void deBefore(JoinPoint joinPoint) throws Throwable {
    12         System.out.println("second before");
    13     }
    14 
    15     @Around("@annotation(userAccess)")
    16     public Object around(ProceedingJoinPoint pjp, UserAccess userAccess) {
    17         //获取注解里的值
    18         System.out.println("second around:" + userAccess.desc());
    19         try {
    20             return pjp.proceed();
    21         } catch (Throwable throwable) {
    22             throwable.printStackTrace();
    23             return null;
    24         }
    25     }
    26 }

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

    执行:mvn spring-boot:run

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

    方法环绕start.....
    URL : http://localhost:8092/second
    HTTP_METHOD : GET
    IP : 0:0:0:0:0:0:0:1
    CLASS_METHOD : com.code.aop.controller.UserController.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设小一点,具体到某个细节的就设大一点。

  • 相关阅读:
    vim 命令详解
    Helloworld with c
    VS扩展开发框架
    Putty是一个专业的SSH连接客户端
    你的Java代码对JIT编译友好么?(转)
    java nio的一个严重BUG(转)
    JVM可支持的最大线程数(转)
    Java下获取可用CPU数
    java观察者模式(转)
    Android开发之控制Toast的开启与关闭
  • 原文地址:https://www.cnblogs.com/UniqueColor/p/10577072.html
Copyright © 2020-2023  润新知