Spring MVC(基于spring4.0)笔记

Spring MVC即Spring的web模块

MVC(旧)

M:Model模型;封装和映射数据(javaBean) V: View视图;界面显示工作(.jsp) C:Controller控制器;控制整个网站的跳转逻辑(Servlet) MVC提倡:每一层只编写自己的东西,不写任何其他代码

控制器{
    调用业务逻辑处理
    调整到某个页面
}
分层就为了解耦,解耦为了维护方便和分工合作

Spring MVC

POJO:Plain Old Java Object(普通的Java对象,不是JavaBean、EntityBean、SessionBean);

  1. 导包 1)SpringMVC是Spring的web模块;所有模块的运行都是依赖核心模块(IOC模块)
    • 核心容器模块 beans、expression、context、core
    • web模块 web、webmvc(spring-webmvc-4.0.0.RELEASE.jar)
  2. 写配置
  • 在web.xml中编写一个前端控制器
    <!--  配置前端控制器DispacherServlet-->
    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
<!--      contextConfigLocation:指定Springmvc配置文件位置-->
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
<!--    servlet启动加载,servlet原本是第一次访问创建对象-->
<!--    load-on-startup:服务器启动的时候创建对象;值越小优先级越高,越先创建对象-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springDispatcherServlet</servlet-name>
        <!--
            /*和/都是拦截所有请求;  /:会拦截所有请求,但是不会拦截*.jsp;能保证jsp访问正常
            /*的范围更大:还会拦截到*.jsp这些请求;一旦拦截jsp页面就不能显示了;
        -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
  • 需要指定spring的配置文件springmvc.xml,同时在spring的配置文件中配置一个视图解析器(拼接字符串,避免写太长的路径)
    <!--    配置一个视图解析器;能帮我们拼接页面地址-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
  1. 测试
    1. 运行流程说明: 1)客户端点击链接会发送http://localhost:8080/1_SpringMVC_helloWorld/hello请求 2)来到tomcat服务器 3)SpringMVC的前端控制器收到所有请求; 4)来看请求地址和@RequestMapping标注的那个匹配,来找到到底使用哪个类的哪个方法处理 5)前端控制器找到了目标处理器和目标方法,直接利用返回执行目标方法; 6)方法执行完成以后会有一个返回值;Spinrgmvc认为这个返回值就是要去的页面地址 7)拿到方法返回值以后;用视图解析器进行拼串得到完整的页面地址 8)拿到页面地址,前端控制器帮我们转发到页面

    2. @RequestMapping: 就是告诉springmvc这个方法用来处理什么请求; /可以省略,即使省略了,也是默认从当前项目下开始;

    3. 如果在web.xml不指定spring配置文件的位置 指定:运行会寻找/WEB-INF/springDispatcherServlet-servlet.xml 不指定:会默认去找/WEB-INF/xxx-servlet.xml 不指定时为了不报错误,就在/WEB-INF/下创建一个名叫 前端控制器名(需要跟配置DispatcherServlet的名一致)-servlet.xml,即若在web.xml中已经配置好一个前端控制名叫springmvc的话,需要在/WEB-INF/下创建一个名叫springmvc-servlet.xml的文件(原来的配置文件内容可以放在这个文件,且可以删除spring的配置文件了)

    4. url-pattern问题 /:拦截所有请求,不包括拦截jsp页面, /*:拦截所有请求

      处理*.jsp是tomcat做的事;所有项目的小web.xml都是继承于大web.xml DefaultServlet是tomcat中处理静态资源的 除了jsp,servlet外剩下的都是静态资源; index.html:静态资源,tomcat就会在服务器下找到这个资源并返回; 我们前端控制器的/禁用了tomcat服务器中的DefaultServlet

      1. 服务器的大web.xml中有一个DefaultServlet是url-pattern=/
      2. 我们的配置中前端控制器url-pattern=/,相当于重写了默认的。 因此静态资源会来到DispatcherServlet(前端控制器)看哪个方法的requestmapping是这个index.html
      3. 为什么jsp可以访问 因为我们配置/是没有覆盖服务器中JspServlet的配置
      4. /* 直接就是拦截所有请求;我们写/也是为了迎合后来Rest风格的URL地址

1 @RequestMapping注解

  1. 标注在类上:为当前类所有的方法的请求地址指定一个基准路径
  2. 标注在方法上见上面
  3. 注解属性@RequestMapping(value="/hello",method=xxx,.......)
    • method:限定请求方式 HTTP协议中的所有请求方式: 【GET】,HEAD,【POST】,PUT,PATCH,DELETE,OPTIONS,TRACE method=RequestMethod.POST--》只接受这种类型的请求,默认是什么都可以
    • params:规定请求参数
      • param1:表示请求必须包含名为param1的请求参数 eg:params={"username"}-->发送请求的时候必须带上一个名为username的参数
      • !param1:表示请求不能包含名为param1的请求参数 eg:params={"!username"}-->发送请求的时候必须不携带上一个名为username的参数
      • param1 != value1:表示请求包含名为param1的请求参数,但其值不能为value eg:params={"username!=123"}-->发送请求的时候,携带的username值必须不为123(不带username或者username不是123)
      • {"param1=value1","param2"}:请求必须包含名为param1和param2的两个请求参数,且param1的值为value1 eg:params={"username!=123","pwd","!age"} 请求参数必须满足以上规则
    • headers:规定请求头;也和params一样能写简单的表达式 如:headers={"User-Agent=xxxx"}-->浏览器信息
    • consumes:只接受内容类型是哪种的请求,规定请求头中的Content-Type
    • produces:告诉浏览器返回的内容类型是什么,给响应头中加上Content-Type:text/html;charset=utf-8

1.1 @RequestMapping-ant风格的url(自认为不重要)

URL地址可以写模糊的通配符 ?:能替代任意一个字符 *:能替代任意多个字符,和一层路径 **:能替代多层路径 精确的匹配就是上面的那些 模糊匹配eg: @RequestMapping("/antTest0?"):请求/antTest0后接一个字符都可以,只写到0的请求也不行

2 @PathVariable注解

3 Rest

REST:即 Representational State Transfer。(资源)表现层状态转化。

系统希望以非常简洁的URL地址来发送请求; 怎样表示对一个资源的增删改查请求方式来区分 以前的请求: /getBook?id=1 :查询图书 /deleteBook?id=1 :删除1号图书 /updateBook?id=1 :更新1号图书 /addBook : 添加图书


Rest风格 url地址这么起名:/资源名/资源标识符 /book/1 : GET--->查询1号图书 /book/1 :PUT--->更新1号图书 /book/1 : DELETE--->删除1号图书 /book :POST-->添加图书


从页面发起PUT、DELETE形式的请求??Spring提供了对Rest风格的支持

  1. springmvc中有一个Filter(HiddenHttpMethodFilter);他可以把普通的请求转化为规定形式的请求;配置这个filter
  2. 如何发其他形式请求?
    1. 创建一个post类型的表单
    2. 表单项中携带一个_method的参数
    3. 这个_method的值就是DELETE、PUT(大小写都可) 注意:tomcat8以上报错(高版本tomcat有jsp约束): 解决:在jsp页面的头部添加-》isErrorPage="true" 如果仍然解决不了,可以在对应的controller的方法上添加注解@ResponseBody

SpringMVC如何获取请求带来的各种信息 默认方式获取请求参数: 1)直接给方法入参上写一个和请求参数名相同的变量。这个变量就来接受请求参数的值 带:有值 ; 没带:null

4 @RequestParam:获取请求参数的

@RequestParam("user")String username == username = request.getParameter("user"); 使用该注解时,user默认是必须在请求参数中的 该注解可以配置的属性值有:

  • value:指定要获取的参数key
  • require:这个参数是否必须的
  • defaultValue:默认值。没带默认是null,可以指定其他默认值 eg:@RequestParam(value="user",require=false,defaultValue="你没带参数")String username

@RequestParam与@PathVariable的区别: ① @RequestParam是获取请求参数的值,即?后面的值 ② @PathVariable是获取路径上占位符表示的值

5 @RequestHeader:获取请求头中某个key的值

以前获取请求头:request.getHeader("User-Agent"); eg:@RequestHeader("User-Agent")String userAgent userAgent = request.getHeader("User-Agent") 如果请求头没有这个属性,会报错 上面的注解属性也可以放在该注解中

6 @CookieValue:获取某个cookie值

以前:

Cookie[] cookies = request.getCookies()
for(Cookie c:cookies){
    if(c.getName().equals("JSESSIONID")){
        String cv = c.getValue();
    }
}

现在直接在参数前面加@CookieValue("JSESSIONID")来获取cookie的某个值

传入pojo,自动封装成对象

springmvc会按照请求参数名和pojo属性名进行自动匹配,自动为该对象填充属性值: 1)将pojo中的每一个属性,从request参数中尝试获取出来,并封装即可; 2)还可以级联封装 3)请求参数的参数名和对象中的属性名一一对应就行

springmvc同样支持在参数上写原生API:

  • HttpServletRequest,HttpSession,HttpServletResponse

  • java.security.Principal

  • Locale

  • InputStream,OutputStream(字节流) (ServletInputStream inputStream = request.getInputStream()😉 (ServletOutputStream outputStream = response.getOutputStream()😉

  • Reader,Writer(字符流) (BufferedReader reader = request.getReader()😉 (PrintWriter writer = response.getWriter()😉

乱码问题

提交的数据可能有乱码: ① 请求乱码

  • GET请求:改server.xml;在8080端口处URIEncoding="utf-8"
  • POST请求:
    在第一次获取请求参数之前设置 request.setCharacterEncoding("UTF-8"); 自己写一个filter,而springmvc有这个filter:CharacterEncoding 在web.xml中配置该filter:
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <!-- encoding:指定解决POST请求乱码-->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <!-- forceEncoding:顺手解决响应乱码;response.setCharacterEncoding(this.encoding)-->
        <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>
    
    注意: 字符编码的Filter需在其他Filter之前,因为该Filter是第一次获取请求参数起作用

② 响应乱码

  • response.setContentType("text/html;charset=utf-8");

7 数据输出

springmvc除了在方法上传入原生的request和session外还能怎么样把数据带给页面

  1. 可以在方法处传入Map、或者Model或者ModelMap 给这些参数里面保存的所有数据都会放在域中。可以在页面获取 注意:使用该方法获取数据,只能在请求域中获取:<span data-formula="{requestScope.msg}

    • 关系: Map,Model,ModelMap:最终都是BindingAwareModelMap在工作; 相当于给BindingAwareModelMap中保存的东西都会被放在请求域中;
  2. 方法的返回值可以变为ModelAndView类型 既包含视图信息(页面地址)也包含模型数据(给页面带的数据); 而且数据是放在请求域中;

  3. springmvc提供一种可以临时给session域中保存数据的方式;(不推荐) 使用一个注解 @SessionAttributes(只能标在类上) @SessionAttributes(value="msg"):给BindingAwareModelMap中保存的数据,或者ModelAndView中的数据,同时给session中饭一份; value指定保存数据时要给session中放的数据的key

    • value="msg":只要保存的是这种key的数据,给session中放一份
    • types={String.class}:只要保存的是这种类型的数据,给session中也放一份

    推荐不用,因为可能会引发异常 给session中放数据请使用原生api

  4. @ModelAttribute注解 作用:在修改数据时,不希望那些不能修改的字段显示在页面中能修改的地方。而springmvc会将表单提交的数据自动封装一个对象,会导致一些数据为null(因为是new一个新的对象,页面没提交的字段会null)。使用该注解,在方法入参时标注,入参的对象就会放到数据模型中。mybatis的动态sql更好解决

    解决:

    • 不希望springmvc创建一个新对象,接收到页面提交的数据进行覆盖(导致null)
    • 希望通过id(主键)从数据库中拿到该对象,页面提交过来的字段进行覆盖,没提交的字段保持原值,这样保证不会有null。
    1. 该注解标注在方法上,会提前于目标方法运行,也就是说我们可以提前查出数据库中的信息
    2. 在方法上标参数(Map...)保存数据(map.put("key",..)),方便下一个方法还能使用
    3. 告诉springmvc别new了,使用2的数据来操作,需要在目标方法的参数上添加该注解,key是map.put("key")里面的key

    map是BindingAwareModelMap,也就是说可以在请求域中获取出来

8 Spring MVC源码

粗略分析源码的研究方向: 最终是调用到DispatcherServlet类中的doDispatch()方法中 " aria-hidden="true">{requestScope.msg} - 关系: Map,Model,ModelMap:最终都是BindingAwareModelMap在工作; 相当于给BindingAwareModelMap中保存的东西都会被放在请求域中; 2. 方法的返回值可以变为ModelAndView类型 既包含视图信息(页面地址)也包含模型数据(给页面带的数据); 而且数据是放在请求域中; 3. springmvc提供一种可以临时给session域中保存数据的方式;(不推荐) 使用一个注解 @SessionAttributes(只能标在类上) @SessionAttributes(value="msg"):给BindingAwareModelMap中保存的数据,或者ModelAndView中的数据,同时给session中饭一份; value指定保存数据时要给session中放的数据的key

- value="msg":只要保存的是这种key的数据,给session中放一份
- types={String.class}:只要保存的是这种类型的数据,给session中也放一份

推荐不用,因为可能会引发异常
给session中放数据请使用原生api
  1. @ModelAttribute注解 作用:在修改数据时,不希望那些不能修改的字段显示在页面中能修改的地方。而springmvc会将表单提交的数据自动封装一个对象,会导致一些数据为null(因为是new一个新的对象,页面没提交的字段会null)。使用该注解,在方法入参时标注,入参的对象就会放到数据模型中。mybatis的动态sql更好解决

    解决:

    • 不希望springmvc创建一个新对象,接收到页面提交的数据进行覆盖(导致null)
    • 希望通过id(主键)从数据库中拿到该对象,页面提交过来的字段进行覆盖,没提交的字段保持原值,这样保证不会有null。
    1. 该注解标注在方法上,会提前于目标方法运行,也就是说我们可以提前查出数据库中的信息
    2. 在方法上标参数(Map...)保存数据(map.put("key",..)),方便下一个方法还能使用
    3. 告诉springmvc别new了,使用2的数据来操作,需要在目标方法的参数上添加该注解,key是map.put("key")里面的key

    map是BindingAwareModelMap,也就是说可以在请求域中获取出来

8 Spring MVC源码

粗略分析源码的研究方向: 最终是调用到DispatcherServlet类中的doDispatch()方法中

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 {
                //1. 检查是否文件上传请求
				processedRequest = checkMultipart(request);
				multipartRequestParsed = processedRequest != request;

				// Determine handler for the current request.
                //2.根据当前的请求地址找到哪个类能来处理
				mappedHandler = getHandler(processedRequest);
                
                //3.如果没有找到那个处理器(控制器)能处理这个请求,抛异常
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
                //拿到这个类(控制器)的所有方法的适配器(反射工具AnnotationMethodHandlerAdapter)
				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.处理(控制器)的方法被调用
                    //控制器(Controller),处理器(Handler)
                    //5.适配器来执行目标方法;将目标方法执行完成后的返回值作为视图名,设置保存到ModelAndView中
                    //目标方法无论怎么写,最终适配器执行完成以后都会将执行后的信息封装成ModelAndView
					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;
			}
            //转发到目标页面;
            //6、根据方法最终执行完成后封装的ModelAndView;转发到对应页面,而且ModelAndView中的数据可以从请求域中获取
			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源码分析及总结

  1. 所有请求过来,DispatcherServlet收到请求
  2. 调用doDispatch()方法进行处理
    1. getHandler():根据当前请求地址找到能处理这个请求的目标处理器类(处理器)
      1. 根据当前请求在HandlerMapping中找到这个请求的映射信息,获取到目标处理器类
    2. getHandlerAdapter():根据当前处理器类获取到能执行这个处理器方法的适配器;
      1. 根据当前处理器类,找到当前类从HandlerAdapter(适配器)
    3. 使用刚才获取到的适配器(AnnotationMethodHandlerAdapter)执行目标方法;
    4. 目标方法执行后会返回一个ModelAndView对象
    5. 根据ModelAndView的信息转发到具体的页面,并可以在请求域中取出ModelAndView中的模型数据

  1. getHandler()细节:怎么根据当前请求就能找到哪个类能来处理 getHandler()会返回目标处理器类的执行链(mappedHandler=getHandler(processedRequest))-->HandlerExecutionChain;
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //HandlerMapping:处理器映射--》保存了每一个处理器能处理哪些请求的映射信息
		for (HandlerMapping hm : this.handlerMappings) {
			if (logger.isTraceEnabled()) {
				logger.trace(
						"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
			}
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}

handlerMap:ioc容器启动创建Controller对象的时候扫描每个处理器都能处理什么请求,保存在HandlerMapping的handlerMap属性中; 下一次请求过来,就来看哪个HandlerMapping中有这个请求映射信息就行了。 4. 处理器找到后还要找其对应的适配器,适配器执行目标方法

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
				logger.trace("Testing handler adapter [" + ha + "]");
			}
			if (ha.supports(handler)) {
				return ha;
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

AnnotationMethodHandlerAdapter:能解析注解方法的适配器; 处理器类中只要有标了注解的这些方法就能用; 5. DispatcherServlet中有几个应用类型的属性;Springmvc的九大组件(都是接口) spring mvc 在工作的时候,关键位置都是由这些组件完成的; 共同点:九大组件全部都是接口;接口就是规范;提供了非常强大的扩展性;

/** 文件上传解析器 */
	private MultipartResolver multipartResolver;

	/** 区域信息解析器;和国际化有关 */
	private LocaleResolver localeResolver;

	/** 主题解析器;强大的主题效果更换 */
	private ThemeResolver themeResolver;

	/** Handler映射信息;HandlerMapping */
	private List<HandlerMapping> handlerMappings;

	/** Handler的适配器 */
	private List<HandlerAdapter> handlerAdapters;

	/** springmvc强大的异常解析功能;异常解析器 */
	private List<HandlerExceptionResolver> handlerExceptionResolvers;

	/** RequestToViewNameTranslator used by this servlet */
	private RequestToViewNameTranslator viewNameTranslator;

	/** FlashMap+Manager:springmvc中允许重定向携带数据的功能 */
	private FlashMapManager flashMapManager;

	/** 视图解析器; */
	private List<ViewResolver> viewResolvers;

