在Spring早期需要开发者使用到XML配置来配合开发非常的麻烦,在Spring3.0后引进了@Configuration注解,可以被AnnotationConfigApplicationContext和AnnotationConfigWebApplicationContext两个容器给扫描到,就可以实现基于注解来开发。相信各位读者在使用Spring来开发项目时,避免不了使用@Configuration注解标志位配置类,结合@ComponentScan和@MapperScan两个注解来扫描其他类注册到IoC容器中,也可以结合@Bean注解来使用,标有@Bean注解的方法的返回值将注入到IoC容器中,所以@Configuration注解功能是非常的重要,所以此帖从源码来讲解@Configuration注解底层。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args);
MyConfig config = (MyConfig)applicationContext.getBean("myConfig");
Cat cat1 = config.cat();
Cat cat2 = config.cat();
System.out.println(cat1 == cat2);
Cat cat = (Cat)applicationContext.getBean("cat");
System.out.println(cat==cat1);
}
}
/**
* @author liha
* @version 1.0
* @date 2022/3/22 10:04
* @description
*/
@Configuration
public class MyConfig {
@Bean
public Cat cat(){
return new Cat();
}
@Bean
public Dog dog(){
return new Dog();
}
}
public class Cat {
}
public class Dog {
}
可以看到控制台输出的结果,证明@Bean将Cat类给注入到IoC容器中,并且是单例的。
源码前的推理:
那么我们的入口在哪里呢?当琢磨不透的时候就可以看到源码类上的注释,可以看到有一个ConfigurationClassPostProcessor类。
这里注释讲解的特别清楚,就是此类用于引导@Configuration的处理,并且可以看到实现了BeanDefinitionRegistryPostProcessor接口,此接口就是重点了。
可以清楚的看到继承关系,所以需要重写这两个接口,所以我们推测这两个接口应该是用来解析@Configuration注解类,不过在具体查看实现体之前,我觉得有必要带各位读者来了解一下啥时候回调这接口。
我们知道整个Spring项目启动就是刷新上下文,所以肯定是在refresh()方法中,而从接口和方法名我们可以知道是关于BeanFactory工厂的一些后置操作。所以就可以推断出具体的在哪里回调。
postProcessBeanDefinitionRegistry具体实现
// 我们知道Spring中就喜欢接口回调来实现一些高扩张,所以这接口也不列外,具体回调地点后面会说
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 根据HashCode码来验证是否处理过了
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
// 具体的实现看下面的代码块
processConfigBeanDefinitions(registry);
}
上面代码块跳转到这里,processConfigBeanDefinitions()方法的具体实现
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {
return;
}
// Sort by previously determined @Order value, if applicable
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
application context
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set candidates = new LinkedHashSet<>(configCandidates);
Set alreadyParsed = new HashSet<>(configCandidates.size());
do {
parser.parse(candidates);
parser.validate();
Set configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}
可以看到代码确实特别特别的长,不过没关系,因为逻辑并不是很复杂,大概的一个流程如下:
注:想具体了解的可以parse()方法打断点追一追
postProcessBeanFactory具体实现
把所有标有@Configuration注解的BeanDefinition给获取到以后就要开始选择性动态代理了,因为标有@Configuration注解可以设置为不代理,比如Spring boot自动装配的绝大部分都是不启动代理的,因为只运行一次没必要代理浪费性能,而我们自定义的配置类就需要动态代理。
这个方法是实现BeanFactoryPostProcessor重写的方法,回调时机在上面方法之后,所以也就是先获取到BeanDefinition再回调这里来Cglib选择性代理。不懂Cglib的点击下面链接可以看笔者的。
从源码角度分析Cglib动态代理https://blog.csdn.net/qq_43799161/article/details/123604338?spm=1001.2014.3001.5501
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 这里老规矩,通过HASHCODE来判断是否运行过
int factoryId = System.identityHashCode(beanFactory);
if (this.factoriesPostProcessed.contains(factoryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + beanFactory);
}
this.factoriesPostProcessed.add(factoryId);
// 这里是没回调过上面的方法,因为需要BeanDefinition才能开始代理,所以这里手动回调一次。
if (!this.registriesPostProcessed.contains(factoryId)) {
// BeanDefinitionRegistryPostProcessor hook apparently not supported...
// Simply call processConfigurationClasses lazily at this point then.
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
}
// Cglib动态代理逻辑,详情看下面逻辑
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
上面方法跳转到这里,enhanceConfigurationClasses()方法详情,也就是Cglib代理的详情
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map configBeanDefs = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
MethodMetadata methodMetadata = null;
if (beanDef instanceof AnnotatedBeanDefinition) {
methodMetadata = ((AnnotatedBeanDefinition) beanDef).getFactoryMethodMetadata();
}
if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {
// Configuration class (full or lite) or a configuration-derived @Bean method
// -> resolve bean class at this point...
AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;
if (!abd.hasBeanClass()) {
try {
abd.resolveBeanClass(this.beanClassLoader);
}
catch (Throwable ex) {
throw new IllegalStateException(
"Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
}
}
}
if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
if (!(beanDef instanceof AbstractBeanDefinition)) {
throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
}
else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
logger.info("Cannot enhance @Configuration bean definition '" + beanName +
"' since its singleton instance has been created too early. The typical cause " +
"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
"return type: Consider declaring such methods as 'static'.");
}
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
if (configBeanDefs.isEmpty()) {
// nothing to enhance -> return immediately
return;
}
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry 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);
// Set enhanced subclass of the user-specified bean class
Class> configClass = beanDef.getBeanClass();
Class> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
}
beanDef.setBeanClass(enhancedClass);
}
}
}
代码量确实也挺大的,这里老规矩帮读者写一下执行流程,想追特别特别详细的可以自行Debug
所以我们接下来就仔仔细细讲解动态代理的过程。
public Class> enhance(Class> configClass, @Nullable ClassLoader classLoader) {
// 已经增强过就直接返回
if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Ignoring request to enhance %s as it has " +
"already been enhanced. This usually indicates that more than one " +
"ConfigurationClassPostProcessor has been registered (e.g. via " +
"). This is harmless, but you may " +
"want check your configuration and remove one CCPP if possible",
configClass.getName()));
}
return configClass;
}
// 增强的具体的逻辑,也就是查询缓存,缓存中没有就创建,注意这里并不是实例化,只会返回代理的Class
Class> enhancedClass = createClass(newEnhancer(configClass, classLoader));
if (logger.isTraceEnabled()) {
logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
configClass.getName(), enhancedClass.getName()));
}
return enhancedClass;
}
因为Cglib底层ASM技术来操作字节码,这需要对Java字节码技术非常熟悉,所以我们并不关系他是如果一行一行生成的代理字节码文件,我们关心的是生成后的字节码文件,所以我们通过某种方式获取到字节码反编译文件来反推。
运行一下项目就会生成在指定的target包下
我们可以很清楚的看到cat和dog两个方法都生成了两个不同的方法,相信各位读者有Cglib的基础这里就不对反编译字节码过多的讲解。
我们知道Cglib走代理方法是回调到自定义的拦截方法上,所以我们是不是应该更关注Spring对拦截方法的处理呢?
提出疑问:
1.怎么放到代理类中的?
回到前面的生成代理类的逻辑中
2.不同的拦截器在何时被触发?
首先我们继续看到字节码文件中,我们可以通过ctrl+f来搜索每个方法拦截器在那个方法中使用了。
字节码层面我们弄清楚以后,就看到Spring层面做了一些什么!
我们先看到1号拦截器,也就是对应的setBeanFactory方法,从方法名字我们知道就是把BeanFactory赋值到代理类
// 1号拦截器,也就是对应的setBeanFactory方法,从方法名字我们知道就是把BeanFactory赋值到代理类
private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {
@Override
@Nullable
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 获取到代理类中的$$beanFactory字段
Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);
Assert.state(field != null, "Unable to find generated BeanFactory field");
// 给代理类中的$$beanFactory字段赋值,obj是代理类,args是参数,
// 参数也就是BeanFactory,具体哪里回调后面会讲
field.set(obj, args[0]);
// 如果非代理类,也就是原本类实现了BeanFactoryAware接口就会执行这里,将BeanFactory赋值进去
if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
return proxy.invokeSuper(obj, args);
}
// 没有实现BeanFactoryAware接口就返回null
return null;
}
}
再看到0号拦截器,这里也就是cat和dog方法的拦截器。
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {
@Override
@Nullable
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
beanName = scopedBeanName;
}
}
if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
factoryContainsBean(beanFactory, beanName)) {
Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
if (factoryBean instanceof ScopedProxyFactoryBean) {
}
else {
return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
}
}
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
if (logger.isInfoEnabled() &&
BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
logger.info(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()));
}
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
}
0号方法拦截器的流程如下:
下面是两个方法拦截器调用的位置。
0号方法拦截器回调位置。
这里就不具体讲IoC的一个启动流程了,笔者直接上代码了。
因为在前面就生成了Dog和Cat的BeanDefinition,所以bean工厂也会创建他们的实例,但是标有一个记号FactoryBean(并不是FactoryBean接口,这里只是一个记号),这个记号的值就是@Configuration代理类,所以就会走代理方法,就回调到0号方法拦截器上。非常具体流程读者可以自行Debug。
1号拦截器回调位置。
整体来说,难度算比较大的,因为需要很多Spring的基础,后面的方法拦截器回调逻辑讲的不是特别的清楚,因为这需要IoC的基础来讲。叶子->树干->整个树。对于Spring来说就是这样的一个体现。对于此篇讲解@Configuration来说确实挺长,所以需要读者一个基本功和耐心分析。
更希望大家能够自己Debug读一些很细节的流程,如果有不懂的地方可以评论区留言。
最后创作不易,如本帖对您有一定的帮助,希望能点赞+关注+收藏。您的支持是给我最大的动力,后续会一直出各种框架的使用和源码解读~!