> 文章列表 > SpringAop中的五种常见的通知的注解及@annotation 切入点表达式

SpringAop中的五种常见的通知的注解及@annotation 切入点表达式

SpringAop中的五种常见的通知的注解及@annotation 切入点表达式

AOP使用的场景,事务控制,日志的记录,异常的处理等

只要是需要批量的对功能增强都可以使用AOP来实现,这是主要思路一定要记住。

Spring中默认使用的代理方式是cglib,在学习javaEE的时候我们学的是JDk的动态代理。

他们两个的区别前者是基于继承的代理,而后者是基于接口的代理

主要有以下五个通知(默认已会切入点表达式):

@Before: 前置通知注解

定义方法,方法是实现切面功能的。方法的定义要求:1.公共方法 public2.方法没有返回值3.方法名称自定义4.方法可以有参数,也可以没有参数。如果有参数,参数不是自定义的,有几个参数类型可以使用。
属性:value ,是切入点表达式,表示切面的功能执行的位置。位置:在方法的上面特点:1.在目标方法之前先执行的2.不会改变目标方法的执行结果3.不会影响目标方法的执行。* 指定通知方法中的参数 : JoinPoint* JoinPoint:业务方法,要加入切面功能的业务方法*    作用是:可以在通知方法中获取方法执行时的信息, 例如方法名称,				     方法的实参。*    如果你的切面功能中需要用到方法的信息,就加入JoinPoint.*    这个JoinPoint参数的值是由框架赋予, 必须是第一个位置的参数如下:
 @Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))")public void myBefore(JoinPoint jp){//获取方法的完整定义System.out.println("方法的签名(定义)="+jp.getSignature());System.out.println("方法的名称="+jp.getSignature().getName());//获取方法的实参Object args [] = jp.getArgs();for (Object arg:args){System.out.println("参数="+arg);}//就是你切面要执行的功能代码System.out.println("前置通知, 切面功能:在目标方法之前输出执行时间:"+ new Date());}

@AfterReturning:后置通知

  后置通知定义方法,方法是实现切面功能的。方法的定义要求:1.公共方法 public2.方法没有返回值3.方法名称自定义4.方法有参数的,推荐是Object ,参数名自定义
