User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   Spring   ›   Spring Notes   ›   正文
Spring Notes

Spring—注解驱动开发Spring Ioc容器中注册Bean的7种方式

2020-08-24 22:37:21
1100  0 0
参考目录 隐藏
1) 向Spring IOC容器注册Bean 的7种方式
2) 1、xml方式(老方式,现在使用得非常的少)
3) 2、@Configuration @Bean配置类的方式
4) 3、使用@ComponentScan扫描注册组件
5) 4、@Conditional按照条件向Spring中注册Bean
6) 5、@Improt快速导入一个组件
7) 6、ImportSelector和ImportBeanDefinitionRegistrar
8) 7、使用FactoryBean注册组件
9) 抛出一个问题:为何不直接使用@Bean,而使用FactoryBean呢?

阅读完需:约 11 分钟

Spring是一个非常强大的反转控制(IOC)框架,以帮助分离项目组件之间的依赖关系。因此可以说Spring容器对Bean的注册、管理可以说是它的核心内容,最重要的功能部分。

因此本文主要介绍:向Spring容器注册Bean的多种方式

向Spring IOC容器注册Bean 的7种方式

所有项目建立在SpringBoot2的工程基础上构建(哪怕只用到Spring包,也用此项目构建),pom如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sayabc</groupId>
    <artifactId>boot2-demo1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>boot2-demo1</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
            <version>1.18.4</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>


1、xml方式(老方式,现在使用得非常的少)

在resource类路径创建一个文件:beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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.xsd">

    <bean id="person" class="com.sayabc.boot2demo1.bean.Person">
        <property name="name" value="cuzz"></property>
        <property name="age" value="18"></property>
    </bean>

</beans>

然后main函数采用ClassPathXmlApplicationContext来启动Spring容器容器:

    public static void main(String[] args) {
        ApplicationContext applicationContext = createNewApplicationContext();
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean); //Person(name=fsx, age=18)
    }

    //创建、启动Spring容器
    private static ApplicationContext createNewApplicationContext() {
        return new ClassPathXmlApplicationContext("classpath:beans.xml");
    }

从这便可以看出,这个bean就直接放到Spring容器里面了。


2、@Configuration @Bean配置类的方式

创建一个配置类:

/**
 * @author fangshixiang
 * @description
 * @date 2019-01-30 14:28
 */
@Configuration //该注解就相当于一个xml配置文件
public class MainConfig {

    @Bean(value = "person")
    public Person person() {
        return new Person("fsx", 18);
    }

}

这样我们使用AnnotationConfigApplicationContext来启动容器了:

    //创建、启动Spring容器
    private static ApplicationContext createNewApplicationContext() {
        return new AnnotationConfigApplicationContext(MainConfig.class);
    }

效果同上,同样能向容器中放置一个Bean。

@Bean若不指定value值,bean的id默认为方法名的名称。可以指定init-method,destroy-method方法。但是需要注意:单实例Bean容器是管理bean的init和destroy方法的,但是多实例bean容器只管帮你创建和init,之后Spring就不管了

@Bean相关注解:@Scope、@Lazy等

如果是单实例Bean,IOC容器启动时就立马创建Bean,以后获取都从容器里拿(当然你也可以加上@Lazy这个注解,让单实例Bean也懒加载)。如果是多实例Bean,Bean只有获取的时候,获取一次就创建一次。


3、使用@ComponentScan扫描注册组件

只要标注了注解就能扫描到如:@Controller @Service @Repository @component

配置类中加上这个注解:

@Configuration //该注解就相当于一个xml配置文件
@ComponentScan("com.fsx")
public class MainConfig {

}

实体类上加上一个组件组件,让其能扫描到:

@Component
public class Person {

    private String name;
    private Integer age;

}

启动Spring容器输出可以看到:


    //创建、启动Spring容器
    private static ApplicationContext createNewApplicationContext() {
        return new AnnotationConfigApplicationContext(MainConfig.class);
    }

