spring boot如何使用AOP统一处理web请求
为了保证服务的高可用,及时发现问题,迅速解决问题,为应用添加log是必不可少的。
但是随着项目的增大,方法增多,每个方法加单独加日志处理会有很多冗余
那在SpringBoot项目中如何统一的处理Web请求日志?
基本思想:
采用AOP的方式,拦截请求,写入日志
AOP是面向切面的编程,就是在运行期通过动态代理的方式对代码进行增强处理
基于AOP不会破坏原来程序逻辑,因此它可以很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
1.添加依赖
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-aop
引入spring-boot-starter-web依赖之后无需在引入相关的日志依赖,spring-boot-starter-web中已经集成了slf4j的依赖
引入spring-boot-starter-aop依赖之后,AOP的功能即是启动状态
2.配置
application.properties添加
#AOP spring.aop.auto=true spring.aop.proxy-target-class=true
logback-spring.xml,主要是ControllerRequest那部分
info ${CONSOLE_LOG_PATTERN} UTF-8 ${log.path}/debug/debug.log %d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%logger{50}-%msg%n UTF-8 ${log.path}/debug/debug-%d{yyyy-MM-dd}.%i.log 100MB 15 debug ACCEPT DENY ${log.path}/info/info.log %d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%logger{50}-%msg%n UTF-8 ${log.path}/info/info-%d{yyyy-MM-dd}.%i.log 100MB 15 info ACCEPT DENY ${log.path}/warn/warn.log %d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%logger{50}-%msg%n UTF-8 ${log.path}/warn/warn-%d{yyyy-MM-dd}.%i.log 100MB 15 warn ACCEPT DENY ${log.path}/error/error.log %d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%logger{50}-%msg%n UTF-8 ${log.path}/error/error-%d{yyyy-MM-dd}.%i.log 100MB 15 ERROR ACCEPT DENY ${log.path}/request/info.log ${log.path}/request/info.log.%d{yyyy-MM-dd} 30 %d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%logger{50}-%msg%n
3..实现
实现切面的注解
(1)类注解
A.@Aspect将一个java类定义为切面类
B.@order(i)标记切面类的处理优先级,i值越小,优先级别越高。可以注解类,也能注解到方法上
(2)方法注解
A.@Pointcut定义一个切入点,可以是一个表达式
execution表达式,eg:
任意公共方法的执行 execution(public**(..)) 任何一个以“set”开始的方法的执行 execution(*set*(..)) 定义在controller包里的任意方法的执行 execution(public*com.example.demo.controller.*(..)) 定义在controller包里的任意方法的执行 execution(public*com.example.demo.controller.*.*(..)) 定义在controller包和所有子包里的任意类的任意方法的执行 execution(public*com.example.demo.controller..*.*(..))
B.实现在不同的位置切入
- @Before在切点前执行方法,内容为指定的切点
- @After在切点后,return前执行
- @AfterReturning切入点在return内容之后(可用作处理返回值)
- @Around切入点在前后切入内容,并自己控制何时执行切入的内容
- @AfterThrowing处理当切入部分抛出异常后的逻辑
C.@order(i)标记切点的优先级,i越小,优先级越高
@order(i)注解说明
注解类,i值是,值越小,优先级越高
注解方法,分两种情况
注解的是@Before是i值越小,优先级越高
注解的是@After或@AfterReturning中,i值越大,优先级越高
具体实现
packagecom.example.demo.configure; importorg.aspectj.lang.JoinPoint; importorg.aspectj.lang.ProceedingJoinPoint; importorg.aspectj.lang.annotation.*; importorg.aspectj.lang.reflect.MethodSignature; importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importorg.springframework.core.DefaultParameterNameDiscoverer; importorg.springframework.core.ParameterNameDiscoverer; importorg.springframework.stereotype.Component; importorg.springframework.web.context.request.RequestContextHolder; importorg.springframework.web.context.request.ServletRequestAttributes; importjavax.servlet.http.HttpServletRequest; importjava.lang.reflect.Method; importjava.net.InetAddress; importjava.util.HashMap; importjava.util.Map; @Aspect @Component publicclassWebRequestLogAspect{ privatefinalLoggerloggerController=LoggerFactory.getLogger("ControllerRequest"); privatefinalLoggerlogger=LoggerFactory.getLogger(WebRequestLogAspect.class); ThreadLocalstartTime=newThreadLocal<>(); ThreadLocal beanName=newThreadLocal<>(); ThreadLocal user=newThreadLocal<>(); ThreadLocal methodName=newThreadLocal<>(); ThreadLocal params=newThreadLocal<>(); ThreadLocal remoteAddr=newThreadLocal<>(); ThreadLocal uri=newThreadLocal<>(); privatestaticMap getFieldsName(ProceedingJoinPointjoinPoint){ //参数值 Object[]args=joinPoint.getArgs(); ParameterNameDiscovererpnd=newDefaultParameterNameDiscoverer(); MethodSignaturesignature=(MethodSignature)joinPoint.getSignature(); Methodmethod=signature.getMethod(); String[]parameterNames=pnd.getParameterNames(method); Map paramMap=newHashMap<>(32); for(inti=0;i fieldsName=getFieldsName(thisJoinPoint); params.set(fieldsName.toString()); returnobject; } /** *处理完请求返回内容 *@paramresult */ @AfterReturning(returning="result",pointcut="webRequestLog()") publicvoiddoAfterReturning(Objectresult){ try{ longrequestTime=(System.currentTimeMillis()-startTime.get())/1000; loggerController.info("请求耗时:"+requestTime+",uri="+uri.get()+";beanName="+beanName.get()+";remoteAddr="+remoteAddr.get()+";user="+user.get() +";methodName="+methodName.get()+";params="+params.get()+";RESPONSE:"+result); }catch(Exceptione){ logger.error("***操作请求日志记录失败doAfterReturning()***",e); } } /** *获取登录用户远程主机ip地址 * *@paramrequest *@return */ privateStringgetIpAddr(HttpServletRequestrequest){ Stringip=request.getHeader("x-forwarded-for"); if(ip==null||ip.length()==0||"unknown".equalsIgnoreCase(ip)){ ip=request.getHeader("Proxy-Client-IP"); } if(ip==null||ip.length()==0||"unknown".equalsIgnoreCase(ip)){ ip=request.getHeader("WL-Proxy-Client-IP"); } if(ip==null||ip.length()==0||"unknown".equalsIgnoreCase(ip)){ ip=request.getRemoteAddr(); if(ip.equals("127.0.0.1")||ip.equals("0:0:0:0:0:0:0:1")){ //根据网卡取本机配置的IP InetAddressinet=null; try{ inet=InetAddress.getLocalHost(); }catch(Exceptione){ e.printStackTrace(); } ip=inet.getHostAddress(); } } //多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if(ip!=null&&ip.length()>15){ if(ip.indexOf(",")>0){ ip=ip.substring(0,ip.indexOf(",")); } } returnip; } }
4.测试类
packagecom.example.demo.controller; importcom.alibaba.fastjson.JSONObject; importcom.example.demo.dao.UserRepository; importcom.example.demo.domain.User; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.web.bind.annotation.*; importjava.util.List; importjava.util.Map; @RestController publicclassDemo{ @RequestMapping(value="test1") publicStringtest1(@RequestParam(defaultValue="0")Integerid,@RequestParam(defaultValue="0")Stringname){ returnid+name; } @RequestMapping("hello") publicStringhello(){ return"HelloWorld!"; } @PostMapping("/updateStatus") publicObjectupdateStatus(@RequestBodyJSONObjectjsonParam){ returnjsonParam; } }
输出到logs/request/info.log内容
2019-09-1113:31:45.729[http-nio-8080-exec-4]INFOControllerRequest-请求耗时:0,uri=/test1;beanName=com.example.demo.controller.Demo;remoteAddr=172.27.0.17;user=null;methodName=test1;params={name=abcdef(String),id=123(Integer)};RESPONSE:123abcdef 2019-09-1113:32:16.692[http-nio-8080-exec-5]INFOControllerRequest-请求耗时:0,uri=/updateStatus;beanName=com.example.demo.controller.Demo;remoteAddr=172.27.0.17;user=null;methodName=updateStatus;params={jsonParam={"id":"17","type":3,"status":2}(JSONObject)};RESPONSE:{"id":"17","type":3,"status":2} 2019-09-1113:33:32.584[http-nio-8080-exec-7]INFOControllerRequest-请求耗时:0,uri=/hello;beanName=com.example.demo.controller.Demo;remoteAddr=172.27.0.17;user=null;methodName=hello;params={};RESPONSE:HelloWorld!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。