Java代码审计前置知识——SpringBoot基础

目录

前言:

(一)SpringBoot简介

1.1 回顾什么是Spring

1.2 Spring是如何简化Java开发的

1.3 什么是SpringBoot

(二)微服务

2.1 什么是微服务

2.2 单体应用架构

2.3 微服务架构

2.4 如何构建微服务

(三)第一个SpringBoot程序

3.1 准备工作

3.2 创建基础项目说明

3.3 pom.xml 分析

3.4 编写一个http接口

 3.5 将项目打成jar包

(四)运行原理初探

4.1 pom.xml

父依赖

启动器 spring-boot-starter

4.2 主启动类

默认的主启动类

4.3 SpringApplication

4.3.1  SpringApplication

4.3.1 run 方法流程分析

(五)yaml配置注入

5.1 配置文件

5.2 yaml概述

传统xml配置:

yaml配置:

5.3 yaml基础语法

5.4 注入配置文件

5.5 加载指定的配置文件

 5.6 配置文件占位符

6、JSR303数据校验及多环境切换

6.1 先看看如何使用

6.2 常见参数

6.3 多配置文件

6.4 yaml的多文档块

6.5 配置文件加载位置

(七)、自动配置原理

7.1 分析自动配置原理

7.2 总结

7.3  @Conditional

(八)、静态资源的导入

8.1 使用 webjars

8.2 静态资源映射规则

8.3 自定义静态资源路径

8.4 首页处理

8.5 自定义图标

(九)Thymeleaf 模板引擎

9.1 引入 Thymeleaf

9.2 Thymeleaf 分析

9.3 Thymeleaf 语法学习

(十)MVC 自动配置原理

10.1 官网阅读

10.2 内容协商视图解析器

10.3 转换器和格式化器

10.4 修改 SpringBoot 的默认配置

10.5 全面接管SpringMVC

参考资料:


前言:

        本篇文章对于概念性的内容介绍的偏多,准备明天做一个小demo把整个开发的过程像SSM一样分析一遍, 下面是我在github上找的关于SpringBoot相关漏洞,感兴趣的自取。

        由于笔者个人水平有限,行文如有不当,还请各位师傅评论指正,非常感谢KpLi0rn/SpringBootVulExploit: SpringBoot 相关漏洞学习资料,利用方法和技巧合集,黑盒安全评估 checklist

(一)SpringBoot简介


1.1 回顾什么是Spring


Spring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,作者:Rod Johnson 。

Spring是为了解决企业级应用开发的复杂性而创建的,简化开发

1.2 Spring是如何简化Java开发的


为了降低Java开发的复杂性,Spring采用了以下4种关键策略:

1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;

2、通过IOC,依赖注入(DI)和面向接口实现松耦合;

3、基于切面(AOP)和惯例进行声明式编程;

4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;

1.3 什么是SpringBoot


随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件;

Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。

简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。

Spring Boot的主要优点:

  • 为所有Spring开发者更快的入门
  • 开箱即用,提供各种默认配置来简化项目配置
  • 内嵌式容器简化Web项目
  • 没有冗余代码生成和XML配置的要求

(二)微服务


2.1 什么是微服务


微服务是一种架构风格,它要求我们在开发一个应用的时候,这个应用必须构建成一系列小服务的组合;可以通过http的方式进行互通。要说微服务架构,先得说说过去我们的单体应用架构。

2.2 单体应用架构


所谓单体应用架构是指,我们讲一个应用中的所有应用服务都封装在一个应用中,无论是ERP,CRM或是其他什么系统,你都把数据库访问,web访问,等等各个功能放在一个war包中。

  • 这样做的好处是易于开发和测试;十分方便部署;当需要扩展时,只需要将war包复制多份,然后放在多个服务器上,在做个负载均衡就可以了
  • 单体应用架构的缺点,哪怕我要修改一个非常小的地方,我都需要停掉整个服务,重新打包,部署这个war包。特别是对于一个大型应用,我们不可能吧所有内容都放在一个应用里面,我们如何维护如何分工合作都是一个问题

2.3 微服务架构


单体应用的架构方式,我们把所有的功能单元放在一个应用里面,然后我们把整个应用放在服务器上。如果负载能力不行,我们将整个应用进行水平复制,进行扩展,然后再负载均衡。
所谓微服务架构就是打破之前单体应用的架构方式,把每个功能元素独立出来,把独立出来的功能元素动态组合,需要的功能元素才拿来组合,需要多一些的时候就整合多个功能元素。所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制。

这样做的好处:

  1. 节省了调用资源
  2. 每个功能元素都是可替换的,可独立升级的软件代码

Java代码审计前置知识——SpringBoot基础_第1张图片

2.4 如何构建微服务


一个大型系统的微服务架构就像一个复杂的神经网络,每一个神经元都是一个功能元素,他们各自完成各自的功能,然后通过http相互请求调用。比如一个电商系统,查缓存,连数据库,浏览页面,结账,支付,等服务都是一个个独立的功能服务,都被微化了,他们作为一个个微服务共同构造了一个庞大的系统。如果修改一个功能,只需要更新升级其中一个功能服务单元即可.

但这种庞大的系统架构给部署和运维带来了强大的压力,于是,spring为我们带来了构建大型分布式微服务的全套,全程产品。

  • 构建一个功能独立的微服务应用单元,可以使用springboot,可以帮我们快速构建一个应用
  • 大型分布式网络服务的调用,这部分由springboot来完成,实现分布式
  • 在分布式中间进行流式数据计算,批处理,我们有spring cloud data flow
  • spring为我们想清楚了从开始构建应用到大型分布式应用的全流程方案

Java代码审计前置知识——SpringBoot基础_第2张图片

(三)第一个SpringBoot程序


3.1 准备工作


我们将学习如何快速的创建一个Spring Boot应用,并且实现一个简单的Http请求处理。通过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。

我的环境准备:

  • jdk 1.8
  • Maven-3.6.1
  • SpringBoot 2.x 最新版

开发工具:

  • IDEA2021 

3.2 创建基础项目说明


Spring官方提供了非常方便的工具让我们快速构建应用

Spring Initializr:https://start.spring.io/

项目创建方式一:在官网使用Spring Initializr 的 Web页面创建项目

  1. 打开 https://start.spring.io/
  2. 填写项目信息
  3. 点击”Generate Project“按钮生成项目;下载此项目
  4. 解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。
  5. 如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。

项目创建方式二:使用 IDEA 直接创建项目

  1. 创建一个新项目
  2. 选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现
  3. 填写项目信息
  4. 选择初始化的组件(初学勾选 Web 即可)
  5. 填写项目路径
  6. 等待项目构建成功

3.3 pom.xml 分析


打开pom.xml,看看Spring Boot项目的依赖:



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.6.5
         
    
    com.example
    SpringBoot
    0.0.1-SNAPSHOT
    SpringBoot
    SpringBoot
    
        1.8
    
    
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        
 
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    
 
    
                    
            
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    
 

如上所示,主要有四个部分:

  • 项目元数据:创建时候输入的Project Metadata部分,也就是Maven项目的基本元素,包括:groupId、artifactId、version、name、description等
  • parent:继承spring-boot-starter-parent的依赖管理,控制版本与打包等内容
  • dependencies:项目具体依赖,这里包含了spring-boot-starter-web用于实现HTTP接口(该依赖中包含了Spring MVC),官网对它的描述是:使用Spring MVC构建Web(包括RESTful)应用程序的入门者,使用Tomcat作为默认嵌入式容器。spring-boot-starter-test用于编写单元测试的依赖包。更多功能模块的使用将在后面逐步展开。
  • build:构建配置部分。默认使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把Spring Boot应用打包成JAR来直接运行

3.4 编写一个http接口


1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到

Java代码审计前置知识——SpringBoot基础_第3张图片

