jetty是做什么的?
jetty是HTTP服务,HTTP客户端,和javax.servlet的容器。它本身被设计成嵌入式模式,应该将jetty集成到自己的应用,jetty本身可以实例化,能像任何POJO一样使用,用jetty就相当于把Http服务塞进了自己的应用
jetty的口号“Don't deploy your application in Jetty, deploy Jetty in your application.”
启动jetty java -jar start.jar。
运行jetty java -jar start.jar等效于 java -jar start.jar etc/jetty.xml[默认的jetty配置文件]
启动jetty若需要的更多参数,可以统一通过 start.ini 文件来配置
#===========================================================
# Jetty start.ini example
#-----------------------------------------------------------
OPTIONS=Server
etc/jetty.xml
etc/jetty-http.xml
复制代码官网启动Jetty
OPTIONS:指定构建过程中这个目录下面的所有jar都需要添加
etc/jetty.xml:它会添加到启动start.jar命令的后头
在start.ini中同时可以指定JVM的参数,只是必须添加 --exec
#===========================================================
# Jetty start.jar arguments
#-----------------------------------------------------------
--exec
-Xmx512m
-XX:OnOutOfMemoryError='kill -3 %p'
-Dcom.sun.management.jmxremote
OPTIONS=Server,jmx,resources
etc/jetty-jmx.xml
etc/jetty.xml
etc/jetty-ssl.xml
复制代码这么做是因为这里添加的JVM 参数并没有影响start.jar的启动,而是另起一个新的JVM,会加上这些参数来运行
Jetty的启动start.jar分析
主要逻辑在Main.java中
以包含java的参数运行为例
// execute Jetty in another JVM
if (args.isExec()){
//获取参数
CommandLineBuilder cmd = args.getMainArgs(true);
...
ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs());
StartLog.endStartLog();
final Process process = pbuilder.start();
...
process.waitFor();
System.exit(0); // exit JVM when child process ends.
return;
}
复制代码
提取参数的过程中,对于非JPMS,会在最后添加
cmd.addRawArg("-cp");
cmd.addRawArg(classpath.toString());
cmd.addRawArg(getMainClassname());
复制代码
可以追踪MainClassname得到
private static final String MAIN_CLASS = "org.eclipse.jetty.xml.XmlConfiguration";
复制代码
后续新建一个进程,真正的去运行目的程序
pid = forkAndExec(launchMechanism.ordinal() + 1, //获取系统类型
helperpath, //对于java来说就是获取 java 命令地址
prog,
argBlock, argc,
envBlock, envc,
dir,
fds,
redirectErrorStream);
复制代码
XmlConfiguration启动
主要就是加载所有的xml文件,然后运行实现了LifeCycle接口的方法
List objects = new ArrayList<>(args.length);
for (int i = 0; i < args.length; i++)
{
if (!args[i].toLowerCase(Locale.ENGLISH).endsWith(".properties") && (args[i].indexOf('=')<0))
{
XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(args[i]).getURI().toURL());
if (last != null)
configuration.getIdMap().putAll(last.getIdMap());
if (properties.size() > 0)
{
Map props = new HashMap<>();
for (Object key : properties.keySet())
{
props.put(key.toString(),String.valueOf(properties.get(key)));
}
configuration.getProperties().putAll(props);
}
Object obj = configuration.configure();
if (obj!=null && !objects.contains(obj))
objects.add(obj);
last = configuration;
}
}
// For all objects created by XmlConfigurations, start them if they are lifecycles.
for (Object obj : objects)
{
if (obj instanceof LifeCycle)
{
LifeCycle lc = (LifeCycle)obj;
if (!lc.isRunning())
lc.start(); //运行
}
}
复制代码对应着jetty.xml中的配置,他就是Server的start方法
jetty.xml文件
它是默认的jetty配置文件,主要包括:
服务器的类和全局选项
连接池(最大最小线程数)
连接器(端口,超时时间,缓冲区,协议等)
处理器(handler structure,可用默认的处理器或者上下文处理搜集器contextHandlerCollections)
发布管理器(用来扫描要发布的webapp和上下文)
登录服务(做权限检查)
请求日志
jetty支持多配置文件,每一个配置文件中通过指定要初始化的服务器实例,ID来标识,每个ID都会在同一个JVM中创建一个新的服务,如果在多个配置文件中用同一个ID,这些所有的配置都会用到同一个服务上
配置文件一般样式
// 根元素,指定以下配置是给那个类,一般在jetty.xml中server,或者jetty-web.xml中的WebAppContext
// setter方法调用的标识。name属性用来标识setter的方法名,如果这个方法没有找到,就把name中值当做字段来使用。如果有属性class表明这个set方法是静态方法
demo
//初始化对象,class决定new对象的类型,需要写全路径类名,没有用则调用默认的构造函数
10
xyz
//调用对象的某个方法,name属性表明确确调用的方法的名字
false
//引用之前已经生成对象的id
20
//调用当前对象的get方法,同set
demo2
复制代码
它相当于java代码
com.acme.Foo foo = new com.acme.Foo();
foo.setName("demo");
com.acme.Bar bar = new com.acme.Bar(true);
bar.setWibble(10);
bar.setWobble("xyz");
bar.setParent(foo);
bar.init(false);
foo.setNested(bar);
bar.setWibble(20);
bar.getParent().setName("demo2");
复制代码
web项目中的一般配置
web服务指定的服务类一般为 org.eclipse.jetty.server.Server,然后构建对应的实例
ThreadPool。
Connector。
Handler。
这也是jetty整个架构的体现,Connector用来接收连接,Handler用来处理request和response
QueuedThreadPool
jetty的线程池默认使用的就是 QueuedThreadPool,它的构造函数如下
public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout, @Name("reservedThreads") int reservedThreads, @Name("queue") BlockingQueue queue, @Name("threadGroup") ThreadGroup threadGroup)
{
if (maxThreads < minThreads) {
throw new IllegalArgumentException("max threads ("+maxThreads+") less than min threads ("
+minThreads+")");
}
setMinThreads(minThreads);
setMaxThreads(maxThreads);
setIdleTimeout(idleTimeout);
setStopTimeout(5000);
setReservedThreads(reservedThreads);
if (queue==null)
{
int capacity=Math.max(_minThreads, 8);
queue=new BlockingArrayQueue<>(capacity, capacity);
}
_jobs=queue;
_threadGroup=threadGroup;
setThreadPoolBudget(new ThreadPoolBudget(this));
}
复制代码
本质上也是使用最大线程最小线程阻塞队列来实现
ServerConnector
public ServerConnector(
@Name("server") Server server,
@Name("executor") Executor executor,
@Name("scheduler") Scheduler scheduler,
@Name("bufferPool") ByteBufferPool bufferPool,
@Name("acceptors") int acceptors,
@Name("selectors") int selectors,
@Name("factories") ConnectionFactory... factories)
{
super(server,executor,scheduler,bufferPool,acceptors,factories);
_manager = newSelectorManager(getExecutor(), getScheduler(),selectors);
addBean(_manager, true);//在ServerConnector启动的过程中,会被启动
setAcceptorPriorityDelta(-2);
}
复制代码factories:默认使用HttpConnectionFactory
acceptors:表示用来接收新的TCP/IP连接的线程个数int cores = ProcessorUtils.availableProcessors();
if (acceptors < 0)
acceptors=Math.max(1, Math.min(4,cores/8));
if (acceptors > cores)
LOG.warn("Acceptors should be <= availableProcessors: " + this);
_acceptors = new Thread[acceptors];
复制代码
WebAppContext
处理web请求使用的handler,一般使用默认的构造函数,通过set方法来实例化对应的属性。在jetty.xml中比如指定属性configurationClasses一般取值如下
org.eclipse.jetty.webapp.WebInfConfiguration
org.eclipse.jetty.webapp.WebXmlConfiguration
org.eclipse.jetty.webapp.MetaInfConfiguration
org.eclipse.jetty.webapp.FragmentConfiguration
org.eclipse.jetty.plus.webapp.EnvConfiguration
org.eclipse.jetty.plus.webapp.PlusConfiguration
org.eclipse.jetty.annotations.AnnotationConfiguration
org.eclipse.jetty.webapp.JettyWebXmlConfiguration
org.eclipse.jetty.webapp.TagLibConfiguration
复制代码
比如web有两个WebInfConfiguration和WebXmlConfiguration,从名字可以感受到,WebInfConfiguration就是对应web项目中的WEB-INF目录,而WebXmlConfiguration就是对应着web.xml文件
Server启动
Server类是Jetty的HTTP Servlet服务器,它实现了LifeCycle接口。调用的start实现真正执行的就是Server自身的doStart
//AbstractLifeCycle中
@Override
public final void start() throws Exception
{
synchronized (_lock)
{
...
doStart();
...
}
}
复制代码类似的后续的所有相关LifeCycle的start启动,其实就是调用实现了它的类的doStart()方法
Server本身启动
protected void doStart() throws Exception
{
...
//1. 保证JVM自己挂掉的时候,对应的Jetty进程也会关掉
ShutdownMonitor.register(this);
...
//2. 按照Server自己添加的bean的顺序,来一个个的启动他们
super.doStart();
...
//3. 启动connector
for (Connector connector : _connectors)
{
try
{
connector.start();
}
catch(Throwable e)
{
mex.add(e);
}
}
...
}
复制代码
bean启动
对于server来说,它的bean会在每次调用对应的set方法都会执行,包括ThreadPool和Handler
//ContainerLifeCycle中
public boolean addBean(Object o)
{
if (o instanceof LifeCycle)
{
LifeCycle l = (LifeCycle)o;
//尚未启动的统一为AUTO
return addBean(o,l.isRunning()?Managed.UNMANAGED:Managed.AUTO);
}
return addBean(o,Managed.POJO);
}
复制代码
执行对应bean的启动
//ContainerLifeCycle中
protected void doStart() throws Exception
{
...
// start our managed and auto beans
for (Bean b : _beans)
{
if (b._bean instanceof LifeCycle)
{
LifeCycle l = (LifeCycle)b._bean;
switch(b._managed)
{
case MANAGED:
if (!l.isRunning())
start(l);
break;
case AUTO:
if (l.isRunning())
unmanage(b);
else
{
manage(b);
start(l);
}
break;
default:
break;
}
}
}
super.doStart();
}
复制代码
对于web来说,一定会配置一个handerWebAppContext来加载对应的web.xml文件
下面着重介绍 WebAppContext
QueuedThreadPool启动
@Override
protected void doStart() throws Exception
{
_tryExecutor = new ReservedThreadExecutor(this,_reservedThreads);
addBean(_tryExecutor);
super.doStart();
_threadsStarted.set(0);
startThreads(_minThreads);
}
private boolean startThreads(int threadsToStart)
{
while (threadsToStart > 0 && isRunning())
{
...
Thread thread = newThread(_runnable);
thread.setDaemon(isDaemon());
thread.setPriority(getThreadsPriority());
thread.setName(_name + "-" + thread.getId());
_threads.add(thread);
_lastShrink.set(System.nanoTime());
thread.start();
started = true;
--threadsToStart;
...
}
return true;
}
复制代码
可以看到它会直接调用去启动最小的线程数
org.eclipse.jetty.server.ServerConnector
Jetty9中它是主要的实现连接TCP/IP的类。可以在父类中找到对应dostart
//AbstractNetworkConnector
protected void doStart() throws Exception
{
open();
super.doStart();
}
//ServerConnector
public void open() throws IOException
{
if (_acceptChannel == null)
{
_acceptChannel = openAcceptChannel();
_acceptChannel.configureBlocking(true);//阻塞接收连接的channel
_localPort = _acceptChannel.socket().getLocalPort();
if (_localPort <= 0)
throw new IOException("Server channel not bound");
addBean(_acceptChannel);
}
}
复制代码
再向父类执行
//AbstractConnector
protected void doStart() throws Exception
{
...
//1. 选择连接的类型
_defaultConnectionFactory = getConnectionFactory(_defaultProtocol);
...
SslConnectionFactory ssl = getConnectionFactory(SslConnectionFactory.class);
....
//2. 启动自己的bean
super.doStart();
...
//3. 启动接收请求的线程
for (int i = 0; i < _acceptors.length; i++)
{
Acceptor a = new Acceptor(i);
addBean(a);
getExecutor().execute(a);
}
...
}
复制代码
启动自己的bean
在构造函数执行的时候,bean中添加了SelectorManager,它的实例是一个ServerConnectorManager,执行的构造函数如下
protected SelectorManager(Executor executor, Scheduler scheduler, int selectors)
{
if (selectors <= 0)
selectors = defaultSelectors(executor);
this.executor = executor;
this.scheduler = scheduler;
_selectors = new ManagedSelector[selectors];
_selectorIndexUpdate = index -> (index+1)%_selectors.length;
}
复制代码executor用来处理选中的EndPoint
scheduler处理与时间相关的事件
selectors实际就是包装了Java的Selector
启动过程其实就是去创建约定个数的ManagedSelector,它本身维护了一个Jave的Selector,一个Deque
protected void doStart() throws Exception
{
...
for (int i = 0; i < _selectors.length; i++)
{
ManagedSelector selector = newSelector(i);
_selectors[i] = selector;
addBean(selector);
}
//执行自己的bean启动
super.doStart();
}
//新建的ManagedSelector
public ManagedSelector(SelectorManager selectorManager, int id)
{
_selectorManager = selectorManager;
_id = id;
SelectorProducer producer = new SelectorProducer();
Executor executor = selectorManager.getExecutor();
//producer就是SelectorProducer,后续用到
_strategy = new EatWhatYouKill(producer,executor);
addBean(_strategy,true);
setStopTimeout(5000);
}
复制代码
再次看到ManagedSelector的启动
@Override
protected void doStart() throws Exception
{
//1. EatWhatYouKill本身并没有做特别的doStart实现
super.doStart();
//2.获取一个JavaSelector
_selector = _selectorManager.newSelector();
// The producer used by the strategies will never
// be idle (either produces a task or blocks).
// The normal strategy obtains the produced task, schedules
// a new thread to produce more, runs the task and then exits.
//3.执行EatWhatYouKill的produce方法
_selectorManager.execute(_strategy::produce);
// Set started only if we really are started
//4.往Deque中塞一个Start事件,实质就是运行起来就标志这Selector启动了
Start start = new Start();
submit(start);
start._started.await();
}
复制代码
这里的_selectorManager.execute(_strategy::produce);即去获取对应的连接建立后,处理连接事件
接收请求
Acceptor就是集成了Runnable,它的核心就是调用accept方法,对应就是ServerConnector的实现
@Override
public void accept(int acceptorID) throws IOException
{
ServerSocketChannel serverChannel = _acceptChannel;
if (serverChannel != null && serverChannel.isOpen())
{
SocketChannel channel = serverChannel.accept();//等待连接的到来
accepted(channel);
}
}
复制代码
WebAppContext
对于web项目来说,处理请求的一般使用WebAppContext。
WebAppContext是用来协助其它的handlers的构建和配置,以实现标准的web应用配置。它继承了ServletContextHandler,ServletContextHandler则支持标准的通过web.xml配置的session、security,listeners,filter,servlet和JSP
ServletContextHandler拥有 ServletHandler字段,并继承了ContextHandler
WebAppContext同时也实现了LifeCycle类,它的doStart方法核心
protected void doStart() throws Exception{
...
preConfigure();
super.doStart();
postConfigure();
...
}
复制代码
预加载
public void preConfigure() throws Exception
{
// 加载所有的xml文件
loadConfigurations();
...
for (Configuration configuration : _configurations)
{
//对每个设置的要加载的配置进行处理,比如`WebInfConfiguration`和`WebXmlConfiguration`
LOG.debug("preConfigure {} with {}",this,configuration);
configuration.preConfigure(this);
}
}
复制代码
WebInfConfiguration预加载
public void preConfigure(final WebAppContext context) throws Exception
{
//1. 创建Temp目录
resolveTempDirectory(context);
//2. 进行一些解压缩的工作,比如对war进行解压缩
unpack (context);
//3. 找到容器下面classpath上的jar
findAndFilterContainerPaths(context);
//4. 找到没有在 /WEB-INF/lib下面的jar
findAndFilterWebAppPaths(context);
//No pattern to appy to classes, just add to metadata
context.getMetaData().setWebInfClassesDirs(findClassDirs(context));
}
复制代码
它主要是处理了/WEB-INF 目录的相关工作
WebXmlConfiguration预加载
@Override
public void preConfigure (WebAppContext context) throws Exception
{
//parse webdefault.xml 这里就是获取默认的文件
String defaultsDescriptor = context.getDefaultsDescriptor();
if (defaultsDescriptor != null && defaultsDescriptor.length() > 0)
{
Resource dftResource = Resource.newSystemResource(defaultsDescriptor);
if (dftResource == null)
dftResource = context.newResource(defaultsDescriptor);
context.getMetaData().setDefaults (dftResource);
}
//parse, but don't process web.xml
//查找web.xml文件
Resource webxml = findWebXml(context);
...
}
protected Resource findWebXml(WebAppContext context) throws IOException, MalformedURLException
{
...
//获取web-inf目录
Resource web_inf = context.getWebInf();
if (web_inf != null && web_inf.isDirectory())
{
// do web.xml file
Resource web = web_inf.addPath("web.xml");
...
}
return null;
}复制代码
这里就可以确确实实看到web.xml被加载了
调用父类的doStart
沿着路径网上可以看到,在处理了一些类加载器之后
//ContextHandler
protected void doStart() throws Exception
{
...
startContext();
...
}
复制代码
在WebAppContext中对应实现如下
protected void startContext()
throws Exception
{
//1. 调用对应的配置类的配置方法
configure();
//2. 解析xml
_metadata.resolve(this);
//3. 文件加载结束启动web
startWebapp();
}
复制代码
WebXmlConfiguration.configure
它的配置则是加载了一个标签处理器
public void configure (WebAppContext context) throws Exception
{
...
context.getMetaData().addDescriptorProcessor(new StandardDescriptorProcessor());
}
//StandardDescriptorProcessor构造函数如下
public StandardDescriptorProcessor ()
{
try
{
registerVisitor("context-param", this.getClass().getMethod("visitContextParam", __signature));
registerVisitor("display-name", this.getClass().getMethod("visitDisplayName", __signature));
registerVisitor("servlet", this.getClass().getMethod("visitServlet", __signature));
registerVisitor("servlet-mapping", this.getClass().getMethod("visitServletMapping", __signature));
registerVisitor("session-config", this.getClass().getMethod("visitSessionConfig", __signature));
registerVisitor("mime-mapping", this.getClass().getMethod("visitMimeMapping", __signature));
registerVisitor("welcome-file-list", this.getClass().getMethod("visitWelcomeFileList", __signature));
registerVisitor("locale-encoding-mapping-list", this.getClass().getMethod("visitLocaleEncodingList", __signature));
registerVisitor("error-page", this.getClass().getMethod("visitErrorPage", __signature));
registerVisitor("taglib", this.getClass().getMethod("visitTagLib", __signature));
registerVisitor("jsp-config", this.getClass().getMethod("visitJspConfig", __signature));
registerVisitor("security-constraint", this.getClass().getMethod("visitSecurityConstraint", __signature));
registerVisitor("login-config", this.getClass().getMethod("visitLoginConfig", __signature));
registerVisitor("security-role", this.getClass().getMethod("visitSecurityRole", __signature));
registerVisitor("filter", this.getClass().getMethod("visitFilter", __signature));
registerVisitor("filter-mapping", this.getClass().getMethod("visitFilterMapping", __signature));
registerVisitor("listener", this.getClass().getMethod("visitListener", __signature));
registerVisitor("distributable", this.getClass().getMethod("visitDistributable", __signature));
registerVisitor("deny-uncovered-http-methods", this.getClass().getMethod("visitDenyUncoveredHttpMethods", __signature));
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
复制代码
可以看到这些标签也就是平常写web.xml所用到的
解析web.xml
对应resolve则是获取描述符处理器一个个的去处理对应的处理器,以web.xml的处理器来说就是StandardDescriptorProcessor.process
//StandardDescriptorProcessor父类IterativeDescriptorProcessor中
public void process(WebAppContext context, Descriptor descriptor)
throws Exception
{
...
XmlParser.Node root = descriptor.getRoot();
Iterator> iter = root.iterator();
XmlParser.Node node = null;
while (iter.hasNext())
{
Object o = iter.next();
if (!(o instanceof XmlParser.Node)) continue;
node = (XmlParser.Node) o;
visit(context, descriptor, node);
}
...
}
复制代码
它会一个节点的遍历并调用对应的visit方法,StandardDescriptorProcessor中对一个存在着相应的visit
以为例
public void visitListener(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
//读取配置的listerclass的名字
String className = node.getString("listener-class", false, true);
EventListener listener = null;
try
{
if (className != null && className.length()> 0)
{
//Servlet Spec 3.0 p 74
//存在重复的名字不会构建重复的实例
for (ListenerHolder holder : context.getServletHandler().getListeners())
{
if (holder.getClassName().equals(className))
return;
}
((WebDescriptor)descriptor).addClassName(className);
//创建一个持有Listerner的类
ListenerHolder h = context.getServletHandler().newListenerHolder(new Source (Source.Origin.DESCRIPTOR, descriptor.getResource().toString()));
//设置持有的类名,即web.xml中配置的
h.setClassName(className);
//使得ServletHandler持有listener
context.getServletHandler().addListener(h);
context.getMetaData().setOrigin(className+".listener", descriptor);
}
}
catch (Exception e)
{
LOG.warn("Could not instantiate listener " + className, e);
return;
}
}
复制代码类似的ServletHandler会持有 private ServletHolder[] _servlets=new ServletHolder[0]; private FilterHolder[] _filters=new FilterHolder[0];
启动Web
它会调用父类的startContext
protected void startContext() throws Exception
{
ServletContainerInitializerCaller sciBean = getBean(ServletContainerInitializerCaller.class);
if (sciBean!=null)
//1. 调用实现了接口ServletContainerInitializerCaller的start方法
sciBean.start();
if (_servletHandler != null)
{
//Ensure listener instances are created, added to ContextHandler
if(_servletHandler.getListeners() != null)
{
for (ListenerHolder holder:_servletHandler.getListeners())
{
//获取持有listener的holder,这里实际内部实现就通过反射去创建对应listener的class对象
holder.start();
//we need to pass in the context because the ServletHandler has not
//yet got a reference to the ServletContext (happens in super.startContext)
//对class对象进行初始化
holder.initialize(_scontext);
//将新建的Listener加入contextHandler的_eventListeners,做后续启动用
addEventListener(holder.getListener());
}
}
}
//调用父类的startContext
super.startContext();
// 启动ServletHandler中的filter,servlets,listiners
if (_servletHandler != null)
_servletHandler.initialize();
}
复制代码
父类startContext最关键的在于
protected void startContext() throws Exception
{
...
if (!_servletContextListeners.isEmpty())
{
ServletContextEvent event = new ServletContextEvent(_scontext);
for (ServletContextListener listener : _servletContextListeners)
{
callContextInitialized(listener,event);//调用对应配置的listener的contextInitialized方法
_destroySerletContextListeners.add(listener);
}
}
}
复制代码
最后执行所有相关的servlet,filter的holder启动
public void initialize()
throws Exception
{
MultiException mx = new MultiException();
Stream.concat(Stream.concat(
Arrays.stream(_filters),
Arrays.stream(_servlets).sorted()),
Arrays.stream(_listeners))
.forEach(h->{
try
{
if (!h.isStarted())
{
h.start();
h.initialize();
}
}
catch (Throwable e)
{
LOG.debug(Log.EXCEPTION, e);
mx.add(e);
}
});
mx.ifExceptionThrow();
}
复制代码
以ServletHolder为例,它的实现类似于Listener,先通过反射获取对应的类,然后新建对象,最后调用servlet的init方法
private synchronized void initServlet()
throws ServletException
{
...
if (_servlet==null)
_servlet=newInstance();
...
// Handle configuring servlets that implement org.apache.jasper.servlet.JspServlet
if (isJspServlet())
{
initJspServlet();
detectJspContainer();
}
else if (_forcedPath != null)
detectJspContainer();
initMultiPart();
...
//调用Servlet的init方法
_servlet.init(_config);
...
}
复制代码
后置加载
WebInfConfiguration和WebXmlConfiguration没有实现,什么都不做
附jetty8配合maven使用
pom中配置
org.mortbay.jetty
jetty-maven-plugin
jettyVersion
9999
8888
a
10
/
复制代码
这样可以本地使用命令行 mvn jetty:run 运行jetty服务了