文档章节

Java动态代理与CGLIB

Hosee
 Hosee
发布于 2016/04/11 19:58
字数 1743
阅读 984
收藏 18

1. 静态代理模式

因为需要对一些函数进行二次处理,或是某些函数不让外界知道时,可以使用代理模式,通过访问第三方,间接访问原函数的方式,达到以上目的,来看一下代理模式的类图:

interface Hosee{
	String sayhi();
}

class Hoseeimpl implements Hosee{

	@Override
	public String sayhi()
	{
		return "Welcome oschina hosee's blog";
	}
	
}

class HoseeProxy implements Hosee{

	Hosee h;
	
	public HoseeProxy(Hosee h)
	{
		this.h = h;
	}
	
	@Override
	public String sayhi()
	{
		System.out.println("I'm proxy!");
		return h.sayhi();
	}
	
}


public class StaticProxy
{

	public static void main(String[] args)
	{
		Hoseeimpl h = new Hoseeimpl();
		HoseeProxy hp = new HoseeProxy(h);
		System.out.println(hp.sayhi());
	}

}

1.1 静态代理的弊端

    如果要想为多个类进行代理,则需要建立多个代理类,维护难度加大。

    仔细想想,为什么静态代理会有这些问题,是因为代理在编译期就已经决定,如果代理哪个发生在运行期,这些问题解决起来就比较简单,所以动态代理的存在就很有必要了。

2. 动态代理

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface HoseeDynamic
{
	String sayhi();
}

class HoseeDynamicimpl implements HoseeDynamic
{
	@Override
	public String sayhi()
	{
		return "Welcome oschina hosee's blog";
	}
}

class MyProxy implements InvocationHandler
{
	Object obj;
	public Object bind(Object obj)
	{
		this.obj = obj;
		return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
				.getClass().getInterfaces(), this);
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable
	{
		System.out.println("I'm proxy!");
		Object res = method.invoke(obj, args);
		return res;
	}
}

public class DynamicProxy
{
	public static void main(String[] args)
	{
		MyProxy myproxy = new MyProxy();
		HoseeDynamicimpl dynamicimpl = new HoseeDynamicimpl();
		HoseeDynamic proxy = (HoseeDynamic)myproxy.bind(dynamicimpl);
		System.out.println(proxy.sayhi());
	}
}
类比静态代理,可以发现,代理类不需要实现原接口了,而是实现InvocationHandler。通过
Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
				.getClass().getInterfaces(), this);
来动态生成一个代理类,该类的类加载器与被代理类相同,实现的接口与被代理类相同。

通过上述方法生成的代理类相当于静态代理中的代理类。

这样就实现了在运行期才决定代理对象是怎么样的,解决了静态代理的弊端。

当动态生成的代理类调用方法时,会触发invoke方法,在invoke方法中可以对被代理类的方法进行增强。

通过动态代理可以很明显的看到它的好处,在使用静态代理时,如果不同接口的某些类想使用代理模式来实现相同的功能,将要实现多个代理类,但在动态代理中,只需要一个代理类就好了。

除了省去了编写代理类的工作量,动态代理实现了可以在原始类和接口还未知的时候,就确定代理类的代理行为,当代理类与原始类脱离直接联系后,就可以很灵活地重用于不同的应用场景中。

2.1 动态代理的弊端

代理类和委托类需要都实现同一个接口。也就是说只有实现了某个接口的类可以使用Java动态代理机制。但是,事实上使用中并不是遇到的所有类都会给你实现一个接口。因此,对于没有实现接口的类,就不能使用该机制。

而CGLIB则可以实现对类的动态代理

2.2 回调函数原理

上文说了,当动态生成的代理类调用方法时,会触发invoke方法。

很显然invoke方法并不是显示调用的,它是一个回调函数,那么回调函数是怎么被调用的呢?

上述动态代理的代码中,唯一不清晰的地方只有

Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
				.getClass().getInterfaces(), this);
跟踪这个方法的源码,可以看到程序进行了验证、优化、缓存、同步、生成字节码、显示类加载等操作,前面的步骤并不是我们关注的重点,而最后它调用了
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces);
该方法用来完成生成字节码的动作,这个方法可以在运行时产生一个描述代理类的字节码byte[]数组。

