type
status
date
slug
summary
tags
category
icon
password
comment

1. 准备工作

创建一个 maven 项目 添加依赖

2. 注册组件

2.1 XML 方式

在工程的 bean 包下创建一个 Person 类
在工程的 src/main/resources 目录下创建 Spring 的配置文件,例如 beans.xml
在工程的 根目录 创建一个 MainTest 类来进行测试

2.2 注解方式

@Configuration @Bean 在项目的 config 包 下创建一个 MainConfig 类,并在该类上添加 @Configuration 注解来标注该类是一个Spring的配置类,最后通过 @Bean 注解将Person类注入到Spring的IOC容器中。
  • 使用注解注入 JavaBean 时,bean 在 IOC 容器中的名称默认就是使用 @Bean 注解标注的方法名称
  • 如果要给 bean 单独指定名称,只要在@Bean注解中明确指定名称就可以了。比如将person01()方法上的@Bean注解修改成了@Bean(“person”)注解。

3. 包扫描

3.1 XML 方式

这样配置以后,只要在com.projectname包下,或者com.projectname的子包下标注了 @Repository、@Service、@Controller、@Component 注解的类都会被扫描到,并自动注入到Spring容器中。 此时,我们分别创建 BookDao、BookService 以及 BookController 这三个类,并在这三个类中分别添加@Repository、@Service、@Controller 注解
写一个测试类
除了输出我们自己创建的bean的名称之外,也输出了Spring内部使用的一些重要的bean的名称。

3.2 注解方式

使用 @ComponentScan 注解之前我们先将 beans.xml 配置文件中的下述配置注释掉。
在我们的 MainConfig 类上添加 @ComponentScan 注解,并将扫描的包指定为com.projectname即可。
写个测试类

@ComponentScan

@Filter

ComponentScan类中的如下两个方法
includeFilters() 方法指定 Spring 扫描的时候按照什么规则只需要包含哪些组件 excludeFilters() 方法指定 Spring 扫描的时候按照什么规则排除哪些组件 两个方法的返回值都是 Filter[] 数组

3.3 排除指定组件

需求:排除
@Controller 和 @Service 这俩注解标注的组件 实现:在 MainConfig 类上通过 @ComponentScan 注解的 excludeFilters() 方法实现

3.4 只包含指定组件

注意:使用 includeFilters() 方法来指定只包含哪些注解标注的类时,需要 禁用 默认的过滤规则。
使用 xml 方式时,需要先配置好 use-default-filters="false",禁用默认的过滤规则,因为默认的过滤规则就是扫描所有的,只有我们禁用默认的过滤规则之后,只包含才能生效。
需求:只包含 @Controller 注解标注的类 实现:在 MainConfig 类上添加 @ComponentScan 注解,设置只包含 @Controller 注解标注的类,并禁用掉默认的过滤规则

3.5 重复注解

ComponentScan 注解类
@ComponentScans ComponentScans 注解类
可以看到,在ComponentScans注解类的内部只声明了一个返回ComponentScan[]数组的value()方法,说到这里,大家是不是就明白了,没错,这在Java 8中是一个重复注解。
如果你用的是Java 8,那么@ComponentScan 注解就是一个重复注解,也就是说我们可以在一个类上重复使用这个注解
如果你使用的是 Java 8 之前的版本,那也没有问题,虽然我们再也不能直接在类上写多个@ComponentScan注解了,但是我们可以在类上使用 @ComponentScans 注解,同样也可以指定多个 @ComponentScan

3.6 过滤规则

@Filter注解中的 type 属性是一个 FilterType 枚举类

1. ANNOTATION

按照注解进行包含或者排除
比如:按照注解只包含标注了 @Controller 注解的组件

2. ASSIGNABLE_TYPE

按照给定的类型进行包含或者排除
比如:按照给定的类型只包含 BookService 类(接口)或其子类(实现类或子接口)的组件

3. ASPECTJ

按照ASPECTJ表达式进行包含或者排除
这种过滤规则基本上不怎么用!

4. REGEX

按照正则表达式进行包含或者排除
这种过滤规则基本上也不怎么用!

5. 自定义规则

创建一个自定义规则的类 实现org.springframework.core.type.filter.TypeFilter接口
使用@ComponentScan注解进行如下配置
我们现在扫描的是com.projectname包,该包下的每一个类都会进到这个自定义规则里面进行匹配,若匹配成功,则就会被包含在容器中。

4. 组件作用域

Spring 容器中的组件默认是单例的,在 Spring 启动时就会实例化并初始化这些对象,并将其放到 Spring 容器中,之后,每次获取对象时,直接从 Spring 容器中获取,而不再创建对象。如果每次从Spring容器中获取对象时,都要创建一个新的实例对象,那么该如何处理呢?此时就需要使用@Scope注解来设置组件的作用域了。

4.1 基本使用

4.2 @Scope

从@Scope注解类的源码中可以看出,在@Scope注解中可以设置如下值: ConfigurableBeanFactory#SCOPE_PROTOTYPE ConfigurableBeanFactory#SCOPE_SINGLETON org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
看一下 ConfigurableBeanFactory 接口
可以发现,SCOPE_SINGLETON就是singleton,而SCOPE_PROTOTYPE就是prototype。
当我们使用 Web 容器来运行 Spring 应用时,在 @Scope 注解中可以设置 WebApplicationContext 类中的 SCOPE_REQUEST 和SCOPE_SESSION这俩的值,而 SCOPE_REQUEST 的值就是 request ,SCOPE_SESSION 的值就是 session。

1. singleton

  • 表示组件在 Spring 容器中是单实例的,这个是 spring 的默认值
  • Spring 在启动时会将组件进行实例化并加载的 spring 容器中,之后,每次从Spring 容器中获取组件时,直接将实例对象返回,而不会创建新的实例对象。

2. prototype

  • 表示组件在 Spring 容器中是多实例的
  • Spring 在启动时并不会将组件进行实例化操作,而是每次从Spring 容器中获取组件对象时,都会创建新的实例对象并返回。

3. request

  • Spring 容器 必须在 web 环境中
  • 每次请求都会创建一个新的实例对象,同一次请求只创建一个实例对象

4. session

  • Spring 容器 必须在 web 环境中
  • 在同一个session范围内,只创建一个实例对象

5. application

  • Spring 容器 必须在 web 环境中,全局web应用级别的作用域
  • 一个web应用程序对应一个bean实例,通常情况下和 singleton 效果类似。不过也有不一样的地方,singleton 是每个spring容器只有一个bean实例,当一个应用程序中有多个spring容器时,不同的容器可以存在同名的bean。而对于 application ,不管应用程序中有多少个Spring容器,这个应用程序中同名的bean只有一个
其中,request 和 session 作用域是需要 Web 环境来支持的,这两个值基本上使用不到。当我们使用 Web 容器来运行 Spring 应用时,如果需要将组件的实例对象的作用域设置为 request 和 session ,那么我们通常会使用 request.setAttribute("key", object); 和 session.setAttribute("key", object); 这两种形式来将对象实例设置到 request 和 session 中,而不会使用 @Scope 注解来进行设置。

6. 自定义

第一步,实现Scope接口。 第二步,将自定义Scope注册到容器中。 第三步,使用自定义的作用域。也就是在定义bean的时候,指定bean的scope属性为自定义的作用域名称。
举个例子,我们来实现一个线程级别的bean作用域,同一个线程中同名的bean是同一个实例,不同的线程中的bean是不同的实例。
这里,要求bean在线程中是共享的,所以我们可以通过ThreadLocal来实现,ThreadLocal可以实现线程中数据的共享。
首先,我们在com.projectname.scope包下新建一个ThreadScope类 在ThreadScope类中,我们定义了一个THREAD_SCOPE常量,该常量是在定义bean的时候给scope使用的。
然后,我们在com.meimeixia.config包下创建一个配置类,例如MainConfig2,并使用@Scope(“thread”)注解标注Person对象的作用域为Thread范围,如下所示。
接着,我们在IOCTest类中创建一个test04()方法,我们所要做的事情就是在该方法中创建Spring容器,并向Spring容器中注册ThreadScope对象。最后,使用循环创建两个Thread线程,并分别在每个线程中获取两个Person对象,如下所示。

5. 懒加载

Spring在启动时,默认会将单实例bean进行实例化,并加载到Spring容器中去。也就是说,单实例bean默认是在Spring容器启动的时候创建对象,并且还会将对象加载到Spring容器中。如果我们需要对某个bean进行延迟加载,那么该如何处理呢?此时,就需要使用到@Lazy注解了。

5.1 @Lazy

5.2 总结

  • 使用@Lazy注解标注后,单实例bean对象只是在第一次从Spring容器中获取时被创建,以后每次获取bean对象时,直接返回创建好的对象。
  • 懒加载,也称延时加载,仅针对单实例bean生效。
  • 当多次获取bean的时候并不会重复加载,只是在第一次获取的时候才会加载,这不是延迟加载的特性,而是单实例bean的特性。

6. @Conditional

6.1 概述

@Conditional注解是由Spring Framework提供的一个注解,它位于 org.springframework.context.annotation包内,定义如下。
从@Conditional注解的源码来看,@Conditional注解不仅可以添加到类上,也可以添加到方法上。在@Conditional注解中,还存在着一个Condition类型或者其子类型的Class对象数组,Condition是个啥呢?我们点进去看一下。
可以看到,它是一个接口。所以,我们使用@Conditional注解时,需要写一个类来实现Spring提供的Condition接口,它会匹配@Conditional所符合的方法,然后我们就可以使用我们在@Conditional注解中定义的类来检查了。

6.2 使用场景

  • 可以作为类级别的注解直接或间接的与@Component 相关联,包括@Configuration类
  • 可以作为元注解,用于自动编写构造性注解
  • 作为方法级别的注解,作用在任何@Bean方法上

6.3 使用方法

需求:如果当前操作系统是Windows操作系统,那么就向Spring容器中注册名称为bill的Person对象;如果当前操作系统是Linux操作系统,那么就向Spring容器中注册名称为linus的Person对象。要想实现这个需求,我们就得要使用@Conditional注解了。
创建了两个实现Condition接口的类,它们分别是 LinuxCondition 和 WindowsCondition
配置类
写测试
输出:

7. @Import

  • 我们自己写的类,可以通过 包扫描 + 给组件标注注解(@Controller、@Servcie、@Repository、@Component)的形式将其注册到 IOC 容器中
  • 如果不是我们自己写的类(比如说我们在项目中会经常引入一些第三方的类库),我们需要将这些第三方类库中的类注册到 Spring 容器中,该怎么办呢?此时,我们就可以使用 @Bean 和 @Import 注解将这些类快速的导入Spring容器中。
接下来,我们来一起探讨下如何使用 @Import 注解给容器中快速导入一个组件。

7.1 注册bean的方式

向 Spring 容器中注册 bean 通常有以下几种方式:
  • 包扫描+给组件标注注解(@Controller、@Servcie、@Repository、@Component),但这种方式仅限于我们自己写的类
  • @Bean注解,通常用于导入第三方包中的组件
  • @Import注解,快速向Spring容器中导入一个组件

