5、java-spring、boot、安全认证面试题

本文全面介绍了Spring体系,涵盖Spring、Spring Boot、Spring Security和Shiro安全认证等内容。详细阐述了Spring Bean、控制反转、AOP等概念,以及Spring Boot自动配置原理和启动流程。同时,还对比了Spring Security和Shiro的特点,并介绍了Spring体系的各种启动流程。

Spring、boot、安全认证

一、Spring

1、什么是spring bean

1.1在 Spring 中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是一个由Spring IoC容器实例化、组装和管理的对象。

概念简单明了,我们提取处关键的信息:

bean是对象,一个或者多个不限定
bean由Spring中一个叫IoC的东西管理
我们的应用程序由一个个bean构成

1.2、Bean创建方式

可以通过三种不同的方式定义Spring bean:

1.使用构造型*@Component*注释(或其衍生物)注释你的类

2.编写在自定义Java配置类中使用*@Bean*注释的bean工厂方法

3.在XML配置文件中声明bean定义

1.3 Bean作用域

Spring bean的范围定义了框架在运行时创建的特定类的实例数。作用域还描述创建新对象的条件

Spring为你的bean提供了几个作用域。框架的核心有两个:

  • 单例 - 单个实例
  • 原型 - 多个实例

此外,Spring还附带了专门用于Web应用程序的bean作用域:

  • 请求
  • 会话
  • 全局会话
  • 应用级别Application

所有bean的默认作用域是单例。当bean具有单例作用域时,Spring只创建一个实例并在整个应用程序中共享它。单例是无状态对象的完美选择。如今,我们应用程序中的绝大多数bean都是无状态单例。

另一方面,如果对象包含状态,则应考虑其他作用域。要选择正确的一个,你应该问自己框架应该将该状态保留在内存中多长时间

1.4 如何设置作用域?

无论是使用*@Component直接注释还是使用@Bean创建工厂方法,该方法都是相同的。使用@Scope*批注及其字符串属性选择范围。

@Component
@Scope("prototype")
class MyPrototypeClass {
 //...
}
@Bean
@Scope("prototype")
MyPrototypeClass myPrototypeClass() {
 return new MyPrototypeClass();
}
  • 更重要的是,对于Web作用域,Spring附带了额外的别名注释。你可以使用这些注释代替*@Scope*:
  • @RequestScope
  • @SessionScope
  • @ApplicationScope

2 控制反转(IoC)

控制反转英文全称:Inversion of Control,简称就是IoC。

控制反转通过依赖注入(DI)方式实现对象之间的松耦合关系。程序运行时,依赖对象由【辅助程序】动态生成并注入到被依赖对象中,动态绑定两者的使用关系。Spring IoC容器就是这样的辅助程序,它负责对象的生成和依赖的注入,让后在交由我们使用。 简而言之,就是:IoC就是一个对象定义其依赖关系而不创建它们的过程。

这里我们可以细分为两个点。
2.1 私有属性保存依赖

第1点:使用私有属性保存依赖对象,并且只能通过构造函数参数传入,构造函数的参数可以是工厂方法、保存类对象的属性、或者是工厂方法返回值。 假设我们有一个Computer类:

public class Computer {
    private String cpu;     // CPU型号
    private int ram;        // RAM大小,单位GB

    public Computer(String cpu, int ram) {
        this.cpu = cpu;
        this.ram = ram;
    }
}

我们有另一个Person类依赖于Computer类,符合IoC的做法是这样:

public class Person {
    private Computer computer;

    public Person(Computer computer) {
        this.computer = computer;
    }
}

不符合IoC的做法如下:

// 直接在Person里实例化Computer类
public class Person {
    private Computer computer = new Computer(AMD, 3);
}

// 通过【非构造函数】传入依赖
public class Person {
    private Computer computer;

    public void init(Computer computer) {
        this.computer = computer;
    }
}

简单来说,person依赖Computer,但person不控制Computer的创建和销毁,仅使用Computer,那么Computer的控制权交给person之外处理,这叫控制反转(IOC),而person要依赖computer,必然要使用computer的instance,那么
通过a的接口,把b传入;
通过a的构造,把b传入;
通过设置a的属性,把b传入;

这个过程叫依赖注入(DI)。

2.2 让Spring控制类构建过程

第2点:不用new,让Spring控制new过程。在Spring中,我们基本不需要 new 一个类,这些都是让 Spring 去做的。 Spring 启动时会把所需的类实例化成对象,如果需要依赖,则先实例化依赖,然后实例化当前类。 因为依赖必须通过构建函数传入,所以实例化时,当前类就会接收并保存所有依赖的对象。 这一步也就是所谓的依赖注入。

2.3 这就是IoC

在 Spring 中,类的实例化、依赖的实例化、依赖的传入都交由 Spring Bean 容器控制, 而不是用new方式实例化对象、通过非构造函数方法传入依赖等常规方式。 实质的控制权已经交由程序管理,而不是程序员管理,所以叫做控制反转。

3、AOP是什么?

  • AOP是面向切面编程,可以将那些与业务不相关但是很多业务都要调用的代码抽取出来,思想就是不侵入原有代码的情况下对功能进行增强。

  • SpringAOP是基于动态代理实现的,动态代理是有两种,一种是jdk动态代理,一种是cglib动态代理;

    • jdk动态代理是原理是利用反射来实现的,需要调用反射包下的Proxy类的newProxyInstance方法来返回代理对象,这个方法中有三个参数,分别是用于加载代理类的类加载器,被代理类实现的接口的class数组和一个用于增强方法的InvocaHandler实现类。
    • cglib动态代理原理是利用asm开源包来实现的,是把被代理类的class文件加载进来,通过修改它的字节码生成子类来处理
  • jdk动态代理要求被代理类必须有实现的接口,生成的动态代理类会和代理类实现同样的接口,cglib则,生成的动态代理类会继承被代理类。Spring默认使用jdk动态代理,当被代理的类没有接口时就使用cglib动态代理

4、如何定义一个全局异常处理类?

想要定义一个全局异常处理类的话,我们需要在这个类上添加**@ContaollerAdvice注解,然后定义一些用于捕捉不同异常类型的方法,在这些方法上添加@ExceptionHandler(value = 异常类型.class)@ResponseBody注解,方法参数是HttpServletRequest异常类型**,然后将异常消息进行处理。

如果我们需要自定义异常的话,就写一个自定义异常类,该类需要继承一个异常接口,类属性包括final类型的连续id、错误码、错误信息,再根据需求写构造方法;

5、如何使用aop自定义日志?

第一步:创建一个切面类,把它添加到ioc容器中并添加@Aspect注解

第二步: 在切面类中写一个通知方法,在方法上添加通知注解并通过切入点表达式来表示要对哪些方法进行日志打印,然后方法参数为JoinPoint

第三步:通过JoinPoint这个参数可以获取当前执行的方法名、方法参数等信息,这样就可以根据需求在方法进入或结束时打印日志

6、循环依赖是什么,怎么解决的?

循环依赖就是在创建 A 实例的时候里面包含着 B 属性实例,所以这个时候就需要去创建 B 实例,而创 建 B 实例过程中也包含着 A 实例。 这样 A 实例还在创建的过程当中,所以就导致 A 和 B 实例都创建不出来。

spring通过三级缓存来解决循环依赖:

  • 一级缓存:缓存经过完整的生命周期的Bean

  • 二级缓存 :缓存未经过完整的生命周期的Bean

  • 三级缓存:缓存的是ObjectFactory,其中存储了一个生成代理类的拉姆达表达式

我们在创建 A 的过程中,先将 A 放入三级缓存 ,这时要创建B,B要创建A就直接去三级缓存中查找,并且判断需不需要进行 AOP 处理,如果需要就执行拉姆达表达式得到代理对象,不需要就取出原始对象。然后将取出的对象放入二级缓存中,因为这个时候 A 还未经 过完整的生命周期所以不能放入一级缓存。这个时候其他需要依赖 A 对象的直接从二级缓存中去获取即可。当B创建完成,A 继续执行生命周期,当A完成了属性的注入后,就可以放入一级缓存了

7、Bean 的作用域

(1)Singleton:一个IOC容器只有一个

