Эта статья взята из «Основных принципов Spring 5».
Контейнер Spring IoC также имеет некоторые дополнительные функции, такие как использование атрибута lazy-init для предварительной инициализации bean-компонентов, использование FactoryBean для создания или изменения генерации объектов bean-компонентов, а контейнер IoC использует постпроцессор BeanPostProcessor для управления объявлением bean-компонентов. цикл событий во время инициализации bean-компонентов Подождите.
1 О ленивой загрузке
Мы уже знаем, что процесс инициализации контейнера IoC — это позиционирование, загрузка и регистрация ресурсов определения bean-компонента.В это время внедрение зависимостей контейнера в bean-компонент не происходит.Внедрение зависимостей — это когда приложение запрашивает у контейнера bean в первый раз через метод getBean () завершен.
Когда атрибут lazy-init=false настроен в элементе
1.1. Метод обновления()
Контейнер IoC считывает найденные ресурсы определения bean-компонента из метода refresh().Мы начинаем с метода refresh() класса AbstractApplicationContext и просматриваем исходный код:
@Override
public void refresh() throws BeansException, IllegalStateException {
...
//子类的refreshBeanFactory()方法启动
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
...
}
В методе refresh() ConfigurableListableBeanFactory beanFactory =getFreshBeanFactory(); запускает процесс загрузки и регистрации ресурсов определения компонента. В методе finishBeanFactoryInitialization() обрабатываются предварительно созданные экземпляры (lazy-init=false, Spring по умолчанию использует предварительно созданные экземпляры, то есть true) bean-компоненты в зарегистрированном определении bean-компонента.
1.2 Обработка предварительно созданных bean-компонентов с помощью finishBeanFactoryInitialization()
После того, как ресурс определения компонента загружен в контейнер IoC, контейнер анализирует ресурс определения компонента в структуре данных BeanDefinition внутри контейнера и регистрирует его в контейнере.Метод finishBeanFactoryInitialization() в классе AbstractApplicationContext предварительно конфигурирует компоненты с -экземпляры атрибутов.Инициализация, исходный код выглядит следующим образом:
//对配置了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, выглядит следующим образом:
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-компонентов в первый раз.
2 О FactoryBean и BeanFactory
В Spring есть два класса, которые легко спутать: BeanFactory и FactoryBean. BeanFactory: Bean factory — это фабрика. Интерфейс самого высокого уровня контейнера Spring IoC — BeanFactory. Его функция — управлять bean-компонентами, то есть создавать экземпляры, находить, настраивать объекты в приложении и устанавливать зависимости между этими объектами. FactoryBean: Factory Bean — это Bean, роль которого заключается в создании других экземпляров Bean. Для этого вида Bean-компонентов нет особых требований, необходимо предоставить только фабричный метод, который используется для возврата других экземпляров Bean-компонентов. При нормальных обстоятельствах компоненту Bean не нужно реализовывать шаблон фабрики самостоятельно, а контейнер Spring действует как фабрика; в некоторых случаях компонент Bean в самом контейнере является фабрикой, и его роль заключается в создании других экземпляров компонента. . Когда пользователь использует контейнер, escape-символ "&" может использоваться для получения самого FactoryBean, чтобы отличить объект экземпляра, созданный FactoryBean, и сам объект FactoryBean. Экранирующий символ определяется в BeanFactory следующим кодом: Строка 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 Метод getBean() класса AbstractBeanFactory
При анализе исходного кода контейнера Spring IoC, создающего экземпляры bean-компонентов и выполняющего внедрение зависимостей, упоминается, что метод doGetBean() класса AbstractBeanFactory будет вызываться, когда метод getBean() запускает контейнер для создания экземпляров bean-компонентов. следует:
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;
}
В методе getObjectForBeanInstance() получения экземпляра объекта данного Бина выше вызывается метод getObjectFromFactoryBean() класса FactoryBean-RegistrySupport, который реализует Фабрику Бинов для создания объектов экземпляров Бина.
2.3. AbstractBeanFactory создает объекты-экземпляры Bean
Основной исходный код для создания объектов экземпляра Bean в классе AbstractBeanFactory выглядит следующим образом:
//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 Переформулировка автопроводки
Контейнер Spring IoC предоставляет два способа управления зависимостями компонентов: (1) Явное управление: управление зависимостями компонентов реализовано через значение атрибута и метод построения BeanDefinition. (2) автосвязывание: Контейнер Spring IoC имеет функцию автосвязывания зависимостей. Ему не нужно явно объявлять зависимости свойств Bean. Вам нужно только настроить свойство автосвязывания. Контейнер IoC будет автоматически использовать отражение для поиска типа и имя свойства, а затем на основе Тип или имя атрибута автоматически соответствует Bean-компоненту в контейнере, чтобы автоматически завершить внедрение зависимостей. Автосвязывание контейнера с компонентом происходит в процессе внедрения зависимости контейнера с компонентом. При анализе исходного кода внедрения зависимостей контейнера Spring IoC мы уже знаем, что внедрение свойств зависимостей объекта экземпляра Bean контейнером происходит в методе populateBean() класса AbstractAutoWireCapableBeanFactory Ниже приводится анализ принципа реализации. автопроводки через программный поток.
3.1. AbstractAutoWireCapableBeanFactory выполняет внедрение зависимостей атрибутов в объекты экземпляра Bean.
Когда приложение запрашивает bean-компонент из контейнера IoC с помощью метода getBean() (за исключением тех, которые настроены с атрибутами предварительной инициализации отложенной инициализации) в первый раз, контейнер создает объект экземпляра 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 выполняет автоматическую инъекцию зависимостей атрибутов в соответствии с именем или типом компонента.
Важный код для контейнера Spring IoC для выполнения автоматического внедрения зависимостей атрибутов в соответствии с именем или типом компонента выглядит следующим образом:
//根据类型对属性进行自动依赖注入
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);
}
}
}
Из приведенного выше анализа исходного кода видно, что автоматическое внедрение зависимостей через имена атрибутов немного проще, чем автоматическое внедрение зависимостей через типы атрибутов. Но реальной реализацией внедрения свойств является метод registerDependentBean() класса DefaultSingletonBeanRegistry.
3.3 Метод registerDependentBean() класса DefaultSingletonBeanRegistry реализует внедрение зависимостей атрибутов
Важный код для реализации внедрения зависимостей атрибутов в методе registerDependentBean() DefaultSingletonBeanRegistry выглядит следующим образом:
//为指定的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);
}
}
Видно, что процесс реализации автовайринга выглядит следующим образом: (1) Вызовите метод getBean() для свойств Bean, чтобы завершить инициализацию и внедрение зависимостей зависимого Bean. (2) Установите ссылку на атрибут зависимого компонента на атрибут зависимого компонента. (3) Сохраните имя зависимого компонента и имя зависимого компонента в коллекции контейнера IoC. Автоматическое внедрение зависимостей атрибутов контейнера Spring IoC является очень удобной функцией, которая может упростить настройку разработки, но у всего есть две стороны, и у автоматического внедрения зависимостей атрибутов также есть недостатки: во-первых, зависимости Bean не могут быть четко видны в файл конфигурации., это вызовет определенные трудности в обслуживании; во-вторых, поскольку автоматическая инъекция зависимостей атрибутов выполняется контейнером Spring автоматически, контейнер не будет разумно судить. Если конфигурация неверна, это приведет к непредвиденным последствиям. Поэтому вам необходимо всесторонне рассмотреть вопрос об использовании автоматического внедрения зависимостей свойств.
Эта статья является оригиналом "Архитектуры бомбы Тома", пожалуйста, указывайте источник при перепечатке. Технология заключается в обмене, я разделяю свое счастье! Если у вас есть какие-либо предложения, вы также можете оставить комментарий или личное сообщение, Ваша поддержка является движущей силой для меня, чтобы упорствовать в создании. Обратите внимание на «архитектуру бомбы Тома», чтобы получить больше технической галантереи!
Нелегко быть оригинальным, и круто настаивать. Вы все это видели здесь. Не забудьте поставить лайк, добавить в закладки, посмотреть и подписаться одним щелчком мыши! Если вы считаете, что контент слишком сух, вы можете поделиться им и отправить своим друзьям, чтобы они питались и питались!