跳到主要内容Spring 基于注解配置的 AOP 使用与声明式事务控制 | 极客日志Javajava
Spring 基于注解配置的 AOP 使用与声明式事务控制
Spring 基于注解配置的 AOP 使用涉及切面、通知和连接点等核心概念。通过启用 aspectj-autoproxy 或@EnableAspectJAutoProxy,结合@Component 和@Aspect 注解,可替代 XML 配置实现织入。常见通知类型包括@Before、@AfterReturning、@Around、@AfterThrowing 和@After。此外,AOP 也用于声明式事务控制,统一了 JDBC 和 MyBatis 等不同持久层框架的事务管理方式,通过配置数据源和事务管理器,在业务方法层面自动处理事务提交与回滚,提升开发效率与代码质量。
心动瞬间8.5K 浏览 前言
本文将通过具体的代码示例,逐步演示如何使用 Spring 的注解来实现 AOP。我们将涵盖切面(Aspect)、通知(Advice)、连接点(Join Point)等基本概念,并展示如何将这些概念应用于实际项目中。
XML 方式原理剖析
动态代理的实现选择,在调用 getProxy() 方法时,我们可选用的 AopProxy 接口有两个实现类。这两种都是动态生成代理对象的方式,一种基于 JDK,一种是基于 Cglib。
CGlib 动态代理示例
package com.itheima.test;
import com.itheima.advice.MyAdvice;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGlibTest {
public static void main(String[] args) {
Target target = new Target();
MyAdvice myAdvice = new MyAdvice();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Target.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
Object Throwable {
myAdvice.before();
method.invoke(target, objects);
myAdvice.after();
res;
}
});
(Target) enhancer.create();
proxy.show();
}
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
public
intercept
(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws
Object
res
=
return
Target
proxy
=
package com.itheima.test;
public class MyAdvice {
public void before() {
System.out.println("前置增强");
}
public void after() {
System.out.println("后置增强");
}
}
package com.itheima.test;
public class Target {
public void show() {
System.out.println("show..");
}
}
基于注解配置的 AOP
注解方式 AOP 的基本使用
Spring 的 AOP 也提供了注解方式配置,使用相应的注解替代之前的 XML 配置。XML 配置 AOP 时,主要配置了三部分:目标类被 Spring 容器管理、通知类被 Spring 管理、通知与切点的织入(切面)。
<bean id="target" class="com.itheima.aop.TargetImpl"></bean>
<bean id="advices" class="com.itheima.aop.Advices"></bean>
<aop:config proxy-target-class="true">
<aop:aspect ref="advices">
<aop:around method="around" pointcut="execution(* com.itheima.aop.*,*(..))"/>
</aop:aspect>
</aop:config>
@Component("myAdvice")
@Aspect
public class MyAdvice {
@Before("execution(* com.itheima.service.impl.*.*(..))")
public void beforeAdvice() {
System.out.println("前置的增强");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
<context:component-scan base-package="com.itheima"></context:component-scan>
</beans>
配置 AOP,其实配置 AOP 主要就是配置通知类中的哪个方法(通知类型)对应的切点表达式是什么。
注解 @Aspect、@Around 需要被 Spring 解析,所以在 Spring 核心配置文件中需要配置 aspectj 的自动代理:
注解方式配置 AOP 详解
各种注解方式通知类型
@Before("execution(* com.itheima.aop.*.*(..))")
public void before(JoinPoint joinPoint){}
@AfterReturning("execution(* com.itheima.aop.*.*(..))")
public void afterReturning(JoinPoint joinPoint){}
@Around("execution(* com.itheima.aop.*.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable{}
@AfterThrowing("execution(* com.itheima.aop.*.*(..))")
public void afterThrowing(JoinPoint joinPoint){}
@After("execution(* com.itheima.aop.*.*(..))")
public void after(JoinPoint joinPoint){}
@Component("myAdvice")
@Aspect
public class MyAdvice {
@Before("MyAdvice.myPointcut()")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("前置的增强");
}
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
public void myPointcut() {}
}
@Before("MyAdvice.myPointcut()"): 定义一个前置通知,该通知将在执行切点方法之前执行。这里的切点引用到 myPointcut() 方法。
@Pointcut: 用来定义切点表达式。在这个例子中,它指的是 com.itheima.service.impl 包下的所有类的所有方法。
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig {}
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountService bean = applicationContext.getBean(AccountService.class);
bean.transferMoney("out", "in", 100.0);
}
之前在使用 XML 配置 AOP 时,是借助的 Spring 的外部命名空间的加载方式完成的。使用注解配置后,就抛弃了 <aop:config> 标签,而该标签最终加载了名为 AspectJAutoProxyCreator 的 BeanPostProcessor,最终在该 BeanPostProcessor 中完成了代理对象的生成。
基于 AOP 的声明式事务控制
Spring 事务编程概述
事务是开发中必不可少的东西。使用 JDBC 开发时,我们使用 Connection 对事务进行控制;使用 MyBatis 时,我们使用 SqlSession 对事务进行控制。缺点显而易见,当我们切换数据库访问技术时,事务控制的方式总会变化。Spring 就在这些技术基础上,提供了统一的控制事务的接口。Spring 的事务分为:编程式事务控制和声明式事务控制。
- PlatformTransactionManager
- TransactionDefinition
- TransactionStatus
搭建测试环境
搭建一个转账的环境,dao 层一个转出钱的方法,一个转入钱的方法,service 层一个转账业务方法,内部分别调用 dao 层转出钱和转入钱的方法。准备工作如下:
- 数据库准备一个账户表 tb_account;
- dao 层准备一个 AccountMapper,包括 incrMoney 和 decrMoney 两个方法;
- service 层准备一个 transferMoney 方法,分别调用 incrMoney 和 decrMoney 方法;
- 在 application-context 文件中进行 Bean 的管理配置;测试正常转账与异常转账。
package com.itheima.mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
public interface AccountMapper {
@Update("update account set money = money + #{money} where account_name = #{accountName}")
public void incrMoney(@Param("accountName") String accountName, @Param("money") double money);
@Update("update account set money = money - #{money} where account_name = #{accountName}")
public void decrMoney(@Param("accountName") String accountName, @Param("money") double money);
}
package com.itheima.service;
public interface AccountService {
void transferMoney(String outAccount, String inAccount, double money);
}
package com.itheima.service.impl;
import com.itheima.mapper.AccountMapper;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public void transferMoney(String outAccount, String inAccount, double money) {
accountMapper.decrMoney(outAccount, money);
accountMapper.incrMoney(inAccount, money);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.itheima"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.mapper"></property>
</bean>
</beans>
运行前数据库余额状态显示正常。运行后若发生异常,事务回滚,余额保持不变;若成功,则完成转账。
总结
本文介绍了 Spring 基于注解配置的 AOP 使用方法及声明式事务控制的核心原理。通过启用自动代理并结合各类通知注解,开发者可以灵活地实现横切关注点逻辑。同时,利用 AOP 统一事务管理接口,有效降低了不同持久层框架间的耦合度,提升了系统的可维护性与开发效率。