前言
首先,我们要对代理(Proxy)是什么有一个准确的认识,代理是一种设计模式,通俗的说,就是为目标对象提供一个代理对象,并由代理对象控制对目标对象的引用。其目的有两个:
一、通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来不必要的复杂性。
二、通过代理对象在不改变目标对象的基础上对目标对象进行增强(业务逻辑或功能)。
代理模式
代理模式的UML类图如下:
代理模式.jpg
可以看出,代理模式的特点是,目标对象和代理对象均实现了同一个接口,且代理对象内关联了一个目标对象。在调用时,实际的执行者为代理对象,代理对象在执行时回调用目标对象的方法并对其进行增强,下面通过静态代理模式进行详细说明。
静态代理模式
这里,我们就以生活中的租房场景为例介绍静态代理模式。租房时,我们可以自己租房,也可以通过房屋中介租房,代码如下:
租房者公共接口
package com.cube.proxy.jt;
/**
* @author cube.li
* @date 2021/3/6 14:25
* @description 接口
*/
public interface Person {
/**
* 租房
*/
void rentHouse();
}
承租人实现类
package com.cube.proxy.jt;
/**
* @author cube.li
* @date 2021/3/6 14:28
* @description 租房者
*/
public class Tenant implements Person {
private String name;
public Tenant(String name) {
this.name = name;
}
@Override
public void rentHouse() {
System.out.println(this.name + " 正在租房中...");
}
}
房屋中介实现类
package com.cube.proxy.jt;
/**
* @author cube.li
* @date 2021/3/6 14:32
* @description 中介
*/
public class Intermediary implements Person {
private String name;
/**
* 租房者
*/
private Person tenant;
public Intermediary(String name, Person tenant) {
this.name = name;
this.tenant = tenant;
}
@Override
public void rentHouse() {
System.out.println("中介 " + this.name + "确定租房预算,带领查看房源...");
tenant.rentHouse();
System.out.println("中介 " + this.name + "带领客户租房完成,合同签订完成,收取佣金,准备跑路...");
}
}
调用示例
package com.cube.proxy.jt;
/**
* @author cube.li
* @date 2021/3/6 14:32
* @description 中介
*/
public class Intermediary implements Person {
private String name;
/**
* 租房者
*/
private Person tenant;
public Intermediary(String name, Person tenant) {
this.name = name;
this.tenant = tenant;
}
@Override
public void rentHouse() {
System.out.println("中介 " + this.name + "确定租房预算,带领查看房源...");
tenant.rentHouse();
System.out.println("中介 " + this.name + "带领客户租房完成,合同签订完成,收取佣金,准备跑路...");
}
}
调用结果如下:
中介 王五确定租房预算,带领查看房源...
李四 正在租房中...
中介 王五带领客户租房完成,合同签订完成,收取佣金,准备跑路...
-----------------
中介 陈大确定租房预算,带领查看房源...
张三 正在租房中...
中介 陈大带领客户租房完成,合同签订完成,收取佣金,准备跑路...
由上面代码可以看出,代理对象(中介)对目标对象(承租者)在租房(业务逻辑)这一过程中对其进行了增强。可以通过这一个示例对代理模式有一个更深刻的认识,代理过程并不是完全的代理(替代),不可能出现承租者(目标对象)完全不参与租房(业务逻辑)过程,完全交由房屋中介(代理对象),这样会出现房屋中介自己租了房自己去住将承租者完全撇开,显然是不合适的;这也是为什么代理对象必须要关联一个目标对象的原因,代理对象只是对目标对象进行增强,并不能将其与目标对象完全隔离。
并且,还可以看出,静态代理模式的一个缺点是:必须要手动为每一个目标对象创建一个代理对象,在大量使用代理模式的业务场景下,静态代理模式显然不是最合适的选择。
AOP
AOP(Aspect Orient Programming),也即面向切面编程,作为面向对象编程的一种补充,是一种成熟的编程方式。AOP与OOP互为补充,面向对象编程将程序分解成各个层次的对象,而面向切面编程将程序运行过程分解成各个切面,可以这样理解:面向对象编程是从静态角度考虑程序结构,而面向切面编程则是从动态角度考虑程序运行过程。
AOP实现可以分为两类(按AOP框架修改源码的时机)
一、静态AOP实现:AOP框架在编译阶段对程序进行修改,即实现对目标类的增强,生成静态的AOP代理类(生成的.class文件被修改了,需要特定的编辑器),以AspectJ为代表。
二、动态AOP实现:AOP框架在运行阶段生成AOP代理(在内存中以JDK动态代理或cglib动态生成AOP代理类),以实现对目标对象的增强,以Spring AOP为代表。
AOP相关的概念Aspect、Joinpoint、Advice、Pointcut这里就不作展开了。
Spring 动态代理
Spring默认使用Jdk动态代理,也可以使用cglib代理,在需要代理类而不是接口的时候,Spring会自动切换为cglib代理。
1.Jdk动态代理
Jdk动态代理借助Proxy类和InvocationHandler接口实现,Proxy用来创建代理对象,每一个代理对象都会关联一个InvocationHandler实例(实现接口),代理对象调用方法时,此次调用会被指派给其关联的InvocationHandler实例由其执行。
使用jdk动态代理的代码如下:
实现InvocationHandler接口
package com.cube.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author cube.li
* @date 2021/3/6 18:24
* @description
*/
public class RentInvocationHandler implements InvocationHandler {
/**
* 被代理的目标对象
*/
private Object target;
public RentInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("租房前预处理工作...");
Object returnValue = method.invoke(target, args);
System.out.println("租房后善后工作...");
return returnValue;
}
}
创建代理对象调用
package com.cube.proxy.jdk;
import com.cube.proxy.stat.Person;
import com.cube.proxy.stat.Tenant;
import sun.misc.ProxyGenerator;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @author cube.li
* @date 2021/3/6 18:27
* @description jdk动态代理
*/
public class JdkProxyClient {
public static void main(String[] args) {
//目标对象
Person target = new Tenant("李四");
//InvocationHandler
InvocationHandler handler = new RentInvocationHandler(target);
//获取代理对象
Person proxyInstance = (Person) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
//通过代理对象调用
proxyInstance.rentHouse();
printProxyClass();
}
/**
* 将jdk动态代理的.class文件输出
*/
private static void printProxyClass() {
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class<?>[]{Person.class});
try {
String pathDir = "E:\\";
String path = "$Proxy0.class";
File f = new File(pathDir);
if (!f.exists()) {
f.mkdir();
}
path = f.getAbsolutePath() + path;
f = new File(path);
if (f.exists()) {
f.delete();
}
f.createNewFile();
try (FileOutputStream fos = new FileOutputStream(path)) {
fos.write(bytes);
} catch (Exception e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
调用结果如下:
租房前预处理工作...
李四 正在租房中...
租房后善后工作...
为了对Jdk动态代理机制一窥究竟,在debug模式下查看下代理对象的类型
代理对象类型.png
发现代理对象proxyInstance的类型是$Proxy0,将其对应的class文件输出并反编译查看,其源码如下:
import com.cube.proxy.stat.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Person {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject) {
try {
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void rentHouse() {
try {
this.h.invoke(this, m3, null);
return;
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return ((Integer)this.h.invoke(this, m0, null)).intValue();
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.cube.proxy.stat.Person").getMethod("rentHouse", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
} catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
} catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
}
可以看出$Proxy0
这个类继承了Proxy类、实现了Person接口,并且其中的rentHouse()方法内调用了与其关联的InvocationHandler实例的invoke()方法;由于java不支持多继承,经Jdk动态代理的代理对象已经继承了Proxy类,因此,Jdk动态代理只能对接口进行代理而无法再对类进行代理。
实际上,由Jdk动态代理生成的代理对象的类型都是以$Proxy
开头,后面的数字自零开始递增,每生成一个代理对象该数字加一。
2.cglib动态代理
cglib动态代理通过MethodInterceptor接口实现,其定义如下:
package net.sf.cglib.proxy;
/**
* General-purpose {@link Enhancer} callback which provides for "around advice".
* @author Juozas Baliuka <a href="mailto:baliuka@mwm.lt">baliuka@mwm.lt</a>
* @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $
*/
public interface MethodInterceptor
extends Callback
{
/**
* 所有生成的代理方法都会调用这个方法,而不是原始方法.
* 原始方法可以通过反射使用Method对象调用,也可以使用MethodProxy调用.
*
* @param obj 被增强的对象,目标对象
* @param method 被拦截的方法
* @param args 参数
* @param proxy 用户调用父类未被拦截的方法,根据需要可多次调用
* @return 被代理方法的返回值, 如果是void则忽略
* @throws Throwable
*/
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable;
}
示例代码如下:
奔跑者类
package com.cube.proxy.cglib;
/**
* @author cube.li
* @date 2021/3/6 20:34
* @description 奔跑者
*/
public class Runner {
private String name;
public Runner() {
}
public Runner(String name) {
this.name = name;
}
public void run() {
System.out.println(this.name + " 跑的飞快...");
}
}
实现MethodInterceptor
package com.cube.proxy.cglib;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author cube.li
* @date 2021/3/6 20:37
* @description
*/
public class RunnerMethodInterceptor implements MethodInterceptor {
/**
* 目标对象
*/
private Object target;
public RunnerMethodInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("踢两脚 加速...");
Object returnVal = method.invoke(target, objects);
System.out.println("拽两下 减速...");
return returnVal;
}
}
创建代理对象并调用
package com.cube.proxy.cglib;
import org.springframework.cglib.proxy.Enhancer;
/**
* @author cube.li
* @date 2021/3/6 20:33
* @description
*/
public class CglibProxyClient {
public static void main(String[] args) {
//目标对象
Runner runner = new Runner("李四");
//代理对象
RunnerMethodInterceptor interceptor = new RunnerMethodInterceptor(runner);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(runner.getClass());
//设置回调
enhancer.setCallback(interceptor);
Runner proxyInstance = (Runner) enhancer.create();
proxyInstance.run();
}
}
输出结果
踢两脚 加速...
李四 跑的飞快...
拽两下 减速...
在示例中,cglib对Runner类进行了动态代理,增强其原有业务逻辑,因此cglib是对jdk动态代理的补充,Spring在被代理对象没有实现接口时采用cglib动态代理替代jdk动态代理。
综上,本文对动态代理进行了浅显的说明,示例代码见https://gitee.com/li-cube/share.git
以后有时间再分别对Jdk动态代理、cglib动态代理原理进行剖析。
网友评论