是时候自己做个Spring了,Spring的jar包辣么大

写在前面

用过Spring的朋友应该都会知道IOC这个概念,但是不见得能理解其中的原理,于是很多人使用中就会遇到对象的生命周期把控不了,对象不注入等等的问题。本篇就要实现IOC容器,并简单实现一下MVC容器。对于Spring的其他功能,AOP、REST等暂不讨论。

读这篇文章之前,你至少使用过IOC框架,比如 Spring,知道怎么创建一个 bean,怎么给字段注入对象。有一定的Java反射基础,因为这里的源码是使用Java反射实现的。知道Java注解,因为需要注入的变量是用Java注解标注的。

IOC是什么

IOC ( Inverts Of Control ) 中文名叫控制反转。举例说明就是如果你有一个bean,在使用这个 bean 的时候你不需要使用new创建这个bean的对象,而是交给IOC容器帮你创建对象,并把对象传递给你的变量。使用案例如下:

class Example{
  @Autowired
  private final Bird bird;
  
  public void doFly(){
    bird.fly();
  }
}

注意,这里的bird只声明,并没有初始化对象。因为IOC容器会帮你初始化对象,如果你在这里手工创建了对象,就会出现对象的生命周期混乱的问题。

再看Bird类的定义:

@Bean
class Bird{
  public void fly(){
    System.out.println("I am flying!");
  }
} 

使用@Bean注解标注这个类的对象是别的类要注入的对象,也就是Bird类的对象可以注入到Example类的bird字段里面。

怎么实现

通过分析上面的使用案例,我们大概需要做以下东西:

  • 创建@Autowired注解,@Bean注解
  • 扫描类
  • 创建对象,注入对象
  • IOC容器启动

创建一个@Autowired注解

注解是JDK1.5之后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

直接上代码,注解的相关事宜这里不多阐述。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

扫描类

对一个IOC容器来说,并不确定要往哪个类里面的哪个变量里面注入哪个对象,唯一的依赖的标识就是上面创建的@Autowired注解,和@Bean注解。使用场景是在多个类,多个对象之下的,所以我们需要扫描到所有标注@Bean注解或者有字段标注@Autowired注解的类。

如何通过扫描拿到项目中的所有类,这部分代码太长,也有很多解决方案,这里就不贴了,与本篇的内容偏差太大。

扫描的结果是拿到所有标注@Bean注解或者有字段标注@Autowired注解的类。

创建对象,注入对象

先分析一下正常创建对象的方法:

private final Bird bird = new Bird();

使用new关键字调用Bird类的构造方法,返回Bird类的对象给等号左边的bird变量。

通过上面的几个步骤我们拿到了所有标注@Bean注解或者有字段标注@Autowired注解的类,然后我们就需要遍历这些类,创建对象,注入对象,实现如下:

// IOC容器所有的实例
private Map<Class<?>, Object> beanInstances=new HashMap<>();

public void init(){
  // 所有被@Bean标注或者字段被@Autowired标注的类
  List<Class<?>> beanClasses=searchAllClasses();

  // 创建对象单例模式
  for (Class<?> beanClass : beanClasses) {
    Object instance=beanClass.newInstance();
    beanInstances.put(beanClass, instance);
  }

  // 把对象注入标记@Autowired的字段
  for (Class<?> autowiredClass : beanClasses) {
    Field[] fields=autowiredClass.getDeclaredFields();
    for (Field field : fields) {
      if (field.isAnnotationPresent(Autowired.class)) {
        Class<?> fieldType=field.getType();
        Object value=beans.get(fieldType);
        field.setAccessible(true);
        Object toWiredBean=beans.get(autowiredClass);
        field.set(toWiredBean, value);
      }
    }
  }
}

IOC容器启动

上面创建的所有对象都是储存在私有字段beanInstances的一个HashMap里面,这个HashMap里面所有的对象的@Autowired都是已经被初始化过的了,这里没问题。当然前提是IOC容器已经被初始化过了。

但是对于程序的入口来说,比如你创建一个static void main(String[] arges)方法,你不能使用new关键字创建被IOC容器管理的类,因为你用new创建的对象跟IOC容器维护的对象完全是两个不同的对象。在这种情况下你想要获得IOC容器维护的对象,可以对外开放这个HashMap或者使用静态的字段,让IOC容器注入一个可以供static方法访问的对象来实现。

Web容器方面,Servlet的创建与销毁是Web容器去维护的。那么假设你在IndexServlet里面让IOC容器注入了一个IndexService的对象,当你运行Web容器的时候,这个IndexService的对象肯定是为空的,原因跟上面的static main(String[] args)的问题差不多,IOC容器维护的是一套对象,Web容器创建的是另外的对象,而Servlet的对象创建不会交给IOC容器的,那么这种情况下怎么做呢?

可选的做法是创建一个代理Servlet,这个代理Servlet获取请求的URL信息,然后根据URL信息转发到不同的Servlet或者方法,这又是做了类似MVC框架的东西,这里不多说,只要理解了IOC
容器的原理,做一套简单的MVC并没有什么复杂的地方。最后会贴上我的做法。

Web容器的IOC容器初始化,可以选用Listener实现。因为Listener是在项目启动的时候就开始加载,在Servlet创建之前。

context-param => Listener => Filter => Servlet

JunitTest的实现方法跟main方法差不多,这里不多阐述。

与本篇关系不大的简单MVC实现

创建@Mapping注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Mapping {
  public String path() default "";
  public enum METHOD {GET,POST}
  METHOD method() default METHOD.GET;
}

在扫描类加入对@Mapping注解的扫描

private Map<String, Object> servletInstances=new HashMap<>();

Method[] methods=autowiredClass.getDeclaredMethods();
  for (Method method : methods) {
    if (method.isAnnotationPresent(Mapping.class)) {
      Annotation annotation=method.getAnnotation(Mapping.class);
      Method annoMethod=annotation.getClass().getDeclaredMethod("path",new Class<?>[0]);
        String mappingUrl= (String) annoMethod.invoke(annotation, new Object[0]);
        servletInstances.put(mappingUrl, method);
    }
  }

代理Servlet

String url=request.getPathInfo();
Method method=IocContainer.getMethod(url);
Object instance=IocContainer.getInstance(method.getDeclaringClass());
method.invoke(instance, req,resp);

处理请求的方法

@Mapping(path="/index.html")
public void index(HttpServletRequest request,HttpServletResponse response){
}

PS

自己实现的这套IOC与MVC已经在项目中实践了,用起来确实比Spring的东西爽的多,虽然是参照Spring的实现,但是用这套自己做的东西一言不合就可以改源码,而且真的很小很轻便。

要不要在github维护个项目?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容