美文网首页
SpringBoot教程——检视阅读(上)

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

作者: 卡斯特梅的雨伞 | 来源:发表于2020-05-21 16:33 被阅读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 项目引导页面可以在几秒构建一个项目
    • 方便对外输出各种形式的服务,如 REST API、WebSocket、Web、Streaming、Tasks
    • 非常简洁的安全策略集成
    • 支持关系数据库和非关系数据库
    • 支持运行期内嵌容器,如 Tomcat、Jetty
    • 强大的开发包,支持热启动
    • 自动管理依赖自带应用监控
    • 支持各种 IDE,如 IntelliJ IDEA 、NetBeans

    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提供了两种格式的配置方便开发者进行修改。

    • applicaiton*.properties
    • application.yml(或者application.yaml)

    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文件
    • 每个文件里面的环境配置变量不同

    示例:

    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实现多环境配置

    参考

    • 首选pom.xml中要有对应的profiles配置
    • 运行打包命令mvn clean package -Dmaven.test.skip=true -P workdev-business
    <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

    注意以下几点:

    • prefix:代表属性的前缀,如果user.nickname前缀就是user
    • 属性名称必须和properties文件的属性名保持一致
    • 属性必须提供setter方法来注入文件的属性值
    #基本类型
    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热部署

    什么是热部署

    无法热部署的缺点:

    • 在实际开发过程中,每次修改代码就得将项目重启,重新部署,对于一些大型应用来说,重启时间需要花费大量的时间成本。
    • 程序员开发过程中重启缓慢影响开发。在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需要重启虚拟机,对类重新加载才能完成更新操作。

    热部署原理

    深层原理是使用了两个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定义:

    • @EnableScheduling是通过@Import将Spring调度框架相关的bean都加载到IoC容器。
    • @EnableMBeanExport是通过@Import将JMX相关的bean定义加载到IoC容器。

    @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
    

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

    • IDEA的问题,一直重启却识别不了@ServletComponentScan去扫描servlet,导致失败。
    • 启动类的@ServletComponentScan扫描默认是扫描与该启动类同包以及其子包下的类
    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

    相关文章

      网友评论

          本文标题:SpringBoot教程——检视阅读(上)

          本文链接:https://www.haomeiwen.com/subject/ozctahtx.html