使用 maven 创建项目,导入 Spring 依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.16</version>
</dependency>
一、容器相关注解
1.1 组件添加相关注解
1.1.1 @Configuration
**@Configuration**
注解的作用:表示这个类是一个 Spring 配置类。
在 XML 配置文件中,我们需要创建一个beans.xml
文件,用来标识这是一个 Spring 的配置文件: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">
</beans>
在注解开发中,我们就不需要创建这一个 XML 配置文件,取而代之的是使用@Configuration
注解类,使用如下:
// @Configuration 的作用:标识这个类是一个注解类
@Configuration
public class SpringConfig {
}
1.2.2 AnnotationConfigApplicationContext
使用 XML 配置方式获取ApplicationContext
:
public static void main(String[] args) {
ApplicationContext xmlContext = new ClassPathXmlApplicationContext("beans.xml");
}
使用注解方式获取ApplicationContext
:
public static void main(String[] args) {
ApplicationContext classContext = new AnnotationConfigApplicationContext(SpringConfig.class);
}
1.2.3 @Bean
**@Bean**
注解作用:将这个对象注入到容器中,可以被 Spring 框架识别。
使用 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.lbj.bean.Person">
<property name="id" value="1"/>
<property name="name" value="大侠"/>
</bean>
</beans>
获取到这个 Person 对象:
public static void xml() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person = (Person) context.getBean("person");
System.out.println(person);
}
使用注解方式注入实体类:
@Configuration
public class SpringConfig {
@Bean
public Person person() {
return new Person(1, "小侠");
}
}
获取到这个 Person 对象:
public static void annotation() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Person person = context.getBean(Person.class);
System.out.println(person);
// 获取Spring中注册的类型
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
在 XML 配置中,id
属性指向这个实体类的别名,class
属性指向这个实体类的类型。我们可以通过别名或者类型来获取到这个对象。
在注解配置中,方法返回值等于 XML 配置中class
属性,而方法名等于 XML 配置中id
属性。因此可以通过方法名获取到这个实体类。
如果要修改这个默认的名称,而又不想修改方法名,可以在@Bean
注解上添加参数即可:
@Configuration
public class SpringConfig {
@Bean({"1111"}) // 修改默认别名
public Person person01() {
return new Person(1, "小侠");
}
}
1.2.4 @ComponentScan
**@ComponentScan**
注解作用:扫描指定包下的全部标有 @Component 的类,并注册成 bean。**@ComponentScans**
注解作用:可添加多个 @ComponentScan 注解。
在 XML 配置中,使用context:component-scan
标签扫描包路径:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- context:component-scan 标签作用:扫描指定包下的全部标有@Component 的类,并注册成bean -->
<!-- base-package:指定包扫描的位置 -->
<!-- use-default-filters:是否使用默认的规则,默认值为true -->
<!-- include-filter: 扫描哪些内容 -->
<!-- exclude-filter:不扫描哪些内容 -->
<context:component-scan base-package="com.lbj"
use-default-filters="false">
<!-- type:annotation:指定扫描使用某个注解的类 -->
<!-- type:aspectj:指定扫描AspectJ表达式相匹配的类 -->
<!-- type:assignable:指定扫描某个接口派生的类 -->
<!-- type:custom:指定扫描自定义的实现了org.springframework.core.type.filter.TypeFilter接口的类 -->
<!-- type:regex:指定扫描符合正则表达式的类 -->
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
在注解配置中,使用@ComponentScan
配置包扫描路径:
@Configuration
@ComponentScan(
value = "com.lbj",
useDefaultFilters = false,
includeFilters = {
@ComponentScan.Filter(
type = FilterType.ANNOTATION,
value = {Controller.class,Service.class}
),
@ComponentScan.Filter(
type = FilterType.ANNOTATION,
value = {Service.class}
)
},
excludeFilters = {})
public class SpringConfig {}
上面需要注意的是,如果需要使用includeFilters
和excludeFilters
,则必须要将useDefaultFilters
的值设为 false,表示不使用默认的规则,这样我们自定义的规则才会生效。
下面是属性@ComponentScan
注解的属性说明:
注解属性 | XML配置属性 | 属性说明 |
---|---|---|
value | base-package | 指定包扫描的位置 |
useDefaultFilters | use-default-filters | 默认为true,如果使用includeFilters和excludeFilters需要设置为false,否则不会生效 |
includeFilters | context:include-filter | 指定内容中,扫描哪些内容 |
excludeFilters | context:exclude-filter | 指定内容中,不扫描哪些内容 |
includeFilters
和excludeFilters
过滤器具有 5 个类型(type),说明如下:
Type类型 | 说明 |
---|---|
annotation | 指定扫描使用某个注解的类 |
aspectj | 指定扫描AspectJ表达式相匹配的类 |
assignable | 指定扫描某个接口派生的类 |
custom | 指定扫描自定义的实现了org.springframework.core.type.filter.TypeFilter接口的类 |
regex | 指定扫描符合正则表达式的类 |
常用 过滤规则:annotation_根据指定的注解类型匹配
@ComponentScan.Filter(
type = FilterType.ANNOTATION,
value = {Controller.class,Service.class}
)
常用 过滤规则:assignable_根据指定的类匹配,指定某一个特定的类
@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
value = {BookMapper.class}
)
过滤规则:custom_自定义规则
使用FilterType.CUSTOM
自定义规则时,需要实现TypeFilter
接口,源码如下:
所以首先创建自己的规则,实现TypeFilter
接口:
public class MyTypeFilter implements TypeFilter {
/**
* 匹配规则
*
* @param metadataReader 扫描的当前类的信息
* @param metadataReaderFactory 可以获取所有类的信息
* @return 返回true表示符合规则,返回false表示不合符规则
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// 获取注解信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 获取类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 获取资源信息()
Resource resource = metadataReader.getResource();
// 获取类的类名
String className = classMetadata.getClassName();
// 返回类名中含有“Mapper”的结果
return className.contains("Mapper");
}
}
使用自定义规则:
@ComponentScan.Filter(
type = FilterType.CUSTOM,
value = {MyTypeFilter.class}
)
最终注入到容器的结果为:
// @BookMapper
bookMapper
不常用 过滤规则:aspectj_根据指定的aspectj表达式匹配
过滤规则:regex_使用正则表达式匹配
1.2.4 @Component、@Controller、@Service、@Repository
被@Component
标记的类在配置包扫描路径后,默认会被 Spring 容器扫描,并注册成 bean。默认别名是类名首字母小写。
@Controller
、@Service
、@Repository
与@Component
作用没有任何区别,只是别名不同,方便程序员标记不同的模块,源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
@AliasFor(annotation = Component.class)
就表示@Controller
是@Component
注解的别名。
1.2.5 @Scope
**@Scope**
注解作用:指定 Bean 的作用域。
@Scope 的属性
@Scope 属性值 | 字符串内容 | 说明 |
---|---|---|
ConfigurableBeanFactory.SCOPE_SINGLETON | singleton | 默认值,单实例。 IOC容器启动时会调用方法创建对象放入到IOC容器中。以后每次获取就是直接从容器中获取(map.get())。 |
ConfigurableBeanFactory.SCOPE_PROTOTYPE | prototype | IOC容器启动时并不会调用方法创建对象放入到容器中,而是每次获取的时候才会调用方法创建对象。 |
WebApplicationContext.SCOPE_REQUEST | request | 同一次请求创建一个实例 |
WebApplicationContext.SCOPE_SESSION | session | 同一个session创建一个实例 |
// @Scope 的四种参数
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope(WebApplicationContext.SCOPE_REQUEST)
@Scope(WebApplicationContext.SCOPE_SESSION)
@Bean({"1111"})
public Person person01() {
return new Person(1, "小侠");
}
// 如果@Scope是singleton,则在这里会调用一次person()方法
// 如果@Scope是prototype,则在这里不会调用person()方法
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
// 如果@Scope是singleton,则在这里不会调用person()方法
// 如果@Scope是prototype,则在这里会调用一次person()方法
Person person = context.getBean(Person.class);
// 如果@Scope是singleton,则在这里不会调用person()方法
// 如果@Scope是prototype,则在这里会调用一次person()方法
Person person2 = context.getBean(Person.class);
1.2.6 @Lazy
**@Lazy**
注解的作用:因为单实例@Bean
会在 IOC 容器创建时会进行注入。如果我们不希望在 IOC 容器创建时注入,而是希望在使用时注入,则可以使用这个注解,表示懒加载的意思。
@Lazy // 懒加载,针对单实例
@Bean({"person"})
public Person person() {
System.out.println("调用方法一次");
return new Person(1, "小侠");
}
1.2.7 @Conditional
**@Conditional**
注解的作用:按照一定的条件判断,满足条件给容器注册 bean。
通过ApplicationContext
获取当前系统名称:
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
System.out.println(property); // 输出 Windows 10
现在有以下 2 个Person
注册类,我们需要在 Window 系统环境下注册window
名称的类,需要在 Linux 系统环境下注册linux
类:
@Configuration
public class SpringConfig {
@Bean("linux")
public Person linux() {
return new Person(1, "linux");
}
@Bean("window")
public Person window() {
return new Person(2, "window");
}
}
可通过下面方法查看注册信息,默认情况下 2 个值都会被注册进容器:
public static void annotation() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Map<String, Person> personMap = context.getBeansOfType(Person.class);
// {linux=Person{id=1, name='linux'}, window=Person{id=2, name='window'}}
System.out.println(personMap);
}
如果按照上面的需求我们就需要使用到@Conditional
注解:
// 该注解可以标注在类和方法上
// 标注在方法上表示在满足条件时才会调用该方法注册
// 标注在类上表示满足条件时,该类下的所有Bean才会被注入
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition} classes that must {@linkplain Condition#matches match}
* in order for the component to be registered.
* 如果要使用@Conditional注解,就需要实现Condition接口
*/
Class<? extends Condition>[] value();
}
- @Conditional 注解可以标注在类和方法上;
- @Conditional 标注在方法上表示在满足条件时才会调用该方法注册;
- @Conditional 标注在类上表示满足条件时,该类下的所有Bean才会被注入;
- 如果要使用 @Conditional 注解,属性值就需要实现 Condition 接口。
下面是WindowCondition
实现类,作用是当当前环境是Window
时,返回true
:
public class WindowCondition implements Condition {
/**
* @param context 判断条件能使用到的上下文
* @param metadata 注释信息
* @return 返回 true 表示条件满足
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取到 IOC 使用的 beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 获取到类加载器
ClassLoader classLoader = context.getClassLoader();
// 获取到 bean 定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
// 查找是否包含某个Bean的注册信息
registry.containsBeanDefinition("");
ResourceLoader resourceLoader = context.getResourceLoader();
// 获取到当前环境
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if (property == null) return false;
return property.contains("Window");
}
}
下面是LinuxCondition
实现类,作用是当当前环境是Linux
时,返回true
:
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if (property == null)
return false;
return property.contains("linux");
}
}
使用@Conditional
注解:
@Configuration
public class SpringConfig {
@Conditional({LinuxCondition.class})
@Bean("linux")
public Person linux() {
return new Person(1, "linux");
}
@Conditional(WindowCondition.class)
@Bean("window")
public Person window() {
return new Person(2, "window");
}
}
再次运行测试代码就可以看到只注册了其中一个 Bean。
public static void annotation() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Map<String, Person> personMap = context.getBeansOfType(Person.class);
System.out.println(personMap);
}
输出结果:
{window=Person{id=2, name='window'}}
如果需要测试linux
是否成功,可以在 Linux 系统环境下进行测试,或者使用VM options
模拟数据测试:
在VM options
中输入下面命令:
-Dos.name=linux
再次运行测试结果如下:
public static void annotation() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Map<String, Person> personMap = context.getBeansOfType(Person.class);
System.out.println(personMap);
}
输出结果:
{linux=Person{id=1, name='linux'}}
1.2.8 @Import
在 Spring 中,给容器中注册组件共有 3 种方式:
- 包扫描+组件标注注解(@Controller/@Service/@Repository/@Component):适合自己写的类;
- @Bean:适合导入的第三方包里面的组件;
- @Import:快速给容器中导入一个组件。
@Import 快速导入
定义类,添加@Import
注解:
public class Blue { }
public class Green { }
public class Blue { }
@Configuration
@Import({Red.class, Green.class, Blue.class})
public class SpringConfig {}
测试结果如下:
public static void annotation() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] names = context.getBeanDefinitionNames();
System.out.println(Arrays.asList(names));
}
// 打印结果如下:
com.lbj.bean.Red,
com.lbj.bean.Green,
com.lbj.bean.Blue
通过上面可以知道,通过@Import
快速导入的组件的id
默认值是该类的全路径。
ImportSelector 接口
实现ImportSelector
接口:
public class MyImportSelector implements ImportSelector {
/**
* @param importingClassMetadata 当前标注@Import注解的类的所有注解信息
* @return 返回需要导入类的全类名
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
Green.class.getCanonicalName(),
Red.class.getCanonicalName()
};
}
@Override
public Predicate<String> getExclusionFilter() {
return ImportSelector.super.getExclusionFilter();
}
}
使用ImportSelector
实现类:
@Configuration
@Import({MyImportSelector.class, Blue.class})
public class SpringConfig {}
测试结果如下:
public static void annotation() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println("==> " + name);
}
}
// 打印结果如下:
==> com.lbj.bean.Green
==> com.lbj.bean.Red
==> com.lbj.bean.Blue
ImportBeanDefinitionRegistrar 接口
实现ImportBeanDefinitionRegistrar
接口:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 当前类的所有注解信息
* @param registry 通过该对象注册Bean
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 判断是否存在这个Bean
boolean hasGreen = registry.containsBeanDefinition("com.lbj.bean.Green");
boolean hasRed = registry.containsBeanDefinition("com.lbj.bean.Red");
boolean hasBlue = registry.containsBeanDefinition("com.lbj.bean.Blue");
// 当上面3个Bean都存在时,注册另外一个Bean
if (hasGreen && hasRed && hasBlue) {
// 设置Bean的所有信息,包括Scope/Lazy等
BeanDefinition beanDefinition = new RootBeanDefinition(Yellow.class);
// 注册一个Bean,并指定id
registry.registerBeanDefinition("yellow", beanDefinition);
}
}
}
使用ImportBeanDefinitionRegistrar
实现类:
@Configuration
@Import({Blue.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class SpringConfig {}
测试结果如下:
public static void annotation() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println("==> " + name);
}
}
// 打印结果如下:
==> com.lbj.bean.Blue
==> com.lbj.bean.Green
==> com.lbj.bean.Red
==> yellow
1.2.9 FactoryBean
上面介绍了 3 种注入 Bean 的方式,使用FactoryBean
方式可以实现第 4 种注册 Bean 的方式,这种方式是其他框架和 Spring 整合的常用方式。
实现FactoryBean
接口:
public class ColorFactoryBean implements FactoryBean<Color> {
// 返回类对象
@Override
public Color getObject() throws Exception {
return new Color();
}
// 返回类类型
@Override
public Class<?> getObjectType() {
return Color.class;
}
// 是否单例
@Override
public boolean isSingleton() {
return true;
}
}
注册ColorFactoryBean
:
@Configuration
public class SpringConfig {
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
}
测试结果如下:
public static void annotation() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println("==> " + name);
}
Object obj1 = context.getBean("colorFactoryBean");
System.out.println("obj1 = " + obj1);
Object obj2 = context.getBean("&colorFactoryBean");
System.out.println("obj2 = " + obj2);
}
// 打印结果如下:
==> colorFactoryBean
obj1 = com.lbj.bean.Color@35ef1869
obj2 = com.lbj.bean.ColorFactoryBean@c33b74f
从上面结果可知:
- 打印1的结果说明注册了一个 id 名为
colorFactoryBean
的Bean。 - 打印2的结果说明通过 id 获取 Bean 并不是
FactoryBean
本身,而是该接口的泛型对象。 - 打印3的结果说明要想获取
FactoryBean
本身,需要在 id 前面添加&
符号。
这是因为通过FactoryBean
注入的类,会默认调用其getObject()
方法,所以返回泛型对象:
@Override
public Color getObject() throws Exception {
return new Color();
}
而添加&+id
方式获取FactoryBean
本身,也是因为FactoryBean
接口中已有的默认定义:
1.2 Bean 生命周期
1.2.1 指定 init() 和 destory() 方法
Bean 的生命周期遵循“创建”-“初始化”-“销毁”过程。
创建一个Car
类:
public class Car {
public Car() {
System.out.println("Car - 构造方法调用");
}
public void init() {
System.out.println("Car - 初始化方法");
}
public void destory() {
System.out.println("Car - 销毁方法");
}
}
在 XML 配置中,我们定义一个 Bean 的初始化和销毁方法如下:
<bean id="car" class="com.lbj.bean.Car"
init-method="init"
destroy-method="destory"/>
init-method
和destroy-method
分别就是指定初始化和销毁方法的属性,要求**指定的方法不能有参数**
。
在注解注入中,通过@Bean
注解的initMethod
和destroyMethod
指定初始化和销毁方法:
@Configuration
public class SpringConfig {
@Bean(initMethod = "init", destroyMethod = "destory")
public Car car() {
return new Car();
}
}
测试结果如下:
public static void annotation() {
// 注意这里是 AnnotationConfigApplicationContext,而不是使用 ApplicationContext
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
System.out.println("IOC容器创建");
Car car = (Car) context.getBean("car");
// 关闭IOC容器
context.close();
}
// 打印结果如下:
Car - 构造方法调用
Car - 初始化方法
IOC容器创建
Car - 销毁方法
针对单实例和多实例的生命周期如下:
创建对象时:
- 单实例:在容器启动的时候创建对象;
- 多实例:在每次获取的时候创建对象;
初始化时:
- 单实例:对象创建完成,并赋值好后,调用初始化方法;
- 多实例:和单实例一样;
销毁时:
- 单实例:IOC容器销毁时销毁;
- 多实例:IOC容器不管理多实例,所以IOC容器销毁时不会销毁,如需要销毁则需要我们手动销毁。
1.2.2 实现 InitializingBean 和 DisposableBean 接口
让 Bean 实现InitializingBean
和DisposableBean
接口:
@Component
public class Car implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
// Properties属性设置好后调用该方法,就是初始化方法
}
@Override
public void destroy() throws Exception {
// 销毁方法
}
}
1.2.3 使用 JSR250
@Component
public class Cat {
// Properties属性设置好后调用该方法
@PostConstruct
public void init() {}
// Bean销毁之前调用
@PreDestroy
public void destory() {}
}
1.2.4 后置处理器 BeanPostProcessor
@Component
public class Dog implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 在所有初始化方法执行之前调用
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 在所有初始化方法执行之后调用
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
为什么后置处理器一定会在所有初始化方法执行之前或之后调用呢?
通过在postProcessBeforeInitialization
方法上打断点,通过方法栈我们可以找到AbstractAutowireCapableBeanFactory
类的initializeBean()
方法,该方法源码如下:
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
} else {
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 执行后置处理器Before方法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// 执行初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
// 执行后置处理器After方法
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
而进入invokeInitMethods
方法中我们可以发现,这个方法主要是执行几个初始化的方法,包括afterPropertiesSet
和initMethod
等方法。
因此通过上面代码我们可以知道,3个方法的执行顺序如下:
- applyBeanPostProcessorsBeforeInitialization
- invokeInitMethods
- applyBeanPostProcessorsAfterInitialization
另外,在执行initializeBean()
方法的上一步时,如图:
而在populateBean
方法的作用就是设置各种 property 属性。因此后置处理器一定是在设置 Bean 属性的后面执行。
1.3 组件赋值相关注解
1.3.1 @Value
**@Value**
注解的作用:为 Bean 属性赋值。
在 XML 注入中,为属性注入值如下代码:
<bean id="person" class="com.lbj.bean.Person">
<property name="id" value="1"/>
<property name="name" value="大侠"/>
</bean>
在注解注入中,为属性注入值如下代码:
public class Person {
@Value("1")
private Integer id;
@Value("张三")
private String name;
}
@Value
中可写的类型有:
- 基本数值。如“1”,“张三”等;
- SpEL表达式。如:#{ 1 + 1};
- ${ }。取出配置文件
properties
中的值(在运行环境里面的值)。
1.3.2 @PropertySource 和 @PropertySources
@PropertySource
注解的作用:引入 properties 文件到环境变量中。@PropertySources
注解的作用:存放多个@PropertySource
注解。
新建外部配置文件person.properties
:
person.address=beijing
在 XML 注入时,我们需要通过property-placeholder
指定外部配置文件:
<context:property-placeholder location="classpath:person.properties"/>
在注解注入时,我们需要通过@PropertySource
注入外部文件:
@Configuration
@ComponentScan("com.lbj")
// 注意:文件前需要添加 “/”
@PropertySource("classpath:/person.properties")
public class SpringConfig {}
@Component
public class Person {
@Value("1") // 普通注入
private Integer id;
@Value("#{1 + 1}") // SpEL表达式
private String name;
@Value("${person.address}") // 引入环境变量中的值
private String address;
}
外部配置文件person.properties
被引入之后,就可以在环境变量中获取这个值:
public static void annotation() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
ConfigurableEnvironment environment = context.getEnvironment();
System.out.println(environment.getProperty("person.address"));
}
1.3.2 @Autowired
@Autowired
自动注入的步骤如下:
- 默认优先
按照类型
去容器中找对应的组件:
2:如果找到多个相同类型的组件,再将属性的名称作为组件的applicationContext.getBean(BookDao.class);
id
去容器中查找: ```java @Autowired private BookDao bookDao01;
// 按照属性名称 “bookDao01” 去容器中查找 applicationContext.getBean(“bookDao01”);
3. 使用`@Qualifier`注解强制使用该注解的属性值作为组件的 id,而不是使用属性名:
```java
@Qualifier("bookDao")
@Autowired
private BookDao bookDao01;
// 因为 @Qualifier,所以使用 bookDao 去查找组件,而不是使用 bookDao01
applicationContext.getBean("bookDao");
- 自动装配默认情况是一定要将属性赋值好,没有就报错;(也就是说一定要找到组件,否则就报错)。
如果希望能找到组件就找到,没找到也不报错可使用下面方法:(第4步的解决方案)
@Service
public class BookService {
// required值默认是true,修改为false即可
@Autowired(required = false)
private BookMapper bookMapper;
}
如果具有多个相同的 id,我们可以使用
@Primary
注解,这个注解表示首选的意思,也就是说多个相同的id,默认使用这个注解标记方法或类。
@Primary
注解也只能用来标注方法和类。
@Qualifier
和@Primary
的区别在于:前者是强制使用的,优先级很高;后者是默认的,再不与前者冲突时,优先使用该注解标记的方法或类:
@Primary
@Bean({"person"})
public Person person() {
return new Person(1, "小侠");
}