前言:
做过web开发的小伙伴都应该使用过SpringMVC(或者Struts1/Struts2)等框架来处理前端请求,并响应对应的页面;
也有些特别老的项目,直接使用Servlet来处理web请求。
当然,无论使用哪种技术来处理Web请求,底层都是Servlet
Servlet应用发布在Web容器(如Tomcat)中,Web容器对请求参数进行封装,对请求路径进行解析,然后将请求转发到对应的Servlet中,调用Servlet的doGet()或doPost()方法来进行业务处理,处理完成后,返回对应的页面。整个请求结束。
Servlet与Web容器的关系,请参考:https://www.ibm.com/developerworks/cn/java/j-lo-servlet/
1.Servlet的简单使用
1)创建一个Web应用,名为servletDemo,笔者使用JDK版本为1.8.0.133,Tomcat版本为8.5
2)创建Servlet
@WebServlet("/HelloForm")
public class HelloForm extends HttpServlet {
private static final long serialVersionUID = 1L;
public HelloForm() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 直接跳转到WEB-INF/res.html页面
request.getRequestDispatcher("/res.html").forward(request, response);
}
// 处理 POST 方法请求的方法
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
3)在WebContent下创建res.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>res</title>
</head>
<body>
this is res html
</body>
</html>
4)在web.xml中添加servlet映射
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>HelloForm</servlet-name>
<servlet-class>servletdemo.HelloForm</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloForm</servlet-name>
<url-pattern>/TomcatTest/HelloForm</url-pattern>
</servlet-mapping>
</web-app>
5)测试
笔者tomcat端口号设置为8081,将servletDemo项目加入到Tomcat中,启动Tomcat,访问路径
http://localhost:8081/servletdemo/TomcatTest/HelloForm
则可以看到
说明请求成功。
总结:以上展示了最简单的Servlet使用方式,接收到用户请求后,直接跳转到HTML页面,最关键的代码处理即
request.getRequestDispatcher("/res.html").forward(request, response);
获得RequestDispatcher然后调用其forward()方法即可。
读者要记得这句处理,后面关于SpringMVC源码分析的时候,实际最核心的处理也就是在这里。
2.SpringMVC简单使用
1)创建maven-web应用springweb
2)在src/main/webapp/WEB-INF/web.xml添加以下内容
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<display-name>springweb</display-name>
<servlet>
<servlet-name>springweb</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springweb-servlet.xml</param-value>
</init-param>
<!-- <load-on-startup>1</load-on-startup> -->
</servlet>
<servlet-mapping>
<servlet-name>springweb</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
主要是配置servlet和servlet-mapping,拦截所有的请求(/)统一交由DispatcherServlet来处理,并配置参数contextConfigLocation,指明xml路径
3)在与web.xml同目录下 创建springweb-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置注解驱动,替代推荐使用的映射器以及适配器,json转换器 -->
<mvc:annotation-driven />
<!-- 开启注解扫描 -->
<context:component-scan base-package="test.SpringMVC"></context:component-scan>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
主要是配置视图解析器
4)创建Controller
package test.SpringMVC;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/mvc")
public class MvcController {
@RequestMapping("/hello")
public String hello(){
return "hello";
}
}
5)在WEB-INF/jsp下创建hello.jsp
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>
6)启动springweb项目并测试
访问 http://localhost:8081/springweb/mvc/hello ,可以看到Hello World!出现,测试成功
总结:可以看到SpringMVC的配置使用与Servlet差不多,只是SpringMVC将所有的请求都交由DispatcherServlet来处理
以下进行源码分析的时候,我们重点分析这个DispatcherServlet类就可以了
3.DispatcherServlet源码结构分析
先看下DispatcherServlet的类结构分析
可知,DispatcherServlet实现了HttpServlet,那也就有对应的init()、doGet()等方法,init()用来初始化资源,doGet()用来处理请求
1)DispatcherServlet.init()初始化资源(默认实现在HttpServletBean类中)
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
// 重点在这里,子类负责实现,主要来初始化ApplicationContext及一些Resolve
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
2)DispatcherServlet.doGet()
一路跟踪到doDispatch()方法,这里是真正的业务实现方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 1.获取handler(主要包含request相对应的Controller处理类和拦截器列表)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 2.获取HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 3.根据获取到的HandlerAdapter,调用其handler方法,
// handler方法会先执行拦截器的方法,然后执行Controller的对应方法获取对应的ModelAndView,也就是对应的视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
...
// 4.跳转到指定页面(跳转到指定的视图)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
...
}
总结:
由以上分析可知,初始化资源的任务放在init()方法中;
处理请求时,主要步骤可分为:
* 获取请求对应的HandlerExecutionChain(主要包含request相对应的Controller处理类和拦截器列表)
* 获取HandlerAdapter
* HandlerAdapter.handler()方法会调用拦截器的方法和Controller的方法处理,最终返回一个ModelAndView(主要就是视图路径名称)
* 跳转到指定视图界面
对框架结构的简单分析之后,会发现与最原始的Servlet的使用差不多,只不过包装的过程更复杂点而已。
下文笔者会对请求过程进行详细分析,请关注 SpringMVC源码解析(下)。
参考:Spring源码深度解析(郝佳)