Java:动态代理
什么是代理
代理模式
是一种设计模式,它为其他对象提供了一种代理以控制对这个对象的访问。代理对象通常包装实际的目标对象,以提供一些附加的功能(如延迟加载、访问控制、日志记录等)。我们一般可以使用装饰器模式
来包装实际对象,从而实现代理模式,比如说:
//具体提供的服务接口
interface HelloService {void sayHello();
}
//服务的具体实现类
class ServiceImpl implements HelloService {@Overridepublic void sayHello() {System.out.println("Hello World!");}
}
//具体实现类的代理类--用来控制具体的服务访问和资源回收,日志打印等增强功能
class ServiceProxy implements HelloService{private HelloService target;ServiceProxy(HelloService target) {this.target = target;}@Overridepublic 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,也是通过动态代理来实现具体的方法调用的:
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];@Overridepublic @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);}});}
我们也可以改造之前的例子实现一个动态代理的例子:
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 {@Overridepublic 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;}@Overridepublic 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()
作为一个模拟线上获取资源的方法,当获取到资源一时执行被代理类的原有逻辑,当获取到资源二时,我们就完全拦截原有的逻辑,而去执行我们自己的逻辑。这样就相当于是可以动态的选择方法的实际执行逻辑
。
使用场景
这种场景在直觉上显然就很适合鉴权访问的场景,先在先上验证当前用户是否有相应的权限,如果确定有相应权限在执行访问的逻辑,反之则拦截并提示无权限。
我们先用静态代理代理的方法实现需求:
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;}@Overridepublic 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());@Overridepublic String getResource(String Id) {return resourceMap.get(Id).toString();}
}
如果我们后续有一个新的接口,或者说接口升级的话,我们还需要为这个新接口新实现一个代理类,而用动态代理就可以用一个动态代理类管理这两个逻辑:
interface ConnectionInterface {public String getResource(String Id);
}interface ConnectionInterfaceV2 {public String getResourceV2(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;}@Overridepublic String getResource(String Id) {if (!whiteList.contains(Id)) {System.out.println("没有权限!");return "Error";};return target.getResource(Id);}
}class ConnectionProxyV2 implements ConnectionInterfaceV2 {protected static Set<String> whiteList = Set.of("Android","IOS","Web","Server");private ConnectionInterface target;public ConnectionProxyV2(ConnectionInterface target) {this.target = target;}@Overridepublic String getResourceV2(String Id) {System.out.println("新逻辑V2");if (!whiteList.contains(Id)) {System.out.println("没有权限!");return "Error";}return target.getResource(Id);}
}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;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("拦截方法:"+method.getName());String param = (String) args[0];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";}}if (target instanceof ConnectionInterfaceV2) {if (whiteList.contains(param)) {System.out.println("有权限v2");return method.invoke(target,param);}System.out.println("无权限v2");}return (Object) "Default";}
}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());@Overridepublic String getResource(String Id) {return resourceMap.get(Id).toString();}
}
这样我们相当于是减少了无用的代码量,实现了代码的逻辑复用。调用时我们可以这样使用:
public class DynamicPro {public static void main(String[] args) {ConnectionInterface service = new ConnectionProxy(new ConnectionService());ConnectionInterfaceV2 service2 = new ConnectionProxyV2(new ConnectionService());ConnectionInterface v1 = (ConnectionInterface)Proxy.newProxyInstance(service.getClass().getClassLoader(),service.getClass().getInterfaces(),new Dynamic_Proxy((Object) service));v1.getResource("Windows");ConnectionInterfaceV2 v2 = (ConnectionInterfaceV2) Proxy.newProxyInstance(service2.getClass().getClassLoader(),service2.getClass().getInterfaces(),new Dynamic_Proxy((Object) service2));v2.getResourceV2("Windows");}
}
这样我们轻松用一个代理类代理了两个对象。
具体的原理
这种在程序运行时动态修改方法入口的效果具体是基于Java的动态分派
机制来实现的,即一个对象的方法调用总是在被调用时才真正确定其方法入口,对应到一个对象中,每个对象都有其的一个虚方法表,每次调用的时候就从这个虚方法表中查找具体的方法入口。
第二个机制就是基于Java中自己提供的反射框架
,即在运行时可以动态生成方法对象,即Method对象,然后用Method对象作为逻辑,被代理类作为对象来执行。
使用 Proxy 类生成代理对象,InvocationHandler 接口来处理方法调用,是实现 AOP(面向切面编程)和其他动态功能的核心技术。