Java最新面试造火箭系列,栽在了cglib和jdk动态代理,步步图解让你明白Spring循环依赖

复习的面试资料

这些面试全部出自大厂面试真题和面试合集当中,小编已经为大家整理完毕(PDF版)

  • 第一部分:Java基础-中级-高级

image

  • 第二部分:开源框架(SSM:Spring+SpringMVC+MyBatis)

image

  • 第三部分:性能调优(JVM+MySQL+Tomcat)

image

  • 第四部分:分布式(限流:ZK+Nginx;缓存:Redis+MongoDB+Memcached;通讯:MQ+kafka)

image

  • 第五部分:微服务(SpringBoot+SpringCloud+Dubbo)

image

  • 第六部分:其他:并发编程+设计模式+数据结构与算法+网络

image

进阶学习笔记pdf

  • Java架构进阶之架构筑基篇(Java基础+并发编程+JVM+MySQL+Tomcat+网络+数据结构与算法

image

  • Java架构进阶之开源框架篇(设计模式+Spring+SpringMVC+MyBatis

image

image

image

  • Java架构进阶之分布式架构篇 (限流(ZK/Nginx)+缓存(Redis/MongoDB/Memcached)+通讯(MQ/kafka)

image

image

image

  • Java架构进阶之微服务架构篇(RPC+SpringBoot+SpringCloud+Dubbo+K8s)

image

image

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  1. 可以做到在符合开闭原则的情况下对目标对象进行功能扩展。

  2. 职责非常清晰,一目了然。

缺点:

  1. 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。

  2. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

  3. 代码层面来看,如果接口发生改变,代理类也会发生变更。

动态代理

有了上面的基础,咱们正式聊聊动态代理。 上面的例子其实我们不难发现,每个代理类只能够实现一个接口服务。那么如果当我们的软件工程中出现多个适用代理模式的业务类型时那么咱们就会创建多个代理类,那么我们如何去解决这个问题呢?其实我们的动态代理就应运而生了。

很显然动态代理类的字节码在程序运行时由Java反射机制动态生成,无需我们手工编写它的源代码。 那咱们基于上述的案例来先看看JDK动态代理类

JDK动态代理

直接看一下JDK动态代理的使用,如下代码块

public class JDKDynamicProxy implements InvocationHandler {

//被代理的对象

private Object object;

public JDKDynamicProxy(Object object) {

this.object = object;

}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

Object result = method.invoke(object,args);

return result;

}

//生成代理类

public Object createProxyObj(){

return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);

}

}

复制代码

静态代理,动态代理调用如下:

public class TestProxy {

public static void main(String[] args) {

//静态代理测试

RailwayStation railwayStation = new RailwayStation();

railwayStation.getTicket();

RailwayAgencyProxy railwayAgencyProxy = new RailwayAgencyProxy(railwayStation);

railwayAgencyProxy.getTicket();

//动态代理测试

Ticket ticket = new RailwayStation();

JDKDynamicProxy jdkDynamicProxy = new JDKDynamicProxy(ticket);

Ticket proxyBuyTicket = (Ticket) jdkDynamicProxy.createProxyObj();

proxyBuyTicket.getTicket();

}

}

复制代码

观察上面动态代理以及静态代理测试,动态代理的优势就显而易见了。如果我们再演示另外猪八戒和高翠兰代理场景的时候,是不是就不用再去创建GaocuiLanProxy了,我们只需要通过JDKDynamicProxy的方式去创建代理类即可。

注意Proxy.newProxyInstance()方法接受三个参数:

  1. ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的。

  2. Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型

  3. InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法

通过上面的例子以及上述参数,我们不难发现JDK动态代理有这样一个特性: JDK动态代理是面向接口的代理模式,如果要用JDK代理的话,首先得有个接口,例如上面例子中的Ticket接口

cglib动态代理

咱们再来看一下cglib动态代理。先了解一下cglib是什么。关于cglib,其实其官方解释是比较少的,但是其本身是十分强大的,这也是很多人所诟病的。CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。CGLIB是一个强大的高性能的代码生成包。它广泛的被许多AOP的框架使用,例如Spring AOP为他们提供方法的interception(拦截)。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。除了CGLIB包,脚本语言例如Groovy和BeanShell,也是使用ASM来生成java的字节码。当然不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

接下来我们看一下用法,由于cglib并不是jdk自带的,所以如果是maven项目的话,咱们首先需要的是引入cglib相关的pom依赖,如下:

cglib

cglib

3.3.0

复制代码

由于cglib代理的对象是类,这个是和JDK动态代理不一样的地方,这个地方标红加重点。 这样的话,咱们同样的代理类的话则应该如下定义,

public class Ticket {

public void getTicket(){

System.out.println(“买了张火车票”);

}

final public void refundTicket(){

System.out.println(“退了张火车票”);

}

}

复制代码

显然,上面的类定义了两个方法,一个是买火车票另外的话退火车票。

public class CglibDynamicProxy implements MethodInterceptor {

/**

  • @param o cglib生成的代理对象

  • @param method 被代理对象的方法

  • @param objects 传入方法的参数

  • @param methodProxy 代理的方法

  • @return

  • @throws Throwable

*/

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

System.out.println(“execute pre”);

Object obj = methodProxy.invokeSuper(o,objects);

System.out.println(“execute after”);

return obj;

}

}

