使用 maven 创建项目,导入 Spring 依赖:

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-context</artifactId>
  4. <version>5.3.16</version>
  5. </dependency>

一、容器相关注解

1.1 组件添加相关注解

1.1.1 @Configuration

**@Configuration**注解的作用:表示这个类是一个 Spring 配置类。

在 XML 配置文件中,我们需要创建一个beans.xml文件,用来标识这是一个 Spring 的配置文件:
QQ截图20220315144004.png
beans.xml文件默认内容如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. </beans>

在注解开发中,我们就不需要创建这一个 XML 配置文件,取而代之的是使用@Configuration注解类,使用如下:
QQ截图20220315144511.png

  1. // @Configuration 的作用:标识这个类是一个注解类
  2. @Configuration
  3. public class SpringConfig {
  4. }

1.2.2 AnnotationConfigApplicationContext

使用 XML 配置方式获取ApplicationContext

  1. public static void main(String[] args) {
  2. ApplicationContext xmlContext = new ClassPathXmlApplicationContext("beans.xml");
  3. }

使用注解方式获取ApplicationContext:

  1. public static void main(String[] args) {
  2. ApplicationContext classContext = new AnnotationConfigApplicationContext(SpringConfig.class);
  3. }

1.2.3 @Bean

**@Bean**注解作用:将这个对象注入到容器中,可以被 Spring 框架识别。

使用 XML 配置方式注入实体类:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="person" class="com.lbj.bean.Person">
  6. <property name="id" value="1"/>
  7. <property name="name" value="大侠"/>
  8. </bean>
  9. </beans>

获取到这个 Person 对象:

  1. public static void xml() {
  2. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  3. Person person = (Person) context.getBean("person");
  4. System.out.println(person);
  5. }

使用注解方式注入实体类:

  1. @Configuration
  2. public class SpringConfig {
  3. @Bean
  4. public Person person() {
  5. return new Person(1, "小侠");
  6. }
  7. }

获取到这个 Person 对象:

  1. public static void annotation() {
  2. ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
  3. Person person = context.getBean(Person.class);
  4. System.out.println(person);
  5. // 获取Spring中注册的类型
  6. String[] names = context.getBeanDefinitionNames();
  7. for (String name : names) {
  8. System.out.println(name);
  9. }
  10. }

在 XML 配置中,id属性指向这个实体类的别名,class属性指向这个实体类的类型。我们可以通过别名或者类型来获取到这个对象。

在注解配置中,方法返回值等于 XML 配置中class属性,而方法名等于 XML 配置中id属性。因此可以通过方法名获取到这个实体类。

如果要修改这个默认的名称,而又不想修改方法名,可以在@Bean注解上添加参数即可:

  1. @Configuration
  2. public class SpringConfig {
  3. @Bean({"1111"}) // 修改默认别名
  4. public Person person01() {
  5. return new Person(1, "小侠");
  6. }
  7. }

1.2.4 @ComponentScan

**@ComponentScan**注解作用:扫描指定包下的全部标有 @Component 的类,并注册成 bean。
**@ComponentScans**注解作用:可添加多个 @ComponentScan 注解。

在 XML 配置中,使用context:component-scan标签扫描包路径:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context
  8. http://www.springframework.org/schema/context/spring-context.xsd">
  9. <!-- context:component-scan 标签作用:扫描指定包下的全部标有@Component 的类,并注册成bean -->
  10. <!-- base-package:指定包扫描的位置 -->
  11. <!-- use-default-filters:是否使用默认的规则,默认值为true -->
  12. <!-- include-filter: 扫描哪些内容 -->
  13. <!-- exclude-filter:不扫描哪些内容 -->
  14. <context:component-scan base-package="com.lbj"
  15. use-default-filters="false">
  16. <!-- type:annotation:指定扫描使用某个注解的类 -->
  17. <!-- type:aspectj:指定扫描AspectJ表达式相匹配的类 -->
  18. <!-- type:assignable:指定扫描某个接口派生的类 -->
  19. <!-- type:custom:指定扫描自定义的实现了org.springframework.core.type.filter.TypeFilter接口的类 -->
  20. <!-- type:regex:指定扫描符合正则表达式的类 -->
  21. <context:include-filter type="annotation"
  22. expression="org.springframework.stereotype.Controller"/>
  23. <context:exclude-filter type="annotation"
  24. expression="org.springframework.stereotype.Controller"/>
  25. </context:component-scan>
  26. </beans>

