Spring的模型-视图-控制器(MVC)框架是围绕一个DispatcherServlet来设计的,这个Servlet会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染等,甚至还能支持文件上传。
- 1、HTTP 请求:客户端请求提交到 DispatcherServlet。
- 2、HandlerMapping 寻找路由器:由 DispatcherServlet 查询一个或多个 HandlerMapping,找到处理请求的 Controller。
- 3、Controller 调用处理器:DispatcherServlet 将请求提交到 Controller。
- 4-5、Service & ModelAndView 调用业务处理并返回模型:Controller 调用业务逻辑 Service 处理后,返回 ModelAndView。
- 6-7、ViewResolver & Model 处理视图映射并返回模型:DispatcherServlet 查询一个或多个 ViewResolver 视图解析器,找到 ModelAndView 指定的视图。
- 8、Http 响应:视图负责将结果显示到客户端。
一、主要注解
@Controller:
- 用于标注控制层组件
- 用于标记在一个类上,SpringMVC Controller 对象
- HandlerMapping 分发处理器会扫描使用了该注解的类方法,并检测是否使用了 @RequestMapping 注解。
- 可以把 Request 请求 header 部分的值绑定到方法的参数上。
- @RestController:
等于 @Controller 和 @ResponseBody 的组合效果
- @Component
泛指组件,当组件不好归类时,可以是用这个注解进行标注。
- @Repository
用于注解 DAO 层,在 daoImpl 类上注解。
- @Service
用于标注业务层组件
@ResponseBody
- 异步请求
- 用于将 Controller 的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区。
- 返回的数据不是 HTML 标签的页面,而是其他某种格式的数据时(JSON、XML)使用
- @RequestMapping
一个处理请求地址映射的注解,可用于类或方法上。类上:表示类中的所有响应请求的方法都是以该地址作为父路径。
- @Autowired
它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
通过 @Autowired 的使用来消除 set、get 方法
- @PathVariable
用于将请求 URL 中的模板变量映射到功能处理方法的参数上,即取出 uri 模板中的变量作为参数
- @RequestParam
主要用于在 SpringMVC 控制层获取参数,类似 request.getParameter("name")
- @RequestHeader
可以把 Request 请求 header 部分的值绑定到方法的参数上。
二、ContextLoaderListener
1、首先了解 web.xml 的作用
- 一个 web 工程中可以没有 web.xml。
web.xml 文件是用来初始化配置信息
- Welcome 页面
- servlet
- servlet-mapping
- filter
- listener
- 启动加载级别等。
当要启动web项目,服务器软件或容器(tomcat)会第一步加载项目中的web.xml文件,通过其中的各种配置类启动项目,只有其中配置的各项均无误时,项目才能正确启动。
- 记载过程的顺序依次为:context-param >> listener >> filter >> servlet(同类多个节点以出现顺序加载)
【web.xml 加载过程】
1、启动 web 项目的时候,容器首先会去找它的配置文件 web.xml 读取两个节点:<listener></listener> 和 <context-param></context-param>
2、接着,容器创建一个 ServletContext(application),这个 web 项目所有部分都将共享这个上下文。
3、容器以 <context-param></context-param> 的 name 作为键,value 作为值,将其转化为键值对,存入 ServletContext。
4、容器创建 <listener></listener> 中的类实例,根据配置的 class 类路径 <listener-class> 来创建监听,在监听中会有 contextInitialzed(ServletContextEvent args) 初始化方法,启动 web 应用时,系统调用 Listener 的该方法,在这个方法获得:* ServletContext application = ServletContextEvent.getServletContext(); * context-param 的值 = application.getInitParameter("context-param 的值"); * 得到这个 context-param 的值之后,就可以做一些操作了。
举例:在项目启动之前就打开database:
* 在 <context-param> 中设置 database 的连接方式(驱动、url、user、password) * 在监听类中初始化database 的连接 * 这个监听是自己写的一个类,除了初始化方法,还有销毁方法,用于关闭应用前释放资源,如 database连接的关闭 * 此时,调用 contextDestroyed(ServletContextEvent args),关闭 web 应用时,系统调用listener 的该方法。
5、容器读取 <filter></filter>,根据指定的类路径类实例化过滤器。
2、SpringMVC 的启动过程分为两个过程
- ContextLoaderListener 初始化,实例化 IoC 容器,并将此容器实例注册到 ServletContext。
- DispatcherServlet 初始化
【web.xml 配置】
contextConfigLocation
* 指定 Spring IoC 容器需要读取的 XML 文件路径 * 默认会去 /WEB-INF/ 下加载 applicationContext.xml
contextLoaderListener
* Spring 监听器 * Spring MVC 在 web 容器中的启动类,读取 applicationContext.xml,负责 Spring IoC 容器在 web 上下文中的初始化
DispatcherServlet
* 前端处理器,接收 HTTP 请求和转发请求的类。
CharacterEncodingFilter
* 字符集过滤器
IntrospectorCleanupListener
* 防止 Spring 内存溢出监听器
其中 ContextLoadListener 监听器实现了 ServletContextListener 接口,在 web.xml 配置这个监听器,启动容器时,就会默认执行它实现的方法。在 ContextLoaderListener 中关联了 ContextLoader 类,所以整个加载配置过程由 ContextLoader 来完成。
- ContextLoaderListener 在 web.xml 中的配置
<!-- 配置 contextConfigLocation 初始化参数 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationServlet.xml</param-value>
</context-param>
<!-- 配置 ContextLoadListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
ServletContextListener 接口有两个方法:contextInitialized、contextDestoryed
三、DispatcherServlet
- DispatcherServlet 在 web.xml 中的配置
<!-- servlet 定义 -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
其中:
- load-on-startup:表示启动容器时初始化该 servlet
- url-pattern:表示哪些请求交给 Spring Web MVC 处理,"/" 是用来定义默认 servlet 映射的。也可以如 "*.html" 表示拦截所有以 .html 为扩展名的请求。
在 Spring MVC 中,每个 DispatcherServlet 都持有一个自己的上下文对象 WebApplicationContext,它又继承了根(root)WebApplicationContext 对象中已经定义的所有 bean。这些继承的 bean 可以在具体的 Servlet 实例中被重载,在每个 Servlet 实例中也可以定义其 scope 下的新 bean。
WebApplicationContext 继承自 ApplicationContext,它提供了一些 web 应用经常需要用到的特性,与普通的 ApplicationContext 不同之处在于,它支持主题的解析,并且知道它关联到的是哪个 servlet(它持有一个该 ServletContext 的引用)
【DispatcherServlet 的继承结构】
spring mvc同时提供了很多特殊的注解,用于处理请求和渲染视图等。DispatcherServlet初始化的过程中会默认使用这些特殊bean进行配置。如果你想指定使用哪个特定的bean,你可以在web应用上下文WebApplicationContext中简单地配置它们。
【特殊 bean】
HandlerMapping
* 处理映射,会根据某些规则将进入容器的请求映射到具体的处理器以及一系列前处理器和后处理器(即处理器拦截)上,具体的规则视 HandlerMapping 类的实现不同而有所不同。其最常用的一个实现支持在控制器上添加注解,配置请求路径。也存在其他实现。
HandlerAdapter
* 处理器适配器,拿到请求所对应的处理器后,适配器将负责去调用该处理器,使得 DispatcherServlet 无需关心具体的调用细节。比如,要调用的是一个基于注解配置的控制器,那么调用前还学要从许多注解中解析出一些相应的信息,因此,HandlerAdapter 的主要任务就是对 DispatcherServlet 屏蔽这些具体的细节。
HandlerExceptionResolver
* 处理器异常解析器,负责将捕获的异常映射到不同的视图上去,此外还支持更复杂的异常处理代码。
ViewResolver
* 视图解析器,负责将一个代表逻辑视图的字符串(String)映射到实际的视图类型 View 上。
LocaleResolver & LocaleContextResolver
* 地区解析器和地区上下文解析器。负责解析客户端所在的地区信息甚至时区信息,为国际化的视图定制提供支持。
ThemeResolver
* 主题解析器,负责解析 web 应用中可用的主题,如,提供一些个性化定制的布局等。
MultipartResolver
* 解析 multi-part 的传输请求,如,支持通过 HTML 表单进行文件上传等。
FlashMapManager
* FlashMap 管理器,能够存储并取回两次请求之间的 FlashMap 对象,后者可用于在请求之间传递数据,通常是在请求重定向的情景下使用。
其中,常用的ViewResolver的配置。以jsp作为视图为例
<!-- 对模型视图名称的解析,即在模型视图名称添加前后缀 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
配置上传文件限制MultipartResolver
<!-- 上传限制 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 上传文件大小限制为31M,31*1024*1024 -->
<property name="maxUploadSize" value="32505856"/>
</bean>
四、applicationContext.xml 中的标签
文件上传
前面说到DispatcherServlet中有个特殊的Bean叫MultipartResolver,可用于限制文件的上传大小等。当解析器MultipartResolver完成处理时,请求便会像其他请求一样被正常流程处理。
* 表单
<form method="post" action="/form" enctype="multipart/form-data">
<input type="text" name="name"/>
<input type="file" name="file"/>
<input type="submit"/>
</form>
* 控制器
@RequestMapping(path = "/form", method = RequestMethod.POST) public String handleFormUpload(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
}
return "redirect:uploadFailure";
}
异常处理
HandlerExceptionResolver
* 可以实现全局异常控制 * HandlerExceptionResolver 接口中定义了一个ieresolveException 方法,用于处理 Controller 中的异常,Exception ex 参数即 Controller 抛出的异常,返回值类型是 ModelAndView,可以通过这个返回值设置异常时显示的页面。
SimpleMappingExceptionResolver
* Spring 提供的一个默认的异常实现类 SimpleMappingExceptionResolver
ExceptionHandler
* 可是实现局部异常控制 * 如果 @ExceptionHandler 方法是在控制器内部定义的,那么它会接收并处理控制前(或其他任何子类)中的 @RequestMapping 方法抛出的异常。 * 如果将 @ExceptionHandler 方法定义在 @ContollerAdvice 类中,那么它会处理相关控制器抛出的异常。
web.xml 中的 error-page 标签
* 简单处理异常和跳转,灵活程序不及 HandlerExceptionResolver 的异常处理
Spring的处理器异常解析器HandlerExceptionResolver接口的实现负责处理各类控制器执行过程中出现的异常。也是上面提到的,是DispatcherServlet中的特殊bean,可以自定义配置处理。
某种程度上讲,HandlerExceptionResolver与你在web应用描述符web.xml文件中能定义的异常映射(exception mapping)很相像,不过它比后者提供了更灵活的方式。比如它能提供异常被抛出时正在执行的是哪个处理器这样的信息。
* HandlerExceptionResolver 提供resolveException接口
public interface HandlerExceptionResolver {
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}
* 在BaseController中使用 @ExceptionHandler注解处理异常
@ExceptionHandler(Exception.class)
public Object exceptionHandler(Exception ex, HttpServletResponse response,
HttpServletRequest request) throws IOException {
String url = "";
String msg = ex.getMessage();
Object resultModel = null; try { if (ex.getClass() == HttpRequestMethodNotSupportedException.class) {
url = "admin/common/500";
System.out.println("--------毛有找到对应方法---------");
} else if (ex.getClass() == ParameterException.class) {//自定义的异常
} else if (ex.getClass() == UnauthorizedException.class) {
url = "admin/common/unauth";
System.out.println("--------毛有权限---------");
}
String header = req.getHeader("X-Requested-With");
boolean isAjax = "XMLHttpRequest".equalsIgnoreCase(header);
String method = req.getMethod();
boolean isPost = "POST".equalsIgnoreCase(method); if (isAjax || isPost) { return Message.error(msg);
} else {
ModelAndView view = new ModelAndView(url);
view.addObject("error", msg);
view.addObject("class", ex.getClass());
view.addObject("method", request.getRequestURI()); return view;
}
} catch (Exception exception) {
logger.error(exception.getMessage(), exception); return resultModel;
} finally {
logger.error(msg, ex);
ex.printStackTrace();
}
}
* 在web.xml中处理异常
<!-- 默认的错误处理页面 -->
<error-page>
<error-code>403</error-code>
<location>/403.html</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/404.html</location>
</error-page>
<!-- 仅仅在调试的时候注视掉,在正式部署的时候不能注释 --><!-- 这样配置也是可以的,表示发生500错误的时候,转到500.jsp页面处理。 -->
<error-page>
<error-code>500</error-code>
<location>/500.html</location>
</error-page>
<!-- 这样的配置表示如果jsp页面或者servlet发生java.lang.Exception类型(当然包含子类)的异常就会转到500.jsp页面处理。 -->
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/500.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/500.jsp</location>
</error-page>
<!-- 当error-code和exception-type都配置时,exception-type配置的页面优先级高及出现500错误,发生异常Exception时会跳转到500.jsp-->