一、接口开关功能

  1、可配置化,依赖配置中心

  2、接口访问权限可控

  3、springmvc不会扫描到,即不会直接的将接口暴露出去

二、接口开关使用场景

  和业务没什么关系,主要方便查询系统中的一些状态信息。比如系统的配置信息,中间件的状态信息。这就需要写一些特定的接口,不能对外直接暴露出去(即不能被springmvc扫描到,不能被swagger扫描到)。

三、SimpleUrlHandlerMapping官方解释

  SimpleUrlHandlerMapping实现HandlerMapping接口以从URL映射到请求处理程序bean。
  支持映射到bean实例和映射到bean名称;后者是非单身处理程序所必需的。
  “urlMap”属性适合用bean引用填充处理程序映射,例如通过XML bean定义中的map元素。
  可以通过“mappings”属性以java.util.Properties类接受的形式设置bean名称的映射,如下所示:/welcome.html=ticketController /show.html=ticketController语法为PATH = HANDLER_BEAN_NAME。  
  如果路径不以斜杠开头,则前置一个。支持直接匹配(给定“/ test” - >注册“/ test”)和“*”模式匹配(给定“/ test” - >注册“/ t *”)。

四、接口开关实现

  就像SimpleUrlHandlerMapping javadoc中描述的那样,其执行原理简单理解就是根据URL寻找对应的Handler。借助这种思想,我们在Handler中再借助RequestMappingHandlerMapping和RequestMappingHandlerAdapter来帮助我们完成URL的转发。这样做的好处是不需要直接暴露的接口开发规则只需要稍作修改,接下来将详细介绍一下。

  请求转发流程如下

  

  想法是好的,如何实现这一套流程呢?首先要解决以下问题。

  1、定义的接口不能被springmvc扫描到。

  2、接口定义还是要按照@RequestMaping规则方式编写,这样才能减少开发量并且能被RequestMappingHandlerMapping处理。

  3、如何自动注册url->handler到SimpleUrlHandlerMapping中去。

  对于上面需要实现的,首先要了解一些springmvc相关源码。

  RequestMappingHandlerMapping初始化method mapping

