background
Taking the microservice system built by SpringCloud as an example, using the front-end and back-end separated architecture, each system will provide some general request parameters, such as system version information and IMEI information on the mobile end, IP information on the Web end, browser version information, etc. these parameters may be placed in the header or in the parameters, If these parameters need to be declared and defined in each method, the workload is too large, and the coupling between these general parameters and business interface methods is too tight, which itself is a bad design.
How can this problem be solved gracefully?
Best practices
Realization idea
- Use the interceptor provided by spring MVC to extract the general header information for the matching request (assuming that all the general fields are placed in the header)
- Separate each requested information from each other without interference.
- When the Controller layer is used, the general header information can be extracted from the request thread (http thread) for use.
- When the request thread completes, the corresponding header header information object needs to be recycled and destroyed.
Implementation mode
- The HandlerInterceptorAdapter provided by spring MVA can be used and implemented by inheritance.
- Use ThreadLocal to record the information of each request. ThreadLocal has the function of isolating thread variables.
Source code implementation and comments of HandlerInterceptorAdapter
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // It is called before the business interface method is processed, and the general header information can be extracted here return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { // This method is called after the execution of the business interface method and before generating the spring MVC modelandview // In today's case, we do not use this method, so it can not be implemented. } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { // This method is called after the DispatcherServlet is fully processed, and the contents of ThreadLocal can be released here } @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // This method is used to handle asynchronous active, but it will also call preHandle first and then execute this method. After the asynchronous thread is completed, it will execute the postHandle and afterCompletion methods, which are not used here for the time being. } }
ThreadLocal source code, main implementation and comments
public class ThreadLocal<T> { protected T initialValue() { return null; } public T get() { // Gets the current thread Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } public void set(T value) { // Gets the current thread Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } }
In short, the key get() and set() methods of ThreadLocal operate against the current thread. When calling the set() method, put the value into threadmap (an implementation of map), take the hash value of the current thread as the key, and the get() method takes the current thread as the key, so as to realize the effect of data isolation of each thread.
In addition, the guide diagram of ThreadLocal class source code interpretation is attached for reference only
Case practice
We simplify the actual business system, assuming that the header information is fixed with ip, uid and deviceId, and start the case demonstration according to the above implementation idea.
DTO definition
General header information, encapsulated by Dto object:
@Data public class CommonHeader implements Serializable { private static final long serialVersionUID = -3949488282201167943L; /** * Real ip */ private String ip; /** * Device id */ private String deviceId; /** * User uid */ private Long uid; // Omit getter/setter / constructor }
Define the encapsulation class Dto of the Request and introduce ThreadLocal:
/** * Put the public request header information in ThreadLocal */ public class RequestWrap { private static ThreadLocal<CommonHeader> current = new ThreadLocal<>(); /** * Gets the static ThreadLocal object * @return */ public static ThreadLocal<CommonHeader> getCurrent() { return current; } /** * Get ip * @return */ public static String getIp() { CommonHeader request = current.get(); if (request == null) { return StringUtils.EMPTY; } return request.getIp(); } /** * Get uid * @return */ public static Long getUid() { CommonHeader request = current.get(); if (request == null) { return null; } return request.getUid(); } /** * Get encapsulated object * @return */ public static CommonHeader getCommonReq() { CommonHeader request = current.get(); if (request == null) { return new CommonHeader(StringUtils.EMPTY, StringUtils.EMPTY,0L); } return request; } }
Tool class
Here, a simple tool class is added to generate the CommonHeader class from HttpServletRequest through the getHeader method:
public class HttpUtil { /** * Get request header information * * @param request * @return */ public static CommonHeader getCommonHeader(HttpServletRequest request) { String UID = request.getHeader("uid"); Long uid = null; if (StringUtils.isNotBlank(UID)) { uid = Long.parseLong(UID); } return new CommonHeader(HttpUtil.getIp(request), request.getHeader("deviceId"), uid); } /** * Get IP * * @param request * @return */ public static String getIp(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if (null != ip && !"".equals(ip.trim()) && !"unknown".equalsIgnoreCase(ip)) { int index = ip.indexOf(','); if (index != -1) { return ip.substring(0, index); } else { return ip; } } ip = request.getHeader("X-Real-IP"); if (null != ip && !"".equals(ip.trim()) && !"unknown".equalsIgnoreCase(ip)) { return ip; } return request.getRemoteAddr(); } }
Interceptor class implementation
The core implementation finally comes out. Here we inherit the HandlerInterceptorAdapter and simplify it:
/** * Request header processing * * @author yangfei */ @Component public class BaseInterceptor extends HandlerInterceptorAdapter { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BaseInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { RequestWrap.getThreadLocal().set(HttpUtil.getCommonHeader(request)); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { RequestWrap.getThreadLocal().remove(); } @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { } }
The logic described in the previous chapter encapsulates the IP, uid and deviceid in the request into the RequestWrap object in the preHandle method, and releases the ThreadLocal value of the thread in afterCompletion.
Use of business interface methods
In the interface method of the Controller class, if you want to obtain uid information, you only need to call requestwrap The getuid () method is enough. It is no longer necessary to declare uid parameters on each interface, as shown in the following example:
/** * Get user basic information */ @PostMapping(value = "/user/info") public Response<UserInfo> getUserInfo() { return userManager.getUserInfo(RequestWrap.getUid()); }
summary
The goal of this actual combat is to solve the problem of repeated definition of general header information in the interface. It is realized based on the implementation of HandlerInterceptorAdapter interceptor and the isolation of thread access data by ThreadLocal. It has a good reference significance in the actual production project application and hopes to be helpful to you.
last
Technology has no end and can't be learned. The most important thing is to live and not be bald. When starting with zero foundation, I read books or watch videos. I think adults, why do they have to do multiple-choice questions? Both. If you like reading, you can read. If you like watching videos, you can watch videos. The most important thing is that in the process of self-study, we must not aim high but practice low, put the learned technology into the project, solve problems, and then further refine our own technology.
After learning the skills, you should start to prepare for the interview. When looking for a job, you must prepare your resume. After all, your resume is the stepping stone to find a job. In addition, you should do more interview questions and review and consolidate. Friends who need information about interview questions Click here to get it for free.
last
Technology has no end and can't be learned. The most important thing is to live and not be bald. When starting with zero foundation, I read books or watch videos. I think adults, why do they have to do multiple-choice questions? Both. If you like reading, you can read. If you like watching videos, you can watch videos. The most important thing is that in the process of self-study, we must not aim high but practice low, put the learned technology into the project, solve problems, and then further refine our own technology.
After learning the skills, you should start to prepare for the interview. When looking for a job, you must prepare your resume. After all, your resume is the stepping stone to find a job. In addition, you should do more interview questions and review and consolidate. Friends who need information about interview questions Click here to get it for free.
[external chain picture transferring... (img-erbizg5W-1627098778395)]