7.2 @Import注解概述

  • Spring 3.0 之前,创建 bean 可以通过 XML 配置文件与扫描特定包下面的类来将类注入到 Spring IOC 容器内。
  • Spring 3.0 之后提供了 JavaConfig 的方式,也就是将 IOC 容器里面 bean 的元信息以 Java 代码的方式进行描述,然后我们可以通过 @Configuration 与 @Bean 这两个注解配合使用来将原来配置在 XML 文件里面的 bean 通过 Java 代码的方式进行描述。
  • @Import 注解提供了 @Bean 注解的功能,同时还有 XML 配置文件里面标签组织多个分散的 XML 文件的功能,当然在这里是组织多个分散的 @Configuration,因为一个配置类就约等于一个 XML 配置文件。
我们先看一下 @Import 注解的源码,如下所示。
从源码里面可以看出 @Import 可以配合 Configuration、ImportSelector 以及 ImportBeanDefinitionRegistrar 来使用,下面的 or 表示也可以把 Import 当成普通的 bean 来使用。
注意:@Import 注解只允许放到类上面,不允许放到方法上。

7.3 使用方式

首先,我们创建一个 Color 类,这个类是一个空类,没有成员变量和方法,假设这是一个第三方类,我们不能在修改这个类的内容
然后,我们在 IOCTest 类中创建一个 testImport() 方法,在其中输出 Spring 容器中所有 bean 定义的名字,来查看是否存在 Color 类对应的 bean 实例,以此来判断 Spring 容器中是否注册有 Color 类对应的 bean 实例。
可以看到 Spring 容器中并没有 Color 类对应的 bean 实例。

1. class 数组

在 MainConfig 配置类的类名上使用@Import
重新测试,可以看到,输出结果中打印了com.projectname.bean.Color,说明使用@Import注解快速地导入组件时,容器中就会自动注册这个组件,并且 id 默认是组件的全类名。
同时导入多个类

2. ImportSelector 接口

接口介绍

ImportSelector 接口是 Spring 中导入外部配置的核心接口,在 Spring Boot 的自动化配置和 @EnableXXX(功能性注解)都有它的存在。我们先来看一下 ImportSelector 接口的源码,如下所示。
该接口文档上说的明明白白,其主要作用是收集需要导入的配置类,selectImports() 方法的返回值就是我们向 Spring 容器中导入的类的全类名。如果该接口的实现类同时实现 EnvironmentAware,BeanFactoryAware,BeanClassLoaderAware 或者 ResourceLoaderAware,那么在调用其 selectImports() 方法之前先调用上述接口中对应的方法,如果需要在所有的 @Configuration 处理完再导入时,那么可以实现 DeferredImportSelector 接口。
在 ImportSelector 接口的 selectImports() 方法中,存在一个 AnnotationMetadata 类型的参数,这个参数能够获取到当前标注 @Import 注解的类的所有注解信息,也就是说不仅能获取到 @Import 注解里面的信息,还能获取到其他注解的信息。

使用方法

首先,我们创建一个 MyImportSelector 类实现 ImportSelector 接口
然后,在 MainConfig 配置类的 @Import 注解中,导入 MyImportSelector 类,如下所示。
至于使用 MyImportSelector 类要导入哪些 bean,就需要你在 MyImportSelector 类的 selectImports()方法中进行设置了,只须在 MyImportSelector 类的 selectImports() 方法中返回要导入的类的全类名(包名 + 类名)即可。

3. ImportBeanDefinitionRegistrar 接口

接口介绍

我们先来看看 ImportBeanDefinitionRegistrar 是个什么鬼,点击进入 ImportBeanDefinitionRegistrar 源码
由源码可以看出,ImportBeanDefinitionRegistrar 本质上是一个接口。在 ImportBeanDefinitionRegistrar 接口中,有一个 registerBeanDefinitions() 方法,通过该方法,我们可以向 Spring 容器中注册 bean 实例。
Spring 官方在动态注册 bean 时,大部分套路其实是使用 ImportBeanDefinitionRegistrar 接口。
所有实现了该接口的类都会被 ConfigurationClassPostProcessor 处理,ConfigurationClassPostProcessor 实现了 BeanFactoryPostProcessor 接口,所以 ImportBeanDefinitionRegistrar 中动态注册的 bean 是优先于依赖其的 bean 初始化的,也能被 aop、validator 等机制处理。
ImportBeanDefinitionRegistrar 需要配合 @Configuration 和 @Import 这俩注解 @Configuration 注解定义 Java 格式的 Spring 配置文件 @Import 注解导入实现了 ImportBeanDefinitionRegistrar 接口的类。

使用方法

创建一个 MyImportBeanDefinitionRegistrar 类,去实现 ImportBeanDefinitionRegistrar 接口,在 registerBeanDefinitions() 方法里面自定义一些逻辑
然后,我们在 MainConfig 配置类上的 @Import 注解中,添加 MyImportBeanDefinitionRegistrar 类
接着,创建一个 RainBow 类,作为测试 ImportBeanDefinitionRegistrar 接口的 bean 来使用
写测试

8. FactoryBean

8.1 概述

一般情况下,Spring 是通过反射机制利用 bean 的 class 属性指定实现类来实例化 bean 的。在某些情况下,实例化 bean 过程比较复杂,如果按照传统的方式,那么则需要在标签中提供大量的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可以得到一个更加简单的方案。Spring 为此提供了一个 org.springframework.bean.factory.FactoryBean 的工厂类接口,用户可以通过实现该接口定制实例化 bean 的逻辑。
FactoryBean 接口对于 Spring 框架来说占有非常重要的地位,Spring 自身就提供了 70 多个 FactoryBean 接口的实现。它们隐藏了实例化一些复杂 bean 的细节,给上层应用带来了便利。从 Spring 3.0 开始,FactoryBean 开始支持泛型,即接口声明改为 FactoryBean<T> 的形式。
在Spring 4.3.12.RELEASE这个版本中,FactoryBean接口的定义如下所示。
  • T getObject():返回由FactoryBean创建的bean实例,如果isSingleton()返回true,那么该实例会放到Spring容器中单实例缓存池中
  • boolean isSingleton():返回由FactoryBean创建的bean实例的作用域是singleton还是prototype
  • Class getObjectType():返回FactoryBean创建的bean实例的类型
