Merge pull request #35 from wolfboys/dev

tomcat-starter support
This commit is contained in:
Wendal Chen 2017-12-11 22:01:41 +08:00 committed by GitHub
commit a149ca8662
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1695 additions and 1 deletions

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>nutzboot-demo-simple</artifactId>
<groupId>org.nutz</groupId>
<version>2.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nutzboot-demo-simple-tomcat</artifactId>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer />
<transformer>
<resource>META-INF/nutz/org.nutz.boot.starter.NbStarter</resource>
</transformer>
<transformer>
<mainClass>io.nutz.demo.simple.MainLauncher</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,74 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-demo-simple</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
<artifactId>nutzboot-demo-simple-tomcat</artifactId>
<dependencies>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-nutz-mvc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter-tomcat</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/nutz/org.nutz.boot.starter.NbStarter</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>io.nutz.demo.simple.MainLauncher</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,21 @@
package io.nutz.demo.simple;
import org.nutz.boot.NbApp;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
@IocBean
public class MainLauncher {
@Ok("raw")
@At("/time/now")
public long now() {
return System.currentTimeMillis();
}
public static void main(String[] args) throws Exception {
new NbApp(MainLauncher.class).setPrintProcDoc(true).run();
}
}

View File

@ -0,0 +1,2 @@
tomcat.port=8080
tomcat.host=0.0.0.0

View File

@ -0,0 +1,7 @@
log4j.rootLogger=debug,Console
log4j.logger.org.eclipse.jetty=info
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%-5p] %d{HH:mm:ss.SSS} %l - %m%n

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello, So NB!</title>
</head>
<body>
Hello, So NB!(Tomcat)
</body>
</html>

View File

@ -19,6 +19,7 @@
<module>nutzboot-demo-simple-mvc-shiro</module>
<module>nutzboot-demo-simple-swagger</module>
<module>nutzboot-demo-simple-undertow</module>
<module>nutzboot-demo-simple-tomcat</module>
<module>nutzboot-demo-simple-thymeleaf</module>
<module>nutzboot-demo-simple-sharding-jdbc</module>
<module>nutzboot-demo-simple-uflo</module>

View File

@ -7,6 +7,7 @@
<version>2.0-SNAPSHOT</version>
</parent>
<artifactId>nutzboot-starter-tomcat</artifactId>
<packaging>jar</packaging>
<name>nutzboot-starter-tomcat</name>
<properties>
@ -32,6 +33,12 @@
<email>wendal1985@gmail.com</email>
<url>http://wendal.net/</url>
</developer>
<developer>
<id>wolfboys</id>
<name>benjobs</name>
<email>benjobs@qq.com</email>
<url>https://github.com/wolfboys</url>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/nutzam/nutzboot.git</connection>
@ -51,4 +58,70 @@
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
</repository>
</distributionManagement>
<dependencies>
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutzboot-starter</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!--tomcat start-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
</dependency>
<!--tomcat end-->
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutz-plugins-websocket</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-core</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>1.5.6.RELEASE</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,204 @@
package org.nutz.boot.starter.tomcat;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nutz.lang.Files;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarFile;
public abstract class AbstractServletContainerFactory {
protected final Log logger = LogFactory.getLog(getClass());
private static final String[] COMMON_DOC_ROOTS = { "webapp", "public","static" };
private File documentRoot;
public AbstractServletContainerFactory() {
super();
}
public File getDocumentRoot() {
return this.documentRoot;
}
/**
* Returns the absolute document root when it points to a valid directory, logging a
* warning and returning {@code null} otherwise.
* @return the valid document root
*/
protected final File getValidDocumentRoot(String staticPath) {
File file = getDocumentRoot();
// If document root not explicitly set see if we are running from a war archive
file = file != null ? file : getWarFileDocumentRoot();
// If not a war archive maybe it is an exploded war
file = file != null ? file : getExplodedWarFileDocumentRoot();
// Or maybe there is a document root in a well-known location
file = file != null ? file : getCommonDocumentRoot();
//static file
file = file != null ? file : Files.findFile(staticPath);
if (file == null && this.logger.isDebugEnabled()) {
this.logger
.debug("None of the document roots " + Arrays.asList(COMMON_DOC_ROOTS)
+ " point to a directory and will be ignored.");
}
else if (this.logger.isDebugEnabled()) {
this.logger.debug("Document root: " + file);
}
return file;
}
private File getExplodedWarFileDocumentRoot() {
return getExplodedWarFileDocumentRoot(getCodeSourceArchive());
}
protected List<URL> getUrlsOfJarsWithMetaInfResources() {
ClassLoader classLoader = getClass().getClassLoader();
List<URL> staticResourceUrls = new ArrayList<URL>();
if (classLoader instanceof URLClassLoader) {
for (URL url : ((URLClassLoader) classLoader).getURLs()) {
try {
if ("file".equals(url.getProtocol())) {
File file = new File(url.getFile());
if (file.isDirectory()
&& new File(file, "META-INF/resources").isDirectory()) {
staticResourceUrls.add(url);
}
else if (isResourcesJar(file)) {
staticResourceUrls.add(url);
}
}
else {
URLConnection connection = url.openConnection();
if (connection instanceof JarURLConnection) {
if (isResourcesJar((JarURLConnection) connection)) {
staticResourceUrls.add(url);
}
}
}
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}
return staticResourceUrls;
}
private boolean isResourcesJar(JarURLConnection connection) {
try {
return isResourcesJar(connection.getJarFile());
}
catch (IOException ex) {
return false;
}
}
private boolean isResourcesJar(File file) {
try {
return isResourcesJar(new JarFile(file));
}
catch (IOException ex) {
return false;
}
}
private boolean isResourcesJar(JarFile jar) throws IOException {
try {
return jar.getName().endsWith(".jar")
&& (jar.getJarEntry("META-INF/resources") != null);
}
finally {
jar.close();
}
}
File getExplodedWarFileDocumentRoot(File codeSourceFile) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Code archive: " + codeSourceFile);
}
if (codeSourceFile != null && codeSourceFile.exists()) {
String path = codeSourceFile.getAbsolutePath();
int webInfPathIndex = path
.indexOf(File.separatorChar + "WEB-INF" + File.separatorChar);
if (webInfPathIndex >= 0) {
path = path.substring(0, webInfPathIndex);
return new File(path);
}
}
return null;
}
private File getWarFileDocumentRoot() {
return getArchiveFileDocumentRoot(".war");
}
private File getArchiveFileDocumentRoot(String extension) {
File file = getCodeSourceArchive();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Code archive: " + file);
}
if (file != null && file.exists() && !file.isDirectory()
&& file.getName().toLowerCase().endsWith(extension)) {
return file.getAbsoluteFile();
}
return null;
}
private File getCommonDocumentRoot() {
for (String commonDocRoot : COMMON_DOC_ROOTS) {
URL url = Thread.currentThread().getContextClassLoader().getResource(commonDocRoot);
if (url!=null) {
File root = new File(url.getPath());
if (root.exists() && root.isDirectory()) {
return root.getAbsoluteFile();
}
}
}
return null;
}
private File getCodeSourceArchive() {
return getCodeSourceArchive(getClass().getProtectionDomain().getCodeSource());
}
File getCodeSourceArchive(CodeSource codeSource) {
try {
URL location = (codeSource == null ? null : codeSource.getLocation());
if (location == null) {
return null;
}
String path;
URLConnection connection = location.openConnection();
if (connection instanceof JarURLConnection) {
path = ((JarURLConnection) connection).getJarFile().getName();
}
else {
path = location.toURI().getPath();
}
if (path.contains("!/")) {
path = path.substring(0, path.indexOf("!/"));
}
return new File(path);
}
catch (Exception ex) {
return null;
}
}
protected final File getValidSessionStoreDir(boolean mkdirs) {
return new ApplicationTemp().getDir("servlet-sessions");
}
}

View File

@ -0,0 +1,158 @@
package org.nutz.boot.starter.tomcat;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.Enumeration;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
public class ApplicationHome {
private final File source;
private final File dir;
/**
* Create a new {@link ApplicationHome} instance.
*/
public ApplicationHome() {
this(null);
}
/**
* Create a new {@link ApplicationHome} instance for the specified source class.
* @param sourceClass the source class or {@code null}
*/
public ApplicationHome(Class<?> sourceClass) {
this.source = findSource(sourceClass == null ? getStartClass() : sourceClass);
this.dir = findHomeDir(this.source);
}
private Class<?> getStartClass() {
try {
ClassLoader classLoader = getClass().getClassLoader();
return getStartClass(classLoader.getResources("META-INF/MANIFEST.MF"));
}
catch (Exception ex) {
return null;
}
}
private Class<?> getStartClass(Enumeration<URL> manifestResources) {
while (manifestResources.hasMoreElements()) {
try {
InputStream inputStream = manifestResources.nextElement().openStream();
try {
Manifest manifest = new Manifest(inputStream);
String startClass = manifest.getMainAttributes()
.getValue("Start-Class");
if (startClass != null) {
return ClassUtils.forName(startClass,
getClass().getClassLoader());
}
}
finally {
inputStream.close();
}
}
catch (Exception ex) {
}
}
return null;
}
private File findSource(Class<?> sourceClass) {
try {
ProtectionDomain domain = (sourceClass == null ? null
: sourceClass.getProtectionDomain());
CodeSource codeSource = (domain == null ? null : domain.getCodeSource());
URL location = (codeSource == null ? null : codeSource.getLocation());
File source = (location == null ? null : findSource(location));
if (source != null && source.exists() && !isUnitTest()) {
return source.getAbsoluteFile();
}
return null;
}
catch (Exception ex) {
return null;
}
}
private boolean isUnitTest() {
try {
for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
if (element.getClassName().startsWith("org.junit.")) {
return true;
}
}
}
catch (Exception ex) {
}
return false;
}
private File findSource(URL location) throws IOException {
URLConnection connection = location.openConnection();
if (connection instanceof JarURLConnection) {
return getRootJarFile(((JarURLConnection) connection).getJarFile());
}
return new File(location.getPath());
}
private File getRootJarFile(JarFile jarFile) {
String name = jarFile.getName();
int separator = name.indexOf("!/");
if (separator > 0) {
name = name.substring(0, separator);
}
return new File(name);
}
private File findHomeDir(File source) {
File homeDir = source;
homeDir = (homeDir == null ? findDefaultHomeDir() : homeDir);
if (homeDir.isFile()) {
homeDir = homeDir.getParentFile();
}
homeDir = (homeDir.exists() ? homeDir : new File("."));
return homeDir.getAbsoluteFile();
}
private File findDefaultHomeDir() {
String userDir = System.getProperty("user.dir");
return new File(StringUtils.hasLength(userDir) ? userDir : ".");
}
/**
* Returns the underlying source used to find the home directory. This is usually the
* jar file or a directory. Can return {@code null} if the source cannot be
* determined.
* @return the underlying source or {@code null}
*/
public File getSource() {
return this.source;
}
/**
* Returns the application home directory.
* @return the home directory (never {@code null})
*/
public File getDir() {
return this.dir;
}
@Override
public String toString() {
return getDir().toString();
}
}

