SpringBoot教程——检视阅读(上)

2020-05-21  本文已影响0人  卡斯特梅的雨伞

基于Spring Boot框架:Spring Boot 2.1.11.RELEASE

Spring Boot基础入门

什么是Spring Boot

Spring Boot概述

Spring Boot 是所有基于 Spring Framework 5.0 开发的项目的起点。Spring Boot 的设计是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的配置文件。

简化了使用Spring的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。

Spring Boot的优点

Spring Boot核心功能

起步依赖

起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。

自动配置

Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。该过程是Spring自动完成的。

Spring Boot快速入门

步骤:

  1. 创建一个普通的maven项目。
  2. pom.xml导入起步依赖 。
  3. 编写引导类

示例:

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.self</groupId>
    <artifactId>hellospringboot</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 导入springboot父工程. 注意:任何的SpringBoot工程都必须有的!!! -->
    <!-- 父工程的作用:锁定起步的依赖的版本号,并没有真正的依赖 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.11.RELEASE</version>
    </parent>

    <dependencies>
        <!--web起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

</project>

引导类,或者说叫启动类

@SpringBootApplication
public class MyBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyBootApplication.class,args);
    }
}
@Controller
public class HelloController {

    @RequestMapping("/hello")
    @ResponseBody
    public String sayHello(){
        return "Hello Spring Boot!";
    }
}

请求:<http://localhost:8080/hello>

输出:

image.png

Spring Boot配置文件

Spring Boot的核心是自动配置(或者叫默认配置),通过自动配置大大减少Spring项目的配置编写。但是在实际开发中,我们仍然需要根据需求来适当修改某些必要的参数配置,这时Spring Boot提供了两种格式的配置方便开发者进行修改。

application*.properties

Spring Boot使用了一个全局的配置文件application.properties,放在src/main/resources目录下或者src/main/resources/config下。在 src/main/resources/config 下的优先级高 。Sping Boot的全局配置文件的作用是对一些默认配置的配置值进行修改。

Spring Boot内置属性

示例:

编写修改Tomcat端口属性 :

application.properties

server.port=9000

Spring Boot内置属性参考

自定义属性

application.properties

server.port=9000

#自定义类型
#基本类型
name=Amy
age=21

#JavaBean类型
user.name=Amy
user.age=21

#数组/List集合
user.list=Amy,Jack,Roy
#或者
user.list[0]=Amy
user.list[1]=Jack
user.list[2]=Roy

#Map集合
user.map={name:"Amy",age:21}
#或者
user.map.name=Amy
user.map.age=21

Q:自定义配置怎么取值使用,什么场景使用?

A:自定义配置一般用@Value("${vv.schedule.mail.vcode.content}")注解来取值,一般我们有些自定义的配置需要时,就可以在yml文件中配置,或者nacos上配置,然后取出来用即可,@RefreshScope注解可以在配置发生改变时及时更新。

@Configuration
@RefreshScope
@Data
public class Config {

    @Value("${v.sche.mail.vcode.content}")
    private String mailContent;

    @Value("${v.sche.mail.vcode.title}")
    private String mailTitle;

    @Value("${v.sche.swagger.enable:false}")
    private boolean enableSwagger;

    @Value("${v.sche.login.fail.limit:6}")
    private Integer failLimit;

    @Value("${v.sche.login.fail.lock.expire.time:1800}")
    private Integer lockExpireTime;

    @Value("${v.sche.mail.send.limit:10}")
    private Integer emailLimit;

    @Value("${v.sche.mail.lock.expire.time:86400}")
    private Integer emailExpireTime;
}
Profile多环境配置

当应用程序需要部署到不同运行环境时,一些配置细节通常会有所不同,最简单的比如日志,生产日志会将日志级别设置为WARN或更高级别,并将日志写入日志文件,而开发的时候需要日志级别为DEBUG,日志输出到控制台即可。如果按照以前的做法,就是每次发布的时候替换掉配置文件,这样太麻烦了,Spring Boot的Profile就给我们提供了解决方案,命令带上参数就搞定。

步骤:

示例:

application.properties

#启用prod生产环境
spring.profiles.active= prod
#当profiles不同环境变量文件里有配置值时,application.properties里配置的变量是会被启用的环境如application-prod.properties里的值所覆盖的。
server.port=9000

application-prod.properties

server.port=9004

输出:

image.png
maven实现多环境配置

参考

