Actual SpringCloud general request field interception processing

background

Taking the microservice system built by SpringCloud as an example, using the front-end and back-end separation architecture, each system will provide some common request parameters, such as system version information, IMEI information on the mobile side, IP information on the Web side, browser version information, etc. These are The 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 will be too much, and the coupling between these general parameters and the business interface method is too tight, which is itself an inconvenience. good design.

How can this problem be solved elegantly?

Best Practices

Realize ideas

  • Use SpringMVC to provide interceptors to extract common header information for matching requests (assuming that all common fields are placed in the header)
  • Isolate each requested information separately and do not interfere with each other.
  • When the Controller layer is used, the general header information can be extracted and used in the request thread (http thread).
  • When the request thread is completed, the corresponding header information object needs to be recycled and destroyed.

Method to realize

  • The HandlerInterceptorAdapter provided by SpringMVA can be used and implemented by inheritance.
  • Use ThreadLocal to record information about each request, and ThreadLocal has the function of isolating thread variables.
Source code implementation and annotation of HandlerInterceptorAdapter
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
        // Called before the business interface method is processed, 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 business interface method is executed and before the SpringMVC ModelAndView is generated
		// In today's case, we do not use this method, so it is not necessary to implement it.
	}
	
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
		// This method is called after the DispatcherServlet is completely processed, and the content 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, the postHandle and afterCompletion methods will be executed, which are temporarily unavailable here.
	}
}
Main implementation and annotation of ThreadLocal source code
public class ThreadLocal<T> {
    
    protected T initialValue() {
        return null;
    }

    public T get() {
		// get 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) {
		// get 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);
    }
}

To put it simply, the most critical get() and set() methods of ThreadLocal operate on the current thread. When the set() method is called, the value is placed in ThreadMap (an implementation of Map), and the current thread's The hash value is the key, and the get() method uses the current thread as the key to obtain the value, so that the data of each thread is isolated.

Attached is a map of the source code interpretation of the ThreadLocal class for reference only

Case combat

We simplify the actual business system, assuming that the header information is fixed with three pieces of information: ip, uid, and deviceId, and start the case demonstration according to the above implementation ideas.

DTO definition

General header information, encapsulated with Dto objects:

@Data
public class CommonHeader implements Serializable {

	private static final long serialVersionUID = -3949488282201167943L;
	
	/**
	 * real ip
 	 */
	private String ip;

	/**
	 * device id
 	 */
	private String deviceId;

	/**
	 * useruid
 	 */
	private Long uid;
	
	// Omit getter/setter/constructor
}

Define the encapsulation class Dto of the Request request and introduce ThreadLocal:

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

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

    /**
	 * Get a 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 the encapsulated object
	 * @return
	 */
	public static CommonHeader getCommonReq() {
		CommonHeader request = current.get();
		if (request == null) {
			return new CommonHeader(StringUtils.EMPTY, StringUtils.EMPTY,0L);
		}
		return request;
	}
}

Tools

Add a simple tool class here, pass HttpServletRequest through the getHeader method, and generate the CommonHeader class:

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 came out, here inherits HandlerInterceptorAdapter, here is simplified processing:

/**
 * 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 {
	}
}

As the logic described in the previous chapter, encapsulate the ip,uid,deviceId in the request into the RequestWrap object in the preHandle method, and release 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 the uid information, you only need to call the RequestWrap.getUid() method, and you no longer need to declare the uid parameter 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());
}

Summarize

The goal of this actual combat is to solve the problem of the repeated definition of general header information in the interface. Based on the implementation of the HandlerInterceptorAdapter interceptor, ThreadLocal isolates the thread access data to achieve, and has a good reference in the actual production project application. you are helpful.

Focus on Java high concurrency and distributed architecture, more technical dry goods sharing and experience, please pay attention to the public number: Java Architecture Community You can scan the QR code on the left to add friends and invite you to join the Java Architecture Community WeChat group to discuss technologies together

Tags: Java

Posted by supergrame on Tue, 31 May 2022 21:57:37 +0530