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

Posted 斐波那切

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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修饰的方法除外)。

以上是关于Spring AOP入门基础-继承装饰者,代理的选择的主要内容,如果未能解决你的问题,请参考以下文章

java代理模式与装饰者模式

Java中动态代理装饰模式和继承简单实现

装饰者模式

Spring09_动态代理

Spring09_动态代理

基于标注的AOP面向切面编程