<profile>
    <id>dev-business</id>
    <properties>
        <profileActive>dev-business</profileActive>
        <discovery.namespace/>
        <application.name>vv-mater</application.name>
        <config.namespace>${discovery.namespace}</config.namespace>
        <nacos.config.address>172.16.6.126:9002</nacos.config.address>
        <nacos.discovery.address>172.16.6.126:9002</nacos.discovery.address>
    </properties>
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
</profile>

bootstrap.yml配置

spring:
  application:
    name: ${application.name}
  cloud:
    nacos:
      config:
        server-addr: ${nacos.config.address}
        shared-dataids: common.yml,${spring.application.name}.yml
        refreshable-dataids: common.yml,${spring.application.name}.yml
        namespace: ${config.namespace}
        username: use
        password: use
      discovery:
        server-addr: ${nacos.discovery.address}
        namespace: ${discovery.namespace}
        register-enabled: true
        metadata:
          version: ${nacos.discovery.metadata.version}
          env: ${nacos.discovery.metadata.env}
        username: use
        password: use
  main:
    allow-bean-definition-overriding: true

application*.yml

YAML(/ˈjæməl/,尾音类似camel骆驼)是一个可读性高,用来表达数据序列化的格式。

yml或yaml所表示的YAML Ain’t Markup Language,YAML是一种简洁的非标记语言,文件名后缀为yml,java中经常用它描述配置文件application.yml。YAML以数据为中心,比json/xml等更适合做配置文件。使用空白,缩进,分行组织数据,从而使得表示更加简洁易读。

在yml之前使用最多的配置文件形式是xml和properties文件。xml文件太过繁琐,看过的人都知道,想要新加一个配置节点的话还需要包含在<>标签里;而properties配置文件没有了标签,不过当你的配置有很多层级的时候,写完之后你会发现会有大量重复的代码。而yml/yaml文件结合了两者的优势,当你新增节点配置的时候,不需要标签,在写多层级配置的时候也不会产生重复代码。

yml格式书写规则
  1. 大小写敏感
  2. 使用缩进表示层级关系
  3. 禁止使用tab缩进,只能使用空格键
  4. 缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级。
  5. 使用#表示注释
  6. 字符串可以不用引号标注
Spring Boot内置属性

注意:

当application.properties和application.yml同时存在时,生效的是application.properties。

#yml文件在写时有提示,友好,优先选择使用
#修改端口
server:
  port: 9090
#基本类型 注意:属性值大小写敏感
name: Bruce Wayne
age: 29
#JavaBean类型
user:
  name: Bruce Wayne
  age: 29
#数组/List集合
#user:层级只能指定一个,后面的如果是在user层级下的只需要tab空格就可以了,再写user层级则会报错
#user:
  list: eric,jack,rose
  #下面这种写法用@Value注解解析不了
  list:
  - Jack
  - Rose
  - Jerry
#Map集合
#user:
#yml语法格式要求key如map: 后面对应的value值必需在冒号:后面空格,否则格式错误
  map: {name: Bruce Wayne,age: 29}
Profile多环境配置

application.yml

#启用test测试环境
#当profiles不同环境变量文件里有配置值时,application.yml里配置的变量是会被启用的环境如application-test.yml里的值所覆盖的。
#还有一点需要特别注意的是当存在application-test.properties与application-test.yml两个并行时,生效的是application-test.properties
spring:
  profiles:
    active: test

application-test.yml

server:
  port: 9092

Spring Boot读取配置文件(properties和yml处理是一样的)

Spring Boot里面有两个注解可以读取application.properties或application.yml文件的属性值。

  1. @Value
  2. @ConfigurationProperties

注意:

1、不能配置user.name=Amy属性配置,因为取不到Amy的值,取到的是计算机的用户名,在这台电脑里我的用户名是Castamere。应该是个系统默认保留的取值配置,这里没有深入去研究。

2、不能配置userName=Amy这个key为userName或者username的基本类型配置,否则取到的是还是计算机的用户名。

@Value

基本类型

application.yml

#基本类型 
firstName: Bruce Wayne1111
age: 29

读取:

@Controller
public class ConfigController {

    @Value("${firstName}")
    private String name;
    @Value("${age}")
    private Integer age;

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return name + " : " + age;
    }
}
JavaBean类型
#JavaBean类型
user:
  first: Bruce Wayne
  age: 31

读取:

@Controller
public class ConfigController {
    @Value("${user.firstName}")
    private String firstName;
    @Value("${user.age}")
    private Integer age;

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return firstName + " : " + age;
    }
}
数组/List集合
user:
  list: Jack,Rose,Jerry

读取:

@Value("#{'${user.list}'.split(',')}")
private List<String> list;

