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();@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@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)) {//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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/531215.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

log4j平稳升级到log4j2

一、前言 公司中的项目虽然已经用了很多的新技术了&#xff0c;但是日志的底层框架还是log4j&#xff0c;个人还是不喜欢用这个的。最近项目再生产环境上由于log4j引起了一场血案&#xff0c;于是决定升级到log4j2。 二、现象 虽然生产环境有多个结点分散高并发带来的压力&…

Springboot集成ES启动报错

报错内容 None of the configured nodes are available elasticsearch.yml配置 cluster.name: ftest node.name: node-72 node.master: true node.data: true network.host: 112.122.245.212 http.port: 39200 transport.tcp.port: 39300 discovery.zen.ping.unicast.hosts: [&…

kafka-manager配置和使用

kafka-manager配置 最主要配置就是用于kafka管理器状态的zookeeper主机。这可以在conf目录中的application.conf文件中找到。 kafka-manager.zkhosts"my.zookeeper.host.com:2181" 当然也可以声明为zookeeper集群。 kafka-manager.zkhosts"my.zookeeper.host.co…

kafka告警简单方案

一、前言 为什么要设计kafka告警方案&#xff1f;现成的监控项目百度一下一大堆&#xff0c;KafkaOffsetMonitor、KafkaManager、 Burrow等&#xff0c;具体参考&#xff1a;kafka的消息挤压监控。由于本小组的项目使用的kafka集群并没有被公司的kafka-manager管理&#xff0c;…

RedisCacheManager设置Value序列化器技巧

CacheManager基本配置 请参考博文&#xff1a;springboot2.0 redis EnableCaching的配置和使用 RedisCacheManager构造函数 /*** Construct a {link RedisCacheManager}.* * param redisOperations*/ SuppressWarnings("rawtypes") public RedisCacheManager(RedisOp…

HashMap 源码阅读

前言 之前读过一些类的源码&#xff0c;近来发现都忘了&#xff0c;再读一遍整理记录一下。这次读的是 JDK 11 的代码&#xff0c;贴上来的源码会去掉大部分的注释, 也会加上一些自己的理解。 Map 接口 这里提一下 Map 接口与1.8相比 Map接口又新增了几个方法&#xff1a;   …

SpringMvc接口中转设计(策略+模板方法)

一、前言 最近带着两个兄弟做支付宝小程序后端相关的开发&#xff0c;小程序首页涉及到很多查询的服务。小程序后端服务在我司属于互联网域&#xff0c;相关的查询服务已经在核心域存在了&#xff0c;查询这块所要做的工作就是做接口中转。参考了微信小程序的代码&#xff0c;发…

SpringSecurity整合JWT

一、前言 最近负责支付宝小程序后端项目设计&#xff0c;这里主要分享一下用户会话、接口鉴权的设计。参考过微信小程序后端的设计&#xff0c;会话需要依靠redis。相关的开发人员和我说依靠Redis并不是很靠谱&#xff0c;redis在业务高峰期不稳定&#xff0c;容易出现问题&…

Springboot定时任务原理及如何动态创建定时任务

一、前言 上周工作遇到了一个需求&#xff0c;同步多个省份销号数据&#xff0c;解绑微信粉丝。分省定时将销号数据放到SFTP服务器上&#xff0c;我需要开发定时任务去解析文件。因为是多省份&#xff0c;服务器、文件名规则、数据规则都不一定&#xff0c;所以要做成可配置是有…

转载:ThreadPoolExecutor 源码阅读

前言 之前研究了一下如何使用ScheduledThreadPoolExecutor动态创建定时任务(Springboot定时任务原理及如何动态创建定时任务)&#xff0c;简单了解了ScheduledThreadPoolExecutor相关源码。今天看了同学写的ThreadPoolExecutor 的源码解读&#xff0c;甚是NB&#xff0c;必须转…

使用pdfBox实现pdf转图片,解决中文方块乱码等问题

一、引入依赖 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>fontbox</artifactId><version>2.0.13</version> </dependency> <dependency><groupId>org.apache.pdfbox</groupId><artif…

Spring异步调用原理及SpringAop拦截器链原理

一、Spring异步调用底层原理 开启异步调用只需一个注解EnableAsync Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Import(AsyncConfigurationSelector.class) public interface EnableAsync {/*** Indicate the async annotation type to be detec…

Spring MVC源码——Root WebApplicationContext

Spring MVC源码——Root WebApplicationContext 打算开始读一些框架的源码,先拿 Spring MVC 练练手,欢迎点击这里访问我的源码注释, SpringMVC官方文档一开始就给出了这样的两段示例: WebApplicationInitializer示例: public class MyWebApplicationInitializer implements Web…

Spring MVC源码——Servlet WebApplicationContext

上一篇笔记(Spring MVC源码——Root WebApplicationContext)中记录了下 Root WebApplicationContext 的初始化代码.这一篇来看 Servlet WebApplicationContext 的初始化代码 DispatcherServlet 是另一个需要在 web.xml 中配置的类, Servlet WebApplicationContext 就由它来创建…

Springboot源码——应用程序上下文分析

前两篇(Spring MVC源码——Root WebApplicationContext 和 Spring MVC源码——Servlet WebApplicationContext)讲述了springmvc项目创建上下文的过程&#xff0c;这一篇带大家了解一下springboot项目创建上下文的过程。 SpringApplication引导类 SpringApplication类用于启动或…

基于zookeeper实现分布式配置中心(一)

最近在学习zookeeper&#xff0c;发现zk真的是一个优秀的中间件。在分布式环境下&#xff0c;可以高效解决数据管理问题。在学习的过程中&#xff0c;要深入zk的工作原理&#xff0c;并根据其特性做一些简单的分布式环境下数据管理工具。本文首先对zk的工作原理和相关概念做一下…

基于zookeeper实现分布式配置中心(二)

上一篇&#xff08;基于zookeeper实现分布式配置中心&#xff08;一&#xff09;&#xff09;讲述了zookeeper相关概念和工作原理。接下来根据zookeeper的特性&#xff0c;简单实现一个分布式配置中心。 配置中心的优势 1、各环境配置集中管理。 2、配置更改&#xff0c;实时推…

Redis分布式锁实战

背景 目前开发过程中&#xff0c;按照公司规范&#xff0c;需要依赖框架中的缓存组件。不得不说&#xff0c;做组件的大牛对CRUD操作的封装&#xff0c;连接池、缓存路由、缓存安全性的管控都处理的无可挑剔。但是有一个小问题&#xff0c;该组件没有对分布式锁做实现&#xff…

基于RobotFramework实现自动化测试

Java robotframework seleniumlibrary 使用Robot Framework Maven Plugin&#xff08;http://robotframework.org/MavenPlugin/&#xff09;执行自动化测试chromedriver下载&#xff1a; http://chromedriver.storage.googleapis.com/index.htmlchromedriver和chrome版本对应…

Springboot国际化信息(i18n)解析

国际化信息理解 国际化信息也称为本地化信息 。 Java 通过 java.util.Locale 类来表示本地化对象&#xff0c;它通过 “语言类型” 和 “国家/地区” 来创建一个确定的本地化对象 。举个例子吧&#xff0c;比如在发送一个具体的请求的时候&#xff0c;在header中设置一个键值对…