Appearance
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控制权则交还给了原来的实现类,只有实现类自身注入你才能找到。那么怎样根据接口就能找到实现类并赋值呢,主要分为以下几个步骤:
- 遍历Bean容器中所有的Class对象
- 找到Class中被Autowired标记的成员变量
- 获取这些成员变量的类型(一般为接口)
- 在容器的Map集合中找到接口的实现类(暂未实现getFieldInstance方法)
- 通过反射给成员变量赋值
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
用于获取接口的实现类;整理后流程如下:
- 先尝试通过class对象直接从集合中获取Bean实例,如果找到则直接返回
- 如果没找到则寻找接口实现类的.class文件
- 如果多于两个实现类且用户未指定其中一个实现类,则抛出异常
- .如果有多个实现类,且指定了其中一个实现类,则返回该实现类
- 根据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框架编写的,我们来对比分析一下。
@Configuration
:这个注解表示Entrance
类是一个Spring Boot应用程序的配置类。它告诉Spring Boot扫描com.xk857
包以查找其他组件,并进行相应的配置;我们没有实现这个注解,只能通过方法传递参数的方式扫描类;ApplicationContext
:Spring使用外观(门面)设计模式隐藏了很多逻辑,创建这个对象的同学就已经执行了IOC注入操作,我们需要手动调用doIoc
;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();
}
}