Spring AOP再入门

131 阅读8分钟

Spring AOP目标

  • 将分散在程序各处的横切关注点剥离出来并以集中的方式进行表达。
  • 使得开发人员能够专注于业务逻辑的实现而非繁杂的非功能代码,简化了程序编写与单元测试。
  • 应用场景
    • 日志
    • 安全
    • 事务

AOP 和 继承的区别

  • AOP重点考虑的是程序的横切逻辑
    • image.png
    • 简单说就各模块间的关系是平级的,只是执行的先后顺序不同。
  • 继承重点考虑的是纵向的职责分派
    • image.png
    • 简单说就子类和父类之间的职责和功能有些许不同,而且子类要严格遵循父类的规则。

注意一种设计原则: 少用继承,多用组合。 AOP核心概念

  • Advice(通知)
    • 定义在连接点处的行为(功能代码),围绕方法调用而进行注入。
  • Pointcut(切点)
    • 确定在哪些连接点处应用通知
  • Advisor (通知器)
    • 组合Advice与Pointcut。

Spring AOP实现

  • ProxyFactoryBean
    • FactoryBean implementation that builds an AOP proxy based on beans inSpring BeanFactory. (From Spring doc)

    FactoryBean和BeanFactory要区分开来,一个是Bean一个是工厂。

    • Spring AOP的底层实现与源头。

ProxyFactoryBean的构成

  • target
    • 目标对象,需要对其进行切面增强。
    • 其实就是普通的Bean。
  • proxyInterfaces
    • 代理对象所实现的接口。
    • 不是代理对象,代理对象一般是动态生成的。
  • interceptorNames
    • 通知器(Advisor)列表,通知器中包含了通知(Advice)与切点(Pointcut)。

到最后,其实并不是使用目标对象,而是使用代理对象。

ProxyFactoryBean的作用

  • 总的来说, ProxyFactoryBean的作用可用下面这句话概括:
    • 针对目标对象来创建代理对象,将对目标对象方法的调用转到对相应代理对象方法的调用,并且可以在代理对象方法调用前后执行与之匹配的各个通知器中定义好的方法。

目标代理对象的创建

  • Spring通过3种方式来创建目标代理对象
    • JDK动态代理
    • CGLIB
    • ObjenesisCglibAopProxy

代理模式

  • 代理模式的作用是∶为其他对象提供一种代理以控制对这个对象(目标对象)的访问
  • 在某些情况下,一个客户不想或者不能直接引用另一个对象而代理对象可以在客户端和目标对象之间起到中介的作用。
  • 代理还分静态代理和动态代理,它们的区别简单说
    • 静态代理要为每个目标对象手动创建代理类,如果很多目标对象就要一对一创建很多代理类,那会很累。
    • 动态代理就是用一个代理对象代理多个类似的目标对象。请注意是类似的目标对象。

JDK动态代理的实现

  • JDK动态代理
    • Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类(接口):
      • Interface lnvocationHandler:
        • 该接口中仅定义了一个方法 public object invoke(Object obj,Method method, Objectargs) 在实际使用时,第一个参数obj一般是指代理类, method是被代理的方法, args为该方法的参数数组。这个抽象方法在代理类中动态实现
      • Class Proxy:
        • 该类即为动态代理类,其中主要包含以下内容
          • static Object newProxyInstance(ClassLoader loader, Classinterfaces,lnvocationHandler h)
          • 返回代理类的一个实例,返回后的代理类可以当作被代理类使用。
    • 动态代理是这样—种class:
      • 运行时生成class,在生成它时你必须提供一组interface给它,然后该class就会声明它实现了这些interface。因此我们可以将该class的实例当作这些interface中的任何一个来用。当然,这个动态代理其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作
      • class E 实现了Interface A B C D,那就可以认为E就是A,E就是B,E就是C...。
      • 这样就是实现了一个类代理多个类,谓之动态代理。
  • JDK动态代理实现步骤
    1. 创建一个实现接口InvocationHandler的类,它必须实现invoke方法。
    2. 创建被代理的类以及接口。
    3. 通过Proxy的静态方法 newProxyInstance (ClassLoader loader, Class[] interfaces,InvocationHandler h)创建一个代理。
    4. 通过代理调用方法。
  • 如果目标类并未实现接口,那么Spring就会使用CGLIB库为其创建代理。

Spring AOP样例

代码

  • 方便起见,没把每一个类都创一个文件。
  • 目标类Service。
public interface Service {
    void serviceMethod();
}

class ServiceImpl implements Service{
    @Override
    public void serviceMethod() {
        System.out.println("serviceMethod invoked");
    }
}
  • 增强目标类功能的代码Advice,Advisor其实最顶层的父接口就是Advice。
public class Advisor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("before serviceMethod invoked");
        Object result = invocation.proceed();
        System.out.println("after serviceMethod invoked");
        return result;
    }
}

配置

<?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">

    <bean id="serviceImp" class="com.spring.aop.ServiceImpl"/>
    <bean id="advisor" class="com.spring.aop.Advisor"/>

    <bean id="myAOP" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces" value="com.spring.aop.Service"/>
        <property name="interceptorNames">
            <list>
                <value>advisor</value>
            </list>
        </property>
        <property name="target" ref="serviceImp"/>
    </bean>
</beans>
  • Main方法。
public class SpringClientAOP {

    public static void main(String[] args)  {
        Resource resource = new ClassPathResource("applicationContext2.xml");
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(factory);
        beanDefinitionReader.loadBeanDefinitions(resource);

        Service service = (Service) factory.getBean("myAOP");
        service.serviceMethod();
        System.out.println("service的类型:" + service.getClass());
        StringJoiner printInterfaces = new StringJoiner("\n");
        Arrays.stream(service.getClass().getInterfaces()).map(Object::toString).forEach(printInterfaces::add);
        System.out.println("service直接实现的接口如下:\n" + printInterfaces);
    }
}

