代理模式
一、逻辑
代理人 和被代理对象
代理人表现出 被代理对象具有的所有方法(因此可以设计出接口)
代理人可以做一些额外的工作,比如帮你记下时间或者检查下权限,然后实际的工作还是要调用被代理对象去做
(因为具有相同的方法,所以可以实现相同的接口,当然不实现也是可以的)
二、类的设计方案
基于这样的逻辑
我们有两种设计类的方案:
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/