Skip to content

Spring

Spring 是一个广受欢迎的企业级 Java 应用程序开发框架。它由 Rod Johnson 创立,并于2003年首次发布。Spring 框架的核心价值在于为开发者提供了简化 Java 开发复杂性的工具和基础设施,尤其是对于构建企业级应用程序而言。

核心特性

  1. 轻量级:Spring 不强制使用特定的应用服务器或全套的 J2EE 组件,而是通过非侵入式的编程方式,使开发者能够选择性地使用其需要的功能,保持应用的轻量化。

  2. 控制反转 (Inversion of Control, IoC)依赖注入 (Dependency Injection, DI):Spring 提供了一个 IoC 容器,用于管理应用程序中的对象及其相互依赖。通过 DI,对象无需自行创建或查找依赖对象,而是由容器负责创建对象并将其所需的依赖注入进来。这种设计模式有助于降低代码间的耦合度,提高组件的复用性和系统的可测试性。

  3. 面向切面编程 (Aspect-Oriented Programming, AOP):Spring AOP 允许开发者定义"切面",这些切面包含了跨越多个对象或服务的关注点,如事务管理、日志记录、权限检查等。通过 AOP,可以将这些横切关注点从核心业务逻辑中解耦出来,实现模块化和代码复用,同时减少代码的冗余和交叉污染。

  4. 事务管理 (Transaction Management):Spring 提供了一致的、与底层持久层技术无关的事务管理抽象层,支持声明式事务处理。开发者可以通过简单的配置或注解来管理事务边界,简化了事务控制的复杂性。

  5. 数据访问/集成 (Data Access/Integration):Spring 支持多种数据访问技术,包括 JDBC、Hibernate、JPA 等。它提供了诸如 JdbcTemplateJpaTemplate 等工具类以及对 ORM 框架的良好整合,简化了数据库操作和 DAO 层的开发。此外,Spring 还支持与消息中间件、NoSQL 数据库等其他数据存储系统的集成。

  6. 模型-视图-控制器 (Model-View-Controller, MVC) 框架:Spring MVC 是一个用于构建 web 应用程序的模块,它遵循 MVC 设计模式,提供了清晰的角色划分和松耦合的组件。Spring MVC 可以与各种视图技术(如 JSP、Thymeleaf、FreeMarker 等)配合使用,为构建灵活、可扩展的 web 应用提供强大支持。

  7. 测试支持:Spring 提供了方便的测试工具和 mock 对象库,如 Spring TestContext Framework,简化了单元测试和集成测试的编写,使得在 Spring 环境中测试代码变得更加容易和高效。

  8. 配置管理:Spring 通过 XML、Java 配置类或基于注解的配置方式,提供了灵活的组件装配和管理机制。随着 Spring Boot 的出现,这种配置方式进一步简化,通过自动配置和starter模块极大地减少了手动配置的工作量。

简单示例

假设有一个简单的业务需求,即查询用户的个人信息。为了实现这一功能,通常会设计如下三个组件:

  1. User 类(POJO):表示用户实体,包含姓名、年龄等属性及其对应的 getter/setter 方法。

  2. UserService 类:业务逻辑层,提供查询用户信息的方法,如 getUser()。此方法内部需要调用 UserDao 来获取数据。

  3. UserDao 类:数据访问层,实现了从数据库或其他数据源查询用户数据的具体逻辑,例如 findUser() 方法。

配置 Spring 容器

使用 XML 配置文件(或 Java 配置类、注解配置)来定义 Spring 容器中的 Bean:

xml
<!-- spring-config.xml -->
<beans>
  <bean id="userDao" class="com.example.UserDaoImpl"/>
  <bean id="userService" class="com.example.UserService">
    <property name="userDao" ref="userDao"/>
  </bean>
</beans>

使用 Spring 容器

java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
    
    UserService userService = context.getBean("userService", UserService.class);
    User user = userService.getUser();
    
    System.out.println("User Details: " + user);
  }
}

控制反转 (IoC)

控制反转(IoC)是一种软件设计原则,其核心思想是将对象的创建、管理及依赖关系的维护从应用程序代码中抽离出来,转交给一个专门的第三方容器(如 Spring 容器)来负责。

依赖注入方式

Setter 注入

java
public class UserService {
    private UserDao userDao;

    // Setter 方法注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public User getUser() {
        return userDao.findUser();
    }
}

构造函数注入

java
public class UserService {
    private final UserDao userDao;

    // 构造函数注入
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public User getUser() {
        return userDao.findUser();
    }
}

字段注入(@Autowired 注解)

java
public class UserService {
    @Autowired
    private UserDao userDao;

    public User getUser() {
        return userDao.findUser();
    }
}

Spring 容器

Spring IoC 的实现离不开 Spring 容器。容器负责:

  • Bean 定义:通过 XML 配置文件、Java 配置类或注解,定义 Bean 的类型、作用域、生命周期回调方法、依赖关系等信息。
  • Bean 实例化:根据 Bean 定义,创建 Bean 实例。
  • 依赖解析与注入:解析 Bean 间的依赖关系,通过上述注入方式将依赖对象注入到目标 Bean 中。
  • Bean 管理:管理 Bean 的生命周期,包括初始化、销毁等过程,以及单例或多例模式下的缓存管理。

面向切面编程 (AOP)

Spring 核心之一是面向切面编程 (Aspect-Oriented Programming, AOP),这是一种编程范式,用于处理那些分散在应用各处、与主业务逻辑分离但仍需在多个位置应用的横切关注点(cross-cutting concerns)。AOP 通过将这些关注点封装成"切面"(aspects),并定义何时(when)、何处(where)以及如何(how)应用这些切面,来实现对系统行为的统一管理和模块化。

核心概念

切面 (Aspect)

切面是 AOP 中的基本单元,它封装了特定的横切关注点,如日志记录、事务管理、安全检查、性能监控等。切面通常包含:

  • 通知 (Advice):实际执行的代码,定义了在程序执行过程中何时和如何插入切面逻辑。常见的通知类型有:
    • 前置通知 (Before advice):在目标方法执行前执行。
    • 后置通知 (After advice):在目标方法正常执行后执行,无论方法是否抛出异常。
    • 返回通知 (After returning advice):在目标方法成功执行并返回结果后执行。
    • 异常通知 (After throwing advice):在目标方法抛出异常后执行。
    • 环绕通知 (Around advice):包围目标方法执行,可以决定是否执行目标方法,何时执行,以及何时退出。
  • 切入点 (Pointcut):一组匹配规则,用于定义哪些连接点(JoinPoint)应该被通知(advice)处理。
  • 连接点 (JoinPoint):程序执行过程中的特定位置,如方法调用、异常抛出等。

代理 (Proxy)

Spring AOP 通常通过代理模式来实现切面的织入。有两种代理方式:

  • JDK 动态代理:基于接口创建代理对象,代理对象实现了目标对象所实现的所有接口。
  • CGLIB 字节码生成代理:针对没有接口的类,通过生成子类的方式创建代理对象。

Spring AOP 实现示例

java
@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logMethodEntry(JoinPoint joinPoint) {
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Entering method: " + className + "." + methodName);
    }

    // 其他通知类型和切入点定义...
}

AOP 应用场景

AOP 适用于处理与业务逻辑分离且需要在多个地方应用的横切关注点,如:

  • 事务管理:在方法执行前后添加事务开启、提交或回滚逻辑。
  • 日志记录:在方法执行前后记录方法调用情况、输入输出参数、执行结果等信息。
  • 权限检查:在方法调用前验证用户是否有足够的权限访问资源。
  • 性能监控:统计方法执行时间、资源消耗等指标,用于性能分析和优化。
  • 缓存控制:在方法调用前检查缓存是否存在结果,存在则直接返回,否则执行方法并更新缓存。

Spring MVC

Spring MVC是Spring框架中的一个模块,它遵循模型-视图-控制器(Model-View-Controller, MVC)设计模式,用于构建Web应用程序。

Spring MVC简介

  • 模型(Model):模型代表数据和业务逻辑。在Spring MVC中,模型通常是Java对象,这些对象封装了应用程序的数据和操作这些数据的逻辑。
  • 视图(View):视图负责展示模型中的数据给用户。在Spring MVC中,视图可以是JSP、Thymeleaf、FreeMarker等技术生成的HTML页面。
  • 控制器(Controller):控制器负责接收用户的请求,处理请求并选择合适的视图来响应用户。在Spring MVC中,控制器通常是一个Java类,其中的方法(称为处理器方法)通过注解(如@RequestMapping)与特定的URL映射。

Spring MVC工作流程

  1. 用户发起HTTP请求到服务器。
  2. 请求被Spring MVC的前端控制器DispatcherServlet捕获。
  3. DispatcherServlet根据请求的URL和预先配置的处理器映射,找到对应的处理器(Controller)。
  4. DispatcherServlet调用处理器,并将处理请求所需的参数传递给处理器方法。
  5. 处理器执行业务逻辑后,返回一个包含模型数据的逻辑视图名给DispatcherServlet
  6. DispatcherServlet通过视图解析器,根据逻辑视图名找到实际的视图。
  7. 视图将模型数据渲染成用户可以理解的页面(HTML等)。
  8. DispatcherServlet将最终的响应发送回用户浏览器。

Controller示例

java
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class LoginController {

    @GetMapping("/login")
    public String showLoginForm() {
        return "login";
    }

    @PostMapping("/login")
    public String handleLogin(@RequestParam("username") String username,
                              @RequestParam("password") String password,
                              Model model) {
        if ("admin".equals(username) && "123456".equals(password)) {
            model.addAttribute("message", "登录成功!");
            return "success";
        } else {
            model.addAttribute("errorMessage", "用户名或密码错误!");
            return "login";
        }
    }
}

事务管理

Spring事务管理是Spring框架中用于处理数据库操作时保证数据一致性和完整性的关键功能。它允许你在服务层的代码中声明性地管理事务,而不需要在具体的SQL操作中手动控制事务边界。Spring事务管理支持编程式和声明式两种方式,但声明式事务管理因其简洁和解耦的特性而被更广泛使用。

基本概念

  1. 事务:事务是数据库操作的基本单元,它包含了一组数据库操作命令。这些操作要么全部成功,要么全部失败,以确保数据的完整性。

  2. ACID原则:事务应遵循原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)四个基本属性。

Spring事务管理类型

  • 编程式事务管理:通过编写代码(如使用TransactionTemplate或者直接使用PlatformTransactionManager)来管理事务的开始、提交或回滚。这种方式更加灵活,但会使得代码与事务管理逻辑紧密耦合。

  • 声明式事务管理:通过在配置文件或者注解中声明事务的边界,由Spring自动管理事务的开始、提交或回滚。这种方式更加简洁,易于维护。

@Transactional注解使用

  • 放置位置:可以放在类级别或方法级别。类级别表示该类中的所有公共方法都将启用相同的事务配置;方法级别则覆盖类级别的配置,为特定方法提供不同的事务设置。

  • 常用属性:

    • propagation:事务传播行为,默认为REQUIRED,定义了事务方法之间的交互规则。
    • isolation:事务的隔离级别,如READ_COMMITTED、REPEATABLE_READ等。
    • readOnly:是否为只读事务,默认为false。
    • timeout:事务超时时间,默认无限制。
    • rollbackFor:遇到指定异常时回滚事务。
    • noRollbackFor:遇到指定异常时不回滚事务。

七种事务传播行为

  1. PROPAGATION_REQUIRED(默认):如果当前没有事务,就新建一个事务;如果已经存在一个事务,则加入到这个事务中。适用于大多数业务操作,确保操作的一致性。

  2. PROPAGATION_SUPPORTS:该传播行为表示当前方法不需要事务上下文,但是如果存在当前事务的话,也可以在这个事务中执行。适用于那些可以独立于事务之外运行的查询操作。

  3. PROPAGATION_MANDATORY:如果当前存在事务,则方法在该事务中运行;如果不存在事务,则抛出异常。适用于那些必须在事务上下文中执行的方法。

  4. PROPAGATION_REQUIRES_NEW:总是新建一个事务,如果当前存在事务,则把当前事务挂起。适用于需要独立事务处理的场景。

  5. PROPAGATION_NOT_SUPPORTED:该传播行为明确表示该方法不应该在事务中运行,如果存在事务,则将当前事务挂起。适用于那些不需要事务且可能对性能有严格要求的操作。

  6. PROPAGATION_NEVER:方法不应该在事务中执行,如果当前存在事务,则抛出异常。适用于绝对不允许在事务中执行的操作。

  7. PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则其行为类似于PROPAGATION_REQUIRED。嵌套事务可以有多个保存点,回滚时可以回滚到某一个保存点,而不是直接回滚整个事务。

事务失效场景

::: caution 有多种场景可能会导致事务失效

在使用Spring框架的@Transactional注解进行声明式事务管理时,有多种场景可能会导致事务失效:

  1. 非public方法:如果@Transactional注解应用在非public修饰的方法上,事务将不会生效。
  2. 内部方法调用:当一个类的某个方法被@Transactional注解,并在同一个类的另一个方法内部直接调用该方法时,事务可能不会生效。
  3. 异常处理不当:如果在事务方法内捕获了异常并直接处理了,没有再次抛出,那么Spring默认的事务回滚策略不会生效。
  4. 事务传播行为设置错误:如果@Transactionalpropagation属性配置不恰当,例如设置为NOT_SUPPORTEDNEVER,则即使方法中发生异常也不会启动事务或回滚事务。
  5. 数据库不支持事务:如果你使用的数据库引擎不支持事务(如某些MySQL存储引擎设置为MyISAM),即使应用层面配置了事务也不会有效果。
  6. 没有启用事务管理器:忘记在Spring配置中启用事务管理器也会导致事务注解失效。

:::

总结

Spring 是一个全方位的支持企业级 Java 应用开发的框架,它通过提供一系列核心功能和工具,帮助开发者构建松散耦合、易于测试、高度可维护的应用系统。随着时间的推移,Spring 生态系统不断发展壮大,衍生出 Spring Boot、Spring Cloud 等项目,为微服务架构、云原生应用开发提供了全面的支持。