属性:位置:在方法定义的上面1.value 切入点表达式2.returning 自定义的变量,表示目标方法的返回值的。自定义变量名必须和通知方法的形参名一样。
特点:1.在目标方法之后执行的。2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能Object res = doOther();3. 可以修改这个返回值后置通知的执行Object res = doOther();参数传递: 传值, 传引用myAfterReturing(res);System.out.println("res="+res)如下:
   @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "res")public void myAfterReturing(  JoinPoint jp  ,Object res ){// Object res:是目标方法执行后的返回值,根据返回值做你的切面的功能处理System.out.println("后置通知:方法的定义"+ jp.getSignature());System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:"+res);if(res.equals("abcd")){//做一些功能} else{//做其它功能}}

@Around: 环绕通知

 环绕通知方法的定义格式1.public2.必须有一个返回值,推荐使用Object3.方法名称自定义4.方法有参数,固定的参数 ProceedingJoinPoint  *    
属性:value 切入点表达式位置:在方法的定义什么特点:1.它是功能最强的通知2.在目标方法的前和后都能增强功能。3.控制目标方法是否被调用执行4.修改原来的目标方法的执行结果。 影响最后的调用结果

环绕通知,等同于jdk动态代理的,InvocationHandler接口
参数: ProceedingJoinPoint 就等同于 Method
作用:执行目标方法的
返回值: 就是目标方法的执行结果,可以被修改。
环绕通知: 经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务

 @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")public Object myAround(ProceedingJoinPoint pjp) throws Throwable {String name = "";//获取第一个参数值Object args [] = pjp.getArgs();if( args!= null && args.length > 1){Object arg=  args[0];name =(String)arg;}//实现环绕通知Object result = null;System.out.println("环绕通知:在目标方法之前,输出时间:"+ new Date());//1.目标方法调用if( "zhangsan".equals(name)){//符合条件,调用目标方法result = pjp.proceed(); //method.invoke(); Object result = doFirst();}System.out.println("环绕通知:在目标方法之后,提交事务");//2.在目标方法的前或者后加入功能//修改目标方法的执行结果, 影响方法最后的调用结果if( result != null){result = "Hello AspectJ AOP";}//返回目标方法的执行结果return result;}

@AfterThrowing:异常通知

异常通知方法的定义格式1.public2.没有返回值3.方法名称自定义4.方法有个一个Exception, 如果还有是JoinPoint,
属性:1. value 切入点表达式2. throwinng 自定义的变量,表示目标方法抛出的异常对象。变量名必须和方法的参数名一样
特点:1. 在目标方法抛出异常时执行的2. 可以做异常的监控程序, 监控目标方法执行时是不是有异常。如果有异常,可以发送邮件,短信进行通知执行就是:try{SomeServiceImpl.doSecond(..)}catch(Exception e){myAfterThrowing(e);}
如下:
   @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")public void myAfterThrowing(Exception ex) {System.out.println("异常通知:方法发生异常时,执行:"+ex.getMessage());//发送邮件,短信,通知开发人员}

@After :最终通知

最终通知方法的定义格式1.public2.没有返回值3.方法名称自定义4.方法没有参数,  如果还有是JoinPoint
属性: value 切入点表达式位置: 在方法的上面特点:1.总是会执行2.在目标方法之后执行的try{SomeServiceImpl.doThird(..)}catch(Exception e){}finally{myAfter()}
如下:
 @After(value = "execution(* *..SomeServiceImpl.doThird(..))")public  void  myAfter(){System.out.println("执行最终通知,总是会被执行的代码");//一般做资源清除工作的。}

此外对于不同通知中相同的切入点表达式可以再定义一个方法,使用@Pointcut指定,其他的有相同的直接引用方法名即可。

示例如下:

@Component
@Aspect
public class AopTestDemo {@Pointcut("execution(* *..SomeServiceImpl.doSecond(..))")public void getExecution(){}@Before("getExecution()")public void logBeforeAop(){System.out.println("前置通知");}@After("getExecution()")public void logAfterAop(){System.out.println("前置通知");}
}

根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。

execution(* *..service.DeptService.list(..)) || 
execution(* *..service.DeptService.delete(..))

以上的方法其实都有局限性,因为对于切入点表达式基本都是有规则的切入,但对于无规则的需求就很难实现,比如当遇到下面的需求该怎么处理?

需求:要求对service层的增删查方法进行日志记录,记录操作的人id,操作的时间,操作的内容,方法的返回值,方法的参数等信息

这个时候再使用上面的方法就不灵活了,这个时候spring也提供了对应的处理方式;

@annotation 切入点表达式

实现步骤:

  1. 编写自定义注解
  2. 在业务类要做为连接点的方法上添加自定义注解
  3. annotation 切入点表达式

自定义注解:MyLog

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

业务类:DeptServiceImpl

@Slf4j
@Service
public class DeptServiceImpl implements DeptService {@Autowiredprivate 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);}@Overridepublic void save(Dept dept) {dept.setCreateTime(LocalDateTime.now());dept.setUpdateTime(LocalDateTime.now());deptMapper.save(dept);}@Overridepublic Dept getById(Integer id) {return deptMapper.getById(id);}@Override@MyLogpublic void update(Dept dept) {dept.setUpdateTime(LocalDateTime.now());deptMapper.update(dept);}
}

这个时候就以再使用在对应的方法上添加该注解。

参数部分不使用execution而是@annotation,参数为注解类的全限定名称。在此时Spring就可以通过表达式中的存在的注解,对有注解的地方进行切入操作

@Slf4j
@Component
@Aspect
public class MyAspect6 {//针对list方法、delete方法进行前置通知和后置通知//前置通知@Before("@annotation(com.yfs1024.anno.MyLog)")public void before(){log.info("MyAspect6 -> before ...");}//后置通知@After("@annotation(com.yfs1024.anno.MyLog)")public void after(){log.info("MyAspect6 -> after ...");}
}

当然此时也可以通过一个方法把相同的注解提出去,这里就不在演示,方式和上面相同

总结一下:

  • execution切入点表达式
    • 根据我们所指定的方法的描述信息来匹配切入点方法,这种方式也是最为常用的一种方式
    • 如果我们要匹配的切入点方法的方法名不规则,或者有一些比较特殊的需求,通过execution切入点表达式描述比较繁琐
  • annotation 切入点表达式
    • 基于注解的方式来匹配切入点方法。这种方式虽然多一步操作,我们需要自定义一个注解,但是相对来比较灵活。我们需要匹配哪个方法,就在方法上加上对应的注解就可以了