Spring 框架之 AOP 原理深度剖析

Spring AOP(Aspect Oriented Programming)是基于面向切面编程的一种框架,它可以对多个目标对象进行统一管理,并在不修改原有业务代码的前提下增加额外功能。实现过程中主要依赖于代理(Proxy)和动态代理(Dynamic Proxy)技术,本文将详细分析Spring AOP的实现原理。

一、AOP的基本概念

AOP通过将一个应用程序分解为重要的部分,然后将那些跨越这些部分的关注点分离出来,从而帮助我们更好地定义我们的业务逻辑。其中,核心概念包括:

  • 切面(Aspect) 切面是织入到目标类中的功能代码,对于Java来说可以是方法、构造器或者类的特定点上的代码。切面可以包括前置通知、后置通知、环绕通知、异常通知和最终通知等,具体可以根据业务需求自定义实现。

  • 连接点(Join Point) 连接点表示切面将会被织入到目标对象的哪个位置,对于Java来说通常是某个方法的执行点或者某个构造器的创建点。

  • 切入点(Pointcut) 切入点是一组连接点的集合,它定义了哪些连接点需要被织入到目标对象中。

  • 通知(Advice) 通知包括各种类型的切面代码,如前置通知、后置通知、环绕通知、异常通知和最终通知等。

  • 织入(Weaving) 织入是将切面代码编织到目标对象中的过程,可以发生在编译时、加载时或者运行时。Spring AOP以动态代理技术为主要方式进行织入。

  • 引入(Introduction) 引入允许我们向现有的类添加新方法和属性,这些方法和属性并不在原先的类定义中,而是在引入的接口中定义。

二、AOP实现的三种方式

在介绍Spring AOP实现原理前,我们先了解实现AOP的三种方式:静态代理,使用JDK的Proxy类实现动态代理,使用CGLIB实现动态代理。

为了更好的理解Spring AOP的实现原理和AOP使用动态代理的方式,我们可以深入了解三种方式:静态代理、JDK动态代理和CGLIB动态代理。

1. 静态代理

静态代理是指在编译时就已经确定需要被代理的类和方法,代理类在编译时就已经存在,其优点是运行效率高,缺点是不灵活,只能代理预先定义好的类和方法。下面以一个简单的示例说明静态代理的原理:

首先定义一个接口Subject:

public interface Subject {
    void request();
}

然后定义一个目标对象RealSubject:

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject is handling the request.");
    }
}

最后定义一个代理对象ProxySubject:

public class ProxySubject implements Subject {
    private Subject realSubject;

    public ProxySubject(Subject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        System.out.println("ProxySubject is handling the request.");
        realSubject.request();
    }
}

以上示例中,ProxySubject是代理对象,它实现了Subject接口,并持有一个真实对象realSubject,并在request()方法中添加了额外的处理逻辑。

当需要调用Subject接口中的方法时,我们可以通过创建代理对象ProxySubject来调用,如下所示:

public static void main(String[] args) {
    Subject subject = new RealSubject();
    subject.request();

    Subject proxySubject = new ProxySubject(subject);
    proxySubject.request();
}

在运行程序时,我们会发现输出了以下结果:

RealSubject is handling the request.
ProxySubject is handling the request.
RealSubject is handling the request.

以上示例中,代理对象ProxySubject在调用request()方法时,先打印一句“ProxySubject is handling the request.”,然后再调用目标对象RealSubject的request()方法。

2. JDK动态代理

JDK动态代理是指通过Java自带的Proxy类和InvocationHandler接口来实现动态生成代理类的过程,在运行时动态生成的代理类可以实现任意接口,相比于静态代理更灵活,但是只能代理实现了接口的类,不能代理没有实现接口的类。下面以一个简单的示例说明JDK动态代理的原理:

首先定义一个接口Subject:

public interface Subject {
    void request();
}

然后定义一个目标对象RealSubject:

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject is handling the request.");
    }
}

接着定义一个InvocationHandler实现类MyInvocationHandler:

public class MyInvocationHandler implements InvocationHandler {
    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("ProxySubject is handling the request.");
        Object result = method.invoke(target, args);
        return result;
    }
}

以上示例中,MyInvocationHandler实现了InvocationHandler接口,并重写了invoke()方法,在该方法中添加了额外的处理逻辑。

最后在主程序中创建动态代理对象:

public static void main(String[] args) {
    Subject realSubject = new RealSubject();
    InvocationHandler invocationHandler = new MyInvocationHandler(realSubject);
    Subject proxySubject = (Subject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), invocationHandler);
    proxySubject.request();
}

当运行程序时,我们会发现输出了以下结果:

ProxySubject is handling the request.
RealSubject is handling the request.

以上示例中,通过调用Proxy.newProxyInstance()方法来创建代理对象proxySubject,它可以代理任意实现了Subject接口的类。

3. CGLIB动态代理

CGLIB动态代理是指使用CGLIB库来实现动态生成代理类的过程,与JDK动态代理不同的是,CGLIB动态代理可以代理没有实现接口的类,具有更广泛的适用性。下面以一个简单的示例说明CGLIB动态代理的原理:

