前言

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

容器加载过程

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

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

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

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

  1. @Override
  2. public void init(ServletConfig config) {
  3. //加载配置
  4.  
  5. //获取要扫描的包地址
  6.  
  7. //扫描要加载的类
  8.  
  9. //实例化要加载的类
  10.  
  11. //加载依赖注入,给属性赋值
  12.  
  13. //加载映射地址
  14.  
  15. }

加载配置

  1. String contextConfigLocation = config.getInitParameter("contextConfigLocation");
  2.  
  3. loadConfig(contextConfigLocation);

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

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

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

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

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

  1. void loadConfig(String contextConfigLocation) {
  2. InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
  3. try {
  4. properties.load(is);
  5. } catch (IOException e) {
  6. e.printStackTrace();
  7. } finally {
  8. if (null != is) {
  9. try {
  10. is.close();
  11. } catch (IOException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16. }

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

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

获取要扫描的包地址

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

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

扫描要加载的类

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

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

  1. void doScanner(String dirpath) {
  2. URL url = this.getClass().getClassLoader().getResource("/" + dirpath.replaceAll("\\.", "/"));
  3. File dir = new File(url.getFile());
  4. File[] files = dir.listFiles();
  5. for (File file : files) {
  6. if (file.isDirectory()) {
  7. doScanner(dirpath + "." + file.getName());
  8. continue;
  9. }
  10.  
  11. //取文件名
  12. String beanName = dirpath + "." + file.getName().replaceAll(".class", "");
  13. beanNames.add(beanName);
  14. }
  15. }

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

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

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

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

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

得到的beanName如下

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

实例化要加载的类

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

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

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

  1. private Map<String, Object> ioc = new HashMap<>();
    接着创建一个方法doInstance
  1. void doInstance() {
  2. if (beanNames.isEmpty()) {
  3. return;
  4. }
  5. for (String beanName : beanNames) {
  6. try {
  7. Class cls = Class.forName(beanName);
  8. if (cls.isAnnotationPresent(JCController.class)) {
  9. //使用反射实例化对象
  10. Object instance = cls.newInstance();
  11. //默认类名首字母小写
  12. beanName = firstLowerCase(cls.getSimpleName());
  13. //写入ioc容器
  14. ioc.put(beanName, instance);
  15.  
  16. } else if (cls.isAnnotationPresent(JCService.class)) {
  17. Object instance = cls.newInstance();
  18. JCService jcService = (JCService) cls.getAnnotation(JCService.class);
  19.  
  20. String alisName = jcService.value();
  21. if (null == alisName || alisName.trim().length() == 0) {
  22. beanName = cls.getSimpleName();
  23. } else {
  24. beanName = alisName;
  25. }
  26. beanName = firstLowerCase(beanName);
  27. ioc.put(beanName, instance);
  28. //如果是接口,自动注入它的实现类
  29. Class<?>[] interfaces = cls.getInterfaces();
  30. for (Class<?> c :
  31. interfaces) {
  32. ioc.put(firstLowerCase(c.getSimpleName()), instance);
  33. }
  34. } else {
  35. continue;
  36. }
  37. } catch (ClassNotFoundException e) {
  38. e.printStackTrace();
  39. } catch (IllegalAccessException e) {
  40. e.printStackTrace();
  41. } catch (InstantiationException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. }

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

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

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

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

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

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

说明:

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

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

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

  1. 接口定义如下:
  1. public interface IHomeService {
  2. String sayHi();
  3. String getName(Integer id,String no);
  4. String getRequestBody(Integer id, String no, GetUserInfo userInfo);
  5. }
  6.  
  7. public interface IStudentService {
  8. String sayHi();
  9. }
  1. 实现类:
  1. @JCService
  2. public class StudentService implements IStudentService{
  3. @Override
  4. public String sayHi(){
  5. return "Hello world!";
  6. }
  7. }
  1. @JCService
  2. public class HomeService implements IHomeService{
  3.  
  4. @JCAutoWrited
  5. StudentService studentService;
  6. @Override
  7. public String sayHi() {
  8. return studentService.sayHi();
  9. }
  10.  
  11. @Override
  12. public String getName(Integer id,String no) {
  13. return "SB0000"+id;
  14. }
  15.  
  16. @Override
  17. public String getRequestBody(Integer id, String no, GetUserInfo userInfo) {
  18. return "userName="+userInfo.getName()+" no="+no;
  19. }
  20. }

依赖实体:

  1. public class GetUserInfo {
  2. public String getName() {
  3. return name;
  4. }
  5.  
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9.  
  10. public Integer getAge() {
  11. return age;
  12. }
  13.  
  14. public void setAge(Integer age) {
  15. this.age = age;
  16. }
  17.  
  18. public BigDecimal getGrowthValue() {
  19. return growthValue;
  20. }
  21.  
  22. public void setGrowthValue(BigDecimal growthValue) {
  23. this.growthValue = growthValue;
  24. }
  25.  
  26. private String name;
  27. private Integer age;
  28. private BigDecimal growthValue;
  29.  
  30. }
  1.  

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

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

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

  1. void doAutoWrited() {
  2. for (Map.Entry<String, Object> obj : ioc.entrySet()) {
  3. try {
  4. for (Field field : obj.getValue().getClass().getDeclaredFields()) {
  5. if (!field.isAnnotationPresent(JCAutoWrited.class)) {
  6. continue;
  7. }
  8. JCAutoWrited autoWrited = field.getAnnotation(JCAutoWrited.class);
  9. String beanName = autoWrited.value();
  10. if ("".equals(beanName)) {
  11. beanName = field.getType().getSimpleName();
  12. }
  13.  
  14. field.setAccessible(true);
  15.  
  16. field.set(obj.getValue(), ioc.get(firstLowerCase(beanName)));
  17. }
  18. } catch (IllegalAccessException e) {
  19. e.printStackTrace();
  20. }
  21.  
  22. }
  23.  
  24. }

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

加载映射地址

  1. //加载映射地址
  2. doRequestMapping();

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

  1. void doRequestMapping() {
  2. if (ioc.isEmpty()) {
  3. return;
  4. }
  5. for (Map.Entry<String, Object> obj : ioc.entrySet()) {
  6. if (!obj.getValue().getClass().isAnnotationPresent(JCController.class)) {
  7. continue;
  8. }
  9. Method[] methods = obj.getValue().getClass().getMethods();
  10. for (Method method : methods) {
  11. if (!method.isAnnotationPresent(JCRequestMapping.class)) {
  12. continue;
  13. }
  14. String baseUrl = "";
  15. if (obj.getValue().getClass().isAnnotationPresent(JCRequestMapping.class)) {
  16. baseUrl = obj.getValue().getClass().getAnnotation(JCRequestMapping.class).value();
  17. }
  18. JCRequestMapping jcRequestMapping = method.getAnnotation(JCRequestMapping.class);
  19. if ("".equals(jcRequestMapping.value())) {
  20. continue;
  21. }
  22. String url = (baseUrl + "/" + jcRequestMapping.value()).replaceAll("/+", "/");
  23. urlMapping.put(url, method);
  24. System.out.println(url);
  25. }
  26. }
  27. }

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

@JCRequestMapping("/sayHi")

取到的就是/sayHi

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

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

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

  1.  

结尾

容器的初始化到这里就结束了,一共使用了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. 微信小程序 键盘显示短信验证码

    1.场景描述: IOS系统 一些APP或者微信小程序在收到短信验证码的时候会在键盘上自动保存验证码信息,当用户点击的时候,会自动赋值到当前所点击的输入框中 2.案例: 2.实现: TIPS:这个功能是 ...

  2. 串门赛: NOIP2016模拟赛——By Marvolo 丢脸记

    前几天liu_runda来机房颓废,顺便扔给我们一个网址,说这上面有模拟赛,让我们感兴趣的去打一打.一开始还是没打算去看一下的,但是听std说好多人都打,想了一下,还是打一打吧,打着玩,然后就丢脸了. ...

  3. secureCRT背景颜色

    secureCRT是我们常用的linux远程登录软件,某些情况下我们安装的secureCRT工具可能默认背景颜色为白色,使用时很容易造成眼睛的疲劳,网上有些教程也只是修改当前登录窗口背景颜色,新的连接 ...

  4. python介绍、安装及相关语法、python运维、编译与解释

    1.python介绍 Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/)是一种广泛使用的解释型.高级编程.通用型编程语言,由吉多.范罗苏姆创造,第一版发布于1991年.可以视 ...

  5. 实验吧--web--天下武功唯快不破

    ---恢复内容开始--- 英文翻译过来嘛,就是:天下武功无快不破嘛.(出题者还是挺切题的) 看看前端源码: 注意这里 please post what you find with parameter: ...

  6. 从0系统学Android-2.3使用 Intent 在 Activity 之间穿梭

    2.3 使用 Intent 在 Activity 之间穿梭 在上一节中我们已经学会了如何创建一个 Activity 了.对于一个应用程序来说,肯定不可能只有一个 Activity.下面就来学习多个 A ...

  7. 【译】宣告推出.NET Core 3.0 Preview 7(英雄的黎明)

    今天,我们宣布推出.NET Core 3.0 Preview 7.我们已经从创建新特性阶段过渡到了完善版本阶段.对于其余的预览版,我们将把重点放在质量(改进)上. 在Windows,macOS和Lin ...

  8. stack函数怎么用嘞?↓↓↓

    c++ stl栈stack的头文件书写格式为: #include 实例化形式如下: stack StackName; 其中成员函数如下: 1.检验堆栈是否为空 empty() 堆栈为空则返回真 形式如 ...

  9. javaweb入门----servlet简介

    servlet 上文已经了解了web服务器和http协议是怎么回事儿,并且也了解了浏览器与服务器之间的联系,现在要介绍一下服务器是如何处理来自客户端的请求的,这就是servlet. servlet:J ...

  10. 第三章、Go-内建容器

    3.1.数组 (1)数组的定义 package main import ( "fmt" ) func main() { //用var定义数组可以不用赋初值 var arr1 [5] ...