java基础增强之代理、AOP学习笔记

来源:互联网 发布:mac os x 10.10懒人版 编辑:程序博客网 时间:2024/04/27 19:55

1.代理的概念:代理其实跟生活中的代理是一个原理,比如说我在湖南,要从联想总部买电脑和从湖南的代理商买电脑,结果都是

   一样的,都是要买一台电脑,虽然从总部买可能便宜点,但是太麻烦,要车费还要我自己扛回湖南,所以决定从代理商那里买,因为
   它能给我提供方便,Java中的代理也一样,现在我想调用一个程序的一个方法,但是在调用这个方法之前和之后我想做点别的事情,
   而我又不能去修改这个方法的源代码,所以我想通过第三方的来调用这个方法,在第三方类里先定义同样的一个方法,在这个方法
   调用前调用我需要处理的代码,这个方法调用后调用另一个处理方法,这样我不仅调用了我原来想调用的方法,而且实现了我想要增加
   的附属功能,非常的方便。
   比如现在有个一个类X,类中有一个方法sayHello:
   class X{       void sayHello(){        System.out.println("你好");       }   }

   我想在输出这个"你好"之前和之后打印一下时间,看下我执行这个方法的时间是多少,这个时候我们可以创建一个代理类XProxy,类中
   定义一个相同名称的方法,在这个方法中我们也调用这个sayHello方法,但是调用前和调用后我们输入一下时间就行了:
   class XProxy{    void sayHello(){        System.out.println(System.currentTimeMillis());        X.sayHello();        System.out.println(System.currentTimeMillis());    }   }

   从上面我们可以看出,代理和目标拥有相同的方法,对外实现相同的方法,结构图如下(虚线代表实现接口):
 
   
   2.AOP
   概念:面向切面编程,正如代理的概念中提到的,当我们想要调用一个方法,在调用前和调用后想增加我们自己的一些处理代码
   ,这时候,如果被调用的方法很多,而且每个方法都有这样的需要(调用前和调用后增加自定义的一些处理代码),
   调用前和调用后的自定义处理代码横穿每个
   方法,就像一个切面,这时候我们可以将这些方法单独提炼出来,这种编程方式就叫面向切面编程。
   而代理是实现AOP的方式和手段。

   3.动态代理技术:
     概念:要为系统中各种接口的类增加代理功能,将会需要很多的代理类,全部采用静态代理方式(也就是硬编码),将显得
     非常麻烦,写成百上千个代理类将会非常累,而恰好,JVM能够在运行期动态生成出类的字节码,这种动态生成的类
     往往被用作代理类,即动态代理类。
     
     JVM实现的动态类必须实现一个或多个接口(因为要做目标类的代理的话,不知道代理类有什么方法,调用就不方便了,而让
     代理类和目标类都实现相同的接口就会让两者具有相同的方法了),所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
     
     那要是我不想实现接口,但是又想作为代理类怎么办呢?
     CGLIB库可以动态生成一个类的子类,一个类的子类可以用作该类的代理,所以,如果要为一个没有实现接口的类实现动态

     代理,可以使用CGLIB库。

 4.代理类的结构

  

//利用Proxy搞出Collection的代理类的字节码文件Class clazz = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);//=============利用反射解剖代理类的字节码文件,看下都有什么东东===============//获取构造函数Constructor[] constructors = clazz.getConstructors();/* 打印构造函数的名称和参数 结果只有一个构造函数: 构造函数名:$Proxy0(java.lang.reflect.InvocationHandler)  */for(Constructor constructor : constructors){System.out.print("构造函数名:"+constructor.getName() +"(");Class[] clazzParams = constructor.getParameterTypes();for(int i=0; ; i++){if(clazzParams.length==0)break;if(i==clazzParams.length-1){System.out.print(clazzParams[i].getName());break;}else{System.out.print(clazzParams[i].getName()+",");}}System.out.println(")");}//获取方法/* 打印方法的名称和参数方法比较多,有很多是继承自Object的: 方法名:add(java.lang.Object)方法名:hashCode()方法名:clear()方法名:equals(java.lang.Object)方法名:toString()方法名:contains(java.lang.Object)方法名:isEmpty()方法名:addAll(java.util.Collection)方法名:iterator()方法名:size()方法名:toArray([Ljava.lang.Object;)方法名:toArray()方法名:remove(java.lang.Object)方法名:containsAll(java.util.Collection)方法名:removeAll(java.util.Collection)方法名:retainAll(java.util.Collection)方法名:isProxyClass(java.lang.Class)方法名:getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)方法名:getInvocationHandler(java.lang.Object)方法名:newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)方法名:wait()方法名:wait(long,int)方法名:wait(long)方法名:getClass()方法名:notify()方法名:notifyAll() */Method[] methods = clazz.getMethods();for(Method method : methods){System.out.print("方法名:"+method.getName() +"(");Class[] clazzParams = method.getParameterTypes();for(int i=0; ; i++){if(clazzParams.length==0)break;if(i==clazzParams.length-1){System.out.print(clazzParams[i].getName());break;}else{System.out.print(clazzParams[i].getName()+",");}}System.out.println(")");} /*  * 既然搞到了构造函数了,我们就来创建一个代理类实例对象,  * 但是我们从打印的结果可以知道,这个代理类的字节码中只有一个构造函数  * 而且这个构造函数需要一个InvocationHandler类型的参数,下面就用这个字节码文件创建一个对象  */Constructor proxyConstructor = clazz.getConstructor(InvocationHandler.class);//InvocationHandler是一个接口不能直接new,所以在作为参数传给构造函数的时候,需要搞一个InvocationHandler的//的实现类,然后用这个实现类的对象给它传进去,下面搞个内部类算了Collection collectionProxy1 = (Collection)proxyConstructor.newInstance(new InvocationHandler(){@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {return null;}});