/*** Scan beans in the ApplicationContext, detect and register handler methods.* @see #isHandler(Class)* @see #getMappingForMethod(Method, Class)* @see #handlerMethodsInitialized(Map)*/
protected void initHandlerMethods() {if (logger.isDebugEnabled()) {logger.debug("Looking for request mappings in application context: " + getApplicationContext());}String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :getApplicationContext().getBeanNamesForType(Object.class));for (String beanName : beanNames) {if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {Class<?> beanType = null;try {beanType = getApplicationContext().getType(beanName);}catch (Throwable ex) {// An unresolvable bean type, probably from a lazy bean - let's ignore it.if (logger.isDebugEnabled()) {logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);}}if (beanType != null && isHandler(beanType)) {detectHandlerMethods(beanName);}}}handlerMethodsInitialized(getHandlerMethods());
}

  isHandler方法【判断方法是不是一个具体handler】逻辑如下

protected boolean isHandler(Class<?> beanType) {return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

  所以我们定义的开关接口为了不被springmvc扫描到,直接去掉类注释上的@Controller注解和@RequestMapping注解就好了,如下。

@Component
@ResponseBody
public class CommonsStateController {@GetMapping("/url1")public String handleUrl1()  {return null;}
  @GetMapping("/url2")public String handleUrl2()  {return null;}
}

  按照如上的定义,url  -> handler(/message/state/* -> CommonsStateController )形式已经出来了,但是还缺少父类路径 /message/state/ 以及 如何让RequestMappingHandlerMapping识别CommonsStateController这个handler 中的所有子handler。

  抽象Handler以及自定义RequestMappingHandlerMapping

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Objects;/*** @author hujunzheng* @create 2018-08-10 12:53**/
public abstract class BaseController extends AbstractController implements InitializingBean {private RequestMappingHandlerMapping handlerMapping = new BaseRequestMappingHandlerMapping();@Autowiredprivate RequestMappingHandlerAdapter handlerAdapter;@Overrideprotected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {HandlerExecutionChain mappedHandler = handlerMapping.getHandler(request);return handlerAdapter.handle(request, response, mappedHandler.getHandler());}@Overridepublic void afterPropertiesSet() {handlerMapping.afterPropertiesSet();}private class BaseRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
      //初始化子handler mapping@Overrideprotected void initHandlerMethods() {detectHandlerMethods(BaseController.this);}
        //合并父路径和子handler路径@Overrideprotected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {RequestMappingInfo info = super.getMappingForMethod(method, handlerType);if (!Objects.isNull(info) && StringUtils.isNotBlank(getBasePath())) {info = RequestMappingInfo.paths(getBasePath()).build().combine(info);}return info;}}//开关接口定义父路径public abstract String getBasePath();
}

  所有开关接口handler都继承这个BaseController 抽象类,在对象初始时创建所有的子handler mapping。SimpleUrlHandlerMapping最终会调用开关接口的handleRequestInternal方法,方法内部通过RequestMappingHandlerMapping和RequestMappingHandlerAdapter 将请求转发到具体的子handler。

@Component
@ResponseBody
public class CommonsStateController extends BaseController {@GetMapping("/url1")public String handleUrl1()  {return null;}@GetMapping("/url2")public String handleUrl2()  {return null;}
}

  自动注册url-handler到SimpleUrlHandlerMapping

import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @author hujunzheng* @create 2018-08-10 13:57**/
public class EnhanceSimpleUrlHandlerMapping extends SimpleUrlHandlerMapping {public EnhanceSimpleUrlHandlerMapping(List<BaseController> controllers) {if (CollectionUtils.isEmpty(controllers)) {//NOSONARreturn;}Map<String, BaseController> urlMappings = new HashMap<>();controllers.forEach(controller -> {String basePath = controller.getBasePath();if (StringUtils.isNotBlank(basePath)) {if (!basePath.endsWith("/*")) {basePath = basePath + "/*";}urlMappings.put(basePath, controller);}});this.setUrlMap(urlMappings);}
}

  获取BaseController父路径,末尾加上‘/*’,然后将url -> handler关系注册到SimpleUrlHandlerMapping的urlMap中去。这样只要请求路径是 父路径/*的模式都会被SimpleUrlHandlerMapping处理并转发给对应的handler(BaseController),然后在转发给具体的子handler。

  接口开关逻辑

import com.cmos.wmhopenapi.service.config.LimitConstants;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.stream.Collectors;/*** @author hujunzheng* @create 2018-08-10 15:17**/
public class UrlHandlerInterceptor extends HandlerInterceptorAdapter {private SimpleUrlHandlerMapping mapping;private LimitConstants limitConstants;public UrlHandlerInterceptor(SimpleUrlHandlerMapping mapping, LimitConstants limitConstants) {this.mapping = mapping;this.limitConstants = limitConstants;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String lookupUrl = mapping.getUrlPathHelper().getLookupPathForRequest(request);String urllimits = limitConstants.getUrllimits();if (StringUtils.isNotBlank(urllimits)) {for (String urllimit : Lists.newArrayList(urllimits.split(",")).stream().map(value -> value.trim()).collect(Collectors.toList())) {if (mapping.getPathMatcher().match(urllimit, lookupUrl)) {return false;}}}return true;}
}

  基本思路就是通过 UrlPathHelper获取到request的lookupUrl(例如 /message/state/url1) ,然后获取到配置中心配置的patter path(例如message/state/*),最后通过 AntPathMatcher进行二者之间的匹配,如果成功则禁止接口访问。

五、接口开关配置

@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ObjectProvider<List<BaseController>> controllers, LimitConstants limitConstants) {SimpleUrlHandlerMapping mapping = new EnhanceSimpleUrlHandlerMapping(controllers.getIfAvailable());mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);mapping.setInterceptors(new UrlHandlerInterceptor(mapping, limitConstants));return mapping;
}

  创建自定义的SimpleUrlHandlerMapping,然后将类型为BaseController所有handler以构造参数的形式传给SimpleUrlHandlerMapping,并设置接口开关逻辑拦截器。

  至此,接口开关能力已经实现完毕。再也不用在担心接口会直接暴露出去了,可以通过配置随时更改接口的访问权限。

转载于:https://www.cnblogs.com/hujunzheng/p/9902475.html

Springmvc借助SimpleUrlHandlerMapping实现接口开关功能相关推荐

  1. springmvc学习笔记(10)-springmvc注解开发之商品改动功能

    springmvc学习笔记(10)-springmvc注解开发之商品改动功能 springmvc学习笔记(10)-springmvc注解开发之商品改动功能 标签: springmvc springmv ...

  2. 功放板加开关音量_一种带音量开关功能的音响功放板的制作方法

    本实用新型涉及音响功放板领域,尤其涉及一种带音量开关功能的音响功放板. 背景技术: 随着时代的变化,人们对于技术的研究飞速,人们的生活中越来越多的工具,其中也缺少不了音响功放板这些设施,在人们使用的过 ...

  3. 台式计算机主板接口识别,解决方法:如何连接计算机主板上的接口?所有主板接口插头功能功能识别图...

    二.主板背面图 主板WIFI天线接口图(当前仅适用于某些高端主板) 背板接口如图所示.从上到下,第一行是两个金色的WIFI天线端口.华硕ROG MAXIMUS IX EXTREME配备了两个配备的天线 ...

  4. SpringMVC之使用Validator接口进行验证

    对于任何一个应用而言在客户端做的数据有效性验证都不是安全有效的,这时候就要求我们在开发的时候在服务端也对数据的有效性进行验证.SpringMVC自身对数据在服务端的校验有一个比较好的支持,它能将我们提 ...

  5. Scala特质trait具备Java中类和接口的功能

    特质trait具备Java中类和接口的功能

  6. jquery.form.js java_使用jQuery.form.js/springmvc框架实现文件上传功能

    使用的技术有jquery.form.js框架, 以及springmvc框架.主要实现异步文件上传的同时封装对象,以及一些注意事项. 功能本身是很简单的,但是涉及到一些传递参数类型的问题.例如:jque ...

  7. JAVA8给我带了什么——并流行和接口新功能

    JAVA8给我带了什么--并流行和接口新功能 流,确定是笔者内心很向往的天堂,有他之后JAVA在处理数据就变更加的灵动.加上lambda表达不喜欢都不行.JAVA8也为流在提供另一个功能--并行流.即 ...

  8. 接口测试-接口定义功能-前端-实现动态增删表单

    基于 springboot+vue 的测试平台开发继续更新. 目前已经进入到接口定义功能的开发阶段,首先我还是直接在前段画了个大概的页面,先预览下: 不过目前只是画了这个页面都主要功能,细节未尽事宜待 ...

  9. C#实现调取钉钉考勤接口的功能

    C#实现调取钉钉考勤接口的功能 公司需要做一个钉钉考勤的页面,让我去写这个功能.结果却比我想象的要麻烦一些!具体是怎么个麻烦呢,下面直入正题. 首先我们找到获取钉钉考勤结果的接口.结果发现请求参数里有 ...

最新文章

  1. 不同浏览器的怪癖小结【转】
  2. 十分钟学习nginx
  3. android Collections.addAll()的使用
  4. 固有属性与自定义属性
  5. mysql多数据源切换_CI 多数据库操作 切换数据库
  6. sperling指标 matlab,sperling指标计算实验报告
  7. 运维部门工作总结_我院召开2020年科室(部门)工作总结暨考核测评会议
  8. 解决 U盘安装Windows Server 2012 R2 报错 Windows 无法打开所需的文件 Sources\install.wim...
  9. red linux系统管理,Red hat Linux 系统管理篇 4
  10. MFC(6)点击按钮弹出新的对话框源码
  11. r型聚类分析matlab实现,浅谈Matlab中的聚类分析 Matlab聚类程序的设计
  12. C#网络编程技术SuperSocket实战项目演练
  13. 识别连笔字的软件_3个最实用的字体识别的在线工具
  14. (基础)SpringCloud Alibaba Nacos作为多环境配置中心
  15. 机器学习——决策树算法之代码+数学实例解析
  16. uniapp如何实现时间轴会议预约显示?
  17. 机器视觉-相机镜头光源介绍及选型-3.光源分类
  18. 【编程不良人】快速入门SpringBoot学习笔记06---RestFul、异常处理、CORS跨域、Jasypt加密
  19. 实战即时聊天,一文说明白:聊天服务器+聊天客户端+Web管理控制台。
  20. Channel L 自然拼读法 Teacher:Lamb

热门文章

  1. pip 安装_安装 pip 轻松管理 PyPI 软件包 | Linux 中国
  2. unc 目录不受支持_Shopify平台对于店铺模版都提供哪些支持
  3. oracle10数据库链接失败,oracle 10g Enterprise Manager 无法连接到数据库实例分析
  4. Docker JFrog Artifactory 7.27.10 maven私服(搭建篇)
  5. VS Code 中的文件添加图标的插件vscode-icons
  6. mysql创建操作用户
  7. linux shell读取配置文件中的内容
  8. 数据装载器连接其他oracle数据库_07
  9. 计算机常用工具软件教案,常用工具软件教案.doc
  10. CAD自控lisp_基于AutoLisp的AutoCAD二次开发自动生成系统图