1. Concept
Java dynamic proxy, in my personal understanding, is essentially creating a temporary implementation class for an interface through reflection mechanism and returning an instantiated object.
Notes:
- Can only proxy interfaces: JDK dynamic proxy can only create proxies for interfaces, not for concrete classes
- Reflection invocation: Every method call is forwarded to InvocationHandler through reflection mechanism
- Temporary class: Proxy class is generated at runtime, exists in memory, and is not persisted to disk
To put it simply, suppose Kun Kun is a big star who wants to hold a concert. Kun Kun is only responsible for singing and dancing, so setting up the stage and other tasks are definitely not done by Kun Kun himself, so we need an agent to do these things.
So we can do this:
package top.mygld.demo.test;
public class KunKun {
public String sing(String name){
System.out.println("Kun Kun sang: " + name);
return "Kun Kun finished singing";
}
public void dance(String name){
System.out.println("Kun Kun danced: " + name + " dance!");
}
}
package top.mygld.demo.test;
public class ProxyPeople {
KunKun kunKun = new KunKun();
public String sing(String name){
System.out.println("Agent sets up the venue." );
System.out.println("Agent finished setting up the venue." );
return kunKun.sing(name);
}
public void dance(String name){
System.out.println("Kun Kun danced: " + name + " dance!");
kunKun.dance(name);
}
}
Then we just need to run in the main class:
ProxyPeople proxyPeople = new ProxyPeople();
proxyPeople.sing("Just Because You're Too Beautiful");
proxyPeople.dance("Ballet");
That’s it.
But writing this way is obviously not elegant enough, so you thought of using polymorphism:
package top.mygld.demo.test;
public interface People {
String sing(String name);
void dance(String name);
}
package top.mygld.demo.test;
public class KunKun implements People{
@Override
public String sing(String name){
System.out.println("Kun Kun sang: " + name);
return "Kun Kun finished singing";
}
@Override
public void dance(String name){
System.out.println("Kun Kun danced: " + name + " dance!");
}
}
package top.mygld.demo.test;
public class ProxyPeople implements People{
KunKun kunKun = new KunKun();
@Override
public String sing(String name){
System.out.println("Agent sets up the venue." );
System.out.println("Agent finished setting up the venue." );
return kunKun.sing(name);
}
@Override
public void dance(String name){
System.out.println("Agent sets up the venue." );
System.out.println("Agent finished setting up the venue." );
kunKun.dance(name);
}
}
This is much more elegant, but it’s still not enough. This way we need to write a separate implementation class ProxyPeople
, which seems somewhat redundant, because our statements are relatively simple. Is there a way to avoid writing a separate implementation class? Dynamic proxy is designed for this purpose.
2. Proxy Class
In the java.lang.reflect
package, there is a Proxy
class, which has a newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
method that can temporarily generate implementation classes and return an object.
Among them, the first parameter of newProxyInstance
is a class loader, the second is a Class array of interfaces, and the third parameter is used to specify what the proxy object should do, for example:
package top.mygld.demo.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtil {
public static People createProxyPeople(KunKun kunKun) {
return (People) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{People.class},
new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("Agent sets up the venue." );
System.out.println("Agent finished setting up the venue." );
if (method.getName().equals("sing")) {
return method.invoke(kunKun, objects);
}
else{
method.invoke(kunKun,objects);
return null;
}
}
});
}
}
Then the KunKun
class doesn’t need to be changed, just run directly in the main class:
People proxyPeople = ProxyUtil.createProxyPeople(new KunKun());
System.out.println(proxyPeople.sing("Just Because You're Too Beautiful"));
proxyPeople.dance("Ballet");
Here’s a supplement: why is the second parameter of newProxyInstance
an array? Because a proxy object can implement multiple interfaces simultaneously, for example:
Suppose you have multiple interfaces:
interface UserService {
void saveUser(String name);
}
interface LogService {
void log(String message);
}
interface CacheService {
void cache(String key, Object value);
}
You can create a proxy object that implements all three interfaces simultaneously:
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Calling method: " + method.getName());
// Handle differently based on method name or interface type
if (method.getName().equals("saveUser")) {
System.out.println("Save user: " + args[0]);
} else if (method.getName().equals("log")) {
System.out.println("Log message: " + args[0]);
} else if (method.getName().equals("cache")) {
System.out.println("Cache data: " + args[0] + " -> " + args[1]);
}
return null;
}
};
// Implement multiple interfaces simultaneously
Object proxy = Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class, LogService.class, CacheService.class}, // Multiple interfaces
handler
);
// Can be cast to any interface for use
UserService userService = (UserService) proxy;
LogService logService = (LogService) proxy;
CacheService cacheService = (CacheService) proxy;
userService.saveUser("Zhang San");
logService.log("Operation completed");
cacheService.cache("user:1", "Zhang San");
When you pass in multiple interfaces, the JVM will generate a proxy class that implements all interfaces:
// The dynamically generated proxy class will implement all interfaces
public final class $Proxy0 extends Proxy
implements UserService, LogService, CacheService {
// Implement UserService methods
public void saveUser(String name) throws Throwable {
super.h.invoke(this, saveUserMethod, new Object[]{name});
}
// Implement LogService methods
public void log(String message) throws Throwable {
super.h.invoke(this, logMethod, new Object[]{message});
}
// Implement CacheService methods
public void cache(String key, Object value) throws Throwable {
super.h.invoke(this, cacheMethod, new Object[]{key, value});
}
}
3. Actual Use Cases
This design is common in frameworks, such as:
- Spring AOP: Proxy objects need to implement original interface + additional enhancement interfaces
- Transaction management: Business interface + transaction management interface
- Cache proxy: Business interface + cache management interface
So the array design is for supporting the flexibility of multiple interface implementation. Even if you only pass in one interface, you still need to use array form new Class[]{YourInterface.class}
.
That’s it. The above are the knowledge points related to Java dynamic proxy.