Jetty较比实用引导程序

Jetty较实用引导程序

本类用于启动Jetty嵌入式服务器


其可在不重启Jetty的情况下重载应用(包括Spring配置文件)

package com.jadyer.startup;

import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.webapp.WebAppClassLoader;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.util.Log4jConfigurer;

/**
 * Jetty嵌入式服务器引导程序
 * @see 本类用于启动Jetty嵌入式服务器
 * @see 本类部分整理自https://github.com/springside/springside4/wiki/Jetty,感谢江南白衣
 * @create Oct 5, 2013 12:08:55 PM
 * @author 玄玉<http://blog.****.net/jadyer>
 */
public class JettyBootStrap {
	private static final int PORT = 8088;
	private static final String CONTEXT_PATH = "/jadyer";
	private static final String WEBAPP_PATH = "F:/Tool/Code/JavaSE/MyJettyDemo/WebRoot";
	//等价于String CLASS_PATH = "F:/Tool/Code/JavaSE/MyJettyDemo/WebRoot/WEB-INF/classes"
	private static final String CLASS_PATH = JettyBootStrap.class.getResource("/").getPath();
	
	/**
	 * 创建用于开发运行调试的JettyServer
	 * @param port        访问服务器的端口
	 * @param contextPath 访问服务器的地址
	 * @param webAppPath  Web应用的目录(需指向到WebRoot目录下)
	 */
	private static Server createServer(int port, String contextPath, String webAppPath){
		Server server = new Server();
		//设置在JVM退出时关闭Jetty的钩子
		//这样就可以在整个功能测试时启动一次Jetty,然后让它在JVM退出时自动关闭
		server.setStopAtShutdown(true);
		SelectChannelConnector connector = new SelectChannelConnector();
		connector.setPort(port);
		//解决Windows下重复启动Jetty不报告端口冲突的问题
		//在Windows下有个Windows + Sun的connector实现的问题,reuseAddress=true时重复启动同一个端口的Jetty不会报错
		//所以必须设为false,代价是若上次退出不干净(比如有TIME_WAIT),会导致新的Jetty不能启动,但权衡之下还是应该设为False
		connector.setReuseAddress(false);
		server.setConnectors(new Connector[]{connector});
		//为了设置reuseAddress=true所以创建Connector,否则直接new Server(port)即可,通过查看Server源码发现,二者是等效的
		//不过使用Connector的好处是可以让Jetty监听多个端口,此时创建多个绑定不同端口的Connector即可,最后一起setConnectors
		//Server server = new Server(port);
		//server.setStopAtShutdown(true);
		WebAppContext context = new WebAppContext(webAppPath, contextPath);
		//解决Windows环境下Lock静态文件的问题
		//Jetty运行时会锁住js、css等文件,导致不能修改保存,解决办法是修改webdefault.xml中的useFileMappedBuffer=false
		//这里试了很多遍都没成功,后来注释掉这里的setDefaultsDescriptor(),也删除项目中的webdefault-windows.xml
		//再重启应用,竟然可以修改保存了,后来又反复重启又试了几次竟然都可以修改保存,很是奇怪
		//context.setDefaultsDescriptor("F:/Tool/Code/JavaSE/MyJettyDemo/test/webdefault-windows.xml");
		server.setHandler(context);
		return server;
	}
	
	
	/**
	 * 快速重新启动Application
	 * @see 通常用Main函数启动JettyServer后,若改动项目的代码,那就需要停止再启动Jetty
	 * @see 虽免去了Tomcat重新打包几十兆的消耗,但比起PHP完全不用重启来说还是慢,特别是关闭,启动一个新的JVM,消耗不小
	 * @see 所以我们可以在Main()中捕捉到回车后调用此函数,即可重新载入应用(包括Spring配置文件)
	 * @param server    当前运行的JettyServer实例
	 * @param classPath 当前运行的Web应用的classpath
	 */
	private static /*synchronized*/ void reloadContext(Server server, String classPath) throws Exception{
		WebAppContext context = (WebAppContext)server.getHandler();
		System.out.println("Application reloading..开始");
		context.stop();
		WebAppClassLoader classLoader = new WebAppClassLoader(context);
		classLoader.addClassPath(classPath);
		context.setClassLoader(classLoader);
		//根据给定的配置文件初始化日志配置(否则应用重载后日志输出组件就会失效)
		Log4jConfigurer.initLogging(classPath + "/log4j.properties");
		context.start();
		System.out.println("Application reloading..完毕");
	}
	
	
//	/**
//	 * 解决JSPTaglib的tld文件不能找到的问题
//	 * @see 设置除jstl-*.jar外其他含tld文件的jar包的名称,jar名称不需要版本号,如sitemesh、shiro-web
//	 * @see 这是Jetty一个很麻烦的设计,详细介绍见https://github.com/springside/springside4/wiki/Jetty
//	 * @see 如果tld文件在当前的classloader中则可顺利载入,若在parent的classloader里就会因为安全原因而拒绝扫描
//	 */
//	@SuppressWarnings("unused")
//	private static void setTldJarNames(Server server, String... jarNames){
//		WebAppContext context = (WebAppContext)server.getHandler();
//		//这里用的是com.google.common.collect.Lists
//		List<String> jarNameExprssions = Lists.newArrayList(".*/jstl-[^/]*\\.jar$", ".*/.*taglibs[^/]*\\.jar$");
//		for(String jarName : jarNames){
//			jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$");
//		}
//		//这里用的是org.apache.commons.lang3.StringUtils
//		context.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", StringUtils.join(jarNameExprssions, '|'));
//	}
	
	
	public static void main(String[] args) {
		Server server = createServer(PORT, CONTEXT_PATH, WEBAPP_PATH);
		//setTldJarNames(server, new String[]{"sitemesh", "spring-webmvc", "shiro-web"});
		try {
			//启动Jetty
			server.start();
			System.out.println("Server running at http://127.0.0.1:" + PORT + CONTEXT_PATH);
			System.out.println("Hit Enter to reload the application quickly");
			//等待用户键入回车重载应用
			while(true){
				char c = (char)System.in.read();
				if(c == '\n'){
					reloadContext(server, CLASS_PATH);
				}
			}
		} catch (Exception e) {
			System.out.println("Jetty启动失败,堆栈轨迹如下");
			e.printStackTrace();
			System.exit(-1);
		}
	}
}