Java中动态代理的实现方法总结

      动态代理在Java中有着广泛的应用,比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。本文主要介绍Java中几种常见的动态代理方式:

一、JDK的动态代理

JDK提供的动态代理功能只能针对(一个或多个)接口提供代理。使用步骤如下:

(1) 新建一个接口

public interface ISubject {
	public void print();
}

(2) 为接口创建一个实现类

public class SubjectImpl implements ISubject {

	@Override
	public void print() {
		System.out.println("hello world!");
	}

}

(3) 创建拦截类,实现java.lang.reflect.InvocationHandler接口

public class MyInvokeHandler implements InvocationHandler {
	
	private Object instance;
	
	public MyInvokeHandler(Object o) {
		this.instance=o;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		this.before(method);
		Object result=method.invoke(this.instance, args);
		this.after(method);
		return result;
	}

	private void before(Method method) {
		System.out.println("run before method:"+method.getName());
	}
	
	private void after(Method method) {
		System.out.println("run after method:"+method.getName());
	}
}

(4) 使用Proxy.newProxyInstance()函数生成代理对象

public class JdkProxyTester {

	public static void main(String[] args) {
		ISubject subject=new SubjectImpl();
		InvocationHandler handler=new MyInvokeHandler(subject);
		ClassLoader classLoader = ISubject.class.getClassLoader();
		Class<?>[] interfaces = { ISubject.class };
		ISubject proxy = (ISubject) Proxy.newProxyInstance(classLoader,interfaces,handler);
		proxy.print();
	}

}

二、CGlib的动态代理

CGlib也可以实现动态代理,但与JDK的不同之处是: JDK只能为接口的实现类提供代理,而CGlib可以为类的实现类提供代理,原理上使用字节码技术,不能对 final类进行继承。

(1) 添加pom依赖

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
	<groupId>cglib</groupId>
	<artifactId>cglib</artifactId>
	<version>3.2.5</version>
</dependency>

(2) 新建一个需要被代理的类

public class MySubject {
	
	private String name;

	public MySubject(String name) {
		this.name=name;
	}

	public void print() {
		System.out.println("hello world!,name=" + this.name);
	}

}

(3) 创建拦截类,实现net.sf.cglib.proxy.MethodInterceptor接口

public class MyInvokeHandler implements MethodInterceptor {

	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		this.before(method);
		Object result = proxy.invokeSuper(obj, args);
		this.after(method);
		return result;
	}

	private void before(Method method) {
		System.out.println("run before method:" + method.getName());
	}

	private void after(Method method) {
		System.out.println("run after method:" + method.getName());
	}
}

(4) 使用net.sf.cglib.proxy.Enhancer的create()方法生成代理对象

public class CglibTester {

	public static void main(String[] args) {
		MethodInterceptor handler = new MyInvokeHandler();
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(MySubject.class);
		enhancer.setCallback(handler);
		Class<?>[] argsType = new Class<?>[] { String.class };
		Object[] argsValue = new Object[] { "aa" };
		MySubject proxy= (MySubject)enhancer.create(argsType, argsValue);
		proxy.print();
	}
}

三、JAVAssist的动态代理

Java代码编译完生成.class文件,就JVM(准确说是JIT)会解释执行这些字节码(转换为机器码并执行),由于字节码的解释执行是在运行时进行的,而Javassist正式再运行时改字节码来实现的。它同样可以对类进行代理。

(1) 添加pom依赖

<!-- https://mvnrepository.com/artifact/javassist/javassist -->
<dependency>
    <groupId>javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.12.1.GA</version>
</dependency>

(2) 新建一个需要被代理的类

public class MySubject {
	
	private String name;

	public MySubject(String name) {
		this.name=name;
	}

	public void print() {
		System.out.println("hello world!,name=" + this.name);
	}

}

(3) 创建拦截类,实现javassist.util.proxy.MethodHandler接口

public class MyMethodHandler implements MethodHandler {

	@Override
	public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
		this.before(thisMethod);
		// thisMethod为被代理方法 ,proceed为代理方法, self为代理实例 ,args为方法参数
		Object result = proceed.invoke(self, args);
		this.after(thisMethod);
		return result;
	}

	private void before(Method method) {
		System.out.println("run before method:" + method.getName());
	}

	private void after(Method method) {
		System.out.println("run after method:" + method.getName());
	}
}

(4) 使用javassist.util.proxy.ProxyFactory的createClass()方法生成代理对象

public class JavassistTester {

	public static void main(String[] args) throws Exception {
		ProxyFactory factory = new ProxyFactory();
		factory.setSuperclass(MySubject.class);
		factory.setFilter(new MethodFilter() {

			@Override
			public boolean isHandled(Method m) {
				return m.getName().equals("print");
			}
		});
		Class<?> clazz = factory.createClass();
		Class<?>[] argsType = new Class<?>[] { String.class };
		Object[] argsValue = new Object[] { "aa" };
		Constructor<?> constructor=clazz.getConstructor(argsType);
		MySubject proxy = (MySubject) constructor.newInstance(argsValue);
		proxy.print();
	}

}

四、Java动态代理实现的原理

Java动态代理实现的原理:在编译期或运行期间操作修改java的字节码。

操作java字节码的工具有两个比较流行,一个是ASM,一个是Javassit 。

  • ASM

直接操作字节码指令,执行效率高,要是使用者掌握Java类字节码文件格式及指令,对使用者的要求比较高。

官网地址:https://asm.ow2.io/index.html

  • Javassit

提供了更高级的API,执行效率相对较差,但无需掌握字节码指令的知识,对使用者要求较低。

官网地址:http://www.javassist.org/

应用层面来讲一般使用建议优先选择Javassit,如果后续发现Javassit 成为了整个应用的效率瓶颈的话可以再考虑ASM。

更多Java字节码操作开源框架介绍请参考:

   https://www.cnblogs.com/zj2lzh/p/3844681.html

 

五、Java常见几种动态代理的对比

参考文章:https://zhuanlan.zhihu.com/p/87393183

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值