DEV Community

Anh Trần Tuấn
Anh Trần Tuấn

Posted on • Originally published at tuanh.net on

Dynamic Proxy in Spring: A Comprehensive Guide with Examples and Demos

1. What is a Dynamic Proxy?

A dynamic proxy in Java is an object that implements one or more interfaces and delegates method calls to an underlying object or performs additional operations. Unlike static proxies, which are created at compile-time, dynamic proxies are generated at runtime.

In Spring, dynamic proxies are primarily used in two ways:

JDK Dynamic Proxies : These proxies are created using the java.lang.reflect.Proxy class and can only proxy interfaces.

CGLIB Proxies : These proxies are created using the CGLIB library and can proxy classes without requiring an interface.

2. How Spring Uses Dynamic Proxies

Spring uses dynamic proxies to provide features such as:

+ Transaction Management : Spring wraps your service objects in proxies that manage transactions automatically.

+ AOP (Aspect-Oriented Programming): Aspects like logging, security, or performance monitoring can be applied through proxies.

+ Lazy Initialization : Proxies can be used to defer the initialization of beans until they are actually needed.

2.1 Transaction Management

Spring’s transaction management is a core feature that uses dynamic proxies to handle transactions declaratively. This allows you to manage transactions without having to write boilerplate code.

How It Works:

+ Proxy Creation : When a service bean is annotated with @Transactional, Spring automatically creates a proxy around it. This proxy is responsible for managing transactions.

+ Transaction Handling : When a method annotated with @Transactional is called, the proxy starts a transaction before invoking the actual method. If the method completes successfully, the transaction is committed. If an exception occurs, the transaction is rolled back.

Implications:

+ Declarative Transactions : By using annotations or XML configuration, you can manage transactions declaratively rather than programmatically.

+ Transaction Boundaries : Proxies ensure that all transactional operations are encapsulated within a single transaction, adhering to the principles of atomicity and consistency.

+ Limitations :

+ Proxy Limitations : Transaction management through proxies has limitations with methods called internally within the same bean. Calls to other methods within the same class won't be proxied, potentially leading to unexpected behavior if transaction management is not considered.

+ Performance Overhead : The use of proxies adds a layer of indirection, which can introduce performance overhead. However, this is generally negligible compared to the benefits of declarative transaction management.

2.2 Aspect-Oriented Programming (AOP)/div>

Spring AOP uses dynamic proxies to apply cross-cutting concerns such as logging, security, and performance monitoring in a modular way, separating these concerns from business logic.

How It Works: + Aspect Creation : Aspects are classes annotated with @aspect, which define advice (actions) that should be applied at specific join points (method executions, object instantiations, etc.). + Proxy Creation : Spring uses proxies to apply aspects to target beans. These proxies delegate method calls to the original bean and then apply the aspect logic before or after the actual method execution.

Implications: + Separation of Concerns : AOP allows you to separate cross-cutting concerns from your business logic, making the code cleaner and more maintainable. Proxy Types: + JDK Dynamic Proxies : Used when the target class implements interfaces. Only interface-based methods can be proxied. + CGLIB Proxies : Used when the target class does not implement interfaces. CGLIB creates a subclass of the target class to apply aspects. + Performance Considerations : While AOP provides flexibility, it introduces additional complexity and potential performance overhead due to the use of proxies. The overhead is typically small but should be considered in performance-sensitive applications.

Dynamic Proxy Handling: + Aspect Application : The proxy intercepts method calls to apply the @Before advice, which logs information before the createUser method execution.

2.3 Lazy Initialization

Lazy initialization is a design pattern in which a bean is not created until it is actually needed. Spring uses dynamic proxies to achieve this behavior, delaying the creation of beans to improve application startup time and reduce memory usage.

How It Works: + Proxy Creation : When a bean is marked with @lazy, Spring creates a proxy for it. This proxy only initializes the actual bean when a method on the bean is first invoked. + Proxy Handling : The proxy manages the lifecycle of the target bean, ensuring that it is instantiated and initialized only when necessary.

Implications: + Improved Startup Time : By deferring the creation of beans until they are needed, the application startup time can be significantly reduced. + Memory Efficiency : Lazy initialization can help reduce the memory footprint of the application, especially if there are many beans that may not be used in all application scenarios. + Complexity : Using lazy initialization can introduce complexity in terms of debugging and managing bean dependencies. Additionally, if the lazy-initialized bean is accessed from multiple threads, concurrency issues need to be carefully managed.

Dynamic Proxy Handling: + Bean Initialization : The ExpensiveService bean is only instantiated when performAction is called. The proxy handles this lazy initialization.

3. Implementing a Dynamic Proxy in Spring

3.1 Using JDK Dynamic Proxy

Let's start by creating a simple service interface and implementation:

public interface UserService {
    void createUser(String username);
}

