Spring框架中的约定优于配置设计

153 阅读3分钟

一、什么是约定优于配置?

约定优于配置(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)遵循一系列合理的默认规则:

  1. 类型优先:首先按类型匹配,当有多个同类型bean时才按名称
  2. 构造器注入:当类只有一个构造器时,@Autowired可省略
  3. 名称派生:当需要按名称装配时,参数名/属性名作为默认限定符

三、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();
}