前言

上一篇我们讲了spring的一些特点并且分析了需要实现哪些功能,已经把准备工作都做完了,这一篇我们开始实现具体功能。

容器加载过程

我们知道,在spring中refesh()方法做了很多初始化的工作,它几乎涵盖了spring的核心流程

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//刷新之前的准备工作,包括设置启动时间,是否激活标识位,初始化属性源(property source)配置
prepareRefresh();
//由子类去刷新BeanFactory(如果还没创建则创建),并将BeanFactory返回
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//准备BeanFactory以供ApplicationContext使用
prepareBeanFactory(beanFactory);
try {
//子类可通过格式此方法来对BeanFactory进行修改
postProcessBeanFactory(beanFactory);
//实例化并调用所有注册的BeanFactoryPostProcessor对象
invokeBeanFactoryPostProcessors(beanFactory);
//实例化并调用所有注册的BeanPostProcessor对象
registerBeanPostProcessors(beanFactory);
//初始化MessageSource
initMessageSource();
//初始化事件广播器
initApplicationEventMulticaster();
//子类覆盖此方法在刷新过程做额外工作
onRefresh();
//注册应用监听器ApplicationListener
registerListeners();
//实例化所有non-lazy-init bean
finishBeanFactoryInitialization(beanFactory);
//刷新完成工作,包括初始化LifecycleProcessor,发布刷新完成事件等
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}

做的东西比较复杂,而我们实现做些基本的就好了。

我们在CJDispatcherServlet 类的init方法中,实现如下业务逻辑,就能将spring功能给初始化了,就可以使用依赖注入了

    @Override
public void init(ServletConfig config) {
//加载配置 //获取要扫描的包地址 //扫描要加载的类 //实例化要加载的类 //加载依赖注入,给属性赋值 //加载映射地址 }

加载配置

 String contextConfigLocation = config.getInitParameter("contextConfigLocation");

        loadConfig(contextConfigLocation);

这里会获取到web.xml中init-param节点中的值

具体指向的是spring文件下的application.properties配置文件,里面只有一行配置

通过配置的key名字可以知道,这是指定了需要扫描的包路径

代表的是扫描红框中定义的所有类

第二行代码是创建了一个loadConfig方法,将包路径传进去

    void loadConfig(String contextConfigLocation) {
InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

黃色部分的代码需要注意,这里使用了一个成员变量

private Properties properties = new Properties();
在类的上半部分定义就好了,这里的作用是获取application.properties文件中的配置内容加载到properties变量中,供后面使用。

获取要扫描的包地址

 //获取要扫描的包地址
String dirpath = properties.getProperty("scanner.package");

这里使用配置中的key读取出目录地址

扫描要加载的类

   //扫描要加载的类
doScanner(dirpath);

扫描类我们定义一个doScanner方法,把包目录地址传进去

     void doScanner(String dirpath) {
URL url = this.getClass().getClassLoader().getResource("/" + dirpath.replaceAll("\\.", "/"));
File dir = new File(url.getFile());
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
doScanner(dirpath + "." + file.getName());
continue;
} //取文件名
String beanName = dirpath + "." + file.getName().replaceAll(".class", "");
beanNames.add(beanName);
}
}

第二行代码进行了转义替换

本方法内的代码作业是读取指定路径下的文件,如果是文件夹,则递归调用,如果是文件,把文件名称和路径存进集合容器内

需要注意黄色部分的变量,是在外部定义了一个成员变量

private List<String> beanNames = new ArrayList<>();

我们在类的上半部分加上它。

得到的beanName如下

从这里看出,它已经把我们定义的注解给找出来了。

实例化要加载的类

 //实例化要加载的类
doInstance();

刚才我们已经得到了这些定义好的类的名称列表,现在我们需要一个个实例化,并且保存在ioc容器当中。

先定义个装载类的容器,使用HashMap就能做到,将它设为成员变量,在类的上半部分定义

