layout: mypost
title: Spring中的机制与实现
categories: [Spring系统学习]
Spring AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在将横切关注点(如日志记录、事务管理、权限控制等)与核心业务逻辑分离,使代码更加模块化、灵活和易于维护。AOP 可以通过“切面”在不修改现有代码的情况下,动态地向现有的业务逻辑中添加行为。
Spring AOP 的几个核心概念如下:
AspectJ 的切入点表达式来定义哪些方法应该被拦截。Spring AOP 实现是基于代理模式的,主要通过两种方式来生成代理对象:
Spring AOP 主要用于解决横切关注点问题,常见的应用场景包括:
使用 Spring AOP 的基本步骤如下:
@Aspect 注解将其标记为切面。@Pointcut 注解来定义切入点表达式。@Before、@After、@Around 等注解定义通知逻辑,通知的逻辑会在匹配的切入点处执行。@EnableAspectJAutoProxy 注解来启用 Spring AOP 的自动代理功能。@Aspect
@Component
public class LoggingAspect {
// 定义一个切入点,匹配所有 controller 包下的类
@Pointcut("execution(* com.example.controller..*(..))")
public void controllerMethods() {}
// 前置通知:方法执行前记录日志
@Before("controllerMethods()")
public void logBeforeMethod(JoinPoint joinPoint) {
System.out.println("Before Method: " + joinPoint.getSignature().getName());
}
// 后置通知:方法执行后记录日志
@After("controllerMethods()")
public void logAfterMethod(JoinPoint joinPoint) {
System.out.println("After Method: " + joinPoint.getSignature().getName());
}
}
在这个例子中,LoggingAspect 是一个切面,定义了两个通知:一个是在方法执行之前记录日志,另一个是在方法执行之后记录日志。通过 @Pointcut 注解指定了切入点,这里匹配了 controller 包下的所有方法。
Spring AOP 使用 AspectJ 的切入点表达式,常见的表达式有:
execution(* com.example.service.*.*(..)):匹配 com.example.service 包下的所有类的所有方法。within(com.example..*):匹配 com.example 包及其子包下的所有类。@annotation(org.springframework.transaction.annotation.Transactional):匹配所有带有 @Transactional 注解的方法。在 Spring 中,占位符(Placeholder)动态替换是一种常见的技术,用于将外部化配置(如属性文件中的配置)注入到 Spring 管理的 Bean 中。通过占位符,开发者可以方便地将静态值与外部化配置解耦,使得应用程序更加灵活、可配置。
Spring 通过 @Value 注解或 XML 配置中的占位符语法来实现占位符替换,并通过 PropertySourcesPlaceholderConfigurer 或 @PropertySource 来加载配置文件。
占位符是一种格式为 ${property.name} 的表达式,Spring 会在运行时替换占位符并将其注入到 Bean 的属性中。这通常与外部配置文件(如 .properties 文件、YAML 文件等)结合使用,以实现动态配置。
例如,在 application.properties 文件中,可能包含以下内容:
app.name=SpringApplication
app.version=1.0
然后,在 Spring 应用中,使用占位符注入这些值:
@Value("${app.name}")
private String appName;
@Value("${app.version}")
private String appVersion;
Spring 会自动将 ${app.name} 和 ${app.version} 替换为属性文件中的值。
使用 @Value 注解
@Value 注解用于将属性文件中的值注入到字段或方法参数中。它的常见语法为 ${property.name:defaultValue},其中 property.name 是属性的名称,defaultValue 是默认值(如果没有找到指定属性时会使用此默认值)。
例子:
@Component
public class AppConfig {
@Value("${app.name:DefaultAppName}")
private String appName;
@Value("${app.version:1.0}")
private String appVersion;
// Getter and Setter
public String getAppName() {
return appName;
}
public String getAppVersion() {
return appVersion;
}
}
在此例子中,如果 application.properties 中没有定义 app.name 和 app.version,Spring 将使用 DefaultAppName 和 1.0 作为默认值。
使用 XML 配置文件
在 Spring XML 配置中,也可以使用占位符来动态注入属性。需要在 XML 文件中引入 context:property-placeholder 进行配置:
application.properties 文件:
app.name=SpringAppXML
app.version=2.0
Spring XML 配置:
<context:property-placeholder location="classpath:application.properties" />
<bean id="appConfig" class="com.example.AppConfig">
<property name="appName" value="${app.name}" />
<property name="appVersion" value="${app.version}" />
</bean>
在 XML 配置中使用 ${} 语法来动态地从属性文件加载配置。
使用 @PropertySource 加载属性文件
在 Spring 的 Java 配置类(@Configuration)中,可以使用 @PropertySource 注解来指定外部配置文件,并结合 @Value 注解实现动态配置注入:
例子:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig {
@Value("${app.name}")
private String appName;
@Value("${app.version}")
private String appVersion;
@Bean
public SomeService someService() {
return new SomeService(appName, appVersion);
}
}
在这个例子中,@PropertySource 用于加载 application.properties 文件,@Value 用于将属性文件中的值动态注入到 appName 和 appVersion 字段中。
PropertySourcesPlaceholderConfigurer
如果在 XML 配置中使用 @Value,或者使用 Java 配置时,可能需要显式地定义 PropertySourcesPlaceholderConfigurer 来确保占位符能够被正确解析。
例子:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfig() {
return new PropertySourcesPlaceholderConfigurer();
}
}
PropertySourcesPlaceholderConfigurer 是 Spring 的一个专门类,用于处理占位符解析。通常情况下,它会自动加载,但有时需要手动配置。
在某些情况下,配置文件中的某些属性可能不存在。为了防止应用程序抛出错误,可以为占位符提供默认值:
@Value("${app.description:No description available}")
private String appDescription;
如果属性文件中没有定义 app.description,那么 Spring 会使用 No description available 作为默认值。
Spring 允许加载多个属性文件,通过 @PropertySource 注解可以加载多个文件:
@Configuration
@PropertySource({"classpath:application.properties", "classpath:additional.properties"})
public class AppConfig {
//...
}
在这种情况下,application.properties 和 additional.properties 中的属性会被加载,Spring 会按照属性文件的顺序解析值,后面的文件可以覆盖前面的配置。
在 Spring Boot 中,application.properties 和 application.yml 文件是默认加载的属性源,不需要额外的配置。通过 @Value 注解可以直接从这些文件中获取属性。
Spring Boot 还提供了更丰富的外部化配置机制,支持从命令行、环境变量、系统属性等多种来源读取配置。Spring Boot 默认会自动加载 application.properties 或 application.yml 文件:
app.name=SpringBootApp
app.version=1.0
然后在 Spring Boot 应用中使用:
@RestController
public class AppController {
@Value("${app.name}")
private String appName;
@Value("${app.version}")
private String appVersion;
@GetMapping("/info")
public String getAppInfo() {
return "App Name: " + appName + ", Version: " + appVersion;
}
}
在 Spring 中,事务管理 是一个重要的功能,它确保数据操作的一致性和可靠性。事务(Transaction)是指一组要么全部执行成功、要么全部回滚的操作,具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),通常简称为 ACID 特性。Spring 提供了对事务的透明支持,可以基于声明式或编程式的方式进行管理。
事务是一系列对数据库的操作,它们在逻辑上是一个整体。要么所有的操作都成功执行并提交,要么如果其中任何一个操作失败,整个事务将被回滚,以确保数据的完整性和一致性。
Spring 提供了两种主要的事务管理方式:
编程式事务管理允许在代码中通过显式调用事务管理 API 来控制事务的边界。Spring 提供了 TransactionTemplate 和 PlatformTransactionManager 来帮助开发者实现这种方式。
例子:
@Service
public class AccountService {
@Autowired
private PlatformTransactionManager transactionManager;
public void transferMoney(Long fromAccountId, Long toAccountId, Double amount) {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 业务逻辑:从一个账户转账到另一个账户
debit(fromAccountId, amount);
credit(toAccountId, amount);
// 提交事务
transactionManager.commit(status);
} catch (Exception ex) {
// 回滚事务
transactionManager.rollback(status);
throw ex;
}
}
}
在这个例子中,通过 PlatformTransactionManager 明确控制事务的开始、提交和回滚。这种方式虽然灵活,但需要手动管理事务的生命周期,容易导致代码复杂。
声明式事务管理是 Spring 推荐的事务管理方式。它可以通过注解或者 XML 配置来定义事务边界,开发者无需显式地在代码中管理事务,Spring 框架会自动处理。
注解方式:
Spring 支持使用 @Transactional 注解来定义事务。这个注解可以用于类或方法级别,标记该类或方法应当在事务上下文中执行。
例子:
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, Double amount) {
// 业务逻辑:从一个账户转账到另一个账户
debit(fromAccountId, amount);
credit(toAccountId, amount);
}
private void debit(Long accountId, Double amount) {
// 扣款逻辑
accountRepository.debit(accountId, amount);
}
private void credit(Long accountId, Double amount) {
// 存款逻辑
accountRepository.credit(accountId, amount);
}
}
在这里,@Transactional 注解声明了 transferMoney() 方法应当在事务上下文中执行。Spring 会在方法执行之前自动启动事务,执行成功后提交事务,发生异常时自动回滚。
声明式事务管理可以通过注解方式实现,也可以通过 XML 配置实现。
基于注解的配置:
在 Spring Boot 中,声明式事务管理通常通过 @EnableTransactionManagement 注解开启:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
// 配置事务管理器
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
基于 XML 的配置:
在传统的 Spring XML 配置中,事务管理可以通过以下方式配置:
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
Spring 事务的传播行为决定了一个事务方法在被另一个事务方法调用时,应该如何工作。常见的传播行为包括:
例子:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void someMethod() {
// 事务逻辑
}
事务的隔离级别决定了多个事务之间的隔离程度,常见的隔离级别包括:
DEFAULT:使用数据库默认的隔离级别。
READ_UNCOMMITTED:允许读取未提交的数据,可能出现“脏读”。
READ_COMMITTED:只能读取已提交的数据,避免“脏读”,但可能出现“不可重复读”。
REPEATABLE_READ:避免“脏读”和“不可重复读”,但可能出现“幻读”。
SERIALIZABLE:最高级别,完全避免脏读、不可重复读和幻读,但性能较低。
@Transactional(isolation = Isolation.SERIALIZABLE)
public void someMethod() {
// 事务逻辑
}
Spring 默认情况下,如果方法抛出 运行时异常(RuntimeException) 或 Error,事务将回滚。如果抛出的是受检异常(Checked Exception),事务不会回滚,除非显式配置了 rollbackFor 属性。
例子:
@Transactional(rollbackFor = Exception.class)
public void someMethod() throws Exception {
// 事务逻辑
}
在这个例子中,指定了无论抛出何种异常,事务都会回滚。
Spring 框架中有许多核心接口类,它们提供了实现依赖注入、面向切面编程、数据访问、事务管理等功能的基础。理解这些接口是掌握 Spring 框架的关键。以下是 Spring 框架中一些重要的核心接口类及其具体用法的详细讲解:
BeanFactory 是 Spring IOC 容器的核心接口,用于定义和管理 Spring 中的 Bean。它是 Spring 框架的基础接口,负责创建和管理依赖关系的实例化和生命周期。BeanFactory 提供了 IoC 容器的最基本功能,通过它可以获取 Bean 实例。
Object getBean(String name):根据 Bean 的名称获取 Bean 实例。Object getBean(String name, Class<?> requiredType):根据 Bean 的名称和类型获取实例。boolean containsBean(String name):判断容器中是否包含指定名称的 Bean。boolean isSingleton(String name):判断指定名称的 Bean 是否是单例。boolean isPrototype(String name):判断指定名称的 Bean 是否是原型模式。BeanFactory 通常用于简单的场景下,加载配置较慢,但内存占用较少。常用于嵌入式应用和资源受限的环境中。BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
MyBean myBean = (MyBean) factory.getBean("myBean");
ApplicationContext 是 BeanFactory 的子接口,提供了更强大的功能,例如国际化支持、事件传播、Bean 自动装配、以及与 Spring AOP 的集成。它是 Spring 框架中使用最广泛的容器接口。
void publishEvent(ApplicationEvent event):发布应用程序事件。Resource getResource(String location):加载指定路径的资源文件。Environment getEnvironment():获取当前应用的环境信息。String[] getBeanDefinitionNames():获取容器中所有 Bean 的名称。ApplicationContext 是企业级开发中最常使用的容器接口,支持更丰富的功能,并且在容器启动时预实例化所有 Bean,适合于大多数 Spring 应用。ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
MyBean myBean = context.getBean(MyBean.class);
BeanPostProcessor 是 Spring 提供的一个扩展点接口,用于在 Spring 容器初始化 Bean 之前和之后对 Bean 进行自定义修改。Spring 容器通过 BeanPostProcessor 来增强或修改 Bean 的行为。
Object postProcessBeforeInitialization(Object bean, String beanName):在 Bean 初始化之前执行操作。Object postProcessAfterInitialization(Object bean, String beanName):在 Bean 初始化之后执行操作。BeanPostProcessor 通常用于定制或增强 Spring Bean,例如 AOP 代理、属性修改等。public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Before Initialization: " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("After Initialization: " + beanName);
return bean;
}
}
InitializingBean 是 Spring 提供的一个生命周期回调接口,提供了在 Bean 属性设置后进行初始化的回调方法。
void afterPropertiesSet():当 Bean 的所有属性设置完成后,容器会自动调用这个方法。InitializingBean 通常用于需要在 Bean 初始化完成后执行自定义逻辑的场景,比如初始化数据库连接等资源。public class MyBean implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
// Bean 初始化逻辑
System.out.println("Initializing Bean");
}
}
DisposableBean 是 Spring 提供的另一个生命周期回调接口,用于在 Bean 销毁时执行自定义的清理逻辑。
void destroy():当 Spring 容器销毁 Bean 时,调用该方法来执行资源清理等操作。public class MyBean implements DisposableBean {
@Override
public void destroy() throws Exception {
// Bean 销毁逻辑
System.out.println("Destroying Bean");
}
}
ApplicationListener 是 Spring 的事件机制接口,用于监听和处理由 ApplicationEvent 触发的事件。任何实现了 ApplicationListener 接口的类都可以监听特定类型的事件。
void onApplicationEvent(E event):接收到特定事件时,会触发此方法。ApplicationListener 常用于处理应用程序中的事件,例如用户注册事件、订单创建事件等。public class MyEventListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
System.out.println("Received event: " + event.getMessage());
}
}
HandlerInterceptor 是 Spring MVC 中的核心接口,允许开发者在处理 HTTP 请求的不同阶段拦截请求。这类似于 Servlet 的过滤器,但更加灵活和轻量。
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler):在请求处理之前调用。如果返回 false,则中断请求。void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView):在请求处理之后但在视图渲染之前调用。void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex):在请求完成之后调用(即视图渲染完成后)。HandlerInterceptor 常用于请求的权限验证、日志记录、性能监控等场景。public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Pre-handle logic");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Post-handle logic");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("After completion logic");
}
}
ResourceLoader 是 Spring 用于加载外部资源(如文件、URL 等)的核心接口。它可以根据不同的路径策略自动识别和加载资源。
主要方法
:
Resource getResource(String location):根据指定位置返回 Resource 对象。使用场景: 用于加载配置文件、图片、XML 文件等外部资源。
@Resource
private ResourceLoader resourceLoader;
public void loadResource() {
Resource resource = resourceLoader.getResource("classpath:application.properties");
System.out.println(resource.exists());
}
PlatformTransactionManager 是 Spring 用于管理事务的核心接口,支持声明式和编程式的事务管理。Spring 提供了多种实现,如 DataSourceTransactionManager、JpaTransactionManager 等。
TransactionStatus getTransaction(TransactionDefinition definition):根据事务定义返回事务状态对象。void commit(TransactionStatus status):提交事务。void rollback(TransactionStatus status):回滚事务。@Autowired
private PlatformTransactionManager transactionManager;
public void performTransaction() {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 业务逻辑
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
在 Spring 框架中,Scope 定义了 Bean 的生命周期和可见性。换句话说,Scope 决定了一个 Bean 是如何创建、存储和共享的。在 Spring 中,Bean 的作用域可以通过注解 @Scope 来指定,或者在 XML 配置文件中通过 scope 属性来配置。
Spring 提供了五种常见的作用域:
Singleton 是 Spring 容器中最常见、也是默认的作用域。表示在 Spring 容器中,每个 Bean 只有一个实例,无论在应用的哪一部分引用到该 Bean,容器都会返回同一个实例。
基于注解:
@Component
@Scope("singleton") // 可省略,因为singleton是默认作用域
public class MySingletonBean {
// Bean定义
}
基于 XML:
<bean id="mySingletonBean" class="com.example.MySingletonBean" scope="singleton"/>
Prototype 表示 每次请求该 Bean 时都会创建一个新的实例。与 Singleton 不同,Prototype 作用域的 Bean 并不在容器启动时创建,而是在每次获取时才创建新的实例。
基于注解:
@Component
@Scope("prototype")
public class MyPrototypeBean {
// Bean定义
}
基于 XML:
<bean id="myPrototypeBean" class="com.example.MyPrototypeBean" scope="prototype"/>
Request 作用域用于 Web 应用程序中,表示 每次 HTTP 请求都会创建一个新的 Bean 实例,并且该实例仅在当前请求内有效。在请求处理完成后,Bean 会被销毁。
基于注解:
@Component
@Scope("request")
public class MyRequestBean {
// Bean定义
}
基于 XML:
<bean id="myRequestBean" class="com.example.MyRequestBean" scope="request"/>
Session 作用域表示 每个 HTTP 会话会创建一个 Bean 实例,并且该实例在整个会话期间内有效。这个 Bean 会随着会话的结束而销毁。
基于注解:
@Component
@Scope("session")
public class MySessionBean {
// Bean定义
}
基于 XML:
<bean id="mySessionBean" class="com.example.MySessionBean" scope="session"/>
Application 作用域表示 整个 Web 应用程序共享一个 Bean 实例,该实例的生命周期与 ServletContext 一致。这个作用域与 Singleton 类似,但仅在 Web 应用中使用。
基于注解:
@Component
@Scope("application")
public class MyApplicationBean {
// Bean定义
}
基于 XML:
<bean id="myApplicationBean" class="com.example.MyApplicationBean" scope="application"/>
在 Spring 中,可以通过多种方式指定 Bean 的作用域:
注解方式: 使用 @Scope 注解,结合 Spring 提供的 @Component、@Service、@Controller 等注解,定义 Bean 的作用域。
@Component
@Scope("prototype")
public class MyPrototypeBean {
// 定义
}
XML 配置方式: 在 XML 配置中使用 scope 属性指定 Bean 的作用域。
<bean id="myBean" class="com.example.MyBean" scope="singleton"/>