Spring——原理解析-利用反射和注解模擬IoC的自動裝配


解析Spring的IoC容器基於注解實現的自動裝配(自動注入依賴)的原理


1.本文案例
使用注解和反射機制來模擬Spring中IoC的自動裝配功能
定義兩個注解:@Component,用來標注組件;@Autowired,用來標記需要被織入的屬性。
定義一個@Component注解處理器,用來掃描所有組件。
定義一個bean工廠,用來實例化組件。
測試:有兩個組件,一個組件被設置到另一個組件的屬性中。

2.定義注解
2.1.定義@Component注解
這個注解表示被標注的就是一個組件,將會被容器自動掃描並創建實例

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    public String id();
}

注解的定義有點類似於接口的定義

注解定義

public @interface Component {
}

接口定義

public interface Component {
}

區別只是在於interface這個標識符前面有沒有@符號。

並且注解的定義,還需要使用到幾個原生注解:

@Target(ElementType.TYPE)

這個注解表明自定義的注解Component是用來標記誰的,其中ElementType.TYPE表示這個注解使用來標記類型的,也就是可以標記類、接口等。此外還有FIELD、METHOD等,分別表示用來標記字段、方法等。

@Retention(RetentionPolicy.RUNTIME)

表示這個自定義的注解需要保留到什么時候,如只保留到源碼中,編譯之后就沒有了;或者保留到運行時,就是在運行的時候也一直有。這里設置為運行時。

然后這個注解中有這樣一行:

public String id();

有點類似於接口中方法的聲明,不過在注解中,這個表示注解的一個屬性,后面用到的時候可以看看是怎么使用的,就明白了。

2.2.定義 @Autowired注解
這個注解是一個針對成員變量的注解,使用這個注解則表示,這個字段需要由程序來為其賦值的。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowire {
    public String id();

}

 

3.定義 Beanfactory(也就是注解處理器)
自定義注解完之后,實際上並沒有什么用處。要想讓注解發揮用處,重點在於注解處理器。
首先來明確下這個處理器干了那些事情,首先根據給定的組件的包名,掃描這個包,找出其中所有的被@Component注解標注的類,將類型的信息保存下來。
然后提供一個getBean()方法,允許根據bean的id來獲取bean。
接下來看看這個BeanFactory是如何編寫的。

3.1.BeanFactory.java

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
 
public class BeanFactory {
 
    private HashMap<String, Object> beanPool;
    private HashMap<String, String> components;
    
    public BeanFactory(String packageName) {
        beanPool = new HashMap<>();
        
        scanComponents(packageName);
    }
    
    private void scanComponents(String packageName) {
        components = ComponentScanner
                .getComponentClassName(packageName);
    }
    
    public Object getBean(String id) throws ClassNotFoundException, 
            InstantiationException, IllegalAccessException, 
            NoSuchMethodException, SecurityException, 
            IllegalArgumentException, InvocationTargetException {
        
        if (beanPool.containsKey(id)) {
            return beanPool.get(id);
        }
        
        if (components.containsKey(id)) {
        
            Object bean = Class.forName(components.get(id))
                    .newInstance();
            
            bean = assemblyMember(bean);
            
            beanPool.put(id, bean);
            
            return getBean(id);
        }
        
        throw new ClassNotFoundException();
    }
    
    private Object assemblyMember(Object obj) throws 
            ClassNotFoundException, InstantiationException, 
            IllegalAccessException, NoSuchMethodException, 
            SecurityException, IllegalArgumentException, 
            InvocationTargetException {
        
        Class cl = obj.getClass();
        
        for (Field f : cl.getDeclaredFields()) {
            Autowire at = f.getAnnotation(Autowire.class);
            
            if (at != null) {
 
                Method setMethod = cl.getMethod("set" 
                        + captureName(f.getName()), f.getType());
                        
                setMethod.invoke(obj, getBean(at.id()));
            }
        }
        return obj;
    }
    
    public static String captureName(String name) {
        char[] cs=name.toCharArray();
        cs[0]-=32;
        return String.valueOf(cs);
    }

}

 

 

3.2.ComponentScann.java
這個BeanFactory在構造函數中使用到了一個類,用來掃描出一個包中所有的類的信息。

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
 
public class ComponentScanner {
 
    public static HashMap<String, String> getComponentClassName(
            String packageName) {
        List<String> classes = getClassName(packageName);
        HashMap<String, String> components = new HashMap<String, String>();
        
        try {
            
            for (String cl : classes) {
                cl = cl.replace("workspace_java.LearningJava.bin.", "");
                
                Component comp = Class.forName(cl).getAnnotation(Component.class);
                
                if (comp != null) {
                    components.put(comp.id(), cl);
                }
            }
        
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return components;
    }
    
    public static List<String> getClassName(String packageName) {
        String filePath = ClassLoader.getSystemResource("").getPath() 
                + packageName.replace(".", "\\");  
        List<String> fileNames = getClassName(filePath, null);
        return fileNames;
    }
    
    private static List<String> getClassName(String filePath
            , List<String> className) {  
        List<String> myClassName = new ArrayList<String>();  
        File file = new File(filePath);  
        File[] childFiles = file.listFiles();  
        for (File childFile : childFiles) {  
            if (childFile.isDirectory()) {  
                myClassName.addAll(getClassName(childFile.getPath()
                        , myClassName));  
            } else {  
                String childFilePath = childFile.getPath();  
                childFilePath = childFilePath.substring(childFilePath
                        .indexOf("\\classes") + 9, childFilePath.lastIndexOf("."));  
                childFilePath = childFilePath.replace("\\", ".");  
                myClassName.add(childFilePath);  
            }  
        }  
  
        return myClassName;  
    }
    
    public static void main(String[] args) {
        getComponentClassName("com.oolong.javase.annotation");
    }

}

 

4.測試

定義一個模擬的數據庫訪問接口

@Component(id = "dataAccessInterface")
public class DataAccessInterface {
 
    public String queryFromTableA() {
        return "query result";
    }
}

這個類使用了Component這個注解,並且注意,這里使用了這個注解的id屬性。

定義一個模擬的業務接口

@Component(id="businessObject")
public class BusinessObject {
 
    @Autowire(id="dataAccessInterface")
    private DataAccessInterface dai;
    
    public void print() {
        System.out.println(dai.queryFromTableA());
    }
    
    public void setDai(DataAccessInterface dai) {
        this.dai = dai;
    }
}

這個接口除了使用@Component這個注解標注之外,還有個成員變量,使用了Autowire這個注解標注。使用這個注解標注,表示這個成員變量的初始化將會交給BeanFactory來進行。

測試

public class BeanFactoryTester {
 
    public static void main(String[] args) {
        BeanFactory beanFactory = new BeanFactory("com.oolong.javase.annotation");
        
        BusinessObject obj = (BusinessObject) beanFactory.getBean("businessObject");
        obj.print();
            
    }

}

這里使用BeanFactory創建了一個BusinessObject的對象之后,調用這個對象的print方法,最終打印出來一個結果。

而回到這個類的定義中,可以看到:

public void print() {
    System.out.println(dai.queryFromTableA());
}

這個方法調用的是成員變量dai的queryFromTableA方法。而在這個類中,只有這個成員變量的聲明,而沒有賦值。

這個賦值又是在哪里進行的呢?

這個就是有我們編寫的這個BeanFactory執行的。通過注解和反射機制,自動為類注入依賴。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM