ConfigurationClassPostProcessor

在前面一个章节,笔者和大家介绍了在构造一个应用上下文时,spring会执行到PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(...)方法,我们已经清楚这个方法的整个流程,也知道在这个方法里会调用spring内置的BeanDefinitionRegistryPostProcessor实现类——ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry(...)方法,在这个方法中会完成BeanDefinition的扫描。本章我们就要来学习ConfigurationClassPostProcessor这个类,看看这个类实现的processConfigBeanDefinitions(...)是如何完成扫描BeanDefinition。

在这个方法中会在<1>处获取传入的BeanDefinitionRegistry对象的唯一哈希码(identityHashCode)registryId,判断这个哈希码是否在registriesPostProcessed集合中,如果存在则表示当前的配置类后置处理器(ConfigurationClassPostProcessor)曾经处理过传入的BeanDefinitionRegistry对象,会进入<2>处的分支抛出异常。如果配置类后置处理器从未处理过传入的BeanDefinitionRegistry对象,在<3>处会把BeanDefinitionRegistry对象的哈希码加入到registriesPostProcessed集合。

代码<1>~<3>只是做了幂等性校验,避免配置类后置处理器重复处理同一个BeanDefinitionRegistry对象,这里我们也可以看出扫描BeanDefinition的工作一定不是在<1>~<3>处的代码完成的,那就只能是在21行执行processConfigBeanDefinitions(registry)时完成的。同时我们也注意到在<2>~<3>之间会把哈希码加到factoriesPostProcessed集合,之所以有这一步操作是为了后续执行配置类后置处理器时如果发现传入的BeanFactory对象的哈希码在factoriesPostProcessed集合中,可以将BeanFactory对象强制转换成BeanDefinitionRegistry类型做一些额外的工作。

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
……
private final Set<Integer> registriesPostProcessed = new HashSet<>(); private final Set<Integer> factoriesPostProcessed = new HashSet<>();
……
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);//<1>
if (this.registriesPostProcessed.contains(registryId)) {//<2>
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);//<3> processConfigBeanDefinitions(registry);
}
……
}

  

注:通常获取对象的哈希码都是调用继承自Object类的hashCode()方法,但因为这个方法存在被重写的可能,所以上面的代码在<1>处System.identityHashCode(Object x)本地方法获取对象默认的哈希码。

在下面这个测试用例中,我们创建了A类和B类,B类相较于A类重写了hashCode()方法,我们对比下重写hashCode()方法后调用obj.hashCode()和调用System.identityHashCode(obj)的区别。

    @Test
public void test08() {
class A {
}
A a = new A();
System.out.println("a hashCode:" + a.hashCode());
System.out.println("a identityHashCode:" + System.identityHashCode(a));
class B {
@Override
public int hashCode() {
return 0;
}
}
B b1 = new B();
System.out.println("b1 hashCode:" + b1.hashCode());
System.out.println("b1 identityHashCode:" + System.identityHashCode(b1));
B b2 = new B();
System.out.println("b2 hashCode:" + b2.hashCode());
System.out.println("b2 identityHashCode:" + System.identityHashCode(b2));
}

  

执行结果:

a hashCode:1654589030
a identityHashCode:1654589030
b1 hashCode:0
b1 identityHashCode:33524623
b2 hashCode:0
b2 identityHashCode:947679291

  

从执行结果可以看到,没有重写hashCode()的A类在调用对象本身的hashCode()方法或者调用System.identityHashCode(a),返回的结果都是一样的,而重写hashCode()的B类在调用对象本身的hashCode()方法时返回的都是0,只有调用System.identityHashCode(...)才会返回对象默认的哈希码。

