每篇一句

比你有钱的人一定会比你努力,而比你努力的人终有一天会比你有钱

前言

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*/@Overridepublic 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使用的beanfactoryConfigurableListableBeanFactory 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注解类的所有注解信息@Overridepublic 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 注册类*/@Overridepublic 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对象@Overridepublic Color getObject() throws Exception {return new Color();}@Overridepublic Class<?> getObjectType() {return Color.class;}// 是否为单例@Overridepublic boolean isSingleton() {return true;}
}

通过@Bean注入到容器里:

    @Beanpublic ColorFactoryBean colorFactoryBean() {return new ColorFactoryBean();}

测试一下:

    @Testpublic 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在1.0就支持了这个接口,优秀~

总结

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

一个成熟的框架很忌讳提供封闭的、不全的功能。而Spring在“开闭原则”上显然无疑是做得非常优秀的,值得深入学习


关注A哥

Author A哥(YourBatman)
个人站点 www.yourbatman.cn
E-mail yourbatman@qq.com
微 信 fsx641385712
活跃平台
公众号 BAT的乌托邦(ID:BAT-utopia)
知识星球 BAT的乌托邦
每日文章推荐 每日文章推荐

【小家Spring】Spring注解驱动开发---向Spring Ioc容器中注册Bean的7种方式相关推荐

  1. Spring注解驱动开发第7讲——如何按照条件向Spring容器中注册bean?这次我懂了!!

    写在前面 当bean是单实例,并且没有设置懒加载时,Spring容器启动时,就会实例化bean,并将bean注册到IOC容器中,以后每次从IOC容器中获取bean时,直接返回IOC容器中的bean,而 ...

  2. spring注解驱动开发-5 Spring AOP实现

    Spring AOP实现 前言 AOP案例实现 1.编写目标类 2.编写切面类 3.编写配置类 4.编写测试类 end... 前言 AOP为Aspect Oriented Programming的缩写 ...

  3. spring注解驱动开发-8 Spring 扩展原理

    Spring 扩展原理 前言 BeanFactoryPostProcessor 测试实例编写 ExtConfig MyBeanFactoryPostProcessor ExtTest 源码分析 Bea ...

  4. spring注解驱动开发-4 Spring 自动装配

    Spring 自动装配 前言 Spring 自动装配的几种方式 1.@Autowired @Qualifier("组件id") @Primary 2.@Resource方式 3.@ ...

  5. spring注解驱动开发-7 Spring声明式事务

    Spring 声明式事务 前言 @EnableTransactionManagement AutoProxyRegistrar InfrastructureAdvisorAutoProxyCreato ...

  6. spring注解驱动开发-6 Spring AOP实现原理

    Spring AOP实现原理 前言 1.@EnableAspectJAutoProxy注解原理 2.AnnotationAwareAspectJAutoProxyCreator 分析 1.分析前工作, ...

  7. Spring注解驱动开发第10讲——在@Import注解中使用ImportBeanDefinitionRegistrar向容器中注册bean

    写在前面 在前面的文章中,我们学习了如何使用@Import注解向Spring容器中导入bean,不仅可以使用@Import注解快速向容器中导入bean,也可以在@Import注解中使用ImportSe ...

  8. Spring注解驱动开发第11讲——面试官让我说说:如何使用FactoryBean向Spring容器中注册bean?

    写在前面 经过前面的学习,我们知道可以通过多种方式向Spring容器中注册bean.可以使用@Configuration注解结合@Bean注解向Spring容器中注册bean:可以按照条件向Sprin ...

  9. 【Spring杂烩】探讨Spring向容器注册Bean的三种方式

    探讨Spring向容器注册Bean的三种方式 重点了解@Import实现的三种子方式 前提概要 Spring向容器注册Bean的三种方式 通过@ComponentScan.@Componet 通过@B ...

最新文章

  1. python用print函数打印皮卡丘_Python学习之print()函数
  2. QT mouseMoveEvent事件(qmainwindow内追踪鼠标事件事件)
  3. 科大星云诗社动态20220107
  4. 全国计算机等级考试题库二级C操作题100套(第51套)
  5. 使用 dotnet format 格式化代码
  6. php 未找到驱动程序,Laravel 5 PDOException找不到驱动程序
  7. 计算机检索自考,计算机信息检索02139自考资料(25页)-原创力文档
  8. Android自定义View使用总结
  9. 商丘学院计算机基础,商丘学院
  10. 用户抱怨苹果一体机进灰 苹果称中国环境不好
  11. MapBoxGL 本地离线部署
  12. 适合于图像处理方向的SCI/EI期刊杂志列表
  13. 【OpenCV】人脸旋转角度计算
  14. OccuSeg: Occupancy-aware 3D Instance Segmentation
  15. 【markdown】用markdown制作简历
  16. C语言真的太强大了,C几乎无处不在!
  17. 记录M1Mac基础的Command快捷键
  18. 电子技术——系统性分析反馈电压放大器
  19. 高中计算机会考操作题素材,2021高中信息技术 操作题 (练习二) 精品
  20. if语句不能用连等!!!

热门文章

  1. ActiveMQ-01-MQ概述,安装,入门案例
  2. 用python编写一个简单的计算器
  3. 《最优状态估计-卡尔曼,H∞及非线性滤波》:第12章 H∞滤波器的其他问题
  4. 51单片机项目之OLED显示多功能门禁系统(密码、刷卡、指纹、WIFI、蓝牙等)(一)硬件设计
  5. 山东大学软件学院高级语言课程设计JAVA课程设计-学生在线考试系统平台
  6. Spring Cloud Gateway整合Nacos实现服务路由及集群负载均衡
  7. 电动葫芦的一点总结。。。
  8. 鞋业erp系统主要子系统划分
  9. Tsukinai的第四十九个程序(在字符串每个字符间插入一个空格)
  10. 无论从事什么行业,只要做好两件事就够了,一个是你的专业、一个是你的人品,专业决定了你的存在,人品决定了你的人脉,剩下的就是坚持,用善良專業和真诚赢取更多的信任。不忘初心 方得始终!