聊聊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 万字详解!
925 26
|
9月前
|
SQL Java 数据库
解决Java Spring Boot应用中MyBatis-Plus查询问题的策略。
保持技能更新是侦探的重要素质。定期回顾最佳实践和新技术。比如,定期查看MyBatis-Plus的更新和社区的最佳做法,这样才能不断提升查询效率和性能。
516 1
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
419 12
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
552 12
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
485 6
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
|
Java 开发者 Spring
解析Spring中Bean的生命周期
解析Spring中Bean的生命周期
318 2
|
XML druid Java
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
246 0
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细解析Spring Bean的生命周期及其核心概念,并深入源码分析。Spring Bean是Spring框架的核心,由容器管理其生命周期。从实例化到销毁,共经历十个阶段,包括属性赋值、接口回调、初始化及销毁等。通过剖析`BeanFactory`、`ApplicationContext`等关键接口与类,帮助你深入了解Spring Bean的管理机制。希望本文能助你更好地掌握Spring Bean生命周期。
1369 1
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细介绍了Spring框架中的核心概念——Spring Bean的生命周期,包括实例化、属性赋值、接口回调、初始化、使用及销毁等10个阶段,并深入剖析了相关源码,如`BeanFactory`、`DefaultListableBeanFactory`和`BeanPostProcessor`等关键类与接口。通过理解这些核心组件,读者可以更好地掌握Spring Bean的管理和控制机制。
1723 1

热门文章

最新文章