Spring 線程池實戰


應用場景

老王上線了一個電商平台,最近需要開發一個功能,使用戶能夠直接點擊物流編號查詢到物流信息。通過對比,選擇了一個快遞聚合查詢平台作為接入方,但是由於使用的是免費賬號,快遞聚合平台只允許最多2個並發,否則查詢失敗。我們要如何幫助老王去解決這個問題呢?

聰明的程序員小張想了一下,很快就整理出來解決問題的思路:

  1. 首先用 Httpclient 實現與物流聚合平台的對接
  2. 創建一個物流查詢 Service 類,基於線程接口
  3. 創建一個 Sping 固定大小線程池
  4. Controller 接口通過線程池訪問物流接口,異步返回

POM.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.suoron.springmvc</groupId> <artifactId>myShop-threads</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!-- Apache Http Begin --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.5</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>fluent-hc</artifactId> <version>4.5.5</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpmime</artifactId> <version>4.5.5</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> <!-- Apache Http End --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.20</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.2.5.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>3.0-alpha-1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins </groupId> <artifactId>maven-compiler-plugin </artifactId> <version>2.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project> 

配置文件

web.xml

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!-- 字符集過濾器 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- spring mvc 前端控制器 --> <servlet> <servlet-name>springServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-context.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app> 

spring-context.xml

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd"> <context:annotation-config /> <context:component-scan base-package="com.suoron.springmvc.service.impl"/> <!-- 線程池配置 --> <bean id="myThreadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <!-- 核心線程數 --> <property name="corePoolSize" value="2" /> <!-- 最大線程數 --> <property name="maxPoolSize" value="2" /> <!-- 隊列最大長度 --> <property name="queueCapacity" value="1000" /> <!-- 線程池維護線程所允許的空閑時間 --> <property name="keepAliveSeconds" value="300" /> <!-- 線程池對拒絕任務(無線程可用)的處理策略 --> <property name="rejectedExecutionHandler"> <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" /> </property> </bean> </beans> 

spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <description>Spring MVC Configuration</description> <!-- 加載配置屬性文件 --> <!-- <context:property-placeholder ignore-unresolvable="true" location="classpath:config.properties"/> --> <!-- 使用 Annotation 自動注冊 Bean,只掃描 @Controller --> <context:component-scan base-package="com.suoron.springmvc.controller"/> <!-- 配置注解驅動 可以將request參數與綁定到controller參數上 --> <mvc:annotation-driven /> <!-- html視圖解析器 必須先配置freemarkerConfig,注意html是沒有prefix前綴屬性的--> <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath"> <value>/WEB-INF/views/html</value> </property> <property name="defaultEncoding" value="UTF-8" /> </bean> <bean id="htmlviewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <property name="suffix" value=".html" /> <property name="order" value="0"></property> <property name="contentType" value="text/html;charset=UTF-8"></property> </bean> <!-- 定義視圖解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 這里的配置我的理解是自動給后面action的方法return的字符串加上前綴和后綴,變成一個 可用的url地址 --> <property name="prefix" value="/WEB-INF/views/jsp/" /> <property name="suffix" value=".jsp" /> <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/> <!-- 不能用jstl的那個 --> <property name="contentType" value="text/html;charset=UTF-8"/> <property name="order" value="1"></property> </bean> <!-- 靜態資源映射 --> <mvc:resources mapping="/static/**" location="/static/" cache-period="31536000"/> <mvc:resources mapping="/json/**" location="/json/" cache-period="31536000"/> </beans> 

訪問代碼

HttpUtils.java -- 對httpclient進行了封裝

public class HttpUtils { public static String post(String url, Map<String,String> map){ // 創建 HttpClient 客戶端 CloseableHttpClient httpClient = HttpClients.createDefault(); // 創建 HttpPost 請求 HttpPost httpPost = new HttpPost(url); // 設置長連接 httpPost.setHeader("Connection", "keep-alive"); // 設置代理(模擬瀏覽器版本) httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"); // 設置 Cookie //httpPost.setHeader("Cookie", "UM_distinctid=16442706a09352-0376059833914f-3c604504-1fa400-16442706a0b345; CNZZDATA1262458286=1603637673-1530123020-%7C1530123020; JSESSIONID=805587506F1594AE02DC45845A7216A4"); // 創建 HttpPost 參數 List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>(); for(String key:map.keySet()){ params.add(new BasicNameValuePair(key, map.get(key))); } /* params.add(new BasicNameValuePair("draw", "1")); params.add(new BasicNameValuePair("start", "0")); params.add(new BasicNameValuePair("length", "10")); */ CloseableHttpResponse httpResponse = null; try { // 設置 HttpPost 參數 httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); httpResponse = httpClient.execute(httpPost); HttpEntity httpEntity = httpResponse.getEntity(); // 輸出請求結果 //System.out.println(EntityUtils.toString(httpEntity)); return EntityUtils.toString(httpEntity); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } // 無論如何必須關閉連接 finally { try { if (httpResponse != null) { httpResponse.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (httpClient != null) { httpClient.close(); } } catch (IOException e) { e.printStackTrace(); } } return null; } public static String get(String url){ // 創建 HttpClient 客戶端 CloseableHttpClient httpClient = HttpClients.createDefault(); // 創建 HttpGet 請求 HttpGet httpGet = new HttpGet(url); // 設置長連接 httpGet.setHeader("Connection", "keep-alive"); // 設置代理(模擬瀏覽器版本) httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"); // 設置 Cookie //httpGet.setHeader("Cookie", "UM_distinctid=16442706a09352-0376059833914f-3c604504-1fa400-16442706a0b345; CNZZDATA1262458286=1603637673-1530123020-%7C1530123020; JSESSIONID=805587506F1594AE02DC45845A7216A4"); CloseableHttpResponse httpResponse = null; try { // 請求並獲得響應結果 httpResponse = httpClient.execute(httpGet); HttpEntity httpEntity = httpResponse.getEntity(); // 輸出請求結果 //System.out.println(EntityUtils.toString(httpEntity)); return EntityUtils.toString(httpEntity); } catch (IOException e) { e.printStackTrace(); } // 無論如何必須關閉連接 finally { if (httpResponse != null) { try { httpResponse.close(); } catch (IOException e) { e.printStackTrace(); } } if (httpClient != null) { try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; } } 

LogisticsUtil.java -- 物流聚合平台查詢工具類

/** * * 快遞鳥物流軌跡即時查詢接口 * * @技術QQ群: 456320272 * @see: http://www.kdniao.com/YundanChaxunAPI.aspx * @copyright: 深圳市快金數據技術服務有限公司 * * DEMO中的電商ID與私鑰僅限測試使用,正式環境請單獨注冊賬號 * 單日超過500單查詢量,建議接入我方物流軌跡訂閱推送接口 * * ID和Key請到官網申請:http://www.kdniao.com/ServiceApply.aspx */ public class LogisticsUtil { public static byte[] decodeBase64(String input) throws Exception{ Class clazz=Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64"); Method mainMethod= clazz.getMethod("decode", String.class); mainMethod.setAccessible(true); Object retObj=mainMethod.invoke(input); return (byte[])retObj; } //DEMO public static void main(String[] args) { /* try { String base64encodedString = Base64.getEncoder().encodeToString("小黃人。。。".getBytes("utf-8")); System.out.println("Base64 編碼字符串 (基本) :" + base64encodedString); //System.out.println(new String(decodeBase64("5bCP6buE5Lq6Li4uLi4u"))); // 解碼 byte[] base64decodedBytes = Base64.getDecoder().decode("5bCP6buE5Lq644CC44CC44CC"); System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8")); } catch (Exception e) { e.printStackTrace(); } */ LogisticsUtil api = new LogisticsUtil(); try { String result = api.getOrderTracesByJson("ZTO", "75125101747966"); System.out.print(result); } catch (Exception e) { e.printStackTrace(); } } //電商ID private String EBusinessID="1436451"; //電商加密私鑰,快遞鳥提供,注意保管,不要泄漏 private String AppKey="9b4c6b10-84c6-4223-b1fe-6be2f0277f85"; //請求url private String ReqURL="http://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx"; /** * Json方式 查詢訂單物流軌跡 * @throws Exception */ public String getOrderTracesByJson(String expCode, String expNo) throws Exception{ String requestData= "{'OrderCode':'','ShipperCode':'" + expCode + "','LogisticCode':'" + expNo + "'}"; Map<String, String> params = new HashMap<String, String>(); params.put("RequestData", urlEncoder(requestData, "UTF-8")); params.put("EBusinessID", EBusinessID); params.put("RequestType", "1002"); String dataSign=encrypt(requestData, AppKey, "UTF-8"); params.put("DataSign", urlEncoder(dataSign, "UTF-8")); params.put("DataType", "2"); //String result=sendPost(ReqURL, params); String result = HttpUtils.post(ReqURL,params); //根據公司業務處理返回的信息...... return result; } /** * base64編碼 * @param str 內容 * @param charset 編碼方式 * @throws UnsupportedEncodingException */ private String base64(String str, String charset) throws UnsupportedEncodingException{ //String encoded = base64Encode(str.getBytes(charset)); String encoded = Base64.getEncoder().encodeToString(str.getBytes(charset)); return encoded; } 

MD5Utils.java -- MD5加密工具類,物流接口數據加解密

public class MD5Utils { /** * MD5加密 * @param str 內容 * @param charset 編碼方式 * @throws Exception */ //@SuppressWarnings("unused") public static String MD5(String str, String charset) throws Exception { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(str.getBytes(charset)); byte[] result = md.digest(); StringBuffer sb = new StringBuffer(32); for (int i = 0; i < result.length; i++) { int val = result[i] & 0xff; if (val <= 0xf) { sb.append("0"); } sb.append(Integer.toHexString(val)); } return sb.toString().toLowerCase(); } } 

LogisticsService.java -- 物流查詢service類

public interface LogisticsService extends Callable<String>, Serializable { public void setExpInfo(String expCode,String expNo); } 

LogisticsServiceImpl.java -- 物流查詢實現類

@Service
@Scope("prototype") public class LogisticsServiceImpl implements LogisticsService, Callable<String>, Serializable { private String expCode; private String expNo; public void setExpInfo(String expCode, String expNo) { this.expCode = expCode; this.expNo = expNo; } @Override public String call() throws Exception { LogisticsUtil api = new LogisticsUtil(); try { Thread.sleep(2000); String result = api.getOrderTracesByJson(expCode, expNo); //System.out.print(result); return result; } catch (Exception e) { e.printStackTrace(); } return null; } }

LogisticsController.java -- 訪問controller



作者:索倫x
鏈接:https://www.jianshu.com/p/e961916e28bd
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM