Spring MVC
Spring MVC介绍
- Spring MVC 是Spring体系的轻量级Web MVC框架
- Spring MVC的核心Controller控制器, 用于处理请求, 产生响应
- Spring MVC基于Spring IoC容器运行, 所有对象被IoC管理
Spring 5.x版本变化
- Spring 5.x最低要求JDK8与J2EE 7(Servlet 3.1/Tomcat 8.5+)
- Spring 5.x支持JDK8/9, 可以使用新特性
- Spring 5.x最重要的新特性支持响应式编程
Spring MVC环境配置
-
Maven依赖
spring-webmvc
-
web.xml配置
DispatcherServlet
<!-- DispatcherServlet是Spring MVC最核心对象, DispatcherServlet用于拦截Http请求, 并根据请求的URL调用 与之对应的Controller方法, 来完成Http请求的处理--> <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> <!-- 在Web应用启动时自动创建Spring IoC容器, 并初始化DispatcherServlet --> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- "/"代表拦截所有请求 --> <url-pattern>/</url-pattern> </servlet-mapping>
-
配置applicationContext的mvc标记
<context:component-scan base-package="com.zk.springmvc"/> <!-- 启用Spring MVC的注解开发模式 --> <mvc:annotation-driven/> <!-- 将图片/JS/CSS等静态资源排除在外, 可提高Spring MVC的执行效率 --> <mvc:default-servlet-handler/>
-
开发Controller控制器
Spring MVC数据绑定
URL Mapping
- URL Mapping指将URL与Controller方法绑定
- 通过将URL与方法绑定, Spring MVC便可通过Tomcat对外暴露服务
URL Mapping 注解
@RequestMapping
- 通用绑定@GetMapping
- 绑定Get请求@PostMapping
- 绑定Post请求
接收请求参数
接收请求参数的常用做法
- 使用Controller方法参数接受数据
- 使用Java Bean接收数据
接受复合数据
- 直接使用数组
- 使用List集合, 要在参数前添加
@RequestParam
注解 - 在实体对象中使用List集合属性
- 使用Map时只能接受单个数据, 对于复合数据会出现数据丢失
关联对象赋值
修改表单name属性增加关联对象名作为前缀
日期类型转换
在方法参数前增加@DateTimeFormat(pattern = "yyyy-MM-dd")
或者
增加全局日期类型转换器
-
日期转换器类需要实现
Converter
接口 -
配置applicationContext.xml通知Spring MVC有哪些自定义的转换类
<mvc:annotation-driven conversion-service="conversionService"/> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.zk.springmvc.converter.MyDateConverter"/> </set> </property> </bean>
如果既书写了@DateTimeFormat
也增加了日期转换器, 在实际运行时以哪个为准呢?
- Sping MVC的强制要求是一旦增加了对应的日期转换器, 那优先使用转换器类来处理类型转换
解决中文乱码
Web应用的中文乱码由来
- Tomcat 默认使用字符集ISO-8859-1, 属于西欧字符集
- 解决乱码的核心思路是将ISO-8859-1转换为UTF-8
- Controller中请求与响应都需要设置UTF-8字符集
中文乱码的配置
-
Get请求乱码 - 在Tomcat的server.xml增加
URIEncoding
属性指明UTF-8- 在Tomcat 8.0以后版本的
URIEncoding
属性默认就是UTF-8
- 在Tomcat 8.0以后版本的
-
Post请求乱码 - web.xml配置
CharacterEncodingFilter
<filter> <filter-name>characterFilter</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>characterFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
Response响应乱码 - Spring配置文件中配置
StringHttpMessageConverter
<mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=utf-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
响应输出结果
响应中产生结果的两种方式
@ResponseBody
- 产生响应文本- ModelAndView - 利用模板引擎渲染输出
@ResponseBody
@ResponseBody
注解代表直接产生响应体的数据, 过程不涉及任何视图@ResponseBody
可产生标准字符串/JSON/XML等格式数据@ResponseBody
所产生的字符串会被StringHttpMessageConverter
所影响
ModelAndView对象
- ModelAndView对象是指"模型(数据)与视图(界面)"对象
- 通过ModelAndView可将包含数据对象与模板引擎进行绑定
- Spring MVC中默认的View是JSP, 也可以配置其他模板引擎
ModelAndView对象核心用法
- mav.addObject()方法设置的属性默认存放在当前请求中
- 默认ModelAndView使用请求转发(forward)至页面
- 重定向使用
new ModelAndView("redirect:/index.jsp")
// String 与 ModelMap实现类似 ModelAndView 的功能
@GetMapping("/xxx")
public String showView1(Integer userId, ModelMap modelMap) {
String view = "/um/view.jsp";
User user = new User();
if (userId == 1) {
user.setUsername("lily");
} else if (userId == 2) {
user.setUsername("smith");
}
modelMap.addAttribute("u", user);
return view;
}
Spring MVC整合Freemarker
-
pom.xml引入依赖
freemarker
spring-context-support
-
通知Spring MVC 使用Freemarker模板引擎
在applicationContext.xml中配置<bean id="ViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <property name="contentType" value="text/html;charset=utf-8"/> <property name="suffix" value=".ftl"/> </bean>
-
配置Freemarker参数
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath" value="/WEB-INF/ftl"/> <property name="freemarkerSettings"> <props> <prop key="defaultEncoding">utf-8</prop> </props> </property> </bean>
RESTful开发风格
传统Web应用的问题
- 客户端只能是支持HTML的浏览器
- 而小程序, app等不支持
REST与RESTful
- REST - (Representational State Transfer)表现层状态转换, 资源在网络中以某种表现形式进行状态转移
- RESTful是基于REST理念的一套开发风格, 是具体的开发规则
RESTful传输数据
RESTful开发规范
- 所有的资源都使用URL作为用户交互入口
- 明确的语义规范(GET | POST | PUT | DELETE)
- 只返回数据(JSON | XML), 不包含任何展现
RESTful命名要求
URI | 说明 | 修改建议 |
---|---|---|
GET /articles?au=lily | 正确用法 | |
GET /a/1 | URI必须具有语义 | GET /student/1 |
POST /createArticle/1 | URI必须使用名词 | POST /article/1 |
GET /articles/author/1 | URI扁平化, 不超两级 | GET /articles/author?id=1 |
DELETE /articles/1 | URI名词区分单复数 | GET /articles?au=lily DELETE /article/1 |
RestController注解与路径变量
@RestController
代替@Controller
注解 说明当前类的所有方法的返回值都是RESTful风格的数据, 而不是页面跳转
路径变量是指存放在URI中可变的那一部分数值
// POST /article/1
// POST /restful/request/100
@PostMapping("/request/{rid}")
public String doPostRequest(@PathVariable("rid") Integer requestId) {
return "{\"message\":\"数据新建成功\", \"id\":" + requestId + "}";
}
简单请求与非简单请求
- 简单请求是指标准结构的HTTP请求, 对应GET/POST请求
- 非简单请求是复杂要求的HTTP请求, 指PUT/DELETE 或 扩展的标准请求
- 两者最大区别是非简单请求发送前需要发送预检请求
Spring MVC需要在web.xml中添加以下过滤器, 才支持非简单请求(PUT/DELETE)的请求参数获取
<filter>
<filter-name>formContentFilter</filter-name>
<filter-class>org.springframework.web.filter.FormContentFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>formContentFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
JSON序列化
利用Jackson
-
引入Maven依赖
jackson-core
jackson-databind
jackson-annotations
-
在方法定义的时候不再返回String, 而是直接返回需要JSON序列化的对象, 在Spring MVC在对外输出响应的时候, 这个对象会被Jackson自动序列化, 然后随着响应一起发送到客户端
-
在进行时间输出的时候, 需要在属性上添加
@JsonFormat
注解, 用于对对象进行格式化输出(日期, 数字, 货币等)@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date birthday;
浏览器的跨域访问
浏览器的同源策略
- 同源策略阻止从一个域加载的脚本去获取另一个域上的资源
- 只要协议、域名(二级域名)、端口有任何一个不同, 都被当作是不同的域
- 浏览器Console看到
Access-Control-Allow-Origin
错误就代表跨域了(触发了浏览器的同源策略), 请求得到的结果并不会被浏览器所处理
HTML中允许跨域的标签
- <img> - 显示远程图片
- <script> - 加载远程JS
- <link> - 加载远程CSS
Spring MVC跨域访问
CORS 跨域资源访问
- CORS是一种机制, 使用额外的HTTP头通知浏览器可以访问其他域
- URL响应头包含
Access-Control-*
指明请求允许跨域
Spring MVC解决跨域访问
@CrossOrigin
- Controller跨域注解(当前Controller所有映射的URL都可以被那些其他域的请求跨域访问)
@CrossOrigin(origins = {"http://localhost:8080"})
- <mvc:cors> - Spring MVC全局跨域配置
说明当前的请求需要跨域访问了
当前的响应可以被浏览器所解析
CORS全局配置
<mvc:cors>
<mvc:mapping path="/restful/**" allowed-origins="http://localhost:8080, https://www.baidu.com"
<!-- max-age是预检请求缓存的时间3600为1小时 -->
max-age="3600"/>
</mvc:cors>
Spring MVC拦截器
拦截器 - Interceptor
- 拦截器(Interceptor)用于对URL请求进行前置/后置过滤
- Interceptor与Filter用途相似, 但实现方式不同
- Interceptor底层就是基于Spring AOP面向切面编程实现
拦截器开发流程
-
Maven依赖
servlet-api
-
实现
HandlerInterceptor
接口 -
applicationContext配置过滤地址
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.zk.restful.interceptor.MyInterceptor"/> </mvc:interceptor> </mvc:interceptors>
HandlerInterceptor
接口
- preHandle - 前置执行处理
- postHandle - 目标资源已被Spring MVC处理, 但是还没产生响应之前 (后置执行处理)
- afterCompletion - 产生了响应文本以后
拦截器使用细则
对于资源的过滤和排除
<mvc:exclude-mapping path="/**.ico"/>
<mvc:exclude-mapping path="/**.jpg"/>
<mvc:exclude-mapping path="/**.gif"/>
<mvc:exclude-mapping path="/**.js"/>
<mvc:exclude-mapping path="/**.css"/>
或者 通过静态资源文件存放的规范来统一排除
<mvc:exclude-mapping path="/resources/**"/>
或者 通过缩小拦截的范围
<mvc:mapping path="/restful/**"/>
<mvc:mapping path="/restful2/**"/>
多Interceptor执行顺序
开发用户流量拦截器
-
拦截器产生的用户数据作为日志输出到日志文件上
配置logback.xml<configuration> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>[%thread] %d %level %logger{10} - %msg%n</pattern> </encoder> </appender> <appender name="accessHistoryLog" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>/var/log/history.%d.log</fileNamePattern> </rollingPolicy> <encoder> <pattern>[%thread] %d %level %logger{10} - %msg%n</pattern> </encoder> </appender> <root level="debug"> <appender-ref ref="console"/> </root> <logger name="com.zk.restful.interceptor.AccessHistoryInterceptor" level="INFO" additivity="false"> <appender-ref ref="accessHistoryLog"/> </logger> </configuration>
-
开发拦截器
public class AccessHistoryInterceptor implements HandlerInterceptor { private Logger logger = LoggerFactory.getLogger(AccessHistoryInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { StringBuilder log = new StringBuilder(); log.append(request.getRemoteAddr()); log.append("|"); log.append(request.getRequestURL()); log.append("|"); log.append(request.getHeaders("User-Agent")); logger.info(log.toString()); return true; } }