Java Reflection & Dynamic Proxy

Reflection

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

API

Getting Class Objects

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 Through 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 classes, convenient for unit testing
1
2
3
Field nameField = personClass.getDeclaredField("name");
nameField.setAccessible(true); // If field is private, need to set accessibility
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

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

  1. Define an interface UserService with method A that I want to be proxied
  2. Create a proxy class UserServiceProxy that implements the InvocationHandler interface, which means overriding the invoke() method. Specifically, add the logic I want before or after calling method A. invoke calls method A through reflection.
  3. UserServiceImpl class implements the UserService interface methods.
  4. In the main program, first create a UserServiceImpl class object, then use the newProxyInstance method to create a UserService class object, this is a proxy. The method parameters include: UserService’s class loader, UserService’s interface, and a UserServiceProxy class object.
  5. userService can then call method A to implement the logic we want
 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 call");
        Object result = method.invoke(target, args);
        System.out.println("Logic after method call");
        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 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 passed object
Vehicle motorcycleProxy = (Vehicle) Proxy.newProxyInstance(
        motorcycle.getClass().getClassLoader(),
        motorcycle.getClass().getInterfaces(),
        motorcycleHandler);
motorcycleProxy.drive();

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

CGLib Proxy

The Spring framework chooses which proxy method to use: if there’s an interface, use JDK Proxy; if there’s no interface, use CGLib.
For example, if there’s a class that doesn’t implement an interface and you want to add some functionality to its methods, you can use CGLIB.

  1. Define implementation class
  2. Define an Interceptor class that implements the MethodInterceptor interface, implementing the logic we want to be proxied.
  3. Use Enhancer to set its parent class as the target class, then set the callback (method interceptor). This way CGLIB can dynamically generate a subclass of the target class as the proxy class at runtime.
ScenarioJDK ProxyCGLib Proxy
Proxy Generation SpeedFaster (directly generates interface proxy class)Slower (needs to generate subclass and enhance bytecode)
Method Call SpeedSlower (reflection call)Faster (directly calls subclass method)
Memory UsageLow (lightweight proxy class)Higher (generated 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("Save 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 parent class 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();