这段时间本意是想要研究一下Netty的多线程异步NIO通讯框架,看完原理想要做下源码分析。查找资料发现Jetty框架底层支持用Netty做web请求的多线程分发处理,于是就筹备着将Jetty框架内嵌到手头的一个测试项目中,调试源码分析实现原理。结果这集成一搞就是两天,有些细节部分还是要真正接触之后才会了解,为此特意整理博客一篇,就集成过程中的问题做一下总结。

  项目说明:Maven多模块,springmvc,spring-security;

  参考项目:JFinal(国产优秀的mvc开发框架);

  问题说明:1、设置webapp根目录后,项目根本无法启动;2、解决项目根路径问题后,spring-security过滤器链不执行;

一、内嵌的Jetty服务最终实现

  1.1、Jetty服务的站点根目录与监听端口号设置代码实现

 package com.train.simulate.web.server;

 public class JettyServerRun {
public static final int DEFAULT_PORT = 8898;
public static final String DEFAULT_CONTEXT_PATH = "/train";
private static final String DEFAULT_APP_CONTEXT_PATH = "src/main/webapp"; public static void main(String[] args) { runJettyServer(DEFAULT_PORT, DEFAULT_CONTEXT_PATH); } public static void runJettyServer(int port, String contextPath) { new JettyServerForIDEA(DEFAULT_APP_CONTEXT_PATH,port,contextPath).start(); }
}
 /**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package com.train.simulate.web.server; import java.io.File;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.util.EnumSet; import com.train.simulate.common.utils.*;
import org.eclipse.jetty.server.DispatcherType;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.SessionManager;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.server.session.HashSessionManager;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.web.filter.DelegatingFilterProxy; /**
* IDEA 专用于在 IDEA 之下用 main 方法启动,原启动方式在 IDEA 下会报异常
* 注意:用此方法启动对热加载支持不完全,只支持方法内部修改的热加载,不支持添加、删除方法
* 不支持添加类文件热加载,建议开发者在 IDEA 下使用 jrebel 或者 maven 下的 jetty
* 插件支持列为完全的热加载
*/
public class JettyServerForIDEA implements IServer { private String webAppDir;
private int port;
private String context;
// private int scanIntervalSeconds;
private boolean running = false;
private Server server;
private WebAppContext webApp; public JettyServerForIDEA(String webAppDir, int port, String context) {
if (webAppDir == null) {
throw new IllegalStateException("Invalid webAppDir of web server: " + webAppDir);
}
if (port < 0 || port > 65535) {
throw new IllegalArgumentException("Invalid port of web server: " + port);
}
if (StrKit.isBlank(context)) {
throw new IllegalStateException("Invalid context of web server: " + context);
} this.webAppDir = webAppDir;
this.port = port;
this.context = context;
// this.scanIntervalSeconds = scanIntervalSeconds;
} public void start() {
if (!running) {
try {
running = true;
doStart();
} catch (Exception e) {
System.err.println(e.getMessage());
//LogKit.error(e.getMessage(), e);
}
}
} public void stop() {
if (running) {
try {server.stop();} catch (Exception e) {
//LogKit.error(e.getMessage(), e);
System.err.println(e.getMessage());
}
running = false;
}
} private void doStart() {
if (!available(port)) {
throw new IllegalStateException("port: " + port + " already in use!");
}
deleteSessionData();
System.out.println("Starting Jetty ...... ");
server = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(port);
server.addConnector(connector);
webApp = new WebAppContext();
webApp.setThrowUnavailableOnStartupException(true); // 在启动过程中允许抛出异常终止启动并退出 JVM
webApp.setContextPath(context);
String rootPath = PathKit.getWebRootPath();
System.out.println(rootPath);
String webDir = rootPath + "/" + webAppDir;
//webApp.setDescriptor(webDir + "/WEB-INF/web.xml");
webApp.setResourceBase(webDir); // webApp.setWar(webAppDir);
//postStart(webApp);
webApp.setWelcomeFiles(new String[]{"/index.do"});
webApp.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
webApp.setInitParameter("org.eclipse.jetty.servlet.Default.useFileMappedBuffer", "false"); // webApp.setInitParams(Collections.singletonMap("org.mortbay.jetty.servlet.Default.useFileMappedBuffer", "false"));
persistSession(webApp); server.setHandler(webApp);
try {
System.out.println("Starting web server on port: " + port);
server.start();
System.out.println("Starting Complete. Welcome To The Jetty :)");
server.join();
} catch (Exception e) {
//LogKit.error(e.getMessage(), e);
System.err.println(e.getMessage());
System.exit(100);
}
return;
}
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));
} private void deleteSessionData() {
try {
FileKit.delete(new File(getStoreDir()));
}
catch (Exception e) {
//LogKit.logNothing(e);
System.err.println(e.getMessage());
}
} private String getStoreDir() {
String storeDir = PathKit.getWebRootPath() + "/../../session_data" + context;
if ("\\".equals(File.separator)) {
storeDir = storeDir.replaceAll("/", "\\\\");
}
return storeDir;
} private void persistSession(WebAppContext webApp) {
String storeDir = getStoreDir(); SessionManager sm = webApp.getSessionHandler().getSessionManager();
if (sm instanceof HashSessionManager) {
try {
((HashSessionManager)sm).setStoreDirectory(new File(storeDir));
} catch (Exception e) {
e.printStackTrace();
}
return ;
} HashSessionManager hsm = new HashSessionManager();
try {
hsm.setStoreDirectory(new File(storeDir));
} catch (Exception e) {
e.printStackTrace();
}
SessionHandler sh = new SessionHandler();
sh.setSessionManager(hsm);
webApp.setSessionHandler(sh);
} private static boolean available(int port) {
if (port <= 0) {
throw new IllegalArgumentException("Invalid start port: " + port);
} ServerSocket ss = null;
DatagramSocket ds = null;
try {
ss = new ServerSocket(port);
ss.setReuseAddress(true);
ds = new DatagramSocket(port);
ds.setReuseAddress(true);
return true;
} catch (IOException e) {
//LogKit.logNothing(e);
System.err.println(e.getMessage());
} finally {
if (ds != null) {
ds.close();
} if (ss != null) {
try {
ss.close();
} catch (IOException e) {
// should not be thrown, just detect port available.
// LogKit.logNothing(e);
System.err.println(e.getMessage());
}
}
}
return false;
}
}
 package com.train.simulate.web.server;

 public interface IServer {
void start();
void stop();
}

  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工作原理啦!