2、在包中新建一个HelloController类

package com.springmvc.xiaowei.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    // 接口: http://localhost:8080/hello
    @RequestMapping("/hello")
    public String hello(){
        // 调用业务,接收前端的参数!
        return "hello,world";
    }
}

3、编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!

Java代码审计前置知识——SpringBoot基础_第4张图片

 简单几步,就完成了一个web接口的开发,SpringBoot就是这么简单。所以我们常用它来建立我们的微服务项目!(虽然说这样很简单,但是我建议初学者先从spring学起,这样才会对开发的整个流程理解的更加深入,谁都不想只做脚本小子)

 3.5 将项目打成jar包


1、点击 maven的 package

Java代码审计前置知识——SpringBoot基础_第5张图片

 2、假如打包不成功,测试用例报错



    org.apache.maven.plugins
    maven-surefire-plugin
    
        
        true
    

3、如果打包成功,则会在target目录下生成一个 jar 包

Java代码审计前置知识——SpringBoot基础_第6张图片

 4、打成了jar包后,就可以在任何地方运行,成功实现前后端分离

java -jar SpringBoot-0.0.1-SNAPSHOT.jar

(四)运行原理初探


我们之前写的HelloSpringBoot,到底是怎么运行的呢,Maven项目,我们一般从pom.xml文件探究起;

4.1 pom.xml


父依赖

其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件。


    org.springframework.boot
    spring-boot-starter-parent
    2.2.5.RELEASE
     

点进去,发现还有一个父依赖


    org.springframework.boot
    spring-boot-dependencies
    2.2.5.RELEASE
    ../../spring-boot-dependencies

这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;

以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;

启动器 spring-boot-starter


    org.springframework.boot
    spring-boot-starter-web
  • springboot-boot-starter-xxx:就是spring-boot的场景启动器
  • spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;

SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter;

4.2 主启动类


默认的主启动类

// @SpringBootApplication 来标注一个主程序类
// 说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {
 
   public static void main(String[] args) {
     //以为是启动了一个方法,没想到启动了一个服务
      SpringApplication.run(SpringbootApplication.class, args);
   }
 
}

但是一个简单的启动类并不简单!我们来分析一下这些注解都干了什么

接下来一大段没听懂的注解加载配置…

Java代码审计前置知识——SpringBoot基础_第7张图片

 结论:SpringBoot 所有自动配置都是在启动的时候扫描并加载:spring.factories 所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入了对应的 start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功.

  1. SpringBoot在启动的时候从类路径下的META-INF/spring.factores中获取EnableAutoConfiguration指定的值;
  2. 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
  3. 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
  4. 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
  5. 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

4.3 SpringApplication


@SpringBootApplication
public class Springboot01HellowordApplication {
 
    public static void main(String[] args) {
           //该方法返回一个ConfigurableApplicationContext对象
         //参数一:应用入口的类; 参数二:命令行参数  
        SpringApplication.run(Springboot01HellowordApplication.class, args);
    }
}

SpringApplication.run分析

  • 分析该方法主要分两部分
  • 一是SpringApplication的实例化,
  • 二是run方法的执行;

4.3.1  SpringApplication


这个类主要做了以下四件事情:

  1. 推断应用的类型是普通的项目还是Web项目
  2. 查找并加载所有可用初始化器 , 设置到initializers属性中
  3. 找出所有的应用程序监听器,设置到listeners属性中
  4. 推断并设置main方法的定义类,找到运行的主类

查看构造器:

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    // ......
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances();
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

4.3.1 run 方法流程分析


Java代码审计前置知识——SpringBoot基础_第8张图片

(五)yaml配置注入


5.1 配置文件


SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的

  • application.properties
  • 语法结构 :key=value
  • application.yml
  • 语法结构 :key:空格 value

配置文件的作用 :修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;

比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下:

server.port=8081

5.2 yaml概述


YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:”Yet Another Markup Language”(仍是一种标记语言)

这种语言以数据作为中心,而不是以标记语言为重点!

以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml

传统xml配置


    8081

yaml配置:

server:
  prot: 8080

5.3 yaml基础语法


说明:语法要求严格

  1. 空格不能省略
  2. 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的
  3. 属性和值的大小写都是十分敏感的

字面量:普通的值 [ 数字,布尔值,字符串 ]

字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;

k: v

注意:

  • “ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思; 比如 :name: "jinyou \n xin" 输出 :jinyou 换行 xin
  • '' 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出 比如 :name: ‘jinyou \n xin’ 输出 :jinyou \n xin

对象、Map(键值对)

#对象、Map格式
k: 
    v1:
    v2:

在下一行来写对象的属性和值得关系,注意缩进(可以类比python);比如:

student:
    name: jinyouxin
    age: 3

行内写法

student: {name: qinjiang,age: 3}

数组( List、set )

用 - 值表示数组中的一个元素,比如:

pets:
 - cat
 - dog
 - pig

行内写法

pets: [cat,dog,pig]

修改SpringBoot的默认端口号

配置文件中添加,端口号的参数,就可以切换端口;

server:
  port: 8082

yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值。

5.4 注入配置文件


  1. 在springboot项目中的resources目录下新建一个文件 application.yaml
  2. 编写一个实体类 Dog;
package com.example.pojo;
 
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component    //注册bean到容器中
public class Dog {
    private String name;
    private Integer age;
}
  • 3、思考一下,我们原来是如何给bean注入属性值的!@Value,给狗类狗测试一下:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Dog {
    @Value("旺财")
    private String name;
    @Value("3")
    private Integer age;
}
  • 4、在SpringBoot的测试类下注入狗狗输出一下;
@SpringBootTest
class DemoApplicationTests {
 
    @Autowired //将狗狗自动注入进来
    Dog dog;
 
    @Test
    public void contextLoads() {
        System.out.println(dog); //打印看下狗狗对象
    }
 
}

结果成功输出,@Value注入成功,这是我们原来的办法

Java代码审计前置知识——SpringBoot基础_第9张图片

  •  5、我们在编写一个复杂一点的实体类:Person 类
package com.example.pojo;
 
 
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component  // 注册bean
public class Person {
 
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map maps;
    private List lists;
    private Dog dog;
 
} 
  
  • 6、我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置,如图5-1
Java代码审计前置知识——SpringBoot基础_第10张图片 图5-1 yaml配置

 

person:
  name: John
  age: 3
  happy: false
  birth: 2022/03/26
  maps: {k1: v1,k2: v2}
  lists:
    - code
    - music
    - girl
  dog:
    name: 旺财
    age: 3
  • 7、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
 
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component  // 注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
 
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map maps;
    private List lists;
    private Dog dog;
 
} 
  
  • 8、IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖


  org.springframework.boot
  spring-boot-configuration-processor
  true
  • 9、确认以上配置都OK之后,我们去测试类中测试一下
@SpringBootTest
class Springboot02ConfigApplicationTests {
 
    @Autowired
    private Person person;
 
    @Test
    void contextLoads() {
        System.out.println(person);
    }
 
}

结果:所有值全部注入成功!

Java代码审计前置知识——SpringBoot基础_第11张图片

 yaml配置注入到实体类完全是可以的

  1. 将配置文件的key 值 和 属性的值设置为不一样,则结果输出为null,注入失败
  2. 在配置一个person2,然后将 @ConfigurationProperties(prefix = “person2”) 指向我们的person2;

5.5 加载指定的配置文件


@PropertySource :加载指定的配置文件;

@configurationProperties:默认从全局配置文件中获取值;

1、我们去在resources目录下新建一个person.properties文件

name=John

2、然后在我们的代码中指定加载person.properties文件

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component  // 注册bean
// 加载指定的配置文件
@PropertySource(value = "classpath:person.properties")
public class Person {
 
    // SPEL表达式去除配置文件的值
    @Value("${name}")
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map maps;
    private List lists;
    private Dog dog;
 
} 
  

3、再次输出测试一下:指定配置文件绑定成功,如图5-2

Java代码审计前置知识——SpringBoot基础_第12张图片 图 5-2 测试运行结果

 5.6 配置文件占位符


配置文件还可以编写占位符生成随机数

person:
  name: John${random.uuid}
  age: ${random.int}
  happy: false
  birth: 2022/03/26
  maps: {k1: v1,k2: v2}
  lists:
    - code
    - music
    - girl
  dog:
    name: ${person.hello:hello}_旺财
    age: 3

 运行结果如下图5-3

Java代码审计前置知识——SpringBoot基础_第13张图片 图 5-3 运行结果

@value比较复杂,这里不再介绍。

6、JSR303数据校验及多环境切换


6.1 先看看如何使用


Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component  // 注册bean
@ConfigurationProperties(prefix = "person")
@Validated  // 数据校验
public class Person {
 
    @Email()    //name必须是邮箱格式
    private String name;
    private Integer age;
    ... ...

Java代码审计前置知识——SpringBoot基础_第14张图片

 使用数据校验,可以保证数据的正确性;

6.2 常见参数


@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
 
空检查
@Null       验证对象是否为null
@NotNull    验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank   检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty   检查约束元素是否为NULL或者是EMPTY.
 
Booelan检查
@AssertTrue     验证 Boolean 对象是否为 true  
@AssertFalse    验证 Boolean 对象是否为 false  
 
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  
@Length(min=, max=) string is between min and max included.
 
日期检查
@Past       验证 Date 和 Calendar 对象是否在当前时间之前  
@Future     验证 Date 和 Calendar 对象是否在当前时间之后  
@Pattern    验证 String 对象是否符合正则表达式的规则
 
.......等等
除此以外,我们还可以自定义一些数据校验规则

6.3 多配置文件


profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;

例如:

application-test.properties 代表测试环境配置

application-dev.properties 代表开发环境配置

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件

我们需要通过一个配置来选择需要激活的环境:

#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev

6.4 yaml的多文档块


和properties配置文件中一样,但是使用yaml去实现不需要创建多个配置文件,更加方便.

server:
  port: 8081
#选择要激活那个环境块
spring:
  profiles:
    active: prod
 
---
server:
  port: 8083
spring:
  profiles: dev #配置环境的名称
 
 
---

server:
  port: 8084
spring:
  profiles: prod  #配置环境的名称

注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的.

6.5 配置文件加载位置


外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置。

官方外部配置文件说明参考文档,如下图 6-5:

Java代码审计前置知识——SpringBoot基础_第15张图片 图 6-5 配置文件

springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:

优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件

 优先级由高到底,高优先级的配置会覆盖低优先级的配置;

SpringBoot会从这四个位置全部加载主配置文件;互补配置;

(七)、自动配置原理


Spring Boot Reference Documentation

7.1 分析自动配置原理


我们以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;

//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration 
 
  //启动指定类的ConfigurationProperties功能;
  //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
  //并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class}) 
 
  //Spring底层@Conditional注解
  //根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
  //这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
 
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})
 
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
  //如果不存在,判断也是成立的
  //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
 
public class HttpEncodingAutoConfiguration {
    //它已经和SpringBoot的配置文件映射了
    private final Encoding properties;
    //只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
 
    //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @Bean
    @ConditionalOnMissingBean //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
    //... ...
}

一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效

  • 一旦这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http") 
public class HttpProperties {
    // .....
}

7.2 总结


  1. SpringBoot启动会加载大量的自动配置类
  2. 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
  3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。所以说我们只需要在配置文件中指定这些属性的值即可。

7.3  @Conditional


了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;

@Conditional派生注解(Spring注解版原生的@Conditional作用)


作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效。

我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类
debug=true
  • Positive matches:(自动配置类启用的:正匹配)
  • Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
  • Unconditional classes: (没有条件的类)

(八)、静态资源的导入


SpringBoot 中,SpringMVC 的 Web 配置都在 WebMvcAutoConfiguration 这个配置类里面;我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;

        有一个方法:addResourceHandlers 添加资源处理

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        // 已禁用默认资源处理
        logger.debug("Default resource handling disabled");
        return;
    }
    // 缓存控制
    Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
    // webjars 配置
    if (!registry.hasMappingForPattern("/webjars/**")) {
        customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                                             .addResourceLocations("classpath:/META-INF/resources/webjars/")
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
    // 静态资源配置
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    if (!registry.hasMappingForPattern(staticPathPattern)) {
        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                                             .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
}

表明:所有的 /webjars/** , 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源;

8.1 使用 webjars


Webjars 本质就是以 jar 包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。

使用 SpringBoot 需要使用 Webjars,我们可以去搜索一下:

网站:https://www.webjars.org

要使用 jQuery,我们只要要引入 jQuery 对应版本的 pom 依赖即可


    org.webjars
    jquery
    3.4.1

访问:只要是静态资源,SpringBoot 就会去对应的路径寻找资源,我们这里访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

8.2 静态资源映射规则


// 进入方法
public String[] getStaticLocations() {
    return this.staticLocations;
}
// 找到对应的值
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
// 找到路径
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { 
    "classpath:/META-INF/resources/",
  "classpath:/resources/", 
    "classpath:/static/", 
    "classpath:/public/" 
};

以下四个目录存放的静态资源可以被我们识别:

"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"

其他根据:

package org.springframework.boot.autoconfigure.web.servlet
@Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            //若有默认路径则直接返回
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
                return;
            }
            addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
            addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
                registration.addResourceLocations(this.resourceProperties.getStaticLocations());
                if (this.servletContext != null) {
                    ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
                    registration.addResourceLocations(resource);
                }
            });
        }

其他优先级:resourcesstatic>public

8.3 自定义静态资源路径


可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在 application.properties 中配置;

1spring.resources.static-locations=classpath:/coding/,classpath:/xxx/

8.4 首页处理


SpringBoot 在 WebMvcAutoConfiguration

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService,ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
        new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), // getWelcomePage 获得欢迎页
        this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    return welcomePageHandlerMapping;
}

在里面

private Optional getWelcomePage() {
    String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
    // ::是java8 中新引入的运算符
    // Class::function的时候function是属于Class的,应该是静态方法。
    // this::function的funtion是属于这个对象的。
    // 简而言之,就是一种语法而已,是一种简写
    return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
// 欢迎页就是一个location下的的 index.html 而已
private Resource getIndexHtml(String location) {
    return this.resourceLoader.getResource(location + "index.html");
}

在 org.springframework.boot.autoconfigure:2.6.0 改成了:

private Resource getIndexHtml(String location) {
            return getIndexHtml(this.resourceLoader.getResource(location));
        }
 
        private Resource getIndexHtml(Resource location) {
            try {
                Resource resource = location.createRelative("index.html");
                if (resource.exists() && (resource.getURL() != null)) {
                    return resource;
                }
            }
            catch (Exception ex) {
            }
            return null;
        }

欢迎页,静态资源文件夹下的所有 index.html 页面;被 /** 映射。

比如我访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html

新建一个 index.html ,在我们上面的 3 个目录中任意一个;然后访问测试 http://localhost:8080/ 看结果

8.5 自定义图标


  1. 将自己的 logo 图片转为.ico 格式的,命名必须为【favicon.ico】
  2. 将该图片直接放在 src/main/resourecs 目录下
  3. 重启项目,刷新一下浏览器缓存,就会发现图标更换了

       一般推荐放在资源文件夹内并在 HTML 中用, 这样我们就很容易理解在做信息收集关于CMS指纹收集的时候,会找这个favicon,从而找到该系统的CMS信息。

Java代码审计前置知识——SpringBoot基础_第16张图片

 

需要注意的是 favicon.ico 需要改名字,我在这里加了个 1 才成功修改。

(九)Thymeleaf 模板引擎


前端交给我们的页面,是 HTML 页面。如果是我们以前开发,我们需要把他们转成 JSP 页面,JSP 好处就是当我们查出一些数据转发到 JSP 页面以后,我们可以用 JSP 轻松实现数据的显示,及交互等。

JSP 支持非常强大的功能,包括能写 Java 代码,但是呢,我们现在的这种情况SpringBoot 这个项目首先是以 jar 的方式,不是像war;第二,我们用的还是嵌入式的 Tomcat,所以呢,他现在默认是不支持 JSP 的

        但 SpringBoot 给我们推荐的 Thymeleaf 模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。

9.1 引入 Thymeleaf


Thymeleaf 官网:https://www.thymeleaf.org/

Thymeleaf 在 GitHub 的主页:https://github.com/thymeleaf/thymeleaf

Spring 官方文档:找到我们对应的版本

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

找到对应的 pom 依赖:可以适当点进源码看下本来的包:



    org.springframework.boot
    spring-boot-starter-thymeleaf

或者导入:

        
        
            org.thymeleaf
            thymeleaf-spring5
            3.0.11.RELEASE
        
 
        
        
            org.thymeleaf.extras
            thymeleaf-extras-java8time
            3.0.4.RELEASE
        

9.2 Thymeleaf 分析


ThymeleafPropert

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
}

我们只需要把我们的 HTML 页面放在类路径下的 templates 下,thymeleaf 就可以帮我们自动渲染了。

使用 thymeleaf 什么都不需要配置,只需要将他放在指定的文件夹下即可

测试:

  • 编写一个TestController
   @Controller
   public class TestController {
 
       @RequestMapping("/test")
       public String test1(){
           //classpath:/templates/test.html
           return "test";
       }
 
   }
  • 编写一个测试页面 test.html 放在 templates 目录下
   
   
   
       
       Title
   
   
   

Test页面

  • 启动项目请求测试

9.3 Thymeleaf 语法学习


  • Thymeleaf 官网:https://www.thymeleaf.org/ ,
  • 民间汉化版:https://raledong.gitbooks.io/using-thymeleaf/content/

Java代码审计前置知识——SpringBoot基础_第17张图片

 

  • 简单表达式 Simple Expressions:
  • 变量表达式 Variable Expressions:${...}
  • 选中变量表达式 Selection Variable Expressions:*{...}
  • 消息表达式 Message Expressions:#{...}
  • 连接 URL 表达式 Link URL Expressions:@{...}
  • 片段表达式 Fragment Expressions:~{...}
  • 常量 Literals
  • 文本常量 Text literals:'one text','Another one!',…
  • 数字常量 Number literals:0,34,3.0,12.3,…
  • 布尔常量 Boolean literals:true,false
  • 空常量 Null literal:null
  • 常符号 Literal tokens:one,sometext,main,…
  • 文本操作 Text operations:
  • 字符串连接 String concatenation:+
  • 常量替换 Literal substitutions:|The name is ${name}|
  • 算数操作 Arithmetic operations:
  • Binary operators:+,-,*,/,%
  • Minus sign (unary operator):-
  • 布尔操作 Boolean operations:
  • 布尔操作符 Binary operators:and,or
  • 布尔否定 一元操作符 Boolean negation (unary operator):!,not
  • 比较和相等 Comparisons and equality:
  • 比较符 Comparators:>,<,>=,<= (gt,lt,ge,le)
  • 相等符 Equality operators:==,!= (eq,ne)
  • 条件操作符 Conditional operators:
  • If-then:(if) ? (then)
  • If-then-else:(if) ? (then) : (else)
  • 默认值 Default:(value) ?: (defaultvalue)
  • 特殊符号 Special tokens:
  • 无操作符 No-Operation:_

测试

@RequestMapping("/t2")
public String test2(Map map){
    //存入数据
    map.put("msg","

Hello

"); map.put("users", Arrays.asList("test","test1")); //classpath:/templates/test.html return "test"; }

测试页面取出数据




    
    jinyouxin technology


测试页面

[[${user}]]

(十)MVC 自动配置原理


10.1 官网阅读


在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。

Spring Boot Reference Documentation

Spring MVC Auto-configuration
// Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在Spring默认设置的基础上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径,以及webjars
Support for serving static resources, including support for WebJars 
// 自动注册了Converter:
// 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
// Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释;
Support for HttpMessageConverters (covered later in this document).
// 定义错误代码生成规则的
Automatic registration of MessageCodesResolver (covered later in this document).
// 首页定制
Static index.html support.
// 图标定制
Custom Favicon support (covered later in this document).
// 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
 
/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己
的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration 
(interceptors, formatters, view controllers, and other features), you can add your own 
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide 
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or 
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
 
// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

我们紧接着进行分析;

10.2 内容协商视图解析器


ContentNegotiatingViewResolver

  • 自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器;
  • 即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
  • 我们去看看这里的源码:我们找到 WebMvcAutoConfiguration , 然后搜索ContentNegotiatingViewResolver。找到如下方法:
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
    // ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
}
  • 我们可以点进这类看看,找到对应的解析视图的代码;
@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
    List requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
    if (requestedMediaTypes != null) {
        // 获取候选的视图对象
        List candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
        // 选择一个最适合的视图对象,然后把这个对象返回
        View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
        if (bestView != null) {
            return bestView;
        }
    }
    // .....
}

其中 getCandidateViews 中看到他是把所有的视图解析器拿来,进行 while 循环,挨个解析

结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的

10.3 转换器和格式化器


找到格式化转换器:

@Bean
@Override
public FormattingConversionService mvcConversionService() {
    // 拿到配置文件中的格式化规则
    WebConversionService conversionService = 
        new WebConversionService(this.mvcProperties.getDateFormat());
    addFormatters(conversionService);
    return conversionService;
}

点击去:

public String getDateFormat() {
    return this.dateFormat;
}
 
/**
* Date format to use. For instance, `dd/MM/yyyy`. 默认的
 */
private String dateFormat;

可以看到在我们的 Properties 文件中,我们可以进行自动配置它.

如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:

  • 2.2.x版本之前的

配置文件

# 配置文件
spring.nvc.date-format=

源码

@Deprecated
public void setDateFormat(String dateFormat) {
    this.dateFormat = dateFormat;
}
  • 2.2.x版本之后的

配置文件

spring.nvc.date=

源码

@Deprecated
public void setDateFormat(String dateFormat) {
    this.format.setDate(dateFormat);
}
 
public void setDate(String date) {
    this.date = date;
}

10.4 修改 SpringBoot 的默认配置


SpringBoot 在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;

如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来。

扩展使用 SpringMVC 官方文档如下:

If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMappingRequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;

我们去自己写一个;我们新建一个包叫 config,写一个类 MyMvcConfig;

//应为类型要求为WebMvcConfigurer,所以我们实现其接口
//可以使用自定义类扩展MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
 
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 浏览器发送/hello , 就会跳转到test页面;
        registry.addViewController("/hello").setViewName("test");
    }
}

我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置。

10.5 全面接管SpringMVC


  • 官方文档:
  If you want to take complete control of Spring MVC
  you can add your own @Configuration annotated with @EnableWebMvc.
  • 全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!
  • 只需在我们的配置类中要加一个@EnableWebMvc
  • 这时候Springboot的自动配置都会无效

我们开发中,不推荐使用全面接管SpringMVC

参考资料:

【狂神说Java】SpringBoot最新教程IDEA版通俗易懂_哔哩哔哩_bilibili

你可能感兴趣的:(Javaee,spring,boot,java,开发语言,java-ee,web安全)