zoukankan      html  css  js  c++  java
  • 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工作原理啦!

  • 相关阅读:
    [翻译] 编写高性能 .NET 代码--第五章 通用编码与对象设计 -- 类 vs 结构体
    [翻译] 编写高性能 .NET 代码--第二章 GC -- 配置选项
    [翻译]编写高性能 .NET 代码 第二章:垃圾回收 基本操作
    [翻译]编写高性能 .NET 代码 第二章:垃圾回收
    [翻译]编写高性能 .NET 代码 第一章:工具介绍 -- Visual Studio
    [翻译]编写高性能 .NET 代码 第一章:工具介绍 -- Performance Counters(性能计数器)
    [翻译]编写高性能 .NET 代码 第一章:性能测试与工具 -- 平均值 vs 百分比
    [翻译]编写高性能 .NET 代码 第一章:性能测试与工具 -- 选择什么来衡量
    NGUI锚定系统:UIAnchorUIRect
    NGUI Panel裁剪、层级实现原理
  • 原文地址:https://www.cnblogs.com/MrSi/p/9126949.html
Copyright © 2011-2022 走看看