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