[适合初中级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写写。

相关文章
|
6天前
|
人工智能 Java 程序员
【AI程序员】通义灵码 AI 程序员全面上线JAVA使用体验
通过 AI 程序编写一个JAVA后台项目登陆页面
191 17
|
8天前
|
网络协议 Java Shell
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
40 7
|
1月前
|
Kubernetes Java 持续交付
小团队 CI/CD 实践:无需运维,Java Web应用的自动化部署
本文介绍如何使用GitHub Actions和阿里云Kubernetes(ACK)实现Java Web应用的自动化部署。通过CI/CD流程,开发人员无需手动处理复杂的运维任务,从而提高效率并减少错误。文中详细讲解了Docker与Kubernetes的概念,并演示了从创建Kubernetes集群、配置容器镜像服务到设置GitHub仓库Secrets及编写GitHub Actions工作流的具体步骤。最终实现了代码提交后自动构建、推送镜像并部署到Kubernetes集群的功能。整个过程不仅简化了部署流程,还确保了应用在不同环境中的稳定运行。
75 9
|
2月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
103 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
3月前
|
SQL 存储 Java
面向 Java 程序员的 SQLite 替代品
SQLite 是轻量级数据库,适用于小微型应用,但其对外部数据源支持较弱、无存储过程等问题影响了开发效率。esProc SPL 是一个纯 Java 开发的免费开源工具,支持标准 JDBC 接口,提供丰富的数据源访问、强大的流程控制和高效的数据处理能力,尤其适合 Java 和安卓开发。SPL 代码简洁易懂,支持热切换,可大幅提高开发效率。
|
3月前
|
Java Maven Spring
Java Web 应用中,资源文件的位置和加载方式
在Java Web应用中,资源文件如配置文件、静态文件等通常放置在特定目录下,如WEB-INF或classes。通过类加载器或Servlet上下文路径可实现资源的加载与访问。正确管理资源位置与加载方式对应用的稳定性和可维护性至关重要。
87 7
|
3月前
|
Java 持续交付 项目管理
使用Maven进行项目管理:提高Java Web开发的效率
Maven 是一款强大的项目管理和构建自动化工具,广泛应用于Java社区。它通过依赖管理、构建生命周期管理、插件机制和多模块项目支持等功能,简化了项目的构建过程,提高了开发效率。本文将介绍Maven的核心功能及其在Java Web开发中的应用。
90 0
|
3月前
|
存储 安全 搜索推荐
理解Session和Cookie:Java Web开发中的用户状态管理
理解Session和Cookie:Java Web开发中的用户状态管理
110 4
|
2月前
|
前端开发 安全 JavaScript
2025年,Web3开发学习路线全指南
本文提供了一条针对Dapp应用开发的学习路线,涵盖了Web3领域的重要技术栈,如区块链基础、以太坊技术、Solidity编程、智能合约开发及安全、web3.js和ethers.js库的使用、Truffle框架等。文章首先分析了国内区块链企业的技术需求,随后详细介绍了每个技术点的学习资源和方法,旨在帮助初学者系统地掌握Dapp开发所需的知识和技能。
2025年,Web3开发学习路线全指南
|
3月前
|
存储 前端开发 JavaScript
如何在项目中高效地进行 Web 组件化开发
高效地进行 Web 组件化开发需要从多个方面入手,通过明确目标、合理规划、规范开发、加强测试等一系列措施,实现组件的高效管理和利用,从而提高项目的整体开发效率和质量,为用户提供更好的体验。
55 7

热门文章

最新文章

  • 1
    打造高效的Web Scraper:Python与Selenium的完美结合
    13
  • 2
    Burp Suite Professional 2025.2 (macOS, Linux, Windows) - Web 应用安全、测试和扫描
    26
  • 3
    AppSpider Pro 7.5.015 for Windows - Web 应用程序安全测试
    20
  • 4
    【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
    54
  • 5
    部署使用 CHAT-NEXT-WEB 基于 Deepseek
    342
  • 6
    【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
    26
  • 7
    java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
    40
  • 8
    零基础构建开源项目OpenIM桌面应用和pc web- Electron篇
    28
  • 9
    【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
    22
  • 10
    FastAPI与Selenium:打造高效的Web数据抓取服务 —— 采集Pixabay中的图片及相关信息
    55