Spring Boot从2.x版本开始,默认使用CGLIB作为动态代理实现。这是因为在Spring Boot 2.x中,为了解决使用JDK动态代理可能导致的类型转换异常问题,选择了CGLIB作为默认的代理方式。
Spring Boot项目代码示例,展示了如何使用CGLIB和JDK动态代理。
效果展示
项目结构
cglib-learn
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─tyron
│ │ │ └─cgliblearn
│ │ │ ├─ CglibLearnApplication.java
│ │ │ ├─ aspect
│ │ │ │ └── LoggingAspect.java
│ │ │ ├─ config
│ │ │ │ └── AppConfig.java
│ │ │ └── service
│ │ │ ├── UserService.java
│ │ │ └── UserServiceInterface.java
│ │ └── resources
│ │ └── application.properties
└── pom.xml
1、cglib代理
1.1、 pom.xml
确保您的项目包含 Spring Boot 和 AspectJ 依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.4</version></parent><groupId>com.tyron</groupId><artifactId>cglib-learn</artifactId><version>0.0.1-SNAPSHOT</version><name>cglib-learn</name><description>cglib-learn</description><url/><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency></dependencies>
</project>
1.2、 CglibLearnApplication.java
package com.tyron.cgliblearn;import com.tyron.cgliblearn.service.UserService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;@SpringBootApplication
public class CglibLearnApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(CglibLearnApplication.class, args);// 使用cglib代理UserService userService = context.getBean(UserService.class);// 使用jdk代理// UserServiceInterface userService = context.getBean(UserServiceInterface.class);System.out.println("Proxy class: " + userService.getClass());userService.sayHello();}
}
1.3、 UserService.java
package com.tyron.cgliblearn.service;import org.springframework.stereotype.Service;@Service
public class UserService {public void sayHello() {System.out.println("Hello, World!");}
}
1.4、 LoggingAspect.java
package com.tyron.cgliblearn.aspect;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Aspect
@Component
public class LoggingAspect {@Before("execution(* com.tyron.cgliblearn.service.*.*(..))")public void logBefore() {System.out.println("Method is about to be called.");}
}
1.5、运行项目(cglib代理)
下图打印中可看出,在SpringBoot 2.5.4 版本中,默认使用 cglib 动态代理,后面我们再尝试使用jdk动态代理。
2、jdk代理
2.1、application.properties
在application.properties
中配置使用JDK动态代理
spring.aop.proxy-target-class=false
2.2、UserServiceInterface.java
如果希望使用JDK动态代理,可以创建一个接口和实现类:
package com.tyron.cgliblearn.service;public interface UserServiceInterface {public void sayHello();
}
2.3、UserServiceImpl.java
package com.tyron.cgliblearn.service;import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserServiceInterface {@Overridepublic void sayHello() {System.out.println("Hello, World!");}
}
2.4、运行项目(jdk代理)
3、cglib代理和jdk代理的区别
在Spring Boot中,CGLIB代理和JDK代理的主要区别在于它们的实现方式和适用场景。以下是两者的详细对比:
特性 | CGLIB代理 | JDK代理 |
---|---|---|
实现方式 | 通过生成目标类的子类来创建代理对象 | 通过实现目标类的接口来创建代理对象 |
适用场景 | 可以代理没有实现接口的类 | 只能代理实现了接口的类 |
性能 | 通常比JDK代理快,因为它使用了FastClass机制 | 可能稍慢一些,因为它依赖于反射 |
限制 | 不能代理final类或final方法 | 不能代理没有接口的类 |
默认配置 | Spring Boot默认使用CGLIB代理 | 需要显式配置使用JDK代理 |
为什么Spring Boot默认使用CGLIB代理?
Spring Boot默认使用CGLIB代理是因为CGLIB提供了更广泛的代理支持,可以代理没有实现接口的类。这对于提供更灵活的AOP配置和更高的性能是有益的。此外,CGLIB代理在某些情况下可能比JDK代理更快,因为它使用了FastClass机制来直接调用目标类的方法,而不是使用反射****。
实际应用中的优缺点
- CGLIB代理的优点:可以代理没有实现接口的类,通常性能更好。
- CGLIB代理的缺点:不能代理final类或final方法,可能需要额外的配置来启用。
- JDK代理的优点:最小化依赖关系,减少依赖意味着简化开发和维护
- JDK代理的缺点:只能代理实现了接口的类,可能性能稍逊于CGLIB代理。
总结
在选择CGLIB代理还是JDK代理时,你需要考虑目标类是否实现了接口,以及你对性能的要求。如果你的应用程序中的类没有实现接口,或者你需要更高的性能,那么CGLIB代理可能是更好的选择。如果你的应用程序中的类已经实现了接口,或者你更倾向于使用JDK提供的功能,那么JDK代理可能更适合你。