从零写Spring注解版框架系列 IoC篇 (2)实现 @Component、@Autowired、@Qualifier注解

本文承接了上一篇文章的思路进行代码实现,并搭建起一个基本可用的基于@Component、@Autowired、@Qualifier 注解的 IoC 框架。

项目 Github 地址为:https://github.com/linshenkx/winter-core
相关文章地址:从零写Spring注解版框架系列 IoC篇 (1) 框架设计

文章目录

    • 一 结构设计
    • 二 注解类编写
    • 二 ApplicationContext构建
    • 三 scanPackage包扫描方法
      • 任务
    • 思路
    • 代码
    • 四 initBean 初始化Bean方法
      • 任务
      • 思路
      • 代码
    • 五 getBean 获取Bean方法
      • 任务
      • 思路
      • 代码
    • 六 工具类方法
      • getSuperClassList 父类获取方法
      • getClasses 类获取方法
      • toLowerCaseFirstOne 首字母转小写方法

一 结构设计

首先创建一个 Maven 工程 winter-core,这里我们我们只需要依赖于 commons-lang 工具包,另外还有测试用的 junit 包。再创建各个类如下:
从零写Spring注解版框架系列 IoC篇 (2)实现 @Component、@Autowired、@Qualifier注解_第1张图片

核心注解在 annotation 包下,util 包下是 ClassUtil 工具类 和 StringUtil 工具类,真正的核心组件是 BeanFactory 下的 ClassPathAnnotaionApplicationContext,由该类来完成 Bean 扫描和初始化及注入等功能。

二 注解类编写

注解类本身没什么特别的

/**
 * @version V1.0
 * @author: lin_shen
 * @date: 18-11-28
 * @Description: 依赖注入的注解,目前只实现 Field 注入
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

/**
 * @version V1.0
 * @author: lin_shen
 * @date: 18-11-28
 * @Description: 标记扫描注解,使用该注解的类会被 BeanFactory 收入管理
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {
    String value() default "";
}

/**
 * @version V1.0
 * @author: lin_shen
 * @date: 18-11-28
 * @Description: 指定名称用的注解,跟@Autowired搭配使用,实现在类型相同的情况下通过指定名识别
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
    String value() default "";

}

二 ApplicationContext构建

首先来看一下 ClassPathAnnotationApplicationContext 类的总体结构,主要方法的实现放后面说

ApplicationContext构造方法的唯一参数是 packageName , 包名,即扫描包的范围。构造方法里主要是调用 scanPackage方法对指定范围进行扫描,将含有 @Component 注解的类收集存储到 beanDefinationFactory 里。需要注意的是这个时候收集的是 Class 对象而非 Bean 实例。

除了构造方法的唯一的 public 方法是 getBean(Class type,String beanId,boolean force),对于使用者来说构建完 ApplicationContext 后即可通过此方法来获取由 IoC 容器管理的实例了。如果 singletonbeanFactory 中已经存在目标单例对象,则直接返回,否则该方法将调用 initBean(Class type,String beanId) 完成目标单例的初始化操作,再返回。

也就是说,这个 IoC 容器的工作流程是在初始化的时候完成包扫描工作,将 Bean信息收集起来,外界使用 getBean 方法获取实例的时候再进行该Bean的初始化工作,并将单例实例存储管理,下次索要时直接返回。

public class ClassPathAnnotationApplicationContext {

    /**
     * 扫包范围
     */
    private String packageName;

    /**
     * 类的缓存map,用于避免单例情况下的循环引用
     */
    private Map<String,Object> cacheBeanFactory=new ConcurrentHashMap<>();

    /**
     * 用于存储真正的类信息
     * 全称类名(type)+自定义类名(beanId)==> 真类信息
     */
    private Map<String,Map<String,Class>> beanDefinationFactory;

    /**
     * 由真类信息的全称类名确定唯一单例对象
     */
    private Map<String,Object> singletonbeanFactory=new ConcurrentHashMap<>();

    /**
     * 构造方法,传入基础扫描包地址
     * @param packageName
     */
    public ClassPathAnnotationApplicationContext(String packageName) {
        this.packageName = packageName;
        scanPackage(packageName);
    }

    /**
     * 获取指定 Bean 实例
     * @param type 类型
     * @param beanId beanId,即指定名
     * @param force 在该类型只存在一个 Bean 实例的时候是否按照 必须按照 beanId 匹配(如为false则可直接返回唯一实例)
     * @return
     */
    public Object getBean(Class type,String beanId,boolean force){};

    /**
     * 使用反射机制获取该包下所有的类已经存在bean的注解类
     * @param packageName 扫描包路径
     */
    private void scanPackage(String packageName) {};

    /**
     * 完成类的初始化
     * @param type 类型
     * @param beanId 指定名
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private Object initBean(Class type,String beanId)
            throws InstantiationException, IllegalAccessException {};

    /**
     * 取出clazz上Component注解的非默认值,如非Component注解标识类或使用的是默认值则抛出运行时异常
     * @param clazz
     * @return
     */
    private static String getComponentName(Class clazz){};