注意:当配置文件中标签的 class 属性配置的实现类是 FactoryBean 时,通过 getBean()方法返回的不是 FactoryBean 本身,而是 FactoryBean#getObject() 方法所返回的对象,相当于 FactoryBean#getObject()代理了 getBean() 方法。

8.2 使用方法

首先,创建一个ColorFactoryBean类,它得实现FactoryBean接口,如下所示。
然后,我们在MainConfig配置类中加入ColorFactoryBean的声明,如下所示。
注意:我在这里使用@Bean注解向Spring容器中注册的是ColorFactoryBean对象。
那现在我们就来看看Spring容器中到底都有哪些bean,发现 输出了一个 colorFactoryBean,然后根据这个名字去获取实例
可以看到,虽然我在代码中使用 @Bean 注解注入的是 ColorFactoryBean 对象,但是实际上从 Spring 容器中获取到的 bean 对象却是调用 ColorFactoryBean 类中的 getObject() 方法获取到的 Color 对象。
在 ColorFactoryBean 类中,如果把 Color 对象设置为单实例 bean,那么多次获取的对象为同一对象 如果把 Color 对象修改成多实例 bean ,那么每次获取对象都会创建新的实例

8.3 获取 FactoryBean 对象

之前,我们使用 @Bean 注解向 Spring 容器中注册的是 ColorFactoryBean,获取出来的却是 Color 对象。那么,小伙伴们可能会问了,我就想获取 ColorFactoryBean 实例,那么该怎么办呢? 其实,这也很简单,只需要在获取工厂 Bean 本身时,在 id 前面加上 & 符号即可,例如 &colorFactoryBean。 Object colorFactoryBean = applicationContext.getBean("&colorFactoryBean"); 为什么在 id 前面加上 & 符号就会获取到 ColorFactoryBean 实例对象呢?
答案在 BeanFactory 接口中,查看其源码。
BeanFactory 接口中定义了一个 & 前缀,只要我们使用 bean 的 id 来从 Spring 容器中获取 bean 时,Spring 就会知道我们是在获取 FactoryBean 本身。

9. 生命周期

通常意义上讲的 bean 的生命周期,指的是 bean 从创建到初始化,经过一系列的流程,最终销毁的过程。只不过,在 Spring 中,bean 的生命周期是由 Spring 容器来管理的。在 Spring 中,我们可以自己来指定 bean 的初始化和销毁的方法。我们指定了 bean 的初始化和销毁方法之后,当容器在 bean 进行到当前生命周期的阶段时,会自动调用我们自定义的初始化和销毁方法。

9.1 XML方式

在标签中指定bean的初始化和销毁方法
在我们自己写的Person类中,需要存在init()方法和destroy()方法。而且Spring中还规定,这里的init()方法和destroy()方法必须是无参方法,但可以抛出异常。

9.2 @Bean 注解方式

通过 @Bean 注解指定初始化和销毁方法。 在 MainConfig 配置类的 @Bean 注解中指定 initMethod 属性和 destroyMethod 属性
在Spring容器中,先是调用了Car类的构造方法来创建Car对象,接下来便是调用了Car对象的init()方法来进行初始化。 bean的销毁方法是在容器关闭的时候被调用的。

使用场景

数据源的管理 在配置数据源时,在初始化的时候,会对很多的数据源的属性进行赋值操作; 在销毁的时候,我们需要对数据源的连接等信息进行关闭和清理。 这个时候,我们就可以在自定义的初始化和销毁方法中来做这些事情了!

调用时机

初始化方法和销毁方法是在什么时候被调用的呢?
  • 初始化方法:对象创建完成,如果对象中存在一些属性,并且这些属性也都赋好值之后,那么就会调用 bean 的初始化方法。
    • 对于单实例 bean 来说,在 Spring 容器创建完成后,Spring 容器会自动调用 bean 的初始化方法;
    • 对于多实例 bean 来说,在每次获取 bean 对象的时候,调用 bean 的初始化方法。
  • 销毁方法
    • 对于单实例 bean 来说,在容器关闭的时候,会调用 bean 的销毁方法;
    • 对于多实例 bean 来说,Spring 容器不会管理这个 bean,也就不会自动调用这个 bean 的销毁方法了。不过,小伙伴们可以手动调用多实例 bean 的销毁方法。

9.3 InitializingBean 接口

接口概述

Spring 中提供了一个 InitializingBean 接口,该接口为 bean 提供了属性初始化后的处理方法,它只包括 afterPropertiesSet 方法,凡是继承该接口的类,在 bean 的属性初始化后都会执行该方法。 InitializingBean 接口的源码如下所示
根据 InitializingBean 接口中提供的 afterPropertiesSet() 方法的名字不难推断出,afterPropertiesSet() 方法是在属性赋好值之后调用的。

调用时机

