Spring 面试题

Spring 基础

什么是 spring?

Spring 是另一个主流的 Java Web 开发框架,该框架是一个轻量级的应用框架。

Spring 是分层的 Java SE/EE full-stack 轻量级开源框架,以 IoC(Inverse of Control,控制反转)和 AOP(Aspect Oriented Programming,面向切面编程)为内核,使用基本的 JavaBean 完成以前只可能由 EJB 完成的工作,取代了 EJB 臃肿和低效的开发模式。

在实际开发中,通常服务器端采用三层体系架构,分别为表现层(web)、业务逻辑层(service)、持久层(dao)。Spring 对每一层都提供了技术支持,在表现层提供了与 Struts2 框架的整合,在业务逻辑层可以管理事务和记录日志等,在持久层可以整合 Hibernate 和 JdbcTemplate 等技术。

Spring 的优点?

  • 轻量:Spring 是轻量的,基本的版本大约 2MB
  • 控制反转:Spring 通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们
  • 面向切面的编程(AOP):Spring 支持面向切面的编程,并且把应用业务逻辑和系统服务分开,可以方便地实现对程序进行权限拦截和运行监控等功能。
  • 容器:Spring 包含并管理应用中对象的生命周期和配置
  • MVC 框架:Spring 的 WEB 框架是个精心设计的框架,是 Web 框架的一个很好的替代品
  • 事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA),只需要通过配置就可以完成对事务的管理,而无须手动编程
  • 异常处理:Spring 提供方便的 API 把具体技术相关的异常(比如由 JDBC,Hibernate or JDO 抛出的)转化为一致的 unchecked 异常
  • 方便解耦,简化开发:Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给 Spring 管理。
  • 方便集成各种优秀框架:Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如 Struts2、Hibernate、MyBatis 等)的直接支持。
  • 降低 Java EE API 的使用难度:Spring 对 Java EE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了封装,使这些 API 应用的难度大大降低。
  • 方便程序的测试:Spring 支持 JUnit4,可以通过注解方便地测试 Spring 程序。

Spring 的模块组成?

Spring 框架采用分层架构,根据不同的功能被划分成了多个模块,这些模块大体可分为 Data Access/IntegrationWebAOPAspectsMessagingInstrumentationCore ContainerTest

  1. Data Access/Integration(数据访问/集成)
    1. JDBC 模块:提供了一个 JDBC 的抽象层,大幅度减少了在开发过程中对数据库操作的编码。
    2. ORM 模块:对流行的对象关系映射 API,包括 JPA、JDO、Hibernate 和 iBatis 提供了的集成层。
    3. OXM 模块:提供了一个支持对象/XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。
    4. JMS 模块:指 Java 消息服务,包含的功能为生产和消费的信息。
    5. Transactions 事务模块:支持编程和声明式事务管理实现特殊接口类,并为所有的 POJO。
  2. Web 模块
    1. Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IoC 容器初始化以及 Web 应用上下文。
    2. Servlet模块:包括 Spring 模型---视图---控制器(MVC)实现 Web 应用程序。
    3. Struts 模块:包含支持类内的 Spring 应用程序,集成了经典的 Struts Web 层。
    4. Portlet 模块:提供了在 Portlet 环境中使用 MV C实现,类似 Web-Servlet 模块的功能。
  3. Core Container(核心容器)
    1. Beans 模块:提供了 BeanFactory,是工厂模式的经典实现,Spring 将管理对象称为 Bean。
    2. Core 核心模块:提供了 Spring 框架的基本组成部分,包括 IoC 和 DI 功能。
    3. Context 上下文模块:建立在核心和 Beans 模块的基础之上,它是访问定义和配置任何对象的媒介。ApplicationContext 接口是上下文模块的焦点。
    4. Expression Language 模块:是运行时查询和操作对象图的强大的表达式语言。
  4. 其他模块
    1. AOP 模块:提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以降低耦合性。
    2. Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
    3. Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
    4. Test 模块:支持 Spring 组件,使用 JUnit 或 TestNG 框架的测试。

Spring 框架的七大模块?

  • Spring Core:框架的最基础部分,提供 IoC 容器,对 bean 进行管理。
  • Spring Context:继承 BeanFactory,提供上下文信息,扩展出 JNDI、EJB、电子邮件、国际化等功能。
  • Spring DAO:提供了 JDBC 的抽象层,还提供了声明性事务管理方法。
  • Spring ORM:提供了 JPA、JDO、Hibernate、MyBatis 等 ORM 映射层.
  • Spring AOP:集成了所有 AOP 功能
  • Spring Web:提供了基础的 Web 开发的上下文信息,现有的 Web 框架,如 JSF、Tapestry、Structs 等,提供了集成。
  • Spring Web MVC:提供了 Web 应用的 Model-View-Controller 全功能实现。

Spring 依赖 JAR 包?

  1. spring-core-3.2.13.RELEASE.jar:包含 Spring 框架基本的核心工具类,Spring 其他组件都要用到这个包中的类,是其他组件的基本核心。
  2. spring-beans-3.2.13.RELEASE.jar:所有应用都要用到的,它包含访问配置文件、创建和管理 bean 以及进行 Inversion of Control(IoC)或者 Dependency Injection(DI)操作相关的所有类。
  3. spring-context-3.2.13.RELEASE.jar:Spring 提供在基础 IoC 功能上的扩展服务,此外还提供许多企业级服务的支持,如邮件服务、任务调度、JNDI 定位、EJB 集成、远程访问、缓存以及各种视图层框架的封装等
  4. spring-expression-3.2.13.RELEASE.jar:定义了 Spring 的表达式语言。
  5. commons.logging:在使用 Spring 开发时,除了 Spring 自带的 JAR 包以外,还需要一个第三方 JAR 包 commons.logging 处理日志信息

Spring 配置文件?

Spring 配置文件支持两种不同的格式,分别是 XML 文件格式和 Properties 文件格式。通常情况下,Spring 会以 XML 文件格式作为 Spring 的配置文件,这种配置方式通过 XML 文件注册并管理 Bean 之间的依赖关系。

XML 格式配置文件的根元素是<beans>,该元素包含了多个<bean>子元素,每一个<bean>子元素定义了一个 Bean,并描述了该 Bean 如何被装配到 Spring 容器中。

定义 Bean 的示例代码如下所示:

XML 复制代码
​<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="Index of /schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

    <!-- 使用id属性定义person1,其对应的实现类为com.mengma.person1 -->
    <bean id="person1" class="com.mengma.damain.Person1" />
    <!--使用name属性定义person2,其对应的实现类为com.mengma.domain.Person2-->
    <bean name="Person2" class="com.mengma.domain.Person2"/>
</beans>

<bean> 元素常用属性:

  • id:是一个 Bean 的唯一标识符,Spring 容器对 Bean 的配置和管理都通过该属性完成
  • name:Spring 容器同样可以通过此属性对容器中的 Bean 进行配置和管理,name 属性中可以为 Bean 指定多个名称,每个名称之间用逗号或分号隔开
  • class:该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,使用类的全限定名
  • scope:用于设定 Bean 实例的作用域,其属性值有 singleton(单例)、prototype(原型)、request、session 和 global Session。其默认值是 singleton
  • constructor-arg<bean>元素的子元素,可以使用此元素传入构造参数进行实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型
  • property<bean>元素的子元素,用于调用 Bean 实例中的 Set 方法完成属性赋值,从而完成依赖注入。该元素的 name 属性指定 Bean 实例中的相应属性名
  • ref<property><constructor-arg>等元素的子元索,该元素中的 bean 属性用于指定对 Bean 工厂中某个 Bean 实例的引用
  • value<property><constractor-arg>等元素的子元素,用于直接指定一个常量值
  • list:用于封装 List 或数组类型的依赖注入
  • set:用于封装 Set 类型属性的依赖注入
  • map:用于封装 Map 类型属性的依赖注入
  • entry<map>元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值

Spring 中的设计模式?

  1. 工厂模式:Spring 中的 BeanFactory 就是简单工厂模式的体现,根据传入唯一的标识来获得 Bean 对象;
  2. 单例模式:提供了全局的访问点 BeanFactory;
  3. 代理模式:AOP 功能的原理就使用代理模式(1、JDK 动态代理。2、CGLib 字节码生成技术代理。)
  4. 装饰器模式:依赖注入就需要使用 BeanWrapper;
  5. 观察者模式:Spring 中 Observer 模式常用的地方是 listener 的实现。如 ApplicationListener。
  6. 策略模式:Bean 的实例化的时候决定采用何种方式初始化 bean 实例(反射或者 CGLIB 动态字节码生成)

Spring IOC

什么是 Spring IOC?

控制反转(Inversion of Control)。通常要创建一个实例,由程序员在代码中通过 new 来实现,而"控制反转"把创建对象和对象之间的调用过程交给 Spring 进行管理,Spring IoC 容器利用 Java 的 POJO 类和配置文件来生成完全配置和可执行的系统或应用程序。

Spring IOC 的容器?

Spring 提供了以下两种不同类型的容器:

  1. BeanFactory 容器
  2. ApplicationContext 容器

BeanFactory 容器

它是最简单的容器,给 DI 提供了基本的支持,它用org.springframework.beans.factory.BeanFactory接口来定义。BeanFactory 或者相关的接口,如 BeanFactoryAware,InitializingBean,DisposableBean,在 Spring 中仍然存在具有大量的与 Spring 整合的第三方框架的反向兼容性的目的。

在 Spring 中,有大量对 BeanFactory 接口的实现。其中,最常被使用的是 XmlBeanFactory 类。这个容器从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。

java 复制代码
@Test
public void test1() {
    XmlBeanFactory factory = new XmlBeanFactory (new ClassPathResource("Beans.xml"));
    Users users = factory.getBean("users, Users.class");
    users.getMessage();
}

ApplicationContext 容器

ApplicationContext 是 BeanFactory 的子接口,也被称为 Spring 上下文。

Application Context 是 Spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。

另外该容器添加了更多的企业特定的功能,例如从一个属性文件中解析文本信息的能力,发布应用程序事件给感兴趣的事件监听器的能力。该容器是由 org.springframework.context.ApplicationContext 接口定义。

ApplicationContext 常用实现类?

  1. FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。
  2. ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
  3. WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。

BeanFactory 和 ApplicationContext 区别?

  • BeanFactory 一般是 Spring 内部使用,ApplicationContext 是面向开发者的。
  • ApplicationContext 容器包括 BeanFactory 容器的所有功能,所以通常不建议使用 BeanFactory。
  • BeanFactory 在加载配置文件的时候不会创建对象,在使用的时候才会创建,ApplicationContext 在加载配置文件的时候就会创建对象。
  • BeanFactory 仍然可以用于轻量级的应用程序,如移动设备或基于 applet 的应用程序,其中它的数据量和速度是显著。
  • Application contexts 提供一种方法处理文本消息,一个通常的做法是加载文件资源(比如镜像),它们可以向注册为监听器的 bean 发布事件。
  • 另外,在容器或容器内的对象上执行的那些不得不由 bean 工厂以程序化方式处理的操作,可以在 Application contexts 中以声明的方式处理 。
  • Application contexts 实现了 MessageSource 接口,该接口的实现以可插拔的方式提供获取本地化消息的方法。

IoC 底层原理?

  1. xml 解析------XML有三种解析方式:DOM SAX STAX。存储和传输数据经常一起使用,XML数据通常由程序生成的,用程序解析XML(XML一般不加约束),配置文件单独使用(通常会加约束)
  2. 工厂模式------把对类的创建初始化全都交给一个工厂来执行,而用户不需要去关心创建的过程是什么样的
  3. 反射 ------反射可以在运行时根据指定的类名获得类的信息
  4. 将创建好的对象存储到 Spring 自身维护的 Map 当中。map 中的 key 就是 bean 的 ID,map 中的 value 就是创建的对象。之后在获取对象时就是根据 bean 中的 id(Map 中的 Key)获取对象就可。

什么是依赖注入?

依赖注入 DI,是 Spring 框架核心 IOC 的具体实现。这概念是说你不用创建对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和服务,但是要在配置文件里描述哪些组件需要哪些服务,之后一个容器(IOC 容器)负责把他们组装起来。

通过依赖注入的方式来管理 Bean 之间的依赖关系。

依赖注入方式?

  1. 构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
  2. Setter 方法注入:Setter 方法注入是容器通过调用无参构造器或无参 static 工厂 方法实例化 bean 之后,调用该 bean 的 setter 方法,即实现了基于 setter 的依赖注入。

构造方法注入和 setter 注入区别:

  • setter 注入方法支持大部分的依赖注入,如果我们仅需要注入 int、string 和 long 型的变量,我们不要用设值的方法注入。对于基本类型,如果我们没有注入的话,可以为基本类型设置默认值。在构造方法注入不支持大部分的依赖注入,因为在调用构造方法中必须传入正确的构造参数,否则的话为报错。
  • setter 注入不会重写构造方法的值。如果我们对同一个变量同时使用了构造方法注入又使用了 setter 方法注入的话,那么构造方法将不能覆盖由 setter 方法注入的值。很明显,因为构造方法仅在对象被创建时调用。
  • 在使用 setter 注入时有可能还不能保证某种依赖是否已经被注入,也就是说这时对象的依赖关系有可能是不完整的,而构造器注入则不允许生成依赖关系不完整的对象。
  • 在构造方法注入时如果对象A和对象B互相依赖,在创建对象A时 Spring 会抛出 ObjectCurrentlyInCreationException 异常,因为在B对象被创建之前A对象是不能被创建的,反之亦然。Spring 用 setter 注入的方法解决了循环依赖的问题,因对象的 setter 方法是在对象被创建之前被调用的。

Spring 循环依赖?

从字面上来理解就是A依赖B的同时B也依赖了A,就像下面这样

代码示例如下:

java 复制代码
@Component
public class A {
    // A中注入了B
    @Autowired
    private B b;
}


@Component
public class B {
    // B中也注入了A
    @Autowired
    private A a;
}

比较特殊的循环依赖如下:

java 复制代码
// 自己依赖自己
@Component
public class A {
    // A中注入了A
    @Autowired
    private A a;
}

什么情况下循环依赖可以被处理?

Spring 解决循环依赖是有前置条件的:

  1. 出现循环依赖的 Bean 必须要是单例
  2. 依赖注入的方式不能全是构造器注入的方式

不能全是构造器注入是为什么呢,有如下示例:

java 复制代码
@Component
public class A {
    public A(B b) {
    }
}


@Component
public class B {
    public B(A a){
    }
}

A中注入B的方式是通过构造器,B中注入A的方式也是通过构造器,这个时候循环依赖是无法被解决,如果你的项目中有两个这样相互依赖的 Bean,在启动时就会报出以下错误:

bash 复制代码
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

解决循环依赖的情况如下:

|--------------|--------------------------------|---------------|
| 依赖情况 | 依赖注入方式 | 循环依赖是否被解决 |
| AB相互依赖(循环依赖) | 均采用setter方法注入 | 是 |
| AB相互依赖(循环依赖) | 均采用构造器注入 | 否 |
| AB相互依赖(循环依赖) | A中注入B的方式为setter方法,B中注入A的方式为构造器 | 是 |
| AB相互依赖(循环依赖) | B中注入A的方式为setter方法,A中注入B的方式为构造器 | 否 |

Spring 如何解决循环依赖?

(1)简单的循环依赖(没有AOP),示例如下:

bash 复制代码
@Component
public class A {

    // A中注入了B
    @Autowired
    private B b;

}



@Component
public class B {

    // B中也注入了A
    @Autowired
    private A a;

}

首先,Spring 在创建 Bean 的时候默认是按照自然排序来进行创建的,所以第一步 Spring 会去创建A。Spring 创建 Bean 的过程分为三步:

  1. 实例化,对应方法:AbstractAutowireCapableBeanFactory 中的 createBeanInstance 方法
  2. 属性注入,对应方法:AbstractAutowireCapableBeanFactorypopulateBean 方法
  3. 初始化,对应方法:AbstractAutowireCapableBeanFactoryinitializeBean 方法

如创建 A 的 Bean 对象,Spring 创建 Bean 就是通过getBean()方法创建的,getBean()的源码如下:

getBean() 方法所在的接口 BeanFactory 接口

java 复制代码
public interface BeanFactory {

    ...
    <T> T getBean(String var1, Class<T> var2) throws BeansException;
    ...

}

getBean() 方法的实现类

java 复制代码
public Object getBean(String name, Object... args) throws BeansException {
    return this.doGetBean(name, (Class)null, args, false);
}

getBean()方法的实现类中,调用了doGetBean()方法,这个方法的源码如下:

java 复制代码
protected <T> T doGetBean(
		String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
		throws BeansException {

	String beanName = transformedBeanName(name);
	Object bean;

	Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
		if (logger.isTraceEnabled()) {
			if (isSingletonCurrentlyInCreation(beanName)) {
				logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
						"' that is not fully initialized yet - a consequence of a circular reference");
			}
			else {
				logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
			}
		}
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	}

	else {
		if (isPrototypeCurrentlyInCreation(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}

		BeanFactory parentBeanFactory = getParentBeanFactory();
		if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
			String nameToLookup = originalBeanName(name);
			if (parentBeanFactory instanceof AbstractBeanFactory) {
				return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
						nameToLookup, requiredType, args, typeCheckOnly);
			}
			else if (args != null) {
				return (T) parentBeanFactory.getBean(nameToLookup, args);
			}
			else if (requiredType != null) {
				return parentBeanFactory.getBean(nameToLookup, requiredType);
			}
			else {
				return (T) parentBeanFactory.getBean(nameToLookup);
			}
		}

		if (!typeCheckOnly) {
			markBeanAsCreated(beanName);
		}

		try {
			RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
			checkMergedBeanDefinition(mbd, beanName, args);

			String[] dependsOn = mbd.getDependsOn();
			if (dependsOn != null) {
				for (String dep : dependsOn) {
					if (isDependent(beanName, dep)) {
						throw new BeanCreationException(mbd.getResourceDescription(), beanName,
								"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
					}
					registerDependentBean(dep, beanName);
					try {
						getBean(dep);
					}
					catch (NoSuchBeanDefinitionException ex) {
						throw new BeanCreationException(mbd.getResourceDescription(), beanName,
								"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
					}
				}
			}

			if (mbd.isSingleton()) {
				sharedInstance = getSingleton(beanName, () -> {
					try {
						return createBean(beanName, mbd, args);
					}
					catch (BeansException ex) {
						// Explicitly remove instance from singleton cache: It might have been put there
						// eagerly by the creation process, to allow for circular reference resolution.
						// Also remove any beans that received a temporary reference to the bean.
						destroySingleton(beanName);
						throw ex;
					}
				});
				bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
			}

			else if (mbd.isPrototype()) {
				Object prototypeInstance = null;
				try {
					beforePrototypeCreation(beanName);
					prototypeInstance = createBean(beanName, mbd, args);
				}
				finally {
					afterPrototypeCreation(beanName);
				}
				bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
			}

			else {
				String scopeName = mbd.getScope();
				if (!StringUtils.hasLength(scopeName)) {
					throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
				}
				Scope scope = this.scopes.get(scopeName);
				if (scope == null) {
					throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
				}
				try {
					Object scopedInstance = scope.get(beanName, () -> {
						beforePrototypeCreation(beanName);
						try {
							return createBean(beanName, mbd, args);
						}
						finally {
							afterPrototypeCreation(beanName);
						}
					});
					bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
				}
				catch (IllegalStateException ex) {
					throw new BeanCreationException(beanName,
							"Scope '" + scopeName + "' is not active for the current thread; consider " +
							"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
							ex);
				}
			}
		}
		catch (BeansException ex) {
			cleanupAfterBeanCreationFailure(beanName);
			throw ex;
		}
	}

	if (requiredType != null && !requiredType.isInstance(bean)) {
		try {
			T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
			if (convertedBean == null) {
				throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
			}
			return convertedBean;
		}
		catch (TypeMismatchException ex) {
			if (logger.isTraceEnabled()) {
				logger.trace("Failed to convert bean '" + name + "' to required type '" +
						ClassUtils.getQualifiedName(requiredType) + "'", ex);
			}
			throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
		}
	}
	return (T) bean;
}

首先调用 getSingleton(a) 方法,这个方法又会调用 getSingleton(beanName, true)getSingleton(beanName, true) 这个方法实际上就是到缓存中尝试去获取 Bean,整个缓存分为三级

  1. singletonObjects:一级缓存,存储的是所有创建好了的单例 Bean
  2. earlySingletonObjects:完成实例化,但是还未进行属性注入及初始化的对象
  3. singletonFactories:提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象

因为 A 是第一次被创建,所以不管哪个缓存中必然都是没有的,因此会进入getSingleton的另外一个重载方法getSingleton(beanName, singletonFactory),源码如下:

java 复制代码
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(beanName, "Bean name must not be null");
	synchronized (this.singletonObjects) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null) {
			if (this.singletonsCurrentlyInDestruction) {
				throw new BeanCreationNotAllowedException(beanName,
						"Singleton bean creation not allowed while singletons of this factory are in destruction " +
						"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
			}
			beforeSingletonCreation(beanName);
			boolean newSingleton = false;
			boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
			if (recordSuppressedExceptions) {
				this.suppressedExceptions = new LinkedHashSet<>();
			}
			try {
				singletonObject = singletonFactory.getObject();
				newSingleton = true;
			}
			catch (IllegalStateException ex) {
				// Has the singleton object implicitly appeared in the meantime ->
				// if yes, proceed with it since the exception indicates that state.
				singletonObject = this.singletonObjects.get(beanName);
				if (singletonObject == null) {
					throw ex;
				}
			}
			catch (BeanCreationException ex) {
				if (recordSuppressedExceptions) {
					for (Exception suppressedException : this.suppressedExceptions) {
						ex.addRelatedCause(suppressedException);
					}
				}
				throw ex;
			}
			finally {
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = null;
				}
				afterSingletonCreation(beanName);
			}
			if (newSingleton) {
				addSingleton(beanName, singletonObject);
			}
		}
		return singletonObject;
	}
}

上面的源码中 this.addSingleton(beanName, singletonObject);表示通过createBean方法返回的 Bean 最终被放到了一级缓存,也就是单例池中。那么一级缓存中存储的是已经完全创建好了的单例 Bean。

然后回到在doGetBean(),在这个方法里面通过判断if (mbd.isSingleton())调用了createBean()方法,源码如下:

java 复制代码
protected abstract Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException;

createBean()方法实现类,源码如下:

java 复制代码
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {

	if (logger.isTraceEnabled()) {
		logger.trace("Creating instance of bean '" + beanName + "'");
	}
	RootBeanDefinition mbdToUse = mbd;

	// Make sure bean class is actually resolved at this point, and
	// clone the bean definition in case of a dynamically resolved Class
	// which cannot be stored in the shared merged bean definition.
	Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
	if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
		mbdToUse = new RootBeanDefinition(mbd);
		mbdToUse.setBeanClass(resolvedClass);
	}

	// Prepare method overrides.
	try {
		mbdToUse.prepareMethodOverrides();
	}
	catch (BeanDefinitionValidationException ex) {
		throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
				beanName, "Validation of method overrides failed", ex);
	}

	try {
		// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
		Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
		if (bean != null) {
			return bean;
		}
	}
	catch (Throwable ex) {
		throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
				"BeanPostProcessor before instantiation of bean failed", ex);
	}

	try {
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		if (logger.isTraceEnabled()) {
			logger.trace("Finished creating instance of bean '" + beanName + "'");
		}
		return beanInstance;
	}
	catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
		// A previously detected exception with proper bean creation context already,
		// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
		throw ex;
	}
	catch (Throwable ex) {
		throw new BeanCreationException(
				mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
	}
}

createBean()方法内部又通过this.doCreateBean(beanName, mbdToUse, args);调用了doCreateBean()方法,源码如下:

java 复制代码
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

	// Instantiate the bean.
	BeanWrapper instanceWrapper = null;
	if (mbd.isSingleton()) {
		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
	}
	if (instanceWrapper == null) {
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	Object bean = instanceWrapper.getWrappedInstance();
	Class<?> beanType = instanceWrapper.getWrappedClass();
	if (beanType != NullBean.class) {
		mbd.resolvedTargetType = beanType;
	}

	// Allow post-processors to modify the merged bean definition.
	synchronized (mbd.postProcessingLock) {
		if (!mbd.postProcessed) {
			try {
				applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
			}
			catch (Throwable ex) {
				throw new BeanCreationException(mbd.getResourceDescription(), beanName,
						"Post-processing of merged bean definition failed", ex);
			}
			mbd.postProcessed = true;
		}
	}

	// Eagerly cache singletons to be able to resolve circular references
	// even when triggered by lifecycle interfaces like BeanFactoryAware.
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
			isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		if (logger.isTraceEnabled()) {
			logger.trace("Eagerly caching bean '" + beanName +
					"' to allow for resolving potential circular references");
		}
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}

	// Initialize the bean instance.
	Object exposedObject = bean;
	try {
		populateBean(beanName, mbd, instanceWrapper);
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	}
	catch (Throwable ex) {
		if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
			throw (BeanCreationException) ex;
		}
		else {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
		}
	}

	if (earlySingletonExposure) {
		Object earlySingletonReference = getSingleton(beanName, false);
		if (earlySingletonReference != null) {
			if (exposedObject == bean) {
				exposedObject = earlySingletonReference;
			}
			else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
				String[] dependentBeans = getDependentBeans(beanName);
				Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
				for (String dependentBean : dependentBeans) {
					if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
						actualDependentBeans.add(dependentBean);
					}
				}
				if (!actualDependentBeans.isEmpty()) {
					throw new BeanCurrentlyInCreationException(beanName,
							"Bean with name '" + beanName + "' has been injected into other beans [" +
							StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
							"] in its raw version as part of a circular reference, but has eventually been " +
							"wrapped. This means that said other beans do not use the final version of the " +
							"bean. This is often the result of over-eager type matching - consider using " +
							"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
				}
			}
		}
	}

	// Register bean as disposable.
	try {
		registerDisposableBeanIfNecessary(beanName, bean, mbd);
	}
	catch (BeanDefinitionValidationException ex) {
		throw new BeanCreationException(
				mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
	}

	return exposedObject;
}