@RequestMapping("/show")
@ResponseBody
public String showConfig() {
    return JSON.toJSONString(list);
}
Map集合
user:
#yml语法格式要求key如map: 后面对应的value值必需在冒号:后面空格,否则格式错误
#读取的时候要加引号""是给@Value注解用的么?
# map: {nickname: erci,age: 20}
  map: "{name: 'SuperMan',age: 28}"

读取:

@Value("#{${user.map}}")
private Map<String,Object> map;

@RequestMapping("/show")
@ResponseBody
public String showConfig() {
    return JSON.toJSONString(map);
}

注意,不加引号会报错.

#报错:Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'user.map' in value "#{${user.map}}"
user:
  map: {name: 'SuperMan',age: 28}

@ConfigurationProperties

注意以下几点:

#基本类型
firstName: Bruce Wayne
age: 30

读取:

@Controller
@ConfigurationProperties
public class ConfigurationController {

    private String firstName;
    private Integer age;

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return firstName + " : " + age;
    }
}

输出:

//成功
Bruce Wayne : 30
//没有setter方法来注入文件的属性值,不会报错,但是没有赋值
null : null

JavaBean类型

#JavaBean类型
user:
  firstName: Bruce
  age: 31

读取:

@Controller
//两种配置方式都可以
@ConfigurationProperties("user")
//@ConfigurationProperties(prefix = "user")
public class ConfigurationController {

    private String firstName;
    private Integer age;

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return firstName + " : " + age;
        //return JSON.toJSONString(list);
        //return JSON.toJSONString(map);
    }
}
Bruce : 31

数组/List集合

#数组/List集合
#user:层级只能指定一个,后面的如果是在user层级下的只需要tab空格就可以了,再写user层级则会报错
#user:
#两种list表达方式都可以,倾下第一种
  list: Jack,Rose,Jerry
  list2:
  - Jack
  - Morty
  - Jerry

读取:

@Controller
//两种配置方式都可以
@ConfigurationProperties("user")
//@ConfigurationProperties(prefix = "user")
public class ConfigurationController {

    private List<String> list;

    public void setList(List<String> list) {
        this.list = list;
    }

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return JSON.toJSONString(list);
    }
}

Map集合

#Map集合
#yml语法格式要求key如map: 后面对应的value值必需在冒号:后面空格,否则格式错误
#读取的时候要加引号""是给@Value注解用的么?@ConfigurationProperties("user")读取map不需要加引号,否则报错,说明两种读取方式不同
#报错:Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'user.map' in value "#{${user.map}}"
  map: {name: 'SuperMan',age: 28}
#  map: "{name: 'SuperMan',age: 28}"

读取:

@Controller
//两种配置方式都可以
@ConfigurationProperties(prefix ="user", ignoreInvalidFields = true)
//@ConfigurationProperties(prefix = "user")
public class ConfigurationController {
    private Map<String,Object> map;

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }
    
    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return JSON.toJSONString(map);
    }
}
{"name":"SuperMan","age":28}

Spring Boot热部署

什么是热部署

无法热部署的缺点:

热部署原理

深层原理是使用了两个ClassLoader,一个Classloader加载那些不会改变的类(第三方Jar包),另一个ClassLoader加载会更改的类,称为restart ClassLoader,这样在有代码更改的时候,原来的restart ClassLoader 被丢弃,重新创建一个restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启时间。

Spring Boot热部署实现方式

Spring Boot有3种热部署方式:

  1. 使用springloaded配置pom.xml文件,使用mvn spring-boot:run启动
  2. 使用springloaded本地加载启动,配置jvm参数
  3. 使用devtools工具包,操作简单,但是每次需要重新部署

Spring Boot使用devtools工具包实现热部署

需要说明以下4点:

  1. devtools可以实现页面热部署(即页面修改后会立即生效,这个可以直接在application.properties文件中配置spring.thymeleaf.cache=false来实现)
  2. 实现类文件热部署(类文件修改后不会立即生效,过会儿生效)
  3. 实现对属性文件的热部署。即devtools会监听classpath下的文件变动,并且会立即重启应用(发生在保存的时候)。这里可能有疑问,为什么还要重启?这样就不是热部署啦!注意:因为其采用的虚拟机机制,该项重启比正常重启会快非常多!
  4. scope配置为true,在修改java文件后就立即热启动,而且会清空Session中的数据。如果有用户登陆的话,项目重启后需要重新登陆。

示例:

pom.xml添加依赖:

<!--devtools热部署-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    <scope>true</scope>
</dependency>

application.yml添加devtools的配置 :

