种一棵树最好的时间是十年前,其次是现在;
前言
我们使用spring框架的时候写Controller层路径映射使用的是直接加在对应的path中:
/**
* @author zhaojm
* @date 2020-03-24 10:55
*/
@RestController
@RequestMapping(path = "hello")
@Api(value = "Hello", tags = {"helloController"})
public class HelloWorldController {
@Autowired
IUserInfoService userInfoService;
@GetMapping(path = "/version")
@ApiOperation("版本号")
public String getVersion(){
return "basic version 0.1";
}
@GetMapping(path = "/user")
@ApiOperation("根据Id取值用户信息")
public UserInfoDTO getUserInfoById(@RequestParam(value = "id") @ApiParam Integer id){
return userInfoService.getUserInfoById(id);
}
}
最终请求两个方法的接口路径就是:
getVersion() :ipAddress:port/hello/version
getUserInfoById(id):ipAddress:port/hello/version?id=1
我们是否使用一种方式把映射路径自动装载呢???
我们应该发现不带方法类型的@RequestMapping是类注解,带了方法类型的@RequestMapping(@GetMapping/@PostMapping/@DeleteMapping)是方法注解。我们是否可以直接使用类名和方法名直接做映射路径呢?
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
正文
1、找到框架实现
我们要确认Spring框架是在哪一步创建@RequestMapping映射的实例的呢?
我们首先找到Spring框架中的 Web Mvc配置支持类 WebMvcConfigurationSupport,再找到类中 创建请求映射处理程序映射使用的类是RequestMappingHandlerMapping
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
就找到了创建映射实例的类org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
其类定义是:
Creates {@link RequestMappingInfo} instances from type and method-level
{@link RequestMapping @RequestMapping} annotations in
{@link Controller @Controller} classes.
根据 @Controller 类中的类型和方法级别的 RequestMapping @RequestMapping 注解创建RequestMappingInfo 实例。
再看具体的实现方法:
/**
* 使用方法和类型级别的 @RequestMapping 注解创建RequestMappingInfo。
*/
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 根据方法名创建映射路径
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
// 如果类注解中有值则拼接 映射路径
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
/**
* 委托给{@link #createRequestMappingInfo(RequestMapping,RequestCondition)},
* 根据提供的{@code annotatedElement}是类还是方法,提供适当的自定义{@link RequestCondition}。
*/
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
/**
*从提供的{@link RequestMapping @RequestMapping}注解中创建一个{@link RequestMappingInfo},
*该注解可以是直接声明的注解,元注解,也可以是在注释层次结构中合并注释属性的综合结果。
* zhaojm
*/
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
// 根据注解中的值设置实例
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
return builder.options(this.config).build();
}
看代码可知具体的实现方法是 createRequestMappingInfo() 根据requestMapping注解中path的值创建的RequestMappingInfo映射路径;
最终我们运行时得到的路径就是
// spring 自定义错误路径映射
Adding key: [/error]
Adding key: [/error]
// swaager 自带的路径映射
Adding key: [/swagger-resources/configuration/security]
Adding key: [/swagger-resources/configuration/ui]
Adding key: [/swagger-resources]
// 真正自己写的路径
Adding key: [hello/user]
Adding key: [hello/version]
2、改成想要的实现
我们的目的就是把RequestMappingInfo.paths中的值改成我们想要的类名加方法名组成的映射路径。
实现方法:
1、重写 getMappingForMethod 方法,把映射值改成类名+方法名;
/**
* @author zhaojm
* @date 2020/4/8 22:18
*/
public class IntegrateRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private final static String PACKAGE_NAME = "com.zhaojm.study";
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = null;
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
if(null != requestMapping){
// 创建方法的映射路径
info = createRequestMappingInfo(requestMapping, method);
RequestMapping typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(handlerType, RequestMapping.class);
if (typeAnnotation != null) {
info = createRequestMappingInfo(typeAnnotation, handlerType).combine(info);
}
}
return info;
}
/**
* 创建方法的映射路径 创建类的映射路径
*/
private RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, AnnotatedElement element) {
if(null == requestMapping) {
return null;
}
RequestCondition<?> customCondition = null;
String[] patterns = resolveEmbeddedValuesInPatterns(requestMapping.path());
if(null != patterns && patterns.length == 0){
if(element instanceof Class){
// 如果是类名就取包路径
customCondition = getCustomTypeCondition((Class<?>) element);
Class<?> handlerType = (Class<?>) element;
if(handlerType.getName().startsWith(PACKAGE_NAME)) {
patterns = new String[]{this.classPathToUrl(handlerType.getName())};
}
}else{
//如果是方法取去方法名称
Method method = (Method) element;
customCondition = getCustomMethodCondition((Method) element);
if(method.getDeclaringClass().getName().startsWith(PACKAGE_NAME)) {
patterns = new String[]{method.getName()};
}
}
}
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(patterns))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
return builder.options(this.config).build();
}
private String classPathToUrl(String path) {
//1.去除包路径 2.去除controller包 3.类名称首字母转小写
//String typeValue = path.replaceAll("^com\\.xxxxxx\\.xxxx\\.[a-zA-z]+\\.|[c|C]ontroller[\\.]*", "");
String typeValue = path.replaceAll("^com\\.zhaojm\\.study\\.[a-zA-z]+\\.|[c|C]ontroller[.]*", "");
String[] typeValues = typeValue.split("\\.");
String lastStr = typeValues[typeValues.length - 1];
typeValues[typeValues.length - 1] = lastStr.substring(0, 1).toLowerCase() + lastStr.substring(1);
return StringUtils.join(typeValues, "/");
}
}
以 com.zhaojm.study.controller.HelloWorldController#getVersion 为例,去掉包名与XXXController的标志最终得到的映射路径就是 /HelloWorld/getVersion。
2、Web Mvc 使用自己写的映射实例类;
/**
* @author zhaojm
* @date 2020/4/7 22:55
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new IntegrateRequestMappingHandlerMapping();
}
}
具体效果
Controller:
类没有写映射路径,方法中getVersion写了,getUserInfoById没有写。
最终我们得到的请求路径如图:
其中getVersion写了路径类名+自定义path:/helloWorld/version
而getUserInfoById没有写根据我们重订的规则类名+方法名:/helloWorld/getUserInfoById?id=1
3、注意事项;
- XXXController中的类一般包含Controller标志可以去除(按照自己需要截取)。
- 除了系统中新加的映射路径还可能会有框架中自带的映射如(spring base error 和 swagger资源路径)可能会影响可以根据规则过滤。
以上为工作学习总结,如果有问题欢迎留言私信一起探讨问题一起成长。
转载注明出处
种一棵树最好的时间是十年前,其次是现在;