doCreateBean()方法内部,通过this.addSingletonFactory()将 Bean 添加到三级缓存中,并通过this.populateBean()方法开始注入属性。addSingletonFactory()方法对应源码如下:

// 这里传入的参数也是一个lambda表达式,() -> getEarlyBeanReference(beanName, mbd, bean)

java 复制代码
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	synchronized (this.singletonObjects) {
		if (!this.singletonObjects.containsKey(beanName)) {
			this.singletonFactories.put(beanName, singletonFactory);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}

这里只是添加了一个工厂,通过这个工厂(ObjectFactory)的getObject方法可以得到一个对象,而这个对象实际上就是通过getEarlyBeanReference这个方法创建的。那么,什么时候会去调用这个工厂的getObject方法呢?这个时候就要到创建B的流程了。

当A完成了实例化并添加进了三级缓存后,就要开始为A进行属性注入了,在注入时发现A依赖了B,那么这个时候Spring 又会去getBean(b),然后反射调用 setter 方法完成属性注入。

因为B需要注入A,所以在创建B的时候,又会去调用getBean(a),这个时候就又回到之前的流程了,但是不同的是,之前的getBean是为了创建 Bean,而此时再调用getBean不是为了创建,而是要从缓存中获取,因为之前A在实例化后已经将其放入了三级缓存 singletonFactories 中,所以此时getBean(a)的流程就是这样子了

这是调用的方法就是下面这个:

java 复制代码
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// Quick check for existing instance without full singleton lock
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		singletonObject = this.earlySingletonObjects.get(beanName);
		if (singletonObject == null && allowEarlyReference) {
			synchronized (this.singletonObjects) {
				// Consistent creation of early reference within full singleton lock
				singletonObject = this.singletonObjects.get(beanName);
				if (singletonObject == null) {
					singletonObject = this.earlySingletonObjects.get(beanName);
					if (singletonObject == null) {
						ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
						if (singletonFactory != null) {
							singletonObject = singletonFactory.getObject();
							this.earlySingletonObjects.put(beanName, singletonObject);
							this.singletonFactories.remove(beanName);
						}
					}
				}
			}
		}
	}
	return singletonObject;
}

调用工厂的getObject(),其实是调用了getEarlyBeanReference,从这里我们可以看出,注入到B中的A是通过getEarlyBeanReference方法提前暴露出去的一个对象,还不是一个完整的 Bean,那么getEarlyBeanReference到底干了啥了,我们看下它的源码

java 复制代码
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
				SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
				exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
	}
	return exposedObject;
}

它实际上就是调用了后置处理器的getEarlyBeanReference,而真正实现了这个方法的后置处理器只有一个,就是通过@EnableAspectJAutoProxy注解导入的AnnotationAwareAspectJAutoProxyCreator。也就是说如果在不考虑AOP的情况下,上面的代码等价于:

java 复制代码
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {

    Object exposedObject = bean;
    return exposedObject;

}

也就是说这个工厂啥都没干,直接将实例化阶段创建的对象返回了!那么三级缓存到底有什么作用呢?

将整个创建A这个Bean的流程走完,如下图:

上图中我们可以看到,虽然在创建B时会提前给B注入了一个还未初始化的A对象,但是在创建A的流程中一直使用的是注入到B中的A对象的引用,之后会根据这个引用对A进行初始化,所以这是没有问题的。

(2)结合了AOP的循环依赖

在普通的循环依赖的情况下,三级缓存没有任何作用。三级缓存实际上跟Spring中的AOP相关,如下 getEarlyBeanReference 的源码:

java 复制代码
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {

    Object exposedObject = bean;

    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {

        for (BeanPostProcessor bp : getBeanPostProcessors()) {

            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {

                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;

                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);

            }

        }

    }

    return exposedObject;

}

如果在开启AOP的情况下,那么就是调用到AnnotationAwareAspectJAutoProxyCreatorgetEarlyBeanReference方法,对应的源码如下:

java 复制代码
public Object getEarlyBeanReference(Object bean, String beanName) {

    Object cacheKey = getCacheKey(bean.getClass(), beanName);

    this.earlyProxyReferences.put(cacheKey, bean);

    // 如果需要代理,返回一个代理对象,不需要代理,直接返回当前传入的这个bean对象

    return wrapIfNecessary(bean, beanName, cacheKey);

}

也就是说,如果对A进行了AOP代理的话,那么此时getEarlyBeanReference将返回一个代理后的对象,而不是实例化阶段创建的对象,这样就意味着B中注入的A将是一个代理对象而不是A的实例化阶段创建后的对象。

那么在给B注入的时候为什么要注入一个代理对象?

  • 当我们对A进行了 AOP 代理时,说明我们希望从容器中获取到的就是A代理后的对象而不是A本身,因此把A当作依赖进行注入时也要注入它的代理对象

明明初始化的时候是A对象,那么 Spring 是在哪里将代理对象放入到容器中的呢?看下面doCreateBean方法的部分源码:

java 复制代码
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {

    ...
    try {
        this.populateBean(beanName, mbd, instanceWrapper);
        // exposedObject 初始化后的对象
        exposedObject = this.initializeBean(beanName, exposedObject, mbd);
    } catch (Throwable var18) {
        if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
        throw (BeanCreationException)var18;
        }
        throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
    }

    if (earlySingletonExposure) {
        // 从二级缓存中获取到代理后的Bean
        Object earlySingletonReference = this.getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                // 替换成代理对象添加到一级缓存中
                exposedObject = earlySingletonReference;
            } else if (!this.allowRawInjectionDespiteWrapping && this.hasDependentBean(beanName)) {
                ...
            }

在完成初始化后,Spring 又调用了一次getSingleton方法,这一次传入的参数又不一样了,false 可以理解为禁用三级缓存,前面图中已经提到过了,在为B中注入A时已经将三级缓存中的工厂取出,并从工厂中获取到了一个对象放入到了二级缓存中,所以这里的这个getSingleton方法做的时间就是从二级缓存中获取到这个代理后的A对象。exposedObject == bean可以认为是必定成立的,除非你非要在初始化阶段的后置处理器中替换掉正常流程中的 Bean。

初始化的时候是对A对象本身进行初始化,而容器中以及注入到B中的都是代理对象,这样不会有问题吗?

不会,这是因为不管是 cglib 代理还是 jdk 动态代理生成的代理类,内部都持有一个目标类的引用,当调用代理对象的方法时,实际会去调用目标对象的方法,A完成初始化相当于代理对象自身也完成了初始化。

三级缓存为什么要使用工厂而不是直接使用引用?换而言之,为什么需要这个三级缓存,直接通过二级缓存暴露一个引用不行吗?

这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象。

总结:

Spring 通过三级缓存解决了循环依赖,其中一级缓存为单例池 singletonObjects,二级缓存为早期曝光对象 earlySingletonObjects,三级缓存为早期曝光对象工厂singletonFactories。

当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。

当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用 getBean(a) 来获取需要的依赖,此时的 getBean(a) 会从缓存中获取,第一步先获取到三级缓存中的工厂;第二步调用对象工工厂的 ​​​​​​​getObject 方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。

为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?

如果要使用二级缓存解决循环依赖,意味着所有 Bean 在实例化后就要完成 AOP 代理,这样违背了 Spring 设计的原则,Spring 在设计之初就是通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来在 Bean 生命周期的最后一步来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理。

Spring Bean

什么是 Spring Bean?

由 Spring IoC 容器管理的对象称为 Bean,Bean 根据 Spring 配置文件中的信息创建。

Spring 有两种 Bean:

  1. 普通 Bean:Spring 创建的 bean 的类型就是在配置文件中定义的 bean 类型。
  2. 工厂 Bean(FactoryBean):Spring 创建的 bean 的类型可以和配置文件中定义的 bean 类型不一样。

Spring Beans 作用域?

默认情况下,所有的 Spring Bean 都是单例的,也就是说在整个 Spring 应用中, Bean 的实例只有一个。

通过在<bean>元素中添加 scope 属性可以配置 Spring Bean 的作用范围。例如,如果每次获取 Bean 时,都需要一个新的 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。

Spring 5 共提供了 6 种 scope 作用域,如下:

|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 作用范围 | 描述 |
| singleton | 默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例 |
| prototype | 原型模式,表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个新的 Bean 实例。 |
| request | 每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。 |
| session | 同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。 |
| application | 同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。 与 singleton 类似,但 singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而一个 Web 应用中可能会存在多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。 |
| websocket | websocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。 |

注意:在以上 6 种 Bean 作用域中,除了 singleton 和 prototype 可以直接在常规的 Spring IoC 容器(例如 ClassPathXmlApplicationContext)中使用外,剩下的都只能在基于 Web 的 ApplicationContext 实现(例如 XmlWebApplicationContext)中才能使用,否则就会抛出一个 IllegalStateException 的异常。

Spring Bean 的生命周期?

在 Spring 中,对象不再通过 new 来创建,而是交给 Spring 来管理,所以 Spring Bean 的生命周期完全也由 Spring 容器控制。

Spring Bean 的生命周期主要指的是 singleton bean 作用域 Bean 的生命周期,在此作用域下,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁。

而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。每次客户端请求 prototype 作用域的 Bean 时,Spring 容器都会创建一个新的实例,并且不会管那些被配置成 prototype 作用域的 Bean 的生命周期。

在 Spring 中,Bean 的生命周期是一个很复杂的执行过程,我们可以利用 Spring 提供的方法定制 Bean 的创建过程。当一个 Bean 被加载到 Spring 容器时,它就具有了生命,而 Spring 容器在保证一个 Bean 能够使用之前,会进行很多工作。

Spring Bean 的生命周期分为如下阶段:

  1. 实例化
  2. 属性赋值
  3. 初始化
  4. 销毁

Spring 容器中 Bean 的生命周期流程如图所示:

Bean 生命周期的整个执行过程描述如下:

  1. 根据配置情况调用 Bean 构造方法或工厂方法实例化 Bean。
  2. 利用依赖注入完成 Bean 中所有属性值的配置注入。
  3. 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
  4. 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
  5. 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
  6. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
  7. 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
  8. 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
  9. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
  10. 如果在 <bean> 中指定了该 Bean 的作用范围为 scope="singleton",则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在 <bean> 中指定了该 Bean 的作用范围为 scope="prototype",则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
  11. 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

Spring 为 Bean 提供了细致全面的生命周期过程,通过实现特定的接口或 <bean> 的属性设置,都可以对 Bean 的生命周期过程产生影响。虽然可以随意配置 <bean> 的属性,但是建议不要过多地使用 Bean 实现接口,因为这样会导致代码和 Spring 的聚合过于紧密。

Spring 的后置处理器?

Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理。

BeanPostProcessor 接口定义回调方法,可以实现该方法来提供自己的实例化逻辑,依赖解析逻辑等。也可以在 Spring 容器通过插入一个或多个 BeanPostProcessor 的实现来完成实例化,配置和初始化一个bean 之后实现一些自定义逻辑回调方法。

你可以配置多个 BeanPostProcessor 接口,通过设置 BeanPostProcessor 实现的 Ordered 接口提供的 order 属性来控制这些 BeanPostProcessor 接口的执行顺序。

BeanPostProcessor 可以对 bean(或对象)实例进行操作,这意味着 Spring IoC 容器实例化一个 bean 实例,然后 BeanPostProcessor 接口进行它们的工作。

注意:

ApplicationContext 会自动检测由 BeanPostProcessor 接口的实现定义的 bean,注册这些 bean 为后置处理器,然后通过在容器中创建 bean,在适当的时候调用它。

在你自定义的 BeanPostProcessor 接口实现类中,要实现以下的两个抽象方法BeanPostProcessor.postProcessBeforeInitialization(Object, String)BeanPostProcessor.postProcessAfterInitialization(Object, String) 和,注意命名要准确,否则会出现:The type InitHelloWorld must implement the inherited abstract method BeanPostProcessor.postProcessBeforeInitialization(Object, String) 之类的错误。

java 复制代码
public class BeanPost implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("【后置处理器】--初始化方法执行之前");
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("【后置处理器】--初始化方法执行之后");
        return bean;
    }

}

Spring Bean 定义继承

bean 定义可以包含很多的配置信息,包括构造函数的参数,属性值,容器的具体信息例如初始化方法,静态工厂方法名,等等。子 bean 的定义继承父定义的配置数据,子定义可以根据需要重写一些值,或者添加其他值。

Spring Bean 定义的继承与 Java 类的继承无关,但是继承的概念是一样的。你可以定义一个父 bean 的定义作为模板和其他子 bean 就可以从父 bean 中继承所需的配置。

当你使用基于 XML 的配置元数据时,通过使用父属性,指定父 bean 作为该属性的值来表明子 bean 的定义。

XML 复制代码
​
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="Index of /schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="parentPojo" class="com.yyds.pojo.ParentPojo">
        <property name="msg1" value="parent-msg-111"/>
        <property name="msg2" value="parent-msg-222"/>
    </bean>
    <bean id="childPojo" class="com.yyds.pojo.ChildPojo" parent="parentPojo">
        <property name="msg1" value="child-msg-111"/>
        <property name="msg3" value="child-msg-333"/>
    </bean>
</beans>

Spring 单例 Bean 是线程安全的吗?

不,Spring 框架中的单例 bean 不是线程安全的。

Spring 的内部 bean?

当一个 bean 仅被用作另一个 bean 的属性时,它能被声明为一个内部 bean,为了定义 inner bean,在 Spring 的基于 XML 的 配置元数据中,可以在 <property/><constructor-arg/> 元素内使用 <bean/> 元素,内部 bean 通常是匿名的,它们的 Scope 一般是 prototype。

正常情况下肯定是在程序里面分别定义 <bean> 节点,而后利用 <ref> 进行关系的引用,但是如果有一些特殊需要也可以设置一些内部 bean 配置。

XML 复制代码
<bean id="emp" class="com.jcn.vo.Emp">
    <property name="empno" value="7902"/>
    <property name="ename" value="jianzhu"/>
    <property name="dept">
    <bean id="mydept" class="com.jcn.vo.Dept">
    <property name="deptno" value="20"/>
    <property name="dname" value="市场部"/>
</bean>

什么是 bean 自动装配?

在之前的属性注入中,是在 XML 配置中通过 ​​​​​​​<constructor-arg> 和 ​​​​​​​<property> 中的 ref 属性,手动维护 Bean 与 Bean 之间的依赖关系的。Spring 容器也可以在不使用 ​​​​​​​<constructor-arg><property> 元素的情况下自动装配 bean 之间的关系。

Spring 的自动装配功能可以让 Spring 容器依据某种规则(自动装配的规则,有五种),为指定的 Bean 从应用的上下文(AppplicationContext 容器)中查找它所依赖的 Bean,并自动建立 Bean 之间的依赖关系。

Spring 的自动装配功能能够有效地简化 Spring 应用的 XML 配置,因此在配置数量相当多时采用自动装配降低工作量。

Spring 框架式默认不支持自动装配的,要想使用自动装配,则需要对 Spring XML 配置文件中 <bean> 元素的 autowire 属性进行设置。

自动装配方式?

有五种自动装配的方式,可以用来指导 Spring 容器用自动装配方式来进行依赖注入

  1. no:默认的方式是不进行自动装配,通过显式设置 ref 属性来进行装配。
  2. byName:通过参数名自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byname,之后容器试图匹配、装配和该 bean 的属性具有相同名字的 bean。
  3. byType:通过参数类型自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byType,之后容器试图匹配、装配和该 bean 的属性具有相同类型的bean。如果有多个 bean 符合条件,则抛出错误。
  4. constructor:这个方式类似于 byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
  5. autodetect:首先尝试使用 constructor 来自动装配,如果无法工作,则使用 byType 方式。
  6. default:表示默认采用上一级元素<beans>设置的自动装配规则(default-autowire)进行装配。

不使用自动装配方式装配:

XML 复制代码
​<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="Index of /schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="com.yyds.pojo.Student">
        <property name="studentName" value="张三"/>
        <property name="grade" ref="grade"/>
    </bean>
    <bean id="grade" class="com.yyds.pojo.Grade">
        <property name="gradeName" value="一年级"/>
    </bean>
</beans>

autowire="byName" 方式装配

XML 复制代码
​<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="Index of /schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="com.yyds.pojo.Student" autowire="byName">
        <property name="studentName" value="张三"/>
    </bean>
    <bean id="grade" class="com.yyds.pojo.Grade">
        <property name="gradeName" value="一年级"/>
    </bean>
</beans>

autowire="byType" 方式装配

XML 复制代码
​<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="Index of /schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="com.yyds.pojo.Student" autowire="byType">
        <property name="studentName" value="张三"/>
    </bean>
    <bean id="grade" class="com.yyds.pojo.Grade">
        <property name="gradeName" value="一年级"/>
    </bean>
</beans>

根据 byType 装配时,不能同时装配多个,否则会报错,如下:

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="Index of /schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="com.yyds.pojo.Student" autowire="byType">
        <property name="studentName" value="张三"/>
    </bean>
    <bean id="grade" class="com.yyds.pojo.Grade">
        <property name="gradeName" value="一年级"/>
    </bean>
    <bean id="grade1" class="com.yyds.pojo.Grade">
        <property name="gradeName" value="二年级"/>
    </bean>
</beans>

autowire="constructor" 方式装配:

XML 复制代码
​<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="Index of /schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="com.yyds.pojo.Student" autowire="constructor">
        <constructor-arg name="studentName" value="张三"/>
    </bean>
    <bean id="grade" class="com.yyds.pojo.Grade">
        <constructor-arg name="gradeName" value="一年级"/>
    </bean>
</beans>

autowire="default" 方式装配,默认采用上一级标签<beans>设置的自动装配规则(default-autowire)进行装配

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="Index of /schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byType">

    <bean id="student" class="com.yyds.pojo.Student" autowire="default">
        <property name="studentName" value="张三"/>
    </bean>
    <bean id="grade" class="com.yyds.pojo.Grade">
        <property name="gradeName" value="一年级"/>
    </bean>
</beans>

自动装配的局限性?

自动装配的局限性是:

  1. 重写:你仍需用 <constructor-arg><property> 配置来定义依赖,意味着总要重写自动装配。
  2. 基本数据类型:你不能自动装配简单的属性,如基本数据类型,String 字符串,和类。
  3. 模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。

Bean 引入外部文件?

java 复制代码
<!--引入外部文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>

Bean 注入空值和特殊符号?

java 复制代码
​<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="Index of /schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="com.yyds.pojo.Student">
        <!--直接使用特殊字符,配置会报错-->
        <property name="name" value="<<张三>>"/>
        <!--使用转义方式:&lt;和&gt;来转移<和>-->
        <property name="name" value="&lt;&lt;张三&gt;&gt;"/>
        <!--使用CDATA方式-->
        <property name="name">
            <value><![CDATA[<<张三>>]]></value>
        </property>
    </bean>
</beans>

​

Spring AOP

什么是 AOP?

AOP(Aspect Oriented Programming),即面向切面编程,AOP 是对 OOP(Object Oriented Programming,面向对象编程)的补充和完善。

Spring AOP 是 Spring 框架的一个关键组件。面向切面的编程需要把程序逻辑分解成不同的部分称为所谓的关注点。跨一个应用程序的多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。

Spring AOP 是 Spring 框架的核心模块之一,它使用纯 Java 实现,因此不需要专门的编译过程和类加载器,可以在程序运行期通过代理方式向目标类织入增强代码。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP 主要用途:

  1. 记录日志
  2. 性能统计
  3. 安全控制
  4. 事务处理
  5. 异常处理

AOP 与 OOP?

AOP、OOP 在字面上虽然非常类似,但却是面向不同领域的两种设计思想,这两种设计思想在目标上有着本质的差异。

  1. OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
  2. AOP 则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
  3. OOP 允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理、事务管理等等,在 OOP 设计中,它导致了大量代码的重复,而不利于各个模块的重用。
  4. AOP 技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

AOP 术语?

  • 连接点 JointPoint:类里面哪些方法可以被增强,这些方法成为连接点。连接点是一个应用执行过程中能够插入一个切面的点,连接点代表了时间,也就是何时去执行。
  • 切入点 Pointcut:切点表示一组连接点 JointPoint,如果通知定义了"什么"和"何时"。那么切点就定义了"何处"。通常使用明确的类或者方法来指定这些切点,即实际被真正增强的方法。切入点用于定义通知被应用的位置(在哪些连接点)。
  • 通知 Advice:实际增强的逻辑部分。
    • 前置通知:org.springframework.aop.MethodBeforeAdvice,在方法执行之前,执行通知。
    • 后置通知:org.springframework.aop.AfterAdvice,在方法执行之后,不考虑其结果,执行通知。
    • 环绕通知:org.aopalliance.intercept.MethodInterceptor,在方法调用之前和之后,执行通知。
    • 异常通知:org.springframework.aop.ThrowsAdvice,在方法抛出异常后,执行通知。
    • 最终通知:org.springframework.aop.AfterReturningAdvice,在方法执行之后,只有在方法成功完成时,才能执行通知。
    • 引入通知:org.springframework.aop.IntroductionInterceptor,在目标类中添加一些新的方法和属性。
  • 切面 Aspect:把通知应用到切入点的过程。切面是通知和切点的集合,通知和切点共同定义了切面的全部功能。
  • 引入 Introduction:引入允许我们向现有的类中添加添加新方法或属性。
  • 织入 Weaving:织入是将切面应用到目标对象来创建的代理对象过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期中有多个点可以织入。

编译期------切面在目标类编译时期被织入,这种方式需要特殊编译器。AspectJ 的织入编译器就是以这种方式织入切面。

类加载期------切面在类加载到

JVM ,这种方式需要特殊的类加载器,他可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5 的 LTW 就支持这种织入方式

运行期------切面在应用运行期间的某个时刻被织入。一般情况下,在织入切面时候,AOP 容器会为目标对象动态的创建代理对象。Spring AOP 就是以这种方式织入切面。

切入点表达式?

  • 作用:用于指定对哪个类中的哪个方法进行增强
  • 语法:execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))

关注点和横切关注的区别?

关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。

横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。

常见 AOP 比较?

自动代理类型?

  1. BeanNameAutoProxyCreator
  2. DefaultAdvisorAutoProxyCreator
  3. Metadata autoproxying

静态代理和动态代理的区别?

代理是一种常用的设计模式,目的是:为其他对象提供一个代理以控制对某个对象的访问,将两个类的关系解耦。代理类和委托类都要实现相同的接口,因为代理真正调用的是委托类的方法。

区别:

  1. 静态代理:由程序员创建或是由特定工具生成,在代码编译时就确定了被代理的类是哪一个是静态代理。静态代理通常只代理一个类;
  2. 动态代理:在代码运行期间,运用反射机制动态创建生成。动态代理代理的是一个接口下的多个实现类;

实现步骤:

  1. 实现 InvocationHandler 接口创建自己的调用处理器;
  2. 给 Proxy 类提供ClassLoader 和代理接口类型数组创建动态代理类;
  3. 利用反射机制得到动态代理类的构造函数;
  4. 利用动态代理类的构造函数创建动态代理类对象;

使用场景:

  1. Retrofit 中直接调用接口的方法;
  2. Spring 的 AOP 机制

Spring 注解

怎样开启注解装配?

注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在 Spring 配置文件中配置以下内容:

XML 复制代码
<!--第一种:开启组件扫描-->
<context:component-scan base-package="com.yyds.service"/>

<!--第二种:开启组件扫描详细配置-->
<!--use-default-filters="false"表示不适应默认的filter,而是自己配置filter-->
<context:component-scan base-package="com.yyds" use-default-filters="false">
    <!--包含哪些扫描-->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <!--排除哪些扫描-->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

@Component注解

@Required注解

这个注解表明 bean 的属性必须在配置的时候设置,通过一个 bean 定义的显式的属性值或通过自动装配,若 @Required注解的 bean 属性未被设置,容器将抛出 BeanInitializationException。

@Autowired注解

@Autowired注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和@Required一样,修饰 setter 方法、构造器、属性或者具有任意名称和/或多个参数的 PN 方法。

@Qualifier注解

当有多个相同类型的 bean 却只有一个需要自动装配时,将@Qualifier注解和@Autowire注解结合使用以消除这种混淆,指定需要装配的确切的 bean。

@Controller注解

该注解表明该类扮演控制器的角色,Spring 不需要你继承任何其他控制器基类或引用 Servlet API。

@Service注解

@Repository注解

@Value注解

@RequestMapping注解

该注解是用来映射一个 URL 到一个类或一个特定的方处理法上。

Spring JDBC

JdbcTemplate?

JdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。

JdbcTemplate 是 Spring JDBC 核心包(core)中的核心类,它可以通过配置文件、注解、Java 配置类等形式获取数据库的相关信息,实现了对 JDBC 开发过程中的驱动加载、连接的开启和关闭、SQL 语句的创建与执行、异常处理、事务处理、数据类型转换等操作的封装。我们只要对其传入SQL 语句和必要的参数即可轻松进行 JDBC 编程。

Spring 对 DAO 的支持?

Spring 对数据访问对象(DAO)的支持旨在简化它和数据访问技术如 JDBC,Hibernate or JDO 结合使用。这使我们可以方便切换持久层。编码时也不用担心会捕获每种技术特有的异常。

Spring 访问 Hibernate?

在 Spring 中有两种方式访问 Hibernate:

  1. 控制反转 Hibernate Template 和 Callback
  2. 继承 HibernateDAOSupport 提供一个 AOP 拦截器

Spring 支持的 ORM?

Spring 支持以下 ORM:

  1. Hibernate
  2. iBatis
  3. JPA (Java Persistence API)
  4. TopLink
  5. JDO (Java Data Objects)
  6. OJB

Spring 事务

Spring 事务管理类型?

Spring 支持以下 2 种事务管理方式:

  1. 编程式事务管理:编程式事务管理是通过编写代码实现的事务管理。这种方式能够在代码中精确地定义事务的边界,我们可以根据需求规定事务从哪里开始,到哪里结束。
  2. 声明式事务管理:Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。

选择编程式事务还是声明式事务,很大程度上就是在控制权细粒度和易用性之间进行权衡。

  1. 编程式对事物控制的细粒度更高,我们能够精确的控制事务的边界,事务的开始和结束完全取决于我们的需求,但这种方式存在一个致命的缺点,那就是事务规则与业务代码耦合度高,难以维护,因此我们很少使用这种方式对事务进行管理。
  2. 声明式事务易用性更高,对业务代码没有侵入性,耦合度低,易于维护,因此这种方式也是我们最常用的事务管理方式。

Spring 事务管理的优点?

  1. 它为不同的事务 API 如 JTA,JDBC,Hibernate,JPA 和 JDO,提供一个不变的编程模式。
  2. 它为编程式事务管理提供了一套简单的 API 而不是一些复杂的事务 API 如
  3. 它支持声明式事务管理。
  4. 它和 Spring 各种数据访问抽象层很好得集成。

事务特性?

事务具有 4 个特性:原子性、一致性、隔离性和持久性,简称为 ACID 特性。

  1. 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的动作要么都做要么都不做。
  2. 一致性(Consistency):事务必须保证数据库从一个一致性状态变到另一个一致性状态,一致性和原子性是密切相关的。
  3. 隔离性(Isolation):一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相打扰。
  4. 持久性(Durability):持久性也称为永久性,指一个事务一旦提交,它对数据库中数据的改变就是永久性的,后面的其它操作和故障都不应该对其有任何影响。

Spring 事务抽象?

Spring 事务抽象的关键是由 org.springframework.transaction.PlatformTransactionManager 接口定义,如下所示:

java 复制代码
public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;

}
  • TransactionStatus getTransaction(TransactionDefinition definition):根据指定的传播行为,该方法返回当前活动事务或创建一个新的事务。
  • void commit(TransactionStatus status):该方法提交给定的事务和关于它的状态。
  • void rollback(TransactionStatus status):该方法执行一个给定事务的回滚。

TransactionDefinition 是在 Spring 中事务支持的核心接口,它的定义如下:

java 复制代码
public interface TransactionDefinition {

    int getPropagationBehavior();
    int getIsolationLevel();
    String getName();
    int getTimeout();
    boolean isReadOnly();

}
  • int getPropagationBehavior():该方法返回传播行为。Spring 提供了与 EJB CMT 类似的所有的事务传播选项。
  • int getIsolationLevel():隔离级别,该方法返回该事务独立于其他事务的工作的程度。
  • String getName():该方法返回该事务的名称。
  • int getTimeout():该方法返回以秒为单位的时间间隔,事务必须在该时间间隔内完成。
  • boolean isReadOnly():该方法返回该事务是否是只读的。

TransactionStatus 接口为事务代码提供了一个简单的方法来控制事务的执行和查询事务状态。

java 复制代码
public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();   
    boolean isCompleted();

}
  • boolean hasSavepoint():该方法返回该事务内部是否有一个保存点,也就是说,基于一个保存点已经创建了嵌套事务。
  • boolean isCompleted():该方法返回该事务是否完成,也就是说,它是否已经提交或回滚。
  • boolean isNewTransaction():在当前事务时新的情况下,该方法返回 true。
  • boolean isRollbackOnly():该方法返回该事务是否已标记为 rollback-only。
  • void setRollbackOnly():该方法设置该事务为 rollback-only 标记。

脏读、不可重复读、幻读?

(1)脏读

所谓的脏读,其实就是读到了别的事务回滚前的脏数据。比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。

(2)不可重复读

不可重复读字面含义已经很明了了,比如事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。

(4)幻读

两次读取数据得到的数据条数不一致。事务A首先根据条件索引得到10条数据,然后事务B改变了数据库一条数据,导致也符合事务A当时的搜索条件,这样事务A再次搜索发现有11条数据了,就产生了幻读。

以下是4中事务隔离级别对应的问题:

|------------------|--------|-----------|--------|
| | 脏读 | 不可重复读 | 幻读 |
| Serializable | 不会 | 不会 | 不会 |
| REPEATABLE READ | 不会 | 不会 | 会 |
| READ COMMITTED | 不会 | 会 | 会 |
| Read Uncommitted | 会 | 会 | 会 |

Spring 的事务隔离级别?

事务的隔离级别:

  1. TransactionDefinition.ISOLATION_DEFAULT:这是默认的隔离级别。
  2. TransactionDefinition.ISOLATION_READ_UNCOMMITTED:可能产生脏读、不可重复读和虚读。
  3. TransactionDefinition.ISOLATION_READ_COMMITTED:可以防止脏读;但可能发生不可重复读和虚读。
  4. TransactionDefinition.ISOLATION_REPEATABLE_READ:可以防止误读和不可重复读;但可能发生虚读。
  5. TransactionDefinition.ISOLATION_SERIALIZABLE:可以防止误读、不可重复读和虚读。

Spring 事务传播行为?

传播类型:

(1)TransactionDefinition.PROPAGATION_MANDATORY

支持当前事务;如果不存在当前事务,则抛出一个异常。该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。

(2)TransactionDefinition.PROPAGATION_NESTED

如果存在当前事务,则在一个嵌套的事务中执行。

嵌套级别事务,该传播级别特征是,如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。重点就在于那个save point。看几个问题就明了了:

如果子事务回滚,会发生什么?

父事务会回滚到进入子事务前建立的save point,然后尝试其他的事务或者其他的业务逻辑,父事务之前的操作不会受到影响,更不会自动回滚。

如果父事务回滚,会发生什么?

父事务回滚,子事务也会跟着回滚!为什么呢,因为父事务结束之前,子事务是不会提交的,我们说子事务是父事务的一部分,正是这个道理。那么:

事务的提交,是什么情况?

是父事务先提交,然后子事务提交,还是子事务先提交,父事务再提交?答案是第二种情况,还是那句话,子事务是父事务的一部分,由父事务统一提交。

(3)TransactionDefinition.PROPAGATION_NEVER

不支持当前事务;如果存在当前事务,则抛出一个异常。该事务传播级别要求上下文中不能存在事务,一旦有事务,就抛出 runtime 异常,强制停止执行。

(4)TransactionDefinition.PROPAGATION_NOT_SUPPORTED

不支持当前事务;而总是执行非事务性。当前级别的特点就是上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。这个级别的好处是可以帮助你将事务极可能的缩小。

我们知道一个事务越大,它存在的风险也就越多。所以在处理事务的过程中,要保证尽可能的缩小范围。

假设一段代码是每次逻辑操作都必须调用的,比如循环1000次的某个非核心业务逻辑操作。这样的代码如果包在事务中,势必造成事务太大,导致出现一些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上用场了。用当前级别的事务模板包起来就可以了。

(5)TransactionDefinition.PROPAGATION_REQUIRED

支持当前事务;如果不存在事务,则创建一个新的事务。默认的 Spring 事务传播级别,使用该级别的特点是,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。所以这个级别通常能满足处理大多数的业务场景。

(6)TransactionDefinition.PROPAGATION_REQUIRES_NEW

创建一个新事务,如果存在一个事务,则把当前事务挂起。每次都要一个新事务,该传播级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。

这是一个很有用的传播级别,举一个应用场景:现在有一个发送100个红包的操作,在发送之前,要做一些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送日志,发送日志要求100%的准确,如果日志不准确,那么整个父事务逻辑需要回滚。

怎么处理整个业务需求呢?就是通过这个 PROPAGATION_REQUIRES_NEW 级别的事务传播控制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。

(7)TransactionDefinition.PROPAGATION_SUPPORTS

支持当前事务;如果不存在,则执行非事务性。该传播级别的特点是,如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。所以说并非所有的包在 transactionTemplate.execute 中的代码都会有事务支持。这个通常是用来处理那些并非原子性的非核心业务逻辑操作,应用场景较少。

