zoukankan      html  css  js  c++  java
  • 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,并设置接口开关逻辑拦截器。

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

  • 相关阅读:
    CentOS6.3 编译安装LAMP(4):编译安装 PHP5.2.17
    CentOS6.3 编译安装LAMP(3):编译安装 MySQL5.5.25
    解决URL中包含“%2F”导致Apache地址重写mod_rewrite失效的问题
    Apache静态编译与动态编译详解
    Apache常用2种工作模式prefork和worker比较
    Apache 优化配置10条建议
    Apache prefork 模块指令分析
    PHP上传(单个)文件示例
    CentOS6.3 编译安装LAMP(2):编译安装 Apache2.2.25
    CentOS6.3 编译安装LAMP(1):准备工作
  • 原文地址:https://www.cnblogs.com/hujunzheng/p/9902475.html
Copyright © 2011-2022 走看看