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 beanInfoList = scanPackageAndLoadBeans();
        //2.给bean注入依赖对象(依赖对象也是代理对象)
        injectBeans(beanInfoList);
    }

    private void injectBeans(List 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 scanPackageAndLoadBeans() {
        List myBeanList = new ArrayList<>();
        //获取包路径下的所有类
        Set classNames = ClassUtils.getClassName(basePackage, true);
        for (String className : classNames) {
            try {
                //获取反射
                //Class 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 getBean(Class clz) {
        return (T) context.getBean(clz);
    }

    //根据名称,获取bean
    public  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 containerBeanName = new HashMap<>();
    //相当于IOC容器根据name存储
    private Map containerBeanClass = new HashMap<>();

    public Map 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 getClassName(String packageName, boolean isRecursion) {
        Set 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 getClassNameFromDir(String filePath, String packageName, boolean isRecursion) {
        Set 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 getClassNameFromJar(Enumeration jarEntries, String packageName,
                                                   boolean isRecursion) {
        Set 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 getClassNameFromJars(URL[] urls, String packageName, boolean isRecursion) {
        Set 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时,两者选其一

你可能感兴趣的:(spring_IOC 实现原理)