SpringBoot开发秘籍 - 利用 AOP 记录日志

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Aspect Oriented Programming 面向切面编程。解耦是程序员编码开发过程中一直追求的。AOP也是为了解耦所诞生。

为什么要用AOP?


答案是解耦


Aspect Oriented Programming 面向切面编程。解耦是程序员编码开发过程中一直追求的。AOP也是为了解耦所诞生。


具体思想是:定义一个切面,在切面的纵向定义处理方法,处理完成之后,回到横向业务流。


AOP 主要是利用代理模式的技术来实现的。具体的代理实现可以参考这篇文章,讲解的非常详细。https://www.cnblogs.com/yanbincn/archive/2012/06/01/2530377.html


通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。


1.png


常用的工作场景


  1. 事务控制


  1. 日志记录


本文没有过度深度学习原理,因为是菜鸟一个,先学会怎么不加班。


必须知道的概念

AOP 的相关术语


通知(Advice)


通知描述了切面要完成的工作以及何时执行。比如我们的日志切面需要记录每个接口调用时长,就需要在接口调用前后分别记录当前时间,再取差值。


  • 前置通知(Before):在目标方法调用前调用通知功能;


  • 后置通知(After):在目标方法调用之后调用通知功能,不关心方法的返回结果;


  • 返回通知(AfterReturning):在目标方法成功执行之后调用通知功能;


  • 异常通知(AfterThrowing):在目标方法抛出异常后调用通知功能;


  • 环绕通知(Around):通知包裹了目标方法,在目标方法调用之前和之后执行自定义的行为。


连接点(JoinPoint)


通知功能被应用的时机。比如接口方法被调用的时候就是日志切面的连接点。


切点(Pointcut)


切点定义了通知功能被应用的范围。比如日志切面的应用范围就是所有接口,即所有 controller 层的接口方法。


切面(Aspect)


切面是通知和切点的结合,定义了何时、何地应用通知功能。


引入(Introduction)


在无需修改现有类的情况下,向现有的类添加新方法或属性。


织入(Weaving)


把切面应用到目标对象并创建新的代理对象的过程。


Spring 中使用注解创建切面


相关注解


  • @Aspect:用于定义切面


  • @Before:通知方法会在目标方法调用之前执行


  • @After:通知方法会在目标方法返回或抛出异常后执行


  • @AfterReturning:通知方法会在目标方法返回后执行


  • @AfterThrowing:通知方法会在目标方法抛出异常后执行


  • @Around:通知方法会将目标方法封装起来


  • @Pointcut:定义切点表达式


切点表达式


指定了通知被应用的范围,表达式格式:


