Spring注解驱动开发笔记

Spring注解驱动开发笔记

1 一切的起点

1.1 pom父依赖

pom.xml

  • spring-boot-dependencies:核心依赖在父工程中
  • 我们在写或者引入一些Springboot依赖的时候,不需要指定版本,就因为有这些版本仓库

1、其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件

1
2
3
4
5
6
7
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>

2、点进去,发现还有一个父依赖

1
2
3
4
5
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.1.RELEASE</version>
</parent>

3、这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;

4、以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;

1.2 spring-boot-starter

  • 依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    </dependency>
  • springboot-boot-starter-xxx,说白了就是Springboot的启动场景

  • 比如spring-boot-starter-web,他就会帮我们自动导入web的所有依赖

  • springboot会将所有的功能场景,都变成一个个的启动器

  • 我们要使用什么功能,就只需要找到对应的启动器就好了start

  • 官方的所有starter

1.3 @SpringBootApplication

默认的主启动类

1
2
3
4
5
6
7
8
9
//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
//以为是启动了一个方法,没想到启动了一个服务
SpringApplication.run(SpringbootApplication.class, args);
}
}

但是,一个简单的启动类并不简单,我们来分析一下这些注解都干了什么。

拆分注解(@SpringBootApplication)

  • 作用:标注在某个类上说明这个类是SpringBoot的主配置;

  • SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;

  • 进入这个注解:可以看到上面还有很多其他注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(
    excludeFilters = {
    @Filter(type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class}),
    @Filter(type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class})
    }
    )
    public @interface SpringBootApplication {
    // ......
    }

==@ComponentScan==

  • 这个注解在Spring中很重要,它对应XML配置中的元素。
  • 作用:自动扫描并加载符合条件的组件或者bean,将这个bean定义加载到IOC容器中。

==@SpringBootConfiguration==

  • 作用:SpringBoot的配置类,标注在某个类上,表示这是一个SpringBoot的配置类;

  • 我们继续进去这个注解查看

    1
    2
    3
    4
    5
    6
    // 点进去得到下面的 @Component
    @Configuration
    public @interface SpringBootConfiguration {}

    @Component
    public @interface Configuration {}
  • 这里的 @Configuration,说明这是一个spring的配置类,配置类就是对应Spring的xml 配置文件;

  • @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用

  • 我们回到 SpringBootApplication 注解中继续看。

==@EnableAutoConfiguration==

  • 开启自动配置功能

    • 1
      2
      3
      4
      5
      @AutoConfigurationPackage
      @Import({AutoConfigurationImportSelector.class})
      public @interface EnableAutoConfiguration {
      // ......
      }
    • 以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置;

    • @EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;

    点进注解接续查看:

  • @AutoConfigurationPackage :自动配置包

    1
    2
    3
    @Import({Registrar.class})
    public @interface AutoConfigurationPackage {
    }
    • @import:Spring底层注解@import,给容器中导入一个组件
    • Registrar.class 作用:实现了ImportBeanDefinitionRegistrar接口,自动配置包注册,将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;
    • 这个分析完了,退到上一步,继续看
  • @Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;

    • AutoConfigurationImportSelector:自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:
  • 获取所有的配置:==List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);==

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 和下面的方法对应
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());

    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
    }

    // 这里的getSpringFactoriesLoaderFactoryClass()方法
    // 和上面的类的方法loadFactoryNames里面的第一个参数对应
    // 返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
    }
  • 这个方法getCandidateConfigurations()又调用了 SpringFactoriesLoader 类的loadFactoryNames静态方法,我们进入SpringFactoriesLoader类loadFactoryNames() 方法,获取所有的加载配置

    1
    2
    3
    4
    5
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    //这里它又调用了 loadSpringFactories 方法
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }
  • 我们继续点击查看 loadSpringFactories 方法

    • 项目资源:META-INF/spring.factories

    • 系统资源:META-INF/spring.factories

    • 从这些资源中配置了所有的nextElement(自动配置),封装成properties

      1
      2
      //将所有的资源加载到配置类中(将下面的抽离出来分析,第15行)
      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
      //获得classLoader,我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身
      MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
      if (result != null) {
      return result;
      } else {
      try {
      //去获取一个资源 "META-INF/spring.factories"
      Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
      LinkedMultiValueMap result = new LinkedMultiValueMap();
      //判断有没有更多的元素,将读取到的资源循环遍历,封装成为一个Properties
      while(urls.hasMoreElements()) {
      URL url = (URL)urls.nextElement();
      UrlResource resource = new UrlResource(url);
      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
      Iterator var6 = properties.entrySet().iterator();
      while(var6.hasNext()) {
      Entry<?, ?> entry = (Entry)var6.next();
      String factoryClassName = ((String)entry.getKey()).trim();
      String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
      int var10 = var9.length;
      for(int var11 = 0; var11 < var10; ++var11) {
      String factoryName = var9[var11];
      result.add(factoryClassName, factoryName.trim());
      }
      }
      }
      cache.put(classLoader, result);
      return result;
      } catch (IOException var13) {
      throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
      }
      }
      }
    • 发现一个多次出现的文件:spring.factories,全局搜索它

spring.factories

我们根据源头打开spring.factories,看到了很多自动配置的文件;这就是自动配置根源所在。

1595415587435

每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;

案例说明

我们以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration

//启动指定类的ConfigurationProperties功能;
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class})

//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
type = Type.SERVLET
)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)

public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}

//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
//。。。。。。。
}

一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;

  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;

  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;

  • 配置文件能配置什么就可以参照某个功能对应的这个属性类

    1
    2
    3
    4
    5
    //从配置文件中获取指定的值和bean的属性进行绑定
    @ConfigurationProperties(prefix = "spring.http")
    public class HttpProperties {
    // .....
    }

我们去配置文件里面试试前缀,看提示

1595493884773

所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类,然后将这些都汇总成为一个实例并加载到IOC容器中。

