手寫IOC實現過程
一.手寫ioc前基礎知識
1.什麼是IOC(Inversion of Control 控制反轉)?
IoC不是一種技術,只是一種思想,一個重要的面向物件程式設計的法則,它能指導我們如何設計出鬆耦合、更優良的程式。傳統應用程式都是由我們在類內部主動建立依賴物件,從而導致類與類之間高耦合,難於測試;有了IoC容器後,把建立和查詢依賴物件的控制權交給了容器,由容器進行注入組合物件,所以物件與物件之間是鬆散耦合,這樣也方便測試,利於功能複用,更重要的是使得程式的整個體系結構變得非常靈活。
其實IoC對程式設計帶來的最大改變不是從程式碼上,而是從思想上,發生了“主從換位”的變化。應用程式原本是老大,要獲取什麼資源都是主動出擊,但是在IoC/DI思想中,應用程式就變成被動的了,被動的等待IoC容器來建立並注入它所需要的資源了。
IoC很好的體現了面向物件設計法則之一—— 好萊塢法則:“別找我們,我們找你”;即由IoC容器幫物件找相應的依賴物件並注入,而不是由物件主動去找。
2.什麼是DI(Dependency Injection 依賴注入)?
DI—Dependency Injection,即“依賴注入”:是元件之間依賴關係由容器在執行期決定,形象的說,即由容器動態的將某個依賴關係注入到元件之中。依賴注入的目的並非為軟體系統帶來更多功能,而是為了提升元件重用的頻率,併為系統搭建一個靈活、可擴充套件的平臺。通過依賴注入機制,我們只需要通過簡單的配置,而無需任何程式碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。
3.IOC和DI什麼關係?
IoC和DI由什麼關係呢?其實它們是同一個概念的不同角度描述,由於控制反轉概念比較含糊(可能只是理解為容器控制物件這一個層面,很難讓人想到誰來維護物件關係),所以2004年大師級人物Martin Fowler又給出了一個新的名字:“依賴注入”,相對IoC 而言,依賴注入”明確描述了“被注入物件依賴IoC容器配置依賴物件”。
4.什麼是依賴?
傳統應用程式設計中所說的依賴一般指“類之間的關係”,那先讓我們複習一下類之間的關係:
泛化:表示類與類之間的繼承關係、介面與介面之間的繼承關係;
實現:表示類對介面的實現;
依賴:當類與類之間有使用關係時就屬於依賴關係,不同於關聯關係,依賴不具有“擁有關係”,而是一種“相識關係”,只在某個特定地方(比如某個方法體內)才有關係。
關聯:表示類與類或類與介面之間的依賴關係,表現為“擁有關係”;具體到程式碼可以用例項變數來表示;
聚合:屬於是關聯的特殊情況,體現部分-整體關係,是一種弱擁有關係;整體和部分可以有不一樣的生命週期;是一種弱關聯;
組合:屬於是關聯的特殊情況,也體現了體現部分-整體關係,是一種強“擁有關係”;整體與部分有相同的生命週期,是一種強關聯;
Spring IoC容器的依賴有兩層含義:Bean依賴容器和容器注入Bean的依賴資源:
Bean依賴容器:也就是說Bean要依賴於容器,這裡的依賴是指容器負責建立Bean並管理Bean的生命週期,正是由於由容器來控制建立Bean並注入依賴,也就是控制權被反轉了,這也正是IoC名字的由來,此處的有依賴是指Bean和容器之間的依賴關係。
容器注入Bean的依賴資源:容器負責注入Bean的依賴資源,依賴資源可以是Bean、外部檔案、常量資料等,在Java中都反映為物件,並且由容器負責組裝Bean之間的依賴關係,此處的依賴是指Bean之間的依賴關係,可以認為是傳統類與類之間的“關聯”、“聚合”、“組合”關係。
5.依賴注入的好處?
動態替換Bean依賴物件,程式更靈活:替換Bean依賴物件,無需修改原始檔:應用依賴注入後,由於可以採用配置檔案方式實現,從而能隨時動態的替換Bean的依賴物件,無需修改java原始檔;
更好實踐面向介面程式設計,程式碼更清晰:在Bean中只需指定依賴物件的介面,介面定義依賴物件完成的功能,通過容器注入依賴實現;
更好實踐優先使用物件組合,而不是類繼承:因為IoC容器採用注入依賴,也就是組合物件,從而更好的實踐物件組合。
-
採用物件組合,Bean的功能可能由幾個依賴Bean的功能組合而成,其Bean本身可能只提供少許功能或根本無任何功能,全部委託給依賴Bean,物件組合具有動態性,能更方便的替換掉依賴Bean,從而改變Bean功能;
-
而如果採用類繼承,Bean沒有依賴Bean,而是採用繼承方式新增新功能,,而且功能是在編譯時就確定了,不具有動態性,而且採用類繼承導致Bean與子Bean之間高度耦合,難以複用。
增加Bean可複用性:依賴於物件組合,Bean更可複用且複用更簡單;
降低Bean之間耦合:由於我們完全採用面向介面程式設計,在程式碼中沒有直接引用Bean依賴實現,全部引用介面,而且不會出現顯示的建立依賴物件程式碼,而且這些依賴是由容器來注入,很容易替換依賴實現類,從而降低Bean與依賴之間耦合;
程式碼結構更清晰:要應用依賴注入,程式碼結構要按照規約方式進行書寫,從而更好的應用一些最佳實踐,因此程式碼結構更清晰。
從以上我們可以看出,其實依賴注入只是一種裝配物件的手段,設計的類結構才是基礎,如果設計的類結構不支援依賴注入,Spring IoC容器也注入不了任何東西,從而從根本上說“如何設計好類結構才是關鍵,依賴注入只是一種裝配物件手段”。
二. 手寫IOC
標記配置分為集中式管理和分散式管理,即xml檔案方式和註解方式配置元資料資訊,手寫ioc我採用註解配置元資料方式來實現ioc的大致執行過程。
1.定義元資料配置資訊
@Component/@Controller/@Repository@Service
掃描什麼樣的class裝載到ioc容器中進行管理。
@Autowired
什麼樣的物件進行依賴注入。
/**
* @Classname Component
* @Description
* @Date 2020/8/6 14:54
* @Created by zhangtianci
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
/**
* @Classname Controller
* @Description TODO
* @Date 2020/8/6 14:56
* @Created by zhangtianci
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
/**
* @Classname Repository
* @Description TODO
* @Date 2020/8/6 14:58
* @Created by zhangtianci
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
}
/**
* @Classname Service
* @Description TODO
* @Date 2020/8/6 14:57
* @Created by zhangtianci
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}
/**
* @Classname Autowired
* @Description 自動注入註解
* @Date 2020/8/6 14:28
* @Created by zhangtianci
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
String value() default "";
}
2.實現ioc容器
核心功能:載入被配置標記的class檔案,交給ioc容器管理。
package org.simplespring.core;
import lombok.extern.slf4j.Slf4j;
import org.simplespring.core.annotation.Component;
import org.simplespring.core.annotation.Controller;
import org.simplespring.core.annotation.Repository;
import org.simplespring.core.annotation.Service;
import org.simplespring.util.ClassUtil;
import org.simplespring.util.ValidationUtil;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Classname BeanContainer
* @Description bean容器
* <p>
* BeanContainer應該是單例的 採用內部列舉的方式實現。
*
* 應該擁有的例項方法:
* 1.boolean isLoad() 是否載入
* 2.int getSize() 獲取bean個數
* 3.loadBeans(String packageName) 根據包名載入所有被配置標記的class檔案/
* 4.獲取/刪除/增加bean及提供一些便利的方法
*
* @Date 2020/8/6 14:41
* @Created by zhangtianci
*/
@Slf4j
public class BeanContainer {
/**
* 存放所有被配置標記(xml/註解)的class,並new出一個例項物件bean
* 存放在一個map中
*/
private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>();
/**
* 載入bean的註解列表
*/
private static final List<Class<? extends Annotation>> BEAN_ANNOTATION
= Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);
/**
* 是否已經載入過bean
*/
private boolean loaded = false;
/**
* 獲取bean容器
*
* @return
*/
public static BeanContainer getInstance() {
return ContainerHolder.HOLDER.instance;
}
private enum ContainerHolder {
HOLDER;
private BeanContainer instance;
ContainerHolder() {
instance = new BeanContainer();
}
}
/**
* 是否已經載入過bean
*/
public boolean isLoad(){
return loaded;
}
/**
* 獲取容器中bean的個數
*/
public int getSize(){
return beanMap.size();
}
/**
* 載入指定路徑下的所有class檔案
* 並將所有被配置標記(xml/註解)的class物件和new出一個例項物件放入容器
*/
public synchronized void loadBeans(String packageName){
//判斷容器是否已經載入過
if (loaded){
log.warn("Container has loaded!");
return;
}
// 匯出所有的class物件
Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName);
//為空直接return
if (ValidationUtil.isEmpty(classSet)) {
log.warn("extract nothing from packageName" + packageName);
return;
}
classSet.stream().forEach(clazz -> {
for (Class<? extends Annotation> annotationClazz : BEAN_ANNOTATION) {
if (clazz.isAnnotationPresent(annotationClazz)){
//將目標類本身作為鍵,目標類的例項作為值,放入到beanMap中
beanMap.put(clazz, ClassUtil.newInstance(clazz, true));
}
}
});
loaded = true;
}
/**
* 新增一個class物件及其Bean例項
*
* @param clazz Class物件
* @param bean Bean例項
* @return 原有的Bean例項, 沒有則返回null
*/
public Object addBean(Class<?> clazz, Object bean) {
return beanMap.put(clazz, bean);
}
/**
* 移除一個IOC容器管理的物件
*
* @param clazz Class物件
* @return 刪除的Bean例項, 沒有則返回null
*/
public Object removeBean(Class<?> clazz) {
return beanMap.remove(clazz);
}
/**
* 根據Class物件獲取Bean例項
*
* @param clazz Class物件
* @return Bean例項
*/
public Object getBean(Class<?> clazz) {
return beanMap.get(clazz);
}
/**
* 獲取容器管理的所有Class物件集合
*
* @return Class集合
*/
public Set<Class<?>> getClasses(){
return beanMap.keySet();
}
/**
* 獲取所有Bean集合
*
* @return Bean集合
*/
public Set<Object> getBeans(){
return new HashSet<>( beanMap.values());
}
/**
* 根據註解篩選出Bean的Class集合
*
* @param annotation 註解
* @return Class集合
*/
public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation){
//1.獲取beanMap的所有class物件
Set<Class<?>> keySet = getClasses();
if(ValidationUtil.isEmpty(keySet)){
log.warn("nothing in beanMap");
return null;
}
//2.通過註解篩選被註解標記的class物件,並新增到classSet裡
Set<Class<?>> classSet = new HashSet<>();
for(Class<?> clazz : keySet){
//類是否有相關的註解標記
if(clazz.isAnnotationPresent(annotation)){
classSet.add(clazz);
}
}
return classSet.size() > 0? classSet: null;
}
/**
* 通過介面或者父類獲取實現類或者子類的Class集合,不包括其本身
*
* @param interfaceOrClass 介面Class或者父類Class
* @return Class集合
*/
public Set<Class<?>> getClassesBySuper(Class<?> interfaceOrClass){
//1.獲取beanMap的所有class物件
Set<Class<?>> keySet = getClasses();
if(ValidationUtil.isEmpty(keySet)){
log.warn("nothing in beanMap");
return null;
}
//2.判斷keySet裡的元素是否是傳入的介面或者類的子類,如果是,就將其新增到classSet裡
Set<Class<?>> classSet = new HashSet<>();
for(Class<?> clazz : keySet){
//判斷keySet裡的元素是否是傳入的介面或者類的子類
if(interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)){
classSet.add(clazz);
}
}
return classSet.size() > 0? classSet: null;
}
}
3.定義依賴注入器
核心功能:掃描被管理的bean物件,進行依賴注入。
package org.simplespring.inject;
import lombok.extern.slf4j.Slf4j;
import org.simplespring.core.BeanContainer;
import org.simplespring.inject.annotation.Autowired;
import org.simplespring.util.ClassUtil;
import org.simplespring.util.ValidationUtil;
import java.lang.reflect.Field;
import java.util.Set;
/**
* @Classname DependencyInjector
* @Description 依賴注入器
* @Date 2020/8/6 14:38
* @Created by zhangtianci
*/
@Slf4j
public class DependencyInjector {
/**
* 擁有一個Bean容器
*/
private BeanContainer beanContainer;
public DependencyInjector(){
beanContainer = BeanContainer.getInstance();
}
/**
* 執行依賴注入
*
* 1.遍歷Bean容器中所有的Class物件
* 2.遍歷Class物件的所有成員變數
* 3.找出被Autowired標記的成員變數
* 4.獲取這些成員變數的型別
* 5.獲取這些成員變數的型別在容器裡對應的例項
* 6.通過反射將對應的成員變數例項注入到成員變數所在類的例項裡
*/
public void doIoc(){
if(ValidationUtil.isEmpty(beanContainer.getClasses())){
log.warn("empty classset in BeanContainer");
return;
}
//1.遍歷Bean容器中所有的Class物件
for(Class<?> clazz : beanContainer.getClasses()){
//2.遍歷Class物件的所有成員變數
Field[] fields = clazz.getDeclaredFields();
if (ValidationUtil.isEmpty(fields)){
continue;
}
for(Field field : fields){
//3.找出被Autowired標記的成員變數
if(field.isAnnotationPresent(Autowired.class)){
Autowired autowired = field.getAnnotation(Autowired.class);
String autowiredValue = autowired.value();
//4.獲取這些成員變數的型別
Class<?> fieldClass = field.getType();
//5.獲取這些成員變數的型別在容器裡對應的例項
Object fieldValue = getFieldInstance(fieldClass, autowiredValue);
if(fieldValue == null){
throw new RuntimeException("unable to inject relevant type,target fieldClass is:" + fieldClass.getName() + " autowiredValue is : " + autowiredValue);
} else {
//6.通過反射將對應的成員變數例項注入到成員變數所在類的例項裡
Object targetBean = beanContainer.getBean(clazz);
ClassUtil.setField(field, targetBean, fieldValue, true);
}
}
}
}
}
/**
* 根據Class在beanContainer裡獲取其例項或者實現類
*/
private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) {
Object fieldValue = beanContainer.getBean(fieldClass);
if (fieldValue != null){
return fieldValue;
} else {
Class<?> implementedClass = getImplementedClass(fieldClass, autowiredValue);
if(implementedClass != null){
return beanContainer.getBean(implementedClass);
} else {
return null;
}
}
}
/**
* 獲取介面的實現類
*/
private Class<?> getImplementedClass(Class<?> fieldClass, String autowiredValue) {
Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass);
if(!ValidationUtil.isEmpty(classSet)){
if(ValidationUtil.isEmpty(autowiredValue)){
if(classSet.size() == 1){
return classSet.iterator().next();
} else {
//如果多於兩個實現類且使用者未指定其中一個實現類,則丟擲異常
throw new RuntimeException("multiple implemented classes for " + fieldClass.getName() + " please set @Autowired's value to pick one");
}
} else {
for(Class<?> clazz : classSet){//別名採用clazz.getSimpleName()簡單實現
if(autowiredValue.equals(clazz.getSimpleName())){
return clazz;
}
}
}
}
return null;
}
}
4.工具包
作為框架 類載入/反射/校驗等功能。
package org.simplespring.util;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* @Classname ClassUtil
* @Description
* @Date 2020/8/8 15:20
* @Created by zhangtianci
*/
@Slf4j
public class ClassUtil {
public static final String FILE_PROTOCOL = "file";
/**
* 獲取類載入器
* 因為專案部署在tomcat等一些web容器中
* 這些web容器載入部署在裡面的apps 所以需要自定義classLoader去載入class檔案
* 當我們寫框架需要去通過類載入器去載入class檔案時 就需要通過Thread.currentThread().getContextClassLoader()
* 拿到tomcat的自定義的classLoader去載入專案裡面的class檔案
* 詳情 參考我寫的classLoader載入相關文章
* @return
*/
public static ClassLoader getClassLoader(){
return Thread.currentThread().getContextClassLoader();
}
/**
* 通過包名載入class
*
* 點進去Class.forName()方法進去看看
*
* @CallerSensitive
* public static Class<?> forName(String className)
* throws ClassNotFoundException {
* Class<?> caller = Reflection.getCallerClass();
* return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
* }
*
* 獲取到呼叫這個方法的class物件 然後用這個class物件的classLoader去載入這個class檔案
* 所以歸根結底是 拿到tomcat的自定義的classLoader去載入的class檔案
* 所以沒問題
* @param className class全名=package + 類名
* @return
*/
public static Class<?> loadClass(String className){
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
log.error("loadClass failed!",e);
throw new RuntimeException(e);
}
}
/**
* 通過一個class物件例項化一個例項物件(通過預設構造器)
* @param clazz
* @param accessible
* @param <T>
* @return
*/
public static <T> T newInstance(Class<?> clazz, boolean accessible){
try {
//clazz.getDeclaredConstructor() 獲取指定引數類表的構造器
//這裡獲取預設構造器
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(accessible);
return (T)constructor.newInstance();
} catch (Exception e) {
log.error("new instance failed!");
throw new RuntimeException(e);
}
}
/**
* 設定類的屬性值
*
* @param field 成員變數
* @param target 類例項
* @param value 成員變數的值
* @param accessible 是否允許設定私有屬性
*/
public static void setField(Field field, Object target, Object value, boolean accessible){
field.setAccessible(accessible);
try {
field.set(target, value);
} catch (IllegalAccessException e) {
log.error("setField error", e);
throw new RuntimeException(e);
}
}
/**
* 載入包路徑下的所有的class檔案
* 將class物件放進set集合中
*
* 1.獲取classLoader載入器
* 2.遞迴載入路徑下的所有class檔案
* @param packageName
* @return
*/
public static Set<Class<?>> extractPackageClass(String packageName){
//1.獲取classLoader載入器
ClassLoader classLoader = getClassLoader();
//2.獲取資原始檔的的url
URL url = classLoader.getResource(packageName.replace(".","/"));
if (url == null){
log.warn("unable to retrieve anything from package: " + packageName);
return null;
}
//3.依據不同的資源型別,採用不同的方式獲取資源的集合
Set<Class<?>> classSet = null;
//過濾出檔案型別的資源
if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)){
classSet = new HashSet<Class<?>>();
File packageDirectory = new File(url.getPath());
extractClassFile(classSet, packageDirectory, packageName);
}
//TODO 此處可以加入針對其他型別資源的處理
return null;
}
/**
* 遞迴載入包路徑下的class檔案
* @param classSet
* @param packageDirectory
* @param packageName
*/
private static void extractClassFile(Set<Class<?>> classSet, File packageDirectory, String packageName) {
if(!packageDirectory.isDirectory()){
return;
}
//如果是一個資料夾,則呼叫其listFiles方法獲取資料夾下的檔案或資料夾
File[] files = packageDirectory.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if(pathname.isDirectory()){
return true;
} else{
//獲取檔案的絕對值路徑
String absoluteFilePath = pathname.getAbsolutePath();
if(absoluteFilePath.endsWith(".class")){
//若是class檔案,則直接載入
addToClassSet(absoluteFilePath);
}
}
return false;
}
//根據class檔案的絕對值路徑,獲取並生成class物件,並放入classSet中
private void addToClassSet(String absoluteFilePath) {
//1.從class檔案的絕對值路徑裡提取出包含了package的類名
//如/Users/zhangtc/springframework/simple-spring/target/classes/com/zhangtianci/entity/dto/MainPageInfoDTO.class
//需要弄成com.zhangtianci.entity.dto.MainPageInfoDTO
absoluteFilePath = absoluteFilePath.replace(File.separator, ".");
String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName));
className = className.substring(0, className.lastIndexOf("."));
//2.通過反射機制獲取對應的Class物件並加入到classSet裡
Class targetClass = loadClass(className);
classSet.add(targetClass);
}
});
if(files == null){
return;
}
Arrays.stream(files).forEach( file -> {
extractClassFile(classSet,file,packageName);
});
}
}
package org.simplespring.util;
import java.util.Collection;
import java.util.Map;
public class ValidationUtil {
/**
* String是否為null或""
*
* @param obj String
* @return 是否為空
*/
public static boolean isEmpty(String obj) {
return (obj == null || "".equals(obj));
}
/**
* Array是否為null或者size為0
*
* @param obj Array
* @return 是否為空
*/
public static boolean isEmpty(Object[] obj) {
return obj == null || obj.length == 0;
}
/**
* Collection是否為null或size為0
*
* @param obj Collection
* @return 是否為空
*/
public static boolean isEmpty(Collection<?> obj){
return obj == null || obj.isEmpty();
}
/**
* Map是否為null或size為0
*
* @param obj Map
* @return 是否為空
*/
public static boolean isEmpty(Map<?, ?> obj) {
return obj == null || obj.isEmpty();
}
}