SpringMVC的运行流程:用户发出一个url请求在web.xml文件中被前端控制器DispatcherServlet进行拦截匹配,然后根据web.xml文件中的接口访问SpringMVC的核心配置文件springmvc-servlet.xml。处理器映射器将url映射到相对应的处理器上,但是真正执行处理器的是处理器适配器。在处理器适配器执行处理器方法进行参数绑定的时候,会先判断该参数类型是否需要自定义参数绑定器进行绑定,是否需要进行参数校验,是否存在文件类型的上传等,然后才进行参数绑定!执行完处理器方法后一般将逻辑视图交给视图解析器进行解析,前端控制器再将解析后的视图和模型进行渲染,最后返回给用户浏览器界面进行显示。
一、SpringMVC的运行原理
- 第一步:发起请求到前端控制器(DispatcherServlet)
- 第二步:前端控制器请求HandlerMapping查找 Handler
- 可以根据xml配置、注解进行查找
- 第三步:处理器映射器HandlerMapping向前端控制器返回Handler
- 第四步:前端控制器调用处理器适配器去执行Handler
- 第五步:处理器适配器去执行Handler
- 第六步:Handler执行完成给适配器返回ModelAndView
- 第七步:处理器适配器向前端控制器返回ModelAndView
- ModelAndView是springmvc框架的一个底层对象,包括 Model和view
- 第八步:前端控制器请求视图解析器去进行视图解析
- 根据逻辑视图名解析成真正的视图(jsp)
- 第九步:视图解析器向前端控制器返回View
- 第十步:前端控制器进行视图渲染
- 视图渲染将模型数据(在ModelAndView对象中)填充到request域
- 第十一步:前端控制器向用户响应结果
二、SpringMVC的核心组件
1、前端控制器DispatcherServlet【不需要程序员开发】
作用:接受用户请求,响应结果,相当于转发器,在struts中也存在类似一个。
有个DispatcherServlet减少了其他组件之间的耦合度
2、处理器映射器HandlerMapping【不需要程序员开发】
作用:根据请求的url查找Hander
3、处理器适配器HandlerAdapter【不需要程序员开发】
作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler
4、处理器Handler(controller)【需要程序员开发,分两步:一是Handler代码编写,二是在配置文件中进行配置注册】
- 注意:我们在编写Handler(controller)时必须按照HandlerAdapter要求的去做,这样适配器才可以正确执行Handler
5、视图解析器View resolver【不需要程序员开发】
作用:进行视图解析,根据逻辑视图名解析成真正的视图(View)
6、视图View【需要程序员开发jsp】
View是一个接口,实现类支持不同的View类型(例如:jsp、freemarker、pdf...)
- 注意:不需要我们开发的组件在实际应用中就需要去配置,而需要开发的组件就需要我们去手动编程实现,所以首先看需要程序员做的配置事情如下:
项目地址:
三、需要程序员做的配置
1.web.xml:配置前端控制器(DispatcherServlet)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 配置springmvc前端控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置springmvc加载的核心配置文件所在路径,如果不配置,默认加载的是
/WEB-INF/pages/【servlet-name】-servlet.xml(在这里是springmvc-servlet.xml) -->
<!-- <init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:web/springmvc-servlet.xml</param-value>
</init-param> -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--
第一种:*.mvc,访问以*.mvc结尾由DispatcherServlet进行解析
第二种:/,所有访问的地址都由DispatcherServlet进行解析,对于静态文件的解析需要配置DispatcherServlet
进行解析,使用此种方式可以实践RESTful风格的url【拦截所有的请求排除jsp!】
第三种:/*,这样配置不对,使用这种配置,最终要转发到一个jsp页面时无法找到handler,会报错!
-->
<url-pattern>*.mvc</url-pattern>
</servlet-mapping>
</web-app>
2.springmvc-servlet.xml:配置处理器映射器(HandlerMapping)、处理器适配器(HandlerAdapter)、视图解析器(View resolver)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
<!-- 一、配置处理器Handler -->
<!-- 加载如下所述的两个Handler到Spring容器 -->
<!-- 第一种Handler:实现 Controller接口 -->
<bean id = "usercontroller1" name="/login1.mvc" class="com.cyn.controller.UserController1">
</bean>
<!-- 第二种Handler:实现 HttpRequestHandler接口-->
<bean id = "usercontroller2" class="com.cyn.controller.UserController2">
</bean>
<bean id = "usercontroller3" class="com.cyn.controller.UserController3">
</bean>
<!-- 第三种Handler:基于注解的Handler -->
<!-- 因为不可能每写一个Handler都在这里添加一个bean进行注册
所以就出现了下面通过context:component-scan进行组件扫描的方式
*切记:同一个Handler不可以通过不同方法多次注册!否则会报错找不到该路径
-->
<!-- <bean class="com.cyn.controller.UserController3">
</bean> -->
<!-- 二、配置处理器映射器 -->
<!-- 注意:
所有的映射器都实现了HandlerMapping接口,所有不同实现形成了不同的映射方法。
在配置文件中可以有同一个url被多个映射器映射,但是存在优先级!
·多种映射器可以共存使用,在这里通过三种url均可访问UserController1控制器进行处理
·第一种url:/login.mvc通过第一种映射器进行映射
·第二、三种url:/login1.mvc和/login2.mvc通过第二种映射器进行映射
-->
<!-- 第一种映射器(基于xml映射):
将bean的name作为url进行查找,需要在配置Handler时知道beanname(就是url)
-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
</bean>
<!-- 第二种映射器(基于xml映射) -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<!-- 对id为usercontroller1的这个bean进行url映射,
url是/login1.mvc或者是/login2.mvc
-->
<prop key="/login2.mvc">usercontroller1</prop>
<prop key="/login3.mvc">usercontroller1</prop>
<prop key="/login4.mvc">usercontroller2</prop>
</props>
</property>
</bean>
<!-- 第三种映射器 (基于注解映射)-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
</bean>
<!-- 三、配置处理器适配器 -->
<!-- 注意:所有的适配器都实现了HandlerAdapter方法 -->
<!-- 第一种适配器(基于xml)
要求Handler实现 Controller接口
-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter">
</bean>
<!-- 第二种适配器 (基于xml)
要求Handler实现 HttpRequestHandler接口
-->
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter">
</bean>
<!-- 第三种适配器(基于注解)-->
<bean class = "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
</bean>
<!-- 重要:实际开发中使用以下基于注解的处理器、映射器和适配器 -->
<!-- 【1.配置基于注解的映射器和适配器】 -->
<!-- 使用mvc:annotation-driven可以代替上面注解映射器和注解适配器
另外实际开发选择这种方法,因为mvc:annotation-driven默认还加载了很
多的参数绑定方法,比如:json转换解析器默认加载...
-->
<!--属性:
(1)conversion-service:在适配器中注入我们以后自定义的参数绑定器
(2)validator:在适配器中注入我们以后自定义的校验器
-->
<mvc:annotation-driven>
</mvc:annotation-driven>
<!-- 【2.配置基于注解的处理器】 -->
<!-- 组件扫描:可以扫描controller、service、dao
这里让扫描controller,扫描controller包下所有@Controller标识的Handler
目的:加载所有Handler到Spring容器中 ,通过注解映射器进行查找!
-->
<!-- <context:component-scan base-package="com.cyn.controller">
</context:component-scan> -->
<!-- 【3.配置视图解析器 】
解析jsp视图,默认使用jstl标签,classpath下面得有jstl的jar包
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置jsp路径的前缀 -->
<property name="prefix" value="/WEB-INF/pages/" />
<!-- 配置jsp路径的后缀 -->
<property name="suffix" value=".jsp" />
</bean>
</beans>
四、需要程序员编程实现处理器Handler
1.第一种Hnadler:实现 Controller接口
package com.cyn.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
//第一种适配器要求Handler实现 Controller接口
public class UserController1 implements Controller{
@Override
public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
// TODO Auto-generated method stub
ModelAndView model = new ModelAndView();
//设置视图
model.setViewName("login");
//ModelAndView的addObject()方法相当于在Servlet中request的setAttribute()
//设置模型数据
model.addObject("username","user1");
//相当于转发,但是需要视图解析器进行解析
return model;
}
}
2.第二种Hnadler:实现 HttpRequestHandler接口
package com.cyn.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import jdk.nashorn.internal.ir.RuntimeNode.Request;
//第二种适配器 要求Handler实现 HttpRequestHandler接口
public class UserController2 implements HttpRequestHandler{
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
//设置模型数据
request.setAttribute("username", "user2");
//设置转发的视图,转发不需要视图解析器进行解析
request.getRequestDispatcher("login.jsp").forward(request, response);
//使用此种方法可以通过response,设置响应的数据格式,比如json数据
/*response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("json串");*/
}
}
3.第三种Handlr:基于注解【实际开发使用】
package com.cyn.controller;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import org.apache.tomcat.jni.File;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import com.cyn.controller.validation.ValidGroup1;
import com.cyn.po.User;
import com.cyn.po.po.ItemUser;
//第三种适配器:注解开发下的Handler
//使用@Controller标识它是一个控制器
@Controller
/*为了对url进行分类管理,分层拦截类和方法,可以在这里定义根路径,最终访
*问url是根路径(确定某个controller类)+子路径(确定method方法)
*例如:请求用户登陆界面的url为:/user/login.mvc
*第一个/user:映射到有关用户操作的Controller控制类
*第二个/login:映射用户的具体请求操作为登陆
*/
@RequestMapping("/user")
public class UserController3{
@ModelAttribute("itemuser")
public Map<String,String> getItemUser(){
Map<String,String> itemUser = new HashMap<String,String>();
itemUser.put("user1", "lisi");
itemUser.put("user2", "zhangsan");
return itemUser;
}
/*使用@RequestMapping实现对getLogin()方法和url进行
*映射一个方法对应一个url,一般建议url和方法名一致,方便维护
*method:用来限制http请求方法
*/
@RequestMapping(value = "/login",method = {RequestMethod.GET})
//切记:不论是哪种返回值类型,最终都要经过SpringMVC的适配器内置策略返回ModelAndView类型!
public ModelAndView getLogin(){
ModelAndView modelAndView = new ModelAndView();
//设置视图,如果在视图解析器中配置了jsp路径的前缀和后缀,修改为如下:
//model.setViewName("/WEB-INF/pages/login.jsp");
modelAndView.setViewName("login");
//ModelAndView的addObject()方法相当于在Servlet中request的setAttribute()
//设置模型数据
modelAndView.addObject("username","user1");
//相当于转发,但是需要视图解析器进行解析
return modelAndView;
}
}