Java Reflection & Dynamic Proxy

Reflection

Reflection is Java’s ability to dynamically obtain class information (such as class name, methods, fields, constructors, etc.) and manipulate classes at runtime.

API

Getting a Class Object

1
2
3
4
5
6
7
String str="test";
Class<?> clazz = String.class;

String str = "test";
Class<?> clazz = str.getClass();

Class<?> clazz = Class.forName("java.lang.String");

Creating Objects via Reflection

1
2
3
4
5
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.getDeclaredConstructor().newInstance();

Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("Alice", 25);

Getting Fields

getDeclaredField("fieldName")

Advantages

  1. Can access private objects/fields of a class, convenient for unit testing.
1
2
3
Field nameField = personClass.getDeclaredField("name");
nameField.setAccessible(true); // If the field is private, accessibility needs to be set
System.out.println("Name before: " + nameField.get(person));
  1. Dynamic proxy, improves code reusability.

  2. Spring framework.

Relationship between Generics, Reflection, and Type Inference

Dynamic Proxy

Advantage: Dynamically generates proxy classes, no need to manually define multiple proxy classes.

  1. Define an interface UserService, in which I define method A that I want to be proxied.
  2. Create a proxy class UserServiceProxy, which implements the InvocationHandler interface, meaning it overrides the invoke() method. Specifically, it adds the desired logic before or after calling method A. Inside invoke, method A is called via reflection.
  3. The UserServiceImpl class implements the methods of the UserService interface.
  4. In the main program, first create a UserServiceImpl class object, then use the newProxyInstance method to create a UserService class object, which is a proxy. The parameters of the method are: the class loader of UserService, the interface of UserService, and a UserServiceProxy class object.
  5. userService can then call method A to achieve the desired logic.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class UserServiceProxy implements InvocationHandler {

    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Logic before method invocation");
        Object result = method.invoke(target, args);
        System.out.println("Logic after method invocation");
        return result;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        UserServiceProxy proxy = new UserServiceProxy(userService);

        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                proxy);

        userServiceProxy.addUser();
    }
}

vs Static Proxy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Car car = new Car();
InvocationHandler carHandler = new DynamicProxyHandler(car); // Dynamically generate proxy class based on the passed object
Vehicle carProxy = (Vehicle) Proxy.newProxyInstance(
        car.getClass().getClassLoader(),
        car.getClass().getInterfaces(),
        carHandler);
carProxy.drive();

Motorcycle motorcycle = new Motorcycle();
InvocationHandler motorcycleHandler = new DynamicProxyHandler(motorcycle); // Dynamically generate proxy class based on the passed object
Vehicle motorcycleProxy = (Vehicle) Proxy.newProxyInstance(
        motorcycle.getClass().getClassLoader(),
        motorcycle.getClass().getInterfaces(),
        motorcycleHandler);
motorcycleProxy.drive();

Static proxy requires implementing a CarProxy and a Motorcycle proxy class separately for the Vehicle interface, and passing a Car and Motor object respectively.

CGLib Proxy

When the Spring framework chooses which proxy method to use, it uses JDK Proxy if there is an interface, and CGLib if there is no interface. For example, if there is a class that does not implement an interface, and you want to add some functionality to its methods, you can use CGLIB.

  1. Define the implementation class.
  2. Define an Interceptor class that implements the MethodInterceptor interface, and implement the desired proxy logic within it.
  3. Use Enhancer to set its superclass to the target class, and then set the callback, which is the method interceptor. This allows CGLIB to dynamically generate a subclass of the target class as the proxy class at runtime.
ScenarioJDK ProxyCGLib Proxy
Proxy generation speedFaster (directly generates interface proxy)Slower (needs to generate subclass and enhance bytecode)
Method invocation speedSlower (reflection invocation)Faster (directly calls subclass method)
Memory usageLow (lightweight proxy class)Higher (generates subclass occupies more metaspace)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Target class (no need to implement interface)
public class UserService {
    public void save() {
        System.out.println("Saving user");
    }
}

// Method interceptor
public class MyMethodInterceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Pre-processing");
        Object result = proxy.invokeSuper(obj, args); // Call superclass method
        System.out.println("Post-processing");
        return result;
    }
}

// Create proxy object
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new MyMethodInterceptor());
UserService proxy = (UserService) enhancer.create();
proxy.save();