美文网首页程序员一些收藏
别说你还不会!Spring5.0源码学习系列之Spring AO

别说你还不会!Spring5.0源码学习系列之Spring AO

作者: 码上入坟 | 来源:发表于2020-11-27 17:02 被阅读0次

    在学习Spring AOP源码之前,您是否对AOP有足够熟悉的理解?在对应用都不熟悉之前就去学习源码,肯定是很难理解的,所以本文先不描述源码的实现,先通过本篇博客了解熟悉Spring AOP,然后再学习源码

    1、什么是AOP技术?

    引用 Spring官网 对AOP技术的概述:

    Aspect-Oriented Programming (AOP) complements Object-Oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns such as transaction management that cut across multiple types and objects. (Such concerns are often termed crosscutting concerns in AOP literature.)
    

    挑重点来说,所谓AOP(Aspect-Oriented Programming)也即面向方面的编程,是指通过跨领域关注点的分离来实现模块化的一种面向对象技术。

    • 跨领域也即跨多种类型和对象的事务管理等等;
    • 关注点通常被称为横切关注点,OOP中模块化的关键单元是类,而在AOP中模块化是方面关注点通常被称为横切关注点

    2、AOP的本质目的

    AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,这个横切逻辑可以是权限校验逻辑、日志监控、事务控制等等

    AOP相关知识详情可以参考: Spring AOP官方文档

    3、AOP的相关术语

    名词 描述
    连接点(Joinpoint) 连接点是一个程序的执行,如方法的执行或异常的处理过程中的一个点
    切入点(Pointcut) 指的是将增强代码织入到业务主线进来之后的连接点
    通知/增强(Advice) Advice可以翻译为通知或者增强,指的是切面类中用于提供增强功能的方法
    目标对象(Target) 指代理的目标对象,即被代理对象
    代理(Proxy) 指一个类被AOP织入增强之后,产生的代理类,即代理对象
    织入(Weaving) 指的是将增强(Advice)应用到目标对象(Target)产生代理对象(Proxy)的过程。ps:AspectJ采用的是编译期织入和类装载期织入,而Spring AOP采用的是动态代理织入
    切面(Aspect) 切面也就是AOP的关注点,也就是说是Advice代码的关注点,切面是对上述概念的一个综合。将这些Advice代码放在一个类中,这个类就是切面类,切面类是跨多个类的关注点的模块化类

    看了前面这些理论,您可能不是很理解,所以引用国外网站的图例进行说明AOP概念,图来自 链接

    image

    综上所述,其实所谓的目的其实只是要锁定在某个切入点(Pointcut)织入(Weaving)特定的增强逻辑(Advice)

    4、Spring AOP和AspectJ

    有了前面对AOP概念的概述之后,我们能够大致理解AOP了,不过本文还是要理理Spring AOP和AspectJ的关系

    image

    Spring官网 给指出了Spring AOP和AspectJ的关系,官网明确指出其立场,表明Spring AOP不会和AspectJ项目竞争哪个项目能提供更成熟的AOP解决方案,Spring AOP和AspectJ是一种互补的关系,Spring AOP无缝结合了IOC和AspectJ,从而使Spring AOP能够符合大部分的AOP需求,这是一种能提供代理对象的模式

    从官网也可以知道了Spring AOP和AspectJ的大致关系,其实这是两种AOP的实现技术,Spring AOP是集成了AspectJ部分功能,同时结合IOC实现的,AspectJ则是一种比较完善成熟的AOP解决方案,接着本文做下简单对比

    • Spring AOP

      • Spring AOP是SpringFramework的组件,属于Springframework的一个比较核心的功能,Spring AOP是结合了AspectJ和IOC实现的,提供了AspectJ的功能
      • Spring AOP 致力于解决的是企业级开发中最普遍的 AOP 需求(方法织入),Spring AOP不会和AspectJ竞争
      • Spring AOP 只能作用于 Spring 容器中的 Bean
      • 性能方面,Spring AOP是在运行时进行动态织入的,所以性能比不上AspectJ
      • Spring AOP使用动态代理的方式进行方法织入,有两种动态代理方法,一种是CGLIB,另外一种是JDK提供的动态代理
        引用 https://www.baeldung.com/spring-aop-vs-aspectj 的图进行说明
        image
    • AspectJ

      • AspectJ来自Eclipse的开源项目,链接: https://www.eclipse.org/aspectj
      • AspectJ是一种比较成熟的AOP解决方案,能够提供比Spring AOP更多的AOP功能
      • AspectJ的方法织入属于静态织入,它的织入时机可以是:compile-time(编译期)、post-compile(编译后)、load-time(JVM类加载器加载时候)
      • AspectJ在编译时进行方法织入,所以性能比Spring AOP好

    ok,前面已经简单列举了Spring AOP和AspectJ的主要不同,现在可以用表格列举出不同点对比,表格参考自 国外网站

    对比 Spring AOP AspectJ
    实现语言 使用存Java语言 使用Java编程语言的扩展实现
    编译过程 无需单独的编译过程 除非设置了LTW,否则需要AspectJ编译器(ajc)
    织入时机 动态代理,在运行时织入 静态织入,在编译过程织入,它的织入时机可以是:compile-time(编译期)、post-compile(编译后)、load-time(JVM类加载器加载时候)
    功能 基本的方法织入 可以编织字段,方法,构造函数,静态初始值设定项,最终类/方法等…
    范围 只能作用于Spring容器管理的bean上 可以作用于所有领域对象上实施
    性能 比AspectJ慢得多 更好的性能(编译时编织比运行时编织要快得多)
    学习 易于学习和应用 比Spring AOP复杂

    补充,AspectJ静态织入时机:

    • compile-time weaving:编译期织入,在编译时候就直接进行方法织入,直接编译出包含织入代码的 .class 文件
    • post-compile weaving:编译后织入,也可以称之为二进制织入,它将Advice织入于编织后现有的类文件和JAR文件
    • Load-time weaving(LTW):指的是在加载类的时候进行织入,与以前的二进制编织完全一样,不同之处在于编织被推迟到类加载器将类文件加载到JVM的过程

    5、Spring中AOP代理选择

    在前面知识,我们知道Spring AOP是使用动态代理技术实现Spring AOP中的代理选择,方法织入实现有两种方法,一种是JDK动态代理,一种是CGLIB

    • 默认情况下,Spring框架会根据被代理对象(Target Object)是否实现接口来选择JDK还是CGLIB。如果被代理对象没有实现任何接口,Spring会选择CGLIB,如果被代理对象有实现接口,Spring会选择JDK提供的动态代理。
    • ps:虽然默认情况是这样的,不过我们可以通过配置的方式来自定义选择动态代理方式

    6、实验环境准备参考

    学习了前面的理论知识之后,现在可以通过例子进行实践,实践之前,您需要如下的环境准备,实验环境参考:

    • SpringFramework版本
      • Springframework5.0.x
    • 开发环境
      • JAR管理:gradle 4.9/ Maven3.+
      • 开发IDE:IntelliJ IDEA 2018.2.5
      • JDK:jdk1.8.0_31
      • Git Server:Git fro window 2.8.3
      • Git Client:SmartGit18.1.5(可选)

    7、Spring AOP实现方式

    在Spring AOP中,主要提供了三种配置方式:

    • Spring1.2 基于接口的配置:Spring最早的AOP实现是基于Spring提供的AOP接口实现的,通过实现接口,进行Advice逻辑代码编写等等
    • Spring2.0+ schema-based 配置 :Spring2.0之后,提供了 schema-based 配置,也就是xml类型的配置,使用命名空间 <aop>
    • Spring2.0+ @Aspect配置:Spring2.0之后,也提供了 @Aspect 这种方法, @Aspect 是用AspectJ的jar,但是实现是Spring AOP自己实现的

    8、Spring AOP例子参考

    前面介绍了Spring AOP实现的三种方式,接着本文通过代码例子进行验证:

    maven配置

    <properties>
        <springframework.version>5.0.19.RELEASE</springframework.version>
        <aspectj.version>1.9.4</aspectj.version>
    </properties>
    
    <dependencies>
        <!-- Spring aop配置-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <!-- 本文的测试类需要 ioc配置-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <!-- @Aspect才需要加上-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
    
    
    </dependencies>
    

    8.1、Spring1.2 基于接口的配置

    8.1.1、基础类编写

    package com.example.spring.aop.bean;
    
    public class User {
        private String username;
        private String password;
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }
    

    UserService .java:

    package com.example.spring.aop.service;
    
    import com.example.spring.aop.bean.User;
    
    /**
     * <pre>
     *      UserService
     * </pre>
     *
     * <pre>
     * @author mazq
     * 修改记录
     *    修改后版本:     修改人:  修改日期: 2020/11/20 18:02  修改内容:
     * </pre>
     */
    public interface UserService {
    
        User addUser(User user);
    
        User getUser();
    }
    

    UserServiceImpl .java

    package com.example.spring.aop.service.impl;
    
    import com.example.spring.aop.bean.User;
    import com.example.spring.aop.service.UserService;
    import org.springframework.beans.BeanUtils;
    import org.springframework.stereotype.Service;
    
    /**
     * <pre>
     *      UserServiceImpl 
     * </pre>
     *
     * <pre>
     * @author mazq
     * 修改记录
     *    修改后版本:     修改人:  修改日期: 2020/11/20 17:57  修改内容:
     * </pre>
     */
    @Service
    public class UserServiceImpl implements UserService {
    
        private static User user = null;
    
        @Override
        public User addUser(User userDto) {
            user = new User();
            BeanUtils.copyProperties(userDto,user);
            return user;
        }
    
        @Override
        public User getUser() {
            return user;
        }
    }
    

    8.1.2、使用Advice接口

    LogMethodBeforeAdvice .java

    package com.example.spring.aop.core.advice;
    
    import org.springframework.aop.MethodBeforeAdvice;
    
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    /**
     * <pre>
     *      LogMethodBeforeAdvice
     * </pre>
     *
     * <pre>
     * @author mazq
     * 修改记录
     *    修改后版本:     修改人:  修改日期: 2020/11/20 17:38  修改内容:
     * </pre>
     */
    public class LogMethodBeforeAdvice implements MethodBeforeAdvice {
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println(String.format("执行方法:%s,参数列表:%s", method.getName(), Arrays.toString(args) ));
        }
    }
    

    LogAfterReturningAdvice .java

    package com.example.spring.aop.core.advice;
    
    import org.springframework.aop.AfterReturningAdvice;
    
    import java.lang.reflect.Method;
    
    /**
     * <pre>
     *      LogAfterReturningAdvice
     * </pre>
     *
     * <pre>
     * @author mazq
     * 修改记录
     *    修改后版本:     修改人:  修改日期: 2020/11/20 17:41  修改内容:
     * </pre>
     */
    public class LogAfterReturningAdvice implements AfterReturningAdvice {
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
            System.out.println(String.format("方法返回:%s", returnValue ));
        }
    }
    

    spring_interfaces_config.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- 具体业务实现类(target Object)-->
        <bean id="userServiceTarget" class="com.example.spring.aop.service.impl.UserServiceImpl"></bean>
    
        <!-- 实现MethodBeforeAdvice-->
        <bean id="logMethodBeforeAdvice" class="com.example.spring.aop.core.advice.LogMethodBeforeAdvice"></bean>
        <!-- 实现AfterReturningAdvice-->
        <bean id = "logAfterReturningAdvice" class="com.example.spring.aop.core.advice.LogAfterReturningAdvice"></bean>
    
        <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
            <!-- 配置代理接口 proxy-->
            <property name="proxyInterfaces">
                <list>
                    <value>com.example.spring.aop.service.UserService</value>
                </list>
            </property>
            <!-- 配置目标对象,也就是被代理类,具体业务实现类-->
            <property name="target" ref="userServiceTarget"></property>
            <!-- 配置拦截器,可以配置advice、advisor、interceptor -->
            <property name="interceptorNames">
                <list>
                    <value>logMethodBeforeAdvice</value>
                    <value>logAfterReturningAdvice</value>
                </list>
            </property>
        </bean>
    
    </beans>
    

    TestApplication.java:

    package com.example.spring.aop;
    
    
    import com.example.spring.aop.bean.User;
    import com.example.spring.aop.service.UserService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class TestApplication {
    
        public static void testAopProxy() {
            ApplicationContext ioc = new ClassPathXmlApplicationContext("classpath:spring_interfaces_config.xml");
            UserService userService = (UserService) ioc.getBean("userServiceProxy");
            User userDto = new User();
            userDto.setUsername("tom");
            userDto.setPassword("11");
            userService.addUser(userDto);
            System.out.println(String.format("用户数据打印:%s",userService.getUser().toString()));
        }
    
        public static void main(String[] args) {
            testAopProxy();
        }
    }
    

    执行方法:addUser,参数列表:[User{username='tom', password='11'}]

    方法返回:User{username='tom', password='11'}

    执行方法:getUser,参数列表:[]

    方法返回:User{username='tom', password='11'}

    用户数据打印:User{username='tom', password='11'}

    8.1.3、使用Advisor接口

    • NameMatchMethodPointcutAdvisor使用

      定义一个只会拦截查询方法的Advisor,修改配置:

    <!-- 定义一个只会拦截查询方法的Advisor -->
    <bean id="logOnlyObtainQueryAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <!-- 定义Advice类 -->
        <property name="advice" ref="logMethodBeforeAdvice"></property>
        <!-- 只有查询方法才会被拦截 -->
        <property name="mappedNames" value="getUser"></property>
    </bean>
    
    <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 配置代理接口 proxy-->
        <property name="proxyInterfaces">
            <list>
                <value>com.example.spring.aop.service.UserService</value>
            </list>
        </property>
        <!-- 配置目标对象,也就是被代理类,具体业务实现类-->
        <property name="target" ref="userServiceTarget"></property>
        <!-- 配置拦截器,可以配置advice、advisor、interceptor -->
        <property name="interceptorNames">
            <list>
                <value>logOnlyObtainQueryAdvisor</value>
            </list>
        </property>
    </bean>
    

    执行方法:getUser,参数列表:[]

    用户数据打印:User{username='tom', password='11'}

    • RegexpMethodPointcutAdvisor使用

      前面介绍了 NameMatchMethodPointcutAdvisor ,不过不够通用,所以Spring aop还提供了 RegexpMethodPointcutAdvisor ,可以支持正则表达式

    <!-- 定义支持正则匹配的Advisor,只拦截查询方法-->
     <bean id="regexpMethodAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
         <property name="advice" ref="logMethodBeforeAdvice"></property>
         <property name="pattern" value="com.example.spring.aop.*.service.*.get.*"></property>
     </bean>
    
    <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
       <!-- 配置代理接口 proxy-->
       <property name="proxyInterfaces">
           <list>
               <value>com.example.spring.aop.service.UserService</value>
           </list>
       </property>
       <!-- 配置目标对象,也就是被代理类,具体业务实现类-->
       <property name="target" ref="userServiceTarget"></property>
       <!-- 配置拦截器,可以配置advice、advisor、interceptor -->
       <property name="interceptorNames">
           <list>
               <value>regexpMethodAdvisor</value>
           </list>
       </property>
    </bean>
    

    执行方法:getUser,参数列表:[]

    用户数据打印:User{username='tom', password='11'}

    8.1.4、Interceptor接口使用

    TestMethodInterceptor .java:

    package com.example.spring.aop.core.interceptor;
    
    
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    
    /**
     * <pre>
     *      TestMethodInterceptor
     * </pre>
     *
     * <pre>
     * @author mazq
     * 修改记录
     *    修改后版本:     修改人:  修改日期: 2020/11/23 10:28  修改内容:
     * </pre>
     */
    public class TestMethodInterceptor  implements MethodInterceptor {
    
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            System.out.println(String.format("方法调用前(before method invoke) :%s",methodInvocation));
            Object implObj = methodInvocation.proceed();
            System.out.println(String.format("方法调用后(after method invoke) :%s",implObj));
            return implObj;
        }
    }
    

    修改配置文件:

    <!-- 定义MethodInterceptor -->
    <bean id="methodInterceptor" class="com.example.spring.aop.core.interceptor.TestMethodInterceptor"></bean>
    
    <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
       <!-- 配置代理接口 proxy-->
        <property name="proxyInterfaces">
            <list>
                <value>com.example.spring.aop.service.UserService</value>
            </list>
        </property>
        <!-- 配置目标对象,也就是被代理类,具体业务实现类-->
        <property name="target" ref="userServiceTarget"></property>
        <!-- 配置拦截器,可以配置advice、advisor、interceptor -->
        <property name="interceptorNames">
            <list>
                <value>logMethodBeforeAdvice</value>
                <value>logAfterReturningAdvice</value>
                <value>methodInterceptor</value>
            </list>
        </property>
    </bean>
    

    挑addUser方法的日志信息:

    方法调用前(before method invoke) :ReflectiveMethodInvocation: public abstract com.example.spring.aop.bean.User com.example.spring.aop.service.UserService.addUser(com.example.spring.aop.bean.User); target is of class [com.example.spring.aop.service.impl.UserServiceImpl]

    方法调用后(after method invoke) :User{username='tom', password='11'}

    8.1.5、beanNameAutoProxy使用

    前面介绍了 ProxyFactoryBean 配置对应的业务代理进行调用,不过不够灵活,所以Spring中还提供了 beanNameAutoProxy ,这种方式是自动匹配beanName id,不需要每个业务都配对应的proxy进行代理

    新建一个新的配置文件,spring_beanNameAutoProxy_config.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- 具体业务实现类(target Object)-->
        <bean id="userServiceTarget" class="com.example.spring.aop.service.impl.UserServiceImpl"></bean>
    
        <!-- 实现MethodBeforeAdvice-->
        <bean id="logMethodBeforeAdvice" class="com.example.spring.aop.core.advice.LogMethodBeforeAdvice"></bean>
        <!-- 实现AfterReturningAdvice-->
        <bean id = "logAfterReturningAdvice" class="com.example.spring.aop.core.advice.LogAfterReturningAdvice"></bean>
    
        <!-- 定义MethodInterceptor -->
        <bean id="methodInterceptor" class="com.example.spring.aop.core.interceptor.TestMethodInterceptor"></bean>
    
        <!-- 定义BeanNameAutoProxyCreator -->
        <bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
            <!-- interceptorNames可以配置advice、advisor、interceptor-->
            <property name="interceptorNames">
                <list>
                    <value>logMethodBeforeAdvice</value>
                    <value>logAfterReturningAdvice</value>
                    <value>methodInterceptor</value>
                </list>
            </property>
            <!-- 注意:beanNames这里是拦截对应的实现类bean id,eg:userServiceTarget-->
            <property name="beanNames" value="*ServiceTarget"></property>
        </bean>
    
    
    </beans>
    

    TestApplication.java:

    package com.example.spring.aop;
    
    
    import com.example.spring.aop.bean.User;
    import com.example.spring.aop.service.UserService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class TestApplication {
    
        public static void testBeanNameAutoProxy() {
            ApplicationContext ioc = new ClassPathXmlApplicationContext("classpath:spring_beanNameAutoProxy_config.xml");
            UserService userService = ioc.getBean(UserService.class);
            User userDto = new User();
            userDto.setUsername("tom");
            userDto.setPassword("11");
            userService.addUser(userDto);
            System.out.println(String.format("用户数据打印:%s",userService.getUser().toString()));
        }
    
        public static void main(String[] args) {
            // BeanNameAutoProxyCreator
            testBeanNameAutoProxy();
        }
    }
    

    8.2、Spring2.0+ @Aspect配置

    @Aspect 这种方式是比较常用的,pom需要加上 aspectjweaver 配置,spring aop引用了aspectJ的api,但是实现是spring自己进行实现拓展的

    8.2.1、启用@AspectJ支持

    注解方式,使用 @EnableAspectJAutoProxy 开启

    @Configuration
    @EnableAspectJAutoProxy
    public class AppConfig {
    
    }
    

    xml方式,可以使用 <aop:aspectj-autoproxy/> 开启

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
        <aop:aspectj-autoproxy/>
    </beans>
    

    8.2.2、声明方面

    xml方式:

    <bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
        <!-- configure properties of aspect here as normal -->
    </bean>
    

    java方式,使用注解 @Aspect

    package org.xyz;
    import org.aspectj.lang.annotation.Aspect;
    
    @Aspect
    public class NotVeryUsefulAspect {
    
    }
    

    8.2.3、声明切入点

    切入点(Pointcut)的类型, Spring官网 给出了比较详情的介绍:

    image

    在官网的建议是声明一个通用的 SystemArchitecture

    package com.xyz.someapp;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    
    @Aspect
    public class SystemArchitecture {
    
        /**
         * A join point is in the web layer if the method is defined
         * in a type in the com.xyz.someapp.web package or any sub-package
         * under that.
         */
        @Pointcut("within(com.xyz.someapp.web..*)")
        public void inWebLayer() {}
    
        /**
         * A join point is in the service layer if the method is defined
         * in a type in the com.xyz.someapp.service package or any sub-package
         * under that.
         */
        @Pointcut("within(com.xyz.someapp.service..*)")
        public void inServiceLayer() {}
    
        /**
         * A join point is in the data access layer if the method is defined
         * in a type in the com.xyz.someapp.dao package or any sub-package
         * under that.
         */
        @Pointcut("within(com.xyz.someapp.dao..*)")
        public void inDataAccessLayer() {}
    
        /**
         * A business service is the execution of any method defined on a service
         * interface. This definition assumes that interfaces are placed in the
         * "service" package, and that implementation types are in sub-packages.
         *
         * If you group service interfaces by functional area (for example,
         * in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then
         * the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
         * could be used instead.
         *
         * Alternatively, you can write the expression using the 'bean'
         * PCD, like so "bean(*Service)". (This assumes that you have
         * named your Spring service beans in a consistent fashion.)
         */
        @Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
        public void businessService() {}
    
        /**
         * A data access operation is the execution of any method defined on a
         * dao interface. This definition assumes that interfaces are placed in the
         * "dao" package, and that implementation types are in sub-packages.
         */
        @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
        public void dataAccessOperation() {}
    
    }
    

    因为官网的介绍比较详细,所以本博客只挑部分比较重要的进行介绍:

    • execution

      execution:执行,这是最基本的切入点类型:

    // 匹配UserService里的任何方法
     @Pointcut("execution(* com.example.spring.aop.service.UserService.*(..))")
        public void regexpExecution(){}
    

    ps:第一个 * 表示匹配任何返回值,第二个 * 表示匹配任何方法, (..) 表示匹配任何数量的方法参数

    eg: execution(* *..find*(Long,..)) 用于匹配方法名为find...,而第一个参数是long类型的

    @Pointcut("within(com.example.spring.aop..*) && execution(* *..find*(Long,..))")
        public void regexpExecutionByMethodName(){}
    
    • within

      within:表示服务包中的任何连接点(仅在Spring AOP中执行方法)

    @Pointcut("within(com.example.spring.aop..*)")
    
    • this和target

      前者在Spring AOP创建基于CGLIB的代理时起作用,而后者在创建基于JDK的代理时使用

    @Pointcut("within(com.example.spring.aop..*)")
    
    • this和target

      前者在Spring AOP创建基于CGLIB的代理时起作用,而后者在创建基于JDK的代理时使用

    如下实例代码:

    public class UserServiceImpl implements UserService {
        //...
    }
    

    对于UserService,使用 target ,这种情况是基于CGLIB的代理

    @Pointcut("target(com.example.spring.aop.service.UserService)")
    

    对于UserService,使用 this ,这种情况是基于JDK的代理

    @Pointcut("this(com.example.spring.aop.service.impl.UserServiceImpl)")
    
    • args

      限制匹配点(参数是给定类型的实例)的连接点(使用Spring AOP时方法的执行)

    • @target

      @target 不要和target混淆了,其中类的执行的对象的具有给定类型的注释

    @Pointcut("@target(org.springframework.stereotype.Repository)")
    
    • @args

      @args 其中传递的实际参数的运行时类型具有给定类型的注释

    package com.example.spring.aop.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    // 作用于类
    @Target(ElementType.TYPE)
    public @interface Entity {
    }
    
    @Pointcut("within(com.example.spring.aop..*) && @args(com.example.spring.aop.annotation.Entity)")
        public void argsMethod(){}
    
    • @within

      将匹配限制为具有给定注释的类型内的连接点

    Pointcut("@within(org.springframework.stereotype.Repository)")
    

    等效于:

    @Pointcut("within(@org.springframework.stereotype.Repository *)")
    
    • @annotation

      这个 @annotation 是用于匹配注解的,比较常用,我们可以自己写个注解类:

    package com.example.spring.aop.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * <pre>
     *      EnableLog
     * </pre>
     *
     * <pre>
     * @author mazq
     * 修改记录
     *    修改后版本:     修改人:  修改日期: 2020/11/23 18:27  修改内容:
     * </pre>
     */
    @Retention(RetentionPolicy.RUNTIME)
    // 作用于方法
    @Target(ElementType.METHOD)
    public @interface EnableLog {
    }
    

    然后,在对应方法加上 @EnableLog 的方法都能被拦截到:

    @Pointcut("within(com.example.spring.aop..*) && @annotation(com.example.spring.aop.annotation.EnableLog)")
        public void annotationMethod(){}
    
    • 组合表达式

      组合表达式可以使用&&,||和!进行组合

    @Pointcut("within(com.example.spring.aop..*) && execution(public String com.example.spring.aop.service.UserService.*(..))")
    

    8.2.4、声明Advice类型

    Advice类型, Spring官网 也有比较详细的介绍:

    image.png

    归纳一下通知类型:

    • 前置通知( @Before ):在方法执行之前,使用@Before注释声明
    • 后置通知( @AfterReturning ):当匹配的方法执行正常返回时运行建议。它使用 @AfterReturning 注释声明
    • 异常通知( @AfterThrowing ):程序抛出异常后执行,不抛异常不会调用,使用 @AfterThrowing 注释声明
    • 最后通知( @After ):匹配的方法执行退出,如果是有try...catch,一般是在finally执行完成后,使用 @After 注释声明
    • 环绕通知( @Around ):围绕建议在匹配的方法执行过程中“围绕”运行。它有机会在该方法执行之前和之后进行工作,并确定该方法何时,如何以及什至完全可以执行,使用 @Around注释声明
    package com.example.spring.aop.config;
    
    import com.example.spring.aop.service.UserService;
    import com.example.spring.aop.service.impl.UserServiceImpl;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    import java.text.SimpleDateFormat;
    import java.util.Arrays;
    import java.util.Date;
    
    /**
     * <pre>
     *      SpringAspectJConfiguration
     * </pre>
     *
     * <pre>
     * @author mazq
     * 修改记录
     *    修改后版本:     修改人:  修改日期: 2020/11/24 10:52  修改内容:
     * </pre>
     */
    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = false)
    @Aspect
    public class SpringAspectJConfiguration {
    
        @Bean
        public UserService userService(){
            return new UserServiceImpl();
        }
    
        private ThreadLocal<SimpleDateFormat> simpleDateFormat =new ThreadLocal<SimpleDateFormat>(){
            @Override
            protected SimpleDateFormat initialValue() {
                //return super.initialValue();
                return new SimpleDateFormat("[yyyy-mm-dd hh:mm:ss:SSS]");
            }
        };
    
        //---------------------------------------------------------------------
        // Types of pointcut
        //---------------------------------------------------------------------
    
        @Pointcut("within(com.example.spring.aop..*) && execution(public String com.example.spring.aop.service.UserService.*(..))")
        public void regexpExecution(){}
    
        @Pointcut("within(com.example.spring.aop..*) && execution(* *..find*(Long,..))")
        public void regexpExecutionByMethodName(){}
    
        @Pointcut("within(com.example.spring.aop..*) && target(com.example.spring.aop.service.UserService)")
        public void targetInterface(){}
    
        @Pointcut("within(com.example.spring.aop..*) && @args(com.example.spring.aop.annotation.Entity)")
        public void argsMethod(){}
    
        @Pointcut("within(com.example.spring.aop..*) && @annotation(com.example.spring.aop.annotation.EnableLog)")
        public void annotationMethod(){}
    
        //---------------------------------------------------------------------
        // Types of advice
        //---------------------------------------------------------------------
    
        @Before(value = "regexpExecution()")
        public void beforeAdvice(JoinPoint joinPoint){
            Object[] args = joinPoint.getArgs();
            String methodName = joinPoint.getSignature().getName();
            System.out.println(String.format("前置通知:beforeAdvice,参数是:%s", Arrays.toString(args)));
            System.out.println(simpleDateFormat.get().format(new Date()) + methodName);
        }
    
        @AfterReturning(value = "regexpExecution()", returning = "returnVal")
        public void afterReturningAdvice(Object returnVal){
            System.out.println(String.format("后置通知:afterReturningAdvice,返回参数是:%s", returnVal));
        }
    
        @AfterThrowing(value = "regexpExecution()", throwing = "e")
        public void afterThrowingAdvice(Throwable e) {
            System.out.println(String.format("异常通知:afterThrowingAdvice,异常信息:%s", e));
        }
    
        @After(value = "regexpExecution()")
        public void afterAdvice() {
            System.out.println(String.format("最后通知:afterAdvice"));
        }
    
        @Around(value = "regexpExecution()")
        public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
            Object rtValue = null;
            try {
                System.out.println("aroundAdvice前置通知!");
                // 获取参数
                Object[] args = proceedingJoinPoint.getArgs();
                // 执行切入点方法
                rtValue = proceedingJoinPoint.proceed(args);
    
                System.out.println("aroundAdvice后置通知!");
            } catch (Throwable e) {
                System.out.println("aroundAdvice异常通知!");
                e.printStackTrace();
            } finally {
                System.out.println("aroundAdvice最后通知!");
            }
            return rtValue;
        }
    
    }
    

    aroundAdvice前置通知!

    前置通知:beforeAdvice,参数是:[1]

    [2020-13-24 02:13:48:509]findUserNameById

    aroundAdvice后置通知!

    aroundAdvice最后通知!

    最后通知:afterAdvice

    后置通知:afterReturningAdvice,返回参数是:tom

    8.2.5、例子:实现日志监控

    Service加个方法:

    image image.png

    AopConfiguration.java:

    package com.example.spring.aop.config;
    
    import com.example.spring.aop.core.interceptor.TestMonitoringInterceptor;
    import com.example.spring.aop.service.UserService;
    import com.example.spring.aop.service.impl.UserServiceImpl;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.aop.Advisor;
    import org.springframework.aop.aspectj.AspectJExpressionPointcut;
    import org.springframework.aop.support.DefaultPointcutAdvisor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    /**
     * <pre>
     *      AOP日志监控配置类
     * </pre>
     *
     * <pre>
     * @author mazq
     * 修改记录
     *    修改后版本: V1.0.0    修改人:mazq  修改日期: 2020/11/23 14:30  修改内容: 新增配置类
     * </pre>
     */
    @Configuration
    @Aspect
    @EnableAspectJAutoProxy
    public class AopLogMonitorConfiguration {
    
        @Pointcut("within(com.example.spring.aop..*) && execution(public String com.example.spring.aop.service.UserService.findUserNameById(Long, ..))")
        public void monitor(){ }
    
        @Bean
        public TestMonitoringInterceptor monitoringInterceptor() {
            return new TestMonitoringInterceptor(true);
        }
    
        @Bean
        public Advisor monitoringAdvisor() {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression("com.example.spring.aop.config.AopConfiguration.monitor()");
            return new DefaultPointcutAdvisor(pointcut, monitoringInterceptor());
        }
    
        @Bean
        public UserService userService(){
            return new UserServiceImpl();
        }
    
    
    }
    

    TestMonitoringInterceptor.java

    package com.example.spring.aop.core.interceptor;
    
    import org.aopalliance.intercept.MethodInvocation;
    import org.apache.commons.logging.Log;
    import org.springframework.aop.interceptor.AbstractMonitoringInterceptor;
    
    /**
     * <pre>
     *      TestMonitoringInterceptor
     * </pre>
     *
     * <pre>
     * 修改记录
     *    修改后版本:     修改人:  修改日期: 2020/11/23 16:39  修改内容:
     * </pre>
     */
    public class TestMonitoringInterceptor extends AbstractMonitoringInterceptor {
    
        public TestMonitoringInterceptor(){}
    
        public TestMonitoringInterceptor (boolean useDynamicLogger) {
            setUseDynamicLogger(useDynamicLogger);
        }
    
        @Override
        protected Object invokeUnderTrace(MethodInvocation methodInvocation, Log log) throws Throwable {
            String name = createInvocationTraceName(methodInvocation);
            long start = System.currentTimeMillis();
            try {
                return methodInvocation.proceed();
            } finally {
                long end = System.currentTimeMillis();
                long time = end - start;
                log.info(String.format("方法名:%s,执行时间:%s ms",name,time));
                if (time > 10) {
                    log.warn(String.format("方法名:%s,执行时间超过10 ms! ",name));
                }
            }
        }
    }
    

    pom.xml加上logback配置:

    <properties>
        <slf4j.version>1.7.25</slf4j.version>
        <logback.version>1.2.3</logback.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>${logback.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-access</artifactId>
            <version>${logback.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>
    </dependencies>
    

    logback.xml,copy @ https://github.com/eugenp/tutorials/blob/master/spring-aop/src/main/resources/logback.xml,进行一点改写:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <!-- copy @https://github.com/eugenp/tutorials/blob/master/spring-aop/src/main/resources/logback.xml-->
    
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
                </pattern>
            </encoder>
        </appender>
    
        <logger name="org.springframework" level="WARN" />
        <logger name="org.springframework.transaction" level="WARN" />
    
        <!-- in order to debug some marshalling issues, this needs to be TRACE -->
        <logger name="org.springframework.web.servlet.mvc" level="WARN" />
    
        <logger name="com.example.spring.aop.core.interceptor.TestMonitoringInterceptor" level="INFO" />
    
        <logger name="org.springframework.aop.interceptor.PerformanceMonitorInterceptor" level="TRACE" />
    
        <root level="TRACE">
            <appender-ref ref="STDOUT" />
        </root>
    </configuration>
    
    package com.example.spring.aop;
    
    
    import com.example.spring.aop.bean.User;
    import com.example.spring.aop.config.AopConfiguration;
    import com.example.spring.aop.service.UserService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class TestApplication {
    
        public static void testLogMonitoring() {
            AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext();
            // 注册配置类
            ioc.register(AopConfiguration.class);
            // 启动IOC容器
            ioc.refresh();
            UserService userService = (UserService) ioc.getBean("userService");
            System.out.println(userService.findUserNameById(1L));
        }
    
        public static void main(String[] args) {
            // logging monitoring
            testLogMonitoring();
        }
    }
    

    17:54:05.553 [main] INFO c.e.s.a.service.impl.UserServiceImpl - 方法名:com.example.spring.aop.service.UserService.findUserNameById,执行时间:2531 ms

    17:54:05.559 [main] WARN c.e.s.a.service.impl.UserServiceImpl - 方法名:com.example.spring.aop.service.UserService.findUserNameById,执行时间超过10 ms!

    8.3、Spring2.0+ schema-based 配置

    Spring2.0之后提供了基于 <aop /> 命名空间的 XML 配置,这也就是本文介绍的schema-based 配置

    SchemaBasedAspect .java:

    package com.example.spring.aop.aspect;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    
    import java.text.SimpleDateFormat;
    import java.util.Arrays;
    import java.util.Date;
    
    public class SchemaBasedAspect {
    
        private ThreadLocal<SimpleDateFormat> simpleDateFormat =new ThreadLocal<SimpleDateFormat>(){
            @Override
            protected SimpleDateFormat initialValue() {
                //return super.initialValue();
                return new SimpleDateFormat("[yyyy-mm-dd hh:mm:ss:SSS]");
            }
        };
    
        public void beforeAdvice(JoinPoint joinPoint){
            Object[] args = joinPoint.getArgs();
            String methodName = joinPoint.getSignature().getName();
            System.out.println(String.format("前置通知:beforeAdvice,参数是:%s", Arrays.toString(args)));
            System.out.println(simpleDateFormat.get().format(new Date()) + methodName);
        }
    
        public void afterReturningAdvice(Object returnVal){
            System.out.println(String.format("后置通知:afterReturningAdvice,返回参数是:%s", returnVal));
        }
    
        public void afterThrowingAdvice(Throwable e) {
            System.out.println(String.format("异常通知:afterThrowingAdvice,异常信息:%s", e));
        }
    
        public void afterAdvice() {
            System.out.println(String.format("最后通知:afterAdvice"));
        }
    
        public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
            Object rtValue = null;
            try {
                System.out.println("aroundAdvice前置通知!");
                // 获取参数
                Object[] args = proceedingJoinPoint.getArgs();
                // 执行切入点方法
                rtValue = proceedingJoinPoint.proceed(args);
    
                System.out.println("aroundAdvice后置通知!");
            } catch (Throwable e) {
                System.out.println("aroundAdvice异常通知!");
                e.printStackTrace();
            } finally {
                System.out.println("aroundAdvice最后通知!");
            }
            return rtValue;
        }
    }
    

    spring_schemaBased_config.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 具体业务实现类(target Object)-->
        <bean id="userService" class="com.example.spring.aop.service.impl.UserServiceImpl"></bean>
        <!-- SchemaBased Aspect-->
        <bean id="LoggingAspect" class="com.example.spring.aop.aspect.SchemaBasedAspect"></bean>
    
        <aop:aspectj-autoproxy/>
    
        <!--开始aop的配置-->
        <aop:config>
            <aop:pointcut id="executionPointcut" expression="execution(* com.example.spring.aop.service.UserService.*(..))" />
            <!--配置切⾯-->
            <aop:aspect id="logAspect" ref="LoggingAspect">
                <!--配置前置通知-->
                <aop:before method="beforeAdvice"
                            pointcut-ref="executionPointcut"></aop:before>
                <!--配置后置通知-->
                <aop:after-returning method="afterReturningAdvice"
                                     pointcut-ref="executionPointcut" returning="returnVal"></aop:after-returning>
                <!-- 配置异常通知-->
                <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="executionPointcut" throwing="e"
                ></aop:after-throwing>
                <!-- 配置最后通知-->
                <aop:after method="afterAdvice" pointcut-ref="executionPointcut"></aop:after>
                <!-- 配置环绕通知-->
                <aop:around method="aroundAdvice" pointcut-ref="executionPointcut"></aop:around>
            </aop:aspect>
        </aop:config>
    
    </beans>
    

    TestApplication.java:

    package com.example.spring.aop;
    
    import com.example.spring.aop.bean.User;
    import com.example.spring.aop.service.UserService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class TestApplication {
    
        public static void testSchemaBasedAop(){
            ApplicationContext ioc = new ClassPathXmlApplicationContext("classpath:spring_schemaBased_config.xml");
            UserService userService = (UserService) ioc.getBean("userService");
            User userDto = new User();
            userDto.setUsername("tom");
            userDto.setPassword("11");
            userService.addUser(userDto);
            System.out.println(String.format("用户数据打印:%s",userService.getUser().toString()));
        }
    
        public static void main(String[] args) {
            // schema Based config
            testSchemaBasedAop();
        }
    }
    

    本文的代码例子可以在github找到下载链接: 链接

    相关文章

      网友评论

        本文标题:别说你还不会!Spring5.0源码学习系列之Spring AO

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