最近在看源码的时候,发现很多源码中写着@Configuration(proxyBeanMethods = false)
,引起了我的好奇,为啥这么写, 这个proxyBeanMethods属性是干嘛的?@Configuration
和@Component
注解有什么区别呢?
@Configuration
注解可以加在类上,让这个类的功能等同于一个bean xml配置文件,可以在这个类中管理创建Bean。
注解源码:
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Component
- public @interface Configuration {
-
-
- @AliasFor(annotation = Component.class)
- String value() default "";
-
-
- boolean proxyBeanMethods() default true;
-
- }
- 复制代码
属性说明:
元注解说明:
注解场景使用场景:
- @Configuration
- public class AppConfig {
-
- @Bean
- public MyBean myBean() {
- // instantiate, configure and return bean ...
- }
- }
- 复制代码
@PropertySource
注解搭配使用,获取外部配置- @Configuration
- @PropertySource("classpath:/com/acme/app.properties")
- public class AppConfig {
-
- @Inject Environment env;
-
- @Value("${bean.name}") String beanName;
-
- @Bean
- public MyBean myBean() {
- return new MyBean(env.getProperty("bean.name"));
- }
- }
- 复制代码
@Import
注解搭配使用,导入其他配置类- @Configuration
- public class DatabaseConfig {
-
- @Bean
- public DataSource dataSource() {
- // instantiate, configure and return DataSource
- }
- }
-
- @Configuration
- @Import(DatabaseConfig.class)
- public class AppConfig {
-
- private final DatabaseConfig dataConfig;
-
- public AppConfig(DatabaseConfig dataConfig) {
- this.dataConfig = dataConfig;
- }
-
- @Bean
- public MyBean myBean() {
- // reference the dataSource() bean method
- return new MyBean(dataConfig.dataSource());
- }
- }
- 复制代码
@Profile
搭配使用,指定当前配置使用的profile- @Profile("development")
- @Configuration
- public class EmbeddedDatabaseConfig {
-
- @Bean
- public DataSource dataSource() {
- // instantiate, configure and return embedded DataSource
- }
- }
-
- @Profile("production")
- @Configuration
- public class ProductionDatabaseConfig {
-
- @Bean
- public DataSource dataSource() {
- // instantiate, configure and return production DataSource
- }
- }
-
- @Configuration
- public class ProfileDatabaseConfig {
-
- @Bean("dataSource")
- @Profile("development")
- public DataSource embeddedDatabase() { ... }
-
- @Bean("dataSource")
- @Profile("production")
- public DataSource productionDatabase() { ... }
- }
- 复制代码
@ImportResource
搭配使用,导入xml的spring配置- @Configuration
- @ImportResource("classpath:/com/acme/database-config.xml")
- public class AppConfig {
-
- @Inject DataSource dataSource; // from XML
-
- @Bean
- public MyBean myBean() {
- // inject the XML-defined dataSource bean
- return new MyBean(this.dataSource);
- }
- }
- 复制代码
这里我们通过一个案例来了解下注解的proxyBeanMethods方法。
- @Configuration
- public class TestConfig {
-
- /**
- * 定义猫
- * @return
- */
- @Bean(name = "cat")
- public Cat cat() {
- return new Cat("小猫" + new Random().nextInt(100));
- }
-
- /**
- * 爸爸
- * @return
- */
- @Bean(name = "dad")
- public Person dadPerson() {
- return new Person("dad", cat());
- }
-
- /**
- * 爸爸
- * @return
- */
- @Bean(name = "mom")
- public Person momPerson() {
- return new Person("mom", cat());
- }
- }
- 复制代码
- Cat cat = context.getBean("cat", Cat.class);
- Person dadPerson = context.getBean("dad", Person.class);
- Person momPerson = context.getBean("mom", Person.class);
- System.out.println(cat);
- System.out.println(dadPerson.getCat());
- System.out.println(momPerson.getCat());
-
- TestConfig testConfig = context.getBean(TestConfig.class);
- System.out.println(testConfig);
- 复制代码
大家觉得打印的是同一个cat吗?
他们同一个对象,同一只小猫。
看到这个config类的类型是CGLIB,说明被代理了。
现在我们把@Configuration
注解的proxyBeanMethods改为false,会有什么不一样呢?
再次执行上面的输出,如下:
发现每只小猫都不一样了,这个配置类也不是代理类。
这是怎么一回事,我们就要分析下源码了。
因为@Configuration注解是被@Component注解修饰,说明他有@Compnent注解的功能,可以被Spring扫描到成为Bean, 具体原理可以看www.yuque.com/alvinscript…。
整一个实现的源码都是在ConfigurationClassPostProcessor中。
这里主要是将@Configuration
注解的Class解析出BeanDefinition,注册到Spring的BeanDefinition Registry中。
上图是BeanDefinition
解析的调用堆栈。
- public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
- ......
- // Parse each @Configuration class
- ConfigurationClassParser parser = new ConfigurationClassParser(
- this.metadataReaderFactory, this.problemReporter, this.environment,
- this.resourceLoader, this.componentScanBeanNameGenerator, registry);
-
- Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
- Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
- do {
- StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
- // 根据路径扫描解析出BeanDefinition并且注册到registry
- parser.parse(candidates);
- parser.validate();
-
-
- ......
-
- }
- 复制代码
- public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
- ......
- // Parse each @Configuration class
- for (String candidateName : newCandidateNames) {
- if (!oldCandidateNames.contains(candidateName)) {
- BeanDefinition bd = registry.getBeanDefinition(candidateName);
- // 校验类
- if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
- !alreadyParsedClasses.contains(bd.getBeanClassName())) {
- candidates.add(new BeanDefinitionHolder(bd, candidateName));
- }
- }
- }
- ......
-
- }
- 复制代码
- public static boolean checkConfigurationClassCandidate(
- BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
-
- .....
- // 获取Configuration 注解的信息
- Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
- // 如果不为空,并且proxyBeanMethods不是false
- if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
- // 设置bean definition为 full
- beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
- }
- else if (config != null || isConfigurationCandidate(metadata)) {
- // 设置bean definition为 lite
- beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
- }
- else {
- return false;
- }
-
- // It's a full or lite configuration candidate... Let's determine the order value, if any.
- Integer order = getOrder(metadata);
- if (order != null) {
- beanDef.setAttribute(ORDER_ATTRIBUTE, order);
- }
-
- return true;
- }
- 复制代码
bean在创建的时候会调用BeanFactoryPostProcessor
的postProcessBeanFactory
方法来处理Bean。
ConfigurationClassPostProcessor
实现了BeanFactoryPostProcessor
接口,在Bean创建的时候回调。- @Override
- public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
- int factoryId = System.identityHashCode(beanFactory);
- if (this.factoriesPostProcessed.contains(factoryId)) {
- throw new IllegalStateException(
- "postProcessBeanFactory already called on this post-processor against " + beanFactory);
- }
- this.factoriesPostProcessed.add(factoryId);
- if (!this.registriesPostProcessed.contains(factoryId)) {
- // BeanDefinitionRegistryPostProcessor hook apparently not supported...
- // Simply call processConfigurationClasses lazily at this point then.
- processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
- }
- // 关键在这里增强配置类
- enhanceConfigurationClasses(beanFactory);
- beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
- }
- 复制代码
enhanceConfigurationClasses
增强处理,主要判断属性是否式full,如果是的话,需要用cglib动态代理增强。- public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
- .....
- // 如果指定属性是full, 则进行下面处理,该属性在前面阶段设置
- if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
- if (!(beanDef instanceof AbstractBeanDefinition)) {
- throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
- beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
- }
- else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
- logger.info("Cannot enhance @Configuration bean definition '" + beanName +
- "' since its singleton instance has been created too early. The typical cause " +
- "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
- "return type: Consider declaring such methods as 'static'.");
- }
- // 加入到增强集合中
- configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
- }
- }
- if (configBeanDefs.isEmpty() || NativeDetector.inNativeImage()) {
- // nothing to enhance -> return immediately
- enhanceConfigClasses.end();
- return;
- }
- // 新建增强器
- ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
- // 遍历待增强的容器列表
- for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
- AbstractBeanDefinition beanDef = entry.getValue();
- // If a @Configuration class gets proxied, always proxy the target class
- beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
- // Set enhanced subclass of the user-specified bean class
- Class<?> configClass = beanDef.getBeanClass();
- // 进行增强处理
- Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
- if (configClass != enhancedClass) {
- if (logger.isTraceEnabled()) {
- logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
- "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
- }
- beanDef.setBeanClass(enhancedClass);
- }
- }
- enhanceConfigClasses.tag("classCount", () -> String.valueOf(configBeanDefs.keySet().size())).end();
- }
- 复制代码
增强逻辑主要在ConfigurationClassEnhancer
类中。
这里面的逻辑它会在从Spring Bean中先获取cat在这个Bean,所以会是同一个对象。
回到一开始,为什么很多源码中写着@Configuration(proxyBeanMethods = false)
,这主要是出去性能的考虑,proxyBeanMethods设置为flase的情况下,将不会进行代理处理,并且每次调用@Bean注解标注的方法时都会创建一个新的Bean实例这种做法可能会稍微缩短启动时间,因为它不需要为配置类创建代理。但是还是要谨慎使用,因为它会改变方法的行为(并且可能最终会创建一个bean的多个实例,而不是单个实例!尤其是当与@Lazy每个调用结合使用时,会创建一个新实例,从而导致每次都是获取类似@Scope注解标注的效果)。