Actual SpringCloud common request field interception processing

background

Take the microservice system built by SpringCloud as an example. Using a front-end and back-end separated architecture, each system will provide some common request parameters, such as the system version information of the mobile end, IMEI information, IP information of 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 will be too heavy, Second, these general parameters are too tightly coupled with business interface methods, which is a bad design in itself.

How can this problem be solved gracefully?

Best practices

Implementation ideas

  • Use the interceptor provided by SpringMVC to extract general header information for matching requests (assuming that all 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 SpringMVA 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 business interface method is executed and before SpringMVC ModelAndView is generated
		// 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. You can release the contents of ThreadLocal here
	}

	@Override
	public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response,
			Object handler) throws Exception {
		// This method is used to handle the asynchronous initiative, but it will also call the preHandle first, and then execute this method. After the asynchronous thread completes, it will execute the postHandle and afterCompletion methods, which are temporarily unavailable here.
	}
}
Main implementation and comments of ThreadLocal source code
public class ThreadLocal<T> {
    
    protected T initialValue() {
        return null;
    }

    public T get() {
		// Get 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 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 key get() and set() methods of ThreadLocal operate on the current thread. When calling the set() method, the value is placed in the threadmap (an implementation of map), and the hash value of the current thread is taken as the key. The get() method takes the current thread as the key, so as to achieve the effect that the data of each thread is isolated.

In addition, the guide diagram for source code interpretation of ThreadLocal class 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 ideas.

DTO definition

General header information is 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<>();

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

Tools

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 sufficient. 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 practice is to solve the problem of repeated definition of the general header information in the interface. It is realized based on the implementation of the HandlerInterceptorAdapter interceptor and the isolation of thread access data by ThreadLocal. It has a good reference in the actual production project application and hopes to be helpful to you.

Focus on Java high concurrency and distributed architecture, and share more technical dry goods and experiences. Please pay attention to official account: Java architecture community
You can scan the QR code on the left to add friends, and invite you to join the wechat group of the Java architecture community to discuss technology together

Tags: Spring Cloud

Posted by phpnewbie911 on Wed, 01 Jun 2022 08:15:18 +0530