前言
本文是为了学习Spring IOC
容器的执行过程而写,不能完全代表Spring IOC
容器,只是简单实现了容器的依赖注入和控制反转功能,无法用于生产,只能说对理解Spring容器能够起到一定的作用。
开始
创建项目
创建Gradle项目,并修改build.gradle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| plugins { id 'java' id "io.franzbecker.gradle-lombok" version "3.1.0" }
group 'io.github.gcdd1993' version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories { mavenCentral() }
dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' }
|
创建BeanFactory
BeanFactory
是IOC中用于存放bean实例以及获取bean的核心接口,它的核心方法是getBean
以及getBean
的重载方法,这里简单实现两个getBean
的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package io.github.gcdd1993.ioc.bean;
public interface BeanFactory {
Object getBean(String name);
<T> T getBean(Class<T> tClass);
}
|
创建ApplicationContext
上下文
ApplicationContext
,即我们常说的应用上下文,实际就是Spring容器本身了。
我们创建ApplicationContext
类,并实现BeanFactory
接口。
1 2
| public class ApplicationContext implements BeanFactory { }
|
getBean
方法
既然说是容器,那肯定要有地方装我们的bean实例吧,使用两个Map作为容器。
1 2 3 4 5 6 7 8 9
|
private final Map<String, Object> beanByNameMap = new ConcurrentHashMap<>(256);
private final Map<Class<?>, Object> beanByClassMap = new ConcurrentHashMap<>(256);
|
然后,我们可以先完成我们的getBean
方法。
1 2 3 4 5 6 7 8 9
| @Override public Object getBean(String name) { return beanByNameMap.get(name); }
@Override public <T> T getBean(Class<T> tClass) { return tClass.cast(beanByClassMap.get(tClass)); }
|
直接从Map中获取bean实例,是不是很简单?当然了,在真实的Spring容器中,是不会这么简单啦,不过我们这次是要化繁为简,理解IOC容器。
构造器
Spring提供了@ComponentScan
来扫描包下的Component
,我们为了简便,直接在构造器中指定要扫描的包。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private final Set<String> basePackages;
public ApplicationContext() { this(new HashSet<>(Collections.singletonList(ApplicationContext.class.getPackage().getName()))); }
public ApplicationContext(Set<String> basePackages) { this.basePackages = basePackages; }
|
refresh
方法
refresh的过程基本按照以下流程来走

- 扫描指定的包下所有带
@Bean
注解(Spring中是@Component
注解)的类。
1 2 3 4 5 6 7 8 9 10
| List<Class> beanClasses = PackageScanner.findClassesWithAnnotation(packageName, Bean.class); System.out.println("scan classes with Bean annotation : " + beanClasses.toString());
for (Class beanClass : beanClasses) { try { createBean(beanClass); } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { e.printStackTrace(); } }
|
- 遍历类,获取类的构造器以及所有字段。
1 2 3
| Constructor constructor = beanClass.getDeclaredConstructor(); Object object = constructor.newInstance(); Field[] fields = beanClass.getDeclaredFields();
|
判断字段是依赖注入的还是普通字段。
如果是普通字段,通过字段类型初始化该字段,并尝试从@Value
注解获取值塞给字段。
1 2 3 4 5 6 7
| Value value = field.getAnnotation(Value.class); if (value != null) { field.setAccessible(true); field.set(object, value.value()); }
|
- 如果是依赖注入的字段,尝试从
beanByClassMap
中获取对应的实例,如果没有,就先要去实例化该字段对应的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| Autowired autowired = field.getAnnotation(Autowired.class); if (autowired != null) { String name = autowired.name(); Object diObj; if (!name.isEmpty()) { diObj = beanByNameMap.get(name) == null ? createBean(name) : beanByNameMap.get(name); } else { Class<?> aClass = field.getType(); diObj = beanByClassMap.get(aClass) == null ? createBean(aClass) : beanByClassMap.get(aClass); } field.setAccessible(true); field.set(object, diObj); }
|
测试我们的IOC容器
创建Address
1 2 3 4 5 6 7 8 9
| @Data @Bean public class Address { @Value("2222") private String longitude;
@Value("1111") private String latitude; }
|
创建Person
并注入Address
1 2 3 4 5 6 7 8 9 10 11 12
| @Data @Bean public class Person { @Autowired private Address address;
@Value("gaochen") private String name;
@Value("27") private String age; }
|
创建测试类ApplicationContextTest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class ApplicationContextTest {
@Test public void refresh() { Set<String> basePackages = new HashSet<>(1); basePackages.add("io.github.gcdd1993.ioc"); ApplicationContext ctx = new ApplicationContext(basePackages); ctx.refresh();
Person person = ctx.getBean(Person.class); System.out.println(person);
Object person1 = ctx.getBean("Person"); System.out.println(person1); } }
|
控制台将会输出:
1 2 3 4
| scan classes with Bean annotation : [class io.github.gcdd1993.ioc.util.Address, class io.github.gcdd1993.ioc.util.Person] scan classes with Bean annotation : [class io.github.gcdd1993.ioc.util.Address, class io.github.gcdd1993.ioc.util.Person] Person(address=Address(longitude=2222, latitude=1111), name=gaochen, age=27) Person(address=Address(longitude=2222, latitude=1111), name=gaochen, age=27)
|
可以看到,我们成功将Address实例注入到了Person实例中,并且将它们存储在了我们自己的IOC容器中。其实,Spring容器的原理大致就是如此,只不过为了应对企业级开发,提供了很多便捷的功能,例如bean的作用域、bean的自定义方法等等。
获取源码
完整源码可以在我的github
仓库获取👉Simple-IOC-Container