仿照spring-boot实现一个简单的ioc容器(二)

本文介绍了一个模仿Spring Boot的简易IoC容器实现,包括项目结构、关键注解及核心类的解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

跳过废话,直接看正文
仿照spring-boot的项目结构以及部分注解,写一个简单的ioc容器。
测试代码完成后,便正式开始这个ioc容器的开发工作。


正文

项目结构

  • simpleioc
    • boot
      • SimpleIocApplication.java
      • SimpleIocApplicationRunner.java
      • SimpleIocBootApplication.java
    • context
      • annotation
        • SimpleAutowired.java
        • SimpleBean.java
        • SimpleComponent.java
      • factory
        • Bean
        • BeanFactory
        • ListableBeanFactory
      • ApplicationContext.java
      • ApplicationContextInitializer.java
      • SimpleApplicationContext.java
      • SimpleApplicationContextInitializer.java
    • util
      • ClassUtil.java
      • ConcurrentHashSet.java
      • LogUtil.java
      • StringUtil.java

实际上三四个类完全能搞定这个简单的ioc容器,但是出于可扩展性的考虑,还是写了不少的类。
因篇幅限制,接下来只将几个最重要的类的代码贴出来并加以说明,完整的代码请直接参考https://github.com/clayandgithub/simple-ioc

SimpleAutowired

代码
import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleAutowired {
    boolean required() default true;

    String value() default ""; // this field is moved from @Qualifier to here for simplicity
}
说明

@SimpleAutowired的作用是用于注解需要自动装配的字段。
此类和spring的@Autowired的作用类似。但又有以下两个区别:
- @SimpleAutowired只能作用于类字段,而不能作用于方法(这样实现起来相对简单些,不会用到aop)
- @SimpleAutowired中包括了required(是否一定需要装配)和value(要装配的bean的名字)两个字段,实际上是将spring中的@Autowired以及Qualifier的功能简单地融合到了一起

SimpleBean

代码
import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleBean {
    String value() default "";
}
说明

@SimpleBean作用于方法,根据方法返回值来生成一个bean,对应spring中的@Bean
用value来设置要生成的bean的名字

SimpleComponent

代码
import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleBean {
    String value() default "";
}
说明

@SimpleComponent作用于类,ioc容器会为每一个拥有@SimpleComponent的类生成一个bean,对应spring中的@Component。特殊说明,为了简单起见,@SimpleComponent注解的类必须拥有一个无参构造函数,否则无法生成该类的实例,这个在之后的SimpleAppliationContext中的processSingleClass方法中会有说明。

SimpleIocBootApplication

代码
import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleIocBootApplication {
    String[] basePackages() default {};
}
说明

@SimpleIocBootApplication作用于应用的入口类。
这个启动模式是照搬了spring-boot的启动模式,将启动任务委托给SimpleIocApplication来完成。ioc容器将根据注解@SimpleIocBootApplication的相关配置自动扫描相应的package,生成beans并完成自动装配。(如果没有配置,默认扫描入口类(测试程序中的SampleApplication)所在的package及其子package)


以上就是这个ioc容器所提供的所有注解,接下来讲解ioc容器的扫描和装配过程的实现。


SimpleIocApplication

代码
import com.clayoverwind.simpleioc.context.*;
import com.clayoverwind.simpleioc.util.LogUtil;

import java.util.Arrays;
import java.util.Map;
import java.util.logging.Logger;


public class SimpleIocApplication {
    private Class<?> applicationEntryClass;

    private ApplicationContext applicationContext;

    private final Logger LOGGER = LogUtil.getLogger(this.getClass());

    public SimpleIocApplication(Class<?> applicationEntryClass) {
        this.applicationEntryClass = applicationEntryClass;
    }

    public static void run(Class<?> applicationEntryClass, String[] args) {
        new SimpleIocApplication(applicationEntryClass).run(args);
    }

    public void run(String[] args) {
        LOGGER.info("start running......");

        // create application context and application initializer
        applicationContext = createSimpleApplicationContext();
        ApplicationContextInitializer initializer = createSimpleApplicationContextInitializer(applicationEntryClass);

        // initialize the application context (this is where we create beans)
        initializer.initialize(applicationContext); // here maybe exist a hidden cast

        // process those special beans
        processSpecialBeans(args);

        LOGGER.info("over!");
    }

