文档章节

【Java设计模式】之代理模式

卯金刀GG
 卯金刀GG
发布于 2018/02/27 08:38
字数 2567
阅读 10
收藏 7

代理模式是Java常见的设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。
为什么要采用这种间接的形式来调用对象呢?一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。

代理模式的UML图

从UML图中,可以看出代理类与真正实现的类都是继承了抽象的主题类,这样的好处在于代理类可以与实际的类有相同的方法,可以保证客户端使用的透明性。

代理模式的实现

代理模式可以有两种实现的方式,一种是静态代理类,另一种是各大框架都喜欢的动态代理。下面我们主要讲解一下这两种代理模式

1、静态代理

我们先看针对上面UML实现的例子,再看静态代理的特点。
Subject接口的实现

public interface Subject {
    void visit();
}

实现了Subject接口的两个类:

public class RealSubject implements Subject {

    private String name = "byhieg";
    @Override
    public void visit() {
        System.out.println(name);
    }
}
public class ProxySubject implements Subject{

    private Subject subject;

    public ProxySubject(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void visit() {
        subject.visit();
    }
}

具体的调用如下:

public class Client {

    public static void main(String[] args) {
        ProxySubject subject = new ProxySubject(new RealSubject());
        subject.visit();
    }
}

通过上面的代理代码,我们可以看出代理模式的特点,代理类接受一个Subject接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。但是也有缺点,每一个代理类都必须实现一遍委托类(也就是realsubject)的接口,如果接口增加方法,则代理类也必须跟着修改。其次,代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。

2、动态代理(JDK)

动态代理有别于静态代理,是根据代理的对象,动态创建代理类。这样,就可以避免静态代理中代理类接口过多的问题。动态代理是实现方式,是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。
其步骤如下:

  1. 编写一个委托类的接口,即静态代理的(Subject接口)
  2. 实现一个真正的委托类,即静态代理的(RealSubject类)
  3. 创建一个动态代理类,实现InvocationHandler接口,并重写该invoke方法
  4. 在测试类中,生成动态代理的对象。

第一二步骤,和静态代理一样,不过说了。第三步,代码如下:

public class DynamicProxy implements InvocationHandler {
    private Object object;
    public DynamicProxy(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object, args);
        return result;
    }
}

第四步,创建动态代理的对象

Subject realSubject = new RealSubject();
DynamicProxy proxy = new DynamicProxy(realSubject);
ClassLoader classLoader = realSubject.getClass().getClassLoader();
Subject subject = (Subject) Proxy.newProxyInstance(classLoader, new  Class[]{Subject.class}, proxy);
subject.visit();

创建动态代理的对象,需要借助Proxy.newProxyInstance。该方法的三个参数分别是:

  • ClassLoader loader表示当前使用到的appClassloader。
  • Class<?>[] interfaces表示目标对象实现的一组接口。
  • InvocationHandler h表示当前的InvocationHandler实现实例对象。

 JDK代理模式

public class ProxyFactory {  
      
    private Object obj;  
      
      
    public ProxyFactory(Object obj) {  
        super();  
        this.obj = obj;  
    }  
  
    public Object getTransactionProxyInstance(){  
          
        Object proxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {  
            /** 
             * 三个参数:1、代理对象,2、目标对象的方法,3、目标对象的参数值列表 
             */  
            @Override  
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
                System.out.println("开启事务..."); //执行核心业务之前执行的内容  
                method.invoke(obj, args);      //执行目标对象方法,即核心业务  
                System.out.println("关闭事务..."); //执行核心业务之后执行的内容  
                return proxy;  
            }  
        });  
          
        return proxy;  
    }  
    
} 

3、动态代理(CGLIB)

JDK动态代理机制只能代理实现接口的类,一般没有实现接口的类不能进行代理。cglib就是针对类来实现代理的,它的原理是对指定目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

使用cglib实现动态代理,完全不受代理类必须实现接口的限制,而且cglib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用java反射效率要高。

需要引入两个jar包:cglib.jar,asm.jar

定义了一个拦截器,在调用目标方法之前,cglib回调MethodInterceptor接口方法拦截,来实现自己的业务逻辑,类似

于JDK中的InvocationHandler接口。

@Override  
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable ;

proxy:为cglib动态生成的代理实例

method:为上文中实体类所调用的被代理的方法调用

args:为method参数数值列表

methodProxy:为生成代理类对方法的代理引用

返回:从代理实例方法调用返回的值

 

其中,methodProxy.invokeSuper(obj,arg):

调用代理类实例上的proxy方法的父类方法

 

UserDaoImpl.java

public class CglibProxyFactory {  
      
    private Object obj;  
  
    public CglibProxyFactory(Object obj) {  
        super();  
        this.obj = obj;  
    }  
      
    public Object getProxyFactory(){  
        //Enhancer类是cglib中的一个字节码增强器,它可以方便的为你所要处理的类进行扩展  
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(obj.getClass());//将目标对象所在的类作为Enhaner类的父类  
        enhancer.setCallback(new MethodInterceptor() {  
            //通过实现MethodInterceptor实现方法回调  
            @Override  
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {  
                System.out.println("事务开启...");  
                method.invoke(obj, args);  
                System.out.println("事务结束...");  
                  
                return proxy;  
            }  
        });  
          
        return enhancer.create();//生成目标对象并返回  
    }  
}  

测试类

public class TestCglibProxy {  
      
    @Test  
    public void test1(){  
        UserDaoImpl userDao = new UserDaoImpl();  
        UserDaoImpl userDaoProxy = (UserDaoImpl) new CglibProxyFactory(userDao).getProxyFactory();  
        userDaoProxy.save();  
        System.out.println("目标对象类型:"+userDao.getClass());  
        System.out.println("代理对象类型:"+userDaoProxy.getClass());  
    }  
      
      
}  

附动态加载类的原理:

1、类定义

public class Programmer {  
  
    public void code()  
    {  
        System.out.println("I'm a Programmer,Just Coding.....");  
    }  
}

2、自定义类加载器

public class MyClassLoader extends ClassLoader {  
  
    public Class<?> defineMyClass( byte[] b, int off, int len)   
    {  
        return super.defineClass(b, off, len);  
    }  
      
} 

3、然后编译成Programmer.class文件,在程序中读取字节码,然后转换成相应的class对象,再实例化

package samples;  
  
import java.io.File;  
import java.io.FileInputStream;  
import java.io.FileNotFoundException;  
import java.io.IOException;  
import java.io.InputStream;  
import java.net.URL;  
  
public class MyTest {  
  
    public static void main(String[] args) throws IOException {  
        //读取本地的class文件内的字节码,转换成字节码数组  
        File file = new File(".");  
        InputStream  input = new FileInputStream(file.getCanonicalPath()+"\\bin\\samples\\Programmer.class");  
        byte[] result = new byte[1024];  
          
        int count = input.read(result);  
        // 使用自定义的类加载器将 byte字节码数组转换为对应的class对象  
        MyClassLoader loader = new MyClassLoader();  
        Class clazz = loader.defineMyClass( result, 0, count);  
        //测试加载是否成功,打印class 对象的名称  
        System.out.println(clazz.getCanonicalName());  
                  
               //实例化一个Programmer对象  
               Object o= clazz.newInstance();  
               try {  
                   //调用Programmer的code方法  
                    clazz.getMethod("code", null).invoke(o, null);  
                   } catch (IllegalArgumentException | InvocationTargetException  
                        | NoSuchMethodException | SecurityException e) {  
                     e.printStackTrace();  
                  }  
    }  
}

在运行期的代码中生成二进制字节码

由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。

在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。当前有很多开源框架可以完成这些功能,如ASM,Javassist。

Java字节码生成开源框架介绍--ASM:

ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

不过ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解。

 

package samples;  
  
import java.io.File;  
import java.io.FileOutputStream;  
import java.io.IOException;  
  
import org.objectweb.asm.ClassWriter;  
import org.objectweb.asm.MethodVisitor;  
import org.objectweb.asm.Opcodes;  
public class MyGenerator {  
  
    public static void main(String[] args) throws IOException {  
  
        System.out.println();  
        ClassWriter classWriter = new ClassWriter(0);  
        // 通过visit方法确定类的头部信息  
        classWriter.visit(Opcodes.V1_7,// java版本  
                Opcodes.ACC_PUBLIC,// 类修饰符  
                "Programmer", // 类的全限定名  
                null, "java/lang/Object", null);  
          
        //创建构造函数  
        MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);  
        mv.visitCode();  
        mv.visitVarInsn(Opcodes.ALOAD, 0);  
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>","()V");  
        mv.visitInsn(Opcodes.RETURN);  
        mv.visitMaxs(1, 1);  
        mv.visitEnd();  
          
        // 定义code方法  
        MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V",  
                null, null);  
        methodVisitor.visitCode();  
        methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",  
                "Ljava/io/PrintStream;");  
        methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");  
        methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",  
                "(Ljava/lang/String;)V");  
        methodVisitor.visitInsn(Opcodes.RETURN);  
        methodVisitor.visitMaxs(2, 2);  
        methodVisitor.visitEnd();  
        classWriter.visitEnd();   
        // 使classWriter类已经完成  
        // 将classWriter转换成字节数组写到文件里面去  
        byte[] data = classWriter.toByteArray();  
        File file = new File("D://Programmer.class");  
        FileOutputStream fout = new FileOutputStream(file);  
        fout.write(data);  
        fout.close();  
    }  
}

Java字节码生成开源框架介绍--Javassist:

  Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

下面通过Javassist创建上述的Programmer类:

 

import javassist.ClassPool;  
import javassist.CtClass;  
import javassist.CtMethod;  
import javassist.CtNewMethod;  
  
public class MyGenerator {  
  
    public static void main(String[] args) throws Exception {  
        ClassPool pool = ClassPool.getDefault();  
        //创建Programmer类       
        CtClass cc= pool.makeClass("com.samples.Programmer");  
        //定义code方法  
        CtMethod method = CtNewMethod.make("public void code(){}", cc);  
        //插入方法代码  
        method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");  
        cc.addMethod(method);  
        //保存生成的字节码  
        cc.writeFile("d://temp");  
    }  
}

 

本文转载自:https://www.cnblogs.com/qifengshi/p/6566752.html

卯金刀GG
粉丝 26
博文 265
码字总数 72868
作品 0
昌平
程序员
私信 提问
设计模式 2014-12-19

book: 阎宏《JAVA与模式》 架构设计栏目 http://blog.csdn.net/enterprise/column.html 概要: http://bbs.csdn.net/forums/Embeddeddriver 23种设计模式分别是: 1.单例模式 2.工厂方法模式...

jayronwang
2014/12/19
279
0
设计模式15——Template Method设计模式

Template Method模板方法设计模式定义一个操作中算法的骨架,将具体步骤的执行延迟到子类中实现。Java中的抽象类就是使用了模板方法设计模式。模板方法设计模式结构如下: 以文档处理为例,T...

小米米儿小
2014/01/24
207
0
简单工厂、工厂方法、抽象工厂、策略模式、策略与工厂的区别

转载:原地址http://www.cnblogs.com/zhangchenliang/p/3700820.html 简单工厂、工厂方法、抽象工厂、策略模式、策略与工厂的区别 结合简单示例和UML图,讲解工厂模式简单原理。 一、引子 话说...

法斗斗
2018/05/08
256
0
策略模式与SPI机制,到底有什么不同?

这里说的策略模式是一种设计模式,经常用于有多种分支情况的程序设计中。例如我们去掉水果皮,一般来说对于不同的水果,会有不同的拨皮方式。此时用程序语言来表示是这样的: 如上面代码所写...

陈树义
2018/09/03
0
0
学了那么多年设计模式依然不会用!那可真蠢!

什么是设计模式? 设计模式(Design Pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决...

GitChat技术杂谈
2018/10/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

CSS--列表

一、列表标识项 list-style-type none:去掉标识项 disc:默认实心圆 circle:空心圆 squire:矩形 二、列表项图片 list-style-img: 取值:url(路径) 三、列表项位置 list-style-position:...

wytao1995
今天
6
0
linux 命令-文本比较comm、diff、patch

本文原创首发于公众号:编程三分钟 今天学了三个文本比较的命令分享给大家。 comm comm 命令比较相同的文本 $ cat charabc$ cat chardiffadc 比如,我有两个文件char和chardiff如上,...

编程三分钟
今天
7
0
QML教程

https://blog.csdn.net/qq_40194498/article/category/7580030 https://blog.csdn.net/LaineGates/article/details/50887765...

shzwork
今天
5
0
HA Cluster之5

对于使用heartbeat v2版的CRM配置的集群信息都是保存在一个名为cib.xml的配置文件中,存放在/var/lib/heartbeat/crm/下。CIB:Cluster Information Base,由于xml文件配置不是那么方便,所以...

lhdzw
今天
6
0
玩转Redis-Redis基础数据结构及核心命令

  《玩转Redis》系列文章主要讲述Redis的基础及中高级应用,文章基于Redis5.0.4+。本文主要讲述Redis的数据结构String,《玩转Redis-Redis基础数据结构及核心命令》相关操作命令为方便对比...

zxiaofan666
今天
11
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部