將一個@RequestMapping定義的方法對映為兩個http服務----記一次有趣的排查問題過程
阿新 • • 發佈:2021-01-16
小夥伴遇到個問題,某個controller釋出的http服務直接訪問沒問題,通過nginx轉發後就報404,此模組其他url訪問都正常。。
controller程式碼如下:
springboot應用,未配置上下文根。所以此http服務未使用nginx轉發時的直接訪問地址為:http://localhost:8299/addrExport.spr?method=exportDynamicQueryData
前臺ajax呼叫url:
nginx對映路徑:
採用方案2修改nginx配置驗證後發現確實能解決問題。但問題來了。。。。
修改nginx轉發規則後,此模組原來正常訪問的的功能應該報錯才對,但事實上這些請求順暢無比,彷彿世界從未發生改變,也就是說這些url在nginx轉發規則增加space-addr和去掉space-addr時都可以正常訪問。這真的不科學。。。
。。此處省略走過的彎路。。。
抓包查看了這些訪問正常的功能對應的請求,發現都是一樣的url,只是引數不同:http://localhost:8888/portal/space-addr/rescommon/service/callServerFunction
檢視此url對應的controller(ResCommonServiceController,隱藏特別深,由公共模組提供,見下圖)發現:類上無@RequestMapping註解,也無@Controller或者@RestController註解,方法上此註解的value=callServerFunction。而根據nginx轉發規則,此註解的value至少應該包含rescommon/service/callServerFunction。那麼缺失的rescommon/service是在哪裡被拼接的呢?
@RequestMapping("/addrExport.spr") public class AddrExportController @RequestMapping(params = "method=exportDynamicQueryData") public void exportDynamicQueryData(HttpServletRequest request, HttpServletResponse response, String downLoadData) { System.out.println("hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh"); ..... }
server { listen 8888; ...... location /portal/space-addr/ { proxy_pass http://localhost:8299/space-addr/; } ..... }
使用nginx埠訪問的url如下,就是此url訪問404 http://localhost:8888/portal/space-addr/addrExport.spr?method=exportDynamicQueryData 一陣兵荒馬亂,小夥伴發現此url轉發到後臺的路徑為 http://localhost:8299/space-addr/addrExport.spr?method=exportDynamicQueryData 多了一個space-addr 此時有兩種方案, 方案1.在controller裡@RequestMapping時增加space-addr字首 方案2.修改nginx對映去掉proxy_pass中多餘的字首,注意紅色字型部分去掉了space-addr,見下文:
location /portal/space-addr/ {
proxy_pass http://localhost:8299/;
}
感謝spring的日誌輸出,偶然搜尋發現瞭如下內容,什麼鬼,竟然增加了兩個rescommon/service/callServerFunction相關的requestmapping:一個有space-addr字首,一個無字首。。 最終發現公共側對ResCommonServiceController做了特殊處理,具體見CommonConfig類的registerCommonServerMapping方法(見下圖),預設會生成兩個requestmapping: 1./rescommon/service/callServerFunction 2.${pub.commonservice.prefix}/rescommon/service/callServerFunction,對於排查問題的這個模組存在配置pub.commonservice.prefix=space-addr,最終效果為space-addr/rescommon/service/callServerFunction 正是因為生成了兩個requestmapping,所以才會出現nginx裡轉發規則配或不配space-addr都沒問題的情況,因為都能匹配到requestmapping。。
類全文如下:
1 @Configuration 2 public class CommonConfig { 3 4 @Value("${pub.commonservice.prefix}") 5 String prefix; 6 7 @Bean 8 public ResCommonServiceController registerCommonServiceController() { 9 return new ResCommonServiceController(); 10 } 11 12 @Autowired 13 public void registerCommonServerMapping(ResCommonServiceController registerCommonServiceController, RequestMappingHandlerMapping mapping) { 14 String uri = "/rescommon/service/"; 15 RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths(uri).build(); 16 ResRequestMappingHandlerMapping resRequestMappingHandlerMapping = new ResRequestMappingHandlerMapping(mapping, requestMappingInfo); 17 resRequestMappingHandlerMapping.detectHandlerMethods(registerCommonServiceController); 18 if (!("".equals(prefix) || prefix == null || "${pub.commonservice.prefix}".equals(prefix))) { 19 String[] split = prefix.split(","); 20 for (String prefix1 : split) { 21 ResRequestMappingHandlerMapping resRequestMappingHandlerMapping1 = new ResRequestMappingHandlerMapping(mapping, RequestMappingInfo.paths(prefix1 + uri).build()); 22 resRequestMappingHandlerMapping1.detectHandlerMethods(registerCommonServiceController); 23 } 24 } 25 } 26 }這個寫法很有趣,第一次見。。自定義的ResRequestMappingHandlerMapping類擴充套件了spring的RequestMappingHandlerMapping,覆寫了detectHandlerMethods方法,在registerMapping時將自定義的url字首和method上的url做拼接。
1 public class ResRequestMappingHandlerMapping extends RequestMappingHandlerMapping { 2 3 /** 4 * 代理物件 5 */ 6 private RequestMappingHandlerMapping requestMappingHandlerMapping; 7 8 /** 9 * 存放類的RequestMapping資訊 10 */ 11 RequestMappingInfo typeMapping; 12 13 public ResRequestMappingHandlerMapping(RequestMappingHandlerMapping requestMappingHandlerMapping, RequestMappingInfo typeMapping) { 14 this.requestMappingHandlerMapping = requestMappingHandlerMapping; 15 this.typeMapping = typeMapping; 16 } 17 18 @Override 19 public void detectHandlerMethods(final Object handler) { 20 if(handler == null){ 21 return; 22 } 23 Class<?> handlerType = (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass()); 24 final Class<?> userType = ClassUtils.getUserClass(handlerType); 25 26 Map<Method, RequestMappingInfo> methods = MethodIntrospector.selectMethods(userType, new MethodIntrospector.MetadataLookup<RequestMappingInfo>() { 27 public RequestMappingInfo inspect(Method method) { 28 try { 29 return getMappingForMethod(method, userType); 30 } 31 catch (Exception ex) { 32 throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex); 33 } 34 } 35 }); 36 37 if (logger.isDebugEnabled()) { 38 logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods); 39 } 40 for (Map.Entry<Method, RequestMappingInfo> entry : methods.entrySet()) { 41 Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType); 42 RequestMappingInfo mapping = entry.getValue(); 43 if (typeMapping != null) { 44 mapping = typeMapping.combine(mapping); 45 } 46 requestMappingHandlerMapping.registerMapping(mapping, handler, invocableMethod); 47 } 48 }
至此,謎題解開了,因為公共側對ResCommonServiceController類做了特殊定製,使其釋出的@RequestMapping方法釋出了兩個http服務。。所以nginx的轉發規則帶不帶字首都不影響功能使用。。