一.切入点表达式
1.概念:描述切入点方法的表达式,主要用来决定项目中的哪些方法需要加入通知。
2.常见形式:execution(...)和@annotation(...)
3.execution
execution主要根据方法的返回值,包名,类名,方法名,方法参数等信息来匹配,语法为
execution(访问修饰符[可省略] 返回值[可省略] 包名.类名[可省略].方法名(方法参数) throws 异常(可省略))
注:
[1]可省略的部分:访问修饰符,包名.类名,throws 异常 必须有的部分:方法名
[2]可以使用通配符描述切入点:
*:单个独立的任意符号(不一定是*),可以通配任意返回值,包名,类名,方法名,任意类型的一个参数,也可以通配包,类,方法名的一部分(下面【4】的第一点需要用到)
..:多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数
区别(*号是代替一个,而..是代替多个)
举几个例子就知道了:
- 未修改版
execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
- 使用`*`代替返回值类型
execution(* com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
- 使用`*`代替包名(一层包使用一个`*`)
execution(* com.itheima.*.*.DeptServiceImpl.delete(java.lang.Integer))
- 使用`..`省略包名
execution(* com..DeptServiceImpl.delete(java.lang.Integer))
- 使用`*`代替类名
execution(* com..*.delete(java.lang.Integer))
- 使用`*`代替方法名
execution(* com..*.*(java.lang.Integer))
- 使用 `*` 代替参数
execution(* com.itheima.service.impl.DeptServiceImpl.delete(*))
- 使用`..`省略参数
execution(* com..*.*(..))
[3]可以使用且(&&),或(||),非(!)来组合比较复杂的切入点表达式
[4]书写规范:
*所有业务方法名在命名时尽量规范,方便切入点表达式的快速匹配。如:查询类方法都是find开头,更新类方法都是update开头。
execution(* com.itheima.service.impl.DeptServiceImpl.find*(..))
*描述切入点方法通常基于接口描述,而不是直接表述实现类,增强拓展性。
execution(* com.itheima.service.DeptService.*(..))
*在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用..,而是使用*匹配单个包。
execution(* com.itheima.*.*.DeptServiceImpl.find*(..))
4.@annotation
如果我们要匹配多个无规则的方法,比如:list()和delete()这两个方法,用切入点表达式要将两个切入点组合在一起完成需求,比较繁琐,我们可以借助与另一种切入点表达式annotation来描述这一类的切入点,从而来简化切入点表达式的书写。
实现步骤:
1.编写自定义注解
2.在业务类要作为连接点的方法上添加自定义注解
3.在切面类使用@annotation注解中书写自定义注解的方式来指定连接点。
例子:
**自定义注解**:MyLog
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
**业务类**:DeptServiceImpl
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Override
@MyLog //自定义注解(表示:当前方法属于目标方法)
public List<Dept> list() {
List<Dept> deptList = deptMapper.list();
//模拟异常
//int num = 10/0;
return deptList;
}
@Override
@MyLog //自定义注解(表示:当前方法属于目标方法)
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);
}
}
**切面类**
@Slf4j
@Component
@Aspect
public class MyAspect6 {
//针对list方法、delete方法进行前置通知和后置通知
//前置通知
@Before("@annotation(com.itheima.anno.MyLog)")
public void before(){
log.info("MyAspect6 -> before ...");
}
//后置通知
@After("@annotation(com.itheima.anno.MyLog)")
public void after(){
log.info("MyAspect6 -> after ...");
}
}
两种方法各有优缺点,如果方法名有规则,选第一种合适,如果方法名没有规则,选第二种合适。
二.连接点
1.连接点可以简单理解为可以被AOP控制的方法,在SpringAOP中,连接点又特指方法的执行。
2.在Spring中类抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名,方法名,方法参数等。
*对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型(有proceed方法)
*对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型(没有proceed方法)
例子:
@Slf4j
@Component
@Aspect
public class MyAspect7 {
@Pointcut("@annotation(com.itheima.anno.MyLog)")
private void pt(){}
//前置通知
@Before("pt()")
public void before(JoinPoint joinPoint){
log.info(joinPoint.getSignature().getName() + " MyAspect7 -> before ...");
}
//后置通知
@Before("pt()")
public void after(JoinPoint joinPoint){
log.info(joinPoint.getSignature().getName() + " MyAspect7 -> after ...");
}
//环绕通知
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//获取目标类名
String name = pjp.getTarget().getClass().getName();
log.info("目标类名:{}",name);
//目标方法名
String methodName = pjp.getSignature().getName();
log.info("目标方法名:{}",methodName);
//获取方法执行时需要的参数
Object[] args = pjp.getArgs();
log.info("目标方法参数:{}", Arrays.toString(args));
//执行原始方法
Object returnValue = pjp.proceed();
return returnValue;
}
}