前言
如果还不会 Spring源码编译
可去看看 Spring源码编译
面试官问我咋实现Spring框架IOC和DI好吧打趴下,深度解析手动实现Spring框架的IOC与DI功能
工程搭建
使用 Maven 创建普通 Web 工程:
修改 pom.xml 添加依赖内容如下图:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.28</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> </dependencies>
配置 Tomcat
添加 Tomcat 的 jar 包:
点击上图中的 Add Selected
即可完成添加,添加完成了之后你在创建的时候就会出现 Servlet
选项如下图所示:
创建一个 Servlet 测试一下:
/** * 控制器 * * @author yby6 * @date 2023/09/29 */ @WebServlet(name = "UserServlet", value = "/UserServlet") public class UserServlet extends HttpServlet { @Override public void service(HttpServletRequest req, HttpServletResponse resp) { System.out.println("test Servlet"); } }
启动 Tomcat 在浏览器中访问:http://127.0.0.1:8080/UserServlet 即可测试我的测试结果如下图所示:
好了到了这里咱们的工程就已经创建完毕了,回归到我们文章的主要内容。
整体思路
解析配置
将相关配置加载进内存当中,存入定义好的数据结构,要管理哪些目标对象,通过编程语言进行解析,配置可以使用 xml,properties 或注解的方式。
定位与注册对象
查看哪些类当中标记了注解,定位到了目标对象之后,将对象注册到容器当中管理起来。
注入对象
在用户需要用到对象时,从容器中精确的获取对象,返回给用户给对应的属性进行注入(赋值)
提供通用的工具类
通过封装一些通用的工具,能够方便框架或用户方便进行操作。
创建注解
分别创建如下几个注解:
Component.java
/** * @author yby6 * @program SpringPro * @date Created in 2023/9/30 030 9:07 * @description **/ // 作用在类上 @Target(ElementType.TYPE) // 注解的生命周期为RUNTIME,因为使用反射创建对象 @Retention(RetentionPolicy.RUNTIME) public @interface Component { }
Controller.java
/** * @author yby6 * @program SpringPro * @date Created in 2023/9/30 030 9:07 * @description **/ // 作用在类上 @Target(ElementType.TYPE) // 注解的生命周期为RUNTIME,因为使用反射创建对象 @Retention(RetentionPolicy.RUNTIME) public @interface Controller { }
Repository.java
/** * @author yby6 * @program SpringPro * @date Created in 2023/9/30 030 9:07 * @description **/ // 作用在类上 @Target(ElementType.TYPE) // 注解的生命周期为RUNTIME,因为使用反射创建对象 @Retention(RetentionPolicy.RUNTIME) public @interface Repository { }
Service.java
/** * @author yby6 * @program SpringPro * @date Created in 2023/9/30 030 9:07 * @description **/ // 作用在类上 @Target(ElementType.TYPE) // 注解的生命周期为RUNTIME,因为使用反射创建对象 @Retention(RetentionPolicy.RUNTIME) public @interface Service { }
获取指定包下所有的类
指定范围,获取范围内的所有类,遍历所有类,获取被注解标记的类并加载进容器里,使用 classLoad 获取资源路径,直接使用 top.it6666
包是无法定位到对应的文件夹所在的路径,必须拿到具体路径,才能获取到该路径下所有 .class
文件。
ClassLoad 的作用:根据一个指定的类的名称,找到或者生成其对应的字节码,加载 Java 应用所需的资源:图片文件、配置文件、文件目录。
获取 URL 中文件与目录
根据文件与目录提取所有的 .class 文件
最终 ClassUtil.java 的内容如下:
/** * @author yby6 * @program SpringPro * @date Created in 2023/9/30 030 9:24 * @description 获取指定包下所有的类 **/ @Slf4j @UtilityClass public class ClassUtil { public static final String FILE_PROTOCOL = "file"; public static final String CLASS_SUFFIX = ".class"; /** * 获取指定包下所有的类 * * @param packageName 包名 * @return {@link Set}<{@link Class}<{@link ?}>> */ @SuppressWarnings("all") public Set<Class<?>> getPackageClass(String packageName) { // 1.获取类加载器 ClassLoader classLoader = ClassUtil.getClassLoad(); // 2.通过类加载器获取到加载的资源 // 2.1把top.it6666转成top/it6666 // 2.2通过类加载器获取加载的资源 URL url = classLoader.getResource(packageName.replace(".", "/")); if (null == url) { log.error("{}:该包下没有任何内容", packageName); } Set<Class<?>> classSet = null; // 判断url是否为文件 if (Objects.equals(FILE_PROTOCOL, url.getProtocol())) { // 创建集合 classSet = new HashSet<>(); String path = url.getPath(); try { path = URLDecoder.decode(path, "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } // 获取资源的实际路径 File packageDir = new File(path); // 从路径当中提取Class文件,放到Set集合 ClassUtil.getDirClass(classSet, packageDir, packageName); } return classSet; } ClassLoader getClassLoad() { return Thread.currentThread().getContextClassLoader(); } /** * 根据目录提出所有的class文件 * * @param classSet Ioc容器 * @param packageDir 文件 * @param packageName 包名 */ private static void getDirClass(Set<Class<?>> classSet, File packageDir, String packageName) { // 如果不是一个目录直接结束 if (!packageDir.isDirectory()) { return; } // 如果是目录,对目录里面的内容进行过滤 File[] files = packageDir.listFiles(new FileFilter() { @Override public boolean accept(File file) { if (file.isDirectory()) { return true; } String absolutePath = file.getAbsolutePath(); // 判断absolutePath是不是以 .class 后缀结尾 if (absolutePath.endsWith(CLASS_SUFFIX)) { // 到了这里就代表是 .class 结尾的 addToClassSet(absolutePath); } return false; } private void addToClassSet(String absolutePath) { // absolutePath 有可能是:/D:/aa/aaa 或者是:D:\bb\bb 可以使用 File.separator 来统一代替 // 把文件目录转成包的形式 absolutePath = absolutePath.replace(File.separator, "."); String className = absolutePath.substring(absolutePath.indexOf(packageName)); className = className.substring(0, className.lastIndexOf(".")); try { Class<?> targetClass = Class.forName(className); classSet.add(targetClass); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }); if (null != files) { Arrays.stream(files).forEach(childFile -> ClassUtil.getDirClass(classSet, childFile, packageName)); } } }
创建 BeanContainer 容器
BeanContainer.java
/** * @author yby6 * @program SpringPro * @date Created in 2023/10/08 008 17:24 * @description **/ @NoArgsConstructor(access = AccessLevel.PRIVATE) @SuppressWarnings("unused") public class BeanContainer { public static BeanContainer getInstance() { return ContainerHolder.HOLDER.instance; } /** * 容器枚举 * * @author yby6 * @date 2023/10/08 */ private enum ContainerHolder { /** * 持有人 */ HOLDER; /** * 实例 */ private final BeanContainer instance; /** * 容器 */ ContainerHolder() { instance = new BeanContainer(); } } }