项目地址:https://github.com/xiaogou446/jsonboot
使用branch:implementIOC
命令行:git checkout implementIOC
构建IOC容器
书接上节,我们大致完成了SpringMVC的相关功能,通过外部的请求进入到处理完请求的链路已经打通,但是还是有着不少的问题。比如在最后通过反射执行请求时,每执行一次,都需要newInstance创建一个对象,那足以造成资源的浪费,以及如果需要在一个类中引用另一个类的方法呢,再创建一次引用类对象?显然不合时宜,所以我们迫切的需要一个管理所需对象的容器,每次调用方法时直接在容器中取出对象,而不是重新创建。本节,就来创建一个简单的IOC容器。
先定义 @Component注解,被该注解标注的类,代表可以被加载到IOC的容器中。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Component {
String value() default "";
}
加载类,进行范围的类扫描,将标有 @Component注解和 @RestController注解的类在特定的包路径下都扫描出来。
/**
* 加载包路径下的类
*
* @param packageName 需要加载类的包路径
*/
public static void loadClass(String packageName){
AnnotatedClassScanner annotatedScanner = new AnnotatedClassScanner();
Set<Class<?>> restControllerClasses = annotatedScanner.scan(packageName, RestController.class);
Set<Class<?>> componentClasses = annotatedScanner.scan(packageName, Component.class);
ApplicationContext.CLASSES.put(RestController.class, restControllerClasses);
ApplicationContext.CLASSES.put(Component.class, componentClasses);
}
定义BEANS用来存储bean,以后需要获取对象的实例直接在BEANS这个map中取,而不是自己实例化。
/**
* 设置存放Bean的map
*/
public static final Map<String, Object> BEANS = new ConcurrentHashMap<>(128);
对扫描到的类进行实例化,实例化后的未完全体收录到BEANS中。当前BEANS存放的是没有进行依赖注入的bean,其中的方法如果在这个时候进行调用都会出现大问题。
/**
* 加载bean 根据类生成对应的bean存入BEANS中
*/
public static void loadBeans(){
Map<Class<? extends Annotation>, Set<Class<?>>> classes = ApplicationContext.CLASSES;
classes.forEach((key, value) -> {
if (key == Component.class){
for (Class<?> aClass : value){
Component component = aClass.getAnnotation(Component.class);
//获取beanName,如果Component上有设置则使用Component的value,不然使用默认的Class名称。
String beanName = StringUtils.isBlank(component.value()) ? aClass.getName() : component.value();
Object instance = ReflectionUtil.newInstance(aClass);
BEANS.put(beanName, instance);
}
}
if (key == RestController.class){
for (Class<?> aClass : value){
Object instance = ReflectionUtil.newInstance(aClass);
BEANS.put(aClass.getName(), instance);
}
}
});
}
依赖注入
当我们将实例化后的bean存放到BEANS中,其中的每个bean都需要进行依赖注入才能称作是完整的bean。
定义依赖注入的注解 @Autowired,当注入的字段被标注 @Autowired时,代表会从IOC容器中取出这个bean,执行注入。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface Autowired {
}
依赖注入的过程其实很简单,遍历BEANS中的每个元素bean,再遍历元素bean中注入的依赖,找到依赖上是否有标注 @Autowired注解,如果有标注,且不是接口,则通过类名去BEANS中找到对应的bean注入,如果是接口,那么需要扫描范围内的实现类,获取实现类的名称从BEANS取得进行注入。
/**
* 执行依赖注入 @Autowired
*/
public static void loadDependency(String packageName){
Map<String, Object> beanMap = BeanFactory.BEANS;
//遍历每个bean
beanMap.forEach((beanName, bean) -> {
for (Field field : bean.getClass().getDeclaredFields()){
if (field.isAnnotationPresent(Autowired.class)){
String className;
Class<?> fieldTypeClass = field.getType();
//判断是否是接口 如果是接口那存着的就是接口信息,需要顺延找到实现类
if (fieldTypeClass.isInterface()){
Set<Class<?>> subClass = getSubClass(packageName, fieldTypeClass);
//实现类的名字肯定在ioc容器中,目前只考虑单个实现累的情况,如果是多个需要用@Qulifer
className = subClass.iterator().next().getName();
}else{
className = fieldTypeClass.getName();
}
//根据类型的类名获取
Object targetBean = beanMap.get(className);
if (targetBean != null){
ReflectionUtil.setReflectionField(bean, field, targetBean);
}
}
}
});
}
/**
* 获取接口的对应实现类
*
* @param packageName 包名
* @param interfaceClass 接口
* @return 接口的实现类
*/
public static Set<Class<?>> getSubClass(String packageName, Class<?> interfaceClass) {
Reflections reflections = new Reflections(packageName);
return reflections.getSubTypesOf((Class<Object>) interfaceClass);
}
在SpringMVC方法的执行时,也优先从BEANS中取对象。
public static Object executeMethod(Method method, Object... args) {
Object result = null;
try {
String beanName = null;
Object targetObject;
//先判断是否已经生成该对象了 直接在ioc的容器中取出来
Class<?> targetClass = method.getDeclaringClass();
if (targetClass.isAnnotationPresent(RestController.class)){
beanName = targetClass.getName();
}
if (targetClass.isAnnotationPresent(Component.class)){
Component component = targetClass.getAnnotation(Component.class);
beanName = StringUtils.isBlank(component.value()) ? targetClass.getName() : component.value();
}
if (StringUtils.isNotEmpty(beanName)){
targetObject = BeanFactory.BEANS.get(beanName);
}else{
targetObject = method.getDeclaringClass().newInstance();
}
// 调用对象的方法
result = method.invoke(targetObject, args);
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
e.printStackTrace();
}
return result;
}
以上就完成了基础IOC容器的实现,最后BEANS中存储的都是初始化完成的bean,可以进行直接的使用。
使用branch:feature/impQualifier
命令行:git checkout feature/impQualifier
实现@Qualifier
在进行bean的依赖注入时,如果注入的是接口,且有多个不同的实现类,这样如果不通过 @Qualifier指定对应注入名称,那么就会出现异常。这时通过 @Qualifier指定对应的名称,即会获取到目标实现类,进行注入。
定义 @Qualifier注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface Qualifier {
String value() default "";
}
作用在依赖注入的地方,如果注入的依赖是接口,获取他的实现类个数,如果有大于两个,则通过@Qualifier注解进行获取定义的bean的名称。进行依赖注入。
/**
* 执行依赖注入 @Autowired
*/
public static void loadDependency(String packageName){
Map<String, Object> beanMap = BeanFactory.BEANS;
//遍历每个bean
beanMap.forEach((beanName, bean) -> {
for (Field field : bean.getClass().getDeclaredFields()){
if (field.isAnnotationPresent(Autowired.class)){
Class<?> fieldTypeClass = field.getType();
String className = fieldTypeClass.getName();
//判断是否是接口 如果是接口那存着的就是接口信息,需要顺延找到实现类
if (fieldTypeClass.isInterface()){
Set<Class<?>> subClass = ReflectionUtil.getSubClass(packageName, fieldTypeClass);
if (subClass.size() == 0){
throw new InterfaceNotExistsImplementException("接口的实现类不存在");
}else if (subClass.size() == 1){
//实现类的名字肯定在ioc容器中,目前只考虑单个实现累的情况,如果是多个需要用@Qulifer
className = subClass.iterator().next().getName();
}else{
//获取注解@Qualifier上的值
Qualifier qualifier = field.getDeclaredAnnotation(Qualifier.class);
className = StringUtils.isBlank(qualifier.value()) ? beanName : qualifier.value();
}
}
//根据类型的类名获取
Object targetBean = beanMap.get(className);
if (targetBean == null){
throw new NotFountTargetBeanException("该依赖没有找到目标类:" + field.toString());
}
ReflectionUtil.setReflectionField(bean, field, targetBean);
}
}
});
}
测试


以上实现了一个简单的IOC容器,个人觉得问题还是比较多的,在下一节中进行修改。