private Map<String, Object> ioc = new HashMap<>();
接着创建一个方法doInstance
  void doInstance() {
if (beanNames.isEmpty()) {
return;
}
for (String beanName : beanNames) {
try {
Class cls = Class.forName(beanName);
if (cls.isAnnotationPresent(JCController.class)) {
//使用反射实例化对象
Object instance = cls.newInstance();
//默认类名首字母小写
beanName = firstLowerCase(cls.getSimpleName());
//写入ioc容器
ioc.put(beanName, instance); } else if (cls.isAnnotationPresent(JCService.class)) {
Object instance = cls.newInstance();
JCService jcService = (JCService) cls.getAnnotation(JCService.class); String alisName = jcService.value();
if (null == alisName || alisName.trim().length() == 0) {
beanName = cls.getSimpleName();
} else {
beanName = alisName;
}
beanName = firstLowerCase(beanName);
ioc.put(beanName, instance);
//如果是接口,自动注入它的实现类
Class<?>[] interfaces = cls.getInterfaces();
for (Class<?> c :
interfaces) {
ioc.put(firstLowerCase(c.getSimpleName()), instance);
}
} else {
continue;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}

只要提供类的完全限定名,通过Class.forName静态方法,我们就能将类信息加载到内存中并且返回Class 对象,通过反射来实例化,见第10行代码,

我们通过循环beanNames集合,来实例化每个类,并将实例化后的对象装入HashMap中

注意:第12行将类名的首字母小写后存入map,该方法定义如下

   String firstLowerCase(String str) {
char[] chars = str.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}

这行代码会将字符串转成char数组,然后将数组中第一个字符转为大写,这里采用了一种比较巧妙的方式实现,tom老师采用了一种比较骚的操作

实例化完成后,ioc容器中的数据如下:

说明:

图片中可以看出,hashMap的key 都是小写,value已经是对象了 ,见红框。

这里为什么要把蓝框标记出来,是因为这是类中的字段属性,此时可以看到,虽然类已经被实例化了,可是属性还是null呢

我这里为了测试依赖注入,所以加了2个接口和2个实现类

接口定义如下:
public interface IHomeService {
String sayHi();
String getName(Integer id,String no);
String getRequestBody(Integer id, String no, GetUserInfo userInfo);
} public interface IStudentService {
String sayHi();
}
实现类:
@JCService
public class StudentService implements IStudentService{
@Override
public String sayHi(){
return "Hello world!";
}
}
@JCService
public class HomeService implements IHomeService{ @JCAutoWrited
StudentService studentService;
@Override
public String sayHi() {
return studentService.sayHi();
} @Override
public String getName(Integer id,String no) {
return "SB0000"+id;
} @Override
public String getRequestBody(Integer id, String no, GetUserInfo userInfo) {
return "userName="+userInfo.getName()+" no="+no;
}
}

依赖实体:

public class GetUserInfo {
public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} public BigDecimal getGrowthValue() {
return growthValue;
} public void setGrowthValue(BigDecimal growthValue) {
this.growthValue = growthValue;
} private String name;
private Integer age;
private BigDecimal growthValue; }

加载依赖注入,给属性赋值

//加载依赖注入,给属性赋值
doAutoWrited();

现在我们实现依赖注入,需要定义一个无参的方法doAutoWrite

     void doAutoWrited() {
for (Map.Entry<String, Object> obj : ioc.entrySet()) {
try {
for (Field field : obj.getValue().getClass().getDeclaredFields()) {
if (!field.isAnnotationPresent(JCAutoWrited.class)) {
continue;
}
JCAutoWrited autoWrited = field.getAnnotation(JCAutoWrited.class);
String beanName = autoWrited.value();
if ("".equals(beanName)) {
beanName = field.getType().getSimpleName();
} field.setAccessible(true); field.set(obj.getValue(), ioc.get(firstLowerCase(beanName)));
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} } }

这个方法是通过循环ioc里面的实体,反射找出字段,看看是否有需要注入的标记JCAutoWrited,如果加了标记,就反射给字段赋值,类型从ioc容器中获取

加载映射地址

    //加载映射地址
doRequestMapping();

映射地址的作用是根据请求的url匹配method方法

     void doRequestMapping() {
if (ioc.isEmpty()) {
return;
}
for (Map.Entry<String, Object> obj : ioc.entrySet()) {
if (!obj.getValue().getClass().isAnnotationPresent(JCController.class)) {
continue;
}
Method[] methods = obj.getValue().getClass().getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(JCRequestMapping.class)) {
continue;
}
String baseUrl = "";
if (obj.getValue().getClass().isAnnotationPresent(JCRequestMapping.class)) {
baseUrl = obj.getValue().getClass().getAnnotation(JCRequestMapping.class).value();
}
JCRequestMapping jcRequestMapping = method.getAnnotation(JCRequestMapping.class);
if ("".equals(jcRequestMapping.value())) {
continue;
}
String url = (baseUrl + "/" + jcRequestMapping.value()).replaceAll("/+", "/");
urlMapping.put(url, method);
System.out.println(url);
}
}
}

这里其实就是根据对象反射获取到JCRequestMapping上面的value值

@JCRequestMapping("/sayHi")

取到的就是/sayHi

另外注意的是:黄色部分使用的变量是一个hashMap,在类上半部分定义的

private Map<String, Method> urlMapping = new HashMap<>();

这里面存的是 url 和对应的method对象。后面处理请求的时候要使用到的。


结尾

容器的初始化到这里就结束了,一共使用了4个容器来存放相关对象,后续servlet处理请求的时候会用到它们。

下一篇,将会继续完善它,通过请求来验证是否可以达到预期效果。另外会实现参数绑定,能处理各类请求并响应。

完整代码地址

自己实现spring核心功能 二的更多相关文章

  1. 自己实现spring核心功能 一

    聊聊spring spring对于java开发者来说,是最熟悉不过的框架了,我们日常开发中每天都在使用它.它有着各种各样的好处,简单易用,得心应手... ... 我们一说到spring就会讲到ioc ...

  2. Spring 核心功能演示

    Spring 核心功能演示 Spring Framework 简称 Spring,是 Java 开发中最常用的框架,地位仅次于 Java API,就连近几年比较流行的微服务框架 SpringBoot, ...

  3. 自己实现spring核心功能 三

    前言 前两篇已经基本实现了spring的核心功能,下面讲到的参数绑定是属于springMvc的范畴了.本篇主要将请求到servlet后怎么去做映射和处理.首先来看一看dispatherServlet的 ...

  4. Spring系列(二):Spring IoC应用

    一.Spring IoC的核心概念 IoC(Inversion of Control  控制反转),详细的概念见Spring系列(一):Spring核心概念 二.Spring IoC的应用 1.定义B ...

  5. spring 核心

    1 Spring 1.1 专业术语了解 1.1.1 组件/框架设计 侵入式设计 引入了框架,对现有的类的结构有影响:即需要实现或继承某些特定类. 例如:     Struts框架 非侵入式设计 引入了 ...

  6. 读源码之Spring 核心内容

    为什么有这篇文档 工作两三年之后,总感觉什么东西都懂,但是什么东西又都不会.所以日常学习是很有必要的,俗话说学而不思则罔 ,思而不学则殆.所以我们要学思结合,学习的方法有很多,但是思考的深度或者说有没 ...

  7. Spring框架的IOC核心功能快速入门

    2. 步骤一:下载Spring框架的开发包 * 官网:http://spring.io/ * 下载地址:http://repo.springsource.org/libs-release-local/ ...

  8. Spring框架的核心功能之AOP技术

     技术分析之Spring框架的核心功能之AOP技术 AOP的概述        1. 什么是AOP的技术?        * 在软件业,AOP为Aspect Oriented Programming的 ...

  9. Spring 框架的核心功能之AOP技术

    1. AOP 的概述 AOP, Aspect Oriented Programming, 面向切面编程; 通过预编译方式和运行期动态代理实现程序功能的统一维护的技术; AOP 采取横向抽取机制,取代了 ...

随机推荐

  1. Windows下通过CMD命令行程序操作MySQL数据库

    注意:如果您的MySQL没有安装在C盘下,先使用命令进入MySQL的安装目录下的bin目录中才可以进行后续操作. 方法如下:例如您安装在D盘.先输入 D:  回车即可进入D盘,再输入cd D:\您my ...

  2. mybatis基础配置

    我这个写的比较简略,是自己短时间记录的可能只适合自己看,新手或者不懂的建议看看下面大神这篇: https://www.cnblogs.com/homejim/p/9613205.html 1.MyBa ...

  3. 视频4K技术的解读

    前几年4K技术就已经有人提及,今年更是成了一个非常热门的词汇,而且4K技术已经普遍应用于各类终端,如电视机.机顶盒.手机等.那么如何来理解4K这个东东呢?今天博主就谈谈自己对4K技术的认识. 博主认为 ...

  4. Android 开发感想

    18年从.net转行做安卓开发,现在已经过去一年多了.说一下感想和心得体会! 一.开始 说一下我的经厉,从毕业开始出来工作一直是从事.net方向的开发工作.一开始也是没什么经验,加上也没有其他手艺就找 ...

  5. c语言进阶11-经典算法代码

    重要算法一览 #include "stdio.h" #include "stdio.h" void main() { int a,b,c,i,n; int x, ...

  6. 《ElasticSearch6.x实战教程》之实战ELK日志分析系统、多数据源同步

    第十章-实战:ELK日志分析系统 ElasticSearch.Logstash.Kibana简称ELK系统,主要用于日志的收集与分析. 一个完整的大型分布式系统,会有很多与业务不相关的系统,其中日志系 ...

  7. Linux命令大全(简)

    rm--删除文件和目录   -i 删除一个已存在的文件前,提示用户进行确认.   -r 递归的删除目录. mkdir--创建目录 cp--复制文件和目录   -i 在覆盖一个已存在的目录前,提示用户进 ...

  8. sessionID是如何在客户端和服务器端传递的?

    sessionID是如何在客户端和服务器端传递的? 服务器初次创建session的时候后返回session到客服端(在返回头(response)中有setCookie),浏览器会把sessionnam ...

  9. Java程序员注意——审查Java代码的六种常见错误

    代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效.由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug.并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那 ...

  10. mpvue的使用,包含axios、router的集成等完美结合小程序

    mpvue开发微信小程序框架的使用注意事项: 1.路由跳转,引用mpvue-router-patch 在main.js文件中引入控件:import MpvueRouterPatch from 'mpv ...