Java动态代理一览笔录

1、什么是代理?

比较经典的含义如销售代理,签订合同的基础上,为委托人(厂商)销售某些特定产品或全部产品的代理商,对价格、条款及其他交易条件可全权处理。我们从销售代理那里购买产品,通常是不知道销售代理背后的委托人(厂商)是谁,也就是 "委托人" 对于我们来说是不可见的。

代理,简单来说,也就是提供代理人,并有代理人全权处理委托人的事务。

在Java中,代理模式,类似的,也就是为某个对象(即委托人)提供一个代理对象(即代理人),并由代理对象(即代理人)全权控制对于原对象(即委托人)的访问。客户对于委托人不再可见,也不能直接操作,而必须通过代理对象间接地操控。

那么我们稍微总结一下,代理的优点:

优点一:隐藏委托类的实现;

优点二:对客户和委托类间进行解耦,在不修改委托类的代码情况下,可以做一些的额外处理,如预处理,过滤,转发等。

------------------------------------------------------------------------------------------------------------

常用的代理运用场景:

1、方法增强,利用反射、动态代理实现AOP切面编程,典型就是Spring AOP

2、远程调用代理(RPC实现的基础),如Java标准库的RMI,其它比如hessian,dubbo,各种webservice框架中的远程调用

-------------------------------------------------------------------------------------------------------------

2、静态代理

静态代理,是指代理类,在程序运行前已编译为.class文件。一般静态代理,要求代理类和委托类实现同一个接口或派生自同一个父类。

举个例子,以提供商品服务为例。

商品提供接口:

public interface GoodsProivder {

	// 提供商品
	public void provider();
}

商品提供者,如具体的厂商,假设是魅族:

public class GoodsProivderImpl implements  GoodsProivder{
	@Override
	public void provider() {
		System.out.println("provider goods : Meizu MX6");
	}
}

销售代理,替代魅族提供商品给客户:

public class SalesProxy implements GoodsProivder {

	private final GoodsProivder proivder;

	public SalesProxy(GoodsProivder proivder) {
		this.proivder = proivder;
	}

	// 提供商品
	public void provider() {
		System.out.println("欢迎您光临本店,您可以随意浏览和购买,祝您购物愉快...");
		this.proivder.provider();
		System.out.println("欢迎您的光临,再见!...");
	}
}

测试类:

public class Test {

	public static void main(String[] args) {
		// 这里对于客户而言,销售代理是可见的,厂商是不可见的
		GoodsProivder proxy = new SalesProxy(new GoodsProivderImpl());
		proxy.provider();
	}
}

输出:

3、动态代理

动态代码,是指指代理类,在程序运行时通过反射动态生成。相比静态代理,动态代理可以更加方便地统一处理委托类的代理过程,而不需要修改代理类,对委托类的诸多函数方法逐个进行代理。

为什么这么说呢?

如上面的静态代理的例子,如果委托接口不仅是一个方法,如果有上百个方法,当我们需要对上百个方法做统一的处理,如预处理、过滤、日志等。我们就必须对代理类上百个方法逐个进行添加或修改逻辑。

因此,接下来我们看看动态代理如何实现。

4、动态代理的实现

动态代理有很多种,包括JDK动态代理、CGLIB、Javassist、ASM等。其中JDK动态代理指的是JDK默认的动态代理实现。CGLIB、Javassist、ASM则需要对应的第三方类库。

为实现动态代理,这里将抽象出 InvocationHandler,专门用于统一调用处理。

动态代理基本的工作流程:自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法。

那么为实现动态代理,大约有三种方法:

1、比较直观的方式,Proxy 和 功能实现类 都实现 同一个功能接口。

2、比较隐晦的方式,Proxy 继承 功能实现类,实现多态。

3、实际操作字节码,实现动态代码。(这个就不是以上工作模式的范畴了)

这里JDK采用方法1,CGLIB采用方法2,Javassist和ASM采用方法3。

以下为各类动态代码实现的区别比较:

动态代理

特点

易用性

JDK动态代理

需要绑定接口

简易

CGLIB

不需要绑定接口,基于ASM包装

简易

Javassist

动态生成和改变类结构

相对简单,直接使用java编码,不需要了解虚拟机指令

ASM

操纵字节码

复杂,需要对class组织结构和汇编语言有一定了解

推荐使用CGLIB 或者 Javassist Bytecode方式,具体哪种可以根据JDK版本以及第三方类库版本进行测试对比,进行选择。

(参照第8节)

5、JDK动态代理

JDK动态代理工作流程: 1.  获取 功能实现类XXX 上的所有接口列表; 2.  确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX ; 3.  根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码; 4 .  将对应的字节码转换为对应的class 对象; 5.  创建InvocationHandler 实例handler,用来处理Proxy所有方法调用; 6.  Proxy 的class对象 以创建的handler对象为参数,实例化一个proxy对象

我们先看看JDK有哪些API:

java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。 

Proxy方法清单:

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy) 
 
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
 
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl) 
 
// 方法 4: 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 
    InvocationHandler h)

java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。 

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
// 第三个参数是被调用的方法参数。
Object invoke(Object proxy, Method method, Object[] args)

还是刚刚静态代理,提供商品服务的例子,功能接口GoodsProivder、功能实现类GoodsProivderImpl保持不变。

这时候,销售代理SalesProxy类我们可以不写,换成编写销售调用处理器:

import io.flysium.standard.proxy.statics.GoodsProivder;

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

/**
 * 销售代理调用器
 *
 * @author Sven Augustus
 */
public class SalesInvocationHandler implements InvocationHandler {

	private Object target; // 委托类对象;

	public SalesInvocationHandler(Object target) {
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 这里可以做一下预处理
//		if(!proxy instanceof  GoodsProivder || !"provider".equals(method.getName())) {
//			throw new UnsupportedOperationException("不支持");
//		}
		System.out.println("欢迎您光临本店,您可以随意浏览和购买,祝您购物愉快...");
		Object result = method.invoke(this.target, args);
		System.out.println("欢迎您的光临,再见!...");
		return result;
	}
}

测试类:

	public static void main(String[] args) {
//		GoodsProivder proxy = new SalesProxy(new GoodsProivderImpl());
		// 利用 功能实现类,以及调用器,生成代理类实例
		GoodsProivder proxy = (GoodsProivder) Proxy.newProxyInstance(GoodsProivder.class.getClassLoader(),
				new Class[]{GoodsProivder.class},
				new SalesInvocationHandler(new GoodsProivderImpl()));
		proxy.provider();
	}

我们可以看到关键点:

1、编写一个InvocationHandler实现,定义如何调用和处理 功能实现类。

2、使用Proxy的api,加载功能实现类接口定义,以及InvocationHandler实例,创建代理类实例。

6、CGLIB

CGLIB,全称Code Generation Library 。与JDK动态代理不同的是,不需要绑定接口。

源码: https://github.com/cglib/cglib

工作流程: 1.  查找XXX上的所有非final 的public类型的方法定义; 2.  将这些方法的定义转换成字节码; 3.  将组成的字节码转换成相应的代理的class对象; 4.  实现 MethodInterceptor接口,用来处理 对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)

CGLIB的包结构:

  • net.sf.cglib.core 底层字节码处理类。
  • net.sf.cglib.transform 该包中的类用于class文件运行时转换或编译时转换。
  • net.sf.cglib.proxy 该包中的类用于创建代理和方法拦截。
  • net.sf.cglib.reflect 该包中的类用于快速反射,并提供了C#风格的委托。
  • net.sf.cglib.util 集合排序工具类。
  • net.sf.cglib.beans JavaBean工具类。

使用cglib,Maven:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

主要依赖ASM、apache的ant。

我们先看看cglib有哪些主要API:

net.sf.cglib.proxy.Enhancer    主要的增强类。

//设置产生的代理对象的父类
void setSuperclass(java.lang.Class superclass) 。
//设置CallBack接口的实例。一般为 MethodInterceptor
void setCallback(Callback callback) 
//设置多个CallBack接口的实例。一般为 MethodInterceptor
void setCallbacks(Callback[] callbacks) 
//设置方法回调过滤器。
void setCallbackFilter(CallbackFilter filter) 
//使用默认无参数的构造函数创建目标对象。
Object create() 
//使用有参数的构造函数创建目标对象。参数Class[] 定义了参数的类型,第二个Object[]是参数的值。
Object create(Class[], Object[]) 

net.sf.cglib.proxy.MethodInterceptor 主要的方法拦截类,它是Callback接口的子接口,需要用户实现。

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是被代理类的对象实例,第二个参数是被调用的方法对象
// 第三个参数是被调用的方法参数,第四个参数是代理类实例。
Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable

net.sf.cglib.proxy.CallbackFilter 可以有选择的对一些方法使用回调。

还是刚刚静态代理,提供商品服务的例子,功能接口GoodsProivder、功能实现类GoodsProivderImpl保持不变。

这时候,销售代理SalesProxy类我们可以不写,换成编写MethodInterceptor:

import io.flysium.standard.proxy.statics.GoodsProivder;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

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

/**
 * 销售方法拦截器接口
 *
 * @author Sven Augustus
 */
public class SalesMethodInterceptor implements MethodInterceptor {

	@Override
	public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy)
 throws Throwable {
		// 这里可以做一下预处理
//		if(!(target instanceof GoodsProivder) || !"provider".equals(method.getName())) {
//			return null;
//		}
		System.out.println("欢迎您光临本店,您可以随意浏览和购买,祝您购物愉快...");
		//Object result=proxy.invoke(target, args);
		Object result = proxy.invokeSuper(target, args);// 表示调用原始类的被拦截到的方法。
		System.out.println("欢迎您的光临,再见!...");
		return result;
	}
}

测试类:

import net.sf.cglib.proxy.Enhancer;

public static void main(String[] args) {
//		GoodsProivder proxy = new SalesProxy(new GoodsProivderImpl());
		// 利用 功能实现类,以及调用器,生成代理类实例
//		GoodsProivder proxy = (GoodsProivder) Proxy.newProxyInstance(GoodsProivder.class.getClassLoader(),
//				new Class[]{GoodsProivder.class},
//				new SalesInvocationHandler(new GoodsProivderImpl()));
		// cglib 中加强器
		Enhancer enhancer = new Enhancer();
		// 设置要创建动态代理的类
		enhancer.setSuperclass(GoodsProivderImpl.class);
		// 设置回调,这里相当于是对于代理类上所有方法的调用
		enhancer.setCallback(new SalesMethodInterceptor());
		// 创建动态代理类实例
		GoodsProivder proxy = (GoodsProivder) enhancer.create();
		proxy.provider();
	}

我们可以看到关键点:

1、编写一个MethodInterceptor实现,定义如何调用和处理 功能实现类。

2、创建Enhancer,并设置动态被代理的类,设置回调MethodInterceptor的实例,然后创建代理类实例。

7、Javassist

使用javassist,Maven:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.21.0-GA</version>
</dependency>

javassist.util.proxy.MethodHandler 提供了类似的方法调用处理器。

// 第一个参数Javassist动态生成的代理类实例,第二个参数是被调用的方法对象
// 第三个参数是为生成的代理类对方法的代理引用,第四个参数是被调用的方法参数
Object invoke(Object self, Method m, Method proceed, Object[] args) throws Throwable

还是刚刚静态代理,提供商品服务的例子,功能接口GoodsProivder、功能实现类GoodsProivderImpl保持不变。

这时候,销售代理SalesProxy类我们可以不写,换成编写SaleMethodHandler:

import javassist.util.proxy.MethodHandler;

import java.lang.reflect.Method;

/**
 * @author Sven Augustus
 */
public class SaleMethodHandler implements MethodHandler {

	public Object invoke(Object self, Method m, Method proceed, Object[] args) throws Throwable {
		System.out.println("欢迎您光临本店,您可以随意浏览和购买,祝您购物愉快...");
		//Object result = m.invoke(delegate, args);
		Object result = proceed.invoke(self, args); // execute the original method.
		System.out.println("欢迎您的光临,再见!...");
		return result;
	}
}

测试类:

import javassist.util.proxy.MethodFilter;

import javassist.util.proxy.Proxy;

import javassist.util.proxy.ProxyFactory;

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
		ProxyFactory f = new ProxyFactory();
		// 设置被代理类
		f.setSuperclass(GoodsProivderImpl.class);
		// 设置方法过滤器
		f.setFilter(new MethodFilter() {
			public boolean isHandled(Method m) {
				// ignore finalize()
				return !m.getName().equals("finalize");
			}
		});
		// 创建代理类
		Class c = f.createClass();
		GoodsProivder proxy = (GoodsProivder) c.newInstance();
		// 设置方法调用处理器
		((Proxy) proxy).setHandler(new SaleMethodHandler());
		proxy.provider();
	}

8、性能对比

分为两轮多次测试

第一轮:

JDK-1.6.0_45, CGLIB-3.2.5, JAVAASSIST-3.21.0.GA

结果:

Create JDK Proxy: 9 ms
Create CGLIB Proxy: 140 ms
Create JAVAASSIST Proxy: 62 ms
Create JAVAASSIST Bytecode Proxy: 98 ms
----------------
Run JDK Proxy Average: 776.0 ms
Run CGLIB Proxy Average: 223.4 ms
Run JAVAASSIST Proxy Average: 828.8 ms
Run JAVAASSIST Bytecode Proxy Average: 76.0 ms

可以看到JDK6下的JAVAASSIST Bytecode最好,CGLIB动态代理性能也不错,然后是JDK和JAVAASSIST差不多的。

第二轮:

使用的版本分别为:

JDK-1.7.0_80, CGLIB-3.2.5, JAVAASSIST-3.21.0.GA

结果:

Create JDK Proxy: 19 ms
Create CGLIB Proxy: 256 ms
Create JAVAASSIST Proxy: 124 ms
Create JAVAASSIST Bytecode Proxy: 127 ms
----------------
Run JDK Proxy Average: 117.6 ms
Run CGLIB Proxy Average: 129.2 ms
Run JAVAASSIST Proxy Average: 259.4 ms
Run JAVAASSIST Bytecode Proxy Average: 76.2 ms

可以看到JDK7下的JAVAASSIST Bytecode最好,JDK动态代理性能也不错,然后是CGLIB的。

------------------------------------------------------------------------------------------------

测试结论:

1、JAVAASSIST Bytecode字节码生成方式非常快,是CGLIB的5倍。

2、JDK6下 CGLIB次之,是JDK自带的两倍。JDK7下呢两者差不多。

3、JAVAASSIST提供者动态代理接口最慢,比JDK自带的还慢。

推荐:

如果对字节码操作比较熟悉的,首选JAVAASSIST Bytecode字节码方式实现动态代理。

否则就选择CGLIB吧。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏十月梦想

ES6数据传递的传值和传址

看一下上面一段代码,通过正常的理解确实这个样子,但是下面的代码我们只改变了test.y值而obj的也随之改变!这个样子是用于前一部分是传值,后面是传地址!   ...

29340
来自专栏向治洪

23种设计模式

一、设计模式的分类 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:...

45960
来自专栏李鹏的专栏

Java 虚拟机的内存结构

我们都知道虚拟机的内存划分了多个区域,并不是一张大饼。那么为什么要划分为多块区域呢,直接搞一块区域,所有用到内存的地方都往这块区域里扔不就行了,岂不痛快。

27110
来自专栏欧阳大哥的轮子

深入解构objc_msgSend函数的实现

熟悉OC语言的Runtime(运行时)机制以及对象方法调用机制的开发者都知道,所有OC方法调用在编译时都会转化为对C函数objc_msgSend的调用。

12520
来自专栏趣谈编程

高并发下的HashMap

HashMap不是一个线程安全的类,在并发下可能会出现死循环(JDK1.7),今天我们来聊聊这个死循环是如何形成的

11500
来自专栏Zephery

工厂模式

工厂模式 目录 何为工厂模式 工厂方法与抽象工厂 如何在Java EE中通过@Producers与@Inject注解实现工厂模式 如何创建自定义注解以及通过@Q...

440110
来自专栏Kirito的技术分享

警惕不规范的变量命名

就在最近,项目组开始强调开发规范了,今天分享一个变量名命名不规范的小案例,强调一下规范的重要性。 Boolean变量名命名规范 16年底,阿里公开了《Java...

36290
来自专栏Golang语言社区

GoStub框架二次开发实践

序言 要写出好的测试代码,必须精通相关的测试框架。对于Golang的程序员来说,至少需要掌握下面四个测试框架: GoConvey GoStub GoMock M...

418110
来自专栏程序员互动联盟

【答疑解惑】C/C++参数传递

有群友问如下一个问题,他说在下图中sun函数内部的打印是对的,但是为什么调用结束之后主调的结果确是错误的。也就是说,函数sun为什么不能把相加的结果带回主调函数...

37060
来自专栏魂祭心

原 Type System Overvie

36080

扫码关注云+社区

领取腾讯云代金券