手写Spring:第16章-给代理对象的属性设置值

news/2025/3/5 0:29:04/

文章目录

  • 一、目标:给代理对象的属性设置值
  • 二、设计:给代理对象的属性设置值
  • 三、实现:给代理对象的属性设置值
    • 3.1 工程结构
    • 3.2 在Bean生命周期中创建代理对象类图
    • 3.3 判断CGLIB对象
    • 3.4 迁移创建AOP代理方法
      • 3.4.1 实例化感知对象处理
      • 3.4.2 扫描自定义注解类
      • 3.4.3 默认自动代理创建者
    • 3.5 在Bean生命周期中初始化执行
  • 四、测试:给代理对象的属性设置值
    • 4.1 添加测试配置
      • 4.1.1 用户服务层实现类
      • 4.1.2 用户前置处理
      • 4.1.3 Spring属性配置文件
    • 4.2 单元测试
  • 五、总结:给代理对象的属性设置值

一、目标:给代理对象的属性设置值

💡 如何给代理对象中的属性填充相应的值?

  • 因为在之前把 AOP 动态代理,融入到 Bean 的生命周期时,创建代理对象是在整个创建 Bean 对象之前,也就是这个代理对象的创建并不是在 Bean 生命周期中。
  • 所以我们要把代理对象的创建融入到 Bean 的生命周期中,也就是需要把创建代理对象的逻辑迁移到 Bean 对象执行初始化方法之后,再执行代理对象的创建。

二、设计:给代理对象的属性设置值

💡 设计:把创建代理对象的逻辑迁移到对象执行初始化之后。

  • 创建代理对象:DefaultAdvisorAutoProxyCreator 实现的 InstantiationAwareBeanPostProcessor 接口。
  • 那么原本在 Before 中的操作,则需要放到 After 中处理

在这里插入图片描述

  • 在创建 Bean 对象 createBean 的生命周期中,有一个阶段是在 Bean 对象属性填充完成以后,执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理。
    • 例如:感知 Aware 对象、处理 init-method 方法等。
  • 那么在这个阶段的 BeanPostProcessor After 就可以用于创建处理对象操作。
  • DefaultAdvisorAutoProxyCreator 用于创建代理对象的操作中,需要把创建操作从 postProcessBeforeInstantiation 方法中迁移到 postProcessAfterInitialization,这样才能满足 Bean 属性填充后的创建操作。

三、实现:给代理对象的属性设置值

3.1 工程结构

