spring框架不熟悉?,一文教你速通spring!

92 阅读31分钟

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;}
        
  • 工作原理:

    1. 定义 Bean: 开发者通过注解 (@Component, @Service, @Repository, @Controller, @Bean) 或 XML 配置来告诉 Spring 容器哪些类应该被管理为 Bean,以及它们之间的依赖关系。
    2. Spring 容器启动: 当应用程序启动时,Spring IoC 容器(例如 ApplicationContext)会进行初始化。
    3. 扫描与解析: 容器会扫描配置中指定的包,解析所有 Bean 的定义,包括它们的类名、作用域、依赖关系等。
    4. 创建 Bean 实例: 容器根据 Bean 定义,在适当的时机(例如单例 Bean 在容器启动时创建,原型 Bean 在请求时创建)创建 Bean 的实例。
    5. 注入依赖: 在创建 Bean 实例后,容器会根据其依赖关系配置,查找或创建所需的依赖 Bean,并通过构造器、Setter 方法或字段反射将其注入到目标 Bean 中。
    6. Bean 生命周期管理: 容器负责管理 Bean 的整个生命周期,包括初始化前、初始化后、销毁前等回调方法。
    7. 应用程序使用: 应用程序代码可以通过容器获取已准备好的 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 是在运行时通过动态代理进行织入的。

  • 工作原理:

    1. 定义切面: 开发者创建一个带有 @Aspect 注解的类,并在其中定义通知方法(使用 @Before, @AfterReturning 等)和切入点表达式(使用 @Pointcut)。

    2. 启用 AOP: 在 Spring 配置中,通过 @EnableAspectJAutoProxy 注解来启用 Spring 对 AspectJ 风格的 AOP 自动代理支持。

    3. Spring 容器启动: 当 Spring 容器启动时,它会扫描所有的 Bean 定义。

    4. 识别目标对象: 容器会识别哪些 Bean 是目标对象,即它们的某个方法匹配了切面中定义的切入点。

    5. 创建代理: 对于那些需要被增强的目标对象,Spring AOP 会为它们创建代理对象。

      • 如果目标对象实现了接口,Spring 默认使用 JDK 动态代理。
      • 如果目标对象没有实现接口,或者强制使用 CGLIB 代理 (例如通过 proxyTargetClass=true 配置),Spring 会使用 CGLIB 动态代理。
    6. 调用方法: 当应用程序代码调用目标对象的方法时,实际上是调用了其代理对象的方法。

    7. 执行通知: 代理对象会根据切面中定义的通知类型和切入点,在目标方法执行的前后、异常抛出时等特定时机执行相应的通知逻辑。

    8. 转发给目标: 在执行完相应的通知逻辑后,代理对象会将方法调用转发给实际的目标对象来执行业务逻辑。

    9. 返回结果: 目标方法执行完毕后,结果会返回给代理对象,代理对象可能会执行其他通知(如 @AfterReturning),然后将最终结果返回给调用者。

5. Spring 的 Bean 生命周期

Spring Bean 的生命周期是指一个 Bean 从创建到销毁的整个过程。理解 Bean 的生命周期对于优化应用程序、解决问题和进行高级配置至关重要。

以下是 Spring Bean 的典型生命周期阶段:

  1. 实例化 (Instantiation) :

    • Spring IoC 容器根据 Bean 定义(通常是通过反射)创建 Bean 的实例。此时 Bean 只是一个裸对象,其属性尚未填充。
  2. 属性赋值 (Populate Properties) :

    • 容器根据 Bean 定义中的依赖配置(如 @Autowired<property> 标签),将所有依赖的 Bean 注入到刚刚创建的实例中。这可以通过 Setter 方法、构造器参数或字段反射实现。
  3. BeanNameAware 接口回调 (如果实现) :

    • 如果 Bean 实现了 org.springframework.beans.factory.BeanNameAware 接口,容器会调用 setBeanName() 方法,将 Bean 的 ID 或名称传递给它。
  4. BeanFactoryAware 接口回调 (如果实现) :

    • 如果 Bean 实现了 org.springframework.beans.factory.BeanFactoryAware 接口,容器会调用 setBeanFactory() 方法,将当前的 BeanFactory 实例传递给它。
  5. ApplicationContextAware 接口回调 (如果实现) :

    • 如果 Bean 实现了 org.springframework.context.ApplicationContextAware 接口,容器会调用 setApplicationContext() 方法,将当前的 ApplicationContext 实例传递给它。
  6. BeanPostProcessor (前置处理) :

    • 如果容器中注册了 org.springframework.beans.factory.config.BeanPostProcessor 接口的实现类,那么会调用其 postProcessBeforeInitialization() 方法。开发者可以在这个阶段对 Bean 进行自定义处理或修改。
  7. InitializingBean 接口回调 (如果实现) :

    • 如果 Bean 实现了 org.springframework.beans.factory.InitializingBean 接口,容器会调用其 afterPropertiesSet() 方法。这个方法允许 Bean 在所有属性都被设置后执行自定义的初始化逻辑。
  8. 自定义初始化方法 (init-method) :

    • 如果 Bean 定义中通过 @Bean(initMethod = "myInit") 或 XML 配置 init-method="myInit" 指定了初始化方法,容器会调用这个方法。
  9. BeanPostProcessor (后置处理) :

    • 在所有初始化回调(InitializingBean.afterPropertiesSet()init-method)执行完毕后,容器会调用 BeanPostProcessorpostProcessAfterInitialization() 方法。在这个阶段,Spring AOP 会在这里为 Bean 创建代理对象。
  10. Bean 就绪 (Ready for use) :

    • 至此,Bean 已经完全初始化并准备好,可以被应用程序使用。它被存储在 IoC 容器中,等待被其他 Bean 引用或被应用程序获取。
  11. Bean 销毁 (Destruction) :

    • 当 Spring IoC 容器关闭时,对于单例 Bean,容器会调用其销毁方法。

    • DisposableBean 接口回调 (如果实现) :

      • 如果 Bean 实现了 org.springframework.beans.factory.DisposableBean 接口,容器会调用其 destroy() 方法。
    • 自定义销毁方法 (destroy-method) :

      • 如果 Bean 定义中通过 @Bean(destroyMethod = "myDestroy") 或 XML 配置 destroy-method="myDestroy" 指定了销毁方法,容器会调用这个方法。
    • 对于原型 (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 环境,并且需要注册 RequestContextListenerRequestContextFilter

    • 示例:

      @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 应用程序中,根据数据的作用范围选择 requestsessionapplication

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);
          }
      }
      
  • 工作原理:

    1. 配置事务管理器: 在 Spring 配置中,需要定义一个 PlatformTransactionManager 的 Bean,并配置它所管理的数据源。

    2. 启用声明式事务: 通过 @EnableTransactionManagement 注解通知 Spring 容器启用声明式事务支持。

    3. AOP 代理: 当 Spring 容器发现一个 Bean 带有 @Transactional 注解时,它会为这个 Bean 创建一个代理对象(通常是 JDK 动态代理或 CGLIB 代理)。

    4. 拦截方法调用: 当应用程序调用带有 @Transactional 注解的方法时,实际上是调用了代理对象的方法。

    5. 事务切面逻辑: 代理对象中的事务切面会拦截方法调用:

      • 开启事务: 在目标方法执行之前,事务切面会根据 @Transactional 注解的属性(传播行为、隔离级别等)来开启一个事务。

      • 执行业务逻辑: 调用目标对象的实际业务方法。

      • 提交或回滚:

        • 如果业务方法成功执行且没有抛出任何 rollbackFor 指定的异常,事务切面会提交事务。
        • 如果业务方法抛出了 rollbackFor 指定的异常(默认是运行时异常),事务切面会回滚事务。
      • 关闭事务: 事务结束后,释放资源。

8. Spring MVC 的工作流程

Spring MVC 是 Spring 框架的 Web 模块,它遵循 MVC (Model-View-Controller) 设计模式,用于构建 Web 应用程序。