DispatcherServlet中九大组件初始化的地方:onRefresh()里面

protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

初始化HandlerMapping部分:

private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				OrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}

		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

组件的初始化: 有些组件在容器中使用类型找的,有些组件是使用id找的; 去容器中找这个组件,如果没有找到就用默认的配置;

中间部分未看(看不懂)。。。。。未完。。。

源码总结

  1. 运行流程简单版
  2. 确定方法每个参数的值
    1. 标注解:保存注解的信息;最终得到这个注解应该对象解析的值;
    2. 没标注解:
      1. 看是否是原生api;
      2. 看是否是Model或者是Map,xxxx
      3. 都不是,看是否是简单类型
      4. 给attrName赋值;attrName(参数标了@ModelAttribute("")就是指定的,没标就是"")
        • 确定自定义类型参数:
          • attrName使用参数的类型首字母小写;或者使用之前@ModelAttribute("")的值
          • 先看隐含模型中有每个这个attrName作为key对应的值,如果有就从隐含模型中获取并赋值
          • 看是否是@SessionAttribute(value="haha");标注的属性,如果是从session中拿
          • 如果拿不到就抛异常
        • 不是@SessionAttribute标注的,利用反射创建一个对象;
      5. 拿到之前创建好的对象,使用数据绑定器(WebDataBinder)将请求中的每个数据绑定到这个对象中

