文章目录
1. 获取配置信息
1.1 @Value
① 用途
Spring提供了获取properties文件中的配置值的方法,通过注解@Value
的方式可以直接读取配置文件的配置值。
② 配置
applicationContext.xml配置:
<bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations">
<list>
<value>classpath:config/c1.properties</value>
<value>classpath:config/c2.properties</value>
</list>
</property>
</bean>
③ 使用
方式1:通过EL表达式类似的语法 p r o p e r t y : d e f a u l t v a l u e 方 式 获 取 配 置 属 性 , 不 需 要 指 定 配 置 文 件 的 加 载 对 象 实 例 ( B e a n ) 。 b e a n c l a s s 要 使 用 P r e f e r e n c e s P l a c e h o l d e r C o n f i g u r e r 。 示 例 : ‘ @ V a l u e ( " { property : default_value } 方式获取配置属性,不需要指定配置文件的加载对象实例(Bean)。bean class要使用PreferencesPlaceholderConfigurer。示例: `@Value(" property:defaultvalue方式获取配置属性,不需要指定配置文件的加载对象实例(Bean)。beanclass要使用PreferencesPlaceholderConfigurer。示例:‘@Value("{jdbc.driverClass}")`
方式2:通过OGNL表达式类似语法 #{ obj.property? : default_value } 方式获取配置属性,可以指定配置文件的加载对象实例(Bean),配置bean class要使用PropertiesFactoryBean。示例: @Value("#{configProperties['jdbc.driverClass']")
1.2 @ConfigurationProperties
对于yaml配置文件,@Value仍然可以读取相关配置信息,但不适用于复杂类型的读取,只适用于字符串的读取,比如Map,List,Set这种集合类型,SpringBoot提供了另外一种读取方式,即使用POJO的方式来直接读取配置信息。
示例:
Yaml配置文件class-conf.yaml
:
class:
no: 1
name: 一班
students:
- id: 1
name: Bob
age: 18
friends:
- name: Tom
age: 18
- name: Ami
age: 17
- id: 2
name: Tom
friends:
- name: Bob
age: 18
- id: 3
name: Ami
age: 17
friends:
- name: Bob
age: 18
接受配置信息的java类:
package com.example;
@Data
@ConfigurationProperties(prefix = "class", ignoreInvalidFields = true)
public class ClassProperties{
private Integer no;
private String name;
Set<Student> students;
@Data
public static class Student{
private Integer id;
private String name;
private Integer age;
private List<Map<String, Object>> friends;
}
}
测试读取:
package com.example;
import com.example.ClassProperties.Student;
import java.util.List;
import javax.annotation.Resource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;
@SpringBootTest(classes = ConfigReadTest.class)
@Configuration
//加载指定配置文件,默认会加载application.yaml
@PropertySource("classpath:class-conf.yaml")
//指定读取注入配置信息类
@EnableConfigurationProperties(ClassProperties.class)
public class ConfigReadTest{
@Resource ClassProperties classProperties;
@Test
void test(){
Assertions.assertNotNull(classProperties);
List<Student> students = classProperties.getStudents();
Assertions.assertNotNull(students);
Assertions.assertTrue(!students.isEmpty());
Assertions.assertNotNull(students.get(0).getName());
Assertions.assertNotNull(students.get(0).getFriends());
}
}
2. 对外提供二方包Bean注册
2.1 @EnableAutoConfiguration
先看源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
其中最关键的是@Import(AutoConfigurationImportSelector.class)
,借助AutoConfigurationImportSelector
,@EnableAutoConfiguration
可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。
借助于Spring框架原有的一个工具类:SpringFactoriesLoader
的支持,@EnableAutoConfiguration
才可以智能的自动配置!
在AutoConfigurationImportSelector
类中可以看到通过 SpringFactoriesLoader.loadFactoryNames
方法
把 spring-boot-autoconfigure.jar/META-INF/spring.factories中每一个xxxAutoConfiguration文件都加载到容器中了。
其中的每个xxxAutoConfiguration配置类一般都会有下面的条件注解:
@ConditionalOnClass
: classpath中存在该类时起效@ConditionalOnMissingClass
: classpath中不存在该类时起效@ConditionalOnBean
: DI容器中存在该类型Bean时起效@ConditionalOnMissingBean
: DI容器中不存在该类型Bean时起效@ConditionalOnSingleCandidate
: - DI容器中该类型Bean只有一个或@Primary的只有一个时起效@ConditionalOnExpression
: SpEL表达式结果为true时@ConditionalOnProperty
: 参数设置或者值一致时起效@ConditionalOnResource
: 指定的文件存在时起效@ConditionalOnJndi
: 指定的JNDI存在时起效@ConditionalOnJava
: 指定的Java版本存在时起效@ConditionalOnWebApplication
: Web应用环境下起效@ConditionalOnNotWebApplication
: 非Web应用环境下起效
SpringFactoriesLoader
属于Spring框架私有的一种扩展方案(类似于Java的SPI方案java.util.ServiceLoader),其主要功能就是从指定的配置文件META-INF/spring-factories加载配置,spring-factories是一个典型的java properties文件,只不过Key和Value都是Java类型的完整类名。
对于@EnableAutoConfiguration
来说,SpringFactoriesLoader
的用途稍微不同一些,其本意是为了提供SPI扩展的场景,而在@EnableAutoConfiguration
场景中,它更多提供了一种配置查找的功能支持,即根据@EnableAutoConfiguration
的完整类名org.springframework.boot.autoconfig.EnableAutoConfiguration
作为查找的Key,获得对应的一组@Configuration
类。
SpringFactoriesLoader是一个抽象类,类中定义的静态属性定义了其加载资源的路径public static final String FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”,此外还有三个静态方法:
- loadFactories:加载指定的factoryClass并进行实例化。
- loadFactoryNames:加载指定的factoryClass的名称集合。
- instantiateFactory:对指定的factoryClass进行实例化。
在loadFactories方法中调用了loadFactoryNames以及instantiateFactory方法。源码如下:
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryClass, "'factoryClass' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
List<T> result = new ArrayList<>(factoryNames.size());
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
try {
Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
if (!factoryClass.isAssignableFrom(instanceClass)) {
throw new IllegalArgumentException(
"Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
}
return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
}
catch (Throwable ex) {
throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
}
}
loadFactories方法首先获取类加载器,然后调用loadFactoryNames方法获取所有的指定资源的名称集合、接着调用instantiateFactory方法实例化这些资源类并将其添加到result集合中。最后调用AnnotationAwareOrderComparator.sort方法进行集合的排序。
总结:
对外发布的二方包,需要对外提供一些bean的配置、注册、扫描等,如果仅仅是简单打包,外部依赖项目是需要针对此二方包额外配置一些Configuration加载,或者包扫描等,如果外部项目并不知道二方包内有什么,就无法针对性的进行配置。所以就需要二方包通过某种方式,让外部依赖项目可以无感知的、全自动的加载二方包的配置类,从而做到自动化配置。
要达到此目的,仅需要两步:
- 编写配置类并注解
@Configuration
或@SpringBootConfiguration
- 在resources/META-INF/spring.factories中指定xxAutoConfiguration,并写上步骤1编写的配置类全限定类名。
打包对外发布即可。
外部依赖项目仅需要在项目启动入口(通常是ApplicationStartup)写上注解@SpringBootApplication
(因为此注解已继承了@EnableAutoConfiguration
);测试类上则需要打上注解@EnableAutoConfiguration