输出为:
Person(name=null, age=null)

备注:这种扫描的方式,请保证一定要有空构造函数,否则报错的。。。

@ComponentScan有很多属性,可以实现更加精确的扫描。比如:basePackageClasses、includeFilters、excludeFilters、lazyInit、useDefaultFilters等。需要注意的是,要使includeFilters生效,需要useDefaultFilters=false才行,否则默认还是全扫

FilterType枚举的过滤类型,可以实现注解、正则等的精确匹配。当然也能CUSTOM自己实现接口来过滤,功能不可谓不强大


4、@Conditional按照条件向Spring中注册Bean

 /*
 * @author Phillip Webb
 * @author Sam Brannen
 * @since 4.0
 * @see Condition
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition}s that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();

}

这个接口是Spirng4提供出来的。在SpringBoot底层大量的用到了这个接口来按照条件注册Bean。

从注解的属性value来看,我们可以传入Condition条件,因此我们可以传入系统自带的,也可以我们自己去实现这个接口,按照我们的需求来注册Bean

从上图可以看出,SpringBoot工程中对此接口有大量的实现。通过自己的实现,来看看根据条件注册Bean的强大之处。

比如我们要实现如下功能:
如果系统是windows,给容器中注入”bill”,如果系统是linux,给容器中注入”linus”

  public class WindowCondition implements Condition{
  
      /**
       * @param context 判断条件
       * @param metadata 注释信息
       * @return boolean
       */
      @Override
      public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
          Environment environment = context.getEnvironment();
          String property = environment.getProperty("os.name");
          if (property.contains("Windows")) {
              return true;
          }
          return false;
      }
  }

需要注意的是,context还有以下方法:

  // 能获取ioc使用的beanfactory
  ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
  // 能获取到类加载器
  ClassLoader classLoader = context.getClassLoader();
  // 获取到环境变量
  Environment environment = context.getEnvironment();
  // 获取到Bean定义的注册类
  BeanDefinitionRegistry registry = context.getRegistry();

LinuxCondition类的写法略。配置类如下:

@Configuration
public class MainConfig2 {

    @Conditional({WindowCondition.class})
    @Bean("bill")
    public Person person01() {
        return new Person("Bill Gates", 60);
    }
    @Conditional({LinuxCondition.class})
    @Bean("linux")
    public Person person02() {
        return new Person("linus", 45);
    }

}

运行:(测试时候可以设置运行时参数:-Dos.name=linux)

结果我们会发现,注册的Bean已经按照我们的条件去注册了

备注:@Conditonal注解不仅可以标注在方法上,还可以标注在类上。如果标注在配置类上,那么若不生效的话,这个配置类所有就将不会再生效了


5、@Improt快速导入一个组件

@Improt快速导入特别重要,在SpringBoot自动装配的过程中起到了非常关键的作用

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
	 * or regular component classes to import.
	 */
	Class<?>[] value();

}

@Import可以导入第三方包,或则自己写的类,比较方便,Id默认为全类名(这个需要注意)

比如新建一个类Color:

public class Color {
}

配置类上:

@Import({Color.class})
@Configuration
public class MainConfig2 {}

6、ImportSelector和ImportBeanDefinitionRegistrar

ImportSelector:

从注解中的注释中可以看出,import除了导入具体的实体类外,还可以导入实现了指定接口的类。现在我们自己来实现一个,编写一个MyImportSelector类实现ImportSelector接口

public class MyImportSelector implements ImportSelector{

    // 返回值就导入容器组件的全类名
    // AnnotationMetadata:当前类标注的@Import注解类的所有注解信息
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {"com.cuzz.bean.Car"};
    }
}

在配置类中,通过@Import导入

@Import({Color.class, MyImportSelector.class})
@Configuration
public class MainConfig2 {}

这样子我们发现,Car类已经被导入进去了。

ImportBeanDefinitionRegistrar:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * @param importingClassMetadata 当前类的注解信息
     * @param registry 注册类
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 查询容器
        boolean b = registry.containsBeanDefinition("com.cuzz.bean.Car");
        // 如果有car, 注册一个汽油类
        if (b == true) {
            // 需要添加一个bean的定义信息
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Petrol.class);
            // 注册一个bean, 指定bean名
            registry.registerBeanDefinition("petrol", rootBeanDefinition);
        }

    }
}

配置类:

@Import({Color.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
@Configuration
public class MainConfig2 {}

在SpringBoot中的使用,举个栗子:
注解@ServletComponentScan的解析,从下面代码可以看出:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ServletComponentScanRegistrar.class)
public @interface ServletComponentScan {}

是ServletComponentScanRegistrar注册进去和解析的。在看看这个类:

class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar {}

它就是个标准的ImportBeanDefinitionRegistrar 。然后在方法registerBeanDefinitions这里面做了很多事:比如添加注解的后置处理器等等

7、使用FactoryBean注册组件

工厂Bean。此Bean非常的重要,因为第三方框架要和Spring整合,大都是通过实现此接口来实现的。

public interface FactoryBean<T> {
	T getObject() throws Exception;
	Class<?> getObjectType();
	default boolean isSingleton() {
		return true;
	}
}

举个例子,我自己来实现这个Bean接口:

public class ColorFactoryBean implements FactoryBean<Color> {
    // 返回一个Color对象
    @Override
    public Color getObject() throws Exception {
        return new Color();
    }

    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }
    // 是否为单例
    @Override
    public boolean isSingleton() {
        return true;
    }
}

通过@Bean注入到容器里:

@Bean
public ColorFactoryBean colorFactoryBean() {
    return new ColorFactoryBean();
}

测试一下:

@Test
public void test05() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);

    Object bean = applicationContext.getBean("colorFactoryBean");
    // 工厂bean调用的是getClass()方法
    System.out.println("colorFactoryBean的类型是: " + bean.getClass());
}

输出一下,发现此时的bean调用的方法是getObjectType方法输出为:class com.fsx.boot2demo1.bean.Color

如果需要获取FactoryBean本身,可以在id前面加一个“&”标识

Object bean2 = applicationContext.getBean("&colorFactoryBean");

这个时候输出的就是:com.fsx.boot2demo1.bean.ColorFactoryBean

抛出一个问题:为何不直接使用@Bean,而使用FactoryBean呢?

Spring 中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean 即 FactoryBean。FactoryBean跟普通Bean不同,其返回的对象不是指定类的一个实例,而是该FactoryBean的getObject方法所返回的对象。创建出来的对象是否属于单例由isSingleton中的返回决定。

官方解释:
FactoryBean 通常是用来创建比较复杂的bean,一般的bean 直接用xml配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,用xml配置比较困难,这时可以考虑用FactoryBean。

简单的说:它是用来处理复杂的Bean,在初始化过程中需要做很多事情(比如MyBatis的SqlSessionFactoryBean等等),从而屏蔽内部实现,对调用者/使用者友好的一种解决方案。
如果这种Bean用xml去配置,几乎是不可能的。当用注解驱动@Bean去做后,虽然也是可以的,但是很显然对调用很不友好的(因为我们使用MyBatis可不想去知道它初始化到底要做些啥事之类的)。因此使用这个方法是最优雅的解决方案。

Spring提供了非常多的方式来向容器内注册Bean,从而来满足各式各样的需求。每种方式都有他独特的使用场景。比如@Bean是最长使用的,@Import导入Bean在SpringBoot的自动装配中得到了大量的使用。

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

0 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 594454浏览

随机文章
SpringSecurity—OAuth 2(二)授权码模式
5年前
SpringBoot—MyBatis整合多数据源
5年前
SpringSecurity—多种加密方案共存
4年前
MyBatis笔记14—一对多查询
5年前
SpringMVC—Web九大组件之HandlerExceptionResolver异常处理器
3年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1927 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 594454 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付