IDEA內嵌Jetty啟動SpringMvc項目


  這段時間本意是想要研究一下Netty的多線程異步NIO通訊框架,看完原理想要做下源碼分析。查找資料發現Jetty框架底層支持用Netty做web請求的多線程分發處理,於是就籌備着將Jetty框架內嵌到手頭的一個測試項目中,調試源碼分析實現原理。結果這集成一搞就是兩天,有些細節部分還是要真正接觸之后才會了解,為此特意整理博客一篇,就集成過程中的問題做一下總結。

  項目說明:Maven多模塊,springmvc,spring-security;

  參考項目:JFinal(國產優秀的mvc開發框架);

  問題說明:1、設置webapp根目錄后,項目根本無法啟動;2、解決項目根路徑問題后,spring-security過濾器鏈不執行;

一、內嵌的Jetty服務最終實現

  1.1、Jetty服務的站點根目錄與監聽端口號設置代碼實現

 1 package com.train.simulate.web.server;
 2 
 3 public class JettyServerRun {
 4     public static final int DEFAULT_PORT = 8898;
 5     public static final String DEFAULT_CONTEXT_PATH = "/train";
 6     private static final String DEFAULT_APP_CONTEXT_PATH = "src/main/webapp";
 7 
 8 
 9     public static void main(String[] args) {
10 
11         runJettyServer(DEFAULT_PORT, DEFAULT_CONTEXT_PATH);
12 
13     }
14 
15     public static void runJettyServer(int port, String contextPath) {
16 
17         new JettyServerForIDEA(DEFAULT_APP_CONTEXT_PATH,port,contextPath).start();
18 
19     }
20 }
View Code
  1 /**
  2  * Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
  3  *
  4  * Licensed under the Apache License, Version 2.0 (the "License");
  5  * you may not use this file except in compliance with the License.
  6  * You may obtain a copy of the License at
  7  *
  8  *      http://www.apache.org/licenses/LICENSE-2.0
  9  *
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an "AS IS" BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  */
 16 
 17 package com.train.simulate.web.server;
 18 
 19 import java.io.File;
 20 import java.io.IOException;
 21 import java.net.DatagramSocket;
 22 import java.net.ServerSocket;
 23 import java.util.EnumSet;
 24 
 25 import com.train.simulate.common.utils.*;
 26 import org.eclipse.jetty.server.DispatcherType;
 27 import org.eclipse.jetty.server.Server;
 28 import org.eclipse.jetty.server.SessionManager;
 29 import org.eclipse.jetty.server.nio.SelectChannelConnector;
 30 import org.eclipse.jetty.server.session.HashSessionManager;
 31 import org.eclipse.jetty.server.session.SessionHandler;
 32 import org.eclipse.jetty.servlet.FilterHolder;
 33 import org.eclipse.jetty.webapp.WebAppContext;
 34 import org.springframework.web.filter.DelegatingFilterProxy;
 35 
 36 /**
 37  * IDEA 專用於在 IDEA 之下用 main 方法啟動,原啟動方式在 IDEA 下會報異常
 38  * 注意:用此方法啟動對熱加載支持不完全,只支持方法內部修改的熱加載,不支持添加、刪除方法
 39  *      不支持添加類文件熱加載,建議開發者在 IDEA 下使用 jrebel 或者 maven 下的 jetty
 40  *      插件支持列為完全的熱加載
 41  */
 42 public class JettyServerForIDEA implements IServer {
 43     
 44     private String webAppDir;
 45     private int port;
 46     private String context;
 47     // private int scanIntervalSeconds;
 48     private boolean running = false;
 49     private Server server;
 50     private WebAppContext webApp;
 51     
 52     public JettyServerForIDEA(String webAppDir, int port, String context) {
 53         if (webAppDir == null) {
 54             throw new IllegalStateException("Invalid webAppDir of web server: " + webAppDir);
 55         }
 56         if (port < 0 || port > 65535) {
 57             throw new IllegalArgumentException("Invalid port of web server: " + port);
 58         }
 59         if (StrKit.isBlank(context)) {
 60             throw new IllegalStateException("Invalid context of web server: " + context);
 61         }
 62         
 63         this.webAppDir = webAppDir;
 64         this.port = port;
 65         this.context = context;
 66         // this.scanIntervalSeconds = scanIntervalSeconds;
 67     }
 68     
 69     public void start() {
 70         if (!running) {
 71             try {
 72                 running = true;
 73                 doStart();
 74             } catch (Exception e) {
 75                 System.err.println(e.getMessage());
 76                 //LogKit.error(e.getMessage(), e);
 77             }
 78         }
 79     }
 80     
 81     public void stop() {
 82         if (running) {
 83             try {server.stop();} catch (Exception e) {
 84                 //LogKit.error(e.getMessage(), e);
 85                 System.err.println(e.getMessage());
 86             }
 87             running = false;
 88         }
 89     }
 90     
 91     private void doStart() {
 92         if (!available(port)) {
 93             throw new IllegalStateException("port: " + port + " already in use!");
 94         }
 95         deleteSessionData();
 96         System.out.println("Starting Jetty ...... ");
 97         server = new Server();
 98         SelectChannelConnector connector = new SelectChannelConnector();
 99         connector.setPort(port);
100         server.addConnector(connector);
101         webApp = new WebAppContext();
102         webApp.setThrowUnavailableOnStartupException(true);    // 在啟動過程中允許拋出異常終止啟動並退出 JVM
103         webApp.setContextPath(context);
104         String rootPath = PathKit.getWebRootPath();
105         System.out.println(rootPath);
106         String webDir = rootPath + "/" + webAppDir;
107         //webApp.setDescriptor(webDir + "/WEB-INF/web.xml");
108         webApp.setResourceBase(webDir);    // webApp.setWar(webAppDir);
109         //postStart(webApp);
110         webApp.setWelcomeFiles(new String[]{"/index.do"});
111         webApp.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
112         webApp.setInitParameter("org.eclipse.jetty.servlet.Default.useFileMappedBuffer", "false");    // webApp.setInitParams(Collections.singletonMap("org.mortbay.jetty.servlet.Default.useFileMappedBuffer", "false"));
113         persistSession(webApp);
114 
115         server.setHandler(webApp);
116         try {
117             System.out.println("Starting web server on port: " + port);
118             server.start();
119             System.out.println("Starting Complete. Welcome To The Jetty :)");
120             server.join();
121         } catch (Exception e) {
122             //LogKit.error(e.getMessage(), e);
123             System.err.println(e.getMessage());
124             System.exit(100);
125         }
126         return;
127     }
128     private void postStart(WebAppContext root){
129         /**spring內部過濾器代理 里面包含了默認的11個過濾器 這里的初始化參數可以直接些spring的bean名稱*/
130         FilterHolder filterHolder=new FilterHolder(DelegatingFilterProxy.class);
131         filterHolder.setName("springSecurityFilterChain");
132         root.addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));
133     }
134     
135     private void deleteSessionData() {
136         try {
137             FileKit.delete(new File(getStoreDir()));
138         }
139         catch (Exception e) {
140             //LogKit.logNothing(e);
141             System.err.println(e.getMessage());
142         }
143     }
144     
145     private String getStoreDir() {
146         String storeDir = PathKit.getWebRootPath() + "/../../session_data" + context;
147         if ("\\".equals(File.separator)) {
148             storeDir = storeDir.replaceAll("/", "\\\\");
149         }
150         return storeDir;
151     }
152     
153     private void persistSession(WebAppContext webApp) {
154         String storeDir = getStoreDir();
155         
156         SessionManager sm = webApp.getSessionHandler().getSessionManager();
157         if (sm instanceof HashSessionManager) {
158             try {
159                 ((HashSessionManager)sm).setStoreDirectory(new File(storeDir));
160             } catch (Exception e) {
161                 e.printStackTrace();
162             }
163             return ;
164         }
165         
166         HashSessionManager hsm = new HashSessionManager();
167         try {
168             hsm.setStoreDirectory(new File(storeDir));
169         } catch (Exception e) {
170             e.printStackTrace();
171         }
172         SessionHandler sh = new SessionHandler();
173         sh.setSessionManager(hsm);
174         webApp.setSessionHandler(sh);
175     }
176     
177     private static boolean available(int port) {
178         if (port <= 0) {
179             throw new IllegalArgumentException("Invalid start port: " + port);
180         }
181         
182         ServerSocket ss = null;
183         DatagramSocket ds = null;
184         try {
185             ss = new ServerSocket(port);
186             ss.setReuseAddress(true);
187             ds = new DatagramSocket(port);
188             ds.setReuseAddress(true);
189             return true;
190         } catch (IOException e) {
191             //LogKit.logNothing(e);
192             System.err.println(e.getMessage());
193         } finally {
194             if (ds != null) {
195                 ds.close();
196             }
197             
198             if (ss != null) {
199                 try {
200                     ss.close();
201                 } catch (IOException e) {
202                     // should not be thrown, just detect port available.
203                     // LogKit.logNothing(e);
204                     System.err.println(e.getMessage());
205                 }
206             }
207         }
208         return false;
209     }
210 }
View Code
1 package com.train.simulate.web.server;
2 
3 public interface IServer {
4     void start();
5     void stop();
6 }
View Code

  1.2、Jetty架包的maven配置

