聊一聊Spring框架中的約定優于配置設計
一、什么是約定優于配置?
約定優于配置(Convention over Configuration, CoC)是一種軟件設計范式,它主張通過預定義合理的默認約定來減少開發人員需要做出的決策數量。在Spring框架中,這一理念貫穿始終,使得開發者能夠專注于業務邏輯而非繁瑣的配置。
二、Spring中的默認行為
2.1 組件掃描
Spring從2.5版本引入注解驅動開發后,組件掃描成為核心特性之一。通過@ComponentScan注解,Spring會自動掃描指定包及其子包下的組件。
@Configuration
@ComponentScan("com.example.demo")
public class AppConfig {
// 不需要顯式定義所有bean
}
分析:
- 默認掃描與配置類相同的包及其子包
- 自動檢測帶有@Component及其派生注解(@Service, @Repository, @Controller)的類
- 默認bean名稱生成策略:類名首字母小寫(如UserService變為userService)
2.2 @Autowired自動裝配
Spring的自動裝配(@Autowired)遵循一系列合理的默認規則:
- 類型優先:首先按類型匹配,當有多個同類型bean時才按名稱
- 構造器注入:當類只有一個構造器時,@Autowired可省略
- 名稱派生:當需要按名稱裝配時,參數名/屬性名作為默認限定符
三、SpringBoot中配置理念
Spring Boot將約定優于配置的理念發揮到極致,通過自動配置和啟動器(starter)大大簡化了開發。
3.1 自動配置機制
Spring Boot的@SpringBootApplication實際上組合了三個核心注解:
@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 {
// ...
}
- Spring Boot啟動時加載META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
- 根據classpath存在的類來決定哪些配置生效(通過@Conditional系列注解)
- 應用合理的默認配置
3.2 屬性綁定的默認約定
# 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(短橫線分隔)會自動匹配到Java類的camelCase
- 也支持PascalCase、snake_case等多種格式
四、自定義約定配置
4.1 創建自定義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中注冊:
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 自定義掃描規則
@Configuration
@ComponentScan(
basePackages = "com.example",
includeFilters = @Filter(type = FilterType.ANNOTATION, classes = CustomAnnotation.class),
nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class
)
public class CustomScannerConfig {
// 使用完全限定名作為bean名稱
}
五、源碼分析
5.1 默認bean名稱生成器
AnnotationBeanNameGenerator實現了默認的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;
}
}
// 默認實現:首字母小寫的類名
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)是自動配置的核心:
// 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中實現:
// 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();
}