代理模式
一、逻辑
代理人 和被代理对象
代理人表现出 被代理对象具有的所有方法(因此可以设计出接口)
代理人可以做一些额外的工作,比如帮你记下时间或者检查下权限,然后实际的工作还是要调用被代理对象去做
(因为具有相同的方法,所以可以实现相同的接口,当然不实现也是可以的)
二、类的设计方案
基于这样的逻辑
我们有两种设计类的方案:
1.继承
代理类继承被代理类。这样构造出来的代理人,将被代理人传入构造方法,然后作为其父类对象。这样相当于儿子给父亲做代理,任何事情先找儿子。你要和万达集团合作,先找王思聪谈,他要审核要先预处理下,然后他再给王健林签个字就完了。(仅仅是比喻而已)
但这个方式有个很明显的问题,如果有多个代理人,只能依次调用(按照继承的顺序),如果想改变代理逻辑,则得直接修改类,重新编译。
很明显这个方案的可扩展性不好,灵活性也很差
2.聚合
聚合就是一个对象中包含另外一个对象的引用
它们可以随意组合,顺序可以调整,这个只要在客户端改动就好。如果写在配置文件里面了,直接修改配置文件,客户端都不需要改动。
题外话:
这个设计方案看起来很眼熟,可以发现这种设计和装饰模式一模一样。
我个人的理解是,设计模式是一个逻辑上的概念,可能设计上一样,但它们逻辑上是不同的
三、编码方案
那么我们知道如何设计这个类了,那么我们程序中又可以有两种产生这个类和这个类对象的方法
1.静态代理
也就是直接将刚刚说的设计类的方案硬编码,需要什么代理类就写什么代理类
http://www.cnblogs.com/java-my-life/archive/2012/04/23/2466712.html
这个方案的劣势在于什么呢:你要给A,B,C三个类代理,就要分别写三个代理类(因为A.B,C表现出来的方法都不一样),如果分别是时间和日志两种代理。那么就是要写2*3=6个类。
但其实我们用户是想对A,B,C实现一样的代理逻辑,却要为A,B,C都写代理类,其实就有很多重复编码。
重复的东西------>交给程序帮我们实现
因此我们动态的来生成这些代理类,用户只需要指定代理的逻辑(时间和日志)
2.动态代理
package myproxy; public interface UserService { public void addUser(); }
package myproxy; public class UserServiceImpl implements UserService { public void addUser() { System.out.println("this is add user operation"); } }
package myproxy; import java.lang.reflect.Method; public interface InvocationHandler { public void invoke(Object o,Method m); }
package myproxy; import java.lang.reflect.Method; public class TimeHandler implements InvocationHandler{ private Object target; public TimeHandler(Object target) { this.target=target; } public void invoke(Object o,Method m) { System.out.println("start time"); try { m.invoke(target, new Object[]{}); } catch (Exception e) { e.printStackTrace(); } System.out.println("end time"); } }
package myproxy; import java.io.File; import java.io.FileWriter; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; public class Proxy { public static Object newProxyInstance(Class intfc, InvocationHandler h) throws Exception { String rt="\r\n"; String src=""; //src+="package myproxy;"+rt+ src= rt+ "public class "+intfc.getSimpleName()+"$Proxy implements "+intfc.getName()+"{"+rt+ " private myproxy.InvocationHandler h;"+rt+ " public "+intfc.getSimpleName()+"$Proxy(myproxy.InvocationHandler h){"+rt+ " this.h=h;"+rt+ " }"+rt; String methodString=""; Method[] methods=intfc.getDeclaredMethods(); for(Method m:methods) { methodString+=" @Override"+rt+ " public void "+m.getName()+"()"+rt+ " { "+rt+ " try{"+rt+ " java.lang.reflect.Method md="+intfc.getName()+".class.getMethod(\""+m.getName()+"\"); "+rt+ " h.invoke(this,md);"+rt+ " }catch(Exception e){}"+ " }"+rt; } src=src+methodString+"}"; System.out.println(src); //将生成的代理类写入文件 String fileName="d:\\myproxy\\"+intfc.getSimpleName()+"$Proxy.java"; File f=new File(fileName); FileWriter fw=new FileWriter(f); fw.write(src); fw.flush(); fw.close(); //在程序中进行编译 JavaCompiler compiler=ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr=compiler.getStandardFileManager(null, null, null); Iterable units=fileMgr.getJavaFileObjects(fileName); CompilationTask t=compiler.getTask(null, fileMgr, null, null, null, units); t.call(); fileMgr.close(); //加载到内存 URL[] urls=new URL[]{new URL("file:/d:/myproxy/")}; URLClassLoader url=new URLClassLoader(urls); Class c=url.loadClass(intfc.getSimpleName()+"$Proxy"); Constructor constructor=c.getConstructor(InvocationHandler.class); Object o=constructor.newInstance(h); return o; } }
package myproxy; public class Client { public static void main(String[] args) throws Exception { UserService t=new UserServiceImpl(); InvocationHandler h=new TimeHandler(t); UserService u=(UserService) Proxy.newProxyInstance(UserService.class,h); u.addUser(); } }
代码参考《马士兵设计模式教学视频》
总结这个代码的结构如下:
客户端需要做的就是按照需求创建Handler,指定代理的逻辑,然后同时将目标对象放到handler中。
然后把这个handler对象传给Proxy工具类,它就会帮我们生成对应的代理类,然后编译(也可能是直接修改Class二进制文件,就不需要编译了),加载到内存这个代理类Class对象,最后生成代理实例,返回给客户端。
四、实际应用
java的jdk自带动态代理的工具包,(上面是我们模拟的jdk里面的InvocationHandler和Proxy)
实际使用如下:
http://blog.csdn.net/hhzxj2008/article/details/24560575
(API的使用可以查看JDK文档或者网上很多用例的)
jdk的reflect包中的Proxy 只能代理有接口的了类
Cglib 可以修改字节码 反编译 生成子类
有两个操作字节码的框架ASM和Javasisst。
Cglib就是利用ASM进行改变字节码然后生成代理类的
Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
介绍cglib:http://www.blogjava.net/stone2083/archive/2008/03/16/186615.html
http://blog.csdn.net/fenglibing/article/details/7080340
比较cglib和jdk的代理:http://insufficientinformation.blogspot.com/2007/12/spring-dynamic-proxies-vs-cglib-proxies.html
springAOP多个切面http://howtodoinjava.com/2015/01/30/spring-aop-specifying-aspects-ordering/