结论

  1. SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
  2. 将这些值作为自动配置类导入容器,自动配置类就生效,帮我们进行自动配置工作;
  3. 以前我们需要自动配置的东西,现在springboot帮我们做了
  4. 整合JavaEE,整体解决方案和自动配置的东西都在springboot-autoconfigure的jar包中;
  5. 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器中
  6. 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件,并自动配置xxxProperties;
  7. 有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;
  8. springboot配置文件到底能写什么?怎么写?有两种查看方式:

1.4 main方法

视频教程

SpringApplication.run方法主要分两部分,一是SpringApplication的实例化,二是run方法的执行。

1
2
3
4
5
6
7
8
9
@SpringBootApplication
public class Springboot01HellowordApplication {

public static void main(String[] args) {
// 该方法返回一个ConfigurableApplicationContext对象
// 参数一:应用入口的类; 参数二:命令行参数
SpringApplication.run(Springboot01HellowordApplication.class, args);
}
}

整体流程:

  • 创建 SpringApplication

    • 保存一些信息。
    • 判定当前应用的类型。ClassUtils。Servlet
    • ==bootstrappers==:初始启动引导器List<Bootstrapper>):去spring.factories文件中找org.springframework.boot.==Bootstrapper==
    • 找 ==ApplicationContextInitializer==;去spring.factories==ApplicationContextInitializer==
      • List<ApplicationContextInitializer<?>> initializers
    • ==ApplicationListener== 应用监听器。去spring.factories==ApplicationListener==
      • List<ApplicationListener<?>> listeners
  • 运行 SpringApplication

    • StopWatch
    • 记录应用的启动时间
    • 创建引导上下文(Context环境)createBootstrapContext()
      • 获取到所有之前的 ==bootstrappers==挨个执行 intitialize() 来完成对引导启动器上下文环境设置
    • 让当前应用进入headless模式。java.awt.headless
    • 获取所有 ==RunListener==(运行监听器)【为了方便所有Listener进行事件感知】
      • getSpringFactoriesInstances 去spring.factories 找 ==SpringApplicationRunListener==
    • 遍历 SpringApplicationRunListener 调用 starting 方法;
      • ==相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。==
    • 保存命令行参数;ApplicationArguments
    • 准备环境 prepareEnvironment();
      • 返回或者创建基础环境信息对象。StandardServletEnvironment
      • 配置环境信息对象。
        • 读取所有的配置源的配置属性值。
      • 绑定环境信息
      • 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
    • 创建IOC容器(createApplicationContext())
      • 根据项目类型(Servlet)创建容器,
      • 当前会创建 AnnotationConfigServletWebServerApplicationContext
    • 准备ApplicationContext IOC容器的基本信息 prepareContext()
      • 保存环境信息
      • IOC容器的后置处理流程。
      • 应用初始化器;applyInitializers;
        • 遍历所有的 ==ApplicationContextInitializer== 。调用 initialize.。来对ioc容器进行初始化扩展功能
        • 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared
      • 所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded;
    • 刷新IOC容器。refreshContext
      • 创建容器中的所有组件(参见Bean的完整生命周期)
    • 容器刷新完成后工作?afterRefresh
    • 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
    • 调用所有runners;callRunners()
      • 获取容器中的 ==ApplicationRunner==
      • 获取容器中的 ==CommandLineRunner==
      • 合并所有runner并且按照@Order进行排序
      • 遍历所有的runner。调用 run 方法
    • 如果以上有异常,
      • 调用Listener 的 failed
    • 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
    • running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed

image-20201127220232413

2 Bean对象注入容器

在SpringBoot中将对象注入到容器中有多种方法。

2.1 @ComponentScan

通过@ComponentScan自动扫包+组件标注注解(@Component/@Controller/@Service/@Repository)实现。

@ComponentScan可以指定include/exclude某些类,具体的规则通过@Filter参数指定。在Java8中@ComponentScan可以重复标记。

1
2
3
4
5
6
7
8
9
10
11
12
@ComponentScan(value="com.meimeixia", includeFilters={
/* type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
* classes:我们需要Spring在扫描时,只包含@Controller注解标注的类
*/
@Filter(type=FilterType.ANNOTATION, classes={Controller.class})
}, useDefaultFilters=false) // value指定要扫描的包
@ComponentScan(value="com.meimeixia", includeFilters={
/* type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
* classes:我们需要Spring在扫描时,只包含@Service注解标注的类
*/
@Filter(type=FilterType.ANNOTATION, classes={Service.class})
}, useDefaultFilters=false) // value指定要扫描的包

如果不是Java8,但又需要设置多个条件,可以使用如下方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ComponentScans(value={
@ComponentScan(value="com.meimeixia", includeFilters={
/* type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
* classes:我们需要Spring在扫描时,只包含@Controller注解标注的类
*/
@Filter(type=FilterType.ANNOTATION, classes={Controller.class})
}, useDefaultFilters=false), // value指定要扫描的包
@ComponentScan(value="com.meimeixia", includeFilters={
/* type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
* classes:我们需要Spring在扫描时,只包含@Service注解标注的类
*/
@Filter(type=FilterType.ANNOTATION, classes={Service.class})
}, useDefaultFilters=false) // value指定要扫描的包
})

这种注入方式只适用于自己写的类,对于自己写的类,可以在类上面加上组件标注注解,但是对于引用的别人的类,该方法就无法使用。

@Filter使用进阶

Filter支持以下5种类型:

1
2
3
4
5
6
public enum FilterType {
// 被标记注解的类型、类的class类型、ASPECTJ表达式、正则表达式、自定义类型
ANNOTATION, ASSIGNABLE_TYPE, ASPECTJ, REGEX, CUSTOM;

private FilterType() { /* compiled code */ }
}

前两种类型很好理解,重点讲CUSTOM类型的用法。

1
2
3
4
5
6
7
@ComponentScan(value="com.meimeixia", includeFilters={
/*
* type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
*/
// 指定新的过滤规则,这个过滤规则是我们自个自定义的,过滤规则就是由我们这个自定义的MyTypeFilter类返回true或者false来代表匹配还是没匹配
@Filter(type=FilterType.CUSTOM, classes={MyTypeFilter.class})
}, useDefaultFilters=false) // value指定要扫描的包

