SpringMVC框架解决了V与C的交互问题。
SpringMVC中的核心组件
- DispatcherServlet:前端控制器,用于接收所有请求,并负责分发;
- HandlerMapping:根据请求路径映射控制器或控制器的方法,确定请求路径与控制器或控制器中的方法的对应关系;
- Controller:实际处理请求的组件;
- ModelAndView:控制器的返回结果,包括处理完成后的数据,及最终应该响应给客户端的视图名称;
- ViewResolver:根据视图名称得到具体的视图组件。
使用:
首先,在**pom.xml**中添加`spring-webmvc`的依赖。
然后,将Spring的配置文件复制到**src/main/resources**下。
在web.xml配置文件中对`DispatcherServlet`进行配置,并添加初始化属性contextConfigLocation,其值为Spring配置文件的地址(classpath:spring.xml),添加<load-on-startup>属性容器启动时启动
在spring.xml配置文件中添加组件扫描配置,告诉spring框架需要扫描那个包中的类。
在spring.xml配置文件中添加InternalResourceViewResolver的bean配置,其中配置属性prefix前缀和suffix后缀
在处理器类前添加对应的注解如:@Controller,并在处理方法前添加@RequestMapping("请求路径")注解
- 在类声明的前面有可以加@RequestMapping("请求路径")注解,其效果类似于在当前类中所有处理方法的请求路径上面嵌套了一个文件夹,故而访问时需在处理方法请求路径前加上当前类请求路径。在实际应用中,推荐为每一个类都添加该注解!
关于@RequestMapping注解
在处理请求的方法之前添加`@RequestMapping("login.do")`用于配置请求路径与处理响应的方法之间的映射。
该注解也可以添加在类的声明之前,添加在类之前的`@RequestMapping("user")`注解用于配置请求路径中的层次!将作用于当前类中配置的所有请求路径!原有的例如`login.do`的请求路径就会变成`user/login.do`。
无论是添加在类之前配置路径,还是在方法之前配置路径,都不需要考虑是否添加“/”符号(加不加都可以)。
配置注解中的`value`属性或`path`属性,可以配置请求路径与处理请求的方法的映射,且值可以是数组类型。
可以在注解中配置`method`属性,以限制某个请求路径的允许使用的若干种请求方式,如果使用错误的请求方式发出请求,则会导致405错误
关于@RequestParam注解
`@RequestParam`是添加在请求参数之前的注解!
使用该注解value属性,可以修改获取请求参数的名字(默认是以方法参数名为准),当添加该注解后,默认情况下,该参数就是必须提交的,如果请求中没有提交该参数,就会出现400错误。如果并不强制要求客户端提交该参数,可以另外添加required=false参数。
还可以配置`defaultValue`属性,用于配置默认值,即当客户端没有提交该请求参数时,视为客户端提交了某个值
@RequestMapping("login.do")
//容器会从请求参数中获取名为uname的参数赋值给username,若没有提交该参数则,username=“JSD1902”
public String login(@RequestParam(name="uname",required=false,defaultValue="JSD1902") String username,String passwrod){
return "redirect:index.do"
}
获得请求参数:
1. (不推荐) 使用HttpServletRequest
在处理请求的方法的参数列表中添加`HttpServletRequest`参数,然后,在处理过程中,调用`request`的`getParameter()`方法即可获取各请求参数的值。
- 如果期望的数据类型不是`String`,需要自行转换!
- 获取数据的操作比较繁琐;
- 不便于执行单元测试。
2. (推荐) 将请求参数设计为处理请求的方法的参数
当需要获取请求参数时,直接将它们添加到处理请求的方法的参数列表中即可。这种操作要求请求参数的名称与处理请求的方法的参数名称保持一致!如果名称不一致,则处理请求的方法中的参数值将是`null`。
- 这种做法的缺陷在于:不适合处理过多的请求参数。
3. (推荐) 使用封装的类型作为处理请求的方法的参数
当请求参数较多时,可以将这些参数封装到1个自定义的类型中并生成get set方法,然后,将自定义的数据类型作为处理请求的方法的参数即可。同样,这种做法仍要求请求参数的名称,与封装的类型中的属性名称保持一致!
- 这种做法还有1个优点:当请求参数的数量发生变化时,也许处理请求的方法的参数列表可以不用调整,而只调整自定义的封装类型即可。
如果参数的数量较多或参数可能发生变化,应该优先选取第3种做法;如果参数的数量较少且固定,应该优先选取第2种做法。另外,第2种做法和第3种做法可以混合在一起使用!
重定向
在处理请求的方法中,返回`String`类型的结果时,返回值使用`redirect:`作为前缀,则表示重定向!在`redirect:`右侧的必须是重定向到的目标的相对定位或绝对定义的URL
转发数据
1. (不推荐) 通过HttpServletRequest参数封装转发的数据
在SpringMVC中,如果方法的返回值类型是`String`类型,默认转发!
在处理请求的方法中添加`HttpServletRequest`参数,在使用时调用该参数对象的`setAttribute()`方法即可封装转发的数据
2. (更不推荐) 使用ModelAndView
可以使用`ModelAndView`作为处理请求的方法的返回值类型,在该类型的对象中,设置其`viewName`属性即可确定需要转发到的视图名称,另外还使用了`Map<String, ?>`类型的数据作为需要转发的数据
3. (推荐) 使用ModelMap封装转发的数据
使用方式与使用`HttpServletRequest`是相同的,在处理请求的方法中添加ModelMap参数,在使用时调用该参数对象的addAttribute()方法即可封装要转发的数据
SpringMVC中的拦截器(Interceptor)
在SpringMVC中的拦截器可以是运行在控制器(Controller)之前的组件,可以设置拦截器应用于哪些请求路径,当发生这些请求时,拦截器会自动执行,在执行过程中,可以对请求相关数据进行判断,选择阻止继续向后执行,或选择放行。
所有的拦截器类都必须实现`HandlerInterceptor`接口,该接口中有3个方法,只有`preHandle()`方法是运行在控制器(Controller)之前的,另2个方法是运行在控制器之后的,所以,只有`preHandle()`具有真正意义的“拦截”功能,该方法的返回值是`boolean`类型的,当返回`true`时表示放行,返回`false`时将阻止继续向后执行,即控制器并不会被执行。
在拦截器中使用重定项response.sendRedirect(path),后面必须跟return false;
所有的拦截器都需要在Spring的配置文件中进行配置,在SpringMVC框架中,允许使用若干个拦截器,形成拦截器链,即某个请求可能需要经过多个拦截器,仅当每个拦截器都放行时,才会执行控制器中的方法!在配置文件中,配置的先后顺序决定了多个拦截器的执行顺序
<!-- 配置拦截器链 -->
<mvc:interceptors>
<!-- 配置第1个拦截器 -->
<mvc:interceptor>
<!-- 拦截路径 -->
<mvc:mapping path="/main/index.do"/>
<!-- 拦截器类 -->
<bean class="cn.tedu.spring.LoginInterceptor"></bean>
</mvc:interceptor>
<!-- 配置第2个拦截器 -->
</mvc:interceptors>
在配置每个拦截器时,允许使用若干个`<mvc:mapping>`节点以配置若干个拦截路径,在配置路径,还可以使用`*`作为通配符。
在使用`*`作为通配符时,需要注意,1个星号只能匹配1层路径,例如`/main/*`可以匹配上`/main/index.do`,也可以匹配`/main/hello.do`,但是,不可以匹配上`/main/a/index.do`!
如果一定要匹配若干层路径,必须使用2个星号,例如配置为`/main/**`,可以匹配上`/main/index.do`,也可以匹配`/main/a/hello.do`,甚至可以匹配`/main/a/b/c/d/hello.do`,即无视路径中后续的层级。
如果通配符匹配的路径过多,需要从中去除某些请求路径,还可以添加例外:
<mvc:interceptor>
<!-- 拦截路径:黑名单 -->
<mvc:mapping path="/main/**"/>
<mvc:mapping path="/user/**"/>
<!-- 例外路径:白名单 -->
<mvc:exclude-mapping path="/user/reg.do" />
<mvc:exclude-mapping path="/user/handle_reg.do" />
<mvc:exclude-mapping path="/user/login.do" />
<mvc:exclude-mapping path="/user/handle_login.do" />
<!-- 拦截器类 -->
<bean class="cn.tedu.spring.LoginInterceptor"></bean>
</mvc:interceptor>
请求参数的乱码解决方案
CharacterEncodingFilter是springMVC已经实现的过滤器类,所以只需在web.xml中添加配置信息即可实现过滤
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<!--过滤器会将该参数设置到request和response-->
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在SpringMVC中统一处理异常
在SpringMVC中,提供了统一处理异常的做法:将`@ExceptionHandler`添加在自定义的用于处理异常的方法之前。
该方法必须是public修饰的,返回值的设计与处理请求的方法相同可以是String和viewAndModel,方法中必须添加异常类型的参数,该参数必须是要处理的异常类型或者其父类,另外还可以添加例如`HttpServletRequest`等参数,但不可以添加其它参数。
在处理时,`@ExceptionHandler`只能作用于当前控制器类,若要其他类也适用则可以继承该类。
在`@ExceptionHandler`注解中,可以配置需要处理的异常的种类,当配置后,仅当指定的异常出现时,才会调用匹配的方法进行处理,而其它异常是不予处理的!如果没有配置,则任何当前类或其子类异常出现都会进行处理!
@ExceptionHandler({IndexOutOfBoundsException.class, NullPointerException.class})
public String handleException(Throwable ex, HttpServletRequest request) {
String errorMessage = null;
if (ex instanceof NullPointerException) {
errorMessage = "错误:请提交用户名!";
} else if (ex instanceof IndexOutOfBoundsException) {
errorMessage = "错误:使用的索引超出了界限!";
}
request.setAttribute("msg", errorMessage);
return "error";
}
拦截器与过滤器的区别
拦截器(Interceptor)是SpringMVC中的组件,过滤器(Filter)是JavaEE中的组件。
拦截器是运行在`DispatcherServlet`之后且在所有控制器(Controller)之前的组件,所以,仅当被`DispatcherServlet`接收的请求才可能被拦截器处理,例如`DispatcherServlet`配置的是`*.do`,那拦截器就只能处理某些`*.do`的请求,而例如`*.html`或`*.jpg`都不在拦截器可处理的范围之内!过滤器是运行在所有的Servlet之前的组件,甚至可以处理所有的请求,是根据它在**web.xml**中配置的`<url-pattern>`节点的值来决定的。
拦截器的配置更加灵活,可以有若干个拦截路径(黑名单),也可以有若干个例外路径(白名单),而过滤器只能配置1个过滤路径,如果使用了通配符,例如配置为`*.do`或`/*`等,只能在过滤器类中编写代码添加例外。