前面我们学习了关于 Spring AOP 的使用,那么今天这篇文章,我们将深入理解 Spring AOP 的原理,也就是 Spring 是如何实现 AOP 的。
Spring AOP 是基于动态代理来实现 AOP 的,那么什么是代理呢?这里的代理其实是一种模式——代理模式。
代理模式是一种设计模式,它为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的主要作用是扩展目标对象的功能,例如在目标对象的某个方法执行前后可以增加一些自定义的操作。此外,代理模式还可以结合享元模式以减少存储器用量,例如创建一个复杂对象及多个代理者,每个代理者会引用到原本的复杂对象,作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。
这是使用代理之前:
使用代理之后:
我们生活中最常见的可以体现代理模式的就是房屋中介了,当我们想要租房子的时候,如果我们想要直接找到房东的话,会很难且麻烦,因为我们不知道哪一间房子正在出租,如果一个一个问的话就需要很多时间,所以这个时候我们的选择往往就是寻找房屋中介,告诉中介的诉求了之后,房屋中介就会根据我们的需求去寻找合适的房主,这样就极大的方便了我们租房的过程。
代理模式中的主要角色:
代理模式可以分为静态代理和动态代理。静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活且麻烦。而动态代理则是通过在运行时创建一个子类实例来实现的,可以更加灵活地实现代理。
在实际开发中,代理模式可以帮助降低主要业务与辅助业务的耦合度,同时提高辅助业务代码的重用度。
静态代理是指代理类在程序运行前就已经定义好,其与目标类的关系在程序运行前就已经确立。在静态代理中,代理类和目标类都实现相同的接口,代理类通过调用目标类的方法来实现接口中定义的业务逻辑,同时可以在调用前后增加额外的操作。
根据租房子的案例来讲就是:在用户租房子之前 ,我中介和房东就已经协商好了,中介就知道你房东要出租房子还是出售房子,然后当客户有相同的需求的话,我中介就可以直接联系客户和房东进行对接。通过代码展示就是这样的:
定义接口(房东要做的事,也就是中介要做的主要的事):
public interface HouseSubject {
void rentHouse();
}
实现接口(房东出租房子):
public class RealHouseSubject implements HouseSubject {
@Override
public void rentHouse() {
System.out.println("我是房东,我要出租房子...");
}
}
代理(中介帮房东出租房子):
public class HouseProxy implements HouseSubject {
//将被代理对象声明为成员变量
private HouseSubject subject;
public HouseProxy(RealHouseSubject realHouseSubject) {
this.subject = realHouseSubject;
}
@Override
public void rentHouse() {
System.out.println("我是中介,开始代理");
subject.rentHouse();
System.out.println("我是中介,结束代理");
}
}
客户租房子:
public class Main {
public static void main(String[] args) {
RealHouseSubject subject = new RealHouseSubject();
HouseProxy proxy = new HouseProxy(subject);
proxy.rentHouse();
}
}
通过这个过程,客户就通过中介成功找到了房东租房子,但是呢?由于这是静态代理,如果我们的房东之前已经和中介商量好了要出租房子,但是这时房东又想出租+出售房子,那么这时候房东就需要和中介再进行沟通,通过我们代码的体现就是这样:
接口:
public interface HouseSubject {
void rentHouse();
void saleHouse();
}
被代理对象:
public class RealHouseSubject implements HouseSubject {
@Override
public void rentHouse() {
System.out.println("我是房东,我要出租房子...");
}
@Override
public void saleHouse() {
System.out.println("我是房东,我要出售房子");
}
}
代理对象:
public class HouseProxy implements HouseSubject {
//将被代理对象声明为成员变量
private HouseSubject subject;
public HouseProxy(RealHouseSubject realHouseSubject) {
this.subject = realHouseSubject;
}
@Override
public void rentHouse() {
System.out.println("我是中介,开始代理");
subject.rentHouse();
System.out.println("我是中介,结束代理");
}
@Override
public void saleHouse() {
System.out.println("我是中介,开始代理");
subject.saleHouse();
System.out.println("我是中介,结束代理");
}
}
客户需要买房子:
public class Main {
public static void main(String[] args) {
RealHouseSubject subject = new RealHouseSubject();
HouseProxy proxy = new HouseProxy(subject);
proxy.saleHouse();
}
}
由此可以看出,当我们需要修改业务的时候,既需要修改接口,也需要修改RealSubject 类,还需要修改代理类,那么是否有一种方法,可以使用一种代理类来实现变动业务的代理呢?是可以的,这时就需要用到我们的代理类了。
动态代理是一种在运行时动态生成代理对象的技术,它通过在程序运行时创建代理对象来间接访问原始对象,并在访问前后执行额外的操作。动态代理通常用于在不修改原始对象的情况下增强对象的功能,例如实现横切关注点(cross-cutting concerns)如日志记录、性能监控、事务管理等。
在动态代理中,代理类在程序运行期间创建代理对象,而不是在代码中事先定义好。代理对象中的方法是对目标对象方法的增强方法,可以在方法执行前后插入额外的逻辑。动态代理能够根据需要在运行时动态生成代理对象,因此可以更加灵活地扩展对象的功能。
常见的动态代理有JDK代理和CGLib代理,分别可以针对实现了接口的类代理以及没有实现接口的类代理。
JDK动态代理实现的关键步骤如下:
(?′?`?)定义接口
public interface HouseSubject {
void rentHouse();
void saleHouse();
}
(?′?`?)创建目标对象
public class RealHouseSubject implements HouseSubject {
@Override
public void rentHouse() {
System.out.println("我是房东,我要出租房子...");
}
@Override
public void saleHouse() {
System.out.println("我是房东,我要出售房子");
}
}
(?′?`?)实现 InvocationHandler 接口
public class JDKInvocationHandler implements InvocationHandler {
//目标对象/被代理对象
private Object target;
public JDKInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是中介,开始代理...");
//通过反射调?被代理类的?法
Object retVal = method.invoke(target,args);
System.out.println("我是中介,结束代理...");
return retVal;
}
}
(?′?`?)创建代理对象并使用
public class Main {
public static void main(String[] args) {
HouseSubject target = new RealHouseSubject();
//创建?个代理类:通过被代理类、被代理实现的接?、?法调?处理器来创建
HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{HouseSubject.class},
new JDKInvocationHandler(target)
);
proxy.saleHouse();
}
}
InvocationHandler 接?是Java动态代理的关键接?之?,它定义了?个单??法 invoke() ,?于处理被代理对象的?法调?:
public interface InvocationHandler {
/**
* 参数说明
* proxy:代理对象
* method:代理对象需要实现的?法,即其中需要重写的?法
* args:method所对应?法的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
通过实现 InvocationHandler 接?,可以对被代理对象的?法进?功能增强
Proxy 类中使?频率最?的?法是: newProxyInstance() ,这个?法主要?来?成?个代理对象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException
{}
CGLib是另一个流行的Java动态代理框架,它扩展了Java的反射机制,并提供了更高级的功能。CGLib可以用来创建具有完全功能的代理类,并且能够代理没有接口的类。
CGLib动态代理实现的关键步骤如下:
与JDK动态代理相比,CGLib提供了更多的灵活性和功能。它支持代理没有接口的类,并且能够动态地修改和扩展类的行为。CGLib还支持方法级别的拦截和过滤,可以更加精细地控制目标方法的调用。因此,CGLib在许多场景中得到了广泛应用,例如AOP编程、测试框架、远程调用等。
(?′?`?)添加依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
(?′?`?)?定义MethodInterceptor(?法拦截器,并且实现MethodInterceptor接?)
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLibInterceptor implements MethodInterceptor {
private Object target;
public CGLibInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("我是中介,开始代理...");
Object retVal = methodProxy.invoke(target,objects);
System.out.println("我是中介,结束代理...");
return retVal;
}
}
(?′?`?)创建类并使用
public class Main {
public static void main(String[] args) {
HouseSubject target = new RealHouseSubject();
HouseSubject proxy = (HouseSubject) Enhancer.create(target.getClass(),new CGLibInterceptor(target));
proxy.saleHouse();
}
}
总结一下:
1. Spring AOP 是如何实现的?
2. 动态代理是如何实现的?
3. Spring使用的是 JDK 动态代理和 CGLib 代理中的哪一个?
4. 什么时候使用 JDK 代理,什么时候使用 CGLib 代理?
在Spring中,JDK动态代理和CGLib动态代理的选择取决于目标对象的类型。
5. JDK 代理和 CGLib 代理有什么区别?