下面我们来看看ConfigurationClassPostProcessor.processConfigBeanDefinitions(...)完成的工作,既然这个方法要扫描类路径,那一定少不了和配置类打交道,读取配置类上用@ComponentScan注解配置的类路径。这里我们在回顾回顾spring是怎样将配置类注册到容器的,我们在一个类上标记@Configuration、@ComponentScan注解,类似下面的MyConfi5,再将MyConfig5.class这个类对象作为构造参数传给注解应用上下文(AnnotationConfigApplicationContext)构造应用上下文对象,在应用上下文的构造函数中会初始化一个AnnotatedBeanDefinitionReader对象,在构造AnnotatedBeanDefinitionReader对象时会调用到AnnotationConfigUtils.registerAnnotationConfigProcessors(...)静态方法,在这个方法会向spring容器注册一些基础组件的BeanDefinition,之后AnnotatedBeanDefinitionReader对象会将配置类MyConfig5的class对象作为AnnotatedGenericBeanDefinition构造函数的参数创建一个BeanDefinition的实例并注册到spring容器。因此在执行spring容器ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(...)方法之前,spring容器已经包含了配置类和基础组件的BeanDefinition。

@Configuration
@ComponentScan("org.example.service")
public class MyConfig5 { public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig5.class);
}
}

  

当执行ConfigurationClassPostProcessor.processConfigBeanDefinitions(...)方法的时候,会先从代码<1>处获取容器现有的所有beanName,遍历这些beanName在<2>处过滤出配置类对应的BeanDefinition,将配置类对应的BeanDefinition加入到configCandidates列表,后续会从这些BeanDefinition解析出配置类要求扫描的类路径。如果遍历所有的BeanDefinition都没有找到配置类,则configCandidates列表为空,当执行到<3>处就会退出processConfigBeanDefinitions(...)方法。

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
……
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();//<1> for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {//<2>
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
} // Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {//<3>
return;
}
……
}
……
}

  

那么我们来思考下,什么样的BeanDefinition能让ConfigurationClassUtils.checkConfigurationClassCandidate(...)方法返回true,将其加入到configCandidates列表呢?首先我们要明白BeanDefinition存在的目的,在spring容器中BeanDefinition存在的目的就是为了描述一个bean对象,spring可以从BeanDefinition得知如何构造一个bean对象?这个bean对象是单例还是原型?这个bean对象是否是懒加载……等等。就笔者看来,BeanDefinition和bean之间的关系有点类似于类和实例的关系,只是BeanDefinition是spring对Java原生的class做了扩展。

在前面学习BeanDefinition章节的时候我们知道,一个BeanDefinition可以不包含class对象,比如下面的abPerson和xiaomi对应的BeanDefinition,spring并不会针对abPerson去构造一个bean对象,abPerson存在的目的仅仅是包装属性让其他BeanDefinition来继承自己。xiaomi对应的BeanDefinition不需要也不会有class对象,因为spring可以通过调用tvFactory这个bean的工厂方法来创建xiaomi的bean对象。所以如果一个BeanDefinition连class对象都没有,那可以肯定这个BeanDefinition一定不是配置类的BeanDefinition,也就没必要加到configCandidates列表。

    <bean id="abPerson" abstract="true" scope="prototype">
<property name="age" value="18"></property>
</bean>
<bean id="sam" class="org.example.beans.Person" parent="abPerson">
<property name="name" value="Sam"></property>
</bean> <bean id="tvFactory" class="org.example.beans.TVFactory"></bean>
<bean id="xiaomi" factory-bean="tvFactory" factory-method="createMi">
</bean>

    

那么,如果一个BeanDefinition有class对象,就能保证它是配置类吗?也不一定。比如像下面的代码,当把MyConfig6注册进应用上下文后,MyConfig6在spring容器一定会有一个与之对应的BeanDefinition,同时aBean也会存在一个BeanDefinition,这两个BeanDefinition的class对象都是MyConfig6,但这并不意味着aBean的BeanDefinition就是配置类的BeanDefinition。如果判定一个BeanDefinition有class对象就是配置类,那么myConfig6和aBean两个BeanDefinition都会加到configCandidates列表,在后续扫描配置类的类路径时,org.example.service路径下的类就会被扫描两次,所以如果一个BeanDefinition的factoryMethodName属性不为null,这个BeanDefinition一定不是配置类的BeanDefinition。

