手写Spring-IOC篇

1.Spring的IOC的一些总结
谈谈对SpringIOC的理解,总结就是IOC是控制反转,将对象创建的控制权交由spring容器,当我们需要使用时只需要注入,无需关心对象是什么时候创建的,是怎么创建的.

2.手写spring ioc 思想
主要大致分为两大步

  • 读取配置文件,获取配置文件信息
  • 将所有类实例化,放入spring容器中,实际就是一个map容器,key为所有类的Class类型,value是实例化后的bean中,扫描所有bean的属性是否有Autowired注解,再进行注入

3.动手
1.准备环境
创建一个maven项目,pom.xml如下

<dependencies>
        
        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            <version>3.1.0version>
            <scope>providedscope>
        dependency>
        
        <dependency>
            <groupId>javax.servlet.jspgroupId>
            <artifactId>jsp-apiartifactId>
            <version>2.2version>
            <scope>providedscope>
        dependency>
        
        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>jstlartifactId>
            <version>1.2version>
            <scope>runtimescope>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.33version>
            <scope>runtimescope>
        dependency>
        
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-dbcp2artifactId>
            <version>2.0.1version>
        dependency>
        
        <dependency>
            <groupId>commons-dbutilsgroupId>
            <artifactId>commons-dbutilsartifactId>
            <version>1.6version>
        dependency>
        
        <dependency>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-log4j12artifactId>
            <version>1.7.7version>
        dependency>
        
        <dependency>
            <groupId>cglibgroupId>
            <artifactId>cglibartifactId>
            <version>2.2.2version>
        dependency>
        
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-lang3artifactId>
            <version>3.3.2version>
        dependency>
        
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-collections4artifactId>
            <version>4.0version>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.49version>
        dependency>
    dependencies>

2.定义几个注解

/**
 * 依赖注入注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
     
}
/**
 * 处理器注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
     
}
/**
 * 处理器方法注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
     
    /**
     * 请求路径
     * @return
     */
    String value() default "";

    /**
     * 请求方法
     * @return
     */
    RequestMethod method() default RequestMethod.GET;

    /**
     * 请求方法枚举
     */
    public enum RequestMethod{
     
        GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
    }
}
/**
 * 业务类注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
     
}

2.如何加载我们的配置文件呢?
使用spring 当然需要我们配置一些东西,比如数据库连接,先准备还一个配置文件

#数据源
handwritten.framework.jdbc.driver=com.mysql.jdbc.Driver
handwritten.framework.jdbc.url=jdbc:mysql://localhost:3306/mybooksdb?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
handwritten.framework.jdbc.username=root
handwritten.framework.jdbc.password=123456

#java源码路径
handwritten.framework.app.base_package=com.tyshawn
#jsp页面路径
handwritten.framework.app.jsp_path=/WEB-INF/view/
#静态资源路径
 handwritten.framework.app.asset_path=/asset/

定义一个常量接口,用来维护配置文件中的配置项,这样,当我们有多处应用到常量值时,一旦我们的常量名发生变化,我们只需要维护该常量接口中的常量名称即可

/**
 * 定义一个常量接口,用来维护配置文件中相关的配置项名称
 */
public interface ConfigConstant {
     
    //配置文件的名称
    String CONFIG_FILE="application.properties";

    //数据源
    String JDBC_DRIVER = "handwritten.framework.jdbc.driver";
    String JDBC_URL = "handwritten.framework.jdbc.url";
    String JDBC_USERNAME = "handwritten.framework.jdbc.username";
    String JDBC_PASSWORD = "handwritten.framework.jdbc.password";

    //java源码地址
    String APP_BASE_PACKAGE = "handwritten.framework.app.base_package";
    //jsp页面路径
    String APP_JSP_PATH = "handwritten.framework.app.jsp_path";
    //静态资源路径
    String APP_ASSET_PATH = "handwritten.framework.app.asset_path";
}

再准备一个工具类,该类就是用来加载配置文件,并且提供了一些获取配置值的方法

/**
 * @description: 工具类 用来读取属性的配置文件
 * @author: cgw
 * @date: 2020-12-22
 */
public class PropsUtil {
     
