6.5 多切面配置
我们可以为切点配置多个通知,形成多切面,比如希望dao层的每个方法结束后都可以打印日志并发送邮件:
1、编写发送邮件的通知:
public class MyAspectJAdvice2 { // 后置通知 public void myAfterReturning(JoinPoint joinPoint) { System.out.println("发送邮件"); } }
2、配置切面
<!-- 通知对象 --> <bean id="myAspectJAdvice" class="com.itbaizhan.advice.MyAspectAdvice"></bean> <bean id="myAspectJAdvice2" class="com.itbaizhan.advice.MyAspectAdvice2"></bean> <!-- 配置AOP --> <aop:config> <!-- 配置切面 --> <aop:aspect ref="myAspectJAdvice"> <!-- 配置切点 --> <aop:pointcut id="myPointcut" expression="execution(* *..*.*(..))"/> <!-- 后置通知 --> <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"/> </aop:aspect> <aop:aspect ref="myAspectJAdvice2"> <aop:pointcut id="myPointcut2" expression="execution(* com.itbaizhan.dao.UserDao.*(..))"/> <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut2"/> </aop:aspect> </aop:config>
6.6 注解配置AOP
1、在applicationContext.xml文件中开启AOP注解支持
<aop:aspectj-autoproxy/>
2、在通知类上方加入注解@Aspect
3、在通知方法上方加入注解@Before/@AfterReturning/@AfterThrowing/@After/@Around
package com.zj.advice; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; @Aspect public class MyAdviceAspect { //后置通知 @AfterReturning("execution(* com.zj.dao.StudentDao.*(..))") public void AfterReturningMethod(JoinPoint joinPoint){ System.out.println("切点方法名:"+joinPoint.getSignature().getName()); System.out.println("哪个类执行的该方法:"+joinPoint.getTarget()); System.out.println("打印日志……"); } //前置通知 @Before("execution(* com.zj.dao.StudentDao.*(..))") public void BeforeMethod(JoinPoint joinPoint){ System.out.println("前置通知……"); } //异常通知 @AfterThrowing(value = "execution(* com.zj.dao.StudentDao.*(..))",throwing = "exception") public void AfterThrowingMethod(Exception exception){ System.out.println("异常:"+exception.getMessage()); System.out.println("异常通知……"); } //最终通知 @After("execution(* com.zj.dao.StudentDao.*(..))") public void AfterMethod(JoinPoint joinPoint){ System.out.println("最终通知……"); } //环绕通知 @Around("execution(* com.zj.dao.StudentDao.*(..))") public Object RoundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕前……"); Object proceed = proceedingJoinPoint.proceed();//执行原方法 System.out.println("环绕后……"); return proceed; } }
或者直接将切点提出来
package com.zj.advice; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; @Aspect public class MyAdviceAspect { //切点 @Pointcut("execution(* com.zj.dao.StudentDao.*(..))") public void point(){ } //后置通知 @AfterReturning("point()") public void AfterReturningMethod(JoinPoint joinPoint){ System.out.println("切点方法名:"+joinPoint.getSignature().getName()); System.out.println("哪个类执行的该方法:"+joinPoint.getTarget()); System.out.println("打印日志……"); } //前置通知 @Before("point()") public void BeforeMethod(JoinPoint joinPoint){ System.out.println("前置通知……"); } //异常通知 @AfterThrowing(value = "point()",throwing = "exception") public void AfterThrowingMethod(Exception exception){ System.out.println("异常:"+exception.getMessage()); System.out.println("异常通知……"); } //最终通知 @After("point()") public void AfterMethod(JoinPoint joinPoint){ System.out.println("最终通知……"); } //环绕通知 @Around("point()") public Object RoundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕前……"); Object proceed = proceedingJoinPoint.proceed();//执行原方法 System.out.println("环绕后……"); return proceed; } }
配置类如何代替xml中AOP注解支持?
在配置类上方添加@EnableAspectJAutoProxy即可
@Configuration @ComponentScan("com.zj") @EnableAspectJAutoProxy public class SpringConfig { }
七、Spring事务
事务:不可分割的原子操作。即一系列的操作要么同时成功,要么同时失败。
开发过程中,事务管理一般在service层,service层中可能会操作多次数据库,这些操作是不可分割的。否则当程序报错时,可能会造成数据异常。
如:张三给李四转账时,需要两次操作数据库:张三存款减少、李四存款增加。如果这两次数据库操作间出现异常,则会造成数据错误。
1、创建数据库
2、创建实体类
package com.zj.pojo; public class User { private int id; private String username; private String sex; private String address; private int account; //get/set/构造省略 }
3、创建maven项目(spring+mybatis),引入依赖
<?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>springandmybatis</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!--Mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <!--JDBC驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency> <!--Druid连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.16</version> </dependency> <!--Spring--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.13</version> </dependency> <!--事务--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.13</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.13</version> </dependency> <!-- MyBatis与Spring的整合包,该包可以让Spring创建MyBatis的对象 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> <!--AOP框架: AspectJ --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency> <!-- junit,如果Spring5整合junit,则junit版本至少在4.12以上 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- spring整合测试模块 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.13</version> </dependency> </dependencies> </project>
4、dao、service层
@Repository public interface UserDao { //根据id查找用户 @Select("select * from user where id = #{id}") User findUserById(int id); //修改用户余额 @Update("update user set account = #{account} where id = #{id}") void updateUser(User user); } @Service public class UserService { @Autowired private UserDao userDao; //转账业务 public void updateUser(int id1, int id2,int money){ /*转出人减少金额*/ User user1 = userDao.findUserById(id1); System.out.println("未转账之前:"+user1.getUsername()+"有"+user1.getAccount()+"元。"); user1.setAccount(user1.getAccount() - money); userDao.updateUser(user1); /*转入人增加金额*/ User user2 = userDao.findUserById(id2); System.out.println("未转账之前:"+user2.getUsername()+"有"+user2.getAccount()+"元。"); user2.setAccount(user2.getAccount() + money); userDao.updateUser(user2); User userById1 = userDao.findUserById(id1); System.out.println("转账之后:"+userById1.getUsername()+"有"+userById1.getAccount()+"元。"); User userById2 = userDao.findUserById(id2); System.out.println("转账之后:"+userById2.getUsername()+"有"+userById2.getAccount()+"元。"); } }
5、配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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 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.zj"/> <!--读取数据源配置文件--> <context:property-placeholder location="classpath:db.properties"/> <!--配置数据源--> <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!--创建Spring封装过的SqlSessionFactory对象--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="datasource"/> </bean> <!--创建Spring封装的SqlSession--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> <!-- 该对象可以自动扫描持久层接口,并为接口创建代理对象 --> <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 配置扫描的接口包 --> <property name="basePackage" value="com.zj.dao"/> </bean> <!--开启AOP注解支持--> <aop:aspectj-autoproxy/> </beans>
6、测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class TestUserService { @Autowired private UserService userService; @Test public void testUpdateUser(){ userService.updateUser(5,6,500); } }
这么看来似乎是没啥大问题但是如果在转账的时候出现错误的话会怎么样呢?下面我们就来模拟转账业务出现错误看看转账业务是否还能正常进行。
@Service public class UserService { @Autowired private UserDao userDao; //转账业务 public void updateUser(int id1, int id2,int money){ /*转出人减少金额*/ User user1 = userDao.findUserById(id1); System.out.println("未转账之前:"+user1.getUsername()+"有"+user1.getAccount()+"元。"); user1.setAccount(user1.getAccount() - money); userDao.updateUser(user1); //模拟错误 int a = 1/0; /*转入人增加金额*/ User user2 = userDao.findUserById(id2); System.out.println("未转账之前:"+user2.getUsername()+"有"+user2.getAccount()+"元。"); user2.setAccount(user2.getAccount() + money); userDao.updateUser(user2); User userById1 = userDao.findUserById(id1); System.out.println("转账之后:"+userById1.getUsername()+"有"+userById1.getAccount()+"元。"); User userById2 = userDao.findUserById(id2); System.out.println("转账之后:"+userById2.getUsername()+"有"+userById2.getAccount()+"元。"); } }
测试后
发现翟玲娇把钱转给了张晓但是钱没有到张晓的手里,钱丢了。我们希望的是转账业务要么成功要么失败。所以要使用事务。