当前位置: 首页 > 工具软件 > Aspect# > 使用案例 >

Spring AOP配置 之 @Aspect

欧阳山
2023-12-01

一、AOP概念(百度)

    AOP(Aspect Oriented Programming):面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
    AOP是Spring框架面向切面的编程思想,AOP采用一种称为“横切”的技术,将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中。

二、AOP基本术语

  1. Aspect(切面):切面泛指交叉业务逻辑。类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice,如事务处理、日志记录就可以理解为切面。切面实际就是对主业务逻辑的一种增强。
  2. Joint Point(连接点):程序执行的某个特定位置,如类开始初始化前,类初始化后,类某个方法调用前,调用后,方法抛出异常之后。一个类或一段代码拥有一些具有边界性质的特定点,这些代码中的特定点就成为连接点。(指切面可以织入的位置。) Spring仅支持方法的连接点,即只能在方法调用之前,调用之后,方法抛出异常,以及方法调用前后这些连接点织入增强;
  3. PointCut(切点):指切面具体织入的位置。是一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  4. Advice(增强):Advice是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。通知定义了Advice代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。Advice类型不同,切入时间不同。
  5. Target(目标对象):织入 Advice 的目标对象.。
  6. Weaving(织入):把切面应用到目标对象来创建新的代理对象的过程。

三、Advice类型

  1. @Before:标识一个前置增强方法。
  2. @After:final增强,不管是抛出异常或者正常退出都会执行。
  3. @AfterReturning:后置增强,方法正常退出时执行。
  4. @AfterThrowing:异常抛出增强,方法执行抛出异常后执行。
  5. @Around:环绕增强,能控制切点执行前,执行后,,用这个注解后,程序抛异常,会影响@AfterThrowing这个注解。

四、Sping @Aspect开发步骤

  1. 添加依赖
	<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
	<dependency>
		<groupId>org.aspectj</groupId>
		<artifactId>aspectjrt</artifactId>
		<version>1.9.2</version>
	</dependency>
	<dependency>
		<groupId>org.aspectj</groupId>
		<artifactId>aspectjweaver</artifactId>
		<version>1.9.2</version>
	</dependency>
  1. 配置自动扫描包
<context:component-scan 	base-package="com.test.spring.aop">
</context:component-scan>
  1. 编写切面
    3.1 定义一个类,该类添加了@Component、@Aspect注解
    3.2 定义切点(切点定义方式可参考《Spring AOP配置 之 @PointCut注解》)
    3.3 配置增强,给方法添加@Before、@After、@AfterReturning、@AfterThrowing、@Around等增强配置。
  2. 书写增强方法的具体业务逻辑,通过JoinPoint对象参数,可以获取到方法的签名和方法的参数等信息。

五、执行顺序

  1. @Around环绕通知
  2. @Before通知执行
  3. @Before通知执行结束
  4. @Around环绕通知执行结束
  5. @After后置通知执行了
  6. @AfterReturning第一个后置返回通知后执行

六、试例代码

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.SourceLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Optional;