@Configuration(proxyBeanMethods = false)
@ComponentScan("org.example.service")
@ImportResource("spring.xml")
public class MyConfig6 {
public static A getA() {
return new A();
}
}

      

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/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="aBean" class="org.example.config.MyConfig6" factory-method="getA"></bean>
</beans>

  

测试用例:

    @Test
public void test11() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig6.class);
BeanDefinition myConfig5 = ac.getBeanDefinition("myConfig6");
BeanDefinition getA = ac.getBeanDefinition("aBean");
System.out.println("myConfig6 class:" + myConfig5.getBeanClassName());
System.out.println("aBean class:" + getA.getBeanClassName());
System.out.println("aBean factoryMethodName:" + getA.getFactoryMethodName());
}

      

运行结果:

myConfig6 class:org.example.config.MyConfig6
aBean class:org.example.config.MyConfig6
aBean factoryMethodName:getA

  

在判断BeanDefinition包含class对象,且没有工厂方法后,我们还可以接着根据一些基本条件过滤不是配置类的BeanDefinition,比如应用上下文在初始化的时候同样会初始化一个AnnotatedBeanDefinitionReader对象,在初始化AnnotatedBeanDefinitionReader对象时会往spring容器注册一些基础组件的BeanDefinition,这些基础组件或多或少都实现了spring设计的接口,比如用于扫描类路径的ConfigurationClassPostProcessor类就实现了BeanFactoryPostProcessor接口、处理@Autowired和@Inject注解的AutowiredAnnotationBeanPostProcessor类实现了BeanPostProcessor接口、处理@Resource注解的CommonAnnotationBeanPostProcessor类同样实现了BeanPostProcessor接口。所以我们可以规定一些接口,比如:BeanFactoryPostProcessor、BeanPostProcessor……等等,如果一个类实现了这些接口,那么这个类可能不是一个配置类。  

下面我们来看看ConfigurationClassUtils.checkConfigurationClassCandidate(...)方法是如何判定一个BeanDefinition是配置类的BeanDefinition。在这个方法执行之初的<1>处,也是先判定传入的BeanDefinition包含一个class对象,且这个BeanDefinition并不是根据工厂函数来创建bean的,表示这个BeanDefinition有可能是一个配置类的BeanDefinition。

之后会根据BeanDefinition获取元数据,如果传进来的BeanDefinition是配置类,则这个BeanDefinition的真实类型为AnnotatedGenericBeanDefinition,这个类是AnnotatedBeanDefinition的子类,会进入<2>处的分支。如果传进来的BeanDefinition是spring内置的基础组件,比如:ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor……等等,其BeanDefinition的真实类型为RootBeanDefinition,这个类是AbstractBeanDefinition的子类,会进入<3>处的分支,在分支<3>内再次判断是否实现了spring设计的BeanFactoryPostProcessor、BeanPostProcessor、AopInfrastructureBean和EventListenerFactory这几个接口,如果是的话则判断这个BeanDefinition不是一个配置类。如果传进来的BeanDefinition既不是AnnotatedBeanDefinition的子类,也不是AbstractBeanDefinition的子类,则会进入<4>处的分支尝试获取元数据对象,这里可能获取失败,如果失败的话则会抛出异常,告诉外部这个BeanDefinition不是配置类的BeanDefinition。

在获取到元数据后,会通过元数据会判断BeanDefinition对应的类上是否有@Configuration注解,如果配置类上有@Configuration注解,则config不为null。spring对配置类分为两种模式:即<7>处的full和<8>处的lite模式。如果配置类上有@Configuration注解,且注解的proxyBeanMethods属性为true,则会进入分支<5>,在元数据上设置CONFIGURATION_CLASS_ATTRIBUTE(org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass)属性为full,表示这个配置类是full模式的,@Configuration的proxyBeanMethods属性默认为true,所以如果我们不额外设置@Configuration的属性,配置类通常都是full模式的,比如前面MyConfig5就是一个full模式的配置类,至于设置proxyBeanMethods属性有什么效果笔者会在后面讲解。如果配置类上有配置@Configuration注解,但proxyBeanMethods属性为false,则会进入<6>处的分支。或者配置类上根本没有@Configuration注解,config为null,但isConfigurationCandidate(metadata)返回true,同样会进入<6>处的分支,在<6>处的分支内会在元数据上设置CONFIGURATION_CLASS_ATTRIBUTE为lite,表示这个配置类是lite模式的。

