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,所以这里includeFilter为org.springframework.core.type.filter.AnnotationTypeFilter(excludeFilters为空),它会将包含@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。