赞
踩
一,背景
业务存在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方法。也就是说这个方法就是关键。
后续的就不写了。哈哈哈哈哈
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。