第4章 Spring MVC 4.x
2018-12-06 本文已影响0人
意大利大炮
4.1 Spring MVC概述
- Spring MVC是Spring中的一个用于web开发的模块
- MVC:Model + View + Controller(数据模型 + 视图 + 控制器)
- 三层架构:Presentation tier + Application tier + Data tier (展现层 + 应用层 + 数据访问层)
- MVC与三层架构的关系:MVC只存在于三层架构的展现层
- M实际上是数据模型,是包含数据的对象,在Spring MVC例有一个专门的类叫Model,用来和V之间的数据交互。传值
- V指的是视图页面,包含JSP、freeMarker、Velocity、Thymeleaf、Tile等。
- C就是控制器了(Spring MVC中注解@Controller的类)
4.2 Spring MVC项目快速搭建
4.2.1 构建maven项目
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.spring</groupId>
<artifactId>springmvcDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<jsp.verion>2.2</jsp.verion>
<jstl.version>1.2</jstl.version>
<servlet.version>3.1.0</servlet.version>
<!-- spring -->
<spring-framework.version>4.1.5.RELEASE</spring-framework.version>
<!-- logging -->
<logback.version>1.0.13</logback.version>
<slf4j.version>1.7.5</slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- 其他web依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp.verion}</version>
<scope>provided</scope>
</dependency>
<!-- spring and Transactions -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- 使用slf4J 和 LogBack作为日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>${logback.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.2.2 日志配置
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="1 seconds">
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
<resetJUL>true</resetJUL>
</contextListener>
<jmxConfigurator/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>logback: %d{HH:mm:ss.SSS} %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.web" level="DEBUG"/>
<root level="info">
<appender-ref ref="console"/>
</root>
</configuration>
4.2.3 演示页面
- 在src/main.resource建立views目录,并建立index.jsp(resource下防止页面是spring boot的放置方式)
<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<pre>
Welcome to Spring MVC world
</pre>
</body>
</html>
4.2.4 SpringMVC配置
- 使用配置类配置一个JSP的ViewResolver,用来映射路径和实际页面的位置
- @EnableWebMvc注解会开启一些默认配置,如一些ViewResolve或者MessageConverter等等
@Configuration
@EnableWebMvc
@ComponentScan("com.springmvcDemo")
public class MyMvcConfig {
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/classes/views/");
viewResolver.setSuffix(".jsp");
viewResolver.setViewClass(JstlView.class);
return viewResolver;
}
}
4.2.5 Web配置
- 创建一个集成了WebApplicationInitializer接口的类,实现此接口可以自动的呗SpringServletContainerInitializer(用来启动Servlet3.0的容器)获取到
public class WebInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
// 注册
ctx.register(MyMvcConfig.class);
// 关联
ctx.setServletContext(servletContext);
ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
}
4.2.6 简单控制器
@Controller
public class HelloController {
@RequestMapping("/index")
public String hello() {
return "index";
}
}
4.2.7 运行
- 程序部署到Tomcat,启动tomcat后,访问控制器的地址
- 不知道为什么这里没能启动成功,需要再多次测试
4.3 Spring MVC 的常用注解
4.3.1 常用注解
- @Controller
- @RequestMappering
- @ResponseBody: 防止返回结果被解析为页面
- @RequestBody
- @PathVariable
- @RestController
4.3.2 使用示例
- 添加jackson以及相关依赖,获得对象和json或xml之间的转换
- 正常开发中,很少用到xml,一般使用json,因为json比xml简洁,但这里演示同时支持json和xml的maven与只支持json的maven
同时支持xml和json的依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.5.3</version>
</dependency>
只纯支持json的依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.3</version>
</dependency>
创建一个对象,用于演示获取request对象参数和返回此对象到response
jackson对对象和json做转换时,一定需要一个空的构造方法???
public class DemoObj {
private Long id;
private String name;
public DemoObj() {
super();
}
public DemoObj(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
创建演示控制器
@Controller // 表明是一个控制器
@RequestMapping("/anno") // 映射此类的访问路径
public class DemoAnnoContraoller {
@RequestMapping(produces = "text/plain;charset=UTF-8") // 未设置路径,所以使用类级别的路径;produces可指定返回的response的媒体类型和字符集
public @ResponseBody String index(HttpServletRequest request) { // 演示可接收HttpServletRequest作为参数
return "url:" + request.getRequestURL() + "can access";
}
@RequestMapping(value = "/pathvar/{str}", produces = "text/plain;charset=UTF-8") // 演示可接收路径参数,并在方法参数前结合@PathVariable使用
public @ResponseBody String demoPathVar(@PathVariable String str, HttpServletRequest request) {
return "url:" + request.getRequestURL() + "can access, str" + str;
}
@RequestMapping(value = "/requestParam/", produces = "text/plain;charset=UTF-8") // 演示常规的request参数获取,访问路径为/anno/requestParam?id=1
public @ResponseBody String passRequestParam(Long id, HttpServletRequest request) {
return "url:" + request.getRequestURL() + "can access, id" + id;
}
@RequestMapping(value = "/obj", produces = "application/json;charset=UTF-8") // 演示直接解释参数到对象,访问路径为/anno/obj?id=1&name=xx
@ResponseBody // 演示@ResponseBody注解可以用到方法上
public String passObj(DemoObj obj, HttpServletRequest request) {
return "url:" + request.getRequestURL() + "can access, obj id" + obj.getId() + " obj name: " + obj.getName();
}
@RequestMapping(value = {"/name1", "/name2"}, produces = "text/plain;charset=UTF-8") // 演示映射不同的路径到相同的方法上,访问路径为/anno/name1或/anno/name2
public @ResponseBody String remove(HttpServletRequest request) {
return "url:" + request.getRequestURL() + "can access";
}
}
演示rest方式的控制器
@RestController
@RequestMapping("/rest")
public class DemoRestController {
// 演示返回对象自动解析为json
@RequestMapping(value = "/getJson", produces = "application/json;charset=UTF-8")
public DemoObj getJson(DemoObj obj) {
return new DemoObj(obj.getId() + 1, obj.getName() + "yy");
}
// 演示返回对象为xml
@RequestMapping(value = "/getJson", produces = "application/xml;charset=UTF-8")
public DemoObj getXml(DemoObj obj) {
return new DemoObj(obj.getId() + 1, obj.getName() + "yy");
}
}
4.4 Spring MVC的基本配置
- Spring MVC的定制配置需要我们的配置类继承一个WebMvcConfigurerAdapter类,并在此类使用@EnableWebMvc注解,来开启对Spring MVC的配置支持,这样就可以重写这个类的方法,完成常用的配置
- WebMvcConfigurerAdapter类是WebMvcConfigurer接口的实现,所以WebMvcConfigurer的API内的方法也可以用来配置MVC。下面展示一下这两个类的源码:
WebMvcConfigurerAdapter:
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
public WebMvcConfigurerAdapter() {
}
public void addFormatters(FormatterRegistry registry) {
}
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
public Validator getValidator() {
return null;
}
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
public void configurePathMatch(PathMatchConfigurer configurer) {
}
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
}
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
}
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
}
public MessageCodesResolver getMessageCodesResolver() {
return null;
}
public void addInterceptors(InterceptorRegistry registry) {
}
public void addViewControllers(ViewControllerRegistry registry) {
}
public void configureViewResolvers(ViewResolverRegistry registry) {
}
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
}
WebMvcConfigurer:
public interface WebMvcConfigurer {
void addFormatters(FormatterRegistry var1);
void configureMessageConverters(List<HttpMessageConverter<?>> var1);
void extendMessageConverters(List<HttpMessageConverter<?>> var1);
Validator getValidator();
void configureContentNegotiation(ContentNegotiationConfigurer var1);
void configureAsyncSupport(AsyncSupportConfigurer var1);
void configurePathMatch(PathMatchConfigurer var1);
void addArgumentResolvers(List<HandlerMethodArgumentResolver> var1);
void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> var1);
void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> var1);
// 注册拦截器
void addInterceptors(InterceptorRegistry var1);
MessageCodesResolver getMessageCodesResolver();
// 无任何事务处理,只是简单页面转向的简化配置
void addViewControllers(ViewControllerRegistry var1);
void configureViewResolvers(ViewResolverRegistry var1);
// 增加静态文件映射
void addResourceHandlers(ResourceHandlerRegistry var1);
void configureDefaultServletHandling(DefaultServletHandlerConfigurer var1);
}
4.4.1 静态资源映射
- 程序的静态文件(js、css、图片)等需要直接访问,这时我们可以重写addResourceHandlers来实现
/**
* 测试,静态资源映射
*/
@Configuration
@EnableWebMvc
@ComponentScan("com.springmvcDemo.resource_handlers")
public class MyMvcConfig extends WebMvcConfigurerAdapter {
// 类似于xml的<mvc: resource />的静态资源映射配置
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 指定目录下的本地文件映射为一个url,使得可以直接访问
registry.addResourceHandler("/views/**").addResourceLocations("classpath:/views/");
}
}
4.4.2 拦截器配置
- 让Bean实现HanlderInterceptor接口或者继承HandlerInterceptorAdapter类来实现拦截器
- 通过重写WebMvcConfigurerAdapter 的addInterceptors方法来注册自定义的拦截器
实现一个拦截器:
public class DemoInterceptor implements HandlerInterceptor {
// 请求前调用
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
httpServletRequest.setAttribute("startTime", System.currentTimeMillis());
return true;
}
// 请求后调用
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("本次请求处理时间为:" + (System.currentTimeMillis() - (long) httpServletRequest.getAttribute("startTime")));
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
注册拦截器到配置Bean:
@Bean
public DemoInterceptor demoInterceptor() {
return new DemoInterceptor();
}
// 注册拦截器
@Autowired
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(demoInterceptor());
}
4.4.3 @ControllerAdvice(建言)
- 通过@ControllerAdvice注解,可以将控制器的全局配置放到一个位置,注解了@Controller的类的方法可以使用@ExeptionHandler、@InitBinder、@ModelAttribute注解到方法上,这对所有注解了@RequestMapping的控制器内方法有效
- @ExceptionHandler:用于全局处理控制器内的异常
- @InitBinder:用啦设置WebDataBinder,用来自动绑定一个前台请求参数到Model中
- @ModelAttribute:绑定键值到Model内,让全聚德@RequestMapping可以获得在此处设置的键值对
- 创建建言类
@ControllerAdvice //生命一个控制器建言,此注解组合了@Component注解
public class DemoHandlerAdvice {
// 定义全局异常处理,拦截所有的Exception
@ExceptionHandler(value = Exception.class)
public ModelAndView exception(Exception exception, WebRequest request) {
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("errorMsg", exception.getMessage());
return modelAndView;
}
// 将键值对添加到全局,所有注解@RequestMappering的方法都可以获得此键值对
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("msg", "额外信息");
}
// 定制WebDataBinder
@InitBinder
public void initBinder(WebDataBinder webDataBinder) {
webDataBinder.setDisallowedFields("id"); // 这里过滤掉了Id,即后台无法获取到该字段
}
}
创建演示的控制器
@Controller
public class AdviceController {
@RequestMapping("/advice")
public String getSomething(@ModelAttribute("msg") String msg, DemoObj demoObj) { // 这里接收msg和demoObj,但demoObj中的id无法获取到了
throw new IllegalArgumentException("非常抱歉,参数有误");
}
}
4.4.4 其他配置
- ViewController
正常的页面转向代码:
// 演示简单页面转向
@RequestMapping("/index")
public String hello() {
return "index";
}
通过重写addViewController方法来简化:
@Autowired
// 配置页面转向
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("index").setViewName("index");
}