代理模式是Java常见的设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象,而是经过调用代理,来间接的调用实际的对象。
为何要采用这种间接的形式来调用对象呢?通常是由于客户端不想直接访问实际的对象,或者访问实际的对象存在困难,所以经过一个代理对象来完成间接的访问。java
从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)的接口,若是接口增长方法,则代理类也必须跟着修改。其次,代理类每个接口对象对应一个委托对象,若是委托对象很是多,则静态代理类就很是臃肿,难以胜任。框架
动态代理有别于静态代理,是根据代理的对象,动态建立代理类。这样,就能够避免静态代理中代理类接口过多的问题。动态代理是实现方式,是经过反射来实现的,借助Java自带的java.lang.reflect.Proxy
,经过固定的规则生成。
其步骤以下:ide
InvocationHandler
接口,并重写该invoke
方法第一二步骤,和静态代理同样,不过说了。第三步,代码以下:函数
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
。该方法的三个参数分别是:
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; } }
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"); } }