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 实例。

转自:https://blog.csdn.net/isea533/article/details/78072133

Spring @Configuration 和 @Component 区别的更多相关文章

  1. pring @Configuration 和 @Component 区别

    一句话概括就是 @Configuration 中所有带 @Bean 注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例. 从定义来看, @Configuration 注解本质上还是 @Com ...

  2. @configuration和@component之间的区别

    @configuration和@component之间的区别是:@Component注解的范围最广,所有类都可以注解,但是@Configuration注解一般注解在这样的类上:这个类里面有@Value ...

  3. Spring Configuration Check Unmapped Spring configuration files found

    Spring Configuration Check Unmapped Spring configuration files found 项目中有xml文件,但没有被用IntelliJ 导入现有工程时 ...

  4. IntelliJ 15 unmapped spring configuration files found

    IntelliJ Spring Configuration Check 用IntelliJ 导入现有工程时,如果原来的工程中有spring,每次打开工程就会提示:Spring Configuratio ...

  5. Spring与SpringMVC的区别

    Spring是IOC和AOP的容器框架,SpringMVC是基于Spring功能之上添加的Web框架,想用SpringMVC必须先依赖Spring. 简单点的话可以将SpringMVC类比于Strut ...

  6. 出现unmapped spring configuration files found

    intell idea启动出现unmapped spring configuration files found提示. 把spring里面的内容都打勾.

  7. IntelliJ IDEA 2017 提示“Unmapped Spring configuration files found.Please configure Spring facet.”解决办法

    当把自己的一个项目导入IDEA之后,Event Log提示“Unmapped Spring configuration files found.Please configure Spring face ...

  8. "Unmapped Spring configuration files found.Please configure Spring facet."解决办法

    最近在学习使用IDEA工具,觉得与Eclipse相比,还是有很多的方便之处. 但是,当把自己的一个项目导入IDEA之后,Event Log提示"Unmapped Spring configu ...

  9. Spring 学习——Spring常用注解——@Component、@Scope、@Repository、@Service、@Controller、@Required、@Autowired、@Qualifier、@Configuration、@ImportResource、@Value

    Bean管理注解实现 Classpath扫描与组件管理 类的自动检测与注册Bean 类的注解@Component.@Service等作用是将这个实例自动装配到Bean容器中管理 而类似于@Autowi ...

随机推荐

  1. net core体系-网络数据采集(AngleSharp)-1初探

    有这么一本Python的书: <<Python 网络数据采集>> 我准备用.NET Core及第三方库实现里面所有的例子. 这是第一部分, 主要使用的是AngleSharp:  ...

  2. Codeforces 922F Divisibility 构造

    Divisibility 我们考虑删数字 首先我们可以发现有一类数很特殊就是大于 n / 2的素数, 因为这些素数的贡献只有1, 并且在n大的时候, 这些素数的个数不是很少, 我们可以最后用这些数去调 ...

  3. BZOJ1911 [Apio2010]特别行动队 - 动态规划 - 斜率优化

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 UPD(2018-04-01):用Latex重打了公式…… 题意概括 把一个整数序列划分成任意连续的段,使得划分出 ...

  4. Ubuntu 硬盘分区只读,重新挂载为读写分区之后,文件依然创建出错

    原因: 分区只读,可能是windows没有正常关机,或者使用了混合休眠模式. 解决方案: sudo mount -o remount,rw /dev/sdaX 若重新挂载后,创建文件以及文件夹失败: ...

  5. spring的webutils包。适用于访问httpservletrequest和httpservletresponse

    WebUtils位 于 org.springframework.web.util 包中的 WebUtils 是一个非常好用的工具类,它对很多 Servlet API 提供了易用的代理方法,降低了访问 ...

  6. UVA 796 Critical Links(模板题)(无向图求桥)

    <题目链接> 题目大意: 无向连通图求桥,并将桥按顺序输出. 解题分析: 无向图求桥的模板题,下面用了kuangbin的模板. #include <cstdio> #inclu ...

  7. Wan Android 项目总结

    Wan Android 项目总结 项目的由来 这个项目也算是自己学习了一段时间的Android以后的一个总结和学习吧,项目采用了Kotlin语言,Api采用的hongyang大神的WanAndroid ...

  8. php文件及文件夹操作(创建、删除、移动、复制)

    <?php /** * 操纵文件类 * * 例子: * FileUtil::createDir('a/1/2/3'); 测试建立文件夹 建一个a/1/2/3文件夹 * FileUtil::cre ...

  9. Redis自学笔记:4.3进阶-排序

    4.3排序 4.3.1有序集合的集合操作 有序集合没有zinter和zunion命令,使用其他命令实现方法: multi zinterstore tempKey ... zrange tempKey ...

  10. C#中的BeginInvoke和EndEndInvoke 异步问题