spring boot如何使用AOP统一处理web请求_java

来源:脚本之家  责任编辑:小易  

Spring Boot项目的主要目标如下:为所有的Spring开发工作提供一个更快、更广泛的入门经验开箱即用,你也可以通过修改默认值来快速满足你的项目的需求提供了一系列大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等Spring Boot不生成代码,且完全不需要XML配置。下图显示了Spring Boot在Spring生态系统中的位置。Spring Boot还附带了一个命令行工具,你可以用来快速创建Spring原型应用,它提供了一种让你的Spring应用起步的最快速的方式。该工具允许运行Groovy脚本,这意味着你可以使用类似于Java的语法www.zgxue.com防采集请勿采集本网。

为了保证服务的高可用,及时发现问题,迅速解决问题,为应用添加log是必不可少的。

Spring Cloud介绍 spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞癣分布式会话

但是随着项目的增大,方法增多,每个方法加单独加日志处理会有很多冗余

3)使用Spring提供的注解:@Schedule。如果定时任务执行时间较短,并且比较单一,可以使用这个注解。案例: SpringBootApplication 开启对定时任务的支持 在相应的方法上添加@Scheduled声明需要执行的定时

那在SpringBoot项目中如何统一的处理Web请求日志?

而Spring Boot通常和maven一起使用,达到不需要配置spring的applicationConetxt.xml和web.xml,快速启动项目。springboot是现在的主流了。具体如何实现 我给你提供个网址 自己学习去 网页链接 需要注册,

基本思想:

6.右键MySpringBootApplication中的main方法,Run As->Spring Boot App,项目就可以启动了。package com.example;import org.springframework.boot.SpringApplication;import org.springframework.boot.

采用AOP的方式,拦截请求,写入日志

要Spring Boot进行功能开发,需要使用Gradle或者Maven作为构建工具。在本例中,我们会使用Eclipse和Maven插件进行开发。要使用Spring Boot,首先创建一个Maven工程,并修改Maven主要的配置文件

AOP 是面向切面的编程,就是在运行期通过动态代理的方式对代码进行增强处理

基于AOP不会破坏原来程序逻辑,因此它可以很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

1.添加依赖

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency>

引入spring-boot-starter-web 依赖之后无需在引入相关的日志依赖,spring-boot-starter-web中已经集成了slf4j 的依赖

引入spring-boot-starter-aop 依赖之后,AOP 的功能即是启动状态

2.配置

application.properties添加

# AOPspring.aop.auto=truespring.aop.proxy-target-class=true

logback-spring.xml,主要是ControllerRequest那部分

<?xml version="1.0" encoding="UTF-8"?><configuration scan="true" scanPeriod="60 seconds" debug="false"> <property name="log.path" value="logs" /> <!--0. 日志格式和颜色渲染 --> <!-- 彩色日志依赖的渲染类 --> <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" /> <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" /> <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" /> <!-- 彩色日志格式 --> <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> <!--1. 输出到控制台--> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息--> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>info</level> </filter> <encoder> <Pattern>${CONSOLE_LOG_PATTERN}</Pattern> <!-- 设置字符集 --> <charset>UTF-8</charset> </encoder> </appender> <!--2. 输出到文档--> <!-- 2.1 level为 DEBUG 日志,时间滚动输出 --> <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文档的路径及文档名 --> <file>${log.path}/debug/debug.log</file> <!--日志文档输出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> <!-- 设置字符集 --> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 日志归档 --> <fileNamePattern>${log.path}/debug/debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文档保留天数--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文档只记录debug级别的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>debug</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- 2.2 level为 INFO 日志,时间滚动输出 --> <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文档的路径及文档名 --> <file>${log.path}/info/info.log</file> <!--日志文档输出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 每天日志归档路径以及格式 --> <fileNamePattern>${log.path}/info/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文档保留天数--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文档只记录info级别的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>info</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- 2.3 level为 WARN 日志,时间滚动输出 --> <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文档的路径及文档名 --> <file>${log.path}/warn/warn.log</file> <!--日志文档输出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> <!-- 此处设置字符集 --> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.path}/warn/warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文档保留天数--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文档只记录warn级别的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>warn</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- 2.4 level为 ERROR 日志,时间滚动输出 --> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文档的路径及文档名 --> <file>${log.path}/error/error.log</file> <!--日志文档输出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> <!-- 此处设置字符集 --> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.path}/error/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文档保留天数--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文档只记录ERROR级别的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <springProfile name="dev"> <root level="info"> <appender-ref ref="CONSOLE" /> <appender-ref ref="DEBUG_FILE" /> <appender-ref ref="INFO_FILE" /> <appender-ref ref="WARN_FILE" /> <appender-ref ref="ERROR_FILE" /> </root> </springProfile> <springProfile name="prod"> <root level="info"> <appender-ref ref="DEBUG_FILE" /> <appender-ref ref="INFO_FILE" /> <appender-ref ref="WARN_FILE" /> <appender-ref ref="ERROR_FILE" /> </root> </springProfile> <appender name="ControllerRequest" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${log.path}/request/info.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${log.path}/request/info.log.%d{yyyy-MM-dd}</FileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <logger name="ControllerRequest" level="DEBUG" additivity="false"> <appender-ref ref="ControllerRequest"/> </logger></configuration>

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值越大,优先级越高

