Java 动态代理解析

引言

本博客总结自《Java 编程思想》第十四章

一、实现第一个动态代理程序

代理是软件设计中重要的设计思想,它允许我们在调用实际操作之前或之后解耦式地编写额外的操作,而一旦不需要这些操作了,就可以轻易的移除它们。

浏览了《编程思想》中对动态代理的解释,我发现动态代理的实现也是非常简单的。

想要实现动态代理,除了要借助于 Java 的运行时类型信息( RTTI :Run-Time Type Identification),即 Class 对象,还需要反射包(java.lang.reflect.*)下的一些 API  的帮助。

1.1 完成原始调用

动态代理的本质还是代理,其最根本的目的就是充当中介者的角色,将请求转发,并在转发的过程中添加操作。因此,我们还是需要先按部就班地完成最原始的调用。

以最常见的 web 调用为例,我们写一个 Service 接口,并实现它。再写一个 Controller 来进行接口的调用。

RequestService 接口:

public interface RequestService {
	public void processRequest(Object request);
}

RequestServiceImpl 接口实现:

public class RequestServiceImpl implements RequestService {
	@Override
	public void processRequest(Object request) {
		System.out.println("执行请求处理逻辑...");
		System.out.println("请求对象:" + request);
		System.out.println("处理请求完成!");
	}
}

MyController :

public class MyController {
	
	private RequestService service;
	
	// 模拟依赖注入
	public MyController(RequestService service) {
		this.service = service;
	}
	// 模拟@RequestMapping接口方法
	public void receiveRequest(String httpRequest) {
		service.processRequest(httpRequest);
	}
}

然后我们写一个 main 方法,来模拟浏览器的调用,直接向 MyController 传递一个请求:

public class GoogleBrowser {

	// 模拟浏览器的调用
	public static void main(String[] args) {
            // 初始化service组件
	    RequestService service = new RequestServiceImpl();
            // 初始化controller组件
            MyController clr = new MyController(service);
            // 向controller发送请求
            clr.receiveRequest("Morty");
	}
}

上面几段代码是最最简单的 web 调用的层级关系,Controller 调用 Service 的方法完成业务逻辑。

执行结果:

1.2 实现调用处理器

实现动态代理的关键是要自定义一个代理类也即调用处理器,它必须实现一个叫作 InvocationHandler 的接口。

public class MyDynamicProxy implements InvocationHandler {

	private Object proxied;

	public MyDynamicProxy(Object proxied) {
		this.proxied = proxied;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args);
		return method.invoke(proxied, args);
	}
}

invoke 方法接收三个参数,第一个 proxy 是代理对象本身,一般我们不需要关心,第二个和第三个参数是请求转发的必须参数。我们在调用一个实例方法的时候,必须要知道方法的签名和实例对象,方法的签名又包括方法名和参数列表。method 参数就是接口中的抽象方法映射,而 args 就是方法所需的参数列表,而 proxied 就是能够调用方法的实际对象。

1.3 获得动态代理对象

这是动态代理调用的最关键一步,我们虽然定义好了一个动态代理类,但是通常情况下,我们并不是直接通过 new 关键字来创建动态代理对象,而是通过一个静态方法。改造后的 main 如下所示:

public class GoogleBrowser {

	// 模拟浏览器的调用
	public static void main(String[] args) {
		RequestService service = new RequestServiceImpl();

		// 获取动态代理对象
		RequestService serviceProxy = (RequestService) Proxy.newProxyInstance(RequestService.class.getClassLoader(),
				new Class[] { RequestService.class }, new MyDynamicProxy(service));

        MyController clr = new MyController(serviceProxy);
		// 调用 controller 接口
		clr.receiveRequest("Morty");
	}
}

执行结果:

二、Proxy.newProxyInstance(...) 解析

这个静态方法会返回一个指定接口的代理类实例,这个代理类实例可以将方法调用转发给指定的调用处理器。

该静态方法需要传递三个参数:

1、ClassLoader loader :通常可以直接将被代理接口 Class 对象的类加载器传递进来。

2、Class<?>[] interfaces :一个你希望代理对象实现的接口列表(注意不是类或抽象类)。

3、InvocationHandler h :InvocationHandler 接口的一个实现对象(就是刚刚定义的调用处理器对象)。

通过该方法生成的代理对象,实际上也是接口的子类对象,在调用者只有接口引用的情况下,代理对象将自动接管发送给接口的请求,并在处理后转发给被代理对象

三、对动态代理的理解

代理其实并不复杂,它是基于对接口调用的一种内部处理技巧。

代理类必须要伪装成被代理接口的子类,并封装被代理对象,才能骗过调用者的眼睛。

从编码的过程不难看出,动态代理类本身就是一个调用处理器,也就是说,动态代理的本质就是请求转发。由于使用了接口引用调用这种动态绑定的方式(多态),因此在发生真正调用行为之前,JVM 会进行类型检查,当发现接口引用指向的对象同时也是一个 InvocationHandler 接口的子类时,就明白了一切,从而自动调用 invoke() 方法,然后将必要的反射信息传递进去。这样就完成了运行时动态的请求处理。

动态代理的工作过程可以这样来描述:

在 controller 中,只有一个 RequestService 接口的引用。

当 controller 调用接口中的 processRequest() 方法时,JVM 会通过类型检查检测到 service 对象真正的类型是(通过 Proxy.newProxyInstance()方法获得的)MyDynamicProxy 类型。

那么 JVM 内部就会将请求直接转发给 MyDynamicProxy 对象内部的 invoke() 方法。

invoke() 方法收到请求后,可以通过传来的 Method 参数(实际上就是被调用的抽象接口方法信息)以及 args 参数做额外的处理。

处理完成后,将被代理对象参数列表传递给 method.invoke() 方法,完成请求的 “转发”(其中被代理对象是在Proxy.newProxyInstance() 创建代理对象的时候传递给代理对象的构造器,参数列表则是在调用者进行方法调用的时候传递给调用处理器的)。

动态代理是普通代理的进一步扩展和应用,代理类在定义之初并不知道将会代理哪个接口以及哪个被代理对象,但通过 newProxyInstance 静态方法,我们可以让代理类代理多个接口,这种变化让程序显得更加自由。

动态的创建代理,以及动态的处理所代理方法的调用都是动态代理这项技术的优秀之处。

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页