Spring AOP(AOP+JDBC 模板 + 转账案例)

Spring 框架核心笔记(AOP+JDBC 模板 + 转账案例)

一、AOP 概念与入门案例

AOP(面向切面编程)是 Spring 核心特性,通过横向抽取机制分离业务逻辑与通用功能(如事务、日志),降低耦合度。

1. 入门案例环境准备

创建 Maven 项目,引入核心依赖:
<dependencies> <!-- Spring核心 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!-- 日志相关 --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <!-- 测试相关 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> <scope>test</scope> </dependency> <!-- 数据库相关 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> </dependencies>

2. 核心业务代码

(1)实体类 Account

public class Account { private Integer id; private String name; private Double money; // getter、setter方法省略 }

(2)DAO 层接口与实现

// AccountDao接口 public interface AccountDao { void save(Account account) throws SQLException; } // AccountDaoImpl实现类 public class AccountDaoImpl implements AccountDao { @Override public void save(Account account) throws SQLException { Connection conn = TxUtils.getConnection(); String sql = "insert into account values (null,?,?)"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setString(1, account.getName()); stmt.setDouble(2, account.getMoney()); stmt.executeUpdate(); stmt.close(); } }

(3)Service 层接口与实现

// AccountService接口 public interface AccountService { void saveAll(Account account1, Account account2) throws SQLException; } // AccountServiceImpl实现类 public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void saveAll(Account account1, Account account2) throws SQLException { accountDao.save(account1); // 模拟异常:int a = 1/0; accountDao.save(account2); } }

(4)事务工具类 TxUtils(需拷贝引入)

用于事务管理,包含开启事务、提交、回滚、关闭资源等方法。

(5)JDK 动态代理类

public class JdkProxy { public static Object getProxy(AccountService accountService) { return Proxy.newProxyInstance( JdkProxy.class.getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { TxUtils.startTransaction(); result = method.invoke(accountService, args); TxUtils.commit(); } catch (Exception e) { e.printStackTrace(); TxUtils.rollback(); } finally { TxUtils.close(); } return result; } } ); } }

(6)测试类

public class Demo1 { @Test public void run1() throws SQLException { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_demo1.xml"); AccountService accountService = (AccountService) ac.getBean("accountService"); Account account1 = new Account(); account1.setName("熊大"); account1.setMoney(1000d); Account account2 = new Account(); account2.setName("美羊羊"); account2.setMoney(1000d); Object proxyobj = JdkProxy.getProxy(accountService); AccountService proxy = (AccountService) proxyobj; proxy.saveAll(account1, account2); } }

(7)Spring 配置文件 applicationContext_demo1.xml

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="accountDao" class="com.qcbyjy.demo1.AccountDaoImpl"/> <bean id="accountService" class="com.qcbyjy.demo1.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> </bean> </beans>


二、AOP 核心概念与优势

1. 核心概念

  • 连接点(Joinpoint):被拦截的方法(Spring 仅支持方法级连接点)。
  • 切入点(Pointcut):定义需要拦截的连接点(即要增强的方法)。
  • 通知(Advice):拦截后执行的操作,分前置、后置、异常、最终、环绕通知。
  • 目标对象(Target):被代理的原始业务对象。
  • 织入(Weaving):将通知应用到目标对象生成代理的过程。
  • 代理(Proxy):织入增强后的结果对象。
  • 切面(Aspect):切入点与通知的组合。

2. 核心优势

  • 无需修改源代码,运行期动态增强。
  • 减少重复代码(如事务、日志)。
  • 提高开发效率,简化维护。

3. 底层原理

  • JDK 动态代理:基于接口生成代理类,要求目标对象实现接口。
  • CGLIB 代理:基于类生成代理子类,无需目标对象实现接口。

三、Spring AOP 配置文件方式

1. 依赖补充

需额外引入 AOP 相关依赖:
<!-- AOP联盟 --> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <!-- Spring Aspects --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!-- AspectJ Weaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.3</version> </dependency>

2. 配置文件约束

<?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"> </beans>

3. 切入点表达式

格式:execution([修饰符] 返回值类型 包名.类名.方法名(参数))

