springboot启动扩展原理
1.@Enable**原理
Enable 注解是在 JavaConfig 框架上更进一 步的完善,用户在使用 spring 相关的框架时,避免配置大量的代码从而降低使用的难度 。每个Enable开头的注解都会有一个@Import的注解。
如@Import({AsyncConfigurationSelector.class})
@Impart是为了导入配置类,@Import 注解可以配置三种不同的 class
- 基于普通 bean 或者带有 @Configuration 的 bean 进行注入
- 实现 ImportSelector 接口进行动态注入
- 实现 ImportBeanDefinitionRegistrar 接口进行动态注入
以下例子是EnableAsync
异步框架使用的注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
Class<? extends Annotation> annotation() default Annotation.class;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
重点就是在@Import注解中的AsyncConfigurationSelector配置类中。
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
public AsyncConfigurationSelector() {
}
@Nullable
public String[] selectImports(AdviceMode adviceMode) {
switch(adviceMode) {
case PROXY:
return new String[]{ProxyAsyncConfiguration.class.getName()};
case ASPECTJ:
return new String[]{"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"};
default:
return null;
}
}
}
这个的selectImports方法就是返回一个需要配置信息Config的类名集合,然后框架会对这个集合中的类进行注册调用,这样就实现了注册逻辑。
自己的实现
1.Enable类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({CommonAsyncSelect.class})
public @interface EnableCommonAsync {
}
这个注解代表你要使用一个模块,会启动这个模块所依赖的配置信息的调用
2.Import类
public class CommonAsyncSelect implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.meng.common.spring.starter.AsyncConfiguration"};
}
}
这个类返回这个模块所需要的配置信息类在哪里,需要哪些配置信息类。
3.配置信息类
@Configuration
@ComponentScan("com.meng.common.spring.async")
@EnableConfigurationProperties(AsyncProperties.class)
public class AsyncConfiguration {
@Autowired
private AsyncProperties asyncProperties;
@Bean
public AsyncConfig asyncConfig() {
AsyncConfig asyncConfig = new AsyncConfig();
asyncConfig.setProjectName(asyncProperties.getProjectName());
asyncConfig.setExecutor(asyncProperties.getExecutor());
return asyncConfig;
}
}
这个就是一个正常的配置信息类。他配置了这个模块所需要的所有配置信息逻辑。
总结:
Enable就是指定了你需要使用的模块,然后会去初始化这个模块所使用的配置信息。
2.Spring SPI
SPI 是一种服务发现机制。可以用它来做服务的扩展发现。简单来说,它是一种动态发现的机制。一般面向框架的扩展使用
在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后解析成properties文件,找到指定名称的配置后返回。需要注意的是,其实这里不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包。
spring.factories文件是key-value键值对的方式,key使用这个SPI的类名,value是需要加载的配置文件的名字。
下面的配置的意思就是使用EnableAutoConfiguration的逻辑来加载AsyncConfiguration类。key可以重复。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.meng.common.spring.starter.AsyncConfiguration
这里的EnableAutoConfiguration其实就是使用了@Enable**原理。
springboot的启动注解@SpringBootApplication
中就包含了@EnableAutoConfiguration
。
代表着EnableAutoConfiguration在启动时会运行
1.EnableAutoConfiguration
本质上来说,其实 EnableAutoConfiguration 会帮助 springboot 应用把所有符合@Configuration 配置都加载到当前 SpringBoot 创建的 IoC 容器,而这里面借助了 Spring 框架提供的一个工具类 SpringFactoriesLoader 的 支持。以及用到了 Spring 提供的条件注解 @Conditional,选择性的针对需要加载的 bean 进行条件 过滤
2.SpringFactoriesLoader
SpringFactoriesLoader 这个工具类的使用。它其实和 java 中的 SPI 机制的原理是一样的,不过它比 SPI 更好的点在于不会一次性加载所有的类,而是根据 key 进行加 载。 首 先 , SpringFactoriesLoader 的作用是从 classpath/META-INF/spring.factories 文件中,根据 key 来 加载对应的类到 spring IoC 容器中。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// spring.factories文件的格式为:key=value1,value2,value3
// 从所有的jar包中找到META-INF/spring.factories文件
// 然后从文件中解析出key=factoryClass类名称的所有value值
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// 取得资源文件的URL
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
// 遍历所有的URL
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 根据资源文件URL解析properties文件,得到对应的一组@Configuration类
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
// 组装数据,并返回
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
例子
- 需要启动时运行的配置信息类,因为这个都是其他组件中的配置信息,所有在本项目中的springboot扫描不会扫描到。
@Configuration
@ComponentScan("com.meng.common.spring.async")
@EnableConfigurationProperties(AsyncProperties.class)
public class AsyncConfiguration {
@Autowired
private AsyncProperties asyncProperties;
@Bean
@ConditionalOnMissingBean
public AsyncConfig asyncConfig() {
AsyncConfig asyncConfig = new AsyncConfig();
asyncConfig.setProjectName(asyncProperties.getProjectName());
asyncConfig.setExecutor(asyncProperties.getExecutor());
return asyncConfig;
}
}
2.spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.meng.common.spring.starter.AsyncConfiguration
这个配置会被SpringFactoriesLoader扫描到,当组件被maven添加到项目后,当项目启动时,配置信息就会被调用。对于开发人员来说,只要这个组件被导入,就可以直接使用,无需做任何配置。