流程说明:
spring mvc本质上还是在使用Servlet处理,并在其基础上进行了封装简化了开发流程,提高易用性、并使用程序逻辑结构变得更清晰
1. 基于注解的URL映射
2. 表单参数映射
3. 缓存处理
4. 全局统一异常处理
5. 拦截器的实现
6. 下载处理
为便于理解,这里给出一个最简单,配置最少的spring mvc 示例:
web.xml servlet配置:
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring/spring-*.xmlparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcherservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
编写Control 方法:
public class MvcController implements Controller {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView();
mv.setViewName("hello");
mv.addObject("name", "mvc");
return mv;
}
}
配置spring-mvc.xml
整个过程是如何实现的?
在面试中要回答好上述问题,就必须得弄清楚spring mvc 的体系组成。
从技术角度去思考 任何一个现存的框架都有其存在理由,而这个理由就是解决实际的问题。或者提供更好的解决问题的方案。spring mvc 它解决了什么问题呢?
上术解决在spring mvc 中都体现在如下组件当中
其对应具体uml如下 图:
mvc 各组件执行流程
其为mvc 中url路径与Control对像的映射,DispatcherServlet 就是基于此组件来寻找对应的Control,如果找不到就会报 No mapping found for HTTP request with URI的异常。
HandlerMapping 接口结构分析:
HandlerMapping 作用是通过url找到对应的Handler ,但其HandlerMapping.getHandler()方法并不会直接返回Handler 对象,而是返回 HandlerExecutionChain 对象在通过 HandlerExecutionChain.getHandler() 返回最终的handler
常用实现类:
目前主流的三种mapping 如下:
SimpleUrlHandlerMapping
演示基于 SimpleUrlHandlerMapping配置映射。
编写mvc 文件
<bean id="urlMappingController" class="com.naixue.web.UrlMappingController"/>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/urlMapping">urlMappingControllerprop>
props>
property>
bean>
SimpleUrlHandlerMapping体系结构:
初始化SimpleUrlHandlerMapping流程关键源码:
org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#setUrlMap
org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#initApplicationContext
org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#registerHandlers
// /表示根路径 /* 表示默认路径
org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#registerHandler()
获取 Handler流程关键源码:
org.springframework.web.servlet.DispatcherServlet#doService
org.springframework.web.servlet.DispatcherServlet#doDispatch
org.springframework.web.servlet.DispatcherServlet#getHandler
org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal
// 获取URL路径
org.springframework.web.util.UrlPathHelper#getPathWithinApplication
// 查找handler
org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#lookupHandler
// 封装执行链
org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerExecutionChain
BeanNameUrlHandlerMapping
BeanNameUrlHandlerMapping 实现上与 SimpleUrlHandlerMapping 一至,唯一区别在于 继承自AbstractDetectingUrlHandlerMapping ,通过对应detectHandlers 可以在无配置的情况下发现url 与handler 映射。
结构图:
RequestMappingHandlerMapping
其基于注解实现,在后续章节讲解注解映射的时候在详细讲。
Handler 类型
在 AbstractUrlHandlerMapping 我们可以看到存储handler 的Map 值类型是Object ,是否意味着所有的类都可以做来Handler 来使用?
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
ApplicationContext applicationContext = obtainApplicationContext();
if (applicationContext.isSingleton(handlerName)) {
//可以看出实际是put了一个bean为handler
resolvedHandler = applicationContext.getBean(handlerName);
}
}
......
this.handlerMap.put(urlPath, resolvedHandler);
...
}
}
Handler 对应类型如下如图:
可以看出 Handler 没有统一的接口,当dispatchServlet获取当对应的Handler之后如何调用呢?调用其哪个方法?这里有两种解决办法,**一是用instanceof 判断Handler 类型然后调用相关方法 。二是通过引入适配器实现,每个适配器实现对指定Handler的调用。**spring 采用后者。
spring mvc 采用适配器模式来适配调用指定Handler,根据Handler的不同种类采用不同的Adapter,其Handler与 HandlerAdapter 对应关系如下:
Handler类别 | 对应适配器 | 描述 |
---|---|---|
Controller | SimpleControllerHandlerAdapter | 标准控制器,返回ModelAndView |
HttpRequestHandler | HttpRequestHandlerAdapter | 业务自行处理 请求,不需要通过modelAndView 转到视图 |
Servlet | SimpleServletHandlerAdapter | 基于标准的servlet 处理 |
HandlerMethod | RequestMappingHandlerAdapter | 基于@requestMapping对应方法处理 |
HandlerAdapter 接口方法
HandlerAdapter 接口结构图
<bean class="org.springframework.web.servlet.handler.SimpleServletHandlerAdapter"/>
<bean id="/servlet" class="com.naixue.web.servlet.OneServlet"/>
public class OneServlet extends HttpServlet {
private static final long serialVersionUID = 46587649263984732L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("this is a servlet!");
}
}
上述例子中当IOC 中实例化这些类之后 DispatcherServlet 就会通过
org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter() 方法查找对应handler的适配器 ,如果找不到就会报 如下异常 。
javax.servlet.ServletException: No adapter for handler [com.tuling.control.SimpleControl@3c06b5d5]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler
org.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(DispatcherServlet.java:1198)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)
找到应的Adapter 之后就会基于适配器调用业务处理,处理完之后业务方会返回一个ModelAndView ,在去查找对应的视图进行处理。其在org.springframework.web.servlet.DispatcherServlet#resolveViewName() 中遍历 viewResolvers 列表查找,如果找不到就会报一个 Could not resolve view with name 异常。
BeanNameViewREsolver示例:
添加自定义视图:
public class MyView implements View {
@Override
public String getContentType() {
return "text/html; charset=utf-8";
}
@Override
public void render(Map<String, ?> model, HttpServletRequest
request, HttpServletResponse response) throws Exception {
Integer num = Integer.valueOf((String) model.get("num"));
response.setHeader("Content-Type","text/html; charset=utf-8");
response.getWriter().print("10进制num:"+num+"\n转\n16进制为:"+Integer.toHexString(num));
}
}
视图解析器:
public class NXViewResolver implements ViewResolver, Ordered {
private Integer order;
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
//根据视图名返回视图对象
if (viewName.startsWith("nx:")) {
return new MyView();
}
return null;
}
//实现Orderd接口,可以修改视图解析器的优先级
@Override
public int getOrder() {
return this.order;
}
//改变视图解析器的优先级
public void setOrder(Integer i) {
this.order = i;
}
}
配置视图解析器:
<bean name= "myView" class="com.naixue.web.view.MyView"/>
<bean class="com.naixue.web.view.NXViewResolver">
<property name="order" value="1">property>
bean>
修改视图跳转方法 :
public class ViewController implements Controller {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
String num = request.getParameter("num");
if (!StringUtils.isNumeric(num)) {
throw new HttpServerErrorException(HttpStatus.BAD_REQUEST,"请输入num");
}
ModelAndView mv = new ModelAndView();
mv.setViewName("nx:view");
mv.addObject("num", num);
return mv;
}
}
配置Controller的bean
<bean name="/view" class="com.naixue.web.ViewController"/>