5.创建代理类的另外一种方式

对于上面先创建代理类字节码文件,再通过反射拿到字节码的构造函数,再通过构造函数new一个实例的方法
稍显麻烦,Proxy提供了更加方便的操作,那就是:
              newProxyInstance(ClassLoader loader,
                  Class<?>[] interfaces,
                  InvocationHandler h)
               throws IllegalArgumentException

演示另一种方式创建代理类的实例:

Collection collectionProxy2 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler(){@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {return null;}});
但是,上面的程序,如果直接打印System.out.println(collectionProxy2.size());的话,会报空指针异常的错误,为什么会这样呢?

通过打印collectionProxy2我们
发现collectionProxy2也是为空的,这就很奇怪了,难道真的为空吗?这里有个现象,那就是一个对象打印为空,有两种
情况,一种是这个对象确实为空,一种是这个对象的toString()方法总是返回null,我们可以打印collectionProxy2.toString()
方法,发现还是为null,这时候答案就出来了,肯定是toString()方法总是返回空,因为null对象调用toString()方法是
会报异常的,之所以toString()返回为空,是因为代理类每次调用方法时都会去调用InvocationHandler实现类的invoke
方法,而上面我们这个类里面总是返回null,所以就发生了这样的情况,所以下面,我们对invoke方法添加具体的实现代码:

Collection collectionProxy3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler(){//定义目标类ArrayList target = new ArrayList();@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {long beginTime = System.currentTimeMillis();//此处调用的应该是目标对象的方法,而不是代理对象proxy的方法Object retVal = method.invoke(target, args);long endTime = System.currentTimeMillis();System.out.println(method.getName()+" 方法处理耗时:"+(endTime-beginTime) +" ms");return retVal;}});collectionProxy3.add("张三");collectionProxy3.add("李四");collectionProxy3.add("王五");System.out.println(collectionProxy3.size());System.out.println(collectionProxy3.getClass().getName());

再用上面的代码打印size就没问题了,但是调用collectionProxy3.getClass().getName()的时候,我们又发现一个奇怪的问题,那就是,

既然调用代理类的方法,最终都要去调用目标类的方法,方法的返回结果也就是目标类的返回结果,
那为什么collectionProxy3.getClass().getName()方法获取到的值是$Proxy0而不是ArrayList
的字节码文件对象的名称java.lang.Class呢?因为getClass这个方法是继承自Object对象的,而Object对象
只有toString,hashCode和equals三个方法委托给代理类来处理,其他的方法都不委托


/*
             *思考:
             *上面的方法已经实现了ArrayList类的代理功能
             *但是只能代理ArrayList这个目标类,如果需要代理Vector和LinkedList就没办法了,所以代码需要改进
             *而且InvocationHandler实现类中的invoke方法里面的代码只能打印方法执行的时间差,
             *如果我还想在每次调用方法的时候加上日志的功能和事务的功能的话
             *就又得改代码了,所以代码需要改进,需要解耦,需要将目标对象和invoke方法的执行代码抽取出来,
             *封装成两个对象,这样代码的复用性更高,耦合度更低
             *下面定义一个接口Advice,定义两个方法:before和after,在调用目标方法前会执行before方法
             *在调用目标的方法后执行after方法,只要实现了这个接口的类的对象都可以传入getProxy方法,
             *所以再定义一个实现类MyAdvice,实现Advice接口并传递到getProxy方法中去
             */

//定义目标类final ArrayList target = new ArrayList();Collection collectionProxy4 = (Collection)getProxy(target,new MyAdvice());collectionProxy4.add("哈哈1");collectionProxy4.add("哈哈2");collectionProxy4.add("哈哈3");collectionProxy4.add("哈哈4");System.out.println(collectionProxy4.size());

将产生代理的方法单独提炼出来:

private static Object getProxy(final Object target, final Advice advice) {Object obj = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {advice.before(method);//此处调用的应该是目标对象的方法,而不是代理对象proxy的方法Object retVal = method.invoke(target, args);advice.after(method);return retVal;}});return obj;}

提供创建代理需要的接口和实现类:interface Advice{        void before(Method method);        void after(Method method);    }    class MyAdvice implements Advice{        private long beginTime = 0;        @Override        public void before(Method method) {            beginTime = System.currentTimeMillis();        }        @Override        public void after(Method method) {            long endTime = System.currentTimeMillis();            System.out.println(method.getName()+" 方法处理耗时:"+(endTime-beginTime) +" ms");        }            }


原创粉丝点击