spring_IOC 实现原理

IOC 实现原理

开发工作多年,spring源码没有特意去看过。理解实现原理,不如自己实现简易版的进一步理解IOC到底是怎样实现。下面实现一个最简单的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容器是存储源对象代理对象)
 * 2.给bean注入依赖对象(依赖对象也是代理对象)
 * 3.获取bean(获取的也是代理对象)
 */
public class BeanFactory {

    //基础扫描包路径
    private String basePackage;

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

    //工厂构造函数
    public BeanFactory(String basePackage) {
        this.basePackage = basePackage;
        init();
    }

    //工厂初始化
    private void init() {
        //1.扫描包到IOC容器(注意IOC容器是存储源对象代理对象)
         List<BeanInfo> beanInfoList = scanPackageAndLoadBeans();
        //2.给bean注入依赖对象(依赖对象也是代理对象)
        injectBeans(beanInfoList);
    }

    private void injectBeans(List<BeanInfo> beanInfoList) {
        //遍历每一个bean
        for (BeanInfo beanInfo : beanInfoList) {
            try {
                //获取IOC的bean类型
                Class beanClass = beanInfo.getClz();
                //获取IOC的bean实例对象
                Object bean = beanInfo.getBean();

                //查询当前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)) {
                            //根据名称,获取bean
                            proxyBean = context.getBean(injectBeanName);
                        }else {
                            //根据类型,获取bean
                            proxyBean = context.getBean(injectBeanType);
                        }


                        //设置当前字段可访问
                        declaredField.setAccessible(true);

                        //将从IOC获取的bean,注入到当前字段
                        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();
                    //获取当前类实现的接口
                    Class[] interfaces = aClass.getInterfaces();
                    //记录是否可以使用jdk动态代理(有接口方可进入jdk动态代理,创建代理对象)
                    boolean canJdkProxyBean = interfaces != null && interfaces.length > 0;
                    //获取bean类型,存入IOC容器要用
                    Class beanType = getBeanType(aClass, canJdkProxyBean);

                    //实例对象
                    Object bean = aClass.newInstance();//原始对象实例对象
                    Object iocBean;//存入IOC容器 实例对象
                    if(canJdkProxyBean) {
                        //如果可jdk动态代理,就创建动态代理对象
                        iocBean = this.createProxyBean(bean);
                    }else {
                        iocBean = bean;
                    }


                    //把解析出实例对象bean,存入到IOC容器
                    if(!"".equals(className)) {
                        //按照名称 存入到IOC容器
                        context.putBean(beanName, iocBean);
                    }
                    //存入容器时,根据类型一定要存入的,根据名称存入是依赖传参
                    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) {
            //如果是实现接口的类,可以使用jdk动态代理类
            beanType = aClass.getInterfaces()[0];
        } else {
            beanType = aClass;
        }
        return beanType;
    }

    //根据类型,获取bean
    public <T> T getBean(Class clz) {
        return (T) context.getBean(clz);
    }

    //根据名称,获取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实例对象
    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 上下文对象,用于保存应用运行时的信息 类似applicationContext

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

/**
 *上下文对象,用于保存应用运行时的信息 类似applicationContext
 * 1.map结构IOC容器,存入的bean
 * 2.存入bean
 * 3.取出bean
 */
public class Context {

    //相当于IOC容器根据name存储
    private Map<String, Object> containerBeanName = new HashMap<>();
    //相当于IOC容器根据name存储
    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;
    }

    /**
     * 从项目文件获取某包下有类
     *
     * @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()) {
                /*
                 * 这里是为了方便,先把"/" 转成 "." 再判".class" 的做法可能会有bug
                 * (FIXME: 先把"/" 转成 "." 再判".class" 的做法可能会有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: 只有实现接口的类,可以jdk动态代理,代理目的为了增强功能

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

/**
代理对象,jdk动态代理
 */
public class BeanProxy implements InvocationHandler {

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

    //构造函数,初始化被代理的对象
    public BeanProxy(Object bean) {
        this.bean = bean;
    }

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

  • 验证测试
  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 "当前用户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);

        //调用bean中方法
        String user = userController.getUser(1l);

        System.out.println(user);


    }
}
  1. 结果打印
代理对象执行方法前..........:getUser
代理对象执行方法后............:getUser
当前用户id:1

总结

  1. 注解@Target和Retention,使用作用
    注解@Target和@Retention可以用来修饰注解,是注解的注解,称为元注解。

@Target : Target翻译中文为目标,即该注解可以声明在哪些目标元素之前,也可理解为注释类型的程序元素的种类。

ElementType.PACKAGE:该注解只能声明在一个包名前。

ElementType.ANNOTATION_TYPE:该注解只能声明在一个注解类型前。

ElementType.TYPE:该注解只能声明在一个类前。

ElementType.CONSTRUCTOR:该注解只能声明在一个类的构造方法前。

ElementType.LOCAL_VARIABLE:该注解只能声明在一个局部变量前。

ElementType.METHOD:该注解只能声明在一个类的方法前。

ElementType.PARAMETER:该注解只能声明在一个方法参数前。

ElementType.FIELD:该注解只能声明在一个类的字段前。

@Retention :Retention 翻译成中文为保留,可以理解为如何保留,即告诉编译程序如何处理,也可理解为注解类的生命周期。

RetentionPolicy.SOURCE : 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;

RetentionPolicy.CLASS : 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;

RetentionPolicy.RUNTIME : 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;

这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。
那怎么来选择合适的注解生命周期呢?
首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。

  1. 工厂模式,代理模式

  2. jdk动态代理原理,什么情况才可以使用
    它是在运行时生成的一种类,在生成它时,必须提供一组 interfaces 给它,然后该类就会实现这些 interface。动态代理类就是 Proxy,它不会替你干任何事,在生成它的时候,也必须提供一个 handler,由它接管实际的工作。
    jdk动态代理原理

  3. 名称注入和类型注入
    存入IOC容器时,存储两份,一份名称一份类型,获取bean时,两者选其一

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容