    private static final Logger LOGGER= LoggerFactory.getLogger(PropsUtil.class);
    /**
     * 加载属性文件
     */
    public static Properties loadProps(String fileName) {
     
        Properties props = null;
        InputStream is = null;
        try {
     
            is = ClassUtil.getClassLoader().getResourceAsStream(fileName);
            if (is == null) {
     
                throw new FileNotFoundException(fileName + " file is not found");
            }
            props = new Properties();
            props.load(is);
        } catch (IOException e) {
     
            LOGGER.error("load properties file failure", e);
        } finally {
     
            if (is != null) {
     
                try {
     
                    is.close();
                } catch (IOException e) {
     
                    LOGGER.error("close input stream failure", e);
                }
            }
        }
        return props;
    }

    /**
     * 获取 String 类型的属性值(默认值为空字符串)
     */
    public static String getString(Properties props, String key) {
     
        return getString(props, key, "");
    }

    /**
     * 获取 String 类型的属性值(可指定默认值)
     */
    public static String getString(Properties props, String key, String defaultValue) {
     
        String value = defaultValue;
        if (props.containsKey(key)) {
     
            value = props.getProperty(key);
        }
        return value;
    }

    /**
     * 获取 int 类型的属性值(默认值为 0)
     */
    public static int getInt(Properties props, String key) {
     
        return getInt(props, key, 0);
    }

    /**
     * 获取 int 类型的属性值(可指定默认值)
     */
    public static int getInt(Properties props, String key, int defaultValue) {
     
        int value = defaultValue;
        if (props.containsKey(key)) {
     
            value = Integer.parseInt(props.getProperty(key));
        }
        return value;
    }

    /**
     * 获取 boolean 类型属性(默认值为 false)
     */
    public static boolean getBoolean(Properties props, String key) {
     
        return getBoolean(props, key, false);
    }

    /**
     * 获取 boolean 类型属性(可指定默认值)
     */
    public static boolean getBoolean(Properties props, String key, boolean defaultValue) {
     
        boolean value = defaultValue;
        if (props.containsKey(key)) {
     
            value = Boolean.parseBoolean(props.getProperty(key));
        }
        return value;
    }
}

定义一个配置类,主要是用来加载用户自定义的配置

/**
 * @description: 通过该配置类来加载用户自定义的配置文件,用户没有定义的将使用默认配置
 * @author: cgw
 * @date: 2020-12-22
 */
public final class ConfigHelper {
     

    /**
     * 加载配置文件的属性
     */
    private static final Properties CONFIG_PROPS = PropsUtil.loadProps(ConfigConstant.CONFIG_FILE);

    /**
     * 获取 JDBC 驱动
     */
    public static String getJdbcDriver() {
     
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.JDBC_DRIVER);
    }

    /**
     * 获取 JDBC URL
     */
    public static String getJdbcUrl() {
     
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.JDBC_URL);
    }

    /**
     * 获取 JDBC 用户名
     */
    public static String getJdbcUsername() {
     
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.JDBC_USERNAME);
    }

    /**
     * 获取 JDBC 密码
     */
    public static String getJdbcPassword() {
     
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.JDBC_PASSWORD);
    }

    /**
     * 获取应用基础包名
     */
    public static String getAppBasePackage() {
     
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.APP_BASE_PACKAGE);
    }

    /**
     * 获取应用 JSP 路径
     */
    public static String getAppJspPath() {
     
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.APP_JSP_PATH, "/WEB-INF/view/");
    }

    /**
     * 获取应用静态资源路径
     */
    public static String getAppAssetPath() {
     
        return PropsUtil.getString(CONFIG_PROPS, ConfigConstant.APP_ASSET_PATH, "/asset/");
    }

    /**
     * 根据属性名获取 String 类型的属性值
     */
    public static String getString(String key) {
     
        return PropsUtil.getString(CONFIG_PROPS, key);
    }

    /**
     * 根据属性名获取 int 类型的属性值
     */
    public static int getInt(String key) {
     
        return PropsUtil.getInt(CONFIG_PROPS, key);
    }

    /**
     * 根据属性名获取 boolean 类型的属性值
     */
    public static boolean getBoolean(String key) {
     
        return PropsUtil.getBoolean(CONFIG_PROPS, key);
    }
}

