前言

aop面向切面编程,是编程中一个很重要的思想本篇文章主要介绍的是SpringBoot切面Aop的使用和案例

什么是aop

AOP(Aspect OrientedProgramming):面向切面编程,面向切面编程(也叫面向方面编程),是目前软件开发中的一个热点,也是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

使用场景

利用AOP可以对我们边缘业务进行隔离,降低无关业务逻辑耦合性。提高程序的可重用性,同时提高了开发的效率。一般用于日志记录,性能统计,安全控制,权限管理,事务处理,异常处理,资源池管理。使用场景

为什么需要面向切面编程

面向对象编程(OOP)的好处是显而易见的,缺点也同样明显。当需要为多个不具有继承关系的对象添加一个公共的方法的时候,例如日志记录、性能监控等,如果采用面向对象编程的方法,需要在每个对象里面都添加相同的方法,这样就产生了较大的重复工作量和大量的重复代码,不利于维护。面向切面编程(AOP)是面向对象编程的补充,简单来说就是统一处理某一“切面”的问题的编程思想。如果使用AOP的方式进行日志的记录和处理,所有的日志代码都集中于一处,不需要再每个方法里面都去添加,极大减少了重复代码。

技术要点

  1. 通知(Advice)包含了需要用于多个应用对象的横切行为,完全听不懂,没关系,通俗一点说就是定义了“什么时候”和“做什么”。

  2. 连接点(Join Point)是程序执行过程中能够应用通知的所有点。

  3. 切点(Poincut)是定义了在“什么地方”进行切入,哪些连接点会得到通知。显然,切点一定是连接点。

  4. 切面(Aspect)是通知和切点的结合。通知和切点共同定义了切面的全部内容——是什么,何时,何地完成功能。

  5. 引入(Introduction)允许我们向现有的类中添加新方法或者属性。

  6. 织入(Weaving)是把切面应用到目标对象并创建新的代理对象的过程,分为编译期织入、类加载期织入和运行期织入。

整合使用

导入依赖

在springboot中使用aop要导aop依赖

 <!--aop 切面-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

注意这里版本依赖于spring-boot-start-parent父pom中的spring-boot-dependencies

编写拦截的bean

这里我们定义一个controller用于拦截所有请求的记录

@RestController
public class AopController { @RequestMapping("/hello")
public String sayHello(){
System.out.println("hello");
return "hello";
}
}

定义切面

SpringBoot在使用切面的时候采用@Aspect注解对POJO进行标注,该注解表明该类不仅仅是一个POJO,还是一个切面容器

定义切点

切点是通过@Pointcut注解和切点表达式定义的。

@Pointcut注解可以在一个切面内定义可重用的切点。

由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且实际中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。如图是execution表达式的语法:

execution表示在方法执行的时候触发。以“”开头,表明方法返回值类型为任意类型。然后是全限定的类名和方法名,“”可以表示任意类和任意方法。对于方法参数列表,可以使用“..”表示参数为任意类型。如果需要多个表达式,可以使用“&&”、“||”和“!”完成与、或、非的操作。

定义通知

通知有五种类型,分别是:

  1. 前置通知(@Before):在目标方法调用之前调用通知
  2. 后置通知(@After):在目标方法完成之后调用通知
  3. 环绕通知(@Around):在被通知的方法调用之前和调用之后执行自定义的方法
  4. 返回通知(@AfterReturning):在目标方法成功执行之后调用通知
  5. 异常通知(@AfterThrowing):在目标方法抛出异常之后调用通知

代码中定义了三种类型的通知,使用@Before注解标识前置通知,打印“beforeAdvice...”,使用@After注解标识后置通知,打印“AfterAdvice...”,使用@Around注解标识环绕通知,在方法执行前和执行之后分别打印“before”和“after”。这样一个切面就定义好了,代码如下:

@Aspect
@Component
public class AopAdvice { @Pointcut("execution (* com.shangguan.aop.controller.*.*(..))")
public void test() { } @Before("test()")
public void beforeAdvice() {
System.out.println("beforeAdvice...");
} @After("test()")
public void afterAdvice() {
System.out.println("afterAdvice...");
} @Around("test()")
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("before");
try {
proceedingJoinPoint.proceed();
} catch (Throwable t) {
t.printStackTrace();
}
System.out.println("after");
} }

运行结果

案例场景

这里我们通过一个日志记录场景来完整的使用Aop切面业务层只需关心代码逻辑实现而不用关心请求参数和响应参数的日志记录