在注解配置中,使用@ComponentScan配置包扫描路径:

  1. @Configuration
  2. @ComponentScan(
  3. value = "com.lbj",
  4. useDefaultFilters = false,
  5. includeFilters = {
  6. @ComponentScan.Filter(
  7. type = FilterType.ANNOTATION,
  8. value = {Controller.class,Service.class}
  9. ),
  10. @ComponentScan.Filter(
  11. type = FilterType.ANNOTATION,
  12. value = {Service.class}
  13. )
  14. },
  15. excludeFilters = {})
  16. public class SpringConfig {}

上面需要注意的是,如果需要使用includeFiltersexcludeFilters,则必须要将useDefaultFilters的值设为 false,表示不使用默认的规则,这样我们自定义的规则才会生效。

下面是属性@ComponentScan注解的属性说明:

注解属性 XML配置属性 属性说明
value base-package 指定包扫描的位置
useDefaultFilters use-default-filters 默认为true,如果使用includeFilters和excludeFilters需要设置为false,否则不会生效
includeFilters context:include-filter 指定内容中,扫描哪些内容
excludeFilters context:exclude-filter 指定内容中,不扫描哪些内容

includeFiltersexcludeFilters过滤器具有 5 个类型(type),说明如下:

Type类型 说明
annotation 指定扫描使用某个注解的类
aspectj 指定扫描AspectJ表达式相匹配的类
assignable 指定扫描某个接口派生的类
custom 指定扫描自定义的实现了org.springframework.core.type.filter.TypeFilter接口的类
regex 指定扫描符合正则表达式的类

常用 过滤规则:annotation_根据指定的注解类型匹配

  1. @ComponentScan.Filter(
  2. type = FilterType.ANNOTATION,
  3. value = {Controller.class,Service.class}
  4. )

常用 过滤规则:assignable_根据指定的类匹配,指定某一个特定的类

  1. @ComponentScan.Filter(
  2. type = FilterType.ASSIGNABLE_TYPE,
  3. value = {BookMapper.class}
  4. )

过滤规则:custom_自定义规则

使用FilterType.CUSTOM自定义规则时,需要实现TypeFilter接口,源码如下:
QQ截图20220315163343.png
所以首先创建自己的规则,实现TypeFilter接口:

  1. public class MyTypeFilter implements TypeFilter {
  2. /**
  3. * 匹配规则
  4. *
  5. * @param metadataReader 扫描的当前类的信息
  6. * @param metadataReaderFactory 可以获取所有类的信息
  7. * @return 返回true表示符合规则,返回false表示不合符规则
  8. */
  9. @Override
  10. public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
  11. throws IOException {
  12. // 获取注解信息
  13. AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
  14. // 获取类信息
  15. ClassMetadata classMetadata = metadataReader.getClassMetadata();
  16. // 获取资源信息()
  17. Resource resource = metadataReader.getResource();
  18. // 获取类的类名
  19. String className = classMetadata.getClassName();
  20. // 返回类名中含有“Mapper”的结果
  21. return className.contains("Mapper");
  22. }
  23. }

使用自定义规则:

  1. @ComponentScan.Filter(
  2. type = FilterType.CUSTOM,
  3. value = {MyTypeFilter.class}
  4. )

最终注入到容器的结果为:

  1. // @BookMapper
  2. bookMapper

不常用 过滤规则:aspectj_根据指定的aspectj表达式匹配

过滤规则:regex_使用正则表达式匹配

1.2.4 @Component、@Controller、@Service、@Repository

@Component标记的类在配置包扫描路径后,默认会被 Spring 容器扫描,并注册成 bean。默认别名是类名首字母小写。

@Controller@Service@Repository@Component作用没有任何区别,只是别名不同,方便程序员标记不同的模块,源码如下:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Component
  5. public @interface Controller {
  6. /**
  7. * The value may indicate a suggestion for a logical component name,
  8. * to be turned into a Spring bean in case of an autodetected component.
  9. * @return the suggested component name, if any (or empty String otherwise)
  10. */
  11. @AliasFor(annotation = Component.class)
  12. String value() default "";
  13. }

@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创建一个实例
  1. // @Scope 的四种参数
  2. @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
  3. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  4. @Scope(WebApplicationContext.SCOPE_REQUEST)
  5. @Scope(WebApplicationContext.SCOPE_SESSION)
  6. @Bean({"1111"})
  7. public Person person01() {
  8. return new Person(1, "小侠");
  9. }
  1. // 如果@Scope是singleton,则在这里会调用一次person()方法
  2. // 如果@Scope是prototype,则在这里不会调用person()方法
  3. ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
  4. // 如果@Scope是singleton,则在这里不会调用person()方法
  5. // 如果@Scope是prototype,则在这里会调用一次person()方法
  6. Person person = context.getBean(Person.class);
  7. // 如果@Scope是singleton,则在这里不会调用person()方法
  8. // 如果@Scope是prototype,则在这里会调用一次person()方法
  9. Person person2 = context.getBean(Person.class);

1.2.6 @Lazy

**@Lazy**注解的作用:因为单实例@Bean会在 IOC 容器创建时会进行注入。如果我们不希望在 IOC 容器创建时注入,而是希望在使用时注入,则可以使用这个注解,表示懒加载的意思。

  1. @Lazy // 懒加载,针对单实例
  2. @Bean({"person"})
  3. public Person person() {
  4. System.out.println("调用方法一次");
  5. return new Person(1, "小侠");
  6. }

1.2.7 @Conditional

**@Conditional**注解的作用:按照一定的条件判断,满足条件给容器注册 bean。

通过ApplicationContext获取当前系统名称:

  1. ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
  2. Environment environment = context.getEnvironment();
  3. String property = environment.getProperty("os.name");
  4. System.out.println(property); // 输出 Windows 10

现在有以下 2 个Person注册类,我们需要在 Window 系统环境下注册window名称的类,需要在 Linux 系统环境下注册linux类:

  1. @Configuration
  2. public class SpringConfig {
  3. @Bean("linux")
  4. public Person linux() {
  5. return new Person(1, "linux");
  6. }
  7. @Bean("window")
  8. public Person window() {
  9. return new Person(2, "window");
  10. }
  11. }

可通过下面方法查看注册信息,默认情况下 2 个值都会被注册进容器:

  1. public static void annotation() {
  2. ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
  3. Map<String, Person> personMap = context.getBeansOfType(Person.class);
  4. // {linux=Person{id=1, name='linux'}, window=Person{id=2, name='window'}}
  5. System.out.println(personMap);
  6. }

如果按照上面的需求我们就需要使用到@Conditional注解:

  1. // 该注解可以标注在类和方法上
  2. // 标注在方法上表示在满足条件时才会调用该方法注册
  3. // 标注在类上表示满足条件时,该类下的所有Bean才会被注入
  4. @Target({ElementType.TYPE, ElementType.METHOD})
  5. @Retention(RetentionPolicy.RUNTIME)
  6. @Documented
  7. public @interface Conditional {
  8. /**
  9. * All {@link Condition} classes that must {@linkplain Condition#matches match}
  10. * in order for the component to be registered.
  11. * 如果要使用@Conditional注解,就需要实现Condition接口
  12. */
  13. Class<? extends Condition>[] value();
  14. }
  • @Conditional 注解可以标注在类和方法上;
  • @Conditional 标注在方法上表示在满足条件时才会调用该方法注册;
  • @Conditional 标注在类上表示满足条件时,该类下的所有Bean才会被注入;
  • 如果要使用 @Conditional 注解,属性值就需要实现 Condition 接口。

下面是WindowCondition实现类,作用是当当前环境是Window时,返回true

  1. public class WindowCondition implements Condition {
  2. /**
  3. * @param context 判断条件能使用到的上下文
  4. * @param metadata 注释信息
  5. * @return 返回 true 表示条件满足
  6. */
  7. @Override
  8. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  9. // 获取到 IOC 使用的 beanFactory
  10. ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
  11. // 获取到类加载器
  12. ClassLoader classLoader = context.getClassLoader();
  13. // 获取到 bean 定义的注册类
  14. BeanDefinitionRegistry registry = context.getRegistry();
  15. // 查找是否包含某个Bean的注册信息
  16. registry.containsBeanDefinition("");
  17. ResourceLoader resourceLoader = context.getResourceLoader();
  18. // 获取到当前环境
  19. Environment environment = context.getEnvironment();
  20. String property = environment.getProperty("os.name");
  21. if (property == null) return false;
  22. return property.contains("Window");
  23. }
  24. }

