手动开发-简单的Spring基于注解配置的程序--源码解析

news2025/1/18 6:52:09

文章目录

    • @设计注解@
    • $设计容器 $
    • #完整代码#

在前文中 《手动开发-简单的Spring基于XML配置的程序–源码解析》,我们是从XML配置文件中去读取bean对象信息,再在自己设计的容器中进行初始化,属性注入,最后通过getBean()方法进行返回。这篇文章,我们将基于注解的视角,实现简单的Spring容器。在这里我们还将做一些改动,前文我们是通过xml文件名进行传值容器初始化,这里,我们通过传值接口类型进行初始化容器。所以本文有下面两个特色:

  • 基于注解实现Spring容器模拟
  • 通过接口类型初始化ioc容器

@设计注解@

Spring中有很多注解,在这里我们将自己设计一个注解进行使用。那么怎么设计注解呢?Spring的注解设计是基于 元注解实现的。元注解是Java基础,元注解如下:

  • @Target

    用于指定注解的使用范围

    • ElementType.TYPE:类、接口、注解、枚举
    • ElementType.FIELD:字段、枚举常量
    • ElementType.METHOD:方法
    • ElementType.PARAMETER:形式参数
    • ElementType.CONSTRUCTOR:构造方法
    • ElementType.LOCAL_VARIABLE:局部变量
    • ElementType.ANNOTATION_TYPE:注解
    • ElementType.PACKAGE:包
    • ElementType.TYPE_PARAMETER:类型参数
    • ElementType.TYPE_USE:类型使用
  • @Retention

    用于指定注解的保留策略

    • RetentionPolicy.SOURCE:注解只保留在源码中,在编译时会被编译器丢弃
    • RetentionPolicy.CLASS:(默认的保留策略) 注解会被保留在Class文件中,但不会被加载到虚拟机中,运行时无法获得
    • RetentionPolicy.RUNTIME:注解会被保留在Class文件中,且会被加载到虚拟机中,可以在运行时获得
  • @Documented

    • 用于将注解包含在javadoc中
    • 默认情况下,javadoc是不包括注解的,但如果使用了@Documented注解,则相关注解类型信息会被包含在生成的文档中
  • @Inherited

    用于指明父类注解会被子类继承得到

  • @Repeatable

    用于声明标记的注解为可重复类型注解,可以在同一个地方多次使用

这些元注解就是最基本的部件,我们设计注解需要用到它们。现在我们就设计一个自己的ComponentScan注解:

/**
 * @author linghu
 * @date 2023/8/30 13:56
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
    String value();

}

@Retention(RetentionPolicy.RUNTIME)表明这个注解在运行时会生效;@Target(ElementType.TYPE)表明注解可以修饰的类型,我们进入这个Type的源码,我们通过注释得知,TYPE包含了 Class, interface (including annotation type), or enum declaration,也就是可以是类,接口…:

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

这个时候我们为了验证我们设计的新注解ComponentScan,我们新建一个LingHuSpringConfig配置类,其实这个配置类不会具体实现什么,就是在类名上放一个注解ComponentScan,然后设置一个value值,如下:

/**
 * @author linghu
 * @date 2023/8/30 14:09
 * 这个配置文件作用类似于beans.xml文件,用于对spring容器指定配置信息
 */
@ComponentScan(value = "com.linghu.spring.component")
public class LingHuSpringConfig {

}

我们设置这个配置类的目的是:我们在初始化容器的时候,直接传递 LingHuSpringConfig.class接口就行了,通过接口类型初始化ioc容器,容器根据我们设计的注解去扫描这个全类路径com.linghu.spring.component

$设计容器 $

其实这个容器的设计和《手动开发-简单的Spring基于XML配置的程序–源码解析》讲的差不多,都需要:

  • 一个 ConcurrentHashMap作为容器
  • 一个构造器,对容器进行初始化。
  • 提供一个 getBean方法,返回我们 的ioc容器。

