什么是代理
代理是一种设计模式,给真实对象提供代理,由代理对象进行真实对象的访问,就是隐藏真实对象对方法的直接调用,而是通过代理对象去调用真实的方法。
为什么要使用代理以及应用场景
因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问,比如在日常写业务代码中,一个类只处理自己相关的业务,但如果要在这个业务的前后打上日志,增加权限功能,直接添加到代码里不合适,而且每个类都需要修改,这时我们需要一个代理,在不修改源代码的前提下,实现一些功能,即增强,我们在进入目标类之前,先进入代理类,在代理类中写我们需要的额外功能。
代理分类
代理分为静态代理和动态代理,接下来我将用代码举例方式讲解这两种代理,并在最后给出结论
模拟场景:在实际业务代码的前后打上日志
前期准备:日志类,通用代码
/**
* 增强类 打印日志
*/
public class LogHandle {
public void before() {
System.out.println("增加日志,输出前。。。。。");
}
public void after() {
System.out.println("增加日志,输出后。。。。。");
}
}
真实的业务对象,也就是要增强(代理)的对象,通用代码
/**
* 真实业务对象, 本来这里要增加日志,需要在每个方法的执行前后都加上,现在代码无进入,不用修改
*/
public interface RealObject {
void eat();
void walk();
}
class RealObjectImpl implements RealObject {
@Override
public void eat() {
System.out.println("eat.......");
}
@Override
public void walk() {
System.out.println("walk.......");
}
}
重点开始,静态代理
// 创建代理对象
class StaticProxy implements RealObject {
// 日志处理
LogHandle logHandle = new LogHandle();
// 相当于注入真实对象,以至于能调用真实对象的方法
private RealObject target;
public StaticProxy(RealObject target) {
this.target = target;
}
@Override
public void eat() {
logHandle.before();
target.eat();
logHandle.after();
}
@Override
public void walk() {
logHandle.before();
target.walk();
logHandle.after();
}
}
模拟客户端调用
public class StaticTest {
// 模拟客户端调用
public static void main(String[] args) {
RealObjectImpl impl = new RealObjectImpl();
// 代理对象进行调用,你要调用真实对象的方法,所以代理类必须实现真实对象的接口
StaticProxy staticProxy = new StaticProxy(impl);
staticProxy.eat();
staticProxy.walk();
}
}
分析:静态代理到此结束,通过上面的举例可以发现虽然静态代理实现简单,且不侵入原代码,但真实的业务对象很多,有很多类需要进行代理时,只有两种方法,要么一个代理类继承多个接口,要么创建多个代理类,这种缺点一般是难以接受的,所以才会有接下来的动态代理,而动态代理又分为两类,JDK动态代理和CGLIB动态代理
JDK动态代理
场景不变,还是在前面的通用代码,也是在真实对象的前后加上日志
静态代理是直接创建代理类,动态代理顾名思义代理类不是由我们手动创建的,而是在程序执行过程中自动帮我们创建的
JDK动态代理首先要创建处理类(注意这里不是代理类)实现InvocationHandler 接口,并且实现invoke方法
class DynamicJDKHandle implements InvocationHandler {
LogHandle logHandle = new LogHandle();
// 真实对象 类是Object,实际的方法执行者
private Object target;
public DynamicJDKHandle(Object target) {
this.target = target;
}
/**
*
* @param proxy 代理实例
* @param method 真实对象方法
* @param args 方法参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
logHandle.before();
// 调用 target 的 method 方法
Object invoke = method.invoke(target, args);
logHandle.after();
return invoke;
}
}
模拟客户端
/**
JDK动态代理 这里代码是不会出现代理类的,代理类是动态生成的
*/
public class DynamicJDKTest {
public static void main(String[] args) {
RealObjectImpl realObject = new RealObjectImpl();
ClassLoader classLoader = realObject.getClass().getClassLoader();
Class<?>[] interfaces = realObject.getClass().getInterfaces();
DynamicJDKHandle dynamicJDKProxy = new DynamicJDKHandle(realObject);
// 创建一个代理对象 通过JDK帮我们创建的 强转为实现类 原来是Object ->>>> 转为真实对象 必须使用接口接收,否则会报类型转换异常
RealObject proxy = (RealObject) Proxy.newProxyInstance(classLoader, interfaces, dynamicJDKProxy);
// 代理对象调用真实方法,就会进入代理类的invoke
proxy.eat();
}
}
处理步骤如下:
- 处理类必须先实现InvocationHandler接口(这里不是代理类)
- 实现里面的invoke方法
- 创建代理对象:
RealObject proxy =(RealObject)Proxy.newProxyInstance(classLoader, interfaces, dynamicJDKProxy);
- proxy.eat();代理对象调用真正方法,实际上是调用handle的invoke 这个就是调用真正的方法:
Object invoke= method.invoke(target, args);
关键一点:为什么JDK动态代理只能代理接口
jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口
CGLIB动态代理
基于ASM机制实现,通过生成业务类的子类作为代理类
代码实现如下
/**
* CGLIB动态代理 无需接口 通用类
*/
class RealCglibClass {
public void eat() {
System.out.println("eat.......");
}
public void walk() {
System.out.println("walk.......");
}
}
这里创建代理类实现MethodInterceptor
接口,用于拦截方法回调
/**
* 实现MethodInterceptor 用户方法回调
*/
class DynamicCglibInterceptor implements MethodInterceptor {
LogHandle logHandle = new LogHandle();
/**
*
* @param object 表示要进行增强的对象;
* @param method 表示要被拦截的方法;
* @param objects 表示要被拦截方法的参数;
* @param methodProxy 表示要触发父类的方法对象。
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
logHandle.before();
// 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法
Object result = methodProxy.invokeSuper(object, objects);
logHandle.after();
return result;
}
}
// 回调过滤器: 在CGLib回调时可以设置对不同方法执行不同的回调逻辑,或者根本不执行回调。
class CatFilter implements CallbackFilter {
@Override
public int accept(Method method) {
if ("eat".equals(method.getName())) {
System.out.println("执行eat方法");
return 0; // Callback 列表第1个拦截器
}
return 1; // Callback 列表第2个拦截器,return 2 则为第3个,以此类推
}
}
模拟客户端,需要导入asm和cglib的包
import net.sf.cglib.proxy.*;
import java.lang.reflect.Method;
/**
1. CGLIB实现动态代理,通过生成业务类的子类作为代理类
2. */
public class DynamicCglibTest {
public static void main(String[] args) {
// 增强器
Enhancer enhancer = new Enhancer();
// 代理类继承了业务类,所以业务类是超类
enhancer.setSuperclass(RealCglibClass.class);
// 回调方法
enhancer.setCallback(new DynamicCglibInterceptor());
// 设置过滤器
/** DynamicCglibInterceptor dynamicCglibInterceptor = new DynamicCglibInterceptor();
CatFilter catFilter = new CatFilter();
enhancer.setCallback(dynamicCglibInterceptor);
enhancer.setCallbackFilter(catFilter);
*/
// 创建代理对象
RealCglibClass proxy = (RealCglibClass) enhancer.create();
proxy.eat();
proxy.walk();
}
}
步骤如下:
- 查找目标类上的所有非final 的public类型的方法定义;
- 将这些方法的定义转换成字节码;
- 将组成的字节码转换成相应的代理的class对象;
- 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求
JDK动态代理与CGLIB动态代理对比
JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。
cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类
JDK动态代理的优缺点
jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
优点:解决了静态代理中冗余的代理实现类问题。而且是基于JDK实现的,不用额外导包
缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。
CGLIB动态代理
由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
缺点:CGLib在创建代理对象时所花费的时间却比JDK多一点
Spring AOP的实现就是动态代理,那它是采用哪种呢?
Spring Boot2.0 之前
如果代理对象有接口,就用 JDK 动态代理,否则就是 Cglib 动态代理。
如果代理对象没有接口,那么就直接是 Cglib 动态代理。
开发者可以通过配置文件进行配置
如果开发者设置了 spring.aop.proxy-target-class 为 false,则使用 JDK 代理。
如果开发者设置了spring.aop.proxy-target-class 为 true,则使用 Cglib 代理。
如果开发者一开始就没配置spring.aop.proxy-target-class 属性,则使用 JDK 代理
PS:从 Spring Boot2.0 开始,如果用户什么都没有配置,那么默认情况下使用的是 Cglib 代理。如果想使用JDK动态代理得手动配置
参考链接:
https://juejin.cn/post/6844903744954433544#comment
https://juejin.cn/post/7035880349422845960