前言
对于Spring框架,其中IoC(控制翻转)和AOP(面向切面编程)都是比较重要的概念,而在Spring Boot中主要使用全注解的方式来实现IoC和AOP的功能,因此本文以Spring Boot框架为基础,对其中全注解下的IoC和AOP的相关基础性概念和使用进行介绍,以为后续Spring Boot学习打下基础。
Spring Boot中全注解下的Spring IoC
IoC容器简介
IoC(Inversion of Control,控制反转)是一种通过描述来生成或者获取对象的技术,它不是Spring中独有的。它的作用可以理解为:开始学java创建对象时常使用的就是new关键字来创建对象,而在Spring中则是通过描述(XML或注解)来创建对象,而对象的创建和管理是由IoC容器(类似于工厂方法)来负责。而一个系统中不可能只有一个对象,而且对象之间还具有依赖的关系,这时就需要依赖注入的方式,通过描述来管理各个对象之间的关联关系。
Spring中将每个需要管理的对象称为Bean,而Spring管理这些Bean的容器,被称为Spring IoC容器(简称为IoC容器),IoC容器具有两个基本功能;
- 通过描述来管理Bean,包括创建和获取Bean。
- 通过描述来完成Bean之间的依赖关系的建立。
在Spring的定义中,要求所有的IoC容器都需要实现BeanFactory顶级容器接口,该接口的源码如下:
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
public interface BeanFactory {
// IoC容器管理的Bean名称的前缀
String FACTORY_BEAN_PREFIX = "&";
// 一系列获取Bean的方法
Object getBean(String var1) throws BeansException;
<T> T getBean(String var1, Class<T> var2) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> var1);
<T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
// IoC容器中是否包含Bean
boolean containsBean(String var1);
// Bean是否为单例
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
// Bean是否为原型
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
// 是否匹配类型
boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
// 获取Bean的类型
@Nullable
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String var1, boolean var2) throws NoSuchBeanDefinitionException;
// 获取Bean的别名
String[] getAliases(String var1);
}
在这个顶级接口中对相关的方法进行了注释。同时可以看到上面的代码中包含了很多getBean的重载方法,包括类型(byType),根据名称(byName)获取Bean,这意味着在IoC容器中可以使用Bean类型或者名称来获取相应的Bean,这也是后面依赖注入(Dependency Injection,DI)的基础。
isSingleton方法是判断Bean在容器中是否为单例。在IoC容器中,Bean默认为代理存在的,也就是使用getBean方法返回的Bean都是同一个。相对应的如果isPrototype方法返回为true,则表示以原型模式的方法创建Bean,那么在使用getBean方法获取Bean时,IoC容器会新建Bean进行返回。
由于BeanFactory为IoC容器的顶层接口,Spring在此基础上设计了更高级的接口ApplicationContext,现实中我们使用的大部分Spring IoC容器是ApplicationContext接口的实现类。在Spring Boot中,bean的装备主要是通过注解来实现的,因此下面使用AnnotationConfigApplicationContext(基于注解的IoC容器)来简单实现一个Bean的装配与使用,这种过程实际上和Spring Boot实现的Bean的装配和获取的方法是一致的。
简单的POJO:
@Data
public class UserBean {
private Long id;
private String userName;
private Integer sex;
private String note;
public UserBean() {
}
public UserBean(Long id, String userName, Integer sex, String note) {
this.id = id;
this.userName = userName;
this.sex = sex;
this.note = note;
}
}
定义Spring的配置类文件:
@Configuration
public class AppConfig {
@Bean(name = "UserBean")
public UserBean getInit() {
return new UserBean(1L, "yitian", 1, "none");
}
}
@Configuration注解表示这是一个Java配置文件,Spring容器会根据它来生成IoC容器去装配Bean。
@Bean注解表示该方法返回的对象将作为Bean装配到IoC容器中,name属性指明该Bean的名称,如果没有配置,则默认为方法名。
使用AnnotationConfigApplicationContext:
public class IoCTest {
private static Logger logger = LoggerFactory.getLogger(IoCTest.class);
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserBean userBean = context.getBean(UserBean.class);
logger.info(userBean.getUserName());
}
}
运行该main方法,可以看到如下日志输出:
15:14:10.871 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@29444d75
15:14:10.886 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
15:14:10.974 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
15:14:10.976 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
15:14:10.977 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
15:14:10.978 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
15:14:10.984 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig'
15:14:10.988 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'UserBean'
15:14:11.018 [main] INFO cn.zyt.springbootlearning.ioc.IoCTest - yitian
将Bean装配到IoC容器中
在传统Spring框架中使用XML的方式来装配Bean,而Spring Boot中主要使用全注解的方式将Bean装配到IoC容器中。可以使用如下几个不同的注解方式。
使用@Component或@ComponentScan
@Component注解指明哪个类被扫描到Spring IoC容器中,而@ComponentScan表示采用何种方式去扫描装配Bean。例如对上述UserBean类,加入@Component注解,其所在package路径为:cn.zyt.springbootlearning.ioc.config.UserBean。
@Data
@Component("UserBean")
public class UserBean {
@Value("1")
private Long id;
@Value("yitian")
private String userName;
@Value("1")
private Integer sex;
@Value("none")
private String note;
//...
}
此时为了使Spring IoC容器装配该类,改造AppConfig(所在package为:cn.zyt.springbootlearning.ioc.config.AppConfig)如下:
@Configuration
@ComponentScan("cn.zyt.springbootlearning.ioc.*")
public class AppConfig {
// @Bean(name = "UserBean")
// public UserBean getInit() {
// return new UserBean(1L, "yitian", 1, "none");
// }
}
@ComponentScan注解标注了指定的扫描包和子包来装备其中的所有Bean。如果不进行设置,则默认扫描该类当前所在的包和子包。对于指定要扫描的包,@ComponentScan注解还有其他其个属性,可以从该注解的源码中进行查看:
@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 {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class";
boolean useDefaultFilters() default true;
// 当满足过滤条件时扫描
ComponentScan.Filter[] includeFilters() default {};
// 当不满足过滤条件时扫描
ComponentScan.Filter[] excludeFilters() default {};
// 是否延迟初始化
boolean lazyInit() default false;
// 定义过滤器
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
其中常用的属性使用注释进行了说明。配置向basePackages定义扫描的包名,basePackageClasses定义扫描的类名,所以上面的@ComponentScan注解还可以使用如下方式进行配置。此时运行IoCTest中的main方法,依然得到上述一样的日志输出。
@ComponentScan(basePackages = "cn.zyt.springbootlearning.ioc.*")
或
@ComponentScan(basePackageClasses = {UserBean.class})
而上述的方式会将指定包下的所有Bean装配到IoC容器中,而如果对于该指定包下的一些其他Bean不想装配,则可以使用@ComponentScan注解中的includeFilters和excludeFilters属性,例如,在上述cn.zyt.springbootlearning.ioc包下除了config包,还包括一个service包,其中定义了UserService类,该类使用了@Service注解,默认情况下也会被装配到IoC容器中。如果此时仅想装配UserBean而不装配UserService,则可以使用excludeFilters属性:
@ComponentScan(value = "cn.zyt.springbootlearning.ioc.*",
excludeFilters = {@ComponentScan.Filter(classes = {Service.class})})
这样就可以仅扫描UserBean,而过滤掉@Service定义的Bean。
装配第三方的Bean
在现实中的应用中,常需要引入第三方的包来进行操作,此时需要将第三方包的对象装配到IoC容器中进行使用,此时就可以使用@Bean注解完成这项功能。例如,在maven中引入如下的第三方依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.42</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
此时需要根据引入的依赖创建数据源来连接数据库,将如下代码加入到AppConfig类中:
@Bean("DbcpDataSource")
public DataSource getDataSource() {
Properties properties = new Properties();
properties.setProperty("driver", "com.mysql.jdbc.Driver");
properties.setProperty("url", "jdbc:mysql://localhost:3306/springboot_dev");
properties.setProperty("username", "root");
properties.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
通过@Bean就可以将这里创建的DataSource对象装配到IoC容器中了。
依赖注入
IoC容器的另一个作用为管理Bean之间的依赖,下面以Person对象依赖Animal对象来实现一些服务的例子,说明IoC容器中Bean之间的依赖注入。首先创建Person接口和其实现类,Animal接口和其实现类。
public interface Person {
/**
* 人依赖于动物提供服务
*/
void service();
/**
* 设置依赖的动物
*/
void setAnimal(Animal animal);
}
public interface Animal {
/**
* 动物可以提供的服务
*/
void use();
}
上面两个接口的实现类如下:
@Component
public class BusinessPerson implements Person {
@Autowired
private Animal animal;
@Override
public void service() {
this.animal.use();
}
@Override
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
@Component
public class Dog implements Animal {
@Override
public void use() {
System.out.println("DOG can save person!");
}
}
注意BusinessPerson类中使用的@Autowired注解 ,它会根据属性的类型(buType)找到对应的Bean进行注入,因为Dog为Animal的一种,所以这里IoC容器会将Dog的Bean注入到BusinessPerson对象中,这样BusinessPerson对象就可以通过注入的Dog对象进行相关的服务了。可以使用如下的代码进行运行测试:
@Test
public void testPerson() {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Person person = context.getBean(BusinessPerson.class);
person.service();
}
运行输出如下:
...
17:50:49.712 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig'
17:50:49.745 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dog'
DOG can save person!
@Autowired注解
下面进一步探索一下@Autowired注解的使用,上面创建了Dog类,下面可以创建一个Cat同样是实现了Animal接口:
@Component
public class Cat implements Animal {
@Override
public void use() {
System.out.println("CAT can catch the mice!");
}
}
此时在运行上面的测试代码,发现会得到如下异常:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'businessPerson': Unsatisfied dependency expressed through field 'animal'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'cn.zyt.springbootlearning.ioc.injection.Animal' available: expected single matching bean but found 2: cat,dog
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:643)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:116)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:879)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:89)
at cn.zyt.springbootlearning.ioc.IoCTest.testPerson(IoCTest.java:30)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'cn.zyt.springbootlearning.ioc.injection.Animal' available: expected single matching bean but found 2: cat,dog
at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1265)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1207)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
... 36 more
根据异常的信息,可以看出来这里是因为Animal有两个实现类并都装配成了Bean,@Autowired注解是根据byType的方式进行查找并注入Bean的,因此在IoC容器对Animal对象进行注入的时候就不知道注入哪一个了。
解决该问题其实很简单,@Autowired注解首先是根据byType的方式getBean,如果找不到则会使用byName的方式进行查找。因此如果这里需要使用的animal实际上是Dog,则将BusinessPerson中的属性改为如下即可:
@Component
public class BusinessPerson implements Person {
@Autowired
private Animal dog;
@Override
public void service() {
this.dog.use();
}
@Override
public void setAnimal(Animal animal) {
this.dog = animal;
}
}
此时运行测试代码,得到的输出和上述的输出一样,当想使用Cat对象进行服务的时候,则将属性名称该为cat即可。需要注意的是@Autowired注解是一个默认必须找到对应Bean的注解,如果先后根据byType和byName的方式都没有找到相应的Bean,则会抛出异常。如果允许某个Bean可以为null,那么可以为@Autowired注解设置required=false属性。
@Primary和Quelifier注解
此外,除了上面依赖于@Autowired注解根据bytype和byName先后顺序进行Bean查找的方式,还有其他的方式可以解决上述的歧义性。@Primary注解可以修改Bean的优先级,当为Cat类加上该注解时,说明Cat的优先级大于Dog,则这是IoC容器会优先将Cat对象进行注入,因此不会产生歧义:
@Component
@Primary
public class Cat implements Animal {
@Override
public void use() {
System.out.println("CAT can catch the mice!");
}
}
此时将BusinessPerson类中的属性名改回animal,并运行测试方法可以恢复正常。但如果此时Dog类中也加入了@Primary注解,则歧义问题将会仍存在。
这时就需要使用@Qualifier注解,使用该注解可以设置其value属性,为需要注入的Bean的名称,这样IoC容器就会同时根据byTypeandName的方式来寻找相应的Bean并注入。此时也就是依赖于BeanFactory类中的如下重载方法:
<T> T getBean(String var1, Class<T> var2) throws BeansException;
在保留Cat类中的@Primary注解,并在BussinessPerson类中进行如下设置时:
@Autowired
@Qualifier("dog")
private Animal animal;
运行测试方法没有异常,并且输出:
DOG can save person!
因此说明,该注解作用的优先级是大于@Primary注解的。
Bean的生命周期
上述的内容仅是正确的将Bean装配到IoC容器中,而没有关心Bean如何创建和销毁的过程。有时需要自定义Bean的初始化和销毁方法,以满足一些特殊Bean的要求。此时就需要对Bean的生命周期进行了解。
Spring IoC初始化和销毁Bean的过程大致分为如下四个步骤:
- Bean的定义。该过程包括如下几个步骤。(1)资源定位,使用@ComponentScan注解定义的扫描路径将带有@Component的类找到。(2)通过找到的资源对Bean进行解析,此时还没有初始化Bean,仅是解析的过程。(3)将Bean的定义发布到IoC容器中,此时IoC容器中具有了Bean的定义,依然没有Bean实例的创建。
- Bean的初始化。开始创建的Bean的实例,在默认情况下,Spring IoC容器会在容器初始化的时候执行Bean的实例化并进行依赖注入,也就是在获取一个Bean之前,Bean的实例已经创建完成了。如果需要延迟初始化,可以在@ComponentScan注解中使用lazyInit属性进行配置,该属性默认为false,改为true即可以设置为延迟初始化。
- Bean的生存期。
- Bean的销毁。
Spring Bean的声明周期过程图:

将上述的BusinessPerson修改为如下,对Bean的生命周期进行测试:
@Component
public class BusinessPerson implements Person, BeanNameAware, BeanFactoryAware,
ApplicationContextAware, InitializingBean, DisposableBean {
private Animal animal;
@Override
public void service() {
this.animal.use();
}
@Override
@Autowired
@Qualifier("dog")
public void setAnimal(Animal animal) {
System.out.println("延迟依赖注入测试");
this.animal = animal;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("【" + this.getClass().getSimpleName() + "】调用BeanFactoryAware的setBeanFactory方法");
}
@Override
public void setBeanName(String s) {
System.out.println("【" + this.getClass().getSimpleName() + "】调用BeanNameAware的setBeanName方法");
}
@PostConstruct
public void initCustom() {
System.out.println("【" + this.getClass().getSimpleName() + "】调用@PostConstruct的initCustom方法");
}
@PreDestroy
public void destroyCustom() {
System.out.println("【" + this.getClass().getSimpleName() + "】调用@PreDestroy的destroyCustom方法");
}
@Override
public void destroy() throws Exception {
System.out.println("【" + this.getClass().getSimpleName() + "】调用DisposableBean的destroy方法");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("【" + this.getClass().getSimpleName() + "】调用InitializingBean的afterPropertiesSet方法");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("【" + this.getClass().getSimpleName() + "】调用ApplicationContextAware的setApplicationContext方法");
}
}
这样找个Bean就试下了生命周期中单个Bean可以实现的所有接口,并且通过注解@PostConstruct定义了初始化方法,@PreDestroy定义了销毁方法。为了测试Bean的后置处理器,这里创建一个BeanProcessorExample示例类:
@Component
public class BeanPostProcessorExample implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor调用postProcessBeforeInitialization方法,参数【"
+ bean.getClass().getSimpleName() + "】【" + beanName + "】");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor调用postProcessAfterInitialization方法,参数【"
+ bean.getClass().getSimpleName() + "】【" + beanName + "】");
return null;
}
}
运行如下代码:
@Test
public void beanLifecycleTest() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.close();
}
得到的日志输出如下:
BeanPostProcessor调用postProcessBeforeInitialization方法,参数【AppConfig$$EnhancerBySpringCGLIB$$32ff2f1c】【appConfig】
BeanPostProcessor调用postProcessAfterInitialization方法,参数【AppConfig$$EnhancerBySpringCGLIB$$32ff2f1c】【appConfig】
21:19:06.041 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'UserBean'
BeanPostProcessor调用postProcessBeforeInitialization方法,参数【UserBean】【UserBean】
BeanPostProcessor调用postProcessAfterInitialization方法,参数【UserBean】【UserBean】
21:19:06.052 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessPerson'
21:19:06.086 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dog'
BeanPostProcessor调用postProcessBeforeInitialization方法,参数【Dog】【dog】
BeanPostProcessor调用postProcessAfterInitialization方法,参数【Dog】【dog】
延迟依赖注入测试
【BusinessPerson】调用BeanNameAware的setBeanName方法
【BusinessPerson】调用BeanFactoryAware的setBeanFactory方法
【BusinessPerson】调用ApplicationContextAware的setApplicationContext方法
BeanPostProcessor调用postProcessBeforeInitialization方法,参数【BusinessPerson】【businessPerson】
【BusinessPerson】调用@PostConstruct的initCustom方法
【BusinessPerson】调用InitializingBean的afterPropertiesSet方法
BeanPostProcessor调用postProcessAfterInitialization方法,参数【BusinessPerson】【businessPerson】
21:19:06.087 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'cat'
BeanPostProcessor调用postProcessBeforeInitialization方法,参数【Cat】【cat】
BeanPostProcessor调用postProcessAfterInitialization方法,参数【Cat】【cat】
21:19:06.088 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'DbcpDataSource'
BeanPostProcessor调用postProcessBeforeInitialization方法,参数【BasicDataSource】【DbcpDataSource】
BeanPostProcessor调用postProcessAfterInitialization方法,参数【BasicDataSource】【DbcpDataSource】
21:19:06.125 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigA[email protected], started on Thu Feb 20 21:19:05 CST 2020
【BusinessPerson】调用@PreDestroy的destroyCustom方法
【BusinessPerson】调用DisposableBean的destroy方法
通过日志可以看到,对于Bean的后置处理器BeanProcessor而言,其中的方法对所有的Bean生效。并且在BusinessPerson类中的方法执行顺序与上述生命周期过程图一致。
对于Bean生命周期的底层详解可以参考:https://www.jianshu.com/p/1dec08d290c1
使用属性配置文件
在默认application.properties配置文件中使用自定义属性
Spring Boot项目中默认使用的是application.properties配置文件,其中添加的已经被定义的属性会被Spring Boot自动读取到上下文中。如果需要在application.properties配置文件中添加自定义的属性,并在项目中引用,则需要首先加入如下的依赖:
<!-- 自定义属性配置依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
这样在application.properties文件中添加的自定义属性就可以在项目中引用了。例如在application.properties配置文件中定义了如下属性:
# 自定义的一些属性
database.drivername=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/springboot_dev
database.username=root
database.password=123456
使用Spring EL表达式将配置文件中的内容赋值到类属性中:
@Component
public class DatabaseProperties {
@Value("${database.drivername}")
private String driverName;
@Value("${database.url}")
private String url;
private String username;
private String password;
public String getDriverName() {
return driverName;
}
public void setDriverName(String driverName) {
System.out.println(driverName);
this.driverName = driverName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
System.out.println(url);
this.url = url;
}
public String getUsername() {
return username;
}
@Value("${database.username}")
public void setUsername(String username) {
System.out.println(username);
this.username = username;
}
public String getPassword() {
return password;
}
@Value("${database.password}")
public void setPassword(String password) {
System.out.println(password);
this.password = password;
}
}
启动Spring Boot项目可以看到日志输出如下:
root
123456
此外,如果需要将自定义的属性配置在其他配置文件中,例如将上述数据库连接的属性配置在了jdbc.properties文件中,那么可以使用@PropertySource注解在Spring Boot启动类中定义需要加载的自定义属性文件,将他加载到Spring上下文中,例如:
@SpringBootApplication(scanBasePackages = {"cn.zyt.springbootlearning.*"})
@MapperScan(basePackages = "cn.zyt.springbootlearning.*", annotationClass = Repository.class)
@PropertySource(value = {"classpath:jdbc.properties"}, ignoreResourceNotFound = true)
public class SpringbootLearningApplication {
// ...
}
此时已然可以找到在jdbc.properties配置文件中的自定义属性并进行加载。而其中的ignoreResourceNotFound属性设置为true, 表示在该配置文件找不到时进行忽略而不会报错。
application.yml默认配置文件
Spring Boot中除了默认的application.properties配置文件之外,还有一个application.yml配置文件,如果使用后者作为配置文件可以将application.properties删除,新建名为application.yml文件即可。yml格式的配置文件支持树型的配置格式,例如如下:
server:
port: 8080
session-timeout: 30
tomcat.max-threads: 0
tomcat.uri-encoding: UTF-8
spring:
datasource:
url : jdbc:mysql://localhost:3306/springboot
username : root
password : root
driverClassName : com.mysql.jdbc.Driver
它是将application.properties文件中的配置项,使用树形结构表示,比较清晰。
条件装配Bean
@Conditional注解支持Spring Boot中Bean的条件装配,也就是在IoC容器在装配Bean会对指定的一些条件进行检查,从而避免一些Bean装配的异常。例如如下代码,在装配DataSource Bean之前,先检查是否存在相应的配置项:
@Bean
@Conditional(DatabaseConditional.class)
public DataSource getDataSourceWithConditional(
@Value("${database.drivername}") String driver,
@Value("${database.url}") String url,
@Value("${database.username}") String username,
@Value("${database.password}") String password) {
Properties properties = new Properties();
properties.setProperty("driver", driver);
properties.setProperty("url", url);
properties.setProperty("username", username);
properties.setProperty("password", password);
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
相应的DatabaseConditional类如下:
public class DatabaseConditional implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
// 获取环境配置
Environment environment = conditionContext.getEnvironment();
// 检查配置文件中是否包含相应的属性
return environment.containsProperty("database.drivername")
&& environment.containsProperty("database.url")
&& environment.containsProperty("database.username")
&& environment.containsProperty("database.password");
}
}
在装配Bean之前matches方法首先读取其上下文环境,判断配置文件中是否包含该Bean需要的属性。如果返回为false,则不装配该Bean。
Bean的作用域
在一般的容器中,Bean存在单例(singleton)和原型(prototype)两种作用域。而在Web容器中,则存在如下四种作用域:
- page,页面
- request,请求
- session,会话
- application,应用
其中对于page,是针对JSP当前页面的作用域,Spring是无法支持的。为了满足各类的作用域,在Spring的作用域中就存在了如下的几种类型:
Bean的作用域类型 | 使用范围 | 描述 |
---|---|---|
singleton | 所有Spring应用 | 默认作用域,IoC容器中只存在单例 |
prototype | 所有Spring应用 | 每当从IoC容器中取出一个Bean时,则新创建一个Bean |
session | Spring Web应用 | HTTP会话 |
application | Spring Web应用 | Web工程声明周期 |
request | Spring Web应用 | Web工程单次请求(request) |
globalSession | Spring Web应用 | 在一个全局HTTP session中,一个Bean定义对应一个胜利。基本不使用 |
以上6周Bean的作用域中,常用的为前四种,下面对单例和原型两种作用域进行一些测试。
首先创建一个Bean,由于没有任何作用域的设置,所以此时为默认的作用域singleton:
@Component
public class ScopeBean {
}
使用如下测试代码,先后从容器中获取两个该类型的Bean,然后进行比较:
@Test
public void beanScopeTest() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ScopeBean scopeBean = context.getBean(ScopeBean.class);
ScopeBean scopeBean1 = context.getBean(ScopeBean.class);
System.out.println(scopeBean == scopeBean1);
}
运行得到的结果为true,说明先后两次得到的ScopeBean类型的Bean指向了同一个实例对象,所以默认在ioC容器中Bean只有一个。下面将ScopeBean修改为:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ScopeBean {
}
运行同样的测试,得到的结果为false,说明在对个该Bean的作用域设置为原型后,每次取出的Bean都是一个新创建的实例。
上面使用的为ConfigurableBeanFactory,其只支持单例和原型两种范围,如果使用Spring MVC环境中的WebApplicationContext来定义作用域,其支持SCOPE_REQUEST, SCOPE_SESSION, SCOPE_APPLICATION的作用域。
使用@Profile
在企业开发的过程中,项目往往会存在开发环境,测试环境,预发环境,和生产环境不同的应用环境,而每套环境中的上下文等是不同的,例如他们会有各自的数据源和相应的数据库,这样就需要在不同的数据库之间进行切换。Spring @Profile注解为此提供了支持。
下面假设存在dev和test两个数据库,可以使用@Profile注解来定义两个不同的Bean:
@Bean("DevDataSource")
@Profile("dev")
public DataSource getDevDataSource() {
Properties properties = new Properties();
properties.setProperty("driver", "com.mysql.jdbc.Driver");
properties.setProperty("url", "jdbc:mysql://localhost:3306/dev");
properties.setProperty("username", "root");
properties.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
@Bean("TestDataSource")
@Profile("test")
public DataSource getTestDataSource() {
Properties properties = new Properties();
properties.setProperty("driver", "com.mysql.jdbc.Driver");
properties.setProperty("url", "jdbc:mysql://localhost:3306/test");
properties.setProperty("username", "root");
properties.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
在Spring中存在两个参数来启动并修改对应的profile,分别是spring.profiles.active和spring.profiles.default。这两个属性在默认没有配置的情况下,Spring将不会启动profile机制,这意味着被@Profile标注的Bean不会被装配到IoC容器中。Spring会先判断是否存在spring.profiles.active配置后,在去查找spring.profiles.default。
在Java启动项目中,只需要加入如下的启动配置,就能够启动相应环境的bean:
JAVA_OPTS="-Dspring.profiles.active=dev"
在IDEA中启动时,可以在运行参数中对其进行设置(这里是Java Application):
在IoCTest类中的main方法进行测试:
public static void main(String[] args) throws SQLException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.getBean(DataSource.class);
}
可以看到如下的日志输出:
12:05:35.209 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'DevDataSource'
此时将DevDataSource Bean进行了装配,而没有装配TestDataSource。
此外,Spring Boot中支持在不同环境中配置文件的切换,对于dev环境下的配置文件可以新增application-dev.properties,然后在启动参数中设置 -Dspring.profiles.active=dev时,就会对应的加载application-dev.properties的配置文件。