当前位置: 首页 > 后端技术 > Java

@Component和@Configuration的区别

时间:2023-04-02 10:03:36 Java

第一眼看到这个题目,相信大家心里都会有一个想法:这不都是Spring的注解吗?有这两个注解的类,最后会封装成BeanDefinition,交给Spring管理,能有什么区别呢?首先,让我向您展示一个示例代码:AnnotationBean.javaimportlombok.Data;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation。配置;importorg.springframework.stereotype.Component;@Component//@ConfigurationpublicclassAnnotationBean{@Qualifier("innerBean1")@Bean()publicInnerBeaninnerBean1(){returnnewInnerBean();}@BeanpublicInnerBeanFactoryinnerBeanFactory(){InnerBeanFactory工厂=newInnerBeanFactory();factory.setInnerBean(innerBean1());返厂;}publicstaticclassInnerBean{}@DatapublicstaticclassInnerBeanFactory{privateInnerBeaninnerBean;}}AnnotationTest.java@Testvoidtest7(){AnnotationConfigApplicationContextapplicationContext=newAnnotationConfigA应用程序上下文(基础包);对象bean1=applicationContext.getBean("innerBean1");ObjectfactoryBean=applicationContext.getBean("innerBeanFactory");inthashCode1=bean1.hashCode();InnerBeaninnerBeanViaFactory=((InnerBeanFactory)factoryBean).getInnerBean();inthashCode2=innerBeanViaFactory.hashCode();Assertions.assertEquals(hashCode1,hashCode2);}大家可以先猜猜这个test7()的执行结果是成功还是失败?答案是失败。如果把AnnotationBean的注解从@Component改成@Configuration,那么test7()就会执行成功。为什么?通常Spring管理的bean不都是单例的吗?不着急,让笔者慢慢来~~~以下是来自Spring-source-5.2.8的两个注解的声明@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Indexedpublic@interfaceComponent{/***该值可能表示对逻辑组件名称的建议,*在自动检测到组件的情况下将其转换为Springbean。*@return建议的组件名称,如果有的话(否则为空字符串)*/Stringvalue()default"";}----------------------------------这是分界线--------------------------------@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Componentpublic@interfaceConfiguration{/***显式指定与*{@code@Configuration}类关联的Springbean定义的名称.如果未指定(常见情况),将自动生成一个bean*名称。*

自定义名称仅在选择{@code@Configuration}类时适用*通过组件扫描或直接提供给*{@linkAnnotationConfigApplicationContext}。如果{@code@Configuration}类*注册为传统的XMLbean定义,则bean*元素的名称/id将优先。*@return明确的组件名称,如果有的话(否则为空字符串)*@seeAnnotationBeanNameGenerator*/@AliasFor(annotation=Component.class)Stringvalue()default"";/***指定是否应代理{@code@Bean}方法以强制执行*bean生命周期行为,例如即使在用户代码中直接调用{@code@Bean}方法时,也可以返回共享单例bean实例。此功能*需要方法拦截,通过运行时生成的CGLIB*子类实现,该子类具有配置类和*其方法不允许声明{@codefinal}等限制。*

默认值它是{@codetrue},允许通过直接*配置类中的方法调用进行“bean间引用”,以及外部调用*此配置的{@code@Bean}方法,例如来自另一个配置类。*如果不需要,因为每个特定配置的{@code@Bean}*方法都是自包含的,并且设计为容器使用的普通工厂方法,*将此标志切换为{@codefalse}以避免CGLIB子类处理。*

关闭bean方法拦截有效地处理{@code@Bean}*单独的方法,就像在非{@code@Configuration}类上声明时一样,*a.k.a.“@BeanLiteMode”(参见{@linkBean@Bean's文档})。因此*在行为上等同于移除{@code@Configuration}刻板印象。*@since5.2*/booleanproxyBeanMethods()defaulttrue;}从这两个注解的定义中,可能大家已经看到了一点端倪:@Configuration比@Component多了一个成员变量booleanproxyBeanMethods()默认值为true。从这个成员变量的注释中,我们可以看到一句话Specifywhether{@code@Bean}methodsshouldgetproxiedinordertoenforcebeanlifecyclebehavior,e.g.即使在用户代码中直接调用{@code@Bean}方法的情况下,也可以返回共享单例bean实例。其实从这句话中,我们可以初步得到我们想要的答案:在一个带有@Configuration注解的类中,一个带有@Bean注解的A方法显式调用另一个带有@Bean注解的方法,并返回一个共享的单例对象。下面我们从Spring源码实现的角度来看一下原理。从Spring源码实现来看,可以Spring作者在实现注解的时候,一般都是先收集分析,然后调用@Configuration是基于@Component实现的。在@Component的解析过程中,我们可以看到如下逻辑:=null&&!Boolean.FALSE.equals(config.get("proxyBeanMethods"))){beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE,CONFIGURATION_CLASS_FULL);}elseif(config!=null||isConfigurationCandidate(metadata)){beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE,CONFIGURATION_CLASS_LITE);}默认情况下,Spring在将带有@Configuration注解的类封装到BeanDefinition中时,会设置一个属性CONFIGURATION_CLASS_ATTRIBUTE,属性值为CONFIGURATION_CLASS_FULL,否则,只有@Component注解,则属性值为CONFIGURATION_CLASS_LITE(这个属性值很重要)。在@Component注解的调用过程中,有如下逻辑:org.springframework.context.annotation.ConfigurationClassPostProcessor#enhanceConfigurationClassesfor(StringbeanName:beanFactory.getBeanDefinitionNames()){......if((configClassAttr!=null||methodMetadata!=null)&&beanDefinstanceofAbstractBeanDefinition){......if(ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)){if(!(beanDefinstanceofAbstractBeanDefinition)){thrownewBeanDefinitionStoreException("无法增强@Configurationbean定义'"+beanName+"'因为它没有存储在AbstractBeanDefinition子类中");}elseif(logger.isInfoEnabled()&&beanFactory.containsSingleton(beanName)){logger.info("Cannotenhance@Configurationbeandefinition'"+beanName+"'sinceitssingletoninstancehasbeencreatedtooearly.Thetypicalcause"+“是一个带有BeanDefinitionRegistryPostProcessor的非静态@Bean方法”+“返回类型:考虑将此类方法声明为‘静态’。”);}configBeanDefs.put(beanName,(摘要BeanDefinition)beanDef);}}}if(configBeanDefs.isEmpty()){//nothingtoenhance->returnimmediatelyreturn;}ConfigurationClassEnhancerenhancer=newConfigurationClassEnhancer();...ifBeanDefinition的ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE属性如果值为ConfigurationClassUtils.CONFIGURATION_CLASS_FULL,则BeanDefinition对象将被添加到MapconfigBeanDefs容器中。如果Spring发现Map为空,则认为不需要代理增强,立即返回;否则,由类(本文中被代理的类为AnnotationBean,以下简称此类)创建代理。所以如果这个类的注解是@Component,当用@Bean注解调用innerBean1()方法时,这个对象就是Spring容器中真正的单例对象,比如AnnotationBean@4149.@BeanpublicInnerBeanFactoryinnerBeanFactory(){InnerBeanFactoryfactory=新的InnerBeanFactory();factory.setInnerBean(innerBean1());returnfactory;}然后,在上面的方法中每次调用innerBean1()方法,都势必返回一个新创建的InnerBean对象。如果该类的注解为@Configuration,则该对象为Spring生成的AnnotationBean的代理对象,例如AnnotationBean$$EnhancerBySpringCGLIB$$90f8540c@4296,增强逻辑如下//要使用的回调。注意这些回调必须是stateless.privatestaticfinalCallback[]CALLBACKS=newCallback[]{newBeanMethodInterceptor(),newBeanFactoryAwareMethodInterceptor(),NoOp.INSTANCE};-------------------------------这是分界线------------------------------/***创建一个新的CGLIB{@linkEnhancer}实例。*/privateEnhancernewEnhancer(ClassconfigSuperClass,@NullableClassLoaderclassLoader){Enhancerenhancer=newEnhancer();enhancer.setSuperclass(configSuperClass);增强剂。setInterfaces(newClass[]{EnhancedConfiguration.class});enhancer.setUseFactory(false);enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);enhancer.setStrategy(newBeanFactoryAwareGeneratorStrategy(classLoader));增强器.setCallbackFilter(CALLB)enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());returnenhancer;}在上述方法中调用innerBean1()时,ConfigurationClassEnhancer会遍历3个回调方法来判断本次调用应该使用哪个回调方法,第一个回调类型成功匹配org.springframework.context.annotation.ConfigurationClassEnhancer。BeanMethodInterceptor#isMatch匹配过程如下:@OverridepublicbooleanisMatch(MethodcandidateMethod){return(candidateMethod.getDeclaringClass()!=Object.class&&!BeanFactoryAwareMethodInterceptor.AnnotHannotper.isSetBeanFactory&BeanFactoryisBeanAnnotated(candidateMethod));}匹配成功后,使用org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#intercept拦截innerBean1()方法的调用。在这个例子中,innerBean1()被enhancer调用了两次,第一次是Spring解析带有@Bean注解的innerBean1()方法,将构造好的InnerBean对象添加到Spring单例池中。第二次调用是Spring解析带有@Bean注解的innerBeanFactory()方法。方法显式调用innerBean1()。在第二次调用时,增强过程如下所示:org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInte接受器#resolveBeanReferenceObjectbeanInstance=(useArgs?beanFactory.getBean(beanName,beanMethodArgs):beanFactory.getBean(beanName));看到这里,相信大家和笔者一样明白@Component注解和@Configuration注解的区别了:默认情况下,带有@Configuration注解的类在被Spring解析时,会使用切面进行字节码增强。在用@Bean解析方法innerBeanFactory()时,该方法内部显式调用另一个@Bean注解方法innerBean1(),那么返回的对象与Spring第一次用@Bean解析方法innerBean1()生成的单例对象相同注解。