Java动态代理一览笔录

Java
250
0
0
2023-09-08

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 MX");
	}
}

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

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有哪些API:

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

Proxy方法清单:

// 方法: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy) 
 
// 方法:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
 
// 方法:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl) 
 
// 方法: 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
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动态代理不同的是,不需要绑定接口。

源码:

工作流程:
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>.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>.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: ms
Create CGLIB Proxy: ms
Create JAVAASSIST Proxy: ms
Create JAVAASSIST Bytecode Proxy: ms
----------------
Run JDK Proxy Average:.0 ms
Run CGLIB Proxy Average:.4 ms
Run JAVAASSIST Proxy Average:.8 ms
Run JAVAASSIST Bytecode Proxy Average:.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: ms
Create CGLIB Proxy: ms
Create JAVAASSIST Proxy: ms
Create JAVAASSIST Bytecode Proxy: ms
----------------
Run JDK Proxy Average:.6 ms
Run CGLIB Proxy Average:.2 ms
Run JAVAASSIST Proxy Average:.4 ms
Run JAVAASSIST Bytecode Proxy Average:.2 ms

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

————————————————————————————————

测试结论:

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

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

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