动态代理是一种在运行时动态地建立代理对象,动态地处理代理方法调用的机制。html
实际上它是一种代理机制。代理能够看作是对调用目标的一个封装,直接经过代理来实现对目标代码的调用java
提早写好代理类,每一个业务类都要对应一个代理类,不灵活 git
以上的图解本质上就是代理模式的一个讲解,适用于静态代理和动态代理,区别在于代理对象和代理方法生成的时间和方式不一样。github
动态代理是运行时自动生成代理对象,一个缺点是生成代理对象和调用代理方法须要耗费时间spring
动态代理主要有两种实现方式,一种是JDK动态代理,一种是CGLIB字节码机制,固然还有Javassist或ASM库,这两个在CGLIB那块一并介绍编程
说到JDK动态代理,就不得不提起反射机制,JDK动态代理就是经过反射机制实现的。api
反射就是经过Class类和java.lang.reflect类库在运行时获取某个类的信息。好比经过java.lang.reflect类库中Field,Method以及Constructor类就能够获取类的相关信息数组
下面是经过反射机制实现JDK动态代理的一个简单例子bash
/**
* 动态代理的实现
*
* @author pjmike
* @create 2018-08-04 17:42
*/
public class DynamicProxy {
public static void main(String[] args) {
IHelloImpl hello = new IHelloImpl();
MyInvocationHandler handler = new MyInvocationHandler(hello);
//获取目标用户的代理对象
IHello proxyHello = (IHello) Proxy.newProxyInstance(IHelloImpl.class.getClassLoader(), IHelloImpl.class.getInterfaces(), handler);
//调用代理方法
proxyHello.sayHello();
}
}
/**
* 被访问者接口
*/
interface IHello{
void sayHello();
}
/**
* 被访问者的具体实现类
*/
class IHelloImpl implements IHello {
@Override
public void sayHello() {
System.out.println("Hello World");
}
}
class MyInvocationHandler implements InvocationHandler {
private Object target;
/**
*
* @param target 被代理的目标对象
*/
public MyInvocationHandler(Object target) {
this.target = target;
}
/**
* 执行目标对象的方法
*
* @param proxy 代理对象
* @param method 代理方法
* @param args 方法参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke method");
System.out.println("Method name : "+method.getName());
Object result = method.invoke(target, args);
return result;
}
}
复制代码
从上面的例子中能够看出,代理对象的生成是经过Proxy.newProxyInstance()
来完成的网络
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
复制代码
newProxyInstance()
方法主要如下三个参数
动态代理能够将全部调用重定向到调用处理器,所以一般上会向调用处理器的构造器传递一个"实际"对象的引用,从而使得处理器在执行任务时,能够请求转发。
cglib是一种基于ASM的字节码生成库,用于生成和转换Java字节码.
而ASM是一个轻量但高性能的字节码操做框架。cglib是基于ASM的上层应用,对于代理没有实现接口的类,cglib很是实用。
本质上说,对于须要被代理的类,它只是动态生成一个子类以覆盖非final的方法,同时绑定钩子回调自定义的拦截器。
添加CGLIB依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
/dependency>
复制代码
package com.pjmike.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* CGLIB动态代理实现
*
* @author pjmike
* @create 2018-08-06 16:55
*/
public class CglibProxy {
public static void main(String[] args) {
//Enhancer是CGLIB的核心工具类,是一个字节码加强器,它能够方便的对类进行扩展
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
//设置回调所需的拦截器
enhancer.setCallback(new MyMethodInterceptor());
//经过enhancer.create()方法获取代理对象
//对代理对象全部非final的方法调用都会转发给MethodInterceptor.intercept方法,
//做用跟JDK动态代理的InvocationHandler相似
PersonService personService = (PersonService) enhancer.create();
System.out.println(personService.sayHello("pjmike"));
}
}
class PersonService {
public String sayHello(String name) {
return "Hello, " + name;
}
}
class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(obj, args);
}
}
复制代码
至于上面提到的javassist也是须要直接操做字节码,跟ASM相似,因此这二者使用门槛比较高,通常用于框架的底层实现。好比hibernate底层使用了javassist和cglib.
javassist是一个运行时编译库,能够动态的生成或修改类的字节码。它有两种实现动态代理的方案: javassist提供的动态代理接口和javassist字节码。
由于javassist提供动态代理接口比较慢,因此这里主要分析javassist字节码,先不考虑其动态代理接口。至于这几种代理方案的性能比较问题,参考动态代理方案性能对比。
javassist主要由CtClass,CtMethod,CtField几个类组成,与JDK反射中的Class,Method,Field类似。
添加依赖
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
</dependency>
复制代码
代码
package com.pjmike.proxy;
import javassist.*;
/**
* javassist字节码
*
* @author pjmike
* @create 2018-08-07 0:07
*/
public class JavassistByteCode {
public static void main(String[] args) throws IllegalAccessException, CannotCompileException, InstantiationException, NotFoundException {
ByteCodeAPI byteCodeApi = createJavassistBycodeDynamicProxy();
System.out.println(byteCodeApi.sayHello());
}
public static ByteCodeAPI createJavassistBycodeDynamicProxy() throws CannotCompileException, IllegalAccessException, InstantiationException, NotFoundException {
//获取运行时类的上下文
ClassPool pool = ClassPool.getDefault();
//动态建立类
CtClass cc = pool.makeClass(ByteCodeAPI.class.getName()+"demo");
cc.addInterface(pool.get(ByteCodeAPI.class.getName()));
//建立属性
CtField field = CtField.make("private String a;", cc);
cc.addField(field);
//建立方法
CtMethod method = CtMethod.make("public String sayHello() {return \"hello\";}", cc);
cc.addMethod(method);
//添加构造器
cc.addConstructor(CtNewConstructor.defaultConstructor(cc));
Class<?> pc = cc.toClass();
ByteCodeAPI byteCodeApi = (ByteCodeAPI) pc.newInstance();
return byteCodeApi;
}
}
interface ByteCodeAPI {
public String sayHello();
}
//结果:输出 hello
复制代码
动态代理实际上有不少应用,好比spring aop的实现,rpc框架的实现,一些第三方工具库的内部使用等等。这里简单介绍动态代理在spring aop和RPC框架中的应用
Spring AOP的动态代理实现主要有两种方式,JDK动态代理和CGLIB字节码生成。
默认状况下,若是Spring AOP发现目标对象后实现了相应的interface,则采用JDK动态代理机制为其生成代理对象。若是没有发现接口,则采用CGLIB的方式为目标对象生成动态的代理对象实例
RPC即远程过程调用,它的实现中使用到了动态代理,关于RPC的具体原理参照你应该知道的RPC原理等文章
实际上RPC框架要解决的一个问题就是: 如何调用他人的远程服务?像调用本地服务同样调用远程服务。
如何封装数据,经过网络传输给远程服务,使远程接口透明,这就须要动态代理的帮助了。因此透明化远程服务调用就是要利用动态代理,在代理层对数据进行封装,网络传输等操做。
实际开发中,咱们不少时候都是利用现成的框架和开源库,其中就包含动态代理的应用。只有真正了解了动态代理的知识,才能更好地理解其在框架中的设计,也有利于更好的去使用框架。