spring_IOC 實(shí)現(xiàn)原理

IOC 實(shí)現(xiàn)原理

開發(fā)工作多年叠国,spring源碼沒有特意去看過泡一。理解實(shí)現(xiàn)原理查吊,不如自己實(shí)現(xiàn)簡易版的進(jìn)一步理解IOC到底是怎樣實(shí)現(xiàn)近她。下面實(shí)現(xiàn)一個(gè)最簡單的ioc容器

模擬IOC容器獲取bean

  • 注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 注入注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.FIELD})
public @interface AutoInject {

    //注入bean的名稱
    String value() default "";
}



import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 注冊bean到IOC容器
 */
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBean {

    //存入到IOC容器,bean的名稱
    String value() default "";
}

  • BeanFactory
package com.lg.ioc.core;

import com.lg.ioc.core.annotation.AutoInject;
import com.lg.ioc.core.annotation.MyBean;
import com.lg.ioc.core.utils.ClassUtils;
import com.sun.deploy.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * bean工廠
 * 1.掃描包到IOC容器(注意IOC容器是存儲(chǔ)源對象代理對象)
 * 2.給bean注入依賴對象(依賴對象也是代理對象)
 * 3.獲取bean(獲取的也是代理對象)
 */
public class BeanFactory {

    //基礎(chǔ)掃描包路徑
    private String basePackage;

    //上下文對象
    private Context context = new Context();

    //工廠構(gòu)造函數(shù)
    public BeanFactory(String basePackage) {
        this.basePackage = basePackage;
        init();
    }

    //工廠初始化
    private void init() {
        //1.掃描包到IOC容器(注意IOC容器是存儲(chǔ)源對象代理對象)
         List<BeanInfo> beanInfoList = scanPackageAndLoadBeans();
        //2.給bean注入依賴對象(依賴對象也是代理對象)
        injectBeans(beanInfoList);
    }

