美文网首页一些收藏
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