最后:学习总结——MyBtis知识脑图(纯手绘xmind文档)
学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。下方即为我手绘的MyBtis知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的MyBtis知识脑图原件(包括上方的面试解析xmind文档)
除此之外,前文所提及的Alibaba珍藏版mybatis手写文档以及一本小小的MyBatis源码分析文档——《MyBatis源码分析》等等相关的学习笔记文档,也皆可分享给认可的朋友!
public void setNeedLogRequest(boolean needLogRequest) {
this.needLogRequest = needLogRequest;
}
public boolean isNeedLogResponse() {
return needLogResponse;
}
public void setNeedLogResponse(boolean needLogResponse) {
this.needLogResponse = needLogResponse;
}
public boolean isNeedLogHeader() {
return needLogHeader;
}
public void setNeedLogHeader(boolean needLogHeader) {
this.needLogHeader = needLogHeader;
}
public boolean isNeedLogPayload() {
return needLogPayload;
}
public void setNeedLogPayload(boolean needLogPayload) {
this.needLogPayload = needLogPayload;
}
public int getMaxPayloadLength() {
return maxPayloadLength;
}
public void setMaxPayloadLength(int maxPayloadLength) {
this.maxPayloadLength = maxPayloadLength;
}
public List<String> getExcludeUrlPatterns() {
return excludeUrlPatterns;
}
public void setExcludeUrlPatterns(List<String> excludeUrlPatterns) {
this.excludeUrlPatterns = excludeUrlPatterns;
}
}
上述代码解释:
1、首先请求进入过滤器之后,先进入shouldNotFilter方法,通过 getServletPath() 获取访问路径,然后再根据**`AntPathMatche`类(专门用来进行路劲匹配的,可以单独了解一下)**来进行路径匹配,看是否是需要进行过滤,如果是就返回false 代表执行这个过滤器。
2、然后请求进入doFilterInternal方法,先进行一系列判断,然后如果需要记录日志,就将HttpServletRequest 对象转换为 ContentCachingRequestWrapper 对象,转换成ContentCachingRequestWrapper对象是为了缓存请求体内容,并允许多次读取和修改。因为`HttpServletRequest` 对象只能被读取一次,读取后的数据就无法再次获取。所以一般都会先转换为ContentCachingRequestWrapper对象类型。
然后经过判断,再将HttpServletResponse 转换为:ContentCachingResponseWrapper,原因同理。
3、最后在finally 中打印 请求体数据和响应体数据,
首先 logRequest 方法记录请求日志,在logRequest方法中经过判断,进 getRequestPayload 方法,目的是为了获取请求体中参数,在这个方法中用到了一个方法:
>
> WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
>
>
>
这个方法我的理解是为了将HttpServletRequest对象 转换成 ContentCachingRequestWrapper对象 ,目的在上面也讲了,拿到ContentCachingRequestWrapper对象之后,就可以根据 wrapper.getContentAsByteArray() 方法获取请求体的字节数据了。再根据 getPayloadFromBuf 方法将字节数据转换成字符串。
获取到请求数据之后,再根据 createRequestMessage 方法拼接需要打印的数据即可。
响应数据同理,但需要注意的是,我们需要使用 wrapper.copyBodyToResponse() 方法,重新将响应参数设置到response中,不然客户端获取不到响应数据!
##### 示例二:
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONValidator;
import jdk.nashorn.internal.ir.annotations.Ignore;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@Slf4j
@Component
//@Order()
public class LogFilter extends OncePerRequestFilter implements Ordered {
/**
* 配置要记录请求的路径前缀
*/
private static final String NEED_TRACE_PATH_PREFIX = "/";
/**
* 忽略为multipart/form-data的ContentType的请求
*/
private static final Set<String> IGNORE_CONTENT_TYPE =new HashSet<>(Arrays.asList("multipart/form-data","application/octet-stream"));
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE+1 ;
}
@Override
@SuppressWarnings("NullableProblems")
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (!isRequestValid(request)) {
filterChain.doFilter(request, response);
return;
}
RequestWrapper request1=new RequestWrapper(request);
ResponseWrapper response1=new ResponseWrapper(response);
int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
long startTime = System.currentTimeMillis();
String path = request.getRequestURI();
try {
if (path.startsWith(NEED_TRACE_PATH_PREFIX)) {
// 1. 记录日志
consoleRequestLog(request1);
}
} catch (Exception ignore){
log.error("请求日志打印异常",ignore);
}
filterChain.doFilter(request1, response1);
status = response1.getStatus();
try {
if (path.startsWith(NEED_TRACE_PATH_PREFIX)) {
// 1. 记录日志
consoleResponseLog(path, startTime, status, response1);
}
updateResponse(response1, response);
} catch (Exception ignore) {
log.error("响应日志输出异常",ignore);
}
}
private Boolean ignoreCheck(String contentType){
if(StrUtil.isNotBlank(contentType)) {
for (String s : IGNORE_CONTENT_TYPE) {
if (contentType.contains(s)){
return true;
}
}
}
return false;
}
/**
* 输出请求日志
* @param request
*/
private synchronized void consoleRequestLog(RequestWrapper request){
log.info("请求 | 请求路径:[{}] | 请求方法:[{}] | 请求IP:[{}] | 请求参数:{} | 请求Body:{} ",
request.getRequestURI(),
request.getMethod(),
request.getRemoteAddr(),
JSON.toJSONString(request.getParameterMap()),
getRequestBody(request)
);
}
/**
* 获取请求body
* @param request
* @return 请求body
*/
private String getRequestBody(RequestWrapper request) {
String requestBody="{}";
// ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
// if (wrapper != null) {
try {
// requestBody =new String(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());
requestBody =request.getBody();
if (StrUtil.isNotBlank(requestBody) && (JSONValidator.from(requestBody).validate() || (requestBody.startsWith(“<”) && requestBody.endsWith(“>”)))) {
return requestBody;
} else if (StrUtil.isNotBlank(requestBody)) {
return “IOStream”;
} else {
return “”;
}
} catch (Exception ignore) {
log.error(“请求体转换异常”,ignore);
}
// }
return requestBody;
}
/**
* @Description: 打印日志
* @Param: [path - 请求路径, request - Http请求, startTime - 开始毫秒, status - 响应状态码, response - Http响应]
*/
private synchronized void consoleResponseLog(String path, long startTime, int status, ResponseWrapper response) {
log.info("返回 | 处理耗时:[{}ms] | 响应时间:[{}] | 响应状态:[{}] | 响应Body:{} ",
System.currentTimeMillis() - startTime,
LocalDateTime.now(),
status,
getResponseBody(response)
);
}
/**
* @Description: 判断请求是否合法
* @Param: [request]
* @return: {@link boolean}
*/
private boolean isRequestValid(HttpServletRequest request) {
try {
new URI(request.getRequestURL().toString());
return true;
} catch (URISyntaxException ex) {
return false;
}
}
/**
* @Description: 获取响应Body
* @Param: [response]
* @return: {@link String}
*/
private String getResponseBody(ResponseWrapper response) {
if (Objects.isNull(response.getDataStream())){
return "";
}
String responseBody = new String(response.getDataStream());;
if(JSONValidator.from(responseBody).validate()||responseBody.startsWith("<")&&responseBody.endsWith(">")) {
return responseBody;
}else{
return "FileStream";
}
}
/**
* @Description: 更新响应
* @Param: [response]
*/
private void updateResponse(ResponseWrapper response1,HttpServletResponse response) throws IOException {
response.getOutputStream().write(response1.getDataStream());
response.getOutputStream().flush();
// ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
// Objects.requireNonNull(responseWrapper).copyBodyToResponse();
}
}
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ByteUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.Part;
import java.io.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
@Slf4j
public class RequestWrapper extends ContentCachingRequestWrapper {
private final String MULTIPARTHEADER="multipart/form-data";
private byte[] body;
private Collection<Part> parts;
private BufferedReader reader;
private ServletInputStream inputStream;
public RequestWrapper(HttpServletRequest request) throws IOException, ServletException {
super(request);
if (StrUtil.isNotBlank(request.getContentType())&&request.getContentType().contains(MULTIPARTHEADER)){
parts=request.getParts();
}else {
//读一次 然后缓存起来
body = IoUtil.readBytes(request.getInputStream());
inputStream = new RequestCachingInputStream(body);
}
}
@Override
public Collection<Part> getParts() throws IOException, ServletException {
return parts;
}
public String getBody() {
try {
if (Objects.nonNull(body)&&body.length>0) {
return new String(body, getCharacterEncoding());
}else{
return "";
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (inputStream != null) {
return inputStream;
}
return new RequestCachingInputStream(body);
}
@Override
public BufferedReader getReader() throws IOException {
if (reader == null) {
reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding()));
}
return reader;
}
private static class RequestCachingInputStream extends ServletInputStream {
private final ByteArrayInputStream inputStream;
public RequestCachingInputStream(byte[] bytes) {
inputStream = new ByteArrayInputStream(bytes);
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public boolean isFinished() {
return inputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readlistener) {
}
}
}
import org.springframework.web.util.ContentCachingResponseWrapper;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ResponseWrapper extends ContentCachingResponseWrapper {
public ResponseWrapper(HttpServletResponse response) {
super(response);
}
public byte[] getDataStream() {
return getContentAsByteArray();
}
}
代码解释:
1、首先实现了 Ordered 接口,需要实现 `getOrder()` 方法来指定 Bean 的加载顺序。
2、请求进入之后的逻辑类似,先进入 doFilterInternal() 方法,先判断请求是否合法,如果不合法就不进行日志打印。
3、然后将 HttpServletRequest 转换为 RequestWrapper 对象,其实RequestWrapper对象是自定义对象,继承了 ContentCachingRequestWrapper 对象。所以实际上还是转换成了 ContentCachingRequestWrapper 对象,主要是为了缓存请求数据。
>
> `RequestWrapper` 的类,这个类继承自 `ContentCachingRequestWrapper`,并且用于包装 `HttpServletRequest` 对象。
>
>
> **主要功能:**
>
>
> 1. **处理请求体内容**:
>
>
> * 当请求的 `Content-Type` 是 `multipart/form-data` 时,通过 `request.getParts()` 获取多部分请求的所有 `Part` 对象。
> * 否则,将请求体内容读取一次并缓存起来,以便后续的读取。
> 2. **提供方法获取请求体内容**:
>
>
> * `getBody()`:返回请求体内容的字符串表示。
> * `getInputStream()`:返回输入流,可以用于读取请求体内容。
> * `getReader()`:返回字符流的 `BufferedReader` 对象,可以用于读取请求体内容。
>
>
> **代码解析:**
>
>
> 1. 构造方法 `RequestWrapper(HttpServletRequest request)`:
>
>
> * 根据请求的 `Content-Type` 来判断是否是 `multipart/form-data` 类型的请求。
> * 如果是 `multipart/form-data`,则调用 `request.getParts()` 获取所有的 `Part` 对象。
> * 否则,读取一次请求体内容,并将其缓存在 `body` 数组中。
> 2. `getParts()` 方法:
>
>
> * 如果是 `multipart/form-data` 请求,则直接返回之前获取的 `parts` 集合。
> * 否则,返回 `null`。
> 3. `getBody()` 方法:
>
>
> * 返回请求体内容的字符串表示,首先判断 `body` 数组是否为空,然后将其转换为字符串返回。
> 4. `getInputStream()` 方法:
>
>
> * 如果 `inputStream` 不为空,则直接返回该输入流。
> * 否则,创建一个新的 `RequestCachingInputStream` 对象,并将之前缓存的 `body` 数组传入其中。
> 5. `getReader()` 方法:
>
>
> * 如果 `reader` 为空,则创建一个新的 `BufferedReader` 对象,并使用 `inputStream` 创建。
> * 否则,直接返回之前创建的 `reader` 对象。
> 6. `RequestCachingInputStream` 内部类:
>
>
> * 继承自 `ServletInputStream`,用于提供一个包装 `body` 数组的输入流。
> * 实现了 `read()` 方法,用于读取字节数据。
> * 实现了 `isFinished()` 方法,用于判断输入流是否已经读取完毕。
> * 实现了 `isReady()` 方法,始终返回 `true`。
> * 实现了 `setReadListener()` 方法,空实现。
>
>
>
4、缓存完请求对象的内容之后, 使用consoleRequestLog()方法打印日志,在这个方法中,它记录了 请求路径:[{}] 、 请求方法:[{}]、请求IP:[{}]、请求参数:{} 、 请求Body:{},request.getParameterMap() 就是专门获取get请求 请求参数的。
其中 请求Body 需要使用 getRequestBody 方法获取,这个方法中主要是这个判断需要说一下:
if (StrUtil.isNotBlank(requestBody) && (JSONValidator.from(requestBody).validate() || (requestBody.startsWith(“<”) && requestBody.endsWith(“>”))))
这个判断主要是为了 requestBody 不为空时,且当请求体是 json数据时,或xml数据时才进行打印。
* `JSONValidator.from(requestBody).validate()`:使用 JSON 格式验证器验证请求体内容是否是有效的 JSON 格式。
* `(requestBody.startsWith("<") && requestBody.endsWith(">"))`:检查请求体内容是否以 `<` 开头且以 `>` 结尾。
响应数据同理!
#### 三、MDC:
>
> `MDC`,即 `Mapped Diagnostic Context`,是 logback 日志框架提供的一种上下文信息存储的机制,可以在日志输出中方便地添加和显示额外的上下文信息。
>
>
>
> MDC 的作用:
>
>
> MDC 允许你在一个线程中存储一些额外的信息,并在该线程执行的任何代码中访问这些信息。这些信息可以是任何与日志记录相关的数据,比如用户 ID、请求 ID、会话 ID 等。通过 MDC,你可以将这些信息附加到日志消息中,使日志更加丰富和有用。
>
>
>
简单说一下使用 MDC 添加日志链路追踪id 和 ip 以及 userAgent
import cn.hutool.core.lang.UUID;
import cn.hutool.http.Header;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.util.WebUtils;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@WebFilter(filterName = “logRequestIdFilter”, urlPatterns = “/*”)
@Order(Integer.MIN_VALUE)
public class LogRequestIdFilter implements Filter {
public static final String TRACE_ID = "traceId";
public static final String IP = "ip";
public static final String CLIENT_INFO = "client_info";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
String requestId = httpRequest.getHeader(TRACE_ID);
String userAgent = httpRequest.getHeader(Header.USER_AGENT.getValue());
String realIp = RemortIPUtil.getRealIp(httpRequest);
if (requestId == null) {
requestId = UUID.randomUUID().toString();
}
MDC.put(TRACE_ID, requestId);
MDC.put(IP,realIp);
MDC.put(CLIENT_INFO,userAgent);
httpResponse.setHeader(TRACE_ID, requestId);
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
MDC.clear();
}
}
}
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Pattern;
@Slf4j
public class RemortIPUtil {
// 将长Ip截取
public static String getRemortIP(HttpServletRequest request) {
if (request.getHeader(“x-forwarded-for”) == null) {
return request.getRemoteAddr();
}
// 获得反向代理IP
String ip = request.getHeader(“x-forwarded-for”);
if (null!=ip&&“”!=ip) {
if (ip.indexOf(“,”) > -1) {
ip = ip.substring(0, ip.indexOf(“,”));
}
}
return ip;
}
public static String getRemortIPLong(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)) {
最后
小编在这里分享些我自己平时的学习资料,由于篇幅限制,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
开源分享:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】
程序员代码面试指南 IT名企算法与数据结构题目最优解
这是” 本程序员面试宝典!书中对IT名企代码面试各类题目的最优解进行了总结,并提供了相关代码实现。针对当前程序员面试缺乏权威题目汇总这一-痛点, 本书选取将近200道真实出现过的经典代码面试题,帮助广“大程序员的面试准备做到万无一失。 “刷”完本书后,你就是“题王”!
《TCP-IP协议组(第4版)》
本书是介绍TCP/IP协议族的经典图书的最新版本。本书自第1版出版以来,就广受读者欢迎。
本书最新版进行」护元,以体境计算机网络技不的最新发展,全书古有七大部分共30草和7个附录:第一部分介绍一些基本概念和基础底层技术:第二部分介绍网络层协议:第三部分介绍运输层协议;第四部分介绍应用层协议:第五部分介绍下一代协议,即IPv6协议:第六部分介绍网络安全问题:第七部分给出了7个附录。
Java开发手册(嵩山版)
这个不用多说了,阿里的开发手册,每次更新我都会看,这是8月初最新更新的**(嵩山版)**
MySQL 8从入门到精通
本书主要内容包括MySQL的安装与配置、数据库的创建、数据表的创建、数据类型和运算符、MySQL 函数、查询数据、数据表的操作(插入、更新与删除数据)、索引、存储过程和函数、视图、触发器、用户管理、数据备份与还原、MySQL 日志、性能优化、MySQL Repl ication、MySQL Workbench、 MySQL Utilities、 MySQL Proxy、PHP操作MySQL数据库和PDO数据库抽象类库等。最后通过3个综合案例的数据库设计,进步讲述 MySQL在实际工作中的应用。
Spring5高级编程(第5版)
本书涵盖Spring 5的所有内容,如果想要充分利用这一领先的企业级 Java应用程序开发框架的强大功能,本书是最全面的Spring参考和实用指南。
本书第5版涵盖核心的Spring及其与其他领先的Java技术(比如Hibemate JPA 2.Tls、Thymeleaf和WebSocket)的集成。本书的重点是介绍如何使用Java配置类、lambda 表达式、Spring Boot以及反应式编程。同时,将与企业级应用程序开发人员分享一些见解和实际经验,包括远程处理、事务、Web 和表示层,等等。
JAVA核心知识点+1000道 互联网Java工程师面试题
企业IT架构转型之道 阿里巴巴中台战略思想与架构实战
本书讲述了阿里巴巴的技术发展史,同时也是-部互联网技 术架构的实践与发展史。
本书第5版涵盖核心的Spring及其与其他领先的Java技术(比如Hibemate JPA 2.Tls、Thymeleaf和WebSocket)的集成。本书的重点是介绍如何使用Java配置类、lambda 表达式、Spring Boot以及反应式编程。同时,将与企业级应用程序开发人员分享一些见解和实际经验,包括远程处理、事务、Web 和表示层,等等。
[外链图片转存中…(img-NTs7dlMZ-1715298794517)]
JAVA核心知识点+1000道 互联网Java工程师面试题
[外链图片转存中…(img-JWkDmJHQ-1715298794517)]
[外链图片转存中…(img-efcdPQdu-1715298794518)]
企业IT架构转型之道 阿里巴巴中台战略思想与架构实战
本书讲述了阿里巴巴的技术发展史,同时也是-部互联网技 术架构的实践与发展史。
[外链图片转存中…(img-g3YXt71J-1715298794518)]