下面是LinuxCondition实现类,作用是当当前环境是Linux时,返回true

  1. public class LinuxCondition implements Condition {
  2. @Override
  3. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  4. Environment environment = context.getEnvironment();
  5. String property = environment.getProperty("os.name");
  6. if (property == null)
  7. return false;
  8. return property.contains("linux");
  9. }
  10. }

使用@Conditional注解:

  1. @Configuration
  2. public class SpringConfig {
  3. @Conditional({LinuxCondition.class})
  4. @Bean("linux")
  5. public Person linux() {
  6. return new Person(1, "linux");
  7. }
  8. @Conditional(WindowCondition.class)
  9. @Bean("window")
  10. public Person window() {
  11. return new Person(2, "window");
  12. }
  13. }

再次运行测试代码就可以看到只注册了其中一个 Bean。

  1. public static void annotation() {
  2. ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
  3. Map<String, Person> personMap = context.getBeansOfType(Person.class);
  4. System.out.println(personMap);
  5. }
  6. 输出结果:
  7. {window=Person{id=2, name='window'}}

如果需要测试linux是否成功,可以在 Linux 系统环境下进行测试,或者使用VM options模拟数据测试:
QQ截图20220316105216.png
1647399271(1).png
VM options中输入下面命令:

  1. -Dos.name=linux

再次运行测试结果如下:

  1. public static void annotation() {
  2. ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
  3. Map<String, Person> personMap = context.getBeansOfType(Person.class);
  4. System.out.println(personMap);
  5. }
  6. 输出结果:
  7. {linux=Person{id=1, name='linux'}}

1.2.8 @Import

在 Spring 中,给容器中注册组件共有 3 种方式:

  1. 包扫描+组件标注注解(@Controller/@Service/@Repository/@Component):适合自己写的类;
  2. @Bean:适合导入的第三方包里面的组件;
  3. @Import:快速给容器中导入一个组件。

@Import 快速导入

定义类,添加@Import注解:

  1. public class Blue { }
  2. public class Green { }
  3. public class Blue { }
  4. @Configuration
  5. @Import({Red.class, Green.class, Blue.class})
  6. public class SpringConfig {}

测试结果如下:

  1. public static void annotation() {
  2. ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
  3. String[] names = context.getBeanDefinitionNames();
  4. System.out.println(Arrays.asList(names));
  5. }
  6. // 打印结果如下:
  7. com.lbj.bean.Red,
  8. com.lbj.bean.Green,
  9. com.lbj.bean.Blue

通过上面可以知道,通过@Import快速导入的组件的id默认值是该类的全路径。

ImportSelector 接口

实现ImportSelector接口:

  1. public class MyImportSelector implements ImportSelector {
  2. /**
  3. * @param importingClassMetadata 当前标注@Import注解的类的所有注解信息
  4. * @return 返回需要导入类的全类名
  5. */
  6. @Override
  7. public String[] selectImports(AnnotationMetadata importingClassMetadata) {
  8. return new String[]{
  9. Green.class.getCanonicalName(),
  10. Red.class.getCanonicalName()
  11. };
  12. }
  13. @Override
  14. public Predicate<String> getExclusionFilter() {
  15. return ImportSelector.super.getExclusionFilter();
  16. }
  17. }

使用ImportSelector实现类:

  1. @Configuration
  2. @Import({MyImportSelector.class, Blue.class})
  3. public class SpringConfig {}

测试结果如下:

  1. public static void annotation() {
  2. ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
  3. String[] names = context.getBeanDefinitionNames();
  4. for (String name : names) {
  5. System.out.println("==> " + name);
  6. }
  7. }
  8. // 打印结果如下:
  9. ==> com.lbj.bean.Green
  10. ==> com.lbj.bean.Red
  11. ==> com.lbj.bean.Blue

ImportBeanDefinitionRegistrar 接口

