java基础之动态代理

来源:互联网 发布:淘宝最新市值 编辑:程序博客网 时间:2024/06/05 22:38

代理的原理与作用及AOP的概念

代理
1.要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做? 
2.编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。 
3.如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在代码中引用代理类跟目标类都实现的接口,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易

动态代理技术:

1.要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!
2.JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
3.JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
4.如果目标类没有实现接口,那么可以使用第三方的CGLIB库,CGLIB可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
5.代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前 
2.在调用目标方法之后 
3.在调用目标方法前后 

4.在处理目标方法异常的catch块中 

public class ProxyTest {public static void main(String[] args) throws Exception {// 第一个参数指定的是生成的类的类加载器,因为这个类是JVM动态生成的没有类加载器,// 所以我们需要为其指一个类加载器,这个参数通常设置为这个代理类实现的接口的类加载器,及第二个参数的类加载器。Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);System.out.println(clazzProxy1.getName());System.out.println("----------begin constructors list----------");Constructor[] constructors = clazzProxy1.getConstructors();for (Constructor constructor : constructors) {String name = constructor.getName();StringBuilder sBuilder = new StringBuilder(name);// StringBuilder是线程不安全的,StringBuffer是线程安全的,单线程的时候选择StringBuilder效率比较高,多线程的时候选择StringBuffersBuilder.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());}// 生成的动态类只有一个有参的构造方法,参数就是InvocationHandlerSystem.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());}System.out.println("----------begin create instance object----------");// Object obj = clazzProxy1.newInstance();Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);// 动态类的构造方法没有无参数的class MyInvocationHander1 implements InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// TODO Auto-generated method stubreturn null;}}// 创建动态类的实例 方法一Collection proxy1 = (Collection) constructor.newInstance(new MyInvocationHander1());System.out.println(proxy1);proxy1.clear();// proxy1.size();//只能调用获得的动态类的实例的没有返回值的方法,调用有返回值的方法会报错。// Proxy.newProxyClass()参数ClassLoader是指定一个就行了,因为它是动态生成的,确实没有ClassLoader,// 通常与接口相同的类加载器,也可随便指定。动态创建的动态类要接受一个InvocationHandler对象,// 创建的动态类的闻初印象:// a.创建的动态类的构造方法要接受一个InvocationHandler对象// b.创建完这个动态类的对象后,它的toString返回 null// c.调用它没有返回值的方法成功,调用有返回值的方法则失败// (因为调用时会高用invoke,此时invoke返回的是null,需要的是int,调用没有返回值的方法成功是因为// 没有返回值的方法返回的是void,null可以转化为void)。// 方法二Collection proxy2 = (Collection) constructor.newInstance(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// TODO Auto-generated method stubreturn null;}});// 让jvm创建动态类及其实例对象,需要给它提供哪些信息?// a.生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;// b.产生的类字节码必须有个一个关联的类加载器对象;// c.生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,// 把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,// 它是在创建动态类的实例对象的构造方法时传递进去的。// 方法3,方法1,2只是创建了一个动态类,并没有挂载真实的要代理的目标,所以不能调用有返回值的方法Collection proxy3 = (Collection) Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[] { Collection.class }, new InvocationHandler() {ArrayList target = new ArrayList();// 此时target变成成员变量,不管invoke调用多少次,使用的都是同一个target@Override // 客户端程序调用代理的一个方法时涉及三个要素:proxy,method,args,代理对象每调一次就会用三要素调用invodepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// proxy:代理对象,method:代理对象的哪个方法,args:为方法传递的参数long beginTime = System.currentTimeMillis();// 此处可对入参进行修改Object retVal = method.invoke(target, args);// 此处可对返回结果进行修改long endTime = System.currentTimeMillis();System.out.println(method.getName() + " running time is " + (endTime - beginTime));return retVal;// return method.invoke(proxy, args); //就会死循环}});// 接受handler对象就是要记住它,以后使用它proxy3.add("zxx");// 每调用一下,handler对象的invoke方法就执行一下proxy3.add("lhm");proxy3.add("bxd");System.out.println(proxy3.size());// 现在不报空指针异常了(因为有真实的代理对象了),只是打印size:0,ArrayList移到外面则打印3System.out.println(proxy3.getClass().getName());// com.sun.proxy.$Proxy0 不是target(ArrayList)// 原因在于代理对象从Object上继承的方法中只有hashCode/equals/toString才像接口中的方法一样派发给// handler's invoke方法,其他方法不派发,自己有自己的实现,还记得刚才那个proxy1的toString返回null吧// 分析InvocationHandler对象的运行原理// 动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。// 构造方法接受一个InvocationHandler对象,接受对象了要干什么用呢?该方法内部的代码会是怎样的呢?// 实现Collection接口的动态类中的各个方法的代码又是怎样的呢?// 在生成的动态类的内部有一个InvocationHandler类的实例,构造方法接收的InvocationHandler对象是为了初始化这个实例,具体代码见下// $Proxy0 implements Collection// {// InvocationHandler handler;// public $Proxy0(InvocationHandler handler)// {// this.handler = handler;// }// //生成的Collection接口中的方法的运行原理// int size()// {// return handler.invoke(this,this.getClass().getMethod("size"),null);// }// void clear(){// handler.invoke(this,this.getClass().getMethod("clear"),null);// }// boolean add(Object obj){// return handler.invoke(this,this.getClass().getMethod("add"),obj);// }// }// 调用生成的动态代理类的实例对象的方法,实际上调用的是构造方法传递进去的InvocationHandler对象的invoke方法,每调用一次动态代理类实例对象的方法,就调用传递进去的InvocationHandler对象的invoke方法一次。// InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?图解说明如下:// Client程序调用objProxy.add("abc")方法时,涉及三要素:objProxy对象(proxy)、add方法(method)、"abc"参数(args)// Class Proxy$ {// boolean add(Object object)// {// return handler.invoke(Object proxy, Method method, Object[] args);// }// }// 动态代理的工作原理图}}
以下代码是可行的,你懂的:

public interface Subject {public void request();}

package javaplay.day3;public class RealSubject2 implements Subject {@Overridepublic void request() {System.out.println("From real subject2.");}}
package javaplay.day3;import java.lang.reflect.Method;public class RealSubject implements Subject {@Overridepublic void request() {System.out.println("From real subject.");}public static void main(String[] args) throws Exception {Method method = Subject.class.getMethod("request");Method method1 = RealSubject.class.getMethod("request");Method method2 = RealSubject2.class.getMethod("request");method.invoke(new RealSubject());// From real subject.method1.invoke(new RealSubject());// From real subject.method2.invoke(new RealSubject2());// From real subject2.// method2.invoke(new RealSubject());//出错}}

订正:

其中,在每个方法中调用handler时:

return handler.invoke(this,this.getClass().getMethod("size"),null);
第二个参数不应该从this.getClass()中获取Method,因为这样做得到的是代理类的Method,这个Method无法应用到目标类中
要让Method能够invoke目标类对象,必需获取他们的共同父类,也就是接口中的Method,这也就解释了代理类中存在这样的成员变量的用途:
private static java.lang.reflect.Method $Proxy0.m(x)
而通过反射发现代理类中并没有InvocationHandler类型的成员变量,经过实验得出如下结论:
动态生成的代理类继承java.lang.reflect.Proxy实现传递的所有接口,重写Object中的hashCode、equalst和toString方法








0 0
原创粉丝点击