Spring-SpringMVC

笔记参考柏码知识库

基本配置-全注解配置形式

创建Web应用程序。

创建Servlet容器初始化类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{WebConfiguration.class}; //基本的Spring配置类,一般用于业务层配置
}

@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[0]; //配置DispatcherServlet的配置类、主要用于Controller等配置,这里为了教学简单,就不分这么详细了,只使用上面的基本配置类
}

@Override
protected String[] getServletMappings() {
return new String[]{"/"}; //匹配路径,与上面一致
}
}

配置类中添加注解:

1
2
3
4
5
@Configuration
@EnableWebMvc //快速配置SpringMvc注解,如果不添加此注解会导致后续无法通过实现WebMvcConfigurer接口进行自定义配置
@ComponentScan("com.example.controller")
public class WebConfiguration {
}

创建Controller类:

1
2
3
4
5
6
7
8
@Controller
public class HelloController {
@ResponseBody
@RequestMapping("/")
public String hello(){
return "HelloWorld!";
}
}

Controller控制器

使用DispatcherServlet替代Tomcat提供的默认的静态资源Servlet。

配置视图解析器和控制器

导入Thymeleaf依赖:

1
2
3
4
5
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring6</artifactId>
<version>3.1.1.RELEASE</version>
</dependency>

需要将对应的ViewResolver注册为Bean来配置视图解析器:

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
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfiguration {
//我们需要使用ThymeleafViewResolver作为视图解析器,并解析我们的HTML页面
@Bean
public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine springTemplateEngine){
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setOrder(1); //可以存在多个视图解析器,并且可以为他们设定解析顺序
resolver.setCharacterEncoding("UTF-8"); //编码格式是重中之重
resolver.setTemplateEngine(springTemplateEngine); //和之前JavaWeb阶段一样,需要使用模板引擎进行解析,所以这里也需要设定一下模板引擎
return resolver;
}

//配置模板解析器
@Bean
public SpringResourceTemplateResolver templateResolver(){
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setSuffix(".html"); //需要解析的后缀名称
resolver.setPrefix("/"); //需要解析的HTML页面文件存放的位置,默认是webapp目录下,如果是类路径下需要添加classpath:前缀
return resolver;
}

//配置模板引擎Bean
@Bean
public SpringTemplateEngine springTemplateEngine(ITemplateResolver resolver){
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(resolver); //模板解析器,默认即可
return engine;
}
}

创建Controller,并在类中编写方法用于处理对应地址的请求:

1
2
3
4
5
6
7
8
9
10
@Controller   //直接添加注解即可
public class HelloController {

@RequestMapping(value = "/index")
public ModelAndView index(){
ModelAndView modelAndView = new ModelAndView("index");
modelAndView.getModel().put("name", "啊这"); //将name传递给Model
return modelAndView;
}
}

还可以直接返回View名称,并单独添加Model作为形参进行设置。SpringMVC会将其自动包装为ModelAndView对象,并通过依赖注入自动传递实例对象:

1
2
3
4
5
@RequestMapping(value = "/index")
public String index(Model model){ //这里不仅仅可以是Model,还可以是Map、ModelMap
model.addAttribute("name", "yyds");
return "index";
}

让配置类实现WebMvcConfigurer接口,让静态资源通过Tomcat提供的默认Servlet进行解析:

1
2
3
4
5
6
7
8
9
10
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable(); //开启默认的Servlet
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
//配置静态资源的访问路径
}

@RequestMapping

路径

路径必须全局唯一,多个路径可以由同一方法处理。

可以将@RequestMapping添加到类名上。表示为此类中的所有请求映射添加一个路径前缀。

1
2
3
4
5
6
7
8
9
@Controller
@RequestMapping("/yyds")
public class MainController {

@RequestMapping({"/index", "/test"})
public ModelAndView index(){
return new ModelAndView("index");
}
}

现在需要访问/yyds/index或是/yyds/test才可以得到此页面。

路径还支持使用通配符进行匹配。

限定请求方式

可以使用method属性限定请求方式:

1
2
3
4
@RequestMapping(value = "/index", method = RequestMethod.POST)
public ModelAndView index(){
return new ModelAndView("index");
}

或使用衍生注解直接指定,如@PostMapping与GetMapping。

限定请求参数

使用params属性,并支持表达式,并可以设置固定值

1
2
3
4
@RequestMapping(value = "/index", params = {"username!=test", "!password"})
public ModelAndView index(){
return new ModelAndView("index");
}

headers属性限定请求头中的内容,下面代码中如果请求头中携带了Connection属性,将无法访问:

1
2
3
4
@RequestMapping(value = "/index", headers = "!Connection")
public ModelAndView index(){
return new ModelAndView("index");
}

