概述
最近在看源码的时候,发现很多源码中写着@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; }
属性说明:
- value: 自定义当前组件或者说bean的名称,实际就是@Component的value属性。
- proxyBeanMethods: 判断是否bean的方法应该被代理,默认是true,后面原理解析中重点分析。
元注解说明:
- 该注解只能使用在类,接口、枚举、其他注解上
- 该注解的生命周期是运行时JVM
- @Component元注解,相当于拥有了@Component注解的能力,可以被ComponentScan扫描到,变成spring中的Bean。
注解场景使用场景:
- 和@Bean搭配使用,创建管理Bean
@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中。
BeanDefinition的解析
这里主要是将@Configuration
注解的Class解析出BeanDefinition,注册到Spring的BeanDefinition Registry中。
上图是BeanDefinition
解析的调用堆栈。
- 解析所有@Component注解的Class为BeanDefinition,注册到Spring的BeanDefinition Registry。
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(); ...... }
- 调用ConfigurationClassUtils#checkConfigurationClassCandidate设置Configuraion类的BeanDefinition代理信息。
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; }
Configuration注解的Bean创建
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注解标注的效果)。