Spring boot实现一个简单的ioc(2)

前言

跳过废话,直接看正文
仿照spring-boot的项目结构以及部分注解,写一个简单的ioc容器。
测试代码完成后,便正式开始这个ioc容器的开发工作。

正文

项目结构

实际上三四个类完全能搞定这个简单的ioc容器,但是出于可扩展性的考虑,还是写了不少的类。
因篇幅限制,接下来只将几个最重要的类的代码贴出来并加以说明,完整的代码请直接参考https://github.com/clayandgithub/simple-ioc

SimpleAutowired

代码

import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleAutowired {
 boolean required() default true;

 String value() default ""; // this field is moved from @Qualifier to here for simplicity
}

说明

@SimpleAutowired的作用是用于注解需要自动装配的字段。
此类和spring的@Autowired的作用类似。但又有以下两个区别:
- @SimpleAutowired只能作用于类字段,而不能作用于方法(这样实现起来相对简单些,不会用到aop)
- @SimpleAutowired中包括了required(是否一定需要装配)和value(要装配的bean的名字)两个字段,实际上是将spring中的@Autowired以及Qualifier的功能简单地融合到了一起

SimpleBean

代码

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleBean {
 String value() default "";
}

说明

@SimpleBean作用于方法,根据方法返回值来生成一个bean,对应spring中的@Bean
用value来设置要生成的bean的名字

SimpleComponent

代码

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleBean {
 String value() default "";
}

说明

@SimpleComponent作用于类,ioc容器会为每一个拥有@SimpleComponent的类生成一个bean,对应spring中的@Component。特殊说明,为了简单起见,@SimpleComponent注解的类必须拥有一个无参构造函数,否则无法生成该类的实例,这个在之后的SimpleAppliationContext中的processSingleClass方法中会有说明。

SimpleIocBootApplication

代码

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleIocBootApplication {
 String[] basePackages() default {};
}

说明

@SimpleIocBootApplication作用于应用的入口类。
这个启动模式是照搬了spring-boot的启动模式,将启动任务委托给SimpleIocApplication来完成。ioc容器将根据注解@SimpleIocBootApplication的相关配置自动扫描相应的package,生成beans并完成自动装配。(如果没有配置,默认扫描入口类(测试程序中的SampleApplication)所在的package及其子package)

以上就是这个ioc容器所提供的所有注解,接下来讲解ioc容器的扫描和装配过程的实现。

SimpleIocApplication

代码

import com.clayoverwind.simpleioc.context.*;
import com.clayoverwind.simpleioc.util.LogUtil;

import java.util.Arrays;
import java.util.Map;
import java.util.logging.Logger;

public class SimpleIocApplication {
 private Class<?> applicationEntryClass;

 private ApplicationContext applicationContext;

 private final Logger LOGGER = LogUtil.getLogger(this.getClass());

 public SimpleIocApplication(Class<?> applicationEntryClass) {
 this.applicationEntryClass = applicationEntryClass;
 }

 public static void run(Class<?> applicationEntryClass, String[] args) {
 new SimpleIocApplication(applicationEntryClass).run(args);
 }

 public void run(String[] args) {
 LOGGER.info("start running......");

 // create application context and application initializer
 applicationContext = createSimpleApplicationContext();
 ApplicationContextInitializer initializer = createSimpleApplicationContextInitializer(applicationEntryClass);

 // initialize the application context (this is where we create beans)
 initializer.initialize(applicationContext); // here maybe exist a hidden cast

 // process those special beans
 processSpecialBeans(args);

 LOGGER.info("over!");
 }

 private SimpleApplicationContextInitializer createSimpleApplicationContextInitializer(Class<?> entryClass) {
 // get base packages
 SimpleIocBootApplication annotation = entryClass.getDeclaredAnnotation(SimpleIocBootApplication.class);
 String[] basePackages = annotation.basePackages();
 if (basePackages.length == 0) {
  basePackages = new String[]{entryClass.getPackage().getName()};
 }

 // create context initializer with base packages
 return new SimpleApplicationContextInitializer(Arrays.asList(basePackages));
 }

 private SimpleApplicationContext createSimpleApplicationContext() {
 return new SimpleApplicationContext();
 }

 private void processSpecialBeans(String[] args) {
 callRegisteredRunners(args);
 }

 private void callRegisteredRunners(String[] args) {
 Map<String, SimpleIocApplicationRunner> applicationRunners = applicationContext.getBeansOfType(SimpleIocApplicationRunner.class);
 try {
  for (SimpleIocApplicationRunner applicationRunner : applicationRunners.values()) {
  applicationRunner.run(args);
  }
 } catch (Exception e) {
  throw new RuntimeException(e);
 }
 }
}