自定义过滤规则类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyTypeFilter implements TypeFilter {
/**
* 参数:
* metadataReader:读取到的当前正在扫描的类的信息
* metadataReaderFactory:可以获取到其他任何类的信息的(工厂)
*/
@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();
System.out.println("--->" + className);
// 现在来指定一个规则
if (className.contains("er")) {
return true; // 只要类名包含“er”,就会被包含在容器中(无需被@Component标注)
}
return false;
}
}

2.2 @Bean

使用@Configuration和@Bean注解,手动构建对象,并把它注入到容器中去。被@Configuration标记的配置类相当于Spring中的bean.xml文件,@Bean则相当于<bean>标签。

使用这种方式,被@Configuration标记的配置类也会被注入到容器中去。

  • 用@Bean给容器中添加组件,方法名就是组件的id,返回类型就是组件类型,返回值就是组件在容器中的实例。
  • 被@Configuration标记的配置类的id就是该配置类的类名(首字母自动转为小写,不是全类名)。

以下是@Configuration的使用案例,proxyBeanMethods属性用来设置Full模式与Lite模式

  • 配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
  • 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// ####################Configuration使用示例######################
/**
* 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2、配置类本身也是组件
* 3、proxyBeanMethods:代理bean的方法
* Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
* Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
* 组件依赖必须使用Full模式默认。其他默认是Lite模式
*/
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

/**
* Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}

@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}

// ##################@Configuration测试代码如下#####################
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
public class MainApplication {

public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

//3、从容器中获取组件
Pet tom01 = run.getBean("tom", Pet.class);
Pet tom02 = run.getBean("tom", Pet.class);
System.out.println("组件:"+(tom01 == tom02)); // true

//4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);

//如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
//保持组件单实例
User user = bean.user01();
User user1 = bean.user01();
System.out.println(user == user1);

User user01 = run.getBean("user01", User.class);
Pet tom = run.getBean("tom", Pet.class);

System.out.println("用户的宠物:"+(user01.getPet() == tom)); // true/false与proxyBeanMethods有关
}
}

@Scope

默认@Bean使用的是Singleton单例模式,每次获取的都是同一个对象,并且是饿汉式加载,可以使用@Scope修改为Prototype,每次获取对象均不同,该模式下会启用懒汉式加载。

@Lazy

使用该注解会将单例模式的Bean修改为懒加载模式。

2.3 @Import

简单Import

也可以使用@Import注解注入bean到容器中去.

@Import注解接收一个Class[],被注入bean的id默认是导入类的全类名,bean默认使用无参构造做初始化。

1
2
3
4
5
6
7
8
9
/*
* @Import({User.class, DBHelper.class})
* 给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
*/
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

}

ImportSelector

可以自己创建一个类,并且实现 ImportSelector 接口,重写 ImportSelectorselectImports 方法,selectImports 方法返回的是需要被注入类的全类名,然后把这个自己创建的类加入@Import的数组中。

被注入bean的名字默认是导入类的全类名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Import(TestImportSelector.class)  // 通过@ImportSelector注入
@Configuration
public class TestConfiguration {
@Bean
public String StrBean(){
return "this is a string";
}
}

public class TestImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"qizi.Main"};
}

@Override
public Predicate<String> getExclusionFilter() {
return null;
}
}

ImportBeanDefinitionRegistrar

自己创建一个类,继承ImportBeanDefinitionRegistrar并重写方法,手动注册beanDefinition,可以自定义bean的id名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用ImportBeanDefinitionRegistrar注入
@Import({TestImportSelector.class, TestImportBeanDefinitionRegistrar.class})
@Configuration
public class TestConfiguration {
@Bean
public String StrBean(){
return "this is a string";
}
}

public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 当前类的注解
* @param registry beandefinition注册类
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("diy_hashmap_id", new RootBeanDefinition(HashMap.class));
}
}

2.4 @Conditional

有一个专门的注解叫@Conditional,需要和@Configuration等注解配合使用。可以根据自己指定的类来决定是否加载被标记类到IOC容器,必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效。

也有一些已经封装好的条件装配注解:当满足ConditionalOn指定的条件时,才进行组件注入。

@ConditionalOn扩展注解 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnJava 容器中存在指定Bean
@ConditionalOnMissingBean 容器中不存在指定Bean
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项

那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

自动配置类是否生效

我们可以在application.properties通过启用 debug=true属性;在控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

1
2
# 开启springboot的调试类
debug=true
  • Positive matches:(自动配置类启用的:正匹配)
  • Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
  • Unconditional classes: (没有条件的类)
  • 【演示:查看输出的日志】

2.5 @ImportResource

考虑到部分存量代码是用Spring时代的xml方法配置的,为了兼容历史代码,可以使用@ImportResource注解指定bean.xml配置的路径,加载xml配置中的bean。

1
2
3
@ImportResource("classpath:beans.xml")
public class MyConfig {
}

2.6 FactoryBean

一般情况下,Spring是通过反射机制利用bean的class属性指定实现类来实例化bean的。在某些情况下,实例化bean过程比较复杂,如果按照传统的方式,那么则需要在标签中提供大量的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可以得到一个更加简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化bean的逻辑。

FactoryBean接口对于Spring框架来说占有非常重要的地位,Spring自身就提供了70多个FactoryBean接口的实现。它们隐藏了实例化一些复杂bean的细节,给上层应用带来了便利。从Spring 3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean<T>的形式。

1
2
3
4
5
6
7
8
9
10
11
public interface FactoryBean <T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

@Nullable
T getObject() throws Exception; // 返回由FactoryBean创建的bean实例

@Nullable
java.lang.Class<?> getObjectType(); // 返回bean实例对象的类型

default boolean isSingleton() { /* compiled code */ }
}

需要注意的是:当配置文件中标签的class属性配置的实现类是FactoryBean时,通过 getBean()方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()代理了getBean()方法。如果确实需要获取FactoryBean,使用时需要在context#getBean的beanid前面加上一个&符号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ColorFactoryBean implements FactoryBean<Color> {

