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.pswp.cn/news/531215.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/531215.shtml
英文地址,請注明出處:http://en.pswp.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: [&…

高效使用hibernate-validator校驗框架

一、前言 高效、合理的使用hibernate-validator校驗框架可以提高程序的可讀性&#xff0c;以及減少不必要的代碼邏輯。接下來會介紹一下常用一些使用方式。 二、常用注解說明 限制說明Null限制只能為nullNotNull限制必須不為nullAssertFalse限制必須為falseAssertTrue限制必須為…

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…

Nginx配置以及域名轉發

工程中的nginx配置 #user nobody; worker_processes 24; error_log /home/xxx/opt/nginx/logs/error.log; pid /home/xxx/opt/nginx/run/nginx.pid;events {use epoll;worker_connections 102400; }http {include /home/xxx/opt/nginx/conf.d/mime.types;default_…

java接口簽名(Signature)實現方案續

一、前言 由于之前寫過的一片文章 &#xff08;java接口簽名(Signature)實現方案 &#xff09;收獲了很多好評&#xff0c;此次來說一下另一種簡單粗暴的簽名方案。相對于之前的簽名方案&#xff0c;對body、paramenter、path variable的獲取都做了簡化的處理。也就是說這種方式…

支付寶敏感信息解密

支付寶官方解密文檔&#xff1a;https://docs.alipay.com/mini/introduce/aes String response "小程序前端提交的";//1. 獲取驗簽和解密所需要的參數 Map<String, String> openapiResult JSON.parseObject(response,new TypeReference<Map<String, St…

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;必須轉…

Spring BPP中優雅的創建動態代理Bean

一、前言 本文章所講并沒有基于Aspectj&#xff0c;而是直接通過Cglib以及ProxyFactoryBean去創建代理Bean。通過下面的例子&#xff0c;可以看出Cglib方式創建的代理Bean和ProxyFactoryBean創建的代理Bean的區別。 二、基本測試代碼 測試實體類&#xff0c;在BPP中創建BppTest…

使用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…

線程池優化之充分利用線程池資源

一、前言 最近做了電子發票的需求&#xff0c;分省開票接口和發票下載接口都有一定的延遲。為了完成開票后自動將發票插入用戶微信卡包&#xff0c;目前的解決方案是利用線程池&#xff0c;將開票后插入卡包的任務&#xff08;輪詢分省發票接口&#xff0c;直到獲取到發票相關信…

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 就由它來創建…