9 视图解析

@RequestMapping("/hello")
    public String hello(){
        //想要到webapp下的hello.jsp页面,默认是到/WEB-INF/pages/hello.jsp
        //2种方法实现
        //一、相对路径:../../hello
        return "../../hello";
    }

    @RequestMapping("/handle01")
    public String handle01(){
        System.out.println("handle01...转发");
        //转发到一个页面
        //  /hello.jsp:转发当前项目的hello

        // 一定要加上/,如果不加/就是相对路径。容易出问题;

        //  forward:/hello.jsp不会进行拼串(转发和重定向不会走视图解析)
        return "forward:/hello.jsp";
    }
    
    //handle02转发回handle01
    @RequestMapping("/handle02")
    public String handle02(){

        System.out.println("handle02");
        return "forward:/handle01";
    }

    //重定向到hello.jsp页面
    //转发 --》forward:转发的路径
    //重定向--》redirect:重定向的路径
    //      /hello.jsp:代表就是从当前项目下开始;Springmvc会为路径自动的拼接上项目名

    //  原生的servlet重定向/路径需要加上项目名才能成功
    //  response。sendRedirect("/hello.jsp")
    @RequestMapping("/handle03")
    public String handle03(){
        System.out.println("handle03");
        return "redirect:/hello.jsp";
    }

    @RequestMapping("/handle04")
    public String handle04(){
        System.out.println("handle04");
        return "redirect:/handle03";
    }
  1. 方法执行后的返回值会作为页面地址参考,转发或重定向到页面
  2. 视图解析器可能会进行页面地址的拼串。(有前缀不拼串,即转发或重定向不进行视图解析)

9.1 视图解析源码分析

  1. 任何方法的返回值,最终都会被包装成ModelAndView对象
  2. 来到页面的方法 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 视图渲染流程:将域中的数据在页面展示;页面就是用来渲染模型数据的;
  3. 调用render(mv, request, response);渲染页面
  4. View与ViewResolver; ViewResolver的作用是根据视图名(方法的返回值)得到View对象;
  5. 怎么根据方法的返回值(视图名)得到View对象?
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
			HttpServletRequest request) throws Exception {

        //遍历所有的ViewResolver
		for (ViewResolver viewResolver : this.viewResolvers) {
            //viewResolver视图解析器更加方法的返回值,得到一个View对象;
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				return view;
			}
		}
		return null;
	}

resolverViewName实现

@Override
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		if (!isCache()) {
			return createView(viewName, locale);
		}
		else {
			Object cacheKey = getCacheKey(viewName, locale);
			View view = this.viewAccessCache.get(cacheKey);
			if (view == null) {
				synchronized (this.viewCreationCache) {
					view = this.viewCreationCache.get(cacheKey);
					if (view == null) {
                        //  ||
                        //  \/
						// Ask the subclass to create the View object.
                        //根据方法的返回值创建出视图View对象
						view = createView(viewName, locale);
						if (view == null && this.cacheUnresolved) {
							view = UNRESOLVED_VIEW;
						}
						if (view != null) {
							this.viewAccessCache.put(cacheKey, view);
							this.viewCreationCache.put(cacheKey, view);
							if (logger.isTraceEnabled()) {
								logger.trace("Cached view [" + cacheKey + "]");
							}
						}
					}
				}
			}
			return (view != UNRESOLVED_VIEW ? view : null);
		}
	}

创建View对象

@Override
	protected View createView(String viewName, Locale locale) throws Exception {
		// If this resolver is not supposed to handle the given view,
		// return null to pass on to the next resolver in the chain.
		if (!canHandle(viewName, locale)) {
			return null;
		}
		// Check for special "redirect:" prefix.
		if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
			String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
			RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
			return applyLifecycleMethods(viewName, view);
		}
		// Check for special "forward:" prefix.
		if (viewName.startsWith(FORWARD_URL_PREFIX)) {
			String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
			return new InternalResourceView(forwardUrl);
		}
		// Else fall back to superclass implementation: calling loadView.
        //如果没有前缀就使用父类默认创建一个View;
		return super.createView(viewName, locale);
	}

返回View对象: 视图解析器得到View对象的流程就是,所有配置的视图解析器都来尝试根据视图名(返回值)得到View(视图)对象;如果能得到就返回,得不到就换下一个视图解析器; 调用View对象的render();

@Override
	public void render(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);
		}

		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);

		prepareResponse(request, response);
        //渲染要给页面输出的所有数据
		renderMergedOutputModel(mergedModel, request, response);
	}

InternalResourceView有这个方法renderMergedOutputModel

@Override
	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// Determine which request handle to expose to the RequestDispatcher.
		HttpServletRequest requestToExpose = getRequestToExpose(request);

		// Expose the model object as request attributes.
        //将模型中的数据放在请求域中
		exposeModelAsRequestAttributes(model, requestToExpose);

		// Expose helpers as request attributes, if any.
		exposeHelpers(requestToExpose);

		// Determine the path for the request dispatcher.
		String dispatcherPath = prepareForRendering(requestToExpose, response);

		// Obtain a RequestDispatcher for the target resource (typically a JSP).
		RequestDispatcher rd = getRequestDispatcher(requestToExpose, 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 already included or response already committed, perform include, else forward.
		if (useInclude(requestToExpose, response)) {
			response.setContentType(getContentType());
			if (logger.isDebugEnabled()) {
				logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
			}
			rd.include(requestToExpose, 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(requestToExpose, response);
		}
	}

一句话: 视图解析器只是为了得到视图对象;视图对象才能真正的转发(将模型数据全部放在请求域中)或者重定向到页面 视图对象才能真正的渲染视图

10 Jstl(没必要)

  1. 导包导入了jstl的时候会自动创建为一个jstlView;可以快速方便的支持国际化功能;
  2. 可以支持快速国际化;
    1. javaWeb国际化步骤:
      1. 得到一个Locale对象
      2. 使用ResourceBundle绑定国际化资源文件
      3. 使用ResourceBundle.getString("key")获取到国际化配置文件中的值
      4. web页面的国际化,fmt标签库来做; <fmt:setLocale> <fmt:setBundle> <fmt:message>
    2. 有了JstlView以后
      1. 让spring管理国际化资源就行
    <!-- 让springmvc管理国际化资源文件;配置一个资源文件管理器,id必须叫“messageSource” -->
    <bean id = "messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <!-- basename指定基础名 -->
        <property name="basename" value="i18n"></property>
    </bean>
    
    1. 直接去页面使用fmt:message;导入jstl标签库 一定要过springmvc的视图解析流程,人家会创建一个jstlView帮你快速国际化; 也不能写forward:

10.1 将请求映射到一个页面

在spring配置文件中有mvc标签:

<!-- 
    使用前提:已经配置好视图解析器(InternalResourceViewResolver)
    path="":指定哪个方法
    view-name="":指定映射给哪个视图
    view-name中不要前缀(forward:),这样保证走了springmvc的流程
    使用该功能会导致其他请求不行了,解决方法如下(添加注解驱动)
-->
<!-- view-controller的映射-》不用写过多的方法跳转到页面 -->
<mvc:view-controller path="toLogin" view-name="login"/>
<!-- 开启mvc注解驱动模式-》解决其他请求不行的问题 -->
<mvc:annotation-driven></mvc:annotation-driven>

扩展:加深视图解析器和视图对象; 视图解析器根据方法的返回值得到视图对象; 多个视图解析器都会尝试能否得到视图对象; 视图对象不同就可以具有不同功能;

①让我们的视图解析器工作; ②得到我们的视图对象 ③我们的视图对象自定义渲染逻辑

自定义视图和视图解析器的步骤

  1. 编写自定义的视图解析器,和视图实现类
  2. 视图解析器必须放在ioc容器中

利用springmvc做一个CRUD(增删改查)符号Rest风格的; C: Create:创建 R: Retrieve:查询 U: Update:更新 D: Delete:删除

11 数据转换&数据格式化&数据校验

Springmvc封装自定义类型对象的时候,javabean要和页面提交的数据进行一一绑定 ① 页面提交的数据都是字符串 ② Integeer age,Date birth; employName=zhangsan&age=18&gender=1

牵扯到以下操作:

  1. 数据绑定期间的数据类型转换。String--Integer
  2. 数据绑定期间的数据格式化问题。birth=2022-12-1--》Date 2022/12/1 2022.12.1
  3. 数据校验 提交的数据必须是合法的
    1. 前端校验:js+正则表达式
    2. 后端校验:重要数据也是必须的

WebDataBind:数据绑定器负责数据绑定工作 数据绑定期间产生的类型转换、格式化、数据校验等问题;

  • ConversionService组件:负责数据类型的转换以及格式化功能
    • ConversionService中有非常多的converter,不同类型的转换和格式化用他自己的converter
  • Validator负责数据校验工作
  • BindingResult负责保存以及解析数据绑定期间数据校验产生的错误

在写自定义类型转换器时需要知道:

  1. Converter是ConversionService中的组件,你的Converter得放进ConversionService中;
  2. 将WebDataBinder中的ConversionService设置成我们这个加了自定义类型转换器的ConversionService

11.1 数据绑定流程:

自定义类型转换步骤:

  1. ConversionService:是一个接口,里面有Convertor(转换器)进行工作;实现Converter接口,写一个自定义的类型转换器;

  2. 配置出ConversionService

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <!--converters转换器中添加我们自定义的类型转换器-->
        <property name="converters">
            <set>
                <bean class="com.zhm.component.MyStringToEmployeeConverter"></bean>
            </set>
        </property>
    </bean>
  1. 让Springmvc用我们的ConversionService

总结:

  1. 实现Converter接口,做一个自定义类型的转换器
  2. 将这个Converter配置在ConversionService中
  3. 告诉Springmvc使用这个ConversionService

11.2 关于<mvc:annotation-driver/>的一些事

① 关于<mvc:default-servlet-handler/>与<mvc:annotation-driver/>的一些现象

  1. 都没有配,则动态资源(@RequestMapping映射的资源)能访问,静态资源(.html,.js,.img)不能访问 动态资源能访问:DefaultAnnotationHandlerMapping中的handlerMap中保存了每一个资源的映射信息; 静态资源不能访问:就是handlerMap中没有保存静态资源的映射 HandlerAdapter:方法执行的适配器; AnnotationMethodHandlerAdapter(过时):帮我们执行目标方法
  2. 加上<mvc:default-servlet-handler/>,静态ok,动态不行
    • 动态不能访问原因:DefaultAnnotationHandlerMapping没有了,用SimpleUrlHandlerMapping替换了,他的作用是将所有请求直接交个tomcat,原生的处理是servlet调用doGet或doPost处理请求。但springmvc只配置了一个前端控制器,无法处理。若一个/emp访问,tomcat不知道如何处理
    • 静态能访问的原因:SimpleUrlHandlerMapping把所有请求都映射给tomcat
HandlerAdapter中没有了AnnotationMethodHandlerAdapter(请求都交给tomcat了)
![](2022-07-21-11-42-47.png)
  1. 都加上,静态和动态都能访问 RequestHandlerMapping:动态资源可以访问 handleMethods属性保存了每一个请求用哪个方法来处理 SimpleUrlHandlerMapping:将请求直接交给tomcat,有这个,静态资源能访问; HandlerAdapter: 原来的AnnotationMethodHandlerAdapter被换成RequestMappingHandlerAdapter

11.3 格式化

指定使用某种格式化数据时添加注解@DateTimeFormat(pattern = "yyyy-MM-dd") ConversionServiceFactoryBean:创建的ConversionService组件时没有格式化器存在的 以后写自定义类型转换器的时候,就使用FormattingConversionServiceFactoryBean来注册,即具有类型转换还有格式化功能

11.4 数据校验

只做前端校验是不安全的; 在重要数据一定要加上后端验证;

  1. 可以写程序将我们每一个数据取出进行校验,若失败直接来到添加页面,提示其重新填写;
  2. springmvc;可以jsr303来做数据校验 JDBC:规范---实现(各个厂商的驱动包) JSR303:规范----Hibernate Validator(第三方校验框架)
  3. 如何快速的进行后端校验
    1. 导入校验框架的jar包 hibernate-validator-5.0.0.CR2.jar hibernate-validator-annotation-processor-5.0.0.CR2.jar classmate-0.8.0.jar jboss-logging-3.1.1.GA.jar validation-api-1.1.0.CR1.jar maven工程直接添加依赖:
    2. 只需要给javaBean的属性添加上校验注解
    3. 在springmvc封装对象的时候,告诉springmvc这个JavaBean需要校验 @Valid注解:后面的bean需要进行校验;校验成功与否,可添加BindingResult在bean的后面, result就是封装前一个bean的校验结果;
    4. 获取校验错误信息(原生,错误信息放请求域中;方案2:直接在注解上标message="提示信息") getFieldErrors():获取所有错误信息 getDefaultMessage():获取错误的信息 getField():获取错误的字段名 在页面获取错误信息:<span data-formula="{errrorInfo.lastName}
      • 国际化定制自己的错误信息显示
        • 编写国际化的文件:errors_zh_CN.properties,errors_en_US.properties key是有规定的,不能乱写;每一个字段发生错误以后都会有自己的错误代码;国际化文件中错误消息的key必须对应一个错误代码
        • 让springmvc管理国际化资源文件 " aria-hidden="true">{errrorInfo.lastName}
      • 国际化定制自己的错误信息显示
        • 编写国际化的文件:errors_zh_CN.properties,errors_en_US.properties key是有规定的,不能乱写;每一个字段发生错误以后都会有自己的错误代码;国际化文件中错误消息的key必须对应一个错误代码
        • 让springmvc管理国际化资源文件
          <!--    管理国际化资源文件-->
          <bean id="messageSource" class="org.springframework.context.support.    ResourceBundleMessageSource">
              <property name="basename" value="errors"></property>
          </bean>
          

11.5 ajax

  1. springmvc快速的完成ajax功能
    1. 返回的数据是json就ok
    2. 页面用<span data-formula=".ajax();
  2. 原生Javaweb
    1. 导入GSON
    2. 返回的数据用GSON转成json
    3. 写出去
  3. springmvc-ajax
    1. 导包 jackson-annotations-2.1.5.jar jackson-core-2.1.5.jar jackson-databind-2.1.5.jar
    2. 写配置 无
    3. 测试 在目标方法上添加注解@ResponseBody,可以将对象自动的转为json格式的数据

说明: @RequestBody:接受json数据,封装为对象 @ResponseBody:可以把对象转为json数据,返回给浏览器;非对象时,直接将目标方法的返回值直接放在响应体中 参数位置写HttpEntity<String> str,比@RequestBody更强,可以拿到请求头;而@RequestHeader()只能拿到某一个请求头

12 文件上传

  1. 文件上传(原生)
    1. 文件上传表单准备;enctype="multipart/form-data"

    2. 导入fileuplooad包 commons-fileupload-1.2.1.jar commons-io-2.0.jar

    3. javaweb: object = new FileItemDiskFactory(); ServletFileUpload upload = new ServletFileUpload(object);

      List

      springmvc:只要给springmvc配置文件中编写一个配置,配置文件上传解析器(MultipartResolver) " aria-hidden="true">.ajax();

  2. 原生Javaweb
    1. 导入GSON
    2. 返回的数据用GSON转成json
    3. 写出去
  3. springmvc-ajax
    1. 导包 jackson-annotations-2.1.5.jar jackson-core-2.1.5.jar jackson-databind-2.1.5.jar
    2. 写配置 无
    3. 测试 在目标方法上添加注解@ResponseBody,可以将对象自动的转为json格式的数据

说明: @RequestBody:接受json数据,封装为对象 @ResponseBody:可以把对象转为json数据,返回给浏览器;非对象时,直接将目标方法的返回值直接放在响应体中 参数位置写HttpEntity<String> str,比@RequestBody更强,可以拿到请求头;而@RequestHeader()只能拿到某一个请求头

12 文件上传

  1. 文件上传(原生)
    1. 文件上传表单准备;enctype="multipart/form-data"

    2. 导入fileuplooad包 commons-fileupload-1.2.1.jar commons-io-2.0.jar

    3. javaweb: object = new FileItemDiskFactory(); ServletFileUpload upload = new ServletFileUpload(object);

      List

      springmvc:只要给springmvc配置文件中编写一个配置,配置文件上传解析器(MultipartResolver)

      <!--    配置文件上传解析器-->
       <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
           <property name="maxUploadSize" value="#{1024*1024*20}"></property>
       <!--        设置默认编码-->
           <property name="defaultEncoding" value="utf-8"></property>
       </bean>
      
    4.文件上传请求处理 在处理器方法上写一个 @RequestParam("headerimg") MultipartFile file,封装当前文件的信息,可以直接保存

13 拦截器

Springmvc提供了拦截器机制;允许运行目标方法之前进行一些拦截工作,获取目标方法运行之后进行一些其他处理; Filter是javaweb提出来的; HandlerInterceptor:springmvc preHandle:在目标方法运行之前调用;返回boolean;return true,(chain.doFilter())放行;return false,不放行 postHandle:在目标方法运行之后调用-->目标方法调用之后 afterCompletion:在请求整个完成之后;来到目标页面之后;chain.doFilter()放行;资源响应之后; 须知:

  1. 拦截器是一个接口
  2. 实现HandlerInterceptor接口
  3. 配置拦截器(单个)
<!--    配置拦截器-->
    <mvc:interceptors>
        <!--1.配置某一个拦截器-->
        <!--默认拦截所有请求-->
        <bean class="com.zhm.controller.MyFirstInterceptor"></bean>
        <!--2.配置某个拦截器更详细的信息-->
        <!--<mvc:interceptor>
            拦截test01请求
            <mvc:mapping path="/test01"/>
            <bean class="com.zhm.controller.MyFirstInterceptor"></bean>
        </mvc:interceptor>-->
    </mvc:interceptors>
  1. 拦截器的运行流程(正常) MyFirstInterceptor....preHandle...(目标方法运行前) test01...(目标方法运行) MyFirstInterceptor....postHandle...(目标方法运行之后) success.jsp...(资源响应) MyFirstInterceptor....afterCompletion...(资源响应之后运行) 1.若preHandle()不放行就没有以后的流程 2.只要放行了,afterCompletion()都会执行;

多个拦截器运行流程

  1. 正常: MyFirstInterceptor....preHandle... MySecondInterceptor...preHandle test01... MySecondInterceptor...postHandle MyFirstInterceptor....postHandle... success.jsp... MySecondInterceptor...afterCompletion MyFirstInterceptor....afterCompletion...
  2. 异常(1放行,2不放行): 1 preHandle-->2 preHandle-->1 afterCompletion 也就是说哪一块不放行从此以后都没有; MySecondInterceptor不放行,但是他前面已经放行了的拦截器的afterCompletion总会执行;

流程:filter的流程 拦截器的preHandle:是按照顺序执行 拦截器的postHandle:是按照逆序执行 拦截器的afterCompletion:是按照逆序执行 已经放行了的拦截器的afterCompletion总会执行

14 国际化

步骤:

  1. 写好国际化资源文件
  2. 让springmvc的ResourceBundleMessageSource管理国际化资源文件
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="login"></property>
</bean>
  1. 直接去页面取值

现象:是按照浏览器带来语言信息觉得; Locale locale = request.getLocale();//获取到浏览器的区域信息 springmvc中区域信息是由区域信息解析器(LocaleResolver,九大组件之一)得到的 默认会用一个AcceptHeaderLocaleResolver,所有用到区域信息的地方,都是用到AcceptHeaderLocaleResolver获取的。

获取国际化信息: 若想将国际化信息显示到页面,只需将其添加到请求域中,再从页面获取

14.1 自定义区域信息解析器

  1. 实现LocaleResolver接口
public class MyLocaleResolver implements LocaleResolver {
    /**
     * 解析返回locale
     * @param request
     * @return
     */
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        Locale l = null;
        String localeStr = request.getParameter("locale");
        System.out.println("请求参数的locale:"+localeStr);
        //如果带了locale参数就用参数指定的区域信息,如果没带就用请求头的
        if(localeStr != null && !"".equals(localeStr)){
            l = new Locale(localeStr.split("_")[0],localeStr.split("_")[1]);
        }else {
            //请求头的,也就是浏览器的区域信息
            l =request.getLocale();
        }
        return l;
    }
    /**
     * 修改locale
     * @param request
     * @param response
     * @param locale
     */
    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        throw new UnsupportedOperationException(
                "Cannot change HTTP accept header - use a different locale resolution strategy");
    }
}
  1. ioc中配置该bean
    <!--自定义区域信息解析器-->
    <bean id="localeResolver" class="com.zhm.controller.MyLocaleResolver"></bean>