复制代码

调用测试入口方法调用

public class TestCglibProxy {

public static void main(String[] args) {

// 代理类class文件存入本地磁盘方便我们反编译查看源码

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, “/Users/kdaddy/project/log”);

// 通过CGLIB动态代理获取代理对象的过程

Enhancer enhancer = new Enhancer();

// 设置enhancer对象的父类

enhancer.setSuperclass(Ticket.class);

// 设置enhancer的回调对象

enhancer.setCallback(new CglibDynamicProxy());

// 创建代理对象

Ticket ticket = (Ticket) enhancer.create();

// 通过代理对象调用目标方法

ticket.getTicket();

// 通过代理尝试调用final对象调用目标方法

ticket.refundTicket();

}

}

复制代码

运行之后我们得到的结果如下:

execute pre

买了张火车票

execute after

取消了张火车票

复制代码

根据日志的打印情况,我们很容易发现相关打印“取消了张火车票”并没有被代理,所以我们由此可以得到一个结论cglib动态代理无法代理被final修饰的方法。

上述源码中,我们提及将代理类class写到了相关的磁盘中,打开对应的目录,我们会发现下面三个文件

源码文件

TicketEnhancerByCGLIBEnhancerByCGLIB4e79a04a类为cglib生成的代理类,该类即成了Ticket。 咱们来慢慢看看相关的源码:

public class Ticket E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIB4e79a04a extends Ticket implements Factory {

private boolean CGLIB$BOUND;

public static Object CGLIB$FACTORY_DATA;

private static final ThreadLocal CGLIB$THREAD_CALLBACKS;

private static final Callback[] CGLIB$STATIC_CALLBACKS;

//拦截器

private MethodInterceptor CGLIB$CALLBACK_0;

private static Object CGLIB$CALLBACK_FILTER;

//被代理方法

private static final Method CGLIB$getTicket 0 0 0Method;

//代理方法

private static final MethodProxy CGLIB$getTicket 0 0 0Proxy;

private static final Object[] CGLIB$emptyArgs;

private static final Method CGLIB$equals 1 1 1Method;

private static final MethodProxy CGLIB$equals 1 1 1Proxy;

private static final Method CGLIB$toString 2 2 2Method;

private static final MethodProxy CGLIB$toString 2 2 2Proxy;

private static final Method CGLIB$hashCode 3 3 3Method;

private static final MethodProxy CGLIB$hashCode 3 3 3Proxy;

private static final Method CGLIB$clone 4 4 4Method;

private static final MethodProxy CGLIB$clone 4 4 4Proxy;

static void CGLIB$STATICHOOK1() {

CGLIB$THREAD_CALLBACKS = new ThreadLocal();

CGLIB$emptyArgs = new Object[0];

Class var0 = Class.forName(“kdaddy.com.cglibDynamic.Ticket E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIB4e79a04a”);

Class var1;

CGLIB$getTicket 0 0 0Method = ReflectUtils.findMethods(new String[]{“getTicket”, “()V”}, (var1 = Class.forName(“kdaddy.com.cglibDynamic.Ticket”)).getDeclaredMethods())[0];

CGLIB$getTicket 0 0 0Proxy = MethodProxy.create(var1, var0, “()V”, “getTicket”, “CGLIB$getTicket$0”);

Method[] var10000 = ReflectUtils.findMethods(new String[]{“equals”, “(Ljava/lang/Object;)Z”, “toString”, “()Ljava/lang/String;”, “hashCode”, “()I”, “clone”, “()Ljava/lang/Object;”}, (var1 = Class.forName(“java.lang.Object”)).getDeclaredMethods());

CGLIB$equals 1 1 1Method = var10000[0];

CGLIB$equals 1 1 1Proxy = MethodProxy.create(var1, var0, “(Ljava/lang/Object;)Z”, “equals”, “CGLIB$equals$1”);

CGLIB$toString 2 2 2Method = var10000[1];

CGLIB$toString 2 2 2Proxy = MethodProxy.create(var1, var0, “()Ljava/lang/String;”, “toString”, “CGLIB$toString$2”);

CGLIB$hashCode 3 3 3Method = var10000[2];

CGLIB$hashCode 3 3 3Proxy = MethodProxy.create(var1, var0, “()I”, “hashCode”, “CGLIB$hashCode$3”);

CGLIB$clone 4 4 4Method = var10000[3];

CGLIB$clone 4 4 4Proxy = MethodProxy.create(var1, var0, “()Ljava/lang/Object;”, “clone”, “CGLIB$clone$4”);

}

}

