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

一、接口开关功能

  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();

    @Autowired
    private RequestMappingHandlerAdapter handlerAdapter;

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerExecutionChain mappedHandler = handlerMapping.getHandler(request);
        return handlerAdapter.handle(request, response, mappedHandler.getHandler());
    }

    @Override
    public void afterPropertiesSet() {
        handlerMapping.afterPropertiesSet();
    }

    private class BaseRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
      //初始化子handler mapping
        @Override
        protected void initHandlerMethods() {
            detectHandlerMethods(BaseController.this);
        }
        //合并父路径和子handler路径
        @Override
        protected 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)) {//NOSONAR
            return;
        }

        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;
    }

    @Override
    public 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

时间: 2024-10-05 04:16:28

Springmvc借助SimpleUrlHandlerMapping实现接口开关功能的相关文章

SpringMVC核心类与接口

DispatcherServlet   -- 前置控制器 HandlerMapping接口 -- 处理请求的映射 HandlerMapping接口的实现类: SimpleUrlHandlerMapping  通过配置文件,把一个URL映射到Controller DefaultAnnotationHandlerMapping  通过注解,把一个URL映射到Controller类上 HandlerAdapter接口 -- 处理请求的映射 AnnotationMethodHandlerAdapter类

SpringMVC之使用Validator接口进行验证

对于任何一个应用而言在客户端做的数据有效性验证都不是安全有效的,这时候就要求我们在开发的时候在服务端也对数据的有效性进行验证.SpringMVC自身对数据在服务端的校验有一个比较好的支持,它能将我们提交到服务端的数据按照我们事先的约定进行数据有效性验证,对于不合格的数据信息SpringMVC会把它保存在错误对象中,这些错误信息我们也可以通过SpringMVC提供的标签在前端JSP页面上进行展示. 使用Validator接口进行验证 在SpringMVC中提供了一个Validator接口,我们可以

stm32 DCMI接口CROP功能使用

最近使用stm32F407单片机通过摄像头采集图像进行处理,其中使用到DCMI接口CROP功能.但在网上找了很久,有用的资料不多,只能自己研究,特将自己的使用方法记录下来. 1.如何设置CROP. 定义: DCMI_CROPInitTypeDef DCMI_CROPInitStruct; 参数设置: DCMI_CROPInitStruct.DCMI_CaptureCount=Width;                         //capcnt DCMI_CROPInitStruct.D

anyproxy-rule模块实现接口mock功能

前言 AnyProxy不仅仅可以抓包,还可以拦截请求并修改服务端响应,实现接口mock功能.面试时候经常会问到第三方支付如何测试这种,如果对接的第三方没提供测试环境,那么就需要搭建一个mock服务器,模拟支付接口返回的各种情况. rule模块 AnyProxy提供了二次开发的能力,你可以用js编写自己的规则模块(rule),来自定义网络请求的处理逻辑. 注意:引用规则前,请务必确保文件来源可靠,以免发生安全问题 拦截并修改正在发送的请求 可修改内容包括请求头(request header),请求

springmvc集成swagger实现接口文档自动化生成

一直苦于文档整理工作,因为这是一个很无聊的工作,偶然在网上看到了swagger这东西,感觉不错,于是动手集成了一下,眼前一亮 Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格的 Web 服务.总体目标是使客户端和文件系统作为服务器以同样的速度来更新.文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步.Swagger 让部署管理和使用功能强大的API从未如此简单. 费话少说,下面来看一下集成的过程,我用的环境是:jdk1.8+tomc

springMVC 处理json ------ HttpMessageConverter 接口

一.SpringMVC处理json的使用 1.添加依赖jar包 <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.8.7</version> </dependency> <dependency> <groupId&g

SpringMVC高速实现文件上传功能

SpringMVC为我们封装了上传文件的功能,如今就试用一下 须要的jar包 我用的是Maven项目,就不须要到处下载Jar包了 SpringMVC的搭建 首先要在applicationContext配置文件中注冊一下文件上传的服务 如 <!-- 文件上传配置--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartRes

java程序支付宝接口付费功能的实现

以前做过c#应用程序支付宝api接口功能,现在转移到Java程序上,代码如何实现呢? 1.从你的网站提交到支付宝: /** * 将订单提交支付宝进行网上支付 */ public ActionForward submitAlipayUrl(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { String orderNo

262.微机接口简介&amp;功能&amp;组成

1.简介 随着计算机的不断发展,单独的计算机已不能满足人们的需要,计算机的扩展能力成为人们认识电脑的一个重要的性能指标,常用的接口有鼠标.键盘接口,打印机接口.扫描仪接口等. 微机常用接口外观图如下图所示,这是一个微机主板,主要由CPU插槽.PCI扩展槽.内存插槽.BIOS.CMOS电池.CMOS跳线.ATX电源插座.音频接口.AGP扩展槽.USB接口.鼠标接口.键盘接口等组成. 微机接口(interface)就是微处理器CPU与“外部世界”的连接电路,是CPU与外界进行信息交换的中转站.比如源