@Override
public Color getObject() throws Exception { // 返回一个Color对象到容器中
System.out.println("ColorFactoryBean...getObject...");
return new Color();
}

@Override
public Class<?> getObjectType() { // 返回这个对象的类型
return Color.class;
}

@Override
public boolean isSingleton() { // 是否为单例
return false;
}
}

public class MainConfig{
@Bean
public ColorFactoryBean colorFactoryBean() {
return new ColorFactoryBean();
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testImport() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name); // 会看到有一个definitionName叫colorFactoryBean
}

// 工厂bean获取的是调用getObject方法创建的对象
Object bean2 = applicationContext.getBean("colorFactoryBean");
System.out.println("bean的类型:" + bean2.getClass()); // 实际上这个colorFactoryBean是Color.class类型

// 获取FactoryBean对象本省
Object bean4 = applicationContext.getBean("&colorFactoryBean");
System.out.println(bean4.getClass()); // FactoryBean.class
}

3 Bean常用拓展点

3.1 初始化和销毁

如果是使用XML配置文件的方式配置bean的话,那么可以在标签中指定bean的初始化和销毁方法,如下所示。

1
2
3
4
<bean id="person" class="com.meimeixia.bean.Person" init-method="init" destroy-method="destroy">
<property name="age" value="18"></property>
<property name="name" value="liayun"></property>
</bean>

这里,需要注意的是,在我们自己写的Person类中,需要存在init()方法和destroy()方法。而且Spring中还规定,这里的init()方法和destroy()方法必须是无参方法,但可以抛出异常。

@Bean注解指定

可以通过@Bean注解的两个参数指定初始化和销毁方法。

1
2
3
4
5
6
7
@Configuration
public class MainConfigOfLifeCycle {
@Bean(initMethod="init", destroyMethod="destroy")
public Car car() {
return new Car(); // Car类中需要提供名叫init和destory的两个方法
}
}

初始化方法和销毁方法的调用时机?

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

实现InitializingBean和DisposableBean接口

如果不使用@Bean指定初始化和销毁方法,也可以实现InitializingBean、DisposableBean这两个接口,这样也能实现指定初始化和销毁方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class Cat implements InitializingBean, DisposableBean {

public Cat() {
System.out.println("cat constructor...");
}

@Override // 会在bean创建完成,并且属性都赋好值以后进行调用
public void afterPropertiesSet() throws Exception {
System.out.println("cat afterPropertiesSet...");
}

@Override // 会在容器关闭的时候进行调用
public void destroy() throws Exception {
System.out.println("cat destroy...");
}
}

@PostConstruct和@PreDestroy注解

@PostConstruct和@PreDestroy是Java依据JSR-250规范定义的两个注解。

  • Constructor(构造方法)→@Autowired(依赖注入)→@PostConstruct(注释的方法)
  • 调用destroy()方法→@PreDestroy→destroy()方法→bean销毁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class Dog {
public Dog() {
System.out.println("dog constructor...");
}

@PostConstruct
public void init() { // 在对象创建完成并且属性赋值完成之后调用
System.out.println("dog...@PostConstruct...");
}

@PreDestroy
public void destory() { // 在容器销毁(移除)对象之前调用
System.out.println("dog...@PreDestroy...");
}
}

3.2 BeanPostProcessor

BeanPostProcessor后置处理器。Spring容器中的每一个bean对象初始化前后,都会执行BeanPostProcessor接口的实现类中的这两个方法

postProcessBeforeInitialization方法会在bean实例化和==属性设置之后,自定义初始化方法之前==被调用,而postProcessAfterInitialization方法会在自定义==初始化方法执行之后==被调用。当容器中存在多个BeanPostProcessor的实现类时,会按照它们在容器中注册的顺序执行。对于自定义的BeanPostProcessor实现类,还可以让其实现Ordered接口自定义排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component // 将后置处理器加入到容器中,这样Spring就能让它工作了
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization..." + beanName + "=>" + bean);
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization..." + beanName + "=>" + bean);
return bean;
}

@Override
public int getOrder() { // 自定义后置处理器排序
return 3;
}
}

3.3 XxxAware接口

XxxAware使用案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 以Red类为例来讲解ApplicationContextAware接口、BeanNameAware接口以及EmbeddedValueResolverAware接口
*/
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {

private ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("传入的IOC:" + applicationContext);
this.applicationContext = applicationContext;
}

/**
* 参数name:IOC容器创建当前对象时,为这个对象起的名字
*/
@Override
public void setBeanName(String name) {
System.out.println("当前bean的名字:" + name);
}

/**
* 参数resolver:IOC容器启动时会自动地将这个String值的解析器传递过来给我们
*/
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
String resolveStringValue = resolver.resolveStringValue("你好,${os.name},我的年龄是#{20*18}");
System.out.println("解析的字符串:" + resolveStringValue);
}

}

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

3.4 AOP使用及原理

引入AOP依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>

定义目标类

1
2
3
4
5
6
public class MathCalculator {
public int div(int i, int j) {
System.out.println("MathCalculator...div...");
return i / j;
}
}

定义切面类

AOP中的通知方法及其对应的注解与含义如下:

  • 前置通知(对应的注解是@Before):在目标方法运行之前运行
  • 后置通知(对应的注解是@After):在目标方法运行结束之后运行,无论目标方法是正常结束还是异常结束都会执行
  • 返回通知(对应的注解是@AfterReturning):在目标方法正常返回之后运行
  • 异常通知(对应的注解是@AfterThrowing):在目标方法运行出现异常之后运行
  • 环绕通知(对应的注解是@Around):动态代理,我们可以直接手动推进目标方法运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Aspect  // 告诉Spring当前类是一个切面类
