SpringMVC源码解析(上)

前言:

    做过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源码深度解析(郝佳)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

恐龙弟旺仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值