@RequestParam和@RequestHeader

使用两个注解来获取请求中的参数和请求头中的内容。

@RequestHeader与@RequestParam用法一致,以@RequestParam为例。

添加@RequestParam注解获取请求中的参数。

1
2
3
4
5
@RequestMapping(value = "/index")
public ModelAndView index(@RequestParam("username") String username){
System.out.println("接受到请求参数:"+username);
return new ModelAndView("index");
}

如果参数名称与形式参数名称相同,即使不添加@RequestParam也能获取到参数值。一旦添加@RequestParam,那么此请求必须携带指定参数,可以将require属性设定为false来将属性设定为非必须:

1
2
3
4
5
@RequestMapping(value = "/index")
public ModelAndView index(@RequestParam(value = "username", required = false) String username){
System.out.println("接受到请求参数:"+username);
return new ModelAndView("index");
}

还可以设定默认值:

1
2
3
4
5
@RequestMapping(value = "/index")
public ModelAndView index(@RequestParam(value = "username", required = false, defaultValue = "伞兵一号") String username){
System.out.println("接受到请求参数:"+username);
return new ModelAndView("index");
}

如果需要使用Servlet原本的一些类, 直接添加HttpServletRequest或HttpServletResponse为形式参数即可。 HttpSession也可以作为参数传递:

1
2
3
4
5
6
@RequestMapping(value = "/index")
public ModelAndView index(HttpSession session){
System.out.println(session.getAttribute("test"));
session.setAttribute("test", "鸡你太美");
return new ModelAndView("index");
}

还可以直接将请求参数传递给一个实体类:

1
2
3
4
5
@RequestMapping(value = "/index")
public ModelAndView index(User user){
System.out.println("获取到cookie值为:"+user);
return new ModelAndView("index");
}

@CookieValue和@SessionAttrbutie

通过使用@CookieValue注解,快速获取请求携带的Cookie信息:

1
2
3
4
5
6
7
@RequestMapping(value = "/index")
public ModelAndView index(HttpServletResponse response,
@CookieValue(value = "test", required = false) String test){
System.out.println("获取到cookie值为:"+test);
response.addCookie(new Cookie("test", "lbwnb"));
return new ModelAndView("index");
}

Session也能使用注解快速获取:

1
2
3
4
5
6
7
@RequestMapping(value = "/index")
public ModelAndView index(@SessionAttribute(value = "test", required = false) String test,
HttpSession session){
session.setAttribute("test", "xxxx");
System.out.println(test);
return new ModelAndView("index");
}

重定向和请求转发

重定向需要在视图名称前添加前缀redirect:

1
2
3
4
5
6
7
8
9
@RequestMapping("/index")
public String index(){
return "redirect:home";
}

@RequestMapping("/home")
public String home(){
return "home";
}

请求转发需要forward前缀:

1
2
3
4
5
6
7
8
9
@RequestMapping("/index")
public String index(){
return "forward:home";
}

@RequestMapping("/home")
public String home(){
return "home";
}

Bean的Web作用域

在SpringMVC中的作用域:

  • request:对于每次HTTP请求,使用request作用域定义的Bean都将产生一个新实例,请求结束后Bean也消失。
  • session:对于每一个会话,使用session作用域定义的Bean都将产生一个新实例,会话过期后Bean也消失。
  • global session:不常用,不做讲解。

使用@RequestScope或是@SessionScope表示此Bean的Web作用域:

1
2
3
4
5
@Bean
@RequestScope
public TestBean testBean(){
return new TestBean();
}

RestFul风格

RESTful风格的设计允许将参数通过URL拼接传到服务端,目的是让URL看起来更简洁实用。

对于路径:

1
http://localhost:8080/mvc/index/123456

可以将index的下一级路径作为请求参数进行处理:

1
2
3
4
5
@RequestMapping("/index/{str}")
public String index(@PathVariable String str) {
System.out.println(str);
return "index";
}

方法的形参列表中必须包括一个与占位符同名的并且添加了@PathVariable注解的参数,或是由@PathVariable注解指定为占位符名称:

1
2
3
4
5
@RequestMapping("/index/{str}")
public String index(@PathVariable("str") String text){
System.out.println(text);
return "index";
}

四种请求的映射:

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
@Controller
public class MainController {

@RequestMapping(value = "/index/{id}", method = RequestMethod.GET)
public String get(@PathVariable("id") String text){
System.out.println("获取用户:"+text);
return "index";
}

@RequestMapping(value = "/index", method = RequestMethod.POST)
public String post(String username){
System.out.println("添加用户:"+username);
return "index";
}

@RequestMapping(value = "/index/{id}", method = RequestMethod.DELETE)
public String delete(@PathVariable("id") String text){
System.out.println("删除用户:"+text);
return "index";
}

@RequestMapping(value = "/index", method = RequestMethod.PUT)
public String put(String username){
System.out.println("修改用户:"+username);
return "index";
}
}

Interceptor拦截器

拦截器并不是在Servlet之前,它在Servlet与RequestMapping之间,相当于DispatcherServlet在将请求交给对应Controller中的方法之前进行拦截处理,它只会拦截所有Controller中定义的请求映射对应的请求(不会拦截静态资源)。

创建拦截器

需要实现一个HandlerInterceptor接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MainInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("我是处理之前!");
return true; //只有返回true才会继续,否则直接结束
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("我是处理之后!");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//在DispatcherServlet完全处理完请求后被调用
System.out.println("我是完成之后!");
}
}

在配置类中注册:

1
2
3
4
5
6
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MainInterceptor())
.addPathPatterns("/**") //添加拦截器的匹配路径,只要匹配一律拦截
.excludePathPatterns("/home"); //拦截器不进行拦截的路径
}

如果处理过程中抛出异常,那么就不会执行处理后postHandle方法,但是会执行afterCompletion方法,可以在此方法中获取到抛出的异常。

多级拦截器

注册二号拦截器:

1
2
3
4
5
6
7
@Override
public void addInterceptors(InterceptorRegistry registry) {
//一号拦截器
registry.addInterceptor(new MainInterceptor()).addPathPatterns("/**").excludePathPatterns("/home");
//二号拦截器
registry.addInterceptor(new SubInterceptor()).addPathPatterns("/**");
}

拦截顺序就是注册的顺序,因此拦截器会根据注册顺序依次执行。在处理之前,是按照顺序从前向后进行拦截的,但是处理完成之后,就按照倒序执行处理后方法,而完成后是在所有的postHandle执行之后再同样的以倒序方式执行。

异常处理

请求映射方法中出现异常时,会直接展示在前端页面,这是因为SpringMVC为我们提供了默认的异常处理页面,当出现异常时,我们的请求会被直接转交给专门用于异常处理的控制器进行处理。

自定义异常处理控制器:

1
2
3
4
5
6
7
8
9
10
@ControllerAdvice
public class ErrorController {

@ExceptionHandler(Exception.class)
public String error(Exception e, Model model){ //可以直接添加形参来获取异常
e.printStackTrace();
model.addAttribute("e", e);
return "500";
}
}

编写一个专门显示异常的页面:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
500 - 服务器出现了一个内部错误QAQ
<div th:text="${e}"></div>
</body>
</html>
1
2
3
4
5
6
@RequestMapping("/index")
public String index(){
System.out.println("我是处理!");
if(true) throw new RuntimeException("您的氪金力度不足,无法访问!");
return "index";
}

JSON

后端JSON解析框架有很多种,比较常用的是Jackson和FastJSON。阿里巴巴的FastJSON是目前号称最快的JSON解析框架。

导入依赖:

1
2
3
4
5
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.34</version>
</dependency>

编写配置:

1
2
3
4
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new FastJsonHttpMessageConverter());
}

直接返回一个对象类型,SpringMVC会自动转换为JSON字符串格式:

1
2
3
4
5
6
7
8
@RequestMapping(value = "/data", produces = "application/json")
@ResponseBody
public Student data(){
Student student = new Student();
student.setName("杰哥");
student.setAge(18);
return student;
}

文件的上传与下载

上传

添加配置:

1
2
3
4
5
6
7
8
9
10
11
public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

...

@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
// 直接通过registration配置Multipart相关配置,必须配置临时上传路径,建议选择方便打开的
// 同样可以设置其他属性:maxFileSize, maxRequestSize, fileSizeThreshold
registration.setMultipartConfig(new MultipartConfigElement("/Users/nagocoler/Download"));
}
}

编写Controller:

1
2
3
4
5
6
7
8
9

@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public String upload(@RequestParam MultipartFile file) throws IOException {
File fileObj = new File("test.png");
file.transferTo(fileObj);
System.out.println("用户上传的文件已保存到:"+fileObj.getAbsolutePath());
return "文件上传成功!";
}

前端添加文件上传点:

1
2
3
4
5
6
<div>
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit">
</form>
</div>

下载

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping(value = "/download", method = RequestMethod.GET)
@ResponseBody
public void download(HttpServletResponse response){
response.setContentType("multipart/form-data");
try(OutputStream stream = response.getOutputStream();
InputStream inputStream = new FileInputStream("test.png")){
IOUtils.copy(inputStream, stream);
}catch (IOException e){
e.printStackTrace();
}
}