    private void injectBeans(List<BeanInfo> beanInfoList) {
        //遍歷每一個(gè)bean
        for (BeanInfo beanInfo : beanInfoList) {
            try {
                //獲取IOC的bean類型
                Class beanClass = beanInfo.getClz();
                //獲取IOC的bean實(shí)例對象
                Object bean = beanInfo.getBean();

                //查詢當(dāng)前bean所有字段
                Field[] declaredFields = beanClass.getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    if(declaredField.isAnnotationPresent(AutoInject.class)) {
                        //獲取@AutoInject注解信息
                        AutoInject autoInjectAnnotation = declaredField.getAnnotation(AutoInject.class);
                        //獲取注入bean名稱
                        String injectBeanName = autoInjectAnnotation.value();
                        //獲取注入bean類型
                        Class injectBeanType = declaredField.getType();

                        //從IOC容器查找bean對象
                        Object proxyBean;
                        if(!"".equals(injectBeanName)) {
                            //根據(jù)名稱竟宋,獲取bean
                            proxyBean = context.getBean(injectBeanName);
                        }else {
                            //根據(jù)類型提完,獲取bean
                            proxyBean = context.getBean(injectBeanType);
                        }


                        //設(shè)置當(dāng)前字段可訪問
                        declaredField.setAccessible(true);

                        //將從IOC獲取的bean,注入到當(dāng)前字段
                        declaredField.set(bean, proxyBean);

                    }
                }

            } catch (Exception e) {
                throw new RuntimeException(e);
            }


        }

    }

    private List<BeanInfo> scanPackageAndLoadBeans() {
        List<BeanInfo> myBeanList = new ArrayList<>();
        //獲取包路徑下的所有類
        Set<String> classNames = ClassUtils.getClassName(basePackage, true);
        for (String className : classNames) {
            try {
                //獲取反射
                //Class<? extends String> aClass = className.getClass();
                Class aClass = Class.forName(className);
                //判斷是否存在MyBean注解
                if (aClass.isAnnotationPresent(MyBean.class)) {
                    //獲取注解信息
                    MyBean myBeanAnnotation = (MyBean)aClass.getAnnotation(MyBean.class);
                    //獲取注解value
                    String beanName = myBeanAnnotation.value();
                    //獲取當(dāng)前類實(shí)現(xiàn)的接口
                    Class[] interfaces = aClass.getInterfaces();
                    //記錄是否可以使用jdk動(dòng)態(tài)代理(有接口方可進(jìn)入jdk動(dòng)態(tài)代理,創(chuàng)建代理對象)
                    boolean canJdkProxyBean = interfaces != null && interfaces.length > 0;
                    //獲取bean類型丘侠,存入IOC容器要用
                    Class beanType = getBeanType(aClass, canJdkProxyBean);

                    //實(shí)例對象
                    Object bean = aClass.newInstance();//原始對象實(shí)例對象
                    Object iocBean;//存入IOC容器 實(shí)例對象
                    if(canJdkProxyBean) {
                        //如果可jdk動(dòng)態(tài)代理徒欣,就創(chuàng)建動(dòng)態(tài)代理對象
                        iocBean = this.createProxyBean(bean);
                    }else {
                        iocBean = bean;
                    }


                    //把解析出實(shí)例對象bean,存入到IOC容器
                    if(!"".equals(className)) {
                        //按照名稱 存入到IOC容器
                        context.putBean(beanName, iocBean);
                    }
                    //存入容器時(shí),根據(jù)類型一定要存入的蜗字,根據(jù)名稱存入是依賴傳參
                    context.putBean(beanType, iocBean);

                    //組裝beanInfo,暫存bean信息
                    BeanInfo beanInfo = new BeanInfo();
                    beanInfo.setClz(beanType);
                    beanInfo.setBeanName(beanName);
                    beanInfo.setBeanType(beanType);
                    beanInfo.setBean(bean);
                    beanInfo.setProxyBean(iocBean);
                    myBeanList.add(beanInfo);
                }


            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            } catch (InstantiationException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }

        }

        return myBeanList;
    }

    private Object createProxyBean(Object bean) {
        InvocationHandler invocationHandler = new BeanProxy(bean);
        Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(),
                bean.getClass().getInterfaces(), invocationHandler);
        return proxyBean;
    }


    private Class getBeanType(Class aClass, boolean canJdkProxyBean) {
        Class beanType;
        if(canJdkProxyBean) {
            //如果是實(shí)現(xiàn)接口的類帚称,可以使用jdk動(dòng)態(tài)代理類
            beanType = aClass.getInterfaces()[0];
        } else {
            beanType = aClass;
        }
        return beanType;
    }

    //根據(jù)類型,獲取bean
    public <T> T getBean(Class clz) {
        return (T) context.getBean(clz);
    }

    //根據(jù)名稱秽澳,獲取bean
    public <T> T getBean(String beanName) {
        return (T) context.getBean(beanName);
    }
}

  • bean信息

/**
 * bean類型信息
 */
public class BeanInfo {
    //bean類型
    private Class clz;

    //存入容器IOC的bean名稱
    private String beanName;

    //存入容器IOC的bean類型
    private Class beanType;

    //存入容器IOC的bean實(shí)例對象
    private Object bean;

    //存入容器IOC的bean代理對象
    private Object proxyBean;

    public Class getClz() {
        return clz;
    }

    public void setClz(Class clz) {
        this.clz = clz;
    }

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public Class getBeanType() {
        return beanType;
    }

    public void setBeanType(Class beanType) {
        this.beanType = beanType;
    }

    public Object getBean() {
        return bean;
    }

    public void setBean(Object bean) {
        this.bean = bean;
    }

    public Object getProxyBean() {
        return proxyBean;
    }

    public void setProxyBean(Object proxyBean) {
        this.proxyBean = proxyBean;
    }
}

  • context 上下文對象闯睹,用于保存應(yīng)用運(yùn)行時(shí)的信息 類似applicationContext

