1. Spring 框架的概念、优缺点
-
概念: Spring 是一个开源的 Java 应用程序开发框架,由 Rod Johnson 创建。它提供了一个全面的编程和配置模型,旨在简化企业级 Java 应用程序的开发。Spring 框架的核心是 控制反转 (IoC) 和 面向切面编程 (AOP) 。它是一个轻量级、非侵入式、模块化的框架,可以与其他多种技术集成。
-
优点:
- IoC (控制反转) : 将对象的创建、组装、管理等控制权从应用程序代码转移到 Spring 容器,降低了组件之间的耦合度,提高了可测试性和可维护性。
- AOP (面向切面编程) : 允许开发者将横切关注点(如日志、事务、安全)从核心业务逻辑中分离出来,提高了模块化和代码重用性。
- 轻量级: Spring 框架本身的代码量不大,并且没有侵入性,不会强制开发者继承或实现特定的 Spring 接口。
- 非侵入性: 应用程序的 POJO (Plain Old Java Object) 不需要实现 Spring 特定的接口或继承特定的类,使得业务逻辑更加纯粹。
- 模块化: Spring 框架由多个模块组成,如 Spring Core、Spring AOP、Spring MVC、Spring JDBC、Spring ORM 等,开发者可以根据需要选择使用,避免引入不必要的依赖。
- 强大的生态系统: Spring 提供了大量的功能模块,可以方便地与其他技术(如 JPA、Hibernate、MyBatis、MQ 等)集成。
- 易于测试: 由于 IoC 的特性,组件之间的依赖关系通过容器管理,使得单元测试和集成测试更加容易。
- 声明式事务管理: 提供了基于注解或 XML 配置的声明式事务管理,大大简化了事务管理代码。
-
缺点:
- 复杂性: Spring 框架功能强大,但也相对复杂。对于初学者来说,理解其核心概念(如 IoC、AOP、各种 Bean 的生命周期)需要一定的学习曲线。
- 配置繁琐 (早期) : 在早期版本(特别是 XML 配置时代),Spring 的配置相对繁琐,需要大量的 XML 文件。虽然现在注解配置和 Spring Boot 已经大大简化了配置,但仍然需要一定的了解。
- 性能开销: IoC 容器的启动、Bean 的创建和初始化、AOP 代理的生成等都会带来一定的性能开销,但在大多数企业级应用中,这种开销通常可以忽略不计。
- 抽象层级多: Spring 提供了多层抽象,这使得它非常灵活,但也可能让开发者在遇到问题时难以深入理解其底层工作原理。
2. Spring 核心注解和基本注解
Spring 框架的注解是其配置的核心,极大地简化了开发。
-
核心注解:
@Configuration: 标识一个类为配置类,该类可以定义 Spring Bean。类似于传统的 XML 配置<beans>标签。@ComponentScan: 扫描指定包及其子包下的组件(@Component,@Service,@Repository,@Controller等),并将它们注册到 Spring IoC 容器中。@Bean: 标注在方法上,表示该方法的返回值是一个 Spring Bean,会被注册到 IoC 容器中。
-
基本注解:
-
组件注册相关:
@Component: 通用组件注解,任何 Spring 管理的组件都可以使用此注解。@Service: 标识一个类为业务逻辑层组件,通常用于 Service 层。@Repository: 标识一个类为数据访问层组件,通常用于 DAO (Data Access Object)。@Controller: 标识一个类为 Spring MVC 的控制器,处理 HTTP 请求。@RestController: 组合了@Controller和@ResponseBody,用于构建 RESTful API,直接将返回值作为响应体。
-
依赖注入相关:
@Autowired: 自动装配依赖,可以通过类型或名称进行注入。是 Spring 推荐的依赖注入方式。@Qualifier("beanName"): 当有多个相同类型的 Bean 时,配合@Autowired使用,通过指定 Bean 的名称来唯一确定注入的 Bean。@Resource(name = "beanName"): JSR-250 规范的注解,也可以用于依赖注入,默认按名称注入,如果找不到则按类型注入。
-
配置相关:
@Value("${property.key}"): 注入配置文件中的属性值。@PropertySource("classpath:my.properties"): 导入外部属性文件到 Spring 环境中。@Import(MyConfig.class): 导入其他配置类。@ImportResource("classpath:beans.xml"): 导入传统的 XML 配置。
-
AOP 相关:
@EnableAspectJAutoProxy: 启用 Spring 对 AspectJ 风格的 AOP 自动代理支持。@Aspect: 标识一个类为切面。@Before,@After,@Around,@AfterReturning,@AfterThrowing: 定义通知类型。@Pointcut: 定义切入点表达式。
-
事务相关:
@EnableTransactionManagement: 启用 Spring 的声明式事务管理。@Transactional: 标注在类或方法上,表示该类或方法中的操作将运行在事务上下文中。
-
Spring MVC 相关 (部分) :
@RequestMapping: 映射 HTTP 请求到控制器方法,可以指定请求路径、请求方法等。@GetMapping,@PostMapping,@PutMapping,@DeleteMapping,@PatchMapping:@RequestMapping的快捷方式,分别对应不同的 HTTP 请求方法。@PathVariable: 从 URL 路径中提取参数。@RequestParam: 从请求参数中获取值。@RequestBody: 将请求体的内容绑定到方法参数上,通常用于接收 JSON 或 XML 数据。@ResponseBody: 将方法返回值直接作为响应体返回,通常用于 RESTful API。@RequestHeader: 获取请求头信息。@CookieValue: 获取 Cookie 值。@SessionAttribute: 访问会话中的属性。
-
3. Spring IoC (控制反转) 和 DI (依赖注入) 的概念和工作原理
-
IoC (Inversion of Control - 控制反转) :
- 概念: 控制反转是 Spring 框架的核心思想之一。它指的是将对象创建、管理和对象间依赖关系的控制权从应用程序代码中“反转”给 Spring 容器。传统的编程模式中,对象通常自己负责查找和创建它所依赖的对象;而在 IoC 模式下,对象不再负责这些,而是由容器来负责“注入”它所需要的依赖。
- 核心思想: “不要在代码中手动创建和管理依赖,让容器来做。” 这种反转带来了代码的解耦和更高的可测试性。
-
DI (Dependency Injection - 依赖注入) :
-
概念: 依赖注入是实现 IoC 的一种具体方式。它描述的是容器在运行时,自动将对象所需的依赖关系(其他对象)注入到该对象中。换句话说,被注入的对象不需要自己去查找或创建依赖,而是被动地接受容器提供的依赖。
-
注入方式:
-
构造器注入 (Constructor Injection) : 通过类的构造函数将依赖注入。这是 Spring 推荐的方式,因为它强制了依赖的不可变性,并使得对象在创建时就处于一个完整的、可用的状态。
public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; }} -
Setter 注入 (Setter Injection) : 通过 Setter 方法将依赖注入。这种方式允许在对象创建后灵活地修改依赖,但可能会导致对象在某些状态下不完整。
public class UserService { private UserRepository userRepository; public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; }} -
字段注入 (Field Injection) : 通过反射直接将依赖注入到类的字段上。这是最简洁的方式,但它隐藏了依赖关系,不利于单元测试,且可能导致循环依赖。通常通过
@Autowired注解实现。public class UserService { @Autowired private UserRepository userRepository;}
-
-
-
工作原理:
- 定义 Bean: 开发者通过注解 (
@Component,@Service,@Repository,@Controller,@Bean) 或 XML 配置来告诉 Spring 容器哪些类应该被管理为 Bean,以及它们之间的依赖关系。 - Spring 容器启动: 当应用程序启动时,Spring IoC 容器(例如
ApplicationContext)会进行初始化。 - 扫描与解析: 容器会扫描配置中指定的包,解析所有 Bean 的定义,包括它们的类名、作用域、依赖关系等。
- 创建 Bean 实例: 容器根据 Bean 定义,在适当的时机(例如单例 Bean 在容器启动时创建,原型 Bean 在请求时创建)创建 Bean 的实例。
- 注入依赖: 在创建 Bean 实例后,容器会根据其依赖关系配置,查找或创建所需的依赖 Bean,并通过构造器、Setter 方法或字段反射将其注入到目标 Bean 中。
- Bean 生命周期管理: 容器负责管理 Bean 的整个生命周期,包括初始化前、初始化后、销毁前等回调方法。
- 应用程序使用: 应用程序代码可以通过容器获取已准备好的 Bean,并使用它们进行业务逻辑处理。由于依赖已经注入,应用程序代码无需关心依赖的创建和管理细节。
- 定义 Bean: 开发者通过注解 (
4. Spring AOP (面向切面编程) 的概念和工作原理
-
概念: AOP (Aspect-Oriented Programming - 面向切面编程) 是 Spring 框架的另一个核心概念,它旨在解决横切关注点 (Cross-cutting Concerns) 的问题。横切关注点是指那些分散在应用程序多个模块中,但逻辑上独立的功能,例如日志、事务管理、安全检查、性能监控等。AOP 允许开发者将这些横切关注点从核心业务逻辑中分离出来,从而提高代码的模块化和可维护性。
-
AOP 中的核心概念:
-
横切关注点 (Cross-cutting Concern) : 那些遍布于多个模块的关注点,如日志、事务、安全等。
-
切面 (Aspect) : 一个模块化的横切关注点,它包含通知 (Advice) 和切入点 (Pointcut)。
-
通知 (Advice) : 在特定的连接点上执行的动作。例如,在方法调用前记录日志。
@Before: 在目标方法执行之前执行。@AfterReturning: 在目标方法成功执行(没有抛出异常)之后执行。@AfterThrowing: 在目标方法抛出异常之后执行。@After: 在目标方法执行之后(无论是否成功或抛出异常)执行。@Around: 包裹目标方法的执行,可以在目标方法执行前后自定义行为,甚至阻止目标方法的执行。
-
连接点 (Join Point) : 应用程序执行过程中可以插入切面的点,例如方法调用、异常抛出、字段访问等。Spring AOP 只支持方法连接点。
-
切入点 (Pointcut) : 定义了在哪些连接点上应用通知的表达式。它筛选出满足特定条件的连接点。
-
目标对象 (Target Object) : 包含业务逻辑的普通对象,切面会作用于它。
-
AOP 代理 (AOP Proxy) : Spring AOP 通过代理模式实现。当一个 Bean 被切面增强时,Spring 会为该 Bean 创建一个代理对象。所有对目标对象的调用都会先经过这个代理对象,代理对象再将调用转发给目标对象,并在转发前后执行通知。
- JDK 动态代理: 代理接口。
- CGLIB 代理: 代理类。
-
织入 (Weaving) : 将切面应用到目标对象,创建 AOP 代理的过程。织入可以在编译时、类加载时或运行时进行。Spring AOP 是在运行时通过动态代理进行织入的。
-
-
工作原理:
-
定义切面: 开发者创建一个带有
@Aspect注解的类,并在其中定义通知方法(使用@Before,@AfterReturning等)和切入点表达式(使用@Pointcut)。 -
启用 AOP: 在 Spring 配置中,通过
@EnableAspectJAutoProxy注解来启用 Spring 对 AspectJ 风格的 AOP 自动代理支持。 -
Spring 容器启动: 当 Spring 容器启动时,它会扫描所有的 Bean 定义。
-
识别目标对象: 容器会识别哪些 Bean 是目标对象,即它们的某个方法匹配了切面中定义的切入点。
-
创建代理: 对于那些需要被增强的目标对象,Spring AOP 会为它们创建代理对象。
- 如果目标对象实现了接口,Spring 默认使用 JDK 动态代理。
- 如果目标对象没有实现接口,或者强制使用 CGLIB 代理 (例如通过
proxyTargetClass=true配置),Spring 会使用 CGLIB 动态代理。
-
调用方法: 当应用程序代码调用目标对象的方法时,实际上是调用了其代理对象的方法。
-
执行通知: 代理对象会根据切面中定义的通知类型和切入点,在目标方法执行的前后、异常抛出时等特定时机执行相应的通知逻辑。
-
转发给目标: 在执行完相应的通知逻辑后,代理对象会将方法调用转发给实际的目标对象来执行业务逻辑。
-
返回结果: 目标方法执行完毕后,结果会返回给代理对象,代理对象可能会执行其他通知(如
@AfterReturning),然后将最终结果返回给调用者。
-
5. Spring 的 Bean 生命周期
Spring Bean 的生命周期是指一个 Bean 从创建到销毁的整个过程。理解 Bean 的生命周期对于优化应用程序、解决问题和进行高级配置至关重要。
以下是 Spring Bean 的典型生命周期阶段:
-
实例化 (Instantiation) :
- Spring IoC 容器根据 Bean 定义(通常是通过反射)创建 Bean 的实例。此时 Bean 只是一个裸对象,其属性尚未填充。
-
属性赋值 (Populate Properties) :
- 容器根据 Bean 定义中的依赖配置(如
@Autowired、<property>标签),将所有依赖的 Bean 注入到刚刚创建的实例中。这可以通过 Setter 方法、构造器参数或字段反射实现。
- 容器根据 Bean 定义中的依赖配置(如
-
BeanNameAware 接口回调 (如果实现) :
- 如果 Bean 实现了
org.springframework.beans.factory.BeanNameAware接口,容器会调用setBeanName()方法,将 Bean 的 ID 或名称传递给它。
- 如果 Bean 实现了
-
BeanFactoryAware 接口回调 (如果实现) :
- 如果 Bean 实现了
org.springframework.beans.factory.BeanFactoryAware接口,容器会调用setBeanFactory()方法,将当前的BeanFactory实例传递给它。
- 如果 Bean 实现了
-
ApplicationContextAware 接口回调 (如果实现) :
- 如果 Bean 实现了
org.springframework.context.ApplicationContextAware接口,容器会调用setApplicationContext()方法,将当前的ApplicationContext实例传递给它。
- 如果 Bean 实现了
-
BeanPostProcessor (前置处理) :
- 如果容器中注册了
org.springframework.beans.factory.config.BeanPostProcessor接口的实现类,那么会调用其postProcessBeforeInitialization()方法。开发者可以在这个阶段对 Bean 进行自定义处理或修改。
- 如果容器中注册了
-
InitializingBean 接口回调 (如果实现) :
- 如果 Bean 实现了
org.springframework.beans.factory.InitializingBean接口,容器会调用其afterPropertiesSet()方法。这个方法允许 Bean 在所有属性都被设置后执行自定义的初始化逻辑。
- 如果 Bean 实现了
-
自定义初始化方法 (init-method) :
- 如果 Bean 定义中通过
@Bean(initMethod = "myInit")或 XML 配置init-method="myInit"指定了初始化方法,容器会调用这个方法。
- 如果 Bean 定义中通过
-
BeanPostProcessor (后置处理) :
- 在所有初始化回调(
InitializingBean.afterPropertiesSet()和init-method)执行完毕后,容器会调用BeanPostProcessor的postProcessAfterInitialization()方法。在这个阶段,Spring AOP 会在这里为 Bean 创建代理对象。
- 在所有初始化回调(
-
Bean 就绪 (Ready for use) :
- 至此,Bean 已经完全初始化并准备好,可以被应用程序使用。它被存储在 IoC 容器中,等待被其他 Bean 引用或被应用程序获取。
-
Bean 销毁 (Destruction) :
-
当 Spring IoC 容器关闭时,对于单例 Bean,容器会调用其销毁方法。
-
DisposableBean 接口回调 (如果实现) :
- 如果 Bean 实现了
org.springframework.beans.factory.DisposableBean接口,容器会调用其destroy()方法。
- 如果 Bean 实现了
-
自定义销毁方法 (destroy-method) :
- 如果 Bean 定义中通过
@Bean(destroyMethod = "myDestroy")或 XML 配置destroy-method="myDestroy"指定了销毁方法,容器会调用这个方法。
- 如果 Bean 定义中通过
-
对于原型 (prototype) 作用域的 Bean,Spring 容器不管理其完整的生命周期,只负责创建和属性赋值。销毁工作通常由应用程序代码自行处理。
-
生命周期总结:
实例化 -> 属性赋值 -> BeanNameAware -> BeanFactoryAware -> ApplicationContextAware -> BeanPostProcessor.postProcessBeforeInitialization() -> InitializingBean.afterPropertiesSet() -> 自定义 init-method -> BeanPostProcessor.postProcessAfterInitialization() -> Bean 就绪 -> (容器关闭) -> DisposableBean.destroy() -> 自定义 destroy-method -> Bean 销毁。
6. Spring 的 Bean 作用域 (Scope)
Spring 容器中的 Bean 作用域定义了 Spring IoC 容器如何创建和管理 Bean 实例。
-
Singleton (单例) :
-
描述: 这是 Spring 默认的作用域。在整个 Spring IoC 容器中,一个 Bean 定义只对应一个 Bean 实例。无论多少次请求该 Bean,容器都会返回同一个实例。
-
生命周期: 容器启动时创建,容器关闭时销毁。
-
用途: 无状态的 Bean,如 Service、DAO、Controller 等。
-
示例:
@Component@Scope("singleton") // 默认就是 singleton,可以省略public class MySingletonBean {}或者 XML:
<bean id="mySingletonBean" class="com.example.MySingletonBean" scope="singleton"/>
-
-
Prototype (原型) :
-
描述: 每次请求该 Bean 时,Spring 容器都会创建一个新的 Bean 实例。这意味着每个注入或通过
getBean()获取的 Bean 都是独立的。 -
生命周期: 容器只负责创建和属性赋值,不负责销毁。销毁工作通常由应用程序代码自行处理。
-
用途: 有状态的 Bean,例如每次操作都需要一个全新的工作流实例,或者需要线程安全的 Bean。
-
示例:
@Component@Scope("prototype")public class MyPrototypeBean {}或者 XML:
<bean id="myPrototypeBean" class="com.example.MyPrototypeBean" scope="prototype"/>
-
-
Request (请求) :
-
描述: 仅适用于 Web 应用程序。在单个 HTTP 请求的生命周期内,一个 Bean 定义只对应一个 Bean 实例。每次 HTTP 请求都会创建一个新的 Bean 实例,并在请求结束时销毁。
-
生命周期: 随 HTTP 请求的开始而创建,随 HTTP 请求的结束而销毁。
-
用途: 存储请求相关的数据,如表单数据、请求上下文信息。
-
注意: 使用此作用域需要 Web 环境,并且需要注册
RequestContextListener或RequestContextFilter。 -
示例:
@Component@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)public class RequestScopedBean {}proxyMode是必需的,因为请求作用域的 Bean 被注入到单例 Bean 时,Spring 需要注入一个代理而不是实际的 Bean 实例。
-
-
Session (会话) :
-
描述: 仅适用于 Web 应用程序。在单个 HTTP Session 的生命周期内,一个 Bean 定义只对应一个 Bean 实例。每个用户会话都会创建一个新的 Bean 实例,并在会话结束时销毁。
-
生命周期: 随 HTTP Session 的创建而创建,随 HTTP Session 的销毁而销毁。
-
用途: 存储用户会话相关的数据,如用户登录信息、购物车。
-
注意: 同样需要 Web 环境和
proxyMode。 -
示例:
@Component@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)public class SessionScopedBean {}
-
-
Application (应用) :
-
描述: 仅适用于 Web 应用程序。在整个 Web 应用程序的生命周期内,一个 Bean 定义只对应一个 Bean 实例。这个实例在 Web 应用程序启动时创建,并在 Web 应用程序关闭时销毁。
-
生命周期: 随 Web 应用程序的启动而创建,随 Web 应用程序的关闭而销毁。
-
用途: 存储应用程序级别的共享数据,类似于
ServletContext属性。 -
示例:
@Component@Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)public class ApplicationScopedBean {}
-
-
WebSocket (WebSocket) :
- 描述: 仅适用于 WebSocket 应用程序。在单个 WebSocket 会话的生命周期内,一个 Bean 定义只对应一个 Bean 实例。
- 用途: 存储 WebSocket 会话相关的数据。
选择 Bean 作用域的原则:
- 绝大多数情况下,使用默认的
singleton作用域。 - 如果 Bean 包含可变状态且该状态不能被并发访问,或者每次使用都需要一个新的独立实例,则使用
prototype。 - 在 Web 应用程序中,根据数据的作用范围选择
request、session或application。
7. Spring 的事务管理
Spring 框架提供了强大的事务管理功能,大大简化了企业级应用程序中事务处理的复杂性。Spring 的事务管理支持声明式事务和编程式事务,其中声明式事务是更推荐和常用的方式。
-
概念: 事务是一组操作,这些操作要么全部成功,要么全部失败。它具有 ACID 特性:
- 原子性 (Atomicity) : 事务是一个不可分割的整体,所有操作要么都发生,要么都不发生。
- 一致性 (Consistency) : 事务执行前后,数据库从一个一致性状态转换到另一个一致性状态。
- 隔离性 (Isolation) : 多个并发事务之间互不干扰,一个事务的中间结果对其他事务不可见。
- 持久性 (Durability) : 事务一旦提交,其所做的修改是永久性的,即使系统故障也不会丢失。
-
事务管理器 (PlatformTransactionManager) : Spring 事务抽象的核心接口是
PlatformTransactionManager,它为不同的事务技术(JDBC、JPA、Hibernate 等)提供了统一的编程模型。DataSourceTransactionManager: 用于 JDBC 事务。JpaTransactionManager: 用于 JPA 事务。HibernateTransactionManager: 用于 Hibernate 事务。
-
编程式事务:
-
描述: 通过编写代码来显式地管理事务的开始、提交和回滚。虽然提供了最大的灵活性,但会使业务逻辑与事务逻辑耦合,增加代码的复杂性和重复性。
-
示例:
@Servicepublic class UserServiceImpl implements UserService { private final PlatformTransactionManager transactionManager; private final UserRepository userRepository; public UserServiceImpl(PlatformTransactionManager transactionManager, UserRepository userRepository) { this.transactionManager = transactionManager; this.userRepository = userRepository; } public void createUser(User user) { TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { userRepository.save(user); transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); throw e; } }}
-
-
声明式事务 (推荐) :
-
描述: 通过 AOP 的方式,在不修改业务代码的情况下,通过配置(注解或 XML)来声明事务行为。这是 Spring 推荐的事务管理方式,它将事务管理逻辑从业务逻辑中解耦。
-
核心注解:
@Transactional -
启用声明式事务: 在配置类上添加
@EnableTransactionManagement注解。 -
@Transactional注解的属性:-
propagation: 事务传播行为,定义业务方法在事务上下文中如何运行。REQUIRED(默认): 如果当前有事务,则加入该事务;如果没有,则创建一个新事务。SUPPORTS: 如果当前有事务,则加入该事务;如果没有,则以非事务方式执行。MANDATORY: 如果当前有事务,则加入该事务;如果没有,则抛出异常。REQUIRES_NEW: 总是启动一个新事务,并挂起当前事务(如果存在)。NOT_SUPPORTED: 以非事务方式执行,并挂起当前事务(如果存在)。NEVER: 以非事务方式执行;如果当前有事务,则抛出异常。NESTED: 如果当前有事务,则在嵌套事务中执行;如果没有,则创建一个新事务(类似于REQUIRED)。嵌套事务可以通过保存点回滚到外层事务的特定点。
-
isolation: 事务隔离级别,定义事务在并发访问时如何相互影响。DEFAULT(默认): 使用底层数据库的默认隔离级别。READ_UNCOMMITTED: 允许读取未提交的数据 (脏读)。READ_COMMITTED: 只能读取已提交的数据,但可能出现不可重复读和幻读。REPEATABLE_READ: 解决了不可重复读,但可能出现幻读。SERIALIZABLE: 最高的隔离级别,完全串行化,避免所有并发问题,但性能最低。
-
timeout: 事务超时时间,指定事务在回滚之前可以运行的最长时间(以秒为单位)。 -
readOnly: 只读事务,设置为true表示事务只进行读操作,不进行写操作。可以提高某些数据库的性能。 -
rollbackFor: 指定哪些异常会导致事务回滚。默认情况下,Spring 事务只对运行时异常 (RuntimeException) 及其子类进行回滚,对受检异常 (Checked Exception) 不回滚。 -
noRollbackFor: 指定哪些异常不会导致事务回滚。
-
-
示例:
@Service @EnableTransactionManagement // 放在配置类或主启动类上 public class UserServiceImpl implements UserService { private final UserRepository userRepository; public UserServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void createUser(User user) { userRepository.save(user); // 模拟一个运行时异常,会触发事务回滚 // if (user.getAge() < 0) { // throw new RuntimeException("Age cannot be negative."); // } } @Transactional(readOnly = true) // 只读事务 public User findUserById(Long id) { return userRepository.findById(id); } }
-
-
工作原理:
-
配置事务管理器: 在 Spring 配置中,需要定义一个
PlatformTransactionManager的 Bean,并配置它所管理的数据源。 -
启用声明式事务: 通过
@EnableTransactionManagement注解通知 Spring 容器启用声明式事务支持。 -
AOP 代理: 当 Spring 容器发现一个 Bean 带有
@Transactional注解时,它会为这个 Bean 创建一个代理对象(通常是 JDK 动态代理或 CGLIB 代理)。 -
拦截方法调用: 当应用程序调用带有
@Transactional注解的方法时,实际上是调用了代理对象的方法。 -
事务切面逻辑: 代理对象中的事务切面会拦截方法调用:
-
开启事务: 在目标方法执行之前,事务切面会根据
@Transactional注解的属性(传播行为、隔离级别等)来开启一个事务。 -
执行业务逻辑: 调用目标对象的实际业务方法。
-
提交或回滚:
- 如果业务方法成功执行且没有抛出任何
rollbackFor指定的异常,事务切面会提交事务。 - 如果业务方法抛出了
rollbackFor指定的异常(默认是运行时异常),事务切面会回滚事务。
- 如果业务方法成功执行且没有抛出任何
-
关闭事务: 事务结束后,释放资源。
-
-
8. Spring MVC 的工作流程
Spring MVC 是 Spring 框架的 Web 模块,它遵循 MVC (Model-View-Controller) 设计模式,用于构建 Web 应用程序。
以下是 Spring MVC 请求处理的详细工作流程:
-
客户端发送请求:
- 用户通过浏览器向 Web 应用程序发送一个 HTTP 请求(例如
GET /user/view?id=123)。
- 用户通过浏览器向 Web 应用程序发送一个 HTTP 请求(例如
-
DispatcherServlet接收请求:- Web 服务器(如 Tomcat)接收到请求后,根据
web.xml或 Spring Boot 的自动配置,将请求转发给 Spring MVC 的核心前端控制器DispatcherServlet。DispatcherServlet是整个 Spring MVC 的枢纽。
- Web 服务器(如 Tomcat)接收到请求后,根据
-
HandlerMapping查找处理器:DispatcherServlet委托HandlerMapping(处理器映射器) 根据请求的 URL 路径查找能够处理该请求的处理器(Controller 中的方法)。- 常用的
HandlerMapping实现有RequestMappingHandlerMapping(支持@RequestMapping注解)。 HandlerMapping会返回一个HandlerExecutionChain(处理器执行链),其中包含一个处理器 (Handler) 实例和一系列拦截器 (HandlerInterceptor)。
-
HandlerInterceptor(前置处理) :DispatcherServlet依次调用HandlerExecutionChain中的所有拦截器的preHandle()方法。- 如果任何一个
preHandle()方法返回false,则请求处理中断,不再执行后续的拦截器和处理器方法。这常用于权限验证、日志记录等。
-
HandlerAdapter调用处理器方法:DispatcherServlet委托HandlerAdapter(处理器适配器) 调用找到的处理器方法。HandlerAdapter负责将请求参数绑定到处理器方法的参数上(例如:@PathVariable、@RequestParam、@RequestBody等)。- 处理器方法执行具体的业务逻辑,可能调用 Service 层和 Repository 层进行数据处理。
-
处理器方法返回
ModelAndView或其他类型:-
处理器方法执行完毕后,会返回一个结果。这个结果可以是:
ModelAndView对象: 包含模型数据和逻辑视图名。String: 逻辑视图名 (如果使用视图解析器)。- Java 对象: (如果使用了
@ResponseBody或@RestController),会被HttpMessageConverter转换为 JSON 或 XML 格式的响应体。 void: 如果直接将响应写入HttpServletResponse。
-
-
HandlerInterceptor(后置处理) :- 在处理器方法执行之后,但在视图渲染之前,
DispatcherServlet会依次调用拦截器的postHandle()方法。 - 在这里可以对
ModelAndView对象进行修改,添加公共数据等。
- 在处理器方法执行之后,但在视图渲染之前,
-
ViewResolver解析视图:- 如果处理器方法返回的是逻辑视图名(例如
return "hello";),DispatcherServlet委托ViewResolver(视图解析器) 根据逻辑视图名解析出具体的View对象。 - 常用的
ViewResolver实现有InternalResourceViewResolver(用于 JSP)、ThymeleafViewResolver、FreeMarkerViewResolver等。 ViewResolver根据配置(如前缀和后缀)找到对应的视图资源(如 JSP 文件、HTML 模板)。
- 如果处理器方法返回的是逻辑视图名(例如
-
视图渲染 (View Rendering) :
ViewResolver返回View对象后,View对象负责将模型数据(由控制器添加到Model中的数据)填充到视图模板中,生成最终的 HTML 响应。
-
HandlerInterceptor(完成处理) :- 在整个请求处理完成之后(视图渲染完毕或发生异常),
DispatcherServlet会依次调用拦截器的afterCompletion()方法。 - 这个方法通常用于资源清理、日志记录等,无论请求是否成功都会执行。
- 在整个请求处理完成之后(视图渲染完毕或发生异常),
-
响应返回客户端:
- 最终生成的 HTML 响应(或 JSON/XML 响应)通过
DispatcherServlet和底层的 Servlet 容器发送回客户端浏览器。
- 最终生成的 HTML 响应(或 JSON/XML 响应)通过
简要流程图:
客户端 (Browser)
↓ 请求
DispatcherServlet
↓
HandlerMapping (查找处理器和拦截器)
↓
HandlerInterceptor (preHandle())
↓
HandlerAdapter (调用 Controller 方法)
↓ 执行业务逻辑 (Service, DAO)
Controller 方法返回 ModelAndView/String/Object
↓
HandlerInterceptor (postHandle())
↓
ViewResolver (解析视图)
↓
View (渲染视图,填充模型数据)
↓
HandlerInterceptor (afterCompletion())
↓
响应 (HTML/JSON/XML)
↓
客户端 (Browser)
9. Spring Data JPA 集成和使用
Spring Data JPA 是 Spring 框架的一部分,旨在简化 JPA (Java Persistence API) 的数据访问层开发。它提供了一套强大的抽象,允许开发者通过定义接口和方法命名约定来自动实现数据访问操作,而无需编写大量的样板代码。
-
概念: JPA 是一种 Java 持久化规范,用于将 Java 对象映射到关系数据库中。Spring Data JPA 在 JPA 之上提供了一个更高级别的抽象,使得数据访问层的开发更加高效。
-
集成步骤 (以 Spring Boot 为例) :
-
添加依赖: 在
pom.xml中添加 Spring Data JPA Starter 和数据库驱动(以 H2 内存数据库为例,实际项目会使用 MySQL, PostgreSQL 等)。<dependencies> <!-- Spring Data JPA Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- H2 内存数据库 (开发和测试用,实际项目可替换为其他数据库驱动) --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <!-- Lombok (可选,用于简化实体类) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> -
配置数据源: 在
application.properties或application.yml中配置数据库连接信息。# application.properties spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password spring.jpa.database-platform=org.hibernate.dialect.H2Dialect # 指定 JPA 数据库方言 # Hibernate 相关配置 (Spring Boot 默认使用 Hibernate 作为 JPA 实现) spring.jpa.hibernate.ddl-auto=update # 数据模式自动更新 (生产环境请谨慎使用 'update' 或 'create') spring.jpa.show-sql=true # 打印 SQL 语句 spring.jpa.properties.hibernate.format_sql=true # 格式化 SQL 语句 -
创建实体类 (Entity) : 使用 JPA 注解 (
@Entity,@Id,@GeneratedValue,@Column等) 定义实体类。package com.example.demo.entity; import lombok.Data; import javax.persistence.*; @Entity // 标识这是一个 JPA 实体 @Table(name = "users") // 映射到数据库表名 @Data // Lombok 注解,自动生成 Getter/Setter 等 public class User { @Id // 标识主键 @GeneratedValue(strategy = GenerationType.IDENTITY) // 主键生成策略,IDENTITY 表示数据库自增 private Long id; @Column(name = "user_name", nullable = false, unique = true) // 映射到数据库列名,非空,唯一 private String name; private Integer age; // ... 其他属性 } -
创建 Repository 接口: 定义一个继承自
JpaRepository的接口。JpaRepository已经包含了基本的 CRUD 操作方法。package com.example.demo.repository; import com.example.demo.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository // 标识这是一个数据访问层组件 public interface UserRepository extends JpaRepository<User, Long> { // Spring Data JPA 会根据方法名自动生成 SQL 查询 User findByName(String name); // 根据姓名查找用户 List<User> findByAgeGreaterThan(Integer age); // 查找年龄大于指定值的用户 List<User> findByNameContaining(String name); // 模糊查询 // 可以使用 @Query 注解编写 JPQL 或原生 SQL // @Query("SELECT u FROM User u WHERE u.age = ?1") // JPQL // List<User> findUsersByAge(Integer age); // @Query(value = "SELECT * FROM users WHERE user_name LIKE %?1%", nativeQuery = true) // 原生 SQL // List<User> searchUsersByNameNative(String name); } -
在 Service 层使用 Repository: 在 Service 或 Controller 层中注入
UserRepository接口,即可使用其提供的方法。package com.example.demo.service; import com.example.demo.entity.User; import com.example.demo.repository.UserRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; @Service public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; } @Transactional // 声明事务 public User saveUser(User user) { return userRepository.save(user); // 插入或更新 } @Transactional(readOnly = true) public Optional<User> getUserById(Long id) { return userRepository.findById(id); // 根据 ID 查找 } @Transactional(readOnly = true) public User getUserByName(String name) { return userRepository.findByName(name); // 根据方法名生成的查询 } @Transactional(readOnly = true) public List<User> getAllUsers() { return userRepository.findAll(); // 查找所有 } @Transactional public void deleteUser(Long id) { userRepository.deleteById(id); // 根据 ID 删除 } }
-
-
特点和优势:
- 约定大于配置: Spring Data JPA 极大地减少了配置和样板代码,开发者只需定义接口并遵循命名约定,大部分 CRUD 操作将自动实现。
- 强大的查询方法生成: Spring Data JPA 可以根据方法名自动推断出 SQL 查询。例如
findByNameAndAge()。 - 自定义查询: 可以通过
@Query注解编写 JPQL (Java Persistence Query Language) 或原生 SQL。 - 分页和排序: 内置了分页 (Pageable) 和排序 (Sort) 支持。
- 与 Spring 事务集成: 与 Spring 的声明式事务管理无缝集成,简化了事务处理。
- 与其他 Spring 组件集成: 轻松与 Spring MVC、Spring Security 等集成。
- 降低学习曲线: 对于熟悉 SQL 的开发者来说,上手相对容易。
10. Spring Security 的概念、认证和授权
Spring Security 是一个功能强大且高度可定制的身份验证和授权框架,用于保护 Spring 应用程序。它是 Spring 框架的重要组成部分,旨在解决企业级应用程序中的安全问题。
-
概念:
- 认证 (Authentication) : 验证用户身份的过程。即“你是谁?”。例如,通过用户名和密码验证用户是否是合法用户。
- 授权 (Authorization) : 确定用户是否有权限执行某个操作或访问某个资源的过程。即“你能做什么?”。例如,一个用户是否可以访问某个管理页面或执行删除操作。
- 主体 (Principal) : 访问系统的用户或实体。
- 凭据 (Credentials) : 用于验证主体的证据,如密码、指纹、令牌等。
- 权限 (Authorities/Roles) : 描述主体拥有的权限集合。通常分为角色 (Role) 和权限 (Permission)。角色是一组权限的集合(例如 ADMIN 角色拥有所有权限),权限是更细粒度的操作(例如 DELETE_USER 权限)。
-
Spring Security 的核心组件:
FilterChainProxy: Spring Security 的入口,它是一个 ServletFilter,负责将请求委托给一系列安全过滤器 (SecurityFilterChain)。SecurityContextHolder: 存储当前应用程序的安全上下文,通常包含当前已认证用户的信息(Authentication对象)。Authentication: 表示已认证或正在尝试认证的用户主体。它包含用户的身份信息、凭据和权限。AuthenticationManager: 认证管理器,接收Authentication请求,并尝试对其进行认证。AuthenticationProvider: 认证提供者,AuthenticationManager会委托给一个或多个AuthenticationProvider来执行实际的认证逻辑(如通过数据库验证用户名密码)。UserDetailsService: 用户详情服务,负责加载用户详情 (UserDetails)。UserDetails: 包含用户账户信息(用户名、密码、权限、账户是否启用/锁定/过期等)。GrantedAuthority: 授予用户的权限。AccessDecisionManager: 访问决策管理器,决定用户是否有权限访问某个资源。AccessDecisionVoter: 访问决策投票器,AccessDecisionManager会委托给一个或多个AccessDecisionVoter来对授权请求进行投票。
-
认证过程 (Authentication) :
-
用户提交凭据: 用户在登录页面输入用户名和密码,提交给应用程序。
-
UsernamePasswordAuthenticationFilter拦截: 请求被UsernamePasswordAuthenticationFilter拦截(Spring Security 默认的认证过滤器)。 -
创建
Authentication对象:UsernamePasswordAuthenticationFilter从请求中获取用户名和密码,创建一个UsernamePasswordAuthenticationToken(实现了Authentication接口)。 -
AuthenticationManager认证:UsernamePasswordAuthenticationToken被传递给AuthenticationManager。 -
AuthenticationProvider验证:AuthenticationManager会迭代其配置的AuthenticationProvider列表,找到一个支持UsernamePasswordAuthenticationToken的AuthenticationProvider(通常是DaoAuthenticationProvider)。 -
UserDetailsService加载用户:DaoAuthenticationProvider调用自定义的UserDetailsService来根据用户名加载用户详情 (UserDetails)。 -
密码比对:
DaoAuthenticationProvider使用PasswordEncoder对用户提交的密码进行加密,然后与从UserDetails中获取的加密密码进行比对。 -
认证成功/失败:
- 成功: 如果密码匹配,
DaoAuthenticationProvider返回一个完全认证的Authentication对象。这个对象会被存入SecurityContextHolder中,表示当前用户已认证。 - 失败: 如果密码不匹配或用户不存在,则抛出
AuthenticationException。
- 成功: 如果密码匹配,
-
-
授权过程 (Authorization) :
-
用户访问受保护资源: 已认证的用户尝试访问一个受保护的 URL 或执行一个受保护的方法。
-
FilterSecurityInterceptor拦截: 请求被FilterSecurityInterceptor拦截。 -
获取访问决策信息:
FilterSecurityInterceptor从配置中获取访问该资源所需的权限信息(例如,hasRole('ADMIN'))。 -
AccessDecisionManager决策:FilterSecurityInterceptor将认证信息 (Authentication对象)、受保护资源和所需的权限信息传递给AccessDecisionManager。 -
AccessDecisionVoter投票:AccessDecisionManager会委托给一个或多个AccessDecisionVoter来对授权请求进行投票。- 例如,
RoleVoter会检查Authentication对象中是否包含所需的角色。 WebExpressionVoter会评估 SpEL (Spring Expression Language) 表达式(如hasRole('ADMIN'))。
- 例如,
-
授权通过/拒绝:
- 通过: 如果
AccessDecisionManager最终决定授权通过,则允许用户访问资源。 - 拒绝: 如果授权被拒绝,则抛出
AccessDeniedException,用户会被重定向到错误页面或显示拒绝访问消息。
- 通过: 如果
-
-
配置 Spring Security (示例) :
-
添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> -
创建安全配置类:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; @Configuration @EnableWebSecurity // 启用 Spring Security 的 Web 安全功能 @EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法级别的安全注解,如 @PreAuthorize public class WebSecurityConfig extends WebSecurityConfigurerAdapter { // 配置 URL 级别的安全策略 @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() // 允许所有人访问 /public 路径 .antMatchers("/admin/**").hasRole("ADMIN") // 只有 ADMIN 角色才能访问 /admin 路径 .anyRequest().authenticated() // 其他所有请求都需要认证 .and() .formLogin() // 启用表单登录 .loginPage("/login") // 指定自定义登录页面 .permitAll() // 允许所有人访问登录页面 .and() .logout() // 启用注销功能 .permitAll() // 允许所有人访问注销 .and() .csrf().disable(); // 禁用 CSRF 保护 (生产环境请谨慎禁用) } // 配置用户详情服务 (这里使用内存用户) @Bean @Override public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password(passwordEncoder().encode("password")) .roles("USER") .build(); UserDetails admin = User.withUsername("admin") .password(passwordEncoder().encode("admin")) .roles("ADMIN", "USER") .build(); return new InMemoryUserDetailsManager(user, admin); } // 配置密码编码器 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); // 推荐使用 BCryptPasswordEncoder } } -
在 Controller 中使用方法级别安全注解:
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HomeController { @GetMapping("/") public String home() { return "Welcome!"; } @GetMapping("/user") @PreAuthorize("hasRole('USER')") // 只有 USER 角色才能访问 public String userPage() { return "Hello User!"; } @GetMapping("/admin") @PreAuthorize("hasRole('ADMIN')") // 只有 ADMIN 角色才能访问 public String adminPage() { return "Hello Admin!"; } }
-