spring自动扫描机制
来源:互联网 发布:猪年春晚不提猪 知乎 编辑:程序博客网 时间:2024/05/23 12:02
spring自动扫描机制
记得之前的项目中leader曾经要求我自己写一个类似spring的自动代码扫描器以便扫描我们代码中一些自定义的method,annotation或者注释方式,并且我们要对扫描结果做自己的处理。
这就促使我去看spring中是如何自动扫描的,按照我的认识,应该不外乎2种方法。一是装载配置的根路径及其子文件夹下所有的类文件,并且根据每个被装载的文件的反射属性得到我们想要的annotation或者method等metadata,这个方法按理说比较完美,也比较简单,因为反射能够保证得到的metadata的正确性,但是如果对每个class都装载并反射,应该会非常的慢速,并且没有得到lazyload的好处;二是扫描所有根路径及其子文件下的所有源文件,直接分析源文件的文本属性,同样能得到这些metadata,但是这个方式缺点较多,首先要保证各种写法下都能够正确的解析metadata,如果严格起来就类似词法分析了,另外并非所有源文件都会被放到生产环境中(所以这个方法几乎是不可能的),但是这种方式相对于前一种应该会较快些。
为了能够对这个扫描机制有更深的认识,我还是决定一探spring的究竟。首先我找到ComponentScanAnnotationParser,这个类是负责处理ComponentScan的,中间最重要的方法就是parse, parse是一个很长的方法,请容我将中间不重要的代码省略,这时你发现,parse实际上调用了scanner的doScan方法。
1
public
Set<BeanDefinitionHolder> parse(Map<String, Object> componentScanAttributes) {
2
ClassPathBeanDefinitionScanner scanner =
3
new
ClassPathBeanDefinitionScanner(registry, (Boolean)componentScanAttributes.get(
"useDefaultFilters"
));
4
//……
5
return
scanner.doScan(basePackages.toArray(
new
String[]{}));
6
}
01
protected
Set<BeanDefinitionHolder> doScan(String... basePackages) {
02
Assert.notEmpty(basePackages,
"At least one base package must be specified"
);
03
Set<BeanDefinitionHolder> beanDefinitions =
new
LinkedHashSet<BeanDefinitionHolder>();
04
for
(String basePackage : basePackages) {
05
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
06
for
(BeanDefinition candidate : candidates) {
07
//……
08
}
09
}
10
return
beanDefinitions;
11
}
但是在那之前,我们可以先看看spring是如何配置和定义Resource的,resourcePatternResolver事实上是PathMatchingResourcePatternResolver的实例。通过getResources及其子方法(有兴趣可以自己去挖代码),我们可以看出来,他会处理带通配符和不带通配符的配置,其中带通配符的配置又可以分为3种,分别是jar文件,vfs协议的文件以及普通文件,其中jar文件会装载所有的子文件。这里不免疑问,得到一堆Resource有什么好处,实际上,好处正是隐藏了背后的所有文件类型,将所有的资源抽象。
01
public
Set<BeanDefinition> findCandidateComponents(String basePackage) {
02
Set<BeanDefinition> candidates =
new
LinkedHashSet<BeanDefinition>();
03
try
{
04
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
05
resolveBasePackage(basePackage) +
"/"
+
this
.resourcePattern;
06
Resource[] resources =
this
.resourcePatternResolver.getResources(packageSearchPath);
07
for
(Resource resource : resources) {
08
if
(resource.isReadable()) {
09
try
{
10
MetadataReader metadataReader =
this
.metadataReaderFactory.getMetadataReader(resource);
11
if
(isCandidateComponent(metadataReader)) {
12
ScannedGenericBeanDefinition sbd =
new
ScannedGenericBeanDefinition(metadataReader);
13
sbd.setResource(resource);
14
sbd.setSource(resource);
15
if
(isCandidateComponent(sbd)) {
16
if
(debugEnabled) {
17
logger.debug(
"Identified candidate component class: "
+ resource);
18
}
19
candidates.add(sbd);
20
}
21
else
{
22
//……
23
}
24
}
25
else
{
26
//……
27
}
28
}
29
catch
(Throwable ex) {
30
throw
new
BeanDefinitionStoreException(
31
"Failed to read candidate component class: "
+ resource, ex);
32
}
33
}
34
else
{
35
//……
36
}
37
}
38
}
39
catch
(IOException ex) {
40
throw
new
BeanDefinitionStoreException(
"I/O failure during classpath scanning"
, ex);
41
}
42
return
candidates;
43
}
接下来,从metadataReaderFactory得到MetadataReader,事实上这里的metadataReaderFactory是一个CachingMetadataReaderFactory,它用cache做了唯一性处理,如下。
01
public
MetadataReader getMetadataReader(Resource resource)
throws
IOException {
02
if
(getCacheLimit() <=
0
) {
03
return
super
.getMetadataReader(resource);
04
}
05
synchronized
(
this
.classReaderCache) {
06
MetadataReader metadataReader =
this
.classReaderCache.get(resource);
07
if
(metadataReader ==
null
) {
08
metadataReader =
super
.getMetadataReader(resource);
09
this
.classReaderCache.put(resource, metadataReader);
10
}
11
return
metadataReader;
12
}
13
}
构造SimpleMetadataReader的过程中,会同时构造AnnotationMetadataReadingVisitor(继承自ClassVisitor),并且调用classReader.accept(),这是什么呢?原来这里的classReader跟classVisitor是asm提供的字节码访问类,从字面上就能看出来,其使用了visitor模式,事实上就是classReader.accept的时候会遍历整个类的字节码,遍历的时候如果碰到method,就会调用ClassVisitor中定义的visitMethod,碰到field,就会调用visitField(同样是回调)。asm的介绍见参考文档。
01
SimpleMetadataReader(Resource resource, ClassLoader classLoader)
throws
IOException {
02
InputStream is = resource.getInputStream();
03
ClassReader classReader =
null
;
04
try
{
05
classReader =
new
ClassReader(is);
06
}
finally
{
07
is.close();
08
}
09
10
AnnotationMetadataReadingVisitor visitor =
new
AnnotationMetadataReadingVisitor(classLoader);
11
classReader.accept(visitor,
true
);
12
13
this
.annotationMetadata = visitor;
14
// (since AnnotationMetadataReader extends ClassMetadataReadingVisitor)
15
this
.classMetadata = visitor;
16
this
.resource = resource;
17
}
总之,真象大白了,spring做代码扫描的确是通过编译好的字节码去做的,与之前设想的类似,但是是通过asm对字节码的处理,个人认为应该比反射要快,这样就避免了前述的缺点。来到这里,要写一个代码扫描工具就并不是很困难了。
参考文档:
Java字节码框架ASM-读写字节码的用法 链接地址
- spring自动扫描机制
- spring自动扫描机制
- spring自动扫描机制
- Spring的组件自动扫描机制
- Spring的组件自动扫描机制
- Spring的组件自动扫描机制
- Spring的组件自动扫描机制
- Spring 的组件自动扫描机制
- Spring的组件自动扫描机制
- Spring的组件自动扫描机制
- Spring的组件自动扫描机制
- spring 使用注解自动装配和自动扫描机制
- Spring组件自动扫描
- 5.Spring 自动扫描
- spring 自动扫描 注入
- Spring IOC自动扫描
- Spring 自动扫描组件
- Spring 包自动扫描
- spark RDD 分区
- START
- 近期前端学习规划
- 理解JavaScript中的call、apply、bind
- LintCode 将二叉树拆成链表
- spring自动扫描机制
- kmp模板
- SSL 2295——暗黑破坏神
- 123
- 日记
- 当让系统自动下载gradle-3.4.1-all.zip不下来,系统一直在加载,的处理方法。
- error C2440: “初始化”: 无法从“const char [8]”转换为“const wchar_t *” 1> 与指向的类型无关;转换要求 reinterpret_ca
- Cheap Kangaroo Gym
- 简单的线性回归