Spring MVC原始碼分析(一)
一、Servlet
Spring MVC中核心Servlet(DispatcherServlet)的繼承結構 圖示(C)表示是類,而圖示(I)表示介面
而上圖的其中HttpServlet、GenericServlet、Servlet、ServletConfig 都在 javax-servlet-api-x.x.x.jar裡面(x.x..x表示版本(如4.0.0))
而其他的類或介面都在 spring-xx.jar裡面
1、Servlet
Servlet3.1的Servlet的介面程式碼如下:
package javax.servlet; import java.io.IOException; public interface Servlet { public void init(ServletConfig config) throws ServletException; public ServletConfig getServletConfig(); public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public String getServletInfo(); public void destroy(); }
init方法在容器啟動時被容器呼叫(當load-on-startup設定為負數或者不設定時會在Servlet第一次用到時才被呼叫),
service方法用於具體處理一個請求;
getServiceInfo方法可以獲取一些Servlet相關的資訊,如作者,版權等,可以自定義實現,預設返回空字串。
destroy方法 銷燬Servlet,只會呼叫一次。
init方法被呼叫時傳入ServletConfig型別引數。
web.xml配置Spring MVC Servlet:
<!-- Spring MVC Servlet --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
init-param標籤配置引數通過ServletConfig來保持的。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/applicationContext.xml
</param-value>
</context-param>
context-param配置的contextConfigLocation配置到了ServletContext中。
在servlet中獲取相對應的值:
String contextLocation = getServletConfig().getServletContext().getInitParameter("contextConfigLocation");
String servletLocation = getServletConfig().getInitParameter("contextConfigLocation");
定義Spring MVC的Servlet時制定檔案位置的contextConfigLocation引數就儲存在ServletConfig中。
Servlet通過檔案xml配置,解使xml時會把配置項設定到StandardWrapper中。
StandardWrapper繼承了ServletConfig介面,程式碼如下:
package javax.servlet;
import java.util.Enumeration;
public interface ServletConfig {
public String getServletName();
public ServletContext getServletContext();
public String getInitParameter(String name);
public Enumeration<String> getInitParameterNames();
}
1、-->StandardWrapper類呼叫initServlet方法
-->GenericServlet類(Servlet)呼叫init方法
-->HttpServletBean類呼叫init()方法
而StandardWrapper類在Tomcat中,StandardWrapper類是Tomcat中的Wrapper介面的實現類,而Wrapper是Container容器的子容器之一。每一個Wrapper封裝著一個Servlet .
也就是Servlet的init方法呼叫具體實現時在StandardServlet類的intiServlet方法中具體實現的。
ServletContext時Tomcat中的Context的門面類ApplicationContextFacade.
Tomcat中的 Container容器裝配結構圖
2、HttpServlet
HttpServlet基於Http協議的Servlet基類,HttpServlet重寫了service方法。具體處理交給了doPost、doPut、doGet和doDelete等方法處理
package javax.servlet.http;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.ResourceBundle;
import javax.servlet.*;
public abstract class HttpServlet extends GenericServlet
{
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE =
"javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings =
ResourceBundle.getBundle(LSTRING_FILE);
public HttpServlet() { }
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
protected long getLastModified(HttpServletRequest req) {
return -1;
}
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
NoBodyResponse response = new NoBodyResponse(resp);
doGet(req, response);
response.setContentLength();
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_put_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
protected void doDelete(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_delete_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
private Method[] getAllDeclaredMethods(Class<? extends HttpServlet> c) {
Class<?> clazz = c;
Method[] allMethods = null;
while (!clazz.equals(HttpServlet.class)) {
Method[] thisMethods = clazz.getDeclaredMethods();
if (allMethods != null && allMethods.length > 0) {
Method[] subClassMethods = allMethods;
allMethods =
new Method[thisMethods.length + subClassMethods.length];
System.arraycopy(thisMethods, 0, allMethods, 0,
thisMethods.length);
System.arraycopy(subClassMethods, 0, allMethods, thisMethods.length,
subClassMethods.length);
} else {
allMethods = thisMethods;
}
clazz = clazz.getSuperclass();
}
return ((allMethods != null) ? allMethods : new Method[0]);
}
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
Method[] methods = getAllDeclaredMethods(this.getClass());
boolean ALLOW_GET = false;
boolean ALLOW_HEAD = false;
boolean ALLOW_POST = false;
boolean ALLOW_PUT = false;
boolean ALLOW_DELETE = false;
boolean ALLOW_TRACE = true;
boolean ALLOW_OPTIONS = true;
for (int i=0; i<methods.length; i++) {
String methodName = methods[i].getName();
if (methodName.equals("doGet")) {
ALLOW_GET = true;
ALLOW_HEAD = true;
} else if (methodName.equals("doPost")) {
ALLOW_POST = true;
} else if (methodName.equals("doPut")) {
ALLOW_PUT = true;
} else if (methodName.equals("doDelete")) {
ALLOW_DELETE = true;
}
}
// we know "allow" is not null as ALLOW_OPTIONS = true
// when this method is invoked
StringBuilder allow = new StringBuilder();
if (ALLOW_GET) {
allow.append(METHOD_GET);
}
if (ALLOW_HEAD) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_HEAD);
}
if (ALLOW_POST) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_POST);
}
if (ALLOW_PUT) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_PUT);
}
if (ALLOW_DELETE) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_DELETE);
}
if (ALLOW_TRACE) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_TRACE);
}
if (ALLOW_OPTIONS) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_OPTIONS);
}
resp.setHeader("Allow", allow.toString());
}
protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
int responseLength;
String CRLF = "\r\n";
StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI())
.append(" ").append(req.getProtocol());
Enumeration<String> reqHeaderEnum = req.getHeaderNames();
while( reqHeaderEnum.hasMoreElements() ) {
String headerName = reqHeaderEnum.nextElement();
buffer.append(CRLF).append(headerName).append(": ")
.append(req.getHeader(headerName));
}
buffer.append(CRLF);
responseLength = buffer.length();
resp.setContentType("message/http");
resp.setContentLength(responseLength);
ServletOutputStream out = resp.getOutputStream();
out.print(buffer.toString());
}
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);
}
}
private void maybeSetLastModified(HttpServletResponse resp,
long lastModified) {
if (resp.containsHeader(HEADER_LASTMOD))
return;
if (lastModified >= 0)
resp.setDateHeader(HEADER_LASTMOD, lastModified);
}
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
if (!(req instanceof HttpServletRequest &&
res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
service(request, response);
}
}
3、GenericServlet
javax.servlet.GenericServlet類:
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
// NOOP by default
}
/**
* Writes the specified message to a servlet log file, prepended by the
* servlet's name. See {@link ServletContext#log(String)}.
*
* @param msg
* a <code>String</code> specifying the message to be written to
* the log file
*/
public void log(String msg) { //寫入日誌
getServletContext().log(getServletName() + ": " + msg);
}
/**
* Writes an explanatory message and a stack trace for a given
* <code>Throwable</code> exception to the servlet log file, prepended by
* the servlet's name. See {@link ServletContext#log(String, Throwable)}.
*
* @param message
* a <code>String</code> that describes the error or exception
* @param t
* the <code>java.lang.Throwable</code> error or exception
*/
public void log(String message, Throwable t) { //寫入異常
getServletContext().log(getServletName() + ": " + message, t);
}
GenericServlet實現了Servlet的init(ServletConfig config)方法,模板方法,init()方法,子類可以通過覆蓋它來完成初始化工作。
二、DispatcherServlet分析
DispatcherServlet
Aware介面的繼承介面:EnvironmentAware和ApplicationContextAware。
實現EnvironmentAware或者ApplicationContextAware介面
--》如 需要使用ApplicationContext,只需要實現ApplicationContextAware介面,
然後實現介面中的方法 void setApplicationContext(ApplicationContext applicationContext)
spring會自動管理呼叫這個set方法 賦值。(那什麼時候會呼叫呢?後續待補充)
EnvironmentCapable介面程式碼如下: 唯一方法getEnvironment()可以提供Environment。
public interface EnvironmentCapable {
/**
* Return the {@link Environment} associated with this component.
*/
Environment getEnvironment();
}
HttpServletBean類:
org.springframework.web.servlet.HttpServletBean
/**
* Return the {@link Environment} associated with this servlet.
* <p>If none specified, a default environment will be initialized via
* {@link #createEnvironment()}.
*/
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
/**
* Create and return a new {@link StandardServletEnvironment}.
* <p>Subclasses may override this in order to configure the environment or
* specialize the environment type returned.
*/
protected ConfigurableEnvironment createEnvironment() {
return new StandardServletEnvironment();
}
HttpServletBean裡面獲取Enviroment使用了StandardServletEnvironment類,
而StandardServletEnvironment類裡面有Servlet config ,JNDI property和Servlet context等
package org.springframework.web.context.support;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySource.StubPropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.jndi.JndiLocatorDelegate;
import org.springframework.jndi.JndiPropertySource;
import org.springframework.lang.Nullable;
import org.springframework.web.context.ConfigurableWebEnvironment;
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
/** Servlet context init parameters property source name: {@value} */
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
/** Servlet config init parameters property source name: {@value} */
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
/** JNDI property source name: {@value} */
public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
/**
* Customize the set of property sources with those contributed by superclasses as
* well as those appropriate for standard servlet-based environments:
*/
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
}
@Override
public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}
}
後續再補充