<dependency>
            <groupId>org.eclipse.jetty.aggregate</groupId>
            <artifactId>jetty-all</artifactId>
            <version>7.6.0.v20120127</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

二、關於站點根目錄設置后,站點無法啟動的問題處理

  2.1、在Jfinal中調用org.eclipse.jetty.webapp.WebAppContext.setResourceBase("src/main/webapp") 這樣的相對路徑方式。結果站點無法啟動后,我去跟蹤源碼,發現setResourceBase方法底層使用File.getCanonicalFile方法來獲取站點的絕對根路徑,而這個方法最終調用System.getProperty("user.dir")來獲取項目路徑。具體這個系統變量是如何賦的值,這塊我還沒有具體搞明白,但是跟蹤結果告訴我這個路徑指向的是我的Maven多模塊項目的頂級項目目錄,而不是我想要的web子級項目。因為路徑錯誤,所以無法啟動。

  

  2.2、既然問題已經定位,接下來就有兩種處理方案。一是設置資源根路徑時從web項目名稱開始設置;二是直接讀取web站點的絕對根路徑,將絕對路徑設置到org.eclipse.jetty.webapp.WebAppContext.setResourceBase方法中。我上面的源碼使用的就是直接指明絕對路徑的方式。

  

三、啟用Spring-Security組件,過濾鏈卻不生效的問題

  

  3.1、因為我在登錄頁面啟用了crsf功能,需要crsfFilter為登錄請求輸出crsf-token碼。結果站點啟動執行登錄時,發現這個值始終為null。異常日志顯示請求也僅僅是進入了servlet的處理器,未執行Filter代碼。經過一番的跟蹤分析,最終確定 Tomcat模式下請求由org.springframework.web.filter.DelegatingFilterProxy進行攔截。從結果推斷apache應該是直接內置了該過濾功能,不需要手動設置,但是jetty沒有這個實現。

  3.2、識別這個問題后,相應的也有兩種處理方案(兩種方案二選一)

  • 第一種,在web.xml中顯示指明這個Filter(推薦做法)
<filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
  • 第二種,在代碼中org.eclipse.jetty.webapp.WebAppContext中添加這個Filter,示例代碼如下
private void postStart(WebAppContext root){
        /**spring內部過濾器代理 里面包含了默認的11個過濾器 這里的初始化參數可以直接些spring的bean名稱*/
        FilterHolder filterHolder=new FilterHolder(DelegatingFilterProxy.class);
        filterHolder.setName("springSecurityFilterChain");
        root.addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));
    }

小結:

  紙上得來終覺淺,絕知此事要躬行。折騰之后好在出了結果,得到了一點安慰。接下來就准備從jetty切入分析Netty的nio工作原理啦!


免責聲明!

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



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