本文所述文件服務組件在筆者此前一篇文章中已有闡述(基于netty的文件上傳下載組件),不過本文將基于之前這個實現再次進行升級改造,利用基于注解的方式進行自動裝配。
1. 簡介
1.1 Netty簡介
Netty是一個異步事件驅動的網絡應用程序框架,用于快速開發可維護的高性能協議服務器和客戶端。關于其詳細的介紹可以參考Netty官方網站。
Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.
Netty主要特點:
Unified API for various transport types - blocking and non-blocking socket(統一API)
Based on a flexible and extensible event model which allows clear separation of concerns(事件模型)
Highly customizable thread model - single thread, one or more thread pools such as SEDA(線程模型)
True connectionless datagram socket support (since 3.1)(無連接數據報文Socket支持)
1.2 組件功能介紹
該組件基于netty3.6.3實現,具有如下功能:文件上傳,文件替換,文件刪除,如果是圖片的話,還可以生成縮略圖等功能。使用簡單,只需要引入commons-doc-client-netty,即可以實現文件的以上操作。
本組件分為三個module,分別為:
commons-doc-server-netty:Netty實現文件服務組件的服務端
commons-doc-common:Netty文件服務組件公共組件
commons-doc-client-netty:Netty文件服務組件的客戶端
2. 服務端
2.1 功能簡介
服務端組件實現以下功能:文件上傳,文件替換,文件刪除,如果是圖片的話,還可以生成縮略圖等功能。代碼結構如下圖所示,
所有的文件服務都是基于接口DocServerProcessor進行的,主要有以下幾個實現類:
UploadDocServerHandler實現文件上傳服務
ReplaceDocServerHandler實現文件替換服務
DeleteDocServerHandler實現文件刪除服務
CreateThumbPictureServerHandler實現創建圖片縮略圖服務
2.2 實現步驟
具體實現步驟以文件上傳為例。
首先 org.fortune.doc.server.support.DocServerHandler類會持續監聽客戶端的請求,如果是文件處理動作,則會進入messageReceived方法進行相應的處理邏輯。該類定義了以下成員變量:
//http請求
private HttpRequest request;
//是否需要斷點續傳作業
private boolean readingChunks;
//接收到的文件內容
private final StringBuffer responseContent = new StringBuffer();
//解析收到的文件
private static final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); //16384L
//post請求的解碼類,它負責把字節解碼成Http請求。
private HttpPostRequestDecoder decoder;
//請求參數
private RequestParam requestParams = new RequestParam();
該方法實現中,如果文件大小小于chunked的最小值,則直接進行文件上傳操作。否則,需要進行分塊處理。然后進行文件上傳操作。
文件大小小于1k的操作:
if (request.isChunked()) { //說明還沒有請求完成,繼續
this.readingChunks = true;
LOGGER.info("文件分塊操作....");
} else {
LOGGER.info("文件大小小于1KB,文件接收完成,直接進行相應的文件處理操作....");
//請求完成,則接收請求參數,進行初始化請求參數
RequestParamParser.parseParams(this.decoder, this.requestParams);
//根據請求參數進行相應的文件操作
LOGGER.info("文件處理開始....requestParams參數解析:{}",requestParams);
String result = DocServerHandlerFactory.process(this.requestParams);
LOGGER.info("文件處理結束....FileServerHandlerFactory處理結果:{}",result);
this.responseContent.append(result);
//給客戶端響應信息
writeResponse(e.getChannel());
e.getFuture().addListener(ChannelFutureListener.CLOSE);
}
需要分塊處理操作:
HttpChunk chunk = (HttpChunk) e.getMessage();
try {
//chunk.getContent().capacity();
LOGGER.info("文件分塊操作....文件大小:{} bytes",chunk.getContent().capacity());
this.decoder.offer(chunk);
} catch (HttpPostRequestDecoder.ErrorDataDecoderException e1) {
e1.printStackTrace();
this.responseContent.append(e1.getMessage());
writeResponse(e.getChannel());
Channels.close(e.getChannel());
return;
}
if (chunk.isLast()) {
//文件末尾
this.readingChunks = false;
LOGGER.info("到達文件內容的末尾,進行相應的文件處理操作....start");
RequestParamParser.parseParams(this.decoder, this.requestParams);
LOGGER.info("文件處理開始....requestParams參數解析:{}",requestParams);
String result = DocServerHandlerFactory.process(this.requestParams);
LOGGER.info("文件處理結束....FileServerHandlerFactory處理結果:{}",result);
this.responseContent.append(result);
//給客戶端響應信息
writeResponse(e.getChannel());
e.getFuture().addListener(ChannelFutureListener.CLOSE);
LOGGER.info("到達文件內容的末尾,進行相應的文件處理操作....end");
}
以上操作主要有兩個注意點:
請求參數的解析工作(根據HttpDataType進行相應參數的賦值操作)
根據解析的參數進行相應的文件處理操作(根據文件操作類型,選擇相應的處理句柄進行文件處理)
3. 客戶端
3.1 功能簡介
客戶端組件主要提供對外訪問服務端組件的接口,提供以下接口:文件上傳,文件替換,文件刪除,如果是圖片的話,還可以生成縮略圖等功能。代碼結構如下:
org.fortune.doc.client.DocClient類是對外提供接口的工具類,具有以下主要方法:
uploadFile 文件上傳,對應文件處理句柄類為:org.fortune.doc.client.handler.UploadDocClientHandler
deleteFile 刪除服務端文件,對應文件處理句柄類為:org.fortune.doc.client.handler.DeleteDocClientHandler
replaceFile 替換服務端文件,對應文件處理句柄類為:org.fortune.doc.client.handler.ReplaceDocClientHandler
createThumbPicture 生成縮略圖,對應文件處理句柄類為:org.fortune.doc.client.handler.CreateThumbPictureClientHandler
3.2 實現步驟
實現步驟以上傳文件為例,其他類似實現。直接上代碼:
/**
* 文件上傳
* @param file 需要上傳的文件
* @param fileName 文件名稱
* @param thumbMark 是否需要生成縮略圖
* @return
* @author:landyChris
*/
public static String uploadFile(File file, String fileName,
boolean thumbMark) {
DocClientPipelineFactory clientPipelineFactory = new DocClientPipelineFactory();
//輔助類。用于幫助我們創建NETTY服務
ClientBootstrap bootstrap = createClientBootstrap(clientPipelineFactory);
String strThumbMark = Constants.THUMB_MARK_NO;
if (thumbMark) {
strThumbMark = Constants.THUMB_MARK_YES;
}
//具體處理上傳文件邏輯
uploadFile(bootstrap, DocClientContainer.getInstance().getHost(),
DocClientContainer.getInstance().getPort(), file, fileName, strThumbMark,
DocClientContainer.getInstance().getUserName(),
DocClientContainer.getInstance().getPassword());
Result result = clientPipelineFactory.getResult();
if ((result != null) && (result.isSuccess())) {
return result.getFilePath();
}
return null;
}
具有三個參數,前面幾行代碼都是很一些netty的初始化工作,具體看一個私有方法uploadFile,如下代碼所示:
private static void uploadFile(ClientBootstrap bootstrap, String host,
int port, File file, String fileName, String thumbMark,
String userName, String pwd) {
//1.構建uri對象
URI uri = getUri(host, port);
//2.連接netty服務端
ChannelFuture future = bootstrap.connect(new InetSocketAddress(host,
port));
//3.異步獲取Channel對象
Channel channel = future.awaitUninterruptibly().getChannel();
if (!future.isSuccess()) {
future.getCause().printStackTrace();
bootstrap.releaseExternalResources();
return;
}
//4.初始化文件上傳句柄對象
AbstractDocClientHandler handler = new UploadDocClientHandler(host, uri,
file, fileName, thumbMark, userName, pwd);
//5.獲取Request對象
HttpRequest request = handler.getRequest();
//6.獲取Http數據處理工廠
HttpDataFactory factory = getHttpDataFactory();
//7.進行數據的包裝處理,主要是進行上傳文件所需要的參數的設置,此時調用的句柄是具體的UploadFileClientHandler對象
HttpPostRequestEncoder bodyRequestEncoder = handler
.wrapRequestData(factory);
//8.把request寫到管道中,傳輸給服務端
channel.write(request);
//9.做一些關閉資源的動作
if (bodyRequestEncoder.isChunked()) {
channel.write(bodyRequestEncoder).awaitUninterruptibly();
}
bodyRequestEncoder.cleanFiles();
channel.getCloseFuture().awaitUninterruptibly();
bootstrap.releaseExternalResources();
factory.cleanAllHttpDatas();
}
主要有以下實現步驟:
構建uri對象
連接netty服務端
異步獲取Channel對象
初始化文件上傳句柄對象
獲取Request對象
獲取Http數據處理工廠
進行數據的包裝處理,主要是進行上傳文件所需要的參數的設置,此時調用的句柄是具體的UploadFileClientHandler對象
把request寫到管道中,傳輸給服務端
做一些關閉資源的動作
具體細節實現請參考github上的代碼。如果各位讀者喜歡的話,可以加個star哈。
4. 操作指引
該文件服務組件的使用需要分為兩個部分,一個是服務端配置與啟動,一個是客戶端的配置與啟動。
4.1 服務端配置與啟動
4.1.1 配置
服務端的配置采用yml文件的配置,更加的簡潔明了,主要的注意點是文件存放位置的配置,在開發過程中,可以有兩種方式配置:
Idea自啟動方式:如果采用此種方式則需要把rootPath配置到工程路徑下(target目錄),如下所示:
# 在idea中執行的話,需要配置target目錄下的打包文件
rootPath: C:\03_code\idea_workspace\fortune-commons\commons-doc-server-netty\target\commons-doc-server-netty\ #上傳文件的根目錄,實際工作環境按照實際情況更改即可
打包后在tomcat獨立啟動方式
# 也可以單獨把打包后的war包拷貝到tomcat webapp目錄下直接運行也可以
rootPath: C:\05_webserver\apache-tomcat-8.5.42\webapps\doc-server #上傳文件的根目錄,實際工作環境按照實際情況更改即可
4.1.2 啟動
本文采用的是idea自啟動方式,則需要配置一下tomcat路徑,以及引入相應的module即可,如下圖所示:
需要注意的是,在Deployment頁簽,需要配置該項目訪問的Application Context,否則有可能啟動后出現404的情況。如下圖所示:
配置完成即可啟動文件服務組件,如下圖即為啟動信息日志:
....
2019-07-20 23:48:56.174 [RMI TCP Connection(3)-127.0.0.1] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'mvcViewResolver'
2019-07-20 23:48:56.182 [RMI TCP Connection(3)-127.0.0.1] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'viewResolver'
2019-07-20 23:48:56.208 [RMI TCP Connection(3)-127.0.0.1] INFO org.fortune.commons.core.help.BeanInitializeCompletedListener - spring conf 容器初始化完畢..處理啟動之后事件--start
2019-07-20 23:48:56.212 [RMI TCP Connection(3)-127.0.0.1] INFO org.fortune.doc.server.DocServerContainer - 加入賬戶:fortune
2019-07-20 23:48:56.212 [RMI TCP Connection(3)-127.0.0.1] INFO org.fortune.doc.server.DocServerContainer - 加入賬戶:fortune0
2019-07-20 23:48:56.213 [RMI TCP Connection(3)-127.0.0.1] INFO org.fortune.doc.server.DocServerContainer - 加入默認賬戶:Account{userName='default_account', password='lyx', rootPath='C:\05_webserver\apache-tomcat-8.5.42\bin\', level=1, thumbHeight=20, thumbWidth=20}
2019-07-20 23:48:56.296 [RMI TCP Connection(3)-127.0.0.1] INFO org.fortune.commons.core.help.BeanInitializeCompletedListener - spring conf 容器初始化完畢..處理啟動之后事件--end
2019-07-20 23:48:56.302 [RMI TCP Connection(3)-127.0.0.1] DEBUG org.springframework.jndi.JndiTemplate - Looking up JNDI object with name [java:comp/env/spring.liveBeansView.mbeanDomain]
2019-07-20 23:48:56.303 [RMI TCP Connection(3)-127.0.0.1] DEBUG org.springframework.jndi.JndiLocatorDelegate - Converted JNDI name [java:comp/env/spring.liveBeansView.mbeanDomain] not found - trying original name [spring.liveBeansView.mbeanDomain]. javax.naming.NameNotFoundException: Name [spring.liveBeansView.mbeanDomain] is not bound in this Context. Unable to find [spring.liveBeansView.mbeanDomain].
2019-07-20 23:48:56.303 [RMI TCP Connection(3)-127.0.0.1] DEBUG org.springframework.jndi.JndiTemplate - Looking up JNDI object with name [spring.liveBeansView.mbeanDomain]
2019-07-20 23:48:56.304 [RMI TCP Connection(3)-127.0.0.1] DEBUG org.springframework.jndi.JndiPropertySource - JNDI lookup for name [spring.liveBeansView.mbeanDomain] threw NamingException with message: Name [spring.liveBeansView.mbeanDomain] is not bound in this Context. Unable to find [spring.liveBeansView.mbeanDomain].. Returning null.
2019-07-20 23:48:56.307 [RMI TCP Connection(3)-127.0.0.1] INFO org.springframework.web.context.ContextLoader - Root WebApplicationContext initialized in 2216 ms
2019-07-20 23:48:56.311 [RMI TCP Connection(3)-127.0.0.1] DEBUG org.springframework.web.filter.CharacterEncodingFilter - Filter 'characterEncodingFilter' configured for use
2019-07-20 23:48:56.320 [RMI TCP Connection(3)-127.0.0.1] INFO org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'doc-server'
....
然后就可以訪問如下頁面,顯示如下:
不過我們配置的netty端口是9999,我們試著訪問一下,
此時我們就可以不依賴客戶端,直接在網頁端進行文件上傳的測試操作了。輸入賬戶,密碼,選擇相應的文件即可上傳,上傳成功的返回頁面如下:
后臺控制臺打印出的日志信息如下:
2019-07-20 23:52:12.833 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....
2019-07-20 23:52:12.835 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:1024 bytes
2019-07-20 23:52:12.849 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:2048 bytes
2019-07-20 23:52:12.849 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:2048 bytes
2019-07-20 23:52:12.850 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:3072 bytes
2019-07-20 23:52:12.850 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:4096 bytes
2019-07-20 23:52:12.850 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:6144 bytes
2019-07-20 23:52:12.852 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:8192 bytes
2019-07-20 23:52:12.852 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:8192 bytes
2019-07-20 23:52:12.852 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:3072 bytes
2019-07-20 23:52:12.853 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:8192 bytes
2019-07-20 23:52:12.853 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:7168 bytes
2019-07-20 23:52:12.853 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:8192 bytes
2019-07-20 23:52:12.854 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:8192 bytes
2019-07-20 23:52:12.854 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:6144 bytes
2019-07-20 23:52:12.856 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:8157 bytes
2019-07-20 23:52:12.862 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件分塊操作....文件大小:0 bytes
2019-07-20 23:52:12.862 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 到達文件內容的末尾,進行相應的文件處理操作....start
2019-07-20 23:52:12.865 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件處理開始....requestParams參數解析:
NETTY WEB Server
===================================
UserName=fortune
pwd=fortune
action=uploadFile
fileContentType=image/jpeg
fileSize=81 KB
getform=POST
Send=Send
2019-07-20 23:52:12.867 [New I/O worker #1] INFO org.fortune.doc.server.handler.factory.DocServerHandlerFactory - 進行文件上傳操作....
2019-07-20 23:52:12.869 [New I/O worker #1] INFO org.fortune.doc.server.handler.UploadDocServerHandler - --srcFileName--psb.jpg
2019-07-20 23:52:12.871 [New I/O worker #1] INFO org.fortune.doc.server.handler.UploadDocServerHandler - 文件上傳成功,保存路徑為:fortune\l\190720235212_1027.jpg,真實路徑為:C:\03_code\idea_workspace\fortune-commons\commons-doc-server-netty\target\commons-doc-server-netty\/fortune\l\190720235212_1027.jpg
2019-07-20 23:52:12.871 [New I/O worker #1] DEBUG org.fortune.doc.server.handler.UploadDocServerHandler - 生成縮略圖
2019-07-20 23:52:12.872 [New I/O worker #1] INFO org.fortune.doc.server.handler.UploadDocServerHandler - 生成縮略圖的名稱為:190720235212_1027_thumb.jpg,路徑為:C:\03_code\idea_workspace\fortune-commons\commons-doc-server-netty\target\commons-doc-server-netty\/fortune\l\190720235212_1027_thumb.jpg
2019-07-20 23:52:13.130 [New I/O worker #1] DEBUG org.fortune.doc.server.handler.factory.DocServerHandlerFactory - 執行結果:{"action":"uploadFile","code":1,"filePath":"fortune\\l\\190720235212_1027.jpg","msg":"文件上傳成功","success":true}
2019-07-20 23:52:13.130 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 文件處理結束....FileServerHandlerFactory處理結果:{"action":"uploadFile","code":1,"filePath":"fortune\\l\\190720235212_1027.jpg","msg":"文件上傳成功","success":true}
2019-07-20 23:52:13.133 [New I/O worker #1] INFO org.fortune.doc.server.support.DocServerHandler - 到達文件內容的末尾,進行相應的文件處理操作....end
再觀察一下netty 代碼中的target目錄下就有上傳的文件了,如下所示:
我們可以通過如下地址進行訪問,
http://localhost:8080/doc-server/fortune//l//190720235212_1027.jpg,關于如何得到圖片地址,則在客戶端工具類中有相應的方法獲取。
4.2 客戶端配置與啟動
4.2.1 配置
客戶端的配置比較簡單,也是采用yml文件方式配置如下:
#上傳成功后,在需要訪問文件web工程中配置以下服務器地址,正確格式為:
# ${host}/${port}/${appName}/${path}
# appName為遠程文件服務app名稱,比如doc-server
# path為文件上傳后服務端返回的文件相對路徑
#http://localhost:8080/doc-server/fortune//p//190629082821_8300.jpg
upload:
server:
port: 9999 #需要配置的是遠程文件服務器netty服務端口號
host: 127.0.0.1
access: #訪問文件
port: 8080 #訪問文件 web 服務端口號
domainName: doc-server #訪問文件 web 服務應用名稱
userName: fortune
password: fortune
需要注意的是,文件訪問的端口號跟遠程文件服務器netty對應的端口是不一樣的,這點需要特別注意。在調用文件服務返回的路徑的時候,需要用到服務端訪問文件的地址,進而訪問相應的文件內容。可通過方法 org.fortune.doc.client.DocClientContainer#getDocServerUrl得到相應的服務端地址,再拼接上返回的相對路徑,即可得到文件的完整地址了。
4.2.2 啟動/調用
客戶端單元測試用例如下:
/**
* @author: landy
* @date: 2019/5/30 23:37
* @description:
*/
@RunWith(SpringJUnit4ClassRunner.class) //調用Spring單元測試類
@ContextConfiguration(classes = {
SettingsConfiguration.class, // common settings configuration
DocClientConfiguration.class,
ApplicationContextHelperConfiguration.class
}) //加載Spring配置文件
public class DocClientTest {
@Test
public void test() {
DocClient.uploadFile(new File("C:\\06_temp\\psb.jpg"), "psb.jpg",false);
}
}
同樣的服務端也會顯示出上述相似的日志信息,
2019-07-21 00:04:26.704 [New I/O worker #4] INFO org.fortune.doc.server.handler.UploadDocServerHandler - --srcFileName--psb.jpg
2019-07-21 00:04:26.705 [New I/O worker #4] INFO org.fortune.doc.server.handler.UploadDocServerHandler - 文件上傳成功,保存路徑為:fortune\w\190721000426_6348.jpg,真實路徑為:C:\03_code\idea_workspace\fortune-commons\commons-doc-server-netty\target\commons-doc-server-netty\/fortune\w\190721000426_6348.jpg
2019-07-21 00:04:26.705 [New I/O worker #4] DEBUG org.fortune.doc.server.handler.factory.DocServerHandlerFactory - 執行結果:{"action":"uploadFile","code":1,"filePath":"fortune\\w\\190721000426_6348.jpg","msg":"文件上傳成功","success":true}
2019-07-21 00:04:26.705 [New I/O worker #4] INFO org.fortune.doc.server.support.DocServerHandler - 文件處理結束....FileServerHandlerFactory處理結果:{"action":"uploadFile","code":1,"filePath":"fortune\\w\\190721000426_6348.jpg","msg":"文件上傳成功","success":true}
2019-07-21 00:04:26.706 [New I/O worker #4] INFO org.fortune.doc.server.support.DocServerHandler - 到達文件內容的末尾,進行相應的文件處理操作....end
2019-07-21 00:05:50.539 [http-nio-8080-exec-6] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/doc-server/fortune//w//190721000426_6348.jpg", parameters={}
2019-07-21 00:05:50.540 [http-nio-8080-exec-6] DEBUG org.springframework.web.servlet.handler.SimpleUrlHandlerMapping - Mapped to org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler@4fcd8ee1
2019-07-21 00:05:50.542 [http-nio-8080-exec-6] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK
觀察服務端target目錄下,又多了一個文件,顯示如下,
跟日志信息打印出來的路徑是一致的,我們可以通過返回的地址信息進行訪問文件,
5. 常見問題
5.1 Maven依賴問題
如果某些jar包無法下載的話,可以手動下載然后自己手動執行maven命令安裝到本地倉庫即可(Then, install it using the command)。
mvn install:install-file -DgroupId=com.fasterxml.jackson.core -DartifactId=jackson-core -Dversion=2.9.9.1 -Dpackaging=jar -Dfile=/path/to/file
或者直接利用遠程倉庫地址進行安裝也可( Alternatively, if you host your own repository you can deploy the file there: ),
mvn deploy:deploy-file -DgroupId=com.fasterxml.jackson.core -DartifactId=jackson-core -Dversion=2.9.9.1 -Dpackaging=jar -Dfile=/path/to/file -Durl=[url] -DrepositoryId=[id]
5.2 Tomcat 版本問題
如果使用Tomcat7則會出現以下問題,經過對比發現,需要采用Tomcat8以上版本即可,JDK版本需要為1.8+。
七月 23, 2019 11:25:52 下午 org.apache.catalina.startup.ContextConfig processAnnotationsJar
嚴重: Unable to process Jar entry [module-info.class] from Jar [jar:file:/C:/03_code/idea_workspace/fortune-commons/commons-doc-server-netty/target/commons-doc-server-netty/WEB-INF/lib/asm-7.0.jar!/] for annotations
org.apache.tomcat.util.bcel.classfile.ClassFormatException: Invalid byte tag in constant pool: 19
at org.apache.tomcat.util.bcel.classfile.Constant.readConstant(Constant.java:133)
at org.apache.tomcat.util.bcel.classfile.ConstantPool.(ConstantPool.java:60)
at org.apache.tomcat.util.bcel.classfile.ClassParser.readConstantPool(ClassParser.java:209)
at org.apache.tomcat.util.bcel.classfile.ClassParser.parse(ClassParser.java:119)
at org.apache.catalina.startup.ContextConfig.processAnnotationsStream(ContextConfig.java:2104)
at org.apache.catalina.startup.ContextConfig.processAnnotationsJar(ContextConfig.java:1980)
at org.apache.catalina.startup.ContextConfig.processAnnotationsUrl(ContextConfig.java:1946)
at org.apache.catalina.startup.ContextConfig.processAnnotations(ContextConfig.java:1931)
at org.apache.catalina.startup.ContextConfig.webConfig(ContextConfig.java:1325)
at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:878)
at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:369)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:119)
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5173)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:633)
at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1553)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:622)
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:569)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
at com.sun.jmx.remote.security.MBeanServerAccessController.invoke(MBeanServerAccessController.java:468)
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1468)
at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:76)
at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1309)
at java.security.AccessController.doPrivileged(Native Method)
at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1408)
at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:829)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:357)
at sun.rmi.transport.Transport$1.run(Transport.java:200)
at sun.rmi.transport.Transport$1.run(Transport.java:197)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
23:25:53,087 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
23:25:53,088 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]