@Configuration注解天天用,你真的了解它嗎?
環(huán)境:Spring5.3.23
1. 簡介
@Configuration 是一個類級注解,表示一個對象是 Bean 定義的來源。@Configuration 類通過 @Bean 注解的方法聲明 Bean。對 @Configuration 類上 @Bean 方法的調用也可用于定義Bean之間的依賴關系。
使用@Configuration注解的主要作用是替代Spring的applicationContext.xml文件,使得配置更加靈活和方便。當某個類標注了@Configuration注解時,表示這個類是Spring的一個配置類,能夠自動注冊到IOC容器并進行實例化。
2. 應用示例
static class Person {}
@Configuration
static class AppConfig {
@Bean
public Person person() {
return new Person() ;
}
}
AppConfig是一個配置類,在該類中通過@Bean標注方法注冊Bean對象。示例非常的簡單,但是不是一定的用@Configuration呢?換成@Component試試
@Component
static class AppConfig {
@Bean
public Person person() {
return new Person() ;
}
}
// 測試是否能夠獲取Person bean對象
try (GenericApplicationContext context = new GenericApplicationContext()) {
context.registerBean(AppConfig.class) ;
// ...
System.out.println(context.getBean(Person.class)) ;
}
上面的示例能夠正確的獲取Person bean對象,那這里的@Component與@Configuration有什么區(qū)別呢?接著看下面代碼示例:
@Configuration
static class AppConfig {
@Bean
public Person person() {
return new Person() ;
}
@Bean
public Date d1() {
System.out.println(person()) ;
return new Date() ;
}
@Bean
public Date d2() {
System.out.println(person()) ;
return new Date() ;
}
}
在上面的示例中,隨意定義了2個Date類型的Bean,這2個方法中都調用person()方法,執(zhí)行結果:
com.pack.m.b.CMain$Person@55183b20
com.pack.m.b.CMain$Person@55183b20
控制臺輸出的一模一樣,是不是感覺好奇怪,調用2次應該是不同的Person對象才對是吧?先繼續(xù)往下看,吧@Configuration換成@Component(就是換注解,其它都沒有變化,代碼就不貼了),執(zhí)行結果:
com.pack.m.b.CMain$Person@78aab498
com.pack.m.b.CMain$Person@5dd6264
這次輸出是2個不同的Person對象了,此時你是不是覺得這次符合你的預期?如果你這么認為那么就出大事了。
在 Spring 中,實例化的 Bean 默認具有單例作用域,但是如上執(zhí)行情況,明顯成了多例,我們就應該確保容器中任何時候使用的都是同一個實例。如果這里是DataSource那問題就更加嚴重了。
所以,這里雖然可以使用@Component定義配置類,但是強烈不建議你這樣使用,既然是配置類你就的按規(guī)矩來使用@Configuration注解。那@Configuration是如何保證在內部方法調用返回的對象是同一個呢?
先給出答案:那是因為使用@Configuration注解的類被生成了代理類(通過CGLIB)。接下來我們來看看它的原理。
3. 實現原理
Spring提供了一個ConfigurationClassPostProcessor處理器來注冊@Configuration注解。該處理器是一個BeanFactoryPostProcessor。我們就從這里看起
3.1 給@Configuration注解的類打標記
這里所說的大標記其實就確定你當前這個配置類要不要生成代理,而這個標記模式是生成代理
// 這里的proxyBeanMethods值為true,意為會為當前的配置類生成代理
@Configuration(proxyBeanMethods = true)
static class AppConfig {}
處理配置類
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// ...
// 處理配置類bean
processConfigBeanDefinitions(registry);
}
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 獲取所有的bean
String[] candidateNames = registry.getBeanDefinitionNames();
// 遍歷
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName) ;
// 判斷當前的bean是否有ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE屬性
// 默認都是沒有的,所以這里進入到else if 中
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
// 打印日志
}
// 在checkConfigurationClassCandidate會處理配置類的相應屬性
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// ...
}
}
ConfigurationClassUtils
abstract class ConfigurationClassUtils {
public static final String CONFIGURATION_CLASS_FULL = "full";
public static final String CONFIGURATION_CLASS_LITE = "lite";
public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
// 省去無關代碼
// 獲取到當前配置類的所有注解信息
AnnotationMetadata metadata = AnnotationMetadata.introspect(beanClass) ;
// 獲取注解類@Configuration信息
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
// 如果@Configuration中的proxyBeanMethods屬性為true,那么就將當前配置類對應的BeanDefinition設置屬性
// 標記為true,其實這里的目的就是要不要創(chuàng)建代理,如果為true創(chuàng)建代理
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
// 不創(chuàng)建代理
else if (config != null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
}
}
上面對配置類進行了標記要不要創(chuàng)建代理,下面就是創(chuàng)建代理了。
3.2 為配置類生成代理
上面對配置類要不要創(chuàng)建代理是通過BeanDefinition 設置屬性的方式來標記,標記完后會在postProcessBeanFactory中創(chuàng)建代理。
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// ...
// 增強配置類,創(chuàng)建代理
enhanceConfigurationClasses(beanFactory);
}
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
// 獲取設置的標記屬性,要么是full,要么是lite
Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
// 先保存到集合匯總
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
// 確定配置類的class
Class<?> configClass = beanDef.getBeanClass();
// 創(chuàng)建代理
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
beanDef.setBeanClass(enhancedClass);
}
}
}
}
接下來是核心通過ConfigurationClassEnhancer#enhance創(chuàng)建目標配置類的代理對象。
class ConfigurationClassEnhancer {
private static final Callback[] CALLBACKS = new Callback[] {
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
}
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
return enhancedClass;
}
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(configSuperClass);
// 設置生成的子類要實現的接口,該接口實現了BeanFactoryAware,
// 所以容器在實例化初始化該代理對象的時候會自動注入當前容器的BeanFactory對象。
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
enhancer.setUseFactory(false);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
// 這里有個作用就是為當前的代理bean添加BeanFactory類型的字段。
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}
private Class<?> createClass(Enhancer enhancer) {
Class<?> subclass = enhancer.createClass();
Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
return subclass;
}
}
上面的代碼就是通過CGLIB創(chuàng)建代理,這些不是我們關心的,我們主要關心的是它是如何攔截配置方法的。所以這里我們主要關注的上面createClass方法中設置的CALLBACKS。在這個數組中通過名稱也就清楚核心攔截器是BeanMethodInterceptor。
private static class BeanMethodInterceptor implements MethodInterceptor {
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
// 獲取當前@Bean注解的方法名(也就是對應的BeanName)
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
// ...
// 從容器中查找對應的bean(也就是你調用的那個方法創(chuàng)建的bean對象)
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
ConfigurableBeanFactory beanFactory, String beanName) {
// 獲取bean實例
Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
beanFactory.getBean(beanName));
// ...
return beanInstance;
}
}
以上就是@Configuration注解創(chuàng)建代理及方法調用時的執(zhí)行原理。
你學到了嗎?