JAVA提高第八篇 动态代理技术
对于动态代理,学过AOP的应该都不会陌生,因为代理是实现AOP功能的核心和关键技术。那么今天我们将开始动态代理的学习: 一、引出动态代理 生活中代理应该是很常见的,比如你可以通过代理商去买电脑,也可以直接找厂商买电脑,最终都是买到了电脑。程序中也一样存在代理的情况,比如要为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如:异常处理、日志、计算方法耗时等等,那么我们会怎么做呢? 1.会编写一个与目标类拥有相同接口的代理类,代理类的每个方法调用目标类的相同方法,然后在调用方法前后加上系统功能所需要的代码。 2.如果采用工厂模式或者配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换。 样例如下: public class X{ public void sayHello(){ syso:say hello; } } 现在我要在这个方法之前添加一个时间,方法之后添加一个时间,计算这个方法执行的时间一共是多少.如果我没有得到sayHello源码,那么我怎么做呢?写一个代理: public class XProxy { private X x; public void sayHello { startTime: x.syHello(); endTime; } } 说明:上面的是伪代码。 把开始时间和结束时间放在这个方法的前后就可以了. 二、创建动态代理类 现在试想一下,上面只是代理了一个目标类,如果多个目标类,那么是不是要创建N多个代理类呢?那样不是代码太不灵活且笨重了。当然不会。 java虚拟机可以在运行期间动态生成类,这种类是以字节码的形式生成出来的。这种动态生成的类往往呢就是代理类。即动态代理类。 JVM生成的动态代理类必须满足一定的条件,这就是必须实现一个或多个接口。所以JVM生成的动态代理只能用作具有相同接口的目标类的代理。(动态生成的类不是代理,我们只是吧这个类当成代理来用。) Proxy动态代理的API: 两个参数应该很容易理解: 第一个参数:我们知道任何一个字节码都是需要通过类加载器来加载的,那么这个动态生成的字节码也不例外,需要给它一个类加载器。 第二个参数:就是动态代理类生成,必须满足的一个条件,需要实现一个或者多个接口,否则这个生成的类字节码中就没有方法了,没有方法就失去了其功能意义。 下面我们动手来创建一个动态的代理类,大体思路为: 1.创建实现Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数 2.编码列出动态类中的所有构造方法和参数签名 3.编码列出动态类中的所有方法和参数签名 4.创建动态类的实例对象:1)用反射获取构造方法 2)编写一个最简单的invocationHandle类 3)调用构造方法创建动态类的实例对象,并将编写的InvocationHandle类的实例对象传递进去 4)打印创建对象和调用对象的没有返回的方法和getClass方法,演示调用其他有返回值方法报告了异常。 5)将创建动态类的实例对象的代理写成匿名内部类方式,简化代码。 样例分步实现如下: (1)首先我们来完成前面3步: package study.javaenhance; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collection; public class ProxyTest { public static void main(String[] args) { Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class); System.out.println(clazzProxy1.getName()); //上面输出的为一个类,那么一个类肯定有其构造方法和方法,下面我们来列出来 System.out.println("----------begin constructors list----------"); //1.列出构造方法 Constructor[] constructors = clazzProxy1.getConstructors(); for (Constructor constructor : constructors) { String name = constructor.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzParams = constructor.getParameterTypes(); for (Class clazzParam : clazzParams) { sBuilder.append(clazzParam.getName()).append(','); } if(clazzParams!=null && clazzParams.length != 0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(')'); System.out.println(sBuilder.toString()); } //2.列出这个类字节码中的所有方法 System.out.println("----------begin methods list----------"); Method[] methods = clazzProxy1.getMethods(); for(Method method : methods){ String name = method.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzParams = method.getParameterTypes(); for(Class clazzParam : clazzParams){ sBuilder.append(clazzParam.getName()).append(','); } if(clazzParams!=null && clazzParams.length != 0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(')'); System.out.println(sBuilder.toString()); } } } 输出结果如下: $Proxy0 可以看到所有的方法均是来自Collection 和父类Object中的方法,符合我们的预期结果。接下来我们进入第四步和第五步的实现: 首先,我们来创建这个动态代理类的实例。那么直接clazzProxy1.newInstance();可不可以呢?显然是不可以的嘛.我们刚刚说了,动态生成的这个代理类只有一个构造方法,有没有无参构造方法呢?没有啊.所以,创建 一个参数的构造方法.参数类型是java.lang.reflect.InvocationHandler。 下面我们来实现: 1.定义一个上述接口的实例: package study.javaenhance; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy,Method method,Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } } 2.调用实现创建实例动态类: //3.创建实例对象 System.out.println("----------begin create instance object----------"); Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class); MyInvocationHandler myInvocationHandler = new MyInvocationHandler(); Collection collection = (Collection) constructor.newInstance(myInvocationHandler); System.out.println(collection); collection.clear(); //collection.size(); //报错,异常会发生,产生异常的原因在于其返回值为int类型,但是每一次调用一个方法都会调用到invoke方法,我们此时的invoke返回的为null,所以是没有办法转换为int类型的。 3.我们采用匿名内部类的方式优化: //3.1 采用匿名内部类方式进行创建 Collection collection2 = (Collection) constructor.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy,Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } }); System.out.println(collection2); collection2.clear(); //collection2.size(); 4.继续优化:思考如果我们每次都想上面方式去创建动态的代理类实在有点重复,那么这个是JDK的Proxy类中提供了简单的方法直接去创建动态代理类,方式如下: //3.3 采用Proxy 中提供的简单方法创建 Collection collection3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),new Class[]{Collection.class},new InvocationHandler() { private ArrayList target = new ArrayList(); @Override public Object invoke(Object proxy,Object[] args) throws Throwable { return method.invoke(target,args); } }); //System.out.println(collection3); collection3.add("abc"); collection3.add("def"); collection3.add("hij"); System.out.println(collection3.size()); 三、动态代理的原理简单分析 上面我们创建了动态代理类,下面我们分析下代理的原理:
下面在来看一个问题: 动态代理的工作原理图: 对上面的这个图,我们简单来说说:客户端动态生成代理类,然后调用代理类的方法,代理类内部调用handler.invoke()方法,在invoke中呢,我们又指向的目标类.这样就实现了代理了.我客户端调用代理的什么方法,invoke就只向目标类的同一个方法.而在指定目标类方法的前后呢,我们还可以做其他的操作,比如记录日志.图中用圈圈出来的部分就是代理类自己实现的功能了.这就是代理类的原理. 我们来做最后一步,将上面的动态生成的代理类,编写可生成代理和插入通告的通用方法:
test代码: package study.javaenhance; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; public class ProxyTest { public static void main(String[] args) throws Exception { Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),'); } if(clazzParams!=null && clazzParams.length != 0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(')'); System.out.println(sBuilder.toString()); } //3.创建实例对象 System.out.println("----------begin create instance object----------"); Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class); MyInvocationHandler myInvocationHandler = new MyInvocationHandler(); Collection collection = (Collection) constructor.newInstance(myInvocationHandler); System.out.println(collection); collection.clear(); //collection.size(); //报错,异常会发生,所以是没有办法转换为int类型的。 //3.1 采用匿名内部类方式进行创建 Collection collection2 = (Collection) constructor.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy,Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } }); System.out.println(collection2); collection2.clear(); //collection2.size(); //3.3 采用Proxy 中提供的简单方法创建 Collection collection3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),args); } }); //System.out.println(collection3); collection3.add("abc"); collection3.add("def"); collection3.add("hij"); System.out.println(collection3.size()); System.out.println(collection3.getClass().getName());//这个返回的是 System.out.println("----------begin create instance object 抽化----------"); //3.4抽出动态代理让目标对象和切面对象都是传入进去的 final ArrayList target = new ArrayList(); Collection collection4 = (Collection)getProxy(target,new MyAdvice()); collection4.add("test1"); collection4.add("test2"); System.out.println(collection4.size()); } private static Object getProxy(final Object target,final Advice advice) { Object object = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() { @Override public Object invoke(Object proxy,Object[] args) throws Throwable { advice.beforeMethod(method); Object retValue = method.invoke(target,args); advice.afterMethod(method); return retValue; } }); return object; } } 四、实现类似Spring的可配置的AOP框架 首先,我们要完成的要求如下: 我们来模拟Spring的工厂模式读取从配置文件传递过来的类。如果这个类是一个普通类则直接返回。如果是一个代理类,则创建代理对象,返回代理类。 具体的理论知识可以看上面的图片 首先创建一个BeanFactory.java类。这是一个Bean工厂,用于读取配置文件中的类 package study.javaenhance.aopframework; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import study.javaenhance.Advice; public class BeanFactory { private Properties properties = new Properties(); public BeanFactory(InputStream inStream) { try { properties.load(inStream); } catch (IOException e) { e.printStackTrace(); } finally { if(inStream != null) { try { inStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } public Object getBean(String name) { String className = properties.getProperty(name); Object bean = null; try { Class clazz = Class.forName(className); bean = clazz.newInstance(); if(bean instanceof ProxyFactoryBean) { ProxyFactoryBean proxyBean = (ProxyFactoryBean) bean; Advice advice = (Advice)Class.forName(properties.getProperty(name + ".advice")).newInstance(); Object target = Class.forName(properties.getProperty(name + ".target")).newInstance(); proxyBean.setAdvice(advice); proxyBean.setTarget(target); Object proxy = proxyBean.getProxy(); return proxy; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return bean; } } 如果这个类中包含ProxyFactoryBean,则调用ProxyFactoryBean中的getProxy方法。动态生成代理类。 package study.javaenhance.aopframework; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import study.javaenhance.Advice; public class ProxyFactoryBean { private Object target; private Advice advice; public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } public Advice getAdvice() { return advice; } public void setAdvice(Advice advice) { this.advice = advice; } public Object getProxy() { Object object = Proxy.newProxyInstance(target.getClass().getClassLoader(),new InvocationHandler() { @Override public Object invoke(Object proxy,args); advice.afterMethod(method); return retValue; } }); return object; } } 接下来创建一个config.Properties文件.config.Properties xxx=java.util.ArrayList 最后建立测试类: package study.javaenhance.aopframework; import java.io.InputStream; import java.util.Collection; public class AopFrameworkTest { public static void main(String[] args) { InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties"); Object bean = new BeanFactory(ips).getBean("xxx"); System.out.println(bean.getClass().getName()); ((Collection)bean).clear(); } } 测试OK 总结:整个java增强的视频学习完成了,一共记住了多少我也不知道.但知道了很多内在的知识,如果有时间的话,或者说过一段时间可以拿出来问下,提供自己的技能。 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |