主题
Spring
什么是Spring?
Spring 是一个广受欢迎的企业级 Java 应用程序开发框架。它由 Rod Johnson 创立,并于2003年首次发布。Spring 框架的核心价值在于为开发者提供了简化 Java 开发复杂性的工具和基础设施,尤其是对于构建企业级应用程序而言。以下是对 Spring 框架主要特性和功能的概述:
- 轻量级:Spring 不强制使用特定的应用服务器或全套的 J2EE 组件,而是通过非侵入式的编程方式,使开发者能够选择性地使用其需要的功能,保持应用的轻量化。
- 控制反转 (Inversion of Control, IoC) 和 依赖注入 (Dependency Injection, DI):Spring 提供了一个 IoC 容器,用于管理应用程序中的对象及其相互依赖。通过 DI,对象无需自行创建或查找依赖对象,而是由容器负责创建对象并将其所需的依赖注入进来。这种设计模式有助于降低代码间的耦合度,提高组件的复用性和系统的可测试性。
- 面向切面编程 (Aspect-Oriented Programming, AOP):Spring AOP 允许开发者定义“切面”,这些切面包含了跨越多个对象或服务的关注点,如事务管理、日志记录、权限检查等。通过 AOP,可以将这些横切关注点从核心业务逻辑中解耦出来,实现模块化和代码复用,同时减少代码的冗余和交叉污染。
- 事务管理 (Transaction Management):Spring 提供了一致的、与底层持久层技术无关的事务管理抽象层,支持声明式事务处理。开发者可以通过简单的配置或注解来管理事务边界,简化了事务控制的复杂性。
- 数据访问/集成 (Data Access/Integration):Spring 支持多种数据访问技术,包括 JDBC、Hibernate、JPA 等。它提供了诸如
JdbcTemplate、JpaTemplate等工具类以及对 ORM 框架的良好整合,简化了数据库操作和 DAO 层的开发。此外,Spring 还支持与消息中间件、NoSQL 数据库等其他数据存储系统的集成。 - 模型-视图-控制器 (Model-View-Controller, MVC) 框架:Spring MVC 是一个用于构建 web 应用程序的模块,它遵循 MVC 设计模式,提供了清晰的角色划分和松耦合的组件。Spring MVC 可以与各种视图技术(如 JSP、Thymeleaf、FreeMarker 等)配合使用,为构建灵活、可扩展的 web 应用提供强大支持。
- 测试支持:Spring 提供了方便的测试工具和 mock 对象库,如
Spring TestContext Framework,简化了单元测试和集成测试的编写,使得在 Spring 环境中测试代码变得更加容易和高效。 - 配置管理:Spring 通过 XML、Java 配置类或基于注解的配置方式,提供了灵活的组件装配和管理机制。随着 Spring Boot 的出现,这种配置方式进一步简化,通过自动配置和starter模块极大地减少了手动配置的工作量。
综上所述,Spring 是一个全方位的支持企业级 Java 应用开发的框架,它通过提供一系列核心功能和工具,帮助开发者构建松散耦合、易于测试、高度可维护的应用系统。随着时间的推移,Spring 生态系统不断发展壮大,衍生出 Spring Boot、Spring Cloud 等项目,为微服务架构、云原生应用开发提供了全面的支持。
Spring基础 - 简单的示例
Spring 基础通常会从一个简单的示例入手,以便直观地展示 Spring 框架如何帮助解决实际编程问题,特别是如何体现其核心概念如控制反转 (IoC)、依赖注入 (DI) 等。以下是一个简化的例子说明:
场景描述
假设有一个简单的业务需求,即查询用户的个人信息。为了实现这一功能,通常会设计如下三个组件:
- User 类(POJO):表示用户实体,包含姓名、年龄等属性及其对应的 getter/setter 方法。
- UserService 类:业务逻辑层,提供查询用户信息的方法,如
getUser()。此方法内部需要调用 UserDao 来获取数据。 - UserDao 类:数据访问层,实现了从数据库或其他数据源查询用户数据的具体逻辑,例如
findUser()方法。
在传统的编程方式下,UserService 类可能会直接创建 UserDao 实例来完成查询。然而,这种方式会导致业务逻辑与数据访问细节紧密耦合。Spring 框架通过 IoC 和 DI 来改善这种情况。
Spring 示例实现
配置 Spring 容器: 使用 XML 配置文件(或 Java 配置类、注解配置)来定义 Spring 容器中的 Bean。在这个例子中,需要定义
UserService和UserDao两个 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>上述配置中,
userDaoBean 明确指定了实现类UserDaoImpl。userServiceBean 通过<property>标签注入了对userDaoBean 的引用。实现类与接口:
UserDao接口:定义了findUser()方法。UserDaoImpl类:实现了UserDao接口,具体实现查询逻辑。UserService类:包含一个私有成员变量UserDao userDao和对应的 setter 方法,以及getUser()方法。getUser()内部调用了userDao.findUser()。
使用 Spring 容器: 在应用程序入口处,通过
ClassPathXmlApplicationContext加载 Spring 配置文件,然后从容器中获取UserService的 Bean 实例来执行业务操作。javaimport 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); } }
Spring 要点体现
- 控制反转 (IoC):Spring 容器负责创建和管理
UserService和UserDao的实例,而非由应用程序代码直接创建。这意味着对象的生命周期和依赖关系的控制权从应用程序转移到了容器,实现了控制反转。 - 依赖注入 (DI):
UserService类通过 setter 方法(或构造函数注入)接收UserDao的实例,而不是自行创建。Spring 容器在创建userServiceBean 时,会解析配置文件,发现userDao属性的依赖,并注入已经创建好的UserDaoBean。这样,UserService无需知道UserDao的具体实现细节,只需依赖于UserDao接口,增强了代码的解耦性和可测试性。
通过这个简单例子,我们可以看到 Spring 如何通过 IoC 和 DI 机制,将对象的创建、管理和依赖关系的处理交由容器负责,从而简化代码、降低耦合度,提升应用程序的可维护性和可扩展性。这仅仅是 Spring 基础功能的一个缩影,实际应用中 Spring 还提供了更多的高级特性和工具来支持复杂的企业级应用开发。
Spring核心之控制反转(IOC)
Spring 核心之一是其控制反转 (Inversion of Control, IoC) 设计原则,这是一种软件设计模式,旨在降低代码之间的耦合度,提高模块化和可维护性。以下是关于 Spring 控制反转(IoC)的详细解释:
基本概念
控制反转(IoC)是一种软件设计原则,其核心思想是将对象的创建、管理及依赖关系的维护从应用程序代码中抽离出来,转交给一个专门的第三方容器(如 Spring 容器)来负责。这样做的好处是:
- 解耦:对象不再直接负责依赖对象的创建和管理,而是由容器统一进行生命周期管理,降低了对象间的直接依赖,增强了系统的灵活性和可维护性。
- 依赖透明:对象仅通过接口(或抽象类)来声明它所依赖的外部服务,而不关心这些依赖对象的具体实现,使得组件间依赖关系更加清晰。
- 可配置性:通过配置文件或注解等方式,可以在不修改代码的情况下改变对象的依赖关系,适应不同的运行环境或需求变化。
实现机制:依赖注入 (DI)
Spring 实现 IoC 的主要手段是依赖注入 (Dependency Injection, DI)。依赖注入是一种具体的编程范式,它确保一个对象所需要的依赖对象(即它的依赖项)被自动注入(或者说提供)给它,而不是由该对象自行创建或查找。
Spring 中依赖注入主要有以下几种方式:
setter 注入
通过在目标类中定义setter方法来设置依赖对象。Spring 容器通过调用这些setter方法,将依赖对象注入到目标对象中。
java
public class UserService {
private UserDao userDao;
// Setter 方法注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
// 使用注入的 userDao
public User getUser() {
return userDao.findUser();
}
}构造函数注入
通过在目标类的构造函数中传入依赖对象来实现注入。Spring 容器在创建目标对象时,会调用匹配参数类型的构造函数,并传入相应的依赖对象。
java
public class UserService {
private final UserDao userDao;
// 构造函数注入
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public User getUser() {
return userDao.findUser();
}
}字段注入(@Autowired 注解)
使用 Spring 的 @Autowired 注解直接标记在类的成员变量上,Spring 容器会自动寻找匹配类型的 Bean 并注入。
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 的生命周期,包括初始化、销毁等过程,以及单例或多例模式下的缓存管理。
总结
Spring 控制反转(IoC)通过依赖注入(DI)机制,将对象的创建、管理以及依赖关系的维护工作从应用程序代码转移到一个专门的容器中。这种设计模式显著降低了代码间的耦合度,提高了模块的独立性和可测试性,使得应用程序更易于扩展和维护。Spring 容器作为 IoC 的实现载体,负责管理和协调各个 Bean 的生命周期,确保依赖关系的正确注入。
Spring核心之面向切面编程(AOP)
Spring 核心之一是面向切面编程 (Aspect-Oriented Programming, AOP),这是一种编程范式,用于处理那些分散在应用各处、与主业务逻辑分离但仍需在多个位置应用的横切关注点(cross-cutting concerns)。AOP 通过将这些关注点封装成“切面”(aspects),并定义何时(when)、何处(where)以及如何(how)应用这些切面,来实现对系统行为的统一管理和模块化。
以下是对 Spring AOP 的详细解释:
核心概念
切面 (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 实现
在 Spring 中,开发者通过定义切面类(使用 @Aspect 注解标记),并在其中编写通知方法和定义切入点表达式,来实现 AOP。Spring 容器会在运行时自动识别并织入这些切面。
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);
}
// 其他通知类型和切入点定义...
}上述代码定义了一个名为 LoggingAspect 的切面,其中包含一个前置通知(@Before),当匹配到 com.example.service 包下的任何类的任何方法(*.*(..))被调用时,都会执行 logMethodEntry 方法。
AOP 应用场景
AOP 适用于处理与业务逻辑分离且需要在多个地方应用的横切关注点,如:
- 事务管理:在方法执行前后添加事务开启、提交或回滚逻辑。
- 日志记录:在方法执行前后记录方法调用情况、输入输出参数、执行结果等信息。
- 权限检查:在方法调用前验证用户是否有足够的权限访问资源。
- 性能监控:统计方法执行时间、资源消耗等指标,用于性能分析和优化。
- 缓存控制:在方法调用前检查缓存是否存在结果,存在则直接返回,否则执行方法并更新缓存。
总结
Spring 面向切面编程(AOP)通过将系统中的横切关注点(如日志、事务、权限等)抽象为切面,并通过代理机制在合适的时机(切入点)将切面逻辑(通知)织入到业务逻辑中,实现了对这些关注点的集中管理、模块化和解耦。AOP 使得开发者可以专注于核心业务逻辑的编写,同时保证了系统在处理共性问题时的一致性和可维护性。
Spring核心之模型视图控制器(MVC)
Spring MVC是Spring框架中的一个模块,它遵循模型-视图-控制器(Model-View-Controller, MVC)设计模式,用于构建Web应用程序。下面是对Spring MVC的基本介绍以及一个简单的示例。
Spring MVC简介
模型(Model):模型代表数据和业务逻辑。在Spring MVC中,模型通常是Java对象,这些对象封装了应用程序的数据和操作这些数据的逻辑。
视图(View):视图负责展示模型中的数据给用户。在Spring MVC中,视图可以是JSP、Thymeleaf、FreeMarker等技术生成的HTML页面。
控制器(Controller):控制器负责接收用户的请求,处理请求并选择合适的视图来响应用户。在Spring MVC中,控制器通常是一个Java类,其中的方法(称为处理器方法)通过注解(如@RequestMapping)与特定的URL映射。
Spring MVC工作流程
- 用户发起HTTP请求到服务器。
- 请求被Spring MVC的前端控制器
DispatcherServlet捕获。 DispatcherServlet根据请求的URL和预先配置的处理器映射,找到对应的处理器(Controller)。DispatcherServlet调用处理器,并将处理请求所需的参数传递给处理器方法。- 处理器执行业务逻辑后,返回一个包含模型数据的逻辑视图名给
DispatcherServlet。 DispatcherServlet通过视图解析器,根据逻辑视图名找到实际的视图。- 视图将模型数据渲染成用户可以理解的页面(HTML等)。
DispatcherServlet将最终的响应发送回用户浏览器。
示例
假设我们有一个简单的用户登录功能,以下是使用Spring MVC实现的简单示例:
1. 添加依赖
确保你的项目中包含了Spring MVC相关的依赖。
2. 创建Controller
创建一个名为LoginController的Java类,用于处理登录请求。
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";
}
}
}3. 视图
你需要创建两个视图文件,一个用于显示登录表单(login.html或login.jsp),另一个用于显示登录成功或失败的消息(success.html或success.jsp)。这些视图文件应放置在项目的视图目录下,具体取决于你使用的视图技术(如Thymeleaf、JSP等)。
以上就是一个非常基础的Spring MVC应用示例,展示了如何接收请求、处理业务逻辑,并返回视图的过程。在实际应用中,还会涉及到更多的功能,如数据验证、异常处理、安全性增强等。
Spring核心之事务管理
Spring事务管理是Spring框架中用于处理数据库操作时保证数据一致性和完整性的关键功能。它允许你在服务层的代码中声明性地管理事务,而不需要在具体的SQL操作中手动控制事务边界。Spring事务管理支持编程式和声明式两种方式,但声明式事务管理因其简洁和解耦的特性而被更广泛使用。
基本概念
- 事务:事务是数据库操作的基本单元,它包含了一组数据库操作命令。这些操作要么全部成功,要么全部失败,以确保数据的完整性。
- ACID原则:事务应遵循原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)四个基本属性。
Spring事务管理类型
- 编程式事务管理:通过编写代码(如使用TransactionTemplate或者直接使用PlatformTransactionManager)来管理事务的开始、提交或回滚。这种方式更加灵活,但会使得代码与事务管理逻辑紧密耦合。
- 声明式事务管理:通过在配置文件或者注解中声明事务的边界,由Spring自动管理事务的开始、提交或回滚。这种方式更加简洁,易于维护。
声明式事务管理实现方式
- 基于XML的配置:在Spring的XML配置文件中,使用
<tx:advice>标签定义事务属性,并通过<aop:config>或<aop:pointcut>来指定哪些方法需要进行事务管理。 - 基于注解的配置:在Spring 3.0及以上版本,可以使用@Transactional注解直接在类或方法上声明事务属性,这是最常用的声明式事务管理方式。
@Transactional注解使用
- 放置位置:可以放在类级别或方法级别。类级别表示该类中的所有公共方法都将启用相同的事务配置;方法级别则覆盖类级别的配置,为特定方法提供不同的事务设置。
- 常用属性:
propagation:事务传播行为,默认为REQUIRED,定义了事务方法之间的交互规则。isolation:事务的隔离级别,如READ_COMMITTED、REPEATABLE_READ等。readOnly:是否为只读事务,默认为false。timeout:事务超时时间,默认无限制。rollbackFor:遇到指定异常时回滚事务。noRollbackFor:遇到指定异常时不回滚事务。
注意事项
- 确保使用Spring的代理对象调用事务方法,直接实例化对象将无法触发事务管理。
- 数据访问层(如JdbcTemplate, Hibernate等)应由Spring管理,以确保事务拦截器能够生效。
- 在异常处理时,要根据业务需求合理选择是否抛出或捕获检查型异常(继承自
java.lang.Exception),因为未被捕获的检查型异常默认会导致事务回滚。
Spring事务管理为开发者提供了强大而灵活的机制来处理数据库操作中的事务问题,极大地简化了企业级应用的开发复杂度。
七种事务传播行为
Spring框架定义了七种事务传播行为,每种行为适用于不同的业务场景,下面是对这些传播行为及其典型应用场景的简要说明:
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。嵌套事务可以有多个保存点,回滚时可以回滚到某一个保存点,而不是直接回滚整个事务。
- 示例: 需要在事务中执行一系列操作,但某些操作失败时,只想回滚这些失败的操作,而保留之前成功的操作,例如银行转账中的一部分手续费扣除操作。
理解这些传播行为并根据具体业务场景选择合适的传播模式,对于构建健壮、高效的应用系统至关重要。
事务失效
有多种场景可能会导致事务失效
在使用Spring框架的@Transactional注解进行声明式事务管理时,有多种场景可能会导致事务失效,以下是常见的一些原因:
- 非public方法:如果
@Transactional注解应用在非public修饰的方法上,事务将不会生效。因为Spring使用Java的动态代理来实现事务管理,非public方法无法被外部调用,因此代理对象无法介入事务管理。 - 内部方法调用:当一个类的某个方法(假设为
methodA)被@Transactional注解,并在同一个类的另一个方法(如methodB)内部直接调用methodA时,事务可能不会生效。这是因为在这种情况下,调用是直接发生在目标对象上而非代理对象,Spring的AOP代理无法拦截到这次调用,从而无法应用事务管理。 - 异常处理不当:
- 如果在事务方法内捕获了异常并直接处理了,没有再次抛出,那么Spring默认的事务回滚策略不会生效。
- 抛出了非运行时异常(即检查型异常,继承自
Exception而非RuntimeException),但没有在rollbackFor属性中指定该异常类型,也会导致事务不回滚。
- 事务传播行为设置错误:如果
@Transactional的propagation属性配置不恰当,例如设置为NOT_SUPPORTED或NEVER,则即使方法中发生异常也不会启动事务或回滚事务。 - 方法被非事务方法捕获异常:当事务方法抛出异常被同类中的非事务方法捕获时,事务可能不会回滚。
- 注解放置位置不当:如果事务管理配置正确但在不应该的地方使用(比如应用在接口上而不是实现类上),或者在Spring未管理的bean上使用
@Transactional注解,事务同样不会工作。 - 数据库不支持事务:如果你使用的数据库引擎不支持事务(如某些MySQL存储引擎设置为MyISAM),即使应用层面配置了事务也不会有效果。
- 没有启用事务管理器:忘记在Spring配置中启用事务管理器(如
DataSourceTransactionManager或JtaTransactionManager)也会导致事务注解失效。 - Timeout超时:如果事务等待资源超时,事务可能被回滚,具体行为取决于超时设置和配置。
- 注解在private或static方法上:由于Spring使用JDK动态代理或CGLIB代理来应用事务,这些代理不能代理private或static方法,所以
@Transactional注解对它们无效。