spring-step-15
|-src|-main| |-java|   |-com.lino.springframework|     |-aop|     | |-aspectj|     | | |-AspectJExpressionPointcut.java|     | | |-AspectJExpressionPointcutAdvisor.java|     | |-framework|     | | |-adapter|     | | | |-MethodBeforeAdviceInterceptor.java|     | | |-autoproxy|     | | | |-DefaultAdvisorAutoProxyCreator.java|     | | |-AopProxy.java|     | | |-Cglib2AopProxy.java|     | | |-JdkDynamicAopProxy.java|     | | |-ProxyFactory.java|     | | |-ReflectiveMethodInvocation.java|     | |-AdvisedSupport.java|     | |-Advisor.java|     | |-BeforeAdvice.java|     | |-ClassFilter.java|     | |-MethodBeforeAdvice.java|     | |-MethodMatcher.java|     | |-Pointcut.java|     | |-PointcutAdvisor.java|     | |-TargetSource.java|     |-beans|     | |-factory|     | | |-annotation|     | | | |-Autowired.java|     | | | |-AutowiredAnnotationBeanPostProcessor.java|     | | | |-Qualifier.java|     | | | |-Value.java|     | | |-config|     | | | |-AutowireCapableBeanFactory.java|     | | | |-BeanDefinition.java|     | | | |-BeanFactoryPostProcessor.java|     | | | |-BeanPostProcessor.java|     | | | |-BeanReference.java|     | | | |-ConfigurableBeanFactory.java|     | | | |-InstantiationAwareBeanPostProcessor.java|     | | | |-SingletonBeanRegistry.java|     | | |-support|     | | | |-AbstractAutowireCapableBeanFactory.java|     | | | |-AbstractBeabDefinitionReader.java|     | | | |-AbstractBeabFactory.java|     | | | |-BeabDefinitionReader.java|     | | | |-BeanDefinitionRegistry.java|     | | | |-CglibSubclassingInstantiationStrategy.java|     | | | |-DefaultListableBeanFactory.java|     | | | |-DefaultSingletonBeanRegistry.java|     | | | |-DisposableBeanAdapter.java|     | | | |-FactoryBeanRegistrySupport.java|     | | | |-InstantiationStrategy.java|     | | | |-SimpleInstantiationStrategy.java|     | | |-xml|     | | | |-XmlBeanDefinitionReader.java|     | | |-Aware.java|     | | |-BeanClassLoaderAware.java|     | | |-BeanFactory.java|     | | |-BeanFactoryAware.java|     | | |-BeanNameAware.java|     | | |-ConfigurableListableBeanFactory.java|     | | |-DisposableBean.java|     | | |-FactoryBean.java|     | | |-HierarcgicalBeanFactory.java|     | | |-InitializingBean.java|     | | |-ListableBeanFactory.java|     | | |-PropertyPlaceholderConfigurer.java|     | |-BeansException.java|     | |-PropertyValue.java|     | |-PropertyValues.java|     |-context|     | |-annotation|     | | |-ClassPathBeanDefinitionScanner.java|     | | |-ClassPathScanningCandidateComponentProvider.java|     | | |-Scope.java|     | |-event|     | | |-AbstractApplicationEventMulticaster.java|     | | |-ApplicationContextEvent.java|     | | |-ApplicationEventMulticaster.java|     | | |-ContextclosedEvent.java|     | | |-ContextRefreshedEvent.java|     | | |-SimpleApplicationEventMulticaster.java|     | |-support|     | | |-AbstractApplicationContext.java|     | | |-AbstractRefreshableApplicationContext.java|     | | |-AbstractXmlApplicationContext.java|     | | |-ApplicationContextAwareProcessor.java|     | | |-ClassPathXmlApplicationContext.java|     | |-ApplicationContext.java|     | |-ApplicationContextAware.java|     | |-ApplicationEvent.java|     | |-ApplicationEventPublisher.java|     | |-ApplicationListener.java|     | |-ConfigurableApplicationContext.java|     |-core.io|     | |-ClassPathResource.java|     | |-DefaultResourceLoader.java|     | |-FileSystemResource.java|     | |-Resource.java|     | |-ResourceLoader.java|     | |-UrlResource.java|     |-stereotype|     | |-Component.java|     |-util|     | |-ClassUtils.java|     | |-StringValueResolver.java|-test|-java|-com.lino.springframework.test|-bean| |-IUserService.java| |-UserService.java| |-UserServiceBeforeAdvice.java|-ApiTest.java|-resources|-spring.xml|-token.properties

3.2 在Bean生命周期中创建代理对象类图

在这里插入图片描述

  • 本节完成关于代理对象中属性的填充问题,但实际解决的思路是处理在 Bean 生命周期中合适的位置(初始化 initializeBean)中处理代理类的创建。
  • 主要包括:
    • DefaultAdvisorAutoProxyCreator 类创建代理对象的操作放置在 postProcessAfterInitialization 方法中。
    • AbstractAutowireCapableBeanFactory 完成初始化方法的调用操作。
  • 注意:在目前的 Spring 框架中,AbstractAutowireCapableBeanFactory 类里使用的是 CglibSubclassingInstantiationStrategy 创建对象,所有有需要判断对象获取接口的方法中,也都需要判断是否为 CGLIB 创建,否则是不能正确获取到接口的。
    • 如:ClassUtils.isCglibProxyClass(clazz) ? clazz.getSuperclass() : clazz

3.3 判断CGLIB对象

TargetSource.java

