美文网首页
spring_IOC 实现原理

spring_IOC 实现原理

作者: Raral | 来源:发表于2022-09-27 18:29 被阅读0次

    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时,两者选其一

    相关文章

      网友评论

          本文标题:spring_IOC 实现原理

          本文链接:https://www.haomeiwen.com/subject/zmxqartx.html