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,并设置接口开关逻辑拦截器。
至此,接口开关能力已经实现完毕。再也不用在担心接口会直接暴露出去了,可以通过配置随时更改接口的访问权限。
Springmvc借助SimpleUrlHandlerMapping实现接口开关功能的更多相关文章
- springmvc学习笔记(10)-springmvc注解开发之商品改动功能
springmvc学习笔记(10)-springmvc注解开发之商品改动功能 标签: springmvc springmvc学习笔记10-springmvc注解开发之商品改动功能 需求 开发mappe ...
- SpringMVC之使用Validator接口进行验证
对于任何一个应用而言在客户端做的数据有效性验证都不是安全有效的,这时候就要求我们在开发的时候在服务端也对数据的有效性进行验证.SpringMVC自身对数据在服务端的校验有一个比较好的支持,它能将我们提 ...
- anyproxy学习2-rule模块实现接口mock功能
前言 AnyProxy不仅仅可以抓包,还可以拦截请求并修改服务端响应,实现接口mock功能. 面试时候经常会问到第三方支付如何测试这种,如果对接的第三方没提供测试环境,那么就需要搭建一个mock服务器 ...
- 8张图,让你彻底理解三极管的开关功能 && 經典線路圖
三极管除了可以当作交流信号放大器之外,也可以作为开关之用.严格说起来,三极管与一般的机械接点式开关在动作上并不完全相同,但是它却具有一些机械式开关所没有的特点. 为了很好的理解三极管的开关功能,下面以 ...
- Intellij Idea下搭建基于Spring+SpringMvc+MyBatis的WebApi接口架构
2018-08-16 09:27 更新 强烈推荐使用Springboot来搭建MVC框架! 强烈推荐使用Springboot来搭建MVC框架! 强烈推荐使用Springboot来搭建MVC框架! 后文 ...
- springmvc中使用文件上传功能
项目代码:https://github.com/PeiranZhang/springmvc-fileupload Servlet3.0之前使用文件上传功能 Servlet3.0之前需要使用common ...
- [java]SpringMVC+Swagger实现自动接口
项目使用SpringMVC+Maven 1.在站点项目的POM文件中引入Swagger的jar包 <properties> <project.build.sourceEncoding ...
- SpringMVC简版教程、部分功能
注:本文只用注解来实现 前言 SpringMVC各种流程图流程图(其他的各种流程图) jsp.xml.action彼此之间的关系,都如何使用 spring-mvc.xml如何配置,放在哪里? acti ...
- JAVA8给我带了什么——并行流和接口新功能
流,确定是笔者内心很向往的天堂,有他之后JAVA在处理数据就变更加的灵动.加上lambda表达不喜欢都不行.JAVA8也为流在提供另一个功能——并行流.即是有并行流,那么是不是也有顺序流.没有错.我前 ...
随机推荐
- Confluence 6 MySQL 创建数据库和数据库用户
一旦你成功的安装和配置了 MySQL 数据库服务器,你需要为你的 Confluence 创建数据库和数据库用户: 在 MySQL 中以超级用户运行 'mysql' .默认的用户为 'root' 同时密 ...
- Confluence 6 管理插件和组件
一个 组件(add-on)是与 Confluence 分开安装的功能,能够加强 Confluence 的功能和使用.插件(plugin)和 组件(add-on)这 2 个词通常是一起使用的. 一共有 ...
- pod 使用详解
cd 进去到 项目目录 包含 xcodeproj 结尾的目录下 1 pod init 创建一个pod 文件 2 打开生产的pod 文件 然后 配置pod 文件 并保存 3 pod install 安 ...
- php回调函数的概念及实例
php提供了两个内置函数call_user_func()和call_user_func_array()提供对回调函数的支持.这两个函数的区别是call_user_func_array是以数组的形式接收 ...
- CF919F
题意: Alice和Bob玩游戏,每人各有8张牌,牌的大小在0~4之间 每次操作,先手可以选择自己一张牌和对方一张牌求和后%5,将新的牌替代自己拿出的那张牌,以此类推,直到有一个人手中的牌全部是0,则 ...
- 实现用VB.Net/(C#)开发K/3 BOS 插件的真正可行方法
转了这一篇文章,原来一直想用C#做k3的插件开发,vb没有C#用的爽呀,这篇文章写与2011年,看来我以前没有认真去找这个方法呀. https://blog.csdn.net/chzjxgd/arti ...
- C# 属性(Property)和字段(Field)的区别
导读: 近期学习过程中发现了一些问题,我的学习只是学习,敲代码就是敲代码,没有加入思考,也不问为什么就直接去敲人家写好的例子去敲,把知识都学死了,逐渐散失了思考能力,所以学习的兴趣大打折扣,正如那句话 ...
- C#学习-索引器
当一个类包含数组成员时,索引器的使用将大大地简化对类中数组成员的访问. 索引器的定义类似于属性,也具有get访问器和set访问器,以下是 [修饰符] 数据类型 this [索引类型index] { g ...
- 开始写博客,学习Linq
除了为处理数据提供全新的方法之外,LINQ还代表了一种朝着声明式以及函数式编程发展的转变. 当人们问我为什么要学习LINQ时,我会告诉他们LINQ可以处理XML.关系型数据以及内存中的集合,更会提到L ...
- Hyper-V 替换 vmwp
要激活 Hyper-V 下的虚机 最简单的方法是用带证书的vmwp替换掉原来的 带证书的vmwp参见:http://bbs.pcbeta.com/viewthread-1408240-1-1.html ...