实现ImportBeanDefinitionRegistrar接口:

  1. public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  2. /**
  3. * @param importingClassMetadata 当前类的所有注解信息
  4. * @param registry 通过该对象注册Bean
  5. */
  6. @Override
  7. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  8. // 判断是否存在这个Bean
  9. boolean hasGreen = registry.containsBeanDefinition("com.lbj.bean.Green");
  10. boolean hasRed = registry.containsBeanDefinition("com.lbj.bean.Red");
  11. boolean hasBlue = registry.containsBeanDefinition("com.lbj.bean.Blue");
  12. // 当上面3个Bean都存在时,注册另外一个Bean
  13. if (hasGreen && hasRed && hasBlue) {
  14. // 设置Bean的所有信息,包括Scope/Lazy等
  15. BeanDefinition beanDefinition = new RootBeanDefinition(Yellow.class);
  16. // 注册一个Bean,并指定id
  17. registry.registerBeanDefinition("yellow", beanDefinition);
  18. }
  19. }
  20. }

使用ImportBeanDefinitionRegistrar实现类:

  1. @Configuration
  2. @Import({Blue.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
  3. public class SpringConfig {}

测试结果如下:

  1. public static void annotation() {
  2. ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
  3. String[] names = context.getBeanDefinitionNames();
  4. for (String name : names) {
  5. System.out.println("==> " + name);
  6. }
  7. }
  8. // 打印结果如下:
  9. ==> com.lbj.bean.Blue
  10. ==> com.lbj.bean.Green
  11. ==> com.lbj.bean.Red
  12. ==> yellow

1.2.9 FactoryBean

上面介绍了 3 种注入 Bean 的方式,使用FactoryBean方式可以实现第 4 种注册 Bean 的方式,这种方式是其他框架和 Spring 整合的常用方式。

实现FactoryBean接口:

  1. public class ColorFactoryBean implements FactoryBean<Color> {
  2. // 返回类对象
  3. @Override
  4. public Color getObject() throws Exception {
  5. return new Color();
  6. }
  7. // 返回类类型
  8. @Override
  9. public Class<?> getObjectType() {
  10. return Color.class;
  11. }
  12. // 是否单例
  13. @Override
  14. public boolean isSingleton() {
  15. return true;
  16. }
  17. }

注册ColorFactoryBean:

  1. @Configuration
  2. public class SpringConfig {
  3. @Bean
  4. public ColorFactoryBean colorFactoryBean(){
  5. return new ColorFactoryBean();
  6. }
  7. }

测试结果如下:

  1. public static void annotation() {
  2. ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
  3. String[] names = context.getBeanDefinitionNames();
  4. for (String name : names) {
  5. System.out.println("==> " + name);
  6. }
  7. Object obj1 = context.getBean("colorFactoryBean");
  8. System.out.println("obj1 = " + obj1);
  9. Object obj2 = context.getBean("&colorFactoryBean");
  10. System.out.println("obj2 = " + obj2);
  11. }
  12. // 打印结果如下:
  13. ==> colorFactoryBean
  14. obj1 = com.lbj.bean.Color@35ef1869
  15. obj2 = com.lbj.bean.ColorFactoryBean@c33b74f

从上面结果可知:

  • 打印1的结果说明注册了一个 id 名为colorFactoryBean的Bean。
  • 打印2的结果说明通过 id 获取 Bean 并不是FactoryBean本身,而是该接口的泛型对象。
  • 打印3的结果说明要想获取FactoryBean本身,需要在 id 前面添加&符号。

这是因为通过FactoryBean注入的类,会默认调用其getObject()方法,所以返回泛型对象:

  1. @Override
  2. public Color getObject() throws Exception {
  3. return new Color();
  4. }

而添加&+id方式获取FactoryBean本身,也是因为FactoryBean接口中已有的默认定义:
QQ截图20220316124635.png

1.2 Bean 生命周期

1.2.1 指定 init() 和 destory() 方法

Bean 的生命周期遵循“创建”-“初始化”-“销毁”过程。
创建一个Car类:

  1. public class Car {
  2. public Car() {
  3. System.out.println("Car - 构造方法调用");
  4. }
  5. public void init() {
  6. System.out.println("Car - 初始化方法");
  7. }
  8. public void destory() {
  9. System.out.println("Car - 销毁方法");
  10. }
  11. }

在 XML 配置中,我们定义一个 Bean 的初始化和销毁方法如下:

  1. <bean id="car" class="com.lbj.bean.Car"
  2. init-method="init"
  3. destroy-method="destory"/>

init-methoddestroy-method分别就是指定初始化和销毁方法的属性,要求**指定的方法不能有参数**

在注解注入中,通过@Bean注解的initMethoddestroyMethod指定初始化和销毁方法:

  1. @Configuration
  2. public class SpringConfig {
  3. @Bean(initMethod = "init", destroyMethod = "destory")
  4. public Car car() {
  5. return new Car();
  6. }
  7. }

测试结果如下:

  1. public static void annotation() {
  2. // 注意这里是 AnnotationConfigApplicationContext,而不是使用 ApplicationContext
  3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
  4. System.out.println("IOC容器创建");
  5. Car car = (Car) context.getBean("car");
  6. // 关闭IOC容器
  7. context.close();
  8. }
  9. // 打印结果如下:
  10. Car - 构造方法调用
  11. Car - 初始化方法
  12. IOC容器创建
  13. Car - 销毁方法

针对单实例和多实例的生命周期如下:
创建对象时:

  • 单实例:在容器启动的时候创建对象;
  • 多实例:在每次获取的时候创建对象;

初始化时:

  • 单实例:对象创建完成,并赋值好后,调用初始化方法;
  • 多实例:和单实例一样;

销毁时:

  • 单实例:IOC容器销毁时销毁;
  • 多实例:IOC容器不管理多实例,所以IOC容器销毁时不会销毁,如需要销毁则需要我们手动销毁。

1.2.2 实现 InitializingBean 和 DisposableBean 接口

让 Bean 实现InitializingBeanDisposableBean接口:

  1. @Component
  2. public class Car implements InitializingBean, DisposableBean {
  3. @Override
  4. public void afterPropertiesSet() throws Exception {
  5. // Properties属性设置好后调用该方法,就是初始化方法
  6. }
  7. @Override
  8. public void destroy() throws Exception {
  9. // 销毁方法
  10. }
  11. }

1.2.3 使用 JSR250

  1. @Component
  2. public class Cat {
  3. // Properties属性设置好后调用该方法
  4. @PostConstruct
  5. public void init() {}
  6. // Bean销毁之前调用
  7. @PreDestroy
  8. public void destory() {}
  9. }

1.2.4 后置处理器 BeanPostProcessor

  1. @Component
  2. public class Dog implements BeanPostProcessor {
  3. @Override
  4. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  5. // 在所有初始化方法执行之前调用
  6. return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
  7. }
  8. @Override
  9. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  10. // 在所有初始化方法执行之后调用
  11. return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
  12. }
  13. }

