ClassPathBeanDefinitionScanner
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator(); }
ConfigurationClassPostProcessor
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware { private BeanNameGenerator componentScanBeanNameGenerator = new AnnotationBeanNameGenerator(); }
同名name覆盖的case演示
由于Spring
给我们提供了非常多的方式来定义Bean,所以势必会出现同名Bean的情况,下面举两个例子来感受一把:
case1:同一个配置文件内出现同名Bean
@Configuration public class RootConfig { @Bean("personBean") public Person person() { return new Person("personBean", 18); } @Bean("personBean") public Person person2() { return new Person("personBean----2222", 18); } }
使用@Bean若不指定value值,默认是方法名。但因为同一个类内方法名不能一样(不考虑重载情况),所以此处用手工指定同一个value值模拟
运行测试:
public static void main(String[] args) throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext(RootConfig.class); Object personBean = context.getBean("personBean"); System.out.println(personBean); } // 结果打印:Person{name='personBean', age=18}
调换Person定义的上下的位置(调换两个Bean上下的位置)
@Configuration public class RootConfig { @Bean("personBean") public Person person2() { return new Person("personBean----2222", 18); } // 注意:此处我使用的类型是Child,但是beanName没变 @Bean("personBean") public Child person() { return new Child(); } } // 结果打印:Person{name='personBean----2222', age=18}
得出结论:同一个配置文件内同名的Bean
,以最上面定义的为准
case2:不同配置文件内出现同名Bean
@Configuration public class RootConfig { @Bean("personBean") public Person person() { return new Person("RootConfig----Bean", 18); } } @Configuration public class TempConfig { @Bean("personBean") public Person person() { return new Person("TempConfig----Bean", 18); } }
测试:
public static void main(String[] args) throws Exception { // 注意此处传值的顺序是先rootConfig 在tempConfig ApplicationContext context = new AnnotationConfigApplicationContext(RootConfig.class, TempConfig.class); Object personBean = context.getBean("personBean"); System.out.println(personBean); } // 结果打印:Person{name='TempConfig----Bean', age=18}
交换顺序:先TempConfig.class,再RootConfig.class(代码略),再运行测试:
// 结果打印:Person{name='RootConfig----Bean', age=18}
得出结论:不同配置文件中存在同名Bean,后解析的配置文件会覆盖先解析的配置文件。
此处需要注意的是:配置文件的先后顺序其实会受到@Order来控制,只是若没有@Order注解的话就按照传入的顺序执行解析。
比如上例中传入顺序是TempConfig,再RootConfig,倘若在RootConfig头上加注解@Order(1)那么它就会被先解析,所以这时候最终结果是TempConfig配置类上的Bean生效,输出:Person{name='TempConfig----Bean', age=18}
关于@Configuration配置文件的解析,请参考:【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser)
里面有对本文非常理解非常中重要的Bean定义的解析、注册顺序说明
case3:同文件中ComponentScan和@Bean出现同名Bean
@ComponentScan( ... ) @Configuration public class RootConfig { @Bean("personBean") public Person person() { return new Person("RootConfig----Bean", 18); } } @Service("personBean") public class B implements BInterface { ... }
测试:
public static void main(String[] args) throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext(RootConfig.class); Object personBean = context.getBean("personBean"); System.out.println(personBean); } // 打印结果:Person{name='RootConfig----Bean', age=18}
得出结论:同文件下@Bean的会生效,@ComponentScan扫描进来不会生效
最后你会发现:通过@ComponentScan扫描进来的优先级是最低的,原因就是它扫描进来的Bean定义是最先被注册的~
上面case都是描绘的在同一个Spring容器的情况下出现同名的情况,那么若在不同的容器内出现同名Bean呢?
看如下例子:
@Configuration public class RootConfig { @Bean public Person person() { Person person = new Person(); person.setName("parent application"); return person; } } @Configuration @EnableWebMvc public class WebMvcConfig extends WebMvcConfigurerAdapter { @Bean public Person person() { Person person = new Person(); person.setName("child application"); return person; } }
测试示例:
@Service public class HelloServiceImpl implements HelloService { @Autowired private Person person; @Override public Person getPerson() { return person; } } @RestController @RequestMapping public class HelloController { @Autowired private ApplicationContext applicationContext; @Autowired private HelloService helloService; @Autowired private Person person; @GetMapping("/getPerson") public Object getPerson() { System.out.println(person); System.out.println(helloService.getPerson()); //从父子容器中获取 System.out.println(applicationContext.getBean(Person.class)); System.out.println(applicationContext.getParent().getBean(Person.class)); return "hello world"; } }
请求打印如下:
Person(name=child application, age=null, child=null) Person(name=parent application, age=null, child=null) Person(name=child application, age=null, child=null) Person(name=parent application, age=null, child=null)
可见如果在不同容器内,即使Bean名称相同,它们也是能够和谐共存的。(@Autowired的时候请注意父子容器问题~)
原因简单解释:每个DefaultListableBeanFactory都持有自己的beanDefinitionNames,然后每个容器在初始化refresh()的时候就是按照这个一个一个进行实例化的,所以不同容器之间即使出现同名的BeanName,也不会互相干扰。它的getBean()基本逻辑是:如果本容器自己有,就不会去父容器里寻觅~
此书提示一点,在有些组件比如BeanPostProcessor需要提前初始化的时候,会调用此方法:
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
获取到容器内所有的BeanPostProcessor名称后,挨个getBean()。此处需要注意一点:所有的getBeanNamesForType()方法默认都是不会去父容器里查找的,并且大多数情况下只会处理top-level的类。因此:你若在父容器里定义了一个BeanPostProcessor处理器,它对子容器是不生效的哦~~~
注意:SpringBoot下因为主容器没有主次之分,粗暴可以理解成它只存在一个容器,因此一般都不会存在此类问题
…
…
…
其它case这里就不能再一一例举了,因为组合下来情况太多了,没完没了的。下面会对大家授之以渔
,从底层实现上告诉大家,一通则百通~