View File

@ -0,0 +1,116 @@
package org.nutz.boot.starter.tomcat;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.io.File;
import java.security.MessageDigest;
public class ApplicationTemp {
private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
private final Class<?> sourceClass;
private volatile File dir;
/**
* Create a new {@link ApplicationTemp} instance.
*/
public ApplicationTemp() {
this(null);
}
/**
* Create a new {@link ApplicationTemp} instance for the specified source class.
* @param sourceClass the source class or {@code null}
*/
public ApplicationTemp(Class<?> sourceClass) {
this.sourceClass = sourceClass;
}
@Override
public String toString() {
return getDir().getAbsolutePath();
}
/**
* Return a sub-directory of the application temp.
* @param subDir the sub-directory name
* @return a sub-directory
*/
public File getDir(String subDir) {
File dir = new File(getDir(), subDir);
dir.mkdirs();
return dir;
}
/**
* Return the directory to be used for application specific temp files.
* @return the application temp directory
*/
public File getDir() {
if (this.dir == null) {
synchronized (this) {
byte[] hash = generateHash(this.sourceClass);
this.dir = new File(getTempDirectory(), toHexString(hash));
this.dir.mkdirs();
Assert.state(this.dir.exists(),
"Unable to create temp directory " + this.dir);
}
}
return this.dir;
}
private File getTempDirectory() {
String property = System.getProperty("java.io.tmpdir");
Assert.state(StringUtils.hasLength(property), "No 'java.io.tmpdir' property set");
File file = new File(property);
Assert.state(file.exists(), "Temp directory" + file + " does not exist");
Assert.state(file.isDirectory(), "Temp location " + file + " is not a directory");
return file;
}
private byte[] generateHash(Class<?> sourceClass) {
ApplicationHome home = new ApplicationHome(sourceClass);
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-1");
update(digest, home.getSource());
update(digest, home.getDir());
update(digest, System.getProperty("user.dir"));
update(digest, System.getProperty("java.home"));
update(digest, System.getProperty("java.class.path"));
update(digest, System.getProperty("sun.java.command"));
update(digest, System.getProperty("sun.boot.class.path"));
return digest.digest();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private void update(MessageDigest digest, Object source) {
if (source != null) {
digest.update(getUpdateSourceBytes(source));
}
}
private byte[] getUpdateSourceBytes(Object source) {
if (source instanceof File) {
return getUpdateSourceBytes(((File) source).getAbsolutePath());
}
return source.toString().getBytes();
}
private String toHexString(byte[] bytes) {
char[] hex = new char[bytes.length * 2];
for (int i = 0; i < bytes.length; i++) {
int b = bytes[i] & 0xFF;
hex[i * 2] = HEX_CHARS[b >>> 4];
hex[i * 2 + 1] = HEX_CHARS[b & 0x0F];
}
return new String(hex);
}
}

View File

@ -0,0 +1,15 @@
package org.nutz.boot.starter.tomcat;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.util.StandardSessionIdGenerator;
class LazySessionIdGenerator extends StandardSessionIdGenerator {
@Override
protected void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
}
}

View File

@ -0,0 +1,69 @@
package org.nutz.boot.starter.tomcat;
import org.apache.catalina.Container;
import org.apache.catalina.Manager;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.session.ManagerBase;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
public class TomcatContext extends StandardContext {
private TomcatStarter starter;
private final boolean overrideLoadOnStart;
TomcatContext() {
this.overrideLoadOnStart = ReflectionUtils
.findMethod(StandardContext.class, "loadOnStartup", Container[].class)
.getReturnType() == boolean.class;
}
@Override
public boolean loadOnStartup(Container[] children) {
if (this.overrideLoadOnStart) {
return true;
}
return super.loadOnStartup(children);
}
@Override
public void setManager(Manager manager) {
if (manager instanceof ManagerBase) {
manager.setSessionIdGenerator(new LazySessionIdGenerator());
}
super.setManager(manager);
}
public void deferredLoadOnStartup() {
// Some older Servlet frameworks (e.g. Struts, BIRT) use the Thread context class
// loader to create servlet instances in this phase. If they do that and then try
// to initialize them later the class loader may have changed, so wrap the call to
// loadOnStartup in what we think its going to be the main webapp classloader at
// runtime.
ClassLoader classLoader = getLoader().getClassLoader();
ClassLoader existingLoader = null;
if (classLoader != null) {
existingLoader = ClassUtils.overrideThreadContextClassLoader(classLoader);
}
if (this.overrideLoadOnStart) {
// Earlier versions of Tomcat used a version that returned void. If that
// version is used our overridden loadOnStart method won't have been called
// and the original will have already run.
super.loadOnStartup(findChildren());
}
if (existingLoader != null) {
ClassUtils.overrideThreadContextClassLoader(existingLoader);
}
}
public void setStarter(TomcatStarter starter) {
this.starter = starter;
}
public TomcatStarter getStarter() {
return this.starter;
}
}

View File

@ -0,0 +1,172 @@
package org.nutz.boot.starter.tomcat;
import org.apache.catalina.Context;
import org.apache.catalina.WebResourceRoot.ResourceSetType;
import org.apache.catalina.core.StandardContext;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import javax.naming.directory.DirContext;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
public abstract class TomcatResources {
private final Context context;
TomcatResources(Context context) {
this.context = context;
}
void addResourceJars(List<URL> resourceJarUrls) {
for (URL url : resourceJarUrls) {
String file = url.getFile();
if (file.endsWith(".jar") || file.endsWith(".jar!/")) {
String jar = url.toString();
if (!jar.startsWith("jar:")) {
// A jar file in the file system. Convert to Jar URL.
jar = "jar:" + jar + "!/";
}
addJar(jar);
}
else {
addDir(file, url);
}
}
}
protected final Context getContext() {
return this.context;
}
/**
* Called to add a JAR to the resources.
* @param jar the URL spec for the jar
*/
protected abstract void addJar(String jar);
/**
* Called to add a dir to the resource.
* @param dir the dir
* @param url the URL
*/
protected abstract void addDir(String dir, URL url);
/**
* Return a {@link TomcatResources} instance for the currently running Tomcat version.
* @param context the tomcat context
* @return a {@link TomcatResources} instance.
*/
public static TomcatResources get(Context context) {
if (ClassUtils.isPresent("org.apache.catalina.deploy.ErrorPage", null)) {
return new Tomcat7Resources(context);
}
return new Tomcat8Resources(context);
}
/**
* {@link TomcatResources} for Tomcat 7.
*/
private static class Tomcat7Resources extends TomcatResources {
private final Method addResourceJarUrlMethod;
Tomcat7Resources(Context context) {
super(context);
this.addResourceJarUrlMethod = ReflectionUtils.findMethod(context.getClass(),
"addResourceJarUrl", URL.class);
}
@Override
protected void addJar(String jar) {
URL url = getJarUrl(jar);
if (url != null) {
try {
this.addResourceJarUrlMethod.invoke(getContext(), url);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
private URL getJarUrl(String jar) {
try {
return new URL(jar);
}
catch (MalformedURLException ex) {
// Ignore
return null;
}
}
@Override
protected void addDir(String dir, URL url) {
if (getContext() instanceof StandardContext) {
try {
Class<?> fileDirContextClass = Class
.forName("org.apache.naming.resources.FileDirContext");
Method setDocBaseMethod = ReflectionUtils
.findMethod(fileDirContextClass, "setDocBase", String.class);
Object fileDirContext = fileDirContextClass.newInstance();
setDocBaseMethod.invoke(fileDirContext, dir);
Method addResourcesDirContextMethod = ReflectionUtils.findMethod(
StandardContext.class, "addResourcesDirContext",
DirContext.class);
addResourcesDirContextMethod.invoke(getContext(), fileDirContext);
}
catch (Exception ex) {
throw new IllegalStateException("Tomcat 7 reflection failed", ex);
}
}
}
}
/**
* {@link TomcatResources} for Tomcat 8.
*/
static class Tomcat8Resources extends TomcatResources {
Tomcat8Resources(Context context) {
super(context);
}
@Override
protected void addJar(String jar) {
addResourceSet(jar);
}
@Override
protected void addDir(String dir, URL url) {
addResourceSet(url.toString());
}
private void addResourceSet(String resource) {
try {
if (isInsideNestedJar(resource)) {
// It's a nested jar but we now don't want the suffix because Tomcat
// is going to try and locate it as a root URL (not the resource
// inside it)
resource = resource.substring(0, resource.length() - 2);
}
URL url = new URL(resource);
String path = "/META-INF/resources";
getContext().getResources().createWebResourceSet(
ResourceSetType.RESOURCE_JAR, "/", url, path);
}
catch (Exception ex) {
// Ignore (probably not a directory)
}
}
private boolean isInsideNestedJar(String dir) {
return dir.indexOf("!/") < dir.lastIndexOf("!/");
}
}
}

View File

@ -0,0 +1,583 @@
package org.nutz.boot.starter.tomcat;
import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardThreadExecutor;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.startup.Tomcat;
import org.apache.jasper.servlet.JspServlet;
import org.apache.naming.ContextBindings;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.nutz.boot.AppContext;
import org.nutz.boot.annotation.PropDoc;
import org.nutz.boot.aware.AppContextAware;
import org.nutz.boot.aware.ClassLoaderAware;
import org.nutz.boot.aware.IocAware;
import org.nutz.boot.starter.ServerFace;
import org.nutz.boot.starter.WebEventListenerFace;
import org.nutz.boot.starter.WebFilterFace;
import org.nutz.boot.starter.WebServletFace;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
import org.nutz.lang.util.LifeCycle;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
import org.springframework.boot.context.embedded.tomcat.ConnectorStartFailedException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StreamUtils;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* tomcat 启动器
*
* @author benjobs (benjobs@qq.com)
*/
public class TomcatStarter extends AbstractServletContainerFactory implements ClassLoaderAware, IocAware, ServerFace, LifeCycle, AppContextAware {
private static final Log logger = Logs.get();
protected static final String PRE = "tomcat.";
@PropDoc(group = "tomcat", value = "监听的ip地址", defaultValue = "0.0.0.0")
public static final String PROP_HOST = PRE + "host";
@PropDoc(group = "tomcat", value = "监听的端口", defaultValue = "8080", type = "int")
public static final String PROP_PORT = PRE + "port";
@PropDoc(group = "tomcat", value = "上下文路径")
public static final String PROP_CONTEXT_PATH = PRE + "contextPath";
@PropDoc(group = "tomcat", value = "session过期时间", defaultValue = "20")
public static final String PROP_SESSION = PRE + "session";
@PropDoc(group = "tomcat", value = "过滤器顺序", defaultValue = "whale,druid,shiro,nutz")
public static final String PROP_WEB_FILTERS_ORDER = "web.filters.order";
@PropDoc(group = "tomcat", value = "静态文件路径", defaultValue = "/static/")
public static final String PROP_STATIC_PATH = PRE + "staticPath";
public static final String PROP_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
protected Tomcat tomcat;
protected TomcatContext tomcatContext;
protected ClassLoader classLoader;
protected Ioc ioc;
protected AppContext appContext;
private PropertiesProxy conf;
private final Map<Service, Connector[]> serviceConnectors = new HashMap<Service, Connector[]>();
private final Object monitor = new Object();
private static final AtomicInteger containerCounter = new AtomicInteger(-1);
private Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Charset uriEncoding = DEFAULT_CHARSET;
private final boolean autoStart = true;
private volatile boolean started;
//--getConf---
public int getPort() {
return conf.getInt(PROP_PORT, 8080);
}
public String getHost() {
return conf.get(PROP_HOST, "0.0.0.0");
}
public String getStaticPath() {
return conf.get(PROP_STATIC_PATH, "static");
}
public String getContextPath() {
return conf.get(PROP_CONTEXT_PATH, "");
}
public int getSessionTimeout() {
return conf.getInt(PROP_SESSION, 30);
}
public int getMaxThread() {
return Lang.isAndroid ? 50 : 500;
}
@Override
public void init() {
this.conf = appContext.getConfigureLoader().get();
this.tomcat = new Tomcat();
File baseDir = createTempDir("tomcat");
this.tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.PROP_PROTOCOL);
connector.setPort(getPort());
connector.setURIEncoding(getUriEncoding().name());
//maxThread
this.tomcat.getService().addConnector(connector);
StandardThreadExecutor executor = new StandardThreadExecutor();
executor.setMaxThreads(getMaxThread());
connector.getService().addExecutor(executor);
// If ApplicationContext is slow to start we want Tomcat not to bind to the socket
// prematurely...
connector.setProperty("bindOnInit", "false");
this.tomcat.setConnector(connector);
this.tomcat.setHostname(getHost());
this.tomcat.getHost().setAutoDeploy(false);
this.tomcat.getEngine().setBackgroundProcessorDelay(30);
prepareContext(this.tomcat.getHost());
try {
addInstanceIdToEngineName();
removeServiceConnectors();
this.tomcat.start();
this.nutzSupport();
rethrowDeferredStartupExceptions();
Context context = findContext();
try {
ContextBindings.bindClassLoader(
context,
getNamingToken(context),
getClass().getClassLoader()
);
} catch (NamingException ex) {
}
startDaemonAwaitThread();
} catch (Exception ex) {
this.containerCounter.decrementAndGet();
throw new EmbeddedServletContainerException("Unable to start embedded Tomcat", ex);
}
}
@Override
public void start() throws EmbeddedServletContainerException {
synchronized (this.monitor) {
if (this.started) {
return;
}
try {
addPreviouslyRemovedConnectors();
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
startConnector(connector);
}
checkThatConnectorsHaveStarted();
this.started = true;
logger.info("Tomcat started on port(s): " + getPortsDescription(true));
} catch (ConnectorStartFailedException ex) {
stopSilently();
throw ex;
} catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat servlet container", ex);
} finally {
Context context = findContext();
ContextBindings.unbindClassLoader(context, getNamingToken(context),
getClass().getClassLoader());
}
}
}
@Override
public void stop() throws LifecycleException {
this.tomcat.stop();
this.started = false;
}
@Override
public boolean isRunning() {
return this.started;
}
@Override
public void setAppContext(AppContext appContext) {
this.appContext = appContext;
}
@Override
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void setIoc(Ioc ioc) {
this.ioc = ioc;
}
@Override
public boolean failsafe() {
return false;
}
@Override
public void fetch() {
}
@Override
public void depose() {
}
private void prepareContext(Host host) {
File docBase = getValidDocumentRoot(getStaticPath());
docBase = (docBase != null ? docBase : createTempDir("tomcat-docbase"));
this.tomcatContext = new TomcatContext();
this.tomcatContext.setName(getContextPath());
this.tomcatContext.setPath(getContextPath());
this.tomcatContext.setDocBase(docBase.getAbsolutePath());
this.tomcatContext.addLifecycleListener(new Tomcat.FixContextListener());
this.tomcatContext.setParentClassLoader(ClassUtils.getDefaultClassLoader());
for (String welcomeFile : Arrays.asList("index.html", "index.htm", "index.jsp", "index.do")) {
this.tomcatContext.addWelcomeFile(welcomeFile);
}
resetDefaultLocaleMapping();
try {
this.tomcatContext.setUseRelativeRedirects(false);
} catch (NoSuchMethodError ex) {
// Tomcat is < 8.0.30. Continue
}
WebappLoader loader = new WebappLoader(tomcatContext.getParentClassLoader());
loader.setLoaderClass(TomcatWebappClassLoader.class.getName());
loader.setDelegate(true);
this.tomcatContext.setLoader(loader);
//注册defaultServlet
addDefaultServlet();
//注册JspServlet
addJspServlet();
this.tomcatContext.addLifecycleListener(new StoreMergedWebXmlListener());
this.tomcatContext.addLifecycleListener(new LifecycleListener() {
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
TomcatResources.get(TomcatStarter.this.tomcatContext)
.addResourceJars(getUrlsOfJarsWithMetaInfResources());
}
}
});
host.addChild(tomcatContext);
}
private void nutzSupport() {
// 添加其他starter提供的WebXXXX服务
Map<String, WebFilterFace> filters = new HashMap<String, WebFilterFace>();
for (Object object : appContext.getStarters()) {
if (object instanceof WebFilterFace) {
WebFilterFace webFilter = (WebFilterFace) object;
if (webFilter == null || webFilter.getFilter() == null) {
continue;
}
filters.put(webFilter.getName(), webFilter);
}
if (object instanceof WebServletFace) {
WebServletFace webServlet = (WebServletFace) object;
if (webServlet == null || webServlet.getServlet() == null) {
continue;
}
addServlet(webServlet);
}
if (object instanceof WebEventListenerFace) {
WebEventListenerFace contextListener = (WebEventListenerFace) object;
if (contextListener == null || contextListener.getEventListener() == null) {
continue;
}
this.tomcatContext.addApplicationEventListener(contextListener.getEventListener());
}
}
String _filterOrders = conf.get(PROP_WEB_FILTERS_ORDER);
if (_filterOrders == null)
_filterOrders = "whale,druid,shiro,nutz";
else if (_filterOrders.endsWith("+")) {
_filterOrders = _filterOrders.substring(0, _filterOrders.length() - 1) + ",whale,druid,shiro,nutz";
}
String[] filterOrders = Strings.splitIgnoreBlank(_filterOrders);
for (String filterName : filterOrders) {
addFilter(filters.remove(filterName));
}
for (WebFilterFace webFilter : filters.values()) {
addFilter(webFilter);
}
}
private void addDefaultServlet() {
Wrapper defaultServlet = this.tomcatContext.createWrapper();
defaultServlet.setName("default");
defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet");
defaultServlet.addInitParameter("debug", "0");
defaultServlet.addInitParameter("listings", "false");
defaultServlet.setLoadOnStartup(1);
defaultServlet.setOverridable(true);
this.tomcatContext.addChild(defaultServlet);
addServletMapping("/", "default");
}
private void addJspServlet() {
Wrapper jspServlet = this.tomcatContext.createWrapper();
jspServlet.setName("jsp");
jspServlet.setServletClass(JspServlet.class.getName());
jspServlet.addInitParameter("fork", "false");
jspServlet.addInitParameter("development", "false");
jspServlet.setLoadOnStartup(3);
this.tomcatContext.addChild(jspServlet);
addServletMapping("*.jsp", "jsp");
addServletMapping("*.jspx", "jsp");
}
private void addServlet(WebServletFace webServlet) {
logger.debugf("[NutzBoot] add servlet name=%s pathSpec=%s", webServlet.getName(), webServlet.getPathSpec());
Wrapper servlet = tomcatContext.createWrapper();
servlet.setName(webServlet.getName());
servlet.setServletClass(webServlet.getServlet().getClass().getName());
for (Map.Entry<String, String> entry : webServlet.getInitParameters().entrySet()) {
servlet.addInitParameter(entry.getKey(), entry.getValue());
}
servlet.setOverridable(true);
tomcatContext.addChild(servlet);
addServletMapping(webServlet.getPathSpec(), webServlet.getName());
}
private void addFilter(WebFilterFace filterFace) {
if (filterFace == null || filterFace.getFilter() == null) {
return;
}
logger.debugf("[NutzBoot] add filter name=%s pathSpec=%s", filterFace.getName(), filterFace.getPathSpec());
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filterFace.getFilter());
filterDef.setFilterName(filterFace.getName());
filterDef.setFilterClass(filterFace.getFilter().getClass().getName());
if (filterFace.getInitParameters() != null && !filterFace.getInitParameters().isEmpty()) {
for (Map.Entry<String, String> entry : filterFace.getInitParameters().entrySet()) {
filterDef.addInitParameter(entry.getKey(), entry.getValue());
}
}
this.tomcatContext.addFilterDef(filterDef);
}
private Context findContext() {
for (Container child : this.tomcat.getHost().findChildren()) {
if (child instanceof Context) {
return (Context) child;
}
}
throw new IllegalStateException("The host does not contain a Context");
}
private void addInstanceIdToEngineName() {
int instanceId = containerCounter.incrementAndGet();
if (instanceId > 0) {
Engine engine = this.tomcat.getEngine();
engine.setName(engine.getName() + "-" + instanceId);
}
}
private void removeServiceConnectors() {
for (Service service : this.tomcat.getServer().findServices()) {
Connector[] connectors = service.findConnectors().clone();
this.serviceConnectors.put(service, connectors);
for (Connector connector : connectors) {
service.removeConnector(connector);
}
}
}
private void rethrowDeferredStartupExceptions() {
Container[] children = this.tomcat.getHost().findChildren();
for (Container container : children) {
if (!LifecycleState.STARTED.equals(container.getState())) {
throw new IllegalStateException(container + " failed to start");
}
}
}
private void addPreviouslyRemovedConnectors() {
Service[] services = this.tomcat.getServer().findServices();
for (Service service : services) {
Connector[] connectors = this.serviceConnectors.get(service);
if (connectors != null) {
for (Connector connector : connectors) {
service.addConnector(connector);
if (!this.autoStart) {
stopProtocolHandler(connector);
}
}
this.serviceConnectors.remove(service);
}
}
}
private void stopProtocolHandler(Connector connector) {
try {
connector.getProtocolHandler().stop();
} catch (Exception ex) {
logger.error("Cannot pause connector: ", ex);
}
}
private void startConnector(Connector connector) {
try {
for (Container child : this.tomcat.getHost().findChildren()) {
if (child instanceof TomcatContext) {
((TomcatContext) child).deferredLoadOnStartup();
}
}
} catch (Exception ex) {
logger.error("Cannot start connector: ", ex);
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat connectors", ex);
}
}
private void checkThatConnectorsHaveStarted() {
for (Connector connector : this.tomcat.getService().findConnectors()) {
if (LifecycleState.FAILED.equals(connector.getState())) {
throw new ConnectorStartFailedException(connector.getPort());
}
}
}
private void resetDefaultLocaleMapping() {
this.tomcatContext.addLocaleEncodingMappingParameter(Locale.ENGLISH.toString(), DEFAULT_CHARSET.displayName());
this.tomcatContext.addLocaleEncodingMappingParameter(Locale.FRENCH.toString(), DEFAULT_CHARSET.displayName());
}
private void addServletMapping(String pattern, String name) {
this.tomcatContext.addServletMappingDecoded(pattern, name);
}
private void startDaemonAwaitThread() {
Thread awaitThread = new Thread("container-" + (containerCounter.get())) {
@Override
public void run() {
TomcatStarter.this.tomcat.getServer().await();
}
};
awaitThread.setContextClassLoader(getClass().getClassLoader());
awaitThread.setDaemon(false);
awaitThread.start();
}
private void stopSilently() {
try {
stop();
} catch (LifecycleException ex) {
// Ignore
}
}
private String getPortsDescription(boolean localPort) {
StringBuilder ports = new StringBuilder();
for (Connector connector : this.tomcat.getService().findConnectors()) {
ports.append(ports.length() == 0 ? "" : " ");
int port = (localPort ? connector.getLocalPort() : connector.getPort());
ports.append(port + " (" + connector.getScheme() + ")");
}
return ports.toString();
}
public Charset getUriEncoding() {
return uriEncoding;
}
protected File createTempDir(String prefix) {
try {
File tempDir = File.createTempFile(prefix + ".", "." + getPort());
tempDir.delete();
tempDir.mkdir();
tempDir.deleteOnExit();
return tempDir;
} catch (IOException ex) {
throw new RuntimeException(
"Unable to create tempDir. java.io.tmpdir is set to "
+ System.getProperty("java.io.tmpdir"),
ex);
}
}
private static class StoreMergedWebXmlListener implements LifecycleListener {
private static final String MERGED_WEB_XML = "org.apache.tomcat.util.scan.MergedWebXml";
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
onStart((Context) event.getLifecycle());
}
}
private void onStart(Context context) {
ServletContext servletContext = context.getServletContext();
if (servletContext.getAttribute(MERGED_WEB_XML) == null) {
servletContext.setAttribute(MERGED_WEB_XML, getEmptyWebXml());
}
}
private String getEmptyWebXml() {
InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("empty-web.xml");
Assert.state(stream != null, "Unable to read empty web.xml");
try {
try {
return StreamUtils.copyToString(stream, Charset.forName("UTF-8"));
} finally {
stream.close();
}
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}
private Object getNamingToken(Context context) {
try {
return context.getNamingToken();
} catch (NoSuchMethodError ex) {
// Use the context itself on Tomcat 7
return context;
}
}
}

View File

@ -0,0 +1,97 @@
package org.nutz.boot.starter.tomcat;
import org.apache.catalina.loader.WebappClassLoader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.net.URL;
public class TomcatWebappClassLoader extends WebappClassLoader {
private static final Log logger = LogFactory.getLog(TomcatWebappClassLoader.class);
public TomcatWebappClassLoader() {
super();
}
public TomcatWebappClassLoader(ClassLoader parent) {
super(parent);
}
@Override
public synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class<?> result = findExistingLoadedClass(name);
result = (result == null ? doLoadClass(name) : result);
if (result == null) {
throw new ClassNotFoundException(name);
}
return resolveIfNecessary(result, resolve);
}
private Class<?> findExistingLoadedClass(String name) {
Class<?> resultClass = findLoadedClass0(name);
resultClass = (resultClass == null ? findLoadedClass(name) : resultClass);
return resultClass;
}
private Class<?> doLoadClass(String name) throws ClassNotFoundException {
checkPackageAccess(name);
if ((this.delegate || filter(name, true))) {
Class<?> result = loadFromParent(name);
return (result == null ? findClassIgnoringNotFound(name) : result);
}
Class<?> result = findClassIgnoringNotFound(name);
return (result == null ? loadFromParent(name) : result);
}
private Class<?> resolveIfNecessary(Class<?> resultClass, boolean resolve) {
if (resolve) {
resolveClass(resultClass);
}
return (resultClass);
}
@Override
protected void addURL(URL url) {
// Ignore URLs added by the Tomcat 8 implementation (see gh-919)
if (logger.isTraceEnabled()) {
logger.trace("Ignoring request to add " + url + " to the tomcat classloader");
}
}
private Class<?> loadFromParent(String name) {
if (this.parent == null) {
return null;
}
try {
return Class.forName(name, false, this.parent);
}
catch (ClassNotFoundException ex) {
return null;
}
}
private Class<?> findClassIgnoringNotFound(String name) {
try {
return findClass(name);
}
catch (ClassNotFoundException ex) {
return null;
}
}
private void checkPackageAccess(String name) throws ClassNotFoundException {
if (this.securityManager != null && name.lastIndexOf('.') >= 0) {
try {
this.securityManager
.checkPackageAccess(name.substring(0, name.lastIndexOf('.')));
}
catch (SecurityException ex) {
throw new ClassNotFoundException("Security Violation, attempt to use "
+ "Restricted Class: " + name, ex);
}
}
}
}

View File

@ -0,0 +1 @@
org.nutz.boot.starter.tomcat.TomcatStarter

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
</web-app>

47
pom.xml
View File

@ -12,6 +12,7 @@
<slf4j.version>1.7.25</slf4j.version>
<dubbo.version>2.5.6</dubbo.version>
<jetty.version>9.4.8.v20171121</jetty.version>
<tomcat.version>8.5.23</tomcat.version>
<shiro.version>1.3.2</shiro.version>
<jetx.version>2.1.5</jetx.version>
</properties>
@ -254,6 +255,52 @@
</exclusion>
</exclusions>
</dependency>
<!--tomcat start-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-log4j</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.4.2</version>
</dependency>
<!--tomcat end-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>