AspectJ AOP的使用(@Before、@PointCut、@Around等)


最近在看一个项目中使用到AOP的功能,现在将自己过去所学的知识梳理一下。

AOP概念

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
了开发的效率。
具体概念请看这篇文章:https://blog.csdn.net/q982151756/article/details/80513340

使用AspectJ面向切面编程

切面(Aspect) :通知(advice)和切入点(pointcut)共同组成了切面(aspect)。可以从注解方式来理解,代码如下。
@Aspect为类上面的注解——切面
@pointcut(…)——切入点。为此类内一个空方法上面的注解。可以把拦截的地址表达式表示为方法签名,利于使用起来方便。
@before、@after等——通知。为此类下面的方法上面的注解。
三者在一块组成一个切面。

package com.nowcoder.community.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @author cjy
 * @Package com.nowcoder.community.aspect
 * @date 2021/8/11 16:48
 */
/*@Component
@Aspect*/
public class AlphaAspect {
    //定义一个切点,目标方法
    @Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
    public void pointcut(){

    }
    //目标方法之前
    @Before("pointcut()")
    public void before(){
        System.out.println("before");
    }

    @After("pointcut()")
    public void after(){
        System.out.println("after");
    }

    //方法返回之后
    @AfterReturning("pointcut()")
    public void afterReturning(){
        System.out.println("afterReturning");
    }

    //再抛异常的时候植入代码
    @AfterThrowing("pointcut()")
    public void afterThrowing(){
        System.out.println("AfterThrowing");
    }

    //环绕,参数ProceedingJoinPoint连接点,植入的部位
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("around before");  //前
        Object obj = joinPoint.proceed();//调用目标组件的方法
        System.out.println("around after");  //后
        return obj;
    }
}

详细说明

@Aspect

首先,代码的类的最上面使用的是@Aspect注解,作用是把当前类标识为一个切面供容器读取,之后使用@Component将切面注入IOC容器。

@Pointcut、execution

进入类中,是使用@Pointcut(“execution(* com.nowcoder.community.service..(…))”)这样的一个注解修饰一个pointcut()方法。这样是为了指定切入点和一个point方法签名。execution中可以匹配如下内容:

修饰符匹配(modifier-pattern?)
返回值匹配(ret-type-pattern)可以为表示任何返回值,全路径的类名等
类路径匹配(declaring-type-pattern?)
方法名匹配(name-pattern)可以指定方法名 或者 代表所有, set 代表以set开头的所有方法
参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“
”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(*,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;可以用(…)表示零个或多个任意参数
异常类型匹配(throws-pattern?)
例如:

1execution(* *(..))  
//表示匹配所有方法  
2execution(public * com. savage.service.UserService.*(..))  
//表示匹配com.savage.server.UserService中所有的公有方法  
3execution(* com.savage.server..*.*(..))  
//表示匹配com.savage.server包及其子包下的所有方法 

然后要使用所定义的Pointcut时,可以指定Pointcut签名。例如:

@Before("pointCut")

这种使用方式等同于以下方式,直接定义execution表达式使用:

@Before("execution(* com.savage.aop.MessageSender.*(..))")

Pointcut定义时,还可以使用&&、||、! 这三个运算,例如:

@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
private void logSender(){}

@Pointcut("execution(* com.savage.aop.MessageReceiver.*(..))")
private void logReceiver(){}

@Pointcut("logSender() || logReceiver()")
private void logMessage(){}

笔者更推荐这一种,将一些公用的Pointcut放到一个类中,以供整个应用程序使用。类似于常量存储,便于修改和扩展。

package com.savage.aop;

import org.aspectj.lang.annotation.*;

public class Pointcuts {
@Pointcut("execution(* *Message(..))")
public void logMessage(){}

@Pointcut("execution(* *Attachment(..))")
public void logAttachment(){}

@Pointcut("execution(* *Service.*(..))")
public void auth(){}
}

使用如下:

@Aspect
public class LogBeforeAdvice {
		@Before("com.sagage.aop.Pointcuts.logMessage()")
		public void before(JoinPoint joinPoint) {
		System.out.println("Logging before " + joinPoint.getSignature().getName());
	}
}

各种通知

最后,就是整个aop的核心代码了,使用了@Before、@Around等注解修饰的方法。
@Before 前置通知(Before advice) :在某连接点(JoinPoint)——核心代码(类或者方法)之前执行的通知,但这个通知不能阻止连接点前的执行。
@After 后通知(After advice) :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
@AfterReturning 返回后通知(After return advice) :在某连接点正常完成后执行的通知,不包括抛出异常的情况。
@Around 环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。这时aop的最重要的,最常用的注解。用这个注解的方法入参传的是ProceedingJionPoint pjp,可以决定当前线程能否进入核心方法中——通过调用pjp.proceed();
@AfterThrowing 抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。

有如下的一个例子:某一个系统需要一个日志组件,用来记录用户在什么时间访问了哪个资源。正常情况下来讲,需要在每一个controller层接口部分返回一个用户操作的log。这样需要修改的代码很多,如果使用面向切面思想,如果直接在controller层方法后,或者service方法前指定一个切面,就可以对其进行扩展。代码如下:

package com.nowcoder.community.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author cjy
 * @Package com.nowcoder.community.aspect
 * @date 2021/8/11 17:00
 */
@Component
@Aspect
public class ServiceLogAspect {
    //日志组件
    private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);
    //指定切入点,类路径匹配,匹配所有com.nowcoder.community.service中所有的方法
    @Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
    //point签名
    public void pointCut(){

    }

    //在方法执行前执行
    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        //记录内容
        //用户【1,2,3(ip)】,在【时间】访问量【com....功能】的方法
        //转换为子类型
        ServletRequestAttributes attr = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        if (attr == null){
            logger.info("使用kafka消费");
            return;
        }
        HttpServletRequest request = attr.getRequest();

        String ip = request.getRemoteHost();
        String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        //类名和方法名获取
        String target = joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName();
        logger.info(String.format("用户[%s],在[%s]访问了[%s]",ip,now,target));
    }
    /*
    * 也可以写为controller方法,使用@AfterReturning注解。当这个方法(连接点)正常返回后执行通知,异常的话不执行通知
    * 或者使用@Around环绕通知,自定义方法完成前后执行。而且还可以决定当前线程是否进入到核心方法中
    * 主要是ProceedingJoinPoint.proceed()方法可以真正的调用目标方法,必须有返回值返回
    * pjp.getArgs()可以获取参数,可以对参数进行校验
    * */
}

完成以后,在每个service方法旁边,都会有一个->m的标识,也就是切入点在此。
在这里插入图片描述
这样的切入点范围市比较广的,有时候只对其中的一些功能切入,我们可以配合自定义注解方式使用AOP编程。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值