美文网首页一些收藏
Spring AOP之@Aspect

Spring AOP之@Aspect

作者: AC编程 | 来源:发表于2022-09-18 11:27 被阅读0次

    一、编写测试代码

    1.1 新建一个Spring Boot项目,并引入@Aspect依赖
        <dependency>
           <groupId>org.aspectj</groupId>
           <artifactId>aspectjweaver</artifactId>
        </dependency>
    
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-aop</artifactId>
           <scope>test</scope>
       </dependency>
    

    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.7.3</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.alanchen</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>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
                <scope>test</scope>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    1.2 demo代码
    package com.alanchen.demo.aspect;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    /**
     * @author Alan Chen
     * @description
     * @date 2022-09-18
     */
    @ComponentScan("com.alanchen.demo.aspect")
    @EnableAspectJAutoProxy
    public class AspectConfig {
    
    }
    
    package com.alanchen.demo.aspect;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    /**
     * @author Alan Chen
     * @description
     * @date 2022-09-18
     */
    @Aspect
    @Component
    public class OrderLogAspect {
    
        @Before("execution(public void com.alanchen.demo.aspect.OrderService.order())")
        public void beforeOrder(JoinPoint joinPoint){
            System.out.println("执行下单接口前,打印的切面日志");
        }
    }
    
    package com.alanchen.demo.aspect;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.stereotype.Service;
    
    /**
     * @author Alan Chen
     * @description
     * @date 2022-09-18
     */
    @Service
    public class OrderService {
    
        @Autowired
        private UserService userService;
    
        public void order(){
            userService.userInfo();
            System.out.println("正在下单");
        }
    }
    
    package com.alanchen.demo.aspect;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    /**
     * @author Alan Chen
     * @description
     * @date 2022-09-18
     */
    @Service
    public class UserService {
    
        public void userInfo(){
            System.out.println("获取用户信息");
        }
    }
    
    package com.alanchen.demo.aspect;
    
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    /**
     * @author Alan Chen
     * @description
     * @date 2022-09-18
     */
    public class Test {
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AspectConfig.class);
    
            //拿到的是OrderService的代理对象
            OrderService orderService = context.getBean(OrderService.class);
            orderService.order();
    
            System.out.println("下单接口执行完了");
        }
    }
    
    1.3 项目截图
    项目截图
    1.4 运行结果
    执行下单接口前,打印的切面日志
    获取用户信息
    正在下单
    下单接口执行完了
    

    二、代码分析

    2.1 Spring Bean 生命周期步骤伪代码

    1、类
    2、推断选择构造方法(默认调用类无参构造方法)
    3、普通对象(new出来)
    4、为对象的属性(加了注解的,如@Autowired)进行依赖注入
    5、初始化前:判断方法上是否加了@PostConstruct注解
    6、初始化:判断是否实现了InitializingBean接口,反射调用afterPropertiesSet方法
    7、初始化后:进行AOP
    8、代理对象
    9、放入单例池Map
    10、成为Bean

    2.2 debug调试

    我们在orderService.order();System.out.println("下单接口执行完了"); 处都打上断点,发现OrderService里的userService对象竟然为null,为什么order方法按预期执行成功了,并没有抛出空指针异常?

    断点1 断点2
    2.3 Spring AOP中动态代理的两种方式

    Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

    • JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。

    • 如果代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。Cglib是基于ASM实现的AOP。

    由此可得,本本demo中,Spring会采用CGLIB的方式给OrderService生成代理类,证据如下:

    CGLIB
    2.4 CGLIB实现代码方式
    2.4.1 实现方式1 伪代码
    public OrderServiceProxy extends OrderService{
    
       @Override
        public void order(){
            // 执行@Before切面逻辑
    
            //调用父类(被代理类)方法
            super.order()
        }
    }
    

    如果Spring采用的是这种代理实现方式,那么OrderService里的userService依然为null,order方法也不可能执行成功。

    补充说明:这里代理对象OrderServiceProxy继承的是一个普通对象OrderService(不是Bean对象),所以OrderService里的属性为null,而且Spring Bean的生命周期中AOP是最后一步,AOP后没有依赖注入的步骤,所以代理对象OrderServiceProxy里的属性为null。

    2.4.2 实现方式2 伪代码
    public OrderServiceProxy extends OrderService{
    
      // target里的userService里不为null
      @Autowired
      private OrderService target;
    
       @Override
        public void order(){
            // 执行@Before切面逻辑
    
            //调用target(被代理类)方法
            target.order()
        }
    }
    

    实现方式2的妙处在于在OrderServiceProxy类里引入了一个target,这个target是OrderService的一个普通对象(非AOP代理对象),这个普通对象已完成了Spring的依赖注入,所以这个对象里的userService是有值的。

    我们再回到2.1中的Spring Bean生命周期来进行分析,放入Spring单例池的是OrderService AOP代理对象OrderServiceProxy,虽然OrderService普通对象(已完成依赖注入,依赖对象不为null)没有放入Spring单例池,但他被代理对象OrderServiceProxy引用,因此OrderService普通对象依然在JVM中,不会被销毁。

    问题:为什么要用继承?
    代理类OrderServiceProxy继承了OrderService后,用的时候就可以当成OrderService来用。

    2.5 证实CGLIB采用的是实现方式2

    我们在order()方法里打一个断点,继续来调试

    断点1 断点2

    我们看第一个断点:
    OrderService代理类中的userService依然是null,但有一个target,这个target是OrderService类型,地址是OrderService@2304,target里的userService地址是UserService@2305

    继续往下执行,我们来看第二个断点:
    OrderService执行order()方法,this地址为OrderService@2304,这个地址正是OrderServiceProxy代理类中的target对象,而且userService地址也刚好是UserService@2305

    另外,我们可以在切面代码里,通过joinPoint可以取到被代理的类OrderService,如下:

    @Aspect
    @Component
    public class OrderLogAspect {
    
        @Before("execution(public void com.alanchen.demo.aspect.OrderService.order())")
        public void beforeOrder(JoinPoint joinPoint){
            //通过joinPoint可以取到被代理的类OrderService
            OrderService orderService = (OrderService)joinPoint.getTarget();
    
            System.out.println("执行下单接口前,打印的切面日志");
        }
    }
    

    因此我们可以证实CGLIB采用的是实现方式2

    相关文章

      网友评论

        本文标题:Spring AOP之@Aspect

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