过滤器实现URL拦截,跳转URL
一,背景
业务存在pc和pad两个客户端,pc已经上线。并且pc和pad大部分的接口都可以复用,为了避免重复的代码写2份(即相同的controller),所以目标是pc和pad共用controller,至于pad新的接口,则pad专属。
二,实现
为了实现pc和pad接口公用,pad会在url上传入特定的前缀,比如/pad。
使用过滤器,在DispatcherServlet#doDispatch方法执行前,将url进行替换。 AbstractHandlerMethodMapping的属性mappingRegistry,是保存了URL相关信息,以及handlerMethod的相关信息的,并且在Spring初始化的时候,在对象RequestMappingHandlerMapping里会被赋值。 于是替换思路就有了: 1.在过滤器中拦截url 2.如果URL不包含目标前缀,则FilterChain#doFilter正常放过 3.否则在Spring容器中取出, 并且通过该对象的getHandlerMethods(),拿到保存的URL相关的handlerMethodsMap 4.通过该map能找到信息的,就意味着是pad端独有的接口,通过FilterChain#doFilter正常放过 5.找不到信息的,就将URL中目标前缀替换为空后执行FilterChain#doFilter进行放过。
三,实现代码
拦截器:
@Slf4j
@Component
public class MyOncePerRequestFilter extends OncePerRequestFilter {
private static final String PREFIX = "/pad";
// 从spring里面拿 RequestMappingHandlerMapping
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String originalRequestURI = request.getRequestURI();
if (StrUtil.isNotEmpty(originalRequestURI)) {
// http://localhost:8002/v1/te/turn/get/query?item=95270244541
// 输出的结果:===== 获取的 requestURI:/v1/te/turn/get/query
System.out.println("===== 获取的 requestURI:" + originalRequestURI);
if (originalRequestURI.contains(PREFIX)) {
// {GET /v1/te/turn/get/query}
String httpMethodType = request.getMethod();
String queryURI = "{" + httpMethodType + " " + originalRequestURI + "}";
// 在DispatcherServlet存放urls里面找,找不到则去掉prefix
if (!MyUrlMappingContainer.urlMappingContainer.contains(queryURI)) {
String targetURI = originalRequestURI.replace(PREFIX, "");
StringBuffer requestURL = request.getRequestURL();
String tempURLStr = requestURL.toString();
StringBuffer stringBuilder = new StringBuffer(tempURLStr.replace(PREFIX, ""));
// 内部类的写法
final HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(request) {
@Override
public StringBuffer getRequestURL() {
return stringBuilder;
}
@Override
public String getRequestURI() {
return targetURI;
}
@Override
public String getServletPath() {
return targetURI;
}
};
filterChain.doFilter(requestWrapper, response);
return;
}
}
}
filterChain.doFilter(request, response);
}
}
封装的获取Spring初始的URL信息
@Order
@Component
public class MyUrlMappingContainer {
static final List<String> urlMappingContainer = new ArrayList<>();
@Autowired
public void initContainer(ApplicationHelper applicationHelper) {
RequestMappingHandlerMapping mapping = applicationHelper.getBeanByClass(RequestMappingHandlerMapping.class);
// Key 是个对象 requestMappingInfo
// RequestMappingInfo requestMappingInfo = new RequestMappingInfo();
Map handlerMethodsMap = mapping.getHandlerMethods();
for (Object obj : handlerMethodsMap.keySet()) {
urlMappingContainer.add(obj.toString());
}
System.out.println(JSON.toJSONString(urlMappingContainer));
}
}
ps: 如何找到RequestMappingHandlerMapping 类的 通过断点调试
在controller打上断点,比如: 然后找调用堆栈 就能找到DispatcherServlet这个分发器,以及在这个DispatcherServlet之前执行的filter(这个顺序就印证了过滤器是在请求分发前执行的)
找到这个方法,断点进入 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
往上找 找到这个方法 org.springframework.web.servlet.DispatcherServlet#getHandler
可以看出 mappedHandler = getHandler(processedRequest);这个方法,通过请求,返回了匹配的controller方法。也就是说这个方法就是关键。 后续的就不写了。哈哈哈哈哈
|