• Java高新技术6(静态代理与动态代理,AOP)


    1.代理概述:

    代理:一个角色代表别一个角色来完成某些特定的功能。
    比如:生产商,中间商,客户这三者这间的关系
    客户买产品并不直接与生产商打交道,也不用知道产品是如何产生的,客户只与中间商打交道,而中间商就可以对产品进行一些包装,提供一些售后的服务.(增加了一些系统功能)

    要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码

    代理示意图

    2.静态代理示例:参考http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html

    静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。 

    interface Count{
        public abstract void queryCount();
        public abstract void updateCount();
    }
    class Target implements Count{
    
        @Override
        public void queryCount() {
            // TODO Auto-generated method stub
            System.out.println("查看账户方法");
        }
    
        @Override
        public void updateCount() {
            // TODO Auto-generated method stub
            System.out.println("修改账户方法");
        }
        
    } 
    public class StaticProxy implements Count{
        private Target target;
        public StaticProxy(Target target) {
            super();
            this.target = target;
        }
        /**
         * @param args
         */
        @Override
        public void queryCount() {
            // TODO Auto-generated method stub
            System.out.println("事务处理之前");
            target.queryCount();
            System.out.println("事务处理之后");
        }
        
        @Override
        public void updateCount() {
            // TODO Auto-generated method stub
            System.out.println("事务处理之前");
            target.updateCount();
            System.out.println("事务处理之后");
        }
    
    }
    class StaticProxyDemo{
        public static void main(String[] args){
            Count proxy=new StaticProxy(new Target());
            proxy.queryCount();
            proxy.updateCount();
        }
    }

    静态代理的缺点:

    1.如果接口中的方法特别多,那么代理类和目标类都要实现这些方法,代码量相当大,维护难度加大.

    2.可以看到,代理类的方法都在做类似的工作:增加一些系统功能,通过目标类实例调用其同名方法,代码复用性太低

    由此产生动态代理技术

    3.动态代理:

    1.JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。

    2.JVM生成的动态类必须实现一个或多个接口(JVM生成的动态类有哪些方法?->通过提供一个接口,让JVM实现这个接口,从而实现里面的方法),所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。(代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码)

    3.利用反射机制实现

    代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:

    1.在调用目标方法之前

    2.在调用目标方法之后

    3.在调用目标方法前后

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

    动态代理类中的方法:

    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class ProxyDemo1 {
    
        /**
         * @param args
         */
        public static void main(String[] args)throws Exception {
            // TODO Auto-generated method stub
          //获取动态代理类中的方法,以Collection接口为例,包括继承和复写的方法,如果使用getDeclaredMethods将获取不到继承()的方法
         Class clzzProxy=Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
            
           for(Method methods : clzzProxy.getMethods()){
                String name=methods.getName();
                StringBuilder sb=new StringBuilder(name);//StringBuilder(适合单线程操作)与StringBuffer(如果被多个线程共享,StringBuffer是安全的)
                sb.append("(");
                
                //打印该构造函数形参列表
                Class[] clazzParas=methods.getParameterTypes();
                for(Class clazzPara : clazzParas)
                   sb.append(clazzPara.getName()).append(",");
                if(clazzParas.length!=0)//空参数函数不在删除','
                   sb.deleteCharAt(sb.length()-1);//删除最后一个','
                sb.append(")"); 
               
               System.out.println(sb);
           
          }
         
        }
        
    
    }

    动态代理类 继承or复写的方法

    4.动态代理类的原理:

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Arrays;
    import java.util.Collection;
    
    /*利用反射创建实现Collection接口的代理类实例*/
    public class ProxyDemo2 {
        public static void main(String[] args) throws Exception{
            
            Class proxyCls=Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
            System.out.println(Arrays.toString(proxyCls.getConstructors()));//获取代理类实例的public构造函数
            
            InvocationHandler ih=new InvocationHandler(){
    
                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                    // TODO 自动生成的方法存根
                    return null; //在这里不做任何处理
                }
                
            };
            
            Collection proxyInstance=(Collection)proxyCls.
                    getConstructor(InvocationHandler.class).newInstance(ih);//返回Object,为了使用Collection中方法,强转为Collection.
            
            System.out.println(proxyInstance);//null,难道没有Proxy实例没有创建成功?
            System.out.println(proxyInstance.toString());//也是null,说明Proxy实例创建成功,
                                     //不然调用toString会报NullPointerException                                     
    //返回null的原因是通过动态代理的原理InvocationHandler的Invoke方法也返回null,
                                      //上一条语句返回null因为需要打印对象的字符串形式,一定会调用toString
         
            proxyInstance.clear();//没有异常
             proxyInstance.size();
            proxyInstance.add("abc");//add方法和size方法均会报出NullPointerException
                            //invoke方法描述中有这样一句话:如果此方法返回的值为 null 
                           //并且接口方法的返回类型是基本类型(size返回类型int,add返回类型为boolean),
                           //则代理实例上的方法调用将抛出 NullPointerException。
            
           
            System.out.println(proxyInstance.getClass());//class com.sun.proxy.$Proxy0(代理类实例对应的字节码对象)
                                                        //该方法并没有被指派给InvocationHandler的invoke方法
            /*
                 在代理实例上的 java.lang.Object 中声明的 hashCode、equals 或 toString 方法的
             调用将按照与编码和指派接口方法调用相同的方式进行编码,
                  并被指派到调用处理程序的 invoke 方法,如上所述,传递到 invoke 的 Method 对象的声明类是 java.lang.Object。
                  代理类不重写从 java.lang.Object 继承的代理实例的其他公共方法,
            所以这些方法的调用行为与其对 java.lang.Object 实例的操作一样。
           */                                            
        }                 
    }

    反射创建代理类实例

    将以上创建代理类实例的过程简化利用Proxy的newPrxoyInstance方法:

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Collection;
    
    
    public class ProxyDemo3 {
        public static void main(String[] args){
            System.out.println(System.getProperty("sun.boot.class.path"));
            Collection proxyInstace=(Collection)Proxy.newProxyInstance(
                    Collection.class.getClassLoader(),
                    new Class[]{Collection.class},new InvocationHandler(){
    
                        @Override
                        public Object invoke(Object proxy, Method method,
                                Object[] args) throws Throwable {
                            // TODO 自动生成的方法存根
                            return null;
                        }
                        
                    });
          
        }
    }

        

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.lang.reflect.Method;
    
    public class ProxyDemo3 {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub    
            Collection proxy=(Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), 
                                 new Class[]{Collection.class},
                                 new InvocationHandler(){//也可以同过一个类实现InvocationHandler接口,传入子类对象
                                     Collection target=new ArrayList();
                                      public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
                                          
                                           long beginTime=System.currentTimeMillis();
                                             long loop=9999999L;
                                             while((loop--)!=0);//为了防止CPU运算太快,使程序运行时间近似于0
                                             
                                          Object retVal=method.invoke(target, args);//调用目标类(ArrayList)的同名方法,
                                                                                    //同时在调用前后加上系统功能代码
                                             
                                          long endTime=System.currentTimeMillis();
                                             System.out.println("开始到结束的时间: "+(endTime-beginTime)+"ms");
                                          return retVal;
                                      }
                                 });
    
           proxy.add("abc");
           proxy.add(1);
           System.out.println(proxy.size());//2
        }
    
    }

    动态代理类原理

    猜想JVM生成的代理的代码:

    $Proxy0 implements Collection
    {
        InvocationHandler handler;
        public $Proxy0(InvocationHandler handler)
        {
            this.handler = handler;
        }
    
    int size()//其返回值就是invoke的返回值
        {
            return handler.invoke(this,this.getClass().getMethod("size"),null);
        }
        void clear(){
            handler.invoke(this,this.getClass().getMethod("clear"),null);
        }
        boolean add(Object obj){
            handler.invoke(this,this.getClass().getMethod("add“,Object.class),obj);
        }
    }

    动态类工作原理示意图:

    image

    5.AOP(面向切面编程)思想:很不错的一篇博文:http://www.cnblogs.com/beliefbetrayal/archive/2012/02/03/2337522.html

    代理模式的原理是使用一个代理将对象包装起来,然后用该代理对象取代原始的对象,任何对原始对象的调用首先要经过代理。代理对象负责决定是否以及何时将方法调用信息转发到原始对象上。与此同时,围绕着每个方法的调用,代理对象也可以执行一些额外的工作。可以看出代理模式非常适合实现横切关注点。(出自上面的博文)

       将系统功能进一步封装成对象,以对象形式传递给代理类的方法,通过系统功能的对象使用其方法,这样做有个很大好处->把系统功能和代理方法分开降低两者紧密程度,另系统功能便于维护.

    package com.itheima.day6;
    
    public interface Advice {//为系统功能约定一个接口,代理类不知道接收的对象,通过接口让其知道,我实现该接口
      public abstract void begin();
      public abstract void end();
    }
    package com.itheima.day6;
    
    public class Advices implements Advice {//其中一个实现类
        private long beginTime,endTime;
        public void begin(){
          beginTime=System.currentTimeMillis();
        }
        public void end(){
            System.out.println((endTime=System.currentTimeMillis())-beginTime+"ms");        
        }
    }
    package com.itheima.day6;
    
    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 ProxyDemo4 {
    
        /**
         * @param args
         */
        public static Object getProxy(final Object target,final Advice advices){
         Object proxy=Proxy.newProxyInstance(target.getClass().getClassLoader(), 
               target.getClass().getInterfaces(),
                new InvocationHandler(){
                     public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
                         
                          advices.begin();//根据需要可以在系统功能中使用proxy,method,args,只需传入即可
                            long loop=9999999L;
                            while((loop--)!=0);//为了防止CPU运算太快,使程序运行时间近似于0
                          Object retVal=method.invoke(target, args);
                            advices.end();
                         return retVal;
                     }
                });
         return proxy;
        }
        public static void main(String[] args) {
            // TODO Auto-generated method stub
          Collection proxy=(Collection)getProxy(new ArrayList(),new Advices());//通过接口Advice来约定,实现Advice实现内部方法
          proxy.add("123");
          proxy.size();
          
          //自己代理自己,语法上没问题,但没有意义
          Object obj=getProxy(new Advices(),new Advices());
          System.out.println(obj.getClass().getName());
        }
    }
    /*
     由此可以见只需要根据需要传入 目标实例和系统功能实例
     AOP思想:
     面向切面编程,在这里利用动态代理关注的重心是切面->系统功能(程序的开始和结束时间)
     
     */

    6.实现类似spring的可配置的AOP框架:

    1.这里需要定义两个类:

    BeanFactory类作用: 
        工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。
        其getBean方法根据参数字符串返回一个相应的实例对象,
        如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,
        则直接返回该类的实例对象(直接返回没有代理的目标类实例),否则,返回该类实例对象的getProxy方法返回的对象。

    ProxyFactoryBean类作用:
        ProxyFacotryBean充当封装生成动态代理的工厂,返回一个目标类的代理类实例

    3.其实要做的就是对以上代码进一步封装:

    package com.itheima.day6.aopframework;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    import com.itheima.day6.Advice;
    
    //对刚才方法进行封装
    public class ProxyFactoryBean {
       private static Advice advices;
       private static Object target;
        public Advice getAdvices() {
            return advices;
        }
        public void setAdvices(Advice advices) {
            this.advices = advices;
        }
        public Object getTarget() {
            return target;
        }
        public void setTarget(Object target) {
            this.target = target;
        }
      public static Object getProxy(){
             Object proxy=Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                   target.getClass().getInterfaces(),
                    new InvocationHandler(){
                        public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
                              advices.begin();//根据需要可以在系统功能中使用proxy,method,args,只需传入即可
                                long loop=9999999L;
                                while((loop--)!=0);//为了防止CPU运算太快,使程序运行时间近似于0
                              Object retVal=method.invoke(target, args);
                                advices.end();
                             return retVal;
                         }
                    });
             return proxy;
            }
    
    }
    package com.itheima.day6.aopframework;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Properties;
    
    import com.itheima.day6.Advice;
    
    public class BeanFactory {
      private Properties pro=new Properties();
      public BeanFactory(InputStream is){
         try {
            pro.load(is);//将流中的数据存储到集合中
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
      }
      public Object getBean(String name){
         String className=pro.getProperty(name);
         Object readObject=null;
        try {
            readObject = Class.forName(className).newInstance();//将读取到类名的字节码加载到JVM中
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        if(readObject instanceof ProxyFactoryBean){
              Object target=null;
              Advice advices=null;
             try {
                     target = Class.forName(
                            pro.getProperty("className" + ".target")).newInstance();
                     advices = (Advice) Class.forName(
                            pro.getProperty("className" + ".advice")).newInstance();//此时"className"代表某个代理类
                    ProxyFactoryBean proxy=(ProxyFactoryBean) readObject;
                     proxy.setAdvices(advices);
                     proxy.setTarget(target);
                   
                    return proxy.getProxy();
                     
               } catch (Exception e) {
                    // TODO: handle exception
                    e.printStackTrace();
               }
             
          
        }
        return readObject;
      }
    
    }
    #className=
    java.util.ArrayList
    className=com.itheima.day6.aopframework.ProxyFactoryBean
    className.target=java.util.ArrayList
    className.advice=com.itheima.day6.Advices
    package com.itheima.day6.aopframework;
    
    import java.io.InputStream;
    
    public class TestAopFrame {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            InputStream is=BeanFactory.class.getResourceAsStream("config.properties");
            Object obj=new BeanFactory(is).getBean("className");
            System.out.println(obj.getClass().getName());//com.sun.proxy.$Proxy0
        }
    
    }

    这样做优点:如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易(概述:实现目标类和代理类切换,方便维护系统功能)

  • 相关阅读:
    python中单例模式
    python中常用的内置方法
    面向对象之反射
    绑定方法与非绑定方法
    python多态与抽象类
    python的组合与封装
    面向对象之继承与派生
    面向对象之类与对象
    python模块与包
    数据结构与算法_语言和框架特性前瞻和bug修复
  • 原文地址:https://www.cnblogs.com/yiqiu2324/p/3224489.html
Copyright © 2020-2023  润新知