Spring自動注入的簡單實現

申明:本文不是講解Spring如何使用註解,本文只是經過一個簡單的實現,來理解Spring是如何注入一個對象的。html

  用過Spring的同窗都知道,Spring利用註解來實現依賴注入,使得各個類之間的耦合性極大的下降了。可是僅僅是使用,並不能理解到Spring內部是怎麼實現的。筆者沒有看過Spring的源碼。只能從本身的角度來談談Spring是怎麼實現的。感興趣的同窗能夠在看過本文以後,深刻的瞭解Spring.
  不少時候,咱們都有這樣的應用場景。好比DAO層,你會先申明一個接口,好比IUserDao,表示用來處理User的一個接口,而後再寫一個實現類UserDaoImpl實現了IUserDao中的方法,而後在上層service層中注入。啓動以後Spring將本身掃描自動爲咱們注入實例化的對象,使得咱們不用在乎各個對象的生命週期。接下來就來聊聊具體是怎麼注入的。
假設如今已經有如下的類:git

public interface IUserDao {
    
    public void setData(String data);
    public String getData();
}

public class UserDaoImpl implements IUserDao{

    @Override
    public void setData(String data) {
        System.out.println("data is : " + data);
    }

    @Override
    public String getData() {
        return "just test";
    }

}

  其中FieldInject是筆者模仿寫的一個註解,具體定義以下github

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FieldInject {
    //假設有一些變量用於控制策略
}

  具體關於註解上面的元註解的含義,能夠看另一篇博客。這裏就不展開說明了。
以上就是準備工做了,接下來就是講解真正的初始化方法了。
  假設咱們如今有一個類的Class對象,那麼咱們能夠根據這個Class對象找到哪些成員變量是加了指定的註解的。代碼以下ide

//下面開始注入
            for(int i=0; i<field.length; i++){
                FieldInject annotation = field[i].getAnnotation(FieldInject.class);
                if(annotation != null){//說明這個成員變量有註解
                    //獲取到成員變量的全限定名
                    String fullClassName = field[i].getType().getName(); 
                    Class sub = findSubClass(fullClassName);
                    if(sub == null) continue; //找不到,略過
                    String lowCase = "set"+field[i].getName();
                    
                    //採用setter方法幅值,與下面的代碼二選一便可
//                    injectMethod(method,obj, sub, lowCase.toLowerCase()); 
                    
                    //如下是直接幅值
                    injectField(field[i],obj,sub);
                }
            }

  在這段代碼中,筆者查詢的註解是本身實現的一個FieldInject註解,註解自己並不影響代碼的執行。經過判斷是否爲空能夠得出某個成員變量是否加了指定的註解。若是發現成員變量加了註解,就能夠爲該成員變量注入實例化的對象了。
  問題1:怎麼知道注入哪一個對象?
  問題2:怎麼注入?
  問題2很好解決,若是原來的類中帶有setter方法,那麼可使用method.invoke()方法來調用並注入。或者經過field直接注入均可以。那麼主要是問題1,怎麼找到合適的注入對象。
  Spring有多種注入的策略,好比按照裝配名稱,或者是默認實現了接口或者抽象類的子類實例對象來注入。總之,不一樣的策略只是選擇的不一樣,咱們能夠假定使用找到的第一個合適子類的實例對象來注入code

//找到某個類的子類【涉及到Spring的選擇策略】
    private Class findSubClass(String fullClassName){
        try {
            Class target = Class.forName(fullClassName);
            
            //不是抽象類,不是接口,那自身就行了。
            if(!target.isInterface()){
                boolean isAbs = Modifier.isAbstract(target.getModifiers());
                if(!isAbs) return target;
            }
            
            int size = clazzList.size();
            for(int i=0; i<size; i++){
                Class p = clazzList.get(i);
                if(p.getSuperclass() != null && p.getSuperclass().getName().equals(fullClassName)) return p;
                Class[] inter = p.getInterfaces();
                for(Class c : inter){
                    if(c.getName().equals(fullClassName)) return p;
                }
            }
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

  findSubClass是用來找到某個類的合適子類,相似於Spring中根據某種策略來查找,這裏使用了比較簡單的方法。找到第一個合適的子類便可。這個方法中,作了一些簡單的判斷,若是這個類自己就不是一個抽象類或者不是一個接口,那麼這個類就是第一個合適的類。若是這個類是一個接口或者一個抽象類,那麼就在全局掃描的classList中找到合適的類。找到合適的類以後,下一步就是一個注入了,筆者採用的是給setter方法注入,若是想直接給成員變量賦值也是很是簡單的。只要替換掉方法injectMethod,換成下面兩句代碼便可。htm

field[i].setAccessible(true);
field[i].set(target, obj);

  injectMethod實現也是比較簡單,經過比對Method中的方法,找到合適的setter方法(這裏是經過field的名稱來判斷的),並將實例對象賦值進去便可。以上就是一個簡單的注入過程的實現。筆者寫的比較匆忙,可能有些細節上經不起推敲。可是若是能爲迷惑的初學者提供一個思路也是不錯的,這份代碼我都上傳到github上了,若是想下載進行運行的能夠移步個人github對象

相關文章
相關標籤/搜索