超详细SpringMVC介绍学习笔记,深入底层

文章目录

一、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 /> 会自动注册:RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver  三个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 请求相应流程图

基本步骤:

  1. 客户端请求提交到DispatcherServlet
  2. 由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller
  3. DispatcherServlet将请求提交到Controller(也称为Handler);
  4. Controller调用业务逻辑处理后,返回ModelAndView
  5. DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图;
  6. 视图负责将结果显示到客户端;
    在这里插入图片描述

三、 前端控制器

DispatcherServlet 是前端控制器设计模式的实现,提供 Spring Web MVC 的集中访问点,并把请求分发到不同的控制器去处理,根据控制器处理后的结果,生成相应的响应发送到客户端。前端控制器既可以使用Filter实现(Struts2采用这种方式),也可以使用Servlet来实现(spring MVC框架)。

3.1 Servlet回顾

SpringMVC是用Servlet来实现前端控制器,所以首先来回忆一下servlet的相关知识。
Servlet生命周期的三个阶段

在Servlet容器启动后,客户首次向Servlet发送请求,Servlet容器创会建一个Servlet实例。

  1. 初始化:实例化之后紧接着执行初始化,只执行一次,调用init()方法。
  2. 服务:处理请求并响应浏览器,每次从浏览器发送请求访问此servlet,都会调用service()方法。
  3. 销毁:服务器关闭时执行销毁的方法,只执行一次,调用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 返回值的设置

显式使用请求转发 / 重定向来跳转页面:

  1. 需要在返回值前面添加 forward: 或 redirect: 字符串;
  2. 不和视图解析器的前后缀做拼接操作;
  3. 后面一定要写上完整的跳转路径
  4. 后面的地址分为相对路径和绝对路径;
  • 绝对路径 ==>> 以是斜杠打头的路径( 使用绝对路径永远不会错 )

    /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风格只需以下两点:

  1. 把请求参数加入到请求的资源地址中。
  2. 原来的增,删,改,查。使用HTTP请求方式,POST、DELETE、PUT、GET分别一一对应。

6.2 Restful风格请求地址

传统的方式:

restful风格:

6.3 GET,POST,PUT,DELETE请求

form表单标签中,设置method=”get / ”post” 可以发送get请求或post请求,但如何发送put和delete请求呢?

  1. 写一个post请求的form标签
  2. 在form表单中,添加一个额外的隐藏域_method=”PUT”或_method=”DELETE”
  3. 在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 具体实现

  1. 先编写一个类去实现HandlerInterceptor接口;
  2. 实现拦截器的方法;
  3. 到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,用于对处理器进行预处理和后处理。

  1. 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
  2. 权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;
    性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
  3. 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。
  4. 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工作流程描述

  1. 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获;

  2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI):
    判断请求URI对应的映射:

    1. 不存在:

      1. 再判断是否配置了mvc:default-servlet-handler:
        a)如果没配置,则控制台报映射查找不到,客户端展示404错误
        ​ b) 如果有配置,则执行目标资源(一般为静态资源,如:JSP,HTML)
    2. 存在:

      1. 执行下面流程;
  3. 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;

  4. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter;

  5. 如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法【正向】;

  6. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

    ①HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息;

    ②数据转换:对请求消息进行数据转换。如String转换成Integer、Double等;

    ③数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等;

    ④数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中;

  7. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;

  8. 此时将开始执行拦截器的postHandle(…)方法【逆向】;

  9. 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet,根据Model和View,来渲染视图;

  10. 在返回给客户端时需要执行拦截器的AfterCompletion方法【逆向】;

  11. 将渲染结果返回给客户端;


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工作流程描述

  1. 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获;

  2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI):
    判断请求URI对应的映射:

    1. 不存在:

      1. 再判断是否配置了mvc:default-servlet-handler:
        a)如果没配置,则控制台报映射查找不到,客户端展示404错误
        ​ b) 如果有配置,则执行目标资源(一般为静态资源,如:JSP,HTML)
    2. 存在:

      1. 执行下面流程;
  3. 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;

  4. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter;

  5. 如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法【正向】;

  6. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

    ①HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息;

    ②数据转换:对请求消息进行数据转换。如String转换成Integer、Double等;

    ③数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等;

    ④数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中;

  7. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;

  8. 此时将开始执行拦截器的postHandle(…)方法【逆向】;

  9. 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet,根据Model和View,来渲染视图;

  10. 在返回给客户端时需要执行拦截器的AfterCompletion方法【逆向】;

  11. 将渲染结果返回给客户端;

本文转载于:https://blog.csdn.net/Q_771615002/article/details/109306253

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值