背景:
近期研究spring的源码发现源码中很多地方都用到了动态代理,比如:
1、spring中的AOP主要由动态代理实现切面编程;
2、spring中的DI(依赖注入)功能主要是通过动态代理和反射技术来实现的;
3、还有其他一些使用策略等设计模式的地方等等;
看到很多核心功能都用到了动态代理所以有感而发,觉得对于使用spring框架搭建项目的开发者来说非常有必要弄清楚动态代理实现的原理,下面请看我细细道来;
java中的动态主要有两种实现方式:基于JDK自带的IvocationHandler实现和基于开源框架CGlib实现;下面我将逐一讲解;
将具体实现之前,我们先捋一捋必要的理论知识,防止晕车,对于基础内功深厚的请直接跳过这段扫盲文字,直接移步动态代理;
什么是代理模式:
代理模式属于23种设计模式中的结构型设计模式; 为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
代理模式分:
1、静态代理:因在编程时就已经确定某个类的代理类以及代理类要去代理的类,不会再改变,所以称为静态代理
优点
代理使客户端不需要知道实现类是什么,怎么做,而客户端只需知道代理即可
方便增加功能,扩展业务逻辑
缺点
代理类中常出现大量冗余的代码,非常不利于扩展和维护
静态代理方式需要为每个接口实现一个代理类,而这些代理类中的代码 几乎是一致的。这在大型系统中将会产生很大的维护问题。
2、动态代理:在运行时,创建目标类,可以调用和扩展目标类的方法
优点:动态代理的应用使我们的类职责更加单一,复用性更强。代理逻辑与业务逻辑是互相独立的,没有耦合
缺点: 从代码上没法直观的看出代理关系;
jdk和cglib两种实现方式有各自不同的缺点,看下面详细描述;
一、基于JDK实现
新建一个java类,implements InvocationHandler接口,然后实现invoke,在invoke方法里面写代理操作,代码如下;
代理类:
public class MyJdkProxy implements InvocationHandler {
// 目标类,也就是被代理对象
private Object target;
public void setTarget(Object target)
{
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增加一下");
Object obj = method.invoke(target,args);
return obj;
}
// 生成代理类
public Object CreatProxyedObj()
{
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
被代理类:
public class HunterOne implements Hunter {
@Override
public void display() {
System.out.println("HunterOne");
}
}
测试代码:
public static void main(String[] args) {
Hunter hunter = new HunterTwo();
MyJdkProxy proxy = new MyJdkProxy();
proxy.setTarget(hunter);
Hunter object = (Hunter) proxy.CreatProxyedObj();
object.display();
}
实现原理:
1、根据InvocationHandler 的实现类调用newProxyInstance生成一个被代理对象的代理类 $Proxy0,该对象是弱引用的;
2、在$Proxy0中的静态代码块中,通过反射首先获得被代理目标类中的调用方法和方法参数,将方法拷贝一份;
3、调用InvocationHandler实现类的invoke方法来完成实际调用;
优点:
无需依赖三方jar;
jdk团队一直在优化升级;
生成的代理类是弱引用的,不会出现内存问题;
缺点:
必须要求代理对象实现一个接口,这个跟实现的原理有关系;
无法直接看出代理关系;
二、基于开源框架cglib
新建一个class implements MethodInterceptor ,然后实现intercept方法;代码如下:
public class MyCglibProxy implements MethodInterceptor {
// 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中
public Object CreatProxyedObj(Class<?> clazz) {
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("Cglib开始增强");
Object obj = methodProxy.invokeSuper(o,objects);
System.out.println("Cglib结束增强");
return obj;
}
}
被代理类:
这里面大家注意我故意写了一个用final修饰的方法,为了测试Cglib无法代理final修饰的方法;
public class CglibDemo {
public CglibDemo() {
System.out.println("构造器");
}
public void display() {
System.out.println("CglibDemo");
}
final public void heihei() {
System.out.println("嘿嘿");
}
}
测试类:
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//设置enhancer对的父类,就是被代理对象
enhancer.setSuperclass(CglibDemo.class);
//设置回调对象
enhancer.setCallback(new MyCglibProxy());
//创建代理对象
CglibDemo proxy = (CglibDemo) enhancer.create();
proxy.display();
proxy.heihei();
}
控制台打印结果:
构造器
Cglib开始增强
CglibDemo
Cglib结束增强
嘿嘿
实现原理:
1、实现CGLIB动态代理必须实现MethodInterceptor(方法拦截器)接口;
2、通过Enhancer.create()方法创建代理对象;
3、代理对象继承于被代理对象,通过拦截器调用intercept()方法,
4、intercept()方法由自定义的MethodInterceptor实现类实现,所以,最后调用MyMethodInterceptor中
的intercept()方法,从而完成了由代理对象访问到目标对象的动态代理实现。
优点:
CGLib动态代理是通过字节码底层继承要代理类来实现,速度快;
相对于jdk动态代理,可以代理没有实现接口的目标类;
缺点:
因为是用继承的方式实现代理,所以无法代理有final修饰的类和方法;
生成的字节码class文件都是直接进入永久代,会造成内存问题;
以上为记录的内容;后面深入spring 的时候,还会再继续详细阐述两个动态代理底层对象创建的原理;