public class LogAspects {

// 如果切入点表达式都一样的情况下,那么我们可以抽取出一个公共的切入点表达式
@Pointcut("execution(public int com.meimeixia.aop.MathCalculator.*(..))")
public void pointCut() {}

// @Before:在目标方法(即div方法)运行之前切入,public int com.meimeixia.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入
// @Before("public int com.meimeixia.aop.MathCalculator.*(..)")
@Before("pointCut()") // 抽取公用的切点
public void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs(); // 拿到参数列表,即目标方法运行需要的参数列表
System.out.println(joinPoint.getSignature().getName() + "运行......@Before,参数列表是:{" + Arrays.asList(args) + "}");
}

// 在目标方法(即div方法)结束时被调用
// @After("public int com.meimeixia.aop.MathCalculator.*(..)")
@After("pointCut()") // 抽取公用的切点
public void logEnd(JoinPoint joinPoint) {
// System.out.println("除法结束......@After");
System.out.println(joinPoint.getSignature().getName() + "结束......@After");
}

// 在目标方法(即div方法)正常返回了,有返回值,被调用
// @AfterReturning("public int com.meimeixia.aop.MathCalculator.*(..)")
@AfterReturning("pointCut()") // 抽取公用的切点
public void logReturn(JoinPoint joinPoint, Object result) { // 一定要注意:JoinPoint这个参数要写,一定不能写到后面,它必须出现在参数列表的第一位,否则Spring也是无法识别的,就会报错
System.out.println(joinPoint.getSignature().getName() + "正常返回......@AfterReturning,运行结果是:{" + result + "}");
}

// 在目标方法(即div方法)出现异常,被调用
// @AfterThrowing("public int com.meimeixia.aop.MathCalculator.*(..)")
@AfterThrowing("pointCut()") // 抽取公用的切点
public void logException() {
System.out.println("除法出现异常......异常信息:{}");
}
}

开启基于注解的AOP模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {

// 将业务逻辑类(目标方法所在类)加入到容器中
@Bean
public MathCalculator calculator() {
return new MathCalculator();
}

// 将切面类加入到容器中
@Bean
public LogAspects logAspects() {
return new LogAspects();
}
}

AOP底层原理

最后,我们还需要对AOP原理做一个简单的总结,完美结束对其研究的旅程。

  1. 利用@EnableAspectJAutoProxy注解来开启AOP功能

  2. 这个AOP功能是怎么开启的呢?主要是通过@EnableAspectJAutoProxy注解向IOC容器中注册一个AnnotationAwareAspectJAutoProxyCreator组件来做到这点的

  3. AnnotationAwareAspectJAutoProxyCreator组件是一个后置处理器

  4. 该后置处理器是怎么工作的呢?在IOC容器创建的过程中,我们就能清楚地看到这个后置处理器是如何创建以及注册的,以及它的工作流程。

    1. 首先,在创建IOC容器的过程中,会调用refresh()方法来刷新容器,而在刷新容器的过程中有一步是来注册后置处理器的,如下所示:

      1
      registerBeanPostProcessors(beanFactory); // 注册后置处理器,在这一步会创建AnnotationAwareAspectJAutoProxyCreator对象

      其实,这一步会为所有后置处理器都创建对象。

    2. 在刷新容器的过程中还有一步是来完成BeanFactory的初始化工作的,如下所示:

      1
      finishBeanFactoryInitialization(beanFactory); // 完成BeanFactory的初始化工作。所谓的完成BeanFactory的初始化工作,其实就是来创建剩下的单实例bean的。

      很显然,剩下的单实例bean自然就包括MathCalculator(业务逻辑类)和LogAspects(切面类)这两个bean,因此这两个bean就是在这儿被创建的。

      1. 创建业务逻辑组件和切面组件
      2. 在这两个组件创建的过程中,最核心的一点就是AnnotationAwareAspectJAutoProxyCreator(后置处理器)会来拦截这俩组件的创建过程
      3. 怎么拦截呢?主要就是在组件创建完成之后,判断组件是否需要增强。如需要,则会把切面里的通知方法包装成增强器,然后再为业务逻辑组件创建一个代理对象。我们也认真仔细探究过了,在为业务逻辑组件创建代理对象的时候,使用的是cglib来创建动态代理的。当然了,如果业务逻辑类有实现接口,那么就使用jdk来创建动态代理。一旦这个代理对象创建出来了,那么它里面就会有所有的增强器。这个代理对象创建完以后,IOC容器也就创建完了。接下来,便要来执行目标方法了。
  5. 执行目标方法

    1. 此时,其实是代理对象来执行目标方法
    2. 使用CglibAopProxy类的intercept()方法来拦截目标方法的执行,拦截的过程如下:
      1. 得到目标方法的拦截器链,所谓的拦截器链其实就是每一个通知方法又被包装为了方法拦截器,即MethodInterceptor
      2. 利用拦截器的链式机制(责任链模式),依次进入每一个拦截器中进行执行
      3. 最终,整个的执行效果就会有两套:(据说Spring5中下面的拦截顺序有调整)
        • 目标方法正常执行:前置通知→目标方法→后置通知→返回通知
        • 目标方法出现异常:前置通知→目标方法→后置通知→异常通知

3.5 声明式事务

添加c3p0数据源、MySQL数据库驱动以及spring-jdbc的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>

引入spring-jdbc模块后就可以用Spring提供的JDBC模板(即JdbcTemplate)来操作数据库,从而简化对数据库的操作以及事务控制。

配置类bean注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@EnableTransactionManagement // 开启基于注解的事务管理功能的
@ComponentScan("com.meimeixia.tx")
@Configuration
public class TxConfig {
@Bean // 注册c3p0数据源
public DataSource dataSource() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("liayun");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
return dataSource;
}

@Bean
public JdbcTemplate jdbcTemplate() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}

