引言
如果你是一名Java后端开发者,Spring Framework对你来说绝不陌生。但你可能不知道,这个统治了Java企业级开发近二十年的框架,从2004年诞生至今,已经走过了七个大版本的演进历程。
每个版本都承载着那个时代开发者的痛点与期盼,也带来了革命性的思想和能力。今天,我们不聊空洞的版本号,而是顺着时间线,看看Spring Framework是如何一步步从简化J2EE开发的"破局者",演变成如今云原生时代的"技术基石"。
在这个过程中,我会把每个版本最核心的知识点、最实用的技巧、最完整的配置示例,以及升级时容易踩的坑,都一一梳理出来。
第一章:Spring 1.x ------ 在XML的汪洋大海中,种下IoC的种子
1.1 时代背景:J2EE的复杂之痛
时间回到2004年。
当时的Java企业级开发是什么样子?EJB 2.x是主流,但它的复杂程度令人发指:
-
编写Home接口、Remote接口、实现类
-
配置繁琐的
ejb-jar.xml、web.xml -
部署过程冗长,需要打包、发布到应用服务器
-
单元测试困难,必须依赖容器运行
业界开始质疑:开发一个简单的业务逻辑,为什么需要这么复杂的容器?Rod Johnson在他的《Expert One-on-One J2EE Development without EJB》一书中提出了一个激进的方案:用简单的Java对象(POJO)和依赖注入,替代复杂的EJB。Spring 1.x就这样诞生了。
1.2 核心思想:控制反转(IoC)与面向切面(AOP)
Spring 1.x的核心理念是:让开发者专注于业务逻辑,而非框架的复杂性。
1.2.1 控制反转(IoC)
概念:对象的创建和依赖关系的管理不再由对象自身控制,而是交给Spring容器。开发者通过XML配置文件定义Bean及其依赖关系,Spring容器在运行时实例化和管理这些Bean。
为什么要叫"反转" ?传统开发中,对象通过new关键字主动创建依赖对象,是"正向"控制;IoC模式下,对象被动接收容器注入的依赖,控制权"反转"给了容器。
1.2.2 依赖注入(DI)
Spring 1.x支持两种依赖注入方式:
| 注入方式 | XML配置语法 | 适用场景 |
|---|---|---|
| Setter注入 | <property name="userDao" ref="userDao"/> |
可选依赖,可重新配置 |
| 构造器注入 | <constructor-arg ref="userDao"/> |
强制依赖,确保不可变性 |
最佳实践:对于必需的依赖,推荐使用构造器注入;对于可选的依赖,可以使用Setter注入。
1.2.3 面向切面编程(AOP)
概念:将日志、事务、安全等横切关注点从业务逻辑中剥离出来,通过动态代理在运行时织入。
实现方式:
-
JDK动态代理:基于接口的代理,要求目标类实现接口
-
CGLIB:基于类的代理,通过生成子类实现
典型应用:声明式事务管理。开发者只需在XML中配置事务属性,Spring AOP会在运行时为Service方法织入事务控制逻辑。
1.3 配置方式详解:一切皆XML
Spring 1.x完全依赖XML配置。配置文件的根元素是<beans>,内部包含零个或多个<bean>定义。
1.3.1 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="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 最简单的Bean定义 -->
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<!-- 带依赖注入的Bean定义 -->
<bean id="userService" class="com.example.service.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<!-- 带构造器参数和属性的Bean -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</bean>
</beans>
1.3.2 Bean的元数据属性
<bean>标签支持丰富的属性:
| 属性 | 作用 | 示例 |
|---|---|---|
id |
Bean的唯一标识 | id="userService" |
class |
Bean的全限定类名 | class="com.example.UserServiceImpl" |
scope |
Bean的作用域(singleton/prototype) | scope="singleton" |
init-method |
初始化回调方法 | init-method="init" |
destroy-method |
销毁回调方法 | destroy-method="cleanup" |
factory-method |
工厂方法名称 | factory-method="getInstance" |
factory-bean |
工厂Bean的引用 | factory-bean="beanFactory" |
1.3.3 依赖注入的详细配置
Setter注入:
xml
<bean id="userService" class="com.example.service.UserServiceImpl">
<!-- 基本类型注入 -->
<property name="serviceName" value="UserService"/>
<!-- 引用类型注入 -->
<property name="userDao" ref="userDao"/>
<!-- 集合类型注入 -->
<property name="userList">
<list>
<value>user1</value>
<value>user2</value>
</list>
</property>
<!-- Map类型注入 -->
<property name="userMap">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
</property>
<!-- Properties类型注入 -->
<property name="settings">
<props>
<prop key="maxTotal">100</prop>
<prop key="minIdle">10</prop>
</props>
</property>
</bean>
构造器注入:
xml
<bean id="userService" class="com.example.service.UserServiceImpl">
<!-- 按参数顺序注入 -->
<constructor-arg index="0" value="UserService"/>
<constructor-arg index="1" ref="userDao"/>
<!-- 或按参数类型注入 -->
<constructor-arg type="java.lang.String" value="UserService"/>
<constructor-arg type="com.example.dao.UserDao" ref="userDao"/>
</bean>
1.4 功能模块详解
Spring 1.x已经奠定了模块化架构的基础:
| 模块 | 包名 | 功能 |
|---|---|---|
| Core Container | spring-core, spring-beans |
IoC容器核心,包括BeanFactory和ApplicationContext |
| Context | spring-context |
在Core基础上扩展,提供JNDI、国际化等企业级服务 |
| AOP | spring-aop |
面向切面编程支持,可配置声明式事务 |
| DAO | spring-dao |
JDBC抽象框架、异常层次结构 |
| ORM | spring-orm |
与Hibernate、iBATIS等ORM框架的集成 |
| Web | spring-web, spring-webmvc |
Spring MVC雏形,可与Struts等框架集成 |
| 事务 | spring-tx |
声明式事务管理 |
1.4.1 核心容器(Core Container)
BeanFactory :Spring容器的根接口,提供最基本的IoC功能。典型的实现是XmlBeanFactory(已废弃)。
ApplicationContext:BeanFactory的子接口,增加了企业级功能:
-
国际化支持(MessageSource)
-
资源访问(ResourceLoader)
-
事件发布(ApplicationEventPublisher)
-
容器生命周期管理
常用实现类:
-
ClassPathXmlApplicationContext:从类路径加载配置文件 -
FileSystemXmlApplicationContext:从文件系统加载配置文件 -
XmlWebApplicationContext:Web应用专用
使用示例:
java
// 1. 加载配置文件,创建容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 获取Bean
UserService userService = (UserService) context.getBean("userService");
// 3. 使用Bean
userService.doSomething();
1.4.2 数据访问模块
JDBC抽象框架:
-
JdbcTemplate:核心JDBC操作类,简化了资源获取和释放 -
SimpleJdbcTemplate:2.x引入的简化版本 -
DataSourceUtils:获取和释放连接的工具类
事务管理:
-
PlatformTransactionManager:事务管理器接口 -
DataSourceTransactionManager:基于JDBC的事务管理器 -
HibernateTransactionManager:基于Hibernate的事务管理器
声明式事务配置示例:
xml
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务代理 -->
<bean id="userServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="target" ref="userService"/>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
1.5 优缺点分析
优点:
-
降低耦合度:通过IoC容器管理依赖,组件间解耦
-
提高可测试性:依赖注入使得单元测试变得简单(可轻松注入Mock对象)
-
声明式事务:通过配置实现事务管理,减少了样板代码
-
非侵入式设计:业务类不需要继承Spring的特定类或实现特定接口
缺点:
-
配置繁琐:一个中型项目往往需要上千行XML配置
-
类型不安全:XML配置中的类名、属性名拼写错误只能在运行时发现
-
缺乏注解支持:Java 5的注解特性未能利用
-
编译期无法校验:IDE只能提供有限的XML校验
当年老开发的心声:
"那时候搭一个新项目,光复制粘贴上一堆XML配置文件就得半天。更别提整合Hibernate了,数据源、会话工厂、事务管理器...每一个都得手动配。最怕的是深夜上线,一个拼写错误就导致启动失败,还得对着几百行的XML一行行排查。"
第二章:Spring 2.x ------ 注解初现,XML与注解的"混搭时代"
2.1 版本概览
2006年,Spring 2.0发布;2007年,Spring 2.5发布。这两个版本共同标志着Spring进入了一个新的阶段:在保留XML的同时,开始拥抱注解。
核心升级点:
-
引入基于注解的配置方式(2.5)
-
AOP增强:支持@AspectJ风格的切面定义
-
新增Web模块:Spring MVC注解支持
-
JPA支持
-
新的Bean作用域:request、session
2.2 注解驱动配置详解
Java 5的发布为Spring带来了新的可能性。从2.5开始,Spring引入了基于注解的配置方式。
2.2.1 核心注解体系
| 注解 | 作用 | 替代XML配置 |
|---|---|---|
@Component |
通用的Spring Bean注解 | <bean id="..." class="..."/> |
@Repository |
标注数据访问层组件 | 同上,但增加DAO异常转换 |
@Service |
标注业务逻辑层组件 | 同上,仅用于语义区分 |
@Controller |
标注Web层组件 | 同上,用于Spring MVC |
@Autowired |
自动装配依赖 | <property name="..." ref="..."/> |
@Qualifier |
按名称限定自动装配 | 解决多个候选Bean的歧义 |
@Resource |
Java EE标准注解,按名称装配 | 同上,JSR-250规范 |
@PostConstruct |
初始化回调方法 | init-method属性 |
@PreDestroy |
销毁回调方法 | destroy-method属性 |
2.2.2 @Component及其衍生注解
java
// 通用组件
@Component("userDao")
public class UserDaoImpl implements UserDao {
// ...
}
// 数据访问层组件(自动添加持久化异常转换)
@Repository("userDao")
public class UserDaoImpl implements UserDao {
// ...
}
// 业务逻辑层组件
@Service("userService")
public class UserServiceImpl implements UserService {
// ...
}
// Web层组件
@Controller("userController")
public class UserController {
// ...
}
源码分析 :查看@Service的源码可以发现,它其实就是@Component:
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 元注解,表明@Service也是Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
2.2.3 @Autowired自动装配
java
@Service("userService")
public class UserServiceImpl implements UserService {
// 字段注入
@Autowired
private UserDao userDao;
// 构造器注入
private final RoleDao roleDao;
@Autowired
public UserServiceImpl(RoleDao roleDao) {
this.roleDao = roleDao;
}
// Setter注入
private PermissionDao permissionDao;
@Autowired
public void setPermissionDao(PermissionDao permissionDao) {
this.permissionDao = permissionDao;
}
// 任意方法注入
@Autowired
public void init(ConfigDao configDao) {
// 方法参数会被自动注入
}
}
@Autowired装配规则:
-
默认按类型装配
-
如果找到多个类型匹配的Bean,尝试按名称装配(字段名匹配Bean ID)
-
如果仍无法确定,抛出异常
-
可以通过
@Qualifier指定Bean名称 -
可以通过
required=false允许依赖不存在
java
@Autowired
@Qualifier("oracleDataSource") // 指定Bean名称
private DataSource dataSource;
@Autowired(required = false) // 依赖可选
private CacheManager cacheManager;
2.2.4 @Resource(JSR-250)
@Resource是Java EE标准注解,Spring也支持:
java
@Service
public class UserService {
@Resource(name = "userDao") // 按名称装配
private UserDao userDao;
@Resource // 默认按字段名装配
private RoleDao roleDao;
}
@Autowired vs @Resource对比:
| 维度 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring特有 | Java EE标准(JSR-250) |
| 默认装配方式 | 按类型 | 按名称 |
| 指定名称 | 需配合@Qualifier | 使用name属性 |
| required属性 | 支持 | 不支持 |
| 适用范围 | 字段、方法、构造器 | 字段、方法 |
2.2.5 开启注解支持
要使用注解,需要在XML中开启组件扫描:
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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启组件扫描,指定基础包 -->
<context:component-scan base-package="com.example"/>
<!-- 或者更细粒度的控制 -->
<context:component-scan base-package="com.example">
<!-- 包含哪些注解 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!-- 排除哪些类 -->
<context:exclude-filter type="regex" expression="com\.example\.legacy\..*"/>
</context:component-scan>
</beans>
2.3 AOP增强:@AspectJ支持
Spring 2.0引入了对@AspectJ注解的支持,允许用POJO定义切面。
2.3.1 启用@AspectJ支持
xml
<!-- 开启AspectJ自动代理 -->
<aop:aspectj-autoproxy/>
2.3.2 定义切面
java
@Aspect
@Component
public class LoggingAspect {
// 前置通知
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before: " + joinPoint.getSignature().getName());
}
// 后置通知(正常返回)
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("After returning: " + result);
}
// 异常通知
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
System.out.println("After throwing: " + error);
}
// 最终通知
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After (finally): " + joinPoint.getSignature().getName());
}
// 环绕通知
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - start;
System.out.println("Method execution time: " + elapsedTime + "ms");
return result;
} catch (IllegalArgumentException e) {
System.out.println("Illegal argument: " + Arrays.toString(joinPoint.getArgs()));
throw e;
}
}
}
2.3.3 切入点表达式
切入点表达式语法:
| 表达式 | 含义 |
|---|---|
execution(public * *(..)) |
所有public方法 |
execution(* set*(..)) |
所有以set开头的方法 |
execution(* com.example.service.*.*(..)) |
service包下所有类的所有方法 |
execution(* com.example.service..*.*(..)) |
service包及其子包下所有类的所有方法 |
within(com.example.service.*) |
service包下的所有方法 |
@annotation(org.springframework.transaction.annotation.Transactional) |
标注了@Transactional的方法 |
2.4 新增Bean作用域
Spring 2.x增加了适用于Web应用的作用域:
| 作用域 | 说明 | 适用场景 |
|---|---|---|
singleton |
单例(默认) | 无状态Bean |
prototype |
原型,每次获取创建新实例 | 有状态Bean |
request |
每个HTTP请求创建一个Bean | Web应用,请求级别的数据 |
session |
每个HTTP会话创建一个Bean | Web应用,用户会话级别的数据 |
globalSession |
全局HTTP会话(Portlet环境) | Portlet应用 |
配置方式:
java
@Scope("prototype")
@Component
public class ShoppingCart {
// 每个用户有自己的购物车实例
}
// 或XML方式
<bean id="shoppingCart" class="com.example.ShoppingCart" scope="session"/>
2.5 Spring MVC注解支持
Spring 2.5为MVC引入了基于注解的控制器。
2.5.1 @Controller和@RequestMapping
java
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/user/list")
public ModelAndView listUsers() {
List<User> users = userService.findAll();
ModelAndView mav = new ModelAndView("user/list");
mav.addObject("users", users);
return mav;
}
@RequestMapping(value = "/user/save", method = RequestMethod.POST)
public String saveUser(User user) {
userService.save(user);
return "redirect:/user/list";
}
}
2.5.2 请求参数绑定
java
@Controller
@RequestMapping("/user")
public class UserController {
// @RequestParam绑定请求参数
@RequestMapping("/detail")
public ModelAndView detail(@RequestParam("id") Long userId) {
User user = userService.findById(userId);
return new ModelAndView("user/detail").addObject("user", user);
}
// 自动绑定到对象(基于同名参数)
@RequestMapping("/save")
public String save(User user) {
userService.save(user);
return "redirect:/user/list";
}
}
2.6 实用知识点:2.x时代的"混搭"原则
2.x时代的最佳实践是:基础架构用XML,业务逻辑用注解。
-
XML配置:负责数据源、事务管理器、视图解析器等基础设施
-
注解配置:负责业务组件(Controller、Service、DAO)的定义和依赖注入
这种"混搭"风格一直延续了很多年,但也带来了一个尴尬:开发者需要同时维护两种配置方式,心智负担并不轻。
2.7 局限性
-
配置分散:Bean定义分散在XML和注解中,查找依赖关系困难
-
混合复杂度:新手难以理解哪些配置应该用XML,哪些用注解
-
类型安全:XML中配置的类名、属性名仍然缺乏编译时检查
第三章:Spring 3.x ------ Java配置崛起,告别XML的序章
3.1 版本概览
2009年,Spring 3.0发布。这是一个里程碑式的版本:第一次让"零XML配置"成为可能。
核心升级点:
-
引入基于Java类的配置方式(@Configuration、@Bean)
-
Spring表达式语言(SpEL)
-
基于注解的REST支持(@RestController)
-
统一的注解配置入口(@ComponentScan)
-
模块化架构完善
3.2 Java配置详解
Spring 3.0从JavaConfig项目汲取灵感,引入了基于Java类的配置方式。
3.2.1 @Configuration与@Bean
java
@Configuration
public class AppConfig {
// 定义Bean,方法名默认是Bean名称
@Bean
public UserDao userDao() {
return new UserDaoImpl();
}
// 指定Bean名称
@Bean("userService")
public UserService userService() {
UserServiceImpl service = new UserServiceImpl();
service.setUserDao(userDao()); // 调用另一个@Bean方法
return service;
}
// 带初始化/销毁方法的Bean
@Bean(initMethod = "init", destroyMethod = "cleanup")
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/test");
ds.setUsername("root");
ds.setPassword("password");
return ds;
}
// 配置作用域
@Bean
@Scope("prototype")
public ShoppingCart shoppingCart() {
return new ShoppingCart();
}
}
3.2.2 @ComponentScan自动扫描
java
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 无需显式定义Bean,自动扫描@Component等注解
}
// 更细粒度的扫描控制
@Configuration
@ComponentScan(
basePackages = "com.example",
includeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = {Controller.class}
),
excludeFilters = @ComponentScan.Filter(
type = FilterType.REGEX,
pattern = "com\\.example\\.legacy\\..*"
)
)
public class WebConfig {
}
3.2.3 @Value与属性源
java
@Configuration
@PropertySource("classpath:application.properties") // 加载属性文件
public class AppConfig {
@Value("${database.url}") // 注入属性
private String dbUrl;
@Value("${database.username:root}") // 带默认值
private String dbUsername;
@Value("#{2 + 3}") // SpEL表达式
private int calculation;
@Value("#{systemProperties['user.timezone']}") // 系统属性
private String timezone;
@Value("#{userDao.findByAge(18)}") // 调用其他Bean的方法
private List<User> youngUsers;
@Bean
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setUrl(dbUrl);
ds.setUsername(dbUsername);
return ds;
}
}
3.2.4 @Profile环境区分
Spring 3.1引入@Profile注解,用于区分不同环境。
java
@Configuration
@Profile("development")
public class DevConfig {
@Bean
public DataSource dataSource() {
// 开发环境使用H2内存数据库
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
@Configuration
@Profile("production")
public class ProdConfig {
@Bean
public DataSource dataSource() {
// 生产环境使用连接池
BoneCPDataSource ds = new BoneCPDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://prod-server:3306/prod");
// ...其他配置
return ds;
}
}
激活Profile的方式:
-
JVM参数:
-Dspring.profiles.active=development -
编程方式:
context.getEnvironment().setActiveProfiles("development") -
web.xml配置:
<context-param><param-name>spring.profiles.active</param-name><param-value>development</param-value></context-param>
3.3 Spring表达式语言(SpEL)
SpEL是Spring 3.0引入的强大表达式语言,支持在运行时查询和操作对象。
3.3.1 基础语法
java
@Value("#{2 + 3}") // 算术运算
private int sum; // 5
@Value("#{'Hello' + ' ' + 'World'}") // 字符串连接
private String greeting; // "Hello World"
@Value("#{1 eq 1}") // 相等比较
private boolean equal; // true
@Value("#{userService.getUser(1)}") // 调用方法
private User user;
@Value("#{systemProperties['os.name']}") // 系统属性
private String osName;
@Value("#{T(java.lang.Math).random() * 100}") // 调用静态方法
private double random;
3.3.2 集合操作
java
@Value("#{userList.?[age > 18]}") // 筛选(年龄>18的用户)
private List<User> adults;
@Value("#{userList.![name]}") // 投影(提取所有用户的name)
private List<String> names;
@Value("#{userList.?[age > 18].![name]}") // 组合操作
private List<String> adultNames;
@Value("#{userMap['key']}") // Map访问
private User user;
3.3.3 安全导航操作符
java
@Value("#{user?.address?.city}") // 如果user或address为null,返回null而非NPE
private String city;
3.4 REST支持增强
Spring 3.0为REST风格Web服务提供了原生支持。
3.4.1 @RestController
java
@RestController // = @Controller + @ResponseBody
@RequestMapping("/api/users")
public class UserRestController {
@Autowired
private UserService userService;
@GetMapping("/{id}") // @RequestMapping(method=GET)的简写
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
@GetMapping
public List<User> getUsers() {
return userService.findAll();
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public User createUser(@RequestBody User user) {
return userService.save(user);
}
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
return userService.update(user);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
}
3.4.2 内容协商
Spring 3.0支持基于请求头Accept的内容协商,可返回JSON、XML等不同格式。
java
@Configuration
public class WebConfig {
@Bean
public ContentNegotiationManager contentNegotiationManager() {
return new ContentNegotiationManagerBuilder()
.favorParameter(true)
.parameterName("format")
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML)
.mediaType("json", MediaType.APPLICATION_JSON)
.build();
}
}
3.5 模块化架构完善
Spring 3.x将框架拆分为多个模块,开发者可按需引入:
| 模块组 | Maven坐标 | 功能 |
|---|---|---|
| spring-core | org.springframework:spring-core |
核心工具类 |
| spring-beans | org.springframework:spring-beans |
Bean工厂和DI支持 |
| spring-context | org.springframework:spring-context |
ApplicationContext、事件、国际化 |
| spring-context-support | org.springframework:spring-context-support |
集成第三方库(Quartz、Velocity等) |
| spring-expression | org.springframework:spring-expression |
SpEL表达式语言 |
| spring-aop | org.springframework:spring-aop |
AOP支持 |
| spring-jdbc | org.springframework:spring-jdbc |
JDBC抽象层 |
| spring-orm | org.springframework:spring-orm |
ORM框架集成 |
| spring-tx | org.springframework:spring-tx |
事务管理 |
| spring-web | org.springframework:spring-web |
Web基础支持 |
| spring-webmvc | org.springframework:spring-webmvc |
Spring MVC |
| spring-test | org.springframework:spring-test |
测试支持 |
3.6 实用知识点:从XML到Java配置的迁移策略
3.6.1 混合配置
可以在Java配置中导入XML配置:
java
@Configuration
@ImportResource("classpath:legacy-config.xml") // 导入XML配置
public class AppConfig {
// Java配置...
}
也可以在XML中导入Java配置:
xml
<beans>
<!-- 导入Java配置类 -->
<bean class="com.example.AppConfig"/>
<!-- 开启组件扫描,识别@Configuration -->
<context:component-scan base-package="com.example"/>
</beans>
3.6.2 逐步迁移策略
对于大型项目,建议逐步从XML迁移到Java配置:
-
第一阶段:将基础设施配置(数据源、事务管理器)迁移到Java配置
-
第二阶段:将数据访问层(DAO)配置迁移到Java配置
-
第三阶段:将业务逻辑层(Service)配置迁移到Java配置
-
第四阶段:将Web层(Controller)配置迁移到Java配置
-
第五阶段:移除所有XML配置,实现纯注解开发
3.7 局限性
-
Java版本要求:虽然支持Java 5+,但未能充分利用Java 7/8新特性
-
配置复杂度:Java配置虽然类型安全,但代码量可能比XML更多
-
学习曲线:开发者需要同时掌握XML、注解、Java配置三种方式
第四章:Spring 4.x ------ 拥抱Java 8,开启WebSocket时代
4.1 版本概览
2013年,Spring 4.0发布。这是第一个完全支持Java 8的版本,同时也为后来的微服务架构奠定了基础。
核心升级点:
-
Java 8全面支持(Lambda、日期时间API)
-
WebSocket与STOMP消息支持
-
条件化配置(@Conditional)
-
泛型依赖注入
-
更灵活的测试支持
4.2 Java 8全面支持
4.2.1 Lambda表达式与方法引用
Spring的回调接口现在可以接受Lambda表达式,代码更简洁:
java
// 传统方式(匿名内部类)
jdbcTemplate.queryForObject(sql, new RowMapper<User>() {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return new User(rs.getString("name"));
}
});
// Lambda方式
jdbcTemplate.queryForObject(sql, (rs, rowNum) -> new User(rs.getString("name")));
// 方法引用
jdbcTemplate.queryForObject(sql, BeanPropertyRowMapper.newInstance(User.class));
4.2.2 新日期时间API支持
Spring 4.x全面支持JSR-310(Java 8日期时间API),可以直接在@RequestParam、@PathVariable、@RequestBody中使用LocalDate、LocalDateTime等类型。
java
@RestController
@RequestMapping("/orders")
public class OrderController {
@GetMapping("/search")
public List<Order> searchOrders(
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
return orderService.findByDateBetween(startDate, endDate);
}
@PostMapping
public Order createOrder(@RequestBody Order order) {
// Order实体中包含LocalDateTime字段
return orderService.save(order);
}
}
4.2.3 @Repeatable注解
Spring的多个注解(如@PropertySource)被改造为可重复注解:
java
@Configuration
@PropertySource("classpath:db.properties")
@PropertySource("classpath:redis.properties")
@PropertySource("classpath:mq.properties")
public class AppConfig {
// 配置类
}
// Spring 3.x时代的替代写法
@Configuration
@PropertySources({
@PropertySource("classpath:db.properties"),
@PropertySource("classpath:redis.properties")
})
public class OldConfig {
}
4.3 WebSocket与STOMP消息
Spring 4.x引入了全新的spring-websocket模块,支持基于WebSocket的双向通信。
4.3.1 WebSocket基础
什么是WebSocket?WebSocket协议(RFC 6455)为Web应用提供了全双工、双向通信能力。与HTTP的请求-响应模式不同,WebSocket连接建立后,服务器和客户端都可以随时主动发送消息。
Spring WebSocket架构:
java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/websocket")
.addInterceptors(new HttpSessionHandshakeInterceptor())
.setAllowedOrigins("*"); // 允许跨域
}
@Bean
public WebSocketHandler myHandler() {
return new MyWebSocketHandler();
}
}
4.3.2 WebSocketHandler实现
java
public class MyWebSocketHandler extends TextWebSocketHandler {
private static final Logger log = LoggerFactory.getLogger(MyWebSocketHandler.class);
// 连接建立后
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
log.info("连接建立: {}", session.getId());
session.sendMessage(new TextMessage("欢迎连接服务器"));
}
// 收到文本消息
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
log.info("收到消息: {}", message.getPayload());
// 处理消息
String response = "服务器已收到: " + message.getPayload();
session.sendMessage(new TextMessage(response));
}
// 连接关闭后
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
log.info("连接关闭: {}, 状态: {}", session.getId(), status);
}
// 传输错误
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
log.error("传输错误", exception);
}
}
4.3.3 SockJS降级方案
WebSocket在浏览器端支持不完善,Spring提供了SockJS作为降级方案。
java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/websocket")
.withSockJS() // 启用SockJS降级
.setClientLibraryUrl("https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js")
.setStreamBytesLimit(512 * 1024)
.setHttpMessageCacheSize(1000);
}
}
SockJS会在浏览器不支持WebSocket时,自动降级到轮询、长轮询等替代方案。
4.3.4 STOMP协议支持
STOMP(Simple Text Oriented Messaging Protocol)是一个简单的文本消息协议,类似HTTP的帧结构。Spring提供了基于WebSocket的STOMP支持。
配置STOMP消息代理:
java
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 注册STOMP端点,客户端通过此端点连接
registry.addEndpoint("/stomp")
.withSockJS(); // 启用SockJS
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 设置应用前缀(客户端发送消息的路径前缀)
registry.setApplicationDestinationPrefixes("/app");
// 启用简单消息代理,客户端订阅这些前缀的目的地
registry.enableSimpleBroker("/topic", "/queue");
// 也可以使用外部消息代理(如RabbitMQ)
// registry.enableStompBrokerRelay("/topic", "/queue")
// .setRelayHost("localhost")
// .setRelayPort(61613);
}
}
STOMP控制器:
java
@Controller
public class StompController {
// 处理发送到 /app/hello 的消息
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) {
return new Greeting("Hello, " + message.getName() + "!");
}
// 处理发送到 /app/chat/{roomId} 的消息
@MessageMapping("/chat/{roomId}")
@SendTo("/topic/chat/{roomId}")
public ChatMessage chat(@DestinationVariable String roomId, ChatMessage message) {
message.setTimestamp(LocalDateTime.now());
return message;
}
// 发送给特定用户的消息
@MessageMapping("/private")
public void privateMessage(Principal principal, PrivateMessage message) {
message.setFrom(principal.getName());
// 发送到用户的私有队列
simpMessagingTemplate.convertAndSendToUser(
message.getTo(), "/queue/private", message);
}
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
}
客户端JavaScript代码:
javascript
// 连接
var socket = new SockJS('/stomp');
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
// 订阅公共话题
stompClient.subscribe('/topic/greetings', function(greeting) {
console.log(JSON.parse(greeting.body));
});
// 发送消息
stompClient.send("/app/hello", {}, JSON.stringify({'name': '张三'}));
});
4.4 条件化配置(@Conditional)
Spring 4.x引入了@Conditional注解,允许根据特定条件创建Bean,比@Profile更灵活。
4.4.1 基本用法
java
@Configuration
public class DataSourceConfig {
@Bean
@Conditional(WindowsCondition.class) // Windows环境
public DataSource windowsDataSource() {
// Windows环境使用特定配置
return new WindowsDataSource();
}
@Bean
@Conditional(LinuxCondition.class) // Linux环境
public DataSource linuxDataSource() {
// Linux环境使用特定配置
return new LinuxDataSource();
}
}
// 自定义条件
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String osName = context.getEnvironment().getProperty("os.name");
return osName != null && osName.contains("Windows");
}
}
4.4.2 内置条件注解
Spring Boot在4.x基础上扩展了一系列条件注解:
| 注解 | 作用 |
|---|---|
@ConditionalOnClass |
类路径中存在指定类时生效 |
@ConditionalOnMissingClass |
类路径中不存在指定类时生效 |
@ConditionalOnBean |
容器中存在指定Bean时生效 |
@ConditionalOnMissingBean |
容器中不存在指定Bean时生效 |
@ConditionalOnProperty |
配置文件中存在指定属性时生效 |
@ConditionalOnResource |
类路径中存在指定资源文件时生效 |
@ConditionalOnWebApplication |
是Web应用时生效 |
@ConditionalOnNotWebApplication |
不是Web应用时生效 |
@ConditionalOnExpression |
SpEL表达式为true时生效 |
使用示例:
java
@Configuration
public class RedisConfig {
@Bean
@ConditionalOnClass(RedisOperations.class) // 存在Redis依赖
@ConditionalOnProperty(name = "redis.enabled", havingValue = "true", matchIfMissing = true)
public RedisTemplate<String, Object> redisTemplate() {
return new RedisTemplate<>();
}
}
4.5 核心容器增强
4.5.1 泛型依赖注入
Spring 4.x支持将泛型作为限定符,按泛型类型注入Bean。
java
// 泛型接口
public interface Repository<T> {
void save(T entity);
T findById(Long id);
}
// 具体实现
@Repository
public class UserRepository implements Repository<User> {
public void save(User user) { /* ... */ }
public User findById(Long id) { /* ... */ }
}
@Repository
public class ProductRepository implements Repository<Product> {
public void save(Product product) { /* ... */ }
public Product findById(Long id) { /* ... */ }
}
// 按泛型类型注入
@Service
public class UserService {
@Autowired
private Repository<User> userRepository; // 自动注入UserRepository
@Autowired
private Repository<Product> productRepository; // 自动注入ProductRepository
}
4.5.2 @Order注解
控制集合注入时的顺序:
java
public interface Validator {
void validate(Object obj);
}
@Component
@Order(1)
public class EmailValidator implements Validator {
public void validate(Object obj) { /* ... */ }
}
@Component
@Order(2)
public class PhoneValidator implements Validator {
public void validate(Object obj) { /* ... */ }
}
@Service
public class ValidationService {
@Autowired
private List<Validator> validators; // 按@Order顺序注入
public void validateAll(Object obj) {
validators.forEach(v -> v.validate(obj)); // 先执行EmailValidator,后执行PhoneValidator
}
}
4.6 测试增强
4.6.1 测试上下文缓存
Spring 4.x优化了测试上下文缓存,同一配置的测试类共享ApplicationContext,大幅提升测试速度。
java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void testFindUser() {
User user = userService.findById(1L);
assertNotNull(user);
}
}
4.6.2 Servlet API Mock
支持Servlet 3.0 Mock对象,方便测试Web层:
java
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration // 启用Web上下文
@ContextConfiguration(classes = WebConfig.class)
public class UserControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void testGetUser() throws Exception {
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("张三"));
}
}
4.7 实用知识点:WebSocket选型建议
根据实际场景选择合适的推送技术:
| 技术 | 延迟 | 复杂度 | 适用场景 |
|---|---|---|---|
| 轮询 | 高 | 低 | 低频数据,简单需求 |
| 长轮询 | 中 | 中 | 中等频率,对延迟不敏感 |
| WebSocket | 低 | 高 | 高频、低延迟,如游戏、金融行情 |
| SSE | 中 | 低 | 服务器单向推送,如新闻推送 |
选择建议:如果需要服务器主动推送数据给客户端,且对延迟要求不高,考虑SSE;如果需要双向高频通信,选择WebSocket;如果只是想实现简单的通知功能,轮询已经足够。
第五章:Spring 5.x ------ 响应式编程的革命
5.1 版本概览
2017年,Spring 5.0发布。这是一次质的飞跃------引入了响应式编程模型,为高并发场景提供了全新的解决方案。
核心升级点:
-
响应式编程(Reactive Programming)支持
-
Spring WebFlux:完全非阻塞的响应式Web框架
-
函数式编程模型
-
Kotlin支持
-
HTTP/2支持
-
JUnit 5集成
5.2 响应式编程详解
5.2.1 什么是响应式编程?
响应式编程是一种基于数据流 和变化传播的编程范式。当数据发生变化时,依赖这些数据的地方会自动更新。
核心特点:
-
异步非阻塞:不会阻塞调用线程
-
背压(Backpressure):消费者可以控制生产者发送数据的速率
-
事件驱动:基于事件的处理模型
-
声明式:类似于函数式编程的链式调用
5.2.2 Reactor核心类型
Spring WebFlux基于Reactor库,提供了两种核心响应式类型:
| 类型 | 含义 | 类比 | 适用场景 |
|---|---|---|---|
| Mono<T> | 0-1个元素的异步序列 | Optional + CompletableFuture | 单个结果(查询单个对象) |
| Flux<T> | 0-N个元素的异步序列 | Stream + CompletableFuture | 多个结果(查询列表、流式数据) |
创建Mono/Flux:
java
// 创建Mono
Mono<String> emptyMono = Mono.empty();
Mono<String> justMono = Mono.just("hello");
Mono<User> userMono = Mono.justOrEmpty(user);
Mono<User> fromCallableMono = Mono.fromCallable(() -> findUserFromDb());
// 创建Flux
Flux<String> emptyFlux = Flux.empty();
Flux<String> justFlux = Flux.just("a", "b", "c");
Flux<Integer> rangeFlux = Flux.range(1, 5);
Flux<User> fromArrayFlux = Flux.fromArray(users);
操作符示例:
java
// map: 同步转换
Flux<String> flux = Flux.just("a", "b", "c")
.map(String::toUpperCase); // ["A", "B", "C"]
// flatMap: 异步转换(每个元素返回一个Publisher)
Flux<User> userFlux = Flux.just(1, 2, 3)
.flatMap(id -> userRepository.findById(id)); // Mono<User>合并为Flux<User>
// filter: 过滤
Flux<Integer> evenFlux = Flux.range(1, 10)
.filter(i -> i % 2 == 0); // [2, 4, 6, 8, 10]
// zip: 合并多个流
Flux<String> names = Flux.just("张三", "李四");
Flux<Integer> ages = Flux.just(25, 30);
Flux<User> users = Flux.zip(names, ages)
.map(tuple -> new User(tuple.getT1(), tuple.getT2()));
// error handling
Mono<User> userMono = userRepository.findById(id)
.switchIfEmpty(Mono.error(new UserNotFoundException()))
.onErrorResume(e -> {
log.error("Error", e);
return Mono.just(defaultUser());
});
5.2.3 背压(Backpressure)
背压是响应式编程的核心概念,它解决了消费者处理速度跟不上生产者的问题。
java
// 消费者通过request控制数据量
Flux.range(1, 100)
.subscribe(new BaseSubscriber<Integer>() {
@Override
protected void hookOnSubscribe(Subscription subscription) {
request(10); // 一次只请求10个元素
}
@Override
protected void hookOnNext(Integer value) {
process(value);
if (processedCount % 10 == 0) {
request(10); // 处理完一批再请求下一批
}
}
});
5.3 Spring WebFlux详解
WebFlux是Spring 5.x引入的响应式Web框架,提供两种编程模型:基于注解 和函数式端点。
5.3.1 编程模型对比
| 维度 | 基于注解 | 函数式端点 |
|---|---|---|
| 编程风格 | 声明式(类似Spring MVC) | 函数式(类似Router Functions) |
| 适用人群 | 熟悉Spring MVC的开发者 | 喜欢函数式编程的开发者 |
| 代码量 | 较少 | 中等 |
| 灵活性 | 中等 | 高 |
| 测试难度 | 容易 | 中等 |
5.3.2 基于注解的WebFlux
与Spring MVC非常相似,区别在于返回值是Mono/Flux。
java
@RestController
@RequestMapping("/api/users")
public class UserReactiveController {
private final ReactiveUserRepository userRepository;
public UserReactiveController(ReactiveUserRepository userRepository) {
this.userRepository = userRepository;
}
// 返回单个对象
@GetMapping("/{id}")
public Mono<User> getUser(@PathVariable String id) {
return userRepository.findById(id)
.switchIfEmpty(Mono.error(new UserNotFoundException(id)));
}
// 返回多个对象
@GetMapping
public Flux<User> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return userRepository.findAll()
.skip(page * size)
.take(size);
}
// 创建资源
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Mono<User> createUser(@Valid @RequestBody Mono<User> userMono) {
return userMono.flatMap(userRepository::save);
}
// 更新资源
@PutMapping("/{id}")
public Mono<User> updateUser(@PathVariable String id, @Valid @RequestBody User user) {
user.setId(id);
return userRepository.save(user);
}
// 删除资源
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> deleteUser(@PathVariable String id) {
return userRepository.deleteById(id);
}
// 流式返回(SSE)
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> streamUsers() {
return userRepository.findAll()
.delayElements(Duration.ofSeconds(1)); // 每秒推送一个
}
}
5.3.3 函数式端点(Functional Endpoints)
函数式端点提供了更灵活的路由和处理方式。
HandlerFunction:处理请求并返回响应
java
@Component
public class UserHandler {
private final ReactiveUserRepository userRepository;
public UserHandler(ReactiveUserRepository userRepository) {
this.userRepository = userRepository;
}
public Mono<ServerResponse> getUserById(ServerRequest request) {
String id = request.pathVariable("id");
return userRepository.findById(id)
.flatMap(user -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(user))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> getUsers(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(userRepository.findAll(), User.class);
}
public Mono<ServerResponse> createUser(ServerRequest request) {
Mono<User> userMono = request.bodyToMono(User.class);
return userMono.flatMap(user ->
ServerResponse.status(HttpStatus.CREATED)
.contentType(MediaType.APPLICATION_JSON)
.body(userRepository.save(user), User.class)
);
}
public Mono<ServerResponse> deleteUser(ServerRequest request) {
String id = request.pathVariable("id");
return userRepository.deleteById(id)
.then(ServerResponse.noContent().build());
}
}
RouterFunction:定义路由规则
java
@Configuration
public class UserRouter {
@Bean
public RouterFunction<ServerResponse> userRoutes(UserHandler handler) {
return RouterFunctions.route()
.GET("/api/users/{id}", handler::getUserById)
.GET("/api/users", handler::getUsers)
.POST("/api/users", handler::createUser)
.DELETE("/api/users/{id}", handler::deleteUser)
.build();
}
// 也可以使用静态方法
@Bean
public RouterFunction<ServerResponse> nestedRoutes(UserHandler handler) {
return route()
.path("/api/users", builder -> builder
.GET("/{id}", handler::getUserById)
.GET("", handler::getUsers)
.POST("", handler::createUser)
.DELETE("/{id}", handler::deleteUser))
.build();
}
}
5.3.4 WebClient:响应式HTTP客户端
WebClient是WebFlux提供的非阻塞HTTP客户端,用于替代传统的RestTemplate。
java
@Service
public class UserServiceClient {
private final WebClient webClient;
public UserServiceClient(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder
.baseUrl("http://user-service")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
// 获取单个用户
public Mono<User> getUserById(String id) {
return webClient.get()
.uri("/users/{id}", id)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response ->
Mono.error(new UserNotFoundException()))
.bodyToMono(User.class)
.timeout(Duration.ofSeconds(5))
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)));
}
// 获取所有用户
public Flux<User> getAllUsers() {
return webClient.get()
.uri("/users")
.retrieve()
.bodyToFlux(User.class);
}
// 创建用户
public Mono<User> createUser(User user) {
return webClient.post()
.uri("/users")
.bodyValue(user)
.retrieve()
.bodyToMono(User.class);
}
// 批量调用(组合多个请求)
public Flux<User> getUsersByIds(List<String> ids) {
return Flux.fromIterable(ids)
.flatMap(this::getUserById) // 并发调用
.parallel() // 并行处理
.runOn(Schedulers.parallel())
.sequential();
}
// 自定义配置
@Bean
public WebClient customWebClient() {
return WebClient.builder()
.baseUrl("http://api.example.com")
.defaultHeader(HttpHeaders.USER_AGENT, "MyApp/1.0")
.defaultCookie("sessionId", "123456")
.filter(ExchangeFilterFunctions.basicAuthentication("user", "password"))
.filter((request, next) -> {
// 自定义过滤器,记录日志
log.info("Request: {} {}", request.method(), request.url());
return next.exchange(request)
.doOnSuccess(response ->
log.info("Response status: {}", response.statusCode()));
})
.build();
}
}
5.4 Kotlin支持
Spring 5.x对Kotlin提供了原生支持:
kotlin
@RestController
@RequestMapping("/api/products")
class ProductController(private val productService: ProductService) {
@GetMapping("/{id}")
suspend fun getProduct(@PathVariable id: String): Product {
// 使用协程,挂起而非阻塞
return productService.findById(id)
}
@GetMapping
fun getProducts(): Flow<Product> = productService.findAll() // Kotlin Flow
@PostMapping
suspend fun createProduct(@RequestBody product: Product): Product {
return productService.save(product)
}
}
// 使用协程的Service
@Service
class ProductService(private val repository: ProductRepository) {
suspend fun findById(id: String): Product =
withContext(Dispatchers.IO) {
repository.findById(id) ?: throw ProductNotFoundException()
}
fun findAll(): Flow<Product> =
repository.findAll().asFlow()
}
5.5 HTTP/2支持
Spring 5.x支持HTTP/2协议,需要容器支持(Tomcat 9+、Undertow 1.4+、Jetty 9.3+)。
java
@Configuration
public class Http2Config {
@Bean
public ConfigurableServletWebServerFactory servletWebServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(connector -> {
connector.setProperty("protocol", "org.apache.coyote.http11.Http11NioProtocol");
connector.setProperty("SSLEnabled", "true");
connector.setProperty("maxThreads", "200");
// HTTP/2配置
connector.addUpgradeProtocol(new Http2Protocol());
});
return factory;
}
}
5.6 JUnit 5集成
Spring 5.x完全支持JUnit 5,提供了更灵活的测试扩展。
java
@ExtendWith(SpringExtension.class) // 替代@RunWith
@ContextConfiguration(classes = AppConfig.class)
@WebAppConfiguration
class UserServiceTest {
@Autowired
private UserService userService;
@Test
@DisplayName("测试查找用户")
void testFindUser() {
User user = userService.findById(1L);
assertAll("用户信息",
() -> assertNotNull(user),
() -> assertEquals("张三", user.getName()),
() -> assertTrue(user.getAge() > 0)
);
}
@ParameterizedTest
@ValueSource(strings = {"张三", "李四", "王五"})
void testFindUsersByName(String name) {
List<User> users = userService.findByName(name);
assertThat(users).allMatch(u -> u.getName().equals(name));
}
@Nested
@DisplayName("用户创建测试")
class CreateUserTest {
@Test
void createValidUser() {
User user = new User("赵六", 25);
User saved = userService.save(user);
assertNotNull(saved.getId());
}
}
}
5.7 实用知识点:WebFlux vs WebMVC选型指南
根据实际场景选择合适的Web框架:
选择WebFlux的场景:
-
需要处理大量并发连接(如API网关、实时推送)
-
I/O密集型应用(频繁的数据库查询、远程调用)
-
希望用少量线程支撑高并发
-
团队熟悉响应式编程,愿意学习新模型
选择WebMVC的场景:
-
传统CRUD应用,业务简单
-
CPU密集型任务(计算量大)
-
依赖JDBC等阻塞式数据库驱动
-
团队对响应式编程不熟悉
-
需要快速开发,不愿引入额外复杂度
混合使用:可以在一个应用中同时使用WebMVC和WebFlux,但通常不推荐,会增加认知负担。
第六章:Spring 6.x ------ Java 17与GraalVM原生时代
6.1 版本概览
2022年,Spring Framework 6.0发布。这是一次架构级别的重大跃迁,要求开发者必须升级到Java 17,并全面迁移到Jakarta EE 9+。
核心升级点:
-
Java 17成为基线
-
Jakarta EE 9迁移(javax.* → jakarta.*)
-
GraalVM原生镜像与AOT编译
-
HTTP Interface客户端
-
可观测性增强(Micrometer Tracing)
-
虚拟线程初步支持(JDK 19+)
6.2 Java 17成为基线
Spring 6.x强制要求Java 17+,这意味着可以利用Java 17 LTS的新特性。
6.2.1 记录类(Record Classes)
记录类是一种透明的数据载体,减少样板代码:
java
// 传统Java类
public class User {
private Long id;
private String name;
private String email;
// 构造器、getter/setter、equals、hashCode、toString...
}
// Record(Java 17)
public record User(Long id, String name, String email) {}
// 使用
User user = new User(1L, "张三", "zhangsan@example.com");
System.out.println(user.name()); // 自动生成getter(注意不是getName)
6.2.2 密封类(Sealed Classes)
密封类限制哪些类可以继承,增强类型安全:
java
public sealed interface Payment
permits CreditCardPayment, PayPalPayment, CashPayment {
void process();
}
final class CreditCardPayment implements Payment {
public void process() { /* ... */ }
}
final class PayPalPayment implements Payment {
public void process() { /* ... */ }
}
final class CashPayment implements Payment {
public void process() { /* ... */ }
}
6.2.3 模式匹配(Pattern Matching)
简化instanceof类型判断:
java
// Java 8方式
if (obj instanceof User) {
User user = (User) obj;
System.out.println(user.name());
}
// Java 17方式
if (obj instanceof User user) { // 直接声明变量
System.out.println(user.name());
}
6.2.4 文本块(Text Blocks)
多行字符串:
java
// 传统方式
String json = "{\n" +
" \"name\": \"张三\",\n" +
" \"age\": 25\n" +
"}";
// 文本块
String json = """
{
"name": "张三",
"age": 25
}
""";
6.3 Jakarta EE 9迁移:包名变更
这是最具破坏性的变更------所有javax.*包名改为jakarta.*。
6.3.1 为什么变更?
Oracle将Java EE移交给Eclipse基金会后,由于商标问题,原有的javax.*包名无法继续使用。Jakarta EE 9将所有API包名切换为jakarta.*。
6.3.2 变更范围
| 规范 | Spring 5.x(javax.*) | Spring 6.x(jakarta.*) |
|---|---|---|
| Servlet | import javax.servlet.* |
import jakarta.servlet.* |
| JPA | import javax.persistence.* |
import jakarta.persistence.* |
| Validation | import javax.validation.* |
import jakarta.validation.* |
| JTA | import javax.transaction.* |
import jakarta.transaction.* |
import javax.mail.* |
import jakarta.mail.* |
|
| WebSocket | import javax.websocket.* |
import jakarta.websocket.* |
6.3.3 代码迁移示例
java
// Spring 5.x
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.constraints.NotBlank;
@Entity
public class User {
@Id
private Long id;
@NotBlank
private String name;
}
java
// Spring 6.x
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotBlank;
@Entity
public class User {
@Id
private Long id;
@NotBlank
private String name;
}
6.3.4 依赖兼容性要求
升级到6.x时,必须确保所有第三方依赖已经支持Jakarta EE 9+:
| 组件 | 兼容版本 | 检查要点 |
|---|---|---|
| Tomcat | 10.x+ | 必须升级,Tomcat 9不支持jakarta.* |
| Jetty | 11.x+ | 同上 |
| Undertow | 2.3+ | 2.3+支持Jakarta EE |
| Hibernate | 6.x+ | Hibernate 5.x不支持jakarta.* |
| MyBatis | 3.5.10+ | 需要明确升级 |
| Thymeleaf | 3.1.0+ | 3.0.x不支持 |
| Lombok | 1.18.20+ | 需要升级以支持注解处理 |
6.4 GraalVM原生镜像与AOT编译
这是6.x最重要的新特性之一,让Spring应用可以编译成原生可执行文件。
6.4.1 什么是AOT?
AOT(Ahead-of-Time)编译是在构建阶段将应用编译成机器码,而非传统的JIT(Just-in-Time)运行时编译。Spring 6.x的AOT引擎会在构建时处理反射、动态代理等元数据,生成优化后的代码。
6.4.2 原生镜像的优势
| 维度 | 传统JAR | 原生镜像 | 提升 |
|---|---|---|---|
| 启动时间 | 3-5秒 | 50-100ms | 30-50倍 |
| 内存占用 | 500MB-1GB | 50-150MB | 70-90% |
| 镜像体积 | 18-20MB | 50-80MB | 增大(但包含运行时) |
| 峰值性能 | 高(JIT优化) | 中等(无JIT) | -20%左右 |
适用场景:
-
Serverless/FaaS应用(启动时间关键)
-
微服务(快速扩缩容)
-
容器化部署(内存受限环境)
6.4.3 启用AOT编译
Maven配置:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aot</artifactId>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>aot</id>
<goals>
<goal>aot</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
GraalVM原生镜像构建:
bash
# 安装GraalVM(需要native-image组件)
gu install native-image
# 构建原生镜像
mvn -Pnative native:compile
# 运行
./target/myapp
6.4.4 AOT限制与解决方案
原生编译对反射、动态代理、资源加载有严格限制,需要在编译时提供配置。
常见问题:
-
反射 :代码中通过
Class.forName()、obj.getClass().getMethod()等调用 -
动态代理 :使用
Proxy.newProxyInstance()创建代理 -
资源加载:动态加载文件、类路径资源
-
序列化:Java原生序列化
解决方案:
java
// 1. 使用@TypeHint提供反射配置
@TypeHint(
types = {User.class, Order.class},
access = AccessBits.ALL
)
@NativeImage
public class NativeHints {
}
// 2. 使用GraalVM Tracing Agent收集配置
// 运行应用时加上参数
java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image -jar myapp.jar
// 3. 在配置文件中手动添加
// src/main/resources/META-INF/native-image/reflect-config.json
[
{
"name": "com.example.User",
"allDeclaredFields": true,
"allPublicMethods": true
}
]
6.5 HTTP Interface客户端
Spring 6.x引入了声明式HTTP客户端接口,简化服务间调用。
6.5.1 定义接口
java
@HttpExchange("/users")
public interface UserClient {
@GetExchange("/{id}")
Mono<User> getUser(@PathVariable Long id);
@GetExchange
Flux<User> getUsers(@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size);
@PostExchange
Mono<User> createUser(@RequestBody User user);
@PutExchange("/{id}")
Mono<User> updateUser(@PathVariable Long id, @RequestBody User user);
@DeleteExchange("/{id}")
Mono<Void> deleteUser(@PathVariable Long id);
}
6.5.2 创建客户端
java
@Configuration
public class ClientConfig {
@Bean
public UserClient userClient() {
WebClient webClient = WebClient.builder()
.baseUrl("http://user-service")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builderFor(WebClientAdapter.create(webClient))
.build();
return factory.createClient(UserClient.class);
}
}
6.5.3 使用客户端
java
@Service
public class UserFacadeService {
private final UserClient userClient;
public UserFacadeService(UserClient userClient) {
this.userClient = userClient;
}
public Mono<User> getUserWithRetry(String id) {
return userClient.getUser(id)
.timeout(Duration.ofSeconds(5))
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
.onErrorResume(e -> {
log.error("获取用户失败", e);
return Mono.just(new User(id, "未知用户"));
});
}
}
6.6 可观测性增强
Spring 6.x集成了Micrometer Tracing,提供了统一的追踪、指标、日志观测模型。
6.6.1 启用可观测性
xml
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
yaml
management:
tracing:
enabled: true
sampling:
probability: 1.0 # 采样率
zipkin:
tracing:
endpoint: http://zipkin:9411/api/v2/spans
6.6.2 自定义观测
java
@Service
public class OrderService {
private final Tracer tracer;
private final ObservationRegistry observationRegistry;
public OrderService(Tracer tracer, ObservationRegistry observationRegistry) {
this.tracer = tracer;
this.observationRegistry = observationRegistry;
}
// 方式1:手动创建Span
public Order processOrder(Order order) {
Span span = tracer.nextSpan().name("processOrder").start();
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
// 业务逻辑
return doProcess(order);
} finally {
span.end();
}
}
// 方式2:使用@Observed注解
@Observed(name = "order.create",
contextualName = "creating-order",
lowCardinalityKeyValues = {"orderType", "standard"})
public Order createOrder(Order order) {
return doCreate(order);
}
// 方式3:使用Observation API
public Order updateOrder(Order order) {
return Observation.createNotStarted("order.update", observationRegistry)
.lowCardinalityKeyValue("orderType", order.getType())
.observe(() -> doUpdate(order));
}
}
6.6.3 指标收集
java
@Service
public class MetricService {
private final MeterRegistry meterRegistry;
private final Counter orderCounter;
public MetricService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.orderCounter = meterRegistry.counter("orders.created");
}
public void recordOrder(Order order) {
orderCounter.increment();
// 带标签的指标
meterRegistry.counter("orders.by.status",
Tags.of("status", order.getStatus())).increment();
// Timer
meterRegistry.timer("order.processing.time")
.record(() -> processOrder(order));
// DistributionSummary
meterRegistry.summary("order.amount")
.record(order.getAmount().doubleValue());
}
}
6.7 虚拟线程初步支持
Spring 6.x对JDK 19+引入的虚拟线程提供了初步支持。
6.7.1 配置虚拟线程
java
@Configuration
public class VirtualThreadConfig {
@Bean
public AsyncTaskExecutor applicationTaskExecutor() {
// 使用虚拟线程执行器
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadCustomizer() {
// Tomcat使用虚拟线程处理请求
return protocolHandler ->
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}
}
6.7.2 虚拟线程的优势
java
// 传统线程模型:一个请求占用一个操作系统线程
@GetMapping("/blocking")
public String blocking() throws InterruptedException {
Thread.sleep(1000); // 阻塞线程
return "done";
}
// 虚拟线程模型:阻塞时不占用操作系统线程
// 只需配置上述Executor,代码无需修改
6.8 升级指南与注意事项
6.8.1 从5.x升级到6.x检查清单
-
JDK升级到17或更高
-
全局替换
javax.*为jakarta.* -
升级所有依赖到兼容版本(Hibernate 6、Tomcat 10等)
-
检查Spring Security配置(迁移到Security 6)
-
验证JPA/Hibernate查询(Hibernate 6语法变化)
-
测试XML配置迁移(如果存在)
-
使用
spring-boot-properties-migrator检查配置属性
6.8.2 常见兼容性问题
java
// 1. Hibernate 6 ID生成器变更
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 5.x
private Long id;
// 6.x可能需要显式指定生成器
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@UuidGenerator
private UUID id;
}
// 2. Spring Security 6配置变更
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// 5.x:继承WebSecurityConfigurerAdapter
// 6.x:使用SecurityFilterChain Bean
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated());
return http.build();
}
}
第七章:Spring 7.x ------ 虚拟线程深度整合与智能韧性
7.1 版本概览
2025年底,Spring Framework 7.0发布。这是对未来云原生和现代硬件的深度优化版本,将弹性设计能力内置到框架核心。
核心升级点:
-
虚拟线程深度集成
-
核心韧性特性:@ConcurrencyLimit、@Retryable
-
模块化优化
-
可观测性增强
-
智能默认值
7.2 虚拟线程深度集成
JDK 21正式将虚拟线程纳入标准。Spring 7.x对虚拟线程的支持更加成熟,只需简单配置即可开启。
7.2.1 虚拟线程原理
什么是虚拟线程?虚拟线程(Virtual Threads)是JDK 21引入的轻量级线程,由JVM管理而非操作系统内核。单个JVM可创建百万级虚拟线程。
与传统线程对比:
| 特性 | 平台线程 | 虚拟线程 |
|---|---|---|
| 内存占用 | ~1MB/线程 | 初始几KB |
| 最大数量 | 数千 | 数百万 |
| 创建开销 | 高 | 极低 |
| 上下文切换 | 操作系统调度 | JVM调度 |
| 阻塞影响 | 阻塞操作系统线程 | 仅挂起虚拟线程 |
工作原理:
-
载体线程:虚拟线程实际运行在称为"载体线程"的平台线程上
-
挂载/卸载:执行时挂载到载体线程,阻塞时卸载,载体线程可运行其他虚拟线程
-
续体:虚拟线程基于续体实现,可在阻塞点保存执行状态,后续恢复
7.2.2 一行配置开启虚拟线程
yaml
spring:
threads:
virtual:
enabled: true # 全局启用虚拟线程
开启后,以下组件自动使用虚拟线程:
-
Tomcat、Jetty等Web容器
-
@Async异步方法 -
@Scheduled定时任务 -
@EventListener事件监听器 -
WebClient异步HTTP调用
7.2.3 代码级控制
java
@Configuration
public class VirtualThreadConfig {
// 自定义虚拟线程执行器
@Bean
@ConditionalOnProperty(name = "spring.threads.virtual.enabled", havingValue = "true")
public AsyncTaskExecutor applicationTaskExecutor() {
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
// 按需创建虚拟线程
@Bean
public Executor virtualThreadExecutor() {
return Executors.newVirtualThreadPerTaskExecutor();
}
}
@Service
public class SomeService {
@Async // 自动使用虚拟线程
public CompletableFuture<String> processAsync() {
// 阻塞操作不会阻塞操作系统线程
String result = remoteApi.call();
return CompletableFuture.completedFuture(result);
}
// 手动创建虚拟线程
public void doWork() {
Thread.startVirtualThread(() -> {
// 在虚拟线程中执行
});
}
}
7.2.4 性能提升预估
根据Spring官方测试,在4C8G机器上,支付网关场景压测结果:
| 指标 | 传统线程池 | 虚拟线程 | 提升 |
|---|---|---|---|
| RPS | 12,000 | 85,000 | +608% |
| P99延迟 | 120ms | 18ms | -85% |
| CPU占用 | 75% | 45% | -40% |
| 线程数 | 200 | 10,000 | +50x |
7.2.5 注意事项与最佳实践
1. 避免synchronized
java
// 不推荐 - synchronized会"钉住"载体线程
public synchronized void doSomething() {
Thread.sleep(1000); // 阻塞时,载体线程也被占用
}
// 推荐 - 使用ReentrantLock
private final Lock lock = new ReentrantLock();
public void doSomething() {
lock.lock();
try {
Thread.sleep(1000);
} finally {
lock.unlock();
}
}
2. 慎用ThreadLocal
java
// 不推荐 - 虚拟线程数量大,ThreadLocal可能导致内存问题
private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
// 推荐 - JDK 22引入的ScopedValue
private static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
public void processRequest(User user) {
ScopedValue.where(CURRENT_USER, user)
.run(() -> {
// 在这个作用域内可以访问CURRENT_USER
doWork();
});
}
3. 监控虚拟线程
java
@RestController
public class ThreadMonitorController {
@GetMapping("/virtual-threads")
public Map<String, Object> getVirtualThreadStats() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long virtualThreadCount = Thread.getAllStackTraces().keySet().stream()
.filter(Thread::isVirtual)
.count();
return Map.of(
"virtualThreadCount", virtualThreadCount,
"platformThreadCount", Thread.activeCount(),
"totalStartedThreadCount", threadMXBean.getTotalStartedThreadCount(),
"peakThreadCount", threadMXBean.getPeakThreadCount()
);
}
}
7.3 核心韧性特性
Spring 7.x将弹性设计能力内置到框架核心,提供了声明式的限流和重试支持。
7.3.1 @ConcurrencyLimit:并发限流
对于某些任务和资源,可能需要限制并发级别。并发限流可以保护目标资源不被过多线程同时访问,类似于线程池或连接池的池大小限制效果。这种限制在虚拟线程环境中特别有用,因为虚拟线程通常没有线程池限制。
基本用法:
java
@Service
@EnableResilientMethods // 启用韧性方法支持
public class NotificationService {
// 限制并发数为10
@ConcurrencyLimit(10)
public void sendNotification(Notification notification) {
this.jmsClient.destination("notifications").send(notification);
}
// 限制并发数为1,相当于锁,确保串行访问
@ConcurrencyLimit(1)
public void processPayment(Payment payment) {
// 需要严格串行的操作
}
}
工作原理 :
@ConcurrencyLimit通过AOP在方法调用前后进行并发计数,超过限制时阻塞或抛出异常。
配置选项:
java
@ConcurrencyLimit(
value = 5, // 最大并发数
blocking = true, // 超过限制时是否阻塞(false则抛出异常)
timeout = 1000, // 阻塞超时时间(毫秒)
exceptionType = TooManyConcurrentRequestsException.class // 非阻塞模式抛出的异常
)
public void limitedMethod() {
// ...
}
7.3.2 @Retryable:声明式重试
俗话说:如果一开始不成功,那就再试一次。某些类别的错误通常可以成功重试。
基本用法:
java
@Service
@EnableResilientMethods
public class MessageService {
// 默认:最多3次重试,延迟1秒
@Retryable
public void sendMessage(Message message) {
this.kafkaTemplate.send("topic", message);
}
// 指定异常类型
@Retryable(includes = MessageDeliveryException.class)
public void sendWithRetry(Message message) {
this.jmsClient.destination("notifications").send(message);
}
// 完整配置
@Retryable(
includes = {TimeoutException.class, DeadlockLoserDataAccessException.class},
excludes = {IllegalArgumentException.class},
maxAttempts = 5, // 最大重试次数(注意:这是额外重试次数,不是总次数)
delay = 100, // 初始延迟(毫秒)
multiplier = 2, // 延迟倍数(指数退避)
maxDelay = 1000, // 最大延迟
jitter = 10 // 随机抖动,避免同时重试
)
public void sendWithComplexRetry(Message message) {
// 业务逻辑
}
}
与传统Spring Retry的区别:
-
maxAttempts在Spring 7.x中只表示额外重试次数,总调用次数 = maxAttempts + 1 -
内置在spring-core模块,无需额外依赖
-
支持响应式返回类型,自动与Reactor集成
响应式方法支持:
java
@Retryable(maxAttempts = 5, delay = 100)
public Mono<Void> sendReactiveMessage(Message message) {
return Mono.fromRunnable(() -> {
// 响应式操作
}); // 这个Mono会自动被装饰重试逻辑
}
7.3.3 RetryTemplate:编程式重试
与@Retryable的声明式方式相对,RetryTemplate提供了编程式API,用于重试任意代码块。
基本用法:
java
// 默认配置(3次重试,延迟1秒)
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.execute(() -> {
jmsClient.destination("notifications").send(message);
return null;
});
// 带返回值的操作
String result = retryTemplate.execute(() -> {
return httpClient.get("https://api.example.com/data");
});
自定义RetryPolicy:
java
// 使用工厂方法
RetryTemplate retryTemplate = new RetryTemplate(
RetryPolicy.withMaxAttempts(5) // 5次重试
);
// 使用Builder模式
RetryPolicy retryPolicy = RetryPolicy.builder()
.includes(MessageDeliveryException.class)
.excludes(IllegalArgumentException.class)
.maxAttempts(5)
.delay(Duration.ofMillis(100))
.multiplier(2.0)
.maxDelay(Duration.ofSeconds(1))
.jitter(Duration.ofMillis(10))
.build();
RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
retryTemplate.execute(() -> {
// 需要重试的操作
});
异步重试:
java
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return retryTemplate.execute(() -> {
return remoteService.call();
});
});
7.4 模块化优化
Spring 7.x进一步模块化,核心jar包体积更小,启动时间进一步缩短。
7.4.1 模块拆分
-
spring-core:进一步精简,只保留最核心的工具类
-
spring-beans:Bean定义和DI核心
-
spring-context:ApplicationContext、事件、国际化
-
spring-aop:AOP支持(可选的)
-
spring-expression:SpEL表达式(可选的)
7.4.2 按需加载
自动配置被拆分为独立模块,按需加载:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 会自动引入必要的模块,不会有多余依赖 -->
</dependency>
7.4.3 启动优化
-
启动时间:相比6.x减少约30%
-
内存占用:基础容器降低约20%
-
原生镜像体积:减小约15%
7.5 可观测性增强(7.x版)
7.5.1 新增Actuator端点
| 端点路径 | 功能 |
|---|---|
/actuator/virtual-threads |
虚拟线程数量、状态、阻塞栈 |
/actuator/thread-dump/virtual |
虚拟线程的线程转储 |
/actuator/concurrency-limits |
当前并发限流状态 |
/actuator/retry-stats |
重试统计信息 |
7.5.2 结构化日志
支持直接输出JSON格式日志,便于ELK等日志系统收集:
yaml
logging:
structured:
format: json
include:
- traceId
- spanId
- userId
- requestId
输出示例:
json
{
"timestamp": "2026-02-17T10:15:30.123Z",
"level": "INFO",
"logger": "com.example.OrderService",
"message": "Order created successfully",
"service": "order-service",
"traceId": "4f2e1c3a-8b7d-4e9f-9a1c-2d3e4f5a6b7c",
"spanId": "a1b2c3d4e5f6",
"userId": "12345",
"orderId": "67890"
}
7.6 智能默认值
Spring 7.x引入了更智能的默认配置,减少手动配置:
| 特性 | 6.x默认 | 7.x默认 |
|---|---|---|
| 虚拟线程 | 未启用 | 如JDK 21+则自动检测启用 |
| 连接池 | HikariCP | HikariCP + 自动调优 |
| JSON库 | Jackson | Jackson(若存在) |
| 可观测性 | 需手动配置 | 自动配置基础指标 |
7.7 实用知识点:虚拟线程迁移指南
从传统线程模型迁移到虚拟线程的建议步骤:
第1步:环境检查
-
升级JDK到21 LTS或更高
-
检查代码中是否有
synchronized、ThreadLocal使用
第2步:小范围试用
yaml
spring:
threads:
virtual:
enabled: true
# 先从特定Executor开始
executor:
task:
enabled: true # 只对@Async生效
第3步:监控分析
java
// 添加监控,检查"钉住"的虚拟线程
@EventListener
public void handleVirtualThreadPinned(VirtualThreadPinnedEvent event) {
log.warn("虚拟线程被钉住: {}", event.getStackTrace());
}
第4步:全量开启
yaml
spring:
threads:
virtual:
enabled: true
# 开启所有组件的虚拟线程支持
tomcat:
enabled: true
scheduler:
enabled: true
taskExecutor:
enabled: true
第5步:配合韧性特性
java
@ConcurrencyLimit(20) // 保护数据库连接池
@Retryable(maxAttempts = 3) // 处理临时性故障
public Order processOrder(Order order) {
// 业务逻辑
}
第八章:版本演进全景图与升级指南
8.1 版本演进核心脉络
| 版本 | 发布时间 | Java基线 | 核心主题 | 代表特性 |
|---|---|---|---|---|
| 1.x | 2004 | J2SE 1.3 | IoC/AOP奠基 | XML配置、声明式事务 |
| 2.x | 2006 | J2SE 5.0 | 注解初现 | @Component、@AspectJ |
| 3.x | 2009 | Java 5 | Java配置 | @Configuration、SpEL |
| 4.x | 2013 | Java 6+ | Java 8准备 | WebSocket、@Conditional |
| 5.x | 2017 | Java 8+ | 响应式革命 | WebFlux、Kotlin支持 |
| 6.x | 2022 | Java 17+ | 云原生重构 | Jakarta EE、AOT、GraalVM |
| 7.x | 2025 | Java 21+ | 智能韧性 | 虚拟线程、@ConcurrencyLimit |
8.2 配置方式演进
| 版本 | 核心配置方式 | 特点 |
|---|---|---|
| 1.x | XML | 一切皆XML,配置繁琐 |
| 2.x | XML + 注解 | 混搭风格,基础架构用XML,业务用注解 |
| 3.x | Java配置 + 注解 | @Configuration替代XML,纯注解开发 |
| 4.x | Java配置 + 注解 | 条件配置、泛型注入增强 |
| 5.x | Java配置 + 注解 | 响应式编程,函数式配置 |
| 6.x | Java配置 + 注解 | AOT支持,代码编译期优化 |
| 7.x | Java配置 + 注解 | 虚拟线程,声明式韧性 |
8.3 版本选择建议
| 场景 | 推荐版本 | 说明 |
|---|---|---|
| 维护1.x/2.x老项目 | 暂留 | 评估升级成本,优先考虑2.x最新版 |
| 3.x/4.x稳定项目 | 逐步升级6.x | 需测试Jakarta迁移和Hibernate 6 |
| 新项目(常规) | 6.x + JDK 21 | 稳定成熟,社区支持广泛 |
| 新项目(高并发) | 7.x + JDK 21 | 虚拟线程大幅简化高并发编程 |
| 云原生/Serverless | 6.x/7.x + GraalVM | 启动快、内存少,适合K8s |
8.4 升级路径建议
从1.x升级到2.x:
-
先升级到1.5.x(最后一个1.x版本)
-
关注废弃API,如
SimpleJdbcTemplate、HibernateTemplate -
注意注解与XML混用时的Bean名称冲突
从2.x升级到3.x:
-
学习JavaConfig配置方式
-
关注
@Configuration与XML的互操作性 -
可以使用
@ImportResource逐步迁移
从3.x升级到4.x:
-
升级JDK到8(如有条件)
-
利用Java 8新特性重构代码(Lambda、日期API)
-
关注WebSocket新模块
从4.x升级到5.x:
-
响应式编程需要思维转变
-
评估是否引入WebFlux(如无必要,保持MVC)
-
注意WebFlux与MVC的共存问题
从5.x升级到6.x:
-
JDK 17升级:必须
-
Jakarta迁移 :批量替换
javax.*到jakarta.* -
第三方依赖检查:Hibernate 6、Tomcat 10等
-
Spring Security 6:配置方式重构
-
使用
spring-boot-properties-migrator检查配置属性
从6.x升级到7.x:
-
JDK 21升级:推荐
-
虚拟线程测试 :检查
synchronized、ThreadLocal使用 -
Spring Retry迁移 :使用内置
@Retryable -
开启韧性特性 :
@EnableResilientMethods
第九章:面试题库
5道难度递增的基础面试题
第1题:Spring Framework的核心是什么?请简述IoC和AOP的概念。
难度:⭐
参考答案:
Spring Framework的核心是控制反转(IoC) 和面向切面编程(AOP)。
-
控制反转(IoC) :将对象的创建和依赖关系的管理交给Spring容器,而不是由对象自身控制。传统开发中,对象通过
new关键字主动创建依赖对象;IoC模式下,对象被动接收容器注入的依赖。这降低了组件间的耦合度,提高了可测试性。 -
面向切面编程(AOP):将日志、事务、安全等横切关注点从业务逻辑中剥离出来,通过动态代理在运行时织入。这样业务代码只需关注核心逻辑,而非样板式的横切代码。
一句话总结:IoC管理对象生命周期和依赖关系,AOP处理横切关注点。
第2题:请对比Spring 2.x、3.x和4.x在配置方式上的演进。
难度:⭐⭐
参考答案:
| 版本 | 配置方式 | 代表注解/特性 | 特点 |
|---|---|---|---|
| 2.x | XML为主,注解为辅 | @Component、@Autowired |
基础架构用XML,业务用注解 |
| 3.x | Java配置崛起 | @Configuration、@Bean |
完全零XML成为可能 |
| 4.x | 条件配置增强 | @Conditional、泛型注入 |
更灵活的Bean定义 |
演进趋势 :从XML驱动 到注解驱动 ,再到Java配置驱动,配置越来越精简、类型安全越来越高。
第3题:Spring 5.x引入的WebFlux是什么?它与传统的Spring MVC有什么区别?
难度:⭐⭐⭐
参考答案:
WebFlux是Spring 5.x引入的响应式Web框架,基于Reactor实现,支持非阻塞、背压等特性。
核心区别:
| 维度 | Spring MVC | Spring WebFlux |
|---|---|---|
| 线程模型 | 每请求一线程(阻塞) | 事件循环(少量线程) |
| 适用场景 | 传统CRUD、计算密集型 | I/O密集型、高并发 |
| 数据库驱动 | JDBC(阻塞) | R2DBC(非阻塞) |
| 底层容器 | Servlet容器(Tomcat等) | Netty、Servlet 3.1+ |
| 编程风格 | 命令式 | 响应式(Mono/Flux) |
适用建议:
-
WebFlux适合高并发I/O密集型应用(如API网关、实时推送)
-
WebMVC适合传统应用、计算密集型任务,或团队不熟悉响应式编程的场景
第4题:Spring 6.x最重要的两个变化是什么?升级时需要注意哪些问题?
难度:⭐⭐⭐⭐
参考答案:
两个核心变化:
-
Java 17成为基线:强制使用JDK 17+,利用密封类、模式匹配、记录类等新特性
-
Jakarta EE 9迁移 :所有
javax.*包名改为jakarta.*(JPA、Servlet、Validation等)
升级注意事项:
-
JDK升级:安装JDK 17,配置IDE和CI环境
-
包名替换 :全局替换
javax.*为jakarta.*(IDE批量替换) -
第三方依赖检查:
| 依赖 | 兼容版本要求 |
|---|---|
| Tomcat | 10.x+ |
| Jetty | 11.x+ |
| Hibernate | 6.x+ |
| MyBatis | 3.5.10+ |
| Thymeleaf | 3.1.0+ |
-
Spring Security 6 :配置方式重构,不再继承
WebSecurityConfigurerAdapter -
Hibernate 6适配:检查实体类的ID生成策略,测试JPQL查询
第5题:Spring 7.x中新增的@ConcurrencyLimit和@Retryable注解解决了什么问题?请说明它们的典型使用场景。
难度:⭐⭐⭐⭐⭐
参考答案:
@ConcurrencyLimit:解决并发限流问题,保护下游资源不被过多线程同时访问。
典型场景:
-
虚拟线程环境 :由于虚拟线程没有线程池限制,需通过
@ConcurrencyLimit控制对数据库、消息队列等资源的并发访问 -
敏感资源保护 :如支付接口,设置
@ConcurrencyLimit(1)确保串行处理 -
外部API调用:限制对第三方API的并发请求,避免被限流
java
@ConcurrencyLimit(10)
public void callExternalApi() {
// 最多10个并发调用
}
@Retryable:解决临时性故障的重试问题,提高系统韧性。
典型场景:
-
网络抖动:远程服务调用偶尔超时,可重试
-
数据库死锁:死锁发生后,等待片刻重试
-
消息发送失败:JMS/Kafka发送失败,重试几次
java
@Retryable(
includes = {TimeoutException.class, DeadlockLoserDataAccessException.class},
maxAttempts = 5,
delay = 100,
multiplier = 2
)
public void sendMessage() {
// 网络请求
}
设计理念:Spring 7.x将弹性设计能力内置到框架核心,让开发者用声明式的方式为系统增加容错能力。
3道实战场景题
场景1:老项目升级之痛
场景描述:
你接手了一个基于Spring 3.2.8的老项目,项目中大量使用XML配置(数据源、事务、AOP),同时混用@Autowired等注解。项目启动缓慢(约60秒),经常出现配置冲突。公司希望将其升级到Spring 6.x。作为技术负责人,请设计详细的升级方案。
考察点:版本升级经验、风险控制、迁移策略
参考思路:
升级方案分为四个阶段:
第一阶段:现状评估(1周)
-
依赖分析:列出所有第三方依赖,识别兼容性问题
-
代码扫描:统计XML配置文件数量、自定义标签、
javax.*使用位置 -
测试覆盖率:确保核心功能有自动化测试
第二阶段:先升级到Spring 4.x(2周)
xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>4.3.30.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
-
解决4.x内部的废弃API警告
-
将JDK升级到8(4.x需要)
-
使用
@Conditional逐步替换部分XML条件配置
第三阶段:升级到Spring 5.x(2周)
-
JDK升级到8或11
-
评估是否引入WebFlux(如无必要,保持MVC)
-
测试JUnit 5迁移
-
使用新版
RestTemplate或考虑WebClient
第四阶段:升级到Spring 6.x(3周)
-
JDK升级到17:必须
-
Jakarta迁移 :全局替换
javax.*到jakarta.* -
XML配置迁移 :将核心XML配置逐步迁移到
@Configuration类 -
第三方依赖升级:Hibernate 6、Tomcat 10、MyBatis 3.5.10+
-
Spring Security 5→6:重构安全配置
风险应对:
| 风险点 | 可能性 | 应对措施 |
|---|---|---|
| XML配置与Java配置冲突 | 中 | 每迁移一个模块测试一次 |
| Hibernate 6查询语法变化 | 中 | 用测试覆盖所有复杂查询 |
| 包名替换遗漏 | 中 | 使用IDE全局替换+代码审查 |
| 第三方依赖不兼容 | 高 | 提前调研,准备备选方案 |
场景2:高并发性能优化
场景描述:
你们的订单系统基于Spring 5.2.x开发,每天处理约800万订单。每逢大促,系统出现响应时间飙升(从80ms到800ms)、CPU使用率居高不下(90%以上)。通过监控发现,线程数经常达到2500+,大量线程处于BLOCKED状态。作为架构师,你会如何优化?如果允许将Spring版本升级到7.x,会带来什么新方案?
考察点:性能调优、线程模型理解、虚拟线程应用
参考思路:
问题诊断 :从现象(线程数高、大量BLOCKED、CPU高)可以推断:系统是I/O密集型阻塞。大量线程在等待数据库查询、Redis访问、远程服务调用。
第一阶段:快速优化(2周)
- 调整线程池参数:
yaml
server:
tomcat:
max-threads: 500 # 减少最大线程数,避免过度竞争
accept-count: 200
- 数据库连接池优化:
yaml
spring:
datasource:
hikari:
maximum-pool-size: 50
connection-timeout: 3000
-
添加缓存层:使用Redis缓存热点数据
-
超时设置:为RestTemplate、Feign配置合理的连接超时和读取超时
第二阶段:架构优化(1-2个月)
-
异步化改造:将非核心操作(发短信、邮件)改为异步
-
读写分离:主库写、从库读
-
局部响应式改造:对高并发接口使用WebFlux
第三阶段:升级到Spring 7.x + 虚拟线程(长期)
这是最彻底的解决方案:一行配置开启虚拟线程
yaml
spring:
threads:
virtual:
enabled: true
原有代码无需修改,自动受益:
java
// 这些阻塞调用在虚拟线程下不会阻塞操作系统线程
Order order = orderRepository.findById(orderId).orElseThrow();
User user = userService.getUser(order.getUserId());
List<Product> products = productService.getProducts(orderId);
同时利用Spring 7.x的弹性特性:
java
@ConcurrencyLimit(20) // 保护数据库连接池
@Retryable(maxAttempts = 3) // 处理临时性故障
public Order processOrder(Order order) {
// 业务逻辑
}
性能提升预估:
| 指标 | 优化前 | 第二阶段后 | 第三阶段后 |
|---|---|---|---|
| 最大并发 | 2500 | 5000 | 50000+ |
| P99延迟 | 800ms | 200ms | 50ms |
| CPU使用率 | 90% | 70% | 40% |
| 线程数 | 2500+ | 500 | 500(载体)+ 50000(虚拟) |
场景3:云原生部署挑战
场景描述:
你们公司计划将所有Spring应用迁移到Kubernetes平台。但现有应用(基于Spring 5.3.x)存在以下问题:
-
启动时间35-45秒,Pod就绪检查频繁失败
-
内存占用高(基础堆1GB),资源成本高
-
滚动更新时约3%请求失败
作为架构师,请设计解决方案,重点考虑Spring 6.x/7.x的新特性。
考察点:云原生知识、GraalVM应用、K8s最佳实践
参考思路:
核心方案 :升级到Spring 6.x + GraalVM原生镜像 + 后续演进到Spring 7.x
第一阶段:升级到Spring 6.x + GraalVM(1个月)
-
升级到Spring 6.x:
-
JDK升级到17
-
Jakarta EE 9迁移
-
第三方依赖升级
-
-
添加GraalVM原生镜像支持:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-native</artifactId>
</dependency>
-
代码适配:
-
使用
@TypeHint配置反射需求 -
避免动态代理,静态化代理类
-
使用Tracing Agent收集配置
-
-
构建原生镜像:
bash
mvn -Pnative native:compile
预期收益:
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 启动时间 | 40秒 | <100毫秒 | 400x |
| 内存占用 | 1GB | 120MB | -88% |
| 镜像体积 | 180MB | 70MB | -61% |
第二阶段:升级到Spring 7.x + 虚拟线程(后续)
-
升级到JDK 21 + Spring 7.x
-
开启虚拟线程,进一步提升并发能力
-
使用@ConcurrencyLimit保护资源
第三阶段:Kubernetes配置优化
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # 确保至少一个Pod可用
template:
spec:
containers:
- name: app
image: order-service:latest
startupProbe:
httpGet:
path: /actuator/health/startup
initialDelaySeconds: 0 # 原生镜像启动极快
periodSeconds: 1
resources:
requests:
memory: "128Mi" # 大幅降低资源请求
cpu: "500m"
limits:
memory: "256Mi"
cpu: "1000m"
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"] # 优雅停机
流量无损更新:
-
配置
spring.lifecycle.timeout-per-shutdown-phase=20s -
设置
server.shutdown=graceful
最终收益:
-
启动速度:40秒 → 100毫秒,扩容秒级响应
-
资源成本:降低70%以上
-
滚动更新:3%失败率 → 接近零损失
-
并发能力:虚拟线程带来10倍+提升
结语
从2004年到2026年,Spring Framework走过了七个大版本的演进之路。从1.x的"XML汪洋"到2.x的"注解初现",从3.x的"Java配置"到4.x的"WebSocket",从5.x的"响应式革命"到6.x的"云原生重构",再到7.x的"智能韧性"------每一次迭代都紧跟Java生态的发展脉搏,回应着开发者的真实需求。
理解这些演进历程,不仅有助于我们在面试中展现技术深度,更重要的是能在实际项目中做出正确的技术选型,写出更优雅、更高效的代码。
技术不断向前,但Spring的核心思想始终未变:让开发者专注于业务逻辑,而非框架的复杂性。这或许正是它能统治Java企业级开发近二十年的根本原因。
附录:参考资料
-
spring1.x详解介绍. CSDN博客, 2025.
-
Spring Framework\]注解开发①(纯注解开发). 阿里云开发者社区, 2022.
-
Spring 6.x 详解介绍. CSDN博客, 2025.
-
Core Spring Resilience Features: @ConcurrencyLimit, @Retryable, and RetryTemplate. Spring Official Blog, 2025.
-
推荐收藏系列:Spring boot 2.x注解Annotation大全. 腾讯云开发者社区, 2019.
-
java-study-springboot-基础学习-01-Spring的发展. UCloud优刻得, 2019.
-
Spring 4 官方文档学习(十四)WebSocket支持. CSDN文库.