Spring | AOP 编程详解

Spring | AOP 编程详解

AOP 编程

AOP 概念

AOP,Aspect Oriented Programing,即面向切面编程,是指以切面为基本单位进行程序的开发,通过切面间的相互协同和调用,完成程序的构建

AOP 的本质,就是 Spring 的动态代理开发,其根本目的就是通过代理类为原始类(目标类)增加额外的功能,有利于原始类的维护

在几何学上,一个面就是由一些点且是具有相同性质的点组成,而 Spring 中的切面也就是由切入点额外功能共同组成,例如有三个类 UserServiceImplOrderServiceImplProductServiceImpl,它们各自存在一个方法(切入点),需要加入相同的额外功能,那么把这些切入点取出,连同额外功能,就可以抽象成一个切面

切面.png

AOP 开发步骤

AOP 编程实质上就是 Spring 动态代理开发,因此二者的开发步骤完全相同

  1. 准备原始对象
  2. 定义额外功能(例如 MethodInterceptor
  3. 定义切入点
  4. 组装切面(切入点+额外功能)

AOP 的底层实现

AOP 有两个核心问题需要解决

  1. 动态代理类没有与之对应的源码和字节码文件,如何创建动态代理类?(动态字节码技术)
  2. Spring 工厂如何加工创建代理对象?为什么通过原始对象的 id 值,获得了代理对象?

JDK 动态代理

使用 JDK 的动态代理创建代理对象的三个要素是原始对象、额外功能、代理对象和原始对象实现相同的接口,具体的实现依赖于核心类 ProxyProxy.newProxyInstance() 方法

Proxy.newProxyInstance() 详解

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

首先该方法是 Proxy 类的一个静态方法,可以返回一个代理对象,该方法需要三个参数分别是 ClassLoaderinterfacesInvocationHandler

  1. ClassLoader 是指类加载器,我们知道,类加载器有两个作用,一是 JVM 通过类加载器将对应类的字节码文件(.class)文件加载进内存,二是通过类加载器创建类的 Class 对象,进而创建这个类的实例对象,在不使用动态代理创建对象,也就是说每个类都存在与之对应的 .class 文件,那么 JVM 会为该类自动分配一个 ClassLoader,但是如果需要创建代理对象,需要我们指定一个 ClassLoader,通常可以将其指定为原始对象的类加载器,例如为 UserServiceImpl 类创建代理对象,第一个参数可以给 UserServiceImpl.class.getClassLoader()

  2. Class<?>[] interfaces 是指原始对象实现的接口(可以多个),代理对象需要同样需要实现这些接口,该参数可以指定为 UserServiceImpl.class.getInterfaces()

  3. InvocationHandler 是一个接口,其作用就是书写额外功能,定义额外功能的运行时机(原始方法运行前后)以及抛出异常,其有一个未实现的抽象方法,我们需要实现该方法并创建一个该类的对象作为 newProxyInstance() 的第三个参数,InvocationHandler 未实现的方法是 public Object invoke(Object proxy, Method method, Object[] args),该方法的三个参数

    • Object proxy:代表代理对象,可以忽略
    • Method method:代表额外功能所增加给的那个原始方法
    • Object[] args :代表原始方法的参数

实例

原始类

public class UserServiceImpl implements UserService{
    // 注入其他组件
    private final UserDAO userDAO = new UserDAOImpl();
    // 核心功能
    public void save(User user) {
        // 核心业务逻辑
        System.out.println("UserServiceImpl.save() run");
        // 调用 DAO
        userDAO.save(user);
    }
}

创建代理类,并获得代理对象

public class JDKProxyTest {
    public static void main(String[] args) {
        // 创建 InvocationHandler 对象,实现 invoke() 方法,定义额外功能和运行时机
        InvocationHandler invocationHandler = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 额外功能:运行在原始方法执行之前
                System.out.println("----log:原始方法执行之前");
                // 原始方法执行,参数1:原始对象,参数2:原始方法的参数,返回值:原始方法的返回值
                Object ret = method.invoke(new UserServiceImpl(),args);
                // 额外功能:运行在原始方法执行之后
                System.out.println("----log:原始方法执行之后");
                // 可以修改原始方法的返回值,或者直接返回
                return ret;
            }
        };
        // 获取代理对象
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(),UserServiceImpl.class.getInterfaces(),invocationHandler);
        // 通过代理对象调用接口方法
        userServiceProxy.save(new User());
    }
}

