✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉
🍎个人主页:Leo的博客
💞当前专栏: Spring专栏
✨特色专栏: MySQL学习
🥭本文内容:Spring5学习笔记—CGlib动态代理
🖥️个人小站 :个人博客,欢迎大家访问
📚个人知识库: 知识库,欢迎大家访问
-
学习参考 :
- 讲师:孙帅老师
- 课程:孙哥说Spring5
1.前言
前面文章我们学习了关于Spring的IOC与AOP相关知识点,在此之前,我们主要学习Spring的一些核心概念,IOC和AOP等等。我们之前学习了简单了解了AOP如何借助动态字节码技术来构建动态代理类。实现动态代理的方式不止一种。本次系列文章主要介绍两种:JDK动态代理和CGlib动态代理,主要主要介绍CGlib动态代理。好了,话不多说,让我们开始吧😎😎😎。
2.JDK动态代理有什么缺陷
在上一章关于JDK动态代理的讨论中,我们确实注意到了其一个明显的限制:原始类和代理类都必须实现同一个接口,代理类方法添加额外功能并调用原始类方法。这种方式就像房屋中介:他们为租客提供服务,而租客可能并不知道背后的真正房东。但这种模式确实存在局限性,特别是当我们的系统设计中没有采用接口,或者原始类并没有实现任何接口而是直接写具体方法实现时。
那么,面对这种情况,我们怎么办?我们如何实现动态代理而不受到必须实现接口这一限制的约束?为了解决这个问题,Cglib(Code Generation Library) 应运而生。Cglib为我们提供了一种不基于接口的代理实现方式,它可以直接代理类。相对于JDK的代理方式,Cglib是基于继承父类(原始类)的,它会动态生成一个子类来覆盖原始类的方法,从而实现代理功能。这样既可以保证二者方法一致,这样既可以保证在代理类中提供新的实现,同时又调用了原始方法
这种方法提供了更大的灵活性,特别是对于那些没有接口的类。在接下来的章节中,我们将深入探讨Cglib,了解它是如何提供这种强大功能的,以及它与JDK动态代理的区别和优缺点
3.什么是CGlib动态代理
CGLib**(Code Generation Library)**是一个强大的、高性能的代码生成库,它是基于Java字节码操作的开源框架。CGLib动态代理是CGLib框架提供的一种动态代理技术。与Java标准库中的动态代理(基于接口)不同,CGLib动态代理可以代理普通的类,而不仅限于接口。
使用CGLib动态代理,我们可以在运行时创建一个目标类的子类,并在子类中拦截并增强目标类的方法调用。这种方式不需要目标类实现任何接口,而是通过继承来实现代理。
CGLib动态代理的原理是通过生成目标类的子类来实现代理。在子类中,代理类重写了目标类的方法,并在方法中添加了额外的逻辑,例如前置处理、后置处理等。当我们调用代理对象的方法时,实际上是调用了子类中重写的方法,从而实现了对目标类方法的拦截和增强。
相比于基于接口的动态代理,CGLib动态代理的性能更高,但也因为需要生成子类,所以在代理类的创建过程中会消耗一定的时间和内存。因此,如果目标类已经实现了接口,且性能要求不高,那么使用基于接口的动态代理可能更合适;而如果目标类没有实现接口,或者对性能有较高要求,那么可以选择CGLib动态代理。
需要注意的是,CGLib动态代理只能代理非final的类,因为无法生成final类的子类。另外,由于CGLib动态代理是直接操作字节码的,所以在一些特殊的环境中可能会受到限制。
4.Spring5整合CGlib动态代理开发步骤
Spring框架提供了丰富的AOP功能,可以帮助我们更好地管理代码。在Spring中,我们可以使用CGlib动态代理来实现AOP功能。下面是一个简单的示例,演示如何使用Spring5整合CGlib动态代理。
- 原始类的设计:首先,我们要设计并定义一个原始类。特别要注意的是,这里的原始类无需实现任何接口。这为那些基于纯类设计而非接口设计的应用提供了方便。
- 定义增强功能:就像在JDK动态代理中一样,您需要确定您希望添加到原始类中的额外或增强功能。
- 生成动态代理对象:使用Cglib的Enhancer对象可以为上一步中定义的原始类生成动态代理对象。为了实现此过程,确保项目中包含了Cglib的必要依赖。幸运的是这些依赖已经包含在spring-context库中,从而简化了集成过程.
4.1创建目标对象
首先,我们需要创建一个目标对象,这个目标对象将被代理。在本例中,我们创建一个UserServiceImpl类作为目标对象,它包含一个addUser方法。
public class UserServiceImpl {
public void addUser(String name) {
System.out.println("添加用户:" + name);
}
}
4.2创建切面类
接下来,我们需要创建一个切面类,这个切面类将在目标对象执行前或执行后执行一些操作。在本例中,我们创建一个LogAspect类作为切面类,它包含一个before方法,在目标对象执行前打印日志。
public class LogAspect {
public void before() {
System.out.println("开始执行方法...");
}
}
4.3配置Spring AOP
1.配置文件方式
接下来,我们需要配置Spring AOP,以便在运行时生成代理对象。在本例中,我们使用XML配置文件来配置Spring AOP。
<bean id="userService" class="org.javatop.UserService">
<property name="userServiceImpl" ref="userServiceImpl"/>
</bean>
<bean id="logAspect" class="org.javatop.LogAspect"/>
<aop:config>
<aop:aspect ref="logAspect">
<aop:before method="before" pointcut="execution(* org.javatop.UserServiceImpl.addUser(..))"/>
</aop:aspect>
</aop:config>
在上面的配置中,我们首先创建了一个UserService类,并将其注入到UserServiceImpl类中。然后,我们创建了一个LogAspect类,并将其配置为切面类。最后,我们使用aop:before标签来定义一个切入点,指定在执行UserServiceImpl类的addUser方法之前执行LogAspect类的before方法。‘
2.原始方式
public class TestCglibProxy {
private static final Logger log = LoggerFactory.getLogger(TestCglibProxy.class);
@Test
public void test1() {
// 创建原始对象
UserService userService = new UserService();
// 创建Cglib提供的Enhancer对象,用于设置好创建代理对象
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(TestCglibProxy.class.getClassLoader());
// 设置被代理类(原始父类)
enhancer.setSuperclass(UserService.class);
// 设置方法拦截器
MethodInterceptor methodInterceptor = new MethodInterceptor() {
// 等同于InvocationHandler中的invoke方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
log.debug("log before");
Object ret = method.invoke(userService, objects);
log.debug("log after");
return ret;
}
};
enhancer.setCallback(methodInterceptor);
// 创建代理对象
UserService userServiceProxy = (UserService) enhancer.create();
userServiceProxy.login("Leo", "123456");
}
}
4.4测试
最后,我们可以编写一个测试类来测试我们的代码。在本例中,我们创建一个Main类,通过Spring容器获取UserService对象,并调用其addUser方法。
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.addUser("Leo");
}
}
当我们运行Test类时,我们可以看到在执行addUser方法之前打印了一条日志。
5.最后总结
JDK动态代理以接口为核心,通过Proxy.newInstance()方法为我们提供了创建代理对象的能力,要求原始类必须实现某个接口。而Cglib动态代理则更为灵活,不受接口的限制。通过Enhancer.create(),它直接生成原始类的子类,并在其中嵌入额外功能,从而实现代理。当然我们也可以通过设置让Spring在创建的时候只使用Cglib而不使用JDK
Spring AOP聪明地整合了这两种机制,为开发者提供了无缝的体验。它默认采用JDK动态代理,但当遇到没有实现接口的原始类时,会智能切换到Cglib动态代理。
本文介绍了如何使用Spring5整合CGlib动态代理实现AOP功能,并给出了具体的案例。通过使用动态代理,我们可以在运行时生成代理对象,从而实现对目标对象的增强。在实际开发中,动态代理是一个非常重要的概念,可以帮助我们更好地管理代码,提高代码的可维护性和可扩展性。
6.参考文献
- https://www.itheima.com/news/20210525/165219.html
- https://blog.csdn.net/luoyoub/article/details/80101376
- https://spring.io/
- https://blog.csdn.net/qq_43266723/article/details/133488696
7.总结
以上便是本文的全部内容,本人才疏学浅,文章有什么错误的地方,欢迎大佬们批评指正!我是Leo,一个在互联网行业的小白,立志成为更好的自己。
如果你想了解更多关于Leo,可以关注公众号-程序员Leo,后面文章会首先同步至公众号。