第一章:Spring 框架基础与 IoC 容器
1.1 Spring 简介与核心概念
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
getBean | Object getBean(String name) | 从 Spring 容器中获取指定名称的 Bean 实例 | context.getBean("userService") | 返回的是 Object 类型,需手动强转;建议使用泛型版本 |
getBean (泛型) | <T> T getBean(Class<T> requiredType) | 根据类型从容器中获取 Bean 实例 | UserService service = context.getBean(UserService.class) | 推荐使用此方式,类型安全,无需强转 |
getBean (带参数) | Object getBean(String name, Object... args) | 获取带有构造参数或工厂参数的 Bean | context.getBean("orderService", orderId) | 用于动态传参创建 Bean,适用于原型作用域 |
1.2 控制反转(IoC)与依赖注入(DI)
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
setApplicationContext | void setApplicationContext(ApplicationContext applicationContext) throws BeansException | 实现 ApplicationContextAware 接口后可获取上下文 | public void setApplicationContext(...) { this.context = applicationContext; } | 用于非 Bean 类中获取 Spring 容器,慎用,破坏 IoC 原则 |
@DependsOn | @DependsOn("beanName") | 指定 Bean 的初始化顺序 | @Component @DependsOn("dataSource") class DataInitializer { } | 保证被依赖的 Bean 先于当前 Bean 初始化 |
setBeanName | void setBeanName(String name) | 实现 BeanNameAware 接口获取 Bean 的名称 | public void setBeanName(String name) { this.beanName = name; } | 用于调试或日志记录,了解当前 Bean 名称 |
1.3 Bean 的定义与配置方式(XML 与 注解)
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
registerBeanDefinition | void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) | 动态向容器注册 Bean 定义 | beanDefinitionRegistry.registerBeanDefinition("myBean", bd) | 通常在 BeanFactoryPostProcessor 中使用,不推荐运行时频繁调用 |
loadBeanDefinitions | int loadBeanDefinitions(Resource resource) | 从 XML 资源加载 Bean 定义 | xmlBeanFactory.loadBeanDefinitions(new ClassPathResource("beans.xml")) | 传统方式,现多用于兼容老项目 |
scan | void scan(String... basePackages) | 扫描指定包下的注解类并注册为 Bean | ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry); scanner.scan("com.example"); | 用于自定义扫描逻辑,Spring Boot 自动配置中常见 |
1.4 ApplicationContext 与 BeanFactory
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
getBean | <T> T getBean(Class<T> requiredType) | 从容器中获取指定类型的 Bean 实例 | UserService userService = context.getBean(UserService.class); | ApplicationContext 推荐使用此方法,类型安全 |
containsBean | boolean containsBean(String name) | 检查容器中是否包含指定名称的 Bean | if (context.containsBean("dataSource")) { ... } | 可用于条件初始化逻辑,避免 NoSuchBeanDefinitionException |
isSingleton | boolean isSingleton(String name) | 判断指定名称的 Bean 是否为单例 | boolean isSingle = context.isSingleton("service"); | 注意:原型(prototype)Bean 每次获取都是新实例 |
isPrototype | boolean isPrototype(String name) | 判断指定名称的 Bean 是否为原型作用域 | boolean isProto = context.isPrototype("task"); | 与 isSingleton 互斥,仅用于判断作用域 |
getType | Class<?> getType(String name) | 获取指定名称 Bean 的类型 | Class<?> clazz = context.getType("userRepository"); | 可能返回 null(如 FactoryBean 或未加载) |
getAliases | String[] getAliases(String name) | 获取指定 Bean 名称的所有别名 | String[] aliases = context.getAliases("userService"); | 一个 Bean 可有多个别名,用于逻辑解耦或测试 |
1.5 Bean 的作用域与生命周期
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
setScope | void setScope(String scopeName) | 设置 Bean 的作用域(在 BeanDefinition 中) | beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE); | 通常在 BeanFactoryPostProcessor 中修改,不可运行时动态切换 |
setInitMethodName | void setInitMethodName(String initMethodName) | 指定初始化方法名 | beanDefinition.setInitMethodName("init"); | 配合 InitializingBean 或 @PostConstruct 使用 |
setDestroyMethodName | void setDestroyMethodName(String destroyMethodName) | 指定销毁方法名 | beanDefinition.setDestroyMethodName("cleanup"); | 仅单例 Bean 有效,容器关闭时调用 |
afterPropertiesSet | void afterPropertiesSet() throws Exception | 实现 InitializingBean 接口的初始化回调 | public class MyBean implements InitializingBean { public void afterPropertiesSet() { ... } } | 不推荐使用,建议用 @PostConstruct,避免耦合 Spring API |
destroy | void destroy() throws Exception | 实现 DisposableBean 接口的销毁回调 | public class MyBean implements DisposableBean { public void destroy() { ... } } | 同上,建议用 @PreDestroy 替代 |
说明:
- BeanFactory 是 Spring 的底层容器接口,提供基本的依赖查找能力。
- ApplicationContext 是 BeanFactory 的子接口,提供更多企业级功能(如事件发布、国际化、资源加载等),是实际开发中推荐使用的容器。
- Bean 的作用域包括:
singleton(默认)、prototype、request、session、application、websocket。
- 生命周期回调建议使用 JSR-250 注解
@PostConstruct 和 @PreDestroy,而非实现 Spring 特定接口,以降低耦合。
第二章:Spring 注解驱动开发
2.1 常用注解概述(@Component, @Service, @Repository, @Controller)
| 注解 | 语法位置 | 用途 | 代码示例 | 注意事项 |
|---|
@Component | 类级别 | 通用组件注解,标识一个 Spring 管理的 Bean | @Component public class EmailService { } | 最基础的注解,其他如 @Service 是其衍生 |
@Service | 类级别 | 标识业务逻辑层组件 | @Service public class UserService { } | 语义化更强,便于 AOP 切入业务层 |
@Repository | 类级别 | 标识数据访问层(DAO)组件 | @Repository public class UserRepository { } | 能自动翻译数据库异常为 Spring 的 DataAccessException |
@Controller | 类级别 | 标识控制层组件(MVC) | @Controller public class UserController { } | 通常与 @RequestMapping 配合使用 |
@RestController | 类级别 | 组合注解(@Controller + @ResponseBody) | @RestController public class ApiUserController { } | 用于构建 RESTful API,返回数据而非视图 |
2.2 @Autowired 与 @Qualifier 依赖注入
| 注解 | 语法位置 | 用途 | 代码示例 | 注意事项 |
|---|
@Autowired | 字段、构造器、方法、参数 | 自动装配 Bean,基于类型匹配 | @Autowired private UserService userService; | 可用于字段(不推荐)、构造器(推荐)、setter 方法 |
@Qualifier | 字段、参数、类型 | 指定具体 Bean 名称,解决类型冲突 | @Autowired @Qualifier("premiumUserServiceImpl") private UserService userService; | 必须与 @Autowired 配合使用,用于多实现类场景 |
@Primary | 类级别 | 标识首选 Bean,当存在多个候选时优先注入 | @Component @Primary public class DefaultPaymentService { } | 避免 @Qualifier 的重复使用,提高可读性 |
@Lookup | 方法 | 支持方法注入,返回一个 Bean 实例 | @Lookup public Command createCommand() { return null; } | 用于单例 Bean 注入原型 Bean 的特殊场景 |
@Autowired(required=false) | 字段、方法 | 允许依赖项为可选 | @Autowired(required = false) private OptionalService optionalService; | 若未找到匹配 Bean,不会报错,注入 null |
2.3 @Value 注入基本类型与配置
| 注解 | 语法位置 | 用途 | 代码示例 | 注意事项 |
|---|
@Value | 字段、方法、参数 | 注入属性值,支持字面量、SpEL、配置文件 | @Value("${app.name}") private String appName; | 支持 ${} 读取 properties,#{} 使用 SpEL 表达式 |
@Value (SpEL) | 字段 | 使用 Spring 表达式语言注入动态值 | @Value("#{systemProperties['user.home']}") private String homeDir; | 可调用方法、访问系统属性、运算等 |
@Value (默认值) | 字段 | 提供默认值 | @Value("${server.port:8080}") private int port; | 当配置项不存在时使用冒号后的默认值 |
@Value (数组/集合) | 字段 | 注入集合类型 | @Value("#{'${db.urls}'.split(',')}") private List<String> urls; | 配合 SpEL 可实现字符串分割为集合 |
@Value (Resource) | 字段 | 注入资源文件 | @Value("classpath:data.sql") private Resource dataScript; | 支持 classpath:、file:、http: 等资源协议 |
2.4 @Configuration 与 @Bean 配置类
| 注解/方法 | 语法位置 | 用途 | 代码示例 | 注意事项 |
|---|
@Configuration | 类级别 | 标识配置类,等价于 XML 配置文件 | @Configuration public class AppConfig { } | 通常与 @Bean 方法配合使用,被 Spring 容器管理 |
@Bean | 方法级别 | 定义一个 Bean,方法返回值注册为容器实例 | @Bean public DataSource dataSource() { return new DriverManagerDataSource(); } | 方法名默认为 Bean 名称,可自定义 |
@Bean (自定义名称) | 方法级别 | 指定 Bean 的名称 | @Bean("myDataSource") public DataSource dataSource() { ... } | 当需要多个同类型 Bean 时用于区分 |
@Bean (初始化方法) | 方法级别 | 指定初始化回调方法 | @Bean(initMethod = "init") public RedisClient redis() { ... } | 等价于 XML 中的 init-method 属性 |
@Bean (销毁方法) | 方法级别 | 指定销毁回调方法 | @Bean(destroyMethod = "shutdown") public ThreadPool taskExecutor() { ... } | 容器关闭时调用,用于资源释放 |
@Import | 类级别 | 导入其他配置类 | @Configuration @Import(DatabaseConfig.class) public class AppConfig { } | 实现配置类的模块化与复用 |
@ImportResource | 类级别 | 导入 XML 配置文件 | @Configuration @ImportResource("classpath:beans.xml") public class XmlConfig { } | 用于兼容老项目或特定场景 |
2.5 @Scope、@Primary、@Lazy 注解使用
| 注解 | 语法位置 | 用途 | 代码示例 | 注意事项 |
|---|
@Scope | 类、方法(@Bean) | 指定 Bean 的作用域 | @Scope("prototype") @Component public class Task { } | 常用值:singleton、prototype、request、session |
@Scope (代理模式) | 类、方法 | 解决作用域代理问题 | @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) | 在单例中注入非单例 Bean 时需使用代理 |
@Primary | 类、方法(@Bean) | 标识首选 Bean | @Bean @Primary public CacheManager ehCache() { ... } | 当存在多个候选 Bean 时优先注入 |
@Lazy | 类、方法、参数 | 延迟初始化 Bean | @Lazy @Component public class HeavyService { } | 减少启动时间,首次使用时才创建实例 |
@Lazy (条件延迟) | 构造器参数 | 延迟注入依赖 | @Autowired public UserController(@Lazy UserService userService) { this.userService = userService; } | 构造器中使用 @Lazy 可延迟依赖的创建 |
@DependsOn | 类、方法 | 强制指定初始化顺序 | @DependsOn("dataSource") @Bean public DataInitializer initializer() { ... } | 保证被依赖的 Bean 先初始化,避免空指针 |
第三章:Spring AOP(面向切面编程)
3.1 AOP 核心概念(切面、连接点、通知、切入点)
| 概念 | 说明 | 示例 | 注意事项 |
|---|
| 切面(Aspect) | 横切关注点的模块化封装,通常是一个类 | 日志记录、事务管理、安全控制 | 使用 @Aspect 注解标识切面类 |
| 连接点(Join Point) | 程序执行过程中的特定点,如方法调用或异常抛出 | UserService 的 save() 方法调用前 | Spring AOP 仅支持方法级别的连接点 |
| 通知(Advice) | 切面在特定连接点上执行的动作 | 在方法执行前打印日志 | 分为前置、后置、环绕等类型 |
| 切入点(Pointcut) | 匹配连接点的表达式,定义通知在何处执行 | execution(* com.service.*.*(..)) | 决定哪些类或方法被织入切面逻辑 |
| 引入(Introduction) | 为类添加新的方法或属性 | 为目标类引入新的接口实现 | 高级功能,较少使用 |
| 织入(Weaving) | 将切面应用到目标对象并创建代理对象的过程 | 编译期、类加载期或运行期织入 | Spring AOP 在运行期通过代理实现 |
3.2 基于注解的 AOP 实现(@Aspect, @Before, @After 等)
| 注解 | 语法位置 | 用途 | 代码示例 | 注意事项 |
|---|
@Aspect | 类级别 | 标识一个切面类 | @Component @Aspect public class LoggingAspect { } | 必须配合 @Component 或配置注册为 Bean |
@Before | 方法级别 | 前置通知,在目标方法执行前运行 | @Before("execution(* com.service.*.*(..))") public void logBefore(JoinPoint jp) { } | 无法阻止方法执行,除非抛出异常 |
@After | 方法级别 | 后置通知,目标方法执行后运行(无论是否异常) | @After("pointcut()") public void logAfter(JoinPoint jp) { } | 类似 finally 块,常用于资源清理 |
@AfterReturning | 方法级别 | 返回通知,目标方法成功执行后运行 | @AfterReturning(pointcut="savePointcut()", returning="result") public void logReturn(Object result) { } | 可获取方法返回值,用于日志或缓存 |
@AfterThrowing | 方法级别 | 异常通知,目标方法抛出异常后运行 | @AfterThrowing(pointcut="serviceLayer()", throwing="ex") public void logException(Exception ex) { } | 可获取异常对象,用于统一错误处理 |
@Around | 方法级别 | 环绕通知,包裹目标方法,可控制执行流程 | @Around("execution(* .*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable { return pjp.proceed(); } | 最强大也最复杂,需显式调用 proceed() |
3.3 切入点表达式语法详解
| 表达式类型 | 语法格式 | 用途 | 代码示例 | 注意事项 |
|---|
execution | execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) | 匹配方法执行连接点 | execution(* com.service.UserService.save*(..)) | 最常用,支持通配符:* 匹配任意字符,.. 匹配任意参数 |
within | within(type-pattern) | 匹配指定类型内的方法执行 | within(com.service.*) | 匹配 service 包下所有类的方法 |
this | this(type) | 匹配代理对象类型为给定类型的 Bean | this(com.service.UserService) | 基于代理对象类型匹配,适用于 JDK 动态代理 |
target | target(type) | 匹配目标对象类型为给定类型的 Bean | target(com.dao.UserRepository) | 基于目标对象类型匹配,更贴近实际业务对象 |
args | args(arg-pattern) | 匹配参数类型符合指定模式的方法 | args(java.lang.String, ..) | 匹配第一个参数为 String 的方法,可结合类型安全使用 |
@target | @target(annotation-type) | 匹配带有指定注解的类 | @target(org.springframework.stereotype.Service) | 匹配所有被 @Service 标注的类的方法 |
@within | @within(annotation-type) | 匹配带有指定注解的类(与 @target 类似) | @within(com.annotation.Loggable) | 常用于自定义注解切面 |
@annotation | @annotation(annotation-type) | 匹配方法上带有指定注解的方法 | @annotation(com.annotation.Timer) | 适用于方法级自定义注解,如性能监控 |
@args | @args(annotation-type) | 匹配参数上带有指定注解的方法 | @args(com.annotation.Validated) | 用于校验等场景 |
bean | bean(id-or-name-wildcards) | 匹配指定 Bean 名称的方法 | bean(*Service) | Spring 特有,支持通配符匹配 Bean 名称 |
3.4 环绕通知与异常通知处理
| 方法/注解 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
proceed() | Object proceed() throws Throwable | 执行目标方法(ProceedingJoinPoint) | try { return pjp.proceed(); } catch (Exception e) { ... } | 必须调用,否则目标方法不会执行 |
proceed(Object[]) | Object proceed(Object[] args) throws Throwable | 使用新参数执行目标方法 | Object[] newArgs = {"newName"}; return pjp.proceed(newArgs); | 可实现参数修改,如统一处理空值 |
throwing 属性 | @AfterThrowing(throwing="ex", ...) | 捕获异常对象 | @AfterThrowing(pointcut="serviceLayer()", throwing="ex") public void handle(Exception ex) { } | 参数名需与 throwing 属性一致 |
| 异常类型过滤 | @AfterThrowing(pointcut="...", throwing="ex") public void handle(IOException ex) { } | 指定捕获特定异常类型 | 可分别处理不同异常,如 IOException、IllegalArgumentException | 支持多异常通知 |
| 环绕中捕获异常 | try { return pjp.proceed(); } catch (Exception e) { log.error(e); throw e; } | 在环绕通知中统一处理异常 | 常用于记录异常日志、包装异常或返回默认值 | 注意重新抛出异常以保持原有行为 |
| 异常转换 | catch (DataAccessException dae) { throw new ServiceException(dae); } | 将技术异常转换为业务异常 | 在 DAO 层切面中常用,屏蔽底层实现细节 | 提高上层调用的稳定性 |
3.5 AOP 底层实现原理(JDK 动态代理与 CGLIB)
| 机制 | 触发条件 | 实现原理 | 代码/配置示例 | 注意事项 |
|---|
| JDK 动态代理 | 目标类实现至少一个接口 | 基于 java.lang.reflect.Proxy 为接口创建代理实例 | 代理对象必须通过接口类型引用,如 UserService proxy = (UserService) Proxy.newProxyInstance(...) | 仅能代理接口方法,无法代理类的非接口方法 |
| CGLIB 代理 | 目标类未实现接口或强制使用 | 基于 ASM 字节码生成库,创建目标类的子类并重写方法 | 使用 @EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用 CGLIB | 需添加 spring-core 中的 CGLIB 依赖;目标类不能为 final |
proxyTargetClass | @EnableAspectJAutoProxy(proxyTargetClass = true) | 强制使用 CGLIB 代理 | @Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) public class AopConfig { } | 默认为 false,优先使用 JDK 动态代理 |
exposeProxy | @EnableAspectJAutoProxy(exposeProxy = true) | 暴露代理对象,解决 self-invocation 问题 | AopContext.currentProxy() 获取当前代理对象 | 自调用(内部方法调用)无法被切面拦截,需启用此选项 |
| 自调用问题 | this.method() | 类内部方法调用绕过代理 | 使用 ((UserService) AopContext.currentProxy()).save() 显式通过代理调用 | 启用 exposeProxy 后方可使用 AopContext |
| 代理对象类型判断 | instanceof 接口或类 | 判断代理类型 | if (bean instanceof Advised) { ... } | 可通过 Spring 的 Advised 接口访问代理配置 |
| Final 方法/类 | private、final 方法或类 | 无法被代理 | final void doInternal() { } // 无法被 AOP 拦截 | CGLIB 也无法重写 final 方法,设计时应避免 |
| 性能对比 | — | JDK 代理轻量,CGLIB 生成子类开销略高 | 大多数场景性能差异可忽略 | 优先使用接口编程,便于切换代理方式 |
第四章:Spring 事务管理
4.1 事务的基本概念(ACID 特性)
| 特性 | 全称 | 说明 | Spring 中的体现 | 注意事项 |
|---|
| 原子性(Atomicity) | Atomicity | 事务是最小执行单位,不可分割,要么全部成功,要么全部失败 | @Transactional 方法中所有操作作为一个整体提交或回滚 | 若方法中部分 SQL 执行成功,但后续出错,整个事务将回滚 |
| 一致性(Consistency) | Consistency | 事务执行前后,数据库从一个一致状态转移到另一个一致状态 | 通过业务逻辑和约束(如外键、唯一索引)保证数据正确性 | 需开发者编写正确逻辑,Spring 不自动保证业务一致性 |
| 隔离性(Isolation) | Isolation | 多个事务并发执行时,一个事务的操作不能被其他事务干扰 | 通过 @Transactional(isolation = Isolation.READ_COMMITTED) 设置隔离级别 | 隔离级别越高,并发性能越低,需权衡选择 |
| 持久性(Durability) | Durability | 事务一旦提交,其结果是永久性的,即使系统故障也不会丢失 | 提交后数据写入磁盘,由数据库保证 | 依赖数据库的持久化机制,Spring 不直接管理 |
| 脏读(Dirty Read) | — | 一个事务读取了另一个未提交事务的数据 | 设置 Isolation.READ_COMMITTED 可避免 | 在 READ_UNCOMMITTED 级别下可能发生 |
| 不可重复读(Non-repeatable Read) | — | 同一事务内两次读取同一数据结果不同(因其他事务修改并提交) | Isolation.REPEATABLE_READ 可避免 | 比脏读更严重,影响事务内部一致性 |
| 幻读(Phantom Read) | — | 同一事务内两次查询范围数据,结果集不一致(因其他事务插入) | Isolation.SERIALIZABLE 可避免 | 通常通过行锁或表锁解决,影响并发 |
4.2 声明式事务管理(@Transactional 注解)
| 属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
value / transactionManager | String | 指定事务管理器 Bean 名称 | @Transactional("txManager") | 多数据源时必须指定,否则使用默认事务管理器 |
propagation | Propagation 枚举 | 设置事务传播行为 | @Transactional(propagation = Propagation.REQUIRED) | 默认为 REQUIRED,决定事务如何参与现有事务 |
isolation | Isolation 枚举 | 设置事务隔离级别 | @Transactional(isolation = Isolation.READ_COMMITTED) | 默认由数据库决定,可显式覆盖 |
timeout | int(秒) | 事务超时时间 | @Transactional(timeout = 30) | 超时后自动回滚,防止长时间占用资源 |
readOnly | boolean | 是否为只读事务 | @Transactional(readOnly = true) | 可提升性能,适用于查询操作,数据库可优化 |
rollbackFor | Class<? extends Throwable>[] | 指定哪些异常触发回滚 | @Transactional(rollbackFor = Exception.class) | 默认仅对 RuntimeException 和 Error 回滚 |
noRollbackFor | Class<? extends Throwable>[] | 指定哪些异常不触发回滚 | @Transactional(noRollbackFor = BusinessException.class) | 用于业务异常无需回滚的场景 |
label | String[] | 事务标签,用于监控或自定义逻辑 | @Transactional(label = "performance") | Spring 5.2+ 支持,可用于 AOP 增强 |
4.3 事务传播行为与隔离级别
| 传播行为 | 说明 | 使用场景 | 代码示例 | 注意事项 |
|---|
REQUIRED | 支持当前事务,若无则新建 | 大多数业务方法的默认选择 | @Transactional(propagation = Propagation.REQUIRED) | 外部有事务则加入,无则创建新事务 |
REQUIRES_NEW | 总是新建事务,挂起当前事务 | 日志记录、独立扣款等需独立提交的场景 | @Transactional(propagation = Propagation.REQUIRES_NEW) | 内部事务提交/回滚不影响外部,常用于补偿机制 |
SUPPORTS | 支持当前事务,若无则以非事务方式执行 | 查询方法,可有可无事务 | @Transactional(propagation = Propagation.SUPPORTS) | 不会主动开启事务,适合只读操作 |
NOT_SUPPORTED | 以非事务方式执行,挂起当前事务 | 执行非事务性操作(如发送邮件) | @Transactional(propagation = Propagation.NOT_SUPPORTED) | 避免事务占用过长时间 |
MANDATORY | 必须在已有事务中执行,否则抛异常 | 强制要求调用方提供事务 | @Transactional(propagation = Propagation.MANDATORY) | 用于核心业务逻辑,确保一致性 |
NEVER | 以非事务方式执行,若当前有事务则抛异常 | 确保不在事务中执行的操作 | @Transactional(propagation = Propagation.NEVER) | 防止误在事务中调用 |
NESTED | 嵌套事务,使用 Savepoint 实现 | 需要部分回滚的场景 | @Transactional(propagation = Propagation.NESTED) | JDK 1.4+ 支持,依赖数据库的 Savepoint 机制 |
4.4 事务的回滚机制与异常处理
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
rollbackFor | @Transactional(rollbackFor = Exception.class) | 指定检查型异常也触发回滚 | @Transactional(rollbackFor = IOException.class) public void processFile() { ... } | 默认仅 RuntimeException 和 Error 回滚,需显式配置检查型异常 |
noRollbackFor | @Transactional(noRollbackFor = BusinessException.class) | 指定某些异常不回滚 | @Transactional(noRollbackFor = ValidationException.class) public void createUser(User user) { ... } | 用于业务校验失败但无需回滚数据库的场景 |
| 手动回滚 | TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() | 在方法内手动标记回滚 | try { businessLogic(); } catch (ExternalException e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } | 适用于捕获异常后仍想回滚的复杂逻辑 |
| 异常抛出 | throw new RuntimeException("error") | 抛出运行时异常自动触发回滚 | if (user == null) { throw new IllegalArgumentException("User is null"); } | 是最常见、最推荐的回滚方式 |
| try-catch 吞异常 | try { riskyOperation(); } catch (Exception e) { log.error(e); } | 捕获异常但不抛出或标记回滚 | 若未重新抛出或调用 setRollbackOnly,则事务仍会提交 | 危险操作:会导致数据不一致 |
| 嵌套事务回滚 | 内层异常未被捕获 | 内层事务异常传递到外层 | 外层事务感知异常并整体回滚 | REQUIRED 传播下,异常会向上传播导致整个事务回滚 |
REQUIRES_NEW 回滚 | 内部方法抛异常 | 独立事务失败不影响外部 | 外部可捕获内部异常并继续执行 | 内部事务回滚,外部可选择是否回滚 |
4.5 编程式事务管理(TransactionTemplate)
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
execute | <T> T execute(TransactionCallback<T> action) | 执行事务性操作,支持返回值 | transactionTemplate.execute(status -> { userRepository.save(user); return userService.generateId(); }); | 推荐使用,自动处理提交与回滚 |
executeWithoutResult | void executeWithoutResult(Consumer<TransactionStatus> action) | 执行无返回值的事务操作(Spring 5.2+) | transactionTemplate.executeWithoutResult(status -> { jdbcTemplate.update(sql, args); }); | 简化无返回值场景的代码 |
setPropagationBehavior | transactionTemplate.setPropagationBehavior(Propagation.REQUIRES_NEW.value()) | 设置事务传播行为 | TransactionTemplate tt = new TransactionTemplate(transactionManager); tt.setPropagationBehavior(Propagation.REQUIRES_NEW.value()); | 可动态调整事务行为 |
setIsolationLevel | transactionTemplate.setIsolationLevel(Isolation.READ_UNCOMMITTED.value()) | 设置隔离级别 | tt.setIsolationLevel(Isolation.SERIALIZABLE.value()); | 根据业务需求设置,影响并发性能 |
setTimeout | transactionTemplate.setTimeout(30) | 设置事务超时时间(秒) | tt.setTimeout(60); | 防止长时间事务占用连接 |
setReadOnly | transactionTemplate.setReadOnly(true) | 设置为只读事务 | tt.setReadOnly(true); | 提升查询性能,数据库可优化执行计划 |
setTransactionManager | transactionTemplate.setTransactionManager(txManager) | 指定事务管理器 | 必须先设置事务管理器才能使用 | 通常通过构造函数或依赖注入设置 |
说明:
- 声明式事务(@Transactional)适用于大多数场景,代码简洁,关注点分离。
- 编程式事务(TransactionTemplate)适用于复杂事务逻辑,如条件提交、多阶段操作等,灵活性更高。
- 两者可结合使用,但应避免过度嵌套导致逻辑混乱。
- 无论哪种方式,都需确保事务边界清晰,避免过长事务影响系统性能。
第五章:Spring 与数据访问(JDBC 与 ORM 集成)
5.1 Spring JDBC 模板(JdbcTemplate)
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
queryForObject | <T> T queryForObject(String sql, Class<T> requiredType) | 执行查询并返回单个对象(如 count、sum) | Integer count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Integer.class); | 结果必须为单行单列,否则抛异常 |
queryForObject (带参数) | <T> T queryForObject(String sql, Object[] args, Class<T> requiredType) | 带参数的单值查询 | String name = jdbcTemplate.queryForObject("SELECT name FROM users WHERE id = ?", new Object[]{1}, String.class); | 使用 ? 占位符,防止 SQL 注入 |
query | <T> List<T> query(String sql, RowMapper<T> rowMapper) | 执行查询并返回对象列表 | List<User> users = jdbcTemplate.query("SELECT * FROM users", new UserRowMapper()); | 需提供 RowMapper 实现结果集映射 |
query (BeanPropertyRowMapper) | <T> List<T> query(String sql, BeanPropertyRowMapper<T> mapper) | 使用属性名自动映射 | List<User> users = jdbcTemplate.query("SELECT * FROM users", new BeanPropertyRowMapper<>(User.class)); | 要求数据库列名与 Java 属性名匹配(支持驼峰转下划线) |
update | int update(String sql, Object... args) | 执行 INSERT、UPDATE、DELETE 等更新操作 | int rows = jdbcTemplate.update("INSERT INTO users(name) VALUES(?)", "John"); | 返回受影响的行数,可用于判断操作结果 |
batchUpdate | int[] batchUpdate(String sql, List<Object[]> batchArgs) | 批量执行更新操作 | List<Object[]> batch = Arrays.asList(new Object[]{"Alice"}, new Object[]{"Bob"}); jdbcTemplate.batchUpdate("INSERT INTO users(name) VALUES(?)", batch); | 提升批量操作性能,减少网络往返 |
execute | void execute(String sql) | 执行任意 SQL(如 DDL) | jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS temp (id INT)"); | 用于执行建表、删表等管理语句 |
query (RowCallbackHandler) | void query(String sql, RowCallbackHandler rch) | 逐行处理结果集(适用于大数据量) | jdbcTemplate.query("SELECT * FROM large_table", rs -> { processRow(rs); }); | 避免一次性加载全部数据到内存 |
5.2 数据源配置与连接池管理
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
DriverManagerDataSource | new DriverManagerDataSource() | 简单数据源,不支持连接池 | DriverManagerDataSource ds = new DriverManagerDataSource(); ds.setDriverClassName("com.mysql.cj.jdbc.Driver"); | 仅用于测试,生产环境必须使用连接池 |
BasicDataSource (DBCP) | org.apache.commons.dbcp2.BasicDataSource | DBCP2 连接池实现 | BasicDataSource ds = new BasicDataSource(); ds.setUrl("jdbc:mysql://localhost:3306/test"); ds.setUsername("root"); ds.setPassword("pass"); ds.setInitialSize(5); ds.setMaxTotal(20); | 需引入 commons-dbcp2 依赖,配置丰富 |
HikariDataSource | com.zaxxer.hikari.HikariDataSource | HikariCP 高性能连接池 | HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/test"); config.setUsername("root"); config.setPassword("pass"); config.setMaximumPoolSize(10); HikariDataSource ds = new HikariDataSource(config); | Spring Boot 默认,性能优异,配置简洁 |
setInitialSize | dataSource.setInitialSize(5) | 设置初始连接数 | 生产环境建议与 minIdle 一致 | 避免启动时连接创建开销 |
setMaxActive / setMaximumPoolSize | dataSource.setMaxTotal(20) 或 config.setMaximumPoolSize(20) | 设置最大连接数 | 根据数据库承载能力和应用并发量调整 | 过大会导致数据库压力过大 |
setMaxWait / setConnectionTimeout | dataSource.setMaxWaitMillis(5000) 或 config.setConnectionTimeout(30000) | 获取连接的最长等待时间 | 超时后抛出 SQLException | 建议设置合理超时,避免线程无限等待 |
setValidationQuery | dataSource.setValidationQuery("SELECT 1") | 设置连接有效性检查 SQL | HikariCP 默认自动检测,DBCP 需手动设置 | 确保连接池返回有效连接 |
@Primary | @Primary @Bean DataSource dataSource() | 指定主数据源(多数据源场景) | 当应用有多个 DataSource Bean 时,使用 @Primary 标识默认数据源 | 避免 @Autowired 时出现歧义 |
5.3 集成 MyBatis 框架
| 方法/注解 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
@Mapper | 接口级别 | 标识 MyBatis Mapper 接口 | @Mapper public interface UserMapper { User findById(Long id); } | 需配合 @MapperScan 使用,或每个接口添加 |
@MapperScan | 类级别(配置类) | 扫描指定包下的 Mapper 接口 | @Configuration @MapperScan("com.example.mapper") public class MyBatisConfig { } | 推荐方式,集中管理 Mapper 扫描 |
@Select | 方法级别 | 定义查询 SQL | @Select("SELECT * FROM users WHERE id = #{id}") User findById(Long id); | 支持动态 SQL,但复杂 SQL 建议写在 XML 中 |
@Insert | 方法级别 | 定义插入 SQL | @Insert("INSERT INTO users(name) VALUES(#{name})") @Options(useGeneratedKeys = true, keyProperty = "id") int insert(User user); | @Options 用于获取自增主键 |
@Update | 方法级别 | 定义更新 SQL | @Update("UPDATE users SET name = #{name} WHERE id = #{id}") int update(User user); | 返回受影响行数 |
@Delete | 方法级别 | 定义删除 SQL | @Delete("DELETE FROM users WHERE id = #{id}") int delete(Long id); | 简单删除操作 |
@Param | 方法参数 | 指定参数名称(多个参数时必需) | User findByCondition(@Param("name") String name, @Param("age") int age); | 在 SQL 中使用 #{name}、#{age} 引用 |
SqlSessionTemplate | spring 提供的线程安全 SqlSession | 编程式访问数据库 | User user = sqlSession.selectOne("com.example.mapper.UserMapper.findById", 1L); | 可在非 Mapper 场景使用,灵活性高 |
5.4 集成 Hibernate 框架
| 方法/注解 | 语法位置 | 用途 | 代码示例 | 注意事项 |
|---|
@Entity | 类级别 | 标识持久化实体类 | @Entity public class User { ... } | 必须有无参构造函数,类不能为 final |
@Table | 类级别 | 指定对应数据库表名 | @Entity @Table(name = "users") public class User { ... } | name 属性可自定义表名,schema 属性指定模式 |
@Id | 字段/属性 | 标识主键字段 | @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; | 必须存在,一个实体只能有一个 @Id |
@GeneratedValue | 字段/属性 | 定义主键生成策略 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq") | 常用策略:IDENTITY、AUTO、SEQUENCE、TABLE |
@Column | 字段/属性 | 映射数据库列 | @Column(name = "user_name", nullable = false, length = 50) private String name; | 可指定列名、是否可空、长度、唯一性等 |
@ManyToOne | 字段/属性 | 定义多对一关联 | @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "dept_id") private Department department; | fetch 默认 EAGER,建议按需设为 LAZY 防止 N+1 问题 |
@OneToMany | 字段/属性 | 定义一对多关联 | @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List<Order> orders; | mappedBy 指向对方维护关系的字段,避免双向维护 |
@OneToOne | 字段/属性 | 定义一对一关联 | @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "profile_id") private Profile profile; | 可通过 @JoinColumn 或 @PrimaryKeyJoinColumn 指定关联方式 |
@ManyToMany | 字段/属性 | 定义多对多关联 | @ManyToMany @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) private Set<Role> roles; | 必须使用 @JoinTable 指定中间表 |
@Transactional (Hibernate) | 方法/类 | 管理 Hibernate 会话与事务 | @Transactional public void saveUser(User user) { sessionFactory.getCurrentSession().save(user); } | 必须在事务中执行持久化操作 |
5.5 异常统一处理与模板回调
| 方法/机制 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
DataAccessException | Spring 数据访问异常根类 | 统一异常体系,屏蔽底层 ORM 差异 | try { ... } catch (DataAccessException e) { log.error("DB error", e); } | 所有 Spring 数据访问操作抛出此体系异常,无需关心具体实现 |
SQLExceptionTranslator | 接口 | 将 SQLException 转换为 Spring 的 DataAccessException | SQLExceptionTranslator translator = new SQLStateSQLExceptionTranslator(); DataAccessException ex = translator.translate("query", sql, sqlEx); | JdbcTemplate 自动使用,开发者通常无需直接调用 |
RowMapper | 接口 | 自定义结果集到对象的映射逻辑 | public class UserRowMapper implements RowMapper<User> { public User mapRow(ResultSet rs, int rowNum) throws SQLException { User u = new User(); u.setId(rs.getLong("id")); u.setName(rs.getString("name")); return u; } } | 适用于复杂映射或 BeanPropertyRowMapper 无法处理的场景 |
ResultSetExtractor | 接口 | 提取整个结果集(如多行映射为集合) | ResultSetExtractor<List<User>> extractor = rs -> { List<User> list = new ArrayList<>(); while (rs.next()) { list.add(mapRow(rs)); } return list; }; | 比 RowMapper 更灵活,可处理复杂结果集结构 |
PreparedStatementSetter | 接口 | 设置预编译语句参数 | PreparedStatementSetter pss = ps -> { ps.setString(1, name); ps.setInt(2, age); }; jdbcTemplate.update(sql, pss); | 用于复杂参数设置逻辑,替代简单的 Object[] 参数 |
BatchPreparedStatementSetter | 接口 | 批量设置预编译语句参数 | BatchPreparedStatementSetter bpss = new BatchPreparedStatementSetter() { public void setValues(PreparedStatement ps, int i) { ps.setString(1, names.get(i)); } public int getBatchSize() { return names.size(); } }; | 配合 batchUpdate 使用,实现动态批量操作 |
@ExceptionHandler (Data) | 方法(@ControllerAdvice) | 全局处理数据访问异常 | @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(DataAccessException.class) public ResponseEntity<String> handleDataError(DataAccessException e) { return ResponseEntity.status(500).body("Database error"); } } | 实现异常统一响应,提升 API 友好性 |
第六章:Spring MVC 框架详解
6.1 MVC 架构模式与 Spring MVC 核心组件
| 组件 | 说明 | 职责 | 示例 | 注意事项 |
|---|
| MVC 模式 | Model-View-Controller 架构 | 分离关注点,提高可维护性 | 用户请求 → Controller 处理 → Model 数据 → View 展示 | 适用于 Web 应用,实现前后端逻辑解耦 |
DispatcherServlet | 前端控制器(核心) | 接收所有请求,协调各组件工作 | DispatcherServlet 作为 Servlet 配置在 web.xml 或通过 Java 配置 | 是 Spring MVC 的入口,所有请求的统一调度中心 |
HandlerMapping | 处理器映射 | 根据请求 URL 找到对应的处理器(Controller 方法) | @RequestMapping("/user") 映射到具体方法 | 支持多种实现,如 RequestMappingHandlerMapping |
HandlerAdapter | 处理器适配器 | 调用处理器方法,适配不同类型的处理器 | 调用 @Controller 中的 @RequestMapping 方法 | 屏蔽处理器实现差异,实现统一调用 |
| Controller | 控制器 | 处理业务逻辑,返回模型和视图 | @Controller public class UserController { ... } | 接收请求参数,调用 Service,返回结果 |
ModelAndView | 模型与视图 | 封装数据模型和视图信息 | return new ModelAndView("user/list", "users", userList); | 可同时返回数据和视图名 |
ViewResolver | 视图解析器 | 将逻辑视图名解析为实际视图对象 | InternalResourceViewResolver 解析 JSP 路径 | 支持 JSP、Thymeleaf、Freemarker 等多种模板 |
| View | 视图 | 渲染模型数据,生成响应内容 | JSP 页面、Thymeleaf 模板、JSON 数据 | 最终呈现给用户的内容 |
| Model | 模型 | 业务数据的容器 | model.addAttribute("users", userList); | 用于在 Controller 和 View 之间传递数据 |
6.2 DispatcherServlet 配置与请求流程
| 步骤 | 说明 | 关键组件 | 代码/配置示例 | 注意事项 |
|---|
| 1. 请求到达 | 客户端发送 HTTP 请求 | Web 容器(如 Tomcat) | GET /user/list HTTP/1.1 | 所有匹配 DispatcherServlet URL 模式的请求均被拦截 |
| 2. 前端控制器接收 | DispatcherServlet 接收请求 | DispatcherServlet | web.xml 配置 <servlet><servlet-name>dispatcher</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class></servlet> | 必须正确配置 <servlet-mapping> |
| 3. 处理器映射查找 | 根据 URL 查找处理器 | HandlerMapping | @RequestMapping("/user/list") | 多个 HandlerMapping 按优先级匹配 |
| 4. 获取处理器适配器 | 获取能执行该处理器的适配器 | HandlerAdapter | Spring 自动选择合适的适配器 | 开发者通常无需关心 |
| 5. 执行拦截器(preHandle) | 调用拦截器的前置处理方法 | HandlerInterceptor | preHandle(HttpServletRequest, HttpServletResponse, Object) | 可进行权限校验、日志记录等 |
| 6. 调用处理器方法 | 执行 Controller 中的业务逻辑 | @Controller 方法 | public String list(Model model) { ... } | 参数自动绑定,支持多种返回类型 |
| 7. 处理返回值 | 解析处理器返回值(视图名、Model 等) | HandlerMethodReturnValueHandler | return "user/list"; 或 @ResponseBody | 返回值处理器决定如何处理结果 |
| 8. 执行拦截器(postHandle) | 调用拦截器的后置处理方法 | HandlerInterceptor | postHandle(HttpServletRequest, HttpServletResponse, Object, ModelAndView) | 可修改 ModelAndView |
| 9. 视图解析 | 将逻辑视图名解析为实际视图 | ViewResolver | prefix="/WEB-INF/views/", suffix=".jsp" | 配置前缀和后缀简化视图名 |
| 10. 视图渲染 | 使用模型数据渲染视图 | View(如 JSP) | JSP 中使用 ${users} 访问模型数据 | 生成 HTML、JSON 等响应内容 |
| 11. 执行拦截器(afterCompletion) | 请求完成后执行清理工作 | HandlerInterceptor | afterCompletion(HttpServletRequest, HttpServletResponse, Object, Exception) | 无论成功或异常都会执行,适合资源释放 |
6.3 @Controller 与 @RequestMapping 映射
| 注解 | 语法位置 | 用途 | 代码示例 | 注意事项 |
|---|
@Controller | 类级别 | 标识控制器类,交由 Spring 管理 | @Controller public class UserController { } | 需配合 <context:component-scan> 或 @ComponentScan |
@RestController | 类级别 | 组合注解(@Controller + @ResponseBody) | @RestController public class ApiUserController { } | 用于构建 RESTful API,返回 JSON/XML 数据 |
@RequestMapping | 类/方法 | 映射请求 URL | @RequestMapping("/user") 类 + @RequestMapping("/list") 方法 | 类上定义基础路径,方法上定义具体路径 |
@RequestMapping (method) | 方法 | 指定请求方法 | @RequestMapping(value = "/save", method = RequestMethod.POST) | 可限定 GET、POST、PUT、DELETE 等 |
@GetMapping | 方法 | 简化 GET 请求映射 | @GetMapping("/list") public List<?> getAll() { ... } | 等价于 @RequestMapping(method = RequestMethod.GET) |
@PostMapping | 方法 | 简化 POST 请求映射 | @PostMapping("/save") public String saveUser(@RequestBody User user) { ... } | 常用于表单提交或 JSON 数据创建 |
@PutMapping | 方法 | 简化 PUT 请求映射 | @PutMapping("/update/{id}") public ResponseEntity<?> update(@PathVariable Long id, @RequestBody User user) { ... } | 用于更新资源 |
@DeleteMapping | 方法 | 简化 DELETE 请求映射 | @DeleteMapping("/delete/{id}") public String delete(@PathVariable Long id) { ... } | 用于删除资源 |
@RequestMapping (consumes) | 方法 | 指定请求体内容类型 | @PostMapping(path = "/data", consumes = "application/json") | 限制只处理 JSON 请求 |
@RequestMapping (produces) | 方法 | 指定响应内容类型 | @GetMapping(path = "/data", produces = "application/json;charset=UTF-8") | 支持内容协商,如 JSON、XML |
6.4 请求参数绑定(@RequestParam, @PathVariable)
| 注解/方法 | 语法位置 | 用途 | 代码示例 | 注意事项 |
|---|
@RequestParam | 方法参数 | 绑定请求参数(query string 或 form data) | public String search(@RequestParam String keyword, @RequestParam(defaultValue = "1") int page) { ... } | 用于获取 ?keyword=spring&page=1 中的参数 |
@RequestParam (required) | 方法参数 | 指定参数是否必需 | @RequestParam(required = false) String sort | 默认为 true,设为 false 可选,配合 defaultValue 使用 |
@RequestParam (defaultValue) | 方法参数 | 设置参数默认值 | @RequestParam(defaultValue = "10") int size | 当参数缺失时使用默认值,避免空指针 |
@PathVariable | 方法参数 | 绑定 URL 路径变量 | @GetMapping("/user/{id}") public User getUser(@PathVariable Long id) { ... } | 用于 RESTful 风格 URL,如 /user/123 |
@PathVariable (value) | 方法参数 | 指定路径变量名称 | @GetMapping("/order/{orderId}") public Order getOrder(@PathVariable("orderId") Long id) { ... } | 当参数名与路径变量名不一致时使用 |
@MatrixVariable | 方法参数 | 绑定 URL 矩阵变量(分号分隔) | @GetMapping("/user/{id}") public User getUser(@MatrixVariable String city) { ... } | URL 如 /user/123;city=beijing,较少使用 |
@RequestHeader | 方法参数 | 绑定请求头信息 | @RequestHeader("User-Agent") String userAgent | 获取客户端信息、认证令牌等 |
@CookieValue | 方法参数 | 绑定 Cookie 值 | @CookieValue("JSESSIONID") String sessionId | 读取客户端 Cookie |
@RequestBody | 方法参数 | 绑定请求体(JSON/XML)到对象 | @PostMapping("/user") public User createUser(@RequestBody User user) { ... } | 需配合 HttpMessageConverter(如 Jackson) |
| 自动绑定(Command Object) | 方法参数 | 将请求参数自动绑定到对象属性 | public String save(UserForm form) { ... } | 参数名需与对象属性名匹配,支持级联属性 address.city |
6.5 数据绑定与类型转换
| 机制/注解 | 说明 | 用途 | 代码示例 | 注意事项 |
|---|
DataBinder | Spring 数据绑定核心类 | 将请求参数绑定到目标对象 | WebDataBinder binder = ...; binder.bind(propertyValues); | 自动处理类型转换和格式化 |
PropertyEditor | 旧式类型转换机制 | 将字符串转换为特定类型 | public class CustomDateEditor extends PropertyEditorSupport { ... } | 已被 Converter 和 Formatter 取代,但仍兼容 |
Converter<S, T> | 通用类型转换接口 | 实现自定义类型转换 | public class StringToUserConverter implements Converter<String, User> | 需注册到 ConversionService |
Formatter | 格式化接口(支持 Locale) | 实现带格式的类型转换与反向格式化 | public class DateFormatter implements Formatter<Date> | 适用于日期、货币等需格式化的类型 |
@DateTimeFormat | 字段/参数 | 指定日期时间格式 | @DateTimeFormat(pattern = "yyyy-MM-dd") private Date birthDate; | 用于表单提交的日期字符串转换 |
@NumberFormat | 字段/参数 | 指定数字格式 | @NumberFormat(style = Style.CURRENCY) private BigDecimal salary; | 用于金额、百分比等格式化 |
ConversionService | 类型转换服务 | 管理所有转换器 | @Autowired ConversionService conversionService; | Spring MVC 自动配置,可通过 @EnableWebMvc 自定义 |
| Validation (JSR-303) | 数据校验 | 验证绑定后的对象 | public String save(@Valid User user, BindingResult result) | 需引入 hibernate-validator,配合 @NotNull、@Size 等使用 |
BindingResult | 方法参数 | 接收数据绑定和校验结果 | if (result.hasErrors()) { ... } | 必须紧跟在 @Valid 参数后声明,用于处理错误 |
第七章:Spring Boot 快速开发
7.1 Spring Boot 简介与自动配置原理
| 概念 | 说明 | 实现机制 | 代码/注解示例 | 注意事项 |
|---|
| Spring Boot 目标 | 简化 Spring 应用的初始搭建和开发 | 约定优于配置,内嵌容器,开箱即用 | spring-boot-starter-web 包含 Tomcat 和 Web MVC | 减少样板代码和 XML 配置 |
@SpringBootApplication | 组合注解(核心入口) | 组合了 @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan | @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } } | 主类应放在根包下,以便扫描所有组件 |
@EnableAutoConfiguration | 启用自动配置 | 根据 classpath 中的依赖自动配置 Bean | @EnableAutoConfiguration(通常由 @SpringBootApplication 包含) | 是自动配置的核心开关 |
spring.factories | 自动配置元数据文件 | 在 META-INF/spring.factories 中定义自动配置类列表 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.example.autoconfig.UserAutoConfiguration | Spring Boot 启动时加载并处理这些配置类 |
条件化配置 @Conditional | 基于条件决定是否创建 Bean | 多种衍生注解控制自动配置生效条件 | @ConditionalOnClass(DataSource.class) @ConditionalOnMissingBean(Service.class) | 常见条件:类路径存在、Bean 缺失、属性匹配等 |
| 自动配置类命名 | 命名规范 | 以 XXXAutoConfiguration 结尾 | DataSourceAutoConfiguration, WebMvcAutoConfiguration | 内部使用 @Configuration 定义一系列 Bean |
| Starter POMs | 场景化依赖集 | 提供一键式依赖管理,避免版本冲突 | spring-boot-starter-data-jpa, spring-boot-starter-security | 引入 Starter 即可开始使用对应功能 |
| 内嵌服务器 | 内置 Servlet 容器 | 默认集成 Tomcat,也可切换为 Jetty 或 Undertow | 无需外部部署,直接运行 main 方法启动 | 打包为 JAR 可独立运行 |
7.2 Starter 依赖与常用场景集成
| Starter 名称 | 功能 | Maven 依赖坐标 | 典型用途 | 注意事项 |
|---|
spring-boot-starter | 核心 Starter | org.springframework.boot:spring-boot-starter | 所有应用的基础依赖,包含自动配置、日志等 | 每个 Spring Boot 项目都必须引入 |
spring-boot-starter-web | Web 开发 | spring-boot-starter-web | 构建 RESTful API 或传统 Web 应用,包含 Tomcat 和 Spring MVC | 创建 Web 应用的首选 Starter |
spring-boot-starter-data-jpa | JPA 数据访问 | spring-boot-starter-data-jpa | 集成 Hibernate,实现 ORM 操作 | 需配合数据库驱动和连接池 |
spring-boot-starter-data-jdbc | JDBC 数据访问 | spring-boot-starter-data-jdbc | 轻量级 JDBC 支持,基于 Spring JDBC | 不使用 ORM,追求性能和简单性 |
spring-boot-starter-data-redis | Redis 集成 | spring-boot-starter-data-redis | 缓存、会话存储、消息队列 | 自动配置 RedisTemplate 和 StringRedisTemplate |
spring-boot-starter-security | 安全框架 | spring-boot-starter-security | 认证、授权、CSRF 防护 | 引入后默认保护所有端点,需配置放行规则 |
spring-boot-starter-thymeleaf | 模板引擎 | spring-boot-starter-thymeleaf | 服务端页面渲染(HTML) | 适用于传统 MVC 应用,非前后端分离 |
spring-boot-starter-test | 测试支持 | spring-boot-starter-test | 单元测试和集成测试,包含 JUnit, Mockito, AssertJ | 测试范围依赖,不会打包到生产环境 |
spring-boot-starter-actuator | 应用监控 | spring-boot-starter-actuator | 暴露健康检查、指标、审计等端点 | 生产环境需安全配置,避免信息泄露 |
spring-boot-starter-aop | AOP 支持 | spring-boot-starter-aop | 启用 Spring AOP,自动引入 AspectJ | 用于日志、事务、权限等横切关注点 |
7.3 配置文件(application.properties/yml)管理
| 特性 | 说明 | 文件示例 | 代码获取方式 | 注意事项 |
|---|
application.properties | 属性文件格式 | server.port=8081 spring.datasource.url=jdbc:mysql://localhost:3306/test | @Value("${server.port}") private int port; | 键值对形式,结构扁平,适合简单配置 |
application.yml | YAML 格式(推荐) | server:\n port: 8081\nspring:\n datasource:\n url: jdbc:mysql://localhost:3306/test | @Value("${spring.datasource.url}") 或 @ConfigurationProperties | 层次清晰,支持复杂结构,注意缩进 |
| 配置优先级 | 多种配置源,优先级不同 | 命令行参数 > JVM 系统属性 > 配置文件 > 默认值 | java -jar app.jar --server.port=9090 | 了解优先级有助于调试配置问题 |
@Value 注解 | 注入单个配置值 | @Value("${app.name:MyApp}") private String appName; | 支持 SpEL 表达式和默认值(冒号后) | 适用于简单配置注入 |
@ConfigurationProperties | 类型安全的配置绑定 | @Component @ConfigurationProperties(prefix = "app") public class AppProperties { private String name; ... } | 需启用 @EnableConfigurationProperties 或组件扫描 | 推荐方式,将相关配置组织在 POJO 中 |
| 配置文件位置 | 外部化配置 | /config 子目录 > 当前目录 > classpath:/config/ > classpath:/ | --spring.config.location=file:/path/to/config/ | 支持从多个位置加载配置 |
| 随机值生成 | 生成随机属性 | secret.token=${random.value} user.id=${random.long} | 使用 RandomValuePropertySource | 用于密钥、ID 等需要随机性的场景 |
| 多文档块(YAML) | 在一个文件中定义多组配置 | spring:\n profiles: dev\n datasource:\n url: jdbc:h2:mem:dev\n---\nspring:\n profiles: prod\n datasource:\n url: jdbc:mysql://prod-db:3306/app | 结合 spring.profiles.active 使用 | 方便管理不同环境的配置 |
7.4 日志配置与 Profile 环境切换
| 方法/配置 | 说明 | 配置示例 | 代码/命令 | 注意事项 |
|---|
| 默认日志系统 | Spring Boot 默认使用 Logback | 无需额外配置,自动集成 | import org.slf4j.Logger; import org.slf4j.LoggerFactory; | 基于 SLF4J 门面,实际实现是 Logback |
logback-spring.xml | 自定义 Logback 配置 | <configuration><include resource="org/springframework/boot/logging/logback/defaults.xml"/><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">...<logger name="com.example" level="DEBUG"/></configuration> | 放置在 src/main/resources | 使用 -spring 后缀可利用 Spring 扩展功能(如 Profile) |
application-{profile}.properties | 环境特定配置文件 | application-dev.properties, application-prod.properties | logging.level.com.example=DEBUG | 文件中的配置会覆盖主配置文件 |
spring.profiles.active | 激活指定 Profile | spring.profiles.active=dev,metrics | 命令行:--spring.profiles.active=prod | 可激活多个 Profile,逗号分隔 |
@Profile 注解 | 条件化 Bean 注册 | @Profile("dev") @Bean public DataSource devDataSource() { ... } | 结合 @Configuration 使用 | Bean 只在指定 Profile 激活时创建 |
logging.level.* | 设置日志级别 | logging.level.root=WARN logging.level.com.example.service=DEBUG | 控制不同包的日志输出粒度 | 生产环境建议设为 INFO 或 WARN |
logging.file.name / logging.file.path | 指定日志文件 | logging.file.name=app.log 或 logging.file.path=./logs | 自动生成日志文件,支持滚动 | 避免日志占满磁盘空间 |
| Profile 分组 | 将相关 Profile 分组 | spring.profiles.group.production[0]=proddb spring.profiles.group.production[1]=prodmq | --spring.profiles.active=production | 简化复杂环境的激活操作 |
7.5 启动流程与 SpringApplication 自定义
| 阶段 | 说明 | 可自定义点 | 示例代码 | 注意事项 |
|---|
| 1. 创建 SpringApplication 实例 | 初始化应用上下文 | SpringApplication app = new SpringApplication(MyApp.class); | 可设置资源加载器、Banner 等 | 通常直接使用静态 run 方法 |
| 2. 运行 SpringApplication | run(args) 方法启动 | SpringApplication.run(MyApp.class, args); | 返回 ConfigurableApplicationContext | 核心启动入口 |
| 3. 应用监听器调用 | 发布启动事件 | 实现 ApplicationListener<ApplicationStartingEvent> | 用于早期初始化或监控 | Spring Boot 提供多个生命周期事件 |
| 4. 推断应用类型 | 判断是 Servlet、Reactive 还是其他 | 自动判断 classpath | 无需手动干预 | 影响后续容器的选择 |
| 5. 加载 ApplicationContextInitializer | 上下文初始化前回调 | app.addInitializers(ctx -> ctx.getBeanFactory().registerSingleton("myBean", new MyBean())); | 用于修改应用上下文 | — |
| 6. 加载 ApplicationListener | 加载事件监听器 | app.addListeners(new MyEventListener()); | 响应 ContextRefreshedEvent 等事件 | — |
| 7. 推断主类 | 确定主配置类 | 通常为主类本身 | 用于查找配置源 | — |
| 8. 环境构建 | 创建并配置 Environment | app.setEnvironment(new StandardEnvironment()); | 可自定义环境变量来源 | — |
| 9. 配置 Environment 属性 | 设置默认属性 | app.setDefaultProperties(Collections.singletonMap("server.port", "9090")); | 优先级低于外部配置 | — |
| 10. 打印 Banner | 输出启动 Logo | app.setBanner((environment, sourceClass, out) -> out.print("Welcome!")); | 可禁用:app.setBannerMode(Banner.Mode.OFF); | — |
| 11. 创建 ApplicationContext | 根据应用类型创建上下文 | Servlet → AnnotationConfigServletWebServerApplicationContext | 核心容器创建 | — |
| 12. 失败分析仪 | 异常时提供诊断信息 | FailureAnalyzers 自动注册 | 如 DataSourceBeanCreationFailureAnalyzer | 提升错误可读性 |
| 13. 加载初始器 | 调用 ApplicationContextInitializer | context.addBeanFactoryPostProcessor(...); | 修改 BeanFactory 配置 | — |
| 14. 环境关联 | 将 Environment 关联到上下文 | context.setEnvironment(environment); | 完成环境注入 | — |
| 15. 上下文后处理 | Bean 定义加载前的最后处理 | applyInitializers(context); | — | — |
| 16. 准备上下文 | 刷新前准备(加载 Bean 定义) | load(context, sources.toArray(new Object[0])); | 扫描 @Component 等注解 | — |
| 17. 刷新上下文 | 核心步骤,实例化 Bean | context.refresh(); | 触发 ContextRefreshedEvent | — |
| 18. 调用 CommandLineRunner / ApplicationRunner | 应用启动后执行任务 | @Component public class StartupTask implements CommandLineRunner { public void run(String... args) { ... } } | 按 order 属性排序执行 | — |
| 19. 发布就绪事件 | 应用启动完成 | context.publishEvent(new ApplicationReadyEvent(...)); | 可监听此事件执行后续操作 | — |
| 20. 运行失败 | 启动异常处理 | catch (Throwable ex) { handleRunFailure(context, ex, listeners); } | 输出详细错误信息 | — |
第八章:RESTful API 与 JSON 处理
8.1 REST 架构风格与设计规范
| 原则 | 说明 | 正确示例 | 错误示例 | 注意事项 |
|---|
| 资源导向 | 以资源为中心设计 API | /users, /orders | /getUser, /createOrder | URL 表示资源,而非操作 |
| 统一接口 | 使用标准 HTTP 方法 | GET /users(查询), POST /users(创建), PUT /users/1(更新), DELETE /users/1(删除) | POST /users/delete?id=1 | 方法语义明确,符合幂等性 |
| 无状态 | 每个请求包含所有必要信息 | 服务端不保存会话状态,使用 Token 认证 | 依赖服务器 Session 存储用户状态 | 便于水平扩展 |
| HATEOAS | 超媒体作为应用状态引擎 | 响应中包含相关资源链接 | 仅返回数据,客户端硬编码 URL | 提高 API 可发现性 |
| 版本控制 | API 版本管理 | /api/v1/users, Accept: application/vnd.myapp.v1+json | /users-v1 | 推荐 URL 路径或 Header 版本控制 |
| HTTP 状态码 | 正确使用状态码 | 200 OK, 201 Created, 400 Bad Request, 404 Not Found, 500 Internal Server Error | 所有成功返回 200,错误返回 200 带错误码 | 客户端依赖状态码判断结果 |
| 资源命名 | 使用名词、复数、小写 | /products, /user-profiles | /getProducts, /Product | 避免动词和大小写混合 |
| 过滤与分页 | 支持查询参数 | GET /users?status=active&page=1&size=10 | GET /users/active | 标准化分页参数(page, size) |
| 安全传输 | 使用 HTTPS | 所有生产环境 API | HTTP 明文传输 | 保护数据安全 |
| 文档化 | 提供 API 文档 | 使用 OpenAPI (Swagger) 生成文档 | 无文档或文档不更新 | 工具:springdoc-openapi |
8.2 @RestController 与 @RequestBody / @ResponseBody
| 注解 | 语法位置 | 用途 | 代码示例 | 注意事项 |
|---|
@RestController | 类级别 | 组合注解(@Controller + @ResponseBody) | @RestController public class UserController { @GetMapping("/users") public List<User> getAll() { ... } } | 所有方法返回值自动序列化为 JSON,无需 @ResponseBody |
@ResponseBody | 方法/类 | 将返回值写入 HTTP 响应体 | @Controller public class ApiController { @ResponseBody @GetMapping("/data") public Map<String, Object> getData() { ... } } | 通常与 @Controller 配合使用,返回 JSON 数据 |
@RequestBody | 方法参数 | 将请求体 JSON 反序列化为对象 | @PostMapping("/users") public User createUser(@RequestBody User user) { ... } | 需配合 HttpMessageConverter(如 Jackson) |
@ResponseStatus | 方法/类 | 自定义 HTTP 响应状态码 | @PostMapping("/users") @ResponseStatus(HttpStatus.CREATED) public User createUser(@RequestBody User user) { ... } | 覆盖默认状态码(如 200) |
ResponseEntity | 方法返回值 | 精确控制响应(状态码、头、体) | @GetMapping("/users/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { return userRepository.findById(id).map(user -> ResponseEntity.ok(user)).orElse(ResponseEntity.notFound().build()); } | 最灵活的方式,推荐用于复杂响应 |
@ExceptionHandler (API) | 方法(@ControllerAdvice) | 全局处理 API 异常并返回 JSON | @ControllerAdvice public class ApiExceptionHandler { @ExceptionHandler(UserNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) @ResponseBody public ErrorResponse handleNotFound(UserNotFoundException e) { return new ErrorResponse("USER_NOT_FOUND", e.getMessage()); } } | 实现统一的错误响应格式 |
8.3 使用 Jackson 进行 JSON 序列化与反序列化
| 注解 | 用途 | 代码示例 | 注意事项 |
|---|
@JsonProperty | 重命名 JSON 字段 | @JsonProperty("user_name") private String name; | 序列化/反序列化时使用指定名称 |
@JsonIgnore | 忽略字段 | @JsonIgnore private String password; | 敏感信息不暴露,也可用 transient |
@JsonIgnoreProperties | 忽略多个字段或未知属性 | @JsonIgnoreProperties({"password", "temp"}) public class User { ... } 或 @JsonIgnoreProperties(ignoreUnknown = true) | 防止反序列化时因未知字段报错 |
@JsonInclude | 控制 null/empty 值输出 | @JsonInclude(JsonInclude.Include.NON_NULL) | 减少 JSON 体积,避免 null 值 |
@JsonFormat | 日期格式化 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; | 统一日期时间格式 |
@JsonPropertyOrder | 指定 JSON 字段顺序 | @JsonPropertyOrder({"id", "name", "email"}) | 控制输出顺序,提高可读性 |
@JsonCreator | 自定义反序列化构造器 | @JsonCreator public User(@JsonProperty("name") String name) { ... } | 用于不可变对象或复杂构造 |
@JacksonInject | 注入值 | @JacksonInject private Service service; | 从外部注入依赖,非常规用法 |
@JsonRawValue | 原生 JSON 值 | @JsonRawValue private String jsonContent; | 字段值作为原始 JSON 插入 |
@JsonSerialize / @JsonDeserialize | 自定义序列化/反序列化器 | @JsonSerialize(using = CustomDateSerializer.class) | 实现复杂逻辑,如加密、特殊格式 |
配置示例:在 application.yml 中配置 Jackson 全局行为:
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
serialization:
write-dates-as-timestamps: false
deserialization:
fail-on-unknown-properties: false
8.4 统一响应格式设计
| 字段 | 类型 | 说明 | 示例值 | 注意事项 |
|---|
code | int | 业务状态码 | 200, 400, 500 | 区分于 HTTP 状态码,用于业务逻辑 |
message | String | 状态描述 | ”Success”, “Invalid request” | 友好提示,可国际化 |
data | Object | 业务数据 | { "id": 1, "name": "John" } 或 null | 实际返回的数据内容 |
timestamp | long | 响应时间戳 | 1715692800000 | 便于问题追踪 |
success | boolean | 是否成功 | true, false | 快速判断请求结果 |
统一响应类设计:
public class ApiResponse<T> {
private int code;
private String message;
private T data;
private long timestamp;
// 构造函数
public ApiResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
this.timestamp = System.currentTimeMillis();
}
// 静态工厂方法
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "Success", data);
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null);
}
// getters and setters...
}
控制器使用示例:
@RestController
public class UserController {
@GetMapping("/users/{id}")
public ApiResponse<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return user != null ?
ApiResponse.success(user) :
ApiResponse.error(404, "User not found");
}
}
8.5 文件上传与下载处理
| 场景 | 方法/注解 | 代码示例 | 注意事项 |
|---|
| 单文件上传 | @RequestParam("file") MultipartFile file | @PostMapping("/upload") public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return ResponseEntity.badRequest().body("File is empty"); } // 保存文件... return ResponseEntity.ok("Upload success"); } | 需配置 multipart.max-file-size 等限制 |
| 多文件上传 | @RequestParam("files") MultipartFile[] files | public String uploadFiles(@RequestParam("files") MultipartFile[] files) { ... } | 循环处理每个文件 |
| 文件下载 | HttpServletResponse 输出流 | @GetMapping("/download/{filename}") public void downloadFile(@PathVariable String filename, HttpServletResponse response) throws IOException { File file = new File(uploadDir, filename); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=" + filename); Files.copy(file.toPath(), response.getOutputStream()); } | 设置正确的 Content-Type 和 Content-Disposition |
| 返回 Resource | Resource 对象 | @GetMapping("/download/{filename}") public ResponseEntity<Resource> downloadFile(@PathVariable String filename) throws IOException { Path path = Paths.get(uploadDir).resolve(filename); Resource resource = new UrlResource(path.toUri()); return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"").body(resource); } | Spring MVC 自动处理流,更安全 |
| 配置限制 | application.yml | spring:\n servlet:\n multipart:\n enabled: true\n max-file-size: 10MB\n max-request-size: 10MB | 防止过大文件导致服务器问题 |
| 大文件流式处理 | StreamingResponseBody | @GetMapping("/large-file") public ResponseEntity<StreamingResponseBody> streamFile() { StreamingResponseBody stream = outputStream -> { outputStream.write(data); // 分块写入输出流 }; return ResponseEntity.ok().body(stream); } | 避免内存溢出,适用于超大文件 |
第九章:Spring Security 安全控制
9.1 认证(Authentication)与授权(Authorization)
| 概念 | 说明 | 核心组件 | 示例 | 注意事项 |
|---|
| 认证 (Authentication) | 验证”你是谁” | AuthenticationManager, UserDetailsService, PasswordEncoder | 用户登录时验证用户名密码 | 是授权的前提 |
| 授权 (Authorization) | 验证”你能做什么” | AccessDecisionManager, SecurityExpressionOperations | 检查用户是否有权限访问 /admin | 基于角色或权限 |
| Principal | 主体 | Authentication.getPrincipal() | 通常是 UserDetails 对象 | 代表当前用户 |
| Credentials | 凭证 | Authentication.getCredentials() | 密码(认证后通常设为 null) | 敏感信息 |
| Authorities | 权限 | Authentication.getAuthorities() | ROLE_ADMIN, PERM_USER_READ | 通常以 ROLE_ 开头表示角色 |
UserDetailsService | 用户信息加载 | loadUserByUsername(String username) | 从数据库加载用户信息 | 必须实现此接口 |
PasswordEncoder | 密码编码器 | encode(), matches() | 使用 BCryptPasswordEncoder 加密密码 | 绝对不要存储明文密码 |
SecurityContextHolder | 安全上下文持有者 | SecurityContextHolder.getContext().getAuthentication() | 获取当前用户信息 | ThreadLocal 存储,注意线程安全 |
| Anonymous Authentication | 匿名认证 | AnonymousAuthenticationToken | 未登录用户被视为匿名用户 | 可设置默认角色 ROLE_ANONYMOUS |
| Remember-Me | 记住我 | RememberMeServices | 登录时勾选”记住我” | 使用令牌长期有效 |
9.2 基于表单的登录配置
| 配置项 | 说明 | 代码示例 | 注意事项 |
|---|
formLogin() | 启用表单登录 | http.formLogin(); | 默认提供登录页 /login |
| 自定义登录页 | 指定登录页面 | http.formLogin().loginPage("/login").permitAll(); | 登录页必须允许匿名访问 |
| 登录处理 URL | 处理登录请求 | .loginProcessingUrl("/doLogin") | 表单 action 应指向此 URL |
| 成功处理 | 登录成功后跳转 | .defaultSuccessUrl("/home", true) 或 .successHandler(...) | true 表示总是跳转到指定页面 |
| 失败处理 | 登录失败后跳转 | .failureUrl("/login?error") 或 .failureHandler(...) | 可在页面显示错误信息 |
| 用户名参数 | 自定义用户名字段 | .usernameParameter("uname") | 表单 name 属性需匹配 |
| 密码参数 | 自定义密码字段 | .passwordParameter("pword") | — |
| 登出配置 | 退出登录 | http.logout().logoutUrl("/logout").logoutSuccessUrl("/login"); | 清除 Session 和 Remember-Me 令牌 |
| Session 管理 | 控制并发会话 | http.sessionManagement().maximumSessions(1).expiredUrl("/login?expired"); | 防止同一账号多处登录 |
| CSRF 保护 | 跨站请求伪造防护 | 默认启用,表单需包含 _csrf | 对 REST API 可禁用 .csrf().disable() |
9.3 方法级安全控制(@PreAuthorize, @Secured)
| 注解 | 用途 | 代码示例 | 注意事项 |
|---|
@PreAuthorize | 方法执行前授权检查 | @PreAuthorize("hasRole('ADMIN')") public void deleteUser(Long id) { ... } | 支持 SpEL 表达式,功能最强大 |
@PostAuthorize | 方法执行后授权检查 | @PostAuthorize("returnObject.owner == authentication.name") public Document getDocument(Long id) { ... } | 基于返回值进行检查 |
@Secured | 基于角色的简单授权 | @Secured("ROLE_ADMIN") public void adminTask() { ... } | 仅支持角色检查,不支持 SpEL |
@RolesAllowed | JSR-250 注解 | @RolesAllowed("ADMIN") public void manage() { ... } | 标准注解,需启用 @EnableGlobalMethodSecurity(jsr250Enabled=true) |
| SpEL 表达式 | 复杂权限逻辑 | @PreAuthorize("#userId == authentication.principal.id") | # 引用方法参数,authentication 引用当前认证 |
| 启用方法安全 | 开启注解支持 | @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class MethodSecurityConfig { } | 必须添加此配置类 |
| 结合参数 | 动态权限检查 | @PreAuthorize("hasPermission(#id, 'document', 'read')") public Document read(Long id) { ... } | 需自定义 PermissionEvaluator |
@DenyAll / @PermitAll | 拒绝/允许所有 | @DenyAll public void forbidden() { } | 明确的访问控制 |
9.4 CSRF 与 CORS 配置
| 配置项 | 说明 | 代码示例 | 注意事项 |
|---|
| CSRF 启用 | 默认开启 | http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); | 保护表单提交 |
| CSRF 禁用 | REST API 场景 | http.csrf().disable(); | 仅在无状态 API 中使用,确保其他安全措施 |
| CORS 配置 | 跨域资源共享 | http.cors().configurationSource(corsConfigurationSource()); | 处理浏览器跨域请求 |
| 全局 CORS | 全局配置 | @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOriginPatterns(Arrays.asList("*")); config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); config.setAllowedHeaders(Arrays.asList("*")); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } | 推荐方式,集中管理 |
@CrossOrigin | 类/方法级 CORS | @CrossOrigin(origins = "https://example.com") | 适用于特定控制器或方法 |
| CSRF Token 传递 | 前端获取 Token | 从 Cookie XSRF-TOKEN 读取,放入请求头 X-XSRF-TOKEN | Angular 等框架自动处理 |
| CORS 预检请求 | 处理 OPTIONS | 服务器自动响应 Access-Control-Allow-* 头 | 不需要手动处理 |
| 安全权衡 | CSRF vs CORS | 同时启用时需谨慎配置 | 确保 withHttpOnlyFalse() 允许 JS 读取 CSRF Cookie |
9.5 集成 JWT 实现无状态认证
| 组件 | 说明 | 代码/库 | 注意事项 |
|---|
| JWT 结构 | 三部分:Header.Payload.Signature | eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c | Base64 编码,点分隔 |
| jjwt 库 | JWT 实现 | io.jsonwebtoken:jjwt-api:0.11.5 | 官方推荐库 |
| Token 生成 | 创建 JWT | String token = Jwts.builder().setSubject(user.getUsername()).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + 86400000)).signWith(SignatureAlgorithm.HS512, secretKey).compact(); | 使用强密钥,设置合理过期时间 |
| Token 解析 | 验证并解析 | try { Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); String username = claims.getBody().getSubject(); } catch (JwtException e) { /* 处理异常 */ } | 捕获 SignatureException、ExpiredJwtException 等 |
| AuthenticationFilter | 拦截请求 | 继承 OncePerRequestFilter,从 Authorization 头提取 Token | Bearer <token> |
| UserDetailsService | 加载用户 | 通过解析出的用户名从数据库加载 UserDetails | 用于构建 Authentication 对象 |
| SecurityContext | 设置认证 | SecurityContextHolder.getContext().setAuthentication(authentication); | 完成认证流程 |
| 刷新 Token | 长期有效机制 | 额外发放 Refresh Token,用于获取新 Access Token | 提高安全性,避免频繁登录 |
| Token 存储 | 前端存储 | localStorage 或 HttpOnly Cookie | HttpOnly 更安全,防止 XSS |
| 黑名单 | 退出登录 | 使用 Redis 存储失效 Token 的 JTI (JWT ID) | 实现主动退出和 Token 吊销 |
第十章:Spring 高级特性与扩展
10.1 事件驱动模型(ApplicationEvent 与 Listener)
| 组件 | 说明 | 代码示例 | 注意事项 |
|---|
ApplicationEvent | 自定义事件 | public class UserRegisteredEvent extends ApplicationEvent { private final User user; public UserRegisteredEvent(User user) { super(user); this.user = user; } public User getUser() { return user; } } | 继承 ApplicationEvent,携带事件数据 |
ApplicationListener | 事件监听器 | @Component public class UserRegistrationListener implements ApplicationListener<UserRegisteredEvent> { @Override public void onApplicationEvent(UserRegisteredEvent event) { System.out.println("Welcome email sent to: " + event.getUser().getEmail()); } } | 实现接口,处理特定事件 |
@EventListener | 注解式监听 | @EventListener public void handleUserRegistration(UserRegisteredEvent event) { ... } | 更简洁,支持 SpEL 条件 @EventListener(condition = "#event.user.active") |
ApplicationEventPublisher | 发布事件 | @Autowired private ApplicationEventPublisher publisher; publisher.publishEvent(new UserRegisteredEvent(user)); | 在业务逻辑中发布事件 |
| 异步监听 | 异步处理事件 | @EventListener @Async public void handleAsync(UserRegisteredEvent event) { ... } | 需启用 @EnableAsync,避免阻塞主流程 |
| 事务绑定事件 | 事务提交后触发 | @EventListener @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | 确保事件在事务成功后才处理 |
| 内置事件 | Spring 容器事件 | ContextRefreshedEvent, ContextStartedEvent, ContextClosedEvent | 监听容器生命周期 |
| 泛型事件 | 类型安全事件 | public class GenericEvent<T> extends ApplicationEvent { ... } | 提高事件处理的灵活性 |
10.2 条件注解(@Conditional)与自动装配控制
| 注解 | 用途 | 代码示例 | 注意事项 |
|---|
@Conditional | 条件化 Bean 注册 | @Conditional(OnClassCondition.class) | 核心条件注解,需指定条件类 |
@ConditionalOnClass | 类路径存在指定类 | @ConditionalOnClass(DataSource.class) | 常用于自动配置 |
@ConditionalOnMissingBean | 容器中不存在指定 Bean | @ConditionalOnMissingBean(DataSource.class) | 避免重复定义,允许用户覆盖 |
@ConditionalOnProperty | 配置属性匹配 | @ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true") | 基于 application.yml 配置启用功能 |
@ConditionalOnWebApplication | Web 应用环境 | @ConditionalOnWebApplication | 区分 Web 和非 Web 环境 |
@ConditionalOnExpression | SpEL 表达式条件 | @ConditionalOnExpression("${app.cache.enabled} and ${app.redis.enabled}") | 复杂条件判断 |
@Profile | 环境 Profile 条件 | @Profile("dev") | 基于 spring.profiles.active |
| 自定义条件 | 实现复杂逻辑 | public class CustomCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return "custom".equals(context.getEnvironment().getProperty("app.mode")); } } | 实现 Condition 接口 |
10.3 国际化(i18n)支持
| 组件 | 说明 | 配置/代码 | 注意事项 |
|---|
MessageSource | 消息源 | @Bean public MessageSource messageSource() { ... } | Spring 提供 ReloadableResourceBundleMessageSource |
| 资源文件 | 语言包 | messages.properties, messages_zh_CN.properties, messages_en_US.properties | 文件名前缀相同,后缀为语言代码 |
| 配置 MessageSource | Java 配置 | @Bean public MessageSource messageSource() { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.setBasename("classpath:messages"); messageSource.setDefaultEncoding("UTF-8"); return messageSource; } | 设置基名和编码 |
LocaleResolver | 区域解析器 | AcceptHeaderLocaleResolver, CookieLocaleResolver, SessionLocaleResolver | 决定当前请求的 Locale |
LocaleChangeInterceptor | 区域变更拦截器 | @Bean public LocaleChangeInterceptor localeChangeInterceptor() { LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor(); interceptor.setParamName("lang"); return interceptor; } | 通过请求参数(如 ?lang=zh_CN)切换语言 |
| Thymeleaf 使用 | 模板中使用 | <p th:text="#{welcome.message}">Welcome</p> | #{} 语法 |
| Java 代码使用 | 程序中获取 | @Autowired private MessageSource messageSource; String msg = messageSource.getMessage("error.required", null, Locale.CHINA); | 提供默认值和 Locale |
| 日期/数字格式化 | 格式化输出 | @DateTimeFormat, @NumberFormat | 结合 Locale 自动格式化 |
10.4 缓存抽象(@Cacheable, @CacheEvict)
| 注解 | 用途 | 代码示例 | 注意事项 |
|---|
@Cacheable | 缓存方法结果 | @Cacheable(value = "users", key = "#id") public User getUser(Long id) { ... } | 方法执行前检查缓存,命中则返回缓存值 |
@CachePut | 更新缓存 | @CachePut(value = "users", key = "#user.id") public User updateUser(User user) { ... } | 方法总是执行,并将结果放入缓存 |
@CacheEvict | 清除缓存 | @CacheEvict(value = "users", key = "#id") public void deleteUser(Long id) { ... } | 清除指定缓存条目 |
@Caching | 组合多个缓存操作 | @Caching(evict = { @CacheEvict("users"), @CacheEvict(value = "cache2", key = "#user.id") }) | 同时执行多个操作 |
CacheManager | 缓存管理器 | ConcurrentMapCacheManager, RedisCacheManager, EhCacheCacheManager | 需配置具体的缓存实现 |
| 启用缓存 | 开启缓存支持 | @EnableCaching | 主配置类上添加 |
| SpEL in Key | 动态缓存键 | key = "#user.name + '_' + #user.age" | 使用 SpEL 表达式生成缓存键 |
| 条件缓存 | 条件化缓存 | @Cacheable(value = "users", condition = "#id < 100") | 满足条件才缓存 |
| 缓存未命中 | 处理空值 | @Cacheable(..., cacheNullValues = false) | 避免缓存 null 值造成”缓存穿透” |
| TTL 配置 | 设置过期时间 | Redis 中配置 timeToLive | 防止缓存数据长期不一致 |
10.5 异步任务执行(@Async)
| 注解/配置 | 用途 | 代码示例 | 注意事项 |
|---|
@Async | 标记异步方法 | @Async public void sendEmail(String to, String content) { ... } | 方法将在单独线程中执行 |
@EnableAsync | 启用异步支持 | @Configuration @EnableAsync public class AsyncConfig { } | 必须添加以启用 @Async |
| 返回值 | 异步方法返回值 | public Future<String> processTask() 或 public CompletableFuture<String> | Future 用于获取结果,CompletableFuture 支持回调 |
| 自定义线程池 | 配置线程池 | @Bean("taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.setThreadNamePrefix("async-"); executor.initialize(); return executor; } | 避免使用默认线程池,防止资源耗尽 |
| 指定执行器 | 使用特定线程池 | @Async("taskExecutor") | 当有多个 Executor Bean 时指定 |
| 异常处理 | 异步异常 | 实现 AsyncUncaughtExceptionHandler | @Async 方法的异常不会被主线程捕获 |
| 调用限制 | 同类调用 | @Async 方法不能在同一个类中被直接调用 | @Async 是通过 Spring AOP 代理实现的,自调用会绕过代理 |
附录:Spring Boot 常用功能与进阶
附1 Spring Boot Actuator 监控端点
| 端点 (Endpoint) | 默认启用 | 敏感性 | 用途 | 访问路径 | 注意事项 |
|---|
/health | 是 | 低 | 显示应用健康状态(数据库、磁盘等) | /actuator/health | 生产环境可暴露,支持 show-details=always |
/info | 是 | 低 | 显示应用信息(git、构建等) | /actuator/info | 需在 application.yml 中配置 info.* 属性 |
/metrics | 是 | 中 | 展示应用性能指标(JVM、HTTP、缓存等) | /actuator/metrics | 可查看具体指标如 /actuator/metrics/jvm.memory.max |
/beans | 否 | 高 | 列出容器中所有 Bean 及其依赖关系 | /actuator/beans | 暴露后可能泄露架构信息,生产慎用 |
/env | 否 | 高 | 显示所有环境变量和配置属性 | /actuator/env | 包含敏感信息(如密码),严禁生产暴露 |
/mappings | 否 | 中 | 列出所有 @RequestMapping 端点 | /actuator/mappings | 用于调试路由问题 |
/conditions | 否 | 中 | 显示自动配置的启用/禁用条件 | /actuator/conditions | 调试自动配置问题非常有用 |
/httptrace | 否 | 中 | 追踪最近的 HTTP 请求响应 | /actuator/httptrace | 需引入 spring-boot-starter-actuator |
/shutdown | 否 | 高 | 关闭应用(POST 请求) | /actuator/shutdown | 必须显式启用,生产环境极度危险 |
/prometheus | 否 | 低 | 暴露 Prometheus 格式的监控数据 | /actuator/prometheus | 需引入 micrometer-registry-prometheus |
安全提示:通过 management.endpoints.web.exposure.include=* 可暴露所有端点,但生产环境必须结合 Spring Security 进行权限控制。
附2 Spring Boot 集成安全框架(Spring Security)
| 配置/注解 | 用途 | 代码示例 | 注意事项 |
|---|
spring-boot-starter-security | 引入安全依赖 | <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency> | 引入后默认保护所有端点 |
WebSecurityConfigurerAdapter | 安全配置类(旧方式) | @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/public/**").permitAll().anyRequest().authenticated().and().formLogin(); } } | Spring Boot 2.7+ 推荐使用组件化配置 |
SecurityFilterChain | 新一代安全配置(推荐) | @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(authz -> authz.requestMatchers("/public/**").permitAll().anyRequest().authenticated()).formLogin(withDefaults()); return http.build(); } | 基于 Lambda,代码更简洁 |
@EnableWebSecurity | 启用 Web 安全 | @Configuration @EnableWebSecurity public class SecurityConfig { ... } | 通常与 @Configuration 一起使用 |
| 内存用户存储 | 测试用用户配置 | @Bean public UserDetailsService users() { UserDetails user = User.builder().username("user").password("{noop}password").roles("USER").build(); return new InMemoryUserDetailsManager(user); } | {noop} 表示不加密,生产环境必须使用 BCrypt |
| JDBC 用户存储 | 数据库存储用户 | http.authorizeRequests()....and().jdbcAuthentication().dataSource(dataSource); | 需要标准的 users、authorities 表结构 |
| CSRF 防护 | 防跨站请求伪造 | 默认启用,表单需包含 _csrf token | 对 REST API 可禁用:.csrf().disable() |
| JWT 集成 | 实现无状态认证 | 需自定义 AuthenticationFilter 和 AuthorizationFilter | 结合 jjwt 库生成和验证 Token |
附3 Spring Boot 测试(Test)
| 注解/类 | 用途 | 代码示例 | 注意事项 |
|---|
@SpringBootTest | 集成测试主注解 | @SpringBootTest(classes = MyApp.class) | 加载完整上下文,启动服务器(可选) |
@WebMvcTest | Web 层测试 | @WebMvcTest(UserController.class) | 仅加载 Web 相关 Bean,速度快 |
@DataJpaTest | JPA 层测试 | @DataJpaTest | 配置内存数据库(如 H2),自动管理事务 |
@MockBean | 创建 Mock Bean | @MockBean private UserService userService; | 替换上下文中真实的 Bean,用于隔离测试 |
TestRestTemplate | 测试 HTTP 请求 | @Autowired private TestRestTemplate restTemplate; | 发送请求到嵌入式服务器,无需端口管理 |
@BeforeEach / @AfterEach | JUnit 5 生命周期 | @BeforeEach void setUp() { ... } | 替代 JUnit 4 的 @Before / @After |
@Sql | 执行 SQL 脚本 | @Sql("/test-data.sql") | 在测试前或后初始化数据库 |
MockMvc | 模拟 MVC 请求 | @Autowired private MockMvc mockMvc; | 不启动服务器,直接调用控制器,速度极快 |
@TestPropertySource | 自定义测试属性 | @TestPropertySource(properties = "app.name=test") | 覆盖配置文件中的属性 |
| Assertions (AssertJ) | 断言库 | assertThat(result).isNotNull().hasSize(2); | 比 JUnit 原生断言更强大、更易读 |
附4 Spring Boot 应用打包与部署
| 方法/工具 | 说明 | 命令/配置 | 注意事项 |
|---|
| Maven 打包 | 使用 Maven 构建可执行 JAR | mvn clean package | 生成 target/*.jar,包含所有依赖 |
| Gradle 打包 | 使用 Gradle 构建 | gradle build | 生成 build/libs/*.jar |
| 可执行 JAR | 内嵌容器的独立包 | java -jar myapp.jar | Spring Boot 默认打包方式,无需外部 Tomcat |
| WAR 包部署 | 部署到外部 Servlet 容器 | 1. 修改 pom.xml <packaging>war</packaging> 2. 主类继承 SpringBootServletInitializer | 适用于必须使用外部容器的场景 |
| Docker 镜像 | 容器化部署 | FROM openjdk:17-jre COPY target/app.jar app.jar ENTRYPOINT ["java", "-jar", "/app.jar"] | 使用分层 JAR 或构建优化镜像大小 |
| Profile 激活 | 指定运行环境 | java -jar app.jar --spring.profiles.active=prod | 结合配置文件实现环境隔离 |
| 外部配置 | 覆盖内嵌配置 | java -jar app.jar --server.port=9090 | 命令行参数优先级最高 |
| JVM 参数调优 | 优化运行性能 | java -Xms512m -Xmx1024m -jar app.jar | 根据服务器资源设置堆大小 |
| 进程守护 | 后台运行应用 | nohup java -jar app.jar > app.log 2>&1 & | 使用 systemd 或 supervisor 更佳 |
| 健康检查 | 容器编排集成 | 结合 /actuator/health 端点 | Kubernetes 中用于 Liveness/Readiness 探针 |
附5 Spring Boot 与微服务初步
| 概念 | 说明 | 实现方式 | 注意事项 |
|---|
| 服务拆分 | 将单体应用拆分为微服务 | 每个服务独立开发、部署、数据库 | 遵循单一职责原则,避免服务过大 |
| RESTful API | 服务间通信 | 使用 @RestController 暴露 JSON API | 设计清晰的 API 文档(如 OpenAPI) |
| Feign Client | 声明式 HTTP 客户端 | @FeignClient("user-service") public interface UserClient { @GetMapping("/users/{id}") User findById(@PathVariable Long id); } | 需引入 spring-cloud-starter-openfeign |
| Eureka 注册中心 | 服务发现 | 服务启动时注册,调用时从注册中心发现 | 需搭建 Eureka Server,实现高可用 |
| Ribbon 负载均衡 | 客户端负载均衡 | Feign 默认集成 Ribbon | 支持轮询、随机等策略 |
| Hystrix 断路器 | 容错与降级 | 防止雪崩效应,超时熔断 | 已进入维护模式,推荐使用 Resilience4j |
| Zuul / Gateway | API 网关 | 统一入口、路由、过滤、限流 | Spring Cloud Gateway 基于 WebFlux,性能更优 |
| Config Server | 集中化配置管理 | 从 Git 仓库加载配置,支持动态刷新 | 使用 @RefreshScope 刷新 Bean |
| Sleuth + Zipkin | 分布式链路追踪 | 生成 Trace ID,追踪请求链路 | 诊断跨服务调用问题 |
| 独立数据库 | 数据库隔离 | 每个微服务拥有独立数据库 | 避免直接跨库访问,通过 API 通信 |