package com.lino.springframework.aop;import com.lino.springframework.util.ClassUtils;/*** @description: 被代理的目标对象*/
public class TargetSource {private final Object target;public TargetSource(Object target) {this.target = target;}/*** 获取目标对象列表** @return 目标对象列表*/public Class<?>[] getTargetClass() {Class<?> clazz = this.target.getClass();clazz = ClassUtils.isCglibProxyClass(clazz) ? clazz.getSuperclass() : clazz;return clazz.getInterfaces();}/*** 获取目标对象** @return 目标对象*/public Object getTarget() {return this.target;}
}
  • TargetSource#getTargetClass 是用于获取 target 对象的接口信息的,那么这个 target 可以是 JDK 代理创建,也可能是 CGLIB 创建。
  • 为了保证都能正确的获取到结果,这里需要增加判断 ClassUtils.isCglibProxyClass(clazz)

3.4 迁移创建AOP代理方法

3.4.1 实例化感知对象处理

InstantiationAwareBeanPostProcessor.java

package com.lino.springframework.beans.factory.config;import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.PropertyValues;/*** @description: 实例化感知对象处理*/
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {/*** 在 Bean 对象执行初始化方法之前,执行此方法** @param beanClass 对象类* @param beanName  对象名* @return 新对象* @throws BeansException 异常*/Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;/*** 在 Bean 对象执行初始化方法之后,执行此方法** @param bean     对象* @param beanName 对象名称* @return 是否执行* @throws BeansException 异常*/boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;/*** 在 Bean 对象实例化完成后,设置属性操作之前执行此方法** @param pvs      属性值集合* @param bean     对象* @param beanName 对象名称* @return 属性值集合* @throws BeansException 异常*/PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException;
}

3.4.2 扫描自定义注解类

AutowiredAnnotationBeanPostProcessor.java

package com.lino.springframework.beans.factory.annotation;import cn.hutool.core.bean.BeanUtil;
import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.PropertyValues;
import com.lino.springframework.beans.factory.BeanFactory;
import com.lino.springframework.beans.factory.BeanFactoryAware;
import com.lino.springframework.beans.factory.config.ConfigurableBeanFactory;
import com.lino.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import com.lino.springframework.util.ClassUtils;
import java.lang.reflect.Field;/*** @description: 扫描自定义注解类*/
public class AutowiredAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {...@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return null;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return null;}@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {return null;}@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {return true;}
}

3.4.3 默认自动代理创建者

DefaultAdvisorAutoProxyCreator.java

package com.lino.springframework.aop.framework.autoproxy;import com.lino.springframework.aop.*;
import com.lino.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import com.lino.springframework.aop.framework.ProxyFactory;
import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.PropertyValues;
import com.lino.springframework.beans.factory.BeanFactory;
import com.lino.springframework.beans.factory.BeanFactoryAware;
import com.lino.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import com.lino.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import java.util.Collection;/*** @description: 默认自动代理创建者*/
public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {private DefaultListableBeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = (DefaultListableBeanFactory) beanFactory;}@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {return null;}@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {return true;}private boolean isInfrastructureClass(Class<?> beanClass) {return Advice.class.isAssignableFrom(beanClass) || Pointcut.class.isAssignableFrom(beanClass) || Advisor.class.isAssignableFrom(beanClass);}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (isInfrastructureClass(bean.getClass())) {return bean;}Collection<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();for (AspectJExpressionPointcutAdvisor advisor : advisors) {ClassFilter classFilter = advisor.getPointcut().getClassFilter();// 过滤匹配类if (!classFilter.matches(bean.getClass())) {continue;}AdvisedSupport advisedSupport = new AdvisedSupport();TargetSource targetSource = new TargetSource(bean);advisedSupport.setTargetSource(targetSource);advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());advisedSupport.setProxyTargetClass(false);// 返回代理对象return new ProxyFactory(advisedSupport).getProxy();}return bean;}@Overridepublic PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException {return pvs;}
}
  • 关于 DefaultAdvisorAutoProxyCreator 类的操作主要就是把创建 AOP 代理的操作从 postProcessBeforeInstantiation 移动到 postProcessAfterInitialization 中。
  • 通过设置一些 AOP 的必备参数后,返回代理对象 new ProxyFactory(advisedSupport).getProxy()
    • 这个代理对象中就包括间接调用 TargetSource#getTargetClass 的获取。

3.5 在Bean生命周期中初始化执行

AbstractAutowireCapableBeanFactory.java