这里面大部分工作是在构造器里完成的,完成的工作如下:

  • 找到 @ComponentScan配置类,并读取value值,得到类路径。
  • 通过上一步的类路径,我们需要到对应的 target目录的路径下去索引所有文件,其实就是那些.class文件,我们对这些文件进行过滤,过滤的过程中判断它们有没有加注解,如果加了就把这些文件的类路径放到ioc容器中保存下来。
  • 在对文件进行检索过滤的时候,我们需要把保存在 component文件下的.class文件的名字提取出来,然后保存这些名字到容器中。
  • 获取完整的类路径,判断这些类有没有注解:@compoment,@controller,@Service…。是不是需要注入容器
  • 获取Component注解的value值,这个值作为bean对象的id名,存到ioc容器中

最后我们实现了,我们通过自己定义的注解,将被注解的类的类路径扫描并加入到了我们自己创建的容器ioc中,最后我们通过我们自己设计的ioc容器得到了我们需要的对象。ioc怎么帮我们创建的对象?通过反射创建的,反射所需要的类路径是我们在注解上读取过来的。

img

#完整代码#

LingSpringApplicationContext.java:

package com.linghu.spring.annotation;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author linghu
 * @date 2023/8/30 14:13
 * 这个类充当spring原生的容器ApplicationContext
 */
public class LingSpringApplicationContext {
    private Class configClass;

    //ioc里存放的是通过反射创建的对象(基于注解形式)
    private final ConcurrentHashMap<String,Object> ioc=
            new ConcurrentHashMap<>();