spring:
 devtools:
   restart:
     enabled: true  #设置开启热部署
     additional-paths: src/main/java #重启目录
     exclude: WEB-INF/**
 freemarker:
   cache: false    #页面不加载缓存,修改即时生效

修改了类文件后,IDEA不会自动编译,必须修改IDEA设置。

1、
File-Settings-Compiler-Build Project automatically 
2、
ctrl + shift + alt + / ,选择Registry,勾上 Compiler autoMake allow when app running
image.png image.png

Spring Boot访问静态资源

Spring Boot默认静态资源目录

在Spring Boot应用启动过程中,会读取加载一个静态资源文件加载路径这个属性

# 默认值为
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/

这个属性的默认值代表静态资源扫描目录:

classpath:/META-INF/resources/ 
classpath:/resources/
classpath:/static/ 
classpath:/public/
/:当前项目的根路径

这意味着我们可以只要把静态资源文件存放在以上目录,即可以被访问到

需注意优先级问题:根据前后关系确定优先级,配置路径在前面会优先访问前面的静态资源。也就是说如果classpath:/resources/目录和classpath:/public/都有一个test.html,那么根据默认的优先级,会去访问classpath:/resources/下的资源。

示例:分别建立了public、resources、static目录,在目录下建立html静态页面。项目启动后,我们都可以直接访问这些页面 。

image.png
//请求
http://localhost:8080/index.html
http://localhost:8080/img/test.gif
http://localhost:8080/hello.html //在public和resources文件夹下都有,优先访问resources下的静态资源
http://localhost:8080/success.jsp//jsp文件访问会直接下载,而不会去解析。

修改Spring Boot静态资源路径

我们可以在application.yml文件中修改静态资源路径,如:

# 修改静态资源加载路径
spring:
  resources:
    static-locations: classpath:/download,classpath:/static/,classpath:/public/

注意:

如果按照以上写法会覆盖Spring Boot的默认路径。如果希望保留默认路径,那就要先写上之前所有值,再最后加上新的路径。

Spring Boot进阶

Spring Boot异常处理

有5种处理方式:

  1. Spring Boot默认异常提示。
  2. 自定义error的错误页面。
  3. @ExceptionHandler注解(Controller中自定义)。
  4. @ControllerAdvice注解 加 @ExceptionHandler注解 抽取所以共用的异常处理方法。
  5. 实现HandlerExceptionResovler。

Spring Boot默认异常提示

在Spring Boot应用执行过程中难免会出现各种错误,默认情况下,只要出现异常,就会跳转到Spring Boot默认错误提示页面,如下:

image.png

为了给用户更好的体验,我们可以使用以下四种手段来优化异常捕获的情况。

自定义error的错误页面

SpringBoot应用默认已经提供一套错误处理机制:就是把所有后台错误统一交给error请求,然后跳转到了本身自己的错误提示页面。这时,我们利用springboot的错误处理机制,重新建立了一个新的error.html,该页面必须放在resources的templates目录下 。

示例:

pom.xml ——页面用到了Thymeleaf,所以项目中需要导入Thymeleaf的依赖。如果没有加依赖则页面加载失败继续调整到第一种springboot默认异常提示页面上。

image.png

error.html

<head>
    <meta charset="UTF-8">
    <title th:text="${title}"></title>
</head>
<body>
<div >
    <div>
        <div>
            <p><span>页面出现</span><span class="code" th:text="${status}"></span>错误,非常抱歉!</p>
            <a href="/" class="btn-back common-button">您可以点击返回首页</a>
            <div >
                <div th:text="${#dates.format(timestamp,'yyyy-MM-dd HH:mm:ss')}"></div>
                <div>错误原因:</div>
                <div th:text="${message}"></div>
                <div th:text="${error}"></div>
            </div>
        </div>
    </div>
</div>
</body>

异常展示:

image.png image.png

@ExceptionHandler注解

 /**@ExceptionHandler 注解只能作用为对象的方法上,并且在运行时有效,value() 可以指定异常类。由该注解注*释的方法可以具有灵活的输入参数。
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
    Class<? extends Throwable>[] value() default {};
}

示例:

@Controller
public class HelloController {

    @Autowired
    private User user;

    @RequestMapping("/hello")
    @ResponseBody
    public String sayHello() {
        throw new NullPointerException();
        //return "Hello Spring Boot!";
    }

    @RequestMapping("/user")
    @ResponseBody
    public String helloUser() {
        int i = 10 / 0;
        return JSON.toJSONString(user);
    }

    // 处理java.lang.ArithmeticException
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public String handlerArithmeticException(Exception e) {
        return "数学运算错误:" + e.getMessage();
    }

    // 处理java.lang.NullPointerException
    @ExceptionHandler(value = {NullPointerException.class})
    @ResponseBody
    public String handlerNullPointerException(Exception e) {
        // e:该对象包含错误信息
        return "空指针错误:" + e;
    }
}

输出:

image.png image.png

@ControllerAdvice注解

刚才的@ExceptionHandler注解是用在控制器类里面的,这样每个控制器都需要定义相关方法,比较繁琐。这时可以使用@ControllerAdvice来抽取所有共同的@ExceptionHandler方法,从而简化异常方法的定义。

注意:

当业务Controller有自己的@ExceptionHandler注解处理方法时,生效的是Controller上的异常处理方法,@ControllerAdvice里的不会生效。

示例:

@ControllerAdvice
public class CommonExceptionHandler {

    // 处理java.lang.ArithmeticException
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public String handlerArithmeticException(Exception e) {
        return "数学运算错误1:" + e.getMessage();
    }

    // 处理java.lang.NullPointerException
    @ExceptionHandler(value = {NullPointerException.class})
    @ResponseBody
    public String handlerNullPointerException(Exception e) {
        // e:该对象包含错误信息
        return "空指针错误1:" + e;
    }
}

输出:

image.png

HandlerExceptionResovler

注意:

异常处理优先级顺序:Controller层异常 > @ControllerAdvice > HandlerExceptionResovler

示例:

@Configuration
public class CommonHandlerExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        ModelAndView mv = new ModelAndView();
        //判断不同异常类型,做不同处理
        if(e instanceof ArithmeticException){
            mv.setViewName("error1");
        }
        if(e instanceof NullPointerException){
            mv.setViewName("error2");
        }
        mv.addObject("error", e.toString());
        return mv;
    }
}

输出:

image.png

Spring Boot表单数据验证

在Spring Boot中我们经常需要对表单数据进行合法性验证。下面讲解如何在Spring Boot中进行表单验证。

更多校验规则参考Spring,两者是一样的。

示例:

pom.xml

<dependencies>
        <!--web起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 导入thymeleaf坐标 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>

Pojo,添加验证注解

public class User {

    private Integer id;

    @NotEmpty(message = "姓名不能为空")
    private String name;
    @Min(1)
    private Integer age;
    @Email(message = "邮箱地址不正确")
    private String email;
    @NotEmpty(message = "描述不能为空")
    @Length(min = 5, max = 100, message = "描述必须在5-100个字之间")
    private String desc;
    //...}
@Controller
@RequestMapping("/user")
public class UserController {
    /**
     * 跳转到add.html
     * @return
     */
    @RequestMapping("/toAdd")
    public String toAdd() {
        return "add";
    }
    /**
     * 用户添加
     * BindingResult: 用于封装验证对象(user)里面的验证结果
     */
    @RequestMapping("/add")
    public String add(@Valid User user, BindingResult result) {
        if (result.hasErrors()) {
            return "add";
        }
        //save
        System.out.println("保存用户:" + JSON.toJSONString(user));
        return "success";
    }
}

设计页面,回显错误信息

add.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>用户添加</title>
</head>
<body>
<h3>用户添加</h3>
<form action="/user/add" method="post">
    用户名:<input type="text" name="name"/><font color="red" th:errors="${user.name}"></font><br/>
    描述:<input type="text" name="desc"/><font color="red" th:errors="${user.desc}"></font><br/>
    年龄:<input type="text" name="age"/><font color="red" th:errors="${user.age}"></font><br/>
    邮箱:<input type="text" name="email"/><font color="red" th:errors="${user.email}"></font><br/>
    <input type="submit" value="保存"/>
</form>
</body>
</html>

success.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>提示页面</title>
</head>
<body>
保存成功
</body>
</html>
保存用户:{"age":25,"desc":"黄金脑殿下","email":"roy@inc.com","name":"艾米"}

Spring Boot文件上传

示例:

\resources\static\upload.html——在静态资源文件夹下这样我们就可以直接访问静态资源。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>文件上传页面</title>
</head>
<body>
文件上传页面

<hr/>
<form action="/upload" method="post" enctype="multipart/form-data">
    请选择文件:<input type="file" name="fileName"/><br/>
    <input type="submit" value="开始上传"/>
</form>
</body>
</html>
@Controller
public class UploadController {

    @RequestMapping("/upload")
    public String upload(MultipartFile fileName, HttpServletRequest request){
        //处理文件
        System.out.println("文件原名称:"+fileName.getOriginalFilename());
        System.out.println("文件类型:"+fileName.getContentType());
        String upload = UploadController.class.getResource("/").getFile()+"/upload";
        File file = new File(upload);
        if (!file.exists()) {
            file.mkdir();
        }
        //目标文件传入地址路径+名称
        try {
            fileName.transferTo(new File(upload + "/" + fileName.getOriginalFilename()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "success";
    }
}

Spring Boot文件下载

示例:

\resources\static\download.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>文件下载</title>
</head>
<body>
<h3>文件下载</h3>
<a href="/download">下载</a>
</body>
</html>
@Controller
public class DownloadController {

    @RequestMapping("/download")
    public void download(HttpServletResponse response) throws IOException {
        InputStream inputStream = new FileInputStream(DownloadController.class.getResource("/").getFile()+"/static/img/test.gif");
        //2.输出文件
        //设置响应头
        response.setHeader("Content-Disposition","attachment;filename=export.gif");
        OutputStream outputStream = response.getOutputStream();
        byte[] buff = new byte[1024];
        int lenth = 0;
        while ((lenth= inputStream.read(buff))!= -1){
            outputStream.write(buff,0,lenth);
        }
        //3.关闭资源
        outputStream.close();
        inputStream.close();
    }
}

Spring Boot原理分析

@SpringBootApplication

首先,我从引导类开始:

@SpringBootApplication
public class MyBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyBootApplication.class,args);
    }

}

引导类代码很简单,但可以看出最关键的是@SpringBootApplication注解以及在main方法中运行的SpringAppliation.run()了,我们进去@SpringBootApplication的源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
  ......
}

我们看到@SpringBootApplication其实是一个复合的注解,它就是由@SpringBootConfiguration@EnableAutoConfiguration以及@ComponentScan 三个注解组成,所以如果我们把SpringBoot启动类改写成如下方式,整个SpringBoot应用依然可以与之前的启动类功能一样:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public class MyBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBootApplication.class, args);
    }
}

因为我们每次新建项目时都要写上三个注解来完成配置,这显然太繁琐了,SpringBoot就为我们提供了@SpringBootApplication这样注解来简化我们的操作。接着,我们重点分析这三个注解的作用。

@SpringBootConfiguration

我们来看@SpringBootConfiguration注解的源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

我们可以看到,SpringBoot为了区别@Configuration而新提供的专属于SpringBoot的注解,功能其实和@Configuration一模一样。而这里的@Configuration注解对于我们来说并不陌生,它就是是个IoC容器的配置类。看到这里,我们其实可以把SpringBoot的启动类这样来看就清楚了:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class MyBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBootApplication.class, args);
    }
}

启动类MyBootApplication其实就是一个标准的Spring纯注解下的启动类,也并没有什么特殊。

@EnableAutoConfiguration

看到这个注解,我们不禁联想出Spring 中很多以“@Enable”开头的注解,比如:@EnableScheduling、@EnableCaching以及@EnableMBeanExport等,@EnableAutoConfiguration注解的理念和工作原理和它们其实一脉相承。简单的来说,就是该注解借助@Import注解的支持,Spring的IoC容器收集和注册特定场景相关的Bean定义:

@EnableAutoConfiguration注解也是借助@Import将所有符合配置条件的bean定义加载到IoC容器,仅此而已!@EnableAutoConfiguration注解的源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ...
}

这其中最关键的就是@Import(AutoConfigurationImportSelector.class)了,它借助AutoConfigurationImportSelector.class可以帮助SpringBoot应用将所有符合条件的@Configuration配置类都加载到当前SpringBoot创建并使用的IoC容器,就像下图一样。

image.png

下面我们给出AutoConfigurationImportSelector.java的部分源码,来解释和验证上图:

public class AutoConfigurationImportSelector
        implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
    protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
                this.beanClassLoader);
    }
    protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class,
                this.beanClassLoader);
    }
}

以上源码可以看出,@EnableAutoConfiguration正是借助SpringFactoriesLoader的支持,才能完成所有配置类的加载

SpringFactoriesLoader

SpringFactoriesLoader属于Spring框架专属的一种扩展方案(其功能和使用方式类似于Java的SPI方案:java.util.ServiceLoader),它的主要功能就是从指定的配置文件META-INF/spring.factories中加载配置,spring.factories是一个非常经典的java properties文件,内容格式是Key=Value形式,只不过这Key以及Value都非常特殊,为Java类的完整类名(Fully Qualified Name),比如:

org.springframework.context.ApplicationListener=org.springframework.boot.autoconfigure.BackgroundPreinitializer

然后Spring框架就可以根据某个类型作为Key来查找对应的类型名称列表了,SpringFactories源码如下:

public abstract class SpringFactoriesLoader {

    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader){
        ...
    }

    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        ...
    }
    // ...
}

