参考
B站颜群老师Spring教程,B站雷丰阳老师Spring教程,《Spring实战》
https://zhuanlan.zhihu.com/p/137507309
https://liayun.blog.csdn.net/article/details/115053350
容器
属性赋值
在之前探讨了如何向Spring的IOC容器中注册bean组件,并且还讲解了有
关bean组件的生命周期的知识。接下来学习一下@Value注解的用法
@Value
Spring中的@Value注解可以为bean中的属性赋值
1 | ({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, |
@Value注解可以标注在字段、方法、参数以及注解上,而且在程序运行期间生效
@Value注解的用法
第一种是不通过配置文件注入属性的情况,通过@Value注解将外部的
值动态注入到bean的属性中
- 注入普通字符串
1
2"天海") (
private String name; // 注入普通字符串 - 注入操作系统属性
1
2"#{systemProperties['os.name']}") (
private String systemPropertiesName; // 注入操作系统属性 - 注入其他bean中属性的值
1
2"#{person.name}") (
private String username; // 注入其他bean中属性的值,即注入person对象的name属性中的值 - 注入文件资源
1
2"classpath:/config.properties") (
private Resource resourceFile; // 注入文件资源 - 注入URL资源
1
2"http://www.baidu.com") (
private Resource url; // 注入URL资源
第二种是通过配置文件注入属性的情况,在项目的src/main/resources
目录下新建一个属性文件,例如person.properties
1 | 天海 = |
新建一个MainConfigOfPropertyValues配置类,并在该类上使用
@PropertySource注解读取外部配置文件中的key/value并保存到
运行的环境变量中
1 | "classpath:/person.properties"}) (value={ |
加载完外部的配置文件以后,接着我们就可以使用${key}取出配置文件
中key所对应的值,并将其注入到bean的属性中了
1 | public class Person { |
@Value中#{···}和${···}的区别
在这里提供一个测试属性文件advance_value_inject.properties
1 | server1,server2,server3 = |
新建一个AdvanceValueInject类,并在该类上使用@PropertySource
注解读取外部属性文件中的key/value并保存到运行的环境变量中,即
加载外部的advance_value_inject.properties属性文件
1 |
|
- ${···}的用法
{}里面的内容必须符合SpEL表达式,通过@Value(“${spelDefault.
value}”)我们可以获取属性文件中对应的值,但是如果属性文件中
没有这个属性,那么就会报错。不过,我们可以通过赋予默认值来
解决这个问题上述代码的含义是表示向bean的属性中注入属性文件中的author.name1
2"${author.name:meimeixia}") (
private String name;
属性所对应的值,如果属性文件中没有author.name这个属性,那么便
向bean的属性中注入默认值meimeixia - #{···}的用法
{}里面的内容同样也是必须符合SpEL表达式1
2
3
4
5
6
7// SpEL:调用字符串Hello World的concat方法
"#{'Hello World'.concat('!')}") (
private String helloWorld;
// SpEL:调用字符串的getBytes方法,然后再调用其length属性
"#{'Hello World'.bytes.length}") (
private String helloWorldBytes;
${···}和#{···}可以混合使用,通过${server.name}从属性文件中获
取值并进行替换,然后就变成了执行SpEL表达式{‘server1,server2,
server3’.split(‘,’)}
1 | /* SpEL:传入一个字符串,根据","切分后插入列表中, |
必须#{}在外面,${}在里面,因为Spring执行${}的时机要早于#{},
当Spring执行外层的${}时,内部的#{}为空,所以会执行失败。小结
- #{···}:用于执行SpEl表达式,并将内容赋值给属性
- ${···}:主要用于加载外部属性文件中的值
加载配置文件
@PropertySource注解概述
@PropertySource注解是Spring 3.1c 开始引入的配置类注解。通过
@PropertySource注解可以将properties 配置文件中的key/value
存储到Spring的Environment中,Environment接口提供了方法去读
取配置文件中的值,参数是properties配置文件中定义的key值。也
可以使用@Value注解用${}占位符为bean的属性注入值
1 | ({ElementType.TYPE}) |
可以通过@PropertySource注解指定多个properties文件
1 | "classpath:/person.properties", (value={ |
@PropertySources注解概述
也可以使用@PropertySources注解来指定properties配置文件
1 | ({ElementType.TYPE}) |
指定配置文件
1 | (value={ |
使用Environment获取值
使用@PropertySource注解读取外部配置文件中的key/value之后,是将
其保存到运行的环境变量中了,所以我们也可以通过运行环境来获取外部
配置文件中的值
1 |
|
自动装配组件
@Autowired注解
@Autowired注解可以对类成员变量、方法和构造函数进行标注,完
成自动装配的工作。@Autowired注解可以放在类、接口以及方法上
1 | ({ElementType.CONSTRUCTOR, ElementType.METHOD, |
@Autowired注解默认是优先按照类型去容器中找对应的组件,相当于
是调用了如下这个方法
1 | applicationContext.getBean(类名.class); |
如果找到多个相同类型的组件,那么是将属性名称作为组件的id,到IOC
容器中进行查找,这时就相当于是调用了如下这个方法
1 | applicationContext.getBean("组件的id"); |
@Qualifier注解
@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,那
么就需要配合@Qualifier注解来使用了
1 | ({ElementType.FIELD, ElementType.METHOD, |
@Primary注解
在Spring中使用注解时,常常会使用到@Autowired这个注解,它默认是
根据类型Type来自动注入的。但有些特殊情况,对同一个接口而言,可能
会有几种不同的实现类,而在默认只会采取其中一种实现的情况下,就
可以使用@Primary注解来标注优先使用哪一个实现类
1 | ({ElementType.TYPE, ElementType.METHOD}) |
自动装配
Spring组件的自动装配就是Spring利用依赖注入,也就是我们通常所说的
DI,完成对IOC容器中各个组件的依赖关系赋值。
创建的BookDao、BookService和BookController为例进行说明
1 | // 名字默认是类名首字母小写 |
BookService
1 |
|
BookController
1 |
|
测试在BookService类中使用@Autowired注解注入的BookDao(最后
输出了该BookDao的信息),和我们直接在Spring IOC容器中获取的
BookDao是不是同一个对象呢
1 | public class IOCTest_Autowired { |
如果在Spring容器中存在对多个BookDao对象,对BookDao类进行
改造,为其加上一个lable字段
1 | // 名字默认是类名首字母小写 |
在MainConfigOfAutowired配置类中注入一个BookDao对象,并且显
示指定该对象在IOC容器中的bean的名称为bookDao2,并还为该对象
的lable字段赋值为2
1 |
|
在我们的IOC容器中就会注入两个BookDao对象,@Autowired注解默认
是优先按照类型去容器中找对应的组件,找到就赋值,如果找到多个相
同类型的组件,那么再将属性的名称作为组件的id,到IOC容器中进行
查找。如果让@Autowired注解装配bookDao2,只须将BookService类
中的bookDao属性的名称全部修改为bookDao2即可
测试@Qualifier注解
如果IOC容器中存在多个相同类型的组件时,想要显示指定@Autowired注
解装配哪个组件,就可以使用@Qualifier注解
1 |
|
如果IOC容器中无相应的组件那么就会抛出异常,如果不希望抛出异
常在@Autowired注解里面添加一个属性required=false
1 |
|
测试@Primary注解
在Spring中,对同一个接口而言,可能会有几种不同的实现类,而默认
只会采取其中一种实现的情况下,就可以使用@Primary注解来标注优先
使用哪一个实现类。如果IOC容器中相同类型的组件有多个,那么我们不
可避免地就要来回用@Qualifier 注解来指定要装配哪个组件,这还是
比较麻烦的,Spring 正是帮我们考虑到了这样一种情况,就提供了这
样一个比较强大的注解,即@Primary。可以利用这个注解让Spring进
行自动装配的时候,默认使用首选的bean
1 |
|
注意需要注释掉BookService类中bookDao字段上的@Qualifier注解,
这是因为@Qualifier注解为显示指定装配哪个组件,如果使用了该注
解,无论是否使用了@Primary注解,都会装配@Qualifier注解标注
的对象
1 |
|
@Resource注解
@Resource注解是Java规范里面的,也可以说它是JSR250规范里面定义
的一个注解。该注解默认按照名称进行装配,名称可以通过name 属性进
行指定,如果没有指定name属性,当注解写在字段上时,那么默认取字
段名将其作为组件的名称在IOC容器中进行查找,如果注解写在setter
方法上,那么默认取属性名进行装配。当找不到与名称匹配的bean时才
按照类型进行装配。但是需要注意的一点是,如果name属性一旦指定
,那么就只会按照名称进行装配
1 | ({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) |
@Inject注解
@Inject注解也是Java规范里面的,也可以说它是JSR330规范里面定义
的一个注解。该注解默认是根据参数名去寻找bean注入,支持Spring的
@Primary注解优先注入,@Inject 注解还可以增加@Named注解指定要
注入的bean。要想使用@Inject注解,需要在项目的pom.xml文件中添
加如下依赖
1 | <dependency> |
可以查看源码
1 | ({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) |
测试@Resource注解
使用@Resource 注解也能够自动装配组件,只不过此时自动装配的
是lable为1的bookDao,不支持@Primary 注解优先注入的功能的
,而且也不能像@Autowired注解一样能添加required=false属性
1 |
|
可以通过@Resource注解的name属性显示指定要装配的组件的名称
1 | "bookDao2") (name= |
测试@Inject注解
用@Inject注解默认输出的是lable为2的bookDao。这是因为@Inject
注解和@Autowired注解一样,默认优先装配使用了@Primary 注解标
注的组件,不能像@Autowired注解一样能添加required=false属性
1 |
|
三个注解之间的区别
三种注解都可以实现bean的自动装配。不同点如下
- @Autowired是Spring中的专有注解,而@Resource是Java中JSR250
规范里面定义的一个注解,@Inject是Java中JSR330规范里面定义的一
个注解 - @Autowired支持参数required=false,而@Resource和@Inject都
不支持 - @Autowired和@Inject支持@Primary注解优先注入,而@Resource
不支持 - @Autowired通过@Qualifier指定注入特定bean,@Resource可以通
过参数name指定注入bean,而@Inject需要通过@Named注解指定注入
bean
方法、构造器位置的自动装配
@Autowired注解不仅可以标注在字段上,而且还可以标注在构造方法、
实例方法以及参数上,新建一个Boss类,在Boss类中有一个Car类的引
用,之前讲过可以在car字段上添加@Autowired注解,使其自动装配
1 | //默认加在IOC容器中的组件,容器启动会调无参构造器创建对象,然后再进行初始化、赋值等操作 |
标注在实例方法上
可以将@Autowired注解标注在setter方法上
1 |
|
当@Autowired注解标注在方法上时,Spring容器在创建当前对象的时
候,就会调用相应的方法为对象赋值。如果标注的方法存在参数时,那
么方法使用的参数和自定义类型的值,需要从IOC容器中获取
标注在构造方法上
为Boss类添加一个有参构造方法,然后去除setCar()方法上的@Autowired
注解,将@Autowired注解标注在有参构造方法上
1 |
|
使用@Autowired注解标注在构造方法上时,如果组件中只有一个有参
构造方法,那么这个有参构造方法上的@Autowired注解可以省略,并
且参数位置的组件还是可以自动从IOC容器中获取
标注在参数上
我们也可以将@Autowired注解标注在参数上,例如,在Boss类中我们
将构造方法上的@Autowired注解标注在构造方法的参数上
1 | public Boss(@Autowired Car car) { |
也可以将@Autowired注解标注在setter方法的参数上
1 | public void setCar(@Autowired Car car) { |
无论@Autowired注解是标注在字段上、实例方法上、构造方法上还是参
数上,参数位置的组件都是从IOC容器中获取
标注在方法位置
@Autowired注解可以标注在某个方法的位置上,新建一个Color类,在
Color类中有一个Car类型的成员变量
1 | public class Color { |
MainConfigOfAutowired配置类中实例化Color类,此时的Color对
象中的Car对象为空
1 |
|
可以将Car对象作为一个参数传递到MainConfigOfAutowired配置类的
color()方法中,并且将该Car对象设置到Color对象中
1 |
|
也可以使用@Autowired注解来标注color()方法中的car参数
1 |
|
自定义组件
自定义组件要想使用Spring容器底层的一些组件,ApplicationContext
(IOC容器)、底层的BeanFactory 等等,那么只需要让自定义组件实现
XxxAware接口即可。此时,Spring在创建对象的时候,会调用XxxAware
接口中定义的方法注入相关的组件
XxxAware接口概览
之前创建的Dog 类,就实现了ApplicationContextAware 接口,实现
该接口的话,需要实现setApplicationContext() 方法。在IOC容器启
动并创建Dog 对象时,Spring会调用setApplicationContext()方法
,并且会将 ApplicationContext 对象传入到该方法中,我们只需要
在Dog 类中定义一个ApplicationContext 类型的成员变量来接收该
方法中的参数,便可在Dog类的其他方法中使用ApplicationContext
对象了
1 | public interface Aware { |
Spring中形如XxxAware这样的接口都继承了Aware接口
XxxAware接口案例
- 通过ApplicationContextAware接口我们可以获取到IOC容器
- 通过BeanNameAware接口获取到当前bean在Spring容器中的名称
- 通过EmbeddedValueResolverAware接口能够获取到String值解析器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class Red implements ApplicationContextAware,
BeanNameAware, EmbeddedValueResolverAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext
applicationContext) throws BeansException {
System.out.println("传入的IOC:" + applicationContext);
this.applicationContext = applicationContext;
}
/**
* 参数name:IOC容器创建当前对象时,为这个对象起的名字
*/
public void setBeanName(String name) {
System.out.println("当前bean的名字:" + name);
}
/*
参数resolver:IOC容器启动时会自动地将这个String值的解析器传递
过来给我们如果这个String类型的值里面有一些占位符,那么也会帮我
们把这些占位符给解析出来,最后返回一个解析后的值
*/
public void setEmbeddedValueResolver(StringValueResolver
resolver) {
String resolveStringValue = resolver.resolveStringValue(
"你好,${os.name},我的年龄是#{20*18}");
System.out.println("解析的字符串:" + resolveStringValue);
}
}
XxxAware原理
XxxAware接口的底层原理是由XxxAwareProcessor实现类实现的
,每一个XxxAware 接口都有它自己对应的XxxAwareProcessor
实现类。例如ApplicationContextAware 接口的底层原理就是
由ApplicationContextAwareProcessor类实现的,其实现了
BeanPostProcessor接口,本质上是一个后置处理器
1 | class ApplicationContextAwareProcessor implements BeanPostProcessor { |
@Profile注解
在实际的企业开发环境中,往往都会将环境分为开发环境、测试环
境和生产环境, 并且每个环境基本上都是互相隔离的,也就是说
,开发环境、测试环境和生产环境它们之间是互不相通的。在以前
的开发过程中,如果开发人员完成相应的功能模块并通过单元测
试后,那么他会通过手动修改配置文件的形式,将项目的配置修
改成测试环境,发布到测试环境中进行测试。测试通过后,再将
配置修改为生产环境,发布到生产环境中。这样手动修改配置的
方式,不仅增加了开发和运维的工作量,而且总是手工修改各
项配置文件会很容易出问题
@Profile注解概述
在容器中如果存在同一类型的多个组件,那么可以使用@Profile注解
标识要获取的是哪一个bean。也可以说@Profile注解是Spring为我们
提供的可以根据当前环境,动态地激活和切换一系列组件的功能。这个
功能在不同的环境使用不同的变量的情景下特别有用,例如,开发环
境、测试环境、生产环境使用不同的数据源,在不改变代码的情况下
,可以使用这个注解来动态地切换要连接的数据库
1 | //@Profile注解不仅可以标注在方法上,也可以标注在配置类上 |
- 如果@Profile注解标注在配置类上,那么只有是在指定的环境的时
候,整个配置类里面的所有配置才会生效 - 如果一个bean上没有使用@Profile注解进行标注,那么这个bean在
任何环境下都会被注册到IOC容器中,当然了,前提是在整个配置类生
效的情况下
实战案例
使用@Profile注解实现开发、测试和生产环境的配置和切换。这里以开发
过程中要用到的数据源为例(数据源也是一种组件)。我们希望在开发环
境中,数据源是连向A数据库的,在测试环境中,数据源是连向B数据库
的,而且在这一过程中,测试人员压根就不需要改动任何代码,最终项
目上线之后,数据源连向C数据库,而且最重要的一点是在整个过程中
,我们不希望改动大量的代码,而实现数据源的切换
环境搭建
需要在pom.xml文件中添加c3p0数据源和MySQL驱动的依赖
1 | <dependency> |
在项目中新建一个配置类,例如MainConfigOfProfile,并在该配置
类中模拟开发、测试、生产环境的数据源
1 |
|
该配置类这样写,是一点儿问题都没有的,但你有没有想过这一点,
在真实项目开发中,那些数据库连接的相关信息,例如用户名、密码
以及MySQL数据库驱动类的全名,这些都是要抽取在一个配置文件中
的,在项目的src/main/resources目录下新建一个配置文件,例
如dbconfig.properties
1 | db.user=root |
然后通过之前学习过的注解知识获取以上配置文件中的值
1 | "classpath:/dbconfig.properties") // 加载外部的配置文件 ( |
根据环境注册bean
我们成功搭建环境之后,接下来就是要实现根据不同的环境来向IOC
容器中注册相应的bean了。也就是说,我们要实现在开发环境注册开
发环境下使用的数据源,在测试环境注册测试环境下使用的数据源
,在生产环境注册生产环境下使用的数据源。此时@Profile注解就
显示出其强大的特性了
1 | "classpath:/dbconfig.properties") // 加载外部的配置文件 ( |
为不同的数据源添加@Profile注解后,默认是不会向IOC容器中注册
bean的,需要我们根据环境显示指定向IOC容器中注册相应的bean。
换句话说,通过@Profile 注解加了环境标识的bean,只有这个环境被
激活的时候,相应的bean才会被注册到IOC容器中。可以通过@Profile
(“default”)注解来标识一个默认的环境
1 | "default") ( |
根据不同的环境来注册相应的bean有两种方式
- 第一种方式就是根据命令行参数来确定环境,我们在运行程序的时
候可以添加相应的命令行参数。例如,如果我们现在的环境是测试环境
,那么可以在运行程序的时候添加如下命令行参数1
2#VM options
-Dspring.profiles.active=test - 第二种方式就是通过写代码的方式来激活某种环境,其实主要是通过
AnnotationConfigApplicationContext类的无参构造方法来实现
- 在bean上加@Profile注解,其value属性值为环境标识,可以自定义
- 使用AnnotationConfigApplicationContext类的无参构造方法创建容器
- 设置容器环境,其值为第1步设置的环境标识
- 设置容器的配置类
- 刷新容器
先在程序中调用AnnotationConfigApplicationContext类的无参构
造方法来创建一个IOC容器,然后在容器进行初始化之前,为其设置相
应的环境,接着再为容器设置主配置类,最后刷新一下容器。例如,
我们将IOC容器设置为测试环境
1 |
|
@Profile注解不仅可以标注在方法上,也可以标注在配置类上。如果标
注在配置类上,那么只有是在指定的环境的时候,整个配置类里面的所
有配置才会生效。例如我们在MainConfigOfProfile 配置类上标注上
@Profile(“dev”)注解
1 | "dev") // @Profile注解除了能写到bean上,还能写到类上 ( |
在test02()方法中指定当前环境为测试环境,而MainConfigOfProfile
配置类上标注的注解为@Profile(“dev”),说明该配置类中的所有配置
只有在开发环境下才会生效。所以,此时没有任何数据源注册到IOC容器
中,自然控制台中就不会输出任何信息了。如果一个bean 上没有使用
@Profile注解进行标注,那么这个bean在任何环境下都会被注册到
IOC容器中