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

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

在前文中 《手动开发-简单的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怎么帮我们创建的对象?通过反射创建的,反射所需要的类路径是我们在注解上读取过来的。

#完整代码#

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容器机制》

目录
相关文章
|
12月前
|
人工智能 运维 Java
Spring AI Alibaba Admin 开源!以数据为中心的 Agent 开发平台
Spring AI Alibaba Admin 正式发布!一站式实现 Prompt 管理、动态热更新、评测集构建、自动化评估与全链路可观测,助力企业高效构建可信赖的 AI Agent 应用。开源共建,现已上线!
8860 131
|
Java 关系型数据库 MySQL
Spring Boot自动配置:魔法背后的秘密
Spring Boot 自动配置揭秘:只需简单配置即可启动项目,背后依赖“约定大于配置”与条件化装配。核心在于 `@EnableAutoConfiguration` 注解与 `@Conditional` 系列条件判断,通过 `spring.factories` 或 `AutoConfiguration.imports` 加载配置类,实现按需自动装配 Bean。
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
3122 0
|
12月前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
12月前
|
前端开发 Java 应用服务中间件
《深入理解Spring》 Spring Boot——约定优于配置的革命者
Spring Boot基于“约定优于配置”理念,通过自动配置、起步依赖、嵌入式容器和Actuator四大特性,简化Spring应用的开发与部署,提升效率,降低门槛,成为现代Java开发的事实标准。
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
2016 5
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
744 0
探索Spring Boot的@Conditional注解的上下文配置
|
Java API Spring
Spring容器如何使用一个注解来指定一个类型为配置类型
Spring容器如何使用一个注解来指定一个类型为配置类型
337 0
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
1146 26
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
831 73

热门文章

最新文章

推荐镜像

更多
  • DNS