Spring boot实现一个简单的ioc(2)
前言
跳过废话,直接看正文
仿照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容器或许可以帮到你。
如果你想做的不仅如此,那么你应该将目光转向。
完整代码参考:。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。