具体实现

package com.example.demo.configure;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.aspectj.lang.reflect.MethodSignature;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.core.DefaultParameterNameDiscoverer;import org.springframework.core.ParameterNameDiscoverer;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;import java.lang.reflect.Method;import java.net.InetAddress;import java.util.HashMap;import java.util.Map;@Aspect@Componentpublic class WebRequestLogAspect { private final Logger loggerController = LoggerFactory.getLogger("ControllerRequest"); private final Logger logger = LoggerFactory.getLogger(WebRequestLogAspect.class); ThreadLocal<Long> startTime = new ThreadLocal<>(); ThreadLocal<String> beanName = new ThreadLocal<>(); ThreadLocal<String> user = new ThreadLocal<>(); ThreadLocal<String> methodName = new ThreadLocal<>(); ThreadLocal<String> params = new ThreadLocal<>(); ThreadLocal<String> remoteAddr = new ThreadLocal<>(); ThreadLocal<String> uri = new ThreadLocal<>(); private static Map<String, Object> getFieldsName(ProceedingJoinPoint joinPoint) { // 参数值 Object[] args = joinPoint.getArgs(); ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); String[] parameterNames = pnd.getParameterNames(method); Map<String, Object> paramMap = new HashMap<>(32); for (int i = 0; i < parameterNames.length; i++) { paramMap.put(parameterNames[i], args[i] + "(" + args[i].getClass().getSimpleName() + ")"); } return paramMap; } @Pointcut("execution(public * com.example.demo.controller..*.*(..))") public void webRequestLog() { } /** * 前置通知,方法调用前被调用 * @param joinPoint */ @Before("webRequestLog()") public void doBefore(JoinPoint joinPoint) { try { startTime.set(System.currentTimeMillis()); // 接收到请求,记录请求内容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); beanName.set(joinPoint.getSignature().getDeclaringTypeName()); methodName.set(joinPoint.getSignature().getName()); uri.set(request.getRequestURI()); remoteAddr.set(getIpAddr(request)); user.set((String) request.getSession().getAttribute("user")); } catch (Exception e) { logger.error("***操作请求日志记录失败doBefore()***", e); } } /** * 环绕通知,环绕增强,相当于MethodInterceptor * @param thisJoinPoint */ @Around("webRequestLog()") public Object proceed(ProceedingJoinPoint thisJoinPoint) throws Throwable { Object object = thisJoinPoint.proceed(); Map<String, Object> fieldsName = getFieldsName(thisJoinPoint); params.set(fieldsName.toString()); return object; } /** * 处理完请求返回内容 * @param result */ @AfterReturning(returning = "result", pointcut = "webRequestLog()") public void doAfterReturning(Object result) { try { long requestTime = (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 (Exception e) { logger.error("***操作请求日志记录失败doAfterReturning()***", e); } } /** * 获取登录用户远程主机ip地址 * * @param request * @return */ private String getIpAddr(HttpServletRequest request) { String ip = 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 InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (Exception e) { e.printStackTrace(); } ip = inet.getHostAddress(); } } // 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if (ip != null && ip.length() > 15) { if (ip.indexOf(",") > 0) { ip = ip.substring(0, ip.indexOf(",")); } } return ip; }}

4.测试类

package com.example.demo.controller;import com.alibaba.fastjson.JSONObject;import com.example.demo.dao.UserRepository;import com.example.demo.domain.User;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import java.util.List;import java.util.Map;@RestControllerpublic class Demo { @RequestMapping (value = "test1") public String test1(@RequestParam(defaultValue = "0") Integer id,@RequestParam(defaultValue = "0")String name){ return id+name; } @RequestMapping("hello") public String hello() { return "Hello World!"; } @PostMapping("/updateStatus") public Object updateStatus(@RequestBody JSONObject jsonParam) { return jsonParam; }}

输出到logs/request/info.log内容

2019-09-11 13:31:45.729 [http-nio-8080-exec-4] INFO ControllerRequest - 请求耗时: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 : 123abcdef2019-09-11 13:32:16.692 [http-nio-8080-exec-5] INFO ControllerRequest - 请求耗时: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-11 13:33:32.584 [http-nio-8080-exec-7] INFO ControllerRequest - 请求耗时:0, uri=/hello; beanName=com.example.demo.controller.Demo; remoteAddr=172.27.0.17; user=null; methodName=hello; params={}; RESPONSE : Hello World!

Spring Boot支持多2113种模版引擎包括:5261FreeMarkerGroovyThymeleaf(官方推荐)MustacheJSP技术Spring Boot官方是不推4102荐的,原因有三:tomcat只支持war的打包方式,不支持可执1653行的jar。Jetty 嵌套的容器不支持jspUndertow创建自定义error.jsp页面不会覆盖错误处理的默认视图,而应该使用自定义错误页面当你使用上述模板引擎中的任何一个,它们默认的模板配置路径为:src/main/resources/templates。当然也可以修改这个路径,具体如何修改,可在后续各模板引擎的配置属性中查询并修改。Thymeleaf模板引擎Thymeleaf是一款用于渲染XML/XHTML/HTML5内容的模板引擎。类似JSP,Velocity,FreeMaker等,它也可以轻易的与Spring MVC等Web框架进行集成作为Web应用的模板引擎。与其它模板引擎相比,Thymeleaf最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用。它的功能特性如下:Spring MVC中@Controller中的方法可以直接返回模板名称,接下来Thymeleaf模板引擎会自动进行渲染模板中的表达式支持Spring表达式语言(Spring EL)表单支持,并兼容Spring MVC的数据绑定与验证机制国际化支持Spring官方也推荐使用Thymeleaf,所以本篇代码整合就使用Thymeleaf来整合内容来自www.zgxue.com请勿采集。


  • 本文相关:
  • springboot+websocket+netty实现消息推送的示例代码
  • springboot @webfilter注解过滤器的实现
  • spring5中的webclient使用方法详解
  • springboot2.0整合websocket代码实例
  • springboot集成websocket实现前后端消息互传的方法
  • springboot websocket集群(stomp协议)连接时候传递参数
  • springboot深入理解之内置web容器及配置的总结
  • 简单了解spring web相关模块运行原理
  • java实现table添加右键点击事件监听操作示例
  • mybatis简介与配置mybatis+spring+mysql的方法
  • java线程池专题(概念和作用)
  • java生成申请单序列号的实现方法
  • springboot中通过实现webmvcconfigurer参数校验的方法示例
  • java中获取类资源的方法总结
  • java进阶教程之string类
  • springboot项目调优及垃圾回收器的比较详解
  • springboot中集成swagger2框架的方法
  • java线程之线程的生命周期的使用
  • spring boot怎么使用thymeleaf
  • 如何使用Spring Boot进行Java EE开发
  • 如何使用spring boot快速构建后台
  • 如何使用spring boot快速构建后台
  • 如何使用Spring Boot/Spring Cloud 实现微服务应用
  • spring boot的定时任务应该如何使用
  • Spring、Spring MVC、Spring Boot 怎么使用,有什么区别啊?
  • spring boot在eclipse中怎么用
  • 如何使用spring boot快速构建后台
  • spring boot怎么使用thymeleaf
  • 网站首页网页制作脚本下载服务器操作系统网站运营平面设计媒体动画电脑基础硬件教程网络安全c#教程vbvb.netc 语言java编程delphijavaandroidiosswiftscala易语言汇编语言其它相关首页springboot+websocket+netty实现消息推送的示例代码springboot @webfilter注解过滤器的实现spring5中的webclient使用方法详解springboot2.0整合websocket代码实例springboot集成websocket实现前后端消息互传的方法springboot websocket集群(stomp协议)连接时候传递参数springboot深入理解之内置web容器及配置的总结简单了解spring web相关模块运行原理java实现table添加右键点击事件监听操作示例mybatis简介与配置mybatis+spring+mysql的方法java线程池专题(概念和作用)java生成申请单序列号的实现方法springboot中通过实现webmvcconfigurer参数校验的方法示例java中获取类资源的方法总结java进阶教程之string类springboot项目调优及垃圾回收器的比较详解springboot中集成swagger2框架的方法java线程之线程的生命周期的使用java使double保留两位小数的多方java8 十大新特性详解java.net.socketexception: connjava写入文件的几种方法分享java环境变量的设置方法(图文教程java 十六进制与字符串的转换java list用法示例详解java中file类的使用方法javaweb实现文件上传下载功能实例java 字符串截取的三种方法(推荐mybatis无缝对接spring的方法elasticsearch中term与match的区别讲解详解springboot自定义异常处理详解spring boot实现多数据源代码实战java中的静态代码块、构造代码块、构造方springboot获取url请求参数的多种方式spring aop访问目标方法的参数操作示例详谈jvm--java中init和clinit的区别深入解析java中的编码转换以及编码和解码java实现洗牌发牌的方法
    免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved