什么是SpringMVC?
SpringMVC是与Spring框架配合使用的一种实现了WebMVC设计思想的Web框架。
所谓MVC思想,是将Web层中的
模型(model)
、
视图(view)
和
控制器(controller)
在业务逻辑层中解耦出来,使之各司其职且互不影响的一种设计思想。
模型:仅负责封装应用程序的数据
视图:仅用于向用户展示模型中包含的数据
控制器:接受用户请求,调用service层和dao层来处理业务逻辑。
它是目前流行的MVC思想框架,由于与spring高度适配且配置简便而风靡业界。
SpringMVC的架构
Spring MVC框架是一个基于请求驱动的Web框架,它使用了前端控制器模式。(每一个请求都会经过一个总的处理程序,然后被指配给对应的处理程序。)
首先让我们整体看一下Spring MVC处理请求的流程:
用户发送请求,请求被SpringMVC前端控制器DispatherServlet捕获
前端控制器DispatherServlet对请求URL解析获取请求URI,根据URI,调用处理器映射器HandlerMapping
处理器映射器HandlerMapping分析出该请求需要调用的方法及拦截器、过滤器等操作,由此形成一个业务逻辑链HandlerExecutionChain(包括Handler对象以及 Handler对象对应的拦截器),将之返回给前端控制器
DispatcherServlet 根据HandlerExecutionChain,选择一个对应的HandlerAdapter。(附 注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法)
HandlerAdapter根据请求的Handler适配并执行对应的Handler;HandlerAdapter(提取Request 中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程 中,根据配置,Spring将做一些额外的工作:
HttpMessageConveter: 将请求消息(如json、xml等数据)转换成一个对象,将对象转换为指定的响应信息。
数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
数据格式化: 数据格式化。 如将字符串转换成格式化数字或格式化日期等
数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
Handler执行完毕,返回一个ModelAndView(即模型和视图)给HandlerAdaptor
HandlerAdaptor适配器将执行结果ModelAndView返回给前端控制器。
前端控制器接收到ModelAndView后,请求对应的视图解析器。
视图解析器解析ModelAndView后返回对应View
渲染视图并返回渲染后的视图给前端控制器
最终前端控制器将渲染后的页面响应给用户或客户端
Spring MVC处理请求的流程
如此一来,便可知SpringMVC的架构何如。
地址映射
针对Web项目(无论资源还是servlet)的访问均是通过资源定位,如此一来,拥有对应的地址便是一项大事。传统的servlet是通过@HttpServlet(value="/X")进行调用,在SpringMVC中,将会利用另一个注解来实现其用法同@HttpServlet几乎一致。
@RequestMapping
通过此注解可以将请求地址与方法进行绑定,可以生命在类和方法上。类级别的注解负责将一个特定的请求路径映射到一个控制器(controller)上,将 url 和类绑定;通过方法级别的注解可以细化映射,能够将一个特定的请求路径映射到某个具体的方法上,将 url 和类的方法绑定。
@Controller
@RequestMapping ( "/urlController" ) // 声明在类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
public class UrlController {
@RequestMapping ( "/test01" ) // 映射单个url |* 路径开头是否加 斜杠"/" 均可
// @RequestMapping(value = {"/test01","Test01"}) // 映射多个url
public ModelAndView test01 (){
ModelAndView modelAndView = new ModelAndView ();
modelAndView . addObject ( "hello" , "test01" );
modelAndView . setViewName ( "hello" );
return modelAndView ;
} // 此时访问路径为 http :// ip : port / springmvc01 / urlController / test01
数据响应
已知,MVC思想中负责数据展示的为视图,负责数据存储的为模型。想要看到对应的响应试图则必须先要有一个存数据Model和一个展示数据的View。在@RequestMapping修饰的方法中,SpringMVC默认会将返回的ModelAndView对象解析出来。这个对象中包含了存的数据与View的名称。
@RequestMapping("/test01")
public ModelAndView test01(){
ModelAndView modelAndView = new ModelAndView(); // 要用到ModelAndView对象那不得先有一个吗?此处选择new一个。
modelAndView.addObject("hello","test01"); // 存储数据(Key-Value)
modelAndView.setViewName("hello"); // 设置视图名称
return modelAndView; // 返回出供视图解析器解析
其实,这个ModelAndView对象,你如果需要,框架会主动为我们创建。
public ModelAndView test01( ModelAndView modelAndView ){ // 直接作为参数,框架会主动创建供我们使用
// 不存数据其实也可以。
modelAndView.setViewName("hello"); // 设置视图名称
return modelAndView; // 返回出供视图解析器解析
当然如果不返回ModelAndView对象也行,可以使用Model对象来单独存储数据。
@RequestMapping("/test01")
public String test01(Model model){
model.addAttribute("hello","test01"); // 存储数据(Key-Value)
return "test01"; // 返回的字符串加上前缀后缀会补足为要访问的视图文件路径。这是在配置文件中配置好的。
这么一来,不传递数据时,直接返回视图名字不就行了么?
@RequestMapping("/test01")
public String test01(){
return "test01"; // 返回的字符串加上前缀后缀会补足为要访问的视图文件路径。
默认的,返回无论是对象还是字符串都是进行视图定位的。可如果我要是想直接在网页上回写信息,并不想跳转视图呢?
请看数据回写!
数据回写(json格式)
所谓数据回写,便是直接将信息打印在基础网页上,并不依赖视图模板进行数据展示。
可是直接返回String还是会被框架当成视图名称去检索,显然服务器会报找不到对应视图的错误,这时便需要用到@ResponseBody注解来向框架表明要返回的其实就是单纯的数据罢了。
@RequestMapping("/test01")
@ResponseBody
public String test01(){
return "test01";
}
很多时候我们要在页面上展示很多的信息,若是全部作为字符串返回的话,其繁琐程度可想而知。所以为了能够更加简便的返回或者诸多信息,一般把信息存在类对象中直接返回。
可是,返回的类对象能成功回写在网页上吗?其实是可以的,因为@RequestBody已为我们做了操作,实现了利用HttpMessageConverter将返回对象转化为json格式的操作。Json 在企业开发中已经作为通用的接口参数类型,在页面解析很方便。我们只需要修改相关配置,添加 json 数据支持功能便可享受SpringMVC 对于 json 提供的支持。
@Controller
@RequestMapping("/user")
public class UserController {
-----------------------------------------------
@RequestMapping("queryUser01")
@ResponseBody //注解设置在方法体上
public User queryUser01(){
User user = new User();
user.setId(1);
user.setUserName("zhangsan");
user.setUserPwd("123456");
// 返回的是user对象
return user;
------------------------------------------------
@RequestMapping("queryUser02")
public @ResponseBody User queryUser02(){ // 注解设置在方法返回对象前,修饰符之后
User user = new User();
user.setId(1);
user.setUserName("zhangsan");
user.setUserPwd("123456");
// 返回的是user对象
return user;
-------------------------------------------------
@RequestMapping("/queryUser03")
@ResponseBody
public List<User> queryUser03(){
List<User> list = new ArrayList<>();
User user01 = new User();
user01.setId(1);
user01.setUserName("zhangsan");
user01.setUserPwd("123456");
User user02 = new User();
user02.setId(2);
user02.setUserName("lisi");
user02.setUserPwd("123321");
list.add(user01);
list.add(user02);
// 返回的是user集合
return list; //@ResponseBody 返回的是JOSN格式的数据,返回集合
}
数据绑定
客户端请求的参数到控制器功能处理方法上的参数的绑定,对于参数绑定非常灵活。
基本数据 / 包装类型 / 字符串类型绑定
* 通过注解 @RequestParam 标记一个形参为请求参数 。( 注解声明在形参的前面 )
* 基本数据类型如果不自行设置是没有默认值 , 会报500异常 。 故需自己设置 , 利用 @defaultValue
* 而包装类型参数和字符串类型数据的默认值为null , 不会报异常 。
@RequestMapping ( "data02" )
public void data02 ( @RequestParam ( defaultValue = "18" ) int age ,
@RequestParam ( defaultValue = "10.0" ) double money ){
System . out . println ( "age:" + age + ", money:" + money );
* 也可以设置参数的别名来接收参数 , 这个别名就是表单提交的时候的name值 。
@RequestMapping ( "data03" )
public void data03 ( @RequestParam ( defaultValue = "18" , name = "userAge" ) int age ,
@RequestParam ( defaultValue = "10.0" , name = "userMoney" ) double money ){
System . out . println ( "age:" + age + ", money:" + money );
数组类型绑定
这种一般都是多选表单传入的参数 , 比如 : * 客户端传参形式 : ids = 1 & ids = 2 & ids = 3
其实与普通类型无疑 , 仅仅是需要修改形参的类型罢了 。
@RequestMapping ( "/data06" )
public void data06 ( String [] ids ){
for ( String id : ids ){
System . out . println ( id + "---" );
JavaBean类型绑定
这种情况下要求表单的name与bean对象的属性名保持一致 , 比如表单中一条为
< input type = text name = user . name value = “ xxx ” >
则对应的 , bean对象必有一个属性为name 。
@RequestMapping ( "/data07" )
public void data07 ( User user ) {
System . out . println ( user );
集合类型绑定
此时 User 实体需要定义对应 list 属性 。( 对于集合的参数绑定 , 一般需要使用 JavaBean 对象进行包
public class User {
private int id ;
private String userName ;
private String userPwd ;
private List < Phone > phones = new ArrayList < Phone >();
public class Phone {
private String num ;
JSP 页面定义
< form action = "data08" method = "post" > // // 如果是map:
< input name = "phones[0].num" value = "123456" /> // // <input name="map['1'].num" value="123456" />
< input name = "phones[1].num" value = "4576" /> // // <input name="map['2'].num" value="4576" />
// 其name为接受集合类型参数的关键。
< button type = "submit" > 提交 </ button >
</ form >
Controller 方法
@RequestMapping ( "/data08" )
public void data08 ( User user ){
System . out . println ( user );
请求转发与重定向
重定向
重定向是发一个302的状态码给浏览器,浏览器自己去请求跳转的网页。地址栏会发生改变。
@RequestMapping(value="/view02")
public String view02(){
return "redirect:view.jsp"; // 利用字符串:redirect
return "redirect:view.jsp?uname=zhangsan&upwd=123456"; // * 传递参数
return "redirect:view.jsp?uname=张三&upwd=123456"; // * 传递参数 (传递中文参数会出现乱码)
* 传递参数 (通过 @RedirectAttributes 对象设置重定向参数,避免中文乱码问题)
@RequestMapping(value="/view04")
public String view04(RedirectAttributes redirectAttributes){
redirectAttributes.addAttribute("uname","张三");
redirectAttributes.addAttribute("upwd","123456");
return "redirect:view.jsp";
也可以直接返回ModelAndView对象
@RequestMapping(value="/view06")
public ModelAndView view06(ModelAndView modelAndView){ // * 返回 ModelAndView 对象,并添加对象避免乱码。
modelAndView.addObject("uname","李四");
modelAndView.addObject("upwd","123321");
modelAndView.setViewName("redirect:view.jsp");
// modelAndView.setViewName("redirect:test01"); // 甚至可以重定向至Controller
return modelAndView;
页面中获取参数值
${param.参数名}
请求转发
请求转发,直接调用跳转的页面,让它返回。对于浏览器来说,它无法感觉服务器有没有forward。 地址栏不发生改变。可以获取请求域中的数据。
@RequestMapping("/view09")
public String view09(){
return "forward:view.jsp";
return "forward:view.jsp?uname=张三&upwd=123456"; // * 设置参数
@RequestMapping("/view10")
public String view10(Model model){ // * 设置请求域
model.addAttribute("uname","张三");
return "forward:view.jsp";
@RequestMapping("/view12")
public ModelAndView view12(ModelAndView modelAndView){
modelAndView.setViewName("forward:test01"); // * 请求转发到 Controller
modelAndView.setViewName("forward:test01?uname=admin"); // 传递参数
return modelAndView;
}
拦截器
SpringMVC 中的 Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理。比如通过它来进行权限验证,或者是来判断用户是否登陆等操作。
对于 SpringMVC 拦截器的定义方式有两种:
实现接口:org.springframework.web.servlet.HandlerInterceptor
继承适配器:org.springframework.web.servlet.handler.HandlerInterceptorAdapter
除了继承或者实现的接口和类的不同,内部的功能实现其实是一样的。
preHandler:在handler执行之前执行的方法,是最为重要的拦截方法,一般的权限控制,准备工作都是在这一步完成的。
postHandler:是在视图展示之前执行的方法。
afterCompletion:目标handler执行完毕且视图生成之后执行的方法,一般用于收尾工作。
这三个方法就是继承类和实现接口后要实现的方法。
全局异常处理
在 JavaEE 项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,则系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。 好在SpringMVC 对于异常处理这块提供了支持,通过 SpringMVC 提供的全局异常处理机制,能够将所有 类型的异常处理从各处理过程解耦出来,既保证了相关处理过程的功能较单一,也实现了异常信息的统 一处理和维护。
全局异常实现方式 SpringMVC 处理异常有 3 种方式:
使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver
实现 Spring 的异常处理接口 HandlerExceptionResolver 自定义自己的异常处理器
使用 @ExceptionHandler 注解实现异常处理
简单异常处理器配置
<!-- 配置全局异常统一处理的 Bean (简单异常处理器) -->
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 页面在转发时出现异常,设置默认的错误页面 (error代表的是一个视图) -->
<property name="defaultErrorView" value="error"></property>
<!-- 异常发生时,设置异常的变量名 -->
<property name="exceptionAttribute" value="ex"></property>
</bean>
实现接口自定义异常处理器
使用实现 HandlerExceptionResolver 接口的异常处理器进行异常处理,具有集成简单、有良好的扩 展性、对已有代码没有入侵性等优点,同时,在异常处理时能获取导致出现异常的对象,有利于提供更 详细的异常处理信息。
@Component
public class GlobalExceptionResolver implements HandlerExceptionResolver { // 实现接口
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, Object handler, Exception ex) {
ModelAndView mv = new ModelAndView("error"); // 指定视图名
mv.addObject("ex","默认错误信息"); // 添加错误信息
// 判断是否是自定义异常
if (ex instanceof ParamsException) { // 自定义异常判断类型
mv.setViewName("params_error");
ParamsException e = (ParamsException) ex;
mv.addObject("ex", e.getMsg());
return mv;
}
利用注解实现全局异常处理
使用 @ExceptionHandler 注解实现异常处理,具有集成简单、有扩展性好(只需要将要异常处理的 Controller 类继承于 BaseController 即可)、不需要附加Spring 配置等优点,但该方法对已有代码存 在入侵性(需要修改已有代码,使相关类继承于 BaseController),在异常处理时不能获取除异常以外的 数据。
public class BaseController {
@ExceptionHandler
public String exc ( HttpServletRequest request , HttpServletResponse
response , Exception ex ){
request . setAttribute ( "ex" , ex );
if ( ex instanceof ParamsException ){
return "error_param" ;
if ( ex instanceof BusinessException ){
return "error_business" ;
return "error" ;
}
未捕获异常的处理
对于 Unchecked Exception 而言,由于代码不强制捕获,往往被忽略,如果运行期产生了 Unchecked Exception,而代码中又没有进行相应的捕获和处理,则我们可能不得不面对尴尬的 404、 500……等服务器内部错误提示页面。
此时需要一个全面而有效的异常处理机制。目前大多数服务器也都支持在 web.xml 中通过 (Websphere/Weblogic)或者(Tomcat)节点配置特定异常情况的显示页面。修改 web.xml 文件,增加以 下内容:
<!-- 出错页面定义 -->
<error-page>
<exception-type> java.lang.Throwable</exception-type>
<location> /500.jsp</location>
</error-page>
<error-page>
<error-code> 500</error-code>