为什么后置处理器一定会在所有初始化方法执行之前或之后调用呢?
通过在postProcessBeforeInitialization方法上打断点,通过方法栈我们可以找到AbstractAutowireCapableBeanFactory类的initializeBean()方法,该方法源码如下:

  1. protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
  2. if (System.getSecurityManager() != null) {
  3. AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
  4. invokeAwareMethods(beanName, bean);
  5. return null;
  6. }, getAccessControlContext());
  7. } else {
  8. invokeAwareMethods(beanName, bean);
  9. }
  10. Object wrappedBean = bean;
  11. if (mbd == null || !mbd.isSynthetic()) {
  12. // 执行后置处理器Before方法
  13. wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
  14. }
  15. try {
  16. // 执行初始化方法
  17. invokeInitMethods(beanName, wrappedBean, mbd);
  18. } catch (Throwable ex) {
  19. throw new BeanCreationException(
  20. (mbd != null ? mbd.getResourceDescription() : null),
  21. beanName, "Invocation of init method failed", ex);
  22. }
  23. if (mbd == null || !mbd.isSynthetic()) {
  24. // 执行后置处理器After方法
  25. wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
  26. }
  27. return wrappedBean;
  28. }

而进入invokeInitMethods方法中我们可以发现,这个方法主要是执行几个初始化的方法,包括afterPropertiesSetinitMethod等方法。