以下是 Spring MVC 请求处理的详细工作流程:

  1. 客户端发送请求:

    • 用户通过浏览器向 Web 应用程序发送一个 HTTP 请求(例如 GET /user/view?id=123)。
  2. DispatcherServlet 接收请求:

    • Web 服务器(如 Tomcat)接收到请求后,根据 web.xml 或 Spring Boot 的自动配置,将请求转发给 Spring MVC 的核心前端控制器 DispatcherServletDispatcherServlet 是整个 Spring MVC 的枢纽。
  3. HandlerMapping 查找处理器:

    • DispatcherServlet 委托 HandlerMapping (处理器映射器) 根据请求的 URL 路径查找能够处理该请求的处理器(Controller 中的方法)。
    • 常用的 HandlerMapping 实现有 RequestMappingHandlerMapping (支持 @RequestMapping 注解)。
    • HandlerMapping 会返回一个 HandlerExecutionChain (处理器执行链),其中包含一个处理器 (Handler) 实例和一系列拦截器 (HandlerInterceptor)。
  4. HandlerInterceptor (前置处理) :

    • DispatcherServlet 依次调用 HandlerExecutionChain 中的所有拦截器的 preHandle() 方法。
    • 如果任何一个 preHandle() 方法返回 false,则请求处理中断,不再执行后续的拦截器和处理器方法。这常用于权限验证、日志记录等。
  5. HandlerAdapter 调用处理器方法:

    • DispatcherServlet 委托 HandlerAdapter (处理器适配器) 调用找到的处理器方法。
    • HandlerAdapter 负责将请求参数绑定到处理器方法的参数上(例如:@PathVariable@RequestParam@RequestBody 等)。
    • 处理器方法执行具体的业务逻辑,可能调用 Service 层和 Repository 层进行数据处理。
  6. 处理器方法返回 ModelAndView 或其他类型:

    • 处理器方法执行完毕后,会返回一个结果。这个结果可以是:

      • ModelAndView 对象: 包含模型数据和逻辑视图名。
      • String: 逻辑视图名 (如果使用视图解析器)。
      • Java 对象: (如果使用了 @ResponseBody@RestController),会被 HttpMessageConverter 转换为 JSON 或 XML 格式的响应体。
      • void: 如果直接将响应写入 HttpServletResponse
  7. HandlerInterceptor (后置处理) :

    • 在处理器方法执行之后,但在视图渲染之前,DispatcherServlet 会依次调用拦截器的 postHandle() 方法。
    • 在这里可以对 ModelAndView 对象进行修改,添加公共数据等。
  8. ViewResolver 解析视图:

    • 如果处理器方法返回的是逻辑视图名(例如 return "hello";),DispatcherServlet 委托 ViewResolver (视图解析器) 根据逻辑视图名解析出具体的 View 对象。
    • 常用的 ViewResolver 实现有 InternalResourceViewResolver (用于 JSP)、ThymeleafViewResolverFreeMarkerViewResolver 等。
    • ViewResolver 根据配置(如前缀和后缀)找到对应的视图资源(如 JSP 文件、HTML 模板)。
  9. 视图渲染 (View Rendering) :

    • ViewResolver 返回 View 对象后,View 对象负责将模型数据(由控制器添加到 Model 中的数据)填充到视图模板中,生成最终的 HTML 响应。
  10. HandlerInterceptor (完成处理) :

    • 在整个请求处理完成之后(视图渲染完毕或发生异常),DispatcherServlet 会依次调用拦截器的 afterCompletion() 方法。
    • 这个方法通常用于资源清理、日志记录等,无论请求是否成功都会执行。
  11. 响应返回客户端:

    • 最终生成的 HTML 响应(或 JSON/XML 响应)通过 DispatcherServlet 和底层的 Servlet 容器发送回客户端浏览器。

简要流程图:

客户端 (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 为例) :

    1. 添加依赖: 在 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>
      
    2. 配置数据源: 在 application.propertiesapplication.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 语句
      
    3. 创建实体类 (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;
      
          // ... 其他属性
      }
      
    4. 创建 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);
      }
      
    5. 在 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 的入口,它是一个 Servlet Filter,负责将请求委托给一系列安全过滤器 (SecurityFilterChain)。
    • SecurityContextHolder: 存储当前应用程序的安全上下文,通常包含当前已认证用户的信息(Authentication 对象)。
    • Authentication: 表示已认证或正在尝试认证的用户主体。它包含用户的身份信息、凭据和权限。
    • AuthenticationManager: 认证管理器,接收 Authentication 请求,并尝试对其进行认证。
    • AuthenticationProvider: 认证提供者,AuthenticationManager 会委托给一个或多个 AuthenticationProvider 来执行实际的认证逻辑(如通过数据库验证用户名密码)。
    • UserDetailsService: 用户详情服务,负责加载用户详情 (UserDetails)。
    • UserDetails: 包含用户账户信息(用户名、密码、权限、账户是否启用/锁定/过期等)。
    • GrantedAuthority: 授予用户的权限。
    • AccessDecisionManager: 访问决策管理器,决定用户是否有权限访问某个资源。
    • AccessDecisionVoter: 访问决策投票器,AccessDecisionManager 会委托给一个或多个 AccessDecisionVoter 来对授权请求进行投票。
  • 认证过程 (Authentication) :

    1. 用户提交凭据: 用户在登录页面输入用户名和密码,提交给应用程序。

    2. UsernamePasswordAuthenticationFilter 拦截: 请求被 UsernamePasswordAuthenticationFilter 拦截(Spring Security 默认的认证过滤器)。

    3. 创建 Authentication 对象: UsernamePasswordAuthenticationFilter 从请求中获取用户名和密码,创建一个 UsernamePasswordAuthenticationToken (实现了 Authentication 接口)。

    4. AuthenticationManager 认证: UsernamePasswordAuthenticationToken 被传递给 AuthenticationManager

    5. AuthenticationProvider 验证: AuthenticationManager 会迭代其配置的 AuthenticationProvider 列表,找到一个支持 UsernamePasswordAuthenticationTokenAuthenticationProvider (通常是 DaoAuthenticationProvider)。

    6. UserDetailsService 加载用户: DaoAuthenticationProvider 调用自定义的 UserDetailsService 来根据用户名加载用户详情 (UserDetails)。

    7. 密码比对: DaoAuthenticationProvider 使用 PasswordEncoder 对用户提交的密码进行加密,然后与从 UserDetails 中获取的加密密码进行比对。

    8. 认证成功/失败:

      • 成功: 如果密码匹配,DaoAuthenticationProvider 返回一个完全认证的 Authentication 对象。这个对象会被存入 SecurityContextHolder 中,表示当前用户已认证。
      • 失败: 如果密码不匹配或用户不存在,则抛出 AuthenticationException
  • 授权过程 (Authorization) :

    1. 用户访问受保护资源: 已认证的用户尝试访问一个受保护的 URL 或执行一个受保护的方法。

    2. FilterSecurityInterceptor 拦截: 请求被 FilterSecurityInterceptor 拦截。

    3. 获取访问决策信息: FilterSecurityInterceptor 从配置中获取访问该资源所需的权限信息(例如,hasRole('ADMIN'))。

    4. AccessDecisionManager 决策: FilterSecurityInterceptor 将认证信息 (Authentication 对象)、受保护资源和所需的权限信息传递给 AccessDecisionManager

    5. AccessDecisionVoter 投票: AccessDecisionManager 会委托给一个或多个 AccessDecisionVoter 来对授权请求进行投票。

      • 例如,RoleVoter 会检查 Authentication 对象中是否包含所需的角色。
      • WebExpressionVoter 会评估 SpEL (Spring Expression Language) 表达式(如 hasRole('ADMIN'))。
    6. 授权通过/拒绝:

      • 通过: 如果 AccessDecisionManager 最终决定授权通过,则允许用户访问资源。
      • 拒绝: 如果授权被拒绝,则抛出 AccessDeniedException,用户会被重定向到错误页面或显示拒绝访问消息。
  • 配置 Spring Security (示例) :

    1. 添加依赖:

      <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>
      
    2. 创建安全配置类:

      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
          }
      }
      
    3. 在 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!";
          }
      }