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,因此 `` 或者 @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 immediatelyreturn;}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 classbeanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);try {// Set enhanced subclass of the user-specified bean classClass<?> 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 {@Beanpublic Country country(){return new Country();}@Beanpublic 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()));}//直接调用原方法创建 beanreturn cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
//如果不满足上面 if,也就是在 userInfo() 中调用的 country() 方法
return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);

关于 isCurrentlyInvokedFactoryMethod 方法

可以参考 SimpleInstantiationStrategy 中的 instantiate 方法,这里先设置的调用方法:

currentlyInvokedFactoryMethod.set(factoryMethod);
return factoryMethod.invoke(factoryBean, args);12

而通过方法内部直接调用 country() 方法时,不走上面的逻辑,直接进的代理方法,也就是当前的 intercept方法,因此当前的工厂方法和执行的方法就不相同了。

obtainBeanInstanceFromFactory 方法比较简单,就是通过 beanFactory.getBean 获取 Country,如果已经创建了就会直接返回,如果没有执行过,就会通过 invokeSuper 首次执行。

因此我们在 @Configuration 注解定义的 bean 方法中可以直接调用方法,不需要 @Autowired 注入后使用。

@Component 注意

@Component 注解并没有通过 cglib 来代理@Bean 方法的调用,因此像下面这样配置时,就是两个不同的 country。

@Component
public class MyBeanConfig {@Beanpublic Country country(){return new Country();}@Beanpublic UserInfo userInfo(){return new UserInfo(country());}}

有些特殊情况下,我们不希望 MyBeanConfig 被代理(代理后会变成WebMvcConfig$$EnhancerBySpringCGLIB$$8bef3235293)时,就得用 @Component,这种情况下,上面的写法就需要改成下面这样:

@Component
public class MyBeanConfig {@Autowiredprivate Country country;@Beanpublic Country country(){return new Country();}@Beanpublic UserInfo userInfo(){return new UserInfo(country);}}

这种方式可以保证使用的同一个 Country 实例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/556597.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

django mysql 过滤所有id_Django数据库中常用的过滤操作

# 获取所有数据&#xff0c;返回QuerySet&#xff0c;里面包含的是对象models.DjangoInfo.objects.all()# 获取所有的id&#xff0c;name数据&#xff0c;返回QuerySet&#xff0c;里面包含的是字典models.DjangoInfo.objects.all().values("id","name")# …

uvc能支持多大分辨率_华为手环B5测评:该有的功能一个不缺,甚至还能变身蓝牙耳机...

前阵子&#xff0c;华为召开nova5系列新品发布会的同时&#xff0c;也给用户们带来了华为手环B5的新配色--铅石青。华为手环B5在去年7月发布&#xff0c;分为三个版本&#xff1a;运动版999元、商务版1199元、时尚版1499元。​此次更新的是运动版&#xff0c;售价仍为999元&…

springboot整合Mybatis提示org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)

在用maven配置mybatis环境时出现此BindingExceptiony异常&#xff0c;发现在classes文件下没有mapper配置文件&#xff0c;应该是maven项目没有扫描到mapper包下的xml文件&#xff0c; 在pom.xml中加入一下代码可以解决&#xff1a; 1.首先在pom.xml中,将xxxmapper.xml添加到类…

cf不能全屏win7的解决方法_win7系统局域网不能访问怎么办 win7局域网不能访问解决方法...

在局域网当中比较实用的功能要数共享文件了&#xff0c;局域网共享可以让用户们共享和管理资源更加便捷&#xff0c;可以大大提高工作效率&#xff0c;不过最近有位win7系统用户使用电脑的时候&#xff0c;发现电脑无法访问局域网中的其他任何一台电脑&#xff0c;这让用户不知…

CGLib动态代理原理

CGLib动态代理原理 CGLib动态代理是代理类去继承目标类&#xff0c;然后重写其中目标类的方法啊&#xff0c;这样也可以保证代理类拥有目标类的同名方法&#xff1b; 看一下CGLib的基本结构&#xff0c;下图所示&#xff0c;代理类去继承目标类&#xff0c;每次调用代理类的方…

sql读取excel数据_Python 读取 Excel 数据,并插入到MySQL

说实话&#xff0c;个人不建议用Python来读取Excel进行入库操作&#xff0c;有条件的话&#xff0c;可以尝试用 ETL 工具&#xff0c;快速导入到MySQL中&#xff0c;或者也可使用 SQL 的导入工具进行。写本文的目的在于&#xff1a;记录一下之前做过一次这个同类型的东西。也为…

无向图的深度优先遍历非递归_图算法总结

&#xfeff;[TOC]图算法1、图的表示1.1、邻接矩阵&#xff08;有向图、无向图、带权图、代码实现&#xff09;1、无向图的邻接矩阵 2、有向图的邻接矩阵 3、带权值的图 有了上述的理解&#xff0c;我们可以设计数据结构&#xff0c;并实现了。C实现如下&#xff1a;#include1.…

华为ipd项目管理流程_IPD:一套卓越的产品开发经营体系

