1.代理原理
利用动态代理可以在运行时创建一个实现了一组给定接口的新类。假设有一个表示接口的Class对象,它的确切类型在编译时无法知道(Proxy.newInstance()方法的ClassLoader参数为null的情况)。所以要想构造一个实现这些接口的类,需要使用newInstance()方法或反射找出这个类的构造器。但是,不能实例化一个接口,需要在程序处于运行状态时定义一个新类。
代理机制可以满足上述需求,代理类可以在运行时创建全新的类,这样的代理类能够实现指定的接口。
2.代理场景
代理这种设计模式是通过不直接访问被代理对象的方式,而访问代理对象的方法。这个就好比商户---->明星经纪人(代理)---->明星这种模式。我们可以不通过直接与明星对话的情况下,而通过明星经纪人(代理)与其产生间接对话。
设计模式中有一个设计原则是开闭原则,是说对修改关闭对扩展开放,我们在工作中有时会接手很多前人的代码,里面代码逻辑让人摸不着头脑,这时就很难去下手修改代码,那么这时我们就可以通过代理对类进行增强。
3.代理实现
在java中规定,要想产生一个对象的代理对象,那么这个对象必须要有一个接口。
public interface Person {
String sing(String name);
String dance(String name);
}
public class LiuDeHua implements Person {
public String sing(String name){
System.out.println("刘德华唱"+name+"歌!!");
return "歌唱完了,谢谢大家!";
}
public String dance(String name){
System.out.println("刘德华跳"+name+"舞!!");
return "舞跳完了,多谢各位观众!";
}
}
要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。这个方法需要三个参数:
ClassLoader loader:指定一个动态加载代理类的类加载器(可为null)
Class<?>[] interfaces:指明被代理类实现的接口,之后我们通过拼接字节码生成的类才能知道调用哪些方法。
InvocationHandler h:这是一个方法委托类,我们通过代理调用被代理类的方法时,就可以将方法名和方法参数都委托给这个委托类。
public class LiuDeHuaProxy {
//设计一个类变量记住代理类要代理的目标对象
private Person ldh = new LiuDeHua();
/**
* 设计一个方法生成代理对象
* @Method: getProxy
* @Description: 这个方法返回刘德华的代理对象:Person person = LiuDeHuaProxy.getProxy();//得到一个代理对象
* @return 某个对象的代理对象
*/
public Person getProxy() {
//使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)返回某个对象的代理对象
return (Person) Proxy.newProxyInstance(LiuDeHuaProxy.class
.getClassLoader(), ldh.getClass().getInterfaces(),
new InvocationHandler() {
/**
* InvocationHandler接口只定义了一个invoke方法,因此对于这样的接口,我们不用单独去定义一个类来实现该接口,
* 而是直接使用一个匿名内部类来实现该接口,new InvocationHandler() {}就是针对InvocationHandler接口的匿名实现类
*/
/**
* 在invoke方法编码指定返回的代理对象干的工作
* proxy : 把代理对象自己传递进来
* method:把代理对象当前调用的方法传递进来
* args:把方法参数传递进来
*
* 当调用代理对象的person.sing("冰雨");或者 person.dance("江南style");方法时,
* 实际上执行的都是invoke方法里面的代码,
* 因此我们可以在invoke方法中使用method.getName()就可以知道当前调用的是代理对象的哪个方法
*/
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
//如果调用的是代理对象的sing方法
if (method.getName().equals("sing")) {
System.out.println("我是他的经纪人,要找他唱歌得先给十万块钱!!");
//已经给钱了,经纪人自己不会唱歌,就只能找刘德华去唱歌!
return method.invoke(ldh, args); //代理对象调用真实目标对象的sing方法去处理用户请求
}
//如果调用的是代理对象的dance方法
if (method.getName().equals("dance")) {
System.out.println("我是他的经纪人,要找他跳舞得先给二十万块钱!!");
//已经给钱了,经纪人自己不会唱歌,就只能找刘德华去跳舞!
return method.invoke(ldh, args);//代理对象调用真实目标对象的dance方法去处理用户请求
}
return null;
}
});
}
}
public class ProxyTest {
public static void main(String[] args) {
LiuDeHuaProxy proxy = new LiuDeHuaProxy();
//获得代理对象
Person p = proxy.getProxy();
//调用代理对象的sing方法
String retValue = p.sing("冰雨");
System.out.println(retValue);
//调用代理对象的dance方法
String value = p.dance("江南style");
System.out.println(value);
}
}
对于特定的类的加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次newProInstance方法的话,那么只能得到同一个类得两个对象。
可以通过调用Proxy类中的isProxyClass方法检测一个特定的Class对象是否代表一个代理类。
4.动态代理和静态代理的区别
静态代理:
public class Proxy implements Person {
private LiuDeHua mLDH;
public Proxy(LiudeHua ldh){
this.mLDH = ldh;
}
public String sing(String name){
mLDH.sing(name);
}
public String dance(String name){
mLDH.dance(name);
}
}
根据加载被代理类的时机不同,将代理分为静态代理和动态代理。如果在代码编译时就确定了被代理的类是哪一个,那么就可以直接使用静态代理;如果不能确定,那么可以使用类的动态加载机制,在代码运行期间加载被代理的类这就是动态代理,比如RPC框架和Spring AOP机制。
对于静态代理方式代理类需要实现和被代理类相同的接口;对于动态代理代理类则不需要显示地实现被代理类所实现的接口。
网友评论