解析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執行的。通過注解和反射機制,自動為類注入依賴。