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

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

代理模式的UML图

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

代理模式的实现

代理模式能够有两种实现的方式,一种是静态代理类,另外一种是各大框架都喜欢的动态代理。下面咱们主要讲解一下这两种代理模式设计模式

一、静态代理

咱们先看针对上面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();
    }
}

具体的调用以下:app

public class Client {

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

经过上面的代理代码,咱们能够看出代理模式的特色,代理类接受一个Subject接口的对象,任何实现该接口的对象,均可以经过代理类进行代理,增长了通用性。可是也有缺点,每个代理类都必须实现一遍委托类(也就是realsubject)的接口,若是接口增长方法,则代理类也必须跟着修改。其次,代理类每个接口对象对应一个委托对象,若是委托对象很是多,则静态代理类就很是臃肿,难以胜任。框架

二、动态代理(JDK)

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

  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() {  
            /** 
             * 三个参数:一、代理对象,二、目标对象的方法,三、目标对象的参数值列表 
             */  
            @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;  
    }  
    
}

三、动态代理(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());  
    }  
      
      
}

附动态加载类的原理:

一、类定义

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

二、自定义类加载器

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

三、而后编译成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");  
    }  
}
相关文章
相关标签/搜索