在main函数中加入

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
加入这句代码后再次运行程序,磁盘中将会产生一个名为"$Proxy().class"的代理类Class文件,反编译(反编译工具我使用的是 JD-GUI )后可以看见如下代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy
  implements HoseeDynamic
{
  private static Method m1;
  private static Method m3;
  private static Method m0;
  private static Method m2;

  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String sayhi()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m3, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("HoseeDynamic").getMethod("sayhi", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}
动态代理类不仅代理了显示定义的接口中的方法,而且还代理了java的根类Object中的继承而来的equals()、hashcode()、toString()这三个方法,并且仅此三个方法。 

可以在上述代码中看到,无论调用哪个方法,都会调用到InvocationHandler的invoke方法,只是参数不同。

2.3 动态代理与静态代理的区别

  1. Proxy类的代码被固定下来,不会因为业务的逐渐庞大而庞大;
  2. 可以实现AOP编程,这是静态代理无法实现的;
  3. 解耦,如果用在web业务下,可以实现数据层和业务层的分离。
  4. 动态代理的优势就是实现无侵入式的代码扩展。 静态代理这个模式本身有个大问题,如果类方法数量越来越多的时候,代理类的代码量是十分庞大的。所以引入动态代理来解决此类问题

3. CGLIB

cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

class CGlibHosee
{
	public String sayhi()
	{
		return "Welcome oschina hosee's blog";
	}
}

class CGlibHoseeProxy
{
	Object obj;

	public Object bind(final Object target)
	{
		this.obj = target;
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(obj.getClass());
		enhancer.setCallback(new MethodInterceptor()
		{
			@Override
			public Object intercept(Object obj, Method method, Object[] args,
					MethodProxy proxy) throws Throwable
			{
				System.out.println("I'm proxy!");
				Object res = method.invoke(target, args);
				return res;
			}
		});
		return enhancer.create();
	}

}

public class CGlibProxy
{
	public static void main(String[] args)
	{
		CGlibHosee cGlibHosee = new CGlibHosee();
		CGlibHoseeProxy cGlibHoseeProxy = new CGlibHoseeProxy();
		CGlibHosee proxy = (CGlibHosee) cGlibHoseeProxy.bind(cGlibHosee);
		System.out.println(proxy.sayhi());
	}
}
cglib需要指定父类和回调方法。当然cglib也可以与Java动态代理一样面向接口,因为本质是继承。

Reference:

1. http://blog.csdn.net/lidatgb/article/details/8941711

2. http://shensy.iteye.com/blog/1698197

3. http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html

4. http://www.shangxueba.com/jingyan/1853835.html

5. 《深入理解Java虚拟机》

6. http://paddy-w.iteye.com/blog/841798

© 著作权归作者所有

上一篇: 责任链模式
下一篇: Spring IOC理解
Hosee
粉丝 614
博文 135
码字总数 209956
作品 0
杭州
程序员
私信 提问
加载中

评论(2)

1
1994
写的很好。
lanseliu
lanseliu
无法收藏
cglib代理和java自带代理的区别

一 动态代理主要有java自带的代理和cglib方式实现的代理 首先讲下java自带的代理 ------------------然后是cglib自带的代理------------------------- 测试类: java动态代理是利用反射机制生...

sen_ye
2018/07/15
63
0
Spring Boot AOP代理

AOP 代理的两种实现: jdk是代理接口,私有方法必然不会存在在接口里,所以就不会被拦截到; cglib是子类,private的方法照样不会出现在子类里,也不能被拦截。 Java 动态代理。 具体有如下四...

xiaolyuh
09/21
38
0
java动态代理和cglib动态代理

动态代理应用广泛,Spring,Struts等框架很多功能是通过动态代理,或者进一步封装来实现的。 常见的动态代理模式实现有Java API提供的动态代理和第三方开源类库CGLIB动态代理。 Java API提供...

Carl_
2014/12/30
277
0
Cglib 与 JDK动态代理

AOP 代理的两种实现: jdk是代理接口,私有方法必然不会存在在接口里,所以就不会被拦截到; cglib是子类,private的方法照样不会出现在子类里,也不能被拦截。 JDK 动态代理。 具体有如下四...

xiaolyuh
09/20
93
0
JDK动态代理与Cglib动态代理(转载)

spring容器通过动态代理再结合java反射思想可以使得方法调用更加简洁 一、动态代理概述: 与静态代理对照(关于静态代理的介绍 可以阅读上一篇:JAVA设计模式之 代理模式【Proxy Pattern】(...

思悟修
2015/08/14
632
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周三乱弹 —— 调查人员问狗 那你在做什么啊?

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 小小编辑推荐:《Let It Be》- John Denver 《Let It Be》- John Denver 手机党少年们想听歌,请使劲儿戳(这里) @FalconChen :每天看一遍,...

小小编辑
今天
6
0
高效程序员的45个习惯总结版-文末脑图

1 做事 一个重大的错误应该被当做一次学习而不是指责他人的机会,团队成员一起工作,应该互相帮助,而不是互相指责 2 欲速则不达 不要为了修复问题而去修复,要投入时间和精力保持代码整洁 ...

阿提说说
今天
18
0
带南海九段线分位数地图可视化(R语言版)

今天带来一篇承诺虾神的可视化博客。内容是使用R语言进行带南海九段线分位数地图可视化。虾神的原博文地址如下(Python版)。 Python实现带南海九段线分位数地图完整可视化版本(附代码及数据...

胖胖雕
今天
12
0
Nginx 的进程结构,你明白吗?

Nginx 进程结构 这篇文章我们来看下 Nginx 的进程结构,Nginx 其实有两种进程结构: 单进程结构 多进程结构 单进程结构实际上不适用于生产环境,只适合我们做开发调试使用。因为在生产环境中...

武培轩
今天
20
0
蓝鲸平台部署

环境 系统:Centos7 依赖包:java8 主机: 10.0.1.150 域名:paas.ops.net;cmdb.ops.net;job.ops.net 生成SSH key ssh-keygen -t rsa -P '' 生成证书 https://bk.tencent.com/download_ssl/......

以谁为师
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部