3.通过以上步骤我们能获取到用户自定义的配置,在上面的基础上,spring是如何将bean加载到spring容器中的呢,对spring有过了解的人都知道,反射嘛,确实如此

先定义一个反射用到的工具类,主要是创建实例,执行方法和操作实例属性

/**
 * @description: 反射工具类,进行各种反射操作
 * @author: cgw
 * @date: 2020-12-23
 */
public final class ReflectionUtil {
     

    private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class);

    /**
     * 创建实例
     */
    public static Object newInstance(Class<?> cls) {
     
        Object instance;
        try {
     
            instance = cls.newInstance();
        } catch (Exception e) {
     
            LOGGER.error("new instance failure", e);
            throw new RuntimeException(e);
        }
        return instance;
    }

    /**
     * 创建实例(根据类名)
     */
    public static Object newInstance(String className) {
     
        Class<?> cls = ClassUtil.loadClass(className);
        return newInstance(cls);
    }

    /**
     * 调用方法
     */
    public static Object invokeMethod(Object obj, Method method, Object... args) {
     
        Object result;
        try {
     
            method.setAccessible(true);
            result = method.invoke(obj, args);
        } catch (Exception e) {
     
            LOGGER.error("invoke method failure", e);
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * 设置成员变量的值
     */
    public static void setField(Object obj, Field field, Object value) {
     
        try {
     
            field.setAccessible(true); //去除私有权限
            field.set(obj, value);
        } catch (Exception e) {
     
            LOGGER.error("set field failure", e);
            throw new RuntimeException(e);
        }
    }
}

继续定义一个工具类,主要作用就是提供一些Class初始化方法,便于ClassHelper类使用

/**
 * @description:
 *  1.第一步是加载用户定义的配置文件,
 *  2.该类实现第二步:将应用中所有的Class对象都存储到一个集合中
 * @author: cgw
 * @date: 2020-12-22
 */
public final class ClassUtil {
     

    private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class);

    /**
     * 获取类加载器
     */
    public static ClassLoader getClassLoader() {
     
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 加载类
     * @param className 类名
     * @param isInitialized 是否初始化
     * @return
     */
    public static Class<?> loadClass(String className, boolean isInitialized) {
     
        Class<?> cls;
        try {
     
            cls = Class.forName(className, isInitialized, getClassLoader());
        } catch (ClassNotFoundException e) {
     
            LOGGER.error("load class failure", e);
            throw new RuntimeException(e);
        }
        return cls;
    }

    /**
     * 加载类(默认将初始化类)
     */
    public static Class<?> loadClass(String className) {
     
        return loadClass(className, true);
    }

    /**
     * 获取指定包名下的所有类
     */
    public static Set<Class<?>> getClassSet(String packageName) {
     
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        try {
     
            Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
            while (urls.hasMoreElements()) {
     
                URL url = urls.nextElement();
                if (url != null) {
     
                    String protocol = url.getProtocol();
                    if (protocol.equals("file")) {
     
                        String packagePath = url.getPath().replaceAll("%20", " ");
                        addClass(classSet, packagePath, packageName);
                    } else if (protocol.equals("jar")) {
     
                        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                        if (jarURLConnection != null) {
     
                            JarFile jarFile = jarURLConnection.getJarFile();
                            if (jarFile != null) {
     
                                Enumeration<JarEntry> jarEntries = jarFile.entries();
                                while (jarEntries.hasMoreElements()) {
     
                                    JarEntry jarEntry = jarEntries.nextElement();
                                    String jarEntryName = jarEntry.getName();
                                    if (jarEntryName.endsWith(".class")) {
     
                                        String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                                        doAddClass(classSet, className);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
     
            LOGGER.error("get class set failure", e);
            throw new RuntimeException(e);
        }
        return classSet;
    }

    private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
     
        File[] files = new File(packagePath).listFiles(new FileFilter() {
     
            public boolean accept(File file) {
     
                return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
            }
        });
        for (File file : files) {
     
            String fileName = file.getName();
            if (file.isFile()) {
     
                String className = fileName.substring(0, fileName.lastIndexOf("."));
                if (StringUtils.isNotEmpty(packageName)) {
     
                    className = packageName + "." + className;
                }
                doAddClass(classSet, className);
            } else {
     
                String subPackagePath = fileName;
                if (StringUtils.isNotEmpty(packagePath)) {
     
                    subPackagePath = packagePath + "/" + subPackagePath;
                }
                String subPackageName = fileName;
                if (StringUtils.isNotEmpty(packageName)) {
     
                    subPackageName = packageName + "." + subPackageName;
                }
                addClass(classSet, subPackagePath, subPackageName);
            }
        }
    }

    private static void doAddClass(Set<Class<?>> classSet, String className) {
     
        Class<?> cls = loadClass(className, false);
        classSet.add(cls);
    }
}

借助ClassUtil来实现ClassHelper助手类,作用就是获取基本包名下所有的类,存储到CLASS_SET集合中

/**
 *  借助ClassUtil来实现ClassHelper助手类
 *     过程:在该类自身被加载的时候通过configHelper助手类获取应用的基本包名,再
 *     通过ClassUtil工具类来获取基本包名下所有的类,存储到CLASS_SET集合中
 * @author: cgw
 * @date: 2020-12-22
 */
public final class ClassHelper {
     

    /**
     * 定义类集合(存放基础包名下的所有类)
     */
    private static final Set<Class<?>> CLASS_SET;

    static {
     
        //获取基础包名
        String basePackage = ConfigHelper.getAppBasePackage();
        //获取基础包名下所有类
        CLASS_SET = ClassUtil.getClassSet(basePackage);
    }

    /**
     * 获取基础包名下的所有类
     */
    public static Set<Class<?>> getClassSet() {
     
        return CLASS_SET;
    }

    /**
     * 获取基础包名下所有 Service 类
     */
    public static Set<Class<?>> getServiceClassSet() {
     
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        for (Class<?> cls : CLASS_SET) {
     
            if (cls.isAnnotationPresent(Service.class)) {
     
                classSet.add(cls);
            }
        }
        return classSet;
    }

    /**
     * 获取基础包名下所有 Controller 类
     */
    public static Set<Class<?>> getControllerClassSet() {
     
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        for (Class<?> cls : CLASS_SET) {
     
            if (cls.isAnnotationPresent(Controller.class)) {
     
                classSet.add(cls);
            }
        }
        return classSet;
    }

    /**
     * 获取基础包名下所有 Bean 类(包括:Controller、Service)
     */
    public static Set<Class<?>> getBeanClassSet() {
     
        Set<Class<?>> beanClassSet = new HashSet<Class<?>>();
        beanClassSet.addAll(getServiceClassSet());
        beanClassSet.addAll(getControllerClassSet());
        return beanClassSet;
    }

    /**
     * 获取基础包名下某父类的所有子类 或某接口的所有实现类
     */
    public static Set<Class<?>> getClassSetBySuper(Class<?> superClass) {
     
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        for (Class<?> cls : CLASS_SET) {
     
            //isAssignableFrom() 指 superClass 和 cls 是否相同或 superClass 是否是 cls 的父类/接口
            if (superClass.isAssignableFrom(cls) && !superClass.equals(cls)) {
     
                classSet.add(cls);
            }
        }
        return classSet;
    }

    /**
     * 获取基础包名下带有某注解的所有类
     */
    public static Set<Class<?>> getClassSetByAnnotation(Class<? extends Annotation> annotationClass) {
     
        Set<Class<?>> classSet = new HashSet<Class<?>>();
        for (Class<?> cls : CLASS_SET) {
     
            if (cls.isAnnotationPresent(annotationClass)) {
     
                classSet.add(cls);
            }
        }
        return classSet;
    }
}

接下来,就是SpringIOC比较核心的事情
准备一个ioc的助手类,其中维护了一个Map集合,key是Class类型,value则是实例化好的bean,并提供了一些获取bean实例的方法

/**
 * @description: Bean容器组手类
 * @author: cgw
 * @date: 2020-12-23
 */
public final class BeanHelper {
     

    /**
     * BEAN_MAP相当于一个Spring容器, 拥有应用所有bean的实例
     */
    private static final Map<Class<?>, Object> BEAN_MAP = new HashMap<Class<?>, Object>();

    static {
     
        //获取应用中的所有bean
        Set<Class<?>> beanClassSet = ClassHelper.getBeanClassSet();
        //将bean实例化, 并放入bean容器中
        for (Class<?> beanClass : beanClassSet) {
     
            Object obj = ReflectionUtil.newInstance(beanClass);
            BEAN_MAP.put(beanClass, obj);
        }
    }

    /**
     * 获取 Bean 容器
     */
    public static Map<Class<?>, Object> getBeanMap() {
     
        return BEAN_MAP;
    }

    /**
     * 获取 Bean 实例
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(Class<T> cls) {
     
        if (!BEAN_MAP.containsKey(cls)) {
     
            throw new RuntimeException("can not get bean by class: " + cls);
        }
        return (T) BEAN_MAP.get(cls);
    }

    /**
     * 设置 Bean 实例
     */
    public static void setBean(Class<?> cls, Object obj) {
     
        BEAN_MAP.put(cls, obj);
    }
}

最终,ioc容器只需要遍历实例的属性来判断是否注入实例,当发现属性上有Autowired的注解,则会通过反射工具类的方法,将实例注入给属性,实现IOC的控制反转

/**
 * @description: TODO
 * @author: cgw
 * @date: 2020-12-23
 */
public final class IocHelper {
     

    /**
     * 遍历bean容器所有bean的属性, 为所有带@Autowired注解的属性注入实例
     */
    static {
     
        //遍历bean容器里的所有bean
        Map<Class<?>, Object> beanMap = BeanHelper.getBeanMap();
        if (MapUtils.isNotEmpty(beanMap)) {
     
            for (Map.Entry<Class<?>, Object> beanEntry : beanMap.entrySet()) {
     
                //bean的class类
                Class<?> beanClass = beanEntry.getKey();
                //bean的实例
                Object beanInstance = beanEntry.getValue();
                //暴力反射获取属性
                Field[] beanFields = beanClass.getDeclaredFields();
                //遍历bean的属性
                if (ArrayUtils.isNotEmpty(beanFields)) {
     
                    for (Field beanField : beanFields) {
     
                        //判断属性是否带Autowired注解
                        if (beanField.isAnnotationPresent(Autowired.class)) {
     
                            //属性类型
                            Class<?> beanFieldClass = beanField.getType();
                            //如果beanFieldClass是接口, 就获取接口对应的实现类
                            beanFieldClass = findImplementClass(beanFieldClass);
                            //获取Class类对应的实例
                            Object beanFieldInstance = beanMap.get(beanFieldClass);
                            if (beanFieldInstance != null) {
     
                                ReflectionUtil.setField(beanInstance, beanField, beanFieldInstance);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 获取接口对应的实现类
     */
    public static Class<?> findImplementClass(Class<?> interfaceClass) {
     
        Class<?> implementClass = interfaceClass;
        //接口对应的所有实现类
        Set<Class<?>> classSetBySuper = ClassHelper.getClassSetBySuper(interfaceClass);
        if (CollectionUtils.isNotEmpty(classSetBySuper)) {
     
            //获取第一个实现类
            implementClass = classSetBySuper.iterator().next();
        }
        return implementClass;
    }
}

总结一下:
Spring的IOC就是借助了反射,实现对对象实例的依赖注入,具体实现就是
1.首先会加载配置文件,通过配置类能获取到用户配置的值
2.首先spring会通过反射加载基础包下的所有的类,生成的Class储存在一个Set集合中,再维护了一个Map,通过变量set集合并通过反射来实例化bean,并储存在一个map集合中,该map的类型为key:class类型,value;实例化好的bean 最终ioc容器会遍历所有bean中的属性,判读是否加了注解,如果是则再通过反射将实例好后的bean注入给属性,实现ioc的功能

该篇是学习参考以下文章做的一些总结,不足之处,请多指教
[原文传送门]

你可能感兴趣的:(spring,java)