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

相关文章
|
17天前
|
Java
使用IDEA创建项目运行我的第一个JAVA文件输出Helloword
本文介绍了如何使用IDEA(IntelliJ IDEA)创建一个新的Java项目,并运行一个简单的Java程序输出"Hello Word"。文章详细展示了创建项目的步骤,包括选择JDK版本、设置项目名称和路径、创建包和类,以及编写和运行代码。最后,还展示了如何通过IDEA的运行功能来执行程序并查看输出结果。
41 4
使用IDEA创建项目运行我的第一个JAVA文件输出Helloword
|
3天前
|
缓存 Java Maven
java: 警告: 源发行版 11 需要目标发行版 11 无效的目标发行版: 11 jdk版本不符,项目jdk版本为其他版本
如何解决Java项目中因JDK版本不匹配导致的编译错误,包括修改`pom.xml`文件、调整项目结构、设置Maven和JDK版本,以及清理缓存和重启IDEA。
11 1
java: 警告: 源发行版 11 需要目标发行版 11 无效的目标发行版: 11 jdk版本不符,项目jdk版本为其他版本
|
1天前
|
Java 关系型数据库 MySQL
java控制Windows进程,服务管理器项目
本文介绍了如何使用Java的`Runtime`和`Process`类来控制Windows进程,包括执行命令、读取进程输出和错误流以及等待进程完成,并提供了一个简单的服务管理器项目示例。
12 1
|
2天前
|
前端开发 JavaScript API
惊呆了!学会AJAX与Fetch API,你的Python Web项目瞬间高大上!
在Web开发领域,AJAX与Fetch API是提升交互体验的关键技术。AJAX(Asynchronous JavaScript and XML)作为异步通信的先驱,通过XMLHttpRequest对象实现了局部页面更新,提升了应用流畅度。Fetch API则以更现代、简洁的方式处理HTTP请求,基于Promises提供了丰富的功能。当与Python Web框架(如Django、Flask)结合时,这两者能显著增强应用的响应速度和用户体验,使项目更加高效、高大上。
12 2
|
15天前
|
前端开发 Python
前后端分离的进化:Python Web项目中的WebSocket实时通信解决方案
在现代Web开发领域,前后端分离已成为一种主流架构模式,它促进了开发效率、提升了应用的可维护性和可扩展性。随着实时数据交互需求的日益增长,WebSocket作为一种在单个长连接上进行全双工通讯的协议,成为了实现前后端实时通信的理想选择。在Python Web项目中,结合Flask框架与Flask-SocketIO库,我们可以轻松实现WebSocket的实时通信功能。
32 2
|
17天前
|
算法 Java
Java项目不使用框架如何实现限流?
Java项目不使用框架如何实现限流?
23 2
|
2月前
|
Java Maven Android开发
解锁Web开发新技能:从零开始的Struts 2之旅——让你的Java编程之路更加宽广,首个应用实例带你飞!
【8月更文挑战第31天】对于初学者,掌握 Struts 2 框架不仅能提升 Web 开发能力,还能深入了解 MVC 架构。Struts 2 是一个基于 Servlet 的 Java 框架,提供表单验证、文件上传、国际化等功能,便于快速构建易维护的 Web 应用。本文通过示例演示如何从零开始搭建环境并创建一个简单的 Struts 2 项目,包括配置 `struts.xml`、编写 Action 类及视图文件,并配置 web.xml。通过这些步骤,你将学会基本的开发流程,为进一步学习高级功能打下基础。
41 0
|
2月前
|
开发者 Java 安全
Struts 2 实战秘籍:Action 驱动业务,配置文件成就高效开发!
【8月更文挑战第31天】Struts 2 框架作为 Apache 软件基金会的顶级项目,广泛应用于企业级 Web 应用开发。其核心组件 Action 类处理用户请求,而配置文件定义请求与 Action 类间的映射关系。掌握 Action 组件的最佳实践包括继承 `ActionSupport` 类、实现 `execute` 方法及使用类型安全的方法;配置文件的最佳实践则涉及组织 Action 到包中、定义全局结果及使用通配符映射。遵循这些最佳实践,可构建高效、可维护的 Web 应用程序。
32 0
|
2月前
|
Java 数据库连接 Spring
Struts 2 插件开发竟如魔法盛宴,为框架注入超能力,开启奇幻编程之旅!
【8月更文挑战第31天】在Web开发中,Struts 2插件开发允许我们在不改动框架核心代码的前提下,通过创建实现特定接口的Java类来扩展框架功能、调整其行为或促进与其他框架(如Spring、Hibernate)的集成,从而更好地满足特定业务需求。遵循良好的设计原则与实践,能够确保插件的高效稳定运行并提升整体项目的可维护性。具体步骤包括创建项目、定义插件类、实现初始化与销毁逻辑,并将插件部署至应用中。
46 0
|
2月前
|
前端开发 Java UED
告别页面刷新时代:Struts 2 Ajax技术揭秘,轻松实现动态加载,Web应用焕然一新!
【8月更文挑战第31天】在Web应用开发中,用户体验至关重要。为减少页面刷新带来的不适,Ajax技术应运而生。Struts 2作为流行的Java EE框架,通过内置的Ajax支持简化了无刷新页面动态加载的实现。本文通过对比传统请求响应模式,展示了Struts 2如何轻松实现Ajax功能,提升了用户体验和开发效率,并灵活地实现了数据交换。然而,使用Ajax时还需注意SEO和跨域请求等局限性。
36 0

热门文章

最新文章