那么首先我们需要自定义一个全局日志记录的切面类GlobalLogAspect

然后在该类添加@Aspect注解,然后在定义一个公共的切入点(Pointcut),指向需要处理的包,然后在定义一个前置通知(添加@Before注解),后置通知(添加@AfterReturning)和环绕通知(添加@Around)方法实现即可

日志信息类

package cn.soboys.core;

import lombok.Data;

/**
* @author kenx
* @version 1.0
* @date 2021/6/18 18:48
* 日志信息
*/
@Data
public class LogSubject {
/**
* 操作描述
*/
private String description; /**
* 操作用户
*/
private String username; /**
* 操作时间
*/
private String startTime; /**
* 消耗时间
*/
private String spendTime; /**
* URL
*/
private String url; /**
* 请求类型
*/
private String method; /**
* IP地址
*/
private String ip; /**
* 请求参数
*/
private Object parameter; /**
* 请求返回的结果
*/
private Object result; /**
* 城市
*/
private String city; /**
* 请求设备信息
*/
private String device; }

全局日志拦截

package cn.soboys.core;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Method; /**
* @author kenx
* @version 1.0
* @date 2021/6/18 14:52
* 切面
*/
public class BaseAspectSupport {
public Method resolveMethod(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature)point.getSignature();
Class<?> targetClass = point.getTarget().getClass(); Method method = getDeclaredMethod(targetClass, signature.getName(),
signature.getMethod().getParameterTypes());
if (method == null) {
throw new IllegalStateException("无法解析目标方法: " + signature.getMethod().getName());
}
return method;
} private Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
try {
return clazz.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
return getDeclaredMethod(superClass, name, parameterTypes);
}
}
return null;
}
}

GlobalLogAspect

package cn.soboys.core;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.json.JSONUtil;
import cn.soboys.core.utils.HttpContextUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam; import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map; /**
* @author kenx
* @version 1.0
* @date 2021/6/18 15:22
* 全局日志记录器
*/
@Slf4j
@Aspect
@Component
public class GlobalLogAspect extends BaseAspectSupport {
/**
* 定义切面Pointcut
*/
@Pointcut("execution(public * cn.soboys.mallapi.controller.*.*(..))")
public void log() { } /**
* 环绕通知
*
* @param joinPoint
* @return
*/
@Around("log()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { LogSubject logSubject = new LogSubject();
//记录时间定时器
TimeInterval timer = DateUtil.timer(true);
//执行结果
Object result = joinPoint.proceed();
logSubject.setResult(result);
//执行消耗时间
String endTime = timer.intervalPretty();
logSubject.setSpendTime(endTime);
//执行参数
Method method = resolveMethod(joinPoint);
logSubject.setParameter(getParameter(method, joinPoint.getArgs())); HttpServletRequest request = HttpContextUtil.getRequest();
// 接口请求时间
logSubject.setStartTime(DateUtil.now());
//请求链接
logSubject.setUrl(request.getRequestURL().toString());
//请求方法GET,POST等
logSubject.setMethod(request.getMethod());
//请求设备信息
logSubject.setDevice(HttpContextUtil.getDevice());
//请求地址
logSubject.setIp(HttpContextUtil.getIpAddr());
//接口描述
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
logSubject.setDescription(apiOperation.value());
} String a = JSONUtil.toJsonPrettyStr(logSubject);
log.info(a);
return result; } /**
* 根据方法和传入的参数获取请求参数
*/
private Object getParameter(Method method, Object[] args) {
List<Object> argList = new ArrayList<>();
Parameter[] parameters = method.getParameters();
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < parameters.length; i++) {
//将RequestBody注解修饰的参数作为请求参数
RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
//将RequestParam注解修饰的参数作为请求参数
RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
String key = parameters[i].getName();
if (requestBody != null) {
argList.add(args[i]);
} else if (requestParam != null) {
map.put(key, args[i]);
} else {
map.put(key, args[i]);
}
}
if (map.size() > 0) {
argList.add(map);
}
if (argList.size() == 0) {
return null;
} else if (argList.size() == 1) {
return argList.get(0);
} else {
return argList;
}
}
}

