手写 Web 框架之实现 IOC/DI

本贴最后更新于 1403 天前,其中的信息可能已经时移世异

介绍

尝试写一个自己的 Web 框架,先模仿,再探索走出自己的道路,最终超越。框架用的多了,但是写框架是第一次,而且模仿的还是 Java 生态中的基石——Spring。先了解其思路,一步一步去实现,或许性能不如正主,但是可以一点点靠近。学习然后模仿是通往大牛道路的第一步。

一.注解

  1. Autowired 注解是自动注入,作用是将实例注入到带有 @Autowired 注解的变量中。
  2. Component 注解是组件,作用是框架会扫描所有带 @Component 的 class 并进行实例化。
  3. ComponentSacn 注解是扫描路径,作用是设置框架要扫描的包路径。
  4. Scope 注解是单例/原型标识,作用是不加默认是单例 Bean,加了 @Scope("prototype")后将是原型 Bean(多例模式)。

1.1 Autowired

package xyz.hcworld.jubilant.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @ClassName: Autowired * @Author: 张红尘 * @Date: 2021-06-15 * @Version: 1.0 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) @Documented public @interface Autowired { /** * @return 组件名 */ String value() default ""; }

1.2 Component

package xyz.hcworld.jubilant.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 注册Bean组件 * * @ClassName: Component * @Author: 张红尘 * @Date: 2021-06-13 * @Version: 1.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented public @interface Component { /** * @return 组件名 */ String value() default ""; }

1.3 ComponentSacn

package xyz.hcworld.jubilant.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 扫描配置文件注解 * * @ClassName: ComponentSacn * @Author: 张红尘 * @Date: 2021-06-13 * @Version: 1.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented public @interface ComponentScan { /** * @return 扫描路径 */ String value() default ""; }

1.4 Scope

package xyz.hcworld.jubilant.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 单例Bean/原型Bean标识 * @ClassName: Scope * @Author: 张红尘 * @Date: 2021-06-15 * @Version: 1.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented public @interface Scope { String value(); }

二、Bean 操作

  1. BeanDefinition 类是建模对象,通过建模对象创建实例。
  2. BeanNameAware 接口是 bean 名的 Aware 回调接口,继承接口可以获取 bean 的 benaName。
  3. BeanPostProcessor 接口是初始化前后的增强接口,继承接口可以对 bean 进行动态增强,可以使用 JDK 增强或者 CGLib 增强。(可以通过这个接口实现 AOP)。
  4. InitializingBean 接口是应用级的初始化接口,继承该接口后框架实例化 Bean 后会对 Bean 进行初始化操作。

2.1 BeanDefinition

package xyz.hcworld.jubilant.beans; /** * bean的配置信息 * @ClassName: BeanDefinition * @Author: 张红尘 * @Date: 2021-06-15 * @Version: 1.0 */ public class BeanDefinition { /** * 类的类型 */ private Class<?> clazz; /** * 作用域 是否是懒加载 */ private String scope; public Class<?> getClazz() { return clazz; } public void setClazz(Class<?> clazz) { this.clazz = clazz; } public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } }

2.2 BeanNameAware

package xyz.hcworld.jubilant.beans; /** * bean名的Aware回调接口 * @ClassName: BeanNameAware * @Author: 张红尘 * @Date: 2021-06-16 * @Version: 1.0 */ public interface BeanNameAware { /** * 名字 * @param name */ void setBeanName(String name); }

2.3 BeanPostProcessor

/** * 后置处理器(bean初始化前后的增强处理) * * @ClassName: BeanPostProcessor * @Author: 张红尘 * @Date: 2021-06-16 * @Version: 1.0 */ public interface BeanPostProcessor { /** * bean初始化前调用 * * @param bean bean本身 * @param beanName bean的名字 * @return */ default Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; } /** * bean初始化后调用 * * @param bean bean本身 * @param beanName bean的名字 * @return */ default Object postProcessAfterInitialization(Object bean, String beanName) { return bean; } }

2.4 InitializingBean

package xyz.hcworld.jubilant.beans; /** * 初始化机制 * @ClassName: Initializingbean * @Author: 张红尘 * @Date: 2021-06-16 * @Version: 1.0 */ public interface InitializingBean { /** * 初始化 * @throws Exception */ void afterPropertiesSet() throws Exception; }

三.容器类

  1. Search 类是搜索 class 的类,分别对 jar 与非 jar 两种情况的 class 进行扫描。
  2. JubilantApplicationContext 是核心容器类,ioc 是整个流程都在当前容器进行。流程如下:获取所有 class 的路径-> 扫描所有带 @Component 的 class-> 生成所有带 @Component 的 class 的 BeanDefinition 对象--> 存入 BeanDefinitionMap--> 遍历 BeanDefinitionMap 生成所有单例 Bean 存入单例池。

3.1 扫描 class 模块

package xyz.hcworld.jubilant.core; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * @ClassName: Search * @Author: 张红尘 * @Date: 2021-06-18 * @Version: 1.0 */ public class Search { /** * 获取某包下(包括该包的所有子包)所有类 * * @param packageName 包名 * @return 类的完整名称 */ List<String> getClassName(String packageName) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); URL url = loader.getResource(packageName.replace(".", "/")); if (url == null) { return new ArrayList<>(); } return "file".equals(url.getProtocol())? getClassNameByFile(url.getPath(), null, packageName.replace(".", System.getProperty("file.separator"))) :getClassNameByJar(url.getPath()); } /** * 从项目文件获取某包下所有类 * * @param filePath 文件路径 * @param className 类名集合 * @return 类的完整名称 */ private static List<String> getClassNameByFile(String filePath, List<String> className, String packageName) { List<String> myClassName = new ArrayList<>(); File file = new File(filePath); File[] childFiles = file.listFiles(); if (childFiles == null) { return myClassName; } for (File childFile : childFiles) { if (childFile.isDirectory()) { myClassName.addAll(getClassNameByFile(childFile.getPath(), myClassName, packageName)); continue; } String childFilePath = childFile.getPath(); if (childFilePath.endsWith(".class")) { //截取路径 childFilePath = childFilePath.substring(childFilePath.indexOf(packageName), childFilePath.lastIndexOf(".")); //将反斜杠转成. myClassName.add(childFilePath.replace(System.getProperty("file.separator"), ".")); } } return myClassName; } /** * 从jar获取某包下所有类 * * @param jarPath jar文件路径 * @return 类的完整名称 */ private static List<String> getClassNameByJar(String jarPath) { List<String> myClassName = new ArrayList<>(); try (JarFile jarFile = new JarFile(System.getProperty("user.dir") + System.getProperty("file.separator") + System.getProperty("java.class.path"))) { Enumeration<JarEntry> entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry jarEntry = entries.nextElement(); String entryName = jarEntry.getName(); if (entryName.startsWith(jarPath) && entryName.endsWith(".class")) { entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf(".class")); myClassName.add(entryName); } } } catch (IOException e) { e.printStackTrace(); } return myClassName; } }

3.2 容器化

package xyz.hcworld.jubilant.core; import xyz.hcworld.jubilant.beans.BeanDefinition; import xyz.hcworld.jubilant.beans.BeanNameAware; import xyz.hcworld.jubilant.beans.BeanPostProcessor; import xyz.hcworld.jubilant.beans.InitializingBean; import xyz.hcworld.jubilant.annotation.Autowired; import xyz.hcworld.jubilant.annotation.Component; import xyz.hcworld.jubilant.annotation.ComponentScan; import xyz.hcworld.jubilant.annotation.Scope; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * 容器类 * * @ClassName: WinterApplicationContext * @Author: 张红尘 * @Date: 2021-06-13 * @Version: 1.0 */ public class JubilantApplicationContext { /** * 配置文件 */ private Class configClass; /** * 单例池,保存单例对象 */ private final ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(); /** * 所有Bean的配置数据的配置池 */ private final ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); /** * 初始化bean的类 */ private final List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>(); /** * 构造方法 * * @param configClass 配置类 */ public JubilantApplicationContext(Class<?> configClass) { this.configClass = configClass; // 判断是否有ComponentScan注解,如果没有就结束不往下走 if (!configClass.isAnnotationPresent(ComponentScan.class)) { return; } // 解析配置类 // 对ComponentScan注解解析 -->扫描路径 -->扫描-->生成BeanDefinition对象-->存入BeanDefinitionMap scan(configClass); // Aspect类先实例化 for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) { if (!entry.getValue().getClazz().isAnnotationPresent(Aspect.class)) { continue; } Object bean = createBean(entry.getKey(), entry.getValue()); singletonObjects.put(entry.getKey(), bean); } for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) { // 创建所有的单例bean,已经实例化的就不需要实例化了 if ("singleton".equals(entry.getValue().getScope()) && !singletonObjects.containsKey(entry.getKey())) { Object bean = createBean(entry.getKey(), entry.getValue()); singletonObjects.put(entry.getKey(), bean); } } } /** * 扫描 * * @param configClass */ private void scan(Class<?> configClass) { ComponentScan componentScanAnnotation = configClass.getDeclaredAnnotation(ComponentScan.class); // 扫描路径(不填路径默认获取Application文件所在的包) xyz.hcworld String path = componentScanAnnotation.value().isEmpty() ? configClass.getPackage().getName() : componentScanAnnotation.value(); Search search = new Search(); //获取文件夹或者jar下的所有类名 List<String> classNames = search.getClassName(path); if (classNames == null) { return; } ClassLoader classLoader = JubilantApplicationContext.class.getClassLoader(); try { for (String className : classNames) { Class<?> clazz = classLoader.loadClass(className); // 如果没有Component代表这不是一个bean结束本次循环 if (!clazz.isAnnotationPresent(Component.class)) { continue; } // 创建bean对象 // 解析类,判断当前Bean是单例还是原型(prototype)Bean 生成BeanDefinition对象 // 统一处理方式:BeanDefinition Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class); // 获取Bean的名字 String beanName = componentAnnotation.value(); //如果没有自定义beanName则以类名(首字母小写)为beanName if (beanName.isEmpty()) { String[] name = className.split("\\."); StringBuilder nameBuffer = new StringBuilder(name[name.length - 1]); beanName = nameBuffer.replace(0, 1, Character.toString(nameBuffer.charAt(0)).toLowerCase()).toString(); } // bean的配置信息 BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setClazz(clazz); // 判断Scope是否存在,存在则是自定义配置,不存在则为单例 beanDefinition.setScope( clazz.isAnnotationPresent(Scope.class) ? clazz.getDeclaredAnnotation(Scope.class).value() : "singleton"); // 存进配置池 beanDefinitionMap.put(beanName, beanDefinition); // 将实现BeanPostProcessor(初始化bean)接口的类存到初始化配置池中 if (BeanPostProcessor.class.isAssignableFrom(clazz)) { BeanPostProcessor beanPostProcessor = (BeanPostProcessor) createBean(beanName, beanDefinitionMap.get(beanName)); beanPostProcessorList.add(beanPostProcessor); } } } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 根据配置创建出一个bean对象(反射) * * @param beanDefinition 自定义配置 * @return bean对象 */ public Object createBean(String beanName, BeanDefinition beanDefinition) { Class<?> clazz = beanDefinition.getClazz(); try { Object instance = clazz.getDeclaredConstructor().newInstance(); // 依赖注入 for (Field declaredField : clazz.getDeclaredFields()) { // 没有Autowired就不注入 if (!declaredField.isAnnotationPresent(Autowired.class)) { continue; } String[] name = declaredField.getType().getName().split("\\."); StringBuilder nameBuffer = new StringBuilder(name[name.length - 1]); nameBuffer.replace(0, 1, Character.toString(nameBuffer.charAt(0)).toLowerCase()); // 通过反射注入属性 Object bean = getBean(nameBuffer.toString()); if (bean == null) { throw new NullPointerException(); } // 当变量为private时需要忽略访问修饰符的检查 declaredField.setAccessible(true); declaredField.set(instance, bean); } // Aware回调 if (instance instanceof BeanNameAware) { ((BeanNameAware) instance).setBeanName(beanName); } //初始化前的增强 for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) { instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName); } // 初始化 if (instance instanceof InitializingBean) { ((InitializingBean) instance).afterPropertiesSet(); } // BeanPostProcessor 外部扩展机制(Bean的前后置处理) //初始化后的增强 for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) { instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName); } return instance; } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 获取Bean对象 * * @param beanName bean名字 * @return bean对象 */ public Object getBean(String beanName) { // 判断bean是否存在,不存在抛出npe异常 if (!beanDefinitionMap.containsKey(beanName)) { throw new NullPointerException(); } BeanDefinition beanDefinition = beanDefinitionMap.get(beanName); // 判断是否是单例,是返回单例对象,不是创建原型对象 return "singleton".equals(beanDefinition.getScope()) ? singletonObjects.get(beanName) : createBean(beanName, beanDefinition); } /** * 获取文件制定包下的所有class文件(不管有多少层) * * @param file 目录 * @param filePaths 临时存储 * @return 所有class文件的绝对路径 */ private Set<File> getFilePath(File file, Set<File> filePaths) { File[] files = file.listFiles(); if (files == null || files.length == 0) { return new HashSet<>(); } for (File file1 : files) { if (file1.isDirectory()) { //递归调用 getFilePath(file1, filePaths); continue; } //保存文件路径到集合中 filePaths.add(file1); } return filePaths; } }

四、结果

ioc.png

未来发展

展望

  • 第一步,通过模仿 Spring 实现 IOC
  • 第二步,通过模仿 Spring 实现 AOP
  • 第三步,实现 MVC 框架

说明

ioc 的代码是跟着 B 站的某教程敲的。但是修改了许多不合理的部分,如扫描 class 部分,原教程打包后无法运行,因为获取不了绝对路径。修改了既能打包 jar 后运行又能在 ide 中运行。以及原本丧心病狂的 if-else-for 嵌套改成了防御性的 if 判断,去除了 else。
先模仿在摸索自己的道路。doge

不足

笔者水平有限也非大厂的大牛,如有不对之处请指正。以及部分硬编码问题纯粹是笔者懒,还没来得及改,自行修改即可。

地址

GitHub:https://github.com/z875479694h/jubilant

Gitee:https://gitee.com/hcworld/jubilant

  • IoC
    19 引用 • 29 回帖
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3201 引用 • 8216 回帖
2 操作
z875479694h 在 2021-07-11 12:47:51 更新了该帖
z875479694h 在 2021-07-11 12:27:02 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...