项目地址:https://github.com/gongxianshengjiadexiaohuihui/noobspring
通过手写spring(一)我们知道,我们可以调用父类的config获取初始化参数contextConfigLocation的值,这里面在spring中存的是一个正则表达式,这里我为了图方便,并没用用正则表达式(后续会完善),得到这个之后我们就知道配置文件的路径,所以我们 doLoadConfig的工作就是读取配置文件的内容并保存
private void doLoadConfig(String path){
logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>初始化配置文件");
/**
* 先把classpath:剥离,后续在添加内容
*/
InputStream fis = null;
try {
fis = this.getClass().getClassLoader().getResourceAsStream(path.replace("classpath:",""));
p.load(fis);
}catch (Exception e){
logger.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>初始化配置文件失败");
e.printStackTrace();
throw new RuntimeException("初始化配置文件失败");
}finally {
if(null != fis){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>初始化配置文件成功");
}
第一步把classpath:剥离出来
然后获取 此路径下文件的输入流,并且把内容保存到配置文件p中
这里来讲一下,Class.getResourceAsStream和Class.getClassLoader.getResourceAsStream的路径问题
Class.getResourceAsStream(String path) path不以‘/’开头时默认是从此类所在的包下获取资源,以‘/’开头是从ClassPath根下获取
第二个,默认是从ClassPath的根下获取,path不能以‘/’开头
还有一种是ServletContext.getResourceAsStream默认是从WebApp根目录下获取资源
我们从配置文件中获取了需要扫描的包路径,然后扫描包和其子目录,并保存扫描到的类名
private void doScanner(String packageName){
URL url = this.getClass().getClassLoader().getResource(packageName.replaceAll("\\.","/"));
File dir = new File(url.getFile());
for(File file: dir.listFiles()){
if(file.isDirectory()){
doScanner(packageName + "." + file.getName());
}else{
classNames.add(packageName + "." + file.getName().replace(".class"," ").trim());
}
}
}
这里是一个递归函数,我们因为我们不仅要扫描该包,还要扫描该包下的子目录,所以如果发现该包下的文件是目录结构,就再次调用此函数。如果不是就保存该类的全限定名,保存到className中
接下来是利用java反射进行实例化并保存到ioc容器中
private void doInstance(){
logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>开始实例化");
if(classNames.size() == 0){
return;
}
try{
for(String className: classNames){
Class<?> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(NBController.class)){
/**
* 将类首字母小写
*/
String beanName = StringUtil.lowerFirstCase(clazz.getSimpleName());
ioc.put(beanName,clazz.newInstance());
}else if(clazz.isAnnotationPresent(NBService.class)){
/**
* 如果@NBService有value的话
*/
String beanName = clazz.getAnnotation(NBService.class).value();
if(!"".equals(beanName.trim())){
beanName = StringUtil.lowerFirstCase(beanName);
ioc.put(beanName , clazz.newInstance());
continue;
}
/**
* 如果@NBService的value为空,则根据其实现的接口创建实例
*/
Class<?>[] interfaces = clazz.getInterfaces();
for(Class<?> tamp : interfaces){
String tampName = StringUtil.lowerFirstCase(tamp.getSimpleName());
ioc.put(tampName, clazz.newInstance());
}
}else if(clazz.isAnnotationPresent(NBComponent.class)){
String beanName = StringUtil.lowerFirstCase(clazz.getSimpleName());
ioc.put(beanName,clazz.newInstance());
}else{
continue;
}
}
}catch (Exception e){
logger.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>实例化失败");
e.printStackTrace();
throw new RuntimeException("实例化失败");
}
logger.debug("实例化的类:{}",ioc.keySet().toString());
logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>实例化成功");
}
第一步是判断前一步有没有扫描到类名,如果没有就直接返回
接着我们就可以理解为什么说注解起到的是标记作用了,我们通过类名获该class文件,然后判断它是否被@NBController,@NBComponent标记,如果是,就把它的类名(首字母小写)作为key,实例作为value保存在
类型为hashMap的ioc中。
特殊的是@NBServcie,被它标记的往往是接口实现类,但是我们知道一个类可以实现多个接口,所以我们需要判断,如果注解指明了key值,我们就按照它的值进行保存,如果没有指明,我们需要遍历它实现的所有接口,分别一一保存