码迷,mamicode.com
首页 > 其他好文 > 详细

设计模式之代理模式

时间:2019-01-22 10:57:22      阅读:171      评论:0      收藏:0      [点我收藏+]

标签:动态   print   sts   finally   and   exists   tcl   代理模式   额外   

代理模式

  代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。Spring 中让我们耳熟能详的 AOP 的底层就是用到了动态代理实现的,以处理调用代理对象的处理方法前后的特殊处理。

优点:

  1、职责清晰,真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。 2、高扩展性。 3、智能化。 

缺点:

  1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。优点

为什么要用代理模式?

  • 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
  • 开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

模式结构:

  • 抽象角色: 声明真实对象和代理对象的共同接口。
  • 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能够代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
  • 真实角色: 代理角色所代表的真实对象,是我们最终要引用的对象。
  代理模式分为静态代理动态代理

  静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

 应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。

静态代理:

技术分享图片

   如上图,就是静态代理的简单实现的示意图,接下去用代码来体现:

  先来一个抽象角色(即实体所共同的接口):

public interface Person {
    //找对象的方法
    void findLove();
    //找工作
    void findJob();
}

  定义真是对象,即代理对象:

public class Programmer implements Person {
    @Override
    public void findLove() {
        //程序员工作很忙需要找媒婆帮忙找对象
        //找对象也要有一些要求
        System.out.println("找对象,肤白貌美大长腿");
    }
    @Override
    public void findJob() {
    }
}

  代理对象媒婆登场:

public class MeiPo {

    private Person person;

    //没办法扩展
    public MeiPo(Person person){
        this.person = person;
    }

    //目标对象的引用给拿到
    public void findLove(){
        System.out.println("根据你的要求物色");
        this.person.findLove();
        System.out.println("匹配成功准备结婚");
    }
}

  测试:运行以下代码完成代理帮程序员找对象。

public class StaticProxyTest {
    public static void main(String[] args) {
        //只能帮程序员找对象,不能帮销售,会计等人
        //因为我所代理的对象很明确
        //静态代理毫无拓展性可言
        MeiPo meipo = new MeiPo(new Programmer());
        meipo.findLove();
    }
}

缺点:

1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

动态代理:

  JDK 动态代理:代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中将对方法进行增强处理,不需要硬编码接口,代码复用率高底层使用反射机制进行方法的调用

技术分享图片

  通过代码的方式演示 JDK动态代理,这里的抽象角色(接口)就沿用上面的 Person 接口。真实对象(即被代理类,要找对象的程序员)也沿用上面的。修改代理类(媒婆):

public class JDKMeipo implements InvocationHandler {
    //被代理的对象,把引用给保存下来
    private Person target;

    public Object getInstance(Person target) throws Exception{
        this.target = target;

        Class<?> clazz = target.getClass();

        //用来生成一个新的对象(字节码重组来实现)
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("我是媒婆:我要给你找对象,现在已经拿到你的需求");
        System.out.println("开始物色");

        method.invoke(this.target,args);

        System.out.println("如果合适的话,就准备办事");

        return  null;
    }
}

  测试:运行以下代码可以看到代理成功帮被代理对象寻到对象。

public class JDKProxyTest {