(8)TransactionDefinition.TIMEOUT_DEFAULT

使用默认超时的底层事务系统,或者如果不支持超时则没有。

Spring 事务的注解配置?

  1. 把一个 DataSource(如DruidDataSource)作为一个 @Bean 注册到Spring容器中,配置好事务性资源。
  2. 把一个 @EnableTransactionManagement 注解放到一个 @Configuration 类上,配置好事务管理器,并启用事务管理。
  3. 把一个 @Transactional 注解放到类上或方法上,可以设置注解的属性,表明该方法按配置好的属性参与到事务中。

@Transactional 失效场景?

(1)@Transactional 应用在非 public 修饰的方法上

如果 Transactional 注解应用在非 public 修饰的方法上,Transactional 将会失效。

之所以会失效是因为在Spring AOP 代理时,TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的

computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。

java 复制代码
protected TransactionAttribute computeTransactionAttribute(Method method, Class targetClass) {

    // Don't allow no-public methods as required.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
    }

}

此方法会检查目标方法的修饰符是否为 public,不是 public 则不会获取@Transactional的属性配置信息。

注意:protected、private 修饰的方法上使用@Transactional注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。

(2)@Transactional注解属性 propagation 设置错误

这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。

  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

(3)@Transactional注解属性 rollbackFor 设置错误

rollbackFor 可以指定能够触发事务回滚的异常类型。Spring 默认抛出了未检查 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务;其他异常不会触发回滚事务。

如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定rollbackFor属性。

java 复制代码
// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED, rollbackFor= MyException.class)

若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。Spring源码如下:

java 复制代码
private int getDepth(Class exceptionClass, int depth) {
    
    if (exceptionClass.getName().contains(this.exceptionName)) {
        // Found it!
        return depth;
    }

    // If we've gone as far as we can go and haven't found it...
    if (exceptionClass == Throwable.class) {
        return -1;
    }
    return getDepth(exceptionClass.getSuperclass(), depth + 1);

}

(4)同一个类中方法调用,导致@Transactional失效

开发中避免不了会对同一个类里面的方法调用,比如有一个类 Test,它的一个方法A,A再调用本类的方法B(不论方法B是用 public 还是 private 修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。

那为啥会出现这种情况?其实这还是由于使用 Spring AOP 代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由 Spring 生成的代理对象来管理。

java 复制代码
//@Transactional

@GetMapping("/test")
private Integer A() throws Exception {
    CityInfoDict cityInfoDict = new CityInfoDict();
    cityInfoDict.setCityName("2");

    /**
     * B 插入字段为 3的数据
     */
    this.insertB();

    /**
     * A 插入字段为 2的数据
     */
    int insert = cityInfoDictMapper.insert(cityInfoDict);
    return insert;
}


@Transactional()
public Integer insertB() throws Exception {

    CityInfoDict cityInfoDict = new CityInfoDict();
    cityInfoDict.setCityName("3");
    cityInfoDict.setParentCityId(3);

    return cityInfoDictMapper.insert(cityInfoDict);
}

(5)异常 catch 捕获导致@Transactional失效

这种情况是最常见的一种@Transactional注解失效场景,

java 复制代码
@Transactional
public Integer A() throws Exception {

    int insert = 0;
    try {
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("2");
        cityInfoDict.setParentCityId(2);

        /**
         * A 插入字段为 2的数据
         */
        insert = cityInfoDictMapper.insert(cityInfoDict);

        /**
         * B 插入字段为 3的数据
         */
        b.insertB();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

如果B方法内部抛了异常,而A方法此时 try catch 了B方法的异常,那这个事务也不能正常回滚,而是会抛出异常:

java 复制代码
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

因为当ServiceB 中抛出了一个异常以后,ServiceB 标识当前事务需要 rollback。但是 ServiceA 中由于你手动的捕获这个异常并进行处理,Service A认为当前事务应该正常 commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的 UnexpectedRollbackException 异常。

spring 的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行 commit or rollback,事务是否执行取决于是否抛出 runtime 异常。如果抛出 runtime exception 并在你的业务方法中没有 catch 到的话,事务会回滚。

在业务方法中一般不需要 catch 异常,如果非要 catch 一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据 commit 造成数据不一致,所以有些时候 try catch 反倒会画蛇添足。

(6)数据库引擎不支持事务

这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的 MySQL 数据库默认使用支持事务的 innodb 引擎,一旦数据库引擎切换成不支持事务的 myisam,那事务就从根本上失效了。

Spring 的 MVC

什么是 Spring 的 MVC 框架?

Spring 配备构建 Web 应用的全功能 MVC 框架。Spring 可以很便捷地和其他 MVC框架集成,如 Struts,Spring 的 MVC 框架用控制反转把业务对象和控制逻辑清晰地隔离。它也允许以声明的方式把请求参数和业务对象绑定。

DispatcherServlet

Spring 的 MVC 框架是围绕 DispatcherServlet 来设计的,它用来处理所有的 HTTP请求和响应。

WebApplicationContext

WebApplicationContext 继承了 ApplicationContext 并增加了一些 WEB 应用必备的特有功能,它不同于一般的 ApplicationContext ,因为它能处理主题,并找到被关联的 servlet。

Spring MVC 框架的控制器?

控制器提供一个访问应用程序的行为,此行为通常通过服务接口实现。控制器解析用户输入并将其转换为一个由视图呈现给用户的模型。Spring 用一个非常抽象的方式实现了一个控制层,允许用户创建多种用途的控制器。

Spring 综合

Spring 中的事件类型?

Spring 的 ApplicationContext 提供了支持事件和代码中监听器的功能。

我们可以创建 bean 用来监听在 ApplicationContext 中发布的事件。ApplicationEvent 类和在 ApplicationContext 接口中处理的事件,如果一个 bean 实现了 ApplicationListener 接口,当一个 ApplicationEvent 被发布以后,bean 会自动被通知。

java 复制代码
public class AllApplicationEventListener implements ApplicationListener<ApplicationEvent>{

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        //process event
    }

}

Spring 提供了以下 5 中标准的事件:

  1. 上下文更新事件(ContextRefreshedEvent):该事件会在 ApplicationContext 被初始化或者更新时发布。也可以在调用 ConfigurableApplicationContext 接口中的 refresh() 方法时被触发。
  2. 上下文开始事件(ContextStartedEvent):当容器调用 ConfigurableApplicationContext 的Start() 方法开始/重新开始容器时触发该事件。
  3. 上下文停止事件(ContextStoppedEvent):当容器调用 ConfigurableApplicationContext 的Stop() 方法停止容器时触发该事件。
  4. 上下文关闭事件(ContextClosedEvent):当 ApplicationContext 被关闭时触发该事件。容器被关闭时,其管理的所有单例 Bean 都被销毁。
  5. 请求处理事件(RequestHandledEvent):在 Web 应用中,当一个 http 请求(request)结束触发该事件。

除了上面介绍的事件以外,还可以通过扩展 ApplicationEvent 类来开发自定义的事件。

java 复制代码
public class CustomApplicationEvent extends ApplicationEvent {
    public CustomApplicationEvent (Object source, final String msg){
        super(source);
        System.out.println("Created a Custom event");
    }
}

为了监听这个事件,还需要创建一个监听器:

java 复制代码
public class CustomEventListener implements ApplicationListener<CustomApplicationEvent>{
    @Override
    public void onApplicationEvent(CustomApplicationEvent applicationEvent) {
        //handle event
    }
}

之后通过 applicationContext 接口的 publishEvent() 方法来发布自定义事件。

java 复制代码
CustomApplicationEvent customEvent = new CustomApplicationEvent(applicationContext, "Test message");
applicationContext.publishEvent(customEvent);
相关推荐
Y编程小白4 分钟前
SpringBoot的创建方式
java·spring boot·后端
基哥的奋斗历程6 分钟前
初识Go语言
开发语言·后端·golang
总是学不会.12 分钟前
【集合】Java 8 - Stream API 17种常用操作与案例详解
java·windows·spring boot·mysql·intellij-idea·java集合
潜意识起点21 分钟前
【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析
java·spring boot·后端
mxbb.23 分钟前
单点Redis所面临的问题及解决方法
java·数据库·redis·缓存
自在的LEE1 小时前
当 Go 遇上 Windows:15.625ms 的时间更新困局
后端·kubernetes·go
云和数据.ChenGuang1 小时前
《XML》教案 第1章 学习XML基础
xml·java·学习
王·小白攻城狮·不是那么帅的哥·天文1 小时前
Java操作Xml
xml·java
发飙的蜗牛'1 小时前
23种设计模式
android·java·设计模式
music0ant1 小时前
Idean 处理一个项目引用另外一个项目jar 但jar版本低的问题
java·pycharm·jar