zoukankan      html  css  js  c++  java
  • 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代理

     
     
     
     
  • 相关阅读:
    程序员:不要自称为码农
    SpringBoot对静态资源配置
    LeetCode 572. Subtree of Another Tree(子树)
    LeetCode 437. Path Sum III(统计路径和等于sum的路径数量)
    LeetCode 112. Path Sum(判断路径和是否等于一个数)
    LeetCode 617. Merge Two Binary Trees(归并两棵二叉树)
    LeetCode 226. Invert Binary Tree(翻转二叉树)
    Failure to transfer org.apache.maven.plugins:maven-resources-plugin:pom:2.6 的解决办法
    linux-查询某软件的安装的目录
    WebService概念解释
  • 原文地址:https://www.cnblogs.com/xiaocao123/p/10686424.html
Copyright © 2011-2022 走看看