Filter与拦截器的使用 如果某些功能需要其他组件配合完成,我们就使用拦截器 其他情况可以写filter

15 异常处理

默认的几个HandlerExcoptionResolver

  • ExceptionHandlerExceptionResolver
  • ResponseStatusExceptionResolver
  • DefaultHandlerExceptionResolver

如果有异常,解析器都不能处理就直接抛出去;


页面渲染之前如果有异常如何处理

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);
			}
		}

		// Did the handler return a view to 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);
		}
	}

异常处理:3个默认的异常解析器都解决不了,直接抛出去给tomcat,tomcat会包异常错误信息

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			Object handler, Exception ex) throws Exception {

		// Check registered HandlerExceptionResolvers...
		ModelAndView exMv = null;
		for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
			exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
			if (exMv != null) {
				break;
			}
		}
		if (exMv != null) {
			if (exMv.isEmpty()) {
				return null;
			}
			// We might still need view name translation for a plain error model...
			if (!exMv.hasView()) {
				exMv.setViewName(getDefaultViewName(request));
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
			}
			WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
			return exMv;
		}

		throw ex;
	}

异常解析器解决的场景:

  • ExceptionHandlerExceptionResolver:解决@ExceptionHandler注解标注的
  • ResponseStatusExceptionResolver:解决@ResponseStatus注解标注的(给自定义异常上标注的)
  • DefaultHandlerExceptionResolver:判断是否springmvc自带的异常