image.png

关键点FactoryBean

一个疑问

image.png

为什么"myAOP"指定的class是ProxyFactoryBean,getBean的时候却可以拿到Service?

image.png

  • 接口FactoryBean 有如下注释:
    • image.png

再次强调BeanFactory和FactoryBean是不一样的。

BeanFactory和FactoryBean的区别

  • BeanFactory是Spring IOC的工厂,它里面管理着Spring所创建出来的各种Bean对象,当我们在配置文件(注解)中声明了某个bean的id后,通过这个id就可以获取到与该id所对应的class对象的实例(可能新建,如果是单例也可能从缓冲中获取)。
  • FactoryBean本质上也是一个Bean,它同其他Bean一样,也是由BeanFactory所管理和维护的,当然它的实例也会缓存到spring的工厂中(如果是单例)。
  • 它与普通的Bean的唯一区别在于:
    • 当Spring创建一个FactoryBean的实例后,它接下来会判断当前所创建的Bean是否是一个FactoryBean实例,如果不是,那么就直接将创建出来的Bean返回给客户端;
    • 如果是,那么它会对其进行进一步的处理,根据配置文件所配置的target,advisor与interfaces等信息,在运行期动态构建出一个类,并生成该类的一个实例,最后将该实例返回给客户端;
    • 因此,我们在声明一个FactoryBean时,通过id获取到的并非这个FactoryBean的实例,而是它动态生成出来的一个代理对象(通过三种方式来进行生成)。
  • 在调用getBean方法时,会途经如下代码块:
    • image.png

简述一下,关于spring AOP代理的生成过程

通过debug跑一下其实就能知晓了

  • 通过ProxyFactoryBean (FactoryBean接口的实现)来去配置相应的代理对象相关的信息。
  • 在获取ProxyFactoryBean实例时,本质上并不是获取到ProxyFactoryBean的对象,而是获取到了由ProxyFactoryBean所返回的那个对象实例。
  • 在整个ProxyFactoryBean实例的构建与缓存的过程中,其流程与普通的Bean对象完全一致。
  • 差别在于,当创建了ProxyFactoryBean对象后,Spring会判断当前所创建的对象是否是一个FactoryBean实例。
    • 如果不是,那么Spring就直接将所创建的对象返回。
    • 如果是,Spring则会进入到一个新的流程分支当中。
  • Spring会根据我们在配置信息中所指定的各种元素,如目标对象是否实现了接口以及advisor等信息,使用JDK动态代理或是CGLIB等方式来为目标对象创建相应的代理对象。
  • 当相应的代理对象创建完毕后,Spring就会通过ProxyFactoryBean的getObject方法,将所创建的对象返回。
  • 对象返回到调用端(客户端),它本质上是一个代理对象,可以代理对目标对象的访问与调用;这个代理对象对用户来说,就好像一个目标对象一样。只是功能有可能增强了一些。
  • 客户在使用代理对象时,可以正常调用目标对象的方法,同时在执行过程中,会根据我们在配置文件中所配置的信息来在调用前后执行额外的附加逻辑。

代理与动态代理及字节码生成

再看一看经典的静态代理

public interface Target {
    void targetMethod();
}

public class RealTarget implements Target{
    @Override
    public void targetMethod() {
        System.out.println("invoke target method");
    }
}

public class ProxyTarget implements Target{
    private RealTarget realTarget;  //被代理(真实)对象

    //增强真实对象的功能
    @Override
    public void targetMethod() {
        this.beforeTargetMethod();
        realTarget = realTarget == null ? new RealTarget() : this.realTarget;
        realTarget.targetMethod();
        this.afterTargetMethod();
    }

    private void beforeTargetMethod() {
        System.out.println("before invoke target method");
    }

    private void afterTargetMethod() {
        System.out.println("after invoke target method");
    }

    public static void main(String[] args) {
        new ProxyTarget().targetMethod();//调用代理对象
    }
}
  • 运行结果如下image.png

如果有多个类继承了Target接口,那么如果要代理这些对象,就要写很多个Proxy类。

动态代理

  • 看看InvocationHandler里的invoke方法的proxy和method是什么。
public interface Subject {
    void subjectMethod();
}

public class RealSubject implements Subject{
    @Override
    public void subjectMethod() {
        System.out.println("subject method invoke");
    }
}

public class DynamicSubject implements InvocationHandler {

    private Object subject;

    public DynamicSubject(Object subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy: " + proxy.getClass().getName());

        System.out.println("before: " + method);
        //call target object method
        method.invoke(subject, args);

        System.out.println("after: " + method);
        return null;
    }

    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();

        InvocationHandler invocationHandler = new DynamicSubject(realSubject);

        Class<?> classType = invocationHandler.getClass();
        Subject subjectInMain = (Subject) Proxy
                .newProxyInstance(classType.getClassLoader(), realSubject.getClass().getInterfaces(), invocationHandler);

        subjectInMain.subjectMethod();
    }
}

  • 运行结果如下 image.png
  • 众所周知,Java代码想要运行一般都需要class字节码,所以可以猜测动态代理必然会生成字节码,而且是在内存中暂存,程序结束后就被清理了。毕竟硬盘里找不到动态生成的class文件。
  • 看看动态生成的字节码是啥样的。要使用jvm参数:
    -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true  //jdk17
    
    • 动态生成的代理类如下
    • image.png image.png image.png