• Spring Boot 2.x系列【13】功能篇之声明Bean的八种方式


    有道无术,术尚可求,有术无道,止于术。

    本系列Spring Boot版本2.7.0

    前言

    在 Spring 中,Bean是由Spring容器实例化、组装和管理的对象,是构成应用程序的组件。

    为了更好的使用和理解Spring Boot ,所以接下来我们汇总学习下Spring 提供声明Bean的多种方式。

    方式1: XML文件

    在初期Spring 1.0 阶段,只提供了XML 文件方式声明Bean ,需要在XML 文件中使用各种标签定义一个Bean,比如:

    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:p="http://www.springframework.org/schema/p"
           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-4.0.xsd">
    
         <bean id="" class="">......bean>
         <bean id="" class="">......bean>
    beans> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    但是随着生态发展,这种繁琐的方式已经没人再用了,所以也无需深入了解。

    方式2: @Component注解

    Spring 2.x -3.x 版本,开始向基于注解开发的方向发展,并逐步趋于完善,出现了一大波注解。

    Spring 2.5 中除了提供 @Component注解,可标记在类上,然后通过配置包扫描将当前使用了该注解的类注册到容器中,这种方式非常适合注册自己项目中的Bean 对象。

    该注解只有一个属性值,用于配置Bean 名称,如果没有配置,默认使用当前类名称首字母小写的驼峰格式。下方实例代码中,Bean 名称为animalTest

    @Component
    public class AnimalTest {
        private String name = "小修勾";
    }
    
    • 1
    • 2
    • 3
    • 4

    此外还提供了@Repository@Service@Controller三个派生注解,分别和持久层、业务层和控制层相对应。

    方式3: @Bean注解

    @Component可以在自己项目中很方便的标识在类上,但是如果类来源于第三方JAR 包,这个时候就不适用了,所以Spring 提供了@Bean注解实现另外一种Bean 注册的方式。

    该注解标记在方法上,并且需要声明在使用了组件注册注解的类上,默认Bean 的名称为当前方法名。

    @Configuration
    public class Config {
        @Bean
        public AnimalTest animalTest() {
            return new AnimalTest();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    @Bean注解源码如下:

    @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Bean {
    	// 声明Bean 的名称
        @AliasFor("name")
        String[] value() default {};
        @AliasFor("value")
        String[] name() default {};
        /** @deprecated */
        @Deprecated
        // 自动装配的类型
        Autowire autowire() default Autowire.NO;
    	// 配置文件bean标签的autowireCandidate属性一样,就是让其他的bean在按照类型注入时,忽略当前的bean。 默认值true。
        boolean autowireCandidate() default true;
    	// bean的初始化之前的执行方法
        String initMethod() default "";
    	// bean销毁时执行方法
        String destroyMethod() default "(inferred)";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    方式4: @ImportResource注解

    @ImportResource注解用于将xml配置文件中定义的 bean 对象加载到Spring容器中。

    @ImportResource(locations = {"classpath:beans.xml"})
    
    • 1

    方式5: @Configuration注解

    为了替换xml配置文件,Spring 提供了@Configuration注解,被注解的类内部包含有一个或多个被@Bean注解的方法,实现多个Bean 的注册。

    @Configuration注解本身也包含了一个@Component,所以也可以直接使用它进行Bean 注册:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface Configuration {
        @AliasFor(
            annotation = Component.class
        )
        // Bean 的名称
        String value() default "";
        boolean proxyBeanMethods() default true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    该注解有一个proxyBeanMethods属性,默认为true,表示开启代理,注册该配置Bean 时,使用CGLIB 代理生成对象,直接使用该对象调用被@Bean注解标识的方法时,实际直接返回一个注册了的Bean 对象,而不是执行该方法返回的对象。

    下方案例中,proxyBeanMethods设置为false,则调用user()方法时,就像执行普通方法一样,每次都会new 一个新的对象返回:
    在这里插入图片描述

    方式6: FactoryBean接口

    FactoryBean接口是一个能生产或修饰对象生成的工厂Bean,首先它也是一个Bean,然后它也是一个工厂,用于生成自定义方式创建的Bean。

    如果使用注解注册Bean ,Spring 通过反射机制来实例化一个对象并放入到容器中,获取时直接返回实例对象,这种方式不够灵活,所以提供了FactoryBean接口,当获取一个Bean 对象时,实际是调用其getObject()方法返回的实例对象。

    该接口源码如下:

    public interface FactoryBean<T> {
        String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
    	// 返回一个对象,该对象会被加载到容器中
        @Nullable
        T getObject() throws Exception;
    	// 对象的类型
        @Nullable
        Class<?> getObjectType();
    	// 是否单例模式
        default boolean isSingleton() {
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    首先我们实现一个FactoryBean接口:

    @Component("myFactoryBean")
    public class MyFactoryBean implements FactoryBean<User> {
        public User getObject() throws Exception {
            // 可以自定义很多操作
            return new User();
        }
    
        public Class<?> getObjectType() {
            return User.class;
        }
    
        public boolean isSingleton() {
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    然后在容器中通过名称获取MyFactoryBean 时,实际获取到的是其getObject()返回的对象:

            ConfigurableApplicationContext run = springApplication.run(args);
            Object myFactoryBean = run.getBean("myFactoryBean"); // 实际获取到的是 User 实例        
    
    • 1
    • 2

    如果想到获取MyFactoryBean ,需要加一个&前缀:

            Object myFactoryBean = run.getBean("&myFactoryBean");
    
    • 1

    这种方式,可以根据设置工厂Bean ,返回不同的Bean 实例,更具有扩展性。

    方式7: @Import注解

    Spring 提供了@Import 注解直接将类导入到容器中,此形式可以有效的降低源代码与Spring技术的耦合度,在spring技术底层及诸多框架的整合中大量使用。

    导入普通类

    可以使用@Import 直接导入一个普通类,自动生成的名称为全路径类名org.pearl.app.study.User

    @Configuration
    @Import(User.class)
    public class Config {
    }
    
    • 1
    • 2
    • 3
    • 4

    导入ImportSelector接口

    可以使用@Import 导入一个实现了ImportSelector 接口的实现类,该接口实现了一个选择器的功能,可以获取@Import 注解所在类的元数据信息,比如类名、类标识的注解等,然后根据这些信息,自定义返回需要注入的Bean 对象。

    public class MyImportSelector implements ImportSelector {
        /**
         * 选择导入某些类
         *
         * @param importingClassMetadata 添加了@Import 注解类的元数据信息
         * @return 需要注册的 Bean 的全路径类名
         */
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            Set<String> annotationTypes = importingClassMetadata.getAnnotationTypes();
            return new String[]{"org.pearl.app.study.User"};
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Spring 相关框架,很多以@Enable开头的注解,都是使用该机制实现相关Bean 的注册,比如@EnableDiscoveryClient注解开启自动服务注册,该注解就是导入了一个EnableDiscoveryClientImportSelector选择器,根据注解的配置项注册不同的Bean。

    导入ImportBeanDefinitionRegistrar接口

    ImportBeanDefinitionRegistrar提供了通过AnnotationMetadataBeanDefinitionRegistry动态创建Bean 的功能,比ImportSelector 更高级。

    在注册Bean 的时候,可以更小粒度的动态配置:

    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            // 1. 获取AnnotationMetadata,根据配置走自定义逻辑
            // 2. 通过BeanDefinitionRegistry 创建Bean
            // 2.1 创建BeanDefinition
            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
            // 2.2 自定义配置
            beanDefinition.setLazyInit(false);
            // 2.3 注册
            registry.registerBeanDefinition("name",beanDefinition);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在Spring Boot 启动类注解@SpringBootApplication中,就使用该接口导入了一个自动扫描包的Bean:

        static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
            public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
                AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
            }
    
            public Set<Object> determineImports(AnnotationMetadata metadata) {
                return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    方式8: 上下文注册Bean

    GenericApplicationContext提供了直接注册Bean 的方法:

        public <T> void registerBean(@Nullable String beanName, Class<T> beanClass, @Nullable Supplier<T> supplier, BeanDefinitionCustomizer... customizers) {
            this.reader.registerBean(beanClass, beanName, supplier, customizers);
        }
    
    • 1
    • 2
    • 3
  • 相关阅读:
    uboot源码——C阶段的start_armboot函数
    LeetCode|动态规划|392. 判断子序列、115. 不同的子序列、 583. 两个字符串的删除操作
    pinia的使用
    rh358 004 bind反向,转发,主从,各种资源记录 unbound ansible部署bind unbound
    C++ 哈希
    软件测试适合女生吗?我是一名文员、不甘心着平凡的生活!!
    Kafka详解(一)
    Android 桌面小组件 AppWidgetProvider
    4.5每日一题(多元函数比较大小通过偏积分)
    【7】Docker中部署RabbitMQ
  • 原文地址:https://blog.csdn.net/qq_43437874/article/details/125838774