15.1 异常处理步骤

  1. 编写一个方法,并添加注解@ExceptionHandler,如果知道要解决什么异常,可以在属性value中添加,多个就用数组(value={ArithmeticException.class , xxx.class})
  2. 编写一个自己的异常错误页面,返回ModelAndView(携带异常信息,方便显示在页面<span data-formula="{"key"})

全局异常处理:由于每一个类都可能发生异常,我们可以集中编写一个类,专门处理异常;该类需要加入到ioc容器中,该注解为@ControllerAdvice 注意: 本类的异常处理,精确的异常优先 本类上的异常处理优先于全局的处理,无论是否精确某个异常,都是本类优先

基于配置的异常: SimpleMappingExceptionHandle

Spring MVC运行流程

  1. 所有请求,前端控制器(DispatcherServlet)收到请求,调用doDispatcher进行处理
  2. 根据HandlerMapping中保存的请求映射信息找到能够处理当前请求的处理器执行链(包括拦截器)
  3. 根据当前处理器找到他的HandlerAdapter(适配器)
  4. 拦截器的preHandle先执行
  5. 适配器执行目标方法
    1. ModelAttribute注解标注的方法提前运行
    2. 执行目标方法的时候(确定目标方法用的参数)
      1. 有注解
      2. 没注解
      • 看是否Model、Map以及其他的
      • 如果是自定义类型
        • 从隐含模型中看有没有,如果有就从隐含模型中拿
        • 如果没有,再看是否SessionAttributes标注的属性,如果是,从Session中拿,如果拿不到会抛异常
        • 都不是,就利用反射创建对象
  6. 拦截器的postHandle执行
  7. 处理结果(页面渲染流程)
    1. 如果有异常使用异常解析器处理异常;处理完后还会返回ModelAndView
    2. 调用render进行页面渲染
      • 视图解析器根据视图名得到视图对象
      • 视图对象调用render方法
    3. 执行拦截器的afterCompletion

Spring MVC和Spring整合

SpringMVC的配置文件用来配置和网站转发逻辑一家网站功能有关的(视图解析器,文件上传解析器,支持ajax,xxx); Spring的配置文件用来配置和业务有关的(事务控制,数据源,xxx)


Spring与SpringMVC配置文件同时运行时,Spring容器相当于一个父容器,SpringMVC容器是一个子容器。在SpringMVC容器中,可以访问到Spring容器中定义的bean,反之则不行。在web开发中,Controller全部在SpringMVC中扫描,除了Controller(和异常处理ControllerAdvice)之外的bean,全部在Spring容器中扫描(Service、Dao),按照这种方式配置,Controller可以访问到Service。

SpringMVC配置: " aria-hidden="true">{"key"})

全局异常处理:由于每一个类都可能发生异常,我们可以集中编写一个类,专门处理异常;该类需要加入到ioc容器中,该注解为@ControllerAdvice 注意: 本类的异常处理,精确的异常优先 本类上的异常处理优先于全局的处理,无论是否精确某个异常,都是本类优先

基于配置的异常: SimpleMappingExceptionHandle

Spring MVC运行流程

  1. 所有请求,前端控制器(DispatcherServlet)收到请求,调用doDispatcher进行处理
  2. 根据HandlerMapping中保存的请求映射信息找到能够处理当前请求的处理器执行链(包括拦截器)
  3. 根据当前处理器找到他的HandlerAdapter(适配器)
  4. 拦截器的preHandle先执行
  5. 适配器执行目标方法
    1. ModelAttribute注解标注的方法提前运行
    2. 执行目标方法的时候(确定目标方法用的参数)
      1. 有注解
      2. 没注解
      • 看是否Model、Map以及其他的
      • 如果是自定义类型
        • 从隐含模型中看有没有,如果有就从隐含模型中拿
        • 如果没有,再看是否SessionAttributes标注的属性,如果是,从Session中拿,如果拿不到会抛异常
        • 都不是,就利用反射创建对象
  6. 拦截器的postHandle执行
  7. 处理结果(页面渲染流程)
    1. 如果有异常使用异常解析器处理异常;处理完后还会返回ModelAndView
    2. 调用render进行页面渲染
      • 视图解析器根据视图名得到视图对象
      • 视图对象调用render方法
    3. 执行拦截器的afterCompletion

Spring MVC和Spring整合

SpringMVC的配置文件用来配置和网站转发逻辑一家网站功能有关的(视图解析器,文件上传解析器,支持ajax,xxx); Spring的配置文件用来配置和业务有关的(事务控制,数据源,xxx)


Spring与SpringMVC配置文件同时运行时,Spring容器相当于一个父容器,SpringMVC容器是一个子容器。在SpringMVC容器中,可以访问到Spring容器中定义的bean,反之则不行。在web开发中,Controller全部在SpringMVC中扫描,除了Controller(和异常处理ControllerAdvice)之外的bean,全部在Spring容器中扫描(Service、Dao),按照这种方式配置,Controller可以访问到Service。

SpringMVC配置:

 <!--扫描Controller组件和异常处理-->
 <!--context:include-filter默认是扫描所有,可以理解为手机的白名单,不在黑名单和不在白名单中的人可以打电话进来,需要禁用默认行为-->
    <context:component-scan base-package="com.zhm" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>

Spring配置

    <!-- 扫描注解是忽略Controller的注解 -->
    <context:component-scan base-package="com.zhm">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

本文章使用limfx的vscode插件快速发布