spring mvc源代码_Spring MVC中的通用代码在哪里放置?

spring mvc源代码

在为非Spring Boot应用程序的执行器编码的过程中,我遇到了一个有趣的问题,即在哪里实际放置一小段通用代码。 这篇文章试图列出所有可用的选项,以及它们在特定情况下的利弊。

作为一个具体示例,让我们使用REST端点返回可通过/jvmprops子上下文访问的所有JVM属性的映射。 此外,我想提供的选项不仅是搜索单个属性, 例如 /jvmprops/java.vm.vendor而且还允许过滤属性的子集, 例如 /jvmprops/java.vm.*

目前的情况

该代码仅针对Spring应用程序的无聊准则而设计。 上层由控制器组成。 它们使用@RestController进行注释,并提供REST终结@RestController ,这些终结点可以作为@RequestMapping注释的方法使用。 这些方法依次调用实现为服务的第二层。

如上所示,过滤器模式本身是最后一个路径段。 通过@PathVariable批注将其映射到方法参数。

@RestControllerclassJvmPropsController(privatevalservice:JvmPropsService){
  @RequestMapping(path=arrayOf("/jvmprops/{filter}"),method=arrayOf(GET))
  funreadJvmProps(@PathVariablefilter:String):Map<String,*>=service.getJvmProps()
}

为了有效地执行过滤,路径段允许使用星号字符。 但是,在Java中,字符串匹配是通过正则表达式实现的。 然后,必须将简单的调用模式“转换”为完整的正则表达式。 对于上述示例,不仅需要转义点字符-from . 而是\\. ,但需要相应翻译星号- .

valregex=filter.replace(".","\\.").replace("*",".*")

然后,关联的服务返回过滤后的地图,该地图又由控制器返回。 Spring Boot和Jackson负责JSON序列化。

简单的选择

一切都很好,直到需要额外的返回地图的端点(例如,获取环境变量),并且以上代码段最终都被复制粘贴到了每个代码段中。

当然必须有一个更好的解决方案,那么将此代码放在哪里?

在控制器父类中

最简单的方法是为所有控制器创建一个父类,将代码放在那里并显式调用它。

abstractclassArtificialController(){
    funtoRegex(filter:String)=filter.replace(".","\\.").replace("*",".*")
}

@RestControllerclassJvmProps(privatevalservice:JvmPropsService):ArtificialController(){
  @RequestMapping(path=arrayOf("/jvmprops/{filter}"),method=arrayOf(GET))
  funreadJvmProps(@PathVariablefilter:String):Map<String,*>{
    valregex=toRegex(filter)
    returnservice.getJvmProps(regex)
  }
}

这种方法有三个主要缺点:

  1. 它只是为了共享通用代码而创建了一个人工的父类。
  2. 其他控制器必须从此父类继承。
  3. 它需要显式调用,将转换的责任放在客户端代码中。 很有可能没有开发人员,只有创建该方法的开发人员才会使用它。

在服务父类中

可以在服务层中设置代码,而不是在控制器层的共享方法中设置代码。

具有与上述相同的缺点。

在第三方依赖中

代替人工的类层次结构,让我们介绍一个不相关的依赖类。 这将转换为以下代码。

classRegexer{
  funtoRegex(filter:String)=filter.replace(".","\\.").replace("*",".*")
}

@RestControllerclassJvmProps(privatevalservice:JvmPropsService,
                               privatevalregexer:Regexer){
  @RequestMapping(path=arrayOf("/jvmprops/{filter}"),method=arrayOf(GET))
  funreadJvmProps(@PathVariablefilter:String):Map<String,*>{
    valregex=regexer.toRegex(filter)
    returnservice.getJvmProps(regex)
  }
}

尽管偏向于继承不是继承 ,但这种方法仍然存在很大的漏洞:需要客户端代码来调用共享的代码。

在Kotlin扩展功能中

如果允许在JVM上使用其他语言,则可能会受益于Kotlin的扩展功能:

interfaceArtificialController

funArtificialController.toRegex(filter:String)=filter.replace(".","\\.").replace("*",".*")

@RestControllerclassJvmProps(privatevalservice:JvmPropsService):ArtificialController{
  @RequestMapping(path=arrayOf("/jvmprops/{filter}"),method=arrayOf(GET))
  funreadJvmProps(@PathVariablefilter:String):Map<String,*>{
    valregex=toRegex(filter)
    returnservice.getJvmProps(regex)
  }
}

与将代码放入父控制器相比,至少将代码本地化到文件中。 但是同样的缺点仍然存在,因此收益只是微不足道的。

更精致的替代品

上述重构在每种可能的情况下都有效。 以下选项专门适用于(Spring Boot)Web应用程序。