public class UserServiceImpl implements UserService {
    @Override
    public void createUser(String username) {
        System.out.println("User created: " + username);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, let's create a dynamic proxy for the UserService interface using the JDK dynamic proxy:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class UserServiceProxy implements InvocationHandler {

    private final UserService target;

    public UserServiceProxy(UserService target) {
        this.target = target;
    }

    @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;
    }

    public static UserService createProxy(UserService target) {
        return (UserService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new UserServiceProxy(target)
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, UserServiceProxy implements InvocationHandler and intercepts method calls to the target object (UserServiceImpl). The createProxy method creates a proxy instance.

You can now use this proxy in your application:

public class Application {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService proxy = UserServiceProxy.createProxy(userService);

        proxy.createUser("JohnDoe");
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Before method: createUser
User created: JohnDoe
After method: createUser
Enter fullscreen mode Exit fullscreen mode

3.2 Using CGLIB Proxy

If your service doesn't implement an interface, or you prefer to proxy classes directly, you can use CGLIB. Spring uses CGLIB proxies if the target class does not have any interfaces.

First, add the CGLIB dependency if you're not using Spring Boot:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Create a proxy using CGLIB:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class UserServiceCGLIBProxy implements MethodInterceptor {

    private final Object target;

    public UserServiceCGLIBProxy(Object target) {
        this.target = target;
    }

    @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;
    }

    public static Object createProxy(Object target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(new UserServiceCGLIBProxy(target));
        return enhancer.create();
    }
}
Enter fullscreen mode Exit fullscreen mode

Usage is similar to JDK dynamic proxies:

public class Application {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        UserServiceImpl proxy = (UserServiceImpl) UserServiceCGLIBProxy.createProxy(userService);

        proxy.createUser("JaneDoe");
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Before method: createUser
User created: JaneDoe
After method: createUser
Enter fullscreen mode Exit fullscreen mode

4. Dynamic Proxy in Spring AOP

Spring AOP (Aspect-Oriented Programming) uses dynamic proxies to apply cross-cutting concerns like logging, transaction management, and security.

4.1 Example of AOP with Dynamic Proxy

Let's create a simple aspect for logging:

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.example.service.UserService.createUser(..))")
    public void logBefore() {
        System.out.println("Before method: LoggingAspect is working!");
    }
}
Enter fullscreen mode Exit fullscreen mode

Spring will automatically create a proxy for the UserService bean and apply the logging aspect.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public void createUser(String username) {
        userRepository.save(new User(username));
        System.out.println("User created: " + username);
    }
}
Enter fullscreen mode Exit fullscreen mode

When you call createUser, the output will include the logging aspect:

Output:

Before method: LoggingAspect is working!
User created: JohnDoe
Enter fullscreen mode Exit fullscreen mode

5. Demo: Dynamic Proxy in a Spring Boot Application

Let's create a Spring Boot application to demonstrate dynamic proxies in a real-world scenario.

5.1 Setting Up the Project

Create a Spring Boot project with the following dependencies: + Spring Web + Spring AOP + Spring Data JPA + H2 Database (for simplicity)

5.2 Implementing the Service and Aspect

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Transactional
    public void placeOrder(String product, int quantity) {
        System.out.println("Placing order for " + quantity + " " + product);
        // Assume this interacts with a repository to save the order
    }
}

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.example.OrderService.placeOrder(..))")
    public void logBefore() {
        System.out.println("Before method: LoggingAspect");
    }
}
Enter fullscreen mode Exit fullscreen mode

5.3 Running the Application

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DynamicProxyDemoApplication implements CommandLineRunner {

    @Autowired
    private OrderService orderService;

    public static void main(String[] args) {
        SpringApplication.run(DynamicProxyDemoApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        orderService.placeOrder("Laptop", 2);
    }
}
Enter fullscreen mode Exit fullscreen mode

Expected Output:

Before method: LoggingAspect
Placing order for 2 Laptop
Enter fullscreen mode Exit fullscreen mode

6. Conclusion

Dynamic proxies in Spring provide a flexible way to implement cross-cutting concerns without intruding into your business logic. Through the use of JDK dynamic proxies and CGLIB, Spring can automatically wrap your beans with proxy objects that manage transactions, apply security, log actions, and much more.

In this article, we explored how dynamic proxies work in Spring, how to implement them using JDK and CGLIB, and how Spring AOP leverages dynamic proxies. We also created a Spring Boot application to demonstrate the use of dynamic proxies in a real-world scenario.

Understanding dynamic proxies is essential for mastering Spring, especially when dealing with complex enterprise applications. By using these proxies effectively, you can write cleaner, more maintainable code that separates business logic from cross-cutting concerns.

Read posts more at : Dynamic Proxy in Spring: A Comprehensive Guide with Examples and Demos

Top comments (0)