日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

@Configuration注解天天用,你真的了解它嗎?

來源: 責編: 時間:2024-01-08 09:15:14 203觀看
導讀環境:Spring5.3.231. 簡介@Configuration 是一個類級注解,表示一個對象是 Bean 定義的來源。@Configuration 類通過 @Bean 注解的方法聲明 Bean。對 @Configuration 類上 @Bean 方法的調用也可用于定義Bean之間的依賴關

環境:Spring5.3.23cy728資訊網——每日最新資訊28at.com

1. 簡介

@Configuration 是一個類級注解,表示一個對象是 Bean 定義的來源。@Configuration 類通過 @Bean 注解的方法聲明 Bean。對 @Configuration 類上 @Bean 方法的調用也可用于定義Bean之間的依賴關系。cy728資訊網——每日最新資訊28at.com

使用@Configuration注解的主要作用是替代Spring的applicationContext.xml文件,使得配置更加靈活和方便。當某個類標注了@Configuration注解時,表示這個類是Spring的一個配置類,能夠自動注冊到IOC容器并進行實例化。cy728資訊網——每日最新資訊28at.com

2. 應用示例

static class Person {}@Configurationstatic class AppConfig {  @Bean  public Person person() {    return new Person() ;  }}

AppConfig是一個配置類,在該類中通過@Bean標注方法注冊Bean對象。示例非常的簡單,但是不是一定的用@Configuration呢?換成@Component試試cy728資訊網——每日最新資訊28at.com

@Componentstatic 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有什么區別呢?接著看下面代碼示例:cy728資訊網——每日最新資訊28at.com

@Configurationstatic 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()方法,執行結果:cy728資訊網——每日最新資訊28at.com

com.pack.m.b.CMain$Person@55183b20com.pack.m.b.CMain$Person@55183b20

控制臺輸出的一模一樣,是不是感覺好奇怪,調用2次應該是不同的Person對象才對是吧?先繼續往下看,吧@Configuration換成@Component(就是換注解,其它都沒有變化,代碼就不貼了),執行結果:cy728資訊網——每日最新資訊28at.com

com.pack.m.b.CMain$Person@78aab498com.pack.m.b.CMain$Person@5dd6264

這次輸出是2個不同的Person對象了,此時你是不是覺得這次符合你的預期?如果你這么認為那么就出大事了。cy728資訊網——每日最新資訊28at.com

在 Spring 中,實例化的 Bean 默認具有單例作用域,但是如上執行情況,明顯成了多例,我們就應該確保容器中任何時候使用的都是同一個實例。如果這里是DataSource那問題就更加嚴重了。cy728資訊網——每日最新資訊28at.com

所以,這里雖然可以使用@Component定義配置類,但是強烈不建議你這樣使用,既然是配置類你就的按規矩來使用@Configuration注解。那@Configuration是如何保證在內部方法調用返回的對象是同一個呢?cy728資訊網——每日最新資訊28at.com

先給出答案:那是因為使用@Configuration注解的類被生成了代理類(通過CGLIB)。接下來我們來看看它的原理。
cy728資訊網——每日最新資訊28at.com

3. 實現原理

Spring提供了一個ConfigurationClassPostProcessor處理器來注冊@Configuration注解。該處理器是一個BeanFactoryPostProcessor。我們就從這里看起cy728資訊網——每日最新資訊28at.com

3.1 給@Configuration注解的類打標記

這里所說的大標記其實就確定你當前這個配置類要不要生成代理,而這個標記模式是生成代理
cy728資訊網——每日最新資訊28at.com

// 這里的proxyBeanMethods值為true,意為會為當前的配置類生成代理@Configuration(proxyBeanMethods = true)static class AppConfig {}

處理配置類cy728資訊網——每日最新資訊28at.com

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));      }    }    // ...  }}

ConfigurationClassUtilscy728資訊網——每日最新資訊28at.com

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,其實這里的目的就是要不要創建代理,如果為true創建代理      beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);    }    // 不創建代理    else if (config != null || isConfigurationCandidate(metadata)) {      beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);    }    else {      return false;    }  }}

上面對配置類進行了標記要不要創建代理,下面就是創建代理了。cy728資訊網——每日最新資訊28at.com

3.2 為配置類生成代理

上面對配置類要不要創建代理是通過BeanDefinition 設置屬性的方式來標記,標記完后會在postProcessBeanFactory中創建代理。cy728資訊網——每日最新資訊28at.com

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {    // ...    // 增強配置類,創建代理    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();      // 創建代理      Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);      if (configClass != enhancedClass) {        beanDef.setBeanClass(enhancedClass);      }    }  }}

接下來是核心通過ConfigurationClassEnhancer#enhance創建目標配置類的代理對象。cy728資訊網——每日最新資訊28at.com

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創建代理,這些不是我們關心的,我們主要關心的是它是如何攔截配置方法的。所以這里我們主要關注的上面createClass方法中設置的CALLBACKS。在這個數組中通過名稱也就清楚核心攔截器是BeanMethodInterceptor。cy728資訊網——每日最新資訊28at.com

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(也就是你調用的那個方法創建的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注解創建代理及方法調用時的執行原理。cy728資訊網——每日最新資訊28at.com

你學到了嗎?cy728資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-57870-0.html@Configuration注解天天用,你真的了解它嗎?

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 圖形編輯器開發:自定義光標

下一篇: Python 的 Sys 模塊:探索系統交互和運行時環境

標簽:
  • 熱門焦點
Top 主站蜘蛛池模板: 依安县| 聂荣县| 微博| 冷水江市| 丘北县| 金华市| 年辖:市辖区| 苍山县| 木里| 连州市| 武清区| 江北区| 章丘市| 凌海市| 伊吾县| 东源县| 松潘县| 黄梅县| 津南区| 济源市| 广汉市| 宜兴市| 平顶山市| 平定县| 长阳| 蕲春县| 呼玛县| 华池县| 且末县| 和静县| 北票市| 元朗区| 财经| 乌鲁木齐市| 文水县| 九江县| 达尔| 长春市| 金坛市| 岳阳县| 花莲县|