Dynamic proxy allows us to add additional behavior to objects without modifying the source code. In SpringBoot applications, dynamic proxy is widely used to implement cross-cutting concerns such as transaction management, caching, security control, and logging.
1. JDK dynamic proxy: Java native proxy solution
Implementation principle
JDK dynamic proxy is a proxy mechanism provided by the Java standard library, based onClass and
InvocationHandler
Interface implementation. It dynamically creates proxy instances of interfaces at runtime by reflection.
Core code examples
public class JdkDynamicProxyDemo { interface UserService { void save(User user); User find(Long id); } // Implementation class static class UserServiceImpl implements UserService { @Override public void save(User user) { ("Save User: " + ()); } @Override public User find(Long id) { ("Find User ID: " + id); return new User(id, "user" + id); } } // Call the processor static class LoggingInvocationHandler implements InvocationHandler { private final Object target; public LoggingInvocationHandler(Object target) { = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ("Before: " + ()); long startTime = (); Object result = (target, args); long endTime = (); ("After: " + () + ", time consuming: " + (endTime - startTime) + "ms"); return result; } } public static void main(String[] args) { // Create target object UserService userService = new UserServiceImpl(); // Create proxy object UserService proxy = (UserService) ( ().getClassLoader(), ().getInterfaces(), new LoggingInvocationHandler(userService) ); // Call proxy method (new User(1L, "Zhang San")); User user = (2L); } }
advantage
- JDK standard library comes with: No need to introduce additional dependencies, reducing project size
- Generate code is simple: Agent logic is centralized in InvocationHandler, easy to understand and maintain
- Relatively stable performance: In the version after JDK 8, the performance has been significantly improved
limitation
- Only proxy interfaces: The proxy class must implement the interface, and the proxy class cannot be proxyed
- Reflection call overhead: Each method call needs to pass the reflection mechanism, which has certain performance losses
- Unable to intercept final method: Cannot proxy the method of final modification
Applications in Spring
In Spring, when the bean implements an interface, the JDK dynamic proxy is used by default. This can be modified by configuration:
@EnableAspectJAutoProxy(proxyTargetClass = false) // The default value is false, indicating that the JDK dynamic proxy is preferred
2. CGLIB proxy: a powerful proxy based on bytecode
Implementation principle
CGLIB (Code Generation Library) is a powerful high-performance bytecode generation library. It implements proxy by inheriting the proxy class to generate subclasses. Spring integrates CGLIB directly into the framework starting from version 3.2.
Core code examples
public class CglibProxyDemo { // Classes that do not need to implement interfaces static class UserService { public void save(User user) { ("Save User: " + ()); } public User find(Long id) { ("Find User ID: " + id); return new User(id, "user" + id); } } // CGLIB method interceptor static class LoggingMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { ("Before: " + ()); long startTime = (); // Call the original method Object result = (obj, args); long endTime = (); ("After: " + () + ", time consuming: " + (endTime - startTime) + "ms"); return result; } } public static void main(String[] args) { // Create a CGLIB proxy Enhancer enhancer = new Enhancer(); (); (new LoggingMethodInterceptor()); // Create proxy object UserService proxy = (UserService) (); // Call proxy method (new User(1L, "Zhang San")); User user = (2L); } }
advantage
- Can proxy classes: The target class does not require the implementation of interfaces, and the application scenarios are more extensive
- High performance: Method calling performance is better than JDK proxy by generating bytecode rather than reflective calls
- Rich features: Supports multiple callback types, such as LazyLoader, Dispatcher, etc.
- Integrate into Spring: Spring framework has built-in CGLIB, no additional dependencies are required
limitation
- Cannot proxy final classes and methods: Due to the use of inheritance mechanism, it is impossible to proxy the class or method that final modified
- Constructor call: The constructor of the target class will be called when generating the proxy object, which may lead to unexpected behavior
- Increased complexity: The generated bytecode is complex and difficult to debug
- Sensitive to Java versions: There may be compatibility issues between different Java versions
Applications in Spring
In Spring, when the bean does not implement the interface or is configuredproxyTargetClass=true
When using CGLIB proxy:
@EnableAspectJAutoProxy(proxyTargetClass = true) // Force use of CGLIB proxy
3. ByteBuddy: Modern bytecode operation library
Implementation principle
ByteBuddy is a relatively new library of bytecode generation and operation, with a more modern design and a more API-friendly one. It can create and modify Java classes without understanding the underlying JVM instruction set.
Core code examples
public class ByteBuddyProxyDemo { interface UserService { void save(User user); User find(Long id); } static class UserServiceImpl implements UserService { @Override public void save(User user) { ("Save User: " + ()); } @Override public User find(Long id) { ("Find User ID: " + id); return new User(id, "user" + id); } } static class LoggingInterceptor { @RuntimeType public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable, @AllArguments Object[] args) throws Exception { ("Before: " + ()); long startTime = (); Object result = (); long endTime = (); ("After: " + () + ", time consuming: " + (endTime - startTime) + "ms"); return result; } } public static void main(String[] args) throws Exception { UserService userService = new UserServiceImpl(); // Create ByteBuddy Agent UserService proxy = new ByteBuddy() .subclass() .method(()) .intercept((new LoggingInterceptor())) .make() .load(()) .getLoaded() .getDeclaredConstructor() .newInstance(); // Call proxy method (new User(1L, "Zhang San")); User user = (2L); } }
advantage
- Smooth API: Provides a chain programming style, and the code is more readable
- Excellent performance: Performance better than CGLIB and JDK proxy in multiple benchmarks
- Type safety: API design focuses more on type safety and reduces runtime errors
- Support new Java features: Better support for new features such as Java 9+ module system
- Rich features: Supports method redirection, field access, constructor interception and other scenarios
limitation
- Extra dependencies: Additional dependency libraries need to be introduced
- Learning curve: Although the API is smooth, it has many concepts and has certain learning costs
- Not very integrated in Spring: Custom configuration is required to replace the default proxy mechanism in Spring
Applications in Spring
Although ByteBuddy is not Spring's default proxy implementation, it can be customizedProxyFactory
To integrate:
@Configuration public class ByteBuddyProxyConfig { @Bean public AopConfigurer byteBuddyAopConfigurer() { return new AopConfigurer() { @Override public void configureProxyCreator(Object bean, String beanName) { // Configure ByteBuddy as a proxy creator // Slightly implement details } }; } }
4. Javassist: an easier-to-use bytecode editing library
Implementation principle
Javassist is an open source Java bytecode operation library that provides two levels of API: source code level and bytecode level. It allows developers to edit bytecode directly with simple Java syntax without having to dig deep into JVM specifications.
Core code examples
public class JavassistProxyDemo { interface UserService { void save(User user); User find(Long id); } static class UserServiceImpl implements UserService { @Override public void save(User user) { ("Save User: " + ()); } @Override public User find(Long id) { ("Find User ID: " + id); return new User(id, "user" + id); } } public static void main(String[] args) throws Exception { ProxyFactory factory = new ProxyFactory(); // Set up the proxy interface (new Class[] { }); // Create method filter MethodHandler handler = new MethodHandler() { private UserService target = new UserServiceImpl(); @Override public Object invoke(Object self, Method method, Method proceed, Object[] args) throws Throwable { ("Before: " + ()); long startTime = (); Object result = (target, args); long endTime = (); ("After: " + () + ", time consuming: " + (endTime - startTime) + "ms"); return result; } }; // Create proxy object UserService proxy = (UserService) (new Class<?>[0], new Object[0], handler); // Call proxy method (new User(1L, "Zhang San")); User user = (2L); } }
advantage
- Source code-level API: You can operate bytecode instruction set without understanding the bytecode in the form of Java code
- Lightweight library: Smaller size than other bytecode libraries
- Comprehensive functions: Supports creating classes, modifying classes, dynamically compiling Java source code, etc.
- Good performance: The generated proxy code performance is close to CGLIB
- Long-term maintenance: The library has a long history, stable and reliable
limitation
- The API is not intuitive enough: Some API designs are relatively old and are not as smooth as ByteBuddy
- Insufficient documentation: There are fewer documents and examples than other libraries
- Memory consumption: It may consume more memory when operating a large number of classes
Applications in Spring
Javassist is not Spring's default proxy implementation, but some Spring-based frameworks use it to implement dynamic proxying, such as Hibernate:
// Custom Javassist agent factory examplepublic class JavassistProxyFactory implements ProxyFactory { @Override public Object createProxy(Object target, Interceptor interceptor) { // Javassist proxy implementation // ... } }
5. AspectJ: Complete AOP Solution
Implementation principle
AspectJ is a complete AOP framework. Unlike the previous dynamic proxy scheme, it provides two ways:
- Compile time weaving: directly modify the bytecode when compiling the source code
- Weaving in during loading: Modify bytecode when the class is loaded into the JVM
AspectJ has a special facet language and is more powerful than Spring AOP.
Core code examples
AspectJ aspect definition:
@Aspect public class LoggingAspect { @Before("execution(* .*(..))") public void logBefore(JoinPoint joinPoint) { ("Before: " + ().getName()); } @Around("execution(* .*(..))") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { ("Before: " + ().getName()); long startTime = (); Object result = (); long endTime = (); ("After: " + ().getName() + ", time consuming: " + (endTime - startTime) + "ms"); return result; } }
Spring configuration AspectJ:
@Configuration @EnableAspectJAutoProxy public class AspectJConfig { @Bean public LoggingAspect loggingAspect() { return new LoggingAspect(); } }
Compile-time weaving configuration (maven):
<plugin> <groupId></groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.14.0</version> <configuration> <complianceLevel>11</complianceLevel> <source>11</source> <target>11</target> <aspectLibraries> <aspectLibrary> <groupId></groupId> <artifactId>spring-aspects</artifactId> </aspectLibrary> </aspectLibraries> </configuration> <executions> <execution> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin>
advantage
- The most comprehensive functions: Supports almost all AOP scenarios, including constructors, field access, exceptions, etc.
- Best performance: No runtime overhead when weaving, performance is close to native code
- Full language support: Have special facet language and grammar, strong expression skills
- More flexible entry points: You can define facets for interfaces, implementation classes, constructors, fields, etc.
limitation
- High complexity: The learning curve is steep, you need to master the AspectJ syntax
- The construction process changes: A special compiler or class loader is required
- Debugging difficulty increases: Modified bytecode may be difficult to debug
Applications in Spring
Spring can use AspectJ in the following ways:
proxy-target-class mode: A proxy generated using Spring AOP but CGLIB
@EnableAspectJAutoProxy(proxyTargetClass = true)
LTW (Load-Time Weaving) mode: Weaving in when class loads
@EnableLoadTimeWeaving
Compile time weaving: Requires configuration of AspectJ compiler
Comparison of usage scenarios and selection suggestions
Agent implementation | The most suitable scenario | Not applicable |
---|---|---|
JDK dynamic proxy | Simple interface-based proxy, lightweight application | No class implementing interface, performance-sensitive scenarios |
CGLIB | Classes that do not implement interfaces need to take into account both performance and convenience | Final class/method, high security environment |
ByteBuddy | Modern projects, focusing on performance optimization, complex proxy logic | Simple project that pursues minimal dependencies |
Javassist | Complex scenarios that require dynamic generation/modification of classes | API design sensitive projects for beginners |
AspectJ | Enterprise-level applications, performance-critical scenarios, complex AOP requirements | Simple project, fast prototype, learning cost-sensitive |
This is the article about the implementation solutions of 5 dynamic proxy solutions in SpringBoot. For more relevant SpringBoot dynamic proxy content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!