设计模式之代理模式
在java的项目中经常用到代理模式,它不是什么高深的技术,只是一种解决问题的思路。
代理模式的由来
程序来源于生活,前贤的总结总是那么精辟。在生活中处处可以见到代理模式的影子。如明星和经纪人与业务方(广告商,导演),租房客和中介与房东,大姑娘和媒婆与小伙子。发现它们的规律了吗?参与方总是有三方,通过中间商赚差价...呸,通过中间商提供沟通的桥梁,不用亲力亲为,还能享受额外的服务,例如经纪人除了告诉明星关于导演广告商提供的信息,还能提前筛选收集资料,分析可行性。中介除了可以给租客介绍房子带看房,还能给介绍房子的相关朝向,过户流程之类的额外服务。
其实总结下来就是两点
1.不用为一些事情投入过多精力,亲力亲为
2.通过中间人,能获得额外的服务和信息
转到程序里面就是
1.有业务需要调用外部接口,内部或者外部可能经常变更的时候,可以采用代理模式,内部业务不需要动,只改代理类就行了。这样代码无侵入性,无耦合
2.代理能在接口调用前后做额外工作,如记录调用时间,次数,能数据校验,拦截等
代理模式的实现方式
1.静态代理
2.动态代理
a. cglib方式
b.jdk方式
静态代理是比较容易理解的,其结构不外乎,创建一个类,里面持有被用者的引用对象,并提供一个方法,供使用者调用。需要调用的时候,找代理类就行,你不用关心他怎么实现的
动态代理其实就是静态代理类,只不过是由jvm通过反射(asm)动态生成一个匿名代理类.class文件.接下来可以了解一下它们的使用与要点。
前置需求
有一个下班接口,有方法go,需要在下班前下班后做点事情
interface OffWork{
void go();
}
有一个实现类
public class Employee implements OffWork { public Employee(){} private String name; public Employee(String name) { this.name = name; } @Override public void go() { System.out.println("员工:"+name+"下班了"); } }
静态代理
优点:结构简单,实现也简单,一目了然
缺点:项目中有大量代理类,结构一致,代码重复,导致项目包文件过大。新增接口麻烦,接口变动代理类也得跟着变动,就算有工具可以生成代理类,也得替换和检查
1 public class StaticProxy { 2 public StaticProxy(OffWork e){ 3 this.work=e; 4 } 5 private OffWork work; 6 public void go(){ 7 System.out.println("下班前准备,好像需要关电脑..."); 8 work.go(); 9 System.out.println("下班路上顺便买个菜"); 10 } 11 }
动态代理-cglib方式
步骤:
1.创建代理类并实现MethodInterceptor 接口
2.持有调用者引用,并提供一个create方法,返回匿名代理类
3.在调用方法前后做额外工作before,after
1 public class CglibProxy implements MethodInterceptor { 2 private Object target; 3 4 public Object create(Object target) { 5 this.target = target; 6 Enhancer enhancer = new Enhancer(); 7 enhancer.setSuperclass(target.getClass()); 8 enhancer.setCallback(this); 9 return enhancer.create(); 10 } 11 12 @Override 13 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 14 before(); 15 Object res= method.invoke(target, objects); 16 after(); 17 return res; 18 } 19 private void before(){ 20 System.out.println("执行之前找点事情干干"); 21 } 22 private void after(){ 23 System.out.println("完成之后收工"); 24 } 25 }
注意!划重点
以上cglib方式只能用于无参构造,。
下面是有参构造,注意他们之前的区别
1 public class CglibProxy implements MethodInterceptor { 2 private Object target; 3 4 public Object create(Object target) { 5 this.target = target; 6 Enhancer enhancer = new Enhancer(); 7 enhancer.setSuperclass(target.getClass()); 8 enhancer.setCallback(this); 9 return enhancer.create(); 10 } 11 public <T> T getInstance(T target,Class[] args,Object[] argsValue){ 12 Enhancer enhancer = new Enhancer(); 13 enhancer.setSuperclass(target.getClass()); 14 enhancer.setCallback(this); 15 return (T) enhancer.create(args,argsValue); 16 } 17 @Override 18 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 19 before(); 20 Object res= methodProxy.invokeSuper(o, objects); 21 /**被注释的是无参构造用法**/ 22 // Object res=method.invoke(target, objects); 23 after(); 24 return res; 25 } 26 private void before(){ 27 System.out.println("执行之前找点事情干干"); 28 } 29 private void after(){ 30 System.out.println("完成之后收工"); 31 } 32 }
动态代理-jdk方式
在java.lang.reflect包下面InvocationHandler接口
步骤:
1.创建代理类并实现InvocationHandler接口
2.创建成员变量,用来存储调用者的引用
3.提供create接口,通过方法注入,给成员变量赋值,返回匿名代理类
4.在invoke方法中调用接口,并添加额外业务
1 public class DynamicProxy implements InvocationHandler { 2 private Object target; 3 4 5 @Override 6 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 7 before(); 8 Object res= method.invoke(target,args); 9 after(); 10 return res; 11 } 12 public Object create(Object t ){ 13 target=t; 14 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); 15 } 16 private void before(){ 17 System.out.println("执行之前找点事情干干"); 18 } 19 private void after(){ 20 System.out.println("完成之后收工"); 21 } 22 }
调用
1 public class MainTest { 2 public static void main(String[] args) { 3 /**静态代理方式**/ 4 OffWork employee= new Employee("张一山"); 5 new StaticProxy(employee).go(); 6 System.out.println("=========================="); 7 /**jdk动态代理方式**/ 8 DynamicProxy dynamicProxy= new DynamicProxy(); 9 OffWork employee1=(OffWork) dynamicProxy.create(employee); 10 employee1.go(); 11 System.out.println("=========================="); 12 /**cglib动态代理方式**/ 13 CglibProxy cglibProxy=new CglibProxy(); 14 /**无参构造方式**/ 15 OffWork offWork= (OffWork)cglibProxy.create(employee); 16 offWork.go(); 17 System.out.println("=========================="); 18 /**有参构造方式**/ 19 OffWork s= cglibProxy.getInstance(employee,new Class[]{String.class},new Object[]{"张一山"}); 20 s.go(); 21 } 22 }
输出结果
下班前准备,好像需要关电脑... 员工:张一山下班了 下班路上顺便买个菜 ========================== 执行之前找点事情干干 员工:张一山下班了 完成之后收工 ========================== 执行之前找点事情干干 员工:null下班了 完成之后收工 ========================== 执行之前找点事情干干 员工:张一山下班了 完成之后收工
分析
我们常用的动态代理jdk方式和cglib 有什么不同呢,如何选择?
这就要聊到它们的实现方式上了。jdk动态代理和cglib动态代理。两种方法的存在,各有各自的优势。
jdk方式 InvocationHandler
jdk动态代理是由java内部的反射机制来实现的
特点:
jdk反射机制在生成类的过程中比较高效,在执行时效率较低
优点:无依赖,直接利用jdk反射
缺点:被代理的类必须实现接口方法,否则无法编译通过,无法使用
cglib方式 MethodInterceptor
cglib动态代理底层则是借助asm来实现。
特点:生成代理类的过程中比较慢,但生成后使用速度快
优点:
被代理类无需实现接口,普通类就可以了,没有局限性
缺点:有外部依赖,不过目前已经被封装到spring-core.jar中
相比较来说,cglib的使用范围更广泛,更通用一点
总结
动态代理给我们程序添加了无数的可能,或者可以这样说,动态编译+反射+动态代理 给java程序带来了很多奇思妙想,spring的核心也是在这里,利用它实现了容器的加载,方法的增强,ioc 与aop 都是通过它们实现的。