• DesignMode_Proxy


    proxyMode 在我看来比较有意思的一点是中介+反射。而为什么在设计模式中这个代理又尤其重要的原因,是java程序猿的春天的AOP核心思想就是这个。所以熟练掌握是必须的

    首先讲一下代理模式是个什么意思,为什么要用代理模式,代理模式有什么好处?我主要采用问答的模式来解决问题,这也是我比较习惯的思考方式。

     代理模式其实来源于生活的各处,就是中介,中介就是帮助委托对象来做一些过滤的事情,比如房屋中介,可以根据委托人加的条件进行删选房源提供,衣服店可以帮助买衣服的人从工厂里面各种各样的衣服里面删选一些衣服给买衣服的人看,包括你们常用的某宝等app都可以称为中介,所以抽象到代码上来就是,为目标对象来扩展相应的繁琐事务的一种代理类。

    当然你可能会问,为什么不直接在目标对象接口实现功能上添加额外的功能代码?

    其一  当然可以,但是人所谓高级动物,就是因为人会根据麻烦的事情去寻求最简单的处理办法,如果你觉得你不需要房屋中介,也可以在偌大的房源市场中寻找到一见钟情的房子,那完全不需要中介啊,映射到代码中也是,某一个接口的功能的实现可以有很多种,但是上线之后客户突然提出要对某一接口的功能添加需求,然而苦逼的程序猿就需要为这个需求的每一实现类都去添加增加的需求代码,这工作量,光是想想就已经失去了对生活的渴望了,所以,猿需要一个代理类来实现接口扩展需求的功能。

    其二  在代码规范中讲究开闭原则,就是对扩展开放,对修改关闭,为什么这么做的原因是,一旦完成某个类后,或者整个软件后,对于要求的新的功能,扩展是最好的办法,因为扩展是以提高软件的稳定性和灵活性为前提的,而修改会导致代码的重编译,而扩展可以编译好后上线。

    以上说了这么多目的其实就是想让你们接受代理这个东东,也相当于劝你们以后找房子的时候,先去看看房屋中介哟,但是被坑神马的本人一概不负责。。。

    现在来具体看看代理类是如何优化我们的操作的?毕竟你们这群人类是要真眼看到好处才会买账的嘛


    代理的方式也分为两种,一种是静态代理,一种是动态代理

    先来看看静态代理 -- 有程序员手动创建的源代码,也就是编译的时候就已经将接口,被代理类,代理类等确定下来,在程序运行之前,代理类的 Class文件已确定

    用一个web开发的dao层实现简单模拟一下

    接口:(锁定的行为)

    package com.itamory.dao;
    
    public interface IUserDao {
    	public void save();
    	public void get();
    }
    

      

    实现类(需要被代理的对象),没被代理之前

    package com.itamory.daoImp;
    
    import org.junit.Test;
    
    import com.itamory.dao.IUserDao;
    
    public class UserDao implements IUserDao{
    
    	@Override
    	@Test
    	public void save() {
    		System.out.println("在目标对象中自己做得 : 一些繁琐但又必须的连接db的操作");
    		System.out.println("目标对象应要处理的事务 : 保存到了对象");
    		System.out.println("在目标对象中自己做得 :一些繁琐但又必须的关闭db连接的操作");
    	}
    
    	@Override
    	public void get() {
    		
    		System.out.println("得到了对象");
    		
    	}
    
    }
    

      service层的测试

    package com.itamory.serviceImp;
    
    import org.junit.Test;
    
    import com.itamory.dao.IUserDao;
    import com.itamory.daoImp.ProxyForDao;
    import com.itamory.daoImp.UserDao;
    
    public class UserService{
    
    	IUserDao dao;
    	@Test
    	public void save() {
    		dao = new UserDao();
    		dao.save();
    	}
    
    	
    	public void get() {
    		// TODO Auto-generated method stub
    		
    	}
    
    }
    

      结果:console:

    在目标对象中自己做得 : 一些繁琐但又必须的连接db的操作
    目标对象应要处理的事务 : 保存到了对象
    在目标对象中自己做得 :一些繁琐但又必须的关闭db连接的操作

     加入静态代理类之后的UserDao有所改变

    package com.itamory.daoImp;
    
    import org.junit.Test;
    
    import com.itamory.dao.IUserDao;
    
    public class UserDaoWithStaticProxy implements IUserDao{
    
        @Override
        @Test
        public void save() {
            System.out.println("目标对象应要处理的事务 : 保存到了对象");
        }
    
        @Override
        public void get() {
            
            System.out.println("得到了对象");
            
        }
    
    }

    这里UserDao的实现类save的处理业务不再繁琐,也不会再改变,所有的其他业务操作,全部交给代理类,如下

    package com.itamory.daoImp;
    
    import com.itamory.dao.IUserDao;
    
    public class ProxyForDao implements IUserDao{
        
        //维护一个委托对象
        UserDaoWithStaticProxy udsp = new UserDaoWithStaticProxy();
    
        @Override
        public void save() {
            // 复杂的操作我来做
            System.out.println("proxy 做这些复杂的db连接操作");
            udsp.save();
            System.out.println("proxy 做这些复杂的db关闭操作");
        }
    
        @Override
        public void get() {
            
        }
    
    }

    可见,代理类是维护了一个委托类这个对象的,也就是目标对象,同时自己也继承了这个接口(行为),才可以对这个接口进行扩展。

    service中的测试

    package com.itamory.serviceImp;
    
    import org.junit.Test;
    
    import com.itamory.dao.IUserDao;
    import com.itamory.daoImp.ProxyForDao;
    import com.itamory.daoImp.UserDao;
    
    public class UserService{
    
        IUserDao dao;
        @Test
        public void save() {
            dao = new UserDao();
            dao.save();
        }
    
        @Test
        public void saveInProxy(){
            dao = new ProxyForDao();
            dao.save();
        }
        
        public void get() {
            // TODO Auto-generated method stub
            
        }
    
    }

    结果:Proxy做复杂其余的操作,目标对象处理主要事务。

    proxy 做这些复杂的db连接操作
    目标对象应要处理的事务 : 保存到了对象
    proxy 做这些复杂的db关闭操作

     静态代理就是对某一个目标对象进行其他的操作,这些操作可能是这个目标接口所要扩展的功能,但很明显,静态的代理类和被代理类接口一起在程序运行之前就已经编译完成,也就是扩展的功能也是写死了的,这样的缺点还是比较多的,就是代理类是需要和委托类实现一样的接口的,所以代理类会越写越多,一旦接口又要增加功能,此时代理类又要增加或者维护

    动态代理 -- 在运行的时候再根据程序员在代码中的指令动态去生成的代理类,这里相比于静态代理写好编译完成的写死的代理更加灵活一些

    这里有涉及到jvm中的编译和运行的知识,简单回顾一下,我们在IDE中写好的代码,一旦ctr+s保存就会生成一个.Class文件放入硬盘中,这个是这个代码的介于机器语言的中间文件,纯二进制文件,这个过程就是编译,编译形成的二进制文件是由jvm解释运行的,这中间会有类的加载,类的执行等等,但是jvm不会随便的加载写好的类文件,而是在你要用的时候才会加载进来,且只加载一次,也就是将二进制的类文件加载到内存中这一个过程已经是运行了,当然运行过程不止这一项操作,我们现在想做的是在运行的过程中再去创建代理类对象。

    当然你会问,没有预先生成的二进制文件,怎么加载类,更何况生成类对象?

    这就要说到,类的生成的最主要的两种方式,第一种就是编译生成的类,然后通过new显式生成类对象,第二种就是通过反射,在运行的时候,通过类文件的加载到jvm内存中得到构造器后创建对象。

    动态代理主要的两大依靠的jdk类库是 Proxy类和InvocationHandler接口,通过这两个可以实现动态的生成代理类和代理类对象,如何实现如下:

    首先还是同样的接口行为IUserDao.java和目标对象UserDaoWithStaticProxy.java(上面有)

    其次是InvocationHandler接口的实现

    package com.itamory.DynamicProxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class UserDaoInvocationHandler<T> implements InvocationHandler{
        T target; // 维护的一个目标对象
        
        public UserDaoInvocationHandler(T target) { //在创建这个invocatonHandler的时候才会传入目标对象
            this.target = target;
        }
        
        /**
         * proxy:代表动态代理对象
         * method:代表正在执行的方法
         * args:代表调用目标方法时传入的实参
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            System.out.println("proxy 执行"+method.getName()+"方法");
            System.out.println("handler这里 执行额外的操作,如复杂的连接动作");
            Object res = method.invoke(target, args); // 实际是传入的目标对象执行方法
            System.out.println("handler这里 执行额外的操作,如复杂的连接关闭动作");
            return res;
        }
    
    }

    从这里可以看出,Invocation的实现类维护了target这个目标对象,具体的实现后面再说,先看动态生成的代理类的地方还是service的里面

    package com.itamory.serviceImp;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    import org.junit.Test;
    
    import com.itamory.DynamicProxy.UserDaoInvocationHandler;
    import com.itamory.dao.IUserDao;
    import com.itamory.daoImp.ProxyForDao;
    import com.itamory.daoImp.UserDao;
    import com.itamory.daoImp.UserDaoWithStaticProxy;
    
    public class UserService{
    
        IUserDao dao;
        @Test
        public void save() {
            dao = new UserDao();
            dao.save();
        }
    
        @Test
        public void saveInProxy(){
            dao = new ProxyForDao();
            dao.save();
        }
        
        @Test // 展示如何动态创建代理类对象
        public void dynamicSaveInProxy(){
            // step 1 : 创建一个目标的对象,就是需要被代理的对象
            dao = new UserDaoWithStaticProxy();
            
            // step 2 : 创建一个和被代理对象相关的invocationHandler--同时传入了目标对象
            InvocationHandler handler = new UserDaoInvocationHandler<IUserDao>(dao);
            
            // step 3: 创建一个代理类对象,传入类加载器和类的相关信息(代理对象的每个执行方法都会替换执行Invocation中的invoke方法)
            IUserDao dynamicProxy = (IUserDao) Proxy.newProxyInstance(IUserDao.class.getClassLoader(), new Class<?>[]{IUserDao.class}, handler);
            System.out.println(dynamicProxy.getClass());
            
            // step 4 : 这里指定要执行的方法
            dynamicProxy.save();
        }
        
        
        public void get() {
            // TODO Auto-generated method stub
            
        }
    
    }

    在step 2 中我传进去了一个我需要实现的目标对象到handler中,让他来维护,然后在step3中利用Proxy这个jdk类库中的自带类,调用的getProxyInstance方法来动态创建代理类,也即是说,这一代码是创建动态类的关键,所以我们需要看看这Proxy的源码一探究竟

    这里我主要是贴上这个方法的源码

     @CallerSensitive
        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            if (h == null) {
                throw new NullPointerException();
            }
    
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, interfaces);
            }
    
            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, interfaces);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                    // create proxy instance with doPrivilege as the proxy class may
                    // implement non-public interfaces that requires a special permission
                    return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                        public Object run() {
                            return newInstance(cons, ih);
                        }
                    });
                } else {
                    return newInstance(cons, ih);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString());
            }
        }

    红色字体的源码所做的事情就是,在运行时得到interfaces的Class二进制的类文件,然后对其Class创建一个实现类的类文件,然后让其成为代理类的类文件,然后根据其类文件的内容,拥有的构造器创建代理类对象,完了,同时还需要注意的是,newProxyInstance方法的第一个参数是提供类加载器,用于运行时的需要加载到缓存的类文件,第二个参数,目标对象需要实现的接口们,这里面拥有了需求扩展的所有功能方法,前两个参数已经完成了一个代理类所需要的需求扩展的接口的属性,而第三个参数,则是主要执行方法的扩展,在这里面维护了目标对象,

    先来看看结果:

    class com.sun.proxy.$Proxy4
    proxy 执行save方法
    handler这里 执行额外的操作,如复杂的连接动作
    目标对象应要处理的事务 : 保存到了对象
    handler这里 执行额外的操作,如复杂的连接关闭动作
    

      

    那为什么dynamicProxy一执行save就可以得到结果呢?

    我们可以对其进行反编译看看,因为上面说过生成了代理类的类文件的,所以加入以下代码进行编译

    package com.itamory.serviceImp;
    
    import java.io.FileOutputStream;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    import org.junit.Test;
    
    import sun.misc.ProxyGenerator;
    
    import com.itamory.DynamicProxy.UserDaoInvocationHandler;
    import com.itamory.dao.IUserDao;
    import com.itamory.daoImp.ProxyForDao;
    import com.itamory.daoImp.UserDao;
    import com.itamory.daoImp.UserDaoWithStaticProxy;
    
    public class UserService{
    
        IUserDao dao;
        @Test
        public void save() {
            dao = new UserDao();
            dao.save();
        }
    
        @Test
        public void saveInProxy(){
            dao = new ProxyForDao();
            dao.save();
        }
        
        @Test // 展示如何动态创建代理类对象
        public void dynamicSaveInProxy(){
            // step 1 : 创建一个目标的对象,就是需要被代理的对象
            dao = new UserDaoWithStaticProxy();
            
            // step 2 : 创建一个和被代理对象相关的invocationHandler--同时传入了目标对象
            InvocationHandler handler = new UserDaoInvocationHandler<IUserDao>(dao);
            
            // step 3: 创建一个代理类对象,传入类加载器和类的相关信息(代理对象的每个执行方法都会替换执行Invocation中的invoke方法)
            IUserDao dynamicProxy = (IUserDao) Proxy.newProxyInstance(IUserDao.class.getClassLoader(), new Class<?>[]{IUserDao.class}, handler);
            // 反编译类文件代码   ProxyGenerator 特殊类的使用需要设置
            byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy4", UserDaoWithStaticProxy.class.getInterfaces());
                String path = "F:/springdemo/ProxyDesign_AOP/src/com/itamory/serviceImp/dynamicProxy.class";
                try(FileOutputStream fos = new FileOutputStream(path)) {
                    fos.write(classFile);
                    fos.flush();
                    System.out.println("代理类class文件写入成功");
                } catch (Exception e) {
                    e.printStackTrace();
                   System.out.println("写文件错误");
                }
            System.out.println(dynamicProxy.getClass());
            
            // step 4 : 这里指定要执行的方法
            dynamicProxy.save();
        }
        @Test
        public void dynamicSaveInProxy_(){
            dao = new UserDaoWithStaticProxy();
            IUserDao proxy = (IUserDao) Proxy.newProxyInstance(IUserDao.class.getClassLoader(),  
                    new Class<?>[]{IUserDao.class}, 
                    new InvocationHandler(){
    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {
                        System.out.println("proxy 实现了"+method.getName()+" method");
                        System.out.println("before() 可动态扩展的地方");
                        Object res = method.invoke(dao, args);
                        System.out.println("after() 可动态扩展的地方");
                        return res;
                    }});
            proxy.save();
        }
        
        public void get() {
            
            
        }
    
    }

    然后会生成一个dynamicProxy.class文件,在cmd中输入jad -sjava dynamicProxy.class可以得到反编译的.java文件。打开后如下:

    // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
    // Jad home page: http://www.kpdus.com/jad.html
    // Decompiler options: packimports(3) 
    
    import com.itamory.dao.IUserDao;
    import java.lang.reflect.*;
    
    public final class $Proxy4 extends Proxy
        implements IUserDao
    {
    
    // 这里将传入的invocationHandler进行维护
    public $Proxy4(InvocationHandler invocationhandler) { super(invocationhandler); } public final void save() { try { super.h.invoke(this, m4, null); //看这里,是将传入的method教过handler去执行的 return; } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final void get() { try { super.h.invoke(this, m3, null); return; } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)super.h.invoke(this, m2, null); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }
    // 这里是method的全部信息
    private static Method m4; private static Method m1; private static Method m0; private static Method m3; private static Method m2; static { try { m4 = Class.forName("com.itamory.dao.IUserDao").getMethod("save", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m3 = Class.forName("com.itamory.dao.IUserDao").getMethod("get", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); } catch(NoSuchMethodException nosuchmethodexception) { throw new NoSuchMethodError(nosuchmethodexception.getMessage()); } catch(ClassNotFoundException classnotfoundexception) { throw new NoClassDefFoundError(classnotfoundexception.getMessage()); } } }

    所以这里的invoke方法就是目标对象的具体method的执行方法,当然method是由代理类来触发的,也就是dynamicProxy里面维护了一个InvocationHandler, 而InvocationHandler里面又维护了目标对象,所以最终就是在运行时,有代理类对象的触发的方法来交给其维护的InvacationHandler实现类去invoke,而InvacationHandler实现类对象的invoke又是由其维护对象来执行相应的method的,动态代理就是这么回事儿

    同时有一个小的知识点,可以看到$Proxy4这个代理类是继承Proxy类的,实现IUserDao的接口的,所以可以得出两个结论,第一动态代理类之只能对接口进行代理,无法对类进行代理,这是由java的单继承特性决定的,第二个,如果直接打印这个代理类对象,会触发toString的方法,得到的就是接口的信息,如果打印的代理类对象的.getClass()方法,得到的就是Proxy类信息.

    最后再来稍微优化一下动态代理的代码

    @Test
        public void dynamicSaveInProxy_(){
            dao = new UserDaoWithStaticProxy();
            IUserDao proxy = (IUserDao) Proxy.newProxyInstance(IUserDao.class.getClassLoader(),  
                    new Class<?>[]{IUserDao.class}, 
                    new InvocationHandler(){
    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {
                        System.out.println("proxy 实现了"+method.getName()+" method");
                        System.out.println("before() 可动态扩展的地方");
                        Object res = method.invoke(dao, args);
                        System.out.println("after() 可动态扩展的地方");
                        return res;
                    }});
            proxy.save();
        }
        

     Over!

  • 相关阅读:
    C#下解决DrawImage画出来的Image变大了的问题
    WPF的TextBox产生内存泄露的情况
    【技术积累】【C#】创建符号链接
    Wix学习整理(7)——在开始菜单中为HelloWorld添加卸载快捷方式
    Wix学习整理(5)——安装时填写注册表
    Wix学习整理(4)——关于WiX文件格式和案例HelloWorld的分析
    【技术积累】【C#】生成字符串的MD5值
    Wix学习整理(6)——安装快捷方式
    【小技巧积累】设置ListView控件的Item不在Tab键导航序列中
    修改Windows的本地hosts文件以访问facebook
  • 原文地址:https://www.cnblogs.com/AmoryWang-JavaSunny/p/7430270.html
Copyright © 2020-2023  润新知