这是笔者,第N次决定去阅读学习spring源码了,虽然这次也和以前一样没有完成,但还是有些收获的,这里做下总结。
这次阅读源码,是基于笔者想要构建一种模块化的项目架构,虽然spring也有一套模块化的分支,但是笔者并不想采用OSGI的架构,并且每个模块配置一个单独的容器实体,而且还有一些自己的不成熟想法,需要实验完成。先来说下笔者的目的:
1. 想要构建一个模块化的项目架构
2.想要spring支持重复ID(也就是实体name),并且能区分最新的类来覆盖原有旧类。
关于第二点,说来惭愧,笔者一直人云亦云的指导spring是通过java反射机制搭建的框架,但指导这次阅读源码才发现并确认,以此对于第二点有一个简单点的方式就是在自己要扩展的模块中创建一个相同目录和类名的实体,那么你就会发现,后续创建的类,即使你没加入spring管理,它依然会被实体化并以原有旧类的id(name)加入到spring容器中。如果对于代码要求不高也可以采用这种方式来完成。
但是感觉上面这种方式,总显得有些奇怪,笔者想要真正实现的是我继承原有类并扩展,然后以扩展jar包方式放入原有项目即可实现功能的更新,而不需要对原项目做修改。这是java继承的好处,也是对接口编程的好处,但是显然这里spring实现不了(更大的可能是笔者实力所限,还请大神不吝赐教)。
这里说说自己的想法,通过阅读源码发现,spring容器在实例化时以DispatcherServlet为入口(笔者是针对springmvc来说的,针对注解形式。),一步步完成所有扫描。多余的就不说了,直接说下关键类org.springframework.context.annotation.ClassPathBeanDefinitionScanner、org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider。通过源码阅读,笔者发现注解的ioc管理,我们要用的关键类就这两个。首先来看看Provider类中的方法:
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + "/" + this.resourcePattern;
<span style="color:#ff0000;">if(basePackage.startsWith("jar")) {
packageSearchPath = basePackage.replace("jar:", "jar:file:" + libPath) + "/" + this.resourcePattern;
}</span>
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
<span style="color:#ff0000;">//当前bean定义集合
List<BeanDefinition> bds = new ArrayList<BeanDefinition>();
bds.addAll(candidates);
candidates.clear();
int len = bds.size();
for(int i = len - 1; i >= 0; i--) {
try {</span>
<span style="color:#ff0000;">ReplaceTarget s = Class.forName(bds.get(i).getBeanClassName()).getAnnotation(ReplaceTarget.class);
if(s != null) {
String replaceClass = s.replaceClass();
if(replaceClass == null) {//如果没有配置我们就获取默认为对象父类
replaceClass = Class.forName(bds.get(i).getBeanClassName()).getSuperclass().getName();
}
if(insteadClass.get(replaceClass) == null) {
insteadClass.put(replaceClass, new InsteadBeanDefinitionInfo(s.priority(), bds.get(i)));
}else if(insteadClass.get(replaceClass).getPriority() < s.priority()) {//优先级低
insteadClass.put(replaceClass, new InsteadBeanDefinitionInfo(s.priority(), bds.get(i)));
}
//如果替代为空,不论它优先级能不能被初始化都要删除
bds.remove(i);//删除替代类
}
} catch (Exception e) {
}
}
//返回所有的原始类
candidates.addAll(bds);</span>
return candidates;
}
context:component-scan
标红的部分是笔者添加的代码,该方法,是在spring容器检测xml配置文件时,处理context:component-scan标签用的,没错就是我们通常说的注解扫描路径(这里额外提一下,笔者通过源码才发现人事支持,分隔的多个路径配置的,笔者以前还傻傻的写多个标签)。
我们修改了这部分源码,使注解扫描标签配置支持jar:file:协议。其实spring的xml配置文件形式是支持jar中实体实例化的,这里笔者偏向于使用注解就增加了这块,而且配置文件的源码笔者没有看啦。以下是笔者配置
<context:component-scan base-package="
jar:test.jar!
com.sgcc.dlsc" />
spring的分隔符是包括回车的。这里配置了两个范围一个是test.jar一个是在建项目的com.sgcc.dlsc目录。其中在com.sgcc.dlsc中有子类继承父类并扩展其中某个方法。
这里需要说明的是我自己增加了一个ReplaceTarget注解,用于标注替代类和目标类之间的替代关系以及替代优先级。
说明:这种修改有一个问题,通过spring源码我们知道,spring在解析配置文件时是采用xml,逐行解析的,不是采用dom整体获取后去解析,那么这种情况下,要想使本次修改真正没有问题,就需要我们都采用注解形式,而且注解都只能出现在一个context:component-scan标签下。