我们定位到 Spring 中的 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory 这个类里面的 invokeInitMethods() 方法中,来查看 Spring 加载 bean 的方法。
我们来到 AbstractAutowireCapableBeanFactory 类中的 invokeInitMethods() 方法处,如下所示。
分析上述代码后,我们可以初步得出如下信息:
  • Spring 为 bean 提供了两种初始化的方式
    • 实现 InitializingBean 接口(也就是要实现该接口中的 afterPropertiesSet 方法)
    • 在配置文件或 @Bean 注解中通过 init-method 来指定
    • 两种方式可以同时使用,同时使用先调用 afterPropertiesSet 方法,后执行 init-method 指定的方法。
  • 实现 InitializingBean 接口是直接调用 afterPropertiesSet() 方法,与通过反射调用 init-method 指定的方法相比,效率相对来说要高点。但是 init-method 方式消除了对 Spring 的依赖。
  • 如果调用 afterPropertiesSet 方法时出错,那么就不会调用 init-method 指定的方法了。

9.4 DisposableBean 接口

接口概述

实现 org.springframework.beans.factory.DisposableBean 接口的 bean 在销毁前,Spring 将会调用 DisposableBean 接口的 destroy() 方法。也就是说我们可以实现 DisposableBean 这个接口来定义咱们这个销毁的逻辑。 我们先来看下 DisposableBean 接口的源码
可以看到,在 DisposableBean 接口中只定义了一个 destroy() 方法。
在 bean 生命周期结束前调用 destroy()方法做一些收尾工作,亦可以使用 destroy-method。
  • DisposableBean 接口与 Spring 耦合高,使用类型强转. 方法名 (),效率高;
  • destroy-method 方法与 Spring 耦合低,使用反射,效率相对来说较低。
  • 两个方法同时使用,先调用 DisposableBean 接口中的方法,再调用 destroy-method 方法
注意事项 多实例 bean 的生命周期不归 Spring 容器来管理,这里的 DisposableBean 接口中的方法是由 Spring 容器来调用的,所以如果一个多实例 bean 实现了 DisposableBean 接口是没有啥意义的,因为相应的方法根本不会被调用,当然了,在 XML 配置文件中指定了 destroy 方法,也是没有任何意义的。所以,在多实例 bean 情况下,Spring 是不会自动调用 bean 的销毁方法的。

9.5 @PostConstruct 注解

注解概述

@PostConstruct 注解好多人以为是 Spring 提供的,其实它是 Java 自己的注解,是 JSR-250 规范里面定义的一个注解。我们来看下 @PostConstruct 注解的源码
从源码可以看出,@PostConstruct 注解是 Java 中的注解,并不是 Spring 提供的注解。

调用顺序

通常我们是会在 Spring 框架中使用到 @PostConstruct 注解的,该注解的方法在整个 bean 初始化中的执行顺序如下
  1. Constructor(构造方法)
  1. @Autowired(依赖注入)
  1. @PostConstruct(注释的方法)
  1. 实现了 InitializingBean 接口的 afterPropertiesSet() 方法。
  1. 最后,如果 bean 在配置中指定了 init() 方法,Spring 会调用该方法。

使用方法

9.6 @PreDestroy 注解

注解概述

@PreDestroy注解同样是Java提供的,它也是JSR-250规范里面定义的一个注解。看下它的源码

调用顺序

在 Spring 容器关闭时,销毁 bean 的顺序如下:
  1. 首先,Spring 会调用被 @PreDestroy 注解修饰的方法。
  1. 接下来,Spring 会调用实现了 DisposableBean 接口的 destroy() 方法。
  1. 最后,如果 bean 在配置中指定了 destroy 方法,Spring 会调用该方法。

使用方法

9.7 BeanPostProcessor

1. 后置处理器概述

看下 BeanPostProcessor 的源码
从源码可以看出,BeanPostProcessor 是一个接口,其中有两个方法,即 postProcessBeforeInitialization 和 postProcessAfterInitialization 这两个方法,这两个方法分别是在 Spring 容器中的 bean 初始化前后执行,所以 Spring 容器中的每一个 bean 对象初始化前后,都会执行 BeanPostProcessor 接口的实现类中的这两个方法。
也就是说,postProcessBeforeInitialization 方法会在 bean 实例化和属性设置之后,自定义初始化方法之前被调用,而 postProcessAfterInitialization 方法会在自定义初始化方法之后被调用。当容器中存在多个 BeanPostProcessor 的实现类时,会按照它们在容器中注册的顺序执行。对于自定义的 BeanPostProcessor 实现类,还可以让其实现 Ordered 接口自定义排序。
因此我们可以在每个 bean 对象初始化前后,加上自己的逻辑。实现方式是自定义一个 BeanPostProcessor 接口的实现类,例如 MyBeanPostProcessor,然后在该类的 postProcessBeforeInitialization 和 postProcessAfterInitialization 这俩方法中写上自己的逻辑。

2. 使用方法

创建一个MyBeanPostProcessor类,实现BeanPostProcessor接口,如下所示。
测试一下

3. 执行顺序

  1. Constructor(构造方法)
  1. @Autowired(依赖注入)
  1. 后置处理器 初始化前方法 postProcessBeforeInitialization
  1. @PostConstruct(注释的方法)
  1. 实现了 InitializingBean 接口的 afterPropertiesSet() 方法。
  1. bean 在配置中指定的 init() 方法
  1. 后置处理器 初始化后方法 postProcessAfterInitialization

4. 多个后置处理器顺序

Spring 容器中可以存在多个后置处理器,当容器初始化 bean 时,会按照这些后置处理器的执行顺序依次调用它们的 postProcessBeforeInitialization() 和 postProcessAfterInitialization() 方法。 实现了 Ordered 接口的后置处理器可以通过 getOrder() 方法返回一个整数值,表示其执行顺序的优先级,数值越小,优先级越高,即越先执行。如果不实现 Ordered 接口,默认的执行顺序为 Integer.MAX_VALUE。

5. Spring 提供的实现类

后置处理器可用于 bean 对象初始化前后进行逻辑增强。Spring 提供了 BeanPostProcessor 接口的很多实现类
  • ApplicationContextAwareProcessor 向组件中注入IOC容器
  • AnnotationAwareAspectJAutoProxyCreator 用于 Spring AOP 的动态代理
  • BeanValidationPostProcessor 用来为bean进行校验操作
  • InitDestroyAnnotationBeanPostProcessor 处理 @PostConstruct 注解和 @PreDestroy 注解
  • AutowiredAnnotationBeanPostProcessor 用于 @Autowired 注解的实现

ApplicationContextAwareProcessor

org.springframework.context.support.ApplicationContextAwareProcessor 是 BeanPostProcessor 接口的一个实现类,这个类的作用是可以向组件中注入IOC容器 大致的源码如下所示 package org.springframework.context.support;
要想使用ApplicationContextAwareProcessor类向组件中注入IOC容器,我们就不得不提Spring中的另一个接口了,即ApplicationContextAware。如果需要向组件中注入IOC容器,那么可以让组件实现ApplicationContextAware接口。 例如,我们创建一个 Dog 类,使其实现 ApplicationContextAware 接口,此时,我们需要实现 ApplicationContextAware 接口中的 setApplicationContext()方法,在 setApplicationContext() 方法中有一个 ApplicationContext 类型的参数,这个就是 IOC 容器对象,我们可以在 Dog 类中定义一个 ApplicationContext 类型的成员变量,然后在 setApplicationContext() 方法中为这个成员变量赋值,此时就可以在 Dog 类中的其他方法中使用 ApplicationContext 对象了

AnnotationAwareAspectJAutoProxyCreator

用于 Spring AOP 的动态代理 我们都知道 spring AOP 的实现原理是动态代理,最终放入容器的是代理类的对象,而不是 bean 本身的对象,那么 Spring 是什么时候做到这一步的呢?就是在 AnnotationAwareAspectJAutoProxyCreator 后置处理器的 postProcessAfterInitialization 方法中,即 bean 对象初始化完成之后,后置处理器会判断该 bean 是否注册了切面,若是,则生成代理对象注入到容器中。这一部分的关键代码是在哪儿呢?我们定位到 AbstractAutoProxyCreator 抽象类中的 postProcessAfterInitialization 方法处便能看到了,如下所示。

BeanValidationPostProcessor

org.springframework.validation.beanvalidation.BeanValidationPostProcessor类主要是用来为bean进行校验操作的,当我们创建bean,并为bean赋值后,我们可以通过BeanValidationPostProcessor类为bean进行校验操作。 BeanValidationPostProcessor类的源码
在 postProcessBeforeInitialization()方法和 postProcessAfterInitialization() 方法都会根据 afterInitialization 这个布尔类型的成员变量的值来判断是否执行校验操作。
  • 如果 afterInitialization 的值为 false,则在 postProcessBeforeInitialization()方法中调用 doValidate() 方法对 bean 进行校验;
  • 如果 afterInitialization 的值为 true,则在 postProcessAfterInitialization()方法中调用 doValidate() 方法对 bean 进行校验。

InitDestroyAnnotationBeanPostProcessor

org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor 类主要用来处理 @PostConstruct 注解和 @PreDestroy 注解 源码分析

AutowiredAnnotationBeanPostProcessor

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor类主要是用于处理标注了@Autowired注解的变量或方法。 AutowiredAnnotationBeanPostProcessor 是 Spring 框架中的一个核心类,其主要作用是处理 @Autowired 注解,实现自动装配(autowiring)的功能。 具体来说,AutowiredAnnotationBeanPostProcessor 主要完成以下几个任务:
  1. 处理 @Autowired 注解:扫描容器中的 bean,找到标注了 @Autowired 注解的字段、构造方法、setter 方法等,并尝试自动装配这些标注了 @Autowired 注解的元素。
  1. 解析依赖:根据 @Autowired 注解指定的依赖查找策略(例如 byType、byName 等),自动解析和注入依赖。
  1. 处理 @Value 注解:除了处理 @Autowired 注解外,AutowiredAnnotationBeanPostProcessor 还处理 @Value 注解,用于从外部配置文件中读取属性值并注入到 bean 中。
  1. 实现 Aware 接口:AutowiredAnnotationBeanPostProcessor 本身也是一个 BeanPostProcessor,因此可以在 bean 初始化前后执行一些额外的处理工作,例如对 bean 进行校验、增强、代理等。

6. 底层原理

在 Spring 中,调用 initializeBean()方法之前,调用了 populateBean() 方法为 bean 的属性赋值,为 bean 的属性赋好值之后,再调用 initializeBean() 方法进行初始化。
在 initializeBean()中,调用自定义的初始化方法(即 invokeInitMethods())之前,调用了 applyBeanPostProcessorsBeforeInitialization()方法,而在调用自定义的初始化方法之后,又调用了 applyBeanPostProcessorsAfterInitialization() 方法。至此,整个 bean 的初始化过程就这样结束了。

10. 属性赋值

10.1 直接注入

@Value 注解

从@Value注解的源码中我们可以看出,@Value注解可以标注在字段、方法、参数以及注解上,而且在程序运行期间生效。

使用方法

10.2 配置文件

@PropertySource

@PropertySource注解是Spring 3.1开始引入的配置类注解。通过@PropertySource注解可以将properties配置文件中的key/value存储到Spring的Environment中,Environment接口提供了方法去读取配置文件中的值,参数是properties配置文件中定义的key值。当然了,也可以使用@Value注解用${}占位符为bean的属性注入值。 @PropertySource注解
  • 从 @PropertySource 的源码中可以看出,我们可以通过 @PropertySource 注解指定多个 properties 文件,使用的形式如下所示。
    • @PropertySource(value={"classpath:/person.properties", "classpath:/car.properties"})
  • 细心的读者可以看到,在 @PropertySource 注解的上面标注了如下的注解信息。
    • @Repeatable(PropertySources.class)
    • 可以使用 @PropertySources 注解来指定 properties 配置文件。
