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

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

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

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

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

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

@Before: 前置通知注解

定义方法,方法是实现切面功能的。
   方法的定义要求:
      1.公共方法 public
      2.方法没有返回值
      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.公共方法 public
  		      2.方法没有返回值
  		      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.public
 	  2.必须有一个返回值,推荐使用Object
 	  3.方法名称自定义
 	  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.public
	       2.没有返回值
	       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.public
   2.没有返回值
   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 {
    @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
    @MyLog
    public 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 切入点表达式
    • 基于注解的方式来匹配切入点方法。这种方式虽然多一步操作,我们需要自定义一个注解,但是相对来比较灵活。我们需要匹配哪个方法,就在方法上加上对应的注解就可以了

你可能感兴趣的:(java,开发语言)