结果

----log:原始方法执行之前
UserServiceImpl.save() run
UserDAOImpl.save() run
----log:原始方法执行之后

CGLib 动态代理

CGLib 是一个开源的代码生成类库,与 JDK 自带的动态代理不同,CGLib 通过父子类继承关系创建代理对象,原始类作为父类,代理类作为子类,这样可以保证代理类的方法与原始类一致,并提供额外的功能,允许原始类没有实现任何接口,CGLib 实现动态代理的核心类是 Enhancer,同样需要为 Enhancer 指定代理类的类加载器、所继承的父类以及要增加的功能,最后通过 Enhancer 类的 create() 方法获取代理类对象

实例

package cool.yzt.proxy;

import cool.yzt.entity.User;
import cool.yzt.service.UserService;
import cool.yzt.service.UserServiceImpl;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CGLibProxyTest {
    public static void main(final String[] args) {
        final UserService userService = new UserServiceImpl();
        // 创建 Enhancer 对象
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(UserServiceImpl.class.getClassLoader());
        // 设置父类
        enhancer.setSuperclass(UserServiceImpl.class);
        // 将额外功能定义在 MethodInterceptor 方法拦截器中,实现其抽象方法,获得对象
        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("----cglib log:原始方法执行之前");
                // 原始方法执行,同样是传入原始对象和参数
                Object ret = method.invoke(userService,objects);
                System.out.println("----cglib log:原始方法执行之后");
                return ret;

            }
        };
        // 将 MethodInterceptor 的实例对象传给 Enhancer
        enhancer.setCallback(methodInterceptor);
        // 获取代理对象
        UserService userServiceProxy = (UserService) enhancer.create();
        userServiceProxy.save(new User());
    }
}

思考:Spring 工厂如何加工原始对象

为什么通过 beanid 值从工厂获得的对象是代理对象?实际上,Spring 在 BeanPostProcesserpostProcessAfterIntialization 方法中,使用动态代理技术,对原始对象进行了再加工,该方法的返回值就是代理对象

工厂创建代理对象.png

基于注解的 AOP 编程

无论是基于配置文件还是基于注解,AOP 的底层实质就是通过动态代理生成代理对象为原始对象添加额外功能,所以基于注解的 AOP 编程依然遵循准备原始对象、定义额外功能和切入点、组装切面的步骤进行开发,Spring 中通过使用 @Aspect@Pointcut@Around@Before@After 等注解进行 AOP 编程。在 Java 中一切皆对象,所以切面也应该被封装成一个切面类,使用 @Aspect 表示一个类是切面类

//切面类
@Aspect
public class MyAspect {
    // 定义切入点:service包下的所有类的所有方法,可以定义多个切入点
    @Pointcut("execution(* cool.yzt.service.*.*(..))")
    private void pointcut() {}

    /**
     * 定义额外功能
     */
    // 在原始方法执行之前调用,切入点是pointcut()方法之上注解中使用切入点函数定义
    @Before(value="pointcut()")
    public void before() {
        System.out.println("------aspect log:before------");
    }
    // 在原始方法执行之后调用
    @After(value="pointcut()")
    public void after() {
        System.out.println("------aspect log:after------");
    }

    @Around(value="pointcut()")
    // 在原始方法之前和之后调用,ProceedingJoinPoint可以看做是原始方法的封装
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("------aspect log:around1------");
        // 原始对象执行,并获取返回值
        Object ret = joinPoint.proceed();
        System.out.println("------aspect log:around2------");
        return ret;
    }
}

在配置文件中配置告知 Spring 基于直接进行 AOP 编程,并将切面类纳入 IOC 工厂管理

<aop:aspectj-autoproxy />
<bean id="myAspect" class="cool.yzt.aspect.MyAspect"/>

参考

孙哥说Spring5

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://yzt.cool/archives/springaop编程详解