SpringBoot项目切面编程

什么是切面

专业术语解释:

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

通俗解释

要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。
我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。
按照正常的逻辑,我们可以这么做。
在这里插入图片描述
这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。
在这里插入图片描述同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。
在这里插入图片描述

这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。上图中红框圈住的部分就是面向切面编程。

使用@Aspect进行切面编程

注解说明

  • @Aspect:作用是把当前类标识为一个切面供容器读取

  • @Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。

  • @Around:环绕增强,相当于MethodInterceptor

  • @AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行

  • @Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有

  • @AfterThrowing:异常抛出增强,相当于ThrowsAdvice

  • @After:final增强,不管是抛出异常或者正常退出都会执行

使用过程

1、引入maven依赖

<!--引入AOP依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在完成了引入AOP依赖包后,不需要去做其他配置。AOP的默认配置属性中,spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy,不需要在程序主类中增加@EnableAspectJAutoProxy来启用。

2、新建一个类,在类上添加@Aspect和@Component 注解即可将一个类定义为切面类。Aspect 注解 使之成为切面类, @Component 注解 把切面类加入到IOC容器中
3、定义切入点
@Pointcut 代表这是一个切入点
方式一:利用execution() 表达式主体进行切入

	/**
     * 指定切入点表达式
     * public * com.hkl.modules.*.controller..*(..))
	*/
    //此处的表达式主体代表所有的controller的方法
    @Pointcut("execution(* com.example.demo.controller..*.*(..))")
    public void pointcut(){}

注解@Pointcut代表这是一个切入点
execution(): 表达式主体,execution切点函数
第一个*符号 表示返回值的类型任意;
com.example.demo.controller下边的所有类以及子包的类
.*表示包下的所有类,而…表示包、子孙包下的所有类。
.
(…) 表示任何方法名,括号表示参数,两个点表示任何参数类型

方式二:利用@annotation()自定义注解进行切入

	/**
     * 指定注解切入
     * @param @annotation(xxx):xxx是自定义注解的全路径
     */
	@Pointcut("@annotation(com.sinosoft.springbootplus.datapermission.aspect.annotation.PermissionData)")
    public void pointCut() {

    }

