共计 14689 个字符,预计需要花费 37 分钟才能阅读完成。
本文节选自《Spring 5 外围原理》
Spring IoC 容器还有一些高级个性,如应用 lazy-init 属性对 Bean 预初始化、应用 FactoryBean 产生或者润饰 Bean 对象的生成、IoC 容器在初始化 Bean 过程中应用 BeanPostProcessor 后置处理器对 Bean 申明周期事件进行治理等。
1 对于延时加载
咱们曾经晓得,IoC 容器的初始化过程就是对 Bean 定义资源的定位、载入和注册,此时容器对 Bean 的依赖注入并没有产生,依赖注入是在应用程序第一次向容器索取 Bean 时通过 getBean() 办法实现的。
当 Bean 定义资源的 < bean> 元素中配置了 lazy-init=false 属性时,容器将会在初始化时对所配置的 Bean 进行预实例化,Bean 的依赖注入在容器初始化时就曾经实现。这样,当应用程序第一次向容器索取被治理的 Bean 时,就不必再初始化和对 Bean 进行依赖注入了,而是间接从容器中获取曾经实现依赖注入的 Bean,进步了应用程序第一次向容器获取 Bean 的性能。
1.1. refresh() 办法
IoC 容器读入曾经定位的 Bean 定义资源是从 refresh() 办法开始的,咱们从 AbstractApplicationContext 类的 refresh() 办法动手剖析,回顾一下源码:
@Override
public void refresh() throws BeansException, IllegalStateException {
...
// 子类的 refreshBeanFactory() 办法启动
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
...
}
在 refresh() 办法中 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 启动了 Bean 定义资源的载入、注册过程。finishBeanFactoryInitialization() 办法是对注册后的 Bean 定义中的预实例化(lazy-init=false,Spring 默认进行预实例化,即为 true)的 Bean 进行解决的中央。
1.2. 应用 finishBeanFactoryInitialization() 解决预实例化的 Bean
当 Bean 定义资源被载入 IoC 容器之后,容器将 Bean 定义资源解析为容器外部的数据结构 BeanDefinition,并注册到容器中,AbstractApplicationContext 类中的 finishBeanFactoryInitialization() 办法对配置了预实例化属性的 Bean 进行预初始化,源码如下:
// 对配置了 lazy-init 属性的 Bean 进行预实例化解决
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {// 这是 Spring 3 新加的代码,为容器指定一个转换服务 (ConversionService)
// 在对某些 Bean 属性进行转换时应用
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
if (!beanFactory.hasEmbeddedValueResolver()) {beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders (strVal));
}
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {getBean(weaverAwareName);
}
// 为了使类型匹配,停止使用长期的类加载器
beanFactory.setTempClassLoader(null);
// 缓存容器中所有注册的 BeanDefinition 元数据,以防被批改
beanFactory.freezeConfiguration();
// 对配置了 lazy-init 属性的单例模式的 Bean 进行预实例化解决
beanFactory.preInstantiateSingletons();}
其中 ConfigurableListableBeanFactory 是一个接口,preInstantiateSingletons() 办法由其子类 DefaultListableBeanFactory 提供。
1.3. 对配置了 lazy-init 属性的单例模式的 Bean 的预实例化
对配置了 lazy-init 属性的单例模式的 Bean 的预实例化相干源码如下:
public void preInstantiateSingletons() throws BeansException {if (this.logger.isDebugEnabled()) {this.logger.debug("Pre-instantiating singletons in" + this);
}
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
for (String beanName : beanNames) {
// 获取指定名称的 Bean 定义
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
//Bean 不是形象的,是单例模式的,且 lazy-init 属性配置为 false
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 如果指定名称的 Bean 是创立容器的 Bean
if (isFactoryBean(beanName)) {
//FACTORY_BEAN_PREFIX="&",当 Bean 名称后面加 "&" 符号
// 时,获取的是容器对象自身,而不是容器产生的 Bean
// 调用 getBean 办法,触发 Bean 实例化和依赖注入
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
// 标识是否须要预实例化
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
// 一个匿名外部类
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>) () ->
((SmartFactoryBean<?>) factory).isEagerInit(),
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {// 调用 getBean() 办法,触发 Bean 实例化和依赖注入
getBean(beanName);
}
}
else {getBean(beanName);
}
}
}
通过对 lazy-init 解决源码的剖析能够看出,如果设置了 lazy-init 属性,则容器在实现 Bean 定义的注册之后,会通过 getBean() 办法触发指定 Bean 的初始化和依赖注入。如前所述,这样当应用程序第一次向容器索取所需的 Bean 时,容器不再须要对 Bean 进行初始化和依赖注入,可间接从曾经实现实例化和依赖注入的 Bean 中取一个现成的 Bean,进步了第一次获取 Bean 的性能。
2 对于 FactoryBean 和 BeanFactory
Spring 中,有两个很容易混同的类:BeanFactory 和 FactoryBean。
BeanFactory:Bean 工厂,是一个工厂(Factory),Spring IoC 容器的最高层接口就是 BeanFactory,它的作用是治理 Bean,即实例化、定位、配置应用程序中的对象及建设这些对象之间的依赖。
FactoryBean:工厂 Bean,是一个 Bean,作用是产生其余 Bean 实例。这种 Bean 没有什么特地的要求,仅须要提供一个工厂办法,该办法用来返回其余 Bean 实例。在通常状况下,Bean 毋庸本人实现工厂模式,Spring 容器负责工厂的角色;在多数状况下,容器中的 Bean 自身就是工厂,其作用是产生其余 Bean 实例。
当用户应用容器时,能够应用转义字符“&”来失去 FactoryBean 自身,以区别通过 FactoryBean 产生的实例对象和 FactoryBean 对象自身。在 BeanFactory 中通过如下代码定义了该转义字符:
String FACTORY_BEAN_PREFIX = “&”;
如果 myJndiObject 是一个 FactoryBean,则应用 &myJndiObject 失去的是 myJndiObject 对象,而不是 myJndiObject 产生的对象。
2.1. FactoryBean 源码
// 工厂 Bean,用于产生其余对象
public interface FactoryBean<T> {
// 获取容器治理的对象实例
@Nullable
T getObject() throws Exception;
// 获取 Bean 工厂创立的对象的类型
@Nullable
Class<?> getObjectType();
//Bean 工厂创立的对象是否是单例模式的,如果是,// 则整个容器中只有一个实例对象,每次申请都返回同一个实例对象
default boolean isSingleton() {return true;}
}
2.2. AbstractBeanFactory 的 getBean() 办法
在剖析 Spring IoC 容器实例化 Bean 并进行依赖注入的源码时,提到在 getBean() 办法触发容器实例化 Bean 时会调用 AbstractBeanFactory 的 doGetBean() 办法,其重要源码如下:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
...
BeanFactory parentBeanFactory = getParentBeanFactory();
// 以后容器的父容器存在,且以后容器中不存在指定名称的 Bean
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// 解析指定 Bean 名称的原始名称
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// 委派父容器依据指定名称和显式的参数查找
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else {
// 委派父容器依据指定名称和类型查找
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
...
return (T) bean;
}
// 获取给定 Bean 的实例对象,次要实现 FactoryBean 的相干解决
protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
// 容器曾经失去了 Bean 实例对象,这个实例对象可能是一个一般的 Bean,// 也可能是一个工厂 Bean,如果是一个工厂 Bean,则应用它创立一个 Bean 实例对象,// 如果调用自身就想取得一个容器的援用,则返回这个工厂 Bean 实例对象
// 如果指定的名称是容器的解援用(dereference,即对象自身而非内存地址)// 且 Bean 实例也不是创立 Bean 实例对象的工厂 Bean
if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
}
// 如果 Bean 实例不是工厂 Bean,或者指定名称是容器的解援用
// 调用者获取对容器的援用时,间接返回以后的 Bean 实例
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {return beanInstance;}
// 解决指定名称不是容器的解援用,或者依据名称获取的 Bean 实例对象是一个工厂 Bean
// 应用工厂 Bean 创立一个 Bean 的实例对象
Object object = null;
if (mbd == null) {
// 从 Bean 工厂缓存中获取指定名称的 Bean 实例对象
object = getCachedObjectForFactoryBean(beanName);
}
// 让 Bean 工厂生产指定名称的 Bean 实例对象
if (object == null) {FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// 如果从 Bean 工厂生产的 Bean 是单例模式的,则缓存
if (mbd == null && containsBeanDefinition(beanName)) {
// 从容器中获取指定名称的 Bean 定义,如果继承了基类,则合并基类的相干属性
mbd = getMergedLocalBeanDefinition(beanName);
}
// 如果从容器失去了 Bean 定义信息,并且 Bean 定义信息不是虚构的,// 则让工厂 Bean 生产 Bean 实例对象
boolean synthetic = (mbd != null && mbd.isSynthetic());
// 调用 FactoryBeanRegistrySupport 类的 getObjectFromFactoryBean() 办法
// 实现工厂 Bean 生产 Bean 实例对象的过程
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}
在下面获取给定 Bean 的实例对象的 getObjectForBeanInstance() 办法中,会调用 FactoryBean- RegistrySupport 类的 getObjectFromFactoryBean() 办法,该办法实现了 Bean 工厂生产 Bean 实例对象。
2.3. AbstractBeanFactory 生产 Bean 实例对象
AbstractBeanFactory 类中生产 Bean 实例对象的次要源码如下:
//Bean 工厂生产 Bean 实例对象
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
//Bean 工厂是单例模式,并且 Bean 工厂缓存中存在指定名称的 Bean 实例对象
if (factory.isSingleton() && containsSingleton(beanName)) {
// 多线程同步,以避免数据不统一
synchronized (getSingletonMutex()) {
// 间接从 Bean 工厂的缓存中获取指定名称的 Bean 实例对象
Object object = this.factoryBeanObjectCache.get(beanName);
// 如果 Bean 工厂缓存中没有指定名称的实例对象,则生产该实例对象
if (object == null) {
// 调用 Bean 工厂的获取对象的办法生产指定 Bean 的实例对象
object = doGetObjectFromFactoryBean(factory, beanName);
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {object = alreadyThere;}
else {if (shouldPostProcess) {
try {object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName,
"Post-processing of FactoryBean's singleton object failed", ex);
}
}
// 将生产的实例对象增加到 Bean 工厂的缓存中
this.factoryBeanObjectCache.put(beanName, object);
}
}
return object;
}
}
// 调用 Bean 工厂的获取对象的办法生产指定 Bean 的实例对象
else {Object object = doGetObjectFromFactoryBean(factory, beanName);
if (shouldPostProcess) {
try {object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
}
}
return object;
}
}
// 调用 Bean 工厂的办法生产指定 Bean 的实例对象
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
throws BeanCreationException {
Object object;
try {if (System.getSecurityManager() != null) {AccessControlContext acc = getAccessControlContext();
try {
// 实现 PrivilegedExceptionAction 接口的匿名外部类
object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->
factory.getObject(), acc);
}
catch (PrivilegedActionException pae) {throw pae.getException();
}
}
else {
// 调用 BeanFactory 接口实现类的创建对象办法
object = factory.getObject();}
}
catch (FactoryBeanNotInitializedException ex) {throw new BeanCurrentlyInCreationException(beanName, ex.toString());
}
catch (Throwable ex) {throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
}
// 创立进去的实例对象为 null,或者因为单例对象正在创立而返回 null
if (object == null) {if (isSingletonCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName, "FactoryBean which is currently in creation returned null from getObject");
}
object = new NullBean();}
return object;
}
从下面的源码剖析中能够看出,BeanFactory 接口调用其实现类的获取对象的办法来实现创立 Bean 实例对象的性能。
2.4. FactoryBean 实现类的获取对象的办法
FactoryBean 接口的实现类十分多,比方 Proxy、RMI、JNDI、ServletContextFactoryBean 等。FactoryBean 接口为 Spring 容器提供了一个很好的封装机制,具体的获取对象的办法由不同的实现类依据不同的实现策略来提供,咱们剖析一下最简略的 AnnotationTestFactoryBean 类的源码:
public class AnnotationTestBeanFactory implements FactoryBean<FactoryCreatedAnnotationTestBean> {private final FactoryCreatedAnnotationTestBean instance = new FactoryCreatedAnnotationTestBean();
public AnnotationTestBeanFactory() {this.instance.setName("FACTORY");
}
@Override
public FactoryCreatedAnnotationTestBean getObject() throws Exception {return this.instance;}
//AnnotationTestBeanFactory 产生 Bean 实例对象的实现
@Override
public Class<? extends IJmxTestBean> getObjectType() {return FactoryCreatedAnnotationTestBean.class;}
@Override
public boolean isSingleton() {return true;}
}
Proxy、RMI、JNDI 等其余实现类都依据相应的策略提供办法,这里不做一一剖析,这曾经不是 Spring 的外围性能,感兴趣的“小伙伴”能够自行深入研究。
3 再述 autowiring
Spring IoC 容器提供了两种治理 Bean 依赖关系的形式:
(1)显式治理:通过 BeanDefinition 的属性值和构造方法实现 Bean 依赖关系治理。
(2)autowiring:Spring IoC 容器有依赖主动拆卸性能,不须要对 Bean 属性的依赖关系做显式的申明,只须要配置好 autowiring 属性,IoC 容器会主动应用反射查找属性的类型和名称,而后基于属性的类型或者名称来主动匹配容器中的 Bean,从而主动实现依赖注入。
容器对 Bean 的主动拆卸产生在容器对 Bean 依赖注入的过程中。在对 Spring IoC 容器的依赖注入源码进行剖析时,咱们曾经晓得容器对 Bean 实例对象的依赖属性注入产生在 AbstractAutoWireCapableBeanFactory 类的 populateBean() 办法中,上面通过程序流程剖析 autowiring 的实现原理。
3.1. AbstractAutoWireCapableBeanFactory 对 Bean 实例对象进行属性依赖注入
应用程序第一次通过 getBean() 办法(配置了 lazy-init 预实例化属性的除外)向 IoC 容器索取 Bean 时,容器创立 Bean 实例对象,并且对 Bean 实例对象进行属性依赖注入,AbstractAutoWire- CapableBeanFactory 的 populateBean() 办法就实现了属性依赖注入的性能,其次要源码如下:
// 将 Bean 属性设置到生成的实例对象上
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
…
// 获取容器在解析 Bean 定义时为 BeanDefinition 设置的属性值
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
// 解决依赖注入,首先解决 autowiring 主动拆卸的依赖注入
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
// 依据 Bean 名称进行 autowiring 主动拆卸解决
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {autowireByName(beanName, mbd, bw, newPvs);
}
// 依据 Bean 类型进行 autowiring 主动拆卸解决
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {autowireByType(beanName, mbd, bw, newPvs);
}
pvs = newPvs;
}
// 对非 autowiring 的属性进行依赖注入解决
...
}
3.2. Spring IoC 容器依据 Bean 名称或者类型进行 autowiring 主动属性依赖注入
Spring IoC 容器依据 Bean 名称或者类型进行 autowiring 主动属性依赖注入的重要代码如下:
// 依据类型对属性进行主动依赖注入
protected void autowireByType(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
// 获取用户定义的类型转换器
TypeConverter converter = getCustomTypeConverter();
if (converter == null) {converter = bw;}
// 寄存解析的要注入的属性
Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
// 对 Bean 对象中非简略属性(不是简略继承的对象,如 8 种原始类型、字符、URL 等都是简略属性)进行解决
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
for (String propertyName : propertyNames) {
try {
// 获取指定属性名称的属性形容器
PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
// 不对 Object 类型的属性进行 autowiring 主动依赖注入
if (Object.class != pd.getPropertyType()) {
// 获取属性的赋值办法
MethodParameter MethodParam = BeanUtils.getWriteMethodParameter(pd);
// 查看指定类型是否能够被转换为指标对象的类型
boolean eager = !PriorityOrdered.class.isInstance(bw.getWrappedInstance());
// 创立一个要被注入的依赖形容
DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(MethodParam, eager);
// 依据容器的 Bean 定义解析依赖关系,返回所有要被注入的 Bean 对象
Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);
if (autowiredArgument != null) {
// 将属性赋值为所援用的对象
pvs.add(propertyName, autowiredArgument);
}
for (String autowiredBeanName : autowiredBeanNames) {
// 为指定名称属性注册依赖 Bean 名称,进行属性的依赖注入
registerDependentBean(autowiredBeanName, beanName);
if (logger.isDebugEnabled()) {
logger.debug("Autowiring by type from bean name'" + beanName + "'via property'"
+ propertyName + "'to bean named'" + autowiredBeanName + "'");
}
}
// 开释已主动注入的属性
autowiredBeanNames.clear();}
}
catch (BeansException ex) {throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
}
}
}
通过下面的源码剖析能够看出,通过属性名进行主动依赖注入相比通过属性类型进行主动依赖注入要略微简略一些。然而真正实现属性注入的是 DefaultSingletonBeanRegistry 类的 registerDependentBean() 办法。
3.3. DefaultSingletonBeanRegistry 的 registerDependentBean() 办法实现属性依赖注入
DefaultSingletonBeanRegistry 的 registerDependentBean() 办法实现属性依赖注入的重要代码如下:
// 为指定的 Bean 注入依赖的 Bean
public void registerDependentBean(String beanName, String dependentBeanName) {
// 解决 Bean 名称,将别名转换为标准的 Bean 名称
String canonicalName = canonicalName(beanName);
Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
if (dependentBeans != null && dependentBeans.contains(dependentBeanName)) {return;}
// 多线程同步,保障容器内数据的一致性
// 在容器中通过“Bean 名称→全副依赖 Bean 名称汇合”查找指定名称 Bean 的依赖 Bean
synchronized (this.dependentBeanMap) {
// 获取指定名称 Bean 的所有依赖 Bean 名称
dependentBeans = this.dependentBeanMap.get(canonicalName);
if (dependentBeans == null) {
// 为 Bean 设置依赖 Bean 信息
dependentBeans = new LinkedHashSet<>(8);
this.dependentBeanMap.put(canonicalName, dependentBeans);
}
// 在向容器中通过“Bean 名称→全副依赖 Bean 名称汇合”增加 Bean 的依赖信息
// 即,将 Bean 所依赖的 Bean 增加到容器的汇合中
dependentBeans.add(dependentBeanName);
}
// 在容器中通过“Bean 名称→指定名称 Bean 的依赖 Bean 汇合”查找指定名称 Bean 的依赖 Bean
synchronized (this.dependenciesForBeanMap) {Set<String> dependenciesForBean = this.dependenciesForBeanMap.get(dependentBeanName);
if (dependenciesForBean == null) {dependenciesForBean = new LinkedHashSet<>(8);
this.dependenciesForBeanMap.put(dependentBeanName, dependenciesForBean);
}
// 在容器中通过“Bean 名称→指定 Bean 的依赖 Bean 名称汇合”增加 Bean 的依赖信息
// 即,将 Bean 所依赖的 Bean 增加到容器的汇合中
dependenciesForBean.add(canonicalName);
}
}
能够看出,autowiring 的实现过程如下:
(1)对 Bean 的属性调用 getBean() 办法,实现依赖 Bean 的初始化和依赖注入。
(2)将依赖 Bean 的属性援用设置到被依赖的 Bean 属性上。
(3)将依赖 Bean 的名称和被依赖 Bean 的名称存储在 IoC 容器的汇合中。
Spring IoC 容器的 autowiring 主动属性依赖注入是一个很不便的个性,能够简化开发配置,然而凡事都有两面性,主动属性依赖注入也有有余:首先,Bean 的依赖关系在配置文件中无奈很分明地看进去,会给保护造成肯定的艰难;其次,因为主动属性依赖注入是 Spring 容器主动执行的,容器是不会智能判断的,如果配置不当,将会带来无奈意料的结果。所以在应用主动属性依赖注入时须要综合思考。
关注微信公众号『Tom 弹架构』回复“Spring”可获取残缺源码。
本文为“Tom 弹架构”原创,转载请注明出处。技术在于分享,我分享我高兴!如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源。关注微信公众号『Tom 弹架构』可获取更多技术干货!
原创不易,保持很酷,都看到这里了,小伙伴记得点赞、珍藏、在看,一键三连加关注!如果你感觉内容太干,能够分享转发给敌人滋润滋润!