基于XML的AOP开发
需求:使用AOP对转账案例进行优化,业务层仅保留核心业务,事务的控制使用AOP来完成
步骤分析
1. 新建maven项目,创建以下包类
2. 导入依赖(pom.xml)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>spring_aop_xml</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!-- Spring的AOP开发依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!-- java单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> <!-- 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.7</version> </dependency> </dependencies> </project>
3. pojo实体类、dao(impl)持久层、service(impl)业务层
package com.bjpowernode.pojo;
public class Account { private Integer id; private String name; private Double money; // 省略了 getter、setter、有参构造、无参构造、toString()方法 }
package com.bjpowernode.dao; import com.bjpowernode.pojo.Account;
public interface AccountDao { Account get(int id); void updateMoney(String name,int money); }
package com.bjpowernode.dao.impl; import com.bjpowernode.dao.AccountDao; import com.bjpowernode.pojo.Account; import com.bjpowernode.utils.JDBCUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import java.sql.SQLException; public class AccountDaoImpl implements AccountDao { private QueryRunner qr; public void setQr(QueryRunner qr) { this.qr = qr; } private JDBCUtils jdbcUtils; public void setJdbcUtils(JDBCUtils jdbcUtils) { this.jdbcUtils = jdbcUtils; } @Override public Account get(int id) { try { String sql = "select * from account where id=?"; return qr.query(sql, new BeanHandler<>(Account.class), id); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } @Override public void updateMoney(String name, int money) { String sql = "update account set money = money + ? where name = ?"; try { qr.update(jdbcUtils.getConnection(),sql,money,name); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } }
package com.bjpowernode.service; import com.bjpowernode.pojo.Account;
public interface AccountService { Account get(int id); void transfer(String fromUser,String toUser,int money); }
package com.bjpowernode.service.impl; import com.bjpowernode.dao.AccountDao; import com.bjpowernode.pojo.Account; import com.bjpowernode.service.AccountService; public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public Account get(int id) { return accountDao.get(id); } @Override public void transfer(String fromUser, String toUser, int money) { accountDao.updateMoney(fromUser, -money); //int a = 1/0; accountDao.updateMoney(toUser, money); } }
4. utils工具类
package com.bjpowernode.utils;import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; public class JDBCUtils { private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } // ThreadLocal和当前线程相关的对象,可以将对象绑定到线程上,在同一个线程中,获取到的始终是同一个对象 private static ThreadLocal<Connection> TL = new ThreadLocal<>(); public Connection getConnection() { Connection conn = null; try { conn = TL.get(); // 从线程上获取同一连接对象 if ( conn == null ) { // 当前线程第一次调用getConnection方法 conn = dataSource.getConnection(); // 将连接对象和线程进行绑定 TL.set(conn); } } catch (SQLException e) { e.printStackTrace(); } // System.out.println(conn); return conn; } public void begin() { try { // 自动连接?:否,改手动 getConnection().setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } public void rollback() { try { getConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } } public void commit() { try { getConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } } public void release() { try { // 不是真正的关闭,而是将连接对象返回给连接池,底层是动态代理技术 getConnection().close(); // 将连接对象从当前线程移除 TL.remove(); } catch (SQLException e) { e.printStackTrace(); } } }
5. 通知类(对目标方法进行增强的类)
package com.bjpowernode.advice; import com.bjpowernode.utils.JDBCUtils; import org.aspectj.lang.ProceedingJoinPoint; import java.sql.SQLException; // 事务管理器,对业务方法提供事务的增强处理 public class TransactionManager { private JDBCUtils jdbcUtils; public void setJdbcUtils(JDBCUtils jdbcUtils) { this.jdbcUtils = jdbcUtils; } public void begin() { System.out.println("开启事务"); try { jdbcUtils.getConnection().setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } public void rollback() { System.out.println("回滚事务"); try { jdbcUtils.getConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } } public void commit() { System.out.println("提交事务"); try { jdbcUtils.getConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } } // 环绕通知:整合前面的通知 public Object around(ProceedingJoinPoint pjp) { Object result = null; try { begin(); // 开启事务 result = pjp.proceed(); // 执行目标方法 } catch (Throwable throwable) { throwable.printStackTrace(); rollback(); // 事务回滚 } finally { commit(); // 事务提交 jdbcUtils.release(); // 释放 } return result; } }
6. applicationContext.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" 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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--开启组件扫描--> <context:component-scan base-package="com.bjpowernode"/> <!--配置数据源--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <!-- set方法注入 --> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/mydb" /> <property name="username" value="root" /> <property name="password" value="root"/> </bean> <!--DBUtils的核心类:QueryRunner--> <bean id="qr" class="org.apache.commons.dbutils.QueryRunner"> <!-- 构造方法注入 --> <constructor-arg name="ds" ref="dataSource"/> </bean> <!-- JDBCUtils --> <bean id="jdbcutils" class="com.bjpowernode.utils.JDBCUtils"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置Dao、Service --> <bean id="accountDao" class="com.bjpowernode.dao.impl.AccountDaoImpl"> <property name="qr" ref="qr" /> <property name="jdbcUtils" ref="jdbcutils" /> </bean> <bean id="accountService" class="com.bjpowernode.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao" /> </bean>
<!-- AOP相关配置--> <!-- 1. 配置通知类(对业务方法提供增强的类)--> <bean id="transactionManager" class="com.bjpowernode.advice.TransactionManager" > <property name="jdbcUtils" ref="jdbcutils" /> </bean> <!-- 2. aop配置:使用哪个通知类对哪些方法进行增强 --> <!-- 方式一 <aop:config> <aop:aspect ref="transactionManager"> <!– pointcut: 切入点,即目标方法 –> <!– 前置通知:在切入点执行之前,先运行transactionManager实例中的begin方法 –> <aop:before method="begin" pointcut="execution(public void com.bjpowernode.service.impl.AccountServiceImpl.transfer(String,String,int))" /> <!– 发生异常时 –> <aop:after-throwing method="rollback" pointcut="execution(public void com.bjpowernode.service.impl.AccountServiceImpl.transfer(String,String,int))" /> <!– 最终通知 –> <aop:after method="commit" pointcut="execution(public void com.bjpowernode.service.impl.AccountServiceImpl.transfer(String,String,int))" /> </aop:aspect> </aop:config> --> <!-- 方式二 <aop:config> <!– pointcut: 切入点,即目标方法 –> <aop:pointcut id="transfer" expression="execution(public void com.bjpowernode.service.impl.AccountServiceImpl.transfer(String,String,int))"/> <!– 配置切面 ref: 使用哪个实例对切面(事务)进行增强 –> <aop:aspect ref="transactionManager"> <!– 前置通知:在切入点执行之前 –> <aop:before method="begin" pointcut-ref="transfer" /> <!– 发生异常时 –> <aop:after-throwing method="rollback" pointcut-ref="transfer" /> <!– 最终通知:在切入点执行之后 –> <aop:after method="commit" pointcut-ref="transfer" /> </aop:aspect> </aop:config> --> <!-- 方式三:推荐!!! --> <aop:config> <!-- pointcut: 切入点,即目标方法 --> <aop:pointcut id="transfer" expression="execution(public void com.bjpowernode.service.impl.AccountServiceImpl.transfer(String,String,int))"/> <!-- 配置切面 ref: 使用哪个实例对切面(事务)进行增强 --> <aop:aspect ref="transactionManager"> <!-- 环绕通知 --> <aop:around method="around" pointcut-ref="transfer" /> </aop:aspect> </aop:config> </beans>
7. 测试类
import com.bjpowernode.service.AccountService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ContextConfiguration("classpath:applicationContext.xml") // 指定配置文件的所在路径 @RunWith(SpringJUnit4ClassRunner.class) // 指定使用spring编写的增强的junit类运行代码 public class Tester { @Autowired AccountService accountService; @Test public void testFindById(){ System.out.println(accountService.get(1)); } @Test public void testUpdateMoney(){ accountService.transfer("tom","jack",100); // tom向jack转移100 } }