execution(方法修饰符返回类型方法所属的包.类名.方法名称(方法参数)
//com.ninesky.study.tiny.controller包中所有类的public方法都应用切面里的通知execution(public*com.ninesky.study.tiny.controller.*.*(..))
//com.ninesky.study.tiny.service包及其子包下所有类中的所有方法都应用切面里的通知execution(*com.ninesky.study.tiny.service..*.*(..))
//com.ninesky.study.tiny.service.PmsBrandService类中的所有方法都应用切面里的通知execution(*com.macro.ninesky.study.service.PmsBrandService.*(..))


实战应用-利用AOP记录日志


从传统行业转行,以前都没想过打日志埋点,第一份工作,真的应该选择一个好的平台比较重要。


定义日志信息封装


用于封装需要记录的日志信息,包括操作的描述、时间、消耗时间、url、请求参数和返回结果等信息


publicclassWebLog {
/*** 操作描述*/privateStringdescription;
/*** 操作用户*/privateStringusername;
/*** 操作时间*/privateLongstartTime;
/*** 消耗时间*/privateIntegerspendTime;
/*** 根路径*/privateStringbasePath;
/*** URI*/privateStringuri;
/*** URL*/privateStringurl;
/*** 请求类型*/privateStringmethod;
/*** IP地址*/privateStringip;
/*** 请求参数*/privateObjectparameter;
/*** 请求返回的结果*/privateObjectresult;
//省略了getter,setter方法}


定义注解,通过注解减少代码量


@Documented@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public@interfaceOperationLog {
Stringname();//调用接口的名称booleanintoDb() defaultfalse;//该条操作日志是否需要持久化存储}


统一日志处理切面


@Aspect@Component@Order(1)
@Slf4jpublicclassWebLogAspect {
privatestaticfinalLoggercontrolLog=LoggerFactory.getLogger("tmall_control");
@Pointcut("execution(public * com.yee.walnut.*.*.*(..))")
publicvoidwebLog() {
    }
@Before(value="webLog()&& @annotation(OperationLog)")
publicvoiddoBefore(ControllerWebLogcontrollerWebLog) throwsThrowable {
    }
@AfterReturning(value="webLog()&& @annotation(OperationLog)", returning="ret")
publicvoiddoAfterReturning(Objectret, ControllerWebLogcontrollerWebLog) throwsThrowable {
    }
@Around(value="webLog()&& @annotation(OperationLog)")
publicObjectdoAround(ProceedingJoinPointjoinPoint, OperationLogoperationLog) throwsThrowable {
longstartTime=System.currentTimeMillis();
//获取当前请求对象ServletRequestAttributesattributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequestrequest=attributes.getRequest();
//记录请求信息Object[] objs=joinPoint.getArgs();
WebLogwebLog=newWebLog();
Objectresult=joinPoint.proceed();//返回的结果,这是一个进入方法和退出方法的一个分界Signaturesignature=joinPoint.getSignature();
MethodSignaturemethodSignature= (MethodSignature) signature;
Methodmethod=methodSignature.getMethod();
longendTime=System.currentTimeMillis();
StringurlStr=request.getRequestURL().toString();
webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
webLog.setIp(request.getRemoteUser());
webLog.setMethod(request.getMethod());
webLog.setParameter(getParameter(method, joinPoint.getArgs()));
webLog.setResult(JSONUtil.parse(result));
webLog.setSpendTime((int) (endTime-startTime));
webLog.setStartTime(startTime);
webLog.setUri(request.getRequestURI());
webLog.setUrl(request.getRequestURL().toString());
controlLog.info("RequestAndResponse {}", JSONObject.toJSONString(webLog));
//必须有这个返回值。可以这样理解,Around方法之后,不再是被织入的函数返回值,而是Around函数返回值returnresult;
    }
/*** 根据方法和传入的参数获取请求参数*/privateObjectgetParameter(Methodmethod, Object[] args) {
List<Object>argList=newArrayList<>();
Parameter[] parameters=method.getParameters();
for (inti=0; i<parameters.length; i++) {
//将RequestBody注解修饰的参数作为请求参数RequestBodyrequestBody=parameters[i].getAnnotation(RequestBody.class);
if (requestBody!=null) {
argList.add(args[i]);
            }
//将RequestParam注解修饰的参数作为请求参数RequestParamrequestParam=parameters[i].getAnnotation(RequestParam.class);
if (requestParam!=null) {
Map<String, Object>map=newHashMap<>();
Stringkey=parameters[i].getName();
if (!StringUtils.isEmpty(requestParam.value())) {
key=requestParam.value();
                }
map.put(key, args[i]);
argList.add(map);
            } else {
argList.add(args[i]);
            }
        }
if (argList.size() ==0) {
returnnull;
        } elseif (argList.size() ==1) {
returnargList.get(0);
        } else {
returnargList;
        }
    }
}


在方法上加上自定义注解即可


@OperationLog(name="TurnOnOffStrategy")
publicStringdoOperation(GlobalDtoglobalDto, DeviceOperatordeviceOperator) {
}
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
12天前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的医院门诊预约挂号系统
基于Java+Springboot+Vue开发的医院门诊预约挂号系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的门诊预约挂号管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
32 2
基于Java+Springboot+Vue开发的医院门诊预约挂号系统
|
1天前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
8 2
|
16天前
|
机器学习/深度学习 数据采集 JavaScript
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
ADR药品不良反应监测系统是一款智能化工具,用于监测和分析药品不良反应。该系统通过收集和分析病历、处方及实验室数据,快速识别潜在不良反应,提升用药安全性。系统采用Java开发,基于SpringBoot框架,前端使用Vue,具备数据采集、清洗、分析等功能模块,并能生成监测报告辅助医务人员决策。通过集成多种数据源并运用机器学习算法,系统可自动预警药品不良反应,有效减少药害事故,保障公众健康。
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
消息中间件 缓存 监控
81 0
|
26天前
|
运维 NoSQL Java
SpringBoot接入轻量级分布式日志框架GrayLog技术分享
在当今的软件开发环境中,日志管理扮演着至关重要的角色,尤其是在微服务架构下,分布式日志的统一收集、分析和展示成为了开发者和运维人员必须面对的问题。GrayLog作为一个轻量级的分布式日志框架,以其简洁、高效和易部署的特性,逐渐受到广大开发者的青睐。本文将详细介绍如何在SpringBoot项目中接入GrayLog,以实现日志的集中管理和分析。
104 1
|
1月前
|
缓存 Java 应用服务中间件
随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架
【9月更文挑战第6天】随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架。Nginx作为高性能的HTTP反向代理服务器,常用于前端负载均衡,提升应用的可用性和响应速度。本文详细介绍如何通过合理配置实现Spring Boot与Nginx的高效协同工作,包括负载均衡策略、静态资源缓存、数据压缩传输及Spring Boot内部优化(如线程池配置、缓存策略等)。通过这些方法,开发者可以显著提升系统的整体性能,打造高性能、高可用的Web应用。
58 2
|
1月前
|
NoSQL 前端开发 Java
使用 Spring Boot + Neo4j 实现知识图谱功能开发
在数据驱动的时代,知识图谱作为一种强大的信息组织方式,正逐渐在各个领域展现出其独特的价值。本文将围绕使用Spring Boot结合Neo4j图数据库来实现知识图谱功能开发的技术细节进行分享,帮助读者理解并掌握这一技术栈在实际项目中的应用。
102 4
|
1月前
|
IDE Java 开发工具
还在为繁琐的配置头疼吗?一文教你如何用 Spring Boot 快速启动,让开发效率飙升,从此告别加班——打造你的首个轻量级应用!
【9月更文挑战第2天】Spring Boot 是一款基于 Spring 框架的简化开发工具包,采用“约定优于配置”的原则,帮助开发者快速创建独立的生产级应用程序。本文将指导您完成首个 Spring Boot 项目的搭建过程,包括环境配置、项目初始化、添加依赖、编写控制器及运行应用。首先需确保 JDK 版本不低于 8,并安装支持 Spring Boot 的现代 IDE,如 IntelliJ IDEA 或 Eclipse。
87 5
|
2月前
|
人工智能 Java Spring
Spring框架下,如何让你的日志管理像‘AI’一样智能,提升开发效率的秘密武器!
【8月更文挑战第31天】日志管理在软件开发中至关重要,不仅能帮助开发者追踪问题和调试程序,还是系统监控和运维的重要工具。在Spring框架下,通过合理配置Logback等日志框架,可大幅提升日志管理效率。本文将介绍如何引入日志框架、配置日志级别、在代码中使用Logger,以及利用ELK等工具进行日志聚合和分析,帮助你构建高效、可靠的日志管理系统,为开发和运维提供支持。
39 0
|
2月前
|
数据库 开发者 Java
颠覆传统开发:Hibernate与Spring Boot的集成,让你的开发效率飞跃式提升!
【8月更文挑战第31天】在 Java 开发中,Spring Boot 和 Hibernate 已成为许多开发者的首选技术栈。Spring Boot 简化了配置和部署过程,而 Hibernate 则是一个强大的 ORM 框架,用于管理数据库交互。将两者结合使用,可以极大提升开发效率并构建高性能的现代 Java 应用。本文将通过代码示例展示如何在 Spring Boot 项目中集成 Hibernate,并实现基本的数据库操作,包括添加依赖、配置数据源、创建实体类和仓库接口,以及在服务层和控制器中处理 HTTP 请求。这种组合不仅简化了配置,还提供了一套强大的工具来快速开发现代 Java 应用程序。
62 0
下一篇
无影云桌面