[适合初中级Java程序员修炼手册从0搭建整个Web项目](三)(下)

简介: 前言文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820…种一棵树最好的时间是十年前,其次是现在


扫描配置文件

doScanner()是递归方法,当它发现当前扫描的文件是目录时要发生递归,直到找到一个class文件,然后把它的全类名添加到集合中

/**
  * 扫描资源文件的递归方法
  */
private void doScanner(String scanPackage) {
    URL url = this.getClass().getClassLoader().getResource(scanPackage.replaceAll("\\.", "/"));
    File classPath = new File(url.getFile());
    for (File file : classPath.listFiles()) {
        if (file.isDirectory()) {
          //如果是目录则递归调用,直到找到class
            doScanner(scanPackage + "." + file.getName());
        } else {
            if (!file.getName().endsWith(".class")) {
                continue;
            }
            String className = (scanPackage + "." + file.getName().replace(".class", ""));
            //className保存到集合
            registyBeanClasses.add(className);
        }
    }
}
复制代码


封装成BeanDefinition

refresh()中接着填充下一步,将上一步扫描好的class集合封装进BeanDefinition

private void refresh() throws Exception {
    //1、定位,定位配置文件
    reader = new BeanDefinitionReader(this.configLocation);
    //2、加载配置文件,扫描相关的类,把它们封装成BeanDefinition
    List<BeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
    //3、注册,把配置信息放到容器里面
    //到这里为止,容器初始化完毕
    //4、把不是延时加载的类,提前初始化
}
复制代码


回到BeanDefinitionReader中填充loadBeanDefinitions()方法。逻辑是:扫描class集合,如果是被@Component注解的class则需要封装成BeanDefinition,表示着它将来可以被IOC进行管理。

/**
  * 把配置文件中扫描到的所有的配置信息转换为BeanDefinition对象
  */
public List<BeanDefinition> loadBeanDefinitions() {
    List<BeanDefinition> result = new ArrayList<>();
    try {
        for (String className : registyBeanClasses) {
            Class<?> beanClass = Class.forName(className);
            //如果是一个接口,是不能实例化的,不需要封装
            if (beanClass.isInterface()) {
                continue;
            }
            Annotation[] annotations = beanClass.getAnnotations();
            if (annotations.length == 0) {
                continue;
            }
            for (Annotation annotation : annotations) {
                Class<? extends Annotation> annotationType = annotation.annotationType();
                //只考虑被@Component注解的class
                if (annotationType.isAnnotationPresent(Component.class)) {
                    //beanName有三种情况:
                    //1、默认是类名首字母小写
                    //2、自定义名字(这里暂不考虑)
                    //3、接口注入
                    result.add(doCreateBeanDefinition(toLowerFirstCase(beanClass.getSimpleName()), beanClass.getName()));
                    Class<?>[] interfaces = beanClass.getInterfaces();
                    for (Class<?> i : interfaces) {
                        //接口和实现类之间的关系也需要封装
                        result.add(doCreateBeanDefinition(i.getName(), beanClass.getName()));
                    }
                    break;
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return result;
}
/**
 * 相关属性封装到BeanDefinition
 */
private BeanDefinition doCreateBeanDefinition(String factoryBeanName, String beanClassName) {
    BeanDefinition beanDefinition = new BeanDefinition();
    beanDefinition.setFactoryBeanName(factoryBeanName);
    beanDefinition.setBeanClassName(beanClassName);
    return beanDefinition;
}
/**
 * 将单词首字母变为小写
 */
private String toLowerFirstCase(String simpleName) {
    char [] chars = simpleName.toCharArray();
    chars[0] += 32;
    return String.valueOf(chars);
}
复制代码


注册到容器

将BeanDefinition保存为以factoryBeanName为Key的Map

//保存factoryBean和BeanDefinition的对应关系
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
private void refresh() throws Exception {
    //1、定位,定位配置文件
    reader = new BeanDefinitionReader(this.configLocation);
    //2、加载配置文件,扫描相关的类,把它们封装成BeanDefinition
    List<BeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
    //3、注册,把配置信息放到容器里面
    //到这里为止,容器初始化完毕
    doRegisterBeanDefinition(beanDefinitions);
    //4、把不是延时加载的类,提前初始化
}
private void doRegisterBeanDefinition(List<BeanDefinition> beanDefinitions) throws Exception {
    for (BeanDefinition beanDefinition : beanDefinitions) {
        if (beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
            throw new Exception("The \"" + beanDefinition.getFactoryBeanName() + "\" is exists!!");
        }
        beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
    }
}
复制代码


非懒加载的提前初始化

这是fresh()的最后一步,逻辑是遍历BeanDefinition集合,将非懒加载的Bean提前初始化。

public void refresh() throws Exception {
    //1、定位,定位配置文件
    reader = new BeanDefinitionReader(this.configLocation);
    //2、加载配置文件,扫描相关的类,把它们封装成BeanDefinition
    List<BeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
    //3、注册,把配置信息放到容器里面(伪IOC容器)
    //到这里为止,容器初始化完毕
    doRegisterBeanDefinition(beanDefinitions);
    //4、把不是延时加载的类,提前初始化
    doAutowired();
}
private void doAutowired() {
    for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionMap.entrySet()) {
        String beanName = beanDefinitionEntry.getKey();
        if (!beanDefinitionEntry.getValue().isLazyInit()) {
            try {
                getBean(beanName);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
复制代码


可见实例化的核心方法就是getBean(),它是BeanFactory中的接口方法,下面来具体实现它。


初始化核心方法getBean

核心逻辑也不难:

  • 如果已经实例化了,则直接获取实例化后的对象返回即可。如果没有实例化则走后面的逻辑
  • 拿到该Bean的BeanDefinition 信息,通过反射实例化
  • 将实例化后的对象封装到BeanWrapper中
  • 将封装好的BeanWrapper保存到IOC容器(实际就是一个Map)中
  • 依赖注入实例化的Bean
  • 返回最终实例
/**保存了真正实例化的对象*/
private Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>();
@Override
public Object getBean(String beanName) throws Exception {
    //如果是单例,那么在上一次调用getBean获取该bean时已经初始化过了,拿到不为空的实例直接返回即可
    Object instance = getSingleton(beanName);
    if (instance != null) {
        return instance;
    }
    BeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
    //1.调用反射初始化Bean
    instance = instantiateBean(beanName, beanDefinition);
    //2.把这个对象封装到BeanWrapper中
    BeanWrapper beanWrapper = new BeanWrapper(instance);
    //3.把BeanWrapper保存到IOC容器中去
    //注册一个类名(首字母小写,如helloService)
    this.factoryBeanInstanceCache.put(beanName, beanWrapper);
    //注册一个全类名(如com.lqb.HelloService)
    this.factoryBeanInstanceCache.put(beanDefinition.getBeanClassName(), beanWrapper);
    //4.注入
    populateBean(beanName, new BeanDefinition(), beanWrapper);
    return this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
}
private Object instantiateBean(String beanName, BeanDefinition beanDefinition) {
//1、拿到要实例化的对象的类名
String className = beanDefinition.getBeanClassName();
//2、反射实例化,得到一个对象
Object instance = null;
try {
    Class<?> clazz = Class.forName(className);
    instance = clazz.newInstance();
} catch (Exception e) {
    e.printStackTrace();
}
return instance;
}
复制代码


依赖注入

上一步中Bean只是实例化了,但是Bean中被@Autowired注解的变量还没有注入,如果这个时候去使用就会报空指针异常。下面是注入的逻辑:

  • 拿到Bean中的所有成员变量开始遍历
  • 过滤掉没有被@Autowired注解标注的变量
  • 拿到被注解变量的类名,并从IOC容器中找到该类的实例(上一步已经初始化放在容器了)
  • 将变量的实例通过反射赋值到变量中
private void populateBean(String beanName, BeanDefinition beanDefinition, BeanWrapper beanWrapper) {
    Class<?> clazz = beanWrapper.getWrappedClass();
    //获得所有的成员变量
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        //如果没有被Autowired注解的成员变量则直接跳过
        if (!field.isAnnotationPresent(Autowired.class)) {
            continue;
        }
        Autowired autowired = field.getAnnotation(Autowired.class);
        //拿到需要注入的类名
        String autowiredBeanName = autowired.value().trim();
        if ("".equals(autowiredBeanName)) {
            autowiredBeanName = field.getType().getName();
        }
        //强制访问该成员变量
        field.setAccessible(true);
        try {
            if (this.factoryBeanInstanceCache.get(autowiredBeanName) == null) {
                continue;
            }
            //将容器中的实例注入到成员变量中
            field.set(beanWrapper.getWrappedInstance(), this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
复制代码


改造

  • 第一个改造的点,当然是MVC的地方

GetRequestHandler->handle

网络异常,图片无法展示
|


什么意思呢?就是我要在Springmvc里面 拿到处理好的bean,那么我的controller 也是需要代理的,所以在这个地方加载了spring的ioc容器

这样就把srpingmvc 和spring合起来了


结尾


好了,今天我们把spring ioc的大致流程写了些,其实只是一个最简单的例子,有助于大家去理解spring,下次看看是补补我们的xml 还是把aop写写。

相关文章
|
4月前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
466 1
|
5月前
|
前端开发 Java API
2025 年 Java 全栈从环境搭建到项目上线实操全流程指南:Java 全栈最新实操指南(2025 版)
本指南涵盖2025年Java全栈开发核心技术,从JDK 21环境搭建、Spring Boot 3.3实战、React前端集成到Docker容器化部署,结合最新特性与实操流程,助力构建高效企业级应用。
1557 1
|
4月前
|
存储 前端开发 Java
【JAVA】Java 项目实战之 Java Web 在线商城项目开发实战指南
本文介绍基于Java Web的在线商城技术方案与实现,涵盖三层架构设计、MySQL数据库建模及核心功能开发。通过Spring MVC + MyBatis + Thymeleaf实现商品展示、购物车等模块,提供完整代码示例,助力掌握Java Web项目实战技能。(238字)
470 0
|
5月前
|
JavaScript Java 微服务
现代化 Java Web 在线商城项目技术方案与实战开发流程及核心功能实现详解
本项目基于Spring Boot 3与Vue 3构建现代化在线商城系统,采用微服务架构,整合Spring Cloud、Redis、MySQL等技术,涵盖用户认证、商品管理、购物车功能,并支持Docker容器化部署与Kubernetes编排。提供完整CI/CD流程,助力高效开发与扩展。
626 64
|
4月前
|
IDE 安全 Java
Lombok 在企业级 Java 项目中的隐性成本:便利背后的取舍之道
Lombok虽能简化Java代码,但其“魔法”特性易破坏封装、影响可维护性,隐藏调试难题,且与JPA等框架存在兼容风险。企业级项目应优先考虑IDE生成、Java Records或MapStruct等更透明、稳健的替代方案,平衡开发效率与系统长期稳定性。
206 1
|
4月前
|
存储 小程序 Java
热门小程序源码合集:微信抖音小程序源码支持PHP/Java/uni-app完整项目实践指南
小程序已成为企业获客与开发者创业的重要载体。本文详解PHP、Java、uni-app三大技术栈在电商、工具、服务类小程序中的源码应用,提供从开发到部署的全流程指南,并分享选型避坑与商业化落地策略,助力开发者高效构建稳定可扩展项目。
|
5月前
|
前端开发 Java 数据库
Java 项目实战从入门到精通 :Java Web 在线商城项目开发指南
本文介绍了一个基于Java Web的在线商城项目,涵盖技术方案与应用实例。项目采用Spring、Spring MVC和MyBatis框架,结合MySQL数据库,实现商品展示、购物车、用户注册登录等核心功能。通过Spring Boot快速搭建项目结构,使用JPA进行数据持久化,并通过Thymeleaf模板展示页面。项目结构清晰,适合Java Web初学者学习与拓展。
414 1
|
6月前
|
缓存 NoSQL Java
Java Web 从入门到精通之苍穹外卖项目实战技巧
本项目为JavaWeb综合实战案例——苍穹外卖系统,涵盖Spring Boot 3、Spring Cloud Alibaba、Vue 3等主流技术栈,涉及用户认证、订单处理、Redis缓存、分布式事务、系统监控及Docker部署等核心功能,助你掌握企业级项目开发全流程。
697 0
|
3月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
298 4
|
7月前
|
缓存 JavaScript 前端开发
鸿蒙5开发宝藏案例分享---Web开发优化案例分享
本文深入解读鸿蒙官方文档中的 `ArkWeb` 性能优化技巧,从预启动进程到预渲染,涵盖预下载、预连接、预取POST等八大优化策略。通过代码示例详解如何提升Web页面加载速度,助你打造流畅的HarmonyOS应用体验。内容实用,按需选用,让H5页面快到飞起!