Servlet
對於一個請求來講,如果只是需要一個靜態頁面,可以直接在伺服器上根據路徑訪問得到,但是如果請求的資料是一個動態頁面,即只有在執行時從後臺資料庫獲取,再拼裝東西返回,然後生成一個對應的html檔案。在Java中為了實現這個功能,使用的就是Servlet規範。
Servlet:server component,執行在伺服器上的java程式碼
Servlet容器
Servlet並不處理任何的協議和連線等等動作,它只是約定了一個種處理request-response的模式。每個功能實現Servlet介面的類都可以用來處理請求,比如加法用1個servlet,減法用一個Servlet,這樣一但多起來,就需要知道,那些請求用那個Servlet處理,對應的配置產生物也就是web.xml,另外Servlet物件的構建、連線埠的請求,處理好對應的對映關係等等都需要有一個程式來負責,這個程式稱作Servlet容器,比如Jetty,從Jetty的總體架構也就可以看出來它很好的實踐了這些
Connector負責連線,Handler則處理對應的請求,交給Servlet來處理
Servlet的生命週期
Servlet的生命週期是由釋出它的容器控制的,比如Jetty,當要把請求對映到一個Servlet上時,容器一般會做如下的事情:
- 如果Servlet不存在,就載入Servlet類,建立Servlet例項,然後呼叫Servlet的init方法
- 執行Servlet的service方法,傳遞request和response物件
- 如果容器要移除掉servlet,它就會呼叫Servlet的destroy方法
javax.servlet
和javax.servlet.http
提供了要實現Servlet的所有介面和相關類,每一個處理Servlet的類必須實現 Servlet.java
介面,它的基本實現為GenericServlet,是與協議無關的一個實現,如果要實現自己的Servlet介面,可以繼承它,僅需要實現對應的Service方法。對於處理HTTP請求則可以使用HttpServlet更方便,它根據Http請求的型別,對service方法進行了細分,這樣可以更好的去處理想要處理的請求
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專案分析可得到,網路請求分成兩部分
- 等待連線建立
- 處理連線請求
等待連線建立
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方法來處理請求