Spring AOP(Aspect Oriented Programming)是基于面向切面编程的一种框架,它可以对多个目标对象进行统一管理,并在不修改原有业务代码的前提下增加额外功能。实现过程中主要依赖于代理(Proxy)和动态代理(Dynamic Proxy)技术,本文将详细分析Spring AOP的实现原理。
AOP通过将一个应用程序分解为重要的部分,然后将那些跨越这些部分的关注点分离出来,从而帮助我们更好地定义我们的业务逻辑。其中,核心概念包括:
切面(Aspect) 切面是织入到目标类中的功能代码,对于Java来说可以是方法、构造器或者类的特定点上的代码。切面可以包括前置通知、后置通知、环绕通知、异常通知和最终通知等,具体可以根据业务需求自定义实现。
连接点(Join Point) 连接点表示切面将会被织入到目标对象的哪个位置,对于Java来说通常是某个方法的执行点或者某个构造器的创建点。
切入点(Pointcut) 切入点是一组连接点的集合,它定义了哪些连接点需要被织入到目标对象中。
通知(Advice) 通知包括各种类型的切面代码,如前置通知、后置通知、环绕通知、异常通知和最终通知等。
织入(Weaving) 织入是将切面代码编织到目标对象中的过程,可以发生在编译时、加载时或者运行时。Spring AOP以动态代理技术为主要方式进行织入。
引入(Introduction) 引入允许我们向现有的类添加新方法和属性,这些方法和属性并不在原先的类定义中,而是在引入的接口中定义。
在介绍Spring AOP实现原理前,我们先了解实现AOP的三种方式:静态代理,使用JDK的Proxy类实现动态代理,使用CGLIB实现动态代理。
为了更好的理解Spring AOP的实现原理和AOP使用动态代理的方式,我们可以深入了解三种方式:静态代理、JDK动态代理和CGLIB动态代理。
静态代理是指在编译时就已经确定需要被代理的类和方法,代理类在编译时就已经存在,其优点是运行效率高,缺点是不灵活,只能代理预先定义好的类和方法。下面以一个简单的示例说明静态代理的原理:
首先定义一个接口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()方法。
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接口的类。
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主要通过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中进行配置:
复制代码
其中,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:config和aop:aspect标签来指定各种通知类型。