SpringMVC 自动装载映射路径

本文探讨如何在SpringMVC中自定义Controller的映射路径,通过重写`RequestMappingHandlerMapping`的`getMappingForMethod`方法,实现类名与方法名组合的路径。文中介绍了修改过程及最终实现的效果,包括自动装载映射的例子。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

种一棵树最好的时间是十年前,其次是现在;

前言


我们使用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:

HelloWorldController.java

类没有写映射路径,方法中getVersion写了,getUserInfoById没有写。

最终我们得到的请求路径如图:

其中getVersion写了路径类名+自定义path:/helloWorld/version

而getUserInfoById没有写根据我们重订的规则类名+方法名:/helloWorld/getUserInfoById?id=1

 

url

 

3、注意事项;

  • XXXController中的类一般包含Controller标志可以去除(按照自己需要截取)。
  • 除了系统中新加的映射路径还可能会有框架中自带的映射如(spring base error 和 swagger资源路径)可能会影响可以根据规则过滤。

 


以上为工作学习总结,如果有问题欢迎留言私信一起探讨问题一起成长。

转载注明出处

种一棵树最好的时间是十年前,其次是现在;

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值