文章目录
一、SpringMVC概述
Spring MVC一种开源的、轻量级的、基于MVC的Web层应用框架。偏前端而不是基于业务逻辑层。
SpringMVC是Spring中的模块,它实现了mvc设计模式的web框架,Spring web MVC框架提供了MVC(模型 - 视图 - 控制器)架构,用于开发灵活和松散耦合的Web应用程序的组件。 MVC模式使应用程序的不同组件(输入逻辑,业务逻辑和UI逻辑)合理有效的分离,同时又有效的将各组件组合一起完成功能。
· 模型(Model) 封装了应用程序数据,通常它们将由POJO
类组成。
· 视图(View) 负责渲染模型数据,一般来说它负责生成客户端浏览器可以解释HTML输出。
· 控制器(Controller) 负责处理用户请求并构建适当的模型,并将其传递给视图进行渲染。
常用组件:
DispatcherServlet:前端控制器
Controller:处理器/页面控制器,做的是MVC中的C的事情,但控制逻辑转移到前端控制器了,用于对请求进行处理
HandlerMapping :请求映射到处理器,如果映射成功返回一个HandlerExecutiongChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器对象)
ViewResolver : 视图解析器,找谁来处理返回的页面。把逻辑视图解析为具体的View,进行这种策略模式,很容易更换其他视图技术;如InternalResourceViewResolver将逻辑视图名映射为JSP视图
LocalResolver:本地化、国际化
MultipartResolver:文件上传解析器
HandlerExceptionResolver:异常处理器
二、环境搭建
2.1 HelloWorld范例
①新建Web工程,加入 jar 包;
②配置web.xml:
<!-- 配置SpringMVC核心控制器:DispatcherServlet -->
<!--前端控制器 dispatcherServlet 其实就是servlet-->
<servlet>
<!--servlet的友好名称,建议跟类名保持一致-->
<servlet-name>dispatcherServlet</servlet-name>
<!--servlet的全类名-->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--servlet实例创建并且读取springmvc配置文件-->
<init-param>
<!--DispatcherServlet类中的一个属性:contextConfigLocation,里面配置springmvc的路径-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 表示前端控制器在服务器启动时就加载servlet实例 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置前端控制器的访问地址 -->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--
/和/*的区别
/ 如果请求访问的是jsp,则不拦截
/* 全部拦截,包含jsp页面
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
③配置Springmvc.xml:
<!-- 包扫描 -->
<context:component-scan base-package="Controller"></context:component-scan>
<!--
配置映射解析器:
拼接完整的页面路径信息
/WEB-INF/views/+逻辑名字+.jsp
/WEB-INF/views/welcome.jsp
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- prefix:逻辑名字前的路径信息 -->
<property name="prefix" value="/WEB-INF/views/"></property>
<!-- suffix:逻辑名字页面的后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 如果将DispatcherServlet请求映射配置为"/",则Spring MVC将捕获Web容器所有的请求,包括静态资源的请求,Spring MVC会将它们当成一个普通请求处理,因此找不到对应处理器将导致错误。 -->
<!-- 在springMVC-servlet.xml中配置<mvc:default-servlet-handler />后,会在Spring MVC上下文中定义一个DefaultServletHttpRequestHandler,它会像一个检查员,对进入DispatcherServlet的URL进行筛查,如果是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。 -->
<mvc:default-servlet-handler></mvc:default-servlet-handler>
<!-- <mvc:annotation-driven /> 会自动注册:RequestMappingHandlerMapping 、RequestMappingHandlerAdapter 与 ExceptionHandlerExceptionResolver 三个bean。-->
<!--
支持使用 ConversionService 实例对表单参数进行类型转换
支持使用 @NumberFormat、@DateTimeFormat 注解完成数据类型的格式化
支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证
支持使用 @RequestBody 和 @ResponseBody 注解
-->
<mvc:annotation-driven></mvc:annotation-driven>
④Controlller控制器类:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
//@Controller 注解是把HelloController类配置到Spring容器中
@Controller
public class HelloController {
/**
* 使用 @RequestMapping 注解来映射请求的 URL
* / : 在web工程中表示路径为:http://ip:port/工程路径
* /hello : 表示地址为:http://ip:port/工程路径/hello
*/
@RequestMapping(value = "/hello")
public String hello(){
System.out.println(" springmvc hello world程序 ");
/**
* 返回值会通过视图解析器解析为实际的物理视图,
* 对于 InternalResourceViewResolver 视图解析器,会做如下的解析:
* 通过 prefix + returnVal + suffix 这样的方式得到实际的物理视图,
* 默认做转发操作.
* /WEB-INF/views/success.jsp
*/
return "success";
}
}
⑤视图配置:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h4>helloworld</h4>
</body>
</html>
2.2 HelloWorld流程图解
2.3 请求相应流程图
基本步骤:
- 客户端请求提交到DispatcherServlet;
- 由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller;
- DispatcherServlet将请求提交到Controller(也称为Handler);
- Controller调用业务逻辑处理后,返回ModelAndView;
- DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图;
- 视图负责将结果显示到客户端;
三、 前端控制器
DispatcherServlet 是前端控制器设计模式的实现,提供 Spring Web MVC 的集中访问点,并把请求分发到不同的控制器去处理,根据控制器处理后的结果,生成相应的响应发送到客户端。前端控制器既可以使用Filter实现(Struts2采用这种方式),也可以使用Servlet来实现(spring MVC框架)。
3.1 Servlet回顾
SpringMVC是用Servlet来实现前端控制器,所以首先来回忆一下servlet的相关知识。
Servlet生命周期的三个阶段:
在Servlet容器启动后,客户首次向Servlet发送请求,Servlet容器创会建一个Servlet实例。
- 初始化:实例化之后紧接着执行初始化,只执行一次,调用init()方法。
- 服务:处理请求并响应浏览器,每次从浏览器发送请求访问此servlet,都会调用service()方法。
- 销毁:服务器关闭时执行销毁的方法,只执行一次,调用destroy()方法
提示:在web.xml里面的<servlet>
标签中,加入<load-on-startup>
,就可以将servlet的加载时间提前到服务器启动时,只能设置正整数,负整数或0没有任何效果,而且值越小越先加载。
3.2 DispatcherServlet前端控制器
前端控制器源码分析:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 确定当前请求的处理程序。
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 确定当前请求的处理程序适配器。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
第一步:用户发送请求至前端控制器DispatcherServlet;
.action类型的URL通过过滤器进入DispatcherServlet类,调用其doDiapatch()方法:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
......
}
第二步:前端控制器调用HandlerMapping处理器映射器,请求获取Handle;
在doDiapatch()方法中调用了DispatcherServlet类的getHandler方法:
HandlerExecutionChain mappedHandler = null;
......
//调用getHandler() 获取HandlerExecutionChain:当前请求地址对应的处理器(链),即请求处理器。
mappedHandler = getHandler(processedRequest, false);
其中getHandler方法:
@Deprecated
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
return getHandler(request);
}
//在getHandler()方法中遍历了已有的handlerMapping,然后调用handlerMapping.getHandler(request)获得HandlerExecutionChain,而HandlerExecutionChain中包含了需要调度的指定handler。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// HandlerMapping: 存储了所有的请求地址和处理器(方法)的对应信息,在组件初始化的时候就存放了所有URL和处理器(方法)的对应信息。
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {//logger记录器,记录日志信息
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
//HandlerExecutionChain:当前请求地址对应的处理器(链)
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;//说明前端传过来的URL和注解上的路径匹配, 就返回对应的控制器
}
}
return null;
}
处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成),返回一个执行器的链(HandlerExecutionChain)给DispatcherServlet前端控制器;
第三步:回到doDiapatch()方法,调用处理器适配器HandlerAdapter执行Handler,确定方法运行时的参数,利用反射执行目标方法得到执行结果ModelAndView。
ModelAndView mv = null;
......
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
......
//确定方法运行时的参数,利用反射执行目标方法。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
四、视图解析器
请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个 ModelAndView 对象,它包含了逻辑名和模型对象的视图。
Spring MVC 借助**视图解析器(ViewResolver)**得到最终的视图对象(View),对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点聚焦在生产模型数据的工作上,从而实现 MVC 的充分解耦。
4.1 视图
视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。为了实现视图模型和具体实现技术的解耦,Spring 在 org.springframework.web.servlet 包中定义了一个高度抽象的 View 接口:
- 视图对象由视图解析器负责实例化。由于视图是无状态的,所以他们不会有线程安全的问题。
常用视图实现类:
4.2 ViewResolve视图解析器
SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。
所有的视图解析器都必须实现ViewResolver接口:
程序员可以选择一种视图解析器或混用多种视图解析器
每个视图解析器都实现了 Ordered 接口并开放出一个 order 属性,可以通过 order 属性指定解析器的优先顺序,order 越小优先级越高。SpringMVC 会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则将抛出 ServletException 异常。
JSP 是最常见的视图技术,可以使用 InternalResourceViewResolve作为视图解析器:
视图解析器源码分析:
经过以上步骤得到了ModelAndView对象之后,接下来视图解析器会将ModelAndView拆分开来,得到数据模型model和视图view,然后渲染视图view,并把model数据模型填充到request域中。
第四步:DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
//DispatcherServlet有一个processDispatchResult方法:处理Dispatch结果,即处理ModelAndView
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
processDispatchResult方法:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// 此处调用了render方法:渲染
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
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) {
//通过resolveViewName()方法,调用视图解析器解析ModelAndView,根据viewName解析得到view.
view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
}
} else {
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() + "'");
}
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
//调用View的render方法去渲染视图并将ModelMap中的值设置到request域中
view.render(mv.getModelInternal(), request, response);
} catch (Exception var8) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Error rendering view [" + view + "]", var8);
}
throw var8;
}
}
View接口是Spring MVC提供的视图渲染接口,定义了render方法对给定的模型数据进行视图渲染:
public interface View {
... ...
/** 把模型数据进行渲染 */
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
... ...
}
AbstractView是实现View接口的抽象类,实现了render方法:
public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {
... ...
@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
// 创建整合后需要返回给浏览器的Model
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
// 设置response 报文头
prepareResponse(request, response);
// 渲染数据,通过模板方法由子类实现,如InternalResourceView
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
... ...
}
InternalResourceView该类继承自AbstractView,并实现renderMergedOutputModel方法:
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 将model中的数据设置到request
exposeModelAsRequestAttributes(model, request);
//从exposeModelAsRequestAttributes()的源码可以看出,该方法将modelmap中的键-值设置到request中
// 本类中的此函数是空函数,留给子类比如JstlView去实现自定义逻辑
exposeHelpers(request);
// 跳转目的页面路径
String dispatcherPath = prepareForRendering(request, response);
// 获取跳转控制器RequestDispatcher
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// 直接返回用户资源
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(request, response);
}
// 携带request和response跳转到另一个控制器方法
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(request, response);//视图解析器用转发处理请求
}
}
五、页面控制器 / 处理器
5.1 @RequestMapping映射
SpringMVC使用@RequestMapping注解为控制器指定可以处理哪些 URL 请求,通俗的说就是给方法配置一个访问地址。
DispatcherServlet 截获请求后,就通过控制器上@RequestMapping 提供的映射信息确定请求所对应的处理方法。
在控制器的类定义及方法定义处都可标注 @RequestMapping
- 标记在类上:提供初步的请求映射信息。相对于 WEB 应用的根目录。
- 标记在方法上:提供进一步的细分映射信息。相对于标记在类上的 URL。
若类上未标注 @RequestMapping,则方法处标记的 URL 相对于 WEB 应用的根目录。
注意: 一个方法可以有多个请求路径 , 一个路径只能分配给一个方法.
RequestMapping属性:表示请求 URL、请求方法、请求参数及请求头的映射条件,它们之间是’与’的关系,联合使用多个条件可让请求映射更加精确化。
5.1.1 value属性
//value是默认属性,表示方法的访问路径 URL,/ 表示工程路径(可以不加),访问地址一般与方法名相同;
@RequestMapping(value = {"/hello","/123","/111"})
public String hello(){URL
System.out.println("http://localhost:8080/08_springmvc/hello");
return "welcome";
}
//注意:一个方法可以有多个请求路径 , 一个路径只能分配给一个方法。
5.1.2 params属性
- params=“username” 表示 请求地址必须带有username参数
- params=“username=abc” 表示 请求参数中必须要有username,而且值还必须是abc
- params=“username!=abc” 表示 ①请求参数中不能有username参数。②有username参数,但值不能等于abc
- params="!username" 表示 请求地址不能带有username参数
//params属性:请求参数的匹配规则
@RequestMapping(value = "/helloParams",params = "userName=123")
public String helloParams(){
System.out.println("http://localhost:8080/08_springmvc/helloParams?userName=123");
return "welcome";
}
5.1.3 headers属性
//headers属性表示对请求头的匹配要求 , 用法上跟params 完全一样
@RequestMapping(value = "/helloHeaders",headers = "HOST")
public String helloHeaders(){
System.out.println("http://localhost:8080/08_springmvc/helloHeaders");
return "welcome";
}
5.1.4 method属性
//method属性:请求方式的匹配规则,默认表示任意的请求方式都可以
@RequestMapping(value = "/helloMethod",method = RequestMethod.POST)
public String helloMethod(){
System.out.println("http://localhost:8080/08_springmvc/helloMethod");
return "welcome";
}
5.1.5 ant模式地址通配符
/*
? :匹配文件名中的一个字符
* :匹配文件名中的任意字符
**:匹配多层路径
*/
@RequestMapping(value = {"/helloAnt?","/helloAnt/**"})
public String helloAnt(){
System.out.println("http://localhost:8080/08_springmvc/helloAnt!");
return "welcome";
}
**注:**当一个路径同时匹配多个规则的时候,调用方法的优先顺序是:绝对匹配—>> ?问号匹配---->> *星号匹配
- 多个路径同时匹配: 越精确越优先;
5.2 请求参数的传入
Spring MVC 框架会将
HTTP 请求的信息绑定到Controller方法入参中,并根据方法的返回值类型做出相应的后续处理。
5.2.1 原生API参数类型
MVC 的 Handler 方法可以接受以下 ServletAPI 类型的参数:
- HttpServletRequest
- HttpServletResponse
- HttpSession
- java.security.Principal
- Locale
- InputStream
- OutputStream
- Reader
- Writer
@RequestMapping(value = "/helloAPI")
//只要在Controller方法中填入API参数,就能获得相应的对象
public void helloAPI(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws ServletException, IOException {
String userName = request.getParameter("userName");//从request作用域获取参数
session.setAttribute("uname",userName);//把参数放在session作用域
//用request实现转发
request.getRequestDispatcher("/WEB-INF/views/welcome.jsp").forward(request,response);
/*
把数据放到application作用域要先在类中自动装配一个servletContext
因为servletContext在tomcat启动时就创建完毕,
所以此处可以用自动装箱获取servletContext作用域,
servletContext代表整个工程项目范围
@Autowired
private ServletContext servletContext;
*/
session.getServletContext().setAttribute("userName",userName);//放在application作用域
System.out.println("http://localhost:8080/08_springmvc/helloAPI");
}
5.2.2 普通类型
//Controller方法可以获取到请求所携带的参数,要求:方法的参数名和请求的参数名一致!
@RequestMapping(value = "/helloParam")
public String helloParam(String userName){
System.out.println("http://localhost:8080/08_springmvc/helloParam");
System.out.println(userName);
return "welcome";
}
5.2.3 数组类型
5.2.4 @RequestParam入参
在Controller方法入参处使用 @RequestParam 可以把请求参数传递给请求方法。
- value:参数名;
- required:是否必须。默认为 true, 表示请求参数中必须包含对应的参数,若不存在,将抛出异常;
- defaultValue: 默认值,当没有传递参数时使用该值;
@RequestMapping(value = "/helloParam1")
public String helloParam1(@RequestParam(value = "userName", required = true, defaultValue = "tom") String userName){
System.out.println("http://localhost:8080/08_springmvc/helloParam1");
System.out.println(userName);
return "welcome";
}
5.2.5 @RequestHeader入参
请求头包含了若干个属性,服务器可据此获知客户端的信息,
通过 @RequestHeader 即可将请求头中的属性值绑定到处理方法的入参中 。
@RequestMapping(value = "/helloParam2")
//@RequestHeader使用方式参考@RequestParam
public String helloParam2(@RequestHeader("HOST") String host){
System.out.println("http://localhost:8080/08_springmvc/helloParam2");
System.out.println(host);
return "welcome";
}
5.2.6 @CookieValue入参
@CookieValue 可让处理方法入参绑定某个 Cookie 值
@RequestMapping(value = "/helloParamCookie")
public String helloParamCookie(@CookieValue("JSESSIONID") String cookie){
System.out.println("http://localhost:8080/08_springmvc/helloParamCookie");
System.out.println(cookie);
return "welcome";
}
5.2.7 Pojo类型&级联属性入参
Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值。支持级联属性。
如:dept.deptId、dept.address.tel 等…
jsp页面:
<a href="${pageContext.request.contextPath}
/toUpdate?name=${book.name}
&author=${book.author}
&price=${book.price}
&sales=${book.sales}
&stock=${book.stock}"
>修改</a></td>
Controller类:
//此时SpringMVC会将页面中对应名字的属性打包进book对象里面
@RequestMapping("/updateBook")
public String updateBook(Book book){
bookService.updateBook(book);
return "redirect:queryAll";
}
5.2.8 字符编码过滤器
- 如果中文有乱码,需要配置字符编码过滤器,且配置在其他过滤器之前,如HiddenHttpMethodFilter,否则不起作用。
<!-- 配置字符编码过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- 配置字符集 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!-- forceEncoding=true是意思是指无论客户端请求是否包含了编码,都用过滤器里的编码来解析请求 -->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!-- 过滤规则:所有请求 -->
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
5.3 响应数据的传出
SpringMVC提供了以下几种途径输出模型数据:
- @SessionAttributes: 将模型中的某个属性暂存到 HttpSession 中,以便多个请求之间可以共享这个属性。
- @ModelAttribute: 方法入参标注该注解后, 入参的对象就会放到数据模型中。
- ModelAndView: 处理方法返回值类型为 ModelAndView 时, 方法体即可通过该对象添加模型数据 。
- Map、Model、ModelMap: 入参为 org.springframework.ui.Model、org.springframework.ui.ModelMap 或 java.uti.Map 时,处理方法返回时,Map 中的数据会自动添加到模型中。
5.3.1 ModelAndView
控制器处理方法的返回值如果为 ModelAndView,
则其既包含视图信息,也包含模型数据信息。
- 添加模型数据:
- MoelAndView addObject(String attributeName, Object attributeValue)
- ModelAndView addAllObject(Map<String, ?> modelMap)
- 设置视图:
- void setView(View view)
- void setViewName(String viewName)
@RequestMapping(value = "/helloModelAndView")
public ModelAndView helloModelAndView (String username){
/**
* 有两种方式可以指定跳转路径 <br/>
* 1 在构造器中直接传入<br/>
* 2 setViewName()设置视图名 <br/>
* 默认情况下.只需要写视图名即可,它会跟视图解析器一起工作.
* 默认情况下,SpringMVC也是使用请求转发来进行跳转 <br/>
*/
//创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
//把数据放到request作用域中
modelAndView.addObject("uname",username);
//利用视图解析器转跳
modelAndView.setViewName("welcome");
//请求转发
modelAndView.setViewName("forward:/pages/ok.jsp");
//请求重定向
modelAndView.setViewName("redirect:/pages/ok.jsp");
//返回值必须是ModelAndView对象
return modelAndView;
}
5.3.2 Map、Model、ModelMap
Spring MVC 在内部使用了一个 org.springframework.ui.Model 接口存储模型数据。在调用方法前会创建一个隐含的模型对象 BindingAwareModelMap 作为模型数据的存储容器。
如果方法的入参为 Map、Model 或 ModelMap 类型,SpringMVC 会将隐含模型的引用传递给这些入参。在方法体内,开发者可以向模型中添加新的属性数据:<key,object>结构,然后在页面中通过key去访问。
隐含数据模型类层次结构:
/** BindingAwareModelMap隐含模型对象介绍:
*
* BindingAwareModelMap对象,我们管它叫隐含模型对象 <br/>
* 隐含模型对象的作用是 : 给视图准备需要渲染的数据 <br/>
* 隐含:是不需要你自己手动定义<br/>
* 模型:保存数据的对象<br/>
* 视图:用户最终收到的数据的一个载体 (html页面,jsp页面) <br/>
* 渲染:就是执行视图的代码叫渲染<br/>
* 视图需要的数据:页面上需要显示的数据 <br/>
*
* 继承关系:
* class org.springframework.validation.support.BindingAwareModelMap 类
* /\
* ||
* BindingAwareModelMap extends ExtendedModelMap
* /\
* ||
* ExtendedModelMap extends ModelMap implements Model
* /\
* ||
* ModelMap extends LinkedHashMap<String, Object>
*/
@RequestMapping("/modelMApToRequest")
public String modelMApToRequest(Map<String,Object> map,Model model,ModelMap modelMap){
//Map形式保存数据到Reqeust域中
map.put("key1", "value1");
map.put("key2", "value2");
//Model形式保存数据到Reqeust域中
model.addAttribute("key3","value1");
model.addAttribute("key4","value2");
//ModelMap形式保存数据到Reqeust域中
modelMap.addAttribute("key5","value1");
modelMap.addAttribute("key6","value2");
//在页面的request作用域中,通过key去访问value
return "welcome";
}
5.3.3 @SessionAttributes注解
若希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个 @SessionAttributes, Spring MVC 将在模型中对应的属性暂存到 HttpSession 中。
@SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中。
例如:
//这个注解只能应用在类上面!
@SessionAttributes(value = { "key1","key2" }, types = { String.class, Book.class })
- value&name属性,它表示把request域中属性名为key1,key2的数据,也保存到Session中;
- types属性,它表示把request域中类型为String.class或Book.class类型的数据,也保存到Session中;
5.3.4 @ModelAttribute注解
@ModelAttribute这个注解可以标注在方法和参数上。
@ModelAttribute三个常见作用:
1、被标注了@ModelAttribute的方法都会在Controller的目标方法之前执行。
2、目标方法的参数(JavaBean对象)会先从隐含模型中获取值传入。
3、被标注在参数上,参数值会按照指定的key从隐含模型中获取值。
@ModelAttribute
public void abc(Map<String,Object> map){
System.out.println(" @ModelAttribute 标识 的方法 abc() ");
map.put("person1", new Person(100, "技多不压身"));
}
@RequestMapping("/target")
public String target(@ModelAttribute("person1") Person person){
System.out.println(" target() 方法 ==>> " + person);
return "scope";
}
5.3.5 返回值的设置
显式使用请求转发 / 重定向来跳转页面:
- 需要在返回值前面添加 forward: 或 redirect: 字符串;
- 不和视图解析器的前后缀做拼接操作;
- 后面一定要写上完整的跳转路径
- 后面的地址分为相对路径和绝对路径;
-
绝对路径 ==>> 以是斜杠打头的路径( 使用绝对路径永远不会错 )
/pages/ok.jsp 解析之后得到的地址是: http://ip:port/工程路径/pages/ok.jsp
-
相对路径 ==>> 是不以斜杠打头的路径 ( 不要试,能用就是运气好 )
pages/ok.jsp 是相对路径,相对路径在工作的时候都需要参照当前请求地址
当返回值类型为String时:
//利用视图解析器转跳(转发)
return "welcome";
//转发
return "forward:/pages/welcome.jsp"
//重定向
return "redirect:/pages/welcome.jsp"
当返回值类型为ModelAndView时:
//利用视图解析器转跳
modelAndView.setViewName("welcome");
//请求转发
modelAndView.setViewName("forward:/pages/ok.jsp");
//请求重定向
modelAndView.setViewName("redirect:/pages/ok.jsp");
//返回值必须是ModelAndView对象
return modelAndView;
六、Restful风格
Representational
State Transfer:**(资源)表现层状态转化,**是一种互联网软件架构。
· 资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的 URI 。获取这个资源,访问它的URI就可以,因此 URI 即为每一个资源的独一无二的识别符。
· 表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层(Representation)。比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式。
· 状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。
- 而这种转化是建立在表现层之上的,所以就是 “表现层状态转化”。
具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。
它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。
6.1 原则条件
简单来说实现Restful风格只需以下两点:
- 把请求参数加入到请求的资源地址中。
- 原来的增,删,改,查。使用HTTP请求方式,POST、DELETE、PUT、GET分别一一对应。
6.2 Restful风格请求地址
传统的方式:
restful风格:
6.3 GET,POST,PUT,DELETE请求
form表单标签中,设置method=”get / ”post” 可以发送get请求或post请求,但如何发送put和delete请求呢?
- 写一个post请求的form标签
- 在form表单中,添加一个额外的隐藏域_method=”PUT”或_method=”DELETE”
- 在web.xml中配置一个Filter过滤器org.springframework.web.filter.HiddenHttpMethodFilter
6.3.1 HiddenHttpMethodFilter
普通浏览器只支持GET,POST方式 ,其他请求方式如DELETE|PUT必须通过过滤器的支持才能实现。
Spring自带了一个过滤器HiddenHttpMethodFilter,支持GET、POST、PUT、DELETE请求。
<!-- 增加HiddenHttpMethodFilte过滤器:给普通浏览器增加 put|delete请求方式 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<!-- 过滤所有:/*-->
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 注意,这个Filter一定要在处理乱码的Filter后面 -->
6.3.2 使用form发送GET,POST,PUT,DELETE请求
<!-- 发送get请求:method="get" -->
<form action="${pageContext.request.contextPath}/book/{id}" method="get">
<!-- action转跳地址:控制器方法地址 -->
<input type="submit" value="查询">
</form>
<!-- 发送post请求:method="post" -->
<form action="${pageContext.request.contextPath}/book" method="post">
<input type="submit" value="添加">
</form>
<!-- 发送put请求:表单需要是post提交方式 -->
<form action="${pageContext.request.contextPath}/book" method="post">
<!-- 在表单中添加一个隐藏域hidden,并设置 name="_method" value="put" -->
<input type="hidden" name="_method" value="put">
<input type="submit" value="修改">
</form>
<!-- 发送delete请求:表单需要是post提交方式 -->
<form action="${pageContext.request.contextPath}/book/{id}" method="post">
<!-- 在表单中添加一个隐藏域hidden,并设置 name="_method" value="delete" -->
<input type="hidden" name="_method" value="delete">
<input type="submit" value="删除">
</form>
6.4 Restful风格Controller
- 尽可能把目标方法访问路径改成一致;
- 在@RequestMapping注解中添加RequestMethod属性约束方法访问的请求方式;
- 使用@PathVariable注解(基本类型)获取路径参数;
- pojo类型入参可以自动映射到pojo对象;
@Controller
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping(value = "/queryAll",method = RequestMethod.GET)
public String queryAll(ModelMap modelMap){
List<Book> books = bookService.queryAll();
modelMap.addAttribute("books",books);
return "bookList";
}
@RequestMapping(value = "/Book/{id}",method = RequestMethod.DELETE)
public String deleteBook(@PathVariable("id")Integer id){
bookService.deleteBook(id);
return "redirect:/queryAll";
}
@RequestMapping(value = "/Book",method = RequestMethod.POST)
public String addBook(Book book){
bookService.addBook(book);
return "redirect:/queryAll";
}
@RequestMapping("/toAdd")
public String toAdd(){
return "bookEdit";
}
@RequestMapping("/toUpdate/{id}/{name}/{author}/{price}/{sales}/{stock}")
public String toUpdate(Book book,ModelMap modelMap){
modelMap.addAttribute("book",book);
return "bookEdit";
}
@RequestMapping(value = "/Book",method = RequestMethod.PUT)
public String updateBook(Book book){
System.out.println(book);
bookService.updateBook(book);
return "redirect:/queryAll";
}
}
七、HandlerInterceptor拦截器
SpringMVC可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现 HandlerInterceptor 接口。SpringMVC的拦截器只对action请求起作用,即只对Controller中的目标方法起作用。
7.1 HandlerInterceptor 接口
HandlerInterceptor接口主要定义了三个方法:
- **preHandle方法:**该方法将在请求处理之前进行调用,只有该方法返回true,才会继续执行后续的Interceptor和Controller,当返回值为true 时就会继续调用下一个Interceptor的preHandle 方法,如果已经是最后一个Interceptor的时候就会是调用当前请求的Controller方法;
- **postHandle方法:**该方法将在请求处理之后,DispatcherServlet进行视图返回渲染之前进行调用,可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。
- **afterCompletion方法:**该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。用于进行资源清理。
- 接口中的方法均为default,选择业务需要的方法实现即可。
7.2 具体实现
- 先编写一个类去实现HandlerInterceptor接口;
- 实现拦截器的方法;
- 到web.xml中去配置拦截器拦截的目标方法;
springmvc.xml:
<!--在springmvc容器中注册拦截器的信息-->
<mvc:interceptors>
<!--配置的拦截器和方法的关系-->
<mvc:interceptor>
<!--拦截器服务的action方法路径-->
<mvc:mapping path="/interceptorController/hello"/>
<!--指定具体的拦截器,拦截器是一个类,所以配置bean对象-->
<bean class="com.JavaSSM.interceptor.FirstInterceptor"></bean>
</mvc:interceptor>
<!--可以配置多个拦截器-->
<mvc:interceptor>
<mvc:mapping path="/interceptorController/hello"/>
<bean class="com.JavaSSM.interceptor.SecondInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
<!-- Spring通过context:component-scan/标签的配置,会自动为我们将扫描到的@Component,@Controller,@Service,@Repository等注解标记的组件注册到工厂中,来处理我们的请求。 -->
<mvc:annotation-driven></mvc:annotation-driven>
Interceptor类:
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 控制器方法的拦截器,接口中的方法均为default,即选择需要的方法实现即可
*/
public class FirstInterceptor implements HandlerInterceptor {
/**
* 在控制器目标方法之前执行
* @param request
* @param response
* @param handler 目标(控制器)方法对象
* @return false 是否放行
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor preHandle..");
//返回值为true 时就会继续调用下一个Interceptor的preHandle方法,
//已经是最后一个Interceptor的时候就会是调用当前请求的Controller方法;
return true;//放行
}
/**
* 在控制器目标方法之后执行
* @param request
* @param response
* @param handler 目标(控制器)方法对象
* @param modelAndView Controller 处理之后的视图&模型对象
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//在此方法可以对 Controller 处理之后的 ModelAndView 对象进行操作
System.out.println("FirstInterceptor postHandle...");
}
/**
* springmvc中视图渲染之后执行
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//此方法只要preHandle放行了,就一定会在最后执行
System.out.println("FirstInterceptor afterCompletion...");
}
}
controller类:
@Controller
@RequestMapping("/interceptorController")
public class InterceptorController {
@RequestMapping("/hello")
public String hello(){
System.out.println("控制器中的目标方法...");
return "welcome";
}
}
7.3 拦截器与过滤器的区别
- ①:拦截器是基于java的反射机制的,而过滤器是基于函数的回调。
- ②:拦截器不依赖于servlet容器,而过滤器依赖于servlet容器。
- ③:拦截器只对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
- ④:拦截器可以访问action上下文、值、栈里面的对象,而过滤器不可以。
- ⑤:在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
- ⑥:拦截器可以获取IOC容器中的各个bean,而过滤器不行,在拦截器里注入一个service,可以调用业务逻辑。
拦截器与过滤器关系图解:
拦截器与过滤器执行流程:
7.4 SpringMVC拦截器使用场景
SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。
- 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
- 权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;
性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录); - 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。
- OpenSessionInView:在进入处理器打开Session,在完成后关闭Session。
八、文件上传&下载
Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。 Spring 用 Jakarta Commons FileUpload 技术实现了一个 MultipartResolver 实现类:CommonsMultipartResovler。
CommonsMultipartResovler 底层对 commons-fileupload-1.2.1.jar 和 commons-io-1.4.jar 的API进行了封装,使我们能够用更简洁的代码风格实现文件上传和下载。
8.1 文件上传具体实现:
- 在springmvc配置文件中配置上传解析器;
- 在Controller控制器编写文件上传的代码;
页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>title</title>
</head>
<body>
<!--multipart/form-data是指表单数据有多部分构成,既有文本数据,又有文件等二进制数据 -->
<!--默认情况下,enctype的值是application/x-www-form-urlencoded,只能上传文本格式的文件 -->
<!-- multipart/form-data是将文件以二进制的形式上传,这样可以实现多种类型的文件上传。-->
<form action="${pageContext.request.contextPath}/upload"
method="post" enctype="multipart/form-data">
文件: <input type="file" name="file"/><br><br>
<input type="submit" value="提交"/>
</form>
</body>
</html>
配置上传解析器CommonsMultipartResolver:
<!--
配置文件上传解析器 CommonsMultipartResolver
id必须是 multipartResolver
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--maxUploadSize:最大上传大小 (选配)-->
<property name="maxUploadSize" value="22000"></property>
<!--defaultEncoding:指定编码格式 防止乱码-->
<property name="defaultEncoding" value="utf-8"></property>
</bean>
在Controller中编写上传代码:
单个文件上传:
@RequestMapping("/upload")
// MultipartFile是SpringMVC提供的一个专门处理文件的类型;
// @RequestParam 把页面中文件域的file映射到 MultipartFile 对象上;
public String upload(@RequestParam("file")MultipartFile file, HttpServletRequest request) throws IOException {
/* 表单中文件域标签的name属性值
String name = file.getName();
//文件名字
String filename = file.getOriginalFilename();//原始的文件名字
//文件的类型
String contentType = file.getContentType();
*/
//getOriginalFilename()获取文件名字,此名字用作上传后保存的名字
String filename = file.getOriginalFilename();//原始的文件名字
//获取服务器具体保存文件的文件夹的绝对路径
String path=request.getServletContext().getRealPath("upload");
//通过绝对路径和文件名创建file对象
File uploadFile = new File(path,filename);
//通过transferTo(转移)实现上传
file.transferTo(uploadFile);//上传
return "welcome";
}
多个文件上传:
@RequestMapping("/uploads")
public String uploads(@RequestParam("files")MultipartFile[] files, HttpServletRequest request) throws IOException {
String path=request.getServletContext().getRealPath("upload");//服务器的绝对路径
for (MultipartFile file : files) {
String filename =
//解决文件名重复问题,即上传后不会覆盖
Math.random() + System.currentTimeMillis() + "_"
+ file.getOriginalFilename();
File uploadFile=new File(path,filename);
file.transferTo(uploadFile);//上传
}
return "welcome";
}
8.2 文件下载具体实现
SpringMVC使用ResponseEntity返回值处理文件下载,ResponseEntity 里包含了
响应行,响应头,响应体的内容。
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@Controller
public class Controller {
@RequestMapping("/downLoad")
// 返回值设置为ResponseEntity,泛型是byte[]:字节数组
public ResponseEntity<byte[]> downLoad(String fileName,HttpServletRequest request) throws IOException {
//以输入流的形式导入要下载的文件-->getResourceAsStream 传入路径和文件名
InputStream inputStream = request.getServletContext().getResourceAsStream("upload/" + fileName);
inputStream.close();//用完关流
// 使用IOUtils工具类的toByteArray方法将流中的数据转换成字节数组
byte[] bytes = IOUtils.toByteArray(inputStream);
// 获取指定文件的数据类型
String mimeType = context.getMimeType("upload/" + fileName);
//创建响应头对象:把文件的下载信息包装在响应头--->往响应头添加信息
HttpHeaders headers=new HttpHeaders();
//在响应头添加文件类型信息
headers.add("Content-Type", mimeType);
// Content-Disposition 响应主体(附件信息),添加文件名作为信息
headers.add("Content-Disposition","attachement; filename=" + fileName);
//HttpStatus.OK-->状态码200 响应行-->服务器状态信息
/**
* 第一个参数是 : 响应体
* 第二个参数是 : 响应头
* 第三个参数是 : 响应行( 响应状态码 )
*/
return new ResponseEntity<byte[]>(bytes,headers, HttpStatus.OK);
}
}
九、SpringMVC异常处理
Spring MVC 通过 HandlerExceptionResolver
处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常。
9.1 @ExceptionHandler处理异常
@ExceptionHandler 注解标识的方法是局部异常处理方法( 只对当前自己的Controller有效 ),它可以标识多个方法,异常类越精确,越优先调用。作用:1、处理异常;2、跳转到更友好的错误提示页面;
@Controller
public class Controller {
// @ExceptionHandler注解的方法会在Controller抛出异常时,自动调用
@ExceptionHandler()
public String error (Exception e){
System.out.println("Exception:" + e);
return "error";
}
@ExceptionHandler
public String error (RuntimeException e){
System.out.println("RuntimeException:" + e);
return "error";
}
//@ExceptionHandler中的参数value写异常的全类名,若不写默认按照方法参数中的异常去匹配优先度
@ExceptionHandler(value={java.lang.ArithmeticException.class})
public String error (ArithmeticException e){
System.out.println("ArithmeticException:" + e);
return "error";
}
}
@ExceptionHandler 注解定义的方法优先级问题:就近原则;例如发生的是NullPointerException,但是声明的异常有 RuntimeException 和Exception,此候会根据异常的最近继承关系找到继承深度最浅的那个@ExceptionHandler 注解方法,即标记了RuntimeException 的方法。
9.2 @ControllerAdvice处理异常
@ControllerAdvice注解标识在Controller类上。它可以让当前Controller中所有的异常处理方法改为全局异常处理方法。
- 当有局部异常处理方法和全局异常处理方法同时存在时.他们的优先顺序是 : 局部优先 —>>> 精确优先;
@Controller
//@ControllerAdvice可以把类中的@ExceptionHandler注解作用范围上升到类级别,即其他类抛出的异常也能处理。
@ControllerAdvice
//即可以专门写一个类对所有异常进行统一处理;
public class Controller {
@ExceptionHandler
public String error (Exception e){
System.out.println("Exception:" + e);
return "error";
}
@ExceptionHandler
public String error (RuntimeException e){
System.out.println("RuntimeException:" + e);
return "error";
}
//@ExceptionHandler中的参数value写异常的全类名,若不写默认按照方法参数中的异常去匹配优先度
@ExceptionHandler(value={java.lang.ArithmeticException.class})
public String error (ArithmeticException e){
System.out.println("ArithmeticException:" + e);
return "error";
}
}
9.3 用xml配置统一处理异常
如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!-- 每一个prop标签表示一种类型的异常.和对应的一个错误跳转路径
key是具体的异常全类名
error1 , error2 , error3 是视图名( 跳转的路径 )
-->
<prop key="java.lang.Exception">error1</prop>
<prop key="java.lang.RuntimeException">error2</prop>
<prop key="java.lang.ArithmeticException">error3</prop>
</props>
</property>
</bean>
十、SpringMVC中使用json
10.1 json简介
JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。
1、json的三种格式:
- json对象:{key1:value1,key2:value2…}
- json数组:[value1,value2,value2…]
- json对象数组:[ {key1:value1},{key2:value2},{key3:value3} ]
2、json两种格式的解析方式:
- json对象:对象.key
- json数组:循环+下标
3、常用的Java对象转换为json的格式:
- List–>json数组
- 实体类–>json对象
- map–>json对象
4、json的使用无法就两种情况:而我们在使用json的时候,一般大多是和Ajax请求(异步请求)一起使用。
- 将查询到的java对象转换为json,返回json数据;
- 接收json数据,转换为Java对象;
5、ajax代表的是异步请求,指页面不刷新的情况下,与服务器进行交互,即实现的局部刷新。
- 同步请求:以页面跳转或刷新(全局刷新)为最终的结果,会阻塞用户的请求
- 异步请求:以页面不刷新的情况下与服务器进行交互(局部刷新),不会阻塞用户的请求
10.2 @ResponseBody转换json
@ResponseBody 负责告诉SpringMVC框架,将返回值的对象转换为 json 字符串;
注意:将一个Java对象作为结果响应到浏览器的ajax,不能直接响应;需要将Java对象转换为json格式,才能响应到浏览器被ajax解析。
① 响应 json 对象:
//只要在方法上加上@ResponseBody,即可讲java对象转化为json对象
@ResponseBody //响应数据(json数据){id:1,name:""...}
@RequestMapping("/jsonObject")
public Book jsonObject(){
//模拟从数据库查出pojo对象
Book book=new Book(1,"红楼梦","曹雪芹",new BigDecimal(30),200,10);
return book ;
}
② 响应 json 集合:
@ResponseBody //响应数据(json数据)[{},{},{}]
@RequestMapping("/jsonList")
public List<Book> jsonList(){
Book book1=new Book(1,"红楼梦","曹雪芹",new BigDecimal(30),200,10);
Book book2=new Book(2,"聊斋","蒲松龄",new BigDecimal(30),200,10);
Book book3=new Book(3,"三国","罗贯中",new BigDecimal(30),200,10);
List<Book> books=new ArrayList<>();
books.add(book1);
books.add(book2);
books.add(book3);
return books ;
}
@ResponseBody //响应数据(json数据)[{},{},{}]
@RequestMapping("/jsonMap")
public Map<String,Book> jsonMap(){
Book book1=new Book(1,"红楼梦","曹雪芹",new BigDecimal(30),200,10);
Book book2=new Book(2,"聊斋","蒲松龄",new BigDecimal(30),200,10);
Book book3=new Book(3,"三国","罗贯中",new BigDecimal(30),200,10);
Map<String,Book> maps=new HashMap<>();
maps.put("book1",book1);
maps.put("book2",book2);
maps.put("book3",book3);
return maps ;
}
10.3 @RequestBody接收json
@RequestBody 负责告诉SpringMVC框架,将请求体的数据转换为 JavaBean对象;
- ContentType:”application/json” 和 @RequestBody 组合使用;
- ContentType:”application/json” 表示发送的数据格式为 json 字符串;
jsp页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
<script type="text/javascript" src="${pageContext.request.contextPath}/script/jquery-1.7.2.js"></script>
<script type="text/javascript">
$(function () {
// 页面加载完成之后
$("#sendOne").click(function () {
var jsonPerson = {
'id' : 100,
'name':"华仔"
};
/*重*/ $.ajax({
/*重*/ url:"${pageContext.request.contextPath}/savePerson", /*请求地址*/
/*重*/ type:"post",/*请求方式 必须是post,服务器才能将json字符串转换为java对象*/
/*重*/ data:JSON.stringify(jsonPerson), /*发送给服务器的数据 json字符串*/
/*重*/ success:function (result) { /*成功的回调函数*/
/*点*/ console.log(result);
/*点*/ },
/*点*/ dataType:"json",/*返回的数据类型*/
/*点*/ contentType:"application/json"/* 发json字符串给服务器,重点!! */
/*点*/ });
});
// 页面加载完成之后
$("#sendMore").click(function () {
alert(1234123);
var jsonPersons = [];//在js中中括号是数组,大括号是对象
jsonPersons[0]={
'id' : 100,
'name':"华仔"
};
jsonPersons[1]={
'id' : 100,
'name':"帅哥"
}
$.ajax({
url:"${pageContext.request.contextPath}/savePersons", /*请求地址*/
type:"post",/*请求方式 必须是post,服务器才能将json字符串转换为java对象*/
data:JSON.stringify(jsonPersons), /*发送给服务器的数据 json字符串*/
success:function (result) { /*成功的回调函数*/
console.log(result);
},
dataType:"json",/*返回的数据类型*/
contentType:"application/json"/* 发json字符串给服务器,需要添加 */
});
});
});
</script>
</head>
<body>
<a id="sendOne" href="#">发一个Java对象</a> <br/>
<a id="sendMore" href="#">发一个多个Java对象</a> <br/>
</body>
</html>
Controller类:
@ResponseBody
@RequestMapping(value = "/savePerson")
public Person savePerson(@RequestBody Person person){
System.out.println( " 保存person: " + person);
return person;
}
@ResponseBody
@RequestMapping(value = "/savePersons")
public List<Person> savePersons(@RequestBody List<Person> personList){
System.out.println( " 保存persons: " + personList);
return personList;
}
十一、SpringMVC运行流程总结
11.1 SpringMVC工作流程图解
11.2 SpringMVC工作流程描述
-
用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获;
-
DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI):
判断请求URI对应的映射:-
不存在:
- 再判断是否配置了mvc:default-servlet-handler:
a)如果没配置,则控制台报映射查找不到,客户端展示404错误
b) 如果有配置,则执行目标资源(一般为静态资源,如:JSP,HTML)
- 再判断是否配置了mvc:default-servlet-handler:
-
存在:
- 执行下面流程;
-
-
根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
-
DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter;
-
如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法【正向】;
-
提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
①HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息;
②数据转换:对请求消息进行数据转换。如String转换成Integer、Double等;
③数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等;
④数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中;
-
Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
-
此时将开始执行拦截器的postHandle(…)方法【逆向】;
-
根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet,根据Model和View,来渲染视图;
-
在返回给客户端时需要执行拦截器的AfterCompletion方法【逆向】;
-
将渲染结果返回给客户端;
Controller类:
```java
@ResponseBody
@RequestMapping(value = "/savePerson")
public Person savePerson(@RequestBody Person person){
System.out.println( " 保存person: " + person);
return person;
}
@ResponseBody
@RequestMapping(value = "/savePersons")
public List<Person> savePersons(@RequestBody List<Person> personList){
System.out.println( " 保存persons: " + personList);
return personList;
}
十一、SpringMVC运行流程总结
11.1 SpringMVC工作流程图解
11.2 SpringMVC工作流程描述
-
用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获;
-
DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI):
判断请求URI对应的映射:-
不存在:
- 再判断是否配置了mvc:default-servlet-handler:
a)如果没配置,则控制台报映射查找不到,客户端展示404错误
b) 如果有配置,则执行目标资源(一般为静态资源,如:JSP,HTML)
- 再判断是否配置了mvc:default-servlet-handler:
-
存在:
- 执行下面流程;
-
-
根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
-
DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter;
-
如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法【正向】;
-
提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
①HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息;
②数据转换:对请求消息进行数据转换。如String转换成Integer、Double等;
③数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等;
④数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中;
-
Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
-
此时将开始执行拦截器的postHandle(…)方法【逆向】;
-
根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet,根据Model和View,来渲染视图;
-
在返回给客户端时需要执行拦截器的AfterCompletion方法【逆向】;
-
将渲染结果返回给客户端;
本文转载于:https://blog.csdn.net/Q_771615002/article/details/109306253