Java:动态代理

Java:动态代理

什么是代理

代理模式 是一种设计模式,它为其他对象提供了一种代理以控制对这个对象的访问。代理对象通常包装实际的目标对象,以提供一些附加的功能(如延迟加载、访问控制、日志记录等)。我们一般可以使用装饰器模式来包装实际对象,从而实现代理模式,比如说:

java 复制代码
//具体提供的服务接口
interface HelloService {
    void sayHello();
}
//服务的具体实现类
class ServiceImpl implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("Hello World!");
    }
}
//具体实现类的代理类--用来控制具体的服务访问和资源回收,日志打印等增强功能
class ServiceProxy implements HelloService{
    private HelloService target;
    ServiceProxy(HelloService target) {
        this.target = target;
    }

    @Override
    public void sayHello() {
        try {
            long currentTimes = System.currentTimeMillis();
            System.out.println("Invoke Time is:" + currentTimes);
            target.sayHello();
            Thread.sleep(2000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        } finally {
            recycleRes();
        }
    }
	//回收资源的方法
    private void recycleRes()  {
        System.out.println("回收Over");
    }
}

代理模式的优点

  • 职责清晰:
    代理模式将真实对象的实现与代理对象的控制逻辑分开,使得每个对象都承担单一职责,符合单一职责原则。
  • 控制访问:
    代理可以控制对目标对象的访问,这在需要控制权限或在访问前后添加额外操作时非常有用。例如,在远程代理中,可以控制客户端与服务器之间的通信。
  • 增强功能:
    在不修改目标对象的情况下,代理模式可以在目标对象的访问前后添加额外的逻辑。例如,缓存代理可以缓存对象的返回结果以减少重复计算;日志代理可以记录方法的调用。
  • 延迟实例化:
    虚拟代理可以在真正需要目标对象时才创建它,从而节省内存和性能。例如,在图形应用程序中,如果图像对象较大,可以在首次需要显示时才进行加载。
  • 灵活性和可扩展性:
    代理模式提供了一种灵活的方式来扩展对象的功能。通过使用不同类型的代理,开发者可以轻松切换或扩展目标对象的行为。
  • 保护目标对象:
    保护代理可以控制对目标对象的访问权限,防止不适当的操作。这在多用户环境中尤其有用。

动态代理

代理的类型具体又可以分为静态代理和动态代理,所谓静态代理,意思就是代理对象是在编译期就已经生成了,无法变更。而动态代理的意思就是代理对象是在运行期动态生成的。

在Java中,反射模块里提供了一个接口InvocationHandler来帮助我们实现动态代理,一些知名的开源库,比如说Retrofit,也是通过动态代理来实现具体的方法调用的:

java 复制代码
public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
            service.getClassLoader(),
            new Class<?>[] {service},
            new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                  throws Throwable {
                // If the method is a method from Object then defer to normal invocation.
                if (method.getDeclaringClass() == Object.class) {
                //如果外部调用的是 Object 中声明的方法的话则直接调用
                //例如 toString()、hashCode() 等方法
                  return method.invoke(this, args);
                }
                args = args != null ? args : emptyArgs;
                //根据 method 是否默认方法来决定如何调用
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }

我们也可以改造之前的例子实现一个动态代理的例子:

java 复制代码
public static void main(String[] args) {
        HelloService service = new ServiceImpl();
        //通过Proxy.newProxyInstance方法生成具体的动态代理对象
        HelloService proxy = (HelloService) Proxy.newProxyInstance(
                //被代理的接口的类加载器
                service.getClass().getClassLoader(),
                //被代理的接口类型
                service.getClass().getInterfaces(),
                //具体实现了InvocationHandler接口的动态代理类
                new DynamicServiceProxy(service)
        );
        //通过动态代理对象访问
        proxy.sayHello();
    }
}

interface HelloService {
    void sayHello();
}

class ServiceImpl implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("Hello World!");
    }
}

class DynamicServiceProxy implements InvocationHandler {
    private HelloService target;
    public static String resource1 = "资源1";
    public static String resource2 = "资源2";

    public DynamicServiceProxy(HelloService target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("拦截方法:"+method.getName());
        System.out.println("执行时间:" + System.currentTimeMillis());
        String res = (String) getRes();
        Object resp = null;
        if (res.equals(resource1)) {
            resp = method.invoke(target,args);
        } else {
            System.out.println(resource2+"不可用!!!");
        }
        System.out.println("获取资源:"+res);
        System.out.println("执行完毕");
        return resp;
    }

    public Object getRes() {
        Random random = new Random();
        int ran = random.nextInt(10);
        return ran <= 5 ? (Object) resource1 : (Object) resource2;
    }
}

我们把 getRes() 作为一个模拟线上获取资源的方法,当获取到资源一时执行被代理类的原有逻辑,当获取到资源二时,我们就完全拦截原有的逻辑,而去执行我们自己的逻辑。这样就相当于是可以动态的选择方法的实际执行逻辑。

使用场景

这种场景在直觉上显然就很适合鉴权访问的场景,先在先上验证当前用户是否有相应的权限,如果确定有相应权限在执行访问的逻辑,反之则拦截并提示无权限。

我们先用静态代理代理的方法实现需求:

java 复制代码
interface ConnectionInterface {
    public String getResource(String Id);
}

//静态代理管理访问权限
class ConnectionProxy implements ConnectionInterface {
    protected static Set<String> whiteList = Set.of("Android","IOS","Web","Server");
    private ConnectionInterface target;
    public ConnectionProxy(ConnectionInterface target) {
        this.target = target;
    }

    @Override
    public String getResource(String Id) {
        if (!whiteList.contains(Id)) {
            System.out.println("没有权限!");
            return "Error";
        };
        return target.getResource(Id);
    }
}

class ConnectionService implements ConnectionInterface {
    static Random random = new Random();
    static Map<String,Integer> resourceMap = Map.of(
            "Android",random.nextInt(),
            "IOS", random.nextInt(),
            "Web",random.nextInt(),
            "Server", random.nextInt()
        );
    @Override
    public String getResource(String Id) {
        return resourceMap.get(Id).toString();
    }
}

如果我们后续有一个新的接口,或者说接口升级的话,我们还需要为这个新接口新实现一个代理类,而用动态代理就可以用一个动态代理类管理这两个逻辑:

java 复制代码
interface ConnectionInterface {
    public String getResource(String Id);
}

interface ConnectionInterfaceV2 {
    public String getResourceV2(String Id);
}

class ConnectionService implements ConnectionInterface,ConnectionInterfaceV2 {
    static Random random = new Random();
    static Map<String,Integer> resourceMap = Map.of(
            "Android",random.nextInt(),
            "IOS", random.nextInt(),
            "Web",random.nextInt(),
            "Server", random.nextInt()
        );
    @Override
    public String getResource(String Id) {
        System.out.println("执行具体逻辑...");
        return resourceMap.get(Id).toString();
    }

    @Override
    public String getResourceV2(String Id) {
        System.out.println("执行具体逻辑V2...");
        return resourceMap.get(Id).toString();
    }
}

class Dynamic_Proxy implements InvocationHandler {
    protected static Set<String> whiteList = Set.of("Android","IOS","Web","Server");
    private Object target;
    public Dynamic_Proxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("拦截方法:"+method.getName());
        String param = (String) args[0];
        //如果同时实现两个接口,优先走新逻辑
        if (target instanceof ConnectionInterfaceV2 && target instanceof ConnectionInterface) {
            if (whiteList.contains(param)) {
                System.out.println("有权限v2");
                return method.invoke(target,param);
            }
            System.out.println("无权限v2");
            return (Object) "Default V2";
        }
        //否则再走老逻辑
        if (target instanceof ConnectionInterface) {
            if (whiteList.contains(param)) {
                System.out.println("有权限 V1");
                return method.invoke(target,param);
            } else {
                System.out.println("无权限 V1");
                return (Object) "Error V1";
            }
        }

        return (Object) "Default_NotFound";
    }
}

这样我们相当于是减少了无用的代码量,实现了代码的逻辑复用。调用时我们可以这样使用:

java 复制代码
public static void main(String[] args) {
    ConnectionInterface service = new ConnectionService();
    ConnectionInterfaceV2 service2 = new ConnectionService();
    ConnectionInterface v1 = (ConnectionInterface)Proxy.newProxyInstance(
         service.getClass().getClassLoader(),
         service.getClass().getInterfaces(),
         new Dynamic_Proxy(service)
    );
    System.out.println(v1.getResource("Android"));
    ConnectionInterfaceV2 v2 = (ConnectionInterfaceV2) Proxy.newProxyInstance(
            service2.getClass().getClassLoader(),
            service2.getClass().getInterfaces(),
            new Dynamic_Proxy(service2)
    );
    System.out.println(v2.getResourceV2("Windows"));
}

这样我们轻松用一个代理类代理了两个对象。

具体的原理

这种在程序运行时动态修改方法入口的效果具体是基于Java的动态分派机制来实现的,即一个对象的方法调用总是在被调用时才真正确定其方法入口,对应到一个对象中,每个对象都有其的一个虚方法表,每次调用的时候就从这个虚方法表中查找具体的方法入口。

第二个机制就是基于Java中自己提供的反射框架,即在运行时可以动态生成方法对象,即Method对象,然后用Method对象作为逻辑,被代理类作为对象来执行。

使用 Proxy 类生成代理对象,InvocationHandler 接口来处理方法调用,是实现 AOP(面向切面编程)和其他动态功能的核心技术。

相关推荐
system_error_x17 分钟前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
酷炫码神29 分钟前
Day 10|Pinia 状态管理:把散落在各组件的数据,收进一个全局仓库
android·开发语言·javascript
驭渊的小故事1 小时前
Spring AI Alibaba 实战:从maven依赖治理到声明式工具调用
java·人工智能·spring
Elastic 中国社区官方博客1 小时前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn1881 小时前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn2 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su2 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
传奇开心果编程3 小时前
【Rust入门练中学】第5课:引用与借用
开发语言·rust·引用·编程语言理论·通用编程语言·借用
qetfw3 小时前
Bash 变量:export、位置参数与默认值展开验证
linux·开发语言·chrome·bash
电子云与长程纠缠3 小时前
UE5魂系游戏开发教程05
开发语言·学习·游戏·ui·ue5