您的位置:首页 > 编程语言

代码详解动态代理

2016-04-23 08:19 537 查看
参考

《深入理解JAVA虚拟机》-周志明

《张孝祥系列》

场景

java的动态代理到底是怎么回事?

怎么为已经存在的多个具有‘相同接口’的‘目标类’的各个方法增加一些系统功能,例如,异常处理、日志、事物与缓存等?

目标类:系统各个业务DAO,eg、UserDaoImpl;相同接口:IBaseDAO => 怎么为 业务DAO增加缓存功能 ?

分析

以上问题的解决,需要用到一个代理类,代理类在目标类各方法的执行周围或者说前后,增加系统交叉业务功能。如果为每一个目标类写一个代理类那就太麻烦了!

对于这类普遍使用的有意义的共性问题,JDK总是为我们提供了一套解决方案-在这个问题上,就提供了一个
java.lang.reflect.Proxy类。

这个类的字节码,不是像一般情况那样:编译器编译.java文件,生成 .class文件,然后再由类加载器加载到内存中成为运行时字节码。而是JVM直接在运行时动态生成的!

实验

package cool.pengych.java.proxy;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author pengyucheng
*  date 2016/04/22
*
* 静态代理技术:为系统中的各种接口的类增加代理功能(下文以下日志为例),如果手动实现,会疯掉? 因为系统中几乎所有的类都需要写日志。
*  动态代理技术应运而生!
*  JVM可以在运行期动态生成类的字节码,这种动态生成的类往往被用作代理类-动态代理
*  限制:JVM生成的动态类必须实现一个或者多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
*
*/
public class ProxyTest
{
public static void main(String[] args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException
{
/*
*   认识JVM动态生成的代理类
*   1、代理类名称
*   2、类的构造方法及参数
*/
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy1.getName());// 代理类名 : com.sun.proxy.$Proxy0

Constructor[] constructors = clazzProxy1.getConstructors();
for (Constructor constructor : constructors)
{
StringBuilder sb = new StringBuilder(constructor.getName());
sb.append("(");
Class[] clazzType = constructor.getParameterTypes();
for (Class clazzParam : clazzType)
{
sb.append(clazzParam.getName());
sb.append(",");
}
if(clazzType != null )
{
sb.deleteCharAt(sb.lastIndexOf(","));
}
sb.append(")");
System.out.println(sb.toString()); // 代理类的构造方法 : com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
}

Method[] methods = clazzProxy1.getMethods();
for (Method method : methods)
{
StringBuilder sb = new StringBuilder(method.getName());
sb.append("(");
Class[] clazzType = method.getParameterTypes();
for (Class clazzParam : clazzType)
{
sb.append(clazzParam.getName());
sb.append(",");
}
if(clazzType != null  && clazzType.length>0)
{
sb.deleteCharAt(sb.lastIndexOf(","));
}
sb.append(")");
System.out.println(sb.toString()); // 代理类的普通方法
}

/*
* 创建动态类的实例对象的两种方式
* 1、分步进行:先创建的动态类的字节码,再通过构造器,创建实例
* 2、一步到位。
*/
//分步进行
Class clazzProxy2 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
Constructor constructor = clazzProxy2.getConstructor(InvocationHandler.class);
Collection proxyCollection = (Collection) constructor.newInstance(new InvocationHandler()
{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
return null;
}
});
System.out.println(proxyCollection); // 返回 null 对象
// proxyCollection.size();
proxyCollection.clear();

// 一步到位
Collection proxy2 = (Collection) Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[] {Collection.class},
new InvocationHandler()
{
ArrayList target = new ArrayList<>(); // 目标对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
long beginTime = System.currentTimeMillis(); // 系统交叉业务
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
return retVal;
}
});
proxy2.add("pingpang");
proxy2.add("basketball");
System.out.println(proxy2);

/*
*  终极版 动态代理之 AOP模型
*  客户端只需要提供 目标类 与 系统交叉业务,即可获取动态代理类。
*/
final ArrayList target = new ArrayList(); // 目标对象
Advice  advice =  new Myadvice();
// ArrayList proxy =   (ArrayList) getProxy(target,advice);// com.sun.proxy.$Proxy1 cannot be cast to java.util.ArrayList
Collection proxy =   (Collection) getProxy(target,advice); //代理对象必须与目标类有相同的接口
System.out.println(proxy);
}

/**
* @param target 目标对象
* @param advice 系统业务
* @return  代理对象
*/
private static Object getProxy(final Object target,final Advice advice)
{
Object proxy3 = Proxy.newProxyInstance(
target.getClass().getClassLoader(),/*Collection.class.getClassLoader(),*/
target.getClass().getInterfaces(),/*new Class[] {Collection.class},*/
new InvocationHandler()
{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
advice.beforeMethod(method);
Object retVal = method.invoke(target, args);
advice.afterMethod(method);
return retVal;
}
});
return proxy3;
}
}


总结

关键认识到:JVM有能动态生成字节码这种机制
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: