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 }
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 }
使用继承类
使用继承者测试代码如下,就是在子类中修改父类的方法,考虑到父类中如果方法很多,这种方法不可取。并且这种只对子类新创建的对象修改方法有效,父类还是原来的方法。
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入门基础-继承装饰者,代理的选择的主要内容,如果未能解决你的问题,请参考以下文章