对于@EnableAutoConfiguraion来说,SpringFactoriesLoader的用途和其本意稍微不同,它本意是为了提供SPI扩展,而在@EnableAutoConfiguration这个场景下,它更多的是提供了一种配置查找的功能的支持,也就是根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为Key来获取一组对应的@Configuration类:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

在SpringBoot的autoconfigure依赖包中的META-INF文件下的spring.factories文件中,我们可以找到以上内容。

总结来说,@EnableAutoConfiguration能实现自动配置的原理就是:SpringFactoriesLoader从classpath中搜寻所有META-INF/spring.fatories文件,并将其中Key[org.springframework.boot.autoconfigure.EnableAutoConfiguration]对应的Value配置项通过反射的方式实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总到当前使用的IoC容器中。

@ComponentScan

@ComponentScan注解在Spring Boot启动的时候其实不是必需的!因为我们知道作为Spring框架里的老成员,@ComponentScan的功能就是自动扫描并加载复合条件的组件或Bean定义,最终将这些Bean定义加载到当前使用的容器中。这个过程,我们可以手工单个进行注册,不是一定要通过这个注解批量扫描和注册,所以说@ComponentScan是非必需的。

所以,如果我们当前应用没有任何Bean定义需要通过@ComponentScan加载到当前SpringBoot应用对应的IoC容器,那么,去掉@ComponentScan注解,当前的SpringBoot应用依旧可以完美运行!

Spring Boot整合其他技术

Spring Boot整合Servlet

在Spring Boot应用如果我们需要编写Servlet相关组件(包括Servlet、Filter、Listener),需要怎么做呢?Spring Boot提供了两种使用Servlet组件的方式

  1. 使用@ServletComponentScan注解注册
  2. 使用@Bean注解注解

@ServletComponentScan

注意:在引导类类必须添加@ServletComponentScan注解,该注解用于扫描应用中Servlet相关组件。

示例:

/**
 * 等同于web.xml配置
 *     <servlet>
 *         <servlet-name>helloServlet</servlet-name>
 *         <servlet-class>com.yiidian.controller.HelloServlet</servlet-class>
 *     </servlet>
 *     <servlet-mapping>
 *            <servlet-name>helloServlet</servlet-name>
 *         <url-pattern>/helloServlet</url-pattern>
 *     </servlet-mapping>
 *
 */
// @WebServlet:声明该类为Servlet程序
@WebServlet("/hi")
//@WebServlet(name="helloServlet",urlPatterns="/hi")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HelloServlet");
        String docType = "<!DOCTYPE html> \n";
        String top = "Hello SpringBoot";
        resp.getWriter().println(docType +
                "<html>\n" +
                "<head><title>" + top + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + top + "</h1>\n" +
                "</body></html>");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       doGet(req,resp);
    }
}
//表示对所有servlet都执行过滤操作
//@WebFilter
//表示对路径为/ 的servlet执行过滤操作
//@WebFilter("/")
//表示对路径为/hi 的servlet执行过滤操作,两种写法都可以
@WebFilter("/hi")
//@WebFilter(filterName="HiFilter",urlPatterns="/hi")
//定义Filter过滤器
public class HiFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("HiFilter init");

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("do HiFilter before");
        //放行执行目标servlet资源
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("do HiFilter after");
    }

    @Override
    public void destroy() {
        System.out.println("HiFilter destroy");
    }
}
//定义Listener监听器
@WebListener
public class HiListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContext对象创建了");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContext对象消耗了");
    }
}

引导类

@SpringBootApplication
@ServletComponentScan //注册Servlet组件
public class MyBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyBootApplication.class,args);
    }

}

输出:

ServletContext对象创建了
HiFilter init
do HiFilter before
HelloServlet
do HiFilter after

注意:在测试时发现按照正确配置去写代码但是一直访问不成功。可能的原因有两个:

image.png

@Bean

第二种方式的代码和第一种几乎一样,就是引导类的差别,引导类改为使用@Bean注解来注解Servlet、Filter和Listener。

示例:

@SpringBootApplication
public class MyBeanBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyBeanBootApplication.class,args);
    }
    //注册Servlet程序
    @Bean
    public ServletRegistrationBean getServletRegistrationBean(){
        ServletRegistrationBean bean = new ServletRegistrationBean(new HelloServlet());
        bean.addUrlMappings("/hi");
        return bean;
    }
    //注册Filter
    @Bean
    public FilterRegistrationBean getFilterRegistrationBean(){
        FilterRegistrationBean bean = new FilterRegistrationBean(new HiFilter());
        bean.addUrlPatterns("/hi");
        return bean;
    }
    //注册Listener
    @Bean
    public ServletListenerRegistrationBean getServletListenerRegistrationBean(){
        return new ServletListenerRegistrationBean(new HiListener());
    }
}