因此通过上面代码我们可以知道,3个方法的执行顺序如下:

  1. applyBeanPostProcessorsBeforeInitialization
  2. invokeInitMethods
  3. applyBeanPostProcessorsAfterInitialization

另外,在执行initializeBean()方法的上一步时,如图:
QQ截图20220316154453.png
而在populateBean方法的作用就是设置各种 property 属性。因此后置处理器一定是在设置 Bean 属性的后面执行。

1.3 组件赋值相关注解

1.3.1 @Value

**@Value**注解的作用:为 Bean 属性赋值。

在 XML 注入中,为属性注入值如下代码:

  1. <bean id="person" class="com.lbj.bean.Person">
  2. <property name="id" value="1"/>
  3. <property name="name" value="大侠"/>
  4. </bean>

在注解注入中,为属性注入值如下代码:

  1. public class Person {
  2. @Value("1")
  3. private Integer id;
  4. @Value("张三")
  5. private String name;
  6. }

@Value中可写的类型有:

  • 基本数值。如“1”,“张三”等;
  • SpEL表达式。如:#{ 1 + 1};
  • ${ }。取出配置文件properties中的值(在运行环境里面的值)。

1.3.2 @PropertySource 和 @PropertySources

@PropertySource注解的作用:引入 properties 文件到环境变量中。
@PropertySources注解的作用:存放多个@PropertySource注解。

新建外部配置文件person.properties:

  1. person.address=beijing

在 XML 注入时,我们需要通过property-placeholder指定外部配置文件:

  1. <context:property-placeholder location="classpath:person.properties"/>

在注解注入时,我们需要通过@PropertySource注入外部文件:

  1. @Configuration
  2. @ComponentScan("com.lbj")
  3. // 注意:文件前需要添加 “/”
  4. @PropertySource("classpath:/person.properties")
  5. public class SpringConfig {}
  1. @Component
  2. public class Person {
  3. @Value("1") // 普通注入
  4. private Integer id;
  5. @Value("#{1 + 1}") // SpEL表达式
  6. private String name;
  7. @Value("${person.address}") // 引入环境变量中的值
  8. private String address;
  9. }

外部配置文件person.properties被引入之后,就可以在环境变量中获取这个值:

  1. public static void annotation() {
  2. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
  3. ConfigurableEnvironment environment = context.getEnvironment();
  4. System.out.println(environment.getProperty("person.address"));
  5. }

1.3.2 @Autowired

@Autowired自动注入的步骤如下:

  1. 默认优先按照类型去容器中找对应的组件:
    1. applicationContext.getBean(BookDao.class);
    2:如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找: ```java @Autowired private BookDao bookDao01;

// 按照属性名称 “bookDao01” 去容器中查找 applicationContext.getBean(“bookDao01”);

  1. 3. 使用`@Qualifier`注解强制使用该注解的属性值作为组件的 id,而不是使用属性名:
  2. ```java
  3. @Qualifier("bookDao")
  4. @Autowired
  5. private BookDao bookDao01;
  6. // 因为 @Qualifier,所以使用 bookDao 去查找组件,而不是使用 bookDao01
  7. applicationContext.getBean("bookDao");
  1. 自动装配默认情况是一定要将属性赋值好,没有就报错;(也就是说一定要找到组件,否则就报错)。
  2. 如果希望能找到组件就找到,没找到也不报错可使用下面方法:(第4步的解决方案)

    1. @Service
    2. public class BookService {
    3. // required值默认是true,修改为false即可
    4. @Autowired(required = false)
    5. private BookMapper bookMapper;
    6. }
  3. 如果具有多个相同的 id,我们可以使用@Primary注解,这个注解表示首选的意思,也就是说多个相同的id,默认使用这个注解标记方法或类。

@Primary注解也只能用来标注方法和类。

@Qualifier@Primary的区别在于:前者是强制使用的,优先级很高;后者是默认的,再不与前者冲突时,优先使用该注解标记的方法或类:

  1. @Primary
  2. @Bean({"person"})
  3. public Person person() {
  4. return new Person(1, "小侠");
  5. }

1.3.4 @Resources

1.3.5 @Inject

1.3.8 @Profile

1.4 组件注入相关注解

1.5 AOP相关注解

1.6 声明式事务相关注解

二、扩展原理

三、Web相关注解