第四章 标题待定

61 阅读3分钟

bean是如何被spring framework托管的?

以下面简单的示例作为开端:

// file name: Application.java
package com.hihusky.hello_bean_annotation;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Application {
    public static void main(String[] args) {
        Greeting greeting;
        SecondGreeting secondGreeting;
        ThirdGreeting thirdGreeting;
        try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
            context.scan("com.hihusky.hello_bean_annotation");
            context.refresh();
            greeting = context.getBean(Greeting.class);
            secondGreeting = context.getBean(SecondGreeting.class);
        }
        System.out.println(greeting);
        System.out.println(secondGreeting);
        System.out.println(thirdGreeting);
    }
}
​
// file name: Greeting.java
package com.hihusky.hello_bean_annotation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Greeting {
    private String message;
    public Greeting(@Value("Hello, Annotation!") String message) {
        this.message = message;
    }
    public String getMessage() {
        return this.message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    @Override
    public String toString() {
        return "Greeting{" +
                "message='" + message + ''' +
                '}';
    }
}
​
// file name: SecondGreeting.java
package com.hihusky.hello_bean_annotation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class SecondGreeting {
  private String message;
​
  public SecondGreeting() {
  }
  public SecondGreeting(@Value("Hello again, Annotation!") String message) {
    this.message = message;
  }
  public String getMessage() {
    return this.message;
  }
  public void setMessage(String message) {
    this.message = message;
  }
  @Override
  public String toString() {
    return "SecondGreeting{" +
        "message='" + message + ''' +
        '}';
  }
}

上述样例借助Spring的注解AnnotationConfigApplicationContext以及配合@Component完成将Greeting以及SecondGreeting实例化,并交给Spring管理。

接下来我们将以Application.java为中心分析代码

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()

构造器创建默认的注解上下文

context.scan("com.hihusky.hello_bean_annotation");

扫描这一步执行的步骤比较繁琐,请耐心看完。

首先是将com.hihusky.hello_bean_annotation这个包名处理成classpath*:com/hihusky/hello_bean_annotation/**/.class,用来检索该目录下的所有类文件,配合ClassLoader同时解析成元数据org.springframework.core.type.classreading.SimpleAnnotationMetadata,其中包含注解、类名等。

然后根据元数据做过滤,过滤的具体方法是org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent(MetadataReader)。该过滤方法会用到成员变量的两个过滤器,一个是excludeFilters,这个过滤器会排出该过滤器符合条件的类文件;一个是includeFilters,这个过滤器会添加符合条件的类文件。

由于我们使用的是以注解的方式添加bean,所以这里includeFilterorg.springframework.core.type.filter.AnnotationTypeFilterexcludeFilters为空),它会将包含@Component注解的类文件进行标记添加。

最终筛选出符合条件的类做成集合Set<BeanDefinitionHolder>

Set<BeanDefinitionHolder> 再经过层层处理成为

org.springframework.beans.factory.support.DefaultListableBeanFactory类中的成员变量:

private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

对这个转换有兴趣的小伙伴可以将断点打在org.springframework.beans.factory.support.DefaultListableBeanFactory$registerBeanDefinition方法上,通过上下文(调用栈附近的代码)来了解。

context.refresh();

这一步以beanDefinitionNames为参考,将所有的bean以单例模式(默认)实例化。

这个参考依据可以查看org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons方法。

大家如果想了解如何被实例化的,我们可以逆向思维,要想实例化,Java就那几种方法:

  • T java.lang.Class#newInstance()
  • T java.lang.reflect.Constructor#newInstance(Object ... initargs)

第一种还被弃用了,如果想要实例化很大可能就是第二种方式进行实例化,先打个无声断点(Mute Break Point 就是灰色的断点)在第二个方法上,我们已知是在扫描开始后才进行初始化的,我们在扫描函数上打上断点,这样我们就可以缩小范围,避免newInstance断点停在非想要的位置(缩小范围)。随后开始调试,先停在扫描操作的断点上,然后将无声断点恢复,然后continue断点让其听到实例化的断点上,这时候我们需要不断的continue同时注意旁边的变量参数我们想要的实例化是否出现,出现后可以看看调用栈和代码之类的。

如果你用了上述操作你可以发现,调用栈中使用了为org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#instantiateBean(String, RootBeanDefinition)方法。

greeting = context.getBean(Greeting.class);

取数据就比较简单,直接从先前的registery中取单例。

综上就完成了Spring托管bean流程,不难看出主角还是Registry