三 scanPackage包扫描方法

任务

scanPackage方法的目标是初始化 beanDefinationFactory。

beanDefinationFactory 是嵌套Map结构,其核心 value 是 Class,即类对象,对应Spring中的BeanDIfinition,即Bean描述信息。其外层 key 是全称类名 type,内层 key 是 指定名 beanId。需要注意的是 type不一定是 Class的全称类名。
正如 通过接口和指定名来确定唯一实现类,type代表的除了自身类还可以是超类或者接口的全称类名。

思路

先使用反射机制获取目标包下所有的类,再获取其中标记 @Component 注解的类,对每一个标记 @Component 的类(classInfo)进行如下操作

  1. 获取 classInfo 的所有非Object父类、实现接口以及自身类作为 superClassList。
  2. 遍历 superClassList中元素 aClass
    1. 获取aClass的全称类名在 beanDefinationFactory 下对应的 Map beanDefinationMap,没有则构建
    2. 根据@Component里有无指定值(非默认值)进行不同处理,有则按指定值存入,没有则按简单类名首字母小写传入。这个过程如果有重复key值则抛出异常

代码

    /**
     * 使用反射机制获取该包下所有的类已经存在bean的注解类
     * @param packageName 扫描包路径
     */
    private void scanPackage(String packageName) {
        beanDefinationFactory=new HashMap<>();
        if (StringUtils.isEmpty(packageName)) {
            throw new RuntimeException("扫包地址不能为空!");
        }
        // 使用反射技术获取当前包下所有的类
        List<Class<?>> classesByPackageName = ClassUtil.getClasses(packageName);
        // 对存在注解的类进行记录
        for (Class classInfo : classesByPackageName) {
            Component component = (Component) classInfo.getDeclaredAnnotation(Component.class);
            if(component==null){
                continue;
            }
            System.out.println("|classInfo:"+StringUtil.toLowerCaseFirstOne(classInfo.getName()));

            //存入按类型存取的单例BeanFactory(接口和非object父类和自身类型)
            classInfo.getAnnotatedInterfaces();
            Class[] interfaces = classInfo.getInterfaces();
            List<Class> superClassList = ClassUtil.getSuperClassList(classInfo);
            superClassList.addAll(Arrays.asList(interfaces));
            superClassList.add(classInfo);

            System.out.println("superClassListSize:"+superClassList.size());

            for (Class aClass : superClassList) {
                Map<String, Class> beanDefinationMap = beanDefinationFactory.computeIfAbsent(StringUtil.toLowerCaseFirstOne(aClass.getName()), k -> new HashMap<>());

                System.out.println("Type:"+StringUtil.toLowerCaseFirstOne(aClass.getName()));

                if(StringUtils.isNotEmpty(component.value())){
                    //如果component有值则使用该值(对应本类classInfo的信息)
                    if (beanDefinationMap.get(getComponentName(classInfo))!=null){
                        throw new RuntimeException("出现无法通过name区分的重复类型:"+StringUtil.toLowerCaseFirstOne(aClass.getName())+" "+getComponentName(classInfo));
                    }
                    //存入按指定名存取的单例BeanFactory
                    beanDefinationMap.put(getComponentName(classInfo),classInfo);
                    System.out.println("putName:"+getComponentName(classInfo));
                }else {
                    //如果component没有值则使用当前类型名
                    if (beanDefinationMap.get(StringUtil.toLowerCaseFirstOne(aClass.getName()))!=null){
                        throw new RuntimeException("出现无法通过name区分的重复类型:"+StringUtil.toLowerCaseFirstOne(aClass.getName()));
                    }
                    beanDefinationMap.put(StringUtil.toLowerCaseFirstOne(aClass.getSimpleName()),classInfo);
                    System.out.println("putType:"+StringUtil.toLowerCaseFirstOne(aClass.getName()));
                }


            }
        }

        for (Map.Entry<String, Map<String, Class>> stringMapEntry : beanDefinationFactory.entrySet()) {
            System.out.println("Type:"+stringMapEntry.getKey());
            stringMapEntry.getValue().keySet().forEach(System.out::println);
        }
        System.out.println("------------");

    }

四 initBean 初始化Bean方法

任务

根据 type(全称类名)和 beanId (指定名)从 beanDefinationFactory 获取 真实类信息,完成实例初始化并放入 singletonbeanFactory中,并返回实例

