基于注解的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>spring03_aop_xml_anno</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <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> <!--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> <!-- DBUtils: 简化持久层(Dao)的代码 --> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.7</version> </dependency> <!--Spring对持久层的支持--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.8.RELEASE</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 findById(Integer 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 org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.sql.SQLException; @Repository // 数据访问层 public class AccountDaoImpl implements AccountDao { @Autowired private QueryRunner queryRunner; @Autowired private JDBCUtils jdbcUtils; @Override public Account findById(Integer id){ String sql = "select * from account where id = ?"; try { return queryRunner.query(jdbcUtils.getConnection(),sql,new BeanHandler<Account>(Account.class),id); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException("查找失败!"); } } @Override public void updateMoney(String name, int money) { String sql = "update account set money = money + ? where name = ?"; try { queryRunner.update(jdbcUtils.getConnection(),sql,money,name); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException("修改失败!"); } } }
package com.bjpowernode.service; import com.bjpowernode.pojo.Account; public interface AccountService { Account findById(Integer 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; import com.bjpowernode.utils.JDBCUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service // 业务层,相当于Bean了一个id public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; @Override public Account findById(Integer id){ return accountDao.findById(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 org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; @Component public class JDBCUtils { @Autowired private 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 org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.sql.SQLException; // 事务管理器,对业务方法提供事务的增强处理 @Component @Aspect public class TransactionManager { @Autowired private 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(); } } //抽取切入点表达式 @Pointcut("execution(* com.bjpowernode.service..*.transfer(..))") private void pc_transfer(){} // 环绕通知:整合前面的通知 @Around("pc_transfer()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object result = null; try { begin(); // 开启事务 result = pjp.proceed(); // 执行目标方法 } catch (Throwable throwable) { throwable.printStackTrace(); rollback(); // 必须将异常抛出去,否则其它的通知类无法捕获异常 throw throwable; } finally { commit(); // 提交事务 jdbcUtils.release(); } return result; } }
// 所有包下的service包及其子包下的所有类中的所有方法,返回值任意,修饰符任意 //抽取切入点表达式 @Pointcut("execution(* *..service..*.*(..))") private void any_method(){} // 环绕通知:整合前面的通知 @Around("any_method()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object result = null; Signature signature = pjp.getSignature(); // 方法签名对象 String methodName = signature.getName(); // 方法名 // 查询方法直接执行,不用事务:query*、get*、find*、select* if (methodName.startsWith("query") || methodName.startsWith("get") || methodName.startsWith("find") || methodName.startsWith("select")){ return pjp.proceed(); // 执行目标方法 } else { try { begin(); // 开启事务 result = pjp.proceed(); // 执行目标方法 } catch (Throwable throwable) { throwable.printStackTrace(); rollback(); // 必须将异常抛出去,否则其它的通知类无法捕获异常 throw throwable; } finally { commit(); // 提交事务 jdbcUtils.release(); } return result; } }
假如有多个通知类,@Around注解方式不能控制哪个通知类先执行。所以建议用xml形式配置。
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"/> <!--配置数据源--> <!-- 方法一: 导入jdbc.properties --> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <!-- EL表达式 --> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <!--DBUtils的核心类:QueryRunner--> <bean id="qr" class="org.apache.commons.dbutils.QueryRunner"> <constructor-arg name="ds" ref="dataSource" /> </bean> <!-- 开启aop注解支持 --> <aop:aspectj-autoproxy /> </beans>
7. jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
jdbc.username=root
jdbc.password=root
8. 测试类
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 findById(){ System.out.println(accountService.findById(1)); } @Test public void testUpdateMoney(){ accountService.transfer("jack","tom",100); } }