IDEA内嵌Jetty启动SpringMvc项目的更多相关文章

  1. spring内嵌jetty容器,实现main方法启动web项目

    Jetty 是一个开源的servlet容器,它为基于Java的web容器,例如JSP和servlet提供运行环境.Jetty是使用Java语言编写的,它的API以一组JAR包的形式发布.开发人员可以将 ...

  2. Message高级特性 & 内嵌Jetty实现文件服务器

    1. Messaage Properties  常见属性 更多的属性以及介绍参考:http://activemq.apache.org/activemq-message-properties.html ...

  3. idea启动springmvc项目时报找不到类

    今天用idea启动springmvc项目时找不到类 . 查了一下,发现是我使用idea20201.1出现的bug 解决方法:File>Settings>Build, Execution, ...

  4. 使用jetty作为内嵌服务器启动项目

    http://blog.csdn.net/robinpipi/article/details/7557035 需求:把jetty作为内嵌的一个服务器,直接启动,web项目不用部署在应用服务器中.在网上 ...

  5. Spring Boot启动过程(四):Spring Boot内嵌Tomcat启动

    之前在Spring Boot启动过程(二)提到过createEmbeddedServletContainer创建了内嵌的Servlet容器,我用的是默认的Tomcat. private void cr ...

  6. IDEA中使用中jetty启动java项目(非springboot)

    1.安装maven helper插件,略 2.项目pom.xml文件中添加jetty插件配置 <build> <plugins> <plugin> <grou ...

  7. eclipse内嵌jetty(run-jetty-run插件) 配置jndi数据源

    运行环境 java 6,eclipse juno,ssh(spring,hibernate,springmvc ) 1.离线安装 下载地址:http://pan.baidu.com/s/1qX67wO ...

  8. java内嵌jetty服务器

    有的时候需要将一个简单的功能封装为服务,相比python使用flask.web.py的简洁,使用java-web显得太重量级,幸好,我们可以直接在java项目中使用jetty来搭建简易服务 1.pom ...

  9. Embed Tomcat Java(内嵌tomcat启动简述)

    简单记录一下内部tomcat启动 maven pom.xml <dependencies> <!-- embed tomcat dependency --> <depen ...

随机推荐

  1. wordpress 自定义删除后台管理菜单

    <?php /* //wordpress共有5种角色:administrator(管理员) editor(编辑) author(作者) contributor(投稿者) subscriber(订 ...

  2. 修改git commit 最后一次提交的注释信息 以及如何退出git bash vim编辑器

    https://www.cnblogs.com/sandy-happyhour/p/5950084.html 今天用git commit -m “注释”提交的时候,注释写错了,于是各种查资料开始了和g ...

  3. BZOJ2342:[SHOI2011]双倍回文

    浅谈\(Manacher\):https://www.cnblogs.com/AKMer/p/10431603.html 题目传送门:https://www.lydsy.com/JudgeOnline ...

  4. BZOJ2716:[Violet 3]天使玩偶

    浅谈离线分治算法:https://www.cnblogs.com/AKMer/p/10415556.html 题目传送门:https://lydsy.com/JudgeOnline/problem.p ...

  5. 机器学习:数据归一化(Scaler)

    数据归一化(Feature Scaling) 一.为什么要进行数据归一化 原则:样本的所有特征,在特征空间中,对样本的距离产生的影响是同级的: 问题:特征数字化后,由于取值大小不同,造成特征空间中样本 ...

  6. 开发环境无错,部署至测试环境报错“NoSuchMethodError”OR"NoSuchClassError"

    背景: 实现一个简单的功能,需要用到jedis的jar包连接Redis.在之前便已经有使用jedis,它的版本比较旧,是2.1的.而新实现的功能,在编码的时候使用的是2.8的.在开发环境完成单元测试后 ...

  7. rails的respond to format

    Here are all the default Rails Mime Types: "*/*" => :all "text/plain" => : ...

  8. 【转】bootstrap模态框(modal)使用remote方法加载数据,只能加载一次的解决办法

    http://blog.csdn.net/coolcaosj/article/details/38369787 bootstrap的modal中,有一个remote选项,可以动态加载页面到modal- ...

  9. 如何将DevExpress的Gridcontrol导出到Excel

    private void simpleButton1_Click(object sender, EventArgs e) { SaveFileDialog saveFileDialog = new S ...

  10. jdbcTemplate学习(一)

    概述 Spring JDBC抽象框架core包提供了JDBC模板类,其中JdbcTemplate是core包的核心类,所以其他模板类都是基于它封装完成的,JDBC模板类是第一种工作模式. JdbcTe ...