jetty、servlet以及spring的銜接源碼分析

Servlet

對於一個請求來說,若是隻是須要一個靜態頁面,能夠直接在服務器上根據路徑訪問獲得,可是若是請求的數據是一個動態頁面,即只有在運行時從後臺數據庫獲取,再拼裝東西返回,而後生成一個對應的html文件。在Java中爲了實現這個功能,使用的就是Servlet規範。html

Servlet:server component,運行在服務器上的java代碼java

Servlet容器

Servlet並不處理任何的協議和鏈接等等動做,它只是約定了一個種處理request-response的模式。每一個功能實現Servlet接口的類均可以用來處理請求,好比加法用1個servlet,減法用一個Servlet,這樣一但多起來,就須要知道,那些請求用那個Servlet處理,對應的配置產生物也就是web.xml,另外Servlet對象的構建、鏈接端口的請求,處理好對應的映射關係等等都須要有一個程序來負責,這個程序稱做Servlet容器,好比Jetty,從Jetty的整體架構也就能夠看出來它很好的實踐了這些web

Connector負責鏈接,Handler則處理對應的請求,交給Servlet來處理spring

Servlet的生命週期

Servlet的生命週期是由發佈它的容器控制的,好比Jetty,當要把請求映射到一個Servlet上時,容器通常會作以下的事情:數據庫

  1. 若是Servlet不存在,就加載Servlet類,建立Servlet實例,而後調用Servlet的init方法
  2. 執行Servlet的service方法,傳遞request和response對象
  3. 若是容器要移除掉servlet,它就會調用Servlet的destroy方法

javax.servletjavax.servlet.http提供了要實現Servlet的全部接口和相關類,每個處理Servlet的類必須實現 Servlet.java接口,它的基本實現爲GenericServlet,是與協議無關的一個實現,若是要實現本身的Servlet接口,能夠繼承它,僅須要實現對應的Service方法。對於處理HTTP請求則可使用HttpServlet更方便,它根據Http請求的類型,對service方法進行了細分,這樣能夠更好的去處理想要處理的請求bash

protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } } 複製代碼

每次請求總會攜帶一些東西,返回也是,Servlet針對請求抽象了ServletRequest接口和ServletResponse接口,而特定於Http協議,則分別設計了HttpServletRequest接口和HttpServletResponse,每次請求Servlet容器負責實現對應的類提交給service方法服務器

web.xml 配置

web.xml常見配置以下網絡

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application-context.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:web-mvc-dispatcher.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

複製代碼

Jetty啓動web項目中的分析可知,會依次的去執行ContextLoaderListener的contextInitialized和DispatcherServlet的init方法,這裏就是jetty容器、servlet和spring的銜接架構

Jetty處理網絡請求

Jetty啓動web項目分析可獲得,網絡請求分紅兩部分mvc

  1. 等待鏈接創建
  2. 處理鏈接請求

等待鏈接創建

Jetty中的ServerConnector接收到請求以後調用accepted

private void accepted(SocketChannel channel) throws IOException
{
    channel.configureBlocking(false); //將新的鏈接設置成非阻塞的
    Socket socket = channel.socket();
    configure(socket);
    _manager.accept(channel);
}
複製代碼

_manager接收到請求以後,獲取一個Selector,進而提交一個Accept,放到Deque隊列中,Accept實現了SelectorUpdate

public void accept(SelectableChannel channel, Object attachment)
{
    final ManagedSelector selector = chooseSelector();
    selector.submit(selector.new Accept(channel, attachment));
}
複製代碼

在從Deque中取值時,會執行它的 update方法

public void update(Selector selector)
    {
        ...
        //執行註冊
        key = channel.register(selector, 0, attachment);
        //執行run方法
        execute(this);
       ...
    }
     
    public void run()
    {
        ...
        createEndPoint(channel, key);
        _selectorManager.onAccepted(channel);
        ...
    }
複製代碼

建立的createEndPoint過程以下

private void createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException
    {
        //注意_selectorManager自己在構造的時候是一個ServerConnectorManager
        //好比這裏是一個SocketChannelEndPoint
        EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey);
        //建立鏈接,好比默認的Http了鏈接
        Connection connection = _selectorManager.newConnection(channel, endPoint, selectionKey.attachment());
        endPoint.setConnection(connection);
        //創建鏈接的key是一個SocketChannelEndPoint
        selectionKey.attach(endPoint);
        endPoint.onOpen();
        _selectorManager.endPointOpened(endPoint);
        //實際做用是調用了httpConnection的onOpen方法
        _selectorManager.connectionOpened(connection);
        if (LOG.isDebugEnabled())
            LOG.debug("Created {}", endPoint);
    }
複製代碼

ServerConnectorManager建立的newEndPoint爲SocketChannelEndPoint

protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
    {
        SocketChannelEndPoint endpoint = new SocketChannelEndPoint(channel, selectSet, key, getScheduler());
        endpoint.setIdleTimeout(getIdleTimeout());
        return endpoint;
    }

複製代碼

建立的鏈接則是根據鏈接工廠創建的,而默認的則是調用的HttpConnectionFactory

public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment) throws IOException
{
    return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint);
}
複製代碼

建立一個HttpConnection對象

public HttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint, HttpCompliance compliance, boolean recordComplianceViolations)
    {
        //1. 父類構建了一個_readCallback回調,當成功的時候就會去執行子類的onFillable方法
        super(endPoint, connector.getExecutor());
        _config = config;
        _connector = connector;
        _bufferPool = _connector.getByteBufferPool();
        //用來構造HTTP的信息
        _generator = newHttpGenerator();
        //裏頭構建了一個Request對象,它實現了HttpServletRequest,同時構建了Response對象,實現了HttpServletRequest
        _channel = newHttpChannel();
        _input = _channel.getRequest().getHttpInput();
        _parser = newHttpParser(compliance);
        _recordHttpComplianceViolations = recordComplianceViolations;
        if (LOG.isDebugEnabled())
            LOG.debug("New HTTP Connection {}", this);
    }
複製代碼

能夠看到Request和Response是和channel綁定的,同一個TCP鏈接用的就是同一個Reqeust和Response,他們會循環的用

鏈接建立完成,調用open方法,它實際在將回調的_readCallback寫入SocketChannelEndPoint的_fillInterest中

處理鏈接請求

Jetty中的EatWhatYouKill的produce方法,即用來處理請求,它核心是隻要獲取task並運行它

Runnable task = produceTask();
複製代碼

這裏的produceTask實際就是初始化的時候傳入的SelectorProducer的方法

public Runnable produce()
    {
        while (true)
        {
            //若是_cursor中有值,也就是有鏈接過來了進行處理
            Runnable task = processSelected();
            if (task != null)
                return task;
            //遍歷Deque,好比鏈接創建的時候會放入一個selector.new Accept,並執行update方法,具體分析見面下的Accept鏈接創建
            processUpdates();

            updateKeys();
            //執行Selector對應的select()方法,將放回的keys放入_cursor中存儲
            if (!select())
                return null;
        }
    }
複製代碼

當有請求過來的時候,也就是執行processSelected方法

//SelectorProducer
   private Runnable processSelected()
    {
    ...
        if (attachment instanceof Selectable)
        {
            // 當鏈接創建後會附上一個SocketChannelEndPoint,它實現了Selectable
            Runnable task = ((Selectable)attachment).onSelected();
            if (task != null)
                return task;
        }
    
    ...
    }
複製代碼

對應的onSelect實如今ChannelEndPoint中

//ChannelEndPoint
 public Runnable onSelected()
    {
        int readyOps = _key.readyOps();
        ...
        //可讀
        boolean fillable = (readyOps & SelectionKey.OP_READ) != 0;
        //可寫
        boolean flushable = (readyOps & SelectionKey.OP_WRITE) != 0;
        ...
        //根據是可讀仍是可寫返回對應的任務
        Runnable task= fillable 
                ? (flushable 
                        ? _runCompleteWriteFillable 
                        : _runFillable)
                : (flushable 
                        ? _runCompleteWrite 
                        : null);
        ...
        return task;
    }
複製代碼

到這裏能夠看到,EatWhatYouKill執行的實際上就是可讀或者可寫的一個channel任務。 以可讀的爲例,_runFillable的實現就是從getFillInterest獲取的或吊執行它的fillable方法

public void run()
    {
        getFillInterest().fillable();
    }
複製代碼

這對應到創建的HttpConnection,執行它的fillable方法,即調用這個鏈接的HttpChannel來處理

public void onFillable()
    {
            ....
            boolean suspended = !_channel.handle();
            ....
    }
複製代碼

而處理的詳情關鍵

public boolean handle()
{
 ...
  getServer().handle(this);
  ...
 }
複製代碼

這裏就對應處理到Server的handle實現

public void handle(HttpChannel channel) throws IOException, ServletException
    {
        final String target=channel.getRequest().getPathInfo();
        //獲取channel上的Request
        final Request request=channel.getRequest();
        //獲取channel上的Response
        final Response response=channel.getResponse();
        ...
        if (HttpMethod.OPTIONS.is(request.getMethod()) || "*".equals(target))
        {
            if (!HttpMethod.OPTIONS.is(request.getMethod()))
                response.sendError(HttpStatus.BAD_REQUEST_400);
            handleOptions(request,response);
            if (!request.isHandled())
                handle(target, request, request, response);
        }
        else
            handle(target, request, request, response);
       ...
    }

複製代碼

對於web項目會有ServletHanlder,對應實現爲

@Override
    public void doHandle(String target, Request baseRequest,HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException
    {
        ....
        if (chain!=null)
            chain.doFilter(req, res);
        else
            servlet_holder.handle(baseRequest,req,res);
        ...
    }
複製代碼

這裏對應找到ServletHolder的handler方法

public void handle(Request baseRequest,
                       ServletRequest request,
                       ServletResponse response)
        throws ServletException,
               UnavailableException,
               IOException
    {
        //獲取Servlet
        Servlet servlet = getServlet();
            if (baseRequest.isAsyncSupported() && !isAsyncSupported())
            {
                try
                {
                    baseRequest.setAsyncSupported(false,this.toString());
                    servlet.service(request,response);
                }
                finally
                {
                    baseRequest.setAsyncSupported(true,null);
                }
            }
            else
                //執行Servlet的service方法,並傳入request和response
                servlet.service(request,response);
    }
複製代碼

源碼參考

至此對於Spring來講已經連上了DispatcherServlet父類FrameWorkService的service方法,而後轉置DispatcherServlet的doService方法。

總結

Jetty自己去鏈接了客戶端,自身去實現了Servlet的規範,在每一個創建的channel上,本身實現了請求request和response,經由handler,對獲取的web.xml配置中的servlet,關聯上Spring的對應servlet的init和service方法來處理請求

相關文章
相關標籤/搜索