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

相关文章
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
850 26
|
8月前
|
SQL Java 数据库
解决Java Spring Boot应用中MyBatis-Plus查询问题的策略。
保持技能更新是侦探的重要素质。定期回顾最佳实践和新技术。比如,定期查看MyBatis-Plus的更新和社区的最佳做法,这样才能不断提升查询效率和性能。
345 1
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
379 12
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
526 12
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
436 6
|
安全 Java Spring
spring接口是否是线程安全
spring接口是否是线程安全
244 0
spring接口是否是线程安全
|
7月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1143 0
|
8月前
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
911 0
|
4月前
|
JavaScript Java Maven
【SpringBoot(二)】带你认识Yaml配置文件类型、SpringMVC的资源访问路径 和 静态资源配置的原理!
SpringBoot专栏第二章,从本章开始正式进入SpringBoot的WEB阶段开发,本章先带你认识yaml配置文件和资源的路径配置原理,以方便在后面的文章中打下基础
443 3
|
4月前
|
Java 测试技术 数据库连接
【SpringBoot(四)】还不懂文件上传?JUnit使用?本文带你了解SpringBoot的文件上传、异常处理、组件注入等知识!并且带你领悟JUnit单元测试的使用!
Spring专栏第四章,本文带你上手 SpringBoot 的文件上传、异常处理、组件注入等功能 并且为你演示Junit5的基础上手体验
961 2