SpringMVC工作原理
SpringMVC简单介绍
什么是MVC?
MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。就是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向偶合。MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。
Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。
View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。
Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。 也就是说控制器做了个调度员的工作。
最典型的MVC就是JSP + servlet + javabean的模式。
SpringMVC
简而言之,springMVC是Spring框架的一部分,是基于java实现的一个轻量级web框架。
学习SpringMVC框架最核心的就是DispatcherServlet的设计,掌握好DispatcherServlet是掌握SpringMVC的核心关键。
1 2 3 4 5 6 7 8 9 10 11
| 1、DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求。 2、HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler。 3、返回处理器执行链,根据url查找控制器,并且将解析后的信息传递给DispatcherServlet 4、HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。 5、执行handler找到具体的处理器 6、Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。 7、HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。 8、DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。 9、视图解析器将解析的逻辑视图名传给DispatcherServlet。 10、DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图,进行试图渲染 11、将响应数据返回给客户端
|
基于XML的Hello_SpringMVC
1.添加pom依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.3.RELEASE</version> </dependency> </dependencies>
|
2.编写web.xml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </init-param> </servlet>
<servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
|
3、编写springmvc需要的spring配置文件,applicationContext.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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.xsd">
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"></property> <property name="suffix" value=".jsp"></property> </bean>
<bean id="/hello" class="com.controller.HelloController"></bean> </beans>
|
4.HelloController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
public class HelloController implements Controller { public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { ModelAndView mv = new ModelAndView(); mv.addObject("msg","helloSpringMVC"); mv.setViewName("hello"); return mv; } }
|
基于注解的Hello_SpringMVC
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Controller public class HelloController{
@RequestMapping("/hello") public String hello(Model model){ model.addAttribute("msg","hello,SpringMVC"); return "hello"; } }
|
1 2 3 4 5 6 7 8
| 具体的运行流程: 1、客户端发送请求http://localhost:8080/hello 2、由tomcat接受到对应的请求 3、SpringMVC的前端控制器DispatcherServlet接收到所有的请求 4、查看请求地址和@RequestMapping注解的哪个匹配,来找到具体的类的处理方法 5、前端控制器找到目标处理类和方法之后,执行目标方法 6、方法执行完成之后会有一个返回值,SpringMVC会将这个返回值用视图解析器进行解析拼接成完整的页面地址 7、DispatcherServlet拿到页面地址之后,转发到具体的页面
|
@RequestMapping
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| @RequestMapping("/kuro") public class HelloController{
@RequestMapping("/hello") public String hello(Model model){ model.addAttribute("msg","hello,SpringMVC"); return "hello"; }
@RequestMapping(value = "/hello2",method = RequestMethod.POST) public String hello2(){ return "hello"; }
@RequestMapping(value = "/hello3",params = {"username!=123","age"}) public String hello3(String username){ return "hello"; }
@RequestMapping(value = "/hello4",headers = {"User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0"}) public String hello4(){ return "hello"; }
@RequestMapping(value = "/**/h*llo?") public String hello5(){ return "hello"; } }
|
@PathVariable
如果需要在请求路径中的参数像作为参数应该怎么使用呢?可以使用@PathVariable注解,此注解就是提供了对占位符URL的支持,就是将URL中占位符参数绑定到控制器处理方法的参数中。
1 2 3 4 5 6 7 8 9
| @Controller @RequestMapping("/kuro") public class HelloController{
@RequestMapping(value = "/pathVariable/{name}") public String pathVariable(@PathVariable("name") String name){ return "hello"; } }
|
REST
1 2 3 4 5
| GET:获取资源 /book/1 POST:新建资源 /book PUT:更新资源 /book/1 DELETE:删除资源 /book/1 一切看起来都非常美好,但是大家需要注意了,我们在发送请求的时候只能发送post或者get,没有办法发送put和delete请求,那么应该如何处理呢?
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Controller public class RestController {
@RequestMapping(value = "/user",method = RequestMethod.POST) public String add(){ return "success"; }
@RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE) public String delete(@PathVariable("id") Integer id){ return "success"; }
@RequestMapping(value = "/user/{id}",method = RequestMethod.PUT) public String update(@PathVariable("id") Integer id){ return "success"; }
@RequestMapping(value = "/user/{id}",method = RequestMethod.GET) public String query(@PathVariable("id") Integer id){ return "success"; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class MyFilter implements Filter { public void init(FilterConfig filterConfig) throws ServletException { }
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { servletRequest.setCharacterEncoding("UTF-8"); servletResponse.setCharacterEncoding("UTF-8"); filterChain.doFilter(servletRequest,servletResponse); }
public void destroy() { } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package org.springframework.web.filter; public class HiddenHttpMethodFilter extends OncePerRequestFilter { private String methodParam = "_method";
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest requestToUse = request; if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) { String paramValue = request.getParameter(this.methodParam); if (StringUtils.hasLength(paramValue)) { String method = paramValue.toUpperCase(Locale.ENGLISH); if (ALLOWED_METHODS.contains(method)) { requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method); } } } filterChain.doFilter((ServletRequest)requestToUse, response); } }
|
1 2 3 4 5 6 7 8 9
| <filter> <filter-name>hiddenFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <body> <form action="/user" method="post"> <input type="submit" value="增加"> </form> <form action="/user/1" method="post"> <input name="_method" value="delete" type="hidden"> <input type="submit" value="删除"> </form> <form action="/user/1" method="post"> <input name="_method" value="put" type="hidden"> <input type="submit" value="修改"> </form> <a href="/user/1">查询</a><br/> </body>
|
@RequestParam
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Controller public class RequestController {
@RequestMapping("/request") public String request(@RequestParam(value = "user",required = false,defaultValue = "hehe") String username){ return "success"; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Controller public class RequestController {
@RequestMapping("/header") public String header(@RequestHeader("User-Agent") String agent){ return "success"; } }
|
@CookieValue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Controller public class RequestController {
@RequestMapping("/cookie") public String cookie(@CookieValue("JSESSIONID") String id){ return "success"; } }
|
乱码问题
GET请求:在tomcat的server.xml文件中,添加URIEncoding=“UTF-8”
POST请求:编写过滤器进行实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <filter> <filter-name>characterEncodingFilter</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> <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>
|
如果配置了多个过滤器,那么字符编码过滤器一定要在最前面,否则失效。
使用原生API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
@RequestMapping("api") public String api(HttpSession session, HttpServletRequest request, HttpServletResponse response){ request.setAttribute("requestParam","request"); session.setAttribute("sessionParam","session"); return "success"; }
|
响应数据
使用Model,Map,ModelMap、ModelAndView传输数据到页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @RequestMapping("output1") public String output1(Model model){ model.addAttribute("msg","hello,Springmvc"); return "output"; }
@RequestMapping("output2") public String output2(ModelMap model){ model.addAttribute("msg","hello,Springmvc"); return "output"; }
@RequestMapping("output3") public String output1(Map map){ map.put("msg","hello,Springmvc"); return "output"; }
@RequestMapping("mv") public ModelAndView mv(){ ModelAndView mv = new ModelAndView(); mv.setViewName("output"); mv.addObject("msg","hello.modelAndView"); return mv; }
|
所有的参数值都设置到了request作用域中
@SessionAttribute
此注解可以表示,当向request作用域设置数据的时候同时也要向session中保存一份,此注解有两个参数,一个value(表示将哪些值设置到session中),另外一个type(表示按照类型来设置数据,一般不用,因为有可能会将很多数据都设置到session中,导致session异常)。
1 2 3 4 5 6 7 8 9 10
| @Controller @SessionAttributes(value = "msg") public class OutputController {
@RequestMapping("output1") public String output1(Model model){ model.addAttribute("msg","hello,Springmvc"); return "output"; } }
|
@ModelAttribute
会在请求执行前先调用MyModelAttribute
方法,在模板的基础上写入请求参数的值,使用都是的同一个Model对象,ModelAttribute除了可以使用设置值到model中之外,还可以利用返回值。
1 2 3 4 5 6 7 8 9 10
| @RequestMapping("update") public String update(@ModelAttribute("user") User user,Model model){
}
@ModelAttribute public void MyModelAttribute(Model model){ User user = new User(); model.addAttribute("user",user); }
|
forward
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@RequestMapping("/forward01") public String forward(){ return "forward:/index.jsp"; }
@RequestMapping("/forward02") public String forward2(){ return "forward:/forward01"; }
|
redirect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@RequestMapping("redirect") public String redirect(){ return "redirect:/index.jsp"; }
@RequestMapping("/redirect2") public String redirect2(){ return "redirect:/redirect"; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 转发: 由服务器的页面进行跳转,不需要客户端重新发送请求: 特点如下: 1、地址栏的请求不会发生变化,显示的还是第一次请求的地址 2、请求的次数,有且仅有一次请求 3、请求域中的数据不会丢失 4、根目录:localhost:8080/项目地址/,包含了项目的访问地址
重定向: 在浏览器端进行页面的跳转,需要发送两次请求(第一次是人为的,第二次是自动的) 特点如下: 1、地址栏的地址发生变化,显示最新发送请求的地址 2、请求次数:2次 3、请求域中的数据会丢失,因为是不同的请求 4、根目录:localhost:8080/ 不包含项目的名称
|
静态资源
由于DispatcherServlet会拦截所有的请求,而此时我们没有对应图片的请求处理方法,所以此时报错了,想要解决的话非常简单,只需要添加一个配置即可
1 2 3 4 5
|
<mvc:default-servlet-handler/>
|
但是加上此配置之后,大家又发现此时除了静态资源无法访问之外,我们正常的请求也无法获取了,因此还需要再添加另外的配置:
1 2
| <mvc:annotation-driven/>
|
组件说明:
以下组件通常使用框架提供实现:
DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。
ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。
组件:
1、前端控制器DispatcherServlet(不需要工程师开发),由框架提供
作用:接收请求,响应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
2、处理器映射器HandlerMapping(不需要工程师开发),由框架提供
作用:根据请求的url查找Handler
HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
3、处理器适配器HandlerAdapter
作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
4、处理器Handler(需要工程师开发)
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler
Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。
5、视图解析器View resolver(不需要工程师开发),由框架提供
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。
6、视图View(需要工程师开发jsp…)
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)
核心架构的具体流程步骤如下:
1、首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
2、DispatcherServlet——>HandlerMapping, HandlerMapping 将会把请求映射为HandlerExecutionChain 对象(包含一个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
3、DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
4、HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名);
5、ModelAndView的逻辑视图名——> ViewResolver, ViewResolver 将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
6、View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
下边两个组件通常情况下需要开发:
Handler:处理器,即后端控制器用controller表示。
View:视图,即展示给用户的界面,视图中通常需要标签语言展示模型数据。
“本篇文章主要摘自参考资料”