SpringBoot Aop 详解和多种使用场景的更多相关文章

  1. Spring全家桶——SpringBoot之AOP详解

    Spring全家桶--SpringBoot之AOP详解 面向方面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP). OOP中模块化的关键单元是类,而在AOP中,模块化单元是方 ...

  2. 【转载】Spring AOP详解 、 JDK动态代理、CGLib动态代理

    Spring AOP详解 . JDK动态代理.CGLib动态代理  原文地址:https://www.cnblogs.com/kukudelaomao/p/5897893.html AOP是Aspec ...

  3. [Spring学习笔记 5 ] Spring AOP 详解1

    知识点回顾:一.IOC容器---DI依赖注入:setter注入(属性注入)/构造子注入/字段注入(注解 )/接口注入 out Spring IOC容器的使用: A.完全使用XML文件来配置容器所要管理 ...

  4. SpringBoot @ConfigurationProperties详解

    文章目录 简介 添加依赖关系 一个简单的例子 属性嵌套 @ConfigurationProperties和@Bean 属性验证 属性转换 自定义Converter SpringBoot @Config ...

  5. Spring4 AOP详解

    Spring4 AOP详解 第一章Spring 快速入门并没有对Spring4 的 AOP 做太多的描述,是因为AOP切面编程概念不好理解.所以这章主要从三个方面详解AOP:AOP简介(了解),基于注 ...

  6. springboot配置详解

    springboot配置详解 Author:SimpleWu properteis文件属性参考大全 springboot默认加载配置 SpringBoot使用两种全局的配置文件,全局配置文件可以对一些 ...

  7. AOP 详解

    1. 需求:统计方法执行的性能情况(来源:<精通Spring 4.x>) // 性能监视类 PerformanceMonitor package com.noodles.proxy; pu ...

  8. Spring AOP详解(转载)所需要的包

    上一篇文章中,<Spring Aop详解(转载)>里的代码都可以运行,只是包比较多,中间缺少了几个相应的包,根据报错,几经百度搜索,终于补全了所有包. 截图如下: 在主测试类里面,有人怀疑 ...

  9. Spring AOP详解及简单应用

    Spring AOP详解   一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...

随机推荐

  1. docker基本底层原理

    docker是怎么工作的 Docker是一个Client-Server结构的系统,Docker的守护进程运行在主机上,通过Socket客户端进行访问 DockerServer接收到DockerClie ...

  2. 孟老板 Paging3 (二) 结合Room

    BaseAdapter系列 ListAdapter系列 Paging3 (一) 入门 Paging3 (二) 结合 Room Paging3 (二)  结合Room Paging 数据源不开放, 无法 ...

  3. 基于Ubuntu下以Docker方式gitlab软件的部署

    基于Ubuntu下以Docker方式gitlab软件的部署 目录 基于Ubuntu下以Docker方式gitlab软件的部署 1.安装Docker Compose 1.1 下载curl 1.2 安装c ...

  4. 『心善渊』Selenium3.0基础 — 10、使用Seleniun定位页面元素归纳总结(超详细)

    目录 1.Selenium中8种基本元素定位方式 (1)单数形式 (2)复数形式 2.By类定位的8种定位方式 (1)单数形式 (2)复数形式 3.XPath定位总结 (1)基础定位语法 (2)属性定 ...

  5. 20201123 《python程序设计》实验四报告

    20201123 2020-2021-2 <python程序设计>实验三报告 课程:<Python程序设计>班级:2011姓名:晏鹏捷学号:20201123实验教师:王志强实验 ...

  6. 最新Unity 与Android 交互通信(基于Unity 2019.4 和 Android Studio 4.1.1)

    原文章链接:https://blog.csdn.net/woshihaizeiwang/article/details/115395519 CLSays:网上找了一圈,真的是很多都不能用,要么太老,要 ...

  7. Centos7.5使用SSH密钥登录

    12.1.查看操作系统版本 # cat /etc/redhat-release CentOS Linux release 7.5.1804 (Core) 12.2.在服务器端创建密钥 # ssh-ke ...

  8. 20 批量ping主机

    #!/bin/bash IP_LIST="`cat /root/ip.txt`" ip_dir="/root/ip_dir" [ ! -d ${ip_dir} ...

  9. 13、linux中用户和用户组

    linux是多用户多进程的系统: 每个文件和进程都需要应对一个用户和用户组: linux系统通过uid和gid来识别用户和组的: 一个用户必须要有唯一的uid和一个主组来识别身份,不同的用户可以使用同 ...

  10. oracle :如何测试数据库安装是否成功

    要测试数据安装是否成功,可按顺序执行以下两个步骤: 测试步骤 1:  请执行操作系统级的命令: tnsping orcl (如果出现[TNS-03505:无法解析名称]的提示错误: 那就改为tnspi ...