    public LingSpringApplicationContext(Class configClass) {
        this.configClass = configClass;
//        System.out.println("this.configClass="+this.configClass);

        //获取到配置类的@ComponentScan(value = "com.linghu.spring.component")
        ComponentScan componentScan = 
                (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //取出注解的value值:com.linghu.spring.component。得到类路径,要扫描的包
        String path = componentScan.value();
//        System.out.println("value="+value);

        //得到要扫描包下的资源(.class文件)
        //1、得到类的加载器
        ClassLoader classLoader =
                LingSpringApplicationContext.class.getClassLoader();
        path = path.replace(".", "/");

        URL resource = classLoader.getResource(path);
//        System.out.println("resource="+resource);

        //将要加载的资源(.class)路径下的文件进行遍历=》io
        File file = new File(resource.getFile());
        if (file.isDirectory()){
            File[] files = file.listFiles();
            for (File f :files) {
                //获取"com.linghu.spring.component"下的所有class文件
                System.out.println("===========");
                //D:\Java\JavaProjects\spring\target\classes\com\linghu\spring\component\UserDAO.class
                System.out.println(f.getAbsolutePath());
                String fileAbsolutePath = f.getAbsolutePath();

                //只处理.class文件
                if (fileAbsolutePath.endsWith(".class")){

                //1、获取到类名=》字符串截取
                String className =
                        fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
//                System.out.println("className="+className);

                //2、获取类的完整的路径
                String classFullName = path.replace("/", ".") + "." + className;
                System.out.println("classFullName="+classFullName);

                //3、判断该类是不是需要注入容器,就看该类是不是有注解@compoment,@controller,@Service...
                    try {
                        //得到指定类的类对象,相当于Class.forName("com.xxx")
                        Class<?> aClass = classLoader.loadClass(classFullName);
                        if (aClass.isAnnotationPresent(Component.class)||
                                aClass.isAnnotationPresent(Service.class)||
                                aClass.isAnnotationPresent(Repository.class)||
                                        aClass.isAnnotationPresent(Controller.class)){

                            //演示一个component注解指定value,分配id
                            if (aClass.isAnnotationPresent(Component.class)){
                                Component component = aClass.getDeclaredAnnotation(Component.class);
                                String id = component.value();
                                if (!"".endsWith(id)){
                                    className=id;//用户自定义的bean id 替换掉类名
                                }
                            }


                            //这时就可以反射对象,放入到ioc容器中了
                            Class<?> clazz = Class.forName(classFullName);
                            Object instance = clazz.newInstance();//反射完成
                            //放入到容器中,将类的首字母变成小写,这里用了Stringutils
                            ioc.put(StringUtils.uncapitalize(className),instance);

                        }

                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    } catch (InstantiationException e) {
                        throw new RuntimeException(e);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }


        }
    }
    }

    //返回容器中的对象
    public Object getBean(String name){
        return ioc.get(name);
    }
}

Gitee:《实现Spring容器机制》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/997832.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Service Mesh目的:是解决系统架构微服务化后的服务间通信和治理问题。

参考链接&#xff1a;https://www.zhihu.com/tardis/bd/art/397945267?source_id1001 1、Service Mesh 是什么 Service Mesh的定义&#xff1a; 服务网格是一个基础设施层&#xff0c;用于处理服务间通信。云原生应用有着复杂的服务拓扑&#xff0c;服务网格保证请求在这些…

Docker:01 OverView

Docker&#xff1a;01 OverView 基本介绍 Docker是一个用于开发、交付、运行应用程序的开放平台&#xff0c;可以使应用程序与基础架构分开&#xff0c;以便快速交付软件。 Docker在一个被叫做容器的隔离环境下&#xff0c;提供了打包和运行的能力。 容器非常轻量化&#x…

[SICTF 2023] webmisc

文章目录 webBaby_PHP涉及知识点 我全都要RCE你能跟得上我的speed吗 miscPixel_art攻破这个压缩包&#xff01; web Baby_PHP 涉及知识点 php解析特性apache换行解析漏洞无参RCE 源代码 <?php highlight_file(__FILE__); error_reporting(0);$query $_SERVER[QUERY_ST…

【新版】软考 - 系统架构设计师(总结笔记)

个人总结学习笔记&#xff0c;仅供参考&#xff01;&#xff01;&#xff01; →点击 笔者主页&#xff0c;欢迎关注哦&#xff08;互相学习&#xff0c;共同成长&#xff09; 笔记目录 &#x1f4e2;【系统架构设计系列】系统架构设计专业技能 计算机组成与结构操作系统信…

AI系统论文阅读:SmartMoE

提出稀疏架构是为了打破具有密集架构的DNN模型中模型大小和计算成本之间的连贯关系的——最著名的MoE。 MoE模型将传统训练模型中的layer换成了多个expert sub-networks&#xff0c;对每个输入&#xff0c;都有一层special gating network 来将其分配到最适合它的expert中&…

UMA 2 - Unity Multipurpose Avatar☀️九.Expressions表情管理与表情插件推荐 (口型同步 / 表情管理)

文章目录 🟥 Expressions文件位置🟧 功能 : 解决嘴巴张开问题🟨 Expressions : 表情面板API讲解🟩 表情插件推荐 : 口型同步 / 表情管理🟥 Expressions文件位置 Expressions也是UMA内置5种实用Recipes之一,位置如下. 使用方法: 如下图所示,将Recipes拖到Additional…

【ESP-S3-BOX-Lite花屏问题】:Github下载源码(出厂源码factory_demo)编译调试到ESP-S3-BOX-Lite中出现花屏现象

项目场景&#xff1a; 最近拿到了一块乐鑫的 ESP-S3-BOX-Lite &#xff08;esp-box: ESP-BOX 是乐鑫信息科技&#xff09; 详细资料&#xff08;esp32_s3_box_lite&#xff09; 版本信息 ESP-BOX依赖的 ESP-IDF分支信息支持状态master> release/v5.1 commit id: 22cfbf3…

基于javaweb的家庭理财管理系统(servlet+jsp)

一、系统简介 本项目采用工具开发&#xff0c;jspservletjquery技术编写&#xff0c;数据库采用的是mysql&#xff0c;navicat开发工具。 系统一共分为2个角色分别是&#xff1a;管理员&#xff0c;用户 获取方式 xystgl master 码盗_java_bishe / java系统 GitCodeGitC…

Edge浏览器没有让我失望! 今天终于可以在win10中模拟IE内核进行前端测试了!

前言 &#x1f61d; ietest现在是不是不好用了? Edge浏览器仿真是不是不见了&#xff1f; 如图 如果我们在前端开发javascript遇见一些老旧的语法标准&#xff0c;想要测试一下都难&#xff0c;想想都抓狂!&#x1f624;&#x1f624; 不过不用担心&#xff0c;经过这几天的…

娱乐时间 —— 用python将图片转为excel十字绘

最近看蛮多朋友在玩&#xff0c;要么只能画比较简单的&#xff0c;要么非常花时间。想了下本质上就是把excel对应的单元格涂色&#xff0c;如果能知道哪些格子要上什么颜色&#xff0c;用编程来实现图片转为excel十字绘应该是很方便的。 图片的每一个像素点都可以数值化&#x…

人工智能TensorFlow PyTorch物体分类和目标检测合集【持续更新】

1. 基于TensorFlow2.3.0的花卉识别 基于TensorFlow2.3.0的花卉识别Android APP设计_基于安卓的花卉识别_lilihewo的博客-CSDN博客 2. 基于TensorFlow2.3.0的垃圾分类 基于TensorFlow2.3.0的垃圾分类Android APP设计_def model_load(img_shape(224, 224, 3)_lilihewo的博客-CS…

C语言指针详解(1)------指针类型(简单例子+详细讲解)

C语言指针类型详解及举例 此文介绍了C语言中常见的6种指针,对每种指针进行了介绍,并举出相应的例子供大家理解学习. 文章目录 C语言指针类型详解及举例1.字符指针2.数组指针3.指针数组4.函数指针5函数指针数组6指向函数指针数组的指针 1.字符指针 字符指针是指向字符数组或字符…

vue学习之事件绑定

事件绑定 创建 demo5.html,内容如下 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</…

手写Mybatis框架

Mybatis核心配置文件就是为了配置Configration 因此要首先会解析Mybatis核心配置文件 首先使用dom4J解析Mybatis核心配置文件 新建模块演示dom4j解析.xml 目录放错了 无所谓 引入依赖 从原来项目可以拷贝过来 就些简单配置就好 解析核心配置文件和解析xxxMapper.xml映射文件…

《React vs. Vue vs. Angular:2023年的全面比较》

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

如何快速下载UE源码

前置技能要求掌握Git和Github的基础操作和知识 1.获取源码仓库授权并下载源代码 (1).获取授权 UE是开源的&#xff0c;但是它的代码仓库在Github不开源&#xff0c;我们没法直接搜索到&#xff0c; 登录->个人信息->应用与账户->连接Github 请按照官网指引成功进…

想要精通算法和SQL的成长之路 - 课程表III

想要精通算法和SQL的成长之路 - 课程表III 前言一. 课程表III&#xff08;贪心优先队列&#xff09;1.1 优先选择截止时间更小的课程1.2 如果当前课程无法学习怎么办&#xff1f;1.3 优化 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 课程表III&#xff08;贪心优先队列&…

【ArcGIS pro】-使用arcpy一次保存多个布局

在arcgis Pro中常常会创建多个地图和多个布局&#xff0c;本文介绍如何使用代码&#xff0c;一次保存多个布局文件 在arcgis pro中打开python视图 找到工程位置 在python视图中输入如下代码 保存为pdf import arcpy# 设置当前项目&#xff0c;这通常是一个.aprx文件 projec…

11.Xaml DatePicker控件 时间控件

1.运行效果 2.运行源码 a.Xaml源码 <Grid Name="Grid1"><!--DisplayDate="2020-5-1" 显示的日期--><!--DisplayDateStart="2020

Linux CentOS7 添加中文输入法

在安装CentOS7时&#xff0c;现在默认安装了桌面中文系统。可以切换为英文&#xff0c;中英文可以按要求随时更换。而在CentOS7桌面环境下&#xff0c;显示中文非常方便、正确&#xff0c;并不能录入中文。 在远程登录系统的情况下&#xff0c;不论是系统语言&#xff08;LANG…