jetty啟動web專案原始碼分析

爬蜥發表於2018-07-17

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中

Jetty Main原始碼

以包含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配置檔案,主要包括:

  1. 伺服器的類和全域性選項
  2. 連線池(最大最小執行緒數)
  3. 聯結器(埠,超時時間,緩衝區,協議等)
  4. 處理器(handler structure,可用預設的處理器或者上下文處理蒐集器contextHandlerCollections)
  5. 釋出管理器(用來掃描要釋出的webapp和上下文)
  6. 登入服務(做許可權檢查)
  7. 請求日誌

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

jetty啟動web專案原始碼分析

Jetty 官網架構

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

jetty啟動web專案原始碼分析

<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服務了

相關文章