前言 大家好,我是希留。 在实际开发当中,对于某些关键业务,我们通常需要记录该操作的内容,一个操作调一次记录方法,每次还得去收集参数等等,会造成大量代码重复。我们希望代码中只有业务相关的操作,在项目中使用注解来完成此项功能。 通常就是使用Spring中的AOP特性来实现的,那么在SpringBoot项目当中应该如何来实现呢?一、AOP是什么? AOP(AspectOrientedProgramming:向切编程),说起AOP,几乎学过Spring框架的人都知道,它是Spring的三大核心思想之一(IOC:控制反转,DI:依赖注入,AOP:面向切面编程)。能够将那些与业务关,却为业务模块所共同调的逻辑或责任(例如事务处理、志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。二、AOP做了什么? 简单说来,AOP主要做三件事:1、在哪里切入,也就是日志记录等非业务代码在哪些业务代码中执行。2、在什么时候切入,是在业务代码执行前还是后。3、切入后做什么事情,比如权限校验,日志记录等。 可以用一张图来理解: 图上的一个核心术语的说明:Pointcut:切点,决定在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。execution方式:可以用路径表达式指定哪些类织入切面,annotation方式:可以指定被哪些注解修饰的代码织入切面。Advice:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。Aspect:切面,即Pointcut和Advice。Jointpoint:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在SpringAOP中,一个连接点总是代表一个方法执行。Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。三、实现步骤 (1)自定义一个注解Log (2)创建一个切面类,切点设置为拦截标注Log的方法,截取传参,进行日志记录 (3)将Log标注在接口上 具体的实现步骤如下:1。添加AOP依赖 代码如下(示例):dependencygroupIdorg。springframework。bootgroupIdspringbootstarteraopartifactIddependency2。自定义一个日志注解 日志一般使用的是注解类型的切点表达式,我们先创建一个日志注解,当spring容器扫描到有此注解的方法就会进行增强。代码如下(示例):Target({ElementType。PARAMETER,ElementType。METHOD})注解放置的目标位置,PARAMETER:可用在参数上METHOD:可用在方法级别上Retention(RetentionPolicy。RUNTIME)指明修饰的注解的生存周期RUNTIME:运行级别保留DocumentedpublicinterfaceLog{模块Stringtitle()功能publicBusinessTypebusinessType()defaultBusinessType。OTHER;是否保存请求的参数publicbooleanisSaveRequestData()是否保存响应的参数publicbooleanisSaveResponseData()}3。切面声明 申明一个切面类,并交给Spring容器管理。代码如下(示例):AspectComponentSlf4jpublicclassLogAspect{AutowiredprivateIXlOperLogServiceoperLogS处理完请求后执行paramjoinPoint切点AfterReturning(pointcutannotation(controllerLog),returningjsonResult)publicvoiddoAfterReturnibng(JoinPointjoinPoint,LogcontrollerLog,ObjectjsonResult){handleLog(joinPoint,controllerLog,null,jsonResult);}protectedvoidhandleLog(finalJoinPointjoinPoint,LogcontrollerLog,finalExceptione,ObjectjsonResult){try{获取当前的用户JwtUserloginUserSecurityUtils。getLoginUser();ahrefhttps:www。bs178。comrizhitargetblankclassinfotextkey日志a记录XlOperLogoperLognewXlOperLog();operLog。setStatus(0);请求的IP地址StringiPServletUtil。getClientIP(ServletUtils。getRequest());if(0:0:0:0:0:0:0:1。equals(iP)){iP127。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));}设置方法名称StringclassNamejoinPoint。getTarget()。getClass()。getName();StringmethodNamejoinPoint。getSignature()。getName();operLog。setMethod(className。methodName());operLog。setRequestMethod(ServletUtils。getRequest()。getMethod());operLog。setOperTime(newDate());处理设置注解上的参数getControllerMethodDescription(joinPoint,controllerLog,operLog,jsonResult);保存数据库operLogService。save(operLog);}catch(Exceptionexp){log。error(异常信息:{},exp。getMessage());exp。printStackTrace();}}获取注解中对方法的描述信息用于Controller层注解paramlogahrefhttps:www。bs178。comrizhitargetblankclassinfotextkey日志aparamoperLog操作ahrefhttps:www。bs178。comrizhitargetblankclassinfotextkey日志athrowsExceptionpublicvoidgetControllerMethodDescription(JoinPointjoinPoint,Loglog,XlOperLogoperLog,ObjectjsonResult)throwsException{设置操作业务类型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中paramoperLog操作ahrefhttps:www。bs178。comrizhitargetblankclassinfotextkey日志athrowsException异常privatevoidsetRequestValue(JoinPointjoinPoint,XlOperLogoperLog)throwsException{StringrequsetMethodoperLog。getRequestMethod();if(HttpMethod。PUT。name()。equals(requsetMethod)HttpMethod。POST。name()。equals(requsetMethod)){StringparsamsargsArrayToString(joinPoint。getArgs());operLog。setOperParam(StringUtils。substring(parsams,0,2000));}else{M?,?paramsMap(M?,?)ServletUtils。getRequest()。getAttribute(HandlerMapping。URITEMPLATEVARIABLESATTRIBUTE);operLog。setOperParam(StringUtils。substring(paramsMap。toString(),0,2000));}}参数拼装privateStringargsArrayToString(Object〔〕paramsArray){Sif(paramsArray!nullparamsArray。length0){for(Objectobject:paramsArray){不为空并且是不需要过滤的对象if(StringUtils。isNotNull(object)!isFilterObject(object)){ObjectjsonObjJSON。toJSON(object);paramsjsonObj。toString();}}}returnparams。trim();}判断是否需要过滤的对象。paramobject对象信息。return如果是需要过滤的对象,则返回否则返回false。SuppressWarnings(rawtypes)publicbooleanisFilterObject(finalObjectobject){C?clazzobject。getClass();if(clazz。isArray()){returnclazz。getComponentType()。isAssignableFrom(MultipartFile。class);}elseif(Collection。class。isAssignableFrom(clazz)){Collectioncollection(Collection)for(Objectvalue:collection){returnvalueinstanceofMultipartF}}elseif(Map。class。isAssignableFrom(clazz)){Mapmap(Map)for(Objectvalue:map。entrySet()){Map。Entryentry(Map。Entry)returnentry。getValue()instanceofMultipartF}}returnobjectinstanceofMultipartFileobjectinstanceofHttpServletRequestobjectinstanceofHttpServletResponseobjectinstanceofBindingR}}4。标注在接口上 将自定义注解标注在需要记录操作日志的接口上,代码如下(示例):Log(title代码生成,businessTypeBusinessType。GENCODE)ApiOperation(value批量生成代码)GetMapping(downloadbatch)publicvoidbatchGenCode(HttpServletResponseresponse,Stringtables)throwsIOException{String〔〕tableNamesConvert。toStrArray(tables);byte〔〕datagenTableService。downloadCode(tableNames);genCode(response,data);}5。实现的效果 执行相关操作就会记录日志,记录了一些基础信息存在数据表里。 总结 好了,以上就是本篇文章的主要内容了,本文主要讲述了使用SpringAOP来实现操作日志的记录,欢迎评论区留言,说说你们的项目中是如何实现操作日志的。 若觉得本文对您有帮助的话,还不忘点赞评论关注,支持一波哟