java代理
java的三种代理模式
1、什么是代理
代理模式是一种设计模式,简单说即是在不改变源码的情况下,实现对目标对象的功能扩展。
2、为什么要使用代理
我们为什么要引入java的代理,除了当前类能够提供的功能外,我们还需要补充一些其他功能。
最容易想到的情况就是权限过滤,我有一个类做某项业务,但是由于安全原因只有某些用户才可以调用这个类,此时我们就可以做一个该类的代理类,要求所有请求必须通过该代理类,由该代理类做权限判断,如果安全则调用实际类的业务开始处理。
可能有人说为什么我要多加个代理类?我只需要在原来类的方法里面加上权限过滤不就完了吗?
在程序设计中有一个类的单一性原则问题,这个原则很简单,就是每个类的功能尽可能单一。为什么要单一,因为只有功能单一这个类被改动的可能性才会最小。
如果你将权限判断放在当前类里面,当前这个类就既要负责自己本身业务逻辑、又要负责权限判断,那么就有两个导致该类变化的原因,现在如果权限规则一旦变化,这个类就必需得改,显然这不是一个好的设计。
3、静态代理
比如有个歌手对象叫Singer,这个对象有一个唱歌方法叫sing()
1 1 public class Singer{ 2 2 public void sing(){ 3 3 System.out.println("我是周杰伦"); 4 4 } 5 5 }
假如你希望,通过你的某种方式生产出来的歌手对象,在唱歌前后还要想观众问好和答谢,也即对目标对象Singer的sing方法进行功能扩展。
1 1 public void sing(){ 2 2 System.out.println("大家好"); 3 3 System.out.println("我是周杰伦"); 4 4 System.out.println("谢谢大家"); 5 5 }
但是往往你又不能直接对源代码进行修改,可能是你希望原来的对象还保持原来的样子,又或许你提供的只是一个可插拔的插件,甚至你有可能都不知道你要对哪个目标对象进行扩展。这时就需要用到java的代理模式了。
1 public interface Singer { 2 void sing(); 3 } 4 5 /** 6 * 目标对象实现了某一接口 7 */ 8 public class Singer implements Singer{ 9 public void sing(){ 10 System.out.println("我是周杰伦"); 11 } 12 } 13 14 /** 15 * 代理对象和目标对象实现相同的接口 16 */ 17 public class SingerProxy implements Singer{ 18 // 接收目标对象,以便调用sing方法 19 private Singer target; 20 public UserDaoProxy(Singer target){ 21 this.target=target; 22 } 23 // 对目标对象的sing方法进行功能扩展 24 public void sing() { 25 System.out.println("大家好"); 26 target.sing(); 27 System.out.println("谢谢大家"); 28 } 29 }
测试类:
1 /** 2 * 测试类 3 */ 4 public class Test { 5 public static void main(String[] args) { 6 //目标对象 7 Singer target = new Singer(); 8 //代理对象 9 Singer proxy = new SingerProxy(target); 10 //执行的是代理的方法 11 proxy.sing(); 12 } 13 }
总结:其实这里做的事情无非就是,创建一个代理类SingerProxy,继承了Singer接口并实现了其中的方法。只不过这种实现特意包含了目标对象的方法,正是这种特征使得看起来像是“扩展”了目标对象的方法。假使代理对象中只是简单地对sing方法做了另一种实现而没有包含目标对象的方法,也就不能算作代理模式了。所以这里的包含是关键。
缺点:1、这种实现方式很直观也很简单,但其缺点是代理对象必须提前写出(在编译期就已经知道了代理对象),如果接口层发生了变化,代理对象的代码也要进行维护。如果能在运行时动态地写出代理对象,不但减少了一大批代理类的代码,也少了不断维护的烦恼,不过运行时的效率必定受到影响。这种方式就是接下来的动态代理。
2、因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
4、动态代理(JDK代理)
跟静态代理的前提一样,依然是对Singer对象进行扩展
/**
* 歌手接口
*/
1 public interface Singer { 2 void sing(); 3 } 4
目标对象1
1 /** 2 * 接口实现 3 * 目标对象 4 */ 5 public class Singer01 implements Singer { 6 @Override 7 public void sing() { 8 System.out.println("我是刘德华"); 9 } 10 }
目标对象2
1 /** 2 * 接口实现 3 * 目标对象 4 */ 5 public class Singer02 implements Singer { 6 7 @Override 8 public void sing() { 9 System.out.println("我是周杰伦"); 10 } 11 }
代理工厂类
1 /** 2 * 代理工厂类 3 */ 4 public class ProxyFactory{ 5 6 //维护一个目标对象 7 private Object target; 8 9 public ProxyFactory(Object target) { 10 this.target = target; 11 } 12 13 //给目标对象生成代理对象 14 public Object getProxyInstance(){ 15 return Proxy.newProxyInstance( 16 target.getClass().getClassLoader(), 17 target.getClass().getInterfaces(), 18 new InvocationHandler() { 19 @Override 20 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 21 System.out.println("大家好"); 22 //执行目标对象方法 23 Object returnValue = method.invoke(target,args); 24 System.out.println("谢谢大家"); 25 return returnValue; 26 } 27 } 28 ); 29 } 30 }
测试类
1 /** 2 * 测试类 3 */ 4 public class App { 5 public static void main(String[] args){ 6 Singer01 target = new Singer01(); 7 Singer02 target02 = new Singer02(); 8 9 //给目标对象创建代理对象 10 Singer proxy = (Singer) new ProxyFactory(target).getProxyInstance(); 11 Singer proxy02 = (Singer) new ProxyFactory(target02).getProxyInstance(); 12 //执行方法 13 proxy.sing(); 14 System.out.println("----------------------------------"); 15 proxy02.sing(); 16 } 17 }
测试结果:
大家好 我是刘德华 谢谢大家 ---------------------------------- 大家好 我是周杰伦 谢谢大家
JDK中生成代理对象的API
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
ClassLoader loader,
:指定当前目标对象使用类加载器,获取加载器的方法是固定的Class<?>[] interfaces,
:目标对象实现的接口的类型,使用泛型方式确认类型InvocationHandler h
:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
总结:JDK动态代理解决了静态代理中需要创建多个代理类的问题。
缺点:可以看出静态代理和JDK代理有一个共同的缺点,就是目标对象必须实现一个或多个接口,假如没有,则可以使用Cglib代理。
5、CGLIB代理
前提条件:
- 需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-3.2.5.jar
- 目标类不能为final
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
1 /** 2 * 目标对象,没有实现任何接口 3 */ 4 public class Singer{ 5 6 public void sing() { 7 System.out.println("我是周杰伦"); 8 } 9 }
1 /** 2 * Cglib子类代理工厂 3 */ 4 public class ProxyFactory implements MethodInterceptor{ 5 // 维护目标对象 6 private Object target; 7 8 public ProxyFactory(Object target) { 9 this.target = target; 10 } 11 12 // 给目标对象创建一个代理对象 13 public Object getProxyInstance(){ 14 //1.工具类 15 Enhancer en = new Enhancer(); 16 //2.设置父类 17 en.setSuperclass(target.getClass()); 18 //3.设置回调函数 19 en.setCallback(this); 20 //4.创建子类(代理对象) 21 return en.create(); 22 } 23 24 @Override 25 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 26 System.out.println("向观众问好"); 27 //执行目标对象的方法 28 Object returnValue = method.invoke(target, args); 29 System.out.println("谢谢大家"); 30 return returnValue; 31 } 32 }
测试类:
/** * 测试类 */ public class Test{ public static void main(String[] args){ //目标对象 Singer target = new Singer(); //代理对象 Singer proxy = (Singer)new ProxyFactory(target).getProxyInstance(); //执行代理对象的方法 proxy.sing(); } }
总结:三种代理模式各有优缺点和相应的适用范围,主要看目标对象是否实现了接口。以Spring框架所选择的代理模式举例
在Spring的AOP编程中: 如果加入容器的目标对象有实现接口,用JDK代理 如果目标对象没有实现接口,用Cglib代理