IPD全流程指的不仅仅是“产品开发流程”&#xff0c;而是如何实现产品“从机会到商业变现”的全流程。华为通过20年的全流程实践&#xff0c;才做到持续性推出高质量产品和解决方案。本期IPD全流程班&#xff0c;帮助企业研发管理者学习这套结构化流程&#xff0c;其中划下重点…

brew 安装mysql5.6_MacOS 10.14安装和brew更新后无法安装mysql gem

在macOS 10.14安装Brew更新后,我的开发系统坏了.我无法安装mysql gem.在尝试安装时,我得到&#xff1a;$gem install mysql -v 2.8.1 --source http://rubygems.org/Building native extensions. This could take a while...ERROR: Error installing mysql:ERROR: Failed to bu…

Springboot中@ComponentScan 注解

三个点&#xff1a; 1、工程中Application类的位置。默认情况下就不需要配置ComponentScan这个注解了。 因为Application类&#xff0c;在启动的时候&#xff0c;默认是加载和Application类所在同一个目录下的所有类&#xff0c;包括所有子目录下的类。所以一般情况下&#xff…

在react里写原生js_小程序原生开发与第三方框架选择

最近正在更新《微信小程序入门与实践》一书的第二版。书中有一章节谈到了”多样化的小程序开发“&#xff0c;摘取并加以整理分享给各位开发者。我一向不推荐也不提倡公众号阅读学习编程&#xff0c;文章更多的是列出小程序如今多样化的框架选择&#xff0c;并简单剖析它们之间…

springboot中service层注入的是实现类,但Controller层接收的是接口

我们习惯这样编写代码&#xff1a; 在service层写接口&#xff0c;然后用实现类去实现接口&#xff0c;并且将实现类注入到容器中 Service public class AccountServiceImpl implements AccountService {}在controller层却是用接口操作service的bean的方法&#xff0c; Auto…

mysql data transfer_MySQL主从同步加速 Transfer-- FAQ

Q: Transfer是什么A: 是一个解决MySQL原生主从同步延迟的方案。 Transfer本身是一个在MySQL源码上打的patch&#xff0c;可以用于当Slave&#xff0c;也可以用于当第三方工具&#xff0c;将Master的数据同步发给Slave。 利用多线程实现主从无延迟。Q: Transfer目前的发布形式&a…

springboot整合mybatisplus中@Mapper与@MapperScan的使用

一、Mapper与MapperScan不可同时使用 二、Mapper用于注解单个mapper接口 三、Mapper Scan用于批量注解Mapper接口 四、Mapper不起作用时&#xff0c;因为缺少下图中的依赖包 以上为自己整合框架中遇到的问题与解决方案&#xff0c;如果以上解决方案不能解决您的问题&#x…

html列表按时间排序代码_按字母顺序排序的列表

有时候你需要自制一份课件PPT或者手写笔记的关键词索引&#xff0c;用来开卷考试查阅PPT或者复习查阅笔记时能快速定位查阅内容位置&#xff0c;这时你可能想要一个按字母顺序排序的列表来帮助构建需要的索引。LaTeX自身的index本身并不支持这样的功能&#xff0c;修改起来也颇…

@MapperScan和@ComponentScan使用问题

报错如下 原因&#xff1a;当MapperScan和ComponentScan一起使用时&#xff0c;项目启动时扫描包会发生冲突&#xff0c;找不到swagger配置类的包和mapper接口的包 解决办法&#xff1a;MapperScan和ComponentScan可以一起使用。 改为MapperScan&#xff08;basePackages {}…

C语言中CY位什么时候才能为1_你真的了解C语言中的整型吗?

整型数据类型1. 整型数据类型在上一节当中&#xff0c;我们遇到了整型(integer)int&#xff0c;用来表示一个整数的数据类型。下面呢我们来讨论一下C语言里面的整数数据类型。看到这里你可能想问&#xff0c;一个整数而已&#xff0c;为什么会需要定义这么多的类型出来呢?要知…

python2中xrange比range优点_【Python面试】 说说Python中xrange和range的区别?

公众号新增加了一个栏目&#xff0c;就是每天给大家解答一道Python常见的面试题&#xff0c;反正每天不贪多&#xff0c;一天一题&#xff0c;正好合适&#xff0c;只希望这个面试栏目&#xff0c;给那些正在准备面试的同学&#xff0c;提供一点点帮助&#xff01;小猿会从最基…

Spring的两种动态代理:Jdk和Cglib 的区别和实现

Spring的两种动态代理&#xff1a;Jdk和Cglib 的区别和实现 一、原理区别&#xff1a; java动态代理是利用反射机制生成一个实现代理接口的匿名类&#xff0c;在调用具体方法前调用InvokeHandler来处理。 而cglib动态代理是利用asm开源包&#xff0c;对代理对象类的class文件…

python语言程序设计难不难_零基础学Python编程开发难度大吗?从哪学起?

转行零基础学Python编程开发难度大吗&#xff1f;从哪学起&#xff1f; 近期很多小伙伴问我&#xff0c;如果自己转行学习Python&#xff0c;完全0基础能否学会呢&#xff1f;Python的难度到底有多大&#xff1f; 今天&#xff0c;小编就来为大家详细解读一下这个问题。 Python…