@Bean // 注册事务管理器
public PlatformTransactionManager platformTransactionManager() throws Exception {
return new DataSourceTransactionManager(dataSource());
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;

public void insert() {
String sql = "insert into `tbl_user`(username, age) values(?, ?)";
String username = UUID.randomUUID().toString().substring(0, 5);
jdbcTemplate.update(sql, username, 19); // 增删改都来调用这个方法
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class UserService {
@Autowired
private UserDao userDao;

@Transactional
public void insertUser() {
userDao.insert();
// otherDao.other(); // 该方法中的业务逻辑势必不会像现在这么简单,肯定还会调用其他dao的方法
System.out.println("插入完成...");
int i = 10 / 0;
}
}

声明式事务的原理

首先,使用AutoProxyRegistrar向Spring容器里面注册一个后置处理器,这个后置处理器会负责给我们包装代理对象。然后,使用ProxyTransactionManagementConfiguration(配置类)再向Spring容器里面注册一个事务增强器,此时,需要用到事务拦截器。最后,代理对象执行目标方法,在这一过程中,便会执行到当前Spring容器里面的拦截器链,而且每次在执行目标方法时,如果出现了异常,那么便会利用事务管理器进行回滚事务,如果执行过程中一切正常,那么则会利用事务管理器提交事务。

分析细节:Spring注解驱动开发第35讲——声明式事务原理的源码分析

3.6 BeanFactoryPostProcessor

与bean实例的后置处理器BeanPostProcessor相似,BeanFactoryPostProcessor是BeanFactory(创建bean的工厂)的后置处理器。

BeanFactoryPostProcessor是Spring提供的一个增强点,该增强点的增强时机是“在BeanFactory标准初始化之后,所有的beanDefinition已经保存加载到BeanFactory中,但是bean的实例还未被创建,可以来定制和修改BeanFactory里面的一些内容”

可以通过实现BeanFactoryPostProcessor接口来自定义自己的bean工厂后置处理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("MyBeanFactoryPostProcessor...postProcessBeanFactory..."); // 这个时候我们所有的bean还没被创建
// 但是我们可以看一下通过Spring给我们传过来的这个beanFactory,我们能拿到什么
int count = beanFactory.getBeanDefinitionCount(); // 我们能拿到有几个bean定义
String[] names = beanFactory.getBeanDefinitionNames(); // 除此之外,我们还能拿到每一个bean定义的名字
System.out.println("当前BeanFactory中有" + count + "个Bean");
System.out.println(Arrays.asList(names));
}
}

分析细节:Spring注解驱动开发第36讲——从源码角度理解BeanFactoryPostProcessor的原理

3.7 BeanDefinitionRegistryPostProcessor

BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor的子接口,同样也是Spring提供的一个增强点。

1
2
3
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor{
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

BeanDefinitionRegistryPostProcessor的切入时机是在所有bean定义信息将要被加载,但是bean实例还未创建的时候

即:BeanDefinitionRegistryPostProcessor ➡️ 加载beanDefinition ➡️ BeanFactoryPostProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("MyBeanDefinitionRegistryPostProcessor...bean的数量:" + beanFactory.getBeanDefinitionCount());
}

/**
* 这个BeanDefinitionRegistry就是Bean定义的保存中心,这个注册中心里面存储了所有的bean定义信息
* BeanFactory就是按照BeanDefinitionRegistry里面保存的每一个bean定义信息来创建bean实例
* bean定义信息包括有:这个bean是单例的还是多例的、bean的类型是什么以及bean的id是什么。
* 也就是说,这些信息都是存在BeanDefinitionRegistry里面的。
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// TODO Auto-generated method stub
System.out.println("postProcessBeanDefinitionRegistry...bean的数量:" + registry.getBeanDefinitionCount());
// 除了查看bean的数量之外,我们还可以给容器里面注册一些bean,我们以前也简单地用过
/*
* 第一个参数:我们将要给容器中注册的bean的名字
* 第二个参数:BeanDefinition对象
*/
// RootBeanDefinition beanDefinition = new RootBeanDefinition(Blue.class); // 现在我准备给容器中添加一个Blue对象
// 咱们也可以用另外一种办法,即使用BeanDefinitionBuilder这个构建器生成一个BeanDefinition对象,很显然,这两种方法的效果都是一样的
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Blue.class).getBeanDefinition();
registry.registerBeanDefinition("hello", beanDefinition);
}

}

BeanDefinitionRegistryPostProcessor组件执行原理小结

  1. 创建IOC容器
  2. 创建IOC容器时,要调用一个刷新方法,即refresh方法
  3. 从IOC容器中获取到所有的BeanDefinitionRegistryPostProcessor组件,并依次触发它们的postProcessBeanDefinitionRegistry方法,然后再来触发它们的postProcessBeanFactory方法
  4. 再来从IOC容器中获取到所有的BeanFactoryPostProcessor组件,并依次触发它们的postProcessBeanFactory方法

分析细节:Spring注解驱动开发第37讲——BeanDefinitionRegistryPostProcessor执行原理

3.8 ApplicationListener

ApplicationListener是Spring提供的基于观察者模式实现的应用监听器。当有指定的事件发生时,就会触发对应的监听器,执行onApplicationEvent方法。该接口中带的泛型就是我们要监听的事件。

1
2
3
public interface ApplicationListener <E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E e);
}

案例:

1
2
3
4
5
6
7
8
9
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {

// 当容器中发布此事件以后,下面这个方法就会被触发
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("收到事件:" + event);
}
}

Spring默认的四个事件:

  • ContextClosedEvent 容器关闭事件
  • ContextRefreshedEvent 容器刷新事件
  • ContextStartedEvent 容器开始事件
  • ContextStoppedEvent 容器停止事件

发布自定义事件

  1. 写一个监听器来监听某个事件。当然了,监听的这个事件必须是ApplicationEvent及其子类。
  2. 把监听器加入到容器中,这样Spring才能知道有这样一个监听器。
  3. 只要容器中有相关事件发布,那么我们就能监听到这个事件。
1
2
3
4
5
6
7
8
9
10
11
12
public class IOCTest_Ext {

@Test
public void test01() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ExtConfig.class); // 触发容器开始事件

applicationContext.publishEvent(new ApplicationEvent(new String("diy事件")));

applicationContext.close(); // 触发容器关闭事件
}

}

事件监听机制原理

  1. IOC容器启动过程中,通过在refresh()方法中调用registerListeners()方法,将所有的监听器注册到事件派发器中去
  2. 事件发布时,首先获取到事件派发器,遍历所有监听器,向所有符合条件的监听器派发事件
  3. 监听器获取到事件后执行onApplicationEvent方法

