自动装配的原理
@EnableAutoConfiguration注解
我们先关注下这个注解的内容,主要由三个核心的注解:@SpringBootConfiguration:内置@Configurantion注解,表示是一个配置类;@EnableAutoConfiguration:自动装配;@ComponentScan:扫描路径以及排除和包含的类。
@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}
)}
)
public @interface SpringBootApplication {
...
}
接下来重点关注@EnableAutoConfiguration注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
spring boot在启动的过程中,底层无非也是在创建spring容器,故也就是会启动spring。我们知道spring在启动过程中会解析配置类,而当启动springboot时,会将被@SpringBootApplication注解的类当做配置类进行解析,那么就会解析@EnableAutoConfiguration注解,这个注解还注解了@Import注解。点开AutoConfigurationImportSelector.class时,发现他是实现了DeferredImportSelector接口,DeferredImportSelector接口还有一个分组接口以及默认方法getImportGroup(),AutoConfigurationImportSelector类实现了getImportGroup()方法,并指定一个Group类返回,然后会先后调用AutoConfigurationGroup#process()和selectImports()方法,process()方法会去读取引入的所有包中的spring.factories包配置的自动装配类。那么为什么会选择实现这个接口呢?
1、spring解析配置类时,会判断如果被导入的类实现的是DeferredImportSelector接口,会被延期加载,也就是等其他需要解析的类都加载为BeanDefinition被存入到beanDefinitionMap中后,再去执行AutoConfigurationGroup#process()和selectImports()方法,并将返回的数组解析为BeanDefinition并注册到spring容器中,当然这里会有判断,如果已经类已经解析过了,则会舍弃,也就是保证程序员配置的bean优先加载。
2、调用AutoConfigurationGroup#selectImports()方法对最终以EnableAutoConfiguration.class为key解析的类进行排除和排序。
自定义starter
首先创建父maven项目:spring-boot-starter-rick
接下来创建两个子项目:rick-spring-boot-starter,rick-spring-boot-starter-autoconfigure
其中rick-spring-boot-starter是在resources目录下创建了spring.factories文件,并指定自动装配的类(HelloServiceAutoConfiguration),并且pom.xml引入了rick-spring-boot-starter-autoconfigure项目
而rick-spring-boot-starter-autoconfigure项目分别创建了三个类:HelloProperties,HelloService,HelloServiceAutoConfiguration。
HelloProperties:中注解了@ConfigurationProperties(prefix = "rick.hello")表示这个类属性与配置文件中前缀为rick.hello的属性进行一一对应。然后注册为bean
@ConfigurationProperties(prefix = "rick.hello")
public class HelloProperties {
private String prefix;
private String suffix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
HelloServiceAutoConfiguration:springboot启动会解析该类并将HelloService绑定HelloProperties然后注册为bean。
@Configuration
@ConditionalOnWebApplication //web应用生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
@Autowired
HelloProperties helloProperties;
@Bean
public HelloService helloService(){
HelloService service = new HelloService();
service.setHelloProperties(helloProperties);
return service;
}
}
HelloService:最终会被程序员所调用。
public class HelloService {
HelloProperties helloProperties;
public HelloService() {
}
public HelloProperties getHelloProperties() {
return this.helloProperties;
}
public void setHelloProperties(HelloProperties helloProperties) {
this.helloProperties = helloProperties;
}
public String sayHello(String name) {
return this.helloProperties.getPrefix() + name + this.helloProperties.getSuffix();
}
}
最后将两个子项目进行install,当starter被其他springboot应用引入后,就可以通过HelloService进行依赖注入进行调用sayHello()方法了
java -jar是如何启动spring boot jar的?
JarLauncher通过加载BOOT-INF/classes目录及BOOT-INF/lib目录下jar文件,实现了fat jar的启动。
SpringBoot通过扩展JarFile、JarURLConnection及URLStreamHandler,实现了jar in jar中资源的加载。
SpringBoot通过扩展URLClassLoader–LauncherURLClassLoader,实现了jar in jar中class文件的加载。
WarLauncher通过加载WEB-INF/classes目录及WEB-INF/lib和WEB-INF/lib-provided目录下的jar文件,实现了war文件的直接启动及web容器中的启动。
spring boot启动
springboot运行后,会new SpringApplication()将传入的配置类缓存到primarySources中,接下来执行run()方法,创建AnnotationConfigServletWebServerApplicationContext的spring上下文,接下来调用refresh()方法创建spring容器
在此期间会解析配置类,也就是spring boot启动传入的类,这里就会去执行解析@EnableAutoConfiguration注解的信息,完成所需要加载的bean。
当执行到onrefresh()方法时,判断当前servletContext(可以理解为tomcat中servlet容器上下文)是否为空,为空代表tomcat内置启动,需要创建新的tomcat,如果不为空,代表tomcat外置启动应用;此时是springboot启动,servletContext为空,故需要new Tomcat()。当tomcat启动以后会调用函数指针(ServletContextInitializer接口函数),这里执行相关类的onStartup()方法,向servletContext注册DispatchServlet处理器。
而外置tomcat启动容器是通过spi的机制来实现的,返回得到指定的接口的实现类SpringServletContainerInitializer,并实例化调用它的onStartup()方法,会调用SpringBootServletInitializer#onStartup()方法,这里会重新创建一个SpringApplication对象,并调用run方法,相当于启动spring boot应用。
个人笔记