说明

前面说到应用的启动会委托SimpleIocApplication来完成,通过将应用入口类(测试程序中的SampleApplication)传入SimpleIocApplication的构造函数,构造出SimpleIocApplication的一个实例并运行run方法。在run方法中,会首先生成一个applicationContext,并调用SimpleApplicationContextInitializer来完成applicationContext的初始化(bean的扫描、装配)。然后调用processSpecialBeans来处理一些特殊的bean,如实现了SimpleIocApplicationRunner接口的bean会调用run方法来完成一些应用程序的启动任务。
这就是这个ioc容器的整个流程。

SimpleApplicationContextInitializer

代码

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public class SimpleApplicationContextInitializer implements ApplicationContextInitializer<SimpleApplicationContext> {

  private Set<String> basePackages = new LinkedHashSet<>();

  public SimpleApplicationContextInitializer(List<String> basePackages) {
    this.basePackages.addAll(basePackages);
  }

  @Override
  public void initialize(SimpleApplicationContext applicationContext) {
    try {
      applicationContext.scan(basePackages, true);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    applicationContext.setStartupDate(System.currentTimeMillis());
  }
}

说明

在SimpleIocApplication的run中,会根据basePackages来构造一个SimpleApplicationContextInitializer 的实例,进而通过这个ApplicationContextInitializer来完成SimpleApplicationContext 的初始化。
在SimpleApplicationContextInitializer中, 简单地调用SimpleApplicationContext 中的scan即可完成SimpleApplicationContext的初始化任务

SimpleApplicationContext

说明:

终于到了最重要的部分了,在SimpleApplicationContext中将真正完成扫描、生成bean以及自动装配的任务。这里scan即为SimpleApplicationContext的程序入口,由SimpleApplicationContextInitializer在初始化时调用。
代码的调用逻辑简单易懂,就不多加说明了。
这里只简单列一下各个字段的含义以及几个比较关键的方法的作用。

字段

- startupDate:启动时间记录字段
- scannedPackages:已经扫描的包的集合,保证不重复扫描
- registeredBeans:已经完全装配好并注册好了的bean
- earlyBeans : 只是生成好了,还未装配完成的bean,用于处理循环依赖的问题
- totalBeanCount : 所有bean的计数器,在生成bean的名字时会用到其唯一性

方法

- processEarlyBeans:用于最终装配earlyBeans 中的bean,若装配成功,则将bean移至registeredBeans,否则报错
- scan : 扫描并处理传入的package集合
- processSingleClass:处理单个类,尝试生成该类的bean并进行装配(前提是此类有@SimpleComponent注解)
- createBeansByMethodsOfClass : 顾名思义,根据那些被@Bean注解的方法来生成bean
- autowireFields:尝试装配某个bean,lastChance代表是否在装配失败是报错(在第一次装配时,此值为false,在装配失败后会将bean移至earlyBeans,在第二次装配时,此值为true,实际上就是在装配earlyBeans中的bean,因此若仍然装配失败,就会报错)。在这个方法中,装配相应的bean时会从registeredBeans以及earlyBeans中去寻找符合条件的bean,只要找到,不管是来自哪里,都算装配成功。

代码

import com.clayoverwind.simpleioc.context.annotation.SimpleAutowired;
import com.clayoverwind.simpleioc.context.annotation.SimpleBean;
import com.clayoverwind.simpleioc.context.annotation.SimpleComponent;
import com.clayoverwind.simpleioc.context.factory.Bean;
import com.clayoverwind.simpleioc.util.ClassUtil;
import com.clayoverwind.simpleioc.util.ConcurrentHashSet;
import com.clayoverwind.simpleioc.util.LogUtil;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;

/**
 * @author clayoverwind
 * @E-mail clayanddev@163.com
 * @version 2017/4/5
 */

public class SimpleApplicationContext implements ApplicationContext {

 private long startupDate;

 private Set<String> scannedPackages = new ConcurrentHashSet<>();

 private Map<String, Bean> registeredBeans = new ConcurrentHashMap<>();

 private Map<String, Bean> earlyBeans = new ConcurrentHashMap<>();

 private final Logger LOGGER = LogUtil.getLogger(this.getClass());

 AtomicLong totalBeanCount = new AtomicLong(0L);

 AtomicLong nameConflictCount = new AtomicLong(0L);

 @Override
 public Object getBean(String name) {
 return registeredBeans.get(name);
 }

 @Override
 public <T> T getBean(String name, Class<T> type) {
 Bean bean = (Bean)getBean(name);
 return bean == null ? null : (type.isAssignableFrom(bean.getClazz()) ? type.cast(bean.getObject()) : null);
 }

 @Override
 public <T> T getBean(Class<T> type) {
 Map<String, T> map = getBeansOfType(type);
 return map.isEmpty() ? null : type.cast(map.values().toArray()[0]);
 }

 @Override
 public boolean containsBean(String name) {
 return getBean(name) != null;
 }

 @Override
 public <T> Map<String, T> getBeansOfType(Class<T> type) {
 Map<String, T> res = new HashMap<>();
 registeredBeans.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> res.put(entry.getKey(), type.cast(entry.getValue().getObject())));
 return res;
 }

 @Override
 public void setStartupDate(long startupDate) {
 this.startupDate = startupDate;
 }

 @Override
 public long getStartupDate() {
 return startupDate;
 }

 /**
 * try to autowire those beans in earlyBeans
 * if succeed, remove it from earlyBeans and put it into registeredBeans
 * otherwise ,throw a RuntimeException(in autowireFields)
 */
 private synchronized void processEarlyBeans() {
 for (Map.Entry<String, Bean> entry : earlyBeans.entrySet()) {
  Bean myBean = entry.getValue();
  try {
  if (autowireFields(myBean.getObject(), myBean.getClazz(), true)) {
   registeredBeans.put(entry.getKey(), myBean);
   earlyBeans.remove(entry.getKey());
  }
  } catch (IllegalAccessException e) {
  throw new RuntimeException(e);
  }
 }
 }

 /**
 * scan base packages and create beans
 * @param basePackages
 * @param recursively
 * @throws ClassNotFoundException
 */
 public void scan(Set<String> basePackages, boolean recursively) throws ClassNotFoundException, IOException {
 LOGGER.info("start scanning......");

 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

 // get all classes who haven't been registered
 Set<Class<?>> classes = new LinkedHashSet<>();
 for (String packageName : basePackages) {
  if (scannedPackages.add(packageName)) {
  classes.addAll(ClassUtil.getClassesByPackageName(classLoader, packageName, recursively));
  }
 }

 // autowire or create bean for each class
 classes.forEach(this::processSingleClass);

 processEarlyBeans();

 LOGGER.info("scan over!");
 }

 /**
 * try to create a bean for certain class, put it into registeredBeans if success, otherwise put it into earlyBeans
 * @param clazz
 */
 private void processSingleClass(Class<?> clazz) {
 LOGGER.info(String.format("processSingleClass [%s] ...", clazz.getName()));

 Annotation[] annotations = clazz.getDeclaredAnnotations();
 for (Annotation annotation : annotations) {
  if (annotation instanceof SimpleComponent) {
  Object instance;
  try {
   instance = clazz.newInstance();
  } catch (InstantiationException e) {
   throw new RuntimeException(e);
  } catch (IllegalAccessException e) {
   throw new RuntimeException(e);
  }

  long beanId = totalBeanCount.getAndIncrement();
  SimpleComponent component = (SimpleComponent) annotation;
  String beanName = component.value();
  if (beanName.isEmpty()) {
   beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId);
  }

  try {
   if (autowireFields(instance, clazz, false)) {
   registeredBeans.put(beanName, new Bean(instance, clazz));
   } else {
   earlyBeans.put(beanName, new Bean(instance, clazz));
   }
  } catch (IllegalAccessException e) {
   throw new RuntimeException(e);
  }

  try {
   createBeansByMethodsOfClass(instance, clazz);
  } catch (InvocationTargetException e) {
   throw new RuntimeException(e);
  } catch (IllegalAccessException e) {
   throw new RuntimeException(e);
  }
  }
 }
 }

 private void createBeansByMethodsOfClass(Object instance, Class<?> clazz) throws InvocationTargetException, IllegalAccessException {
 List<Method> methods = getMethodsWithAnnotation(clazz, SimpleBean.class);
 for (Method method : methods) {
  method.setAccessible(true);
  Object methodBean = method.invoke(instance);
  long beanId = totalBeanCount.getAndIncrement();
  Class<?> methodBeanClass = methodBean.getClass();

  //bean name
  SimpleBean simpleBean = method.getAnnotation(SimpleBean.class);
  String beanName = simpleBean.value();
  if (beanName.isEmpty()) {
  beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId);
  }

  // register bean
  registeredBeans.put(beanName, new Bean(methodBean, methodBeanClass));
 }
 }

 private List<Method> getMethodsWithAnnotation(Class<?> clazz, Class<?> annotationClass) {
 List<Method> res = new LinkedList<>();
 Method[] methods = clazz.getDeclaredMethods();
 for (Method method : methods) {
  Annotation[] annotations = method.getAnnotations();
  for (Annotation annotation : annotations) {
  if (annotation.annotationType() == annotationClass) {
   res.add(method);
   break;
  }
  }
 }
 return res;
 }

 /**
 * try autowire all fields of a certain instance
 * @param instance
 * @param clazz
 * @param lastChance
 * @return true if success, otherwise return false or throw a exception if this is the lastChance
 * @throws IllegalAccessException
 */
 private boolean autowireFields(Object instance, Class<?> clazz, boolean lastChance) throws IllegalAccessException {
 Field[] fields = clazz.getDeclaredFields();
 for (Field field : fields) {
  Annotation[] annotations = field.getAnnotations();
  for (Annotation annotation : annotations) {
  if (annotation instanceof SimpleAutowired) {
   SimpleAutowired autowired = (SimpleAutowired) annotation;
   String beanName = autowired.value();
   Bean bean = getSimpleBeanByNameOrType(beanName, field.getType(), true);
   if (bean == null) {
   if (lastChance) {
    if (!autowired.required()) {
    break;
    }
    throw new RuntimeException(String.format("Failed in autowireFields : [%s].[%s]", clazz.getName(), field.getName()));
   } else {
    return false;
   }
   }
   field.setAccessible(true);
   field.set(instance, bean.getObject());
  }
  }
 }
 return true;
 }

 /**
 * only used in autowireFields
 * @param beanName
 * @param type
 * @param allowEarlyBean
 * @return
 */
 private Bean getSimpleBeanByNameOrType(String beanName, Class<?> type, boolean allowEarlyBean) {
 // 1. by name
 Bean res = registeredBeans.get(beanName);
 if (res == null && allowEarlyBean) {
  res = earlyBeans.get(beanName);
 }

 // 2. by type
 if (type != null) {
  if (res == null) {
  res = getSimpleBeanByType(type, registeredBeans);
  }
  if (res == null && allowEarlyBean) {
  res = getSimpleBeanByType(type, earlyBeans);
  }
 }

 return res;
 }

 /**
 * search bean by type in certain beans map
 * @param type
 * @param beansMap
 * @return
 */
 private Bean getSimpleBeanByType(Class<?> type, Map<String, Bean> beansMap) {
 List<Bean> beans = new LinkedList<>();
 beansMap.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> beans.add(entry.getValue()));
 if (beans.size() > 1) {
  throw new RuntimeException(String.format("Autowire by type, but more than one instance of type [%s] is founded!", beans.get(0).getClazz().getName()));
 }
 return beans.isEmpty() ? null : beans.get(0);
 }

 private String getUniqueBeanNameByClassAndBeanId(Class<?> clazz, long beanId) {
 String beanName = clazz.getName() + "_" + beanId;
 while (registeredBeans.containsKey(beanName) || earlyBeans.containsKey(beanName)) {
  beanName = clazz.getName() + "_" + beanId + "_" + nameConflictCount.getAndIncrement();
 }
 return beanName;
 }
}

