您好,欢迎访问一九零五行业门户网

如何在Java SpringBoot项目中优雅地实现操作日志记录?

一、aop是什么?aop(aspect-oriented programming:⾯向切⾯编程),说起aop,几乎学过spring框架的人都知道,它是spring的三大核心思想之一(ioc:控制反转,di:依赖注入,aop:面向切面编程)。能够将那些与业务⽆关,却为业务模块所共同调⽤的逻辑或责任(例如事务处理、⽇志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
二、aop做了什么?简单说来,aop主要做三件事:
1、在哪里切入,也就是日志记录等非业务代码在哪些业务代码中执行。
2、在什么时候切入,是在业务代码执行前还是后。
3、切入后做什么事情,比如权限校验,日志记录等。
可以用一张图来理解:
图上的一个核心术语的说明:
pointcut:切点,决定在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。execution方式:可以用路径表达式指定哪些类织入切面,annotation方式:可以指定被哪些注解修饰的代码织入切面。
advice:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
aspect:切面,即pointcut和advice。
joint point:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 spring aop 中,一个连接点总是代表一个方法执行。
weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。
三、实现步骤(1)自定义一个注解@log (2)创建一个切面类,切点设置为拦截标注@log的方法,截取传参,进行日志记录 (3)将@log标注在接口上
具体的实现步骤如下:
1. 添加aop依赖代码如下(示例):
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-aop</artifactid></dependency>
2. 自定义一个日志注解日志一般使用的是注解类型的切点表达式,我们先创建一个日志注解,当spring容器扫描到有此注解的方法就会进行增强。
代码如下(示例):
@target({ elementtype.parameter, elementtype.method }) // 注解放置的目标位置,parameter: 可用在参数上 method:可用在方法级别上@retention(retentionpolicy.runtime) // 指明修饰的注解的生存周期 runtime:运行级别保留@documentedpublic @interface log { /** * 模块 */ string title() default ""; /** * 功能 */ public businesstype businesstype() default businesstype.other; /** * 是否保存请求的参数 */ public boolean issaverequestdata() default true; /** * 是否保存响应的参数 */ public boolean issaveresponsedata() default true;}
3. 切面声明申明一个切面类,并交给spring容器管理。
代码如下(示例):
@aspect@component@slf4jpublic class logaspect { @autowired private ixloperlogservice operlogservice; /** * 处理完请求后执行 * @param joinpoint 切点 */ @afterreturning(pointcut = "@annotation(controllerlog)", returning = "jsonresult") public void doafterreturnibng(joinpoint joinpoint, log controllerlog, object jsonresult) { handlelog(joinpoint, controllerlog, null, jsonresult); } protected void handlelog(final joinpoint joinpoint, log controllerlog, final exception e, object jsonresult) { try { // 获取当前的用户 jwtuser loginuser = securityutils.getloginuser(); // 日志记录 xloperlog operlog = new xloperlog(); operlog.setstatus(0); // 请求的ip地址 string ip = servletutil.getclientip(servletutils.getrequest()); if ("0:0:0:0:0:0:0:1".equals(ip)) { ip = "127.0.0.1"; } operlog.setoperip(ip); operlog.setoperurl(servletutils.getrequest().getrequesturi()); if (loginuser != null) { operlog.setopername(loginuser.getusername()); } if (e != null) { operlog.setstatus(1); operlog.seterrormsg(stringutils.substring(e.getmessage(), 0, 2000)); } // 设置方法名称 string classname = joinpoint.gettarget().getclass().getname(); string methodname = joinpoint.getsignature().getname(); operlog.setmethod(classname + "." + methodname + "()"); operlog.setrequestmethod(servletutils.getrequest().getmethod()); operlog.setopertime(new date()); // 处理设置注解上的参数 getcontrollermethoddescription(joinpoint, controllerlog, operlog, jsonresult); // 保存数据库 operlogservice.save(operlog); } catch (exception exp) { log.error("异常信息:{}", exp.getmessage()); exp.printstacktrace(); } } /** * 获取注解中对方法的描述信息 用于controller层注解 * @param log 日志 * @param operlog 操作日志 * @throws exception */ public void getcontrollermethoddescription(joinpoint joinpoint, log log, xloperlog operlog, object jsonresult) throws exception { // 设置操作业务类型 operlog.setbusinesstype(log.businesstype().ordinal()); // 设置标题 operlog.settitle(log.title()); // 是否需要保存request,参数和值 if (log.issaverequestdata()) { // 设置参数的信息 setrequestvalue(joinpoint, operlog); } // 是否需要保存response,参数和值 if (log.issaveresponsedata() && stringutils.isnotnull(jsonresult)) { operlog.setjsonresult(stringutils.substring(json.tojsonstring(jsonresult), 0, 2000)); } } /** * 获取请求的参数,放到log中 * @param operlog 操作日志 * @throws exception 异常 */ private void setrequestvalue(joinpoint joinpoint, xloperlog operlog) throws exception { string requsetmethod = operlog.getrequestmethod(); if (httpmethod.put.name().equals(requsetmethod) || httpmethod.post.name().equals(requsetmethod)) { string parsams = argsarraytostring(joinpoint.getargs()); operlog.setoperparam(stringutils.substring(parsams,0,2000)); } else { map<?,?> paramsmap = (map<?,?>) servletutils.getrequest().getattribute(handlermapping.uri_template_variables_attribute); operlog.setoperparam(stringutils.substring(paramsmap.tostring(),0,2000)); } } /** * 参数拼装 */ private string argsarraytostring(object[] paramsarray) { string params = ""; if (paramsarray != null && paramsarray.length > 0) { for (object object : paramsarray) { // 不为空 并且是不需要过滤的 对象 if (stringutils.isnotnull(object) && !isfilterobject(object)) { object jsonobj = json.tojson(object); params += jsonobj.tostring() + " "; } } } return params.trim(); } /** * 判断是否需要过滤的对象。 * @param object 对象信息。 * @return 如果是需要过滤的对象,则返回true;否则返回false。 */ @suppresswarnings("rawtypes") public boolean isfilterobject(final object object) { class<?> clazz = object.getclass(); if (clazz.isarray()) { return clazz.getcomponenttype().isassignablefrom(multipartfile.class); } else if (collection.class.isassignablefrom(clazz)) { collection collection = (collection) object; for (object value : collection) { return value instanceof multipartfile; } } else if (map.class.isassignablefrom(clazz)) { map map = (map) object; for (object value : map.entryset()) { map.entry entry = (map.entry) value; return entry.getvalue() instanceof multipartfile; } } return object instanceof multipartfile || object instanceof httpservletrequest || object instanceof httpservletresponse || object instanceof bindingresult; }}
4. 标注在接口上将自定义注解标注在需要记录操作日志的接口上,代码如下(示例):
@log(title = "代码生成", businesstype = businesstype.gencode) @apioperation(value = "批量生成代码") @getmapping("/download/batch") public void batchgencode(httpservletresponse response, string tables) throws ioexception { string[] tablenames = convert.tostrarray(tables); byte[] data = gentableservice.downloadcode(tablenames); gencode(response, data); }
5. 实现的效果执行相关操作就会记录日志,记录了一些基础信息存在数据表里。
以上就是如何在java springboot项目中优雅地实现操作日志记录?的详细内容。
其它类似信息

推荐信息