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<Object> 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<String, String> 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,這些所有的配置都會用到同一個服務上
配置檔案一般樣式
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
//<configure> 根元素,指定以下配置是給那個類,一般在jetty.xml中server,或者jetty-web.xml中的WebAppContext
<Configure id="foo" class="com.acme.Foo">
//<set> setter方法呼叫的標識。name屬性用來標識setter的方法名,如果這個方法沒有找到,就把name中值當做欄位來使用。如果有屬性class表明這個set方法是靜態方法
<Set name="name">demo</Set>
<Set name="nested">
//<new>初始化物件,class決定new物件的型別,需要寫全路徑類名,沒有用<arg>則呼叫預設的建構函式
<New id="bar" class="com.acme.Bar
//<arg> 作為建構函式或者一個方法的引數,用於<call>和<new>
<Arg>true</Arg>
<Set name="wibble">10</Set>
<Set name="wobble">xyz</Set>
<Set name="parent"><Ref id="foo"/></Set>
//<call>呼叫物件的某個方法,name屬性表明確確呼叫的方法的名字
<Call name="init">
<Arg>false</Arg>
</Call>
</New>
</Set>
//<ref>引用之前已經生成物件的id
<Ref id="bar">
<Set name="wibble">20</Set>
//<get>呼叫當前物件的get方法,同set
<Get name="parent">
<Set name="name">demo2</Set>
</Get>
</Ref>
</Configure>
複製程式碼
它相當於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<Runnable> 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
一般取值如下
<Array id="plusConfig" type="java.lang.String">
<Item>org.eclipse.jetty.webapp.WebInfConfiguration</Item>
<Item>org.eclipse.jetty.webapp.WebXmlConfiguration</Item>
<Item>org.eclipse.jetty.webapp.MetaInfConfiguration</Item>
<Item>org.eclipse.jetty.webapp.FragmentConfiguration</Item>
<Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
<Item>org.eclipse.jetty.plus.webapp.PlusConfiguration</Item>
<Item>org.eclipse.jetty.annotations.AnnotationConfiguration</Item>
<Item>org.eclipse.jetty.webapp.JettyWebXmlConfiguration</Item>
<Item>org.eclipse.jetty.webapp.TagLibConfiguration</Item>
</Array>
複製程式碼
比如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
以<listener>
為例
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中配置
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>jettyVersion</version>
<configuration>
<connectors>
<!--可選。配置了jetty的監聽介面,沒有特別指定,預設使用org.eclipse.jetty.server.nio.SelectChannelConnector ,埠是8080 -->
<connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>9999</port>
</connector>
</connectors>
<stopPort>8888</stopPort>
<stopKey>a</stopKey>
<!--可選。監控web專案是否改變的時間設定【有改變就熱啟動,單位是秒】,預設是0,禁止掃描,任何大於0的數字都是啟用【掃描的地方包括 pom.xml WEB-INF/lib WEB-INF/classes WEB-INF/web.xml】 -->
<scanIntervalSeconds>10</scanIntervalSeconds>
<webApp>
<contextPath>/</contextPath>
</webApp>
</configuration>
</plugin>
複製程式碼
這樣可以本地使用命令列 mvn jetty:run
執行jetty服務了