那么这个元数据必须满足什么条件才能让isConfigurationCandidate(metadata)返回true?在isConfigurationCandidate(metadata)方法内会先在<9>处判断元数据对应的类是否是一个接口,如果是接口则代表这个类不能成为配置类。之后会在<10>处判断配置类上有@Component、@ComponentScan、@Import、@ImportResource这四个注解,如果有这几个注解就可以成为配置类。如果配置类上都没这几个注解,会在<11>处判断这个类的方法是否有@Bean注解。

总结一下<5>和<6>的判断,如果一个传给应用上下文的类上有@Configuration,那么这个类一定是个配置类,根据proxyBeanMethods属性判断配置类是full模式还是lite模式,如果proxyBeanMethods为true则是full模式的配置类,为false则是lite模式的配置类,默认proxyBeanMethods为true。如果类上没有@Configuration,会接着判断这个类是否有@Component、@ComponentScan、@Import、@ImportResource这四个注解,有的话则是lite模式的配置类,如果没有这四个注解,会再判断这个类的方法上是否有@Bean注解,有的话则是lite模式的配置类,没有的话这个类就不是一个配置类。

再判断一个类不是配置类后会进入<9>处的分支返回,如果一个类是配置类,会在<12>处尝试获取配置类的权重,spring提供了@Order注解来指定配置类的解析顺序,我们可以在配置类上加上@Order注解并指定一个数字作为权重,如果不指定数字的话则会使用默认权重Integer.MAX_VALUE,代码<12>处如果获取到的权重不为null,则会在<13>处将权重存放到元数据内。当spring收集完容器中所有的配置类后会根据权重对这些配置类做一个排序,权重越小的配置类越优先解析。

abstract class ConfigurationClassUtils {

	public static final String CONFIGURATION_CLASS_FULL = "full";//<7>

	public static final String CONFIGURATION_CLASS_LITE = "lite";//<8>

	public static final String CONFIGURATION_CLASS_ATTRIBUTE =
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass"); private static final String ORDER_ATTRIBUTE =
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "order"); private static final Log logger = LogFactory.getLog(ConfigurationClassUtils.class); private static final Set<String> candidateIndicators = new HashSet<>(8); static {
candidateIndicators.add(Component.class.getName());
candidateIndicators.add(ComponentScan.class.getName());
candidateIndicators.add(Import.class.getName());
candidateIndicators.add(ImportResource.class.getName());
} public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {//<1>
return false;
} AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {//<2>
// Can reuse the pre-parsed metadata from the given BeanDefinition...
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {//<3>
// Check already loaded Class if present...
// since we possibly can't even load the class file for this Class.
Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
BeanPostProcessor.class.isAssignableFrom(beanClass) ||
AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
EventListenerFactory.class.isAssignableFrom(beanClass)) {
return false;
}
metadata = AnnotationMetadata.introspect(beanClass);
}
else {//<4>
try {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: " +
className, ex);
}
return false;
}
} Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {//<5>
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
else if (config != null || isConfigurationCandidate(metadata)) {//<6>
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {//<9>
return false;
} // It's a full or lite configuration candidate... Let's determine the order value, if any.
Integer order = getOrder(metadata);//<12>
if (order != null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, order);//<13>
} return true;
} public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
// Do not consider an interface or an annotation...
if (metadata.isInterface()) {//<9>
return false;
} // Any of the typical annotations found?
for (String indicator : candidateIndicators) {//<10>
if (metadata.isAnnotated(indicator)) {
return true;
}
} // Finally, let's look for @Bean methods...
try {
return metadata.hasAnnotatedMethods(Bean.class.getName());//<11>
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
}
return false;
}
} public static Integer getOrder(AnnotationMetadata metadata) {
Map<String, Object> orderAttributes = metadata.getAnnotationAttributes(Order.class.getName());
return (orderAttributes != null ? ((Integer) orderAttributes.get(AnnotationUtils.VALUE)) : null);
} public static int getOrder(BeanDefinition beanDef) {
Integer order = (Integer) beanDef.getAttribute(ORDER_ATTRIBUTE);
return (order != null ? order : Ordered.LOWEST_PRECEDENCE);
} }

  