    public static void main(String[] args) {

        try {
            Person obj = (Person)new JDKMeipo().getInstance(new Programmer());
            obj.findLove();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

JDK动态代理的原理如下:

  1. 拿到被代理对象的引用,并且获取到它的所有的接口,反射获取
  2. JDK Proxy类重新生成一个新的类、同时新的类要实现被代理类所有实现的所有的接口
  3. 动态生成Java代码,把新加的业务逻辑方法由一定的逻辑代码去调用(在代码中体现)
  4. 编译新生成的Java代码.class
  5. 再重新加载到JVM中运行

  以上这个过程就叫字节码重组,稍后我们会通过手写JDK动态代理来深入的看看这个动态代理是怎么运作的。

  Cglib 动态代理:可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口,底层将方法全部存入一个数组中,通过数组索引直接进行方法调用。

技术分享图片

  从代码的角度来看看Cglib代理的实现:

  被代理类:

public class ZhangSan {
    //同样是找对象
    public void findLove(){
        System.out.println("肤白貌美大象腿");
    }
}

  代理类媒婆上线:

public class CglibMeipo implements MethodInterceptor {

    public Object getInstance(Class<?> clazz) throws  Exception{
        //Enhancer是CGLIB的字节码增强器
        Enhancer enhancer = new Enhancer();
        //要把哪个设置为即将生成的新类的父类
        enhancer.setSuperclass(clazz);

        enhancer.setCallback(this);

        return  enhancer.create();

    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //业务的增强
        System.out.println("我是媒婆:我要给你找对象,现在已经拿到你的需求");
        System.out.println("开始物色");

        methodProxy.invokeSuper(o,objects);

        System.out.println("如果合适的话,就准备办事");
        return null;
    }
}

  测试类:运行以下代码能看到代理成功。

public class CglibTest {

    public static void main(String[] args) {

        try {
            ZhangSan obj = (ZhangSan)new CglibMeipo().getInstance(ZhangSan.class);
            obj.findLove();
            System.out.println("--------------------------------");
             System.out.println(obj.getClass());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  JDK代理要求被代理的类必须实现接口,有很强的局限性。而CGLIB动态代理则没有此类强制性要求。简单的说,CGLIB会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。在CGLIB底层,其实是借助了ASM这个非常强大的Java字节码生成框架。

JDK动态代理原理实现:

  JDK动态代理的底层实现的原理在上文中有提到过,是通过字节码重组来实现的,那么我们要自己实现的话需要以下步骤:

  1. 动态生成源代码.java文件
  2. Java文件输出磁盘(做一个临时持久化,供编译后类加载器去加载到JVM)
  3. 把生成的.java文件编译成.class文件
  4. 编译生成的.class文件加载到JVM中来
  5. 返回字节码重组以后的新的代理对象

  我们都知道在使用JDK动态代理的时候需要调用以下方法:

Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)

其中三个参数:

  loader: 用哪个类加载器去加载代理对象,我们这里需要实现自己的类加载器去加载我们动态生成的类文件。

  interfaces:动态代理类需要实现的接口,JDK代理要求被代理的类必须实现接口。这里需要通过这个接口去获取被代理的方法集组装到Proxy类中。

  h:动态代理方法在执行时,会调用h里面的invoke方法去执行。

  我们需要覆盖 JDK 的Proxy的 newProxyInstance方法,ClassLoader , InvocationHandler,上代码:

  Proxy:

public class GPProxy {

    public static final String ln = "\r\n";

    public static Object newProxyInstance(GPClassLoader classLoader,Class<?> [] interfaces,GPInvocationHandler h){

        try {
            //1、动态生成源代码.java文件
            String src = generateSrc(interfaces);

            //2、Java文件输出磁盘
            String filePath = GPProxy.class.getResource("").getPath();
            System.out.println(filePath);
            File f = new File(filePath + "$Proxy0.java");
            FileWriter fw = new FileWriter(f);
            fw.write(src);
            fw.flush();
            fw.close();

            //3、把生成的.java文件编译成.class文件
            // 通过JavaCompiler进行编译
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            //获取标准文件管理器实现的新实例
            StandardJavaFileManager manage = compiler.getStandardFileManager(null,null,null);
            //获取给定文件的文件对象。 返回文件对象列表
            Iterable iterable = manage.getJavaFileObjects(f);
            // 创建任务
            JavaCompiler.CompilationTask task = compiler.getTask(null,manage,null,null,null,iterable);
            // 执行编译
            task.call();
            manage.close();

            //4、编译生成的.class文件加载到JVM中来
            Class proxyClass =  classLoader.findClass("$Proxy0");
            Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);
            f.delete();

            //5、返回字节码重组以后的新的代理对象
            return c.newInstance(h);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class<?>[] interfaces){

        StringBuffer sb = new StringBuffer();
        sb.append("package com.wuzz.demo.pattern.proxy.custom;" + ln);
        sb.append("import com.wuzz.demo.pattern.proxy.staticed.Person;" + ln);
        sb.append("import com.wuzz.demo.pattern.proxy.custom.GPInvocationHandler;" + ln);
        sb.append("import java.lang.reflect.Method;" + ln);
        sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);

        sb.append("GPInvocationHandler h;" + ln);

        sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);

        sb.append("this.h = h;");

        sb.append("}" + ln);

        for (Method m : interfaces[0].getMethods()){
            sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "() {" + ln);
            sb.append("try{" + ln);
            sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{});" + ln);
            sb.append("this.h.invoke(this,m,null);" + ln);
            sb.append("}catch(Throwable e){" + ln);
            sb.append("e.printStackTrace();" + ln);
            sb.append("}");
            sb.append("}");
        }
        sb.append("}" + ln);
        return sb.toString();
    }
}

   ClassLoader:继承ClassLoader 重写 findClass 方法。根据我们指定的路径去加载重新生成的动态类$Proxy0

public class GPClassLoader extends ClassLoader{

    private File classPathFile;

    public GPClassLoader(){
        String classPath = GPClassLoader.class.getResource("").getPath();
        this.classPathFile = new File(classPath);
    }

    @Override
    protected Class<?> findClass(String name) {

        String className = GPClassLoader.class.getPackage().getName() + "." + name;

        if(classPathFile != null){
            File classFile = new File(classPathFile,name.replaceAll("\\.","/") + ".class");
            if(classFile.exists()){
                FileInputStream in = null;
                ByteArrayOutputStream out = null;
                try{
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte [] buff = new byte[1024];
                    int len;
                    while ((len = in.read(buff)) != -1){
                        out.write(buff,0,len);
                    }
                    return  defineClass(className,out.toByteArray(),0,out.size());
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    if(null != in){
                        try {
                            in.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if(out != null){
                        try {
                            out.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return null;
    }
}

  InvocationHandler:

public interface GPInvocationHandler {
     Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

  然后需要修改媒婆的代码:实现我们自己定义的Handler

public class CustomMeipo implements  GPInvocationHandler {

    //被代理的对象,把引用给保存下来
    private Person target;

    public Object getInstance(Person target) throws Exception{
        this.target = target;

        Class<?> clazz = target.getClass();

        //用来生成一个新的对象(字节码重组来实现)
        return GPProxy.newProxyInstance(new GPClassLoader(),clazz.getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是媒婆:我要给你找对象,现在已经拿到你的需求1");
        System.out.println("开始物色1");

        method.invoke(this.target,args);

        System.out.println("如果合适的话,就准备办事1");

        return  null;
    }
}

  测试:运行以下代码可以看到结果,跟JDK动态代理的效果一样。

public class CustomPorxyTest {
    public static void main(String[] args) {
        try {
            Person obj = (Person)new CustomMeipo().getInstance(new XieMu());
            System.out.println(obj.getClass());
            obj.findLove();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  

设计模式之代理模式

标签:动态   print   sts   finally   and   exists   tcl   代理模式   额外   

原文地址:https://www.cnblogs.com/wuzhenzhao/p/10298842.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!