DesignMode_Proxy

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

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

 代理模式其实来源于生活的各处,就是中介,中介就是帮助委托对象来作一些过滤的事情,好比房屋中介,能够根据委托人加的条件进行删选房源提供,衣服店能够帮助买衣服的人从工厂里面各类各样的衣服里面删选一些衣服给买衣服的人看,包括大家经常使用的某宝等app均可以称为中介,因此抽象到代码上来就是,为目标对象来扩展相应的繁杂事务的一种代理类。程序员

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

其一  固然能够,可是人所谓高级动物,就是由于人会根据麻烦的事情去寻求最简单的处理办法,若是你以为你不须要房屋中介,也能够在偌大的房源市场中寻找到一见倾心的房子,那彻底不须要中介啊,映射到代码中也是,某一个接口的功能的实现能够有不少种,可是上线以后客户忽然提出要对某一接口的功能添加需求,然而苦逼的程序猿就须要为这个需求的每一实现类都去添加增长的需求代码,这工做量,光是想一想就已经失去了对生活的渴望了,因此,猿须要一个代理类来实现接口扩展需求的功能。spring

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

以上说了这么多目的其实就是想让大家接受代理这个东东,也至关于劝大家之后找房子的时候,先去看看房屋中介哟,可是被坑神马的本人一律不负责。。。缓存

如今来具体看看代理类是如何优化咱们的操做的?毕竟大家这群人类是要真眼看到好处才会买帐的嘛app


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

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

用一个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!

本站公众号
   欢迎关注本站公众号,获取更多信息