我们在学习Spring的时候会去加载Spring上下文环境,一般常用的加载方式有这三种:
ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。
FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)。
AnnotationConfigApplicationContext:它是用于读取注解创建容器的。
我们现在用的比较多的是依靠注解形式的开发,在Spring中我们通常像下面这样拿到Spring容器的上下文:
先写一个配置类(这里自定义了过滤规则,被@Server标记的类不会被加载到容器里):
@Component
@ComponentScan(basePackages = "text.pros",
useDefaultFilters = false,
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Component.class)},
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Server.class)}
)
public class AppConfig {
}
解析配置类:
@Test
public void test01(){
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
Arrays.asList(beanDefinitionNames).forEach(System.out::println);
}
然后它就会去扫描@ComponentScan 里面标记的包及其子包,并将其加载到上下文中
接下来我们就可以看看这个神奇的AnnotationConfigApplicationContext 是怎么加载Spring上下文环境了
点进去看,我们可以看到:
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
this();
register(componentClasses);
}
public AnnotationConfigApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh();
}
接下来我们分别看这三行代码中Spring是怎么实现过滤包、扫描注册bean、这三个功能的
1. 实现自定义或者默认的包扫描规则
从上面的代码中我们可以知道AnnotationConfigApplicationContext 加载上下文的方式可以有两种:可以传入一个反射字节码、也可以传入一个包名。其实我们猜也可以猜到,传入字节码也只不过是拿到@ComponentScan(basePackages = "text.pros") 里面的包路径名称,所以我们可以先看下面的方法,先探索一下加载上下文的过程中
this() 调用的是其无参构造函数
public AnnotationConfigApplicationContext() {
StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
this.reader = new AnnotatedBeanDefinitionReader(this);
createAnnotatedBeanDefReader.end();
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
扫描包及其子包的奥秘就这这行代码里面:
this.scanner = new ClassPathBeanDefinitionScanner(this);
加载了一个扫描器,并将当前类放入,我们继续点进去,可以看到
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
this(registry, true);
}
但是我们传进来的不是一个AnnotationConfigApplicationContext 吗?为什么参数列表里面变成了BeanDefinitionRegistry
因为他们之间是有继承关系的:
点到this里面,第一个参数就是我们加载上下文的ApplicationContext ,第二个参数表示使用默认的构造器,第三个参数表示要从传入的类中拿到系统的环境变量和jdk的环境变量,就是我们System.getProperties() 能够拿到的东西
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
}
我们继续点进去
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment) {
this(registry, useDefaultFilters, environment,
(registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
}
可以看到参数再不断变多,继续点进去看,接下来就是核心了
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment, @Nullable ResourceLoader resourceLoader) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
if (useDefaultFilters) {
registerDefaultFilters();
}
setEnvironment(environment);
setResourceLoader(resourceLoader);
}
如果没有自定义过滤器,就会使用默认过滤器registerDefaultFilters() ,可以看一下这个默认过滤器
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("jakarta.annotation.ManagedBean", cl)), false));
logger.trace("JSR-250 'jakarta.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("jakarta.inject.Named", cl)), false));
logger.trace("JSR-330 'jakarta.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
}
}
在这一行代码中
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
includeFilters 是一个被final修饰的List集合,final修饰可以保证线程安全,可以看到此过滤器的规则是默认情况下只要Component 标记的类都会被加到容器中,除此之外,还默认加载@ManagedBean 和@Named 注解标记的类
到此我们可以看到Spring是如何设置默认的过滤器规则的,接下来我们看刚开始入口函数中的第二个方法scan(basePackages) ,看下Spring是怎么扫描注册bean的
2. 扫描包 将bean包装成beanDefinition对象,再注册到容器中
把上面的代码贴下来:
public AnnotationConfigApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh();
}
现在我们来研究这个方法scan(basePackages) ,看是Spring是如何扫描包并注册bean的
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
进doScan 方法,看是如何具体扫描的
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
其中这一行是在处理当前bean是否有代理时
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
我们点进去可以看到:
static BeanDefinitionHolder applyScopedProxyMode(
ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {
ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
return definition;
}
boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
}
最后就是扫描注册bean了
registerBeanDefinition(definitionHolder, this.registry);
一直往下点:
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
可以看到我们注册bean的方法
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
继续点进去可以看到,后面就是beanFactory来具体实例化一个bean了
我们继续回到doscan方法,探究下Spring是如何扫描当前包及其子包的
具体追踪路径为:
AnnotationConfigApplicationContext
-> AnnotationConfigApplicationContext(String... basePackages)里面的scan(basePackages)
-> this.scanner.scan(basePackages)
-> doScan(basePackages)
-> Set<BeanDefinition> candidates = findCandidateComponents(basePackage)
-> return scanCandidateComponents(basePackage)
-> Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath)
-> 实现类PathMatchingResourcePatternResolver
-> public Resource[] getResources(String locationPattern) throws IOException
到这里我们就可以看到Spring扫描包及其子包的全过程了😀
|