日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

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

發布時間:2025/3/8 javascript 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringMvc接口中转设计(策略+模板方法) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、前言

  最近帶著兩個兄弟做支付寶小程序后端相關的開發,小程序首頁涉及到很多查詢的服務。小程序后端服務在我司屬于互聯網域,相關的查詢服務已經在核心域存在了,查詢這塊所要做的工作就是做接口中轉。參考了微信小程序的代碼,發現他們要么新寫一個接口調用,要么新寫一個接口包裹多個接口調用。這種方式不容易擴展。由于開發周期比較理想,所以決定設計一個接口中轉器。

二、接口中轉器整體設計

  

?

三、接口中轉器核心Bean

@Bean public SimpleUrlHandlerMapping directUrlHandlerMapping(@Autowired RequestMappingHandlerAdapter handlerAdapter, ObjectProvider<List<IDirectUrlProcessor>> directUrlProcessorsProvider) {List<IDirectUrlProcessor> directUrlProcessors = directUrlProcessorsProvider.getIfAvailable();Assert.notEmpty(directUrlProcessors, "接口直達解析器(IDirectUrlProcessor)列表不能為空!!!");SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();Map<String, Controller> urlMappings = Maps.newHashMap();urlMappings.put("/alipay-applet/direct/**", new AbstractController() {@Overrideprotected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {for (IDirectUrlProcessor directUrlProcessor : directUrlProcessors) {if (directUrlProcessor.support(request)) {String accept = request.getHeader("Accept");request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Sets.newHashSet(MediaType.APPLICATION_JSON_UTF8));if (StringUtils.isNotBlank(accept) && !accept.contains(MediaType.ALL_VALUE)) {request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Sets.newHashSet(Arrays.stream(accept.split(",")).map(value -> MediaType.parseMediaType(value.trim())).toArray(size -> new MediaType[size])));}HandlerMethod handlerMethod = new HandlerMethod(directUrlProcessor, ReflectionUtils.findMethod(IDirectUrlProcessor.class, "handle", HttpServletRequest.class));return handlerAdapter.handle(request, response, handlerMethod);}}throw new RuntimeException("未找到具體的接口直達處理器...");}});mapping.setUrlMap(urlMappings);mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);return mapping; }

  關于核心Bean的示意如下。

  • 使用SimpleUrlHandlerMapping 來過濾請求路徑中包含"/alipay-applet/direct/**"的請求,認為這樣的請求需要做接口中轉。
  • 針對中轉的請求使用一個Controller進行處理,即AbstractController的一個實例,并重寫其handleRequestInternal。
  • 對于不同的中轉請求找到對應的中轉處理器,然后創建相應的HandlerMethod ,再借助SpringMvc的RequestMappingHandlerAdapter調用具體中轉處理器接口以及返回值的處理。

  為什么要使用RequestMappingHandlerAdapter?因為中轉處理器的返回值類型統一為ReponseEntity<String>,想借助RequestMappingHandlerAdapter中的HandlerMethodReturnValueHandler來處理返回結果。

request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Sets.newHashSet(MediaType.APPLICATION_JSON_UTF8));

  為什么會有這段代碼?這是HandlerMethodReturnValueHandler調用的MessageConverter需要的,代碼如下。

  

  我手動設置的原因是因為RequestMappingHandlerAdapter是和RequestMappingHandlerMapping配合使用的,RequestMappingHandlerMapping會在request的attribute中設置RequestMappingInfo.producesCondition.getProducibleMediaTypes()這個值。具體參考代碼如下。

org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#createRequestMappingInfo

四、請求轉發RestTempate配置

