前言

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

举些栗子

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

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

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

...

难点分析&解决方案

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

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

开发步骤

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、五大通知注解

尝试一下

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、项目代码

项目结构

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中如果是切面的话,出现如下图标,点击就可以看到所有被切入的方法:

SpringBoot整合aspectj实现面向切面编程(即AOP)的更多相关文章

  1. 依赖注入(DI)有助于应用对象之间的解耦,而面向切面编程(AOP)有助于横切关注点与所影响的对象之间的解耦(转good)

    依赖注入(DI)有助于应用对象之间的解耦,而面向切面编程(AOP)有助于横切关注点与所影响的对象之间的解耦.所谓横切关注点,即影响应用多处的功能,这些功能各个应用模块都需要,但又不是其主要关注点,常见 ...

  2. Spring——面向切面编程(AOP)详解

    声明:本博客仅仅是一个初学者的学习记录.心得总结,其中肯定有许多错误,不具有参考价值,欢迎大佬指正,谢谢!想和我交流.一起学习.一起进步的朋友可以加我微信Liu__66666666 这是简单学习一遍之 ...

  3. Spring(三)面向切面编程(AOP)

    在直系学长曾经的指导下,参考了直系学长的博客(https://www.cnblogs.com/WellHold/p/6655769.html)学习Spring的另一个核心概念--面向切片编程,即AOP ...

  4. Spring 面向切面编程(AOP)

    Spring 系列教程 Spring 框架介绍 Spring 框架模块 Spring开发环境搭建(Eclipse) 创建一个简单的Spring应用 Spring 控制反转容器(Inversion of ...

  5. 面向切面编程(AOP)及其作用

    在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用. 1.面向切面编程(AOP) 面向切面编程(AOP)就是对软件系统不同关注点的分离,开发者通过拦截方法调用并在方法调用前后添加辅助代码. ...

  6. C# 中使用面向切面编程(AOP)中实践代码整洁

    1. 前言 最近在看<架构整洁之道>一书,书中反复提到了面向对象编程的 SOLID 原则(在作者的前一本书<代码整洁之道>也是被大力阐释),而面向切面编程(Aop)作为面向对象 ...

  7. C# 中使用面向切面编程(AOP)中实践代码整洁(转)

    出处:https://www.cnblogs.com/chenug/p/9848852.html 1. 前言 最近在看<架构整洁之道>一书,书中反复提到了面向对象编程的 SOLID 原则( ...

  8. (转存)面向切面编程(AOP)的理解

    面向切面编程(AOP)的理解 标签: aop编程 2010-06-14 20:17 45894人阅读 评论(11) 收藏 举报  分类: Spring(9)  在传统的编写业务逻辑处理代码时,我们通常 ...

  9. 面向切面编程 (AOP )

    什么是面向切面编程? 面向切面编程就是(AOP --- aspect-oriented programming), 在百科上说: 面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一 ...

  10. 解耦与分离 —— 面向切面编程(AOP)

    家里的电表总结起来有两大特性: 电视机需要(电量管理),空调需要(电量管理),热水器也需要电量管理,即一组对象都需要某一功能特性: 电视机根据信号输出画面,空调吹出冷风,热水器将水加热,这些业务功能的 ...

随机推荐

  1. Cloudquery的学习安装与使用

    Cloudquery的学习安装与使用 下载 官方下载地址: https://www.cloudquery.club/download https://pan.baidu.com/s/1a7XOrnMU ...

  2. [转帖]Linux运维常用150个命令

    Linux运维常用150个命令 转载自:www.cnblogs.com/bananaaa/p/7774467.html 命令 功能说明 线上查询及帮助命令(2个) man 查看命令帮助,命令的词典,更 ...

  3. [转帖]LTP测试

    https://zhuanlan.zhihu.com/p/381538099   整体测试 直接运行runltp命令,将测试/opt/ltp/scenario_groups/default文件中所有的 ...

  4. Linux上面批量更新SQLSERVER SQL文本文件的办法

    1. 今天同事让帮忙更新几个SQL文件.. 本着自己虽然low 但是不能太low的想法, 简单写一个 shell 脚本来执行. 2. 因为我的linux 里面都安装了 sqlcmd 的工具 所以办法就 ...

  5. 【字符串,哈希】【Yandex】Yandex7736

    2023.6.30 Problem Link 定义一个串 \(S\) 是好的,当且仅当 \(S\) 可以不断消去相邻两个相同字符直至消空.给定一个长为 \(n\) 的字符串 \(s\),求有多少个有序 ...

  6. P7900 [COCI2006-2007#2] SJECIŠTA_题解

    [COCI2006-2007#2] SJECIŠTA_题解 rt 我们来看一下题目描述 考虑一个有 \(n\) 个顶点的凸多边形,且这个多边形没有任何三个(或以上) 的对角线交于一点. 这句话什么意思 ...

  7. C# 中判断List集合是否为空

    判断List集合是否为空,可以使用Count和Any,下面是其使用场景(别人总结)

  8. 从零开始配置 vim(10)——快捷键配置

    之前我们对neovim 进行了基础的配置,这篇主要介绍我比较常用的快捷键配置.到这篇开始我们的配置已经可以为两个大的模块--基础配置和快捷键配置.我们的目录也应该按照模块来进行组织.在正式配置之前让我 ...

  9. PGL图学习之图游走类metapath2vec模型[系列五]

    PGL图学习之图游走类metapath2vec模型[系列五] 本项目链接:https://aistudio.baidu.com/aistudio/projectdetail/5009827?contr ...

  10. 5.1 内存CRC32完整性检测

    CRC校验技术是用于检测数据传输或存储过程中是否出现了错误的一种方法,校验算法可以通过计算应用与数据的循环冗余校验(CRC)检验值来检测任何数据损坏.通过运用本校验技术我们可以实现对特定内存区域以及磁 ...