jetty啓動web項目源碼分析

jetty是作什麼的?

jetty是HTTP服務,HTTP客戶端,和javax.servlet的容器。它自己被設計成嵌入式模式,應該將jetty集成到本身的應用,jetty自己能夠實例化,能像任何POJO同樣使用,用jetty就至關於把Http服務塞進了本身的應用html

jetty的口號「Don't deploy your application in Jetty, deploy Jetty in your application.」java

啓動jetty java -jar start.jarnode

運行jetty java -jar start.jar等效於 java -jar start.jar etc/jetty.xml[默認的jetty配置文件]git

啓動jetty若須要的更多參數,能夠統一經過 start.ini 文件來配置github

#===========================================================
# Jetty start.ini example
#-----------------------------------------------------------
OPTIONS=Server
etc/jetty.xml
etc/jetty-http.xml
複製代碼

官網啓動Jetty
OPTIONS:指定構建過程當中這個目錄下面的全部jar都須要添加
etc/jetty.xml:它會添加到啓動start.jar命令的後頭web

在start.ini中同時能夠指定JVM的參數,只是必須添加 --execapache

#===========================================================
# 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,會加上這些參數來運行bash

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 官網架構

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

相關文章
相關標籤/搜索