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代理

 
 
 
 
posted @ 2019-04-10 21:49  沉迷学习、无法自拔  阅读(626)  评论(0编辑  收藏  举报