  • 修饰符可省略,返回值类型不可省略(可用*通配)。
  • 包名、类名、方法名可用*通配,多级包可用..省略。
  • 参数用*表示单个任意参数,..表示任意参数(个数 / 类型)。
  • 通用表达式:execution(* com.qcbyjy.*.*ServiceImpl.*(..))

4. 通知类型配置

(1)切面类

public class MyXmlAspect { // 前置通知 public void beforeLog() { System.out.println("前置通知:方法执行前增强"); } // 后置通知 public void afterReturningLog() { System.out.println("后置通知:方法执行成功后增强"); } // 异常通知 public void afterThrowingLog() { System.out.println("异常通知:方法执行失败后增强"); } // 最终通知 public void afterLog() { System.out.println("最终通知:方法执行完毕(成功/失败)后增强"); } // 环绕通知 public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕通知:方法执行前"); Object result = pjp.proceed(); // 执行目标方法 System.out.println("环绕通知:方法执行后"); return result; } }

(2)AOP 配置

<!-- 目标对象 --> <bean id="userService" class="com.qcbyjy.demo2.UserServiceImpl"/> <!-- 切面类 --> <bean id="myXmlAspect" class="com.qcbyjy.demo2.MyXmlAspect"/> <!-- AOP配置 --> <aop:config> <aop:aspect ref="myXmlAspect"> <!-- 切入点定义(可复用) --> <aop:pointcut id="pointcut" expression="execution(* com.qcbyjy.*.*ServiceImpl.*(..))"/> <!-- 各类通知 --> <aop:before method="beforeLog" pointcut-ref="pointcut"/> <aop:after-returning method="afterReturningLog" pointcut-ref="pointcut"/> <aop:after-throwing method="afterThrowingLog" pointcut-ref="pointcut"/> <aop:after method="afterLog" pointcut-ref="pointcut"/> <aop:around method="aroundLog" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>

(3)测试类

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext_demo2.xml") public class Demo2 { @Autowired private UserService userService; @Test public void run1() { userService.save(); } }


四、Spring AOP 注解方式

1. 纯注解配置步骤

(1)切面类(含注解)

@Component // 交给IOC容器管理 @Aspect // 声明为切面类 public class MyAnnoAspect { // 前置通知 @Before("execution(* com.qcbyjy.demo3.OrderServiceImpl.save(..))") public void log() { System.out.println("注解方式:前置增强"); } }

(2)Spring 配置类

@Configuration @ComponentScan("com.qcbyjy.demo3") // 扫描包 @EnableAspectJAutoProxy // 开启AOP自动代理 public class SpringConfig { }

(3)通知类型注解
  • @Before:前置通知
  • @AfterReturning:后置通知
  • @AfterThrowing:异常通知
  • @After:最终通知
  • @Around:环绕通知(需手动执行pjp.proceed())

五、Spring JDBC 模板技术

1. 依赖补充

<!-- Spring JDBC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!-- Spring事务 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.2.RELEASE</version> </dependency>

2. 配置文件方式使用

(1)属性文件 jdbc.properties

jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///spring_db jdbc.username=root jdbc.password=root

(2)Spring 配置文件

<context:property-placeholder location="classpath:jdbc.properties"/> <!-- 数据源(Druid) --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="{jdbc.driverClassName}"/\> \{jdbc.username}"/\> \(3)增删改查操作

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext_jdbc.xml") public class Demo1_1 { @Autowired private JdbcTemplate jdbcTemplate; // 新增 @Test public void add() { jdbcTemplate.update("insert into account values (null,?,?)", "熊二", 500); } // 修改 @Test public void update() { jdbcTemplate.update("update account set money=? where name=?", 800, "熊二"); } // 删除 @Test public void delete() { jdbcTemplate.update("delete from account where name=?", "熊二"); } // 查询单个 @Test public void findOne() { Account account = jdbcTemplate.queryForObject( "select * from account where name=?", new BeanMapper(), "熊大" ); System.out.println(account); } // 查询所有 @Test public void findAll() { List<Account> list = jdbcTemplate.query("select * from account", new BeanMapper()); list.forEach(System.out::println); } } // 结果集映射类 class BeanMapper implements RowMapper<Account> { @Override public Account mapRow(ResultSet rs, int rowNum) throws SQLException { Account account = new Account(); account.setId(rs.getInt("id")); account.setName(rs.getString("name")); account.setMoney(rs.getDouble("money")); return account; } }


六、模拟转账开发案例

1. 完整实现(DAO 层继承 JdbcDaoSupport)

(1)Service 层

// 接口 public interface AccountService { void pay(String out, String in, double money); } // 实现类 public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void pay(String out, String in, double money) { accountDao.outMoney(out, money); // 付款 // 模拟异常:int a = 1/0; accountDao.inMoney(in, money); // 收款 } }

(2)DAO 层

// 接口 public interface AccountDao { void outMoney(String out, double money); void inMoney(String in, double money); } // 实现类(继承JdbcDaoSupport) public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public void outMoney(String out, double money) { this.getJdbcTemplate().update("update account set money=money-? where name=?", money, out); } @Override public void inMoney(String in, double money) { this.getJdbcTemplate().update("update account set money=money+? where name=?", money, in); } }

(3)Spring 配置文件

<context:property-placeholder location="classpath:jdbc.properties"/> <!-- 数据源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="{jdbc.driverClassName}"/\> \{jdbc.username}"/\> \(4)测试类

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext_dao2.xml") public class Demo3 { @Autowired private AccountService accountService; @Test public void testPay() { accountService.pay("熊大", "熊二", 100); } }

相关推荐
松仔log1 小时前
JetPack——Paging3+Room
android·java·zoom
㳺三才人子6 小时前
初探 Flask
后端·python·flask·html
星栈独行6 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Lei活在当下6 小时前
先用起来,再理解,关于协程Coroutine应该知道的事
android·java·jvm
Java爱好狂.6 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易7 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
tongluowan0077 小时前
以ReentrantLock为例解释AQS的工作流程
java·模板方法模式·aqs·reentrantlock
装不满的克莱因瓶7 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
ltl8 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
身如柳絮随风扬8 小时前
Java 项目打包与部署完全指南:JAR vs WAR,从构建到运行
java·firefox·jar