参考资料
代理
代理模式(Proxy Pattern)给某一个对象提供一个代理,并由代理对象控制原对象的引用。代理对象在客户端和目标对象之间起到中介作用。
代理模式结构
代理模式优缺点
优点
- 代理模式能将代理对象与真实被调用的目标对象分离
- 一定程度上降低了系统的耦合度,扩展性好
- 可以起到保护目标对象的作用。
- 可以对目标对象的功能增强
缺点
- 代理模式会造成系统设计中类的数量增加
- 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢
代理模式分类
代理模式可以分为静态代理和动态代理两种类型,而动态代理中又分为 JDK 动态代理和 CGLIB 代理两种。
- 静态代理:代码编译时就已实现,编译完成后,代理类是一个实际的
class
文件 - 动态代理:在运行时动态生成的,编译完成后没有实际的
class
文件,而是在运行时动态生成类字节码,并加载到 JVM 中- JDK 代理
- CGLIB 代理
3种代理方式的对比
代理方式 | 实现 | 优点 | 缺点 | 特点 |
---|---|---|---|---|
JDK 静态代理 | 代理类与委托类实现同一接口。在代理类中需要硬编码接口 | 实现简单,容易理解 | 代理类需要硬编码接口,在实际应用中可能会导致重复编码,浪费存储空间并且效率较低 | |
JDK 动态代理 | 代理类与委托类实现同一接口。代理类要实现 InvocationHandler 接口并重写 invoke 方法。invoke 方法中可以对委托方法进行增强处理 | 不需要硬编码接口,代码复用率高 | 只能够代理实现了接口的委托类 | 底层使用反射机制进行方法的调用 |
CGLIB 动态代理 | 代理类将委托类作为自己的父类并为其中的非 final 委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过 super 调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor 接口的对象,若存在则将调用 intercept 方法对委托方法进行代理 | 可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口 | 不能对 final 类以及 final 方法进行代理 | 底层将方法全部存入一个数组中,通过数组索引直接进行方法调用 |
静态代理和动态代理的对比
- 静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则。
- 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
- 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。
CGLIB 动态代理比 JDK 动态代理速度快?
- CGLIB 动态代理中,通过字节码技术(使用了字节码处理框架
ASM
)为一个类创建子类,并在子类中通过 FastClass 机制,采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。 - JDK 动态代理中,通过反射技术实现对委托方法的调用。
- 在 JDK 6 之前,CGLIB 动态代理比使用 Java 反射效率要高
- 在 JDK 6、7、8 版本中,逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率高于 CGLIB 代理效率。
- 从 JDK 8 版本开始,每一次 JDK 版本升级,JDK 代理效率都得到较大提升,效率已经高于 CGLIB 代理。
Spring AOP 中是使用的 JDK 代理还是 CGLIB 代理?
- 当 Bean 实现接口时,Spring 就会用 JDK 的动态代理。
- 当 Bean 没有实现接口时,Spring 使用 CGLIB 实现。
- 若两种方式均可,则首选 JDK 做动态代理(效率更高)。不过也可以强制使用 CGLIB。
静态代理
结合「代理模式结构」章节的结构图,对静态代理的实现进行说明
- 对「服务接口」,创建一个「服务类」实现该接口。
- 同时创建一个「代理类」也实现该接口,代理类中持有一个指向「服务对象」的引用成员变量。通常情况下,「代理类」会对其「服务对象」的整个生命周期进行管理。
- 「客户端」通过「代理类」来调用「服务对象」实现的接口方法。
//1.「服务接口」
public interface UserDao {
void save();
}
//2.「服务类」
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("正在保存用户...");
}
}
//3.「代理类」
public class TransactionHandler implements UserDao {
// 目标代理对象
// 2.「服务对象」
private UserDao target;
//构造代理对象时传入目标对象
public TransactionHandler(UserDao target) {
this.target = target; //也可以延迟初始化 如在调用save方法时才创建target对象
}
@Override
public void save() {
//调用目标方法前的处理
System.out.println("开启事务控制...");
//调用目标对象的方法
target.save();
//调用目标方法后的处理
System.out.println("关闭事务控制...");
}
}
// 4. 「客户端」
public class Main {
public static void main(String[] args) {
//新建目标对象
UserDaoImpl target = new UserDaoImpl();
//创建代理对象, 并使用接口对其进行引用
UserDao userDao = new TransactionHandler(target);
//针对接口进行调用
userDao.save();
}
}
复制代码
静态代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。
JDK动态代理
原理
在 JDK 动态代理中,每一个动态代理类都必须实现 InvocationHandler
接口,并且每个动态代理类都关联了一个 handler
。当我们通过代理对象调用一个方法的时候,这个方法的调用,就会被转发为由 InvocationHandler
接口的 invoke
方法来进行调用。invoke
方法会根据传入的代理对象、方法名称以及参数决定调用代理的哪个方法。详情见下 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.
*
* @author Peter Jones
* @see Proxy
* @since 1.3
*/
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
复制代码
使用步骤
使用 JDK 动态代理的 5 大步骤如下
- 为动态代理类实现
InvocationHandler
接口并重写invoke
方法,即为动态代理类关联一个自定义的handler
。 - 通过
Proxy.getProxyClass
获得动态代理类。 - 通过反射机制获得代理类的构造方法,方法签名为
getConstructor(InvocationHandler.class)
。 - 通过构造函数获得代理对象,并将自定义的
InvocationHandler
实例对象作为参数传入。 - 通过代理对象调用目标方法。
Java 的 Proxy
类提供了一个 Proxy.newProxyInstance()
方法,封装了上面的第 2~4 步的工作(详情见下 Proxy
类的源码注释),故步骤可精简为 3 步
- 为动态代理类实现
InvocationHandler
接口并重写invoke
方法,即为动态代理类关联一个自定义的handler
。 - 使用
Proxy.newProxyInstance()
方法获得代理对象 - 通过代理对象调用目标方法。
/**
* 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.
*
* To create a proxy for some interface Foo:
*
* InvocationHandler handler = new MyInvocationHandler(...);
* Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(),
* Foo.class);
* Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).
* newInstance(handler);
*
* or more simply:
*
* Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
* new Class<?>[] { Foo.class },
* handler);
*
*/
public class Proxy implements java.io.Serializable {
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// ...
}
}
复制代码
使用示例
下面给出一个 JDK 动态代理的示例。
//1.「服务接口」
public interface IHello {
void sayHello();
}
//2.「服务类」
public class HelloImpl implements IHello {
@Override
public void sayHello() {
System.out.println("Hello world!");
}
}
复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// 为动态代理类关联一个自定义的 InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
/** 目标对象 */
private Object target;
public MyInvocationHandler(Object target){
this.target = target;
}
//重写invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------插入前置通知代码-------------");
// 执行相应的目标方法
Object rs = method.invoke(target,args);
System.out.println("------插入后置处理代码-------------");
return rs;
}
}
复制代码
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
public class MyProxyTest {
public static void main(String[] args)
throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
// =========================第一种==========================
// 1、生成$Proxy0的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 2、获取动态代理类
Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
// 3、获得代理类的构造函数,并传入参数类型InvocationHandler.class
Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
// 4、通过构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入
IHello iHello1 = (IHello) constructor.newInstance(new MyInvocationHandler(new HelloImpl()));
// 5、通过代理对象调用目标方法
iHello1.sayHello();
// ==========================第二种=============================
/**
* Proxy类中还有个将2~4步骤封装好的简便方法来创建动态代理对象,
*其方法签名为:newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h)
*/
IHello iHello2 = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), // 加载接口的类加载器
new Class[]{IHello.class}, // 一组接口
new MyInvocationHandler(new HelloImpl())); // 自定义的InvocationHandler
iHello2.sayHello();
}
}
复制代码
JDK静态代理和JDK动态代理的对比
相同点
- JDK 静态代理和 JDK 动态代理,两者在使用时都需要创建代理类,并且代理类都要实现对应的接口。
不同点
- JDK 静态代理是通过直接编码创建的,代码编译前,代理类就需要实现和被代理类相同的接口
- JDK 动态代理是利用「反射机制」在运行时创建代理类的。
- JDK 动态代理中,动态代理类需要实现
InvocationHandler
接口,并重写invoke
方法。
CGLIB动态代理
原理
CGLIB 动态代理中,通过字节码技术(使用了字节码处理框架 ASM
)为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
使用步骤
使用 CGLIB 动态代理的步骤如下
- CGLIB动态代理中,要求代理类必须要实现
MethodInterceptor
接口并重写intercept()
。 - 通过 CGLIB 动态代理,获取代理对象。
- 通过代理对象调用目标方法时,对目标方法的调用,将被转发到
MethodInterceptor
接口的intercept()
方法来进行调用。在intercept()
方法中,我们除了会调用委托方法外,还会进行一些增强操作,如日志的记录,调用量的监控等。
需要注意的是
- CGLIB 动态代理中,代理类是继承了被代理类(或服务类),即
enhancer.setSuperclass(Service.class)
。所以,被代理类的final
方法或final
类,不能被代理类继承,也就不能通过代理的方式调用。 - 代理类将委托类作为自己的父类,并为其中的非
final
委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super
调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor
接口的对象,若存在则将调用intercept
方法对委托方法进行代理。
使用示例
public class HelloService {
public HelloService() {
System.out.println("HelloService构造");
}
/**
* final方法不能被子类覆盖
* Cglib 是无法代理 final 修饰的方法的
*/
final public String sayOthers(String name) {
System.out.println("HelloService:sayOthers>>"+name);
return null;
}
public void sayHello() {
System.out.println("HelloService:sayHello");
}
}
复制代码
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定义MethodInterceptor
*/
public class MyMethodInterceptor implements MethodInterceptor{
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("======插入前置通知======");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("======插入后者通知======");
return object;
}
}
复制代码
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
public class Client {
public static void main(String[] args) {
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/lbs/CodeSpace");
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
// 代理类是继承了服务类 !!!
enhancer.setSuperclass(HelloService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
HelloService proxy= (HelloService)enhancer.create();
// 通过代理对象调用目标方法
proxy.sayHello();
}
}
复制代码
FastClass机制
- ref 1-CGLib FastClass | 简书
CGLIB 动态代理中,方法的调用并不是通过反射来完成的,而是通过 FastClass
机制来实现对被拦截方法的调用。
FastClass
机制中,不使用反射类(Constructor
或 Method
)来调用委托类的方法,而是动态生成一个新的类(继承 FastClass
),并为委托类的方法调用语句建立索引,使用者根据方法签名(方法名 + 参数类型)得到索引值,再通过索引值调用相应的方法。
下面给出一个分别使用反射技术和 FastClass 机制调用委托方法的示例。
- 委托类
public class DelegateClass {
public DelegateClass() {
}
public DelegateClass(String string) {
}
public boolean add(String string, int i) {
System.out.println("This is add method: " + string + ", " + i);
return true;
}
public void update() {
System.out.println("This is update method");
}
}
复制代码
- 通过反射技术调用委托方法
// Java Reflect
public static void reflectTest() throws Exception {
Class delegateClass = DelegateClass.class;
// 反射构造类
Constructor delegateConstructor = delegateClass.getConstructor(String.class);
// 创建委托类实例
DelegateClass delegateInstance = (DelegateClass) delegateConstructor.newInstance("Tom");
// 反射方法类
Method addMethod = delegateClass.getMethod("add", String.class, int.class);
// 调用方法
addMethod.invoke(delegateInstance, "Tom", 30);
Method updateMethod = delegateClass.getMethod("update");
updateMethod.invoke(delegateInstance);
}
复制代码
- 通过 FastClass 机制调用委托方法
// FastClass
public static void fastClassTest() throws Exception {
//设置 CGLIB 生成的代理类文件存入本地磁盘,方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/lbs/CodeSpace");
// FastClass动态子类实例
FastClass fastClass = FastClass.create(DelegateClass.class);
// 创建委托类实例
DelegateClass fastInstance = (DelegateClass) fastClass.newInstance(
new Class[] {String.class}, new Object[]{"Jack"});
// 调用委托类方法
fastClass.invoke("add", new Class[]{ String.class, int.class}, fastInstance,
new Object[]{ "Jack", 25});
fastClass.invoke("update", new Class[]{}, fastInstance, new Object[]{});
}
复制代码
- 下面看一下
FastClass
的源码
public abstract class FastClass{
// 委托类
private Class type;
// 子类访问构造方法
protected FastClass() {}
protected FastClass(Class type) {
this.type = type;
}
// 创建动态FastClass子类
public static FastClass create(Class type) {
// Generator:子类生成器,继承AbstractClassGenerator
Generator gen = new Generator();
gen.setType(type);
gen.setClassLoader(type.getClassLoader());
return gen.create();
}
/**
* 调用委托类方法
*
* @param name 方法名
* @param parameterTypes 方法参数类型
* @param obj 委托类实例
* @param args 方法参数对象
*/
public Object invoke(String name, Class[] parameterTypes, Object obj, Object[] args) {
return invoke(getIndex(name, parameterTypes), obj, args);
}
/**
* 根据方法描述符找到方法索引
*
* @param name 方法名
* @param parameterTypes 方法参数类型
*/
public abstract int getIndex(String name, Class[] parameterTypes);
/**
* 根据方法索引调用委托类方法
*
* @param index 方法索引
* @param obj 委托类实例
* @param args 方法参数对象
*/
public abstract Object invoke(int index, Object obj, Object[] args);
/**
* 调用委托类构造方法
*
* @param parameterTypes 构造方法参数类型
* @param args 构造方法参数对象
*/
public Object newInstance(Class[] parameterTypes, Object[] args) throws {
return newInstance(getIndex(parameterTypes), args);
}
/**
* 根据构造方法描述符(参数类型)找到构造方法索引
*
* @param parameterTypes 构造方法参数类型
*/
public abstract int getIndex(Class[] parameterTypes);
/**
* 根据构造方法索引调用委托类构造方法
*
* @param index 构造方法索引
* @param args 构造方法参数对象
*/
public abstract Object newInstance(int index, Object[] args);
}
复制代码
- 找到 CGLIB 生成的代理类文件
DelegateClass$$FastClassByCGLIB$$4af5b667
,反编译查看源码
public class DelegateClass$$FastClassByCGLIB$$4af5b667 extends FastClass {
/**
* 动态子类构造方法
*/
public DelegateClass$$FastClassByCGLIB$$4af5b667(Class delegateClass) {
super(delegateClass);
}
/**
* 根据方法签名得到方法索引
*
* @param name 方法名
* @param parameterTypes 方法参数类型
*/
public int getIndex(String methodName, Class[] parameterTypes) {
switch(methodName.hashCode()) {
// 委托类方法add索引:0
case 96417:
if (methodName.equals("add")) {
switch(parameterTypes.length) {
case 2:
if (parameterTypes[0].getName().equals("java.lang.String") &&
parameterTypes[1].getName().equals("int")) {
return 0;
}
}
}
break;
// 委托类方法update索引:1
case -838846263:
if (methodName.equals("update")) {
switch(parameterTypes.length) {
case 0:
return 1;
}
}
break;
// Object方法equals索引:2
case -1295482945:
if (methodName.equals("equals")) {
switch(parameterTypes.length) {
case 1:
if (parameterTypes[0].getName().equals("java.lang.Object")) {
return 2;
}
}
}
break;
// Object方法toString索引:3
case -1776922004:
if (methodName.equals("toString")) {
switch(parameterTypes.length) {
case 0: return 3;
}
}
break;
// Object方法hashCode索引:4
case 147696667:
if (methodName.equals("hashCode")) {
switch(parameterTypes.length) {
case 0:
return 4;
}
}
}
return -1;
}
/**
* 根据方法索引调用委托类方法
*
* @param methodIndex 方法索引
* @param delegateInstance 委托类实例
* @param parameterValues 方法参数对象
*/
public Object invoke(int methodIndex, Object delegateInstance, Object[] parameterValues) {
DelegateClass instance = (DelegateClass) delegateInstance;
int index = methodIndex;
try {
switch(index) {
case 0:
// 委托类实例直接调用方法语句
return new Boolean(instance.add((String)parameterValues[0],
((Number)parameterValues[1]).intValue()));
case 1:
instance.update();
return null;
case 2:
return new Boolean(instance.equals(parameterValues[0]));
case 3:
return instance.toString();
case 4:
return new Integer(instance.hashCode());
}
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
/**
* 根据构造方法描述符(参数类型)找到构造方法索引
*
* @param parameterTypes 构造方法参数类型
*/
public int getIndex(Class[] parameterTypes) {
switch(parameterTypes.length) {
// 无参构造方法索引:0
case 0:
return 0;
// 有参构造方法索引:1
case 1:
if (parameterTypes[0].getName().equals("java.lang.String")) {
return 1;
}
default:
return -1;
}
}
/**
* 根据构造方法索引调用委托类构造方法
*
* @param methodIndex 构造方法索引
* @param parameterValues 构造方法参数对象
*/
public Object newInstance(int methodIndex, Object[] parameterValues) {
// 创建委托类实例
DelegateClass newInstance = new DelegateClass;
DelegateClass newObject = newInstance;
int index = methodIndex;
try {
switch(index) {
// 调用构造方法(<init>)
case 0:
newObject.<init>();
return newInstance;
case 1:
newObject.<init>((String)parameterValues[0]);
return newInstance;
}
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
public int getMaxIndex() {
return 4;
}
}
复制代码
JDK动态代理和CGLIB动态代理的对比
- JDK 动态代理时,需要委托类(被代理类)实现了一个接口。CGLIB 动态代理时,则无此要求,因为代理类是继承的委托类。
- JDK 动态代理中,代理类通过 Java 反射技术调用委托方法。CGLIB 动态代理中,代理类通过 FastClass 机制,采用方法拦截的技术对委托方法进行调用。