某電商平臺開發記要——客服系統 Java Web開發模式 Java開發web的幾種開發模式 String中intern的方法

假如網站須要提供客服功能,若是隻是簡單的聊天諮詢能夠考慮營銷QQ、百度商橋等(目前大部分網站採用此方式,包括一些知名行業電商);若是須要更精細化的管理,好比客服人員安排、各項數據統計彙總,那麼須要對接專業的第三方客服平臺,好比網易七魚,固然價格不菲;然而如果如京東自己就是一個平臺,須要爲每一個商家提供各自的客服管理,首先目前第三方提供商並沒有此類產品(網易七魚聽說已經開發出來了,可是官網上沒找到),其次即便有,價格也確定不便宜,並且數據在別人那裏總歸很差。因此電商平臺的客服系統,通常都是本身開發。固然了,藉助優秀的開源項目,自主開發[一套簡單能用的]也變得輕鬆不少。html

我採用了openfire+spark+layim,前二者基於java平臺,layim是國人開發的一個webim前端組件。前端

先來看大體效果(左邊是瀏覽器layim-客戶提諮詢,右邊是spark聊天窗口-客服解答)java

圖示:node

本文涉及到的知識點(雜亂,後續會不按期添加內容):linux

Java基礎android

Intellij Idea:Java IDEweb

Mybatis:半ORMajax

XMPP協議spring

smack:XMPP協議的Java封裝sql

openfire

fastpath:openfire插件,咱們須要依賴它實現客服功能

wechat

spark


一秒鐘入門Java

Java SE(J2SE):Standard Edition,可認爲是基礎庫,用於開發和部署桌面、服務器以及嵌入設備(J2ME)和實時環境中的Java應用程序。

Java EE(J2EE):基於SE的高級庫,提供 Web 服務、組件模型、管理和通訊 API,能夠用來實現企業級的面向服務體系結構。

能夠知道J2EE比J2SE多了Web相關的組件和API,可是本人在使用SpringMVC框架開發Web應用程序時,去官網Java SE頁面下載的JDK,也能正常開發。後來查看官網的Java EE的下載頁面,發現提供的SDK中主要包含一個叫GlassFish的開源組件和一些示例及文檔,而Java EE剛開始是以一種規範提出,GlassFish能夠看做是實現了這些規範的JEE容器,而咱們開發Web站點時部署到服務器(好比Tomcat),實現了JEE規範其中的Servlet容器部分,因此以JDK開發Web並不會出現問題。

JNDI 是什麼 :簡單的說就是爲了解耦,非直接引用,而是經過名稱或地址查找而後加載的方式,經常使用依賴注入的方式實現。

目前流行的IDE有Eclipse和IntelliJ IDEA,前者免費且因爲歷史關係佔有率一直很高,後者也有社區版,聽說使用性上目前完勝前者。

final關鍵詞:相似於.NET的readonly

匿名內部類

定義一個類A(能夠爲abstract),爲方便說明,在A中定義一個[抽象]方法dosth。在調用方法裏能夠直接new A,而且同時給dosth賦方法體。

public abstract A{    
    public void dosth() {
    }
}

public abstract B{    
    public void call() {
        final A a = new A() {
            public void dosth() {
                //這裏寫方法體
            }
        };
    }
}

看着是實例化了A的一個對象,實際上是實例化了A類的匿名子類。

Access restriction:eclipse對某些java包(or 類?)有access rules,好比 sun.awt.shell.ShellFolder。由於這些JAR默認包含了一系列的代碼訪問規則(Access Rules),若是代碼中引用了這些訪問規則所禁止引用類,那麼就會提示這個錯誤信息。解決方法:既然存在訪問規則,那麼修改訪問規則便可。打開項目的Build Path Configuration頁面,打開引用的[報錯]JAR包,選中Access rules條目,選擇右側的編輯按鈕,添加一個訪問規則便可。

Java NIO

Apache Mina

CopyOnWrite:CopyOnWrite容器即寫時複製的容器。通俗的理解是當咱們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,複製出一個新的容器,而後新的容器裏添加元素,添加完元素以後,再將原容器的引用指向新的容器。這樣作的好處是咱們能夠對CopyOnWrite容器進行併發的讀,而不須要加鎖。從JDK1.5開始Java併發包裏提供了兩個使用CopyOnWrite機制實現的併發容器,它們是CopyOnWriteArrayList和CopyOnWriteArraySet。

Maven:項目管理工具。不像VS,eclipse是更純粹的編碼工具,在維護jar包和項目之間的依賴關係、項目的構建目標等方面的功能比較弱(好比拷貝了一個項目,咱們須要手動去Configure Build Path),而Maven就是補足於此。Maven獨立於IDE,eclipse有一個插件叫M2E,裏面內置了Maven。Maven項目的配置信息保存在pom.xml文件中。

咱們在導入Maven項目時,有時會發現不止一個pom.xml,那是由於項目中有子項目(module,module有本身的pom.xml),只要選擇最頂層的pom.xml文件便可,會自動加載引用到的子項目。

JavaBean:通常可看做是POJO,可參看 Java Bean 是個什麼概念? (不過這個問題裏有個答主說Java沒有事件的概念,讓我大吃一驚,不過轉念一想,Java主要用於開發服務端應用,確實不怎麼涉及到[自定義]事件。其實Java中是有事件機制的,只是不知變通,就一個半成品的觀察者模式,想一想C#的委託,其實就一個函數指針的事)

MVC:當.Neter們在被Asp.Net的重量壓得踹不過氣來的時候,Java已經有MVC的概念了。不少模式,.Net界都是直接copy,.Neter們並無對其歷史的認知,因此接收不能,MVC就是如此。其實在Asp.Net時代已經有MVC的影子,就是通常處理程序.ashx。很早之前,用戶提交都是提交到具體的一個頁面,因而會常常致使一個頁面並非用於顯示,而是用於業務邏輯的處理,因而後來把業務邏輯單獨拎出來,這即是Controller,用戶請求的是Controller,再也不是具體頁面,而且Controller裏再也不使用相似HttpRequest或者HttpResponse獲取數據和返回響應,而是使用對象的形式(M),這即是MVC模式。可參看 Java Web開發模式

Java中的註解至關於.NET中的Attribute。

Spring是一個IOC和AOP框架。咱們能夠經過在xml文件中配置bean,而後在代碼中使用@Autowired或@Resource注入bean實例,不過配置的環節稍顯繁瑣,能不能省略呢?答案是確定的,Sping2.5開始支持註解注入,具體可看 spring註解注入:<context:component-scan>詳解。須要注意的是,@Component及相關的幾個註解類,在應用到interface上的時候,可能並不如預期工做,由於interface並不能實例化,而這幾個註解類貌似又沒有@Inherited修飾,因此就算有實現類或運行時的動態實現類,也不會註冊到上下文中;且修飾的類要有公共構造函數。另外注入[被注入方]通常只能在注入方自己是已註冊的bean裏,若在普通類裏想經過@Autowired或@Resource的方式注入bean,則稍微有點繞,可參看 Java普通類獲取Spring XML中Bean的方法總結

關於Servlet、Struts、Spring、SpringMVC的關係與區別可參看 Java開發web的幾種開發模式 和 SpringMVC與Struts2的對比

SpringMVC居然URL和參數大小寫敏感,雖然有辦法配置,但這種預設沒有道理吧。。。

Servlet url-pattern /與/*區別:二者的長度不一樣,根據最長路徑匹配的優先級,/*比/更容易被選中,而/的真正含義是,缺省匹配。既全部的URL都沒法被選中的時候,就必定會選中/,可見它的優先級是最低的,這就二者的區別。

xml文件也能夠打包進jar包,可是訪問jar包裏的xml文件就不能按文件目錄的方式來了,可參看 http://blog.csdn.net/jianxin1009/article/details/18814799 

application.getInitParameter:jsp中9個內置對象之一application,它的數據對整個web應用都有效,application有一個重要的用途就是經過getInitParameter()獲取web.xm中的配置參數,這樣能夠提升代碼的移植性。

dwr:簡化ajax調用,使得調用遠程服務器方法看上去像調用本地方法同樣。

在java項目中必不可少的是咱們要指定一個jdk。在指定jdk的同時,還能夠指定jdk的Language level,這個有點像咱們工程最低支持版本。好比Language level 設置了5.0 只是就不能出現使用6.0/7.0特性的代碼,由於這些特性在5.0的環境下是沒法編譯的。或者能夠理解ide會安裝Language level指定的jdk版原本對咱們的代碼進行編譯,以及錯誤檢查。即一樣的jdk對應不一樣的Language Level會採用[可能]不一樣的編譯和優化方式。

Java中也有相似.Net的字符串池的概念,請看 String中intern的方法

Java插件技術: OSGi

貌似在同一package下,protected可見。(和.NET不一樣)

Java的泛型類型只能是引用類型,而不能是基礎類型,可是Java針對每一個基礎類型有對應的封裝類型,好比boolean對應Boolean,後者是引用類型,能夠爲null,當封裝類型不爲null時,能夠隱式轉換,但寫代碼時null的狀況要本身處理,如

private boolean existUser(String username) {
    Boolean result = null; 
    return result != null && result.booleanValue();
}

Ant:相似於.NET的MSBuild,其構建文件默認爲build.xml(能夠在其中指定構建基於的Java平臺版本),每一個構建文件都對應於一個項目,可是大型項目常常包含大量的子項目,每個子項目均可以有本身的構建文件。

一個.java文件中能夠定義多個類,可是public修飾的只能至多有一個,且要與文件名相同,編譯後,有幾個類就會產生幾個對應的.class文件。jar包相似.Net的dll,它將多個.class文件打包一塊。大多數 JAR 文件包含一個 META-INF 目錄,它用於存儲包和擴展的配置數據,如安全性和版本信息。Java 2 平臺識別並解釋 META-INF 目錄中的下述文件和目錄,以便配置應用程序、擴展和類裝載器。具體可看 MANIFEST.MF 文件內容徹底詳解

System.getProperty()獲取系統/項目全局變量,好比Java運行時版本,固然咱們也能夠經過System.setProperty()設置自定義變量。

Java桌面客戶端編程:Java Swing 。桌面程序畢竟不是Java的主流領域,所以各IDE貌似也並未做太多努力,相較VS的所見即所得的控件拖拽開發模式,Java GUI編程就吃力不少了。

Java國際化:i18n,注意中文的資源文件,貌似須要先UTF-8轉碼,大約就是像這樣。(可使用JDK自帶的native2ascii.exe)


Intellij Idea

使用Intellij Idea建立spring mvc時(沒用maven),run都報 Error during artifact deployment. See server log for details 錯誤,後來把lib文件夾拷到WEB-INFO文件夾下就沒問題了,不知何故。

緣由:tomcat默認是去web-info/lib/下找依賴的jar包。手動拷jar包畢竟不是一個好辦法,其實咱們能夠在下圖處進行Artifacts設置

運行項目,項目目錄下會多出一個out文件夾,生成全部的站點文件,依賴包會自動拷貝到下面的WEB-INF/lib/下,以下圖:

IDEA配置artifacts中Web Application:Exploded和Web Application:Archive的區別:前者以文件夾形式(War Exploded)發佈項目,後者以war包形式(每次都會從新打包所有的)。Tomcat會自動解壓war包並啓動站點,缺點是會形成一段時間的站點不可用,而以文件夾形式發佈的話,則支持熱部署(需進行額外的一些配置)。

固然咱們也可使用Maven進行依賴包的管理。在當前項目右鍵->Add Framework Support->Maven便可。注意須要在Project Structure-> Project Settings中移除以前非Maven引用的包依賴。此時運行項目,項目目錄下會多出一個target文件夾,其下有生成的站點文件。可是運行時發現WEB-INF下的文件除了web.xml外,其它的文件都不會覆蓋,貌似用maven管理的web工程,須要將applicationContext.xml等資源文件放在resource目錄下,而後以classpath的方式去訪問。後來發現jsp頁面也沒法自動更新到target目錄,再後來據說maven有一套約定的目錄結構,貌似又能夠經過pom.xml進行自定義配置,神煩!目前靠手動覆蓋。參考 Maven使用點滴 配置便可(webappDirectory我沒設置,就設置了warSourceDirectory,能正常更新了

Intellij Idea中有個Ant Build Window,默認顯示的是主項目下build.xml中的targets,and by default, IDEA only shows the default target and targets that have descriptions。對這個有疑問可參看 How to get Ant Build to list all targets in a hierarchy of build files.

能夠在Run/Debug Configurations Window中設置自定義系統變量,以下圖(-D不能省):


MyBatis

一個半ORM框架,SQL語句並非像EF同樣由框架解析,而是要預先寫在xml中或者寫在Java註解(同.Net的Attribute)中,且不支持匿名類型(即select出來的數據要麼是基礎類型,要麼要有對應的Java Bean)。通常狀況下,咱們使用resultType映射查詢結果和對象便可(MyBatis 會在幕後自動建立一個 ResultMap),當只想映射部分字段或者包含複雜類型屬性的時候,咱們須要自定義ResultMap。

MyBatis不支持方法重載,由於它是經過方法名稱(不加參數)去查找執行方法,所以咱們設置不一樣的方法名,或者使用動態sql。


XMPP協議

JID表示一個地址,由三部分組成——node、domain和resource。例如:xiaoming@xiaoming.home/sleeping,xiaoming就是node ,xiaoming.home就是domain,sleeping就是resource。node domain 和resource任何一部分都不能超過1023 字節 ,加上@和 /,一個JID 總共不能超過3071字節。BareJid就是去掉resource,只包含node@domain。

XMPP包含IQ, message and presence 三種packet。


smack

ConnectionConfiguration.Builder的setXmppDomain和setHost的區別?一個是域(服務器集羣),一個是其中的一臺服務器,應該只要設置其中一個就能夠了。

使用XMPPTCPConnectionConfiguration創建鏈接時報空指針錯誤,調試發現有個base64encoder未賦值,須要引用smack-java7包,該包會初始化base64encoder,若是是安卓開發,那麼就引用smack-android。


openfire 

使用idea導入openfire代碼,過程可參考將openfire源碼部署到IDEA中 或者 IntelliJ IDEA搭建openfire4.1.3開發環境 。使用openfire配置界面只能配置一個數據庫,且我也不打算徹底依賴它生成的數據庫。我須要openfire部分功能使用現有的數據庫(好比用戶表),而openfire的業務數據仍然使用生成的數據庫,所以涉及到多庫鏈接。這隻能去修改源碼了。

上面說到的配置界面設置的項最終存儲在ofproperty表中。在配置界面完成配置後,咱們也能夠在conf/openfire.xml中從新設置值,重啓openfire,配置文件中的值會更新到數據庫中。

以AuthFactory爲例,其initProvider方法裏有 JiveGlobals.migrateProperty("provider.auth.className"); ,XMLProperties根據"provider.auth.className"讀取xml文件中的值(getProperty方法)

//按逗號拆分爲數組
String[] propName = parsePropertyName(name);
// Search for this property by traversing down the XML hierarchy.
Element element = document.getRootElement();
for (String aPropName : propName) {
    element = element.element(aPropName);
    if (element == null) {
        return null;
    }
}
value = element.getTextTrim();

對應的配置節寫法以下(能夠看到,propName對應各層級element,而非attribute形式)

<provider>
    <auth>
        <className>org.jivesoftware.openfire.auth.JDBCAuthProvider</className>
    </auth>
</provider>    

然後覆蓋數據庫值

public void migrateProperty(String name) {
    if (getProperty(name) != null) {
        if (JiveGlobals.getProperty(name) == null) {
            JiveGlobals.setProperty(name, getProperty(name));
            deleteProperty(name);
        }
        else if (JiveGlobals.getProperty(name).equals(getProperty(name))) {
            deleteProperty(name);
        }
        else if (!JiveGlobals.getProperty(name).equals(getProperty(name))) {
            Log.warn("XML Property '"+name+"' differs from what is stored in the database.  Please make property changes in the database instead of the configuration file.");
        }
    }
}

固然,如果咱們有數據庫權限,直接進入數據庫修改也同樣。

openfire源碼採用JDBC方式操做數據庫,並且沒有作很好的封裝,重複代碼較多,以下圖所示

類似代碼在與數據庫交互的地方隨處可見。部分邏輯的抽取,莫過於lambda(回調函數)的方式。考慮到Java8已經支持lambda表達式,重構以下:

public <T> T excuteQuery(String queryText, Function<ResultSet, T> func) {
    T result = null;
    Connection con = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
        con = getConnection();
        pstmt = con.prepareStatement(queryText);
        rs = pstmt.executeQuery();
        if (rs.next()) {
            result = func.apply(rs);
        }
    } catch (SQLException e) {
        Log.error(e.getMessage(), e);
    } finally {
        DbConnectionManager.closeConnection(rs, pstmt, con);
    }
    return result;
}

可是在寫調用代碼的時候提示:

雖然咱們在excuteQuery方法中已經catch了這個異常,可是編譯器並不買帳。並且就算咱們在方法定義時已經throws了相關異常,也沒用,以下圖:

解決方法有兩種:能夠在lambda體內catch異常後再也不throw;或者自定義一個Functional Interface,其中聲明一個定義了異常的方法,

@FunctionalInterface
public interface CheckedSQLExceptionFunction<T, R> {
    R apply(T t) throws SQLException;
}

而後將Function<Result,T>的地方替換爲CheckedSQLExceptionFunction<ResultSet, T>。這兩種都顯得彆扭與不合理,致使這一問題的是,Java Lambda規定若是Lambda中拋出了異常,那麼這個異常必定要在Functional Interface中的abstract方法上定義。這是一個讓人沒法理解的規定。

遇到lambda的另外一個坑:

因爲username有從新賦值,因此編譯報錯,是否是很喜感?我不得不用一個臨時變量解決。。

官方提供了一種集成外部用戶體系的方法(Custom Database Integration Guide),而後並不支持加鹽密碼,因而我只能本身擼碼解決。關鍵是實現兩個接口:AuthProvider 和 UserProvider,只要實現部分方法便可,很簡單不贅述。

部署

部署到centos7。首先 rpm -qa | grep openjdk 查看全部已安裝的jdk,若是版本不知足則先 rpm -e --nodeps [java-1.7.0-openjdk[-headless]] 卸載掉。而後去官網上下載合適版本的server jre/jre/sdk包(下面會進一步說明),而後解壓,設置環境變量,就算安裝完畢了(不過這種安裝方式經過rpm -qa但是找不到的哦)。具體可看 Centos7 JDK8安裝配置。

講道理,jdk是開發時候用的,部署的話咱們只要安裝jre就能夠了。我剛開始下載的是server jre包,在ant的時候報 package javafx.util does not exist 的錯(由於我在代碼裏用到了Pair<>二元組,屬於javafx.util包),然而網上查了下,貌似javaFX是用於客戶端GUI方面的組件(不知道是否我這裏報錯的javafx同個概念)。我懶得探究,立刻去官網下了jre包(官網說Covers most end-users needs. Contains everything required to run Java applications on your system.),載下來以後發現果真有jfxrt.jar(包含javafx.util),歡欣鼓舞,可是ant以後報沒法找到/lib/tools.jar——由於build.xml裏有用到這個jar——以前server jre是有的,也是日了狗了。立刻去下jdk,瘋狂操做以後終於編譯經過。

也能夠在windows平臺編譯打包,而後拷貝到linux系統。

官網上是說./openfire start啓動openfire,然而我只找到openfire.bat和openfirectl,先試了./openfirectl start 報錯:Could not find Openfire installation under /opt,/usr/share,or /usr/local,查看openfirectl的shell代碼,發現當OPENFIRE_HOME未設置時,會去這三個目錄下找openfire,因而爲其設置真實根目錄,然而雖沒報錯,但仍是沒有運行起來。試了下openfire.bat,報Permission denied,尼瑪,我但是用root登陸的。先無論緣由,我再去官網下了4.1.6(目前最新版)的tar包,發現bin目錄下果真有個openfire文件,拷到服務器上後報一樣的Permission denied的錯誤——網上說root並不默認就有全部文件的最高權限,可是他能夠隨意給本身增長權限——好吧,設置了權限以後,執行./openfire start 沒報錯,可是依舊沒有運行起來。。。後來發現沒有輸出錯誤信息,是由於shell裏寫了/dev/null 2>&1,去掉以後終於提示——Could not find or load main class com.install4j.runtime.launcher.UnixLauncher——shell代碼裏該類指向的目錄本地編譯不存在,最後在官網tar包裏發現有一個名爲.install4j的隱藏文件夾,拷貝後總算運行起來了。 

記得打開相應端口。


webchat

用戶通常都是經過瀏覽器進行諮詢,有個webchat示例能夠參考(openfire4.2 配置fastpath、webchat、spark實現客服系統),但那是基於好久之前的smack版本,轉過來也費了很多勁,特別是QueueUpdate包擴展已經再也不內置支持,調試了半天在smack中找到幾個關鍵文件,這些都是內置資源文件,項目運行時會讀取這些文件,調用ProviderManager.addExtensionProvider將配置項緩存起來,若是不修改xml的話,那麼在外部調用該方法也是能夠的。參照着寫了一個QueueUpdateProvider,順便了解了下XmlPullParser的用法。

關於自定義包和擴展,後來才發現官網上有介紹: Provider Architecture: Stanza Extensions and Custom IQ's,也是心累。

再後來,發現部分非內置的擴展的Provider已經在擴展類裏[做爲內部類]定義好了,好比QueueUpdate.Provider。。。吐血。關於內部類可參看 java中的內部類總結

部署

在CentOS安裝tomcat9.0.1。去官網下載tar.gz包,解壓,而後去到bin目錄,在catalina.sh文件添加內容export CLASSPATH=$JAVA_HOME/lib,而後./startup.sh便可,另外記得開放8080端口。固然咱們能夠更改端口以及綁定域名,參考 tomcat發佈應用並配置域名。關於項目打包成war包,參考 Intellij IDEA社區版打包Maven項目成war包,並部署到tomcat上


fastpath

增長几個http接口,如新增客服組,添加客服等,示例代碼以下:

public class MasonServlet extends HttpServlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        AuthCheckFilter.addExclude("fastpath/mason/*"); // 公共接口不需身份校驗
    }

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String action = request.getRequestURI();
        action = action.substring(action.indexOf("mason/") + 6);
        OPResult result = null;
        if (action.toLowerCase().equals("createworkgroup")) {
            String wgName = request.getParameter("wgName");
            String description = request.getParameter("description");
            String agents = request.getParameter("agents");

            result = createWorkgroup(wgName, description, agents);
        }
        if (result == null) {
            result = new OPResult();
            result.setSuccess(false);
            result.setMessage("未找到對應方法");
        }
        response.setContentType("application/json; charset=utf-8");
        response.setCharacterEncoding("UTF-8");
        Genson genson = new Genson();
        String json = genson.serialize(result);
        response.getOutputStream().write(json.getBytes("UTF-8"));
    }

    // 新增工做組(會同時創建一個默認客服組,每一個工做組能夠包含多個客服組)
    private OPResult createWorkgroup(String wgName, String description, String agents) {
        OPResult result = new OPResult();
        Map errors = WorkgroupUtils.createWorkgroup(wgName, description, agents);
        if (errors.size() == 0) {
            Workgroup workgroup = WorkgroupManager.getInstance().getWorkgroup(wgName);
            result.setData(workgroup.getJID());
            result.setSuccess(true);
        } else
            result.setSuccess(false);
        return result;
    }
}

完了咱們就能夠從新構建該插件了,在intellij中能夠在窗口中設置(看了下build.xml,發現plugin任務能夠構建單個插件,它接收plugin的參數代表構建的是哪一個插件):

 

因爲代碼中用到了genson這個第三方jar包,雖然直接編譯沒問題(項目的其它地方有引用),但用ant構建的時候會報錯,提示找不到這個組件,緣由官網說了:Any JAR files your plugin needs during compilation should be put into the lib directory,所以咱們須要將該jar包複製一份到fastpath/lib目錄下。


spark

此spark非彼spark,而是一個開源IM桌面客戶端。下載下來2.8.3代碼,導入到IntelliJ,運行輸出了空指針異常,調試發現找不到資源文件 "META-INF/plugins.xml",查看編譯後的jar文件,裏面已經包含了resources/META-INF/plugins.xml。再查看Project Structure,發現沒有爲主模塊Spark設置Resource Folders,添加了resources文件夾後編譯運行正常,此時再看jar文件,裏面並無resources目錄,META-INF直接在根目錄體現。

也就是說,將某個目錄設置爲資源文件夾(Resource Folders),意即將該目錄下的子目錄一塊兒打包進jar包(不包含該目錄自己),而getResource()方法獲取特定路徑的資源時,是直接去jar包根目錄下查找對應文件。

彷佛還要設置VM arguments:-Djava.library.path=build/lib/dist/windows64,具體值按照操做系統來。參看 openfire-spark 二次開發-(二)運行環境配置

 

相關資料:TCP長鏈接與短鏈接、心跳機制

 

轉載請註明本文出處:http://www.cnblogs.com/newton/p/7269373.html

相關文章
相關標籤/搜索