4.1 Spring MVC概述
几个基本概念:
- 三层架构: Presentation tier+Application tier+Data tier(展现层+应用层+数据访问层)。
- MVC:Model+View+Controller(数据模型+视图+控制器)。
- 实际上MVC只存在三层架构的展现层,M实际上是数据模型,是包含数据的对象。
- 在Spring MVC里,有一个专门的类叫Model,用来和V之间的数据交互、传值;V指的是视图页面,包含JSP、freeMarker、Velocity、Thymeleaf、Tile等;C当然就是控制器(Spring MVC的注解@Controller的类)。
- 三层架构是整个应用的架构,是由Spring框架负责管理的。一般项目结构中都有Service层、DAO层,这两个反馈在应用层和数据访问层。
4.2 Spring MVC项目搭建
Spring MVC提供了一个DispatcherServlet来开发Web应用。在Servlet 2.5及以下的时候只要在web.xml下配置
4.2.1 示例
1.构建Maven项目
2.日志配置
在src/main/resources目录下,新建logback.xml配置:
<?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>logbak: %d{HH:mm:ss.SSS} %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.web" level="DEBUG"/> <!-- 1 -->
<root level="info">
<appender-ref ref="console"/>
</root>
</configuration>
①将org.springframework.web包下的类的日志级别设置为DEBUG,我们开发Spring MVC经常出现和参数类型相关的4XX错误,设置此项我们会看到更详细的错误信息。
3.演示界面
在src/main/resources下建立views目录,并新建index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<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.Spring MVC配置
package com.wisely.highlight_springmvc4;
@Configuration
@EnableWebMvc
@ComponentScan("com.wisely.highlight_springmvc4")
public class MyMvcConfig{
@Bean
public InternalResourceViewResolver viewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/classes/views/");
viewResolver.setSuffix(".jsp");
viewResolver.setViewClass(JstlView.class);
return viewResolver;
}
}
这里我们配置了一个JSP的ViewResolver,用来映射路径和实际页面的位置。
其中,@EnableWebMvc注解会开启一些默认配置,如一些ViewResolver或者MessageConverter等。
Spring MVC的ViewResolver,这是Spring MVC视图(JSP下就是html)渲染的核心机制;Spring MVC里有一个接口叫做ViewResolver(我们的ViewResolver都实现该接口)
前缀配置为/WEB-INF/classes/views/有些奇怪,怎么和我开发的目录不一致?因为看到的页面效果是运行时而不是开发时的代码,运行时代码会将我们的页面自动编译到/WEB-INF/classes/views/下
5.Web配置
package com.wisely.highlight_springmvc4;
public class WebInitializer implements WebApplicationInitializer {//1
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(MyMvcConfig.class);
ctx.setServletContext(servletContext); //2
Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx)); //3
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
}
①WebApplicationInitializer是Spring提供用来配置Servlet 3.0+配置的接口,从而实现了替代web.xml的位置。实现此接口将会自动被SpringServletContainerInitializer(用来启动Servlet 3.0容器)获取到。
②新建WebApplicationContext,注册配置类,并将其和当前servletContext关联。
③注册Spring MVC的DispatcherServlet。
6.简单控制器
package com.wisely.highlight_springmvc4.web;
@Controller//1
public class HelloController {
@RequestMapping("/index")//2
public String hello(){
return "index"; //3
}
}
①利用@Controller注解声明是一个控制器。
②利用@RequestMapping配置URL和方法之间的映射。
③通过上面ViewResolver的Bean配置,返回值为index,说明我们的页面放置的路径为/WEB-INF/classes/views/index.jsp。
7.运行
启动Tomcat之后,访问:http://localhost:8080/highlight_springmvc4/index
4.3 spring MVC常用注解
(1)@Controller
@Controller注解在类上,表明这个类是Spring MVC里的Controller,将其声明为Spring的一个Bean,Dispatcher Servlet会自动扫描注解了此注解的类,并将Web请求映射到注解了@RequestMapping的方法上。
这里特别指出,在声明普通Bean的时候,使用@Component、@Service、@Repository和@Controller是等同的,因为@Service、@Repository、@Controller都组合了@Compoment元注解;但在Spring MVC声明控制器Bean的时候,只能使用@Controller。
(2)@RequestMapping
@RequestMapping注解是用来映射Web请求(访问路径和参数)、处理类和方法的。
@RequestMapping可注解在类或方法上。注解在方法上的@RequestMapping路径会继承注解在类上的路径,@RequestMapping支持Servlet的request和response作为参数,也支持对request和response的媒体类型进行配置。
(3)@ResponseBody
@ResponseBody支持将返回值放在response体内,而不是返回一个页面。我们在很多基于Ajax的程序的时候,可以以此注解返回数据而不是页面;此注解可放置在返回值前或者方法上。
(4)@RequestBody
@RequestBody允许request的参数在request体中,而不是在直接链接在地址后面。此注解放置在参数前。
(5)@PathVariable
@PathVariable用来接收路径参数,如/news/001,可接收001作为参数,此注解放置在参数前。
(6)@RestController
@RestController是一个组合注解,组合了@Controller和@ResponseBody,这就意味着当你只开发一个和页面交互数据的控制的时候,需要使用此注解。若没有此注解,要想实现上述功能,则需自己在代码中加@Controller和@ResponseBody两个注解。
4.3.2 示例
1.传值类
添加jackson及相关依赖,获得对象和json或xml之间的转换:
<!--对json和xml格式的支持 -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.5.3</version>
</dependency>
此类用来演示获取request对象参数和返回此对象到response:
package com.wisely.highlight_springmvc4.domain;
public class DemoObj {
private Long id;
private String name;
public DemoObj() { // 1
super();
}
public DemoObj(Long id, String name) {
super();
this.id = id;
this.name = name;
}
get set 省略....
}
①jackson对对象和json做转换时一定需要此空构造。
2.注解演示控制器
package com.wisely.highlight_springmvc4.web.ch4_3;
@Controller // 1
@RequestMapping("/anno") //2
public class DemoAnnoController {
@RequestMapping(produces = "text/plain;charset=UTF-8") // 3
public @ResponseBody String index(HttpServletRequest request) { // 4
return "url:" + request.getRequestURL() + " can access";
}
@RequestMapping(value = "/pathvar/{str}", produces = "text/plain;charset=UTF-8")// 5
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") //6
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")//7
@ResponseBody //8
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")//9
public @ResponseBody String remove(HttpServletRequest request) {
return "url:" + request.getRequestURL() + " can access";
}
}
①@Controller注解声明此类是一个控制器。
②@RequestMapping(“/anno”)映射此类的访问路径是/anno。
③此方法未标注路径,因此使用类级别的路径/anno;produces可定制返回的response的媒体类型和字符集,或需返回值是json对象,则设置produces=“application/json;charset=UTF-8”,在后面的章节我们会演示此项特性。
④演示可接受HttpServletRequest作为参数,当然也可以接受HttpServletReponse作为参数。此处的@ReponseBody用在返回值前面。
⑤演示接受路径参数,并在方法参数前结合@PathVariable使用,访问路径为/anno/pathvar/xx。
⑥演示常规的request参数获取,访问路径为/anno/requestParam?id=1。
⑦演示解释参数到对象,访问路径为/anno/obj?id=1&name=xx。
⑧@ReponseBody也可以用在方法上。
⑨演示映射不同的路径到相同的方法,访问路径为/anno/name1或/anno/name2。
3.@RestController演示
package com.wisely.highlight_springmvc4.web.ch4_3;
@RestController //1
@RequestMapping("/rest")
public class DemoRestController {
@RequestMapping(value = "/getjson",produces={"application/json;charset=UTF-8"}) //2
public DemoObj getjson (DemoObj obj){
return new DemoObj(obj.getId()+1, obj.getName()+"yy");//3
}
@RequestMapping(value = "/getxml",produces={"application/xml;charset=UTF-8"})//4
public DemoObj getxml(DemoObj obj){
return new DemoObj(obj.getId()+1, obj.getName()+"yy");//5
}
}
①使用@RestController,声明是控制器,并且返回数据时不需要@ResponseBody。
②返回数据的媒体类型为json。
③直接返回对象,对象会自动转换成json。
④返回数据的媒体类型为xml。
⑤直接返回对象,对象会自动转换为xml。
4.4 Spring MVC基本配置
Spring MVC的定制配置需要我们的配置类继承一个WebMvcConfigurerAdapter类,并在此类使用@EnableWebMvc注解,来开启对Spring MVC的配置支持,这样我们就可以重写这个类的方法,完成我们的常用配置。
4.1.1 静态资源映射
程序的静态文件(js、css、图片)等需要直接访问,这时我们可以在配置里重写addResourceHandlers方法来实现。
示例
(1)添加静态资源
在src/main/resources下建立assets/js目录,并复制一个jquery.js放置在此目录下。
(2)配置代码
package com.wisely.highlight_springmvc4;
@Configuration
@EnableWebMvc//1
@ComponentScan("com.wisely.highlight_springmvc4")
public class MyMvcConfig extends WebMvcConfigurerAdapter{//2
@Bean
public InternalResourceViewResolver viewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/classes/views/");
viewResolver.setSuffix(".jsp");
viewResolver.setViewClass(JstlView.class);
return viewResolver;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/");//3
}
}
①@EnableWebMvc开启SpringMVC支持,若无此句,重写WebMvcConfigurerAdapter方法无效。
②继承WebMvcConfigurerAdapter类,重写其方法可对Spring MVC进行配置。
③addResourceLocations指的是文件放置的目录,addResourceHandler指的是对外暴露的访问路径。
4.4.2 拦截器配置
拦截器(Interceptor)实现对每一个请求处理前后进行相关的业务处理,类似于Servlet的Filter。
可让普通的Bean实现HanlderInterceptor接口或者继承HandlerInterceptorAdapter类来实现自定义拦截器。
通过重写WebMvcConfigurerAdapter的addInterceptors方法来注册自定义的拦截器,本节演示一个简单的拦截器的开发和配置,业务含义为计算每一次请求的处理时间。
示例
(1)示例拦截器
package com.wisely.highlight_springmvc4.interceptor;
@Override
public boolean preHandle(HttpServletRequest request, //2
HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
return true;
}
@Override
public void postHandle(HttpServletRequest request, //3 HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
long startTime = (Long) request.getAttribute("startTime");
request.removeAttribute("startTime");
long endTime = System.currentTimeMillis();
System.out.println("本次请求处理时间为:" + new Long(endTime - startTime)+"ms");
request.setAttribute("handlingTime", endTime - startTime);
}
}
①继承HandlerInterceptorAdapter类来实现自定义拦截器。
②重写preHandle方法,在请求发生前执行。
③重写postHandle方法,在请求完成后执行。
(2)配置
@Bean //1
public DemoInterceptor demoInterceptor(){
return new DemoInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {//2
registry.addInterceptor(demoInterceptor());
}
①配置拦截器的Bean。
②重写addInterceptors方法,注册拦截器。
4.4.3 @ControllerAdvice
通过@ControllerAdvice,我们可以将对于控制器的全局配置放置在同一个位置,注解了@Controller的类的方法可使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上,这对所有注解了@RequestMapping的控制器内的方法有效。
注解 | 解释说明 |
---|---|
@ExceptionHandler | 用于全局处理控制器里的异常。 |
@InitBinder | 用来设置WebDataBinder,WebDataBinder用来自动绑定前台请求参数到Model中。 |
@ModelAttribute | @ModelAttribute本来的作用是绑定键值对到Model里,此处是让全局的@RequestMapping都能获得在此处设置的键值对。 |
示例
演示使用@ExceptionHandler处理全局异常,更人性化的将异常输出给用户。
(1)定制ControllerAdvice。
package com.wisely.highlight_springmvc4.advice;
@ControllerAdvice //1
public class ExceptionHandlerAdvice {
@ExceptionHandler(value = Exception.class) //2
public ModelAndView exception(Exception exception, WebRequest request) {
ModelAndView modelAndView = new ModelAndView("error");// error页面
modelAndView.addObject("errorMessage", exception.getMessage());
return modelAndView;
}
@ModelAttribute //3
public void addAttributes(Model model) {
model.addAttribute("msg", "额外信息"); //3
}
@InitBinder //4
public void initBinder(WebDataBinder webDataBinder) {
webDataBinder.setDisallowedFields("id"); //5
}
}
①@ControllerAdvice声明一个控制器建言,@ControllerAdvice组合了@Component注解,所以自动注册为Spring的Bean。
②@ExceptionHandler在此处定义全局处理,通过@ExceptionHandler的value属性可过滤拦截的条件,在此处我们可以看出我们拦截所有的Exception。
③此处使用@ModelAttribute注解将键值对添加到全局,所有注解的@RequestMapping的方法可获得此键值对。
④通过@InitBinder注解定制WebDataBinder。
⑤此处演示忽略request参数的id,更多关于WebDataBinder的配置,请参考WebDataBinder的API文档。
(2)演示控制器
package com.wisely.highlight_springmvc4.web.ch4_4;
@Controller
public class AdviceController {
@RequestMapping("/advice")
public String getSomething(@ModelAttribute("msg") String msg,DemoObj obj){//1
throw new IllegalArgumentException("非常抱歉,参数有误/"+"来自@ModelAttribute:"+ msg);
}
}
4.5 Spring MVC的高级配置
4.5.1 文件上传配置
Spring MVC通过配置一个MultipartResolver来上传文件。
在Spring的控制器中,通过MultipartFile file来接收文件,通过MultipartFile[]files接收多个文件上传。
示例
(1)添加文件上传依赖。
<!-- file upload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!-- 非必需,可简化I/O操作 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.3</version>
</dependency>
(2)上传页面。
在src/main/resources/views下新建upload.jsp。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>upload page</title>
</head>
<body>
<div class="upload">
<form action="upload" enctype="multipart/form-data" method="post">
<input type="file" name="file"/><br/>
<input type="submit" value="上传">
</form>
</div>
</body>
</html>
(3)添加转向到upload页面的ViewController
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("/index");
registry.addViewController("/toUpload").setViewName("/upload");
}
(4)MultipartResolver配置
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver =new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(1000000);
return multipartResolver;
}
(5)控制器。
package com.wisely.highlight_springmvc4.web.ch4_5;
@Controller
public class UploadController {
@RequestMapping(value = "/upload",method = RequestMethod.POST)
public @ResponseBody String upload(MultipartFile file) {//1
try {
FileUtils.writeByteArrayToFile(new File("e:/upload/"+file.getOriginalFilename()),
file.getBytes()); //2
return "ok";
} catch (IOException e) {
e.printStackTrace();
return "wrong";
}
}
}
①使用MultipartFile file接受上传的文件。
②使用FileUtils.writeByteArrayToFile快速写文件到磁盘。