AOP(七)


AOP(七)

事务管理

事务是一组操作的集合,是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败

@Transactional

该注解放于业务(service)层的方法上,类上,接口上。

作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务。

范例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//方法
@Transactional
@Override
public void deleteById(Integer id) {
deptMapper.deleteById(id);
int i=1/0;
empMapper.deleteByDeptId(id);
}

//接口
@Transactional
public interface DeptService{}

//类
@Transactional
@Service
public class DeptServiceImpl implements DeptService {}


//application.yml
#spring事务管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug

rollbackFor

默认情况下,只有出现RuntimeException才会回滚日常。rollbackFor属性用于控制出现何种异常类型,回滚事务。

1
2
3
4
5
6
7
8
9
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteById(Integer id) throws Exception{
deptMapper.deleteById(id);
if(true){
throw new Exception("出现异常");
}
empMapper.deleteByDeptId(id);
}

propagation

事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

  • REQUIRED:大部分情况下都是用该传播行为即可。
  • REQUIRED_NEW:当我们不希望事务之间相互影响时,可使用该传播行为。

AOP

AOP:Aspect Oriented Programming(面向切面编程,面向方面编程),其实就是面向特定方法编程。

步骤

  1. 在pom.xml中导入AOP的依赖
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 编写AOP程序:针对特定方法根据业务需要进行编程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect //AOP类
public class TimeAspect {
@Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))") //切入点表达式
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
//1. 记录开始时间
long begin = System.currentTimeMillis();

//2. 调用原始方法运行
Object result = joinPoint.proceed();

//3. 记录结束时间, 计算方法执行耗时
long end = System.currentTimeMillis();
log.info(joinPoint.getSignature()+"方法执行耗时: {}ms", end-begin);
return result;
}
}

核心概念

连接点:JointPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)

通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)

切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用

切面:Apect,描述通知与切入点的对应关系(通知+切入点)

目标对象:Target,通知所应用的对象

执行流程

进行AOP程序的开发,运行的不再是原始目标对象,而是基于目标对象所生成的代理对象。

通知类型

  1. @Around:环绕通知。此注解标注的通知方法在目标方法前后都被执行
  2. @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  3. @After:后置通知,此注解标注的通知方法在目标方法后被执行
  4. @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  5. @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行

  • @Around环绕通知需要自己调用proceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
  • @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值

:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.itheima.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class MyAspect1 {
@Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void pt(){}
@Before("pt()")
public void before(){
log.info("before ...");
}
@Around("pt()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before ...");
Object result = proceedingJoinPoint.proceed();
//调用目标对象的原始方法执行

log.info("around after ...");
return result;
}
@After("pt()")
public void after(){
log.info("after ...");
}

@AfterReturning("pt()")
public void afterReturning(){
log.info("afterReturning ...");
}

@AfterThrowing("pt()")
public void afterThrowing(){
log.info("afterThrowing ...");
}
}

通知顺序

  1. 不同切面类中,默认按照切面类的类名字母排序
    • 目标方法前的通知方法:字母排名靠前的先执行
    • 目标方法后的通知方法:字母排名靠前的后执行
  2. 用@Order(数字)加在切面类上来控制顺序
    • 目标方法前的通知方法:数字小的先执行
    • 目标方法后的通知方法:数字小的后执行

切入点表达式

描述切入点方法的一种表达式,即切入点表达式

作用:主要用来决定项目中的哪些需要加入通知

常见形式

  1. execution(…):根据方法的签名来匹配
  2. @annotation(….):根据注释匹配

execution

语法:

execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)

其中带?的表示可以省略的部分

通配符

  • *: 单个独立的任意符号,可以通配任意返回值,报名,类名,方法名,任意类型的一个参数,也可以通配包,类,方法名的一部分
  • ..:多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数

书写规范

  • 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配,如:查询类方法都是find开头,更新类方法都是update开头
  • 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强扩展性
  • 在满足业务需要的前提下,尽量缩小切入点的匹配范围,例如:报名匹配尽量不使用..,使用*匹配单个包

@annotation

@annotation切入点表达式,用于匹配标识有特定注解的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.itheima.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Mylog {
}



package com.itheima.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class MyAspect1 {
@Pointcut("@annotation(com.itheima.aop.Mylog)")
public void pt(){}
@Before("pt()")
public void before(){
log.info("aspect1 ...");
}
}


package com.itheima.service.impl;

import com.itheima.aop.Mylog;
import com.itheima.mapper.DeptMapper;
import com.itheima.pojo.Dept;
import com.itheima.service.DeptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;

@Mylog
@Override
public List<Dept> list() {
List<Dept> deptList = deptMapper.list();
return deptList;
}

@Mylog
@Override
public void delete(Integer id) {
//1. 删除部门
deptMapper.delete(id);
}

@Override
public void save(Dept dept) {
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());
deptMapper.save(dept);
}

@Override
public Dept getById(Integer id) {
return deptMapper.getById(id);
}

@Override
public void update(Dept dept) {
dept.setUpdateTime(LocalDateTime.now());
deptMapper.update(dept);
}
}

连接点

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,例如目标类名,方法名,方法参数等。

  • @Around:获取连接点信息只能使用ProceedingJoinPoint
  • 对于其他四种通知,获取连接点信息只能用JoinPoint,它是ProceedingJoinPoint的父类型

:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.ljsblog.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Slf4j
@Component
@Aspect
public class MyAspect1 {
@Pointcut("@annotation(com.itheima.aop.Mylog)")
public void pt(){}
@Around("pt()")
public Object round(ProceedingJoinPoint joinPoint) throws Throwable {

String name = joinPoint.getTarget().getClass().getName();
log.info("目标对象的类名{}",name);
String methodName = joinPoint.getSignature().getName();
log.info("目标对象的方法名{}",methodName);
Object[] args = joinPoint.getArgs();
log.info("目标对象的参数{}", Arrays.toString(args));

//放行目标方法执行
Object result = joinPoint.proceed();

log.info("目标方法的返回值为:{}",result);
return result;
}

}

//非Around注解中,方法参数用JoinPoint,获取目标对象各项属性的方法与ProceedingJoinPoint一致·

Author: ljs
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source ljs !
评论
  TOC