package com.lino.springframework.beans.factory.support;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.PropertyValue;
import com.lino.springframework.beans.PropertyValues;
import com.lino.springframework.beans.factory.*;
import com.lino.springframework.beans.factory.config.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;/*** @description: 实现默认bean创建的抽象bean工厂超类* @author: lingjian* @createDate: 2022/11/22 14:39*/
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();@Overrideprotected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) {Object bean = null;try {// 判断是否返回代理 Bean 对象bean = resolveBeforeInstantiation(beanName, beanDefinition);if (null != bean) {return bean;}// 实例化Beanbean = createBeanInstance(beanDefinition, beanName, args);// 实例化后判断boolean continueWithPropertyPopulation = applyBeanPostProcessorsAfterInstantiation(beanName, bean);if (!continueWithPropertyPopulation) {return bean;}// 在设置Bean属性之前,允许 BeanPostProcessor修改属性值applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition);// 给bean填充属性applyPropertyValues(beanName, bean, beanDefinition);// 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法bean = initializeBean(beanName, bean, beanDefinition);} catch (Exception e) {throw new BeansException("Instantiation of bean failed", e);}// 注册实现 DisposableBean 接口的 Bean 对象registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);// 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPEif (beanDefinition.isSingleton()) {registerSingletonBean(beanName, bean);}return bean;}/*** Bean 实例化后对于返回 false 的对象,不再执行后续设置 Bean 对象属性的操作** @param beanName 对象名称* @param bean     对象* @return 布尔值*/private boolean applyBeanPostProcessorsAfterInstantiation(String beanName, Object bean) {boolean continueWithPropertyPopulation = true;for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor instantiationAwareBeanPostProcessor = (InstantiationAwareBeanPostProcessor) beanPostProcessor;if (!instantiationAwareBeanPostProcessor.postProcessAfterInstantiation(bean, beanName)) {continueWithPropertyPopulation = false;break;}}}return continueWithPropertyPopulation;}...private Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {Object result = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessBeforeInstantiation(beanClass, beanName);if (null != result) {return result;}}}return null;}...
}
  • AbstractAutowireCapableBeanFactory#createBean 方法中,关注点在于 initializeBean -> applyBeanPostProcessorsAfterInitialization 这块逻辑的调用,最终完成 AOP 代理对象的创建操作。

四、测试:给代理对象的属性设置值

4.1 添加测试配置

4.1.1 用户服务层实现类

UserService.java

package com.lino.springframework.test.bean;import com.lino.springframework.beans.factory.annotation.Autowired;
import com.lino.springframework.beans.factory.annotation.Value;
import com.lino.springframework.stereotype.Component;
import java.util.Random;/*** @description: 用户接口实现类*/
public class UserService implements IUserService {private String token;@Overridepublic String queryUserInfo() {try {Thread.sleep(new Random(1).nextInt(100));} catch (InterruptedException e) {e.printStackTrace();}return "张三,10001,杭州," + token;}@Overridepublic String register(String userName) {try {Thread.sleep(new Random(1).nextInt(100));} catch (InterruptedException e) {e.printStackTrace();}return "注册用户:" + userName + " success!";}@Overridepublic String toString() {return "UserService#token = {" + token + "}";}public String getToken() {return token;}public void setToken(String token) {this.token = token;}
}

4.1.2 用户前置处理

UserServiceBeforeAdvice.java

package com.lino.springframework.test.bean;import com.lino.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;/*** @description: 用户前置处理*/
public class UserServiceBeforeAdvice implements MethodBeforeAdvice {@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {System.out.println("拦截方法:" + method.getName());}
}

4.1.3 Spring属性配置文件

spring.xml

<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="userService" class="com.lino.springframework.test.bean.UserService"><property name="token" value="RejDlI78hu223Opo983Ds"/></bean><bean class="com.lino.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/><bean id="beforeAdvice" class="com.lino.springframework.test.bean.UserServiceBeforeAdvice"/><bean id="methodInterceptor" class="com.lino.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor"><property name="advice" ref="beforeAdvice"/></bean><bean id="pointcutAdvisor" class="com.lino.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor"><property name="expression" value="execution(* com.lino.springframework.test.bean.IUserService.*(..))"/><property name="advice" ref="methodInterceptor"/></bean></beans>

4.2 单元测试

ApiTest.java

@Test
public void test_autoProxy() {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");IUserService userService = applicationContext.getBean("userService", IUserService.class);System.out.println("测试结果:" + userService.queryUserInfo());
}

测试结果

拦截方法:queryUserInfo
测试结果:张三,10001,杭州,RejDlI78hu223Opo983Ds

在这里插入图片描述

  • 测试结果看,通过对 Bean 生命周期的调整,在创建 AOP 代理对象就可以把代理对象的属性信息填充进去了。

五、总结:给代理对象的属性设置值

  • 核心内容主要是完善 Bean 的生命周期,在创建类的操作中完成代理对象的创建。
    • 通过这样的方式就可以让代理对象中的属性也可以随着创建过程被填充进行。
  • 除核心功能的实现外,也要关注到对象的初始化操作:CglibSubclassingInstantiationStrategySimpleInstantiationStrategy
    • 这两种方式中 CGLIB 创建对象,会影响到很多地方用于接口获取的操作。因为 CGLIB 创建对象走的是 ASM 字节码生成的操作,所以和普通的 JDK 代理生成对象是不一样,需要注意。

http://www.ppmy.cn/news/1100753.html

相关文章

CrossEntropyLoss() 和 nn.BCEWithLogitsLoss() 举例说明区别

CrossEntropyLoss() 通常用于多分类任务&#xff0c;它接受一个包含类别标签的张量作为目标值&#xff0c;并且假设每个样本只属于一个类别。在多分类任务中&#xff0c;模型的最后一层输出是一个概率分布&#xff0c;表示每个类别的概率。CrossEntropyLoss() 计算模型输出与目…

LeetCode 面试题 04.01. 节点间通路

文章目录 一、题目二、C# 题解 一、题目 节点间通路。给定有向图&#xff0c;设计一个算法&#xff0c;找出两个节点之间是否存在一条路径。 点击此处跳转题目。 示例1: 输入&#xff1a; n 3, graph [[0, 1], [0, 2], [1, 2], [1, 2]], start 0, target 2 输出&#xff1…

VMware的三种连接模式

目录 目录 前言 系列文章列表 思维导图 1&#xff0c;VMware是什么? 2&#xff0c;VMware的连接模式 2.1,VMware的连接模式是什么? 2.2, VMware的连接模式的分类 3&#xff0c;桥接模式 3.1,图示介绍 3.2,详细介绍 3.3,注意点 4.NAT模式 4.1,NAT协议 4.2,图示…

OpenCV 11(图像金字塔)

一、 图像金字塔 **图像金字塔**是图像中多尺度表达的一种&#xff0c;最主要用于图像的分割&#xff0c;是一种以多分辨率来解释图像的有效但概念简单的结构。简单来说, 图像金字塔是同一图像不同分辨率的子图集合. 图像金字塔最初用于机器视觉和图像压缩。其通过梯次向下采…

Matlab图像处理-多阈值分割

多阈值分割 在某些时候图像使用单独的阈值不能够对其实现有效地分割&#xff0c;例如在灰度直方图中有明显的三个峰时候&#xff0c;我们需要提取中间峰&#xff0c;这时我们使用双阈值分割会得到较好的分割效果。如下例子中生成灰度直方图中有两个峰&#xff0c;选择合适的两…

66.C++多态与虚函数

目录 1.什么是多态 2.多态的分类 3.对象转型 3.1 向上转型&#xff1a; 3.2 向下转型&#xff1a; 4.虚函数 1.什么是多态 生活中的多态&#xff0c;是指的客观的事物在人脑中的主观体现。例如&#xff0c;在路上看到⼀只哈士奇&#xff0c;你可以看做是哈士奇&#xf…

迄今为止最完整的DDD实践

作者&#xff1a;章磊(章三) 阿里飞猪技术团队 一、为什么需要DDD 对于一个架构师来说&#xff0c;在软件开发中如何降低系统复杂度是一个永恒的挑战。 复杂系统设计&#xff1a; 系统多&#xff0c;业务逻辑复杂&#xff0c;概念不清晰&#xff0c;有什么合适的方法帮助我们…

猫头虎的技术笔记:Spring Boot启动报错解决方案

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…