@Aspect
@Component
public class LogAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    // java针对处理对象与json之间关系转换用的
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    // 定义切点
    private final String POINT_CUT = "execution( * com.lzq..controller.*.*(..))";

    @Pointcut(value = POINT_CUT)
    public void pointCut() {
    }

    /**
     * 前置增强方法
     *
     * @param joinPoint
     */
    @Before(value = "pointCut()")
    public void before(JoinPoint joinPoint) {
        logger.info("\n\n----------------- before start -------------");
        // 获取目标方法参数信息
        Object[] args = joinPoint.getArgs();
        if (args.length > 0) {
            logger.info("目标方法参数信息:");
            Arrays.stream(args).forEach(arg -> {
                Optional.ofNullable(arg).ifPresent(a -> logger.info(a.toString()));
            });
        }

        // 获取 AOP 代理对象:CGLib代理和JDK动态代理
        logger.info("AOP 代理对象:");
        Object thisObj = joinPoint.getThis();
        logger.info("-" + thisObj.getClass().getName() + ":" + thisObj.toString());

        // 获取被代理的目标对象
        logger.info("被代理的目标对象:");
        Object object = joinPoint.getTarget();
        logger.info("-" + object.getClass().getName() + ":" + object.toString());

        // 获取连接点Joint Point类型
        logger.info("连接点Joint Point类型:");
        String kind = joinPoint.getKind();
        logger.info("-" + kind);// -method-execution

        // 获取连接点的方法签名对象
        logger.info("连接点的方法签名对象:");
        Signature signature = joinPoint.getSignature();
        logger.info("-" + signature.toLongString());
        logger.info("-" + signature.toShortString());
        logger.info("-" + signature.toString());
        logger.info("-方法名:" + signature.getName());
        logger.info("-获取声明类型 方法所在类的class对象" + signature.getDeclaringType().toString());
        logger.info("-获取声明类型名" + signature.getDeclaringTypeName());

        // 获取连接点方法所在类文件中的位置 打印报异常
        logger.info("连接点方法所在类文件中的位置:");
        SourceLocation sourceLocation = joinPoint.getSourceLocation();
        logger.info("-" + sourceLocation.toString());
        // logger.info("-" + sourceLocation.getFileName());
        // logger.info("-" + sourceLocation.getLine() + "");
        // logger.info("-" + sourceLocation.getWithinType().toString());

        // 返回连接点静态部分
        logger.info("连接点静态部分:");
        JoinPoint.StaticPart staticPart = joinPoint.getStaticPart();
        logger.info("-" + staticPart.toLongString());// execution(public com.lzq.selfdiscipline.business.bean.MessageBean com.lzq.selfdiscipline.business.controller.UserFileManagerController.queryFiles(java.lang.String,java.lang.Integer,java.lang.Integer))

        // attributes可以获取request信息 session信息等
        logger.info("request信息:");
        ServletRequestAttributes attributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        logger.info("-" + request.getRequestURL().toString());
        logger.info("-" + request.getRemoteAddr());
        logger.info("-" + request.getMethod());
        logger.info("-" + request.getParameterMap().toString());
        logger.info("----------------- before end -------------");
    }

    /**
     * 后置增强,方法正常退出时执行
     * 如果第一个参数为JoinPoint,则第二个参数为返回值的信息
     * 如果第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
     * returning:限定了只有目标方法返回值与通知方法参数类型匹配时才能执行后置返回通知,否则不执行,
     * 参数为Object类型将匹配任何目标返回值
     *
     * @param joinPoint
     * @param result
     */
    @AfterReturning(value = "pointCut()", returning = "result", argNames = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("----------------- afterReturning -------------" + result);
    }

    /**
     * 异常抛出增强,方法执行抛出异常后执行。
     * 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
     * throwing:限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
     * 对于throwing对应的通知方法参数为 Throwable 类型将匹配任何异常。
     *
     * @param joinPoint
     * @param exception
     */
    @AfterThrowing(value = "pointCut()", throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint, Throwable exception) {
        System.out.println("----------------- afterThrowing -------------");
        if (exception instanceof NullPointerException) {
            logger.info("空指针异常");
        }
    }

    /**
     * final增强,不管是抛出异常或者正常退出都会执行。
     */
    @After(value = "pointCut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("----------------- after -------------\n\n");
    }

    /**
     * 环绕通知:
     * 注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用
     * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
     * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     *
     * @param proceedingJoinPoint
     * @return
     */
    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        // 获取当前方法对象
        Method method = methodSignature.getMethod();
        Object obj = null;
        try {
            // 调用下一个advice或者执行目标方法,返回值为目标方法返回值,因此可以通过更改返回值,修改方法的返回值
            // 如:修改obj,然后将obj返回
            obj = proceedingJoinPoint.proceed();

            /*Object[] args = new Object[] {};
            // //参数为目标方法的参数 因此可以通过修改参数改变方法入参
            obj = proceedingJoinPoint.proceed(args);*/
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return obj;
    }
}

 类似资料: