• Spring AOP入门基础-继承、装饰者,代理的选择


    关于Spring AOP,底层是基于动态代理实现的,下面简单的学习下为什么选择动态代理,而不选择继承实现,装饰者模式实现,下面参考如下业务场景下理解。

    业务场景

    业务层如果有业务需求,需要在注册用户,升级用户,和删除用户方法前都进行一次权限验证,最原始的方法就是在业务层每个方法前都添加代码验证。这是最原始的方式,在实际业务中有很多的方法,那都需要重写修改,很显然这是不合理的,因此衍生如下几个解决方案:

    (1)使用继承类,在继承类中对继承的方法进行修改,参考DogDemo01
    (2)使用装饰者模式,在装饰类中对原有方法进行装饰修改,参考DogDemo02
    (3)使用代理模式,创建出一个代理类,在代理类中修改方法,添加权限,代理模式分为以下两种:
    a 使用静态代理,参考DogDemo03
    b 使用动态代理,动态代理又分为两种,有默认Java JDK动态代理,参考DogDemo04,又有CGlib动态代理,参考DogDemo05
    下面使用狗的案例来简单体验一下几个选择的是否可行。

    代码准备,先准备了Animal接口,还创建了Dog类,实现Animal接口,下面就是基于此展开。

    Animal接口

     1 package com.boe.proxy.service;
     2 
     3 /**
     4  * 动物的接口
     5  */
     6 public interface Animal {
     7     //吃的动作
     8     public void eat();
     9     //工作职责
    10     public void work();
    11 }
    View Code

    Dog类,实现Animal接口,里面还添加自定义方法一个,后面用来测试动态代理。

     1 package com.boe.proxy.service;
     2 
     3 public class Dog implements Animal{
     4     @Override
     5     public void eat() {
     6         System.out.println("我喜欢吃骨头");
     7     }
     8 
     9     @Override
    10     public void work() {
    11         System.out.println("我要守护主人");
    12     }
    13 
    14     //添加一个接口外的方法,测试JDK静态代理类的不足
    15     public void see(){
    16         System.out.println("我能在黑夜中看到一切");
    17     }
    18 }
    View Code

    使用继承类

    使用继承者测试代码如下,就是在子类中修改父类的方法,考虑到父类中如果方法很多,这种方法不可取。并且这种只对子类新创建的对象修改方法有效,父类还是原来的方法。

     1 package com.boe.proxy.service;
     2 
     3 /**
     4  * 使用继承父类,修改方法,但是只对新创建的对象有效
     5  */
     6 public class DogDemo01 {
     7     public static void main(String[] args) {
     8         //修改方法只对新对象有效,对以前的对象无效
     9         System.out.println("-------这是一条继承狗-------");
    10         DogExtend dog=new DogExtend();
    11         dog.eat();
    12         dog.work();
    13 
    14         //原来对象无法修改
    15         System.out.println("-------这是一条老狗-------");
    16         Dog originDog=new Dog();
    17         originDog.eat();
    18         originDog.work();
    19     }
    20 }
    21 
    22 //继承狗,修改狗的方法
    23 class DogExtend extends Dog{
    24     //修改eat
    25     @Override
    26     public void work() {
    27         System.out.println("我要看贼");
    28     }
    29 }

    测试结果,可以完成对方法的修改,work方法被修改了,打印结果由"我要守护主人"变成"我要看贼"。

    使用装饰者模式

    使用装饰者模式测试代码如下,这个方法也有个弊端,必须实现被装饰类所有的接口方法,如果接口中有很多方法将会重写很多方法,也不是最好的选择。

     1 package com.boe.proxy.service;
     2 
     3 /**
     4  * 使用装饰者模式,将需要被装饰的狗注入装饰狗,但是这个有个弊端,需要实现接口Animal中所有方法
     5  * 如果共同的Animal接口有100个方法,则需要重写100个方法,也不是好的解决方案
     6  */
     7 public class DogDemo02 {
     8     public static void main(String[] args) {
     9         System.out.println("-------这是一条老狗-------");
    10         Dog originDog=new Dog();
    11         originDog.eat();
    12         originDog.work();
    13         System.out.println("-------这是一条装饰狗-------");
    14         DecorateDog dog=new DecorateDog(originDog);
    15         dog.eat();
    16         dog.work();
    17     }
    18 }
    19 
    20 /**
    21  * 装饰狗,装饰狗和被装饰狗,都需实现同样的接口
    22  */
    23 class DecorateDog implements Animal{
    24     //属性是装饰狗
    25     private Dog dog=null;
    26     //被装饰狗注入
    27     public DecorateDog(Dog dog) {
    28         this.dog = dog;
    29     }
    30     //如果不修改,也需要实现接口中所有的方法
    31     @Override
    32     public void eat() {
    33         //吃不修改
    34         dog.eat();
    35     }
    36 
    37     @Override
    38     public void work() {
    39         //工作修改
    40         System.out.println("我是装饰狗,我爱工作");
    41     }
    42 }

    测试结果,可以完成对方法work的修改,打印出"我是装饰狗,我爱工作"。

    使用代理模式

    代理模式分为两种,即静态代理和动态代理,静态代理和装饰模式感觉比较类似,动态代理分为JDK原生动态代理和Cglib动态代理,后者是对前者的完善,改善前者只能实现接口方法的局限。

    静态代理

    以下为静态代理代码实现,存在和装饰模式同样的弊端,即可能需要重写很多方法。

     1 package com.boe.proxy.service;
     2 
     3 /**
     4  * 静态代理狗,弊端与装饰狗类似
     5  */
     6 public class DogDemo03 {
     7     public static void main(String[] args) {
     8         System.out.println("-------这是一条老狗-------");
     9         Dog originDog=new Dog();
    10         originDog.eat();
    11         originDog.work();
    12         System.out.println("-------这是一条代理狗-------");
    13         ProxyDog proxyDog=new ProxyDog();
    14         proxyDog.eat();
    15         proxyDog.work();
    16     }
    17 }
    18 
    19 /**
    20  * 代理狗
    21  */
    22 class ProxyDog implements Animal{
    23     //创建老狗对象
    24     Dog dog=new Dog();
    25     //也需要重写所有的接口方法
    26     @Override
    27     public void eat() {
    28         //吃方法不重写
    29         dog.eat();
    30     }
    31 
    32     @Override
    33     public void work() {
    34         //工作方法修改
    35         System.out.println("我是代理狗,我也爱工作");
    36     }
    37 }

    测试结果,可以完成对work方法的修改,打印出"我是代理狗,我也爱工作"。

    动态代理

    动态代理即上面说的两种,接下来分别使用来完成代理生成,两者均有固定套路写法。

    (1)JDK动态代理,使用JDK Proxy接口的静态方法newProxyInstance()来完成,具体参考代码。

     1 package com.boe.proxy.service;
     2 
     3 import java.lang.reflect.InvocationHandler;
     4 import java.lang.reflect.Method;
     5 import java.lang.reflect.Proxy;
     6 
     7 /**
     8  * 动态代理,使用JDK自带代理方法
     9  */
    10 public class DogDemo04 {
    11     public static void main(String[] args) {
    12         System.out.println("-------这是一条老狗-------");
    13         Dog originDog=new Dog();
    14         originDog.eat();
    15         originDog.work();
    16         //下面使用JDK动态代理,需要使用JDK提供的Proxy
    17         /**
    18          * 参考API解释
    19          * 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类
    20          */
    21         Animal proxyDog = (Animal) Proxy.newProxyInstance(
    22                 originDog.getClass().getClassLoader(),
    23                 originDog.getClass().getInterfaces(),
    24                 //InvocationHandler是接口,下面是匿名内部类
    25                 new InvocationHandler() {
    26                     /**
    27                      * 当代理对象执行代理对象的方法时,会执行以下方法
    28                      * @param proxy 被代理对象
    29                      * @param method 被代理对象身上的方法
    30                      * @param args 被代理对象身上方法对应的参数
    31                      * @return
    32                      * @throws Throwable
    33                      */
    34                     @Override
    35                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    36                         //如果是work方法,修改
    37                         if ("work".equals(method.getName())) {
    38                             System.out.println("我是JDK动态代理狗,我非常爱工作");
    39                             return null;
    40                         }
    41                         //如果是其他方法不修改
    42                         else {
    43                             return method.invoke(originDog, args);
    44                         }
    45                     }
    46                 }
    47         );
    48         //使用动态代理狗方法
    49         System.out.println("-------这是一条动态JDK代理狗-------");
    50         proxyDog.eat();
    51         proxyDog.work();
    52         //JDK动态代理类只能调用接口中的方法,不能调用Dog类中自定义方法
    53         //proxyDog.see();
    54 
    55     }
    56 }

    测试结果,可以正常生成一条JDK代理狗,并且需要修改的代码只需要写少量,调用代理对象的方法后,都需要执行InvocationHandler接口里定义的invoke()方法,需要修改的方法单独处理,调用代理对象的方法是修改后的方法,其他不需要修改的使用method.invoke(obj,args)方法返回,即依然调用的是代理对象以前的方法。

    JDK动态代理可以比较完美的解决业务问题,但是它有个明显的问题,即只能实现接口中的方法,Dog类中自定义的see()方法无法通过代理类调用,编译期就会报错,这样就引出了Cglib动态代理。

    (2)Cglib动态代理

    它是对JDK动态代理的完善,代理对象不仅仅实现了接口中的方法,还可以实现父类中的所有方法,Spring-core核心包中就集成了cglib包,案例中导入的Spring-core包完成测试。

     1 package com.boe.proxy.service;
     2 
     3 import org.springframework.cglib.proxy.Enhancer;
     4 import org.springframework.cglib.proxy.MethodInterceptor;
     5 import org.springframework.cglib.proxy.MethodProxy;
     6 
     7 import java.lang.reflect.Method;
     8 
     9 /**
    10  * 使用cglib动态代理,它不仅仅可以实现接口中的方法,还可以实现父类中的非final定义的方法
    11  */
    12 public class DogDemo05 {
    13     public static void main(String[] args) {
    14         System.out.println("-------这是一条老狗-------");
    15         Dog originDog=new Dog();
    16         originDog.eat();
    17         originDog.work();
    18         //下面使用cglib动态代理,需要额外导包,spring-core包集成了cglib包
    19         //1 创建增强器
    20         Enhancer enhancer=new Enhancer();
    21         //2 指定要实现的接口,这句可以不写
    22         enhancer.setInterfaces(originDog.getClass().getInterfaces());
    23         //3 指定要继承的父类
    24         enhancer.setSuperclass(originDog.getClass());
    25         //4 设定回调函数
    26         enhancer.setCallback(new MethodInterceptor() {
    27             /**
    28              * 指定动态代理对象中的方法,会进入这里执行
    29              * @param proxy 被代理对象
    30              * @param method 被代理对象的方法
    31              * @param args 被代理对象方法上的参数
    32              * @param methodProxy 方法代理对象
    33              * @return
    34              * @throws Throwable
    35              */
    36             @Override
    37             public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    38                 //如果是work方法,重写
    39                 if("work".equals(method.getName())){
    40                     System.out.println("我是cglib动态代理狗,我也非常爱工作");
    41                     return null;
    42                 }else{
    43                     return method.invoke(originDog,args);
    44                 }
    45             }
    46         });
    47         //生成动态代理狗
    48         Dog proxyDog = (Dog) enhancer.create();
    49         //使用动态代理狗方法
    50         System.out.println("-------这是一条动态cglib代理狗-------");
    51         proxyDog.eat();
    52         proxyDog.work();
    53         //可以执行父类上的非接口定义方法,因此推荐使用cglib代理方法
    54         proxyDog.see();
    55     }
    56 }

    测试结果,发现不仅仅可以实现接口方法的修改,还可以实现父类Dog自定义方法see()的修改,实现打印"我能在黑夜中看到一切"。这样Cglib就是目前的最优选择。

    总结

    (1)继承类和装饰者模式均可以实现方法的重写,但存在重写大量方法的可能,不可取。

    (2)JDK动态代理也可以解决业务问题,并可以解决书写大量方法代码的问题,但是只能调用被代理对象中接口方法。

    (3)Cglib动态代理弥补了JDK动态代理的不足,可以让代理对象调用被代理类中所有的方法(注意final修饰的方法除外)。

  • 相关阅读:
    C# Socket 入门2(转)
    C# Socket 入门1(转)
    StructLayout特性(转)
    [转载]U3d常规性能优化技巧
    Python语言系统学习(七)
    Python语言系统学习(五)
    python语言系统学习(六)
    python语言系统学习(四)
    白话经典算法-常见排序算法的实现与性能比较
    最快的内容查找算法-----暴雪的Hash算法
  • 原文地址:https://www.cnblogs.com/youngchaolin/p/11594869.html
Copyright © 2020-2023  润新知