后记

至此,一个简单的ioc容器就完成了,总结一下优缺点。

优点:

小而简单。
可以使用@SimpleBean、@SimpleComponent以及@SimpleAutowired 来完成一些简单但常用的依赖注入任务.

缺点:

很明显,实现过于简单,提供的功能太少。
如果你想了解ioc的实现原理,或者你想要开发一个小型个人项目但又嫌spring过于庞大,这个简单的ioc容器或许可以帮到你。

如果你想做的不仅如此,那么你应该将目光转向spring-boot

完整代码参考:https://github.com/clayandgithub/simple-ioc

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • MVC使用Spring.Net应用IOC(依赖倒置)学习笔记3

    到现在,我们已经基本搭建起了项目的框架,但是项目中还存在一个问题,就是尽管层与层之间使用了接口进行隔离,但实例化接口的时候,还是引入了接口实现类的依赖,如下面的代码: private IUserService _userService; private IUserService UserService { get { return _userService ?? (_userService = new UserService()); } set { _userService = value; }

  • Spring中IoC优点与缺点解析

    本文为大家分享了Spring中IoC优点与缺点,供大家参考,具体内容如下 1. 优点 我们知道,在Java基本教程中有一个定律告诉我们:所有的对象都必须创建:或者说:使用对象之前必须创建,但是现在我们可以不必一定遵循这个定律了,我们可以从Ioc容器中直接获得一个对象然后直接使用,无需事先创建它们. 这种变革,就如同我们无需考虑对象销毁一样:因为Java的垃圾回收机制帮助我们实现了对象销毁:现在又无需考虑对象创建,对象的创建和销毁都无需考虑了,这给编程带来的影响是巨大的. 我们从一个简单例子开始,

  • 利用Spring IOC技术实现用户登录验证机制

    利用 Spring IOC 技术实现用户登录的验证机制,对用户进行登录验证. 首先利用 Spring 的自动装配模式将 User 对象注入到控制器中,然后将用户输入的用户名和密码与系统中限定的合法用户的用户名和密码进行匹配. 当用户名与密码匹配成功时,跳转到登录成功页面:当用户名与密码不匹配时,跳转到登录失败的页面. 1.创建 User 对象,定义用户名和密码属性,代码如下: package com.importnew; public class User { private String us

  • Spring核心IoC和AOP的理解

    spring 框架的优点是一个轻量级笔记简单易学的框架,实际使用中的有点优点有哪些呢! 1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦 2.可以使用容易提供的众多服务,如事务管理,消息服务等 3.容器提供单例模式支持 4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能 5.容器提供了众多的辅助类,能加快应用的开发 6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等 7.spring属于低侵入式设计,代码的污染极低 8.独立于

  • 浅析Java的Spring框架中IOC容器容器的应用

    Spring容器是Spring框架的核心.容器将创建对象,它们连接在一起,配置它们,并从创建到销毁管理他们的整个生命周期.在Spring容器使用依赖注入(DI)来管理组成应用程序的组件.这些对象被称为Spring Beans. 容器获得其上的哪些对象进行实例化,配置和组装通过阅读提供的配置元数据的说明.配置元数据可以通过XML,Java注释或Java代码来表示.下面的图是Spring如何工作的高层次图. Spring IoC容器是利用Java的POJO类和配置元数据的产生完全配置和可执行的系统或

  • Spring实现IoC的多种方式小结

    控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法.没有IoC的程序中我们使用面向对象编程对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了. IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现Io

  • 深入理解Java的Spring框架中的IOC容器

    Spring IOC的原型 spring框架的基础核心和起点毫无疑问就是IOC,IOC作为spring容器提供的核心技术,成功完成了依赖的反转:从主类的对依赖的主动管理反转为了spring容器对依赖的全局控制. 这样做的好处是什么呢? 当然就是所谓的"解耦"了,可以使得程序的各模块之间的关系更为独立,只需要spring控制这些模块之间的依赖关系并在容器启动和初始化的过程中将依据这些依赖关系创建.管理和维护这些模块就好,如果需要改变模块间的依赖关系的话,甚至都不需要改变程序代码,只需要将

  • 用java的spring实现一个简单的IOC容器示例代码

    要想深入的理解IOC的技术原理,没有什么能比的上我们自己实现它.这次我们一起实现一个简单IOC容器.让大家更容易理解Spring IOC的基本原理. 这里会涉及到一些java反射的知识,如果有不了解的,可以自己去找些资料看看. 注意 在上一篇文章,我说,启动IOC容器时,Spring会将xml文件里面配置的bean扫描并实例化,其实这种说法不太准确,所以我在这里更正一下,xml文件里面配置的非单利模式的bean,会在第一次调用的时候被初始化,而不是启动容器的时候初始化.但是我们这次要做的例子是容

  • Spring学习笔记1之IOC详解尽量使用注解以及java代码

    在实战中学习Spring,本系列的最终目的是完成一个实现用户注册登录功能的项目. 预想的基本流程如下: 1.用户网站注册,填写用户名.密码.email.手机号信息,后台存入数据库后返回ok.(学习IOC,mybatis,SpringMVC的基础知识,表单数据验证,文件上传等) 2.服务器异步发送邮件给注册用户.(学习消息队列) 3.用户登录.(学习缓存.Spring Security) 4.其他. 边学习边总结,不定时更新.项目环境为Intellij + Spring4. 一.准备工作. 1.m

  • 详解Spring框架---IOC装配Bean

    IOC装配Bean (1)Spring框架Bean实例化的方式提供了三种方式实例化Bean 构造方法实例化(默认无参数,用的最多) 静态工厂实例化 实例工厂实例化 下面先写这三种方法的applicationContext.xml配置文件: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"

随机推荐