AI智能
改变未来

将一个@RequestMapping定义的方法映射为两个http服务—-记一次有趣的排查问题过程

小伙伴遇到个问题,某个controller发布的http服务直接访问没问题,通过nginx转发后就报404,此模块其他url访问都正常。。controller代码如下:

@RequestMapping(\"/addrExport.spr\")public class AddrExportController@RequestMapping(params = \"method=exportDynamicQueryData\")public void exportDynamicQueryData(HttpServletRequest request, HttpServletResponse response, String downLoadData) {System.out.println(\"hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh\");.....}

springboot应用,未配置上下文根。所以此http服务未使用nginx转发时的直接访问地址为:http://localhost:8299/addrExport.spr?method=exportDynamicQueryData前台ajax调用url:nginx映射路径:

server {listen       8888;......location /portal/space-addr/ {proxy_pass http://localhost:8299/space-addr/;}.....}

使用nginx端口访问的url如下,就是此url访问404http://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/;
}

采用方案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是在哪里被拼接的呢?

感谢spring的日志输出,偶然搜索发现了如下内容,什么鬼,竟然增加了两个rescommon/service/callServerFunction相关的requestmapping:一个有space-addr前缀,一个无前缀。。最终发现公共侧对ResCommonServiceController做了特殊处理,具体见CommonConfig类的registerCommonServerMapping方法(见下图),默认会生成两个requestmapping:1./rescommon/service/callServerFunction2.${pub.commonservice.prefix}/rescommon/service/callServerFunction,对于排查问题的这个模块存在配置pub.commonservice.prefix=space-addr,最终效果为space-addr/rescommon/service/callServerFunction正是因为生成了两个requestmapping,所以才会出现nginx里转发规则配或不配space-addr都没问题的情况,因为都能匹配到requestmapping。。

类全文如下:

1 @Configuration2 public class CommonConfig {34     @Value(\"${pub.commonservice.prefix}\")5     String prefix;67     @Bean8     public ResCommonServiceController registerCommonServiceController() {9         return new ResCommonServiceController();10     }1112     @Autowired13     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 {23     /**4      * 代理对象5      */6     private RequestMappingHandlerMapping requestMappingHandlerMapping;78     /**9      * 存放类的RequestMapping信息10      */11     RequestMappingInfo typeMapping;1213     public ResRequestMappingHandlerMapping(RequestMappingHandlerMapping requestMappingHandlerMapping, RequestMappingInfo typeMapping) {14         this.requestMappingHandlerMapping = requestMappingHandlerMapping;15         this.typeMapping = typeMapping;16     }1718     @Override19     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);2526         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         });3637         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的转发规则带不带前缀都不影响功能使用。。

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » 将一个@RequestMapping定义的方法映射为两个http服务—-记一次有趣的排查问题过程