動手實現MVC: 2. bean加載, IoC依賴注入

背景

前面實現了java包的掃描,掃描以後天然就到了bean的加載,以及spring mvc的一大特性 IoC依賴注入的實現;java

這裏則將在以前的基礎上,實現bean的加載和依賴注入的實現git

設計

咱們模仿的輪子就是spring mvc,簡化一些複雜的場景,這裏只實現註解的形式github

1. 依賴spring-mvc的使用姿式,咱們須要先定義幾個註解

  • 類上註解 Service, Component, Repository, Bean

全部類上有上面註解的,都表示須要實例的beanspring

  • 屬性註解 Autowired

表示該屬性用一個bean對象來實例化spring-mvc

2. 實例化bean

經過Class建立Bean對象mvc

3. 加載依賴

掃描每一個Bean的屬性,若包含 @Autowired 註解,則用bean進行賦值ui

4. 提供獲取bean的各類方式

最多見的根據beanName,bean類型來獲取Bean.net

5. 提供動態註冊bean

好比業務方依賴第三方的jar包中的某個類,想將它也註冊爲一個bean,由於不能修改第三方類,因此能夠用動態註冊的方式來加載bean設計


實現

1. 註解定義

這個比較簡單,直接貼一下幾個相關的註解code

幾個聲明類爲Bean的註解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
    String value() default "";
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Bean
public @interface Component {
    String value() default "";
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Bean
public @interface Service {
    String value() default "";
}


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Bean
public @interface Repository {
    String value() default "";
}

Autowired.java, value對應的是業務方定義的beanName

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
    String value() default "";
}

2. 掃描包,類加載,Bean建立

咱們定義一個 BeanFactory 對象,來管理class的加載,bean的建立

掃描包基本上就是以前一篇博文的內容,不作多說,直接看Bean的實例化

實現思路比較清晰,大體流程以下;

  • 遍歷Class集合
  • 判斷class上是否有幾個定義爲Bean的註解
  • 肯定爲bean,則實例化

爲了不每次使用時都掃描一遍,因此這個掃描的結果會保存下來,放在內存中

/**
 * 全部自動實例化的bean的映射表,
 * key爲bean name
 * - (若是註解中有指定value值,則bean name就是value值;若沒有指定,則是首字母小寫的簡單類名)
 * - bean name 區分大小寫
 * <p>
 * 爲了不bean name相同的問題,將value也保存爲一個Map映射表
 */
private Map<String, Map<Class, Object>> nameBeanMap;


/**
 * class到bean的映射表
 */
private Map<Class, Object> clzBeanMap;

實際加載過程以下

/**
 * 實例化自動加載的bean
 *
 * @return
 */
private Map<String, Map<Class, Object>> instanceBean() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    nameBeanMap = new ConcurrentHashMap<>();
    clzBeanMap = new ConcurrentHashMap<>();


    Annotation[] typeAnos;
    String tmpBeanName;
    Method tmpMethod;
    Object tmpBean;
    Map<Class, Object> tmpClzMap;
    for (Class clz : beanClasses) {
        if (clz.isInterface()) {
            continue;
        }


        // 獲取類上註解
        typeAnos = clz.getAnnotations();
        if (typeAnos.length == 0) {
            continue;
        }


        for (Annotation ano : typeAnos) {
            if (ano instanceof Bean || ano.annotationType().isAnnotationPresent(Bean.class)) { // 須要加載bean
                tmpMethod = ano.annotationType().getMethod("value", null);
                if (tmpMethod != null) {
                    tmpBeanName = (String) tmpMethod.invoke(ano, null);
                } else {
                    tmpBeanName = null;
                }

                if (StringUtils.isEmpty(tmpBeanName)) {
                    tmpBeanName = StrUtil.lowerFirstChar(clz.getSimpleName());
                }


                if (nameBeanMap.containsKey(tmpBeanName)) {
                    tmpClzMap = nameBeanMap.get(tmpBeanName);
                } else {
                    tmpClzMap = new ConcurrentHashMap<>();
                }

                if (tmpClzMap.containsKey(clz)) {
                    throw new BeanAlreadyDefinedException("bean " + tmpBeanName + " class: " + clz.getName() + " has already defined!");
                }


                tmpBean = clz.newInstance();
                tmpClzMap.put(clz, tmpBean);
                clzBeanMap.put(clz, tmpBean);
                nameBeanMap.put(tmpBeanName, tmpClzMap);
                break;
            }
        }
    }

    return nameBeanMap;
}

上面的實現比較簡單,惟一須要注意下的是判斷是否包含咱們期待的幾個註解, 不知道是否有更優雅的寫法,下面這種對於自定義一個註解,上面加上 @Service的狀況時,將不太適用

if(ano instanceof Bean || ano.annotationType().isAnnotationPresent(Bean.class)) {
  // xxx
}

其次就是BeanName的生成規則

  • 當註解的value屬性有被指定,則beanName即爲指定的值;
  • 不然,根據class名,首字母小寫便可

3. IoC依賴注入

這個也比較簡單,掃描每一個bean的屬性,將擁有 @Autowired 註解的拎出來, 而後查對應的Bean,賦值便可

/**
 * 依賴注入
 */
private void ioc() throws IllegalAccessException {

    Field[] fields;
    String beanName;
    Object bean;
    for (Object obj : nameBeanMap.values()) {
        fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(Autowired.class)) {
                continue;
            }

            Autowired autowired = field.getAnnotation(Autowired.class);
            beanName = StringUtils.isBlank(autowired.value()) ?
                    StrUtil.lowerFirstChar(field.getName()) : autowired.value();
            bean = nameBeanMap.get(beanName);

            if (bean == null) {
                throw new BeanNotFoundException("bean: " + beanName + " not found! bean class: " + field.getClass().getName());
            }

            field.setAccessible(true);
            field.set(obj, nameBeanMap.get(beanName));
        }
    }
}

屬性賦值,關注下兩行代碼便可

// 強制設置可訪問,這樣私有的變量也能夠修改其內容了
field.setAccessible(true);
field.set(obj, nameBeanMap.get(beanName));

4. 查詢bean & 動態註冊

查詢的幾個接口就比較簡單了,單純的從Map中獲取對象; 註冊也就是向Map中塞對象

其餘

源碼地址: https://github.com/liuyueyi/quick-mvc

相關博文:

我的博客:一灰的我的博客

公衆號獲取更多:

我的信息

相關文章
相關標籤/搜索