本文是爲了學習Spring IOC
容器的執行過程而寫,不能徹底表明Spring IOC
容器,只是簡單實現了容器的依賴注入和控制反轉功能,沒法用於生產,只能說對理解Spring容器可以起到必定的做用。java
建立Gradle項目,並修改build.gradle
git
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
的方法。github
package io.github.gcdd1993.ioc.bean; /** * bean factory interface * * @author gaochen * @date 2019/6/2 */ public interface BeanFactory { /** * 經過bean名稱獲取bean * * @param name bean名稱 * @return bean */ Object getBean(String name); /** * 經過bean類型獲取bean * * @param tClass bean類型 * @param <T> 泛型T * @return bean */ <T> T getBean(Class<T> tClass); } 複製代碼
ApplicationContext
上下文ApplicationContext
,即咱們常說的應用上下文,實際就是Spring容器自己了。bash
咱們建立ApplicationContext
類,並實現BeanFactory
接口。markdown
public class ApplicationContext implements BeanFactory { } 複製代碼
getBean
方法既然說是容器,那確定要有地方裝咱們的bean實例吧,使用兩個Map做爲容器。maven
/** * 按照beanName分組 */ private final Map<String, Object> beanByNameMap = new ConcurrentHashMap<>(256); /** * 按照beanClass分組 */ private final Map<Class<?>, Object> beanByClassMap = new ConcurrentHashMap<>(256); 複製代碼
而後,咱們能夠先完成咱們的getBean
方法。ide
@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容器。oop
Spring提供了@ComponentScan
來掃描包下的Component
,咱們爲了簡便,直接在構造器中指定要掃描的包。學習
private final Set<String> basePackages; /** * 默認構造器,默認掃描當前所在包 */ public ApplicationContext() { this(new HashSet<>(Collections.singletonList(ApplicationContext.class.getPackage().getName()))); } /** * 全參構造器 * @param basePackages 掃描的包名列表 */ public ApplicationContext(Set<String> basePackages) { this.basePackages = basePackages; } 複製代碼
refresh
方法refresh的過程基本按照如下流程來走測試
@Bean
註解(Spring中是@Component
註解)的類。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(); } } 複製代碼
Constructor constructor = beanClass.getDeclaredConstructor();
Object object = constructor.newInstance();
Field[] fields = beanClass.getDeclaredFields();
複製代碼
判斷字段是依賴注入的仍是普通字段。
若是是普通字段,經過字段類型初始化該字段,並嘗試從@Value
註解獲取值塞給字段。
Value value = field.getAnnotation(Value.class); if (value != null) { // 注入 field.setAccessible(true); // 須要作一些類型轉換,從String轉爲對應的類型 field.set(object, value.value()); } 複製代碼
beanByClassMap
中獲取對應的實例,若是沒有,就先要去實例化該字段對應的類型。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); } 複製代碼
建立Address
@Data @Bean public class Address { @Value("2222") private String longitude; @Value("1111") private String latitude; } 複製代碼
建立Person
並注入Address
@Data @Bean public class Person { @Autowired private Address address; @Value("gaochen") private String name; @Value("27") private String age; } 複製代碼
建立測試類ApplicationContextTest
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); } } 複製代碼
控制檯將會輸出:
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