1、概念
MVC
MVC是一种架构模式,将程序的开发分为Mode、View、Controller三个层次,各层之间彼此分离,协同工作。View视图层关注数据的呈现,Model模型层关注支撑业务数据的构成,Controller完成业务逻辑处理和数据传递。
如下所示为一种MVC设计模式,首先用户请求到达前端控制器Front Controller,它将请求转发给能处理该请求的Controller,控制器进行业务处理并产生业务数据并返回给前端控制器。Front Controller将收到的业务数据转发给页面模板View template渲染成最终页面并返回给Front controller。最后Front controller将最终页面返回给用户。在这个模式中,Front controller负责分发调度,Controller负责处理业务数据,View template负责呈现页面。MVC核心思想是业务数据的处理和呈现相分离。
Spring MVC的执行流程
- 用户向服务端发送一次请求,这个请求会先到前端控制器DispatcherServlet(也叫中央控制器)。
- DispatcherServlet接收到请求后会调用HandlerMapping处理器映射器,得到请求被处理的执行链。所谓执行链是指用一些拦截器Interceptor包裹在真正的处理器Handler外围,在请求到达Handler之前进行一些预处理,在Handler返回执行结果后再进行一些操作才将结果返回。
- DispatcherServlet调用HandlerAdapter处理器适配器,告诉处理器适配器具体应该要去执行哪个Controller
- HandlerAdapter处理器适配器去执行Controller并得到ModelAndView(数据和视图),并层层返回给DispatcherServlet
- DispatcherServlet将ModelAndView交给ViewReslover视图解析器解析,然后返回真正的视图。
- DispatcherServlet将模型数据填充到视图中
- DispatcherServlet将结果响应给用户
2、使用Spring MVC
文件配置
如下左图所示对一个customers表进行管理,完成用户信息查询显示和增加等功能,首先通过maven创建一个web项目并手动添加model、view、controller等对映类。项目结构如下右图所示
首先在pom.xml文件中配置项目用到的依赖如servlet、mysql-connector、spring-webmvc等。
接着在web.xml文件中引入两个配置文件:Spring框架的上下文配置文件application-context.xml和Spring MVC的配置文件mvc-dispatcher-servlet.xml。在配置DispatcherServlet时通过<init-param>contextConfigLocation指定了配置文件的位置,否则SpringMVC默认根据<servlet-name>自动查找/web-INF/$servlet-name$-servlet.xml文件
这里需要注意的是<url-pattern>匹配路径“/”只能匹配到类似于/customer/view的路由,“/*”不仅可以匹配到/customer/view,还能匹配到/view.jsp、/view.html等带后缀的url地址。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Archetype Created Web Application</display-name>
<!--配置Spring的上下文context-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/config/application-context.xml</param-value>
</context-param>
<!--配置DispatcherServlet-->
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/mvc-dispatcher-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
如下所示为application-context.xml文件,在其中开启基于注解的Bean管理并自动扫描com.mvc下的类,通过exclude-filter排除@Controller注解的Bean,因为之后将@Controller类交给DispatcherServlet来管理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 启动基于注解的Bean管理 -->
<context:annotation-config></context:annotation-config>
<!-- 扫描com.mvc下的Bean -->
<context:component-scan base-package="com.mvc">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
如下所示为mvc-dispatcher-servlet.xml文件,在其中激活注解的Bean管理并且搜索@Controller类,并且启动HandlerMapping。DispatcherServlet除了对Controller进行管理外,还对View进行管理。配置ViewResolver为JSTL模板页面,并且为页面添加前后缀,例如Controller返回字符串success,添加前后缀就成为/WEB-INF/jsps/success.jsp,从而可以找到对应的页面。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 激活注解 -->
<context:annotation-config></context:annotation-config>
<!-- 让DispatchServlet只搜索@Controller注解的类 -->
<context:component-scan base-package="com.mvc">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 让DispatchServlet启用基于注解的HandlerMapping -->
<mvc:annotation-driven/>
<!-- 映射静态资源目录 -->
<mvc:resources mapping="/resources/**" location="/resources"/>
<!-- 配置ViewResolver为JstlView,并为其添加前后缀 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsps/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
编写Controller
如下实现通过访问url为/customer/view?customerId=3的页面显示用户的姓名。通过@RequestMapping的默认value属性为CustomerController类和viewCustomer()方法指定映射的url路径,还可以通过method = RequestMethod.POST属性来指定对应的请求类型。
在viewCustomer()方法首先通过@RequestParam注解获取url中的参数customerId,然后通过调用CustomerService完成根据id查询数据库的操作并返回一个customer对象,controller再将该对象绑定到Model对象中。Spring MVC通过Model对象携带数据信息,前端页面可以直接从Model对象中获取到customer。
最后返回页面字符串"customer_view",Dispatcher Servlet会根据字符串拼接成正确的页面位置。不仅可以直接书写页面字符串,还可以为“redirect/forward:”+页面来实现重定向的功能。
@Controller
@RequestMapping("/customer")
public class CustomerController {
private CustomerService customerService;
@Autowired
public void setCustomerService(CustomerService customerService) {
this.customerService = customerService;
}
@RequestMapping("/view")
public String viewCustomer(@RequestParam("customerId") int id, Model model) {
Customer customer = customerService.queryById(id);
model.addAttribute(customer); //将customer对象绑定到model中
return "customer_view";
}
}
上面是通过@RequestParam获取参数并通过Model对象返回对象,还可以使用传统的HttpRequest对象来进行参数的获取和数据的绑定
@RequestMapping("/view")
public String viewCustomer(HttpServletRequest request){
int id=Integer.parseInt(request.getParameter("customerId")); //获取参数
Customer customer=customerService.queryName(id);
request.setAttribute("customer",customer); //绑定返回对象
return "customer_view";
}
如果访问的url为RESTful的形式/customer/view/3,可以通过@PathVariable来获取参数。这里使用Map来携带customer对象信息给前端。
@RequestMapping("/view/{customerId}")
public String viewCustomer(@PathVariable("customerId")int id, Map<String,Object> model){
Customer customer=customerService.queryName(id);
model.put("customer",customer);
return "customer_view";
}
最后在customer_view.jsp页面通过EL表达式显示customer对象的信息如下
<h2>编号:${customer.id},名字:${customer.name}</h2>
表单数据绑定
SpringMVC会自动完成表单数据绑定到对象,需要注意的是表单属性的name要和对象属性名相同。如下所示为提交一个customer对象的表单,其name为id、name分别对应Customer对象的id、name属性。
<form action="<%=request.getContextPath()%>/customer/save" method="post">
id:<input type="text" name="id">
用户名:<input type="text" name="name">
<input type="submit" value="提交">
</form>
在Controller中以参数的形式接收customer对象并通过customerService将customer保存到数据库。之后将页面重定向到/customer/view页面并传入customerId为刚保存的customer对象的id。
@RequestMapping(value = "/save",method = RequestMethod.POST)
public String saveCustomer(Customer customer){
System.out.println(customer.getId()+customer.getName());
customerService.addCustomer(customer);
return "redirect:view?customerId="+customer.getId();
}
值得注意的是表单提交时中文会出现乱码,这是需要在web.xml文件中设置编码方式为UTF-8
<filter>
<filter-name>characterEncoding</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>
</filter>
<filter-mapping>
<filter-name>characterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
文件上传
在Spring MVC中使用文件上传,首先在pom.xml文件中引入maven依赖commons-fileupload,接着在mvc-dispatcher-servlet.xml中配置bean CommonMultipartResolver
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760"/> <!--文件最大10M=10485760字节-->
<property name="defaultEncoding" value="UTF-8"/>
<property name="resolveLazily" value="true"/> <!--开启文件延迟解析-->
</bean>
提交文件的表单如下:在其中指定enctype为multipart/form,文件的name为imgFile
<form method="post" action="<%=request.getContextPath()%>/customer/uploadImg" enctype="multipart/form-data">
<input type="file" name="imgFile">
<input type="submit" value="提交">
</form>
在Controller中接收图片并保存,通过MultipartFile对象实现文件操作,其isEmpty()用于判定文件是否为空,getInputStream()获取文件流,getOriginalFilename()获取文件名。通过FileUtils的copyInputStreamToFile()方法将文件拷贝到服务器新建的文件对象中
@RequestMapping(value = "/uploadImg",method = RequestMethod.POST)
public String uploadImg(@RequestParam("imgFile") MultipartFile multipartFile) throws IOException {
if (!multipartFile.isEmpty()){
FileUtils.copyInputStreamToFile(multipartFile.getInputStream(),
new File("D:\\Temp\\Pictures",multipartFile.getOriginalFilename()));
}
return "redirect:upload";
}
返回JSON数据
有时我们需要服务器不是返回一个页面,而是JSON格式的数据以便前端完成异步加载等功能。在SpringMVC中使用Json首先要通过maven引入依赖jackson-databind。之后在mvc-dispatcher-servlet.xml中配置ContentNegotiatingViewResolver默认返回JSON格式的数据。
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</list>
</property>
</bean>
之后在Controller中设置方法直接返回Customer类型的对象,并且添加@ResponseBody注解将结果转化为json格式再返回
@RequestMapping("/json_data")
public @ResponseBody Customer getJsonData(@RequestParam("customerId") int id){
return customerService.queryById(id);
}
通过浏览器访问该url返回JSON格式的数据如下所示
3、拦截器
Spring MVC中的拦截器可以通过继承HandlerInterceptor接口来实现,该接口有三个方法,其中preHandler()为请求到达真正Controller之前进行拦截的方法,它具有返回值,如果为true则放行请求,false代表拦截请求。例如在该方法中对用户是否登录进行验证,验证通过才放行,否则通过Dispatcher将其转发到登陆页面。postHandler()是从Controller返回结果后再次经过拦截器所执行的方法,该方法的参数除了request、response、handler之外还有ModelAndView对象,该对象代表Controller返回的数据和视图信息,我们可以通过该对象修改返回的数据与视图页面。afterCompletion()方法是拦截器执行结束后的方法,类似于析构函数,一般在其中进行对象销毁、连接关闭等操作。
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("请求到达拦截器...");
if (request.getSession().getAttribute("username") == null){ //验证是否已登录
request.getRequestDispatcher("/WEB-INF/jsps/login.jsp").forward(request,response);
return false;
}else
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("返回结果到达拦截器...");
modelAndView.addObject("msg","这是拦截器返回时添加的信息"); //修改返回的数据信息
modelAndView.setViewName("/customer/add_customer"); //修改返回的视图页面
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("拦截器执行结束");
}
}
之后要在mvc-dispatcher-servlet.xml文件中注册拦截器,其中<mvc:mapping>规定拦截器要进行拦截的请求路径,<mvc:exclude-mapping>代表拦截路径下排除的子路径,<bean>放在最后,代表拦截器对应的实现类。
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/customer_view"/>
<mvc:exclude-mapping path="/customer_view/login" />
<bean class="com.mvc.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>