JAVA面试题分享四百一十九:@Configuration注解天天用,你真的了解它吗?

本文详细解释了Spring框架中@Configuration注解的使用,阐述了它如何替代XML配置,以及在实例化过程中如何通过CGLIB生成代理以保证方法调用的单例行为。文中还对比了@Component与@Configuration的区别,并揭示了其背后的实现原理。
摘要由CSDN通过智能技术生成

目录

1. 简介

2. 应用示例

3. 实现原理


1. 简介

@Configuration 是一个类级注解,表示一个对象是 Bean 定义的来源。@Configuration 类通过 @Bean 注解的方法声明 Bean。对 @Configuration 类上 @Bean 方法的调用也可用于定义Bean之间的依赖关系。

使用@Configuration注解的主要作用是替代Spring的applicationContext.xml文件,使得配置更加灵活和方便。当某个类标注了@Configuration注解时,表示这个类是Spring的一个配置类,能够自动注册到IOC容器并进行实例化。

2. 应用示例

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

AppConfig是一个配置类,在该类中通过@Bean标注方法注册Bean对象。示例非常的简单,但是不是一定的用@Configuration呢?换成@Component试试

@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有什么区别呢?接着看下面代码示例:

@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()方法,执行结果:

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

控制台输出的一模一样,是不是感觉好奇怪,调用2次应该是不同的Person对象才对是吧?先继续往下看,吧@Configuration换成@Component(就是换注解,其它都没有变化,代码就不贴了),执行结果:

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

这次输出是2个不同的Person对象了,此时你是不是觉得这次符合你的预期?如果你这么认为那么就出大事了。

在 Spring 中,实例化的 Bean 默认具有单例作用域,但是如上执行情况,明显成了多例,我们就应该确保容器中任何时候使用的都是同一个实例。如果这里是DataSource那问题就更加严重了。

所以,这里虽然可以使用@Component定义配置类,但是强烈不建议你这样使用,既然是配置类你就的按规矩来使用@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,其实这里的目的就是要不要创建代理,如果为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;    }  }}

上面对配置类进行了标记要不要创建代理,下面就是创建代理了。

3.2 为配置类生成代理

上面对配置类要不要创建代理是通过BeanDefinition 设置属性的方式来标记,标记完后会在postProcessBeanFactory中创建代理。

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创建目标配置类的代理对象。

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

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注解创建代理及方法调用时的执行原理。

你学到了吗?

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

之乎者也·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值