- @Repository
- public class BookDaoImpl implements BookDao {
-
- public void update(){
- System.out.println("book dao update ...");
- }
-
- public void delete(){
- System.out.println("book dao delete ...");
- }
-
- public void select(){
- System.out.println("book dao select ...");
- }
- }
- package com.lan.aop
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
-
- @Component
- @Aspect // 设置当前类为AOP切面类
- public class MyAdvice {
- }
- package com.lan.aop
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
-
- @Component
- @Aspect
- public class MyAdvice {
- // 切入点表示要切入的方法,要切入的方法叫连接点
- @Pointcut("execution(void com.lan.dao.BookDao.delete())")
- private void pt(){} // 该方法名随意
- }
- package com.lan.aop
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
-
- @Component
- @Aspect
- public class MyAdvice {
- // 切入点表示要切入的方法,要切入的方法叫连接点
- @Pointcut("execution(void com.lan.dao.BookDao.delete())")
- private void pt(){} // 该方法名随意
-
- @Before("pt()") // 定义切面,@Before表示通知在连接点执行前执行
- public void before(){ // 定义通知
- System.out.println(System.currentTimeMillis());
- }
-
-
- @After("pt()") // 定义切面,@After表示通知在连接点执行后执行
- public void after() {
- System.out.println("after execution");
- }
-
- @Around("pt()") // 定义切面,@Around表示连接点在通知的中间某一步执行
- public Object around(ProceedingJoinPoint pjp) throws Throwable {
- System.out.println("------------------------------");
- Long startTime = System.currentTimeMillis();
- for (int i = 0 ; i<10 ; i++) {
- //调用原始操作
- pjp.proceed();
- }
- Long endTime = System.currentTimeMillis();
- Long totalTime = endTime-startTime;
- System.out.println("执行10次消耗时间:" + totalTime + "ms");
- return null;
- }
- }
- package com.lan.config;
-
- import org.springframework.context.annotation.ComponentScan;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.EnableAspectJAutoProxy;
-
- @Configuration
- @ComponentScan("com.lan")
- @EnableAspectJAutoProxy // 开启AOP功能
- public class SpringConfig {
- }
- ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
- // 此处的参数必须是接口类,不能是实现类BookDaoImpl,因为JDK Proxy代理的只能是接口
- // JDK Proxy可以参考 https://blog.csdn.net/werewolf2017/article/details/124950536
- BookDao bookDao = ctx.getBean(BookDao.class);
- bookDao.delete();
| 注解 | 作用 |
| @EnableAspectJAutoProxy | 开启注解格式AOP功能 |
| @Aspect | 设置当前类为切面类 |
| @Pointcut | 在切面类中设置切入点方法,value为切入点表达式 |
| @Before | 设置前置通知方法 |
| @Around | 设置环绕通知方法 |
| @After | 设置后置通知方法 |
| @AfterReturning | 设置返回后通知方法 |
| @AfterThrowing | 设置抛出异常后通知方法 |
AOP实际上是通过代理模式实现的,匹配到切入点的bean,spring会为其生成代理对象,其class是Proxy;没匹配到切入点的bean,其class是对应接口的实现类。输出bean的class对象可以验证:
- ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
- BookDao bookDao = ctx.getBean(BookDao.class);
- System.out.println(bookDao); // 无论是否匹配到切入点,对象都是BookDaoImpl
- // 匹配到切入点的bean,其class是Proxy;没匹配到切入点的bean,则其class是BookDao对应的实现类
- System.out.println(bookDao.getClass());
表达式格式:
动作关键字 ( 访问修饰符 返回值 包名 . 类 / 接口名 . 方法名 ( 参数 ) 异常名)
以这个为例子
execution(public User com.lan.service.UserService.findById(int))
execution:动作关键字,描述切入点的行为动作,例如execution表示执行到指定切入点
public:访问修饰符,还可以是public,private等,可以省略
User:返回值,写返回值类型
com.lan.service:包名,多级包使用点连接
UserService:类/接口名称
findById:方法名
int:参数,直接写参数的类型,多个类型用逗号隔开
异常名:方法定义中抛出指定异常,可以省略
可以使用通配符描述切入点,简化切入点描述
execution (public * com.lan.*.UserService.find*(*))
execution (public User com..UserService.findById(..))
execution(* *..*Service+.*(..))
- @Before("pt()")
- public void before(JoinPoint jp) {
- Object[] args = jp.getArgs();
- System.out.println(Arrays.toString(args));
- System.out.println("before advice ..." );
- }
- @Around("pt()")
- public Object around(ProceedingJoinPoint pjp) {
- Object[] args = pjp.getArgs();
- System.out.println(Arrays.toString(args));
- args[0] = 666;
- Object ret = null;
- try {
- ret = pjp.proceed(args);
- } catch (Throwable t) {
- t.printStackTrace();
- }
- return ret;
- }
设置返回后通知获取原始方法的返回值,要求returning属性值必须与方法形参名相同
- @AfterReturning(value = "pt()",returning = "ret")
- public void afterReturning(JoinPoint jp,String ret) {
- System.out.println("afterReturning advice ..."+ret);
- }
设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同
- @AfterThrowing(value = "pt()",throwing = "t")
- public void afterThrowing(Throwable t) {
- System.out.println("afterThrowing advice ..."+t);
- }
使用@EnableTransactionManagement注解开启注解式事务驱动
- @Configuration
- @ComponentScan("com.lan")
- @PropertySource("classpath:jdbc.properties")
- @Import({JdbcConfig.class,MybatisConfig.class})
- //开启注解式事务驱动
- @EnableTransactionManagement
- public class SpringConfig {
- }
- //配置事务管理器,mybatis使用的是jdbc事务
- @Bean
- public PlatformTransactionManager transactionManager(DataSource dataSource){
- DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
- transactionManager.setDataSource(dataSource);
- return transactionManager;
- }
在AccountService接口类加@Transactional注解表示该方法内的jdbc操作处于同一个事务中,注解也可以在实现类上加
- public interface AccountService {
- /**
- * 转账操作
- * @param out 传出方
- * @param in 转入方
- * @param money 金额
- */
- //配置当前接口方法具有事务
- @Transactional
- public void transfer(String out,String in ,Double money) ;
- }
场景:在转账操作中,不管成功或失败,都记录操作日志到数据库中。代码如下
- @Transactional(rollbackFor=Exception.class)
- public void transfer(String out,String in ,Double money) {
- try {
- // 转账的数据库操作
- } finally {
- // 记录日志的数据库操作
- log();
- }
- }
-
- public void log() {
- }
以上代码中,转账发生异常时失败时,记录日志的数据库操作也会被回滚,若将日志记录的操作放在一个新的事务中,则可以避免,这就涉及到事务的传播。修改后的代码如下
- @Transactional(rollbackFor=Exception.class)
- public void transfer(String out,String in ,Double money) {
- try {
- // 转账的数据库操作
- } finally {
- // 记录日志的数据库操作
- log();
- }
- }
-
- @Transactional(propagation = Propagation.REQUIRES_NEW) // 表示需要一个新的事务
- public void log() {
- }
@Transaction的propagation属性可以指定事务的传播方式,事务的传播方式有六种,分别是REQUIRED(默认)、REQUIRED_NEW、SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER、NESTED
以上面的transfer调用log为例,讲解两个方法的事务传播的影响:
| log的传播属性 | transfer事务 | log事务结果 |
| REQUIRED | 开启事务T | 加入事务T |
| 无事务 | 新建事务 | |
| REQUIRED_NEW | 开启事务T | 新建事务 |
| 无事务 | 新建事务 | |
| SUPPORTS | 开启事务T | 加入事务T |
| 无事务 | 无事务 | |
| NOT_SUPPORTED | 开启事务T | 无事务 |
| 无事务 | 无事务 | |
| MANDATORY | 开启事务T | 加入事务T |
| 无事务 | ERROR | |
| NEVER | 开启事务T | ERROR |
| 无事务 | 无事务 | |
| NESTED | 设置savePoint,一旦事务回滚,事务将回滚到savePoint处,交由客户响应提交或回滚 | |