概述

Spring Framework 是一个功能强大的 Java 应用程序框架,旨在提供高效且可扩展的开发环境。它结合了轻量级的容器和依赖注入功能,提供了一种使用 POJO 进行容器配置和面向切面的编程的简单方法,以及一组用于 AOP 的模块。Spring 框架还支持各种移动应用开发技术,如 Android 和 iOS。此外,它还提供了对事务管理、对象/关系映射、JavaBeans、JDBC、JMS 和其他技术的支持,从而确保高效开发。


Spring 框架被划分为多个模块。应用程序可以选择他们需要的模块。core 是核心容器的模块,包括一个配置模型和一个依赖注入机制。除此之外,Spring 框架还为不同的应用架构提供了基础支持,包括消息传递、事务性数据和持久性以及 Web。它还包括基于 Servlet 的 Spring MVC Web 框架,以及并行的 Spring WebFlux 响应式 web 框架


从 Spring 框架 6.0 开始,Spring 已经升级到 Jakarta EE 9 级别(例如 Servlet 5.0+,JPA 3.0+),基于 jakarta 命名空间而不是传统的 javax 包。

核心

1.loC 容器

1.1Spring loC 容器和 Bean

IoC 被称为依赖注入(DI)。对象通过构造参数、工厂方法来定义与之合作的其他对象的依赖关系。这个过程从根本上说是 Bean 本身通过使用直接构建类或诸如服务定位模式的机制来控制其依赖关系的实例化或位置的逆过程

BeanFactory:提供了配置框架和基本功能
ApplicationContext:增加了更多的企业特定功能(例如 AOP 集成)

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


1.2 ApplicationContext 接口

代表 Spring IoC 容器,负责实例化、配置和组装 bean。容器通过读取配置元数据来获得关于实例化、配置和组装哪些对象的指示。配置元数据以 XML、Java 注解或 Java 代码表示。它可以让你表达构成你的应用程序的对象以及这些对象之间丰富的相互依赖关系。

1.2.1 配置元素据

配置元数据代表了你,作为一个应用开发者,如何告诉 Spring 容器在你的应用中实例化、配置和组装对象。配置元数据传统上是以简单直观的 XML 格式提供的
元素据的配置还有其他方法:

  • 基于注解的配置:配置元素据定义 Bean
  • Java-based configuration:定义应用类外部的 Bean(@configuration @Bean @Import @DependsOn)

这些 Bean 的定义对应于构成你的应用程序的实际对象,你会定义服务层对象、持久层对象(如存储库或数据访问对象(DAO))、表现对象(如 Web 控制器)、基础设施对象(如 JPA EntityManagerFactory)、JMS 队列等等。

1.2.2 实例化一个容器

它让容器从各种外部资源加载配置元数据。

1
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- services -->

<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>

<!-- more bean definitions for services go here -->

</beans>

1.3 Bean 概述

一个 Spring IoC 容器管理着一个或多个 Bean。这些 Bean 是用你提供给容器的配置元数据创建的
它是由 Spring IoC 容器负责创建、组装、管理生命周期的对象。
它包含(除其他信息外)以下元数据。

核心思想:把对象的创建权、依赖管理权从开发者手中 “反转” 给容器,从而简化开发、降低耦合。

  • 一个全路径类名:通常,被定义的 Bean 的实际实现类。

  • Bean 的行为配置元素,它说明了 Bean 在容器中的行为方式(scope、生命周期回调,等等)。

  • 对其他 Bean 的引用,这些 Bean 需要做它的工作。这些引用也被称为合作者或依赖。

  • 要在新创建的对象中设置的其他配置设置—​ 例如,pool 的大小限制或在管理连接池的 Bean 中使用的连接数。


一个 Bean 通常只有一个标识符。然而,如果它需要一个以上的标识符,多余的标识符可以被视为别名。在基于 XML 的配置元数据中,你可以使用 id 属性、name 属性或两者来指定 Bean 标识符。


1.4 依赖

应用程序并不是由单一的对象组成的。即使是最简单的应用也有一些对象,它们一起工作,呈现出最终用户所看到的连贯的应用。

1.4.1 依赖注入

  1. 基于构造器的依赖注入
  2. 基于 setter()的依赖注入
  3. 使用 XML Bean 定义、注解组件(@Component、@Controller)或基于 Java 的 @Configuration 类中的 @Bean 方法

1.5 基于注解的容器配置

基于注解的配置提供了 XML 设置的替代方案,它依靠字节码元数据来注入组件而不是 XML 声明开发者通过在相关的类、方法或字段声明上使用注解,将配置移入组件类本身,而不是使用 XML 来描述 bean 的装配。
在配置 Spring 时,注解是否比 XML 更好?
基于注解的配置的引入提出了这样一个问题:这种方法是否比 XML “更好”。通常是由开发者来决定哪种策略更适合他们。由于它们的定义方式,注解在其声明中提供了大量的上下文,导致了更短、更简洁的配置。然而,XML 擅长于在不触及源代码或重新编译的情况下对组件进行注入。一些开发者更喜欢在源码附近注入,而另一些人则认为带注解的类不再是 POJO,此外,配置变得分散,更难控制。

1.6 使用@Autowired

任何给定的 Bean 类中只有一个构造函数可以声明 @Autowired,并将 required 属性设置为 true,表示该构造函数在用作 Spring Bean 时要自动注入。因此,如果 required 属性的默认值为 true,则只有一个构造函数可以使用 @Autowired 注解。如果有多个构造函数声明该注解,它们都必须声明 required=false,才能被视为自动注入的候选者

使用@Bean定义一个对象,然后使用@Autowired来引入

示例:

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

@Bean
public StringStore stringStore() {
return new StringStore();
}

@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}


1
2
3
4
5
6
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean


使用@Resource 注入:Spring 还支持通过在字段或 Bean 属性设置方法上使用@Resource 进行注入。
@Resource 需要一个 name 属性。默认情况下,Spring 将该值解释为要注入的 Bean 名称。

1
2
3
4
5
6
7
8
9
10
11
public class SimpleMovieLister {

private MovieFinder movieFinder;

@Resource(name="myMovieFinder") (1)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}



使用@Value 注入:常用于注入外部化 properties

示例:

1
2
3
4
5
6
7
8
9
10
11
// 用来注入配置文件的参数
@Component
public class MovieRecommender {

private final String catalog;

public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}

2.资源(Resources)

用于抽象访问低级资源,是对资源访问的统一抽象

作用:不同类型的资源(如本地文件、类路径资源、URL 资源等)有不同的访问方式(比如本地文件用 java.io.File,类路径资源用类加载器,网络资源用 java.net.URL),而 Resource 接口通过统一的 API 将这些差异封装起来,让开发者无需关心资源的具体类型,只需调用统一方法即可操作。


Spring 的 IoC 容器(如 ApplicationContext)内部大量使用 Resource 接口来加载配置文件(如 XML 配置、注解配置类)、读取资源文件(如 application.properties)等。
IoC 容器负责实例化,配置和组装对象。 IoC 容器从 XML 文件获取信息并相应地工作。 IoC 容器执行的主要任务是:

实例化应用程序类
配置对象
组装对象之间的依赖关系

主要方法

Resource 接口定义了一系列用于资源操作的核心方法,例如:

InputStream getInputStream():获取资源的输入流(最核心的方法,用于读取资源内容)。
boolean exists():判断资源是否存在。
boolean isReadable():判断资源是否可读。
URL getURL():如果资源可转为 URL,返回其 URL。
File getFile():如果资源是文件系统中的资源,返回其对应的 File 对象。
String getFilename():获取资源的文件名(如”application.properties”)。
String getDescription():获取资源的描述信息(常用于错误提示,如资源路径)。

3.验证、数据绑定和类型转换

3.1 Validator 接口(作为验证器)

用它来验证对象。Validator 接口通过使用一个 Errors 对象来工作,这样,在验证时,验证器可以向 Errors 对象报告验证失败。

1
2
3
4
5
6
7
public class Person {

private String name;
private int age;

// the usual getters and setters...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class PersonValidator implements Validator {

public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}

public void validate(Object obj, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}

4.面向切面编程(AOP)

面向切面的编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象的编程(OOP)。OOP 中模块化的关键单位是类,而 AOP 中模块化的单位是切面。切面使跨越多种类型和对象的关注点(如事务管理)模块化。

Spring 通过使用 基于 schema 的方法 或 @AspectJ 注解风格,提供了简单而强大的编写自定义切面的方法。这两种风格都提供了完全类型化的 advice,并使用 AspectJ 的 pointcut 语言,同时仍然使用 Spring AOP 进行织入。


作用:提供声明式的企业服务。最重要的此类服务是 声明式事务管理。让用户实现自定义切面,用 AOP 补充他们对 OOP 的使用。

4.1 AOP 概述

  • Aspect(切面):一个跨越多个类的关注点的模块化。事务管理是企业级 Java 应用中横切关注点的一个很好的例子。在 Spring AOP 中,切面是通过使用常规类(基于 schema 的方法)或使用 @Aspect 注解的常规类(@AspectJ 风格)实现的。
  • Join point:程序执行过程中的一个点,例如一个方法的执行或一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法的执行
  • Advice:一个切面在一个特定的连接点采取的行动。不同类型的 advice 包括 “around”、”before” 和 “after” 的 advice,许多 AOP 框架,包括 Spring,都将 advice 建模为一个拦截器,并在连接点(Join point)周围维护一个拦截器链。
  • Pointcut: 一个匹配连接点的谓词
  • Introduction: 代表一个类型声明额外的方法或字段。Spring AOP 允许你为任何 advice 的对象引入新的接口(以及相应的实现)。例如,你可以使用引入来使一个 bean 实现 IsModified 接口,以简化缓存。
  • Target object: 被一个或多个切面所 advice 的对象。由于 Spring AOP 是通过使用运行时代理来实现的,这个对象总是一个被代理的对象。
  • AOP proxy: 一个由 AOP 框架创建的对象,以实现切面契约(advice 方法执行等)。在 Spring 框架中,AOP 代理是一个 JDK 动态代理或 CGLIB 代理。
  • Weaving(织入): 将 aspect 与其他应用程序类型或对象连接起来,以创建一个 advice 对象。这可以在编译时(例如,使用 AspectJ 编译器)、加载时或运行时完成。Spring AOP 和其他纯 Java AOP 框架一样,在运行时进行织入。

Spring AOP 包括以下类型的 advice。

Before advice: 在连接点之前运行的 Advice ,但它不具备以下能力 阻止执行流进行到 join point 的能力(除非它抛出一个异常)。

After returning advice: 在一个连接点正常完成后运行的 Advice (例如,如果一个方法返回时没有抛出一个异常)。

After (finally) advice: 无论连接点以何种方式退出(正常或特殊返回),都要运行该 advice。

Around advice: 围绕一个连接点的 advice,如方法调用。这是最强大的一种 advice。Around advice 可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续进行连接点或通过返回自己的返回值或抛出一个异常来缩短 advice 方法的执行。

4.2 Spring AOP 的能力和目标

目的是在 AOP 实现和 Spring IoC 之间提供一个紧密的整合,以帮助解决企业应用中的常见问题。
Spring 将 Spring AOP 和 IoC 与 AspectJ 无缝集成,以便在基于 Spring 的应用架构中实现 AOP 的所有用途。

4.3 AOP 代理

Spring AOP 默认使用标准的 JDK 动态代理进行 AOP 代理。这使得任何接口(或一组接口)都可以被代理。

4.4 @AspectJ

@AspectJ 指的是将 aspect 作为带有注解的普通 Java 类来声明的一种风格。
使用前需启用此功能,如何启用呢?我们在使用时查阅文档就可以,不需要记忆,而且一般企业都已经配置好了。

启用 @AspectJ 支持后,任何在你的 application context 中定义的 bean,其类是 @AspectJ 切面(有 @Aspect 注解),会被 Spring 自动检测到,并用于配置 Spring AOP

4.4.1 声明一个切点 Pointcut

1
2
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature

4.4.2 声明 Advice

  • Before Advice

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;

    @Aspect
    public class BeforeExample {

    @Before("execution(* com.xyz.dao.*.*(..))")
    public void doAccessCheck() {
    // ...
    }
    }


  • After Returning Advice:@AfterReturning

  • After Throwing Advice:@AfterThrowing
  • After (Finally) Advice:@After

5.Data Buffer 和 编解码器

Java NIO 提供了 ByteBuffer,但许多库在上面建立了自己的字节缓冲区 API,特别是对于重复使用缓冲区和/或使用直接缓冲区对性能有利的网络操作。
spring-core 模块提供了一组抽象,以便与各种字节缓冲区 API 协同工作,如下所示。

  1. DataBufferFactory 对数据缓冲区的创建进行了抽象。

  2. DataBuffer 代表一个字节缓冲区,它可以被 池化 (pooled)。

  3. DataBufferUtils 为数据缓冲区提供实用方法。

  4. 编解码器(Codecs) 将数据缓冲流解码或编码为更高层次的对象

5.1 DataBufferFactory

DataBufferFactory 用于以两种方式之一创建数据缓冲区。

  • 分配一个新的数据缓冲区(data buffer),如果知道的话,可以选择预先指定容量(capacity),这更有效率,即使 DataBuffer 的实现可以按需增长和收缩。
  • 包裹一个现有的 byte[] 或 java.nio.ByteBuffer,它用 DataBuffer 的实现来装饰给定的数据,并且不涉及分配(allocation)

5.2 DataBuffer

Read 和 write 具有独立的 position,即不需要调用 flip() 来交替读取和写入。

与 java.lang.StringBuilder 一样按需扩展容量。

通过 PooledDataBuffer 的池化缓冲区(Pooled buffers)和引用计数。

把缓冲区(buffer)看成 java.nio.ByteBuffer、InputStream 或 OutputStream。

确定一个给定字节的索引(index),或最后的索引。

5.3 PooledDataBuffer

PooledDataBuffer 是 DataBuffer 的一个扩展,它有助于引用计数,这对字节缓冲区的池化至关重要。

当 PooledDataBuffer 被分配时,引用计数为 1。对 retain() 的调用会增加计数,而对 release() 的调用会减少计数。只要计数在 0 以上,缓冲区就保证不会被释放。当计数减少到 0 时,池中的缓冲区可以被释放,这在实践中可能意味着为缓冲区保留的内存被返回到内存池中。

5.4 DataBufferUtils

提供了许多实用的方法来对数据缓冲区(buffer)进行操作。

  1. read:read(InputStream inputStream, DataBufferFactory factory, int bufferSize)
    从输入流 InputStream 读取数据,通过 DataBufferFactory 创建 DataBuffer,返回 Flux(响应式数据流)。
1
2
3
InputStream is = new FileInputStream("file.txt");
DataBufferFactory factory = new DefaultDataBufferFactory();
Flux<DataBuffer> dataBuffers = DataBufferUtils.read(is, factory, 4096);

read(Path path, DataBufferFactory factory, int bufferSize)
从文件路径 Path 读取数据,返回 Flux,内部自动处理文件通道。

  1. write(Publisher source, OutputStream outputStream)
    将 Flux 或 Mono 中的数据写入 OutputStream,返回 Mono(表示写入完成的信号)。
1
2
3
4
Flux<DataBuffer> dataBuffers = ...; // 数据源
OutputStream os = new FileOutputStream("output.txt");
Mono<Void> writeResult = DataBufferUtils.write(dataBuffers, os);
writeResult.block(); // 阻塞等待写入完成(非响应式场景)
  1. join(Publisher source)
    将 Flux 中的所有缓冲区聚合为一个 Mono
1
2
Flux<DataBuffer> dataBuffers = ...;
Mono<DataBuffer> joinedBuffer = DataBufferUtils.join(dataBuffers);

toByteArray(Publisher source)
将 Flux 转换为字节数组,返回 Mono

1
2
Mono<byte[]> bytesMono = DataBufferUtils.toByteArray(dataBuffers);
bytesMono.subscribe(bytes -> System.out.println(new String(bytes)));

release(DataBuffer dataBuffer)
释放单个 DataBuffer 占用的资源(如内存),避免内存泄漏。
通常与 try-finally 结合使用,或通过 Flux.doOnTerminate() 自动释放。

5.4 编解码器

Encoder,将 Publisher 编码为数据缓冲流。

Decoder,将 Publisher 解码为更高层次的对象流。

spring-core 模块提供 byte[]、ByteBuffer、DataBuffer、Resource 和 String 编码器和解码器的实现。spring-web 模块增加了 Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers 和其他编码和解码器。