(2)Prototype:每次调用getBean()都会生成一个新的对象

(3)request:每个http请求都会创建一个自己的bean

(4)session:同一个session共享一个实例

(5)application:整个serverContext只有一个bean

(6)webSocket:一个websocket只有一个bean

8、Bean 生命周期

实例化 Instantiation->属性赋值 Populate->初始化 Initialization->销毁 Destruction
在这四步的基础上面,Spring 提供了一些拓展点:

*Bean 自身的方法: 包括了 Bean 本身调用的方法和通过配置文件中的 init-method 和 destroy-method 指定的方法
*Bean 级生命周期接口方法:包括了 BeanNameAware、BeanFactoryAware、InitializingBean 和 DiposableBean 这些接口的方法
*容器级生命周期接口方法:包括了 InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。
*工厂后处理器接口方法: 包括了 AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer 等等非常有用的工厂后处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。

9、Spring 事务原理?

spring事务有编程式和声明式,我们一般使用声明式,在某个方法上增加**@Transactional**注解,这个方法中的sql会统一成功或失败。

原理是:

当一个方法加上@Transactional注解,spring会基于这个类生成一个代理对象并将这个代理对象作为bean,当使用这个bean中的方法时,如果存在@Transactional注解,就会将事务自动提交设为false,然后执行方法,执行过程没有异常则提交,有异常则回滚、

10、spring事务失效场景

(1)事务方法所在的类没有加载到容器中

(2)事务方法不是public类型

(3)同一类中,一个没有添加事务的方法调用另外以一个添加事务的方法,事务不生效

(4)spring事务默认只回滚运行时异常,可以用rollbackfor属性设置

(5)业务自己捕获了异常,事务会认为程序正常秩序

11.spring事务的隔离级别

  1. DEFAULT:Spring 中默认的事务隔离级别,以连接的数据库的事务隔离级别为准;
  2. READ_UNCOMMITTED:读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读;
  3. READ_COMMITTED:读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读;
  4. REPEATABLE_READ:可重复读,它能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读 (Phantom Read);
  5. SERIALIZABLE:串行化,最高的事务隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。

12、spring事务的传播行为

(1)支持当前事务,如果不存在,则新启一个事务

(2)支持当前事务,如果不存在,则抛出异常

(3)支持当前事务,如果不存在,则以非事务方式执行

(4)不支持当前事务,创建一个新事物

(5)不支持当前事务,如果已存在事务就抛异常

(6)不支持当前事务,始终以非事务方式执行

13.Spring IoC

14.spring用了哪些设计模式

  • BeanFactory用了工厂模式,

  • AOP用了动态代理模式,

  • RestTemplate用来模板方法模式,

  • SpringMVC中handlerAdaper用来适配器模式,

  • Spring里的监听器用了观察者模式

14、SpringMV工作原理

SpringMVC工作过程围绕着前端控制器DispatchServerlet,几个重要组件有HandleMapping(处理器映射器)、HandleAdapter(处理器适配器)、ViewReslover(试图解析器)

工作流程:

(1)DispatchServerlet接收用户请求将请求发送给HandleMapping

(2)HandleMapping根据请求url找到具体的handle和拦截器,返回给DispatchServerlet

(3)DispatchServerlet调用HandleAdapter,HandleAdapter执行具体的controller,并将controller返回的ModelAndView返回给DispatchServler

(4)DispatchServerlet将ModelAndView传给ViewReslover,ViewReslover解析后返回具体view

(5)DispatchServerlet根据view进行视图渲染,返回给用户

15、Springboot和SpringMVC的区别

1、含义不同

springboot:SpringBoot是一个自动化配置的工具。

springmvc:SpringMVC是一个web框架。

2、配置不同

springboot:SpringBoot采用约定大于配置的方式,通过其自动配置功能自动处理配置,同时内置服务器,打开就可以直接用。

springmvc:此框架需要大量配置,例如 DispatcherServlet 配置和 View Resolver 配置。需要手动配置xml文件,同时需要配置Tomcat服务器。

3、依赖项不同

springboot:springboot具有启动器的概念,一旦将其添加到类路径中,它将带来开发Web应用程序所需的所有依赖项。

springmvc:需要单独指定每个依赖项才能运行功能。

4、开发时间不同

springboot:Spring Boot有助于减少开发时间,因为所有与依赖关系相关的任务都会得到处理。

springmvc:与Spring Boot相比,开发所需的时间更多,因为开发人员需要花时间添加所需的依赖项。

5、生产力不同

springboot:由于开发时间更短,生产力提高。

springmvc:生产力降低,因为需要了解依赖性附加组件。

6、实现JAR打包功能的方式不同

springboot:Spring Boot 允许嵌入式服务器以独立的方式运行该功能。

springmvc:Spring MVC需要大量手动配置才能实现JAR打包的功能。

7、是否提供批处理功能

springboot:它提供强大的批处理。

springmvc:它不提供强大的批处理。

8、作用不同

springboot:Spring Boot也允许构建不同类型的应用程序。

springmvc:Spring MVC仅用于开发动态网页和RESTful网络服务。

16、spring的bean是线程安全的吗?

spring的默认bean作用域是单例的,单例的bean不是线程安全的,但是开发中大部分的bean都是无状态的,不具备存储功能,比如controller、service、dao,他们不需要保证线程安全。

如果要保证线程安全,可以将bean的作用域改为prototype,比如像Model View。

另外还可以采用ThreadLocal来解决线程安全问题。ThreadLocal为每个线程保存一个副本变量,每个线程只操作自己的副本变量。

17、@Autoware和@Resource的区别

@Autowired 和 @Resource 都是 Spring/Spring Boot 项目中,用来进行依赖注入的注解。它们都提供了将依赖对象注入到当前对象的功能,但二者却有众多不同,并且这也是常见的面试题之一,所以我们今天就来盘它。 @Autowired 和 @Resource 的区别主要体现在以下 5 点:

  • 来源不同;
  • 依赖查找的顺序不同;
  • 支持的参数不同;
  • 依赖注入的用法不同;
  • 编译器 IDEA 的提示不同。

1、来源不同

@Autowired 和 @Resource 来自不同的“父类”,其中 @Autowired 是 Spring 定义的注解,而 @Resource 是 Java 定义的注解,它来自于 JSR-250(Java 250 规范提案)。

2、依赖查找顺序不同

依赖注入的功能,是通过先在 Spring IoC 容器中查找对象,再将对象注入引入到当前类中。而查找有分为两种实现:按名称(byName)查找或按类型(byType)查找,其中 @Autowired 和 @Resource 都是既使用了名称查找又使用了类型查找,但二者进行查找的顺序却截然相反。
2.1 @Autowired 查找顺序

@Autowired 是先根据类型(byType)查找,如果存在多个 Bean 再根据名称(byName)进行查找,它的具体查找流程如下:

2.2 @Resource 查找顺序

@Resource 是先根据名称查找,如果(根据名称)查找不到,再根据类型进行查找,它的具体流程如下图所示:

2.3 查找顺序小结

由上面的分析可以得出:

  • @Autowired 先根据类型(byType)查找,如果存在多个(Bean)再根据名称(byName)进行查找;
  • @Resource 先根据名称(byName)查找,如果(根据名称)查找不到,再根据类型(byType)进行查找。

3、支持的参数不同

@Autowired 和 @Resource 在使用时都可以设置参数,比如给 @Resource 注解设置 name 和 type 参数,实现代码如下:

@Resource(name = "userinfo", type = UserInfo.class)
private UserInfo user;

二者支持的参数以及参数的个数完全不同,其中 @Autowired 只支持设置一个 required 的参数,而 @Resource 支持 7 个参数

4.依赖注入的支持不同

@Autowired 和 @Resource 支持依赖注入的用法不同,常见依赖注入有以下 3 种实现:

  1. 属性注入
  2. 构造方法注入
  3. Setter 注入

其中, @Autowired 支持属性注入、构造方法注入和 Setter 注入,而 @Resource 只支持属性注入和 Setter 注入,当使用 @Resource 实现构造方法注入时就会提示以下错误:

总结

