Skip to content

Spring支持成员变量注入,构造器注入以及setter注入,我们为了简单@Autowired目前仅支持成员变量注入,主要是编写IOC的核心逻辑

创建注解@Autowired

java
package org.simpleframework.inject.annotation;

@Target(ElementType.FIELD) // 由于仅支持成员变量注入,因此只能放置在成员变量上,不能在方法、
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
    String value() default "";
}

执行IOC

IOC即控制反转,何为控制反转?传统的开发中控制权在使用方,使用方想使用就使用;而使用IOC控制权则交还给了原来的实现类,只有实现类自身注入你才能找到。那么怎样根据接口就能找到实现类并赋值呢,主要分为以下几个步骤:

  1. 遍历Bean容器中所有的Class对象
  2. 找到Class中被Autowired标记的成员变量
  3. 获取这些成员变量的类型(一般为接口)
  4. 在容器的Map集合中找到接口的实现类(暂未实现getFieldInstance方法)
  5. 通过反射给成员变量赋值
java
package org.simpleframework.inject;

@Slf4j
public class DependencyInjector {

    private final BeanContainer beanContainer;
    public DependencyInjector() {
        beanContainer = BeanContainer.getInstance();
    }

    /**
     * 执行Ioc
     */
    public void doIoc() {
        if (ValidationUtil.isEmpty(beanContainer.getClasses())) {
            log.warn("bean容器集合为空,IOC未执行");
            return;
        }

        // 1.遍历Bean容器中所有的Class对象
        for (Class<?> clazz : beanContainer.getClasses()) {
            // 2.遍历Class对象的所有成员变量
            Field[] fields = clazz.getDeclaredFields();
            if (ValidationUtil.isEmpty(fields)) {
                // 2.1 如果数组内无元素,则跳过
                continue;
            }
            for (Field field : fields) {
                // 3.找出被Autowired标记的成员变量
                if (field.isAnnotationPresent(Autowired.class)) {
                    Autowired autowired = field.getAnnotation(Autowired.class);
                    String autowiredValue = autowired.value();
                    // 4.获取这些成员变量的类型
                    Class<?> fieldClass = field.getType();
                    // 5.获取这些成员变量的类型在容器里对应的实例
                    Object fieldValue = getFieldInstance(fieldClass, autowiredValue);
                    if (fieldValue == null) {
                        throw new RuntimeException("无法注入相关类型,目标fieldClass为:" + fieldClass.getName() + "\tautowiredValue:" + autowiredValue);
                    } else {
                        // 6.1 根据Class对象获取到bean实例
                        Object targetBean = beanContainer.getBean(clazz);
                        // 6.2 设置成员变量的值为targetBean
                        ClassUtil.setField(field, targetBean, fieldValue, true);
                    }
                }
            }
        }
    }
}

完善IOC注入

getFieldInstance方法用于在bean集合获取实例或实现类;getImplementedClass用于获取接口的实现类;整理后流程如下:

  1. 先尝试通过class对象直接从集合中获取Bean实例,如果找到则直接返回
  2. 如果没找到则寻找接口实现类的.class文件
    • 如果多于两个实现类且用户未指定其中一个实现类,则抛出异常
    • .如果有多个实现类,且指定了其中一个实现类,则返回该实现类
  3. 根据class对象,在Bean集合找到具体实现并返回
java
package org.simpleframework.inject;

@Slf4j
public class DependencyInjector {
    
	/** 根据Class在beanContainer里获取其实例或者实现类 */
    private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) {
        // 1.先尝试通过class对象获取Bean实例
        Object fieldValue = beanContainer.getBean(fieldClass);
        if (fieldValue == null) {
            // 2.如果没有获取到,则寻找接口实现的class
            Class<?> implementedClass = getImplementedClass(fieldClass, autowiredValue);
            // 3.根据class在bean集合找到具体实现并返回
            return implementedClass == null ? null : beanContainer.getBean(implementedClass);
        }
        // 如果能直接根据class则直接返回
        return fieldValue;
    }
    
    /** 获取接口的实现类 */
    private Class<?> getImplementedClass(Class<?> fieldClass, String autowiredValue) {
        // 1.通过接口或者父类获取实现类或者子类的Class集合,不包括其本身
        Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass);

        if (!ValidationUtil.isEmpty(classSet)) {
            if (classSet.size() == 1) {
                // 2. 如果只有一个实现类,则直接返回
                return classSet.iterator().next();
            }
            if (ValidationUtil.isEmpty(autowiredValue)) {
                // 3.如果有多个实现类,且未指定其中一个实现类,抛出异常
                throw new RuntimeException("找到多个实现类 " + fieldClass.getName() + " 请设置 @Autowired 的值选择一个");
            } else {
                // 4.如果有多个实现类,且指定了其中一个实现类,则返回该实现类
                for (Class<?> clazz : classSet) {
                    if (autowiredValue.equals(clazz.getSimpleName())) {
                        return clazz;
                    }
                }
            }
        }
        // 兜底操作,如果没找到实现类,或有多个实现类并指定实现类,但未摘到则返回null
        return null;
    }
}

注意事项

在Spring中如何一个接口有多个实现类使用@Qualifier指定实现类,我们为了简单直接为@Autowired设置value属性,替代@Qualifier注解

java
@Autowired // @Autowired("birth")
@Qualifier("birth")
private Date birthday ;

番外篇:比对Spring和我们自己创建的IOC使用方式

java
@Configuration
@ComponentScan("com.xk857")
public class Entrance {
	public static void main(String[] args) {
        // 1.创建一个应用程序上下文对象,使用Entrance类作为配置源
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Entrance.class);
        
        // 2.获取所有的bean定义名称,并打印
		String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
		for (String beanDefinitionName : beanDefinitionNames) {
			System.out.println(beanDefinitionName);
		}
        
        // 3.从应用程序上下文中获取WelcomeController Bean,并调用其方法
		WelcomeController welcomeController = (WelcomeController) applicationContext.getBean("welcomeController");
		welcomeController.handleRequest();
	}
}

@ComponentScan注解我们没有编写,而是直接使用传值的方式,比对一下我们自研IOC的写法,体会Spring的设计精髓;

上面这段代码是使用Spring框架编写的,我们来对比分析一下。

  1. @Configuration:这个注解表示Entrance类是一个Spring Boot应用程序的配置类。它告诉Spring Boot扫描com.xk857包以查找其他组件,并进行相应的配置;我们没有实现这个注解,只能通过方法传递参数的方式扫描类;
  2. ApplicationContext:Spring使用外观(门面)设计模式隐藏了很多逻辑,创建这个对象的同学就已经执行了IOC注入操作,我们需要手动调用doIoc
  3. applicationContext.getBean:Spring可以根据Bean的名称查询,我们没有实现这个功能,只能传递class对象;
java
public class Entrance {
    public static void main(String[] args) {
        // 1.扫描com.xk857包下的所有类,Spring使用@ComponentScan注解实现
        BeanContainer container = BeanContainer.getInstance();
        container.loadBeans("com.xk857");

        // 2.开始扫描,这一步在new AnnotationConfigApplicationContext时Spring就已经完成了这个操作
        DependencyInjector injector = new DependencyInjector();
        injector.doIoc();

        // 3.我们没有实现根据字符串名称查找的功能,只能根据类对象查找
        WelcomeController mainPageController = (WelcomeController) container.getBean(WelcomeController.class);
        welcomeController.handleRequest();
    }
}