此方法只负责对类进行初始化,不检查是否已经有完成初始化的类,检查是否已经有实例化的类并决定是否直接返回是 getBean 的工作

singletonbeanFactory 和 cacheBeanFactory 都是以真实类的全称类名为key存放bean实例。

思路

先从 beanDefinationFactory 获取 真实类型 clazz,如果clazz为null则抛出异常,然后进入创建流程:

  1. 根据 clazz 判断 cacheBeanFactory 中实例,如果已经存在(不为null,即没有完全完成初始化)则意味着在创建bean的过程同一bean又被创建,说明存在循环引用,应抛出异常。如果不存在则将未完成创建的bean放入 cacheBeanFactory,等创建完成的时候再移除。
  2. 获取 clazz 所有的 Field,遍历,对标记有 @Autowired 的 Field 根据有无 @Qualifier 进行对 Field 的值进行注入。
    1. 没有@Qualifier注解:
      设置beanId为域对象的值,采用非强制匹配,在匹配类型仅有一个实现实例时忽略beanId要求
    2. 有@Qualifier
      设置beanId为Qualifier注解的value,采用强制匹配,在匹配类型仅有一个实现实例时如果beanId不匹配仍会报错
  3. 将 完成初始化的 bean 放入 singletonbeanFactory 进行管理
  4. 移除 cacheBeanFactory 中对应的实例
  5. 返回实例

