Springboot
如何将Bean装配到IoC容器中
1.BeanFactory:
在Spring的定义中,它要求所有的IoC容器都需要实现接口BeanFactory
按类型或者名称获取Bean
isSingleton是否单例
isPrototype是否原型
2.ApplicationContext
在BeanFactory的基础上,扩展了消息国际化接口(MessageSource)、环境可配置接口(EnvironmentCapable)、应用事件发布接口(ApplicationEventPublisher)和资源模式解析接口(ResourcePatternResolver),所以它的功能会更为强大。
3.基于注解的IoC容器,AnnotationConfigApplicationContext
将Java配置文件AppConfig传递给AnnotationConfigApplicationContext的构造方法,这样它就能够读取配置了。然后将配置里面的Bean装配到IoC容器中,于是可以使用getBean方法获取对应的POJO。
4.@Component和@ComponentScan。@Component是标明哪个类被扫描进入Spring IoC容器,而@ComponentScan则是标明采用何种策略去扫描装配Bean。
下面展示一些 内联代码片
。
//ComponentScan源码
package org.springframework.context.annotation;
/**imports**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 在一个类中可重复定义
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
// 定义扫描的包
@AliasFor("basePackages")
String[] value() default {};
// 定义扫描的包
@AliasFor("value")
String[] basePackages() default {};
// 定义扫描的类
Class<?>[] basePackageClasses() default {};
// Bean name生成器
Class<? extends BeanNameGenerator> nameGenerator()
default BeanNameGenerator.class;
// 作用域解析器
Class<? extends ScopeMetadataResolver> scopeResolver()
default AnnotationScopeMetadataResolver.class;
// 作用域代理模式
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
// 资源匹配模式
String resourcePattern() default
ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
// 是否启用默认的过滤器
boolean useDefaultFilters() default true;
// 当满足过滤器的条件时扫描
Filter[] includeFilters() default {};
// 当不满足过滤器的条件时扫描
Filter[] excludeFilters() default {};
// 是否延迟初始化
boolean lazyInit() default false;
// 定义过滤器
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
// 过滤器类型,可以按注解类型或者正则式等过滤
FilterType type() default FilterType.ANNOTATION;
// 定义过滤的类
@AliasFor("classes")
Class<?>[] value() default {};
// 定义过滤的类
@AliasFor("value")
Class<?>[] classes() default {};
// 匹配方式
String[] pattern() default {};
}
5.自定义第三方Bean
用@Bean注解
如何进行获取,Bean之间的依赖,在Spring IoC的概念中,我们称为依赖注入(Dependency Injection,DI)。
1.@Autowired
可以用在属性(无参构造器),方法和方法(带有参数的构造方法类)的参数上。
它注入的机制最基本的一条是根据类型(by type),我们回顾IoC容器的顶级接口BeanFactory,就可以知道IoC容器是通过getBean方法获取对应Bean的,而getBean又支持根据类型(by type)或者根据名称(by name)。
@Autowired提供这样的规则,首先它会根据类型找到对应的Bean,如果对应类型的Bean不是唯一的,那么它会根据其属性名称和Bean的名称进行匹配。如果匹配得上,就会使用该Bean;如果还无法匹配,就会抛出异常。
@Autowired是一个默认必须找到对应Bean的注解,如果不能确定其标注属性一定会存在并且允许这个被标注的属性为null,那么你可以配置@Autowired属性required为false,
expected single matching bean but found 2: cat,dog
2.消除歧义性
@Primary和@Quelifier
@Primary的含义告诉Spring IoC容器,当发现有多个同样类型的Bean时,请优先使用我进行注入
有时候@Primary也可以使用在多个类上,也许无论是猫还是狗都可能带上@Primary注解,其结果是IoC容器还是无法区分采用哪个Bean的实例进行注入,又或者说我们需要更加灵活的机制来实现注入,那么@Quelifier可以满足你的这个愿望。它的配置项value需要一个字符串去定义,它将与@Autowired组合在一起,通过类型和名称一起找到Bean。我们知道Bean名称在Spring IoC容器中是唯一的标识,通过这个就可以消除歧义性了。此时你是否想起了BeanFactory接口中的这个方法呢?
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
通过它就能够按照名称和类型的结合找到对象了。下面假设猫已经标注了@Primary,而我们需要的是狗提供服务,因此需要修改BussinessPerson属性animal的标注以适合我们的需要,如下所示:
@Autowired
@Qualifier("dog")
private Animal animal = null;
一旦这样声明,Spring IoC将会以类型和名称去寻找对应的Bean进行注入。根据类型和名称,显然也只能找到狗为我们服务了。
3.带有参数的构造方法类的装配
有些类只有带有参数的构造方法,于是上述的方法都不能再使用了。为了满足这个功能,我们可以使用@Autowired注解对构造方法的参数进行注入。
package com.springboot.chapter3.pojo;
/******** imports ********/
@Component
public class BussinessPerson implements Person {
private Animal animal = null;
public BussinessPerson(@Autowired @Qualifier("dog") Animal animal) {
this.animal = animal;
}
@Override
public void service() {
this.animal.use();
}
@Override
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
代码中取消了@Autowired对属性和方法的标注。注意加粗的代码,在参数上加入了@Autowired和@Qualifier注解,使得它能够注入进来。这里使用@Qualifier是为了避免歧义性。当然,如果你的环境中不是有猫有狗,则可以完全不使用@Qualifier,而单单使用@Autowired就可以了。
生命周期
Bean的生命周期的过程,它大致分为Bean定义、Bean的初始化、Bean的生存期和Bean的销毁4个部分。
Bean定义
-
Spring通过我们的配置,如@ComponentScan定义的扫描路径去找到带有@Component的类,这个过程就是一个资源定位的过程。
-
一旦找到了资源,那么它就开始解析,并且将定义的信息保存起来。注意,此时还没有初始化Bean,也就没有Bean的实例,它有的仅仅是Bean的定义。
-
然后就会把Bean定义发布到Spring IoC容器中。此时,IoC容器也只有Bean的定义,还是没有Bean的实例生成。
ComponentScan中还有一个配置项lazyInit,只可以配置Boolean值,且默认值为false,也就是默认不进行延迟初始化,因此在默认的情况下Spring会对Bean进行实例化和依赖注入对应的属性值。
如图,在没有注释的情况下的流程节点都是针对单个Bean而言的,但是BeanPostProcessor是针对所有Bean而言的,这是我们需要注意的地方。
即使你定义了ApplicationContextAware接口,但是有时候并不会调用,这要根据你的IoC容器来决定。我们知道,Spring IoC容器最低的要求是实现BeanFactory接口,而不是实现ApplicationContext接口。对于那些没有实现ApplicationContext接口的容器,在生命周期对应的ApplicationContextAware定义的方法也是不会被调用的,只有实现了ApplicationContext接口的容器,才会在生命周期调用ApplicationContextAware所定义的setApplicationContext方法。
使用属性文件
在Spring Boot中,我们先在Maven配置文件中加载依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
有了依赖,就可以直接使用application.properties文件为你工作了
配置属性
database.driverName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/chapter3
database.username=root
database.password=123456
会通过其机制读取到上下文中,这样可以引用它了。对于它的引用,有两种方法,首先是用Spring表达式。本节我们只限于读取属性而不涉及运算。关于其运算,后面再谈及
使用属性配置
@Value("${database.driverName}")
private String driverName = null;
@Value("${database.username}")
public void setUsername(String username) {
System.out.println(username);
this.username = username;
}
-
这样我们就可以通过@Value注解,使用${…}这样的占位符读取配置在属性文件的内容。这里的@Value注解,既可以加载属性,也可以加在方法上。
-
value可以配置多个配置文件。使用classpath前缀,意味着去类文件路径下找到属性文件;ignoreResourceNotFound则是是否忽略配置文件找不到的问题。ignoreResourceNotFound的默认值为false,也就是没有找到属性文件,就会报错;这里配置为true,也就是找不到就忽略掉,不会报错。
-
也可以使用注解@ConfigurationProperties,通过它使得配置上有所减少
@Component
@ConfigurationProperties("database")
public class DataBaseProperties {
}
这里在注解@ConfigurationProperties中配置的字符串database,将与POJO的属性名称组成属性的全限定名去配置文件里查找,这样就能将对应的属性读入到POJO当中。
有时候我们会觉得如果把所有的内容都配置到application.properties,显然这个文件将有很多内容。为了更好地配置,我们可以选择使用新的属性文件。例如,数据库的属性可以配置在jdbc.properties中,于是先把代码清单3-22中给出的配置从application.properties中迁移到jdbc.properties中,然后使用@PropertySource去定义对应的属性文件,把它加载到Spring的上下文中,如下:
使用载入属性文件
package com.springboot.chapter3.main;
/******** imports ********/
@SpringBootApplication
@ComponentScan(basePackages = {"com.springboot.chapter3"})
@PropertySource(value={"classpath:jdbc.properties"}, ignoreResourceNotFound=true)
public class Chapter3Application {
public static void main(String[] args) {
SpringApplication.run(Chapter3Application.class, args);
}
}
条件装配Bean
@Conditional注解帮助我们,而它需要配合另外一个接口Condition(org.springframework.context.annotation.Condition)来完成对应的功能。
如果一个类加入了@Conditional注解,并且配置了类DatabaseConditional,那么这个类就必须实现Condition接口。对于Condition接口则要求实现matches方法。
Bean的作用域
在一般的容器中,Bean都会存在单例(Singleton)和原型(Prototype)两种作用域,Java EE广泛地使用在互联网中,而在Web容器中,则存在页面(page)、请求(request)、会话(session)和应用(application)4种作用域。对于页面(page),是针对JSP当前页面的作用域,所以Spring是无法支持的。为了满足各类的作用域,在Spring的作用域中就存在如表3-1所示的几种类型。
使用@Profile
Spring还提供了Profile机制,使我们可以很方便地实现各个环境之间的切换。
在Spring中存在两个参数可以提供给我们配置,以修改启动Profile机制,一个是spring.profiles.active,另一个是spring.profiles.default。在这两个属性都没有配置的情况下,Spring将不会启动Profile机制,这就意味着被@Profile标注的Bean将不会被Spring装配到IoC容器中。Spring是先判定是否存在spring.profiles.active配置后,再去查找spring.profiles.default配置的,所以spring.profiles.active的优先级要大于spring.profiles.default。
引入XML配置Bean
我们也可以在Spring Boot中使用XML对Bean进行配置。这里需要使用的是注解@ImportResource,通过它可以引入对应的XML文件,用以加载Bean。有时候有些框架(如Dubbo)是基于Spring的XML方式进行开发的,这个时候需要引入XML的方式来实现配置。
使用Spring EL
Spring还提供了表达式语言Spring EL。通过Spring EL可以拥有更为强大的运算规则来更好地装配Bean。
最常用的当然是读取属性文件的值,例如:
@Value("${database.driverName}")
String driver
除此之外,它还能够调用方法,例如,我们记录一个Bean的初始化时间:
@Value("#{T(System).currentTimeMillis()}")
private Long initTime = null;
注意,这里采用#{…}代表启用Spring表达式,它将具有运算的功能;T(…)代表的是引入类;System是java.lang.*包的类,这是Java默认加载的包,因此可以不必写全限定名,如果是其他的包,则需要写出全限定名才能引用类;currentTimeMillis是它的静态(static)方法,也就是我们调用一次System.currentTimeMillis()方法来为这个属性赋值。
//使用Spring EL赋值
// 赋值字符串
@Value("#{'使用Spring EL赋值字符串'}")
private String str = null;
// 科学计数法赋值
@Value("#{9.3E3}")
private double d;
// 赋值浮点数
@Value("#{3.14}")
private float pi;
使用Spring EL进行计算
#数学运算
@Value("#{1+2}")
private int run;
#浮点数比较运算
@Value("#{beanName.pi == 3.14f}")
private boolean piFlag;
#字符串比较运算
@Value("#{beanName.str eq 'Spring Boot'}")
private boolean strFlag;
#字符串连接
@Value("#{beanName.str + ' 连接字符串'}")
private String strApp = null;
#三元运算
@Value("#{beanName.d > 1000 ? '大于' : '小于'}")
private String resultDesc = null;