import java.util.HashMap;
import java.util.Map;

/**
 *上下文對象,用于保存應(yīng)用運(yùn)行時(shí)的信息 類似applicationContext
 * 1.map結(jié)構(gòu)IOC容器担神,存入的bean
 * 2.存入bean
 * 3.取出bean
 */
public class Context {

    //相當(dāng)于IOC容器根據(jù)name存儲(chǔ)
    private Map<String, Object> containerBeanName = new HashMap<>();
    //相當(dāng)于IOC容器根據(jù)name存儲(chǔ)
    private Map<Class, Object> containerBeanClass = new HashMap<>();

    public Map<String, Object> getContainerBeanName() {
        return containerBeanName;
    }

    public Object getBean(String beanName) {
        return containerBeanName.get(beanName);
    }

    public Object getBean(Class clz) {
        return containerBeanClass.get(clz);
    }

    public void putBean(String beanName, Object proxyBean) {
        containerBeanName.put(beanName, proxyBean);
    }

    public void putBean(Class clz, Object proxyBean) {
        containerBeanClass.put(clz, proxyBean);
    }

}

  • 工具類:將包路徑下的類解析出來
package com.lg.ioc.core.utils;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 類工具
 */
public class ClassUtils {

    /**
     * 獲取某包下所有類
     *
     * @param packageName 包名
     * @param isRecursion 是否遍歷子包
     * @return 類的完整名稱
     */
    public static Set<String> getClassName(String packageName, boolean isRecursion) {
        Set<String> classNames = new HashSet<>();
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        String packagePath = packageName.replace(".", "/");
        URL url = loader.getResource(packagePath);
        if (url != null) {
            String protocol = url.getProtocol();
            if (protocol.equals("file")) {
                String filePath = null;
                try {
                    filePath = URLDecoder.decode(url.getPath(), "utf-8");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                if (filePath != null) {
                    classNames = getClassNameFromDir(filePath, packageName, isRecursion);
                }
            }else if (protocol.equals("jar")) {
                JarFile jarFile = null;
                try {
                    jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
                } catch (Exception e) {
                    e.printStackTrace();
                }

                if (jarFile != null) {
                    classNames = getClassNameFromJar(jarFile.entries(), packageName, isRecursion);
                }
            }
        } else {
            /*從所有的jar包中查找包名*/
            classNames = getClassNameFromJars(((URLClassLoader) loader).getURLs(), packageName, isRecursion);
        }
        return classNames;
    }

    /**
     * 從項(xiàng)目文件獲取某包下有類
     *
     * @param filePath    文件路徑
     * @param isRecursion 是否遍歷子包
     * @return 類的完整名稱
     */
    private static Set<String> getClassNameFromDir(String filePath, String packageName, boolean isRecursion) {
        Set<String> className = new HashSet<>();
        File file = new File(filePath);
        File[] files = file.listFiles();
        for (File childFile : files) {

            if (childFile.isDirectory()) {
                if (isRecursion) {
                    className.addAll(getClassNameFromDir(childFile.getPath(), packageName + "." + childFile.getName(), isRecursion));
                }
            } else {
                String fileName = childFile.getName();
                if (fileName.endsWith(".class") && !fileName.contains("$")) {
                    className.add(packageName + "." + fileName.replace(".class", ""));
                }
            }
        }
        return className;
    }

    /**
     * @param jarEntries
     * @param packageName
     * @param isRecursion
     * @return
     */
    private static Set<String> getClassNameFromJar(Enumeration<JarEntry> jarEntries, String packageName,
                                                   boolean isRecursion) {
        Set<String> classNames = new HashSet();

        while (jarEntries.hasMoreElements()) {
            JarEntry jarEntry = jarEntries.nextElement();
            if (!jarEntry.isDirectory()) {
                /*
                 * 這里是為了方便楼吃,先把"/" 轉(zhuǎn)成 "." 再判".class" 的做法可能會(huì)有bug
                 * (FIXME: 先把"/" 轉(zhuǎn)成 "." 再判".class" 的做法可能會(huì)有bug)
                 */
                String entryName = jarEntry.getName().replace("/", ".");
                if (entryName.endsWith(".class") && !entryName.contains("$") && entryName.startsWith(packageName)) {
                    entryName = entryName.replace(".class", "");
                    if (isRecursion) {
                        classNames.add(entryName);
                    } else if (!entryName.replace(packageName + ".", "").contains(".")) {
                        classNames.add(entryName);
                    }
                }
            }
        }

        return classNames;
    }

    /**
     * 從所有jar中搜索該包,并獲取該包下所有類
     *
     * @param urls        URL集合
     * @param packageName 包名
     * @param isRecursion 是否遞歸遍歷子包
     * @return 類的完整名稱
     */
    private static Set<String> getClassNameFromJars(URL[] urls, String packageName, boolean isRecursion) {
        Set<String> classNames = new HashSet<>();

        for (int i = 0; i < urls.length; i++) {
            String classPath = urls[i].getPath();
            //不必搜索classes文件夾妄讯?
            if (classPath.endsWith("classes/")) {
                continue;
            }

            JarFile jarFile = null;
            try {
                jarFile = new JarFile(classPath.substring(classPath.indexOf("/")));
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (jarFile != null) {
                classNames.addAll(getClassNameFromJar(jarFile.entries(), packageName, isRecursion));
            }
        }

        return classNames;
    }



}
  • BeanProxy: 只有實(shí)現(xiàn)接口的類孩锡,可以jdk動(dòng)態(tài)代理,代理目的為了增強(qiáng)功能

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
代理對象,jdk動(dòng)態(tài)代理
 */
public class BeanProxy implements InvocationHandler {

    //被代理的對象
    private Object bean;

    //構(gòu)造函數(shù)亥贸,初始化被代理的對象
    public BeanProxy(Object bean) {
        this.bean = bean;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理對象執(zhí)行方法前..........:" + method.getName());
        Object result = method.invoke(bean, args);
        System.out.println("代理對象執(zhí)行方法后............:" + method.getName());
        return result;
    }
}

  • 驗(yàn)證測試
  1. controller

/**
 * 模擬controller
 */

@MyBean("userController")
public class UserController {

    @AutoInject("userService")
    private IUserService userService;


    public String getUser(Long id) {
        return userService.getUser(id);
    }

}

  1. service
public interface IUserService {

    String getUser(Long id);
}

@MyBean("userService")
public class UserServiceImpl implements IUserService{
    @Override
    public String getUser(Long id) {
        return "當(dāng)前用戶id:" + id;
    }
}


  1. main
public class Main {
    public static void main(String[] args) {
        //定義掃描包路徑
        String basePackage = "com.lg.ioc.example";
        //初始化bean工廠
        BeanFactory beanFactory = new BeanFactory(basePackage);

        //獲取bean
        UserController userController = beanFactory.getBean(UserController.class);

        //調(diào)用bean中方法
        String user = userController.getUser(1l);

        System.out.println(user);


    }
}
  1. 結(jié)果打印
代理對象執(zhí)行方法前..........:getUser
代理對象執(zhí)行方法后............:getUser
當(dāng)前用戶id:1

總結(jié)

  1. 注解@Target和Retention,使用作用
    注解@Target和@Retention可以用來修飾注解躬窜,是注解的注解,稱為元注解炕置。

@Target : Target翻譯中文為目標(biāo)荣挨,即該注解可以聲明在哪些目標(biāo)元素之前,也可理解為注釋類型的程序元素的種類朴摊。

ElementType.PACKAGE:該注解只能聲明在一個(gè)包名前默垄。

ElementType.ANNOTATION_TYPE:該注解只能聲明在一個(gè)注解類型前。

ElementType.TYPE:該注解只能聲明在一個(gè)類前甚纲。

ElementType.CONSTRUCTOR:該注解只能聲明在一個(gè)類的構(gòu)造方法前口锭。

ElementType.LOCAL_VARIABLE:該注解只能聲明在一個(gè)局部變量前。

ElementType.METHOD:該注解只能聲明在一個(gè)類的方法前介杆。

ElementType.PARAMETER:該注解只能聲明在一個(gè)方法參數(shù)前鹃操。

ElementType.FIELD:該注解只能聲明在一個(gè)類的字段前。

@Retention :Retention 翻譯成中文為保留春哨,可以理解為如何保留荆隘,即告訴編譯程序如何處理,也可理解為注解類的生命周期悲靴。

RetentionPolicy.SOURCE : 注解只保留在源文件臭胜,當(dāng)Java文件編譯成class文件的時(shí)候莫其,注解被遺棄;

RetentionPolicy.CLASS : 注解被保留到class文件耸三,但jvm加載class文件時(shí)候被遺棄乱陡,這是默認(rèn)的生命周期;

RetentionPolicy.RUNTIME : 注解不僅被保存到class文件中仪壮,jvm加載class文件之后憨颠,仍然存在;

這3個(gè)生命周期分別對應(yīng)于:Java源文件(.java文件) ---> .class文件 ---> 內(nèi)存中的字節(jié)碼积锅。
那怎么來選擇合適的注解生命周期呢爽彤?
首先要明確生命周期長度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用缚陷。

  1. 工廠模式适篙,代理模式

  2. jdk動(dòng)態(tài)代理原理,什么情況才可以使用
    它是在運(yùn)行時(shí)生成的一種類箫爷,在生成它時(shí)嚷节,必須提供一組 interfaces 給它,然后該類就會(huì)實(shí)現(xiàn)這些 interface虎锚。動(dòng)態(tài)代理類就是 Proxy硫痰,它不會(huì)替你干任何事,在生成它的時(shí)候窜护,也必須提供一個(gè) handler效斑,由它接管實(shí)際的工作。
    jdk動(dòng)態(tài)代理原理

  3. 名稱注入和類型注入
    存入IOC容器時(shí)柱徙,存儲(chǔ)兩份缓屠,一份名稱一份類型,獲取bean時(shí)坐搔,兩者選其一

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末藏研,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子概行,更是在濱河造成了極大的恐慌,老刑警劉巖弧岳,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凳忙,死亡現(xiàn)場離奇詭異,居然都是意外死亡禽炬,警方通過查閱死者的電腦和手機(jī)涧卵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腹尖,“玉大人柳恐,你說我怎么就攤上這事。” “怎么了乐设?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵讼庇,是天一觀的道長。 經(jīng)常有香客問我近尚,道長蠕啄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任戈锻,我火速辦了婚禮歼跟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘格遭。我一直安慰自己哈街,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布拒迅。 她就那樣靜靜地躺著骚秦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坪它。 梳的紋絲不亂的頭發(fā)上骤竹,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機(jī)與錄音往毡,去河邊找鬼蒙揣。 笑死,一個(gè)胖子當(dāng)著我的面吹牛开瞭,可吹牛的內(nèi)容都是我干的懒震。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嗤详,長吁一口氣:“原來是場噩夢啊……” “哼个扰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起葱色,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤递宅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后苍狰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體办龄,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年淋昭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俐填。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡翔忽,死狀恐怖英融,靈堂內(nèi)的尸體忽然破棺而出盏檐,到底是詐尸還是另有隱情,我是刑警寧澤驶悟,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布胡野,位于F島的核電站,受9級特大地震影響撩银,放射性物質(zhì)發(fā)生泄漏给涕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一额获、第九天 我趴在偏房一處隱蔽的房頂上張望够庙。 院中可真熱鬧,春花似錦抄邀、人聲如沸耘眨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剔难。三九已至,卻和暖如春奥喻,著一層夾襖步出監(jiān)牢的瞬間偶宫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工环鲤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纯趋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓冷离,卻偏偏與公主長得像吵冒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子西剥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容