java的动态代理机制详解

此笔记作为学习动态代理参考使用。
动态代理作为java编程的一个重要技术点,有着远程调用和不改动源码情况下进行扩展、增强等重要作用,其中应用最为大家所熟悉的应该是Spring中的AOP技术(面向切面编程)。这里会对动态代理进行比较详尽的剖析。
在java的动态代理通过反射机制实现,而在动态代理机制中,有两个重要的接口和类,一个是 InvocationHandler(Interface),一个则是 Proxy(Class),这两个接口和类是实现动态代理所必须的。首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的。
InvocationHandler:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个被代理对象handler,当我们通过代理对象调用一个方法的时候,这个方法的调用会被InvocationHandler接口的invoke方法转发调用。InvocationHandler接口invoke 方法如下:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

这里对invoke方法的三个参数进行说明。
proxy:代理实例对象(源描述:the proxy instance that the method was invoked on),通过proxy.getClass().getName()得到结果为com.sun.proxy.$Proxy0。
method:代理实例当前调用方法Method对象(源描述:the method instance corresponding to the interface method invoked on the proxy instance. The declaring class of the object will be the interface that the method was declared in, which may be a superinterface of the proxy interface that the proxy class inherits the method through.)。
args:代理对象调用方法的实际参数数组(源描述:an array of objects containing the values of the arguments passed in the method invocation on the proxy instance,or if interface method takes no arguments. Arguments of primitive types are wrapped in instances of the appropriate primitive wrapper class, such as java.lang.Integer java.lang.Boolean.)。

Proxy
Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法。如下:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException

这里对newProxyInstance方法的三个参数进行说明
loader:类加载器,一个ClassLoader对象(源描述:the class loader to define the proxy class),指明加载代理类的类加载器。
interfaces:Interface接口数组(源描述:the list of interfaces for the proxy class to implement),指明代理类要实现的接口。
h:InvocationHandler对象(源描述:the invocation handler to dispatch method invocations to),指明动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。


做完了以上准备工作,现在我们通过实例进行观察。
首先,我们定义一个接口。

package com.learn;

public interface IMessageHandler {
	
	public void say();
	public void receive(String msg);

}

接着,定义一个上述接口的实现类。

package com.learn;

public class HelloMessageHandler implements IMessageHandler {

	public void say() {
		System.out.println("Hello World!");
	}
	
	public void receive(String msg){
		System.out.println(msg);
	}

}

下一步,定义这里最核心的动态代理类。

package com.proxy;

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

import com.learn.IMessageHandler;

public class DynamicProxy implements InvocationHandler{
	
	private IMessageHandler handler;
	
	public IMessageHandler getInstance(IMessageHandler handler){
		this.handler = handler;
		return (IMessageHandler) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{IMessageHandler.class}, this);
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("method invoke before.");
		Object obj = method.invoke(handler, args);
		System.out.println("method invoke after.");
		return obj;
	}

}

最后,写一个测试方法进行测试论证。

package com.learn.test;

import org.junit.Test;

import com.learn.HelloMessageHandler;
import com.learn.IMessageHandler;
import com.proxy.DynamicProxy;

public class ProxyTest {
	
	@Test
	public void proxyTest(){
		DynamicProxy dp = new DynamicProxy();//实例化动态代理调用处理器
		IMessageHandler handler = new HelloMessageHandler();//实例化实际处理对象
		IMessageHandler proxyObj = dp.getInstance(handler);//获取代理对象
		System.out.println("代理对象类名>>" + proxyObj.getClass().getName());
		Class<?>[] proxyInterfaces = proxyObj.getClass().getInterfaces();
		for (Class<?> proxyInterface : proxyInterfaces) {
			System.out.println("代理对象实现接口名>>" + proxyInterface.getName());
		}
		System.out.println("----------------- 分割 ----------------");
		proxyObj.say();//调用say方法
		System.out.println("----------------- 分割 ----------------");
		proxyObj.receive("proxy测试");
	}

}

输出结果如下:

代理对象类名>>com.sun.proxy.$Proxy0
代理对象实现接口名>>com.learn.IMessageHandler
----------------- 分割 ----------------
method invoke before.
Hello World!
method invoke after.
----------------- 分割 ----------------
method invoke before.
proxy测试
method invoke after.

我们先看看输出的第一行中的$Proxy0,这是通过proxyObj.getClass().getName()获取的值(细心的同学会注意到invoke方法的第一个参数通过proxy.getClass().getName()获取的值和这里通过proxyObj.getClass().getName()获取的值是一模一样的,也就是说,这其实是同一个对象),可为什么生成的对象是这个对象呢?为什么通过Proxy的newProxyInstance方法生成的对象能强转为IMessageHandler接口实现类对象呢?我们看这段代码:

public IMessageHandler getInstance(IMessageHandler handler){
	this.handler = handler;
	return (IMessageHandler) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{IMessageHandler.class}, this);
}

原因就在newProxyInstance方法的第二个参数上,这里我们为代理提供一组需要实现的接口数组,也就是说,我们给这个代理对象提供了一组接口,那么我这个代理对象就会实现这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是IMessageHandler类型,所以自然可以将其强制转化为IMessageHandler类型了。
同时我们需要注意,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的直接类型,而是在运行时动态生成的一个对象,并且命名方式都是以$开头,proxy为中,最后一个数字表示对象的标号。

接着我们在看测试代码后面两行输出,代码如下:

proxyObj.say();//调用say方法
System.out.println("----------------- 分割 ----------------");
proxyObj.receive("proxy测试");
 

输出结果为:

method invoke before.
Hello World!
method invoke after.
----------------- 分割 ----------------
method invoke before.
proxy测试
method invoke after.

测试中代理对象转发调用方法的实际代码如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	System.out.println("method invoke before.");
	Object obj = method.invoke(handler, args);
	System.out.println("method invoke after.");
	return obj;
}

由上可以看出,我们在代理对象转发调用执行实际对象方法之前和之后做了增强和扩展,实际应用中,在不需要修改源码的情况下,我们可以在代理对象转发调用执行实际对象方法之前或之后增加我们所需要的实际业务功能来实现对原来业务的增强、扩展,从而达到动态代理的目的。这也是spring中AOP中实现前置、后置和环绕的实现方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值