文章目录
- 【README】
- 【1】HanderMapping-处理器映射容器
- 【1.1】HanderMapping实现类
- 【1.1.1】SimpleUrlHandlerMapping
- 【2】Controller(二级控制器)
- 【2.1】AbstractController抽象控制器(控制器基类)
- 【3】ModelAndView(模型与视图)
- 【3.1】ModelAndView中的视图信息
- 【3.2】ModelAndView中的模型数据
- 【4】ViewResolver视图解析器
- 【4.1】可用的ViewResolver实现类
- 【4.1.1】单一视图类型的视图解析器(以UrlBasedViewResolver为基类)
- 【4.1.2】多视图类型的视图解析器
- 【5】View视图
- 【5.1】View实现原理
- 【5.2】View实现类(基类AbstractView)
- 【5.3】AbstractView的子类AbstractUrlBasedView
- 【5.3.1】使用jsp技术的view
- 【5.3.2】使用通用模版技术的view
- 【5.3.3】面向二进制文档格式的View
- 【5.3.4】重定向视图RedirectView
- 【5.3.5】使用XSLT技术的View
- 【5.4】自定义View实现
【README】
本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;
1)springmvc有5个主要组件:
- HandlerMapping: 封装请求标识与二级控制器映射关系;
- Controller:二级控制器;
- ModelAndView:封装模型数据与视图;
- ViewResovler:视图解析器;
- View:视图;
2)DispatcherServlet使用上述5个组件处理web请求的流程(把它们串起来) :
- DispatcherServlet一级控制器根据请求标识(如URL)从HandlerMapping查找对应二级控制器Controller,并把请求转发给该Controller;
- Controller处理完成后返回ModelAndView(模型数据+视图)给DispatcherServlet一级控制器;
- DispatcherServlet一级控制器根据ModelAndView对象,通过视图解析器ViewResolver获取视图实例View;
- DispatcherServlet一级控制器调用view.render()方法做实际的视图渲染;
【1】HanderMapping-处理器映射容器
1)HandlerMapping: 用于封装请求标识(如URL)与二级控制器Controller(处理器)间的映射关系; 它实际上是一个接口;
【HandlerMapping】
public interface HandlerMapping {
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
/** @deprecated */
@Deprecated
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
default boolean usesPathPatterns() {
return false;
}
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
【1.1】HanderMapping实现类
1)常用HanderMapping实现类列表:
- BeanNameUrlHandlerMapping :bean名称与url处理器映射;即一级控制器DispatcherServlet查找BeanName与请求url路径相同的二级控制器, 并把请求转发给该二级控制器处理;
- SimpleUrlHandlerMapping: 简单URL处理器映射; 相比BeanNameUrlHandlerMapping ,SimpleUrlHandlerMapping能够提供更多灵活匹配模式;
【1.1.1】SimpleUrlHandlerMapping
1)为什么有了BeanNameUrlHandlerMapping ,还需要 SimpleUrlHandlerMapping ?
2)SimpleUrlHandlerMapping作用
- 可以配置web请求到具体二级控制器的映射;
- 可以把一组或多组拥有相似特征的web请求映射给二级控制器;
<!-- SimpleUrlHandlerMapping: 可以配置web请求到具体二级控制器的映射, 可以把一组或多组拥有相似特征的web请求映射给二级控制器-->
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<!-- 设置SimpleUrlHandlerMapping优先级为1,优先匹配,若有多个HandlerMapping时 -->
<property name="order" value="1" />
<property name="mappings">
<value>
/userController.do=userController
/bankCard*.do=bankCardController
/bankCard/*.do=bankCardController
</value>
</property>
</bean>
3)一级控制器DispatcherServlet可以有多个HandlerMapping, 请求匹配时,哪个HandlerMapping优先匹配,哪个最后匹配呢?
- HandlerMapping的优先级规定遵循spring框架内一贯的Ordered接口所规定的语义;
- HandlerMapping接口的实现类都实现了Ordered接口,在配置HandlerMapping时,只需要指定其 order属性即可;
【2】Controller(二级控制器)
1)Controller:是springmvc框架支持的用于处理具体web请求的处理器类型之一;
【Controller】二级控制器
@FunctionalInterface
public interface Controller {
@Nullable
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
2)当然,我们可以自行实现Controller接口,包括请求参数的抽取,请求编码的设定,session管理,国际化信息处理,异常处理等;
- 但为了简化开发成本,这些Controller细节,springmvc已经有现成实现,我们复用即可;
3)总结起来:springmvc提供了一套封装Controller细节的实现类,如下。
4)Controller可以分为两类: 基于servletapi的没有封装细节的控制器, 封装处理细节的规范操作控制器;
- 基于servletapi的自行处理细节的控制器,包括 AbstractController,MultiActionController :
- 控制器处理细节逻辑包括:从HttpServletRequest获取参数, 然后参数验证,调用业务层逻辑,最终返回一个 ModelAndView ;特别的,还可以自行通过 HttpServletResponse 输出最终的视图;
- 封装处理细节的规范操作控制器,BaseCommandController及子类; 封装的细节如下:
- 自动抽取请求参数并绑定到 Command对象;
- 提供统一的数据验证方式; BaseCommandController及子类可以接受 Validator进行数据验证,我们可以提供具体Validator实现;
- 规范化表单请求的处理流程, 并且对简单的多页面表单请求处理提供支持;
显然: 使用规范操作控制器,可以复用springmvc封装好的处理细节或者自行实现部分细节,开发成本低;
【注意】本文使用的spring版本是6.1.10,本文发现6.1.10中有AbstractController,但没有MultiActionController, BaseCommandController及其子类,所以Controller二级控制器章节仅做了解 ;
【2.1】AbstractController抽象控制器(控制器基类)
1)AbstractController是所有控制器基类,该类通过模版方法模式定义了控制器处理细节的主要步骤:
- 管理当前Controller所支持的请求方法类型(GET/POST)
- 管理页面的缓存设置, 即是否允许浏览器缓存当前页面;
- 管理执行流程在会话上的同步;
我们需要做的是: 重写AbstractController#handleRequestInternal()模版方法,实现具体业务逻辑处理;
【AbstractController】
public abstract class AbstractController extends WebContentGenerator implements Controller {
private boolean synchronizeOnSession;
public AbstractController() {
this(true);
}
public AbstractController(boolean restrictDefaultSupportedMethods) {
super(restrictDefaultSupportedMethods);
this.synchronizeOnSession = false;
}
public final void setSynchronizeOnSession(boolean synchronizeOnSession) {
this.synchronizeOnSession = synchronizeOnSession;
}
public final boolean isSynchronizeOnSession() {
return this.synchronizeOnSession;
}
@Nullable
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
response.setHeader("Allow", this.getAllowHeader());
return null;
} else {
this.checkRequest(request);
this.prepareResponse(response);
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized(mutex) {
return this.handleRequestInternal(request, response);
}
}
}
return this.handleRequestInternal(request, response); // 处理请求内部逻辑
}
}
@Nullable
protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
【UserController】
public class UserController extends AbstractController {
private UserAppService userAppService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("handleRequestInternal 被访问了");
ModelAndView modelAndView = new ModelAndView("userListPage");
modelAndView.addObject("userList", userAppService.listUser());
return modelAndView;
}
public void setUserAppService(UserAppService userAppService) {
this.userAppService = userAppService;
}
}
【3】ModelAndView(模型与视图)
1)ModelAndView: 包含2部分信息,包括视图内容(逻辑视图名称或具体视图实例),模型数据;
2)ModelAndView实际上是一个数据对象:通过该对象,可以使得web请求处理逻辑与视图渲染解耦开;
【3.1】ModelAndView中的视图信息
1)ModelAndView可以以逻辑视图名的形式或者View视图实例的形式来保存视图信息;
【DispatcherServlet#render()】
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();
response.setLocale(locale);
String viewName = mv.getViewName();
View view;
if (viewName != null) { // 若ModelAndView中逻辑视图名存在,则通过逻辑视图名获取视图实例
view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
String var10002 = mv.getViewName();
throw new ServletException("Could not resolve view with name '" + var10002 + "' in servlet with name '" + this.getServletName() + "'");
}
} else { // 若逻辑视图名【不】存在,则直接通过 ModelAndView 获取视图实例
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");
}
}
// ...
view.render(mv.getModelInternal(), request, response); // 获取视图实例后渲染
//...
}
2)由上述代码可知:
- 若ModelAndView中逻辑视图名存在,则通过逻辑视图名获取视图实例
- 若逻辑视图名【不】存在,则直接通过 ModelAndView 获取视图实例
【3.2】ModelAndView中的模型数据
1)ModelAndView通过 ModelMap来保存模型数据, 通过构造方法传入或实例方法添加的模型数据添加到这个ModelMap中;
2)ModelAndView封装模型数据的代码示例;
public class UserController extends AbstractController {
private UserAppService userAppService;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("handleRequestInternal 被访问了");
ModelAndView modelAndView = new ModelAndView("userListPage");
modelAndView.addObject("userList", userAppService.listUser());
return modelAndView;
}
public void setUserAppService(UserAppService userAppService) {
this.userAppService = userAppService;
}
}
【ModelAndView】
public ModelAndView addObject(String attributeName, @Nullable Object attributeValue) {
this.getModelMap().addAttribute(attributeName, attributeValue);
return this;
}
//
public ModelMap getModelMap() {
if (this.model == null) {
this.model = new ModelMap();
}
return this.model;
}
// ModelMap 定义
public class ModelMap extends LinkedHashMap<String, Object> {
// ...
}
【4】ViewResolver视图解析器
1)视图解析器:通过ModelAndView中的逻辑视图名,为一级控制器DispatcherServlet返回一个视图实例;
【DispatcherServlet#resolveViewName()】
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
Iterator var5 = this.viewResolvers.iterator();
while(var5.hasNext()) {
ViewResolver viewResolver = (ViewResolver)var5.next();
View view = viewResolver.resolveViewName(viewName, locale); // 视图解析器通过视图逻辑名获取视图实例
if (view != null) {
return view;
}
}
}
return null;
}
【BeanNameViewResolver】视图解析器具体实现
public View resolveViewName(String viewName, Locale locale) throws BeansException {
ApplicationContext context = this.obtainApplicationContext();
if (!context.containsBean(viewName)) {
return null;
} else if (!context.isTypeMatch(viewName, View.class)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Found bean named '" + viewName + "' but it does not implement View");
}
return null;
} else {
return (View)context.getBean(viewName, View.class); // 实际上是从spring容器中根据视图名(或beanName)获取View实例对象
}
}
2)视图解析器接口 ViewResolver, 其常用实现类是BeanNameViewResolver,或者AbstractCachingViewResolver子类;
针对每次请求都重新实例化View可能影响性能,所以AbstractCachingViewResolver是对View做了缓存功能的视图解析器; 默认启用缓存功能;可以通过setCache(boolean)重新设置缓存开关;
【AbstractCachingViewResolver】
public void setCache(boolean cache) {
this.cacheLimit = cache ? 1024 : 0;
}
【4.1】可用的ViewResolver实现类
1)springmvc提供的ViewResolver实现类分为2类:
- 支持单一视图类型的视图解析器;
- 支持多种视图类型的视图解析器;
【4.1.1】单一视图类型的视图解析器(以UrlBasedViewResolver为基类)
1)单一视图解析器:顾名思义,仅支持一种视图类型的视图解析; 基类是UrlBasedViewResolver;
2)UrlBasedViewResolver子类如下:
- InternalResourceViewResolver :对应 InternalResourceView视图类型的解析,即处理jsp模型的视图;(默认视图解析器)
- FreeMarkerViewResolver :对应 FreeMarker视图模版的解析;
- XsltViewResolver:根据逻辑视图名查找并返回XsltView类型的View实例;
3)单一视图类型的视图解析器使用:
- 使用prefix属性指定模版所在路径;
- 使用suffix属性指定模版文件的后缀名;
- 对应的视图解析器就可以根据 [prefix] + viewName(逻辑视图名) + [suffix] 拼接成的URL找到对应模版文件,并构造对应view实例返回;
【4.1.2】多视图类型的视图解析器
1)多视图类型的视图解析器:可以对多种视图类型的视图进行解析;
2)多视图类型的视图解析器有3个:ResourceBundleViewResolver, XmlViewResolver, BeanNameViewResolver ;
3)在dispatcher-servlet.xml可以注册多个视图解析器;(dispatcher-servlet.xml是一级控制器DispatcherServlet加载依赖组件时读取的xml配置文件,在web.xml中配置) ;
【dispatcher-servlet.xml】
<!-- 注册视图解析器bean到springweb容器(一级控制器DispatcherServlet的web容器WebApplicationContext) -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 注册BeanNameViewResolver视图解析器到springweb容器(一级控制器DispatcherServlet的web容器WebApplicationContext) -->
<bean id="beanNameViewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="1" />
</bean>
4)与可以配置多个HandlerMapping类似,dispatcher-servlet.xml也可以配置多个视图解析器;
- 通过指定order属性,设置视图解析器优先级;
- 若没有配置视图解析器或DispatcherServlet没有在当前的WebApplicationContext中找到任何ViewResolver定义,则默认使用 InternalResourceViewResolver 作为视图解析器; (建议把InternalResourceViewResolver 优先级设置最低,作为兜底)
【5】View视图
1)View视图:是springmvc中把视图渲染逻辑从DispatcherServlet解耦出来的关键组件; 通过实现View接口, 我们可以支持多种视图渲染技术;
2)视图渲染: 通过view.render()方法实现;
- DispatcherServlet一级控制器根据请求标识(如URL)从HandlerMapping查找对应二级控制器Controller,并把请求转发给该Controller;
- Controller处理完成后返回ModelAndView给DispatcherServlet一级控制器;
- DispatcherServlet一级控制器根据ModelAndView对象,通过视图解析器ViewResolver获取视图实例View;
- DispatcherServlet一级控制器调用view.render()方法做实际的视图渲染;
【DispatcherServlet】
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();
response.setLocale(locale);
String viewName = mv.getViewName();
View view;
if (viewName != null) { // 逻辑视图名不为空
view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request); // 解析该逻辑视图名得到视图实例
if (view == null) {
String var10002 = mv.getViewName();
throw new ServletException("Could not resolve view with name '" + var10002 + "' in servlet with name '" + this.getServletName() + "'");
}
} else {
view = mv.getView(); // 否则直接从 ModelAndView 中获取视图实例
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" +