spring mvc或spring boot使用RequestMappingHandlerMapping自定义统一处理前后端接口不一致问题
场景
以前老项目漏洞修复升级,前端请求有些带.do后缀,有些没带,后端controller接口也是有些带.do后缀,有些没带,而且他们不是一一对应,不知以前是怎么跑得起来,升级之后就很多接口就404了,还无法全局替换修复,因为业务复杂涉及权限等,短时间无法哈清楚,所以只能考虑兼容方案
方式一
修改后端接口,让所有的都支持两种访问方式:@RequestMapping(value = { "/item/index", "/product/index.do" })
,工作量超大
方式二
使用面向切面思想,只需要实现RequestMappingHandlerMapping
统一处理,让每个接口方法覆盖两种url方式,适用于 spring mvc 和 spring boot ,代码如下:
package me.muphy;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
@Component
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected boolean isHandler(Class<?> beanType) {
return super.isHandler(beanType);
}
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
if (info == null) {
return info;
}
try {
PatternsRequestCondition patternsCondition = info.getPatternsCondition();
if (patternsCondition == null) {
return info;
}
Set<String> oldPatterns = patternsCondition.getPatterns();
//Set<String> patterns = new HashSet<>();
for (String pattern : oldPatterns) {
if (pattern.toLowerCase(Locale.ROOT).endsWith(".do")) {
String p = pattern.replaceAll("\\.[dD][Oo]$", "");
oldPatterns.add(p); // set去重
} else {
if (!pattern.matches(".+\\.\\w+$")) {
String p = pattern + ".do";
oldPatterns.add(p);
}
}
}
for (String pattern : oldPatterns) {
System.out.println("pattern = " + pattern);
}
} catch (Exception e) {
e.printStackTrace();
logger.error(e);
}
//PatternsRequestCondition patternsRequestCondition = new PatternsRequestCondition(
// patterns.toArray( new String[]{} ), null, null, true, true, null );
//RequestMappingInfo mappingInfo = new RequestMappingInfo(
// null, patternsRequestCondition, info.getMethodsCondition(), info.getParamsCondition(), info.getHeadersCondition(), info.getConsumesCondition(),
// info.getProducesCondition(), info.getCustomCondition()
//);
return info;
}
}
方式三
上面的方案对处理当前的问题很好,不过如果复杂一点可能会出现地址冲突,使用 @PostConstruct 等实现延时注册避免冲突
package mu.muphy;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.annotation.PostConstruct;
import java.util.*;
@RestController
@RequestMapping("url")
public class UrlHandleController {
@Autowired
private RequestMappingHandlerMapping mapping;
// 同一个对象追加
@PostConstruct
public void register() {
if (mapping == null) {
return;
}
// 获取所有的url
Set<String> allUrls = new HashSet<>();
Map<RequestMappingInfo, HandlerMethod> methods = mapping.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> m : methods.entrySet()) {
RequestMappingInfo info = m.getKey();
for (String pattern : info.getPatternValues()) {
allUrls.add(pattern);
}
}
// 注册新的url
for (Map.Entry<RequestMappingInfo, HandlerMethod> m : methods.entrySet()) {
RequestMappingInfo info = m.getKey();
PatternsRequestCondition patternsCondition = info.getPatternsCondition();
Set<String> patterns = patternsCondition.getPatterns();
Set<String> ps = new HashSet<>();
for (String pattern : patterns) {
if (pattern.toLowerCase(Locale.ROOT).endsWith(".do")) {
String p = pattern.replaceAll("\\.[dD][Oo]$", "");
if (!allUrls.contains(p)) {
ps.add(p); // set去重
}
} else {
if (!pattern.matches(".+\\.\\w+$")) {
String p = pattern + ".do";
if (!allUrls.contains(p)) {
ps.add(p); // set去重
}
}
}
}
patterns.addAll(ps);
}
}
// 才分成两个对象
//@PostConstruct
public void register2() {
if (mapping == null) {
return;
}
// 获取所有的url
Set<String> allUrls = new HashSet<>();
Map<RequestMappingInfo, HandlerMethod> methods = mapping.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> m : methods.entrySet()) {
RequestMappingInfo info = m.getKey();
for (String pattern : info.getPatternValues()) {
allUrls.add(pattern);
}
}
// 注册新的url
for (Map.Entry<RequestMappingInfo, HandlerMethod> m : methods.entrySet()) {
RequestMappingInfo info = m.getKey();
HandlerMethod handlerMethod = m.getValue();
Set<String> patterns = new HashSet<>();
for (String pattern : info.getPatternValues()) {
if (pattern.toLowerCase(Locale.ROOT).endsWith(".do")) {
String p = pattern.replaceAll("\\.[dD][Oo]$", "");
if (!allUrls.contains(p)) {
patterns.add(p); // set去重
}
} else {
if (!pattern.matches(".+\\.\\w+$")) {
String p = pattern + ".do";
if (!allUrls.contains(p)) {
patterns.add(p); // set去重
}
}
}
}
if (!patterns.isEmpty()) {
try {
PatternsRequestCondition patternsRequestCondition = new PatternsRequestCondition(
patterns.toArray(new String[]{}), null, null, true, true, null);
RequestMappingInfo mappingInfo = new RequestMappingInfo(
null, patternsRequestCondition, info.getMethodsCondition(), info.getParamsCondition(), info.getHeadersCondition(), info.getConsumesCondition(),
info.getProducesCondition(), info.getCustomCondition()
);
mapping.registerMapping(mappingInfo, handlerMethod.getBean(), handlerMethod.getMethod());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@GetMapping("/list")
public List<Map<String, String>> getAllUrl(String parentUrl) {
if (mapping == null) {
return new ArrayList<>();
}
Map<RequestMappingInfo, HandlerMethod> methods = mapping.getHandlerMethods();
List<Map<String, String>> list = new ArrayList<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> m : methods.entrySet()) {
Map<String, String> map = new HashMap<>();
RequestMappingInfo info = m.getKey();
HandlerMethod method = m.getValue();
PatternsRequestCondition p = info.getPatternsCondition();
map.put("className", method.getMethod().getDeclaringClass().getName()); // 类名
map.put("method", method.getMethod().getName()); // 方法名
map.put("url", Arrays.toString(p.getPatterns().toArray()));
map.put("parameterTypes", JSON.toJSONString(method.getMethod().getParameterTypes()));
RequestMethodsRequestCondition methodsCondition = info.getMethodsCondition();
for (RequestMethod requestMethod : methodsCondition.getMethods()) {
map.put("type", requestMethod.toString());
}
if (!StringUtils.isEmpty(map.get("url")) && (StringUtils.isEmpty(parentUrl) || map.get("url").contains(parentUrl))) {
list.add(map);
}
}
return list;
}
}
测试
var myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer 6d611410-60ee-45e6-b64d-85b500a7eb50");
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
fetch("http://localhost:8881/url/list.do", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));