Spring-Boot入门

前言

学完Spring和SpringMVC,直接就开始学这个框架,不多BB。

传统的SSM框架的劣势是什么?其实也不是很劣势啦,只是配置有那么一内内繁琐(其实我个人觉得真的只是一点点繁琐而已),而springboot则是大大简化了这些繁琐。

Quick Start

直接用idea生成一个最最普通的maven结构,然后上 https://start.spring.io/ 获取到依赖关系,然后复制粘贴进自己的项目,稍等一会依赖就搞定了。当然你也可以使用idea自带的工具来生成,也很方便。

然后只需要最最简单的代码就可以搞定了:

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
@RestController
public class Demo {
public static void main(String[] args) {
SpringApplication.run(Demo.class, args);
}

@GetMapping("/hello")
@ResponseBody
public String hello() {
return "Hello World";
}
}

直接运行就可以跑起来了,甚至连Tomcat都不需要自己配。

最后部署的话,只需要生成jar包即可,到目标服务器中运行这个jar包就完成啦。果然是轻松简单的很。

稍微探究一下

依赖关系

1
2
3
4
5
6
7
8
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

再向上是spring-boot-dependencies,在这里就有全部的依赖管理了
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

仔细观察会发现上面两个都是spring-boot-starter,称为场景启动器,里面有web需要的东西。String boot将这些常用的功能场景都抽取了出来,只需要导入starter,那么项目中就会都存在这些依赖了。

主程序

@SpringBootApplication标志着这个类是总配置类。打开它的源码查看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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}
)}
)

可以看到其实这个注解非常的麻烦,慢慢分析:

  • 首先是@SpringBootConfiguration,这个从名字就知道这个注解是用来注解配置类的,这个注解在深入,其实就是@Configuration这个注解。需要注意配置类也是容器中的一个组件。
  • @EnableAutoConfiguration开启自动配置功能,把我们以前需要配置的东西,让spring boot帮我们配置。
    • 其底部又包括@AutoConfigurationPackage自动配置包,底层用的是@Import,给容器中导入一个组件。debug追踪一下可以发现,其实就是导入主配置类所在的包,所以记得要把你要导入的组件(controller、service等)放到配置类的同级或者对应的子包下。
    • 还有一个@Import({AutoConfigurationImportSelector.class}),从字面意思就可以知道,是导入哪些组件的选择器。跟踪源代码可以发现,是需要导入组件的全类名。会给容器中导入非常非常多的自动配置类,而这些自动配置类就能够给容器中导入该场景需要的所有组件,并配置好这些组件。其本质上就是从META-INF/spring.factories中获取了EnableAutoConfiguration的值。image-20200410002032401

好了初步探究到这里就差不多了。

配置文件

之所以我们需要研究怎么来写配置文件,一来是为了让我们能够更好的使用配置文件,二来也是希望能够通过自己使用配置文件,来搞清楚springboot是如何自动化进行配置的。

虽然springboot简化了操作,但是必要的配置文件还是并不可少的。springboot使用这两个配置文件:application.ymlapplication.properties,这个配置文件就是用来修改springboot的默认值。properties这个文件应该非常熟悉了,下面简单介绍一下yaml这种文件格式。

YAML

yaml的核心是数据为中心,所以配置就是键值对,用冒号分割,冒号后面必须加一个空格。缩进相同的代表属于同一级。

字面量

  • 数字,直接写
  • 字符串,直接写;如果用双引号引起来,特殊字符就是特殊字符,例如"\n"就是换行;如果单引号引起来,那么里面是什么东西,它会原封不动给你输出出来。

对象

1
2
3
4
5
person: 
name: 张三
age: 13
---
person: {name: 张三,age: 13}

数组

1
2
3
4
5
6
animal: 
- dog
- cat
- pig
---
animal: [dog,cat,pig]

通过实际演示使用一下就行了。首先定义一个Person类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Person {

private String name;
private Integer age;
private Date birthday;
private Boolean isMan;

private Map<String,Object> maps;
private List<Object> lists;
// dog只有名字和年龄
private Dog dog;

// getter setter和toString()省略
}

然后对应的yaml文件即为:

1
2
3
4
5
6
7
8
9
10
person:
name: 张三
age: 14
birthday: 1990/01/01
maps: {a: 1,b: 3,c: lala}
lists: [1,2,3]
dog:
name: 旺旺
age: 1
man: false

上面请特别特别注意一下最后一行,是man而不是isMan哦。因为实际是根据setter的函数名字来决定的,而idea最后设置的setter是setMan,所以是man。(如果还是有疑问,可以妙用idea的提示功能,万无一失)

最后回到类上面,声明一下即可。

1
2
@Component
@ConfigurationProperties(prefix = "person")

注意还需要加入一个依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

然后就大功告成了。相比于@Value一个一个指定,显然这个一次性导入比较方便。

Properties

1
2
3
4
5
6
7
8
9
10
person.age=18
person.birthday=1993/08/08
person.dog.name=旺旺
person.dog.age=1
person.lists=1,2,3
person.man=false
person.name=张三
person.maps.k1=111
person.maps.k2=你好
person.maps.k3=abc

如果出现中文乱码的情况,记得把idea的file encoding有个Transparent native-to-ascii conversion,勾选上即可。这个的意思就是把你输入的所有字符转换成Unicode序列码保存,即变成了下面这个样子:

1
2
3
4
5
6
7
8
9
10
person.age=18
person.birthday=1993/08/08
person.dog.name=\u65FA\u65FA
person.dog.age=1
person.lists=1,2,3
person.man=false
person.name=\u5F20\u4E09
person.maps.k1=111
person.maps.k2=\u4F60\u597D
person.maps.k3=abc

最后当Java去读取properties时,也会将自动将\uxxx的Unicode转成对应的char,不会出现乱码问题。

注入值校验

在类上面使用@Validated,则可以启用注入值校验,它自带了一些校验规则,可以直接使用。

ImportResource

由于springboot里面没有Spring的配置文件,所以需要这个注解来导入spring的配置文件并且生效。当然springboot本身是推荐使用全注解的方式来实现的。

多配置

我们可能针对不同的环境(开发、测试、生产等)或者根据不同的语言(中文、英文等)需要实现不同的配置文件,那么可以通过类似application-{profile}.properties来实现;如果用yml的话,可以用多文档块的方式直接配置。

在使用的时候,当然需要直接指定需要激活哪个配置文件:

  • 直接在配置文件中指定:spring.profiles.active=dev
  • 在启动的时候在命令行中指定:java -jar …jav –spring.profiles.active=dev;
  • 在虚拟机参数中加入:-Dspring.profiles.active=dev

配置文件的加载顺序

由于springboot是约定大于配置,所以默认会从下面的位置加载文件名为application.properties或者的application.yml文件:

  • file:./config/

  • file:./

  • classpath:/config/

  • classpath:/

优先级从高到低,需要注意的是,高优先级会覆盖掉低优先级的配置,且不会因为在某个位置进行了而短路掉其它的配置,利用这个特性我们就可以实现配置的互补了。

当然我们还可以通过在启动项目的时候额外指定配置文件,这样就能够和之前的配置形成互补了。

外部配置

请参考:参考官方文档

自动配置的原理

首先我们在主配置类里面写了@SpringBootApplication,而这个里面即开启了自动配置的功能。然后就能通过配置文件来获得需要开启哪些xxAutoConfiguration类。下面以HttpEncodingAutoConfiguration这个类为案例来分析下自动配置的原理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Configuration(
proxyBeanMethods = false
) // 说明这是一个配置类
@EnableConfigurationProperties({HttpProperties.class}) //把配置文件和这个类组合起来
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({CharacterEncodingFilter.class}) // 判断当前的类是否是web应用,底层用的是@Conditional注解
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
) // 判断是不是有这个配置,不存在也同样生效。
public class HttpEncodingAutoConfiguration {

// 可以发现这个已经和配置文件进行了映射,所以已经加入了容器
private final Encoding properties;

public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}

@Bean
@ConditionalOnMissingBean // 容器中如果没有这个bean就加,有就不加了
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;
}

@Bean
public HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer(this.properties);
}

// 省略一个方法。
}

首先要做的是,判断下目前这个配置类它是否生效,生效就会给容器中加东西,加入的东西都是通过配置文件来获取的。

所以我们不难发现,springboot启动的时候,它其实是加载了各种各样的自动配置类,然后这些自动配置类会替我们加载组件,如果我们需要的组件已经被添加了,那直接拿来用就可以了。自动配置类增加组件的方法就是从properties中获取属性,而这些属性恰恰是配置文件里指定的。

日志

日志框架

框架主要分成两种:抽象类和实现类,抽象类包含了JCL、SLF4j以及Jboss-logging,而实现则有Log4j、Log4j2、Logback以及Java自带的JUL

springboot选择的是SLF4j+Logback,而spring使用的则是JCL。

SLF4j的使用

只需要导入抽象类和实现类的包,下面是示例代码:

1
2
3
4
5
6
7
8
9
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}

特别的简单。这里需要注意的是,由于日志框架参差不齐,有些项目用的是log4j,有的用的jul,我们就需要对它们进行转换,思路其实也特别简单,只需要一个转化的jar包,这个jar包会让那些使用log4j等jar包的以为自己用的就是原来的日志包,但是实际上用的却是logback包。

所以如果我们要同时使用各类框架,而这些框架的实现类各不相同,那么我们只需要:把原来的框架使用的jar排除,然后用中间的包来替换就可以了。

image-20200418133514067

使用

1
2
3
4
5
6
7
8
9
@Test
void contextLoads() {
Logger logger = LoggerFactory.getLogger(getClass());
logger.trace("这是trace日志...");
logger.debug("这是debug日志...");
logger.info("这是info日志...");
logger.warn("这是warn日志...");
logger.error("这是error日志...");
}

你会发现默认情况下,trace和debug是不会输出的,我们需要到application.properties文件中去配置,配置以logging开头。具体可以修改输出内容的格式、级别以及输出到哪里等。当然你也可以在类路径下面放上每个框架的对应配置文件,下面是springboot中各种对应的日志配置文件

Logging System Customization
Logback logback-spring.xml, logback-spring.groovy, logback.xml or logback.groovy
Log4j2 log4j2-spring.xml or log4j2.xml
JDK (Java Util Logging) logging.properties

如果用的是带-spring这个后缀的,则不是直接被对应的日志框架识别,而是先被springboot识别,这样就可以针对不同的profile来实现针对不同环境拥有不同的日志记录功能:比如我在开发的情况下需要事无巨细的记录下各种日志,而到了生产中只需要error和warn就可以了。

切换

每个日志框架其实都对应了自己的核心包,以及别的框架使用自己的包,只需要根据自己的需要对应的添加排除信息即可。

Web

SpringBoot创建web应用还是非常方便的,我们只需要在初始化的时候选好自己需要的模块,然后自己再加那么一内内的配置,然后就可以把重心放到业务代码上面了。

静态资源的映射

我们的一些js文件、css文件、图标等都属于静态文件,那springboot是如何加载它们的呢?利用的是WebMvcAutoConfiguration这个类。

访问jar包内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
}

String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
}

}
}

可以看到,在/webjars下面的所有资源,它都会去classpath:/META-INF/resources/webjars目录下找资源,而我们可以通过引入相对应的jar包,里面的资源就可以直接被我们所使用了,这些jar包可以通过这个网站查找。

比如我希望我的项目中用到jQuery,那么可以直接到该网站找到它,并且引入到项目里面:

1
2
3
4
5
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.0</version>
</dependency>

然后可以通过包的结构分析发现:

image-20200418160114470

这个和上面的类路径下面的路径完全一致,直接拿来用就可以了,现在只需要访问:http://localhost:8080/webjars/jquery/3.5.0/jquery.js 就可以看到结果了。

访问当前项目的资源

如果要访问当前项目的任何资源,那么会去这五个路径下寻找:

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

模板引擎

我们熟悉的jsp就是一个模板引擎,通过特定的占位符替换内容,组装出我们需要的html页面。

而在SpringBoot中,强力推荐的Thymeleaf,因为它的语法更简单,功能更强大;如果你对jsp比较熟悉,上手它还是比较容易的,这里就不多做介绍了。

SpringMVC自动配置

springboot本身自己自动配置好了springmvc,下面是一些详细信息:

  • 注册了ContentNegotiatingViewResolverBeanNameViewResolver这两个组件。
  • 解决了静态资源的位置
  • 解决了静态首页的访问
  • 配置了页面的图标
  • 自动注册了Converter, GenericConverter, Formatter
    • Converter:把String转成对应的类型
    • Formatter:格式化内容,比如日期文本转成Date
  • 等等等等

扩展使用SpringMVC

自动配置肯定是不尽如人意的,就需要我们手动来解决。我们可以创建一个类来继承WebMvcConfigurerAdapter,然后自己编写相对应的方法,就可以完成我们需要的“定制版”的springMVC了。

1
2
3
4
5
6
7
8
9
// 扩展SpringMVC的功能
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("success");
}
}

全面接管

我不需要springboot帮我做任何springMVC的功能,我希望我自己来,此时只需要在上面那个类上加上@EnableWebMvc,springboot配置的springmvc就会失效,此时你就需要自己来做组件了。原理就是springboot在自动配置springmvc的时候会判断下组件里有没有WebMvcConfigurationSupport这个类,有了就不启用;而@EnableWebMvc就是给容器中加入这个类(确切的说是它的子类)。

修改springboot的默认配置

虽然前面提到了我们可以全面接管springMVC,但是其实相辅相成才是最好的。此时我们就需要来修改它。

springboot在自动配置的时候,会先确认下有没有用户配置的,如果有就不自动配置了,没有才会自动配置。当然如果有些组件可以存在多份,那么springboot就会把自动配置的,和我们配置的组合起来用。

同时springboot也提供了很好的扩展性,比如xxxConfigurer帮我们进行扩展配置,而xxxCustomizer帮助我们进行定制配置。

简单的CRUD

默认访问首页,我们只需要把首页放在上面说的几个静态文件夹里面即可。PS:如果确定没问题,但是又频繁404,不妨重启下(或者maven和项目都清理下)。

国际化

我们首先需要编写好国际化的配置文件,springboot为我们自动配置了国际化资源文件,叫MessageSourceAutoConfiguration,然后使用特定的语法,就可以针对浏览器头部发送的Accept-Language信息来判断了。

针对国际化,我们有一个对象是Locale,而springboot里面有一个LocaleResolver来根据请求头的信息来使用对应的语言,所以我们只需要自己实现这个接口,并将它加入到容器中,我们就可以自己根据请求头中的内容来进行国际化内容的输出了。

数据库集成部分

Druid配置

首先是导入jdbc和Druid的jar包,然后只需要在application.properties里面写上:

1
2
3
4
5
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.url=jdbc:mysql://localhost/TEST
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

然后再配置一下即可使用:

1
2
3
4
5
6
7
8
@Configuration
public class DruidConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
}

这里特别注意一下,是spring.datasource.username而不是spring.datasource.data-username

Mybatis