当前位置: 移动技术网 > IT编程>开发语言>Java > Spring boot实现一个简单的ioc(2)

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

2019年07月22日  | 移动技术网IT编程  | 我要评论

前言

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

正文

项目结构

实际上三四个类完全能搞定这个简单的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容器或许可以帮到你。

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

完整代码参考:。

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网