美文网首页@IT·互联网
SpringBoot整合aspectj实现面向切面编程(即AOP

SpringBoot整合aspectj实现面向切面编程(即AOP

作者: sum墨 | 来源:发表于2024-04-28 09:43 被阅读0次

    前言

    “面向切面编程”,这样的名字并不是非常容易理解,且容易产生一些误导。但在实际业务中,AOP有着广泛的用途,比如日志记录,性能统计,安全控制,事务处理,异常处理等等。

    举些栗子

    场景1、现有一个系统,已经运行了几个月,项目经理想要统计每个接口的耗时情况,用来分析系统性能,进而重构代码进行优化;

    场景2、有一个管理后台,存在一些修改、删除的敏感操作,产品经理为了减少风险要求每一次的修改、删除操作都需要记录在数据库中,不仅要记录操作人,还要记录入参出参

    场景3、由于系统在运行中,不可避免的会出现一些异常,但是一出现异常就会有很多报错日志,但这些日志是不能返回给前端的,需要统一处理这些异常,返回前端的只能是“系统出现一些小问题”这样的文案;

    ...

    难点分析&解决方案

    上面的场景都是真实存在的需求,但是如果不能统一处理的话,基本都是一改一大片,除了对业务代码有很强的侵入性,而且难以保证不出问题。

    所以为了解决这种需求,AspectJ框架应运而生。不过SpringBoot官方也推出了 Spring AOP, 具体对比我就不赘述了,详细对比可以参https://www.jianshu.com/p/872d3dbdc2ca

    image.png

    开发步骤

    1、引入依赖

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.5</version>
    </dependency>
    

    2、创建切面类并加上@Component注解

    这一步相当于把切面类的管理权交给了Spring容器,让Spring容器负责该对象的创建与销毁,我们负责使用就行。

    3、指定@Pointcut,可以是方法也可以是注解

    这里说一下Pointcut execution规则

    • 任意公共方法的执行:
      execution(public * *(..))
      public可以省略, 第一个* 代表方法的任意返回值 第二个参数代表任意包+类+方法 (..)任意参数

    • 任何一个以“set”开始的方法的执行:
      execution(* set*(..))

    • UserService接口的任意方法:
      execution(* com.example.springbootaop.UserService.*(..))

    • 定义在com.coffee.service包里的任意方法的执行:execution(* com.example.springbootaop.*.*(..))
      第一个 .* 代表任意类, 第二个 .* 代表人以方法

    • 定义在service包和所有子包里的任意类的任意方法的执行:
      execution(* com.example.springbootaop.service..*.*(..))
      ..* 代表任意包或者子包

    • 定义在com.example.springbootaop包和所有子包里的UserService类的任意方法的执行:
      execution(* com.example.springbootaop..UserService.*(..))")

    4、五大通知注解

    image.png

    尝试一下

    1、配置文件

    SpringBoot项目pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.7.1</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>SpringBoot-aop</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>SpringBoot-aop</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>1.8</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.5</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    

    2、项目代码

    项目结构

    image.png

    SpringBootAopApplication.java

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class SpringBootAopApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringBootAopApplication.class, args);
        }
    
    }
    

    TestController.java

    import com.example.springbootaop.log.Log;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.Map;
    
    @RestController
    @RequestMapping("/aop")
    public class TestController {
    
        @GetMapping("/a")
        @Log(desc = "接口a的描述")
        public String a() {
            return "我是接口a";
        }
    
        @GetMapping("/b")
        @Log(desc = "接口b的描述")
        public String b(String param1) {
            System.out.println("打印参数:" + param1);
            return "我是接口b";
        }
    
        @PostMapping("/c")
        @Log(desc = "接口c的描述")
        public String c(Map<String, String> stringMap) {
            System.out.println("打印参数:" + stringMap);
            return "我是接口c";
        }
    }
    
    

    ControllerLogAspect.java

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.annotation.Order;
    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;
    
    @Aspect
    @Component
    @Order(1)
    public class ControllerLogAspect {
        private static final Logger LOG = LoggerFactory.getLogger(ControllerLogAspect.class);
    
        @Pointcut("execution(* com.example.springbootaop.controller..*.*(..))")
        private void controllerMethod() {
        }
    
        @Around("controllerMethod()")
        public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
            RequestAttributes ra = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes sra = (ServletRequestAttributes) ra;
            HttpServletRequest request = sra.getRequest();
            // 获取请求相关信息
            String url = request.getRequestURL().toString();
            String method = request.getMethod();
            String uri = request.getRequestURI();
            String params = request.getQueryString();
            LOG.info("url:[{}];method:[{}];uri:[{}];params:[{}]", url, method, uri, params);
            return joinPoint.proceed();
        }
    }
    
    

    Log.java

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Log {
    
        String desc() default "";
    }
    
    

    LogAspect.java

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    
    @Aspect
    @Component
    @Order(2)
    public class LogAspect {
    
        private static final Logger LOG = LoggerFactory.getLogger(LogAspect.class);
    
        @Pointcut("@annotation(com.example.springbootaop.log.Log)")
        private void pointCut() {
    
        }
    
        @Around("pointCut()")
        public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            //获取注解
            Log logAnnotation = method.getAnnotation(Log.class);
            LOG.info("当期方法的注解为:[{}]", logAnnotation.desc());
            return joinPoint.proceed();
        }
    }
    
    

    3、项目总结

    上面的项目使用了两种切面

    (1)ControllerLogAspect

    ControllerLogAspect会扫描com.example.springbootaop.controller包下所有的方法,这种方式可以用在性能统计、事务处理等场景。

    (2)Log注解 + LogAspect

    这种方式扫描的不再是某个包,而是某个注解,Pointcut代码如下

    @Pointcut("@annotation(com.example.springbootaop.log.Log)")
        private void pointCut() {
        }
    

    配合注解一起使用,更加清晰且灵活,每一个方法都可以配置自己的信息,这种一般会用在日志记录、异常处理等场景。

    在idea中如果是切面的话,出现如下图标,点击就可以看到所有被切入的方法:


    image.png

    文末小彩蛋,自己花一个星期做的小网站,放出来给大家看看,网址如下:http://47.120.49.119:8080

    相关文章

      网友评论

        本文标题:SpringBoot整合aspectj实现面向切面编程(即AOP

        本文链接:https://www.haomeiwen.com/subject/tkezxjtx.html