1.SpringBoot依赖管理
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
我们引入了很多包,但是我们都没有写名版本号,这是因为他依赖于父项目对包版本进行管理,我们进入父项目:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.2</version>
</parent>
<artifactId>spring-boot-starter-parent</artifactId>
<packaging>pom</packaging>
<name>spring-boot-starter-parent</name>
<description>Parent pom providing dependency and plugin management for applications built with Maven</description>
<properties>
<java.version>1.8</java.version>
<resource.delimiter>@</resource.delimiter>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
.............
会发现他还有个父项目spring-boot-dependencies,我们再进入:
<properties>
<activemq.version>5.16.0</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.84</appengine-sdk.version>
<artemis.version>2.15.0</artemis.version>
<aspectj.version>1.9.6</aspectj.version>
<assertj.version>3.18.1</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.0.3</awaitility.version>
<bitronix.version>2.1.4</bitronix.version>
<build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>
<byte-buddy.version>1.10.19</byte-buddy.version>
<caffeine.version>2.8.8</caffeine.version>
<cassandra-driver.version>4.9.0</cassandra-driver.version>
<classmate.version>1.5.1</classmate.version>
<commons-codec.version>1.15</commons-codec.version>
<commons-dbcp2.version>2.8.0</commons-dbcp2.version>
<commons-lang3.version>3.11</commons-lang3.version>
<commons-pool.version>1.6</commons-pool.version>
<commons-pool2.version>2.9.0</commons-pool2.version>
<couchbase-client.version>3.0.10</couchbase-client.version>
<db2-jdbc.version>11.5.5.0</db2-jdbc.version>
<dependency-management-plugin.version>1.0.11.RELEASE</dependency-management-plugin.version>
<derby.version>10.14.2.0</derby.version>
<dropwizard-metrics.version>4.1.17</dropwizard-metrics.version>
<ehcache.version>2.10.6</ehcache.version>
<ehcache3.version>3.9.0</ehcache3.version>
<elasticsearch.version>7.9.3</elasticsearch.version>
<embedded-mongo.version>2.2.0</embedded-mongo.version>
<flyway.version>7.1.1</flyway.version>
<freemarker.version>2.3.30</freemarker.version>
<git-commit-id-plugin.version>3.0.1</git-commit-id-plugin.version>
<glassfish-el.version>3.0.3</glassfish-el.version>
<glassfish-jaxb.version>2.3.3</glassfish-jaxb.version>
<groovy.version>2.5.14</groovy.version>
<gson.version>2.8.6</gson.version>
<h2.version>1.4.200</h2.version>
<hamcrest.version>2.2</hamcrest.version>
<hazelcast.version>4.0.3</hazelcast.version>
<hazelcast-hibernate5.version>2.1.1</hazelcast-hibernate5.version>
<hibernate.version>5.4.27.Final</hibernate.version>
<hibernate-validator.version>6.1.7.Final</hibernate-validator.version>
<hikaricp.version>3.4.5</hikaricp.version>
<hsqldb.version>2.5.1</hsqldb.version>
会发现此处管理了大量的包版本,是当前版本 SpringBoot 自动仲裁机制提供的版本,在SpringBoot父项目中,通过引用定义的版本号进行依赖包管理。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</exclusion>
</exclusions>
</dependency>
如果我们不想使用SpringBoot的默认包版本,我们也可以重写版本号(覆盖版本外部定义即可):
A.查看包版本命名
<properties>
<mysql.version>8.0.22</mysql.version>
</properties>
B.在自己项目的pom.xml中重写版本命名和版本号
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
[注]:当然了,如果我们导入的包并不在SpringBoot管理之内,还是必须要写版本号的。
2.SpringBoot场景启动器
SpringBoot将各种开发环境封装为一个个场景,以前我们在开发web的时候需要导入一大堆依赖包,而现在,只需要通过导入一个启动器(spring-boot-starter-*),SpringBoot就会自动帮我们把该场景的常规依赖包导入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
SpringBoot支持的所有场景启动器:https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
还有一些非SpringBoot官方的场景启动器: *-spring-boot-starter。
我们以Spring-boot-starter-web为例,看一下依赖结构:
很明显,通过一个starter,我们导入了很多的web开发常规依赖包。
3.SpringBoot自动配置
以web应用程序为例,一个spring-boot-starter-web需要配置很多东西:
A.Tomcat自动配置
1. Tomcat包的引入(在父项目中定义)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.4.2</version>
</dependency>
2. Tomcat的配置
B.SpringMVC自动配置
1.SpringMVC包的引入
2.自动配置SpringMVC的功能组件
以前我们需要在web.xml中配置DispatcherServlet、characterEncodingFilter(字符编码过滤器),在SpringBoot中,只要我们启动了web场景,就是自动配置的。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
注:run()方法返回的是Spring IoC容器。
3.默认扫包规则
以前我们需要在配置文件中定义包扫描器,而SpringBoot默认的扫包规则是主程序所在包及其子包内的所有组件@Component。
当然,SpringBoot提供了修改扫描路径的方式:
1.修改包路径
现在Controller不在主程序所在包,也不在那包的子包下,默认时扫描不到的。
2.启动Tomcat访问
目前容器里是没有该Controller对应的bean的,自然也就不能处理访问请求。
3.自定义扫描位置
@SpringBootApplication(scanBasePackages = "com")
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
@SpringBootApplication有一个属性--String[] scanBasePackages() default {};用于指定要自定义扫描的包,这个属性是个数组,可以赋多个值。
4.重启访问
[注]:之前在学习Spring时我们提到过,现在的Spring正提倡去配置化,我们使用@Configuration表明配置类;因此我们也可以在配置类中著名要扫描的包。我们测试一下让配置类注明扫描com.controller,再看看com.zzt.controller内的controller是否会被扫描到
package com.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "Hello,StringBoot";
}
}
package com.zzt.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AnotherController {
@RequestMapping("/hello2")
public String hello(){
return "Hello,StringBoot 第二个";
}
}
package com.zzt.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(value = "com.controller")
public class MyWebConfiguration {
}
注:为了使配置类生效,我们还是要将配置类放置在默认位置让主程序能够扫描到的位置。
可见,尽管我们使用配置类自定义了扫描路径,最终扫描的位置是默认位置和配置类配置路径的并集。
[注]:我们无法在主程序上增加@ComponentScan注解,这是因为主程序自带的注解 @SpringBootApplication 中带有 @ComponentScan ,会有冲突。
如果一定要在主程序上自定义包,还想使用 @ComponentScan 注解,我们需要将 @SpringBootApplication 拆分为三个小注解:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.controller")
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
需要注意的是,一旦我们使用这种方式,SpringBoot就只会扫描我们定义的位置了:
我们可以使用数组的方式赋予多个扫描路径:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(value = {"com.controller","com.zzt.controller"})
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
当然了,简单起见我们也可以定义扫描总包:(实际开发中不会推荐,因为微服务架构的存在,各个小场景之间应该避免耦合)
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(value = "com")
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
4.各项配置默认值
SpringBoot还为我们配置好了一些项目相关的默认值:如Tomcat的端口默认值
server.port=8081
我们在配置文件中,对着属性 server.port 操作 ctrl + 左键,会进入到相应类的set方法
public void setPort(Integer port) {
this.port = port;
}
prefix就是在配置文件中的属性值前缀--server,用yaml就是对应的yaml对象的名称。
server:
port: 8081
这些属性的配置,最后会赋值到容器创建的对应类的实体bean上。
5.SpringBoot自动配置功能
当我们引入一个场景--starter后,它对应的自动配置功能就会开启;以web为例:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
它依赖于另一个包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.4.2</version>
<scope>compile</scope>
</dependency>
进去之后发现,有一个包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.4.2</version>
<scope>compile</scope>
</dependency>
这个包就负责为我们完成自动配置。
这个包内部集成了很多的自动配置方案,使用 @ConditionalOnXXX 来配置该类是否生效。