Spring Boot 2.X(八):Spring AOP 实现简单的日志切面
AOP
1.什么是 AOP ?
AOP 的全称为 Aspect Oriented Programming,译为面向切面编程,是通过预编译方式和运行期动态代理实现核心业务逻辑之外的横切行为的统一维护的一种技术。AOP 是面向对象编程(OOP)的补充和扩展。
利用 AOP 可以对业务逻辑各部分进行隔离,从而达到降低模块之间的耦合度,并将那些影响多个类的公共行为封装到一个可重用模块,从而到达提高程序的复用性,同时提高了开发效率,提高了系统的可操作性和可维护性。
2.为什么要用 AOP ?
在实际的 Web 项目开发中,我们常常需要对各个层面实现日志记录,性能统计,安全控制,事务处理,异常处理等等功能。如果我们对每个层面的每个类都独立编写这部分代码,那久而久之代码将变得很难维护,所以我们把这些功能从业务逻辑代码中分离出来,聚合在一起维护,而且我们能灵活地选择何处需要使用这些代码。
3.AOP 的核心概念
名词 | 概念 | 理解 |
---|---|---|
通知(Advice) | 拦截到连接点之后所要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类 | 我们要实现的功能,如日志记录,性能统计,安全控制,事务处理,异常处理等等,说明什么时候要干什么 |
连接点(Joint Point) | 被拦截到的点,如被拦截的方法、对类成员的访问以及异常处理程序块的执行等等,自身还能嵌套其他的 Joint Point | Spring 允许你用通知的地方,方法有关的前前后后(包括抛出异常) |
切入点(Pointcut) | 对连接点进行拦截的定义 | 指定通知到哪个方法,说明在哪干 |
切面(Aspect) | 切面类的定义,里面包含了切入点(Pointcut)和通知(Advice)的定义 | 切面就是通知和切入点的结合 |
目标对象(Target Object) | 切入点选择的对象,也就是需要被通知的对象;由于 Spring AOP 通过代理模式实现,所以该对象永远是被代理对象 | 业务逻辑本身 |
织入(Weaving) | 把切面应用到目标对象从而创建出 AOP 代理对象的过程。织入可以在编译期、类装载期、运行期进行,而 Spring 采用在运行期完成 | 切点定义了哪些连接点会得到通知 |
引入(Introduction ) | 可以在运行期为类动态添加方法和字段,Spring 允许引入新的接口到所有目标对象 | 引入就是在一个接口/类的基础上引入新的接口增强功能 |
AOP 代理(AOP Proxy ) | Spring AOP 可以使用 JDK 动态代理或者 CGLIB 代理,前者基于接口,后者基于类 | 通过代理来对目标对象应用切面 |
Spring AOP
1.简介
AOP 是 Spring 框架中的一个核心内容。在 Spring 中,AOP 代理可以用 JDK 动态代理或者 CGLIB 代理 CglibAopProxy 实现。Spring 中 AOP 代理由 Spring 的 IOC 容器负责生成和管理,其依赖关系也由 IOC 容器负责管理。
2.相关注解
注解 | 说明 |
---|---|
@Aspect | 将一个 java 类定义为切面类 |
@Pointcut | 定义一个切入点,可以是一个规则表达式,比如下例中某个 package 下的所有函数,也可以是一个注解等 |
@Before | 在切入点开始处切入内容 |
@After | 在切入点结尾处切入内容 |
@AfterReturning | 在切入点 return 内容之后处理逻辑 |
@Around | 在切入点前后切入内容,并自己控制何时执行切入点自身的内容 |
@AfterThrowing | 用来处理当切入内容部分抛出异常之后的处理逻辑 |
@Order(100) | AOP 切面执行顺序, @Before 数值越小越先执行,@After 和 @AfterReturning 数值越大越先执行 |
其中 @Before、@After、@AfterReturning、@Around、@AfterThrowing 都属于通知(Advice)。
利用 AOP 实现 Web 日志处理
1.构建项目
2.添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 热部署模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
</dependency>
<!-- Spring AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
3.Web 日志注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ControllerWebLog {
String name();//所调用接口的名称
boolean intoDb() default false;//标识该条操作日志是否需要持久化存储
}
4.实现切面逻辑
@Aspect
@Component
@Order(100)
public class WebLogAspect {
private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
private ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<Map<String, Object>>();
/**
* 横切点
*/
@Pointcut("execution(public * cn.zwqh.springboot.controller..*.*(..))")
public void webLog() {
}
/**
* 接收请求,并记录数据
* @param joinPoint
* @param controllerWebLog
*/
@Before(value = "webLog()&& @annotation(controllerWebLog)")
public void doBefore(JoinPoint joinPoint, ControllerWebLog controllerWebLog) {
// 接收到请求
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
// 记录请求内容,threadInfo存储所有内容
Map<String, Object> threadInfo = new HashMap<>();
logger.info("URL : " + request.getRequestURL());
threadInfo.put("url", request.getRequestURL());
logger.info("URI : " + request.getRequestURI());
threadInfo.put("uri", request.getRequestURI());
logger.info("HTTP_METHOD : " + request.getMethod());
threadInfo.put("httpMethod", request.getMethod());
logger.info("REMOTE_ADDR : " + request.getRemoteAddr());
threadInfo.put("ip", request.getRemoteAddr());
logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "."
+ joinPoint.getSignature().getName());
threadInfo.put("classMethod",
joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
threadInfo.put("args", Arrays.toString(joinPoint.getArgs()));
logger.info("USER_AGENT"+request.getHeader("User-Agent"));
threadInfo.put("userAgent", request.getHeader("User-Agent"));
logger.info("执行方法:" + controllerWebLog.name());
threadInfo.put("methodName", controllerWebLog.name());
threadLocal.set(threadInfo);
}
/**
* 执行成功后处理
* @param controllerWebLog
* @param ret
* @throws Throwable
*/
@AfterReturning(value = "webLog()&& @annotation(controllerWebLog)", returning = "ret")
public void doAfterReturning(ControllerWebLog controllerWebLog, Object ret) throws Throwable {
Map<String, Object> threadInfo = threadLocal.get();
threadInfo.put("result", ret);
if (controllerWebLog.intoDb()) {
//插入数据库操作
//insertResult(threadInfo);
}
// 处理完请求,返回内容
logger.info("RESPONSE : " + ret);
}
/**
* 获取执行时间
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around(value = "webLog()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object ob = proceedingJoinPoint.proceed();
Map<String, Object> threadInfo = threadLocal.get();
Long takeTime = System.currentTimeMillis() - startTime;
threadInfo.put("takeTime", takeTime);
logger.info("耗时:" + takeTime);
threadLocal.set(threadInfo);
return ob;
}
/**
* 异常处理
* @param throwable
*/
@AfterThrowing(value = "webLog()", throwing = "throwable")
public void doAfterThrowing(Throwable throwable) {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
// 异常信息
logger.error("{}接口调用异常,异常信息{}", request.getRequestURI(), throwable);
}
}
5.测试接口
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/getOne")
@ControllerWebLog(name = "查询", intoDb = true)
public String getOne(Long id, String name) {
return "1234";
}
}
6.运行测试
浏览器请求:http://127.0.0.1:8080/user/getOne?id=1&name=zwqh ,可以看到后台日志输出:
小结
日志记录只是一个简单的示例,而 Spring AOP 的应用让整个系统变的更加有条不紊,在其他场景应用也很强大。
它帮助我们降低模块间耦合度,提高程序复用性,提高开发效率,提高系统可做性和可维护性。
示例代码
非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处.
原文标题:Spring Boot 2.X(八):Spring AOP 实现简单的日志切面
原文地址:https://www.zwqh.top/article/info/14
如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...
Spring Boot 2.X(八):Spring AOP 实现简单的日志切面的更多相关文章
- Spring Boot 表单验证、AOP统一处理请求日志、单元测试
一.使用@Valid表单验证 于实体类中添加@Min等注解 @Entity public class Girl { @Id @GeneratedValue private Integer id; pr ...
- spring boot / cloud (十八) 使用docker快速搭建本地环境
spring boot / cloud (十八) 使用docker快速搭建本地环境 在平时的开发中工作中,环境的搭建其实一直都是一个很麻烦的事情 特别是现在,系统越来越复杂,所需要连接的一些中间件也越 ...
- Spring Boot 2 (三):Spring Boot 2 相关开源软件
Spring Boot 2 (三):Spring Boot 2 相关开源软件 一.awesome-spring-boot Spring Boot 中文索引,这是一个专门收集 Spring Boot 相 ...
- Spring Boot 必须先说说 Spring 框架!
现在 Spring Boot 非常火,各种技术文章,各种付费教程,多如牛毛,可能还有些不知道 Spring Boot 的,那它到底是什么呢?有什么用?今天给大家详细介绍一下. Spring Boot ...
- Spring 5.x 、Spring Boot 2.x 、Spring Cloud 与常用技术栈整合
项目 GitHub 地址:https://github.com/heibaiying/spring-samples-for-all 版本说明: Spring: 5.1.3.RELEASE Spring ...
- [转]Spring Boot——2分钟构建spring web mvc REST风格HelloWorld
Spring Boot——2分钟构建spring web mvc REST风格HelloWorld http://projects.spring.io/spring-boot/ http://spri ...
- Spring Boot(十)Logback和Log4j2集成与日志发展史
一.简介 Java知名的日志有很多,比如:JUL.Log4j.JCL.SLF4J.Logback.Log4j2,那么这些日志框架之间有着怎样的关系?诞生的原因又是解决什么问题?下面一起来看. 1.1 ...
- Spring Boot (五)Spring Data JPA 操作 MySQL 8
一.Spring Data JPA 介绍 JPA(Java Persistence API)Java持久化API,是 Java 持久化的标准规范,Hibernate是持久化规范的技术实现,而Sprin ...
- (转)Spring Boot 2 (七):Spring Boot 如何解决项目启动时初始化资源
http://www.ityouknow.com/springboot/2018/05/03/spring-boot-commandLineRunner.html 在我们实际工作中,总会遇到这样需求, ...
随机推荐
- CVE-2014-6271 Shellshock 破壳漏洞 复现
补坑. 什么是shellshock ShellShock是一个BashShell漏洞(据说不仅仅是Bash,其他shell也可能有这个漏洞). 一般情况来说,系统里面的Shell是有严格的权限控制的, ...
- Redis真的那么好用吗
Redis是什么 Redis是一个开源的底层使用C语言编写的key-value存储数据库.可用于缓存.事件发布订阅.高速队列等场景.而且支持丰富的数据类型:string(字符串).hash(哈希).l ...
- 安装Harbor管理镜像服务
Harbor是什么? 还记得Docker Registry么?它是Docker官方提供的镜像仓库,简单易用,一键就可以部署.使用. 虽然看起来不错,但是Registry有些问题需要解决: 没有图形界面 ...
- Android 微信支付&支付宝支付
由于项目需求,加入这2个功能记录一些需要注意的地方 一.微信支付 微信支付在2016年4月份左右稍微调整了一下支付过程,但是文档却没怎么更新,这也是百度上为什么那么多开发者都说微信是个大坑. 身为一个 ...
- .Net基础篇_学习笔记_第三天_Convert类型转换
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- charles 镜像工具
本文参考:charles 镜像工具 镜像工具会在你浏览指定网站时,把抓取到的文件克隆一份,并保存在你指定的路径下: 注意:如果你配置是www.aaa.com; 那么只会抓这个域名下的文件,这个域名如果 ...
- jmeter linux压测报错:Error in NonGUIDriver java.lang.IllegalArgumentException: Problem loading XML from:'/home/server/ptest/disk_out.jmx'.
1.linux环境jmeter与win环境编写脚本的jmeter版本不一致,版本改为一致 2.脚本中存在中文,去除中文 3.脚本中存在类似于jp@gc - Active Threads Over Ti ...
- Android 本地化适配:RTL(right-to-left) 适配清单
本文首发自公众号:承香墨影(ID:cxmyDev),欢迎关注. 一. 序 越来越多的公司 App,都开始淘金海外,寻找更多的机会.然而海外市场千差万别,无论是市场还是用户的使用习惯,都有诸多的不同. ...
- 编程必备基础知识|计算机组成原理篇(09):CPU的控制器和运算器
计算机基础方面的知识,对于一些非科班出身的同学来讲,一直是他们心中的痛,而对于科班出身的同学,很多同学在工作之后,也意识到自身所学知识的不足与欠缺,想回头补补基础知识.关于计算机基础的课程很多,内容繁 ...
- XStream实现javabean和xml、json转化
xStream转换XML.Json数据 xStream可以轻易的将javaBean对象和xml相互转换,修改某个特定的属性和节点名称,而且也支持json的转换. maven依赖: 1 <depe ...