聊一聊Spring框架中的約定優(yōu)于配置設(shè)計(jì)
一、什么是約定優(yōu)于配置?
約定優(yōu)于配置(Convention over Configuration, CoC)是一種軟件設(shè)計(jì)范式,它主張通過預(yù)定義合理的默認(rèn)約定來減少開發(fā)人員需要做出的決策數(shù)量。在Spring框架中,這一理念貫穿始終,使得開發(fā)者能夠?qū)W⒂跇I(yè)務(wù)邏輯而非繁瑣的配置。
二、Spring中的默認(rèn)行為
2.1 組件掃描
Spring從2.5版本引入注解驅(qū)動(dòng)開發(fā)后,組件掃描成為核心特性之一。通過@ComponentScan注解,Spring會(huì)自動(dòng)掃描指定包及其子包下的組件。
@Configuration
@ComponentScan("com.example.demo")
public class AppConfig {
// 不需要顯式定義所有bean
}
分析:
- 默認(rèn)掃描與配置類相同的包及其子包
- 自動(dòng)檢測(cè)帶有@Component及其派生注解(@Service, @Repository, @Controller)的類
- 默認(rèn)bean名稱生成策略:類名首字母小寫(如UserService變?yōu)閡serService)
2.2 @Autowired自動(dòng)裝配
Spring的自動(dòng)裝配(@Autowired)遵循一系列合理的默認(rèn)規(guī)則:
- 類型優(yōu)先:首先按類型匹配,當(dāng)有多個(gè)同類型bean時(shí)才按名稱
- 構(gòu)造器注入:當(dāng)類只有一個(gè)構(gòu)造器時(shí),@Autowired可省略
- 名稱派生:當(dāng)需要按名稱裝配時(shí),參數(shù)名/屬性名作為默認(rèn)限定符
三、SpringBoot中配置理念
Spring Boot將約定優(yōu)于配置的理念發(fā)揮到極致,通過自動(dòng)配置和啟動(dòng)器(starter)大大簡化了開發(fā)。
3.1 自動(dòng)配置機(jī)制
Spring Boot的@SpringBootApplication實(shí)際上組合了三個(gè)核心注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration // 啟用自動(dòng)配置
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// ...
}
- Spring Boot啟動(dòng)時(shí)加載META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
- 根據(jù)classpath存在的類來決定哪些配置生效(通過@Conditional系列注解)
- 應(yīng)用合理的默認(rèn)配置
3.2 屬性綁定的默認(rèn)約定
# application.properties
app.database.url=jdbc:mysql://localhost:3306/mydb
app.database.username=admin
@ConfigurationProperties("app.database")
public class DatabaseProperties {
private String url;
private String username;
// getters and setters
}
- 屬性文件中的kebab-case(短橫線分隔)會(huì)自動(dòng)匹配到Java類的camelCase
- 也支持PascalCase、snake_case等多種格式
四、自定義約定配置
4.1 創(chuàng)建自定義starter
1)定義配置類:
@AutoConfiguration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyProperties properties) {
return new MyService(properties);
}
}
2)在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中注冊(cè):
com.example.MyAutoConfiguration
4.2 自定義條件注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnProductionEnvironmentCondition.class)
public @interface ConditionalOnProduction {
}
public class OnProductionEnvironmentCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String env = context.getEnvironment().getProperty("app.env");
return "prod".equalsIgnoreCase(env);
}
}
4.3 自定義掃描規(guī)則
@Configuration
@ComponentScan(
basePackages = "com.example",
includeFilters = @Filter(type = FilterType.ANNOTATION, classes = CustomAnnotation.class),
nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class
)
public class CustomScannerConfig {
// 使用完全限定名作為bean名稱
}
五、源碼分析
5.1 默認(rèn)bean名稱生成器
AnnotationBeanNameGenerator實(shí)現(xiàn)了默認(rèn)的bean命名策略:
// org.springframework.context.annotation.AnnotationBeanNameGenerator
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
return beanName;
}
}
// 默認(rèn)實(shí)現(xiàn):首字母小寫的類名
return buildDefaultBeanName(definition, registry);
}
protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
String beanClassName = definition.getBeanClassName();
String shortClassName = ClassUtils.getShortName(beanClassName);
return Introspector.decapitalize(shortClassName);
}
5.2 條件注解
Spring Boot的條件注解(@Conditional)是自動(dòng)配置的核心:
// org.springframework.boot.autoconfigure.condition.SpringBootCondition
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
ConditionOutcome outcome = getMatchOutcome(context, metadata);
// 記錄日志...
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
throw ex;
}
catch (Throwable ex) {
throw ex;
}
}
以@ConditionalOnClass為例,其匹配邏輯在OnClassCondition中實(shí)現(xiàn):
// org.springframework.boot.autoconfigure.condition.OnClassCondition
protected ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> onClasses = getAllAnnotationAttributes(
metadata, ConditionalOnClass.class.getName());
if (onClasses != null) {
List<String> missing = filter(onClasses.get("value"), context.getClassLoader(), false);
if (!missing.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(
ConditionalOnClass.class).didNotFind("required class", "required classes")
.items(Style.QUOTE, missing));
}
}
// 類似處理@ConditionalOnMissingClass
return ConditionOutcome.match();
}