首先定义一个目标对象RealSubject:

public class RealSubject {
    public void request() {
        System.out.println("RealSubject is handling the request.");
    }
}

然后定义一个MethodInterceptor实现类MyMethodInterceptor:

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("ProxySubject is handling the request.");
        Object result = proxy.invokeSuper(obj, args);
        return result;
    }
}

以上示例中,MyMethodInterceptor实现了MethodInterceptor接口,并重写了intercept()方法,在该方法中添加了额外的处理逻辑。

最后在主程序中创建动态代理对象:

public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(RealSubject.class);
    enhancer.setCallback(new MyMethodInterceptor());
    RealSubject proxySubject = (RealSubject) enhancer.create();
    proxySubject.request();
}

当运行程序时,我们会发现输出了以下结果:

ProxySubject is handling the request.
RealSubject is handling the request.

以上示例中,通过调用Enhancer.create()方法来创建代理对象proxySubject,它可以代理任意类,包括没有实现任何接口的类。

这三种方式总得来说,静态代理和JDK动态代理使用更广泛,在需求比较简单、只需要代理少量接口或者类时,可以选择静态代理和JDK动态代理;CGLIB动态代理则相对更灵活,可以代理没有实现接口的类,但是由于其使用ASM字节码操作库,相对于JDK动态代理而言效率稍低。所以在选择实现AOP时,需要根据具体需求来选择适合的实现方式

三、Spring AOP实现原理

Spring AOP主要通过AOP Alliance提供的标准API来实现,核心接口包括:

  • Advisor Advisor是切面的基本类,它与Join Point和Advice关联,外部调用时只需要与Advisor打交道即可。

  • Pointcut Pointcut用于描述AOP中哪些连接点上应该织入切面代码,它可以根据被称为切入点表达式的模式来匹配连接点。

  • Advice Advice是切面的具体逻辑,包括Before、AfterReturning、Around、AfterThrowing和After等。

  • JoinPoint JoinPoint表示目标对象的某个特定连接点,可以在Advice中获取到该连接点的相关信息。

  • ProxyFactory ProxyFactory是代理工厂,它负责创建动态代理对象并将切面织入到目标对象中,同时也可以添加各种装饰器以实现不同的功能。

下面我们通过一个简单的示例来详细讲解Spring AOP实现原理。

首先定义一个Person类:

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void say() {
        System.out.println("Hello, my name is " + name + ", I'm " + age + " years old.");
    }
}

然后定义一个切面类LogAspect:

public class LogAspect {
    public void before(JoinPoint joinPoint) {
        System.out.println("Method " + joinPoint.getSignature().getName() + " is invoked before...");
    }

    public void after(JoinPoint joinPoint) {
        System.out.println("Method " + joinPoint.getSignature().getName() + " is invoked after...");
    }
}

以上示例中,LogAspect是一个切面类,其中包括before和after两个通知方法。

再定义一个测试类:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person = (Person) context.getBean("person");
        person.say();
    }
}

最后在applicationContext.xml中进行配置:

<bean id="person" class="com.example.Person">
    <constructor-arg value="Tom"></constructor-arg>
    <constructor-arg value="18"></constructor-arg>
</bean>

<bean id="logAspect" class="com.example.LogAspect"></bean>

<aop:config>
    <aop:aspect id="log" ref="logAspect">
        <aop:before method="before" pointcut="execution(* com.example.Person.say(..))"></aop:before>
        <aop:after method="after" pointcut="execution(* com.example.Person.say(..))"></aop:after>
    </aop:aspect>
</aop:config>

其中,aop:config标签用于配置AOP代理,aop:aspect标签用于配置切面信息,pointcut属性用于指定切入点,method属性用于指定通知方法。

在运行程序时,我们会发现输出了如下结果:

Method say is invoked before...
Hello, my name is Tom, I'm 18 years old.
Method say is invoked after...

以上示例中,Spring AOP实现原理主要包括以下几个步骤:

  • 根据Spring配置文件解析出Bean定义,并创建对应的Java Bean对象。

  • 在创建Bean对象时,如果该对象被声明为需要被代理,则会根据该Bean定义的所有Advisor创建动态代理对象并返回。

  • 在创建动态代理对象的过程中,Spring会通过JDK自带或者CGLIB等第三方库来创建代理类的字节码并动态加载到JVM内存当中。

  • 动态代理对象在调用目标方法之前会先调用相应的切面逻辑,然后再调用目标方法本身。

  • 目标方法执行完毕后,动态代理对象会再次调用相应的切面逻辑,以完成整个代理过程。

四、总结

Spring AOP是基于代理和动态代理技术实现的切面编程框架,通过将业务逻辑分解为多个部分来提高代码的复用性和可维护性。在实现过程中,Spring AOP主要通过Advisor、Pointcut、Advice、JoinPoint和ProxyFactory等核心接口来管理和织入切面代码。在使用Spring AOP时,我们需要定义切面类、连接点和切入点,并通过Spring配置文件中的aop:configaop:aspect标签来指定各种通知类型。

猜你喜欢

转载自blog.csdn.net/xxxzzzqqq_/article/details/130640995