代码

    /**
     * 完成类的初始化
     * @param type 类型
     * @param beanId 指定名
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private Object initBean(Class type,String beanId)
            throws InstantiationException, IllegalAccessException {

        //如果不存在该类的真类信息,则抛出异常
        Class<?> clazz = beanDefinationFactory.get(type.getName()).get(beanId);
        if(clazz==null){
            throw new RuntimeException("没有找到type为:"+type.getName()+",name为:"+beanId+"的类");
        }

        //进入创建流程
        try {
            //利用cacheBeanFactory识别循环引用
            Object targetObject = cacheBeanFactory.get(clazz.getName());
            if(targetObject!=null){
                //在创建bean的过程bean又被创建,说明存在循环引用,抛出异常
                throw new RuntimeException("循环引用");
            }else {
                targetObject=clazz.newInstance();
                cacheBeanFactory.put(clazz.getName(),targetObject);
            }

            //正式进入初始化,给@Autowired的field赋值
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                Autowired autowired = declaredField.getAnnotation(Autowired.class);
                if(autowired==null){
                    continue;
                }
                //判断是否有Qualifier注解
                Qualifier qualifier = declaredField.getAnnotation(Qualifier.class);
                declaredField.setAccessible(true);
                //对该field赋值
                if(qualifier==null){
                    //如果没有Qualifier注解则设置beanId为域对象的值,采用非强制匹配,在匹配类型仅有一个实现实例时忽略beanId要求
                    declaredField.set(targetObject,getBean(declaredField.getType(),declaredField.getName(),false));
                }else {
                    //如果有Qualifier注解则设置beanId为Qualifier注解的value,采用强制匹配,在匹配类型仅有一个实现实例时如果beanId不匹配仍会报错
                    declaredField.set(targetObject,getBean(declaredField.getType(),qualifier.value(),true));
                }
            }
            singletonbeanFactory.put(clazz.getName(),targetObject);
            return targetObject;
        }  finally {
            cacheBeanFactory.remove(clazz.getName());
        }

    }

五 getBean 获取Bean方法

任务

根据 type(全称类名)和 beanId (指定名)返回实例。如果已经存在则直接返回,不存在则调用 initBean 方法再返回。

思路

  1. 先根据 type 从 beanDefinationFactory 获取该类型下不同名的类信息Map: beanClassMap,

    1. 如果不存在则抛出异常。
    2. 如果存在则根据参数 force 判断是否为强匹配模式
      1. 如果是强匹配模式则检测 beanClassMap 中是否有对应 beanId
        1. 没有则抛出异常
        2. 有则继续
      2. 如果是不是强匹配模式则检测 beanClassMap 中是否有对应 beanId,
        1. 没有的话再判断 beanClassMap是否只有一个类信息
          1. 是的话则将其 对应的 beanId 设置给 原beanId
          2. 否则无法分辨,则抛出异常。否则无法分辨,则抛出异常。
        2. 有则继续
  2. 至此则可根据 type和beanId获取真实类信息 clazz

  3. 然后尝试从singletonbeanFactory中获取实例,如果已经存在则直接返回,不存在则调用 initBean 方法再返回。

代码


    /**
     * 获取指定 Bean 实例
     * @param type 类型
     * @param beanId beanId,即指定名
     * @param force 在该类型只存在一个 Bean 实例的时候是否按照 必须按照 beanId 匹配(如为false则可直接返回唯一实例)
     * @return
     */
    public Object getBean(Class type,String beanId,boolean force){

        System.out.println("getBean,type:"+type.getName()+",name:"+beanId);

        Map<String,Class> beanClassMap = beanDefinationFactory.get(type.getName());

        //如果没有此类型则直接报错
        if(beanClassMap.isEmpty()){
            throw new RuntimeException("没有找到类型为:"+type.getName()+"的bean");
        }

        if(force){
            //如果是强匹配则要求beanId必须存在
            if(beanClassMap.get(beanId)==null){
                throw new RuntimeException("没有找到类型为:"+type.getName()+" 指定名为:"+beanId+"的bean");
            }
        }else {
            //如果不是强匹配则允许beanId不存在,但此时对应类型的bean只能有一个,将beanId修改为仅有的那一个的id
            if(beanClassMap.get(beanId)==null ){
                if(beanClassMap.size()!=1){
                    throw new RuntimeException("无法分辨多个同类不同名对象,类型"+type.getName());
                }else {
                    beanId=beanClassMap.keySet().iterator().next();
                }
            }
        }
        Class targetClass=beanDefinationFactory.get(type.getName()).get(beanId);

        Object targetBean=singletonbeanFactory.get(targetClass.getName());

        if(targetBean!=null){
            return targetBean;
        }

        //不存在则初始化并收入管理
        try {

            System.out.println("初始化type为:"+type.getName()+",name为:"+beanId+"的类");

            targetBean = initBean(type,beanId);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return targetBean;
    }

六 工具类方法

getSuperClassList 父类获取方法

    /**
     * 获取clazz的所有父类(非Object)
     * @param clazz
     * @return
     */
    public static List<Class> getSuperClassList(Class clazz){
        List<Class> superClassList=new ArrayList();
        for(Class superClass = clazz.getSuperclass(); ((superClass!=null)&&(!"Object".equals(superClass.getSimpleName()))); superClass=superClass.getSuperclass()){
            superClassList.add(superClass);
        }
        return superClassList;
    }

getClasses 类获取方法

    /**
     * 从包package中获取所有的Class
     *
     * @param packageName
     * @return
     */
    public static List<Class<?>> getClasses(String packageName) {

        // 第一个class类的集合
        List<Class<?>> classes = new ArrayList<>();
        // 是否循环迭代
        boolean recursive = true;
        // 获取包的名字 并进行替换
        String packageDirName = packageName.replace('.', '/');
        // 定义一个枚举的集合 并进行循环来处理这个目录下的things
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            // 循环迭代下去
            while (dirs.hasMoreElements()) {
                // 获取下一个元素
                URL url = dirs.nextElement();
                // 得到协议的名称
                String protocol = url.getProtocol();
                // 如果是以文件的形式保存在服务器上
                if ("file".equals(protocol)) {
                    // 获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // 以文件的方式扫描整个包下的文件 并添加到集合中
                    findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
                } else if ("jar".equals(protocol)) {
                    // 如果是jar包文件
                    // 定义一个JarFile
                    JarFile jar;
                    try {
                        // 获取jar
                        jar = ((JarURLConnection) url.openConnection()).getJarFile();
                        // 从此jar包 得到一个枚举类
                        Enumeration<JarEntry> entries = jar.entries();
                        // 同样的进行循环迭代
                        while (entries.hasMoreElements()) {
                            // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                            JarEntry entry = entries.nextElement();
                            String name = entry.getName();
                            // 如果是以/开头的
                            if (name.charAt(0) == '/') {
                                // 获取后面的字符串
                                name = name.substring(1);
                            }
                            // 如果前半部分和定义的包名相同
                            if (name.startsWith(packageDirName)) {
                                int idx = name.lastIndexOf('/');
                                // 如果以"/"结尾 是一个包
                                if (idx != -1) {
                                    // 获取包名 把"/"替换成"."
                                    packageName = name.substring(0, idx).replace('/', '.');
                                }
                                // 如果可以迭代下去 并且是一个包
                                if ((idx != -1) || recursive) {
                                    // 如果是一个.class文件 而且不是目录
                                    if (name.endsWith(".class") && !entry.isDirectory()) {
                                        // 去掉后面的".class" 获取真正的类名
                                        String className = name.substring(packageName.length() + 1, name.length() - 6);
                                        try {
                                            // 添加到classes
                                            classes.add(Class.forName(packageName + '.' + className));
                                        } catch (ClassNotFoundException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                }
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return classes;
    }

toLowerCaseFirstOne 首字母转小写方法

    /**
     * 首字母转小写
     * @param s
     * @return
     */
    public static String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0))) {
            return s;
        } else {
            return Character.toLowerCase(s.charAt(0)) + s.substring(1);
        }
    }

你可能感兴趣的:(Spring)