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(面向切面编程)和其他动态功能的核心技术。

相关推荐
无问8176 分钟前
数据结构-排序(冒泡,选择,插入,希尔,快排,归并,堆排)
java·数据结构·排序算法
m0_6312704024 分钟前
高级c语言(五)
c语言·开发语言
customer0826 分钟前
【开源免费】基于SpringBoot+Vue.JS在线文档管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
2401_8582861129 分钟前
53.【C语言】 字符函数和字符串函数(strcmp函数)
c语言·开发语言
Flying_Fish_roe44 分钟前
Spring Boot-版本兼容性问题
java·spring boot·后端
程序猿进阶1 小时前
如何在 Visual Studio Code 中反编译具有正确行号的 Java 类?
java·ide·vscode·算法·面试·职场和发展·架构
程序猿练习生1 小时前
C++速通LeetCode中等第5题-无重复字符的最长字串
开发语言·c++·leetcode
slandarer1 小时前
MATLAB | R2024b更新了哪些好玩的东西?
java·数据结构·matlab
2401_858120261 小时前
MATLAB中的无线通信系统部署和优化工具有哪些
开发语言·matlab
MATLAB滤波1 小时前
【PSINS】基于PSINS工具箱的EKF+UKF对比程序|三维定位|组合导航|MATLAB
开发语言·matlab