@Bean public RestTemplate directRestTemplate() throws Exception {try {RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {@Overridepublic void handleError(ClientHttpResponse response) throws IOException {throw new RestClientResponseException(response.getStatusCode().value() + " " + response.getStatusText(),response.getStatusCode().value(), response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response));}protected byte[] getResponseBody(ClientHttpResponse response) {try {InputStream responseBody = response.getBody();if (responseBody != null) {return FileCopyUtils.copyToByteArray(responseBody);}} catch (IOException ex) {// ignore }return new byte[0];}protected Charset getCharset(ClientHttpResponse response) {HttpHeaders headers = response.getHeaders();MediaType contentType = headers.getContentType();return contentType != null ? contentType.getCharset() : null;}});// 修改StringHttpMessageConverter內容轉換器restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));return restTemplate;} catch (Exception e) {throw new Exception("網絡異常或請求錯誤.", e);} }/*** 接受未信任的請求** @return* @throws KeyStoreException* @throws NoSuchAlgorithmException* @throws KeyManagementException*/ @Bean public ClientHttpRequestFactory clientHttpRequestFactory()throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build();httpClientBuilder.setSSLContext(sslContext).setMaxConnTotal(MAX_CONNECTION_TOTAL).setMaxConnPerRoute(ROUTE_MAX_COUNT).evictIdleConnections(CONNECTION_IDLE_TIME_OUT, TimeUnit.MILLISECONDS);httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(RETRY_COUNT, true));httpClientBuilder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());CloseableHttpClient client = httpClientBuilder.build();HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(client);clientHttpRequestFactory.setConnectTimeout(CONNECTION_TIME_OUT);clientHttpRequestFactory.setReadTimeout(READ_TIME_OUT);clientHttpRequestFactory.setConnectionRequestTimeout(CONNECTION_REQUEST_TIME_OUT);clientHttpRequestFactory.setBufferRequestBody(false);return clientHttpRequestFactory; }

  關于RestTemplte配置的示意如下。

  • 設置RestTemplte統一異常處理器,統一返回RestClientResponseException。
  • 設置RestTemplte?HttpRequestFactory連接池工廠(HttpClientBuilder的build方法會創建PoolingHttpClientConnectionManager)。
  • 設置RestTemplte?StringHttpMessageConverter的編碼格式為UTF-8。
  • 設置最大連接數、路由并發數、重試次數、連接超時、數據超時、連接等待、連接空閑超時等參數。

五、接口中轉處理器設計

?  考慮到針對不同類型的接口直達請求會對應不同的接口中轉處理器,設計原則一定要明確(open-close)。平時也閱讀spingmvc源碼,很喜歡其中消息轉換器和參數解析器的設計模式(策略+模板方法)。仔細想想,接口中轉處理器的設計也可以借鑒一下。

  接口中轉處理器接口類

public interface IDirectUrlProcessor {/*** 接口直達策略方法* 處理接口直達請求* */ResponseEntity<String> handle(HttpServletRequest request) throws Exception;/*** 處理器是否支持當前直達請求* */boolean support(HttpServletRequest request); }

  接口定義了子類需要根據不同的策略實現的兩個方法。

  接口中轉處理器抽象類

public abstract class AbstractIDirectUrlProcessor implements IDirectUrlProcessor {private static Logger LOGGER = LoggerFactory.getLogger(AbstractIDirectUrlProcessor.class);@Autowiredprivate RestTemplate directRestTemplate;/*** 接口直達模板方法* */protected ResponseEntity<String> handleRestfulCore(HttpServletRequest request, URI uri, String userId) throws Exception {HttpMethod method = HttpMethod.resolve(request.getMethod());Object body;if (method == HttpMethod.GET) {body = null;} else {body = new BufferedReader(new InputStreamReader(request.getInputStream())).lines().collect(Collectors.joining());// post/formif (StringUtils.isBlank((String) body)) {MultiValueMap<String, String> params = new LinkedMultiValueMap<>();if (!CollectionUtils.isEmpty(request.getParameterMap())) {request.getParameterMap().forEach((paramName, paramValues) -> Arrays.stream(paramValues).forEach(paramValue -> params.add(paramName, paramValue)));body = params;}}}HttpHeaders headers = new HttpHeaders();CollectionUtils.toIterator(request.getHeaderNames()).forEachRemaining(headerName -> CollectionUtils.toIterator(request.getHeaders(headerName)).forEachRemaining(headerValue -> headers.add(headerName, headerValue)));RequestEntity directRequest = new RequestEntity(body, headers, method, uri);try {LOGGER.info(String.format("接口直達UserId = %s, RequestEntity = %s", userId, directRequest));ResponseEntity<String> directResponse = directRestTemplate.exchange(directRequest, String.class);LOGGER.info(String.format("接口直達UserId = %s, URL = %s, ResponseEntity = %s", userId, directRequest.getUrl(), directResponse));return ResponseEntity.ok(directResponse.getBody());} catch (RestClientResponseException e) {LOGGER.error("restapi 內部異常", e);return ResponseEntity.status(e.getRawStatusCode()).body(e.getResponseBodyAsString());} catch (Exception e) {LOGGER.error("restapi 內部異常,未知錯誤...", e);return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("restapi 內部異常,未知錯誤...");}} }

  抽象類中帶有接口直達模板方法,子類可以直接調用,完成請求的轉發。

  接口中轉處理器具體實現類

/*** 自助服務直達查詢*/ @Component public class SelfServiceIDirectUrlProcessor extends AbstractIDirectUrlProcessor {private static final String CONDITION_PATH = "/alipay-applet/direct";@Reference(group = "wmhcomplexmsgcenter")private IAlipayAppletUserInfoSV alipayAppletUserInfoSV;private void buildQueryAndPath(UriComponentsBuilder uriComponentsBuilder, AlipayAppletUser userInfo) {uriComponentsBuilder.path("/" + userInfo.getTelephone()).queryParam("channel", "10008").queryParam("uid", userInfo.getUserId()).queryParam("provinceid", userInfo.getProvinceCode());}public ResponseEntity<String> handle(HttpServletRequest request) throws Exception {String userId = JwtUtils.resolveUserId();AlipayAppletUser userInfo = alipayAppletUserInfoSV.queryUserInfo(userId);UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(AppletConstants.ISERVICE_BASEURL+ request.getServletPath().replace(CONDITION_PATH, StringUtils.EMPTY));if (StringUtils.isNotBlank(request.getQueryString())) {uriComponentsBuilder.query(request.getQueryString());}this.buildQueryAndPath(uriComponentsBuilder, userInfo);String url = uriComponentsBuilder.build().toUriString();URI uri = URI.create(url);return handleRestfulCore(request, uri, userId);}@Overridepublic boolean support(HttpServletRequest request) {return request.getServletPath().contains(CONDITION_PATH);} }

  接口中轉處理器具體實現類需要根據請求的URL判斷是否支持處理當前請求,如果中轉請求中帶有敏感信息(如手機號)需要特殊處理(UriComponentsBuilder 是一個不錯的選擇呦)。

六、總結

  接口中轉器擴展方便,只要按照如上方式根據不同類型的request實現具體的接口中轉處理器就可以了。另外就是接口文檔了,有了接口中轉處理器,只需要改一下真實服務的接口文檔就可以。比如真實服務的請求地址是http://172.17.20.92:28000/XXX/business/points/手機號信息,只需要改成http://172.17.20.92:28000/YYY/alipay-applet/direct/business/points。【手機號信息是敏感信息,需要后端從會話信息中獲取】。還有,不要問我為啥要花時間設計這個東西,第一領導同意了,第二開發周期理想,第三我喜歡!!!

轉載于:https://www.cnblogs.com/hujunzheng/p/10250403.html

總結

以上是生活随笔為你收集整理的SpringMvc接口中转设计(策略+模板方法)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。