前言
跳过废话,直接看正文
仿照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 }</div>
说明
@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 ""; }</div>
说明
@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 ""; }</div>
说明
@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 {}; }</div>
说明
@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); } } }</div>
说明
前面说到应用的启动会委托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()); } }</div>
说明
在SimpleIocApplication的run中,会根据basePackages来构造一个SimpleApplicationContextInitializer 的实例,进而通过这个ApplicationContextInitializer来完成SimpleApplicationContext 的初始化。
在SimpleApplicationContextInitializer中, 简单地调用SimpleApplicationContext 中的scan即可完成SimpleApplicationContext的初始化任务
SimpleApplicationContext
说明:
终于到了最重要的部分了,在SimpleApplicationContext中将真正完成扫描、生成bean以及自动装配的任务。这里scan即为SimpleApplicationContext的程序入口,由SimpleApplicationConte