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

前言

“面向切面编程”,这样的名字并不是非常容易理解,且容易产生一些误导。但在实际业务中,AOP有着广泛的用途,比如日志记录,性能统计,安全控制,事务处理,异常处理等等。
举些栗子
统计每个接口的耗时
记录操作人,还要记录入参出参
统一处理这些异常

难点分析&解决方案

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

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

开发步骤

1、引入依赖

<dependency>

&lt;groupId&gt;org.aspectj&lt;/groupId&gt;<br/>
&lt;artifactId&gt;aspectjweaver&lt;/artifactId&gt;<br/>
&lt;version&gt;1.9.5&lt;/version&gt;<br/>

&lt;/dependency&gt;

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

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

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

这里说一下Pointcut execution规则

execution(public * (..))execution( set(..))execution( com.example.springbootaop.UserService.(..))execution( com.example.springbootaop..(..))execution(* com.example.springbootaop.service...(..))execution(* com.example.springbootaop..UserService.*(..))“)

4、五大通知注解

尝试一下

1、配置文件

SpringBoot项目pom.xml

&lt;?xml version=”1.0“ encoding=”UTF-8“?&gt;
&lt;project xmlns=”http://maven.apache.org/POM/4.0.0&#34; xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance&#34;

     xsi:schemaLocation=&#34;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&#34;&gt;<br/>
&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;<br/>
&lt;parent&gt;<br/>
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br/>
    &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;<br/>
    &lt;version&gt;2.7.1&lt;/version&gt;<br/>
    &lt;relativePath/&gt; &lt;!-- lookup parent from repository --&gt;<br/>
&lt;/parent&gt;<br/>
&lt;groupId&gt;com.example&lt;/groupId&gt;<br/>
&lt;artifactId&gt;SpringBoot-aop&lt;/artifactId&gt;<br/>
&lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;<br/>
&lt;name&gt;SpringBoot-aop&lt;/name&gt;<br/>
&lt;description&gt;Demo project for Spring Boot&lt;/description&gt;<br/>
&lt;properties&gt;<br/>
    &lt;java.version&gt;1.8&lt;/java.version&gt;<br/>
&lt;/properties&gt;<br/>
&lt;dependencies&gt;<br/>
    &lt;dependency&gt;<br/>
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br/>
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;<br/>
    &lt;/dependency&gt;

&lt;dependency&gt;

        &lt;groupId&gt;org.aspectj&lt;/groupId&gt;<br/>
        &lt;artifactId&gt;aspectjweaver&lt;/artifactId&gt;<br/>
        &lt;version&gt;1.9.5&lt;/version&gt;<br/>
    &lt;/dependency&gt;

&lt;dependency&gt;

        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br/>
        &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;<br/>
        &lt;scope&gt;test&lt;/scope&gt;<br/>
    &lt;/dependency&gt;<br/>
&lt;/dependencies&gt;

&lt;build&gt;

    &lt;plugins&gt;<br/>
        &lt;plugin&gt;<br/>
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br/>
            &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;<br/>
        &lt;/plugin&gt;<br/>
    &lt;/plugins&gt;<br/>
&lt;/build&gt;

&lt;/project&gt;

2、项目代码

项目结构

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);<br/>
}

}

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 = &#34;接口a的描述&#34;)<br/>
public String a() {<br/>
    return &#34;我是接口a&#34;;<br/>
}

@GetMapping(”/b“)

@Log(desc = &#34;接口b的描述&#34;)<br/>
public String b(String param1) {<br/>
    System.out.println(&#34;打印参数:&#34; + param1);<br/>
    return &#34;我是接口b&#34;;<br/>
}

@PostMapping(”/c“)

@Log(desc = &#34;接口c的描述&#34;)<br/>
public String c(Map&lt;String, String&gt; stringMap) {<br/>
    System.out.println(&#34;打印参数:&#34; + stringMap);<br/>
    return &#34;我是接口c&#34;;<br/>
}<br/>

}

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() {<br/>
}

@Around(”controllerMethod()“)

public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {<br/>
    RequestAttributes ra = RequestContextHolder.getRequestAttributes();<br/>
    ServletRequestAttributes sra = (ServletRequestAttributes) ra;<br/>
    HttpServletRequest request = sra.getRequest();<br/>
    // 获取请求相关信息<br/>
    String url = request.getRequestURL().toString();<br/>
    String method = request.getMethod();<br/>
    String uri = request.getRequestURI();<br/>
    String params = request.getQueryString();<br/>
    LOG.info(&#34;url:[{}];method:[{}];uri:[{}];params:[{}]&#34;, url, method, uri, params);<br/>
    return joinPoint.proceed();<br/>
}<br/>

}

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 {<br/>
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();<br/>
    Method method = signature.getMethod();<br/>
    //获取注解<br/>
    Log logAnnotation = method.getAnnotation(Log.class);<br/>
    LOG.info(&#34;当期方法的注解为:[{}]&#34;, logAnnotation.desc());<br/>
    return joinPoint.proceed();<br/>
}<br/>

}

3、项目总结

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

(1)ControllerLogAspect

ControllerLogAspectcom.example.springbootaop.controller

(2)Log注解 + LogAspect

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

@Pointcut(”@annotation(com.example.springbootaop.log.Log)“)

private void pointCut() {<br/>
}<br/>

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

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