从“XML汪洋”到“智能原生”:Spring Framework 1.x 到 7.x 演进全记录与核心知识点详解(超详细版)

引言

如果你是一名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.xmlweb.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装配规则

  1. 默认按类型装配

  2. 如果找到多个类型匹配的Bean,尝试按名称装配(字段名匹配Bean ID)

  3. 如果仍无法确定,抛出异常

  4. 可以通过@Qualifier指定Bean名称

  5. 可以通过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配置:

  1. 第一阶段:将基础设施配置(数据源、事务管理器)迁移到Java配置

  2. 第二阶段:将数据访问层(DAO)配置迁移到Java配置

  3. 第三阶段:将业务逻辑层(Service)配置迁移到Java配置

  4. 第四阶段:将Web层(Controller)配置迁移到Java配置

  5. 第五阶段:移除所有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中使用LocalDateLocalDateTime等类型。

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.*
Mail 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限制与解决方案

原生编译对反射、动态代理、资源加载有严格限制,需要在编译时提供配置。

常见问题

  1. 反射 :代码中通过Class.forName()obj.getClass().getMethod()等调用

  2. 动态代理 :使用Proxy.newProxyInstance()创建代理

  3. 资源加载:动态加载文件、类路径资源

  4. 序列化: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调度
阻塞影响 阻塞操作系统线程 仅挂起虚拟线程

工作原理

  1. 载体线程:虚拟线程实际运行在称为"载体线程"的平台线程上

  2. 挂载/卸载:执行时挂载到载体线程,阻塞时卸载,载体线程可运行其他虚拟线程

  3. 续体:虚拟线程基于续体实现,可在阻塞点保存执行状态,后续恢复

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或更高

  • 检查代码中是否有synchronizedThreadLocal使用

第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,如SimpleJdbcTemplateHibernateTemplate

  • 注意注解与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升级:推荐

  • 虚拟线程测试 :检查synchronizedThreadLocal使用

  • 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最重要的两个变化是什么?升级时需要注意哪些问题?

难度:⭐⭐⭐⭐

参考答案

两个核心变化

  1. Java 17成为基线:强制使用JDK 17+,利用密封类、模式匹配、记录类等新特性

  2. Jakarta EE 9迁移 :所有javax.*包名改为jakarta.*(JPA、Servlet、Validation等)

升级注意事项

  1. JDK升级:安装JDK 17,配置IDE和CI环境

  2. 包名替换 :全局替换javax.*jakarta.*(IDE批量替换)

  3. 第三方依赖检查

依赖 兼容版本要求
Tomcat 10.x+
Jetty 11.x+
Hibernate 6.x+
MyBatis 3.5.10+
Thymeleaf 3.1.0+
  1. Spring Security 6 :配置方式重构,不再继承WebSecurityConfigurerAdapter

  2. 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周)

  1. 调整线程池参数

yaml

复制代码
server:
  tomcat:
    max-threads: 500  # 减少最大线程数,避免过度竞争
    accept-count: 200
  1. 数据库连接池优化

yaml

复制代码
spring:
  datasource:
    hikari:
      maximum-pool-size: 50
      connection-timeout: 3000
  1. 添加缓存层:使用Redis缓存热点数据

  2. 超时设置:为RestTemplate、Feign配置合理的连接超时和读取超时

第二阶段:架构优化(1-2个月)

  1. 异步化改造:将非核心操作(发短信、邮件)改为异步

  2. 读写分离:主库写、从库读

  3. 局部响应式改造:对高并发接口使用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个月)

  1. 升级到Spring 6.x

    • JDK升级到17

    • Jakarta EE 9迁移

    • 第三方依赖升级

  2. 添加GraalVM原生镜像支持

xml

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-native</artifactId>
</dependency>
  1. 代码适配

    • 使用@TypeHint配置反射需求

    • 避免动态代理,静态化代理类

    • 使用Tracing Agent收集配置

  2. 构建原生镜像

bash

复制代码
mvn -Pnative native:compile

预期收益

指标 优化前 优化后 提升
启动时间 40秒 <100毫秒 400x
内存占用 1GB 120MB -88%
镜像体积 180MB 70MB -61%

第二阶段:升级到Spring 7.x + 虚拟线程(后续)

  1. 升级到JDK 21 + Spring 7.x

  2. 开启虚拟线程,进一步提升并发能力

  3. 使用@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企业级开发近二十年的根本原因。

附录:参考资料

  1. spring1.x详解介绍. CSDN博客, 2025.

  2. Spring Framework\]注解开发①(纯注解开发). 阿里云开发者社区, 2022.

  3. Spring 6.x 详解介绍. CSDN博客, 2025.

  4. Core Spring Resilience Features: @ConcurrencyLimit, @Retryable, and RetryTemplate. Spring Official Blog, 2025.

  5. 推荐收藏系列:Spring boot 2.x注解Annotation大全. 腾讯云开发者社区, 2019.

  6. java-study-springboot-基础学习-01-Spring的发展. UCloud优刻得, 2019.

  7. Spring 4 官方文档学习(十四)WebSocket支持. CSDN文库.

相关推荐
追随者永远是胜利者1 小时前
(LeetCode-Hot100)56. 合并区间
java·算法·leetcode·职场和发展·go
追随者永远是胜利者2 小时前
(LeetCode-Hot100)55. 跳跃游戏
java·算法·leetcode·游戏·go
知识即是力量ol2 小时前
Java 虚拟机:JVM篇
java·jvm·八股
快乐zbc2 小时前
苍穹外卖 - 菜品起售/停售复习笔记
java·笔记
Cosmoshhhyyy3 小时前
《Effective Java》解读第41条:用标记接口定义类型
java·开发语言
Anastasiozzzz3 小时前
深入浅出:理解控制反转 (IoC) 与 Spring 的核心实现
java·后端·spring
前路不黑暗@3 小时前
Java项目:Java脚手架项目的 B 端用户服务(十四)
android·java·开发语言·spring boot·笔记·学习·spring cloud
亓才孓4 小时前
[SpringBoot]UnableToConnectException : Public Key Retrieval is not allowed
java·数据库·spring boot
嵌入式×边缘AI:打怪升级日志4 小时前
编写Bootloader实现下载功能
java·前端·网络