目录
一、前言
二、原理分析
1、spring.factories的扫描
2、配置信息定义
3、pom依赖
4、tomcat的自动配置
5、Conditional注解
6、tomcat启动
7、结论分析
三、自定义组件
1、自定义spring.factories
2、自定义MyAutoConfiguration
3、自定义属性信息
4、自定义工厂类
5、创建触发条件
6、启动测试
7、控制台信息
一、前言
在使用springboot时,有一个大的原则:约定大于配置。
springboot内置了很多常用的组件,比如tomcat、jdbc、redis等,接下来从根本上分析自动配置的原理及集成过程。
为更好的理解,先提出这几个疑问,也是我们后续分析的思路:
1、tomcat作为默认的服务容器,springboot是如何自动找到的tomcat;
2、jdbc、redis都需要配置链接信息,链接错误服务启动会报错,但没有配置则正常启动
3、我们如何按照自动配置的原则,实现自定义的组件进行装配。
二、原理分析
1、spring.factories的扫描
在《深入理解为什么nacos配置信息要放到bootstrap.properties「源码分析/图文详解」》一文中有详细分析:项目启动的时候,先加载系统信息,所有pom依赖jar包中的META-INF/spring.factories都会在这时进行扫描并缓存。
项目启动的debug调试信息如下,可以看出扫描spring.factories的优先级非常靠前:
2、配置信息定义
springboot自动配置的信息,在系统所示的jar包中的spring.factoires中都进行的定义。
部分spring.factoryes内容如下:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...省略...
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
这里配置了es、redis、solr、mongodb等,其中也包含Tomcat的嵌入配置。
3、pom依赖
当springboot应用作为web项目进行启动时,需要添加pom依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
其中就包含了tomcat的依赖信息,依赖关系如下:
这里就开始体现springboot约定大于配置的思想,指定tomcat作为默认服务容器。
4、tomcat的自动配置
在spring.factories中web容器的初始化在ServletWebServerFactoryAutoConfiguration进行。
源码:
源码第一句注释:
Auto-configuration for servlet web servers.
从这里可以看出,这是自动配置web服务的地方,同时也出现了Tomcat 的信息。
5、Conditional注解
关于@Conditional注解:
@Conditional注解是一个条件装配注解,主要用于限制@Bean注解在什么时候才生效,以指定的条件形式控制bean的创建
@Conditional可以自定义条件进行装配或者不装配
@Conditional本身是一个父注解,派生出很多子注解:
@ConditionalOnClass和@ConditionalOnWebApplication就是其中的子项!
ServletWebServerFactoryAutoConfiguration上使用的注解较多,其它注解作用分别如下:
@EnableConfigurationProperties:
使配置文件中配置的信息生效,如端口配置 server.port=8081
@Import
通过快速导入的方式实现把实例加入spring的IOC容器中
重点是这里
@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
意思是当org.apache.catalina.startup.Tomcat这个class类存在时,注入bean信息。
从pom依赖关系中,已经倒入了tomcat相关依赖,所以这个@Bean自然会被注入到spring容器中。
6、tomcat启动
从Tomcat创建到启动的代码跟踪流程如下:
TomcatServletWebServerFactory#getWebServer源码:
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
TomcatWebServer#initialize()源码:
TomcatWebServer#initialize();
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
// Start the server to trigger initialization listeners
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
注意是这里:
// Start the server to trigger initialization listeners
this.tomcat.start();
然后tomcat已经被内嵌到springboot中并且启动。
7、结论分析
到这里再重新梳理下流程:
- springboot扫描spring.factories中的配置信息;
- 根据@Conditional条件选择性注入;
- 加载属性信息,创建组件对象,并启动或应用
三、自定义组件
了解了自动装备的原理,我们自己实现一个。
1、自定义spring.factories
创建自定义的spring.factories文件
# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zhufeng.demo.config.MyAutoConfiguration
2、自定义MyAutoConfiguration
package com.zhufeng.demo.config;
import com.zhufeng.demo.factory.HelloWorldFactory;
import com.zhufeng.demo.prop.ZhufengProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName: MyAutoConfiguration
* @Description TODO
* @author 月夜烛峰
* @date 2022/9/8 15:32
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ZhufengProperties.class)
public class MyAutoConfiguration {
public MyAutoConfiguration(){
System.out.println("MyAutoConfiguration 已经自动装配。。。");
}
@Bean
@ConditionalOnClass(name = "com.zhufeng.demo.HelloWorld")
public HelloWorldFactory showHelloWorld(
ZhufengProperties zhufengProperties) {
return new HelloWorldFactory(zhufengProperties);
}
}
3、自定义属性信息
package com.zhufeng.demo.prop;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @ClassName: ZhufengProperties
* @Description 自定义配置文件
* @author 月夜烛峰
* @date 2022/9/8 15:25
*/
@ConfigurationProperties(prefix = "com.zhufeng")
public class ZhufengProperties {
private final ZhufengProperties.User user = new ZhufengProperties.User();
private final ZhufengProperties.Msg msg = new ZhufengProperties.Msg();
public User getUser() {
return user;
}
public Msg getMsg() {
return msg;
}
public static class User{
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
}
public static class Msg {
private String type;
private long length;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
@Override
public String toString() {
return "Msg{" +
"type='" + type + '\'' +
", length=" + length +
'}';
}
}
}
在application.properties中添加:
com.zhufeng.user.id=user1001
com.zhufeng.user.name=月夜烛峰
com.zhufeng.msg.type=json
com.zhufeng.msg.length=1024
4、自定义工厂类
package com.zhufeng.demo.factory;
import com.zhufeng.demo.prop.ZhufengProperties;
/**
* @ClassName: MyFactory
* @Description 工厂类
* @author 月夜烛峰
* @date 2022/9/8 15:43
*/
public class HelloWorldFactory {
private final ZhufengProperties zhufengProperties;
public HelloWorldFactory(ZhufengProperties zhufengProperties){
this.zhufengProperties = zhufengProperties;
}
public void showUserInfo(){
System.out.println("用户信息:"+zhufengProperties.getUser());
}
public void showMsgInfo(){
System.out.println("消息信息:"+zhufengProperties.getMsg());
}
}
5、创建触发条件
package com.zhufeng.demo;
/**
* @ClassName: HelloWorld
* @Description 用于触发自动装配
* @author 月夜烛峰
* @date 2022/9/8 15:51
*/
public class HelloWorld {
}
6、启动测试
package com.zhufeng.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @ClassName: MainTest
* @Description 启动测试
* @author 月夜烛峰
* @date 2022/9/7 16:15
*/
@SpringBootApplication
public class MainTest {
public static void main(String[] args) {
SpringApplication.run(MainTest.class);
}
}
创建测试Controller
package com.zhufeng.demo.controller;
import com.zhufeng.demo.factory.HelloWorldFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @ClassName: UserController
* @Description TODO
* @author 月夜烛峰
* @date 2022/9/7 16:12
*/
@RestController
public class UserController {
@Resource
HelloWorldFactory showHelloWorld;
@RequestMapping("view")
public String view(){
showHelloWorld.showUserInfo();
showHelloWorld.showMsgInfo();
return "success";
}
}
启动项目运行。
7、控制台信息
浏览器访问http://127.0.0.1:8080/view
控制台打印:
MyAutoConfiguration 已经自动装配。。。
showUserInfo:com.zhufeng.demo.service.impl.UserServiceImpl@63fd4873
......
用户信息:User{id='user1001', name='月夜烛峰'}
消息信息:Msg{type='json', length=1024}
说明我们自定义的功能已经被自动装配,再把HelloWorld.java删除,使之不满足以下条件:
@ConditionalOnClass(name = "com.zhufeng.demo.HelloWorld")
***************************
APPLICATION FAILED TO START
***************************
Description:
A component required a bean of type 'com.zhufeng.demo.factory.HelloWorldFactory' that could not be found.
The following candidates were found but could not be injected:
- Bean method 'showHelloWorld' in 'MyAutoConfiguration' not loaded because @ConditionalOnClass did not find required class 'com.zhufeng.demo.HelloWorld'
Action:
Consider revisiting the entries above or defining a bean of type 'com.zhufeng.demo.factory.HelloWorldFactory' in your configuration.
项目启动报错,自动装备失败。