@PropertySources 注解
使用方法

示例

在项目的src/main/resources目录下新建一个属性文件,例如book.properties
从配置文件中取值并注入
在配置类上使用 @PropertySource 注解读取外部配置文件

#{···}

  • #{···}:用于执行SpEl表达式,并将内容赋值给属性

${···}

  • ${···}:主要用于加载外部属性文件中的值
{}里面的内容必须符合SpEL表达式,通过 @Value("${spelDefault.value}") 获取属性文件中对应的值,可以设置默认值
上述代码的含义是表示向bean的属性中注入属性文件中的author.name属性所对应的值,如果属性文件中没有author.name这个属性,那么便向bean的属性中注入默认值John。
注意:{···} 和 #{···}可以混合使用,但是必须#{}在外面,{}在里面,因为Spring 先执行${...},后执行#{...}

ConfigurableEnvironment 取值

10.3 XML 方式

从 XML 配置文件中获取 person.properties 文件中的值
  • 在 Spring 的 XML 配置文件中引入 context 名称空间
  • 使用 context 命名空间导入 person.properties 文件
  • 在 bean 的属性字段中使用 ${person.nickName} 的方式取值

11. 自动装配

Spring组件的自动装配就是Spring利用依赖注入(DI),完成对 IOC 容器中各个组件的依赖关系赋值。

11.1 @Autowired 注解

@Autowired注解可以对类成员变量、方法和构造函数进行标注,完成自动装配的工作。@Autowired注解可以放在类、接口以及方法上。 @Autowired 注解的源码
这儿对 @Autowired 注解说明一下:
  • @Autowired 注解默认是优先按照类型去容器中找对应的组件,相当于是调用了如下这个方法 applicationContext.getBean(类名.class); 若找到则就赋值。
  • 如果找到多个相同类型的组件,那么是将属性名称作为组件的 id,到 IOC 容器中进行查找,这时就相当于是调用了如下这个方法:applicationContext.getBean("组件的 id");
  • required 属性
    • 默认值为 true ,如果在赋值的时候找不到对应的组件,就会报错
    • false,如果在赋值的时候找不到对应的组件,则属性值为 null ,不会报错

标注在属性上

标注在构造方法上

  • 参数位置的组件自动从IOC容器中获取
  • 如果只有一个有参构造方法,@Autowired 可以省略

标注在 Set 方法上

  • 参数位置的组件自动从IOC容器中获取

标注在参数位置

标注在注册组件时的方法上

  • 此时 @Autowired 可以省略

11.2 @Qualifier 注解

@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,那么就需要配合@Qualifier注解来使用了。
@Qualifier注解的源码

11.3 @Primary 注解

在 Spring 中使用注解时,常常会使用到 @Autowired 这个注解,它默认是根据类型 Type 来自动注入的。但有些特殊情况,对同一个接口而言,可能会有几种不同的实现类,而在默认只会采取其中一种实现的情况下,就可以使用 @Primary 注解来标注优先使用哪一个实现类。
@Primary 注解的源码

示例

controller
service
配置类
测试类
注意:
  • 我们在 BookController 类中使用@Autowired注解注入的 BookService 对象和直接从IOC容器中获取的BookService 对象是同一个对象
  • 如果找到多个相同类型的组件,那么再将属性名作为组件的 id ,到IOC容器中进行查找
如果想按名称进行装配
使用 @Primary 注解来标注优先使用哪一个实现类

11.4 @Resource 注解

@Resource 注解是 Java 规范里面的,也可以说它是 JSR250 规范里面定义的一个注解 该注解默认按照名称进行装配,名称可以通过 name 属性进行指定 如果没有指定 name 属性,当注解写在字段上时,那么默认取字段名将其作为组件的名称在 IOC 容器中进行查找 如果注解写在 setter 方法上,那么默认取属性名进行装配 当找不到与名称匹配的 bean 时才按照类型进行装配 但是需要注意的一点是,如果 name 属性一旦指定,那么就只会按照名称进行装配
@Resource 注解的源码
注意:
  • @Resource 注解不支持@Primary注解优先注入的功能
  • @Resource 注解没有 required=false 属性

示例

11.5 @Inject 注解

@Inject 注解也是 Java 规范里面的,也可以说它是 JSR330 规范里面定义的一个注解 该注解默认是根据参数名去寻找 bean 注入,支持 Spring 的 @Primary 注解优先注入 @Inject 注解还可以增加 @Named 注解指定要注入的 bean
@Inject 注解的源码
注意:
  • @Inject注解支持 @Primary 注解优先注入
  • @Inject注解没有 required=false属性

示例

当你使用 @Inject 注解进行依赖注入时,如果存在多个类型相同的实例,需要通过 @Named 注解指定具体要注入的实例。以下是一个使用 @Inject 和 @Named 注解的简单示例:
在上面的示例中,ExampleService 类依赖于 DataService 接口的一个实现。但是,如果有多个实现 DataService 接口的类,并且你想注入名为 "database" 的特定实现,你可以在构造函数参数上使用 @Named 注解:
在上面的示例中,我们创建了两个实现 DataService 接口的类:DatabaseDataService 和 FileDataService。通过在类上使用 @Named("database") 和 @Named("file") 注解,我们为它们提供了唯一的标识符。然后,在 ExampleService 中使用 @Inject 和 @Named("database"),我们告诉依赖注入容器我们需要注入名为 "database" 的 DataService 实例。
这样,当你创建 ExampleService 实例时,依赖注入容器会自动注入名为 "database" 的 DataService 实现。