@Autowired 和 @Resource 都是用来实现依赖注入的注解(在 Spring/Spring Boot 项目中),但二者却有着 5 点不同:##

来源不同:@Autowired 来自 Spring 框架,而 @Resource 来自于(Java)JSR-250;
依赖查找的顺序不同:@Autowired 先根据类型再根据名称查询,而 @Resource 先根据名称再根据类型查询;
支持的参数不同:@Autowired 只支持设置 1 个参数,而 @Resource 支持设置 7 个参数;
依赖注入的用法支持不同:@Autowired 既支持构造方法注入,又支持属性注入和 Setter 注入,而 @Resource 只支持属性注入和 Setter 注入;
编译器 IDEA 的提示不同:当注入 Mapper 对象时,使用 @Autowired 注解编译器会提示错误,而使用 @Resource 注解则不会提示错误。

二、Springboot

1、springboot自动配置原理

一.原理解释

Spring Boot的自动配置是Spring框架的一个重要特性,它旨在简化应用程序的开发和部署过程。自动配置通过基于类路径中的依赖关系和配置文件内容来预先配置Spring应用程序的各种组件和功能。这样,我们可以在无需显式配置大量参数的情况下,快速搭建一个运行良好的Spring应用程序,极大的提高了我们的开发效率。

下面我们对于Spring Boot自动配置的工作原理做一个详细解释(我们只谈原理和概念,不设计实现):

1.条件装配:

Spring Boot的自动配置采用了条件装配的机制。条件装配根据特定条件来决定是否创建特定的Bean或应用特定的配置。这些条件可以基于类路径中存在的依赖、配置属性的值、环境变量或其他Spring Bean的存在等。这样,当满足特定条件时,相关的Bean会被自动创建和配置,否则它们将被跳过。

2、Spring Boot Starter:

Spring Boot提供了一系列Starter模块,每个Starter模块都包含了特定功能的默认依赖和配置。例如,spring-boot-starter-web包含了构建Web应用程序所需的依赖和配置。这些Starter模块通过自动配置来简化应用程序的搭建,开发者只需添加相应的Starter依赖,即可自动启用相关功能。

3、Spring Boot的启动过程:

当Spring Boot应用程序启动时,会触发自动配置的过程。首先,它会扫描类路径上的所有Starter模块,并加载它们的自动配置类。然后,Spring Boot会根据条件装配机制,检查是否满足自动配置的条件,并决定是否创建相应的Bean和应用相关的配置。

4、条件注解:

Spring Boot中有许多条件注解,这些注解用于根据特定条件来启用或禁用配置。例如,@ConditionalOnClass注解表示只有类路径中存在指定的类时,相关配置才会生效。@ConditionalOnProperty注解则允许根据配置属性的值来决定是否启用某个配置。

5、自动配置类的优先级:

在某些情况下,可能存在多个自动配置类都能满足条件的情况。为了解决这种冲突,Spring Boot为自动配置类定义了优先级。具有更高优先级的配置类将覆盖具有较低优先级的配置类。这样,开发者可以通过自定义配置类来覆盖Spring Boot默认的自动配置行为。

6、自定义自动配置:

Spring Boot允许开发者定义自己的自动配置类。要创建自定义的自动配置,只需在类上添加@Configuration注解,并在类中配置所需的Bean。然后,Spring Boot会在启动过程中将这些自定义配置类纳入自动配置的流程中。

二、什么是springboot自动配置

SpringBoot通过@EnableAutoConfiguration注解开启自动配置,对jar包下的spring.factories文件进行扫描,这个文件中包含了可以进行自动配置的类,当满足@Condition注解指定的条件时,便在依赖的支持下进行实例化,注册到Spring容器中。

通俗的来讲,我们之前在写ssm项目时候,配置了大量坐标和配置内容,搭环境的过程在项目开发中占据了大量时间,SpringBoot的最大的特点就是简化了各种xml配置内容,所以springboot的自动配置就是用注解来对一些常规的配置做默认配置,简化xml配置内容,使你的项目能够快速运行。

springboot核心配置原理:

  • 自动配置类都存放在spring-boot-autoconfigure-版本号.jar下的org.springframework.boot.autoconfigure中
  • 当我们在application.properties中配置debug=true后启动容器。可以看到服务器初始化的初始化配置
  • DispatcherServletAutoConfigratio注册前端控制器
  • EmbeddedServletContainerAutoConfiguration注册容器类型
  • HttpMessageConvertersAutoConfiguration注册json或者xml处理器
  • JacksonAutoConfiguration注册json对象解析器
  • 如果加入其他功能的依赖,springBoot还会实现这些功能的自动配置

二、Starter组件

Starter组件是可被加载在应用中的Maven依赖项项。只有在Maven配置中添加对应的依赖配置,即可使用对应的Starter组件。例如,添加spring-boot-starter-web依赖,就可以用于构建RESTAPI服务,其包含了SpringMVC和Tomcat内嵌容器。

一个完整的Starter组件包括以下两点:

  • 提供自动配置功能的自动配置模块
  • 提供依赖关系管理岗功能的组件模块,即封装了组件所有功能,开箱即用。

spring-boot-starter-web依赖源码

image-20230820221309931

三、三大注解

@SpringBootConfiguration:继承自Configuration,支持JavaConfig的方式进行配置。

@EnableAutoConfiguration:本文重点讲解,主要用于开启自动配置。

@ComponentScan:自动扫描组件,默认扫描该类所在包及其子包下所有带有指定注解的类,将它们自动装配到bean容器中,会被自动装配的注解包括@Controller、@Service、@Component、@Repository等。也可以指定扫描路径。

四、@EnableAutoConfiguration

这个注解是帮助我们自动加载默认配置的,它里面有两个关键注解@AutoConfigurationPackage和@Import,我们来详细了解@Import注解。

@Import(AutoConfigurationImportSelector.class)注解,这里导入AutoConfigurationImportSelector类。这个类中有一个非常重要的方法——selectImports(),它几乎涵盖了组件自动装配的所有处理逻辑,包括获得候选配置类、配置类去重、排除不需要的配置类、过滤等,最终返回符合条件的自动配置类的全限定名数组。

五、SpringFactoriesLoader

spring-core包里定义了SpringFactoriesLoader类,这个类实现了检索META-INF/spring.factories文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:

  • loadFactories根据接口类获取其实现类的实例,这个方法返回的是对象列表。
  • loadFactoryNames根据接口获取其接口类的名称,这个方法返回的是类名的列表。

上面的两个方法的关键都是从指定的ClassLoader中获取spring.factories文件,并解析得到类名列表,具体代码如下:

2、springboot常用注解

@RestController :修饰类,该控制器会返回Json数据

@RequestMapping(“/path”) :修饰类,该控制器的请求路径

@Autowired : 修饰属性,按照类型进行依赖注入

@PathVariable : 修饰参数,将路径值映射到参数上

@ResponseBody :修饰方法,该方法会返回Json数据

@RequestBody(需要使用Post提交方式) :修饰参数,将Json数据封装到对应参数中
@Controller@Service@Compont: 将类注册到ioc容器

@Transaction:开启事务

3、Spring Boot 有哪些特点?

Spring Boot 是 Spring 的扩展,它消除了设置 Spring 应用程序所需的样板配置。

1、自动配置

  • 这是 Spring Boot 最重要的特性。这极大地消除了手动配置。基础框架附带了一个名为 auto-configure 的内置库,它为我们完成了这项工作。它检测某些类的存在以及类路径上的存在,并为我们自动配置它们。

  • 例如:— 当我们在项目中添加spring-boot-starter-web依赖项时,Spring Boot 自动配置会查找 Spring MVC 是否在类路径中。它自动配置dispatcherServlet、默认错误页面和web jars。— 同样,当我们添加
    spring-boot-starter-data-jpa依赖项时,我们会看到 Spring Boot 自动配置,自动配置一个数据源和一个实体管理器。

  • 嵌入式 Tomcat Web 服务器
    Spring Boot 默认随 Tomcat 服务器一起提供。因此,我们不需要配置服务器来运行应用程序(如果我们的首选服务器是 Tomcat)。

2、入门 POM
Spring Boot 本身提供了许多启动 POM 来完成开发生活中最常见的任务。我们可以依赖它们和框架本身,而不需要去第三方库。我在这里列出了其中的一些。

  • spring-boot-starter-web:创建 REST API
  • spring-boot-starter-data-jpa:连接 SQL 数据库
  • spring-boot-starter-data-mongodb:连接 MongoDB
  • spring-boot-starter -aop:应用面向方面的编程概念
  • spring-boot-starter-security:实现安全性,如基于角色的身份验证
  • spring-boot-starter-test:实现单元测试

3、Actuator执行器 API
Spring Boot Actuator 是 Spring Boot 框架的一个子项目。它使我们能够通过一组 API 端点查看见解和指标并监控正在运行的应用程序。我们不需要手动创建它们。

  • 数据库统计信息:数据源使用情况

  • CPU内存使用情况

  • GC 周期

  • 跟踪 HTTP 请求

4、SpringBoot初始化器

这是一个基于 Web 的 UI,主要提供了使用可用依赖项创建新 Spring Boot 项目并下载创建为 zip 的项目的能力。所以我们不必从头开始创建它。该项目的所有基本结构都已在此下载的 zip 中。Spring Initializer 作为 IDE 插件提供,也具有不同的名称。

例如:对于 IntelliJ - 插件是 Spring Assistant 或 Spring Initializer

4、你知道“@SpringBootApplication”注解在内部是如何工作的吗?

Spring Boot 应用程序使用此注解执行。实际上它是其他 3 个注释的组合

ComponentScan、EnableAutoConfiguration、Configuration。

  • “@Configuration” ——所有带注释的类都被视为 Spring Boot 的广告配置,它们有资格创建 bean 并返回到 IOC 容器。
  • “@ComponentScan” ——所有带注释的类都将通过包(在哪里寻找)进行扫描,并帮助创建这些类的实例。
  • “@EnableAutoConfiguration” ——这是神奇的注解。这会寻找类路径。基础框架附带了一个名为auto-configure的内置库,它为我们完成了这项工作。它检测某些类的存在以及类路径上的存在,并为我们自动配置它们。我在下面放了图书馆的快照。如果您前往 spring.factories 文件,您将看到可用的类配置。

5、我们如何在Spring Boot中实现依赖注入?

DI — 将对象作为依赖项传递给另一个对象

在 Spring Boot 中,我们可以使用“@Autowired”注解来实现这一点。然后 Spring IOC 容器将代表我们创建对象。通常,在控制器层我们注入服务,在服务层我们注入存储库来使用这个注解。

6、我们需要在哪里使用“@Qualifier”注解?

此注解用于专门告诉 Spring Boot 从其所有可用实现 bean 中获取特定类。@Qualifier注解与“ @Autowired”注解一起用于依赖注入。

假设我们有1 个接口和 2 个不同的实现类。

例如:UserService 接口 => AdminUserService、StaffUserService 类

AdminUserService、StaffUserService 都在实现 UserService 接口。我们必须在服务启动时选择StaffUserService 。否则 Spring Boot 将抛出异常并抱怨候选人太多。
所以我们应该在 Autowired 之后放置 Qualifier 注释来解决这个问题:

@Autowired 
@Qualifier("staffUserService") 
private UserService userService;

7、Spring Boot项目中可以替换Tomcat服务器吗?

是的。如果需要,我们可以通过在 POM 中添加 maven 排除来删除 Tomcat 服务器。实际上,Web 服务器捆绑在started-web Spring Boot starter 依赖项中。应该添加排除。

<dependency> 
  <groupId>org.springframework.boot</groupId> 
  <artifactId>spring-boot-starter-web</artifactId> 
  <exclusions> 
    <exclusion> 
      <groupId>org.springframework.boot</groupId> 
      <artifactId> spring-boot-starter-tomcat</artifactId> 
    </exclusion> 
  </exclusions> 
</dependency>

然后我们必须将任何其他服务器(如 Jetty)添加到 POM。无论如何,我们必须在删除 tomcat 后添加一个 Web 服务器。

<dependency> 
  <groupId>org.springframework.boot</groupId> 
  <artifactId>spring-boot-starter-jetty</artifactId> 
</dependency>

8、“@PathVariable”和“@RequestParam”有什么区别?

PathVariable - 当我们设置 API 端点时使用以“/”分隔的参数。

@GetMapping(path = "/profile/{username}") 
public ResponseEntity<?> getUser( @PathVariable("username") String username) { 
    return ResponseEntity.ok().body(authService.findUserByUsername(username)); 
}

9、什么是Spring Boot Actuator?

简单地说,Spring Boot 框架的一个子项目,它使用 HTTP 端点来公开有关任何正在运行的应用程序的操作信息。

这些信息包括应用程序指标、连接的数据库状态、应用程序状态、bean 信息、请求跟踪等。

它可作为启动器依赖项使用。我们可以通过安装以下依赖项来使用该库。

10、SpringBoot的核心配置文件有哪几个?他们的区别是什么?

Spring Boot的核心配置文件有以下几个:

application.properties

application.yml

bootstrap.properties

bootstrap.yml

其中,application.properties和application.yml是应用程序的配置文件,而bootstrap.properties和bootstrap.yml是用于Spring Boot上下文的配置文件。它们之间的区别在于加载的优先级不同,bootstrap配置文件的优先级更高,可以用于设置一些比较敏感和重要的配置信息。

三、SpringSecurity

1、什么是Spring Security?核心功能?

Spring Security是一个基于Spring框架的安全框架,提供了完整的安全解决方案,包括认证、授权、攻击防护等功能。

其核心功能包括:

  • 认证:提供了多种认证方式,如表单认证、HTTP Basic认证、OAuth2认证等,可以与多种身份验证机制集成。

  • 授权:提供了多种授权方式,如角色授权、基于表达式的授权等,可以对应用程序中的不同资源进行授权。

  • 攻击防护:提供了多种防护机制,如跨站点请求伪造(CSRF)防护、注入攻击防护等。

  • 会话管理:提供了会话管理机制,如令牌管理、并发控制等。

  • 监视与管理:提供了监视与管理机制,如访问日志记录、审计等。

Spring Security通过配置安全规则和过滤器链来实现以上功能,可以轻松地为Spring应用程序提供安全性和保护机制。

2、Spring Security的原理?

Spring Security是一个基于Spring框架的安全性认证和授权框架,它提供了全面的安全性解决方案,可以保护Web应用程序中的所有关键部分。

Spring Security的核心原理是过滤器(Filter)。Spring Security会在Web应用程序的过滤器链中添加一组自定义的过滤器,这些过滤器可以实现身份验证和授权功能。当用户请求资源时,Spring Security会拦截请求,并使用配置的身份验证机制来验证用户身份。如果身份验证成功,Spring Security会授权用户访问所请求的资源。

Spring Security的具体工作原理如下:

  • 用户请求Web应用程序的受保护资源。
  • Spring Security拦截请求,并尝试获取用户的身份验证信息。
  • 如果用户没有经过身份验证,Spring Security将向用户显示一个登录页面,并要求用户提供有效的凭据(用户名和密码)。
  • 一旦用户提供了有效的凭据,Spring Security将验证这些凭据,并创建一个已认证的安全上下文(SecurityContext)对象。
  • 安全上下文对象包含已认证的用户信息,包括用户名、角色和授权信息。
  • 在接下来的请求中,Spring Security将使用已经认证的安全上下文对象来判断用户是否有权访问受保护的资源。
  • 如果用户有权访问资源,Spring Security将允许用户访问资源,否则将返回一个错误信息。

3、有哪些控制请求访问权限的方法?

在Spring Security中,可以使用以下方法来控制请求访问权限:

  • permitAll():允许所有用户访问该请求,不需要进行任何身份验证。
  • denyAll():拒绝所有用户访问该请求。
  • anonymous():允许匿名用户访问该请求。
  • authenticated():要求用户进行身份验证,但是不要求用户具有任何特定的角色。
  • hasRole(String role):要求用户具有特定的角色才能访问该请求。
  • hasAnyRole(String… roles):要求用户具有多个角色中的至少一个角色才能访问该请求。
  • hasAuthority(String authority):要求用户具有特定的权限才能访问该请求。
  • hasAnyAuthority(String… authorities):要求用户具有多个权限中的至少一个权限才能访问该请求。