4、通知

  • 前置通知(Before advice):在某个连接点(Join point)之前执行的通知,但这个通知不能阻止连接点的执行(除非它抛出一个异常)。
  • 返回后通知(After returning advice):在某个连接点(Join point)正常完成后执行的通知。例如,一个方法没有抛出任何异常正常返回。
  • 抛出异常后通知(After throwing advice):在方法抛出异常后执行的通知。
  • 后置通知(After(finally)advice):当某个连接点(Join point)退出的时候执行的通知(不论是正常返回还是发生异常退出)。
  • 环绕通知(Around advice):包围一个连接点(Join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

Demo

定义注解

package com.sinosoft.springbootplus.datapermission.aspect.annotation;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
public @interface PermissionData {

    /**
     * 配置菜单的组件路径,用于数据权限
     */
    String permissionId() default "";
}

定义切面类

package com.sinosoft.springbootplus.datapermission.aspect;

import com.sinosoft.springbootplus.datapermission.aspect.annotation.PermissionData;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
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.lang.reflect.Method;


@Component
@Aspect
@Slf4j
public class AopConfigure {

    private long startTime = 0;


    /**
     * 指定切入点表达式
     * public * com.hkl.modules.*.controller..*(..))
     */
    @Pointcut("execution( * com.sinosoft.springbootplus.system.controller..*(..))")
    public void getMethods() {

    }

    /**
     * 指定注解切入
     * @param @annotation(xxx):xxx是自定义注解的全路径
     */
    @Pointcut("@annotation(com.sinosoft.springbootplus.datapermission.aspect.annotation.PermissionData)")
    public void withAnnotationMethods() {
    }


    /***
     * 方法执行之前切入控制层
     * 表达式和注解方式同时满足才会切入
     * @param joinPoint
     */
    @Before(value = "getMethods() && withAnnotationMethods()")
    public void doBefore(JoinPoint joinPoint){
        //获取Servlet容器
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //获取request请求
        HttpServletRequest request = attributes.getRequest();
        //执行方法对象
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        //判断切入的方法是否标记xxx注解
        //boolean flag = method.isAnnotationPresent(xxx.class);
        //CollUtil.toList(role).contains(userType);

        /** 可做操作说明 start */
        //1、鉴权、解析request请求对象中设置的属性
        //2、反射解析注解、记录操作日志等
        /** 可做操作说明 end */

        log.info("测试切入{}成功,方法名:"+method.getName(), "@Before");
        startTime = System.currentTimeMillis();
    }


    /***
     * 方法执行之后切入控制层
     * 表达式和注解方式同时满足才会切入
     * @param joinPoint
     */
    //@After(value = "getMethods() && withAnnotationMethods()")
    public void doAfter(JoinPoint joinPoint) {
        //业务操作同 @Before 方式

        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        log.info("测试切入{}成功,是否包含注解:"+method.isAnnotationPresent(PermissionData.class), "@After");
        log.info("注解中的属性值:"+method.getAnnotation(PermissionData.class).permissionId());

        //log.info("执行方法耗时为:" + (System.currentTimeMillis() - startTime));
    }


    /**
     * <p>环绕增强切入</p>
     * 表达式和注解方式同时满足才会切入
     * @author hkl
     * @date 2022/11/9
     */
    @Around(value = "getMethods() && withAnnotationMethods()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        //业务操作同 @Before 方式

        long startTime = System.currentTimeMillis();
        //方法执行之前动作,等效于@Before
        Object res = point.proceed();
        //方法执行之后动作,等效于@After

        MethodSignature methodSignature = (MethodSignature)point.getSignature();
        Method method = methodSignature.getMethod();
        log.info("测试切入{}成功,是否包含注解:"+method.isAnnotationPresent(PermissionData.class), "@Around");
        log.info("注解中的属性值:"+method.getAnnotation(PermissionData.class).permissionId());

        log.info("执行方法耗时(毫秒)为:" + (System.currentTimeMillis() - startTime));
        return res;
    }


}

在切面中指定路径的方法上增加注解
在这里插入图片描述
swagger中调用该接口,日志如下图所示:
在这里插入图片描述

在这里插入图片描述

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot提供了一种称为切面编程(Aspect-Oriented Programming,AOP)的方式来实现横向关注点的分离模块化。AOP允许开发者通过定义切面来将与业务逻辑无直接关联的横切关注点(如日志记录、事务管理等)从主要业务逻辑中分离出来。 在Spring Boot中,使用AOP的关键是定义切面和连接点。切面(Aspect)是一个包含了通知(Advice)和切点(Pointcut)的类。通知定义了在连接点执行之前、之后或周围执行的代码逻辑,而切点则定义了哪些连接点应该被应用通知。 要使用AOP,首先需要在Spring Boot应用中添加`spring-boot-starter-aop`依赖。然后,可以通过创建一个带有`@Aspect`注解的类来定义切面。在该类中,可以使用`@Before`、`@After`、`@Around`等注解来定义不同类型的通知,并通过`@Pointcut`注解来定义切点表达式。 以下是一个简单的示例: ```java import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class LoggingAspect { @Before("execution(public * com.example.demo.MyService.*(..))") public void beforeMethod() { System.out.println("Before method execution"); } @After("execution(public * com.example.demo.MyService.*(..))") public void afterMethod() { System.out.println("After method execution"); } @Around("execution(public * com.example.demo.MyService.*(..))") public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before method execution"); Object result = joinPoint.proceed(); System.out.println("After method execution"); return result; } } ``` 在上述示例中,`@Before`、`@After`和`@Around`注解分别定义了三种通知类型的方法,并使用`@Pointcut`注解定义了切点表达式。这里的切点表达式表示对`com.example.demo.MyService`类中所有公共方法的调用进行通知。 通过使用AOP,可以在不修改原始业务逻辑的情况下,将横切关注点应用到应用程序中的特定连接点上,从而实现更好的代码复用和可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值