Spring Boot运行原理
在前面章节中,我们见识了Spring Boot为我们做的自动配置,为了让大家快速领略Spring Boot的魅力,我们将在本节先通过分析Spring Boot的运行原理后,根据已掌握的知识自定义一个starter pom。
我们可以借助这一特性来理解Spring Boot运行自动配置的原理,并实现自己的自动配置。Spring Boot关于自动配置的源码在spring-boot-autoconfigure-1.3.0.x.jar内,主要包含了如图所示的配置。
若想知道Spring Boot为我们做了哪些自动配置,可以查看这里的源码。
(略)
可以通过下面三种方式查看当前项目中已启用和未启用的自动配置的报告。
(1)运行jar时增加--debug参数:java -jar xx.jar --debug
(2)在application.properties中设置属性:debug=true
运作原理
关于Spring Boot的运作原理,我们还是回归到@SpringBootApplication注解上来,这个注解是一个组合注解,它的核心功能是由@EnableAutoConfiguration注解提供的。
下面我们来看下@EnableAutoConfiguration注解的源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ EnableAutoConfigurationImportSelector.class,
AutoConfigurationPackages.Registrar.class })
public @interface EnableAutoConfiguration {
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
这里的关键功能是@Import注解导入的配置功能,EnableAutoConfigurationImportSelector使用SpringFactoriesLoader.loadFactoryNames方法来扫描具有META-INF/spring.factories文件的jar包,而我们的spring-boot-autoconfigure-1.3.0.x.jar里就有一个spring.factories文件,此文件中声明了有哪些自动配置,如图所示。
核心注解
打开上面任意一个AutoConfiguration文件,一般都有下面的条件注解,在spring-boot-autoconfigure-1.3.0.x.jar的org.springframwork.boot.autoconfigure.condition包下,条件注解如下。
@ConditionalOnBean:当容器里有指定的Bean的条件下。
@ConditionalOnClass:当类路径下有指定的类的条件下。
@ConditionalOnExpression:基于SpEL表达式作为判断条件。
@ConditionalOnJava:基于JVM版本作为判断条件。
@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置。
@ConditionalOnMissingBean:当容器里没有指定Bean的情况下。
@ConditionalOnMissingClass:当类路径下没有指定的类的条件下。
@ConditionalOnNotWebApplication:当前项目不是Web项目的条件下。
@ConditionalOnProperty:指定的属性是否有指定的值。
@ConditionalOnResource:类路径是否有指定的值。
@ConditionalOnSingleCandidate:当指定Bean在容器中只有一个,或者虽然有多个但是指定首选的Bean。
@ConditionalOnWebApplication:当前项目是Web项目的条件下。
这些注解都是组合了@Conditional元注解,只是使用了不同的条件(Condition)
实战spring-boot-starter-hello
看完前面几节的讲述,是不是觉得Spring Boot的自动配置其实很简单,是不是跃跃欲试地想让自己的项目也具备这样的功能。其实我们完全可以仿照上面http编码配置的例子自己写一个自动配置,不过这里再做的彻底点,我们自己写一个starter pom,这意味着我们不仅有自动配置的功能,而且具有更通用的耦合度更低的配置。
为了方便理解,在这里举一个简单的实战例子,包含当某个类存在的时候,自动配置这个类的Bean,并可将Bean的属性在application.properties中配置。
(1)新建starter的Maven项目,
在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.shrimpking</groupId>
<artifactId>spring-boot-starter-hello</artifactId>
<version>1.0</version>
<name>spring-boot-starter-hello</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>1.3.0.M1</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
属性配置,代码如下:
package com.shrimpking.spring_boot_starter_hello;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Created by IntelliJ IDEA.
*
* @Author : Shrimpking
* @create 2024/1/7 18:58
*/
@ConfigurationProperties(prefix = "hello")
public class HelloServiceProperties
{
private static final String MSG = "world";
private String msg = MSG;
public String getMsg()
{
return msg;
}
public void setMsg(String msg)
{
this.msg = msg;
}
}
这里配置与之前的代码是一样的,是类型安全的属性获取。在application.properties中通过hello.msg=来设置,若不设置,默认为hello.msg=world。
判断依据类,代码如下:
package com.shrimpking.spring_boot_starter_hello;
/**
* Created by IntelliJ IDEA.
*
* @Author : Shrimpking
* @create 2024/1/7 19:01
*/
public class HelloService
{
private String msg;
public String sayHello(){
return "Hello " + msg;
}
public String getMsg()
{
return msg;
}
public void setMsg(String msg)
{
this.msg = msg;
}
}
自动配置类,代码如下:
package com.shrimpking.spring_boot_starter_hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by IntelliJ IDEA.
*
* @Author : Shrimpking
* @create 2024/1/7 19:03
*/
@Configuration
@EnableConfigurationProperties(HelloServiceProperties.class)
@ConditionalOnClass(HelloService.class)
@ConditionalOnProperty(prefix = "hello",value = "enabled",matchIfMissing = true)
public class HelloServiceAutoConfiguration
{
@Autowired
private HelloServiceProperties helloServiceProperties;
@Bean
@ConditionalOnMissingBean(HelloService.class)
public HelloService helloService(){
HelloService helloService = new HelloService();
helloService.setMsg(helloServiceProperties.getMsg());
return helloService;
}
}
根据HelloServiceProperties提供的参数,并通过@ConditionalOnClass判断HelloService这个类在类路径中是否存在,且当容器中没有这个Bean的情况下自动配置这个Bean。
注册配置。
若想自动配置生效,需要注册自动配置类。在src/main/resources下新建META-INF/spring.factories,结构如图
在spring.factories中填写如下内容注册:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.shrimpking.spring_boot_starter_hello.HelloServiceAutoConfiguration
若有多个自动配置,则用“,”隔开,此处“\”是为了换行后仍然能读到属性。
另外,若在此例新建的项目中无src/main/resources文件夹,需自行创建
依次执行上述命令,将jar安装至maven本地库中。
使用starter。
新建Spring Boot项目,并将我们的starter作为依赖
在pom.xml中添加spring-boot-starter-hello的依赖,代码如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.shrimpking</groupId>
<artifactId>springboot82</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot82</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.shrimpking</groupId>
<artifactId>spring-boot-starter-hello</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.0</version>
</plugin>
</plugins>
</build>
</project>
简单的运行类代码如下:
package com.shrimpking.test;
import com.shrimpking.spring_boot_starter_hello.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by IntelliJ IDEA.
*
* @Author : Shrimpking
* @create 2024/1/7 20:57
*/
@RestController
public class TestController
{
@Autowired
private HelloService helloService;
@RequestMapping(value = "/index",method = RequestMethod.GET)
public String index(){
return helloService.sayHello();
}
}
这时在application.properties中配置msg的内容:
server.port=8089
hello.msg = this is my spring boot starter test
以上是全部过程,希望对你有帮助。