SpringMVC的执行流程
1
2
3
4
6
5
7
请求
DispatcherServlet
处理器映射
控制器
模型及逻辑视图名
视图
视图解析器
响应
步骤1
当使用SpringMVC框架时,所有符合要求的请求都会通过一个叫做前端控制(DispatcherServlet)的Servlet。前端控制器是常用的web应用程序模式,在这里一个单例的前端控制器会将请求委托给SpringMVC的其他组件来处理请求。
<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一处理 -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--配置SpringMVC配置文件的位置与名称 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:Spring-MVC.xml</param-value>
</init-param>
<!--将DispatcherServlet的初始化时间提前到服务器启动时 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
<!--
设置SpringMVC的核心控制器所能处理的请求路径
/所表示的请求可以是/login或.js或.html或.css方式的请求路径
但是/不包括.jsp请求路径的请求
-->
</servlet-mapping>
步骤2
前端控制器需要知道将请求发送给哪一个控制器,所以前端控制器会去查询处理器映射器,来确定请求的下一站来哪里。处理器映射器会根据请求携带的URL进行决策。
/*
@Controller注解会将Test类声明为一个控制器
当组件扫描到Test类时,会将其声明为Spring应用上下文中的一个bean
*/
@Controller
public class Test {
/*
@RequestMapping请求映射注解 是将请求地址和处理请求的方法关联起来建立映射关系
*/
@RequestMapping("/info")
public String f(){
return "index";
}
}
这时当URL路径为/info时就会执行f()方法。
//该注解可以加在类和方法上
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
//可以同时定义多个字符串值来映射被注解标记的方法
@AliasFor("path")
String[] value() default {};
//value的别名
@AliasFor("value")
String[] path() default {};
//声明请求的方式 GET , POST , PUT, DELETE
RequestMethod[] method() default {};
// 对请求参数进行约束
String[] params() default {};
// 对请求头进行约束
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
步骤3
当选择了合适的控制器后,前端控制器会将请求发送给对应的控制器。控制器将会处理请求。 控制器处理请求时,会获取请求所携带的请求参数,响应报文的请求头以及域对象的值进行处理,并设置域对象的值以及响应行,响应头和响应体来进行响应。
请求参数的获取
请求中设置请求参数的格式一般有两种: 原始风格: /info?id=1 Restful风格:/info/1 原始风格的获取:
/*
1.通过ServletAPI获取请求参数 String idName = servletRequest.getParameter("id");
2.通过@RequestParam注解获取请求参数 @RequestParam("id")String ID
3.如果请求参数的名称与形参名字一直,将会自动赋值
*/
@RequestMapping("/info")
public String f(ServletRequest servletRequest, @RequestParam("id")String ID, String id){
String idName = servletRequest.getParameter("id");
return "success";
}
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
//指定形参赋值的请求参数的参数名
@AliasFor("name")
String value() default "";
//value的别名
@AliasFor("value")
String name() default "";
//当该值为true时,则当前请求必须传入value指定的请求参数,若没有传递请求参数,且没有设置 defaultValue属性,则会出现400,若设置为false没有传入请求参数且没有默认值时则该值为null
boolean required() default true;
//设置默认值,默认情况没有数值
String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}
Restful风格的获取:
/*
通过@PathVariable注解来获取请求参数
*/
@RequestMapping("/info/{id}")
public String f(@PathVariable("id")String ID){
return "success";
}
请求头的获取
/*
通过@RequestHeader注解获取请求头的值 @RequestHeader("host")String p
该注解用法和RequestParam用法一致
*/
@Controller
public class Test {
@RequestMapping("/info")
public String f(@RequestHeader("host")String p){
System.out.println(p);
return "success";
}
}
域对象共享数据
request域
/*
request域对象可以在一次请求中实现数据的共享
通过ServletAPI设置域对象的值 servletRequest.setAttribute("x", "xxxxx");
通过ModelAndView设置域对象的值 mv.addObject("y", "yyyyy");
*/
@RequestMapping("/info")
public ModelAndView f(ServletRequest servletRequest){
servletRequest.setAttribute("x", "xxxxx");
ModelAndView mv = new ModelAndView();
mv.addObject("y", "yyyyy");
mv.setViewName("success");
return mv;
}
session域
/*
session域对象可以在一次会话中进行数据的共享
利用HttpSession对象进行数据共享 session.setAttribute("p", "hello")
*/
@RequestMapping("/info")
public String f(HttpSession session){
session.setAttribute("p", "hello");
Object p = session.getAttribute("p");
return "success";
}
application域
/*
application域对象可以在整个web应用中进行数据的共享
可以通过HttpSession对象获取ServletContext对象进行数据的共享
ServletContext servletContext = session.getServletContext();
servletContext.setAttribute("p", "hello");
*/
@RequestMapping("/info")
public String f(HttpSession session){
ServletContext servletContext = session.getServletContext();
servletContext.setAttribute("p", "hello");
servletContext.getAttribute("p");
return "success";
}
}
步骤4
当控制器处理完请求后,处理器会向前端控制器返回数据模型及逻辑视图名(不管返回的形式是咋样,最终数据模型和逻辑视图名都会封装在ModelAadView对象中)。
设置响应体
/*
当用@ResponseBody注解标记控制器时,控制器返回的值不在是逻辑视图名来进行页面的跳转而是作为响应体进行数据的展示
*/
@RequestMapping("/info")
@ResponseBody
public String f(){
return "success";
}
设置响应报文
/*
ResponseEntity对象可以作为控制器的返回值类型,该控制器的返回值就是响应到浏览器的响应报文
这里以文件下载案例来演示ResponseEntity对象的用法
*/
@RequestMapping("/info")
public ResponseEntity<byte[]> f(HttpSession session) throws IOException {
//获取servletContext对象
ServletContext servletContext = session.getServletContext();
//通过相对路径来获取图片的真实路径
String path = servletContext.getRealPath("/static/img/1.jpg");
//创建输入流
InputStream is = new FileInputStream(path);
//创建字节数组来存放图片
byte[] bytes = new byte[is.available()];
//将图片读入数组
is.read(bytes);
//创建HttpHeaders对象来设置响应头信息
MultiValueMap<String, String> httpHeaders = new HttpHeaders();
//设置下载方式以及下载文件的名称
httpHeaders.add("Content-Disposition",
"attachment;filename=1.jpg");
//设置响应状态码
HttpStatus ok = HttpStatus.OK;
//分别传入响应体,响应头和响应状态码来创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, httpHeaders, ok);
//关闭输入流
is.close();
return responseEntity;
}
步骤5
前端控制器会使用视图解析器来将控制器返回的逻辑视图名匹配为一个特定视图的实现。它可能是JSP也可能是Thymeleaf或者其他。 1.当返回值只是逻辑视图名时(此时为转发),它会被SpringMVC中配置的视图解析器解析,这时视图的类型就是SpringMVC配置视图解析器的类型 2.当返回值有前缀forward:时,它会被InternalResourceViewResolver视图解析器解析,创建InternalResourceView视图 3.当返回值有redirect:前缀时,它会创建RedirectView视图. 当逻辑视图名有前缀时,首先视图解析器会去掉签注,当没有配置相应的视图解析器时,默认视图解析器会去掉前缀直接返回,配置的视图解析器会加上前后缀。
<!-- 配置InternalResourceView视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" /><!-- 前缀 -->
<property name="suffix" value=".html" /><!-- 后缀 -->
</bean>
<!-- 配置Thymeleaf视图解析器 -->
<bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/templates/" />
<property name="suffix" value=".html" />
<property name="templateMode" value="HTML" />
<property name="cacheable" value="false" />
<property name="characterEncoding" value="UTF-8" />
</bean>
<bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
</bean>
步骤 6 7
视图将使用模型数据渲染输出,这个输出会通过响应对象传递给客户端。
其他
GET、POST、DELETE、PUT请求
当前浏览器只允许GET和POST请求。不支持DELETE和PUT请求。当我们的表单设置的请求方式是PUT或者DELETE时,会被当做GET请求进行处理。
<!--PUT请求会被当作GET请求进行处理 -->
<form action="/111/info" method="PUT">
账号:<input type="text" name="userName">
<br>
密码:<input type="password" name="userPassword">
<br>
<input type="submit" value="提交">
</form>
这里当提交表单访问/info方法时就会报错: 405 Request method ‘GET’ not supported
//由于该方法要求PUT请求方式,但表单的PUT请求会被当做GET请求处理
@RequestMapping(value = {"/info"}, method = RequestMethod.PUT)
public String f() {
return "success";
}
spring3.0添加了一个过滤器,可以将这些请求转换为标准的http方法,使得支持PUT与DELETE请求,该过滤器为HiddenHttpMethodFilter。
<!--配置HiddenHttpMethodFilter过滤器使得支持PUT与DELETE请求 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
当配置完过滤器时,当需要发送PUT和DETELE请求时,只需要将请求方式写为post并传入_method并设置为PUT/DETELE.
<form action="/111/info" method="post">
<input TYPE="hidden" name="_method" value="PUT">
账号:<input type="text" name="userName">
<br>
密码:<input type="password" name="userPassword">
<br>
<input type="submit" value="提交">
</form>
访问静态资源问题
请求的URL路径前端控制器只会拿它和处理器映射器里的映射进行对比。但是当请求的URL不是处理器的URL而是静态资源时,前端控制器就不能处理会报错。这时我们需要配置defaultservlet,当前端控制器不能处理时,交给defaultservlet进行处理。
<!--开放对静态资源的访问 -->
<mvc:default-servlet-handler/>
字符乱码问题
我们只需要在前端控制器处理请求之前,设置编码即可。所以我们可以配置过滤器来解决这个问题.
<!--设置过滤器,在DispatcherServlet执行前执行该过滤器以解决请求参数乱码问题 -->
<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>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
|