复制代码

我们通过代理类的源码可以看到,代理类会获得所有在父类继承来的方法,并且会有MethodProxy与之对应,当然被final修饰的方法除外,上述源码中我们也确实没有看到之前的refundTicket方法。接下来往下看。

咱们看其中一个方法的调用。

//代理方法(methodProxy.invokeSuper会调用)

final void CGLIB$getTicket$0() {

super.getTicket();

}

//被代理方法(methodProxy.invoke会调用,这就是为什么在拦截器中调用methodProxy.invoke会死循环,一直在调用拦截器)

public final void getTicket() {

MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;

if (var10000 == null) {

CGLIB$BIND_CALLBACKS(this);

var10000 = this.CGLIB$CALLBACK_0;

}

if (var10000 != null) {

//调用拦截器

var10000.intercept(this, CGLIB$getTicket 0 0 0Method, CGLIB e m p t y A r g s , C G L I B emptyArgs, CGLIB emptyArgs,CGLIBgetTicket 0 0 0Proxy);

} else {

super.getTicket();

}

}

复制代码

通过上述,我们看下getTicket整个的调用链路: 调用getTicket()方法->调用拦截器->methodProxy.invokeSuper->CGLIBgetTicketgetTicket0->被代理的getTicket方法。

接下来,咱们再来看一下比较核心的MethodProxy,咱们直接看下核心:methodProxy.invokeSuper,具体源码如下:

public Object invokeSuper(Object obj, Object[] args) throws Throwable {

try {

this.init();

MethodProxy.FastClassInfo fci = this.fastClassInfo;

return fci.f2.invoke(fci.i2, obj, args);

} catch (InvocationTargetException var4) {

throw var4.getTargetException();

}

}

复习的面试资料

这些面试全部出自大厂面试真题和面试合集当中,小编已经为大家整理完毕(PDF版)

  • 第一部分:Java基础-中级-高级

image

  • 第二部分:开源框架(SSM:Spring+SpringMVC+MyBatis)

image

  • 第三部分:性能调优(JVM+MySQL+Tomcat)

image

  • 第四部分:分布式(限流:ZK+Nginx;缓存:Redis+MongoDB+Memcached;通讯:MQ+kafka)

image

  • 第五部分:微服务(SpringBoot+SpringCloud+Dubbo)

image

  • 第六部分:其他:并发编程+设计模式+数据结构与算法+网络

image

进阶学习笔记pdf

  • Java架构进阶之架构筑基篇(Java基础+并发编程+JVM+MySQL+Tomcat+网络+数据结构与算法

image

  • Java架构进阶之开源框架篇(设计模式+Spring+SpringMVC+MyBatis

image

image

image

  • Java架构进阶之分布式架构篇 (限流(ZK/Nginx)+缓存(Redis/MongoDB/Memcached)+通讯(MQ/kafka)

image

image

image

  • Java架构进阶之微服务架构篇(RPC+SpringBoot+SpringCloud+Dubbo+K8s)

image

image

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

[外链图片转存中…(img-CZnJIWNx-1715449428511)]

  • Java架构进阶之分布式架构篇 (限流(ZK/Nginx)+缓存(Redis/MongoDB/Memcached)+通讯(MQ/kafka)

[外链图片转存中…(img-RnfKAJlu-1715449428511)]

[外链图片转存中…(img-UxYLZM8C-1715449428511)]

[外链图片转存中…(img-bmNoGs5b-1715449428512)]

  • Java架构进阶之微服务架构篇(RPC+SpringBoot+SpringCloud+Dubbo+K8s)

[外链图片转存中…(img-j3TrBgzi-1715449428512)]

[外链图片转存中…(img-5JS3eSJt-1715449428512)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值