【SpringBoot】⭐️AutoConfiguration配置的前世今生
- 在 Spring Boot 之前(即传统的 Spring Framework 时代),配置主要依靠 XML 文件和Java 配置类,没有像 Spring Boot 那样的自动配置机制。
- SpringBoot有默认扫描主类所在包,传统 Spring Framework需要xml声明位置
- SpringBoot有内嵌服务器tomcat + 零配置启动,而传统 Spring Framework需外部 Tomcat 部署 + web.xml 配置
- SpringBoot有内置的 @ConditionalOnXxx 注解进行 条件化配置,而传统 Spring Framework需要手动实现 Condition 接口
- SpringBoot 属性配置直接yml配置,而Spring Framwork只有Properties 文件 + ${} 占位符
- SpringBoot 有自动配置机制: META-INF/spring.factories 自动发现,老版本没有
- SpringBoot 有常见默认的自动配置,而Spring Framework 需手动配置数据源、事务、MVC 等
- SpringBoot 的自动配置对于顺序是有强要求的(
@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder
),而传统spring 是封装使用AnnotationConfigApplicationContext
来决定配置的执行顺序,不声明,就默认@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder
这三个注解只能作用于自动配置类,而不能是自定义的@Configuration配置类。- 在
@Configuration类中
,@Bean方法调用会使用CGLIB代理来拦截,确保返回的是同一个单例实例(每次调用被@Bean标记的方法,Spring会检查容器中是否已有该bean,有则返回容器中的,不会重新创建),而在普通的@Component
类中,@Bean方法不会被代理,因此每次调用方法都会创建一个新的实例(除非在方法内部自己实现单例)。
历史
spring Framework时代 XMl
<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="..."><!-- 组件扫描 --><context:component-scan base-package="com.example"/><!-- 数据源配置 --><bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/db"/><property name="username" value="root"/><property name="password" value="123456"/></bean><!-- 事务管理器 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><tx:annotation-driven/><!-- MVC 配置 --><mvc:annotation-driven/><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/views/"/><property name="suffix" value=".jsp"/></bean>
</beans>
spring 3.0
@Configuration
@EnableWebMvc
@ComponentScan("com.example")
@EnableTransactionManagement
public class AppConfig {@Beanpublic DataSource dataSource() {BasicDataSource ds = new BasicDataSource();ds.setDriverClassName("com.mysql.jdbc.Driver");ds.setUrl("jdbc:mysql://localhost:3306/db");ds.setUsername("root");ds.setPassword("123456");return ds;}@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}@Beanpublic ViewResolver viewResolver() {InternalResourceViewResolver resolver = new InternalResourceViewResolver();resolver.setPrefix("/WEB-INF/views/");resolver.setSuffix(".jsp");return resolver;}
}
@Enable 模块驱动(spring 3.1)
// 自定义注解开启模块
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyModuleConfig.class) // 导入配置类
public @interface EnableMyModule {}
条件化配置(Spring 4.0+)
@Configuration
public class MyAutoConfig {@Bean@ConditionalOnClass(DataSource.class) // 类路径存在时生效public DataSource dataSource() {// ...}@Bean@ConditionalOnMissingBean // 容器不存在该Bean时生效public MyService myService() {return new DefaultMyService();}
}
自动配置类
@Configuration
@Conditional(OnClassCondition.class)
public class DataSourceAutoConfig {@Bean@ConditionalOnMissingBeanpublic DataSource dataSource(Environment env) {// 从环境变量读取配置DataSource ds = new BasicDataSource();ds.setUrl(env.getProperty("spring.datasource.url"));// ...return ds;}
}
配置类的相关问题
@SpringBootApplication
默认是有包扫描的 ,内部是被@ComponentScan
标记的, 但如果配置类不在其默认的子包下,需要在启动类使用@ComponentScan(basePackages = {"com.csair", "其他包路径"})
更改位置进行扫包
@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 {}
当配置类 已经加上
@Configuration
注解 是不需要加上@Component
注解的,因为@Configuration
注解有标注@Component
的
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {}
当然,如果你是使用简单的配置类,只需要添加
@Configuration
就行了,但如果你想对其配置属性配置,就需要加上@EnableConfigurationProperties
,从底下代码可以看出 关系链: 配置类 需要属性注入,所以需要关联 属性类,而属性类的属性,又可以从阿波罗注入,这样也会安全一点
注解 | 作用 | 示例 |
---|---|---|
@EnableConfigurationProperties | 激活 @ConfigurationProperties 类 | @EnableConfigurationProperties(DataSourceProps.class) |
@ConfigurationProperties | 绑定配置文件到类(通常用在属性类上) | @ConfigurationProperties(prefix=“app.datasource”) |
@PropertySource | 加载自定义 properties 文件 | @PropertySource(“classpath:custom.properties”) |
第一种写法
注意: 如果你已经在
RocketMqMessageProperties
类上 使用了@EnableConfigurationProperties
, 同时又使用了@Component
,这就会导致同一个类会被注册两次(两个Bean定义),因为@EnableConfigurationProperties
会将类交给Bean管理,所以去掉多余的@Component
注解
@EnableConfigurationProperties(RocketmqMessageProperties.class)
@Configuration
public class RocketMqMessageConfig {private static final Logger logger = LoggerFactory.getLogger(RocketMqMessageConfig.class);@Value("${ly.rocketmq.serverAddr}")private String serverAddr;@Beanpublic RocketMQProducer newMessageMq(RocketMqMessageProperties rocketMqMessageProperties) {try {RocketMQProducerConfig rocketMQProducerConfig = RocketMQProducerConfig.newBuilder().serverAddr(serverAddr).appName(rocketMqMessageProperties.getProduceAppName()).topic(rocketMqMessageProperties.getProduceTopicName()).build();return MQProducerFactory.newBuilder().rocketMQProducerConfig(rocketMQProducerConfig).mqProducerType("CANDIDATE").build().createMQProducer();} catch (Exception e) {logger.error("消息池MQ配置初始化失败",e);}return null;}
}
第二种写法
注意
- 是需要将
RocketMMessageProperties
属性类显示注册成@Bean才行,因为@ConfigurationProperties
是不会注册成Bean对象的 ,所以需要添加@Component
注解或其他,将其注册成Bean- 如果有多个
RocketMMessageProperties
属性类的Bean对象,需要@Autowired
注入时加上注解@Qualifier
指定其特殊的Bean
@Configuration
public class RocketMqMessageConfig {@Autowired RocketMqMessageProperties rocketMqMessageProperties;private static final Logger logger = LoggerFactory.getLogger(RocketMqMessageConfig.class);@Value("${ly.rocketmq.serverAddr}")private String serverAddr;@Beanpublic RocketMQProducer newMessageMq() {try {RocketMQProducerConfig rocketMQProducerConfig = RocketMQProducerConfig.newBuilder().serverAddr(serverAddr).appName(rocketMqMessageProperties.getProduceAppName()).topic(rocketMqMessageProperties.getProduceTopicName()).build();return MQProducerFactory.newBuilder().rocketMQProducerConfig(rocketMQProducerConfig).mqProducerType("CANDIDATE").build().createMQProducer();} catch (Exception e) {logger.error("消息池MQ配置初始化失败",e);}return null;}
}
@ConfigurationProperties(prefix = "mq.jd.message.sync.rocket", ignoreUnknownFields = false)
public class RocketMqMessageProperties implements Serializable {private static final long serialVersionUID = 6885212498401171664L;private String configUrl;private String produceAppName;private String produceTopicName;public String getConfigUrl() {return configUrl;}public void setConfigUrl(String configUrl) {this.configUrl = configUrl;}public String getProduceAppName() {return produceAppName;}public void setProduceAppName(String produceAppName) {this.produceAppName = produceAppName;}public String getProduceTopicName() {return produceTopicName;}public void setProduceTopicName(String produceTopicName) {this.produceTopicName = produceTopicName;}
}
app:id: shqxe
apollo:meta: http://127.0.0.1:8081bootstrap:enabled: trueeagerLoad:enabled: truenamespaces: config.yaml,spring-config.yaml,switch.yamlspring:cloud:config:import-check:enabled: false
当然,你也可添加 条件化注解 ,
@ConditionalOnClass
,@ConditionalOnProperty
,@ConditionalOnMissingBean
, 这些注解用于根据特定条件决定是否加载该配置类或其中的Bean`
注解 | 作用 | 示例 | 解释 |
---|---|---|---|
@ConditionalOnProperty | 当配置属性存在且匹配值时生效 | @ConditionalOnProperty(name=“cache.enabled”, havingValue=“true”) | 若 cache.enabled有值,且为true,才注入 |
@ConditionalOnClass | 当类路径存在指定类时生效 | @ConditionalOnClass(RedisTemplate.class) | 为了检测是否有该类,是否加了该依赖,没什么用 |
@ConditionalOnMissingBean | 当容器不存在指定 Bean 时生效 | @ConditionalOnMissingBean(DataSource.class) | 可以用于保底手段,当DataSource的Bean对象不存在或者注入失败,则可以手动创建一个返回 |
@Profile | 仅在指定 Profile 激活时生效 | @Profile(“production”) | … |
区别
@Configuration
: 明确告诉 Spring:我是一个配置类,我的主要目的是通过 @Bean 方法来定义和配置 Spring 容器管理的 Bean。请用 CGLIB 代理增强我,确保 @Bean 方法调用返回的是单例。
@Component
: 告诉 Spring:我是一个需要被自动扫描并注册为 Bean 的组件类。它标记的类本身会被 Spring 实例化并管理为一个 Bean。
@Bean
: 这是方法级别的注解。它用在 @Configuration 类或 @Component 类内部的方法上,告诉 Spring:调用我这个方法返回的对象,应该被注册为一个 Spring Bean。这个方法负责创建和配置这个 Bean 实例。