一、可配置化,依賴配置中心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,並設置接口開關邏輯攔截器。
至此,接口開關能力已經實現完畢。不再用在擔憂接口會直接暴露出去了,能夠經過配置隨時更改接口的訪問權限。