1、请求调度器dispatcher
当请求从客户端发出时,会被后端的dispatcherServlet拦截到,DispatcherServlet将其拦截到的所有请求转发给ioc容器中注册的请求解析器handlerMapping,解析得到字符串之后,根据解析的字符串,在ioc容器中查找用于处理请求的请求处理器。
@Controller
使用该注解标注的类会成为ioc容器实例化的对象
相当于手动在ioc容器中配置一个bean
然后在控制器中进行计算和处理之后会返回模型数据,我们要将这个模型数据返回给客户端,通过将这些数据嵌入到一个页面中传递给用户,那么我们如何完成这一过程呢?
2、在控制器中常用的几种模型数据对象
SpringMVC提供了如下几种方法供我们使用:
- ModelAndView
- Map和Model
- @SessionAttributes
2.1、ModelAndView
modelAndView里面的set方法,能够给该对象set view(视图)和模型数据,然后return给dispatherServlet,这个调度器会通过视图解析器找到对应的视图,并将相应的模型数据填充到其中,至此整个过程结束。
简单的代码如下:
ModelAndView的构造方法
public ModelAndView() {
}
public ModelAndView(String viewName) {
this.view = viewName;
}
public ModelAndView(View view) {
this.view = view;
}
public ModelAndView(String viewName, Map<String, ?> model) {
this.view = viewName;
if (model != null) {
getModelMap().addAllAttributes(model);
}
}
public ModelAndView(View view, Map<String, ?> model) {
this.view = view;
if (model != null) {
getModelMap().addAllAttributes(model);
}
}
public ModelAndView(String viewName, String modelName, Object modelObject) {
this.view = viewName;
addObject(modelName, modelObject);
}
public ModelAndView(View view, String modelName, Object modelObject) {
this.view = view;
addObject(modelName, modelObject);
}
给ModelAndView设置模型数据和视图
public void setViewName(String viewName) {
this.view = viewName;
}
public void setView(View view) {
this.view = view;
}
public ModelAndView addObject(String attributeName, Object attributeValue) {
getModelMap().addAttribute(attributeName, attributeValue);
return this;
}
public ModelAndView addAllObjects(Map<String, ?> modelMap) {
getModelMap().addAllAttributes(modelMap);
return this;
}
使用实例
@RequestMapping(value="/renderView", method={RequestMethod.GET, RequestMethod.POST})
public ModelAndView renderView() {
User user = new User();
user.setName("lmy");
user.setAge(20);
ModelAndView view = new ModelAndView();
view.addObject("name", "lmy");
view.addObject("user", user);
view.setViewName("show");
return view;
}
前台页面
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
user name: ${user.name}<br>
user age: ${user.age}
</body>
</html>
2.2、Map和Model
在我们的请求处理器中可以加上Map或者Model类型的参数,这些类型主要是为了存储模型数据,它和ModelAndView的一个区别就是这些类型是没有存储视图信息,它是通过该请求处理器的返回值来返回一个视图名,其实在后台会将这些Map或者Model中的模型数据和返回的视图名再次封装成一个ModelAndView。
@RequestMapping(value="/renderView", method={RequestMethod.GET, RequestMethod.POST})
public String renderView1(Map<String, Object> map) {
User user = new User();
user.setName("lmy86263");
user.setAge(24);
map.put("user", user);
return "show";
}
2.3、@SessionAttributes
光看这个名字其实就很明显了,这些模型数据是存放在HttpSession中的,说明这里面的数据可以被多个请求所共享。这个注解不能使用在方法上,而是使用在控制器类上。另外它有两种使用方式:
- 通过属性名指定需要放到会话中的属性;
- 通过模型属性的对象类型指定哪些模型属性要放到Session中;
@SessionAttributes(names={"names"}, types={User.class})
@Controller
public class ViewController {
@RequestMapping(value="/renderView1", method={RequestMethod.GET, RequestMethod.POST})
public String renderView1(Map<String, Object> map) {
User user = new User();
user.setName("lmy86263");
user.setAge(24);
map.put("user", user);
map.put("names", Arrays.asList("tom", "jack", "lmy"));
return "show";
}
}
前台页面
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
sessionScope: user name: ${sessionScope.user.name}<br>
sessionScope: user age: ${sessionScope.user.age} <br>
sessionScope: names: ${sessionScope.names} <br>
</body>
</html>
3、视图解析器viewResolver
Spring Boot 自动配置好了SpringMVC
以下是SpringBoot对SpringMVC的默认配置:WebMvcAutoConfiguration)
-
Inclusion of
ContentNegotiatingViewResolver
andBeanNameViewResolver
beans.- 自动配置了ViewResolver(视图解析器作用:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?))
- ContentNegotiatingViewResolver:组合所有的视图解析器的;
- 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来;
-
Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径,webjars
-
Static
index.html
support. 静态首页访问 -
Custom
Favicon
support (see below). favicon.ico
在ContentNegotiatingViewResolver类中有initServletContext的初始化方法,该方法代码如下
protected void initServletContext(ServletContext servletContext) {
Collection<ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), ViewResolver.class).values();
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.size());
for (ViewResolver viewResolver : matchingBeans) {
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
}
else {
for (int i = 0; i < viewResolvers.size(); i++) {
if (matchingBeans.contains(viewResolvers.get(i))) {
continue;
}
String name = viewResolvers.get(i).getClass().getName() + i;
getApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolvers.get(i), name);
}
}
if (this.viewResolvers.isEmpty()) {
logger.warn("Did not find any ViewResolvers to delegate to; please configure them using the " +
"'viewResolvers' property on the ContentNegotiatingViewResolver");
}
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.cnmFactoryBean.setServletContext(servletContext);
}
分析以上代码不难发现,springboot会自动把自定制的viewResolver加入到容器。
ContentNegotiatingViewResolver类中还有resolveViewName解析视图名字的方法
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
if (this.useNotAcceptableStatusCode) {
if (logger.isDebugEnabled()) {
logger.debug("No acceptable view found; returning 406 (Not Acceptable) status code");
}
return NOT_ACCEPTABLE_VIEW;
}
else {
logger.debug("No acceptable view found; returning null");
return null;
}
}
通过该方法解析得到bestView然后返回。
3、总结
学习springboot自动配置springmvc的时候想起了springmvc处理请求流程,所以查阅如上。还是得打好底层基础,这样处理错误,阅读源码大有裨益。如有理解不当之处,望指正。