聊聊Spring的bean覆盖(存在同名name/id问题),介绍Spring名称生成策略接口BeanNameGenerator【享学Spring】(中)

简介: 聊聊Spring的bean覆盖(存在同名name/id问题),介绍Spring名称生成策略接口BeanNameGenerator【享学Spring】(中)

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这里就不能再一一例举了,因为组合下来情况太多了,没完没了的。下面会对大家授之以渔,从底层实现上告诉大家,一通则百通~

相关文章
|
1天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
10天前
|
Java 数据库连接 开发者
浅谈Spring的Bean生命周期
浅谈Spring的Bean生命周期
18 1
|
14天前
|
XML Java 数据格式
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
19 0
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
|
24天前
|
XML Java 程序员
作为Java程序员还不知道Spring中Bean创建过程和作用?
作为Java程序员还不知道Spring中Bean创建过程和作用?
15 0
|
29天前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
43 0
|
2月前
|
缓存 Java Maven
Spring Boot自动配置原理
Spring Boot自动配置原理
48 0
|
1月前
|
缓存 安全 Java
Spring Boot 面试题及答案整理,最新面试题
Spring Boot 面试题及答案整理,最新面试题
111 0
|
1月前
|
前端开发 搜索推荐 Java
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
|
17天前
|
前端开发 Java 应用服务中间件
Springboot对MVC、tomcat扩展配置
Springboot对MVC、tomcat扩展配置