它们都遵循相同的方法:让我们以某种方式将控制器包装在将要执行的单个组件中,而不是显式调用共享代码。

在Servlet过滤器中

在Web应用程序中,必须在servlet筛选器中绑定不同控制器之前/之后执行的代码。

使用Spring MVC,这可以通过过滤器注册bean实现:

@Bean
funfilterBean()=FilterRegistrationBean().apply{
  urlPatterns=arrayListOf("/jvmProps/*")
  filter=object: Filter{
    overridefundestroy(){}
    overridefuninit(config:FilterConfig){}
    overridefundoFilter(req:ServletRequest,resp:ServletResponse,chain:FilterChain){
      chain.doFilter(httpServletReq,resp)
      valhttpServletReq=reqasHttpServletRequest
      valpaths=request.pathInfo.split("/")
      if(paths.size>2){
        valsubpaths=paths.subList(2,paths.size)
        valfilter=subpaths.joinToString("")
        valregex=filter.replace(".","\\.")
                          .replace("*",".*")
        // Change the JSON here...
      }
    }
  }
}

上面代码的好处是不需要控制器显式调用共享代码。 但是,还有一个不太明显的问题:至此,该映射已被序列化为JSON,并已处理为响应。 必须先将初始响应包装在响应包装器中,然后才能继续进行过滤器链并处理JSON(而不是内存中的数据结构)。

这种方式不仅很脆弱,而且对性能有巨大影响。

在Spring MVC拦截器中

不幸的是,将上述代码从Spring MVC拦截器中的过滤器中移出并没有任何改善。

一方面

转换字符串参数和过滤映射的需要是典型的横切关注点。 这是面向方面编程的典型用例。 代码如下所示:

@AspectclassFilterAspect{
  @Around("execution(Map ch.frankel.actuator.controller.*.*(..))")
  funfilter(joinPoint:ProceedingJoinPoint):Map<String,*>{
    valmap=joinPoint.proceed()asMap<String,*>
    valfilter=joinPoint.args[0]asString
    valregex=filter.replace(".","\\.").replace("*",".*")
    returnmap.filter{it.key.matches(regex.toRegex())}
  }
}

选择此选项可以按预期方式进行。 另外,方面将自动应用于已配置包中返回映射的所有类的所有方法。

在Spring MVC建议中

Spring MVC中隐藏着一个漂亮的宝石:在控制器返回之后但返回的值以JSON格式序列化之前 (由于使用@ Dr4K4n作为提示),将执行专门的建议。

该类只需要:

  1. 实现ResponseBodyAdvice接口
  2. @ControllerAdvice注释,以供Spring扫描,并控制将其应用于哪个包
@ControllerAdvice("ch.frankel.actuator.controller")
classTransformBodyAdvice():ResponseBodyAdvice<Map<String,Any?>>{

  overridefunsupports(returnType:MethodParameter,converterType:Class<outHttpMessageConverter<*>>)=
  returnType.method.returnType==Map::class.java

  overridefunbeforeBodyWrite(map:Map<String,Any?>,methodParameter:MethodParameter,
            mediaType:MediaType,clazz:Class<outHttpMessageConverter<*>>,
            serverHttpRequest:ServerHttpRequest,serverHttpResponse:ServerHttpResponse):Map<String,Any?>{
    valrequest=(serverHttpRequestasServletServerHttpRequest).servletRequest
    valfilterPredicate=getFilterPredicate(request)
    returnmap.filter(filterPredicate)
  }

  privatefungetFilterPredicate(request:HttpServletRequest):(Map.Entry<String,Any?>)->Boolean{
    valpaths=request.pathInfo.split("/")
    if(paths.size>2){
      valsubpaths=paths.subList(2,paths.size)
      valfilter=subpaths.joinToString("")
      valregex=filter.replace(".","\\.")
                        .replace("*",".*")
                        .toRegex()
      return{it.key.matches(regex)}
    }
    return{true}
  }
}

不需要显式调用此代码,它将应用于已配置程序包中的所有控制器。 仅当方法的返回类型为Map类型时才应用此方法(尽管由于类型擦除而没有泛型检查)。

更好的是,它为涉及进一步处理(订购,分页等)的未来开发铺平了道路。

结论

在Spring MVC应用程序中,有几种共享通用代码的方法,每种都有各自的优缺点。 在本文中,对于此特定用例, ResponseBodyAdvice具有最大的优势。

这里的主要考虑是,围绕工具带的工具越多,最终选择就越好。 去探索一些您尚不了解的工具:今天阅读一些文档呢?

翻译自: https://blog.frankel.ch/common-code-spring-mvc/

spring mvc源代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值