可以将这些方法应用于Spring Security的配置类或者在Spring Security注解中使用。

4、hasRole 和 hasAuthority 有区别吗?

在Spring Security中,hasRole和hasAuthority都可以用来控制用户的访问权限,但它们有一些细微的差别。

hasRole方法是基于角色进行访问控制的。它检查用户是否有指定的角色,并且这些角色以"ROLE_“前缀作为前缀(例如"ROLE_ADMIN”)。

hasAuthority方法是基于权限进行访问控制的。它检查用户是否有指定的权限,并且这些权限没有前缀。

因此,使用hasRole方法需要在用户的角色名称前添加"ROLE_"前缀,而使用hasAuthority方法不需要这样做。

例如,假设用户有一个角色为"ADMIN"和一个权限为"VIEW_REPORTS",可以使用以下方式控制用户对页面的访问权限:

.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/reports/**").hasAuthority("VIEW_REPORTS")

在这个例子中,只有具有"ROLE_ADMIN"角色的用户才能访问/admin/路径下的页面,而具有"VIEW_REPORTS"权限的用户才能访问/reports/路径下的页面。

5、如何对密码进行加密?

在 Spring Security 中对密码进行加密通常使用的是密码编码器(PasswordEncoder)。PasswordEncoder 的作用是将明文密码加密成密文密码,以便于存储和校验。Spring Security 提供了多种常见的密码编码器,例如 BCryptPasswordEncoder、SCryptPasswordEncoder、StandardPasswordEncoder 等。

以 BCryptPasswordEncoder 为例,使用步骤如下:
1.在 pom.xml 文件中添加 BCryptPasswordEncoder 的依赖:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-crypto</artifactId>
    <version>5.6.1</version>
</dependency>

2.在 Spring 配置文件中注入 BCryptPasswordEncoder:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    // ...
}

3.在使用密码的地方调用 passwordEncoder.encode() 方法对密码进行加密,例如注册时对密码进行加密:

@Service
public class UserServiceImpl implements UserService {
 
    @Autowired
    private PasswordEncoder passwordEncoder;
 
    @Override
    public User register(User user) {
        String encodedPassword = passwordEncoder.encode(user.getPassword());
        user.setPassword(encodedPassword);
        // ...
        return user;
    }
 
    // ...
}

以上就是使用 BCryptPasswordEncoder 对密码进行加密的步骤。使用其他密码编码器的步骤类似,只需将 BCryptPasswordEncoder 替换为相应的密码编码器即可。

6、Spring Security基于用户名和密码的认证模式流程?

请求的用户名密码可以通过表单登录,基础认证,数字认证三种方式从HttpServletRequest中获得,用于认证的数据源策略有内存,数据库,ldap,自定义等。

拦截未授权的请求,重定向到登录页面的过程:

  1. 当用户访问需要授权的资源时,Spring Security会检查用户是否已经认证(即是否已登录),如果没有登录则会重定向到登录页面。
  2. 重定向到登录页面时,用户需要输入用户名和密码进行认证。

表单登录的过程:

  1. 用户在登录页面输入用户名和密码,提交表单。
  2. Spring Security的UsernamePasswordAuthenticationFilter拦截表单提交的请求,并将用户名和密码封装成一个Authentication对象。
  3. AuthenticationManager接收到Authentication对象后,会根据用户名和密码查询用户信息,并将用户信息封装成一个UserDetails对象。
  4. 如果查询到用户信息,则将UserDetails对象封装成一个已认证的Authentication对象并返回,如果查询不到用户信息,则抛出相应的异常。
  5. 认证成功后,用户会被重定向到之前访问的资源。如果之前访问的资源需要特定的角色或权限才能访问,则还需要进行授权的过程。

Spring Security的认证流程大致可以分为两个过程,首先是用户登录认证的过程,然后是用户访问受保护资源时的授权过程。在认证过程中,用户需要提供用户名和密码,Spring Security通过UsernamePasswordAuthenticationFilter将用户名和密码封装成Authentication对象,并交由AuthenticationManager进行认证。如果认证成功,则认证结果会存储在SecurityContextHolder中。在授权过程中,Spring Security会检查用户是否有访问受保护资源的权限,如果没有则会重定向到登录页面进行认证。

拦截未授权的请求,重定向到登录页面

表单登录的过程,进行账号密码认证

四、Shiro安全认证

1、shiro可以完成哪些工作?

shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等

2、Apache Shiro 的三大核心组件

1、Subject :主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如爬虫、机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者。
2、SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是shiro的核心, SecurityManager相当于spring mvc中的dispatcherServlet前端控制器
3、Realms:域,shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

3、shiro有哪些组件?

  • Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验 证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
  • Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储; e、Web Support:Web支持,可以非常容易的集成到Web环境;
  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
  • Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去; h、Testing:提供测试支持;
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

4、比较 SpringSecurity 和 Shiro

相比 Spring Security, Shiro 在保持强大功能的同时,使用简单性和灵活性。 SpringSecurity: 即使是一个一个简单的请求, 最少得经过它的 8 个Filter。SpringSecurity 必须在 Spring 的环境下使用。初学 Spring Security,曲线还是较大,需要深入学习其源码和框架, 配置起来也较费力.

5、Shiro 的优点

  • 简单的身份认证, 支持多种数据源
  • 对角色的简单的授权, 支持细粒度的授权(方法级) c、支持一级缓存,以提升应用程序的性能
  • 内置的基于 POJO 企业会话管理, 适用于 Web 以及非 Web 的环境e、非常简单的加密 API
  • 不跟任何的框架或者容器捆绑, 可以独立运行

6、shiro 注解

1、 @RequiresAuthentication : 表示当前Subject已经通过login进行了身份验证;即 Subject.isAuthenticated() 返回 true
2、@RequiresUser : 表示当前Subject 已经身份验证或者通过记住我登录的
3、@RequiresGuest : 表示当前Subject没有身份验证或通过记住我登陆过,即是游客身份
4、@RequiresRoles(value = { “admin”, “user” }, logical = Logical.AND) : 表示当前 Subject 需要角色 admin和user
5、@RequiresPermissions(value = { “user:a”, “user:b” }, logical = Logical.OR) : 表示当前 Subject 需要权限 user:a 或 user:b

7、自定义Realm

Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
最基础的是Realm接口,CachingRealm负责缓存处理,AuthenticationRealm负责认证,AuthorizingRealm负责授权。
通常自定义的realm继承AuthorizingRealm

7、图解认证、授权

认证:

授权:

5、关于Spring体系的各种启动流程

在介绍spring的启动之前,先来说下启动过程中使用到的几个类

1、基本组件

1、BeanFactory:spring底层容器,定义了最基本的容器功能,注意区分FactoryBean
2、ApplicationContext:扩展于BeanFactory,拥有更丰富的功能。例如:添加事件发布机制、父子级容器,一般都是直接使用ApplicationContext。
3、Resource:bean配置文件,一般为xml文件。可以理解为保存bean信息的文件。
4、BeanDefinition:beandifinition定义了bean的基本信息,根据它来创造bean

2、基础流程

不管是哪种系列的spring(springframework、springmvc、springboot、springcloud),Spring的启动过程主要可以分为两部分:

  • 第一步:解析成BeanDefinition:将bean定义信息解析为BeanDefinition类,不管bean信息是定义在xml中,还是通过@Bean注解标注,都能通过不同的BeanDefinitionReader转为BeanDefinition类,将BeanDefinition向Map中注册 Map<name,beandefinition>。 这里分两种BeanDefinition,RootBeanDefintion和BeanDefinition。RootBeanDefinition这种是系统级别的,是启动Spring必须加载的6个Bean。BeanDefinition是我们定义的Bean。
  • 第二步:参照BeanDefintion定义的类信息,通过BeanFactory生成bean实例存放在缓存中。 这里的BeanFactoryPostProcessor是一个拦截器,在BeanDefinition实例化后,BeanFactory生成该Bean之前,可以对BeanDefinition进行修改。 BeanFactory根据BeanDefinition定义使用反射实例化Bean,实例化和初始化Bean的过程中就涉及到Bean的生命周期了,典型的问题就是Bean的循环依赖。接着,Bean实例化前会判断该Bean是否需要增强,并决定使用哪种代理来生成Bean。

3、Springframework

在这里插入图片描述
在这里插入图片描述

1、容器类

在一般性的spring项目中,大家应该也都知道,一般是通过直接实例化applicationContext类,来实现项目的启动 下面我们来看下通过注解的方式来启动的情况,注解容器定义如下:

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
	this();
	register(componentClasses);
	refresh();
}

public AnnotationConfigApplicationContext() {
	this.reader = new AnnotatedBeanDefinitionReader(this);
	this.scanner = new ClassPathBeanDefinitionScanner(this);
}

创建了注解定义bean读取器和配置文件定义bean扫描器

2、注解定义bean读取器

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
		BeanDefinitionRegistry registry, @Nullable Object source) {

	DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
	if (beanFactory != null) {
		if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
			beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
		}
		if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
			beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
		}
	}

	Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

	if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
	}
	...
}

注册了6个RootBeanDefinition,即系统级别的BeanDefinition。 同时,经过调用registerPostProcessor->registerBeanDefinition,可以看到注册BeanDefinition其实就是放到BeanFactory的缓存中。

DefaultListableBeanFactory.java类中

 public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
      ...
	  this.beanDefinitionMap.put(beanName, beanDefinition);
	  ...
 }

上面的6个beanDefinition的实例参数中都有一个postprocessor后缀的类,我们分别点击进入查看即继承关系,可以看到,最终都继承自``接口

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}

@FunctionalInterface
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}

3、BeanFactoryPostProcessor

1、BeanFactoryPostProcessor是spring初始化bean的扩展点。 官文翻译如下:允许自定义修改应用程序上下文的bean定义,调整上下文的基础bean工厂的bean属性值。应用程序上下文可以在其bean定义中自动检测BeanFactoryPostProcessor bean,并在创建任何其他bean之前先创建BeanFactoryPostProcessor。BeanFactoryPostProcessor可以与bean定义交互并修改bean定义,但绝不能与bean实例交互。这样做可能会导致bean过早实例化,违反容器并导致意外的副作用。如果需要bean实例交互,请考虑实现BeanPostProcessor。实现该接口,可以允许我们的程序获取到BeanFactory,从而修改BeanFactory,可以实现编程式的往Spring容器中添加Bean。

也就是说,我们可以通过实现BeanFactoryPostProcessor接口,获取BeanFactory,操作BeanFactory对象,修改BeanDefinition,但不要去实例化bean。

2、BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子类,在父类的基础上,增加了新的方法,允许我们获取到BeanDefinitionRegistry,从而编码动态修改BeanDefinition。例如往BeanDefinition中添加一个新的BeanDefinition。

这两个接口是在AbstractApplicationContext#refresh方法中执行到invokeBeanFactoryPostProcessors(beanFactory);方法时被执行的。
3、示例代码如下:

@Repository
public class OrderDao {

	public void query() {
		System.out.println("OrderDao query...");
	}
}
public class OrderService {

	private OrderDao orderDao;

	public void setDao(OrderDao orderDao) {
		this.orderDao = orderDao;
	}

	public void init() {
		System.out.println("OrderService init...");
	}

	public void query() {
		orderDao.query();
	}
}

@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		//向Spring容器中注册OrderService
		BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(OrderService.class)
				//这里的属性名是根据setter方法
				.addPropertyReference("dao", "orderDao")
				.setInitMethodName("init")
				.setScope(BeanDefinition.SCOPE_SINGLETON)
				.getBeanDefinition();

		registry.registerBeanDefinition("orderService", beanDefinition);

	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		// 在这里修改orderService bean的scope为PROTOTYPE
		BeanDefinition beanDefinition = beanFactory.getBeanDefinition("orderService");
		beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
	}
}

回到上面,我们拿ConfigurationClassPostProcessor来说: 在Spring中ConfigurationClassPostProcessor同时实现了BeanDefinitionRegistryPostProcessor接口和其父类接口中的方法。

1、ConfigurationClassPostProcessor#postProcessBeanFactory:主要负责对Full Configuration 配置进行增强,拦截@Bean方法来确保增强执行@Bean方法的语义。
2、ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry:负责扫描我们的程序,根据程序的中Bean创建BeanDefinition,并注册到容器中。
我们进入到:

private void loadBeanDefinitionsForConfigurationClass(
		ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

	if (trackedConditionEvaluator.shouldSkip(configClass)) {
		String beanName = configClass.getBeanName();
		if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
			this.registry.removeBeanDefinition(beanName);
		}
		this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
		return;
	}

	if (configClass.isImported()) {
		registerBeanDefinitionForImportedConfigurationClass(configClass);
	}
	for (BeanMethod beanMethod : configClass.getBeanMethods()) {
		loadBeanDefinitionsForBeanMethod(beanMethod);
	}

	loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
	loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

其中,我们可以看到:
1、通过检查是否有·@import·注解,来注册该导入类到容器中

if (configClass.isImported()) {
		registerBeanDefinitionForImportedConfigurationClass(configClass);
	}

2、遍历@Configuration类中的@bean注解,将其类注册到容器中

if (configClass.isImported()) {
		registerBeanDefinitionForImportedConfigurationClass(configClass);
	}

4、refresh

这个方法就是正式进行bean的处理的主要逻辑

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing.
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);

			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);

			// Initialize message source for this context.
			initMessageSource();

			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
			onRefresh();

			// Check for listener beans and register them.
			registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);

			// Last step: publish corresponding event.
			finishRefresh();
		}

		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}

			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();

			// Reset 'active' flag.
			cancelRefresh(ex);

			// Propagate exception to caller.
			throw ex;
		}

		finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
		}
	}
}

前面说的一些扩展点类都是在这里才处理的,spring的扩展机制后面会有专门的文章来讲解。

4、SpringMVC

而在web项目中,我们一般都是使用的spring mvc,Spring Framework本身没有Web功能,Spring MVC使用WebApplicationContext类扩展ApplicationContext,使得拥有web功能。那么,Spring MVC是如何在web环境中创建IoC容器呢?web环境中的IoC容器的结构又是什么结构呢?web环境中,Spring IoC容器是怎么启动呢?

1、配置

以Tomcat为例,在Web容器中使用Spirng MVC,必须进行四项的配置:

  • 修改web.xml,添加servlet定义;
  • 编写servletname-servlet.xml(servletname是在web.xm中配置DispactherServlet时使servlet-name的值)配置;
  • contextConfigLocation初始化参数
  • 配置ContextLoaderListerner; 示例配置如下:
<!-- servlet定义:前端处理器,接受的HTTP请求和转发请求的类 -->
    <servlet>
        <servlet-name>court</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!-- court-servlet.xml:定义WebAppliactionContext上下文中的bean -->
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:court-servlet.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>court</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!-- 配置contextConfigLocation初始化参数:指定Spring IoC容器需要读取的定义了非web层的BeanDAO/Service)的XML文件路径 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/court-service.xml</param-value>
    </context-param>
    <!-- 配置ContextLoaderListernerSpring MVCWeb容器中的启动类,负责Spring IoC容器在Web上下文中的初始化 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

在web.xml配置文件中,有两个主要的配置:ContextLoaderListener和DispatcherServlet。同样的关于spring配置文件的相关配置也有两部分:context-param和DispatcherServlet中的init-param。那么,这两部分的配置有什么区别呢?它们都担任什么样的职责呢?

在Spring MVC中,Spring Context是以父子的继承结构存在的。Web环境中存在一个ROOT Context,这个Context是整个应用的根上下文,是其他context的双亲Context。同时Spring MVC也对应的持有一个独立的Context,它是ROOT Context的子上下文。 对于这样的Context结构在Spring MVC中是如何实现的呢?下面就先从ROOT Context入手,ROOT Context是在ContextLoaderListener中配置的,ContextLoaderListener读取context-param中的contextConfigLocation指定的配置文件,创建ROOT Context。

2、启动过程

Spring MVC启动过程大致分为两个过程:

  • ContextLoaderListener初始化,实例化IoC容器,并将此容器实例注册到ServletContext中;
  • DispatcherServlet初始化;

tomcat在启动的时候,会依次执行listeners的初始化,也就是执行该ContextLoaderListener的初始化,最终会调用下面的代码:

public void contextInitialized(ServletContextEvent event) {
	this.initWebApplicationContext(event.getServletContext());
}

 
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
     //PS : ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE=WebApplicationContext.class.getName() + ".ROOT" 根上下文的名称
     //PS : 默认情况下,配置文件的位置和名称是: DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml" 
     //在整个web应用中,只能有一个根上下文
     if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
         throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ContextLoader* definitions in your web.xml!");
     }
 
     Log logger = LogFactory.getLog(ContextLoader.class);
     servletContext.log("Initializing Spring root WebApplicationContext");
     if (logger.isInfoEnabled()) {
         logger.info("Root WebApplicationContext: initialization started");
     }
     long startTime = System.currentTimeMillis();
 
     try {
         // Store context in local instance variable, to guarantee that
         // it is available on ServletContext shutdown.
         if (this.context == null) {
             // 在这里执行了创建WebApplicationContext的操作
             this.context = createWebApplicationContext(servletContext);
         }
         if (this.context instanceof ConfigurableWebApplicationContext) {
             ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
             if (!cwac.isActive()) {
                 // The context has not yet been refreshed -> provide services such as
                 // setting the parent context, setting the application context id, etc
                 if (cwac.getParent() == null) {
                     // The context instance was injected without an explicit parent ->
                     // determine parent for root web application context, if any.
                     ApplicationContext parent = loadParentContext(servletContext);
                     cwac.setParent(parent);
                 }
                 configureAndRefreshWebApplicationContext(cwac, servletContext);
             }
         }
         // PS: 将根上下文放置在servletContext中
         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
 
         ClassLoader ccl = Thread.currentThread().getContextClassLoader();
         if (ccl == ContextLoader.class.getClassLoader()) {
             currentContext = this.context;
         } else if (ccl != null) {
             currentContextPerThread.put(ccl, this.context);
         }
 
         if (logger.isDebugEnabled()) {
             logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
         }
         if (logger.isInfoEnabled()) {
             long elapsedTime = System.currentTimeMillis() - startTime;
             logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
         }
 
         return this.context;
     } catch (RuntimeException ex) {
         logger.error("Context initialization failed", ex);
         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
         throw ex;
     } catch (Error err) {
         logger.error("Context initialization failed", err);
         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
         throw err;
     }
 }

我们注意到这样一句configureAndRefreshWebApplicationContext(cwac, servletContext); 这个就是具体创建容器的方法,我们进入去看看

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		// The application context id is still set to its original default value
		// -> assign a more useful id based on available information
		String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
		if (idParam != null) {
			wac.setId(idParam);
		}
		else {
			// Generate default id...
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(sc.getContextPath()));
		}
	}

	wac.setServletContext(sc);
	String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
	if (configLocationParam != null) {
		wac.setConfigLocation(configLocationParam);
	}

	// The wac environment's #initPropertySources will be called in any case when the context
	// is refreshed; do it eagerly here to ensure servlet property sources are in place for
	// use in any post-processing or initialization that occurs below prior to #refresh
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
	}

	customizeContext(sc, wac);
	wac.refresh();
}

我们注意到wac.refresh();看起来是不是有点熟悉了,进入看看:

public final void refresh() throws BeansException, IllegalStateException {
	try {
		super.refresh();
	}
	catch (RuntimeException ex) {
		WebServer webServer = this.webServer;
		if (webServer != null) {
			webServer.stop();
		}
		throw ex;
	}
}

这里的super根据继承关系,我们知道,最终就是进入到了springframework中的refresh中,这个方法我们在上面已经说过了。

5、SpringBoot

1、SpringBoot启动流程图

在这里插入图片描述

2、SpringBoot启动类入口

用过SpringBoot的技术人员很显而易见的两者之间的差别就是视觉上很直观的:SpringBoot有自己独立的启动类(独立程序)

@SpringBootApplication
public class ServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceApplication.class, args);
    }
}

这段代码的核心为注解定义(@SpringBootApplication)和类定义(SpringApplication.run)

1.注解:@SpringBootApplication

注解内部源码如下:

@Target({ElementType.TYPE}) // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三个生命周期)
@Documented // 表明这个注解应该被javadoc记录
@Inherited // 子类可以继承该注解
@SpringBootConfiguration // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration // 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
@ComponentScan(excludeFilters = {@Filter( // 扫描路径设置
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
}

在其中比较重要的有三个注解,分别是:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

下面先来说说第一个注解:

@SpringBootConfiguration

根据Javadoc可知,该注解作用就是将当前的类作为一个JavaConfig,然后触发注解@EnableAutoConfiguration和@ComponentScan的处理,本质上与@Configuration注解没有区别。

Java Configuration的配置形式注入bean,参考以下代码:

@Configuration
public class FakeConfiguration{
    @Bean
    public FakeService mockService(){
        return new FakeServiceImpl();
    }
}

@Configuration标注当前类是Java Config配置类,会被扫描并加载到IoC容器。

这边额外简单补充一下 @Configuration 和 @Component的区别:

  • @Component注解的范围最广,所有类都可以注解,而 @Configuration一般注解在这样的类上:这个类里面有 @Value注解的成员变量和@Bean注解的方法,就是一个配置类。
  • 英语字面上意义不同,Configuration为配置,Component为组件,都定义在类的上方,也代表着此类声明的意义。

@EnableAutoConfiguration

此注解顾名思义是可以自动配置,所以应该是springboot中最为重要的注解。

在spring框架中就提供了各种以@Enable开头的注解,例如: @EnableScheduling、@EnableCaching、@EnableMBeanExport等; @EnableAutoConfiguration的理念和做事方式其实一脉相承简单概括一下就是,借助@Import的支持,收集和注册特定场景相关的bean定义。

  • @EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IoC容器【定时任务、时间调度任务】
  • @EnableMBeanExport是通过@Import将JMX相关的bean定义加载到IoC容器【监控JVM运行时状态】

@EnableAutoConfiguration也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器。
@EnableAutoConfiguration作为一个复合注解,其自身定义关键信息如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage 【重点注解】
@Import({AutoConfigurationImportSelector.class}) 【重点注解】
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

还有其中比较重要的一个类就是:AutoConfigurationImportSelector.class

  • @AutoConfigurationPackage
    • 注册当前启动类的根 package
    • 注册 org.springframework.boot.autoconfigure.AutoConfigurationPackages 的 Bean 的定义。
  • @Import(AutoConfigurationImportSelector.class)
    • 可以看到实现了 DeferredImportSelector 接口,该接口继承自 ImportSelector,根据 Javadoc 可知,多用于导入被 @Conditional 注解的Bean,之后会进行 filter 操作
    • AutoConfigurationImportSelector.AutoConfigurationGroup#process 方法,SpringBoot 启动时会调用该方法,进行自动装配。
      • SpringApplication#run(java.lang.String…)
      • SpringApplication#refreshContext(即 Spring IOC 容器初始化的过程中)
      • ConfigurationClassParser#parse
      • AutoConfigurationImportSelector.AutoConfigurationGroup#process
  • 通过 SpringFactoriesLoader#loadFactoryNames 获取应考虑的自动配置名称。
  • 通过 filter 过滤掉当前环境不需要自动装配的类,各种 @Conditional 不满足就被过滤掉。
  • 将需要自动装配的全路径类名注册到 SpringIOC 容器,自此 SpringBoot 自动装配完成。

SpringBoot 自动装配的核心注解,在 Spring 框架中就提供了各种以 @Enable 开头的注解,例如: @EnableCircuitBreaker、@EnableScheduling 等;

@ComponentScan

  • ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义。
  • 我们可以通过 basePackages 等属性来细粒度的定制 @ComponentScan 自动扫描的范围,如果不指定,则默认Spring框架实现会从声明 @ComponentScan 所在类的package进行扫描,所以 SpringBoot 的启动类最好是放在根package下,我们自定义的类就放在对应的子package下,这样就可以不指定 basePackages。
2.main方法

main方法里调用org.springframework.boot.SpringApplication.run(java.lang.Class<?>, java.lang.String…)方法。

SpringApplication#run

调用另外一个同名的重载方法run

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

实例化SpringApplication对象

  • 首先会实例化SpringApplication一个对象。
  • 在构造方法里初始化一些属性,比如webApplicationType,比如"SERVLET",初始化一些listeners。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

SpringApplication.run(java.lang.String…)

经典的观察者模式:

  1. 创建一个StopWatch实例,用来记录SpringBoot的启动时间。
  2. 通过SpringFactoriesLoader加载listeners:比如EventPublishingRunListener。
  3. 发布SprintBoot开始启动事件(EventPublishingRunListener#starting())。
  4. 创建和配置environment(environmentPrepared())。
  5. 打印SpringBoot的banner和版本。
  6. 创建对应的ApplicationContext:Web类型,Reactive类型,普通的类型(非Web)。
  7. prepareContext:
    1. 准备ApplicationContext,Initializers设置到ApplicationContext(contextPrepared())。
    2. 打印启动日志,打印profile信息(如dev, test, prod)。
    3. 最终会调用到AbstractApplicationContext#refresh方法,实际上就是Spring IOC容器的创建过程,并且会进行自动装配的操作,以及发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成(contextLoaded())
  8. afterRefresh hook方法。
  9. stopWatch停止计时,日志打印总共启动的时间。
  10. 发布SpringBoot程序已启动事件(started())。
  11. 调用ApplicationRunner和CommandLineRunner。
  12. 最后发布就绪事件ApplicationReadyEvent,标志着SpringBoot可以处理就收的请求了(running())。
public ConfigurableApplicationContext run(String... args) {
    // 创建一个StopWatch实例,用来记录SpringBoot的启动时间
 StopWatch stopWatch = new StopWatch();
 stopWatch.start();
 ConfigurableApplicationContext context = null;
 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
 configureHeadlessProperty();
    // 通过SpringFactoriesLoader加载listeners:比如EventPublishingRunListener
 SpringApplicationRunListeners listeners = getRunListeners(args);
    // 发布SprintBoot启动事件:ApplicationStartingEvent
 listeners.starting();
 try {
  ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
     // 创建和配置environment,发布事件:SpringApplicationRunListeners#environmentPrepared
  ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  configureIgnoreBeanInfo(environment);
     // 打印SpringBoot的banner和版本
  Banner printedBanner = printBanner(environment);
     // 创建对应的ApplicationContext:Web类型,Reactive类型,普通的类型(非Web)
  context = createApplicationContext();
  exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
    new Class[] { ConfigurableApplicationContext.class }, context);
     // 准备ApplicationContext,Initializers设置到ApplicationContext后发布事件:ApplicationContextInitializedEvent
     // 打印启动日志,打印profile信息(如dev, test, prod)
     // 调用EventPublishingRunListener发布ApplicationContext加载完毕事件:ApplicationPreparedEvent
  prepareContext(context, environment, listeners, applicationArguments, printedBanner);
     // 最终会调用到AbstractApplicationContext#refresh方法,实际上就是Spring IOC容器的创建过程,并且会进行自动装配的操作
     // 以及发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成
  refreshContext(context);
     // hook方法
  afterRefresh(context, applicationArguments);
     // stopWatch停止计时,日志打印总共启动的时间
  stopWatch.stop();
  if (this.logStartupInfo) {
   new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  }
     // 发布SpringBoot程序已启动事件ApplicationStartedEvent
  listeners.started(context);
     // 调用ApplicationRunner和CommandLineRunner
  callRunners(context, applicationArguments);
 }
 catch (Throwable ex) {
  handleRunFailure(context, ex, exceptionReporters, listeners);
  throw new IllegalStateException(ex);
 }

 try {
     // 最后发布就绪事件ApplicationReadyEvent,标志着SpringBoot可以处理就收的请求了
  listeners.running(context);
 }
 catch (Throwable ex) {
  handleRunFailure(context, ex, exceptionReporters, null);
  throw new IllegalStateException(ex);
 }
 return context;
}

3、SpringBoot启动事件

  • SpringApplicationRunListeners的唯一实现是EventPublishingRunListener。
  • 整个SpringBoot的启动,流程就是各种事件的发布,调用EventPublishingRunListener中的方法。
  • 只要明白了EventPublishingRunListener中事件发布的流程,也就明白了SpringBoot启动的大体流程。
EventPublishingRunListener

方法说明如下:

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

 private final SpringApplication application;

 private final String[] args;

 private final SimpleApplicationEventMulticaster initialMulticaster;

 public EventPublishingRunListener(SpringApplication application, String[] args) {
  this.application = application;
  this.args = args;
  this.initialMulticaster = new SimpleApplicationEventMulticaster();
  for (ApplicationListener<?> listener : application.getListeners()) {
   this.initialMulticaster.addApplicationListener(listener);
  }
 }

 @Override
 public int getOrder() {
  return 0;
 }

    // SpringBoot启动事件
 @Override
 public void starting() {
  this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
 }

    // 创建和配置环境
 @Override
 public void environmentPrepared(ConfigurableEnvironment environment) {
  this.initialMulticaster
    .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
 }

    // 准备ApplicationContext
 @Override
 public void contextPrepared(ConfigurableApplicationContext context) {
  this.initialMulticaster
    .multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
 }

    // 发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成
 @Override
 public void contextLoaded(ConfigurableApplicationContext context) {
  for (ApplicationListener<?> listener : this.application.getListeners()) {
   if (listener instanceof ApplicationContextAware) {
    ((ApplicationContextAware) listener).setApplicationContext(context);
   }
   context.addApplicationListener(listener);
  }
  this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
 }

    // SpringBoot已启动事件
 @Override
 public void started(ConfigurableApplicationContext context) {
  context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
  AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
 }

    // "SpringBoot现在可以处理接受的请求"事件
 @Override
 public void running(ConfigurableApplicationContext context) {
  context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
  AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
 }

 @Override
 public void failed(ConfigurableApplicationContext context, Throwable exception) {
  ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
  if (context != null && context.isActive()) {
   // Listeners have been registered to the application context so we should
   // use it at this point if we can
   context.publishEvent(event);
  }
  else {
   // An inactive context may not have a multicaster so we use our multicaster to
   // call all of the context's listeners instead
   if (context instanceof AbstractApplicationContext) {
    for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
      .getApplicationListeners()) {
     this.initialMulticaster.addApplicationListener(listener);
    }
   }
   this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
   this.initialMulticaster.multicastEvent(event);
  }
 }

 private static class LoggingErrorHandler implements ErrorHandler {

  private static final Log logger = LogFactory.getLog(EventPublishingRunListener.class);

  @Override
  public void handleError(Throwable throwable) {
   logger.warn("Error calling ApplicationEventListener", throwable);
  }
 }
}

4、SpringIOC 容器初始化过程

由于现在大都是用SpringBoot开发,所以呢,Spring IOC 初始化的源码,就是AnnotationConfigApplicationContext中的源码,IoC的初始化就是该类实例创建的过程。

创建的过程 (AnnotationConfigApplicationContext的构造方法):

  1. 给我们的Bean,创建与之对应的Bean定义,然后把他们放入 ConcurrentHashMap(key:beanName和value:beanDefinition)中;Bean定义实际上包括一些Bean的信息,比如BeanName, Scope, 是否被@Primary注解修饰,是否是@Lazy,以及@Description等注解。
  2. refresh()方法: 创建IOC需要的资源。
  3. 初始化BeanFactory, set一些属性,如BeanClassLoader,systemEnvironment。
  4. 如果是SpringBoot程序,会调用方法进行自动装配:AutoConfigurationImportSelector.AutoConfigurationGroup#process。
  5. 注册MessageSource,国际化相关的资源,到ApplicationContext。
  6. 注册ApplicationListener到ApplicationContext。
  7. 实例化化lazy-init的Bean。
  8. 最后,publish相关的事件,ApplicationContext 就初始化完成,整个IOC容器初始化完成(IOC容器的本质就是初始化BeanFactory和ApplicationContext),就可以从IOC容器中获取Bean自动注入了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值