springmvc是基于Model2实现的技术框架。
springmvc的整个流程大致如下:
- 客户端发起一个请求,web应用服务器接收这个请求,如果匹配DispatcherServlet的请求映射路径,则将请求转交给DispatcherServlet处理。
- DispatcherServlet接收到这个请求后,将根据请求信息及HandlerMapping的配置找到处理请求的处理器Handler。
- 得到Handler后,通过HandlerAdapter对Handler进行封装,再以统一的适配器接口调用Handler。
- 处理器完成业务逻辑的处理后返回一个ModelAndView给DispatcherServlet。
- DispatcherServlet借由ViewResolver完成逻辑视图名到真实试图对象的解析工作。
- 得到真实的逻辑视图对象之后,DispatcherServlet使用这个View对象对ModelAndView中的模型数据进行视图渲染。
- 最终客户端得到的响应消息可能是一个普通的HTML页面,也可能是一个XML,JSON串或者其他的媒体形式。
注意点
- Web层spring容器作为业务层spring容器的子容器,web层容器可以访问业务层容器的bean,但反过来不可以。
- 一个web.xml可以配置多个DispatcherServlet,通过
<servlet-mapping>
配置,让每个DispatcherServlet处理不同的请求。
DispatcherServlet的属性
- namespace:命名空间。默认为
<servlet-name>-servlet
,用于构造spring配置文件的路径。显示指定该属性后,对应的路径为WEB-INF/namespace.xml。 - contextConfigLocation:配置文件的路径。可以指定多个。
- publishContext:默认为true.表示将WebApplicationContext发布到ServletContext的属性列表中。以便调用者可以通过ServletContext找到WebApplicationContext实例。对用 的属性名为DispatcherServlet#getServletContextAttributeName()方法的返回值。
- publishEvents:布尔型的属性。当DispatcherServlet处理完一个请求后,是否需要向容器发布一个ServletRequestHandlerEvent事件。默认为true.
servlet3.0环境编程方式配置
WebApplicationInitializer是一个接口,只有一个方法:
void onStartup(ServletContext servletContext) throws ServletException;
可以实现它来配置:
public class MyApplicationInitializer implements WebApplicationInitializer{
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
ServletRegistration.Dynamic registration =
servletContext.addServlet("dispatcher", new DispatcherServlet());
registration.setLoadOnStartup(1);
registration.addMapping("*.hmtl");
}
}
原理:在Servlet3.0环境下,容器会在类路径下查找实现了javax.servelt.ServeltContainerInitializer接口的类。如果能发现就用它来配置Servlet容器。
Spring提供了这个接口的实现,即SpringServletContainerInitializer.而这个类又会去查找实现WebApplicationInitializer的类并将配置的任务交给他们来完成。 Spring3.2引入了一个WebApplicationInitializer的基础实现。也就是AbstractAnnotationConfigDispatcherServletInitializer。
可以继承它来配置:
public class SpittAppWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
DispatcherServlet的initStrategies方法
spring如何将spring MVC组件装配到DispatcherServlet中呢?看以下方法:
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
这个方法将在WebApplicationContext 初始化后自动执行,此时Spring上下文的Bean已经初始化完毕。该方法的工作原理是:通过反射机制查找并装配Spring容器中用户显示定义的组件Bean,如果找不到就装配默认的组件实例。
在org.springframework.web.servlet类路径下,有个DispatcherServlet.properties文件。那里定义了默认的组件。有些组件最多只允许存在一个实例。有些则允许存在多个。这些组件都实现了Ordered接口,通过order属性确定优先级顺序。
HttpMessageConverter
HttpMessageConverter是一个消息转换器接口。他有如下方法:
boolean canRead(Class<?> clazz, MediaType mediaType);
boolean canWrite(Class<?> clazz, MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
spring有很多HttpMessageConverter实现类。RequestMappingHandlerAdapter默认装配了:
- StringHttpMessageConverter
- ByteArrayHttpMessageConverter
- SourceHttpMessageConverter
- AllEncompassingFormHttpMessageConverter
如果要装配其他类型的HttpMessageConverter,可在spring的web容器上下文中定义一个RequestMappingHandlerAdapter。
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"
p:messageConverters-ref="messageConverters">
</bean>
<util:list id="messageConverters">
<bean
class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
<bean
class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean
class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"/>
<bean
class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"
p:marshaller-ref="xmlMarshaller" p:unmarshaller-ref="xmlMarshaller">
</bean>
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</util:list>
<bean id="xmlMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="streamDriver">
<bean class="com.thoughtworks.xstream.io.xml.StaxDriver"/>
</property>
<property name="annotatedClasses">
<list>
<value>com.smart.domain.User</value>
</list>
</property>
</bean>
如何使用HttpMessageConverter将请求信息转换并绑定到处理方法的入参中?spring mvc提供了两种方法:
- 使用@ResponseBody,@RequestBody对处理方法进行标注
- 使用
HttpEntity<T>,ResponseEntity<T>
作为处理方法的入参或返回值。
@ModelAttribute
他可以标注在方法入参。表示把入参对象添加到模型中,即request.setAttribute方法。也可以放在方法定义之上。表示:springmvc在调用目标处理方法之前,会逐个调用在方法级上标注了@ModelAttribute注解的方法,并将这些方法的返回值添加到模型中。再调用目标处理方法。
springmvc在调用方法前会创建一个隐含的模型对象,作为模型数据的储存容器。如果处理方法的入参为Map或Model对象,则会将隐含模型的引用传递给这些入参。
@SessionAttributes
如果希望在多个请求之间共用某个模型属性数据,可以在控制器中标注一个@SessionAttributes,springmvc会将对应的属性暂存在HttpSession中。
springmvc处理@SessionAttributes和@ModelAttribute的流程:
1. springmvc在调用处理方法前,在请求线程中自动创建一个隐含的模型对象。
2. 调用所有标注了@ModelAttribute的方法,并将方法返回值添加到隐含模型中。
3. 查看Session中是否存在@SessionAttributes(“xx”)所指定的xx属性,如果有,则将其添加到隐含模型中。
4. 对标注了@ModelAttribute处理方法的入参按如下流程处理:
4.1:如果隐含模型中拥有名为xx的属性,则将其赋给入参。再用请求消息填充该入参对象直接返回。否则转到4.2.
4.2:如果xx是会话属性,则尝试从会话中获取该属性,并将其赋给入参,如果会话中找不到对应的属性,则抛出异常。否则转到4.3
4.3:如果隐含模型不存在xx属性,且xx也不是会话属性,则创建入参实例,然后再填充。
所以对@SessionAttributes要小心。
数据绑定
springmvc将servletRequest对象及处理方法的入参对象实例传递给DataBinder,DataBinder首先调用ConversionService组件进行数据类型转换,数据格式化等工作。将servletRequest中的消息填充到入参对象中,然后调用validation组件对入参对象进行数据合法性校验。最终生成数据绑定结果BindingResult对象。BindingResult包含已经完成数据绑定的入参对象和相应的校验错误对象。