写点什么

手写 Spring 框架之 IOC

用户头像
简爱W
关注
发布于: 2020 年 08 月 26 日
手写Spring框架之IOC

简介


本篇博客主要实现两个功能: Bean 容器 和 IOC.


Bean 容器


Bean 容器也就是 Spring 容器, 在学习完 Spring 之后, 如果要我们用一句话来形容 Spring, 我们经常会说: Spring 是一个容器, 管理着应用中所有 bean 的装配和生命周期. 从这句话中就可以看出 Spring 容器的重要性, Spring 容器其实是一个 Map 映射, 里面存储了应用中所有 bean 的实例, key 为该 bean 实例的 Class 对象. Spring 有两种容器, 分别是 BeanFactory 和 ApplicationContext, 二者的区别在于, BeanFactory 采用延迟加载策略, 在第一次调用 getBean()时才真正装配该对象. 而 ApplicationContext 会在应用启动时就把所有对象一次性全部装配好.


handwritten-mvc-framwork 框架的 bean 容器是一个 ApplicationContext 式的容器.


IOC


IOC 的实现思路如下:


  • 首先有一个配置文件定义了应用的基础包, 也就是 Java 源码路径.

  • 读取基础包名, 然后通过类加载器获取到应用中所有的 Class 对象, 存储到一个集合中.

  • 获取应用中所有 Bean (Controller 和 Service) 的 Class 对象, 通过反射创建实例, 然后存储到 Bean 容器中.

  • 遍历 Bean 容器中的所有 Bean, 为所有带 @Autowired 注解的属性注入实例.

  • IOC 操作要在应用启动时就完成, 所以必须写在静态代码块中.


handwritten-mvc-framwork 实现


定义注解


(1) 处理器注解

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Controller {}
复制代码

(2) 处理器方法注解

@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}
复制代码


(3) 依赖注入注解

@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Autowired {}
复制代码

(4) 业务类注解

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Service {}
复制代码

配置文件


handwritten-mvc-framwork 框架是不存在配置文件的, 配置文件是用户需要自定义一些配置项, 所以创建一个配置文件, 而框架需要做的就是读取用户自定义的配置文件, 如果用户没有配置, 就使用默认的配置. handwritten-mvc-example 实例中的一个配置文件如下:

#数据源handwritten.framework.jdbc.driver=com.mysql.jdbc.Driverhandwritten.framework.jdbc.url=jdbc:mysql://localhost:3306/tyshawn_testhandwritten.framework.jdbc.username=roothandwritten.framework.jdbc.password=123
#java源码路径handwritten.framework.app.base_package=com.tyshawn#jsp页面路径handwritten.framework.app.jsp_path=/WEB-INF/view/#静态资源路径handwritten.framework.app.asset_path=/asset/
复制代码


那 handwritten-mvc-framwork 框架如何来加载用户自定义的配置文件呢?


(1) ConfigConstant 常量接口


首先我们要定义一个名为 ConfigConstant 的常量接口, 让它来维护配置文件中相关的配置项名称, 代码如下:

public interface ConfigConstant {    //配置文件的名称    String CONFIG_FILE = "handwritten.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";}
复制代码


然后使用 PropsUtil 工具类来读取属性文件

public final 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; }}
复制代码

(3) ConfigHelper 助手类


最后借助 PropsUtil 工具类来实现 ConfigHelper 助手类, 框架通过 ConfigHelper 类就可以加载用户自定义的配置文件了, 从代码中可以看到, 部分配置项拥有默认值, 当用户没有自定义时将会使用默认配置.

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); }}
复制代码

Class 对象集合


在完成了第一步加载配置文件之后, 我们接下来的第二步就是将应用中所有的 Class 对象都存储到一个集合中.


(1) ClassUtil 工具类


ClassUtil 工具类可以通过加载全限定类名得到 Class 类, 以及获取指定包名下的所有 Class 类.

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); }}
复制代码

(2) ClassHelper 助手类


借助 ClassUtil 来实现 ClassHelper 助手类, 这个类的功能很重要, 大家需要仔细看一下. ClassHelper 助手类在自身被加载的时候通过 ConfigHelper 助手类获取应用的基础包名, 然后通过 ClassUtil 工具类来获取基础包名下所有类, 存储到 CLASS_SET 集合中. 除此之外, 其他的方法在后面的代码中会经常被使用到.

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; }}
复制代码

Bean 容器


在将应用中所有的 Class 对象都存储到 CLASS_SET 集合中之后, 我们就可以来构建 Bean 容器了.


(1) ReflectionUtil 工具类


我们需要一个反射工具类, 进行各种反射操作.

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); } }}
复制代码

(2) Bean 容器助手类


BeanHelper 在类加载时就会创建一个 Bean 容器 BEANMAP, 然后获取到应用中所有 bean 的 Class 对象, 再通过反射创建 bean 实例, 储存到 BEANMAP 中.

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 功能


最后就是实现 IOC 了, 我们需要做的就是遍历 Bean 容器中的所有 bean, 为所有带 @Autowired 注解的属性注入实例. 这个实例从 Bean 容器中获取.

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; }}
复制代码

以上就是 Bean 容器和 IOC 功能的全部内容, 当应用启动后, 就会生成 Bean 容器, 并实现 IOC 功能, 所有那些加了 @Autowired 注解的属性, 别看他们在代码中只是一个声明, 其实在应用启动后他们都有实例啦!


用户头像

简爱W

关注

还未添加个人签名 2020.07.22 加入

还未添加个人简介

评论

发布
暂无评论
手写Spring框架之IOC