前言
和 Tomcat 类似,Jetty 也是一个 Web 应用服务器,相对于 Tomcat,Jetty 更加轻量、更加简易、更加灵活。今天通过代码来简单分析下 SpringBoot 中是如何启动 Jetty 的。
Jetty简介
使用
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.resource.JarResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.webapp.AbstractConfiguration;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.util.ClassUtils;
public class TestJetty {
public static void main(String[] args) throws Exception {
WebAppContext context = new WebAppContext();
InetSocketAddress address = new InetSocketAddress((InetAddress) null, getPort());
configureWebAppContext(context);
Server server = createServer(address);
server.setHandler(context);
server.start();
}
private static void configureWebAppContext(WebAppContext context) {
context.setTempDirectory(getTempDirectory());
context.setClassLoader(ClassUtils.getDefaultClassLoader());
context.setContextPath(getContextPath());
configureDocumentRoot(context);
addDefaultServlet(context);
Configuration configuration = new AbstractConfiguration() {
@Override
public void configure(WebAppContext context) throws Exception {
ServletContext ctx = context.getServletContext();
Dynamic registration = ctx.addServlet("myServlet", new MyServlet());
registration.setLoadOnStartup(1);
registration.addMapping("/myServlet");
}
};
Configuration[] configurations = new Configuration[]{configuration};
context.setConfigurations(configurations);
context.setThrowUnavailableOnStartupException(true);
}
private static void configureDocumentRoot(WebAppContext handler) {
File docBase = createTempDir("jetty-docbase");
try {
List<Resource> resources = new ArrayList<>();
Resource rootResource = (docBase.isDirectory() ? Resource
.newResource(docBase.getCanonicalFile())
: JarResource.newJarResource(Resource.newResource(docBase)));
resources.add(rootResource);
handler.setBaseResource(new ResourceCollection(resources.toArray(new Resource[0])));
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private static Server createServer(InetSocketAddress address) {
Server server = new Server();
server.setConnectors(new Connector[]{createConnector(address, server)});
return server;
}
private static AbstractConnector createConnector(InetSocketAddress address, Server server) {
ServerConnector connector = new ServerConnector(server, -1, -1);
connector.setHost(address.getHostString());
connector.setPort(address.getPort());
for (ConnectionFactory connectionFactory : connector.getConnectionFactories()) {
if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) {
((HttpConfiguration.ConnectionFactory) connectionFactory).getHttpConfiguration()
.setSendServerVersion(false);
}
}
return connector;
}
private static class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().println("hello Jetty");
resp.getWriter().flush();
}
}
private static void addDefaultServlet(WebAppContext context) {
ServletHolder holder = new ServletHolder();
holder.setName("default");
holder.setClassName("org.eclipse.jetty.servlet.DefaultServlet");
holder.setInitParameter("dirAllowed", "false");
holder.setInitOrder(1);
context.getServletHandler().addServletWithMapping(holder, "/");
context.getServletHandler().getServletMapping("/").setDefault(true);
}
private static File createTempDir(String prefix) {
try {
File tempDir = File.createTempFile(prefix + ".", "." + getPort());
tempDir.delete();
tempDir.mkdir();
tempDir.deleteOnExit();
return tempDir;
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
}
private static File getTempDirectory() {
String temp = System.getProperty("java.io.tmpdir");
return (temp != null) ? new File(temp) : null;
}
private static String getContextPath() {
return "/testjetty";
}
private static int getPort() {
return 8989;
}
}
定义一个 WebAppContext,启动 8989 监听端口,代码启动之后,我们可以通过以下地址来访问。
http://localhost:8989/testjetty/myServlet
分析
上述代码完全是参考 SpringBoot 内创建并启动 Jetty 的过程,具体流程如下
- SpringBoot 默认使用的 ApplicationContext 实现类为 AnnotationConfigServletWebServerApplicationContext,具体判断逻辑为 ApplicationContextFactory 的 DEFAULT。
- AnnotationConfigServletWebServerApplicationContext 继承 ServletWebServerApplicationContext 的 onRefresh() 方法,通过 JettyServletWebServerFactory 的 getWebServer() 方法来创建 WebServer,在这个过程中就会创建 Server 对象并启动。