Skip to content

Java 的动态代理

发表于: 时间 13:40
加载中...

1. 概念

所谓 Java 的动态代理,我个人的理解其实就是通过反射机制,创建一个接口的临时实现类,并返回一个实例化对象。

注意:

通俗来讲,比如坤坤是一位大明星,要开演唱会,坤坤只负责唱歌和跳舞,那么布置舞台这些事情,肯定不是坤坤自己来做,所以就需要代理人来做这些。

于是我们可以这样做:

package top.mygld.demo.test;

public class KunKun {
    public String sing(String name){
        System.out.println("坤坤唱了:" + name);
        return "坤坤唱完了";
    }
    public void dance(String name){
        System.out.println("坤坤跳了:" + name + "舞蹈!");
    }
}
package top.mygld.demo.test;

public class ProxyPeople {
    KunKun kunKun = new KunKun();
    public String sing(String name){
        System.out.println("代理人布置场地。" );
        System.out.println("代理人布置场地完成。" );
        return kunKun.sing(name);
    }
    public void dance(String name){
        System.out.println("坤坤跳了:" + name + "舞蹈!");
        kunKun.dance(name);
    }
}

然后我们只需要在主类中运行:

ProxyPeople proxyPeople = new ProxyPeople();
proxyPeople.sing("只因你太美");
proxyPeople.dance("芭蕾舞");

即可。

但是这样写显然不够优雅,于是你想到了用多态的方法:

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("坤坤唱了:" + name);
        return "坤坤唱完了";
    }
    @Override
    public void dance(String name){
        System.out.println("坤坤跳了:" + name + "舞蹈!");
    }
}
package top.mygld.demo.test;

public class ProxyPeople implements People{
    KunKun kunKun = new KunKun();
    @Override
    public String sing(String name){
        System.out.println("代理人布置场地。" );
        System.out.println("代理人布置场地完成。" );
        return kunKun.sing(name);
    }
    @Override
    public void dance(String name){
        System.out.println("代理人布置场地。" );
        System.out.println("代理人布置场地完成。" );
        kunKun.dance(name);
    }
}

这样就优雅了许多,但是还不够,这样的话我们需要单独去写一个实现类 ProxyPeople,显得有些多余,因为我们的语句比较简单,那有没有不用去单独写一个实现类的方法?动态代理就是做这个的。

2. Proxy 类

java.lang.reflect 包下,有一个 Proxy 类,它有一个 newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 方法,可以临时生成实现类并返回一个对象。

其中,newProxyInstance 第一个参数是一个类加载器,第二个是一个接口的 Class 数组,第三个参数是用来指定代理对象要做什么事情,例如:

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("代理人布置场地。" );
                        System.out.println("代理人布置场地完成。" );
                        if (method.getName().equals("sing")) {
                            return method.invoke(kunKun, objects);
                        }
                        else{
                            method.invoke(kunKun,objects);
                            return null;
                        }
                    }
                });
    }
}

然后 KunKun 的类不需要改动,直接在主类中运行:

People proxyPeople = ProxyUtil.createProxyPeople(new KunKun());
System.out.println(proxyPeople.sing("只因你太美"));
proxyPeople.dance("芭蕾舞");

image.png

这里做一个补充,为什么 newProxyInstance 第二个参数是一个数组呢?因为一个代理对象可以同时实现多个接口,例如:

假设设你有多个接口:

interface UserService {
    void saveUser(String name);
}

interface LogService {
    void log(String message);
}

interface CacheService {
    void cache(String key, Object value);
}

你可以创建一个代理对象,同时实现这三个接口

InvocationHandler handler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用方法: " + method.getName());
        
        // 根据方法名或接口类型进行不同处理
        if (method.getName().equals("saveUser")) {
            System.out.println("保存用户: " + args[0]);
        } else if (method.getName().equals("log")) {
            System.out.println("记录日志: " + args[0]);
        } else if (method.getName().equals("cache")) {
            System.out.println("缓存数据: " + args[0] + " -> " + args[1]);
        }
        
        return null;
    }
};

// 同时实现多个接口
Object proxy = Proxy.newProxyInstance(
    UserService.class.getClassLoader(),
    new Class[]{UserService.class, LogService.class, CacheService.class},  // 多个接口
    handler
);

// 可以转换成任意一个接口使用
UserService userService = (UserService) proxy;
LogService logService = (LogService) proxy;
CacheService cacheService = (CacheService) proxy;

userService.saveUser("张三");
logService.log("操作完成");
cacheService.cache("user:1", "张三");

当你传入多个接口时,JVM会生成一个同时实现所有接口的代理类:

// 动态生成的代理类会实现所有接口
public final class $Proxy0 extends Proxy 
    implements UserService, LogService, CacheService {
    
    // 实现UserService的方法
    public void saveUser(String name) throws Throwable {
        super.h.invoke(this, saveUserMethod, new Object[]{name});
    }
    
    // 实现LogService的方法
    public void log(String message) throws Throwable {
        super.h.invoke(this, logMethod, new Object[]{message});
    }
    
    // 实现CacheService的方法
    public void cache(String key, Object value) throws Throwable {
        super.h.invoke(this, cacheMethod, new Object[]{key, value});
    }
}

3. 实际使用场景

这种设计在框架中很常见,比如:

  1. Spring AOP:代理对象需要实现原始接口 + 额外的增强接口
  2. 事务管理:业务接口 + 事务管理接口
  3. 缓存代理:业务接口 + 缓存管理接口

所以数组设计是为了支持多接口实现的灵活性,即使你只传入一个接口,也要用数组的形式 new Class[]{YourInterface.class}

这样就可以了,以上就是 Java 动态代理的相关知识点。


上一篇文章
线程池
下一篇文章
Java 反射复习