Spring代理方式之静态、动态代理(JDK和CGlib动态代理)
目录
1、代理设计模式的概念
2、静态代理
3、动态代理(JDK和CGlib动态代理)
1. JDK动态代理是基于接口的代理(Interface-based proxy)
2. CGLIB代理是基于类的代理(Class-based proxy)
⭐比较:JDK动态代理和CGLIB代理的区别
4、代理设计模式的目的和作用
小结
1、代理设计模式的概念
在Spring框架中,代理设计模式主要是将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、辅助业务功能可复用的目标。Spring框架利用代理模式来实现AOP(Aspect-Oriented Programming),通过代理对象对目标对象的方法调用进行拦截和增强,以实现横切关注点的功能。为此,spring中的代理方式可以分为静态代理和动态代理,而动态代理中又包含基于接口的JDK动态代理和基于类的CGLIB代理。
P.S. 关于AOP的相关知识点本文不做详细介绍,后面会单独写一篇介绍。
图片来源:《Spring》
2、静态代理
在Spring中,静态代理是一种代理设计模式的实现方式。它通过手动编写代理类来对目标对象进行代理,并在代理类中调用目标对象的方法。
具体来说,静态代理需要满足以下条件:
- 定义一个接口(或者父类),该接口包含了目标对象和代理对象共同的方法。
- 创建一个代理类,实现上述接口,并持有一个目标对象的引用。
- 在代理类的方法中,可以在目标对象的方法调用前后加入额外的处理逻辑,比如日志记录、权限控制等。
- 在使用时,通过创建代理对象来替代直接使用目标对象,从而实现对目标对象方法的代理。
相对于动态代理,静态代理的主要特点是代理类在编译期间就已经存在,代理类在创建时,接口以及代理类就已经确定,因此称为静态代理。静态代理的优点是简单、直观,易于理解和实现。但是缺点也比较明显,每个目标对象都需要对应一个代理类,如果目标对象的方法发生变化,代理类也需要相应地做出修改。
值得注意的是,在Spring框架中,通常更多地使用动态代理来实现AOP的功能,因为动态代理可以更灵活地生成代理对象,避免了频繁编写和维护代理类的问题。但是了解静态代理的工作原理仍然是有益的,可以更好地理解AOP的实现原理。
通过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易更换代理实现类、利 于维护。
图片来源:《Spring》
代理类 = 实现原始类相同接口 + 添加辅助功能 + 调用原始类的业务方法。
静态代理的问题:代理类数量过多,不利于项目的管理。 多个代理类的辅助功能代码冗余,修改时,维护性差。
3、动态代理(JDK和CGlib动态代理)
1. JDK动态代理是基于接口的代理(Interface-based proxy)
它要求被代理的目标对象必须实现一个或多个接口。JDK动态代理通过反射机制在运行时创建一个代理对象,代理对象实现了目标对象相同的接口,并且可以通过代理对象调用目标对象的方法。基于接口的代理要求目标对象必须实现接口,只能代理接口中定义的方法。
JDK动态代理的优点是代理对象的创建和使用都比较简单,缺点是只能代理实现了接口的目标对象。
下面是代码示例:
首先,定义一个接口,并创建实现类
public interface IUserService {
void addUser(String username, String password);
}
public class UserService implements IUserService {
@Override
public void addUser(String username, String password) {
System.out.println("Add user: " + username + ", " + password);
}
}
然后,使用JDK动态代理来生成代理对象。JDK动态代理需要目标对象实现一个接口,它利用Java反射机制在运行时动态地生成代理类。下面是JDK动态代理的实现代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkDynamicProxyExample {
public static void main(String[] args) {
IUserService target = new UserService(); // 创建目标对象
// 创建JDK动态代理
IUserService proxy = (IUserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标对象的类加载器
target.getClass().getInterfaces(), // 目标对象实现的接口
new InvocationHandler() { // 代理对象的调用处理程序
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(target, args); // 调用目标对象的方法
System.out.println("After method: " + method.getName());
return result;
}
}
);
// 调用代理对象的方法
proxy.addUser("Alice", "123456");
}
}
运行上述代码,输出结果为:
Before method: addUser
Add user: Alice, 123456
After method: addUser
可以看到,在JDK动态代理中,通过InvocationHandler来实现代理对象的调用处理程序。在调用代理对象的方法时,实际上是通过反射机制调用目标对象的方法,并在方法调用前后加入了额外的处理逻辑。
2. CGLIB代理是基于类的代理(Class-based proxy)
它可以代理没有实现接口的目标对象。CGLIB(Code Generation Library)是一个强大的第三方类库,其代理通过继承目标对象并重写其中的方法来实现代理,因此它的代理对象是目标对象的子类。当目标对象没有实现任何接口时,Spring会使用CGLIB来创建代理对象。CGLIB会生成目标对象的子类,并拦截目标对象的方法调用。基于类的代理可以代理目标对象的所有方法,包括非公开方法和静态方法。
CGLIB代理的优点是可以代理没有实现接口的目标对象,缺点是代理对象的创建和使用比较复杂。
接下来,使用CGlib动态代理来生成代理对象。CGlib动态代理不需要目标对象实现一个接口,它利用ASM框架在运行时动态地生成代理类。下面是CGlib动态代理的实现代码:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CGlibDynamicProxyExample {
public static void main(String[] args) {
UserService target = new UserService(); // 创建目标对象
// 创建CGlib动态代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = proxy.invokeSuper(obj, args); // 调用目标对象的方法
System.out.println("After method: " + method.getName());
return result;
}
});
UserService proxy = (UserService) enhancer.create();
// 调用代理对象的方法
proxy.addUser("Bob", "654321");
}
}
运行上述代码,输出结果为:
Before method: addUser
Add user: Bob, 654321
After method: addUser
可以看到,在CGlib动态代理中,通过MethodInterceptor来实现代理对象的调用处理程序。在调用代理对象的方法时,实际上是通过MethodProxy调用目标对象的方法,并在方法调用前后加入了额外的处理逻辑。
Spring框架在选择代理方式时遵循以下规则:
- 如果目标对象实现了至少一个接口,则默认使用基于接口的代理(jdk)。
- 如果目标对象没有实现任何接口,则使用基于类的代理(cglib)。
可以通过配置Spring的AOP相关选项来控制代理方式。例如,可以使用<aop:config>
元素来声明切面和通知,并通过proxy-target-class
属性来指定是否使用基于类的代理(默认为false)。
需要注意的是,基于接口的代理要求目标对象实现接口,而基于类的代理则可以代理任何类型的对象。但是,由于基于类的代理需要生成子类,因此在创建代理对象时可能会引入一些性能开销。
⭐比较:JDK动态代理和CGLIB代理的区别
1.实现方式不同:JDK动态代理是基于接口实现的,它要求被代理类必须实现一个接口,代理类实现了这个接口并且调用被代理类的方法;而CGLIB动态代理是基于继承实现的,它可以代理没有实现接口的类,它通过生成被代理类的子类来实现代理。
2.性能不同:JDK动态代理是通过反射来调用被代理类的方法,因此它的性能比CGLIB动态代理要差一些;而CGLIB动态代理是通过生成字节码来调用被代理类的方法,因此它的性能比JDK动态代理要好一些。
3.适用场景不同:JDK动态代理适用于代理接口的情况,它可以为多个接口创建代理对象,而CGLIB动态代理适用于代理类的情况,它可以为单个类创建代理对象。
4、代理设计模式的目的和作用
代理设计模式的主要目的是在不改变原有类结构的情况下,为类的功能增加一些额外的处理逻辑。在Spring中,代理设计模式的作用主要包括以下几点:
实现横切关注点(Cross-Cutting Concerns): 通过代理模式,可以将与核心业务逻辑无关的功能,如日志记录、事务管理、安全性控制等,抽取出来形成独立的横切关注点,并在需要的时候将其应用到目标对象的方法调用中。这样可以使得关注点的代码得以重用,并且更容易实现集中管理。
解耦关注点和核心业务逻辑: 代理模式可以帮助将横切关注点与核心业务逻辑进行解耦。通过代理对象对方法调用的拦截和增强,可以将横切关注点的实现与核心业务逻辑分离开来,使得各部分之间的耦合度降低,提高了代码的可维护性和可扩展性。
实现通用性和复用性: 代理模式可以使得一些通用的横切关注点的实现得以复用,而不需要在每个类中都编写重复的代码。通过定义通用的拦截器或增强逻辑,在需要的时候将其应用到多个类的方法调用中,从而实现代码的通用性和复用性。
实现动态代理: 代理模式还可以实现动态代理,即在运行时动态地创建代理对象,根据需要在目标对象的方法调用前后插入额外的处理逻辑。这种动态代理的方式使得可以更加灵活地控制代理对象的行为,同时也为AOP的实现提供了基础。
小结
总之,Spring框架中的代理设计模式旨在通过代理对象对目标对象的方法调用进行拦截和增强,以实现横切关注点的功能,同时提高代码的可维护性和可扩展性。Spring动态代理提供了JDK动态代理和CGlib动态代理两种实现方式。它们都可以利用AOP技术来实现横切关注点的功能,但是具体的实现方式有所差异。在使用时需要根据实际情况选择合适的代理方式。
参考:
JDK动态代理和CGLIB动态代理_梵晞的博客-CSDN博客
Spring代理模式 - 風栖祈鸢 - 博客园
JAVA高级基础:Spring中AOP的两种代理方式动态代理和CGLIB详解_aop代理的两种方式-CSDN博客
Spring(2)——代理和AOP - 知乎
详解Spring的两种代理方式:JDK动态代理和CGLIB动态代理 - 编程语言 - 亿速云
Spring_代理模式 - 只会干饭的杜某 - 博客园
感谢阅读,码字不易,多谢点赞!如有不当之处,欢迎反馈指出,感谢!