代理模式详解
前言:本文章是以java为例,对代理模型的应用和源码详细讲解与分析。部分内容来自网络文献资料,对其进行了概括归纳补充;除此之外还有一些代码案例。
一、认识代理模式
提问:什么是代理模式?代理又是什么?
首先,代理模式是一种设计范式,所谓的设计范式就是前辈们约定俗成用来解决某些特定问题的规范套路;其次什么是代理,例如,演艺圈的经纪人,婚礼的承办方等等,那么在程序中也是如此。
目标对象:原对象,是代理对象的客户,代理对象主要围绕着它做修饰,增加功能。
代理对象:是原对象的替身,会代替原对象出席某种场合,并且代理具备的功能比原对象更加丰富,但是核心的事情还是由原对象亲自完成。
目的:使用代理模式的只要目标是降低系统的耦合度,扩展性好,并且起到保护目标对象的作用。
二、代理模式常见的创建方式
分类:代理模式有两种分类方式,第一种是以状态,分别是静态和动态之分;第二种是以实现方式,分为抽象和继承。本文中以状态为大类分别介绍2*2四种的实现方式:
设想一个生活中的例子:小明因为饿了,想点一份鱼香肉丝,叫了外卖让外卖员送过来并且让外卖员带一份奶茶。
例子分析:
目标对象:小明
代理对象:外卖员
扩展功能:顺带奶茶
1、静态代理
静态代理就是需要程序员手写代理类;
实现一:实现共同接口
让目标对象和代理对象都实现一个共同的接口,这样目标类和代理类就有了公共方法,就可以基于共同接口的来实现功能的扩展。代码如下:
OrderInterface接口:
public interface OrderInterface {
public String order(String foodName);
}
customer类:
public class Customer implements OrderInterface{
public String order(String foodName){
return "已下单点了"+foodName;
}
}
DeliveryClerk类:
public class DeliveryClerk implements OrderInterface{
//把原来的对象传入并保存到成员位置。也就是目标类对象
private OrderInterface source;
public DeliveryClerk(OrderInterface source) {
this.source=source;
}
@Override
public String order(String foodName) {
String result = source.order(foodName);
System.out.println("已经接订单在送去的路上");
return result+"买了一杯珍珠奶茶";
}
}
测试类:
public class Test {
public static void main(String[] args) {
//1.创建顾客对象
Customer customer = new Customer();
//2.创建代理对象
DeliveryClerk deliveryClerk = new DeliveryClerk(customer);
//3.调用方法
String result = deliveryClerk.order("鱼香肉丝盖饭");
System.out.println(result);
}
}
实现二:继承方式
让代理对象类继承目标对象类,这样代理对象就拥有了目标对象的方法,同时可以对其功能进行扩展。
customer类:
public class Customer {
public String order(String foodName){
return "已下单点了"+foodName;
}
}
DeliveryClerk类:
public class DeliveryClerk extends Customer{
@Override
public String order(String foodName) {
String result = super.order(foodName);
System.out.println("已经接订单在送去的路上");
return result+"买了一杯珍珠奶茶";
}
}
总结:如上所示,静态代理的缺点就是代理类需要程序员自定义,一旦接口变动,所有相关的类和接口都得进行维护,层本较高,代码耦合较大。
2、动态代理
动态代理所谓的“动态”是指程序员不需要创建代理对象,所用关于代理类的创建过程,全由jdk在内存中进行,jdk自带动态代理技术,主要使用一个静态方法来创建对象,它需要目标对象必须实现接口,生产的代理对象和目标对象都实现相同的接口。
jdk自带:基于接口
编写流程介绍:
- 准备一个目标对象。
- 使用jdk的API动态生成代理对象。
- 调用代理对象执行相应的方法。(被扩展功能的方法)
入参介绍:
- ClassLoader loader:固定写法,指定目标类对象的类的加载器即可,用于加载目标类及其接口的字节码文件,通常使用目标类的字节码文件调用getClassLoader()方法可得到。
- Class<?>[] interfaces:固定写法,指定目标类的所以接口的字节码对象的数组,通常使用目标类的字节码文件调用getinterfaces()方法可得到。
- InvocationHander h:这个参数是一个接口,主要关注它里面的一个方法,它会在代理类调用方法时执行,也就是说,在代理类对象中调用的任何方法都会执行invoke()方法。所以在此方法中进行代码的扩展。 invoke()方法中参数的含义:
- proxy:就是代理类对象的一个引用也就是Proxy.newProxyInstance()方法的返回值;此引用几乎不会用到。
- method:对应的就是触发invoke执行的方法的Method对象。假如我们调用了Xxx方法,该方法触发了invoke执行,那么method就是Xxx方法对应的反射对象Method。
- args:代理对象调用方法时传入的实际参数。
OrderInterface接口:
public interface OrderInterface {
public String order(String foodName);
public void test1();
public void test2();
}
Customer类
public class Customer implements OrderInterface {
public String order(String foodName){
return "已下单点了"+foodName;
}
@Override
public void test1() {
}
@Override
public void test2() {
}
}
测试类:
public class Test {
public static void main(String[] args) {
//1. 准备一个目标类对象,也就是顾客对象
Customer customer = new Customer();
//2. 使用jdk的API动态的生成代理对象
OrderInterface deliveryClerk = (OrderInterface) Proxy.newProxyInstance(customer.getClass().getClassLoader(),
customer.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("order".equals(method.getName())) {
Object result = method.invoke(customer, args);
System.out.println("已经接订单在送去的路上");
return result + "买了一杯珍珠奶茶";
} else {
return method.invoke(customer, args);
//使用method反射调用,在原对象中执行方法,并不改变逻辑,也就是说原封不动调用原来的逻辑
}
}
}
);
//3. 调用代理对象执行相应的方法
String result=deliveryClerk.order("鱼香肉丝盖饭");
System.out.println(result);
}
}
第三方包:CGlib基于父类实现
第三方CGlib动态代理技术的优点是它不需要目标类实现接口,但是要求目标类不能是最终类,就不是说不能被final修饰。实现原理是让代理对象继承目标对象,并在原有的基础上进行扩展功能。使用这种方式之前需要先导包。
参数介绍:
- Class type:指定我们要代理的目标类的字节码对象,也就是指定目标类的类型。
- callback:也是一个接口,只是名称定义的作用。只是让别的接口去继承它,提供一个方法它会在合适的时候回来调用它。通常使用其子类,它的子类MethodInterceptor(方法拦截器)接口,其只有一个方法,叫做intercept()方法,其参数:
- proxy:就是代理类对象的一个引用也就是Enharcer.create()方法的返回值;此引用几乎不会用到。
- method:对应的就是触发intercept执行的方法的Method对象。假如我们调用了Xxx方法,该方法触发了invoke执行,那么method就是Xxx方法对应的反射对象Method。
- args:代理对象调用方法时传入的实际参数。
- methodProxy:方法的代理对象,一般不做处理,暂时忽略。
Customer类
public class Customer {
public String order(String foodName){
return "已下单点了"+foodName;
}
public void test1() {
}
public void test2() {
}
}
测试类:
public class Test {
public static void main(String[] args) {
// 创建一个目标类对象,也就是顾客对象
Customer customer = new Customer();
// 使用CGLIB创建代理对象
Customer deliveryClerk = (Customer) Enhancer.create(customer.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 判断,如果是order方法,则增强
if ("order".equals(method.getName())) {
Object result = method.invoke(customer, args);
System.out.println("已经接订单在送去的路上");
return result + "买了一杯珍珠奶茶";
} else {
return method.invoke(customer, args);
//使用method反射调用,在原对象中执行方法,并不改变逻辑,也就是说原封不动调用原来的逻辑
}
}
}
);
//3. 调用代理对象执行相应的方法
String result=deliveryClerk.order("鱼香肉丝盖饭");
System.out.println(result);
}
}
三、原理&源码分析
设想场景:
- 明星Michel有两个技能,唱歌、跳舞。
- 明星都会有个经纪人,他们的工作是帮明星处理各种除了唱歌、跳舞的其他事务。
- 演唱会负责人需要邀请明星Michel来现场唱跳,然后联系Michel的经纪人告知请求。
演示用的相关代码:
唱跳接口
public interface Starter {
void sing();
void dance();
}
明星类具体实现
public class Michel implements Starter{
@Override
public void sing() {
System.out.println("Michel singing...");
}
@Override
public void dance() {
System.out.println("Michel dancing...");
}
}
经纪人:
public class Agent implements InvocationHandler {
private Starter starter;
public Agent(Starter starter) {
this.starter = starter; // 设置代理明星
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我来代理通知Starter...");
Object result = method.invoke(starter, args);
System.out.println("表演结束,谢谢大家...");
return result;
}
}
测试:
public class Main {
public static void main(String[] args) {
Starter michel = new Michel(); // 创建明星
Agent agent = new Agent(michel); // 创建经纪人
// 利用经纪人和明星Michel之间的关系生成最终的代理对象
Starter michelProxy = (Starter) Proxy.newProxyInstance(michel.getClass().getClassLoader(), // 被代理人(Michel)
michel.getClass().getInterfaces(), // 代理了哪些接口(唱歌、跳舞)
agent); // 代理人(经纪人)
// 让代理对象进行唱跳
michelProxy.sing();
michelProxy.dance();
}
}
通过上边的代码,相信大家产生了一些疑问:
- michelProxy这个代理对象是什么?它是怎么来的?为什么它可以强转为Starter类型?
- 最后执行的是michelProxy的sing、dance方法,为什么它会调用到agent的invoke方法?这个代理对象和agent是什么关系?
为了弄清楚上面几个问题:需要看一下代理对象具体属于那个类,并且它的源码是什么。
获取内存中动态代理类源码的方式有许多,这里不再赘述直接给源码;
动态代理类源码:$Proxy0类
public final class $Proxy0 extends Proxy implements Starter { // 继承父类Proxy,实现自定义接口Starter
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
private static Method m0;
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m4 = Class.forName("mike.test.Starter").getMethod("sing");
m3 = Class.forName("mike.test.Starter").getMethod("dance");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
public $Proxy0(InvocationHandler var1) throws { // 构造方法接收代理人,并调用Proxy父类构造方法
super(var1);
}
public final void sing() throws { // 实现唱歌接口,实际上其实是调用了代理人的invoke方法(h属性其实就是我们所创建的agent,下面源码分析会讲解)
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void dance() throws { // 实现跳舞接口,实际上其实是调用了代理人的invoke方法
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
// 以下就是Object类通用方法的覆盖,不多赘述
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
通过阅读上面的代码,相信大家已经了解了动态搭理类的内部结构了。
那么这个动态代理类是如何被生成的呢?我们继续查看Proxy.newProxyInstance方法的源码。
Proxy.newProxyInstance
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
{
// 校验参数,以及接口的访问权限
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs); // 生成代理对象$Proxy0类对象(方法很长,不细讲,有兴趣可自行研究)
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl); // 校验代理对象类的接口权限
}
final Constructor<?> cons = cl.getConstructor(constructorParams); // 获取代理对象类的有参构造函数方法
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) { // 代理对象类若不是Public,则开放访问权限
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h}); // 调用代理对象类的有参构造函数,传入代理人,生成并返回代理对象实例
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
上述代码的核心精剪如下:
Class<?> cl = getProxyClass0(loader, intfs); // 生成代理对象$Proxy0类对象(方法很长,不细讲,有兴趣可自行研究)
final Constructor<?> cons = cl.getConstructor(constructorParams); // 获取代理对象类的有参构造函数方法
return cons.newInstance(new Object[]{h}); // 调用代理对象类的有参构造函数,传入代理人,生成并返回代理对象实例
此片文章对动态代理的讲解和源码分析就到这里;有兴趣的小伙伴可以再进一步深挖下源码,比如getProxyClass0方法是如何实现的。