首页 > 编程语言 >手动开发-简单的Spring基于注解配置的程序--源码解析

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

时间:2023-10-04 12:05:11浏览次数:46  
标签:容器 -- Spring value 源码 注解 ElementType ioc class

在前文中 《手动开发-简单的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容器机制》

标签:容器,--,Spring,value,源码,注解,ElementType,ioc,class
From: https://blog.51cto.com/u_15416819/7701639

相关文章

  • 3个可乐瓶可以换一瓶可乐,现在有364瓶可乐。问一共可以喝多少瓶可乐,剩下几个空瓶?
    这是一道以数学问题为背景下的编程问题,对于小白来说可能不好下手,本题主要以思路为主,体现如何把数学思维转化为编程思维,把数学算法转化为编程算法。3个可乐瓶可以换一瓶可乐,喝的可乐总数就等于喝的加换的,开始有364瓶可乐,我们要从最初的条件入手,摆脱数学想一步求一步的思想,一切以最初......
  • comsol下载-2023全新绿色中文版下载 安装包下载方式
    COMSOL6.0版全称叫做COMSOLMultiphysics,是一款闻名全球的多功能物理仿真软件,这款软件通常被应用于工程、制造、科研等各个方面,相关用户在这里可以进行设计绘图、仿真建模、数据分析等一系列操作,从而可以很好的提升了相关用户的工作效率。另外,该软件还采用了可视化的操作界面,使得用......
  • COMSOL6.0中文版_COMSOL6.0下载 汉化版 安装包下载方式
    comsolmultiphysics是一款超级强大的专业的通用型3D建模软件,应用范围广泛,电气、机械、流体流动和化学应用都可以使用它。运用该软件用户就可以非常轻松地解释耦合或多物理现象,且软件提供30多种附加产品选择,满足用户的需求。软件地址:看置顶贴功能特点App开发器1、可以通过......
  • 多物理场仿真软件COMSOL Multiphysics最新版下载 安装包下载方式
    comsol是一款领先的高级数值仿真软件,非常强大的功能以及直观的用户界面,给到用户们提供了一系列的理解、预测、优化解决方案。软件还引入了多项新功能与产品,包括有望为仿真行业带来彻底变革的App开发器等等,求解器的性能有了很大的提高。软件地址:看置顶贴6.0新特征6.0版还引入了不确......
  • 解决警告UserWarning: Glyph 38388 (\N{CJK UNIFIED IDEOGRAPH-95F4}) missing from
    这个警告是由于在绘图时使用了当前字体不支持的字符,通常出现在使用非英文字符(比如中文、日文等)时。为了解决这个问题,你可以尝试以下几种方法:方法一:选择支持中文的字体在绘图之前,指定一个支持中文的字体。例如,可以使用matplotlib.rcParams来指定字体,示例如下:importmatplotlib.pyplo......
  • 基于Java的摄影素材分享网站的设计与实现(亮点:活动报名、点赞评论、图片下载、视频下载
    (摄影分享网站)网上大部分的毕设套路如下:在b站发毕设项目的演示视频,让你免费领取,你领取完发现代码不全或者数据库少表,根本跑不起来!如果要调试则收费300:sweat_smile:真的是恶心至极有没有!某宝找人帮忙写,简单来说比第一种行为靠谱,但是很贵!说是可以免费修改其实修改基本排不上队,......
  • 认识磁盘阵列RAID
    一:概述磁盘阵列是一种广泛应用的存储技术,使用它可以在很大程度上扩展存储容量,增强数据安全性,提高系统安全性能,磁盘阵列(RAID)是由多个独立的磁盘构成的一个超大容量的磁盘组。相比较单个磁盘,磁盘阵列有着非常突出的优势。二:详细介绍超大容量:可以将多个磁盘组合起来形成一个巨大的磁......
  • ip掩码位换算
    ip掩码位换算联通网关ip 1 65 97129161177193225掩码位25/128 255.255.255.128 可用ip数126个25掩码位,2的1次方,把一个C网划分为2个子网地址范围+125,间隔+128162.193.255.0 , 162.193.255.1 到162.193.255.126 , 162.193.255.127162.193.255.128,......
  • 网络基础知识
    ==============================掩码位变长24掩码位/22借2位1变4  主机1024-2        /21借3位1变8  主机2048-2        /20 借4位1变16 主机4096-2十进制掩码    掩码长度   主机数目   0    ......
  • 静态方法不依赖实例对象的调用例题
    publicclassNull{publicstaticvoidsmile(){System.out.println("haha");}publicstaticvoidmain(String[]args){((Null)null).smile();}} 问代码之后之后,能否正常打印? 答案:是可以的,打印“haha” 解释:由于静态方法......