注意:如果两个都配置则filter和listener会执行两遍。当使用@Bean整合Servlet时,Servlet,Filter,Listener不需要加对应的注解,因为我们在@Bean中已经把new HelloServlet()创建出来了。

Spring Boot整合JSP

示例:

<?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.self</groupId>
    <artifactId>hellospringboot</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 导入springboot父工程. 注意:任何的SpringBoot工程都必须有的!!! -->
    <!-- 父工程的作用:锁定起步的依赖的版本号,并没有真正到依赖 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.11.RELEASE</version>
    </parent>

    <dependencies>
        <!--web起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.60</version>
        </dependency>
        <!--devtools热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
            <scope>true</scope>
        </dependency>
        <!-- jsp依赖 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
 <!--       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>-->
    </dependencies>
</project>

注意:Error resolving template [userlist], template might not exist or might not be accessible by any of the configured Template Resolvers.这是因为整合jsp就不能依赖thymeleaf。

org.thymeleaf.exceptions.TemplateInputException: Error resolving template [userlist], template might not exist or might not be accessible by any of the configured Template Resolvers
    at org.thymeleaf.engine.TemplateManager.resolveTemplate(TemplateManager.java:869) ~[thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE]
    at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:607) ~[thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE]

        <!-- 导入thymeleaf坐标 整合jsp就不能依赖thymeleaf,否则会因为请求不到页面而报错-->
<!--        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>-->
image.png

起步依赖tomcat。因为spring-boot-starter-web已经有spring-boot-starter-tomcat依赖了,如下,因此不再需要再依赖tomcat。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-tomcat</artifactId>
  <version>2.1.11.RELEASE</version>
  <scope>compile</scope>
</dependency>

扩展:

pom中<scope></scope>一些理解
compile:默认值,表示当前依赖包,要参与当前项目的编译,后续测试,运行时,打包
provided:代表在编译和测试的时候用,运行,打包的时候不会打包进去
test:表示当前依赖包只参与测试时的工作:比如Junit
runtime:表示当前依赖包只参与运行周期,其他跳过了
system:从参与度和provided一致,不过被依赖项不会从maven远程仓库下载,而是从本地的系统拿。需要
systemPath属性来定义路径

配置application.yml

#springmvc视图解析器配置
spring:
  mvc:
    view:
      prefix: /WEB-INF/jsp/  # 前缀,注意最后的文件夹jsp后面要加斜杠/
      suffix: .jsp  # 后缀
@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/list")
    public String list(Model model) {
        //模拟用户数据
        List<User> list = new ArrayList<User>();
        for (int i = 0; i < 3; i++) {
            User user = new User();
            user.setId(ThreadLocalRandom.current().nextInt());
            user.setAge(ThreadLocalRandom.current().nextInt(100));
            user.setName(UUID.randomUUID().toString());
            list.add(user);
        }
        //把数据存入model
        model.addAttribute("list", list);
        //跳转到jsp页面: userlist.jsp
        return "userlist";
    }
}

编写userlist.jsp页面

注意:Spring Boot项目并不会到templates目录查找JSP页面,它是到/src/main/webapp目录下查找。所以我们需要创建webapp目录,然后在里面创建我们需要的目录和JSP文件。

image.png
<%@ page language="java" contentType="text/html; charset=utf-8"
         pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!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>用户列表展示</title>
</head>
<body>

<h3>用户列表展示</h3>
<table border="1">
    <tr>
        <th>编号</th>
        <th>姓名</th>
        <th>年龄</th>
    </tr>
    <c:forEach items="${list}" var="user">
        <tr>
            <td>${user.id}</td>
            <td>${user.name}</td>
            <td>${user.age}</td>
        </tr>
    </c:forEach>
</table>
</body>
</html>
@SpringBootApplication
public class MyBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyBootApplication.class,args);
    }
}

修改项目运行目录,经过测试,不修改也行,估计和IDEA版本有关。

Spring Boot应用默认情况下,会到应用的classes目录下加载内容,因为JSP页面并不在classes下面,所以需要把运行目录手动改为应用的根目录下,这样才能加载到JSP页面。 如下:

image.png

请求:http://localhost:8080/user/list

输出:

image.png

报错:

如下图,是因为application.yml配置jsp页面解析器路径名称弄错。

image.png
上一篇下一篇

猜你喜欢

热点阅读