动态代理:
好处:
①可以用代理替代委托者,实现延迟加载。
②可以在代理的方法中织入新的逻辑,完成方法调用前后应有的处理。
③许多框架的核心就是动态代理和反射,如,Hibernate的延迟加载和Spring的AOP。
静态代理的类图:
相较于静态代理的好处:
①动态代理和静态代理在结构原则上是一致的,但将Proxy的生成交给了第三方(JDK/CGLib),这个代理将被放在内存中,减少了不必要的代码量;
②由于是运行时通过黑科技(改字节码)来实现的,因此灵活得多。
JDK实现的动态代理:
原理:
为接口A,B...生成一个代理$ProxyN,让代理出现在本应该由A/B..出现的位置,拦截对这些接口的调用,实际的请求会传给A/B..,并在A/B..上被调用。
类图:
调用流程:
①Proxy中持有对InvocationHandler的引用;
②InvocationHandler中持有对委托人(接口A/B..的实现类)的引用;
③$ProxyN上的方法调用会转给InvocationHandler(继承自Proxy的属性)的invoke方法;
④invoke方法由编码人员自己实现,一般会将请求转给委托人(method.invoke(target,args))。
关于动态代理的设计,一些常见疑问:
①为什么只能实现对接口的代理? Java不支持多继承,你已经看到,$ProxyN继承了Proxy,就算不那样,也无法像接口能代理多个。
②为什么非要继承或者实现委托类? ..代理模式的精髓,就是用一个代理来拦截对真实对象的调用,如果他们类型都不一致会很麻烦。
③加入InvacationHandler作为中继器,而不直接向$Proxy0添加委托类引用的原因?这个问题有点意思,从2个方面回答。
(1最致命的一点,$ProxyN在运行时通过字节码生成,给他添加一个没有模板的引用,我想会很麻烦;
(2代理很多情况下是为了能在方法调用前后织入一些逻辑而使用的。没有了InvocationHandler,那么你在其中实现的before,after等方法将会被放到$ProxyN中,首先他他不符合我们的审美,其次在实现了多个接口时,会逻辑混乱。因此添加一个中继器是很有必要的。
编码:
1.
创建调用中继器,实现InvocationHandler接口,并实现其invoke方法。一般在该对象上需持有委托类的实例。
2.创建代理类:使用Proxy.newProxyInstance(ClassLoader cl,Class<?>[] interfaces,InvocationHandler h)即可。
3.将此方法返回的对象强转,即可直接调用代理,代理会通过中继器将请求转给给委托对象
。
一个例子:
MyInvocationHandler持有两个接口——Hello,jlu实现类的引用,invoke方法通过调用时的方法名来实现对不同target(client/client1)的调用。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//实现自己的请求中继器
public class MyInvocationHandler implements InvocationHandler{
private Object client;//委托人
private Object client1;//委托人
MyInvocationHandler(Object c,Object c1) {
client = c;
client1 = c1;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before...");
//对带有参数...的client调用此method对象表示的底层方法
if(method.getName()=="hello")
method.invoke(client1);
if(method.getName()=="school")
method.invoke(client);
System.out.println("after...");
return null;
}
public static void main(String[] args) {
Hello hTest = new HelloImpl();
jlu sTest = new jluImpl();
MyInvocationHandler handlerTest = new MyInvocationHandler(sTest,hTest);
//创建代理类
jlu o = (jlu)Proxy.newProxyInstance(jlu.class.getClassLoader(),
new Class[]{Hello.class,jlu.class}, handlerTest);
o.school();
Hello h = (Hello)o;
h.hello();
try {
Method m = Hello.class.getMethod("hello",new Class[]{});
try {
System.out.println(666666666);
m.invoke(hTest);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (NoSuchMethodException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class HelloImpl implements Hello {
public void hello() {
System.out.println("HelloImpl");
}
}
class jluImpl implements jlu {
public void school() {
System.out.println("School");
}
}
输出:
before...
School
after...
before...
HelloImpl
after...
666666666
HelloImpl
其他动态代理:
CGLib可以实现对非接口的代理,也是通过修改字节码实现的。有兴趣的话可以搜索相关资料。