SpringMVC实现原理解析
Spring MVC基于MVC设计模式设计,其实现基于Spring IOC容器和Servlet。
Spring MVC的启动
Spring MVC通常运⾏在Web容器(如Tomcat)中,其启动由Web容器触发。
以下是⼀个常规的Web应⽤部署描述⽂件l:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="/2001/XMLSchema-instance"
xmlns="java.sun/xml/ns/javaee"
xmlns:web="java.sun/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="java.sun/xml/ns/javaee java.sun/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID"version="3.0">
<display-name></display-name>
<!-- 配置Spring MVC DispatcherServlet -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 初始化参数 -->
<init-param>
<!-- 加载SpringMVC的xml到 spring的上下⽂容器中 -->
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:l
</param-value>
</init-param>
</servlet>
<!-- 配置DispatcherServlet所需要拦截的 url -->
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 监听spring上下⽂容器 -->
<listener>
<listener-class>org.t.ContextLoaderListener</listener-class>
</listener>
<!-- 加载spring的xml配置⽂件到 spring的上下⽂容器中 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext-*.xml</param-value>
</context-param>
主要分为两部分:
1. DispatcherServlet的配置
2. ContextLoaderListener的配置
这两部分的配置为Spring MVC启动的⼊⼝,也是Web容器与Spring IOC/MVC相耦合的点(通过ServletContext耦合)。其中DispatcherServlet是⼀个Servlet,具备Servlet的⽣命周期,ContextLoaderListener实现了ServletContextListener接⼝,该接⼝提供了关于ServletContext⽣命周
期的回调⽅法。在Web容器启动时,将调⽤Servlet⽣命周期的init⽅法,同时其作为宿主环境的上下⽂ServletContext将触发事件信息使得ServletContextListener调⽤contextInitialized⽅法。
⾸先来看ContextLoaderListner:
@Override
public void contextInitialized(ServletContextEvent event) {
//启动了Spring IOC容器,其配置⽂件位置在l中已经设定
//通常这个容器中的Bean主要是web开发中的Service层和DAO层相关的类
ServletContext());
}
再看DispatcherServlet,在Servlet启动时将调⽤init⽅法。其继承结构如下图:
init()⽅法在HttpServletBean中定义,该⽅法调⽤了在FrameworkServlet中定义的initServletBean()⽅法,部分代码如下:
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
try {
//启动另外⼀个IOC容器,该IOC容器配置⽂件在l中设定过
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
spring ioc注解catch (ServletException ex) {
("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
("Context initialization failed", ex);
throw ex;
}
}
该IOC容器中包含的通常是web开发中的Controller层相关的Bean,启动过程中将会将ContextLoaderListener中启动的容器设置为⽗容器,形成IOC容器体系,在容器体系中获取Bean时,先在⼦容器中查,再去⽗容器中查。如Controller层中Service的注⼊,即需要去⽗容器中查。
⾄此,Spring MVC启动了两个IOC容器,其中ContextLoaderListener启动的为⽗容器(通常负责Service层和DAO层的相关Bean管理),⽽DispatcherServlet启动的为⼦容器(通常负责Controller层
的相关Bean管理),IOC容器体系建⽴完毕,同时两个IOC容器通过ServletContext与Web容器(Tomcat)相耦合。
HttpServletBean的init⽅法最终将调⽤DispatcherServlet的initStrategies⽅法,该⽅法主要⽤来初始化Spring MVC的主要⽀持部件:
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
重点分析HandlerMapping/HandlerAdapters/ViewResolvers这三者。
initStrategies中的主要⼯作就是设置DispatcherServlet中相关属性的值,对于handlerMapping/handlerAdapters/ViewResolvers这三者来说,都是先在IOC容器中查是否已经有配置的各类实例,如果没有则启⽤默认类。默认值在DispatcherServlet.properties⽂件中有定义。⾄此,Spring MVC已经启动完毕。
可见,SpringMVC由tomcat以l⾥⼀个Servlet⼀个Listener的配置触发启动,然后以这两个建⽴IOC容器体系,最终进⾏组件的初始化⼯作,启动完成。
Spring MVC重要组件
HandlerMapping
HandlerMapping接⼝只有⼀个⽅法,getHandler,但是返回的是HandlerExecutionChain,⼀个执⾏链。其主要作⽤是将Http请求的URL映射到对应的handler上,返回的执⾏链中同时包含了handler本⾝和对应的链。handler的类型是多样的,在源码中handler类型为Object,可以是实现了Controller接⼝的对象,也可以是某个⽅法。因此,也有多种实现类,有直接以类作为handler的,有以⽅法作为handler的。
在web开发中在⽅法上常见的RequestMapping注解handleMethod类型的handler,对应的handlerMapping为RequestMappingHandlerMapping。继承AbstractHandlerMethodMapping,持有请求URL与HandlerMethod之间的映射表。
HandlerAdapter
由于handler的多样性,需要为框架提供更好的可拓展性,使⽤了适配器模式,通过handlerAdapter来调⽤handler的handle⽅法。在handlerMapping中获取到HandlerExecutionChain后,从中取出handler本⾝,遍历已经DispatcherServlet中初始化过的handlerAdapter到可以适配的HandlerAdapter。
以RequestMappingHandlerMapping为例,对应的Adapter为RequestMappingHandlerAdapter,该Adapter知道handler类型为HandlerMethod,最终通过反射调⽤完成请求的处理,返回ModelAndView结果。
ViewResolver/View
视图解析器,ViewResolver接⼝只有⼀个⽅法,即resolveViewName,将⼀个视图名解析为⼀个View对象。View对象只是视图在Spring MVC中的表⽰,并⾮实际意义上的视图。由于视图的多样化,⼀类视图解析图解析⼀类视图对象。以JSP视图为例,其ViewResolver为InternalResourceViewResolver,通常都会如下配置:
<bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
InternalResourceViewResolver持有View对象的缓存,如果缓存中不存在,则根据viewname新建
View,InternalResourceViewResolver处理的视图对象类为InternalResourceView。
@Override
protected View createView(String viewName, Locale locale) throws Exception {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
if (!canHandle(viewName, locale)) {
return null;
}
// 处理视图名称中的重定向前缀
// Check for special "redirect:" prefix.
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
return applyLifecycleMethods(viewName, view);
}
// 处理视图名称中的转发前缀
// Check for special "forward:" prefix.
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
ateView(viewName, locale);
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
//通过反射新建了View对象,并设置其URL等属性
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
view.setUrl(getPrefix() + viewName + getSuffix());
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
if (exposeContextBeansAsAttributes != null) {
view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
}
String[] exposedContextBeanNames = getExposedContextBeanNames();
if (exposedContextBeanNames != null) {
view.setExposedContextBeanNames(exposedContextBeanNames);
}
return view;
}
视图解析完成之后,就是最后的视图渲染⼯作了,渲染⼯作由View对象来完成。
InternalResourceView的render⽅法实现在其基类AbstractView中定义:
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { //整合Model对象
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
//设置response响应头
prepareResponse(request, response);
//最终渲染
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
最终渲染部分代码:
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 将各种Model值都设置到Http请求的请求属性中去,这⾥的Model已经
//是经过应⽤处理过的值,将其巧妙地加⼊Request的属性中继续传递给视图
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// 通过View对象的URL即可获取到真正的视图路径
String dispatcherPath = prepareForRendering(request, response);
// 这⾥获取RequestDispatcher,即JSP对应的Servlet对象,并调⽤service⽅法响应请求
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(request, response);
}
}
View对象并不是真正的视图对象,只是视图对象在SpringMVC中的表⽰,持有了真正视图对象的路径,渲染时通过RequestDispatcher 将Model数据传给真正的视图。
Spring MVC对Http请求的处理
DispatcherServlet是spring mvc的核⼼Servlet,承担了请求转发处理的⼯作。它的本质是Servlet,其可以处理的请求在l中定义,处理⽅法即service⽅法,该⽅法在⽗类HttpServlet中实现,并提供了钩⼦给⼦类。在DispatcherServlet中,主要处理逻辑在doDispatch⽅法中,部分代码如下:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论