Spring 注解驱动开发【自动装配】
1.定义
Spring利用依赖注入(DI),完成对ioc容器中各组件的依赖关系进行赋值
2.bean的属性自动装配
1.@Autowired【Spring定义的】
分别创建BookMapper、BookService和BookController
@Reposity
public class BookMapper{
...
}
@Service
// 注册进容器的组件默认bean名字为类名小写
public class BookService {
@Autowired
private BookMapper bookMapper;
}
@Controller
public class BookController {
@Autowired
private BookService bookService;
}
- 创建配置类并添加组件扫描注解
@ComponentScan({
"indi.zhihuali.service",
"indi.zhihuali.mapper",
"indi.zhihuali.controller"})
public class ConfigOfAutowired {
}
- @Autowired
1.1.默认优先按照类型去容器中寻找对应组件(以BookService为例):
applicationContext.getBean(BookMapper.class);
如果找到就给bookService中的bookMapper赋值
1.1.2.若找到多个相同类型的组件 再根据组件名作为组件的id去容器中查找
注意:默认情况下 添加了@Component的组件 id即为类名首字母小写
applicationContext.getBean("bookMapper");
为了区分ioc容器中的组件 给mapper设置一个label从而分辨不同的mapper对象
@Data
@AllArgsConstructor
@NoArgsConstructor
// 默认以类名小写首字母作为ioc容器中bean的名字
public class BookMapper {
// 给BookMapper添加一个标签以分辨在ioc容器中是哪个mapper
// 将BookMapper设置默认的label为 1
private String label = "1";
}
在配置类中再次注册一个组件 命名为bookMapper2并设置label为2
// Config类
@Bean(value = "bookMapper2")
public BookMapper bookMapper(){
BookMapper mapper = new BookMapper();
mapper.setLabel("2");
return mapper;
}
在Service中添加toString方法以便查看Service中的Mapper对象label
@Service
public class BookService {
@Autowired
@Qualifier("bookMapper")
private BookMapper bookMapper;
@Override
public String toString() {
return "BookService{" +
"bookMapper=" + bookMapper +
'}';
}
}
编写测试类
public class TestAutowired {
// 获取ioc容器
private ApplicationContext context = new AnnotationConfigApplicationContext(ConfigOfAutowired.class);
@Test
public void test(){
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
BookService service = context.getBean(BookService.class);
System.out.println(service);
//可以看到容器中的mapper和service中装配的mapper打印出来的地址相同
// BookMapper mapper = context.getBean("bookMapper",BookMapper.class);
// System.out.println(mapper);
}
}
输出:
BookService{bookMapper=BookMapper(label=1)}
到此可以总结出:
@Autowired默认先对类型进行匹配 若同时匹配到多个类型 则按照名称进行匹配
即先byType再byName
1.1.3.同样可以通过@Qualifier(“bookMapper”) 指定需要装配的组件的id 而不是用默认的属性名**
-
在配置类中注册mapper2
@Bean(value = "bookMapper2") public BookMapper bookMapper(){ BookMapper mapper = new BookMapper(); mapper.setLabel("2"); return mapper; }
-
通过@Qualifier对bean指定
@Service
public class BookService {
@Autowired
@Qualifier("bookMapper2")
private BookMapper bookMapper;
@Override
public String toString() {
return "BookService{" +
"bookMapper=" + bookMapper +
'}';
}
}
测试运行结果为:
BookService{bookMapper=BookMapper(label=2)}
1.1.4.当容器中没有匹配bean时
自动装配 默认一定要将属性赋值好 没有就会报错
可以通过
@Autowired(required = false)
当没有指定bean时 为空也可以
BookService{
bookMapper=null}
1.1.5.@Primary
在配置类中注册bean时 可以在其上添加该注解从而使其变成首选bean
当spring自动装配时,默认使用首选的bean
添加该注解后 若指定的是首选bean时 就不需要重复写@Qualifier了 如果是其他bean则仍可以通过@Qualifier进行指定
@Primary
@Bean(value = "bookMapper2")
public BookMapper bookMapper(){
BookMapper mapper = new BookMapper();
mapper.setLabel("2");
return mapper;
}
2.@Resource(JSR250)和@Inject(JSR330)(不常用)【Java规范】
-
@Resource
可以和@Autowired一样实现自动装配功能 默认是按照组件名称进行装配的ByName
@Service
public class BookService {
@Resource
private BookMapper bookMapper;
}
没有能支持@Primary和require = false的功能
-
@Inject
需要导入javax.inject的包 和Autowired的功能一样 但没有require = false的功能
@Autowired、@Resource、@Inject都是通过AutowiredAnnotationBeanPostProcessor 解析完成自动配置功能的
3.bean的方法、构造器位置的自动装配
3.1.在set方法上加@Autowired
创建实体类Boss、Car并注册入容器中
@Component
public class Boss {
// @Autowored
private Car car;
public Car getCar() {
return car;
}
@Autowired
// Autowired标注在方法上 Spring容器在创建当前对象时就会调用这个方法,完成赋值
// 方法使用的参数 自定义类型的值从ioc容器中获取
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Boss{" +
"car=" + car +
'}';
}
}
@Component
public class Car {
public Car() {
}
}
- 测试通过方法@Autowired装配的car是否来自ioc容器中
@Test
public void test02(){
Boss boss = context.getBean(Boss.class);
System.out.println(boss);
Car car = context.getBean(Car.class);
System.out.println(car);
}
测试结果:
Boss{car=indi.zhihuali.pojo.Car@120ba90}
indi.zhihuali.pojo.Car@120ba90
结果显示 Boss的Car属性确实来自于ioc容器中
注:@Autowired注解也可以标在形参处
public void setCar(Car car) {
this.car = car;
}
3.2.在有参构造器上加@Autowired
**默认情况下:加载ioc容器中的组件,容器启动是会调用无参构造器创建对象,之后再进行初始化赋值等操作 **
- 给Boss添加有参构造器
@Autowired
public Boss(Car car) {
this.car = car;
}
- 测试通过方法@Autowired装配的car是否来自ioc容器中
@Test
public void test02(){
Boss boss = context.getBean(Boss.class);
System.out.println(boss);
Car car = context.getBean(Car.class);
System.out.println(car);
}
测试结果:
Boss{car=indi.zhihuali.pojo.Car@120ba90}
indi.zhihuali.pojo.Car@120ba90
因此可得:构造器要用的组件 也是来自ioc容器中
与set方法中一样,注解也可以标在形参前
public Boss(@Autowired Car car) {
this.car = car;
}
标在有参构造器上时 如果组件只有一个有参构造器时,这个有参构造器的@Autowired可以省略 参数位置的组件还是可以自动从容器中获取
3.3.在参数前加@Autowired
public Boss(@Autowired Car car) {
this.car = car;
}
3.4.配置类中@Bean处添加@Autowired(默认不写)
参数从容器中获取 默认不写@Autowired 效果一样 都能自动装配
-
创建实体类Manager
@Data @NoArgsConstructor @AllArgsConstructor public class Manager { private Car car; }
-
在配置类中@Bean注册bean
@Bean public Manager manager(@Autowired(默认不写) Car car){ Manager manager = new Manager(); manager.setCar(car); return manager; }
-
测试通过Bean上添加@Autowired装配的car是否来自ioc容器中
@Test public void test02(){ Car car = context.getBean(Car.class); System.out.println(car); Manager manager = context.getBean(Manager.class); System.out.println(manager); }
测试结果:
Manager{car=indi.zhihuali.pojo.Car@120ba90} indi.zhihuali.pojo.Car@120ba90
4.Aware注入Spring底层组件
当组件需要用到Spring定义的底层组件如:ApplicationContext、BeanFactory…
自定义组件实现xxxAware接口 在创建对象时会调用接口规定的方法注入相关组件
Aware接口的定义:
A marker superinterface indicating that a bean is eligible to be notified by the
Spring container of a particular framework object through a callback-style method.
标记超接口,指示bean有资格被
Spring容器通过回调方式调用特定框架对象。
将Spring底层的一些组件注入到自定义的bean中
实例:
- 自定义类并实现xxxAware接口
@Component
public class Red implements ApplicationContextAware, EmbeddedValueResolverAware, BeanNameAware {
private ApplicationContext applicationContext;
public void setBeanName(String name) {
System.out.println("当前bean的名字"+name);
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
System.out.println("传入的Ioc"+applicationContext);
}
// String值解析器 可以解析占位符
public void setEmbeddedValueResolver(StringValueResolver resolver) {
String s = resolver.resolveStringValue("你好!${os.name},我是#{18*20}!");
System.out.println(s);
}
}
- 测试
@Test
public void test(){
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
BookService service = context.getBean(BookService.class);
System.out.println(service);
}
输出结果为:
当前bean的名字red
你好!Windows 10,我是360!
传入的Iocorg.springframework.context.annotation.AnnotationConfigApplicationContext@117365b, started on Wed Sep 23 17:19:55 CST 2020
postProcessBeforeInitialization...indi.zhihuali.pojo.Red@15d6a01 name:red
postProcessAfterInitialization...indi.zhihuali.pojo.Red@15d6a01 name:red
...
xxxAware往往都会有对应的Processor后置处理器通过debug实现的xxxAware接口中重写的方法 可以看到后置处理器也在工作 也会调用BeforeInitialization()…
5.@Profile
Spring提供的可以根据当前环境 动态激活和切换一系列组件的功能
在实际工作环境中 需要切换不同的环境来适应不同的业务、进行不同的操作
例如:在测试环境与开发环境中,连接数据库中不同的表
在不同的环境下连接不同的Tomcat端口号等等
1.在@Bean上添加@Profile
- 配置不同生产环境中的数据源信息
@Configuration
public class ConfigOfProfile {
// 测试环境(假设) :
public DataSource dataSourceTest() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("1234");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass("com.jdbc.mysql.Driver");
return dataSource;
}
// 开发环境
public DataSource dataSourceDev() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("1234");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
dataSource.setDriverClass("com.jdbc.mysql.Driver");
return dataSource;
}
// 生产环境
public DataSource dataSourceProd() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("1234");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");
dataSource.setDriverClass("com.jdbc.mysql.Driver");
return dataSource;
}
}
-
通常情况下配置信息不会暴露在代码中而会写在配置文件中
db.properties
db.user=root
db.password=1234
db.driverClass=com.mysql.jdbc.Driver
- 获取.properties文件中数据的三种方法:
配置类首先要添加注解配置源文件
@Configuration
@PropertySource("classpath:/db.properties")
public class ConfigOfProfile{
- 直接通过@Value获取配置文件中的数据
@Value("${db.user}")
private String user;
- 通过向@Bean下的方法用@ Value添加形参并获取配置文件中的数据
@Bean("test")
public DataSource dataSourceTest(@Value("{db.password}")String pwd) throws PropertyVetoException {
- 实现EmbeddedValueResolverAware接口并重写setEmbeddedValueResolver方法对占位符进行解析获取
public class xxx implements EmbeddedValueResolverAware{
private StringValueResolver valueResolver;
private String driverClass;
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
driverClass = resolver.resolveStringValue("${db.driverClass}");
}
}
修改后的配置类为:
@Configuration
@PropertySource("classpath:/db.properties")
public class ConfigOfProfile implements EmbeddedValueResolverAware {
@Value("${db.user}")
private String user;
private StringValueResolver valueResolver;
private String driverClass;
@Bean("test")
// test环境(假设) :
public DataSource dataSourceTest(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Bean("dev")
public DataSource dataSourceDev(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Bean("prod")
public DataSource dataSourceProd(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");
dataSource.setDriverClass(driverClass);
return dataSource;
}
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
driverClass = resolver.resolveStringValue("${db.driverClass}");
}
}
-
指定组件在哪个环境的情况下才能被注册到容器中
只有当@Profile的环境被激活时才能将这个bean注册到容器中 默认环境为default
-
通过不同方式来激活不同的环境
- VM options处输入-Dspring.profiles.active=test
- 在Test类中注册ioc容器时通过setActiveProfiles激活环境
public class TestProfile { @Test public void test(){ // 1.通过无参构造器创建一个ioc容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // 2.设置需要激活的环境 context.getEnvironment().setActiveProfiles("test","dev"); // 3.设置主配置类 context.register(ConfigOfProfile.class); // 4.启动刷新容器 context.refresh(); String[] beanDefinitionNames = context.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } } }
注意:
原来直接将配置类作为参数传入构造器初始化ioc容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(xxxConfiguration.class);
可以看一下ioc容器的有参构造器
public AnnotationConfigApplicationContext(Class<?>... componentClasses) { this(); register(componentClasses); refresh(); }
对比上面的代码:
// 1.通过无参构造器创建一个ioc容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 2.设置需要激活的环境
context.getEnvironment().setActiveProfiles("test","dev");
// 3.设置主配置类
context.register(ConfigOfProfile.class);
// 4.启动刷新容器
context.refresh();
可以发现
原来有参构造器直接将配置类进行注入而没有配置任何profile 这里只是插入一个设置活跃profile的过程
- 测试结果为:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
configOfProfile
test
dev
可见 只有test和dev被注册入ioc 而prod并没有 因此可以看出只有当当前环境的profile与bean的@Profile内部参数的一致时,该bean才会被注册入ioc容器
2.在类上添加@Profile
@Profile("test")
@Configuration
public class MyConfig{
....
}
只有当ioc容器中设置的环境与@Profile(“test”)一致时才会注册入整个配置类
- 测试
public class TestProfile {
@Test
public void test(){
// 1.通过无参构造器创建一个ioc容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 2.设置需要激活的环境
context.getEnvironment().setActiveProfiles("dev");
// 3.设置主配置类
context.register(ConfigOfProfile.class);
// 4.启动刷新容器
context.refresh();
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
输出结果为:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
自定义的配置bean都没有被注册进去
此时无论类中是否有@Profile(“dev”) 由于整个配置类都不会被注册进容器 因此也不会有bean被注册
3.没有标注@Profile的bean
在配置类可以被加载的情况下 非2中情况时 没有标注环境的bean一定会被注册进容器中
@Configuration
@PropertySource("classpath:/db.properties")
public class ConfigOfProfile implements EmbeddedValueResolverAware {
@Value("${db.user}")
private String user;
private StringValueResolver valueResolver;
private String driverClass;
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
driverClass = resolver.resolveStringValue("${db.driverClass}");
}
@Bean("test")
@Profile("test")
// Test环境
public DataSource dataSourceTest(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Bean("dev")
@Profile("dev")
// 开发环境
public DataSource dataSourceDev(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Bean("prod")
@Profile("prod")
// 生产环境
public DataSource dataSourceProd(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");
dataSource.setDriverClass(driverClass);
return dataSource;
}
// 给容器中添加一个不指定环境的bean
@Bean
public Person person(){
return new Person();
}
}
public class TestProfile {
@Test
public void test(){
// 1.通过无参构造器创建一个ioc容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 2.设置需要激活的环境
context.getEnvironment().setActiveProfiles("dev");
// 3.设置主配置类
context.register(ConfigOfProfile.class);
// 4.启动刷新容器
context.refresh();
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
测试结果:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
configOfProfile
dev
person
Spring 注解驱动开发【自动装配】
1.定义
Spring利用依赖注入(DI),完成对ioc容器中各组件的依赖关系进行赋值
2.bean的属性自动装配
1.@Autowired【Spring定义的】
分别创建BookMapper、BookService和BookController
@Reposity
public class BookMapper{
...
}
@Service
// 注册进容器的组件默认bean名字为类名小写
public class BookService {
@Autowired
private BookMapper bookMapper;
}
@Controller
public class BookController {
@Autowired
private BookService bookService;
}
- 创建配置类并添加组件扫描注解
@ComponentScan({
"indi.zhihuali.service",
"indi.zhihuali.mapper",
"indi.zhihuali.controller"})
public class ConfigOfAutowired {
}
- @Autowired
1.1.默认优先按照类型去容器中寻找对应组件(以BookService为例):
applicationContext.getBean(BookMapper.class);
如果找到就给bookService中的bookMapper赋值
1.1.2.若找到多个相同类型的组件 再根据组件名作为组件的id去容器中查找
注意:默认情况下 添加了@Component的组件 id即为类名首字母小写
applicationContext.getBean("bookMapper");
为了区分ioc容器中的组件 给mapper设置一个label从而分辨不同的mapper对象
@Data
@AllArgsConstructor
@NoArgsConstructor
// 默认以类名小写首字母作为ioc容器中bean的名字
public class BookMapper {
// 给BookMapper添加一个标签以分辨在ioc容器中是哪个mapper
// 将BookMapper设置默认的label为 1
private String label = "1";
}
在配置类中再次注册一个组件 命名为bookMapper2并设置label为2
// Config类
@Bean(value = "bookMapper2")
public BookMapper bookMapper(){
BookMapper mapper = new BookMapper();
mapper.setLabel("2");
return mapper;
}
在Service中添加toString方法以便查看Service中的Mapper对象label
@Service
public class BookService {
@Autowired
@Qualifier("bookMapper")
private BookMapper bookMapper;
@Override
public String toString() {
return "BookService{" +
"bookMapper=" + bookMapper +
'}';
}
}
编写测试类
public class TestAutowired {
// 获取ioc容器
private ApplicationContext context = new AnnotationConfigApplicationContext(ConfigOfAutowired.class);
@Test
public void test(){
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
BookService service = context.getBean(BookService.class);
System.out.println(service);
//可以看到容器中的mapper和service中装配的mapper打印出来的地址相同
// BookMapper mapper = context.getBean("bookMapper",BookMapper.class);
// System.out.println(mapper);
}
}
输出:
BookService{bookMapper=BookMapper(label=1)}
到此可以总结出:
@Autowired默认先对类型进行匹配 若同时匹配到多个类型 则按照名称进行匹配
即先byType再byName
1.1.3.同样可以通过@Qualifier(“bookMapper”) 指定需要装配的组件的id 而不是用默认的属性名**
-
在配置类中注册mapper2
@Bean(value = "bookMapper2") public BookMapper bookMapper(){ BookMapper mapper = new BookMapper(); mapper.setLabel("2"); return mapper; }
-
通过@Qualifier对bean指定
@Service
public class BookService {
@Autowired
@Qualifier("bookMapper2")
private BookMapper bookMapper;
@Override
public String toString() {
return "BookService{" +
"bookMapper=" + bookMapper +
'}';
}
}
测试运行结果为:
BookService{bookMapper=BookMapper(label=2)}
1.1.4.当容器中没有匹配bean时
自动装配 默认一定要将属性赋值好 没有就会报错
可以通过
@Autowired(required = false)
当没有指定bean时 为空也可以
BookService{
bookMapper=null}
1.1.5.@Primary
在配置类中注册bean时 可以在其上添加该注解从而使其变成首选bean
当spring自动装配时,默认使用首选的bean
添加该注解后 若指定的是首选bean时 就不需要重复写@Qualifier了 如果是其他bean则仍可以通过@Qualifier进行指定
@Primary
@Bean(value = "bookMapper2")
public BookMapper bookMapper(){
BookMapper mapper = new BookMapper();
mapper.setLabel("2");
return mapper;
}
2.@Resource(JSR250)和@Inject(JSR330)(不常用)【Java规范】
-
@Resource
可以和@Autowired一样实现自动装配功能 默认是按照组件名称进行装配的ByName
@Service
public class BookService {
@Resource
private BookMapper bookMapper;
}
没有能支持@Primary和require = false的功能
-
@Inject
需要导入javax.inject的包 和Autowired的功能一样 但没有require = false的功能
@Autowired、@Resource、@Inject都是通过AutowiredAnnotationBeanPostProcessor 解析完成自动配置功能的
3.bean的方法、构造器位置的自动装配
3.1.在set方法上加@Autowired
创建实体类Boss、Car并注册入容器中
@Component
public class Boss {
// @Autowored
private Car car;
public Car getCar() {
return car;
}
@Autowired
// Autowired标注在方法上 Spring容器在创建当前对象时就会调用这个方法,完成赋值
// 方法使用的参数 自定义类型的值从ioc容器中获取
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Boss{" +
"car=" + car +
'}';
}
}
@Component
public class Car {
public Car() {
}
}
- 测试通过方法@Autowired装配的car是否来自ioc容器中
@Test
public void test02(){
Boss boss = context.getBean(Boss.class);
System.out.println(boss);
Car car = context.getBean(Car.class);
System.out.println(car);
}
测试结果:
Boss{car=indi.zhihuali.pojo.Car@120ba90}
indi.zhihuali.pojo.Car@120ba90
结果显示 Boss的Car属性确实来自于ioc容器中
注:@Autowired注解也可以标在形参处
public void setCar(Car car) {
this.car = car;
}
3.2.在有参构造器上加@Autowired
**默认情况下:加载ioc容器中的组件,容器启动是会调用无参构造器创建对象,之后再进行初始化赋值等操作 **
- 给Boss添加有参构造器
@Autowired
public Boss(Car car) {
this.car = car;
}
- 测试通过方法@Autowired装配的car是否来自ioc容器中
@Test
public void test02(){
Boss boss = context.getBean(Boss.class);
System.out.println(boss);
Car car = context.getBean(Car.class);
System.out.println(car);
}
测试结果:
Boss{car=indi.zhihuali.pojo.Car@120ba90}
indi.zhihuali.pojo.Car@120ba90
因此可得:构造器要用的组件 也是来自ioc容器中
与set方法中一样,注解也可以标在形参前
public Boss(@Autowired Car car) {
this.car = car;
}
标在有参构造器上时 如果组件只有一个有参构造器时,这个有参构造器的@Autowired可以省略 参数位置的组件还是可以自动从容器中获取
3.3.在参数前加@Autowired
public Boss(@Autowired Car car) {
this.car = car;
}
3.4.配置类中@Bean处添加@Autowired(默认不写)
参数从容器中获取 默认不写@Autowired 效果一样 都能自动装配
-
创建实体类Manager
@Data @NoArgsConstructor @AllArgsConstructor public class Manager { private Car car; }
-
在配置类中@Bean注册bean
@Bean public Manager manager(@Autowired(默认不写) Car car){ Manager manager = new Manager(); manager.setCar(car); return manager; }
-
测试通过Bean上添加@Autowired装配的car是否来自ioc容器中
@Test public void test02(){ Car car = context.getBean(Car.class); System.out.println(car); Manager manager = context.getBean(Manager.class); System.out.println(manager); }
测试结果:
Manager{car=indi.zhihuali.pojo.Car@120ba90} indi.zhihuali.pojo.Car@120ba90
4.Aware注入Spring底层组件
当组件需要用到Spring定义的底层组件如:ApplicationContext、BeanFactory…
自定义组件实现xxxAware接口 在创建对象时会调用接口规定的方法注入相关组件
Aware接口的定义:
A marker superinterface indicating that a bean is eligible to be notified by the
Spring container of a particular framework object through a callback-style method.
标记超接口,指示bean有资格被
Spring容器通过回调方式调用特定框架对象。
将Spring底层的一些组件注入到自定义的bean中
实例:
- 自定义类并实现xxxAware接口
@Component
public class Red implements ApplicationContextAware, EmbeddedValueResolverAware, BeanNameAware {
private ApplicationContext applicationContext;
public void setBeanName(String name) {
System.out.println("当前bean的名字"+name);
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
System.out.println("传入的Ioc"+applicationContext);
}
// String值解析器 可以解析占位符
public void setEmbeddedValueResolver(StringValueResolver resolver) {
String s = resolver.resolveStringValue("你好!${os.name},我是#{18*20}!");
System.out.println(s);
}
}
- 测试
@Test
public void test(){
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
BookService service = context.getBean(BookService.class);
System.out.println(service);
}
输出结果为:
当前bean的名字red
你好!Windows 10,我是360!
传入的Iocorg.springframework.context.annotation.AnnotationConfigApplicationContext@117365b, started on Wed Sep 23 17:19:55 CST 2020
postProcessBeforeInitialization...indi.zhihuali.pojo.Red@15d6a01 name:red
postProcessAfterInitialization...indi.zhihuali.pojo.Red@15d6a01 name:red
...
xxxAware往往都会有对应的Processor后置处理器通过debug实现的xxxAware接口中重写的方法 可以看到后置处理器也在工作 也会调用BeforeInitialization()…
5.@Profile
Spring提供的可以根据当前环境 动态激活和切换一系列组件的功能
在实际工作环境中 需要切换不同的环境来适应不同的业务、进行不同的操作
例如:在测试环境与开发环境中,连接数据库中不同的表
在不同的环境下连接不同的Tomcat端口号等等
1.在@Bean上添加@Profile
- 配置不同生产环境中的数据源信息
@Configuration
public class ConfigOfProfile {
// 测试环境(假设) :
public DataSource dataSourceTest() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("1234");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass("com.jdbc.mysql.Driver");
return dataSource;
}
// 开发环境
public DataSource dataSourceDev() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("1234");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
dataSource.setDriverClass("com.jdbc.mysql.Driver");
return dataSource;
}
// 生产环境
public DataSource dataSourceProd() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("1234");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");
dataSource.setDriverClass("com.jdbc.mysql.Driver");
return dataSource;
}
}
-
通常情况下配置信息不会暴露在代码中而会写在配置文件中
db.properties
db.user=root
db.password=1234
db.driverClass=com.mysql.jdbc.Driver
- 获取.properties文件中数据的三种方法:
配置类首先要添加注解配置源文件
@Configuration
@PropertySource("classpath:/db.properties")
public class ConfigOfProfile{
- 直接通过@Value获取配置文件中的数据
@Value("${db.user}")
private String user;
- 通过向@Bean下的方法用@ Value添加形参并获取配置文件中的数据
@Bean("test")
public DataSource dataSourceTest(@Value("{db.password}")String pwd) throws PropertyVetoException {
- 实现EmbeddedValueResolverAware接口并重写setEmbeddedValueResolver方法对占位符进行解析获取
public class xxx implements EmbeddedValueResolverAware{
private StringValueResolver valueResolver;
private String driverClass;
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
driverClass = resolver.resolveStringValue("${db.driverClass}");
}
}
修改后的配置类为:
@Configuration
@PropertySource("classpath:/db.properties")
public class ConfigOfProfile implements EmbeddedValueResolverAware {
@Value("${db.user}")
private String user;
private StringValueResolver valueResolver;
private String driverClass;
@Bean("test")
// test环境(假设) :
public DataSource dataSourceTest(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Bean("dev")
public DataSource dataSourceDev(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Bean("prod")
public DataSource dataSourceProd(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");
dataSource.setDriverClass(driverClass);
return dataSource;
}
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
driverClass = resolver.resolveStringValue("${db.driverClass}");
}
}
-
指定组件在哪个环境的情况下才能被注册到容器中
只有当@Profile的环境被激活时才能将这个bean注册到容器中 默认环境为default
-
通过不同方式来激活不同的环境
- VM options处输入-Dspring.profiles.active=test
- 在Test类中注册ioc容器时通过setActiveProfiles激活环境
public class TestProfile { @Test public void test(){ // 1.通过无参构造器创建一个ioc容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // 2.设置需要激活的环境 context.getEnvironment().setActiveProfiles("test","dev"); // 3.设置主配置类 context.register(ConfigOfProfile.class); // 4.启动刷新容器 context.refresh(); String[] beanDefinitionNames = context.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } } }
注意:
原来直接将配置类作为参数传入构造器初始化ioc容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(xxxConfiguration.class);
可以看一下ioc容器的有参构造器
public AnnotationConfigApplicationContext(Class<?>... componentClasses) { this(); register(componentClasses); refresh(); }
对比上面的代码:
// 1.通过无参构造器创建一个ioc容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 2.设置需要激活的环境
context.getEnvironment().setActiveProfiles("test","dev");
// 3.设置主配置类
context.register(ConfigOfProfile.class);
// 4.启动刷新容器
context.refresh();
可以发现
原来有参构造器直接将配置类进行注入而没有配置任何profile 这里只是插入一个设置活跃profile的过程
- 测试结果为:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
configOfProfile
test
dev
可见 只有test和dev被注册入ioc 而prod并没有 因此可以看出只有当当前环境的profile与bean的@Profile内部参数的一致时,该bean才会被注册入ioc容器
2.在类上添加@Profile
@Profile("test")
@Configuration
public class MyConfig{
....
}
只有当ioc容器中设置的环境与@Profile(“test”)一致时才会注册入整个配置类
- 测试
public class TestProfile {
@Test
public void test(){
// 1.通过无参构造器创建一个ioc容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 2.设置需要激活的环境
context.getEnvironment().setActiveProfiles("dev");
// 3.设置主配置类
context.register(ConfigOfProfile.class);
// 4.启动刷新容器
context.refresh();
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
输出结果为:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
自定义的配置bean都没有被注册进去
此时无论类中是否有@Profile(“dev”) 由于整个配置类都不会被注册进容器 因此也不会有bean被注册
3.没有标注@Profile的bean
在配置类可以被加载的情况下 非2中情况时 没有标注环境的bean一定会被注册进容器中
@Configuration
@PropertySource("classpath:/db.properties")
public class ConfigOfProfile implements EmbeddedValueResolverAware {
@Value("${db.user}")
private String user;
private StringValueResolver valueResolver;
private String driverClass;
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
driverClass = resolver.resolveStringValue("${db.driverClass}");
}
@Bean("test")
@Profile("test")
// Test环境
public DataSource dataSourceTest(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Bean("dev")
@Profile("dev")
// 开发环境
public DataSource dataSourceDev(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Bean("prod")
@Profile("prod")
// 生产环境
public DataSource dataSourceProd(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");
dataSource.setDriverClass(driverClass);
return dataSource;
}
// 给容器中添加一个不指定环境的bean
@Bean
public Person person(){
return new Person();
}
}
public class TestProfile {
@Test
public void test(){
// 1.通过无参构造器创建一个ioc容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 2.设置需要激活的环境
context.getEnvironment().setActiveProfiles("dev");
// 3.设置主配置类
context.register(ConfigOfProfile.class);
// 4.启动刷新容器
context.refresh();
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
测试结果:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
configOfProfile
dev
person