    private SimpleApplicationContextInitializer createSimpleApplicationContextInitializer(Class<?> entryClass) {
        // get base packages
        SimpleIocBootApplication annotation = entryClass.getDeclaredAnnotation(SimpleIocBootApplication.class);
        String[] basePackages = annotation.basePackages();
        if (basePackages.length == 0) {
            basePackages = new String[]{entryClass.getPackage().getName()};
        }

        // create context initializer with base packages
        return new SimpleApplicationContextInitializer(Arrays.asList(basePackages));
    }

    private SimpleApplicationContext createSimpleApplicationContext() {
        return new SimpleApplicationContext();
    }

    private void processSpecialBeans(String[] args) {
        callRegisteredRunners(args);
    }

    private void callRegisteredRunners(String[] args) {
        Map<String, SimpleIocApplicationRunner> applicationRunners = applicationContext.getBeansOfType(SimpleIocApplicationRunner.class);
        try {
            for (SimpleIocApplicationRunner applicationRunner : applicationRunners.values()) {
                applicationRunner.run(args);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
说明

前面说到应用的启动会委托SimpleIocApplication来完成,通过将应用入口类(测试程序中的SampleApplication)传入SimpleIocApplication的构造函数,构造出SimpleIocApplication的一个实例并运行run方法。在run方法中,会首先生成一个applicationContext,并调用SimpleApplicationContextInitializer来完成applicationContext的初始化(bean的扫描、装配)。然后调用processSpecialBeans来处理一些特殊的bean,如实现了SimpleIocApplicationRunner接口的bean会调用run方法来完成一些应用程序的启动任务。
这就是这个ioc容器的整个流程。

SimpleApplicationContextInitializer

代码
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public class SimpleApplicationContextInitializer implements ApplicationContextInitializer<SimpleApplicationContext> {

    private Set<String> basePackages = new LinkedHashSet<>();

    public SimpleApplicationContextInitializer(List<String> basePackages) {
        this.basePackages.addAll(basePackages);
    }

    @Override
    public void initialize(SimpleApplicationContext applicationContext) {
        try {
            applicationContext.scan(basePackages, true);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        applicationContext.setStartupDate(System.currentTimeMillis());
    }
}
说明
  • 在SimpleIocApplication的run中,会根据basePackages来构造一个SimpleApplicationContextInitializer 的实例,进而通过这个ApplicationContextInitializer来完成SimpleApplicationContext 的初始化。
  • 在SimpleApplicationContextInitializer中, 简单地调用SimpleApplicationContext 中的scan即可完成SimpleApplicationContext的初始化任务

SimpleApplicationContext

说明:

终于到了最重要的部分了,在SimpleApplicationContext中将真正完成扫描、生成bean以及自动装配的任务。这里scan即为SimpleApplicationContext的程序入口,由SimpleApplicationContextInitializer在初始化时调用。
代码的调用逻辑简单易懂,就不多加说明了。
这里只简单列一下各个字段的含义以及几个比较关键的方法的作用。

字段
- startupDate:启动时间记录字段
- scannedPackages:已经扫描的包的集合,保证不重复扫描
- registeredBeans:已经完全装配好并注册好了的bean
- earlyBeans : 只是生成好了,还未装配完成的bean,用于处理循环依赖的问题
- totalBeanCount : 所有bean的计数器,在生成bean的名字时会用到其唯一性

方法
- processEarlyBeans:用于最终装配earlyBeans 中的bean,若装配成功,则将bean移至registeredBeans,否则报错
- scan : 扫描并处理传入的package集合
- processSingleClass:处理单个类,尝试生成该类的bean并进行装配(前提是此类有@SimpleComponent注解)
- createBeansByMethodsOfClass : 顾名思义,根据那些被@Bean注解的方法来生成bean
- autowireFields:尝试装配某个bean,lastChance代表是否在装配失败是报错(在第一次装配时,此值为false,在装配失败后会将bean移至earlyBeans,在第二次装配时,此值为true,实际上就是在装配earlyBeans中的bean,因此若仍然装配失败,就会报错)。在这个方法中,装配相应的bean时会从registeredBeans以及earlyBeans中去寻找符合条件的bean,只要找到,不管是来自哪里,都算装配成功。

代码
import com.clayoverwind.simpleioc.context.annotation.SimpleAutowired;
import com.clayoverwind.simpleioc.context.annotation.SimpleBean;
import com.clayoverwind.simpleioc.context.annotation.SimpleComponent;
import com.clayoverwind.simpleioc.context.factory.Bean;
import com.clayoverwind.simpleioc.util.ClassUtil;
import com.clayoverwind.simpleioc.util.ConcurrentHashSet;
import com.clayoverwind.simpleioc.util.LogUtil;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;

/**
 * @author clayoverwind
 * @E-mail clayanddev@163.com
 * @version 2017/4/5
 */

public class SimpleApplicationContext implements ApplicationContext {

    private long startupDate;

    private Set<String> scannedPackages = new ConcurrentHashSet<>();

    private Map<String, Bean> registeredBeans = new ConcurrentHashMap<>();

    private Map<String, Bean> earlyBeans = new ConcurrentHashMap<>();

    private final Logger LOGGER = LogUtil.getLogger(this.getClass());

    AtomicLong totalBeanCount = new AtomicLong(0L);

    AtomicLong nameConflictCount = new AtomicLong(0L);

    @Override
    public Object getBean(String name) {
        return registeredBeans.get(name);
    }

    @Override
    public <T> T getBean(String name, Class<T> type) {
        Bean bean = (Bean)getBean(name);
        return bean == null ? null : (type.isAssignableFrom(bean.getClazz()) ? type.cast(bean.getObject()) : null);
    }

    @Override
    public <T> T getBean(Class<T> type) {
        Map<String, T> map = getBeansOfType(type);
        return map.isEmpty() ? null : type.cast(map.values().toArray()[0]);
    }

    @Override
    public boolean containsBean(String name) {
        return getBean(name) != null;
    }

    @Override
    public <T> Map<String, T> getBeansOfType(Class<T> type) {
        Map<String, T> res = new HashMap<>();
        registeredBeans.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> res.put(entry.getKey(), type.cast(entry.getValue().getObject())));
        return res;
    }

    @Override
    public void setStartupDate(long startupDate) {
        this.startupDate = startupDate;
    }

    @Override
    public long getStartupDate() {
        return startupDate;
    }

    /**
     * try to autowire those beans in earlyBeans
     * if succeed, remove it from earlyBeans and put it into registeredBeans
     * otherwise ,throw a RuntimeException(in autowireFields)
     */
    private synchronized void processEarlyBeans() {
        for (Map.Entry<String, Bean> entry : earlyBeans.entrySet()) {
            Bean myBean = entry.getValue();
            try {
                if (autowireFields(myBean.getObject(), myBean.getClazz(), true)) {
                    registeredBeans.put(entry.getKey(), myBean);
                    earlyBeans.remove(entry.getKey());
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * scan base packages and create beans
     * @param basePackages
     * @param recursively
     * @throws ClassNotFoundException
     */
    public void scan(Set<String> basePackages, boolean recursively) throws ClassNotFoundException, IOException {
        LOGGER.info("start scanning......");

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        // get all classes who haven't been registered
        Set<Class<?>> classes = new LinkedHashSet<>();
        for (String packageName : basePackages) {
            if (scannedPackages.add(packageName)) {
                classes.addAll(ClassUtil.getClassesByPackageName(classLoader, packageName, recursively));
            }
        }

        // autowire or create bean for each class
        classes.forEach(this::processSingleClass);

        processEarlyBeans();

        LOGGER.info("scan over!");
    }

    /**
     * try to create a bean for certain class, put it into registeredBeans if success, otherwise put it into earlyBeans
     * @param clazz
     */
    private void processSingleClass(Class<?> clazz) {
        LOGGER.info(String.format("processSingleClass [%s] ...", clazz.getName()));

        Annotation[] annotations = clazz.getDeclaredAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof SimpleComponent) {
                Object instance;
                try {
                    instance = clazz.newInstance();
                } catch (InstantiationException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }

                long beanId = totalBeanCount.getAndIncrement();
                SimpleComponent component = (SimpleComponent) annotation;
                String beanName = component.value();
                if (beanName.isEmpty()) {
                    beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId);
                }

                try {
                    if (autowireFields(instance, clazz, false)) {
                        registeredBeans.put(beanName, new Bean(instance, clazz));
                    } else {
                        earlyBeans.put(beanName, new Bean(instance, clazz));
                    }
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }

                try {
                    createBeansByMethodsOfClass(instance, clazz);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    private void createBeansByMethodsOfClass(Object instance, Class<?> clazz) throws InvocationTargetException, IllegalAccessException {
        List<Method> methods = getMethodsWithAnnotation(clazz, SimpleBean.class);
        for (Method method : methods) {
            method.setAccessible(true);
            Object methodBean = method.invoke(instance);
            long beanId = totalBeanCount.getAndIncrement();
            Class<?> methodBeanClass = methodBean.getClass();

            //bean name
            SimpleBean simpleBean = method.getAnnotation(SimpleBean.class);
            String beanName = simpleBean.value();
            if (beanName.isEmpty()) {
                beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId);
            }

            // register bean
            registeredBeans.put(beanName, new Bean(methodBean, methodBeanClass));
        }
    }

    private List<Method> getMethodsWithAnnotation(Class<?> clazz, Class<?> annotationClass) {
        List<Method> res = new LinkedList<>();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation.annotationType() == annotationClass) {
                    res.add(method);
                    break;
                }
            }
        }
        return res;
    }


    /**
     * try autowire all fields of a certain instance
     * @param instance
     * @param clazz
     * @param lastChance
     * @return true if success, otherwise return false or throw a exception if this is the lastChance
     * @throws IllegalAccessException
     */
    private boolean autowireFields(Object instance, Class<?> clazz, boolean lastChance) throws IllegalAccessException {
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            Annotation[] annotations = field.getAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation instanceof SimpleAutowired) {
                    SimpleAutowired autowired = (SimpleAutowired) annotation;
                    String beanName = autowired.value();
                    Bean bean = getSimpleBeanByNameOrType(beanName, field.getType(), true);
                    if (bean == null) {
                        if (lastChance) {
                            if (!autowired.required()) {
                                break;
                            }
                            throw new RuntimeException(String.format("Failed in autowireFields : [%s].[%s]", clazz.getName(), field.getName()));
                        } else {
                            return false;
                        }
                    }
                    field.setAccessible(true);
                    field.set(instance, bean.getObject());
                }
            }
        }
        return true;
    }

    /**
     * only used in autowireFields
     * @param beanName
     * @param type
     * @param allowEarlyBean
     * @return
     */
    private Bean getSimpleBeanByNameOrType(String beanName, Class<?> type, boolean allowEarlyBean) {
        // 1. by name
        Bean res = registeredBeans.get(beanName);
        if (res == null && allowEarlyBean) {
            res = earlyBeans.get(beanName);
        }

        // 2. by type
        if (type != null) {
            if (res == null) {
                res = getSimpleBeanByType(type, registeredBeans);
            }
            if (res == null && allowEarlyBean) {
                res = getSimpleBeanByType(type, earlyBeans);
            }
        }

        return res;
    }

    /**
     * search bean by type in certain beans map
     * @param type
     * @param beansMap
     * @return
     */
    private Bean getSimpleBeanByType(Class<?> type, Map<String, Bean> beansMap) {
        List<Bean> beans = new LinkedList<>();
        beansMap.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> beans.add(entry.getValue()));
        if (beans.size() > 1) {
            throw new RuntimeException(String.format("Autowire by type, but more than one instance of type [%s] is founded!", beans.get(0).getClazz().getName()));
        }
        return beans.isEmpty() ? null : beans.get(0);
    }

    private String getUniqueBeanNameByClassAndBeanId(Class<?> clazz, long beanId) {
        String beanName = clazz.getName() + "_" + beanId;
        while (registeredBeans.containsKey(beanName) || earlyBeans.containsKey(beanName)) {
            beanName = clazz.getName() + "_" + beanId + "_" + nameConflictCount.getAndIncrement();
        }
        return beanName;
    }
}

后记

至此,一个简单的ioc容器就完成了,总结一下优缺点。
优点:

  • 小而简单。
  • 可以使用@SimpleBean、@SimpleComponent以及@SimpleAutowired 来完成一些简单但常用的依赖注入任务.

缺点:

  • 很明显,实现过于简单,提供的功能太少。

如果你想了解ioc的实现原理,或者你想要开发一个小型个人项目但又嫌spring过于庞大,这个简单的ioc容器或许可以帮到你。

如果你想做的不仅如此,那么你应该将目光转向spring-boot

完整代码参考:https://github.com/clayandgithub/simple-ioc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值