Interception and processing of common request fields of SpringCloud in actual combat

background

Taking the microservice system built by SpringCloud as an example, using a front-end and back-end separated architecture, each system will provide some general request parameters, such as system version information of the mobile terminal, IMEI information, IP information of the Web terminal, 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 is a bad design in itself.

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 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 initiative, but it will also call preHandle first and then execute this method. After the asynchronous thread is completed, it will execute 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
}

The dthread Request class is defined and introduced:

/**
 * Put the public request header information in ThreadLocal
 */
public class RequestWrap {

	private static ThreadLocal<CommonHeader> current = new ThreadLocal<>();

    /**
	 * Get 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 method

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 basic user 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.

Recommended reading:

last

After reading the above knowledge points, if you feel that the foundation of Java is not solid enough, or you don't brush enough questions and your knowledge is not comprehensive

Xiaobian has specially customized a set of < < analysis collection of interview questions for senior positions of large factories in the front line of JAVA: JAVA foundation - intermediate - Advanced interview + SSM framework + distributed + performance tuning + microservice + concurrent programming + Network + design pattern + data structure and algorithm >

Don't panic if you don't have enough knowledge! There is also a complete set of < Java core advanced manual >, which can instantly check and fill gaps

All of them are collected and sorted out one by one. They are made by hand - collected and sorted out*** [my study notes] ***, friends in need can take it from themselves

There are also pure hand-painted outlines of major knowledge systems for combing: xmind hand-painted maps of Java foundation, MySQL, Redis, concurrent programming, Spring, distributed high-performance architecture knowledge, micro service architecture knowledge, open source framework knowledge points, etc~

18758147)]

Don't panic if you don't have enough knowledge! There is also a complete set of < Java core advanced manual >, which can instantly check and fill gaps

[external chain picture transferring... (img-ztDEHhYJ-1623618758147)]

All of them are collected and sorted out one by one. They are made by hand - collected and sorted out*** [my study notes] ***, friends in need can take it from themselves

There are also pure hand-painted outlines of major knowledge systems for combing: xmind hand-painted maps of Java foundation, MySQL, Redis, concurrent programming, Spring, distributed high-performance architecture knowledge, micro service architecture knowledge, open source framework knowledge points, etc~

[external chain picture transferring... (img-vXprWGpP-1623618758149)]

Keywords: Java Interview Programmer

Added by whizzkid-000 on Sat, 29 Jan 2022 21:48:13 +0200