Spring @Configuration和@Component的区别
spring @configuration 和 @component 区别
一句话概括就是 @configuration 中所有带 @bean 注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。
下面看看实现的细节。
@configuration 注解:
@target(elementtype.type) @retention(retentionpolicy.runtime) @documented @component public @interface configuration { string value() default ""; }
从定义来看,@configuration
注解本质上还是@component
,因此<context:component-scan/>
或者 @componentscan
都能处理@configuration
注解的类。
@configuration
标记的类必须符合下面的要求:
- 配置类必须以类的形式提供(不能是工厂方法返回的实例),允许通过生成子类在运行时增强(cglib 动态代理)。
- 配置类不能是 final 类(没法动态代理)。
- 配置注解通常为了通过 @bean 注解生成 spring 容器管理的类,
- 配置类必须是非本地的(即不能在方法中声明,不能是 private)。
- 任何嵌套配置类都必须声明为static。
- @bean 方法可能不会反过来创建进一步的配置类(也就是返回的 bean 如果带有 @configuration,也不会被特殊处理,只会作为普通的 bean)。
加载过程
spring 容器在启动时,会加载默认的一些postprocessor
,其中就有configurationclasspostprocessor
,这个后置处理程序专门处理带有@configuration
注解的类,这个程序会在bean 定义加载完成后,在bean初始化前进行处理。主要处理的过程就是使用cglib
动态代理增强类,而且是对其中带有@bean
注解的方法进行处理。
在configurationclasspostprocessor 中的 postprocessbeanfactory 方法中调用了下面的方法:
/** * post-processes a beanfactory in search of configuration class beandefinitions; * any candidates are then enhanced by a {@link configurationclassenhancer}. * candidate status is determined by beandefinition attribute metadata. * @see configurationclassenhancer */ public void enhanceconfigurationclasses(configurablelistablebeanfactory beanfactory) { map<string, abstractbeandefinition> configbeandefs = new linkedhashmap<string, abstractbeandefinition>(); for (string beanname : beanfactory.getbeandefinitionnames()) { beandefinition beandef = beanfactory.getbeandefinition(beanname); if (configurationclassutils.isfullconfigurationclass(beandef)) { //省略部分代码 configbeandefs.put(beanname, (abstractbeandefinition) beandef); } } if (configbeandefs.isempty()) { // nothing to enhance -> return immediately 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); try { // set enhanced subclass of the user-specified bean class class<?> configclass = beandef.resolvebeanclass(this.beanclassloader); class<?> enhancedclass = enhancer.enhance(configclass, this.beanclassloader); if (configclass != enhancedclass) { //省略部分代码 beandef.setbeanclass(enhancedclass); } } catch (throwable ex) { throw new illegalstateexception( "cannot load configuration class: " + beandef.getbeanclassname(), ex); } } }
在方法的第一次循环中,查找到所有带有@configuration
注解的 bean 定义,然后在第二个 for 循环中,通过下面的方法对类进行增强:
class<?> enhancedclass = enhancer.enhance(configclass, this.beanclassloader);
然后使用增强后的类替换了原有的beanclass
:
beandef.setbeanclass(enhancedclass);
所以到此时,所有带有@configuration
注解的 bean 都已经变成了增强的类。
下面关注上面的enhance
增强方法,多跟一步就能看到下面的方法:
/** * creates a new cglib {@link enhancer} instance. */ private enhancer newenhancer(class<?> superclass, classloader classloader) { enhancer enhancer = new enhancer(); enhancer.setsuperclass(superclass); enhancer.setinterfaces(new class<?>[] {enhancedconfiguration.class}); enhancer.setusefactory(false); enhancer.setnamingpolicy(springnamingpolicy.instance); enhancer.setstrategy(new beanfactoryawaregeneratorstrategy(classloader)); enhancer.setcallbackfilter(callback_filter); enhancer.setcallbacktypes(callback_filter.getcallbacktypes()); return enhancer; }
通过 cglib 代理的类在调用方法时,会通过callbackfilter
调用,这里的callback_filter
如下:
// the callbacks to use. note that these callbacks must be stateless. private static final callback[] callbacks = new callback[] { new beanmethodinterceptor(), new beanfactoryawaremethodinterceptor(), noop.instance }; private static final conditionalcallbackfilter callback_filter = new conditionalcallbackfilter(callbacks);
其中beanmethodinterceptor
匹配方法如下:
@override public boolean ismatch(method candidatemethod) { return beanannotationhelper.isbeanannotated(candidatemethod); } //beanannotationhelper public static boolean isbeanannotated(method method) { return annotatedelementutils.hasannotation(method, bean.class); }
也就是当方法有@bean
注解的时候,就会执行这个回调方法。
另一个beanfactoryawaremethodinterceptor
匹配的方法如下:
@override public boolean ismatch(method candidatemethod) { return (candidatemethod.getname().equals("setbeanfactory") && candidatemethod.getparametertypes().length == 1 && beanfactory.class == candidatemethod.getparametertypes()[0] && beanfactoryaware.class.isassignablefrom(candidatemethod.getdeclaringclass())); }
当前类还需要实现beanfactoryaware
接口,上面的ismatch
就是匹配的这个接口的方法。
@bean 注解方法执行策略
先给一个简单的示例代码:
@configuration public class mybeanconfig { @bean public country country(){ return new country(); } @bean public userinfo userinfo(){ return new userinfo(country()); } }
相信大多数人第一次看到上面 userinfo() 中调用 country() 时,会认为这里的 country 和上面 @bean 方法返回的 country 可能不是同一个对象,因此可能会通过下面的方式来替代这种方式:
@autowired
private country country;实际上不需要这么做(后面会给出需要这样做的场景),直接调用 country() 方法返回的是同一个实例。
下面看调用 country() 和 userinfo() 方法时的逻辑。
现在我们已经知道@configuration
注解的类是如何被处理的了,现在关注上面的beanmethodinterceptor
,看看带有 @bean
注解的方法执行的逻辑。下面分解来看intercept
方法。
//首先通过反射从增强的 configuration 注解类中获取 beanfactory configurablebeanfactory beanfactory = getbeanfactory(enhancedconfiginstance); //然后通过方法获取 beanname,默认为方法名,可以通过 @bean 注解指定 string beanname = beanannotationhelper.determinebeannamefor(beanmethod); //确定这个 bean 是否指定了代理的范围 //默认下面 if 条件 false 不会执行 scope scope = annotatedelementutils.findmergedannotation(beanmethod, scope.class); if (scope != null && scope.proxymode() != scopedproxymode.no) { string scopedbeanname = scopedproxycreator.gettargetbeanname(beanname); if (beanfactory.iscurrentlyincreation(scopedbeanname)) { beanname = scopedbeanname; } } //中间跳过一段 factorybean 相关代码 //判断当前执行的方法是否为正在执行的 @bean 方法 //因为存在在 userinfo() 方法中调用 country() 方法 //如果 country() 也有 @bean 注解,那么这个返回值就是 false. if (iscurrentlyinvokedfactorymethod(beanmethod)) { // 判断返回值类型,如果是 beanfactorypostprocessor 就写警告日志 if (logger.iswarnenabled() && beanfactorypostprocessor.class.isassignablefrom(beanmethod.getreturntype())) { logger.warn(string.format( "@bean method %s.%s is non-static and returns an object " + "assignable to spring's beanfactorypostprocessor interface. this will " + "result in a failure to process annotations such as @autowired, " + "@resource and @postconstruct within the method's declaring " + "@configuration class. add the 'static' modifier to this method to avoid " + "these container lifecycle issues; see @bean javadoc for complete details.", beanmethod.getdeclaringclass().getsimplename(), beanmethod.getname())); } //直接调用原方法创建 bean return cglibmethodproxy.invokesuper(enhancedconfiginstance, beanmethodargs); } //如果不满足上面 if,也就是在 userinfo() 中调用的 country() 方法 return obtainbeaninstancefromfactory(beanmethod, beanmethodargs, beanfactory, beanname);
关于iscurrentlyinvokedfactorymethod
方法
可以参考 simpleinstantiationstrategy 中的 instantiate 方法,这里先设置的调用方法:
currentlyinvokedfactorymethod.set(factorymethod); return factorymethod.invoke(factorybean, args);
而通过方法内部直接调用 country() 方法时,不走上面的逻辑,直接进的代理方法,也就是当前的 intercept方法,因此当前的工厂方法和执行的方法就不相同了。
obtainbeaninstancefromfactory
方法比较简单,就是通过beanfactory.getbean
获取country
,如果已经创建了就会直接返回,如果没有执行过,就会通过invokesuper
首次执行。
因此我们在@configuration
注解定义的 bean 方法中可以直接调用方法,不需要@autowired
注入后使用。
@component 注意
@component
注解并没有通过 cglib 来代理@bean
方法的调用,因此像下面这样配置时,就是两个不同的 country。
@component public class mybeanconfig { @bean public country country(){ return new country(); } @bean public userinfo userinfo(){ return new userinfo(country()); } }
有些特殊情况下,我们不希望mybeanconfig
被代理(代理后会变成webmvcconfig$$enhancerbyspringcglib$$8bef3235293
)时,就得用@component
,这种情况下,上面的写法就需要改成下面这样:
@component public class mybeanconfig { @autowired private country country; @bean public country country(){ return new country(); } @bean public userinfo userinfo(){ return new userinfo(country); } }
这种方式可以保证使用的同一个country
实例。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。如果你想了解更多相关内容请查看下面相关链接