我们已经知道配置类分两种模式:full和lite,那么这两种模式的区别是什么呢?我们来看下面的MyConfig7和MyConfig8,根据我们现在对spring的了解,可以知道MyConfig7是full模式的配置类,MyConfig8是lite模式的配置类。这两个配置类都有一个用@Bean注解标记的方法,我们在测试用例获取这两个配置类的bean,打印这两个bean的class对象,再调用这两个bean里唯一的方法,看看会有什么效果。

@Configuration
public class MyConfig7 {
@Bean
public A getA() {
return new A();
}
} @Configuration(proxyBeanMethods = false)
public class MyConfig8 {
@Bean
public B getB() {
return new B();
}
}

  

测试用例:

    @Test
public void test12() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig7.class, MyConfig8.class);
MyConfig7 myConfig7 = ac.getBean(MyConfig7.class);
System.out.println("myConfig7 class:" + myConfig7.getClass());
System.out.println("a bean:" + myConfig7.getA());
System.out.println("a bean:" + myConfig7.getA());
MyConfig8 myConfig8 = ac.getBean(MyConfig8.class);
System.out.println("myConfig8 class:" + myConfig8.getClass());
System.out.println("b bean:" + myConfig8.getB());
System.out.println("b bean:" + myConfig8.getB());
}

    

运行结果:

myConfig7 class:class org.example.config.MyConfig7$$EnhancerBySpringCGLIB$$e192874b
a bean:org.example.pojo.A@5fbdfdcf
a bean:org.example.pojo.A@5fbdfdcf
myConfig8 class:class org.example.config.MyConfig8
b bean:org.example.pojo.B@4efc180e
b bean:org.example.pojo.B@bd4dc25

  

从执行结果可以看到,MyConfig7的类对象是一个很奇怪的类对象,同时重复调用MyConfig7的getA()方法获取到的都是同一个对象,这很不符合常理,为什么从spring容器获取MyConfig7的bean对象其对应的类型不是org.example.config.MyConfig7,且重复调用getA()方法应该创建两次A类的实例返回。而proxyBeanMethods属性为false的MyConfig8则符合常理,打印MyConfig8的bean对象其对应的类型是org.example.config.MyConfig8,调用两次getB()也是创建两个不同的B类实例。

MyConfig7之所以与MyConfig8有这样的差别,是因为spring在发现MyConfig7是一个full模式的配置类,会用cglib动态代理技术创建一个class对象作为MyConfig7的子类,并将配置类BeanDefinition的class对象替换成子类的class对象。当spring容器要创建配置类对应的bean对象时,full模式的配置类实际上创建的是代理父类的子类实例。在调用配置类@Bean方法时,这个方法会被子类代理,子类会先检查这个方法产生的bean对象是否已经在spring容器中,如果已经存在于容器中则直接返回容器内的bean对象,否则调用父类的方法构造bean对象,将其存放在容器内后再返回。

Spring源码解析之ConfigurationClassPostProcessor(一)的更多相关文章

  1. Spring源码解析之ConfigurationClassPostProcessor(二)

    上一个章节,笔者向大家介绍了spring是如何来过滤配置类的,下面我们来看看在过滤出配置类后,spring是如何来解析配置类的.首先过滤出来的配置类会存放在configCandidates列表, 在代 ...

  2. Spring源码解析之ConfigurationClassPostProcessor(三)

    在上一章笔者介绍了ConfigurationClassParser.doProcessConfigurationClass(...)方法,在这个方法里调用了processImports(...)方法处 ...

  3. Spring源码解析之BeanFactoryPostProcessor(三)

    在上一章中笔者介绍了refresh()的<1>处是如何获取beanFactory对象,下面我们要来学习refresh()方法的<2>处是如何调用invokeBeanFactor ...

  4. Spring源码解析 - AbstractBeanFactory 实现接口与父类分析

    我们先来看类图吧: 除了BeanFactory这一支的接口,AbstractBeanFactory主要实现了AliasRegistry和SingletonBeanRegistry接口. 这边主要提供了 ...

  5. spring 源码解析

    1. [文件] spring源码.txt ~ 15B     下载(167) ? 1 springн┤┬вио╬Ш: 2. [文件] spring源码分析之AOP.txt ~ 15KB     下载( ...

  6. Spring源码解析——循环依赖的解决方案

    一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...

  7. Spring源码解析-ioc容器的设计

    Spring源码解析-ioc容器的设计 1 IoC容器系列的设计:BeanFactory和ApplicatioContext 在Spring容器中,主要分为两个主要的容器系列,一个是实现BeanFac ...

  8. Spring源码解析系列汇总

    相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...

  9. Spring源码解析之PropertyPlaceholderHelper(占位符解析器)

    Spring源码解析之PropertyPlaceholderHelper(占位符解析器) https://blog.csdn.net/weixin_39471249/article/details/7 ...

随机推荐

  1. Java:Java的~取反运算符详解

    例:   ~15 先变成二进制:15:0000 1111 这个其实挺简单的,就是把1变0,0变1 注意:二进制中,最高位是符号位   1表示负数,0表示正数

  2. Linux:linux下解压*压缩tar.xz、tar、tar.gz、tar.bz2、tar.Z、rar、zip、war等文件方法

    tar -c: 建立压缩档案-x:解压-t:查看内容-r:向压缩归档文件末尾追加文件-u:更新原压缩包中的文件 ------------------------------------------ 这 ...

  3. 安装eclipse及Helloworld体验

    准备工作 如果没有配置java环境变量的请移步:https://www.cnblogs.com/lhns/p/9638105.html 下载eclipse 网址:https://www.eclipse ...

  4. mysql Authentication plugin 'caching_sha2_password' is not supported问题处理

    使用mysql8.0版本,登录失败,提示 Authentication plugin 'caching_sha2_password' is not supported. 原因是在MySQL 8.0以后 ...

  5. 由ctf来看java报错的危害

    很多java报错在我们渗透的时候经常会被发现,但由于没什么用,危害比较低被忽略,开发也很不愿意修改. 但从纵深防御的角度来说,多个小问题的结合就会产生严重的问题.此次遇到的一个ctf题就是一个例子. ...

  6. lua环境搭建

    前言: Linux & Mac上安装 Lua 安装非常简单,只需要下载源码包并在终端解压编译即可,本文介绍Linux 系统上,lua5.3.0版本安装步骤: ↓ 1. Linux 系统上安装 ...

  7. C语言:宏完美定义

    #include <stdio.h> #define M (n*n+3*n) #define M1 n*n+3*n int main(){ int sum, n; printf(" ...

  8. Java程序设计当堂测试 9.20

    /*Java当堂测试 ATM机模拟系统由于学习的知识有限,不能完成所有课上项目,文件的应用没有完成,汇款转账功能也没有写,一些要求该退出的地方也没有写,基本功能还算完善*/ 1 package acc ...

  9. [刘阳Java]_SpringMVC与Struts2的对比_第12讲

    今日来具体给讲讲SpringMVC与Struts2的对比,这样方便朋友们在工作中或者是面试学习中对这两者的区别有个更好的了解 把这张图放在这里,我是想说SpringMVC和Struts2真的是不一样的 ...

  10. 在 Intenseye,为什么我们选择 Linkerd2 作为 Service Mesh 工具(Part.2)

    在我们 service mesh 之旅的第一部分中,我们讨论了"什么是服务网格以及我们为什么选择 Linkerd2?".在第二部分,我们将讨论我们面临的问题以及我们如何解决这些问题 ...