为什么要学SpringBoot?如何学?

之前学完SSM整合的时候,写了以下的配置文件:

springMVC.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.wzw.jsTest.controller"/>

    <mvc:annotation-driven/>
    <mvc:default-servlet-handler/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/html/"/>
        <property name="suffix" value=".html"/>
    </bean>

    <mvc:interceptors>
        <mvc:interceptor>
<!--            <mvc:mapping path="/**/**.html"/>-->
            <mvc:mapping path="/user/**"/>
                <mvc:exclude-mapping path="/user/goLogin"/>
                <mvc:exclude-mapping path="/user/goInfo"/>
                <mvc:exclude-mapping path="/user/goRegister"/>
                <mvc:exclude-mapping path="/user/name"/>
                <mvc:exclude-mapping path="/user/session"/>
                <mvc:exclude-mapping path="/user/exit"/>
            <bean class="com.wzw.jsTest.Interceptor.loginInterceptor"/>
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/admin/**"/>
                <mvc:exclude-mapping path="/admin/goLogin"/>
                <mvc:exclude-mapping path="/admin/login"/>
            <bean class="com.wzw.jsTest.Interceptor.adminInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>


</beans>

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.ort/DTD Config 3.0//EN"
        "http://mybatis.ort/dtd/mybatis-3-config.dtd">

<configuration>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <typeAliases>
        <package name="com.wzw.jsTest.bean"/>
    </typeAliases>

    <mappers>
        <package name="com.wzw.jsTest.mapper"/>
    </mappers>

</configuration>

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="com.wzw.jsTest.service"/>
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>
    <!--    <context:component-scan base-package="com.wzw.mapper"/>-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.wzw.jsTest.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
    <!--    事务管理器-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--    启用事务注解-->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

</beans>

因为

单单只是整合SSM就已经写了这么多配置文件了,那如果需要整合Spring生态圈的其他应该怎么办?

所以在springboot之前,java的基于spring的开发被称为配置地狱

所以 SpringBoot应运而生,SpringBoot是为了简化配置而出现的,减少繁琐的配置


SpringBoot有什么优点?

  • Create stand-alone Spring applications

  • 创建独立Spring应用

  • Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)

  • 内嵌web服务器,不用再部署到tomcat了,它内置了web服务器

  • Provide opinionated 'starter' dependencies to simplify your build configuration

  • 自动starter依赖,简化构建配置,不用导入jar包,避免了版本

  • Automatically configure Spring and 3rd party libraries whenever possible

  • 自动配置Spring以及第三方功能

  • Provide production-ready features such as metrics, health checks, and externalized configuration

  • 提供生产级别的监控、健康检查及外部化配置

  • Absolutely no code generation and no requirement for XML configuration

  • 无代码生成、无需编写XML

SpringBoot是整合Spring技术栈的一站式框架

SpringBoot是简化Spring技术栈的快速开发脚手架


HelloWord

目录结构

MainApplication----程序的入口

@SpringBootApplication说明这是一个SpringBoot应用

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

HelloController----控制器

@RestController = @ResponseBody + @Controller

@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String hello(){
        return "helloWord";
    }
}

application.propertiest 配置文件

server.port=8888

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>org.example</groupId>
    <artifactId>springboot-01-helloword</artifactId>
    <version>1.0-SNAPSHOT</version>

    
    <!--    自定义版本号-->
   <properties>
        <mysql.version>5.1.6</mysql.version>
    </properties>
	
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>
<!--    把项目打成jar包,直接在目标服务器执行即可。-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.4.RELEASE</version>
            </plugin>
        </plugins>
    </build>
</project>

自动配置

  • 自动配好Tomcat

  • 引入Tomcat依赖。

  • 配置Tomcat

  • 自动配好SpringMVC

  • 引入SpringMVC全套组件

  • 自动配好SpringMVC常用组件(功能)

  • public static void main(String[] args) { // 获取容器对象 ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 获取容器里自动配置的组件 String[] names = run.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } }

  • 自动配好Web常见功能

  • SpringBoot帮我们配置好了所有web开发的常见场景

  • 比如:字符编码问题,视图解析器之类的,所以我们不用配置了,Boot自动配置好了

  • 默认的包结构

  • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来

  • 无需以前的包扫描配置

  • 想要改变扫描路径,

  • @SpringBootApplication(scanBasePackages="com.wzw")

  • 或者@ComponentScan 指定扫描路径

  • 各种配置拥有默认值

  • 默认配置最终都是映射到某个类上,如:MultipartProperties

  • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象

  • 按需加载所有自动配置项

  • 非常多的starter

  • 引入了哪些场景这个场景的自动配置才会开启

  • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面

  • 如果不开启场景的配置,就不会加载(引入)

  • 例子:引入web场景,自动添加web场景相关的依赖

  • <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>


注解

@Configuration

该注解主要作用在代理对象上

proxyBeanMethods=true 开启代理

该类叫做配置类

@Configuration(proxyBeanMethods=true)
public class MyConfig {
    @Bean
    public People people(){
        Pet cat = new Pet("cat");
        People zs = new People("zs", 10, cat);
        return zs;
    }
    @Bean
    public Pet pet(){
        Pet cat = new Pet("cat");
        return cat;
    }
}

当proxyBeanMethods=true 的时候

代理对象调用方法的时候,每次SpringBoot都会在容器里这个组件存不存在,如果存在,直接返回,如果不存在,创建一个------保持单实例对象

  • 这句话中的容器指的是ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);容器对象

  • 查找哪个组件?这里的例子是通过代理对象创建bean,所以这个组件就是bean

  • 提一句 个人理解:通过注解注入的对象,比如Controller,Service,bean对象都是组件,或者说交给Spring管理的对象都是组件

当proxyBeanMethods=false 的时候

就不用保持单实例了,每次都创建新的

@Configuration(proxyBeanMethods=true)
<!-- ***************--!>
MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);
        Pet pet3 = bean.pet();
        Pet pet4 = bean.pet();
        System.out.println(pet3 == pet4);
<!--通过代理对象调用people()方法返回people对象-->
        People p3 = bean.people();
        People p4 = bean.people();
        System.out.println(p3 == p4);
<!-- ***************-->
com.wzw.boot.config.MyConfig$$EnhancerBySpringCGLIB$$6b6ab8ea@7ea4d397
true
true
@Configuration(proxyBeanMethods=false)
<!-- ***************--!>
MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);
        Pet pet3 = bean.pet();
        Pet pet4 = bean.pet();
        System.out.println(pet3 == pet4);
        People p3 = bean.people();
        People p4 = bean.people();
        System.out.println(p3 == p4);
<!-- ***************-->
com.wzw.boot.config.MyConfig@3003697
false
false

什么时候用True 什么时候用 false?

  • 当一个组件(A)依赖于另一个组件(B)的时候,就要用true,为什么?Full()模式

  • 因为proxyBeanMethods=true 的时候是单实例模式,用到B的时候,不会新建,如果每次都新建那A就会一直变

  • (新建的是什么暂时不知道,我觉得是对象,但是对象也是组件,所以我输出了前后容器的组件长度,发现并没有变化)

  • 当一个组件(A)不依赖于其他组件的时候,就要用false,为什么? Lite()模式

  • 因为不用每次都检测调用的该方法返回的东西存不存在,会加快SpringBoot的启动速度

  • 上面例子,返回的是People,Pet对象,所以每次返回的时候都要看一下该People,Pet对象存不存在


@Import

@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) 
public class MyConfig {
}

@Import({User.class, DBHelper.class})
//给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
//com.wzw.boot.bean.User

@Conditional

@Conditional及其派生注解,在@Conditional上按ctrl+H查看继承树

@Configuration(proxyBeanMethods=true)
public class MyConfig {
//    @Bean("cat")
    public Pet pet(){
        Pet cat = new Pet("cat");
        return cat;
    }

    @Bean("people")
    @ConditionalOnBean(name = "cat")
    public People people(){
        People zs = new People("zs", 10);
        zs.setPet(pet());
        return zs;
    }
}

当存在name=“cat”的组件时,创建name=“people”的组件

boolean people = run.containsBean("people");
System.out.println("people->"+people);
boolean pet = run.containsBean("cat");
System.out.println("cat->"+pet);
people->false
cat->false
@Configuration(proxyBeanMethods=true)
public class MyConfig {
    @Bean("cat")
    public Pet pet(){
        Pet cat = new Pet("cat");
        return cat;
    }

    @Bean("people")
    @ConditionalOnBean(name = "cat")
    public People people(){
        People zs = new People("zs", 10);
        zs.setPet(pet());
        return zs;
    }
}
people->true
cat->true

@ConfigurationProperties()

第一种

@ConfigurationProperties() + @EnableConfigurationProperties()

在配置类上标注:@EnableConfigurationProperties(value = Car.class)

作用:1.开启Car配置的绑定功能 2.把这个Car组件自动注册到容器中

@Configuration(proxyBeanMethods=true)
@EnableConfigurationProperties(value = Car.class)
public class MyConfig {
    @Bean("cat")
    public Pet pet(){
        Pet cat = new Pet("cat");
        return cat;
    }

    @Bean("people")
    @ConditionalOnBean(name = "cat")
    public People people(){
        People zs = new People("zs", 10);
        zs.setPet(pet());
        return zs;
    }
}

Car类

@ConfigurationProperties(prefix = "car")
public class Car {
    private String name;
    private Double price;

配置文件:application.properties

car.name=xxxx
car.price=100000

检测:

@RestController
public class HelloController {
    @Resource
    Car car;
    @RequestMapping("/car")
    public Car car(){
        return  car;
    }

}


第二种

@ConfigurationProperties() + @Component

@ConfigurationProperties(prefix = "car")
@Component
public class Car {
    private String name;
    private Double price;

这两种方式有什么区别呢?

如果需要引入第三方的类,他第三方没有标注@Component注解,那我们不能去改别人的源码,那就可以使用@EnableConfigurationProperties


自动配置原理

从Main方法出发

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {}

之前我们知道@SpringBootApplication的作用是告诉Spring这个是一个Springboot应用

进入注解@SpringBootApplication

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

发现@SpringBootApplication是一个复合注解


先看 @SpringBootConfiguration

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

发现 @SpringBootConfiguration也是一个复合注解,其中@Configuration代表这个注解 注解的类是一个配置类

例如:上面举的例子:

  • 这个就是一个配置类

  • @Configuration(proxyBeanMethods=true) @EnableConfigurationProperties(Car.class) public class MyConfig { @Bean("cat") public Pet pet(){ Pet cat = new Pet("cat"); return cat; } }

而这个 @SpringBootConfiguration 出现在Main方法上,说明这个类也是一个配置类,说明main方法是一个核心配置类


再看@ComponentScan

@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

包扫描


再看@EnableAutoConfiguration

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})

@AutoConfigurationPackage自动配置包

@Import({AutoConfigurationPackages.Registrar.class})

发现导入了一个AutoConfigurationPackages.Registrar.class类 注册

通过断点发现这个类

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    Registrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
    }

    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImports(metadata));
    }
}

利用register给容器中导入一系列组件,这个一系列组件在哪找?

其中:(new PackageImports(metadata)).getPackageNames()就是获取了该注解所在类的包名,

意思就是通过这个包名,导入这个包名下的组件(注册)

在@EnableAutoConfiguration中还有一个@import

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})

进入这个类看一下:

第一个方法:selectImports根据名字知道这个方法是选择导入(注册)哪些类,调用了一个本类中叫getAutoConfigurationEntry的方法

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

进入方法 getAutoConfigurationEntry 根据本类中的getCandidateConfigurations返回一个List集合,名字叫做configurations,能猜测到返回的是所有的组件

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
		//重点
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        
        configurations = this.removeDuplicates(configurations);
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        this.checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = this.getConfigurationClassFilter().filter(configurations);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }
}

第三步:getCandidateConfigurations 调用了一个SpringFactoriesLoader类中的loadFactoryNames,工厂加载器

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

第四步: 进入SpringFactoriesLoader,通过类加载器获取一个资源路径叫做:"META-INF/spring.factories"

  private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                
                //通过类加载器获取一个资源路径叫做:"META-INF/spring.factories"
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                
                
                
                MultiValueMap<String, String> result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

发现跟猜测的一样根据资源路径读取

默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件

spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories

其中Auto Configure写死了127个全限定类


注意!

虽然springboot会一次把所有场景的自动配置导入,但是并不会全部开启,因为有很多类中都加了条件配置@Conditional,只有需要的时候才会开启装配,这就是按需装配

帮助理解

@Import

  • @Import(要导入的组件)容器会自动注册这个组件

  • ImportSelector:返回需要导入的组件的全类名数组

  • ImportBeanDefinitionRegistrar:手动注册

  • AnnotationMetadata:当前类的注解信息

  • BeanDefinitionRegistry:BeanDefinition注册类,把所有需要添加到容器的bean,调用BeanDefinitionRegistry.registerBeanDefinition手动注册进来

自动配置dispatcherServlet的类

public class DispatcherServletAutoConfiguration {}

总结

在springboot的底层中总能发现一个现象:springboot会默认在底层配好所有的组件,但是如果用户自己配置了就以用户的优先

  • springboot会加载所有的自动配置类

  • 每个自动配置类按照条件进行生效(@Conditional),默认都会绑定配置文件指定的值(xxxProperties),而这个xxxProperties又是绑定了一个配置文件,有点抽象?

  • 生效的配置类就会给容器中装配组件

  • 只要容器中有这些组件,相当于这些功能就有了

  • 定制化配置

  • 用户直接自己@Bean替换底层的组件,自己写配置

  • @Bean public CharacterEncodingFilter filter(){ return null; }

  • 用户去看这个组件是获取的配置文件什么值就去修改。

  • 在配置文件中修改:application.properties

  • server.servlet.encoding.charset=UTF-8

可以设置debug=true,查看自动配置报告

debug=true

自定义器 XXXXXCustomizer

SpringBoot的run方法做了什么事情?

  1. 推断应用的类型是普通的项目还是web项目

  1. 查找并加载所有可用初始化器,设置到initializers属性中

  1. 找出所有的应用程序监听器,设置到listeners属性中

  1. 推断并设置main方法的定义类,找到运行的主类


简化开发

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
@Data
@NoArgsConstructor
//@AllArgsConstructor
@ToString
@EqualsAndHashCode
public class People {
    private String name;
    private int age;
    private Pet pet;

    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
  • @Data 自动生成get,set方法

  • @NoArgsConstructor 自动生成无参构造方法

  • @AllArgsConstructor 自动生成有参构造方法,需要定制的时候就自己写

  • @ToString 自动生成toString方法

  • @EqualsAndHashCode 自动生成equal和hashcode方法

@Slf4j
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String hello(){
        log.info("hello请求");
        return "helloWord";
    }

}
  • @Slf4j 自动导入日志


配置文件yaml

基本语法

  • key: value;kv之间有空格

  • 大小写敏感

  • 使用缩进表示层级关系

  • 缩进不允许使用tab,只允许空格

  • 缩进的空格数不重要,只要相同层级的元素左对齐即可

  • '#'表示注释

  • 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null

k: v
  • 对象:键值对的集合。map、hash、set、object

行内写法:  k: {k1:v1,k2:v2,k3:v3}
#或
k: 
	k1: v1
  k2: v2
  k3: v3
  • 数组:一组按次序排列的值。array、list、queue

行内写法:  k: [v1,v2,v3]
#或者
k:
 - v1
 - v2
 - v3

案例

@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix = "person")
@Component
public class Person {
    private String name;
    private Integer age;
    private List<String> hobbies;
    private Map<String,Pet> Pets;
    private Map<String,List<String>> address;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Pet {
    private String name;
}
person:
  name: 张三
  age: 20
#  hobbies:
#    - 篮球
#    - 足球
#    - 羽毛球
  hobbies: [篮球,足球,乒乓球]
#  Pets:
#    pet1:
#      name: 阿花
#    pet2:
#      name: 阿曹
  Pets: {pet3: {name: 啊哈},pet4: {name: 哈哈}}
  address:
    address1:
      - 北京
      - 上海
      - 深圳
    address2:
      - 广东
      - 广西

结果:

 Person(name=张三, age=20, hobbies=[篮球, 足球, 乒乓球], Pets={pet3=Pet(name=), pet4=Pet(name=)}, address={address1=[北京, 上海, 深圳], address2=[广东, 广西]})

yaml的配置值

yaml的配置值在哪看,如何获取?

yaml的值来自于一个自动配置类比如: xxxAutoConfiguration,作用是给容器中添加组件

而这个自动配置类会绑定一个properties比如:xxxProperties,作用是封装配置文件中相关属性

我们需要写的值就在这个绑定的properties当中

xxxConfigurer 这个东西的意思是功能拓展


静态资源

静态资源访问

只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources

访问 : 当前项目根路径/ + 静态资源名

优先级:resource > static > public

原理: 静态映射/**。

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

改变默认的静态资源路径

spring:
  mvc:
    static-path-pattern: /res/**

  resources:
    static-locations: [classpath:/haha/]

相当于说:我访问资源文件的请求:/res/xx,会去haha这个文件夹里面找,而不是去/static (or /public or /resources or /META-INF/resources这里找

WebJars

将资源打包成jar包,以依赖的形式引入:比如说jquery

那如何访问?这个资源也是在META-INF下的resources包所以是符合springboot的要求的,可以访问到

http://localhost:8888/webjars/jquery/3.5.1/jquery.js


静态资源访问前缀

默认无前缀

spring:
  mvc:
    static-path-pattern: /res/**

当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找


欢迎页

  • 静态资源路径下放一个index.html,会自动当作欢迎页

  • 但是如果配置了访问前缀,就不能自动识别index.html和favicon.ico(算是一个bug)

  • 底层写死了要满足static-path-pattern是/**才会转发到index.html页面

restFul风格

默认是禁用了

@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
    prefix = "spring.mvc.hiddenmethod.filter",
    name = {"enabled"},
    matchIfMissing = false
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter();
}

开启restFul

hidden-method:
  filter:
    enabled: true

form表单提交

public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = "_method";

static {
        ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
    }

兼容PUT,DELETE,PATCH提交方式

  • 如果是form表单提交的话,底层会先去看是不是post请求,然后看提交的value里面有没有_method,如果有且开启了restFul,那就将获取的值转成大写,最后new一个HttpMethodRequestWrapper

  • private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper

public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest

由此可见其实这个HttpMethodRequestWrapper也是一个HttpServletRequest,只不过它经过了包装,重写了getMethod()

创建这个HttpMethodRequestWrapper对象的时候需要传一个method进来,把这个“PUT”参数传进去了,最后过滤器链放行的时候传的是wrapper包装类,所以后面的方法使用request的时候,就能获取到“PUT”这个参数,通过getMethod()

private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
    private final String method;

    public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
        super(request);
        this.method = method;
    }

    public String getMethod() {
        return this.method;
    }
}
  • 如果开启了ResulFul,那么每一次form表单提交都会经过这个过滤器校验

总结

  • 所以!在进请求映射之前,先经过这个过滤器,看看是不是post请求

  • 如果是,获取_method,

  • 如果没有,说明是post请求,放行之后,映射器读取request的getMethod(),根据请求方式把请求分发出去,发到post请求的mapping

  • 如果有,经过requestWarpper这个包装类,重写request的getMethod(),把通过_method读取到的value,传进requestWarpper,传入过滤器,此后映射器读取request的getMethod(),现在读取到的就是 PUT , DELETE , PATCH ,根据请求方式分发请求-----> PutMapping DeleteMapping

  • 如果不是,直接放行,说明是get请求

  • 自此所有的请求都区分出来了

拓展

如果需要更改_method怎么办

@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
    prefix = "spring.mvc.hiddenmethod.filter",
    name = {"enabled"},
    matchIfMissing = false
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter();
}

@ConditionalOnMissingBean({HiddenHttpMethodFilter.class}) 当没有这个HiddenHttpMethodFilter类的时候,系统自动注册注解的这个类

所以:

@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
    HiddenHttpMethodFilter h = new HiddenHttpMethodFilter();
    h.setMethodParam("m");
    return h;
}

案例

spring:
  mvc:
    static-path-pattern: /res/**
    hidden-method:
      filter:
        enabled: true
  resources:
    add-mappings: true
    cache:
      period: 10000
@RestController
public class UserController {
//    @RequestMapping(value = "/user",method = RequestMethod.GET)
    @GetMapping("/user")
    public String getUser(){
        return "GET-张三";
    }

//    @RequestMapping(value = "/user",method = RequestMethod.POST)
    @PostMapping("/user")
    public String saveUser(){
        return "POST-张三";
    }


//    @RequestMapping(value = "/user",method = RequestMethod.PUT)
    @PutMapping("/user")
    public String putUser(){
        return "PUT-张三";
    }

//    @RequestMapping(value = "/user",method = RequestMethod.DELETE)
    @DeleteMapping("user")
    public String deleteUser(){
        return "DELETE-张三";
    }
}
<form method="post" action="http://localhost:8888/user">
    <input type="hidden" name="_method" value="delete">
    <input type="submit" value="delete提交">
</form>
<form method="get" action="http://localhost:8888/user">
    <input type="submit" value="get提交">
</form>
<form method="post" action="http://localhost:8888/user">
    <input type="hidden" name="_method" value="put">
    <input type="submit" value="put提交">
</form>
<form method="post" action="http://localhost:8888/user">
    <input type="submit" value="post提交">
</form>

DELETE-张三 GET-张三 PUT-张三 POST-张三


多环境切换

配置文件优先级

  1. file: ./config/

  1. file: ./

  1. classpath:config/

  1. classpath:/

环境切换:

方法1

application.yaml
spring:
  profiles:
    active: test
application-dev.yaml
application-test.yaml

方法2

在yaml中通过---代表不同的模块

dog:
  name: zs
spring:
  profiles:
    active: dev
---
server:
  port: 8083
dog:
  name: zsdev
spring:
  profiles: dev
---
server:
  port: 8082
dog:
  name: zstest
spring:
  profiles: test

理解:

需要定制化功能的时候,只需要实现接口,然后将它注册到容器里,springboot会根据需要,自己选择场景适用

国际化

i18n

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值