3.9 @EventListener

除了通过实现ApplicationListener接口完成事件监听以外,还可以通过使用@EventListener注解,让任意方法都能监听事件。

1
2
3
4
5
6
7
8
9
10
@Service
public class UserService {

// @EventListener(classes=ApplicationEvent.class)
@EventListener(classes={ApplicationEvent.class}) // 可以指定监听多个事件
public void listen() {
System.out.println("UserService...监听到的事件:" + event);
}

}

@EventListener注解原理

Spring会使用EventListenerMethodProcessor这个处理器来解析方法上的@EventListener注解。

EventListenerMethodProcessor实现了一个接口,叫SmartInitializingSingleton,这个接口有一个afterSingletonsInstantiated方法,该方法是在所有的单实例bean已经全部被创建完以后才会被执行。

在创建好所有的单实例bean后,判断每一个bean对象是否是SmartInitializingSingleton这个接口类型的,如果是,那么便调用它里面的afterSingletonsInstantiated方法,而该方法就是SmartInitializingSingleton接口中定义的方法。

4 完整生命周期小结

1、prepareRefresh(); 刷新前的预处理

1
2
3
1. initPropertySources()初始化一些属性设置;子类自定义个性化的属性设置方法;
2. getEnvironment().validateRequiredProperties();检验属性的合法等
3. earlyApplicationEvents= new LinkedHashSet<ApplicationEvent>(); 保存容器中的一些早期的事件

2、obtainFreshBeanFactory(); 获取BeanFactory

1
2
3
4
5
1. refreshBeanFactory();刷新【创建】BeanFactory;
- 创建了一个this.beanFactory = new DefaultListableBeanFactory();
- 设置id;
2. getBeanFactory();返回刚才GenericApplicationContext创建的BeanFactory对象;
3. 将创建的BeanFactory【DefaultListableBeanFactory】返回;

3、prepareBeanFactory(beanFactory); BeanFactory的预准备工作(BeanFactory进行一些设置)

1
1. 设置BeanFactory的类加载器、支持表达式解析器...2. 添加部分BeanPostProcessor【ApplicationContextAwareProcessor】3. 设置忽略的自动装配的接口EnvironmentAware、EmbeddedValueResolverAware、xxx;4. 注册可以解析的自动装配;我们能直接在任何组件中自动注入:	- BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext5. 添加BeanPostProcessor【ApplicationListenerDetector】6. 添加编译时的AspectJ;7. 给BeanFactory中注册一些能用的组件;    - environment【ConfigurableEnvironment】、    - systemProperties【Map<String, Object>】、    - systemEnvironment【Map<String, Object>

4、postProcessBeanFactory(beanFactory); BeanFactory准备工作完成后进行的后置处理工作

1
- 子类通过重写这个方法来在BeanFactory创建并预准备完成以后做进一步的设置

==以上是BeanFactory的创建及预准备工作==

5、invokeBeanFactoryPostProcessors(beanFactory); 执行BeanFactoryPostProcessor的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BeanFactoryPostProcessor:BeanFactory的后置处理器。在BeanFactory标准初始化之后执行的;
两个接口:BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor
1. 先执行BeanDefinitionRegistryPostProcessor
- 获取所有的BeanDefinitionRegistryPostProcessor;
- 看先执行实现了PriorityOrdered优先级接口的BeanDefinitionRegistryPostProcessor、
postProcessor.postProcessBeanDefinitionRegistry(registry)
- 在执行实现了Ordered顺序接口的BeanDefinitionRegistryPostProcessor;
postProcessor.postProcessBeanDefinitionRegistry(registry)
- 最后执行没有实现任何优先级或者是顺序接口的BeanDefinitionRegistryPostProcessors;
postProcessor.postProcessBeanDefinitionRegistry(registry)
2. 加载BeanDefinition
3. 再执行BeanFactoryPostProcessor的方法
- 获取所有的BeanFactoryPostProcessor
- 看先执行实现了PriorityOrdered优先级接口的BeanFactoryPostProcessor、
postProcessor.postProcessBeanFactory()
- 在执行实现了Ordered顺序接口的BeanFactoryPostProcessor;
postProcessor.postProcessBeanFactory()
- 最后执行没有实现任何优先级或者是顺序接口的BeanFactoryPostProcessor;
postProcessor.postProcessBeanFactory()

6、registerBeanPostProcessors(beanFactory); 注册BeanPostProcessor(Bean的后置处理器)【 intercept bean creation】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 不同接口类型的BeanPostProcessor;在Bean创建前后的执行时机是不一样的
- BeanPostProcessor、
- DestructionAwareBeanPostProcessor、
- InstantiationAwareBeanPostProcessor、
- SmartInstantiationAwareBeanPostProcessor、
- MergedBeanDefinitionPostProcessor【internalPostProcessors】、

1. 获取所有的 BeanPostProcessor;后置处理器都默认可以通过PriorityOrdered、Ordered接口来执行优先级
2. 先注册PriorityOrdered优先级接口的BeanPostProcessor;
把每一个BeanPostProcessor;添加到BeanFactory中
beanFactory.addBeanPostProcessor(postProcessor);
3. 再注册Ordered接口的
4. 最后注册没有实现任何优先级接口的
5. 最终注册MergedBeanDefinitionPostProcessor;
6. 注册一个ApplicationListenerDetector;来在Bean创建完成后检查是否是ApplicationListener,如果是,applicationContext.addApplicationListener((ApplicationListener<?>) bean);

7、initMessageSource(); 初始化MessageSource组件(做国际化功能;消息绑定,消息解析)

1
2
3
4
5
6
7
1. 获取BeanFactory
2. 看容器中是否有id为messageSource的,类型是MessageSource的组件
- 如果有赋值给messageSource,如果没有自己创建一个DelegatingMessageSource;
- MessageSource:取出国际化配置文件中的某个key的值;能按照区域信息获取;
3. 把创建好的MessageSource注册在容器中,以后获取国际化配置文件的值的时候,可以自动注入MessageSource;
- beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
- MessageSource.getMessage(String code, Object[] args, String defaultMessage, Locale locale);

8、initApplicationEventMulticaster(); 初始化事件派发器

1
2
3
4
1. 获取BeanFactory
2. 从BeanFactory中获取applicationEventMulticaster的ApplicationEventMulticaster;
3. 如果上一步没有配置;创建一个SimpleApplicationEventMulticaster
4. 将创建的ApplicationEventMulticaster添加到BeanFactory中,以后其他组件直接自动注入

9、onRefresh(); 留给子容器(子类)

1
- 子类重写这个方法,在容器刷新的时候可以自定义逻辑;

10、registerListeners(); 从容器中将所有项目里面的ApplicationListener注册到事件派发器

1
2
3
4
1. 从容器中拿到所有的ApplicationListener
2. 将每个监听器添加到事件派发器中;
- getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
3. 派发之前步骤产生的事件;

11、finishBeanFactoryInitialization(beanFactory); 初始化所有剩下的单实例bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
beanFactory.preInstantiateSingletons();  初始化后剩下的单实例bean
1. 获取容器中的所有Bean,依次进行初始化和创建对象
2. 获取Bean的定义信息;RootBeanDefinition
3. Bean不是抽象的,是单实例的,不是懒加载;
1)、判断是否是FactoryBean;是否是实现FactoryBean接口的Bean;
2)、不是工厂Bean。利用getBean(beanName);创建对象
0、getBean(beanName); ioc.getBean();
1、doGetBean(name, null, null, false);
2、先获取缓存中保存的单实例Bean。如果能获取到说明这个Bean之前被创建过(所有创建过的单实例Bean都会被缓存起来)
从private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);获取的
3、缓存中获取不到,开始Bean的创建对象流程;
4、标记当前bean已经被创建
5、获取Bean的定义信息;
6、【获取当前Bean依赖的其他Bean;如果有按照getBean()把依赖的Bean先创建出来;】
7、启动单实例Bean的创建流程;
1)、createBean(beanName, mbd, args);
2)、Object bean = resolveBeforeInstantiation(beanName, mbdToUse);让BeanPostProcessor先拦截返回代理对象;
【InstantiationAwareBeanPostProcessor】:提前执行;
先触发:postProcessBeforeInstantiation();
如果有返回值:触发postProcessAfterInitialization();
3)、如果前面的InstantiationAwareBeanPostProcessor没有返回代理对象;调用4)
4)、Object beanInstance = doCreateBean(beanName, mbdToUse, args);创建Bean
1)、【创建Bean实例】;createBeanInstance(beanName, mbd, args);
利用工厂方法或者对象的构造器创建出Bean实例;
2)、applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
调用MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition(mbd, beanType, beanName);
3)、【Bean属性赋值】populateBean(beanName, mbd, instanceWrapper);
赋值之前:
1)、拿到InstantiationAwareBeanPostProcessor后置处理器;
postProcessAfterInstantiation();
2)、拿到InstantiationAwareBeanPostProcessor后置处理器;
postProcessPropertyValues();
=====赋值之前:=====
3)、应用Bean属性的值;为属性利用setter方法等进行赋值;
applyPropertyValues(beanName, mbd, bw, pvs);
4)、【Bean初始化】initializeBean(beanName, exposedObject, mbd);
1)、【执行Aware接口方法】invokeAwareMethods(beanName, bean);执行xxxAware接口的方法
BeanNameAware\BeanClassLoaderAware\BeanFactoryAware
2)、【执行后置处理器初始化之前】applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
BeanPostProcessor.postProcessBeforeInitialization();
3)、【执行初始化方法】invokeInitMethods(beanName, wrappedBean, mbd);
1)、是否是InitializingBean接口的实现;执行接口规定的初始化;
2)、是否自定义初始化方法;
4)、【执行后置处理器初始化之后】applyBeanPostProcessorsAfterInitialization
BeanPostProcessor.postProcessAfterInitialization();
5)、注册Bean的销毁方法;
5)、将创建的Bean添加到缓存中singletonObjects;
ioc容器就是这些Map;很多的Map里面保存了单实例Bean,环境信息。。。。;

所有Bean都利用getBean创建完成以后;
1. 检查所有的Bean是否是SmartInitializingSingleton接口的;如果是;就执行afterSingletonsInstantiated();

12、finishRefresh(); 完成BeanFactory的初始化创建工作、IOC容器就创建完成

1
2
3
4
5
6
7
8
9
10
1. initLifecycleProcessor();初始化和生命周期有关的后置处理器;LifecycleProcessor
- 默认从容器中找是否有lifecycleProcessor的组件【LifecycleProcessor】;如果没有,new DefaultLifecycleProcessor();
- 加入到容器;
- 写一个LifecycleProcessor的实现类,可以在BeanFactory
- void onRefresh();
- void onClose();
2. getLifecycleProcessor().onRefresh();
- 拿到前面定义的生命周期处理器(BeanFactory);回调onRefresh();
3. publishEvent(new ContextRefreshedEvent(this));发布容器刷新完成事件;
4. liveBeansView.registerApplicationContext(this);

image-20210409173738495

总结:

  1. Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息;
    • xml注册bean;<bean>
    • 注解注册Bean;@Service、@Component、@Bean、xxx
  2. Spring容器会合适的时机创建这些Bean
    • 用到这个bean的时候;利用getBean创建bean;创建好以后保存在容器中;
    • 统一创建剩下所有的bean的时候;finishBeanFactoryInitialization();
  3. 后置处理器;BeanPostProcessor
    • 每一个bean创建完成,都会使用各种后置处理器进行处理;来增强bean的功能;
      • AutowiredAnnotationBeanPostProcessor:处理自动注入
      • AnnotationAwareAspectJAutoProxyCreator:来做AOP功能;
    • 增强的功能注解:
      • AsyncAnnotationBeanPostProcessor
  4. 事件驱动模型;
    • ApplicationListener;事件监听;
    • ApplicationEventMulticaster;事件派发:

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!