比较

  • @Autowired 是 Spring 中的专有注解,而 @Resource 是 Java 中 JSR250 规范里面定义的一个注解,@Inject 是 Java 中 JSR330 规范里面定义的一个注解
  • @Autowired 支持参数 required=false,而 @Resource 和 @Inject 都不支持
  • @Autowired 和 @Inject 支持 @Primary 注解优先注入,而 @Resource 不支持
  • @Autowired 通过 @Qualifier 指定注入特定 bean,@Resource 可以通过参数 name 指定注入 bean,而 @Inject 需要通过 @Named 注解指定注入 bean

11.6 Aware 接口

自定义的组件要想使用 Spring 容器底层的一些组件,比如 ApplicationContext(IOC 容器)、底层的 BeanFactory 等等,那么只需要让自定义组件实现 XxxAware 接口即可。此时,Spring 在创建对象的时候,会调用 XxxAware 接口中定义的方法注入相关的组件
前面在讲 后置处理器 的时候提到了 ApplicationContextAware 接口
前情回顾
我们创建一个 Dog 类,使其实现 ApplicationContextAware 接口,此时,我们需要实现 ApplicationContextAware 接口中的 setApplicationContext()方法,在 setApplicationContext() 方法中有一个 ApplicationContext 类型的参数,这个就是 IOC 容器对象,我们可以在 Dog 类中定义一个 ApplicationContext 类型的成员变量,然后在 setApplicationContext() 方法中为这个成员变量赋值,此时就可以在 Dog 类中的其他方法中使用 ApplicationContext 对象了
在Spring中,类似于 ApplicationContextAware 接口的设计有很多,本质上,Spring 中形如 XxxAware 这样的接口都继承了 Aware 接口,我们来看下 Aware 接口的源码

BeanNameAware 接口

通过 BeanNameAware 接口获取到当前 bean 在 Spring 容器中的名称

EmbeddedValueResolverAware 接口

通过 EmbeddedValueResolverAware 接口能够获取到 String 值解析器

原理

XxxAware 接口的底层原理是由 XxxAwareProcessor 实现类实现的,也就是说每一个 XxxAware 接口都有它自己对应的 XxxAwareProcessor 实现类。 例如,我们这里以 ApplicationContextAware 接口为例,ApplicationContextAware 接口的底层原理就是由 ApplicationContextAwareProcessor 类实现的。从 ApplicationContextAwareProcessor 类的源码可以看出,其实现了 BeanPostProcessor 接口,本质上是一个后置处理器。

11.7 @Profile注解

注解概述

在容器中如果存在同一类型的多个组件,那么可以使用 @Profile 注解标识要获取的是哪一个 bean。 @Profile 注解是 Spring 为我们提供的可以根据当前环境,动态地激活和切换一系列组件的功能。 这个功能在不同的环境使用不同的变量的情景下特别有用,例如,开发环境、测试环境、生产环境使用不同的数据源,在不改变代码的情况下,可以使用这个注解来动态地切换要连接的数据库。
@Profile注解的源码
从源码中我们可以得出如下三点结论:
  • @Profile 注解不仅可以标注在方法上,也可以标注在配置类上。
  • 如果 @Profile 注解标注在配置类上,那么只有是在指定的环境的时候,整个配置类里面的所有配置才会生效。
  • 如果一个 bean 上没有使用 @Profile 注解进行标注,那么这个 bean 在任何环境下都会被注册到 IOC 容器中,当然了,前提是在整个配置类生效的情况下。

实战案例

环境搭建

在pom.xml文件中添加c3p0数据源和MySQL驱动的依赖
新建一个配置类,例如MainConfigOfProfile,并在该配置类中模拟开发、测试、生产环境的数据源
在真实项目开发中,那些数据库连接的相关信息,例如用户名、密码以及MySQL数据库驱动类的全名,这些要抽取在一个配置文件中
  • 使用 @Bean("devDataSource") 注解标注的是开发环境使用的数据源
  • 使用 @Bean("testDataSource") 注解标注的是测试环境使用的数据源
  • 使用 @Bean("prodDataSource") 注解标注的是生产环境使用的数据源
写测试
可以看到三种不同的数据源成功注册到了IOC容器中,说明我们的环境搭建成功了。

环境标识

我们成功搭建环境之后,接下来,就是要实现根据不同的环境来向 IOC 容器中注册相应的 bean 了。
  • 在开发环境注册开发环境下使用的数据源;
  • 在测试环境注册测试环境下使用的数据源;
  • 在生产环境注册生产环境下使用的数据源。
此时,@Profile 注解就显示出其强大的特性了。
  • 使用 @Profile("dev") 注解来标识在开发环境
  • 使用 @Profile("test") 注解来标识在测试环境
  • 使用 @Profile("prod") 注解来标识在生产环境

环境激活

通过@Profile注解加了环境标识的bean,只有这个环境被激活的时候,相应的bean才会被注册到IOC容器中。 如果需要一个默认的环境,可以通过 @Profile("default") 注解来标识一个默认的环境 激活环境有两种方式
方式一:命令行参数
我们在运行程序的时候可以添加相应的命令行参数。例如,如果我们现在的环境是测试环境,那么可以在运行程序的时候添加如下命令行参数。 -Dspring.profiles.active=test
方式二:代码实现
通过 AnnotationConfigApplicationContext 类的无参构造方法来实现
urllib 学习笔记2050 折线分割平面
Loading...
目录
0%
longlong
longlong
一个普通的干饭人🍚
公告
学业繁忙,随心更新
 
 
目录
0%