一、可配置化,依赖配置中心html
二、接口访问权限可控java
三、springmvc不会扫描到,即不会直接的将接口暴露出去web
和业务没什么关系,主要方便查询系统中的一些状态信息。好比系统的配置信息,中间件的状态信息。这就须要写一些特定的接口,不能对外直接暴露出去(即不能被springmvc扫描到,不能被swagger扫描到)。spring
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 *”)。apache
就像SimpleUrlHandlerMapping javadoc中描述的那样,其执行原理简单理解就是根据URL寻找对应的Handler。借助这种思想,咱们在Handler中再借助RequestMappingHandlerMapping和RequestMappingHandlerAdapter来帮助咱们完成URL的转发。这样作的好处是不须要直接暴露的接口开发规则只须要稍做修改,接下来将详细介绍一下。api
想法是好的,如何实现这一套流程呢?首先要解决如下问题。mvc
一、定义的接口不能被springmvc扫描到。app
二、接口定义仍是要按照@RequestMaping规则方式编写,这样才能减小开发量而且能被RequestMappingHandlerMapping处理。ide
三、如何自动注册url->handler到SimpleUrlHandlerMapping中去。ui
对于上面须要实现的,首先要了解一些springmvc相关源码。
/** * 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。
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; } }
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,并设置接口开关逻辑拦截器。
至此,接口开关能力已经实现完毕。不再用在担忧接口会直接暴露出去了,能够经过配置随时更改接口的访问权限。