什么是代理模式
代理模式是GOF23中代理模式中的一种,代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
代理模式主要起到增强方法和权限控制的作用,是AOP(面向切面编程)的基础,而代理模式分为静态代理和动态代理。
静态代理就是程序员自己编写代理类,一般在程序编译期就产生了代理类。
动态代理是在运行期才产生的代理类,而非程序编译期产生。
1、静态代理
本文从一个接口开始分析代理模式,首先定义一个发消息的接口如下:
public interface Sender {
boolean sendMsg(String msg);
}
定义一个古代发送消息的类实现该接口:
public class ArchaicSender implements Sender {
@Override
public boolean sendMsg(String msg) {
System.out.println("八百里加急: " + msg);
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
return true;
}
}
现在有个需求,需要统计一下发消息方法所执行的时间,那自然的需要在函数执行前打个时间戳,在函数执行后打个时间戳,然后两者相减即为方法运行时间。
在不违反开闭原则的情况下,有两种方法可以实现该功能:继承和组合。
1) 继承方式
public class TimeStaticInheritProxy extends ArchaicSender{
@Override
public boolean sendMsg(String msg) {
Long start = System.currentTimeMillis();
boolean rst = super.sendMsg(msg);
System.out.println(getClass().getSimpleName() + ": "
+(System.currentTimeMillis() - start) + "ms");
return rst;
}
}
在实现需求时,使用如下形式实例化一个对象:
new LogStaticInheritProxy();
2) 组合方式
public class TimeStaticCombProxy implements Sender {
Sender sender;
public TimeStaticCombProxy(Sender sender) {
this.sender = sender;
}
@Override
public boolean sendMsg(String msg) {
Long start = System.currentTimeMillis();
boolean rst = sender.sendMsg(msg);
System.out.println(getClass().getSimpleName() + ": "
+ (System.currentTimeMillis() - start) + "ms");
return rst;
}
}
在实现需求时,使用如下形式实例化一个对象:
new LogStaticInheritProxy (new TimeStaticInheritProxy (new ArchaicSender()) )
组合的代码和装饰器模式有些类似,是对原接口功能的增强。上述两种方法都可以在不违反开闭原则的前提下,实现统计方法执行时间的功能,究竟哪种方法更好呢?
答案是组合。为什么呢?假如我们现在又有一个需求,我们需要在方法中加入日志的功能,要求日志的运行时间不计入方法的运行时间内,
如果需求再发生变化,要求日志的时间要记录在时间戳范围之内呢,对于继承方式需要重新设计一些类,而对于组合方式,只需要在对象实例化时改为如下形式即可
new TimeStaticInheritProxy (new LogStaticInheritProxy (new ArchaicSender()) )
自然地:组合的方式避免了子类的泛滥,可以实现接口的灵活增强。
通过以上的例子可以非常清楚的感受到代理模式可以在不改变原有类的设计的基础之上实现灵活的功能增强,每个增强的代理类如切面一样可以灵活的插拔,这也就是AOP的基础。
2、动态代理Util
以上属于静态代理的范畴,那如何实现动态的代理呢?
JAVA的动态代理实现方式主要有两种:
1) JDK动态代理
JDK的动态代理,是基于接口的动态代理,基于InvocationHandler和Proxy来实现的。InvocationHandler来实现业务增强,而Proxy类可以获取到动态的代理对象。而JDK的实现中代理类是继承自Proxy类的,由于JAVA的单继承设计,故JDK的动态代理要求增强的类必须实现接口。
2) CGLIB动态代理
CGLIB的动态代理是基于继承来实现的,故不要求被增强的类必须实现接口。CGLIB主要基于Enhancer和MethodInterceptor来实现。Enhancer来生成代理对象,MethodInterceptor来实现业务增强。
无论是JDK的动态代理还是CGLIB的动态代理都是直接生成字节码,JDK的动态代理使用了WeakReference进行缓存,CGLIB中使用FashClass来提高执行效率,关于两者的具体实现方式在后续的文章中详细描述。本文重点关注怎样实现自己的动态代理呢。
3、自己实现动态代理
本文参考JDK的动态代理方式来实现动态代理,但不同于JDK的动态代理是使用ProxyGenerator直接生成字节码,本文生成java文件。
1) CODE
首先与JDK动态代理一样定义InvocationHandler:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
然后定义Proxy,实现动态代理java代码的生成及动态编译及加载。为了简化,代码没有考虑安全性,生成的java文件也放在与接口相同的包里,而不像JDK会根据接口的属性来判断包的位置。
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
public class Proxy {
private static final String proxyClassName = "$Proxy0";
public static Object newProxyInstance(ClassLoader loader, Class<?> intf, InvocationHandler handler) throws Exception {
String fileName = System.getProperty("user.dir") + "/src/" + dotToSlash(getPkg(intf)) + "/" + proxyClassName + ".java";
genProxyJavaSrc(intf, fileName);
String classFilePath = System.getProperty("user.dir") + "/bin/";
javac(fileName, classFilePath);
Class<?> cls = loader.loadClass(getPkg(intf) + "." + proxyClassName);
Constructor<?> constructor = cls.getConstructor(InvocationHandler.class);
Object o = constructor.newInstance(handler);
return o;
}
private static void javac(String fileName, String classFilePath) throws IOException {
JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
try (StandardJavaFileManager fileMgr = comp.getStandardFileManager(null, null, null)) {
fileMgr.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(new File[] { new File(classFilePath)}));
Iterable<? extends JavaFileObject> javaFileObjects = fileMgr.getJavaFileObjects(fileName);
CompilationTask task = comp.getTask(null, fileMgr, null, null, null, javaFileObjects);
task.call();
}
}
private static void genProxyJavaSrc(Class<?> intf, String fileName) throws IOException {
File f = new File(fileName);
try(FileWriter fw = new FileWriter(f)){
fw.write(formProxyJavaSrc(intf));
fw.flush();
}
}
private static String formProxyJavaSrc(Class<?> intf) {
String nl = "\r\n";
StringBuilder sb = new StringBuilder(
"package " + getPkg(intf) + ";" + nl +
"import java.lang.reflect.Method;" + nl +
"public class $Proxy0 implements " + intf.getName() + "{" + nl +
" private InvocationHandler h;" + nl +
" public $Proxy0(InvocationHandler h) {" + nl +
" super();" + nl +
" this.h = h;" + nl +
" }" + nl);
for(Method m : intf.getMethods()) {
sb.append(
" @Override" + nl +
" public " + m.getReturnType() + " " + m.getName() + "(" + getMethodPara(m) + ") {" + nl +
" Object rst = null;" + nl +
" try{" + nl +
" Method method = " + intf.getName() + ".class.getMethod(\""+ m.getName() + "\"," + getMethodParaType(m)+");" + nl +
" rst = h.invoke(this, method," + "new Object[]{" +getArgs(m) +"});" + nl +
" }catch(Throwable e){ e.printStackTrace();}" + nl +
" return ("+m.getReturnType()+")rst;" + nl +
" }" + nl
);
}
sb.append("}");
return sb.toString();
}
private static String getPkg(Class<?> intf) {
return intf.getName().substring(0, intf.getName().lastIndexOf('.'));
}
private static String dotToSlash(String name) {
return name.replace('.', '/');
}
private static String getMethodPara(Method m) {
StringBuilder sb = new StringBuilder();
for(Parameter p: m.getParameters()) {
sb.append(p.getType().getTypeName()+ " " + p.getName() +",") ;
}
if(sb.length() > 0) sb.deleteCharAt(sb.length()-1);
return sb.toString();
}
private static String getMethodParaType(Method m) {
StringBuilder sb = new StringBuilder();
for(Parameter p: m.getParameters()) {
sb.append(p.getType().getTypeName()+ ".class" +",") ;
}
if(sb.length() > 0) sb.deleteCharAt(sb.length()-1);
return sb.toString();
}
private static String getArgs(Method m) {
StringBuilder sb = new StringBuilder();
for(Parameter p: m.getParameters()) {
sb.append(p.getName() +",") ;
}
if(sb.length() > 0) sb.deleteCharAt(sb.length()-1);
return sb.toString();
}
}
上述代码在异常处理及返回值处理比较粗暴,但代码主体通过反射机制与动态编译与加载可以实现动态代理。
2)TEST
实现日志记录,在进入函数执行前和之后打印:
public class LogInvocationHandler implements InvocationHandler {
private Object target;
public LogInvocationHandler(Object t) {
this.target = t;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Enter " + method.getName());
Object rst = method.invoke(target, args);
System.out.println("Exit " + method.getName());
return rst;
}
}
测试用例:
@org.junit.Test
public void testDynProxy() throws Exception {
Sender sender = (Sender)Proxy.newProxyInstance(Sender.class.getClassLoader(), Sender.class, new LogInvocationHandler(new ArchaicSender()));
sender.sendMsg("新皇登基...");
}
输出结果:
Enter sendMsg
八百里加急: 新皇登基...
Exit sendMsg
这是第一篇技术博客,后续会写关于JDK动态代理与CGLIB动态代理,也考虑写JAVA8的新特性(如:lambda、函数式接口及编程、流)。
code : https://github.com/WalkeR-ZG/proxy
WalkeR_ZG
网友评论