【Spring 底层原理】手搓一个Spring框架

文章目录

    • 准备工作
      • Spring 框架到底在干啥?
      • 几个概念辨析
      • 注解的定义
      • 自定义核心注解
      • 配置类
      • 启动类
      • 辅助类
    • Spring 容器
      • XxxAware 回调机制
      • 初始化机制
      • 前置、后置处理器
      • 完整的容器代码
      • 源码下载

最近工作接触到的知识比较底层,因此为了突破瓶颈,彻底搞明白Spring到底帮我们干了些啥。学习了很多关于Spring的知识,看了这么多资料能讲清楚的人不多,我将尝试将Spring的原理和读者讲明白。我们手写一个简单的Spring框架用于搞清楚其核心原理,这个手写框架我把它叫做Summer框架,平行时空的Spring框架。 追求高质量文章需要兄弟萌支持,一键三连!

准备工作

Spring 框架到底在干啥?

不知道大家在使用Spring时是否会思考这个框架到底发挥了什么作用呢?大家可能背过八股文可能会脱口而出,我知道Spring帮我们IOCAOP,也就是帮我们依赖注入和支持切面。这个回答很八股,别烦了,老哥告诉你,Spring框架就是一个HashMap,各种注解就是各种标记,通过各种注解从而来识别应该将哪些类进行实例化成Bean然后放入到HashMap中,当通过@AutoWired自动注入一个所依赖的实例Bean时就直接去HashMap中寻找是否已经被实例化了,如果没有就当场创建并放入到HashMap中。当然以上过程是Spring框架IOC的简单核心逻辑,仅涉及到单例且不涉及到循环依赖。那么AOP的逻辑是什么呢?其实很简单,就是允许你在原本的Bean实例方法执行的前后搞一个回调函数,这个回调函数只管被Spring容器执行,Spring不管它是如何实现的,由用户自己定义,仅此而已。这就是Spring框架的核心原理,其他的无非就是缝缝补补解决一些存在的问题,学一学就好了。例如Spring容器在实例化一个Bean时有步骤的,第一步怎么样,第二步怎么样,这些步骤连起来就叫做Bean的生命周期。好了,既然看到了这里,那么接下来正式进入高潮学习,支持高质量原创文章,一键三连哦!

几个概念辨析

  • Spring 容器是什么?
  • 依赖注入是什么?
  • Bean 是什么?
  • 回调函数是什么?

注解的定义

在讲Spring框架之前,如何自己定义一个注解必须要了解,Java中的注解可以写在类上、方法上、属性上、参数上等。其实就是一个标记的作用,注解也可以有参数。可以通过类对象判断是否存在指定的注解,也可以通过类对象拿到指定注解的参数值。这样的话在代码中就能发挥巨大的作用。如下,我就定义了一个性别的注解,这个注解有啥用呢?如果类也有性别的话,那么可以通过在类上加上该注解来注明。

package com.example.log;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.Type)
@Retention(RetentionPolicy.RUNTIME)
public @interface Gender {/*** 默认变量:value*/String value() default "woman"; // 默认参数
}

自定义注解必须使用@interface来标识,并可以定义变量,这里定义的变量与传统Java类的变量不太一样,看起来更像一个方法;default 用于定义默认值;注意:参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、Annotations等数据类型,以及这一些类型的数组。我们也称为JDK的元注解。

  • @Retention:定义注解的保留策略
    • @Retention(RetentionPolicy.SOURCE) ,注解仅存在于源码中,在class字节码文件中不包含
    • @Retention(RetentionPolicy.CLASS) ,默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
    • @Retention(RetentionPolicy.RUNTIME) ,注解会在class字节码文件中存在,在运行时可以通过反射获取到
  • @Target:指定被修饰的Annotation可以放置的位置(被修饰的目标)
    • @Target(ElementType.TYPE) ,接口、类
    • @Target(ElementType.FIELD) ,属性
    • @Target(ElementType.METHOD) ,方法
    • @Target(ElementType.PARAMETER),方法参数
    • @Target(ElementType.CONSTRUCTOR),构造函数
    • @Target(ElementType.LOCAL_VARIABLE) ,局部变量
    • @Target(ElementType.ANNOTATION_TYPE) ,注解
    • @Target(ElementType.PACKAGE) ,包

一般@Retention设置成RetentionPolicy.RUNTIME即可,@Target一般设置在类或者方法或者属性上。 那么搞清楚了如何写一个自定义注解类后,此时是不是有疑惑,那么注解的逻辑在哪里实现呢?这里简单剧透一下,注解仅仅作为一个标识,当某个方法执行时我们需要在自定义注解逻辑中去判断是否有被这个注解所标识,如果有则具体处理,否则放行;以上你很容易想到使用 AOP切片拦截器实现。这两个方法的共同点就是在加了注解的方法执行之前可以先进行注解标识判断。那么最简单的就是通过类对象来判断是否存在指定注解和获取指定注解的值。例如:

Class personClass = Person.class;
if (personClass.isAnnotationPresent(Gender.class)) {Gender genderAnnotation = (Gender) personClass.getAnnotation(Gender.class);String gender = genderAnnotation.value();if("woman".equals(gender)){sout("Person这个类上面标记了@Gender()或者@Gender("woman")");}if("man".equals(gender)){sout("Person这个类上面标记了@Gender("man")");}}

了解了注解的自定义之后,我们就可以直接进入到Spring框架的核心原理篇章了,我们直接手写一个Spring框架,我把它叫做Summer框架。通过Summer框架介绍核心的Spring框架再逐渐引入Spring源码,其实思想是一致的,只不过源码使用了一些设计模式,设计上更加完美。你只要创建最简单的Java工程(甚至都不需要Maven工程),这里先给出整体的项目结构,如下图:
在这里插入图片描述

自定义核心注解

Spring框架有哪些核心注解呢?因此,我们的Summer框架也需要这些核心注解,定义四个注解类,自定义注解如下:

// 组件扫描注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {String value() default "";
}
---------------------------------
// 组件注明注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {String value() default "";
}
---------------------------------
// 注明Bean的多例还是单例,默认单例
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {String value() default "singleton";
}
---------------------------------
// 属性注入(依赖注入)注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}

配置类

首先第一步是编写配置类,配置类需要作为SummerApplicationContext容器类的参数,配置类中先什么都不用写,只需要扫描两个指定的路径即可。它的作用就是路径扫描,说人话就是扫描给定的路径下的文件是不是class文件,如果是再判断有没有被Component注解标注,如果标注了那就需要自动注入到容器中。那么先剧透这些,具体的稍后揭晓。很简单配置类如下:

@ComponentScan("org.cheney.service,org.cheney.config")
public class AppConfig {
}

启动类

写完了配置类,那么就可以将配置类传参给容器对象,这样的话容器就能自动去扫描AppConfig.class类上面注解指定的路径。SummerApplicationContext是需要我们自己定义的容器,模仿了Spring容器底层的核心,所有的一切都是为这个类服务的,下面代码是启动类,只是给出了用法,它的用法和Spring 的 SpringApplicationContext一致。传入配置类即可。使用方法如下:

public class SummerApplication {public static void main(String[] args) {SummerApplicationContext summerApplicationContext =new SummerApplicationContext(AppConfig.class); }
}

全篇都是围绕如何设计SummerApplicationContext 类来揭露Spring的底层原理,因此我们后面基本上都是围绕它来详细介绍的。除此之外,我们先造一些需要的类来展示Spring容器的机制。

辅助类

我们首先定义一个User类,这个类没有什么不同,为了让这个类能够被我们后续设计的容器统一管理起来,所以需要使用@Component注解一下,另外还可以选择使用@Scope定义容器是单例持有该类还是多例。代码如下:

@Component("user")
//@Component
@Scope("prototype")
//@Scope("singleton")
public class User {
}

再定义一个OrderService类和UserService类,其中OrderService类是UserService类的一个依赖属性,因此需要通过@Autowired注解进行注入,并且在UserService 中还调用了OrderService的一个方法。如下:

@Component
public class OrderService {void pong(){System.out.println("Ping Pong,I am a OrderService!");}
}-----------------------------------------------------------------@Component
public class UserService {@Autowiredprivate OrderService orderService; public void pingOrderService(){orderService.pong();}
}

好了,目前的话辅助类和需要的注解我们都准备的差不多了。在这里,我需要明确一下我们的目标是什么!我们希望有个框架可以帮我们统一管理所有使用@Componet注解标注了类的Bean,并且能够自动注入被@Autowired注释标记的依赖,这两个就是IOC的能力。另外,我们还希望我们设计的框架支持在实例化Bean的前后能够执行一下程序员预设的方法,程序员只需要告诉框架在实例化某个Bean前后应该执行那个方法,框架会自动执行。这个就是Spring所谓的生命周期。目前以上两点是非常核心的能力,顺带一些Spring简单的功能我们也介绍一下。所以,下面直接开始手撸Spring框架——Summer框架的开始!

Spring 容器

到现在了,请问什么叫做Spring容器?如果回答不出那么也不要烦了,Spring容器就是维护了一个HashMap的类,只不过在源码中是线程安全的ConcurrentHashMap。容器当然是用来存放东西的,Spring容器存放的是Bean,什么是Bean?Bean就是被@Component注解标注了的类的对象,例如上面我们创建的辅助类UserUser类被容器创建的实例就是一个Bean,你自己new出来的对象没有被容器管理,不能称为Bean。容器负责创建,然后放入HashMap,使用、销毁等对Bean的操作,当然还有其他更多的生命周期管理。综上所述,我们的容器应该具备以下功能:

  • 在容器类的构造方法中就扫描所有的类,并处理。
  • 有一个ConcurrentHashMap用于存放Bean
  • 可以根据扫描到的信息创建Bean,并判断属性是否有@Autowired注解标注,如果有就需要进行属性依赖注入。
  • 接受一个配置类,根据配置类的扫描路径自动扫描@Componet注解的类并实例化到ConcurrentHashMap中。
  • 可以通过Bean的名字直接从ConcurrentHashMap中获取Bean

通过上面分析,我们的容器类的大概逻辑应该如下所示:

public class SummerApplicationContext2 {private Class configClass;private ConcurrentHashMap<String, Object> singletonBeanMap =new ConcurrentHashMap<>();public SummerApplicationContext2(Class configClass) {this.configClass = configClass;// 判断是否存在@ComponentScan注解if (configClass.isAnnotationPresent(ComponentScan.class)) {ComponentScan scanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);// 获取注解中给定的扫描路径的值String pathValue = scanAnnotation.value(); // org.cheney.serviceFile file = new File(pathValue);if (file.isDirectory()) {File[] files = file.listFiles();for (File f : files) {1. 解析每一个.class文件,并且进行实例化得到Bean2. 获取到beanName或者使用默认类名第一个字母小写3. singletonBeanMap.put(beanName,Bean);Bean存放在Map}}}  }private Object createBean(...) { // 根据类路径创建Beanreturn bean;}public Object getBean(String beanName) {// 单例对象通过beanName直接从Map中获取Bean对象// 多例,通过beanName如何创建Bean呢?return 名字为beanName的Bean;}
}

上面给出的伪代码基本上讲清楚了容器的基本原理,只是很多细节我没有去实现,其实上面的伪代码是有问题的。因为,在扫描路径的时候,一旦发现了有@Component.class文件就进行了实例化,并放入了Map中。其实这一步做的太早了,还记得我们有一个getBean(String beanName)方法吗?如果获取的Bean是一个多例,也就是每一次调用getBean方法都返回不同的对象Bean,那这个应该如何实现呢?因此,在扫描路径时不能太早的实例化Bean,因为只有单例Bean才可以在这里实例化,多例的话不能实例化。因此在扫描路径时一旦发现了有@Component.class文件我们就先记录下来,也就是先把需要类的详细信息记载小本本上,如果调用getBean发现是多例(也就是Map中没有beanName的对象)就通过beanName从小本本上拿到类的详细信息,从而可以有的放矢地创建新的对象。那么很明显这个小本本也是一个HashMap结构,在Spring源码中叫做BeanDefinition。我们给出BeanDefinition类的定义:


public class BeanDefinition {private Class type;private String scope;public BeanDefinition(){}public BeanDefinition(Class type, String scope) {this.type = type;this.scope = scope;}public Class getType() {return type;}public void setType(Class type) {this.type = type;}public String getScope() {return scope;}public void setScope(String scope) {this.scope = scope;}
}

BeanDefinition 的目的仅仅是记录扫描到的类的详细信息,这里给出的定义是简化后的定义。这里只有classscope连个属性,一旦提供了class,就拿到了其字节码文件。拿到了字节码就可以为所欲为,通过它进行反射,包括创建实例等。而scope则记录了是单例还是多例。因此我们的代码需要修改成如下的代码(详细的代码最后给出!):

public class SummerApplicationContext2 {private Class configClass;private ConcurrentHashMap<String, Object> singletonBeanMap =new ConcurrentHashMap<>();private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =new ConcurrentHashMap<>();public SummerApplicationContext2(Class configClass) {this.configClass = configClass;// 判断是否存在@ComponentScan注解if (configClass.isAnnotationPresent(ComponentScan.class)) {ComponentScan scanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);// 获取注解中给定的扫描路径的值String pathValue = scanAnnotation.value(); // org.cheney.serviceFile file = new File(pathValue);if (file.isDirectory()) {File[] files = file.listFiles();for (File f : files) {0. 生成 beanName1. 解析每一个.class文件,获取class和scope两个属性2. 创建该类的一个 beanDefinition 对象。3. beanDefinitionMap.put(beanName,beanDefinition); 将beanDefinition存放在Map}}}// 将beanDefinition中的Singleton进行实例化for (String beanName : beanDefinitionMap.keySet()) {BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);if ("singleton".equals(beanDefinition.getScope())) {Object bean = createBean(beanName, beanDefinition);singletonBeanMap.put(beanName, bean);}}}private Object createBean(String beanName, BeanDefinition beanDefinition) {1. 根据 beanDefinition 创建Beanreturn bean;}public Object getBean(String beanName) {1. 通过beanName直接从singletonBeanMap中获取Bean对象2. 如果Map中没有(多例情况),则获取beanDefinition, 调用 createBean 使用 beanDefinition 创建Bean对象return bean;}
}

恭喜你,简单的Spring框架基本上成型了。我们需要做的就是修修补补,把一些重要的有用的细节完善、例如回调方法(XxxAware机制)、前置处理器、初始化方法、后置处理器等完善即可。详细的代码会在后面给出!

XxxAware 回调机制

关于XxxAware回调机制,不懂的同学可以参考一下我以前的一篇博客BeanFactoryAware 机制简介,XxxAware机制提供了一个接口,你需要什么东西只需要实现对应的接口就可以拿到。因为在容器类中会自动判断这个类是否实现了对应的接口,如果实现了就会调用setXxx的方法,将Xxx对象回调回去。这里我再进行详细介绍一下实现原理,假设我们需要让@Component注解了的类可以获取到自己的Bean在容器中的名字,请问你会如何实现?Spring的做法就是上面的原理,首先提供一个BeanNameAware接口,代码如下:

public interface BeanNameAware {void setBeanName(String beanName);
}

然后假设User对象需要获取自己在容器中的名字,只需要实现该接口即可:

@Component("superAdmin")
public class User implements BeanNameAware{private String beanName;@Overridepublic void setBeanName(String beanName) {this.beanName = beanName;}public String getBeanName(){return beanName;}
}

目前并没有什么卵用,需要在容器中判断是否实现了BeanNameAware接口,然后将beanName通过回调方法setBeanName()被动设置。就是在容器类的createBean()方法中实现,代码如下:

private Object createBean(String beanName, BeanDefinition beanDefinition) {Class clazz = beanDefinition.getType();try {Object bean = clazz.getConstructor().newInstance();// 实现依赖注入,通过反射获取属性,判断属性是否有Autowired注解Field[] fields = clazz.getDeclaredFields();for (Field f : fields) {if (f.isAnnotationPresent(Autowired.class)) {f.setAccessible(true);f.set(bean, getBean(f.getName()));}}// BeanNameAware 回调if (bean instanceof BeanNameAware) {((BeanNameAware) bean).setBeanName(beanName);} return bean;} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}

只要我们再容器中提供了BeanNameAware的回调逻辑,那么只需要实现对应的接口就可以获取到容器中才能获取到的对象。非常灵活,其实在Spring中可以通过实现BeanFactoryAware 接口来获得当前BeanFactory,也可以通过实现ApplicationContextAware接口来获取到对应的上下文容器。其实实现都是这个逻辑!

初始化机制

与上面的XxxAware回调机制很类似,XxxAware回调机制是通过判断是否实现指定接口然后回调将参数赋值回去,其接口方法只需要接住传回来的参数即可。初始化机制也是回调,但是并不需要传参,也就是不需要赋值回去。容器只是调用指定的初始化方法,初始化方法的内容是程序员自己实现的,容器并不关心。如下我们先实现初始化接口:

public interface InitializingBean {public void afterPropertiesSet();
}

其中有一个初始化的方法叫做 afterPropertiesSet(),从名字可以看出这个方法是在依赖注入或者说属性设置完毕之后执行的,所以在容器中的实现我们需要注意一下位置。然后如果说User需要初始化,只需要实现该接口既可,如下:

@Component("superAdmin")
public class User implements BeanNameAware, InitializingBean {private String beanName;@Overridepublic void setBeanName(String beanName) {this.beanName = beanName;}public String getBeanName(){return beanName;}@Overridepublic void afterPropertiesSet() {// 执行其他初始化操作 ...System.out.println("super admin 执行初始化方法!");}
}

在容器中,我们需要判断是否实现了InitializingBean 接口,然后调用afterPropertiesSet()方法。加上这部分逻辑的createBean()方法代码如下:

private Object createBean(String beanName, BeanDefinition beanDefinition) {Class clazz = beanDefinition.getType();try {Object bean = clazz.getConstructor().newInstance();// 实现依赖注入Field[] fields = clazz.getDeclaredFields();for (Field f : fields) {if (f.isAnnotationPresent(Autowired.class)) {f.setAccessible(true);f.set(bean, getBean(f.getName()));}}// Aware 回调if (bean instanceof BeanNameAware) {((BeanNameAware) bean).setBeanName(beanName);} // 初始化,在所有属性设置完成后执行!if (bean instanceof InitializingBean) {((InitializingBean) bean).afterPropertiesSet();}return bean; } catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}

前置、后置处理器

Spring 中,BeanPostProcessor是一个非常有用的接口,它可以在 Spring 容器初始化 Bean 的前后进行一些自定义的处理。千万注意一点,BeanPostProcessor是面向所有的Bean的,也就是说如果不加限制只需要实现了BeanPostProcessor接口,就会对所有的Bean的初始化前后都进行自定义的增强。以下是关于如何实现BeanPostProcessor的详细介绍。首先定义BeanPostProcessor 接口如下:

public interface BeanPostProcessor {public void postProcessorBeforeInitialization(String beanName,Object bean);public void postProcessorAfterInitialization(String beanName,Object bean);
}

然后我们实现该接口就会导致所有的Bean的初始化前后都会执行对应的两个方法,例如我们让AppBeanPostProcessor实现BeanPostProcessor 接口,为了避免所有的Bean初始化都执行前后置方法,我们需要在方法中进行判断,只有User类才执行。代码如下:

@Component
public class AppBeanPostProcessor implements BeanPostProcessor {@Overridepublic void postProcessorBeforeInitialization(String beanName, Object bean) {if(bean instanceof Admin){System.out.println(beanName+" before ..." + bean);}}@Overridepublic void postProcessorAfterInitialization(String beanName, Object bean) {if(bean instanceof Admin){System.out.println(beanName+" after ..." + bean);}}
}

到这里其实,写完了都不会执行,因为没有在容器类中编写对应的逻辑。BeanPostProcessor 接口不是只能实现一次,可以实现多次,因此我们需要在容器中记录所有的BeanPostProcessor ,然后在创建Bean初始化前后全部拿出来都调用一遍,省略其他的代码,增加的代码如下:

// 在容器的构造方法中增加如下代码
private ArrayList<BeanPostProcessor> beanPostProcessorList =new ArrayList<>();
public SummerApplicationContext(Class configClass) {// 前置 后置 处理器if (BeanPostProcessor.class.isAssignableFrom(clazz)) {BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();beanPostProcessorList.add(instance);}}

到现在,可以给出完整的createBean()代码,如下所示,注意在初始化前后有我们的前置和后置方法的调用。

private Object createBean(String beanName, BeanDefinition beanDefinition) {Class clazz = beanDefinition.getType();try {Object bean = clazz.getConstructor().newInstance();// 实现依赖注入Field[] fields = clazz.getDeclaredFields();for (Field f : fields) {if (f.isAnnotationPresent(Autowired.class)) {f.setAccessible(true);f.set(bean, getBean(f.getName()));}}// Aware 回调if (bean instanceof BeanNameAware) {((BeanNameAware) bean).setBeanName(beanName);}// BeanPostProcessor 初始化前,前置处理器for (BeanPostProcessor processor : beanPostProcessorList) {processor.postProcessorBeforeInitialization(beanName,bean);} // 初始化if (bean instanceof InitializingBean) {((InitializingBean) bean).afterPropertiesSet();}// BeanPostProcessor 初始化后,后置处理器for (BeanPostProcessor processor : beanPostProcessorList) {processor.postProcessorAfterInitialization(beanName,bean);}return bean;} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}

完整的容器代码

package org.cheney.summer;import org.cheney.summer.annotation.Autowired;
import org.cheney.summer.annotation.Component;
import org.cheney.summer.annotation.ComponentScan;
import org.cheney.summer.annotation.Scope;import java.beans.Introspector;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;/*** Hello world!*/
public class SummerApplicationContext {private Class configClass;private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =new ConcurrentHashMap<>();private ConcurrentHashMap<String, Object> singletonBeanMap =new ConcurrentHashMap<>();private ArrayList<BeanPostProcessor> beanPostProcessorList =new ArrayList<>();public SummerApplicationContext(Class configClass) {this.configClass = configClass;if (configClass.isAnnotationPresent(ComponentScan.class)) {ComponentScan scanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);String pathValue = scanAnnotation.value(); // org.cheney.serviceString[] paths = pathValue.split(",");for (String path : paths) {path = path.replace(".", "/");ClassLoader classLoader = SummerApplicationContext.class.getClassLoader();URL resource = classLoader.getResource(path);File file = new File(resource.getFile());if (file.isDirectory()) {File[] files = file.listFiles();for (File f : files) {String absolutePath = f.getAbsolutePath();if (absolutePath.endsWith(".class")) {// 判断目录下的.class文件是否有 @Component注解String className = absolutePath.substring(absolutePath.indexOf("org"), absolutePath.indexOf(".class"));className = className.replace("\\", ".");
//                        System.out.println(className);try {Class<?> clazz = classLoader.loadClass(className);if (clazz.isAnnotationPresent(Component.class)) {// 扫描到了 @Component 注解,生成一个 BeanDefinition 对象BeanDefinition beanDefinition = new BeanDefinition();beanDefinition.setType(clazz);if (clazz.isAnnotationPresent(Scope.class)) {String value = clazz.getAnnotation(Scope.class).value();beanDefinition.setScope(value);} else {beanDefinition.setScope("singleton");}String beanName = clazz.getAnnotation(Component.class).value();if ("".equals(beanName)) {
//                                    beanName = getBeanName(clazz);beanName = Introspector.decapitalize(clazz.getSimpleName());}beanDefinitionMap.put(beanName, beanDefinition);// 前置 后置 处理器if (BeanPostProcessor.class.isAssignableFrom(clazz)) {BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();beanPostProcessorList.add(instance);}}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}}}}}}// 将beanDefinition中的Singleton进行实例化for (String beanName : beanDefinitionMap.keySet()) {BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);if ("singleton".equals(beanDefinition.getScope())) {Object bean = createBean(beanName, beanDefinition);singletonBeanMap.put(beanName, bean);}}}private Object createBean(String beanName, BeanDefinition beanDefinition) {Class clazz = beanDefinition.getType();try {Object bean = clazz.getConstructor().newInstance();// 实现依赖注入Field[] fields = clazz.getDeclaredFields();for (Field f : fields) {if (f.isAnnotationPresent(Autowired.class)) {f.setAccessible(true);f.set(bean, getBean(f.getName()));}}// Aware 回调if (bean instanceof BeanNameAware) {((BeanNameAware) bean).setBeanName(beanName);}// BeanPostProcessor 初始化前,前置处理器for (BeanPostProcessor processor : beanPostProcessorList) {processor.postProcessorBeforeInitialization(beanName,bean);}// 初始化if (bean instanceof InitializingBean) {((InitializingBean) bean).afterPropertiesSet();}// BeanPostProcessor 初始化后,后置处理器for (BeanPostProcessor processor : beanPostProcessorList) {processor.postProcessorAfterInitialization(beanName,bean);}return bean;} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}public Object getBean(String beanName) {BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);if (null == beanDefinition) {throw new NullPointerException("Summer容器中不存在叫做" + beanName + "的Bean");}if (beanDefinition.getScope().equals("singleton")) {Object bean = singletonBeanMap.get(beanName);if (null == bean) {bean = createBean(beanName, beanDefinition);singletonBeanMap.put(beanName, bean);}return bean;} else {return createBean(beanName, beanDefinition);}}
}

源码下载

【手写Spring框架】summer 源码。支持高质量中文博客,嘿嘿嘿,一键三连,支持知识免费分享!参考内容:B站周瑜,小傅哥。

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

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

相关文章

ubuntu+MobaXterm+ssh+运行Qt(成功版)

点击上方"蓝字"关注我们 01、ubuntu连接SSH >>> 通过串口工具连接ubuntu 登录 解决连接不上的问题 检查 SSH 服务:确保目标机器上 SSH 服务已启动。你可以在目标机器上运行以下命令: sudo systemctl status ssh 如果没有运行,可以使用以下命令启动 SSH …

英特尔AI加速器Gaudi 3下周发布,挑战NVIDIA统治地位!

英特尔正稳步推进其2024年计划&#xff0c;备受瞩目的AI加速器Gaudi3预计将于下周震撼登场。这款被誉为英特尔AI英雄的产品&#xff0c;专注于处理大规模训练和推理任务&#xff0c;拥有无与伦比的扩展能力。面对市场对高效能半导体的旺盛需求&#xff0c;英特尔首席执行官帕特…

FX5 CPU模块和以太网模块的以太网通信功能

FX5 CPU模块和以太网模块的以太网通信功能的概要如下所示。 CPU模块的内置以太网端口的通信规格如下所示。 1、与MELSOFT的直接连接 不使用集线器&#xff0c;用1根以太网电缆直接连接以太网搭载模块与工程工具(GX Torks3)。无需设定IP地址&#xff0c;仅连接目标指定即可进行…

无服务器计算构建人工智能管理区块链系统

图片发自简书App 图片发自简书App 本发明属于网络版权管理技术领域&#xff0c;特别涉及一种以交易信息作 为唯一标准发行虚拟币的区块链系统。 背景技术 数字代币如比特币、以太坊等是区块链技术的实现方式之一&#xff0c;目 标是取代法定货币流通&#xff0c;通过“挖矿”的…

前端-js例子:收钱转账

支付宝转账 在这里用到周期定时器setInterval(function,time)&#xff0c;设置达到目标钱数时停止定时器。 点击转账按钮时&#xff0c;开始函数显示。 同时要确定输入框里输入的是数字。&#xff08;有一定容错&#xff09; window.onloadfunction(){var btn document.que…

什么是慢充优惠话费充值api?如何选择平台

一、话费充值api的定义 话费充值api是一种能够让开发者将话费充值功能集成到自己的平台的接口。通过接入话费充值api接口&#xff0c;就能够实现话费充值平台的搭建&#xff0c;从而为用户提供话费充值服务&#xff0c;这一接口主要适用于对话费充值有长期稳定需求的企业或者商…

K8s容器运行时,移除Dockershim后存在哪些疑惑?

K8s容器运行时&#xff0c;移除Dockershim后存在哪些疑惑&#xff1f; 大家好&#xff0c;我是秋意零。 K8s版本截止目前&#xff08;24/09&#xff09;已经发布到了1.31.x版本。早在K8s版本从1.24.x起&#xff08;22/05&#xff09;&#xff0c;默认的容器运行时就不再是Doc…

排序-----归并排序(递归版)

核心思想&#xff1a;假设数组前后两部分各自有序&#xff0c;然后各定义两个指针&#xff0c;谁小谁放到新开辟的数组里面&#xff0c;最后把新开辟的数组赋值给原数组就完成了。要使前后两部分有序就采用递归的方式&#xff0c;不断往下划分块&#xff0c;最后一层划分为两个…

01 基础request

目录 类 WxRequest 的定义 静态属性 default 构造函数 constructor 方法 request HTTP 方法封装 创建 WxRequest 实例并导出 完整代码&#xff1a; 类 WxRequest 的定义 创建一个 WxRequest 类包含一个静态属性 default 和几个方法&#xff0c;用于处理网络请求。 静态…

【后端开发】JavaEE初阶—Theard类及常见方法—线程的操作(超详解)

前言&#xff1a; &#x1f31f;&#x1f31f;本期讲解多线程的知识哟~~~&#xff0c;希望能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;【后端开发】JavaEE初阶—线程的理解和编程实现-CSDN博客 &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondl…

计算机毕业设计之:基于深度学习的路面检测系统(源码+部署文档+讲解)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

鸿蒙OpenHarmony【轻量系统内核扩展组件(CPU占用率)】子系统开发

基本概念 CPU&#xff08;中央处理器&#xff0c;Central Processing Unit&#xff09;占用率分为系统CPU占用率和任务CPU占用率。 系统CPU占用率&#xff1a;是指周期时间内系统的CPU占用率&#xff0c;用于表示系统一段时间内的闲忙程度&#xff0c;也表示CPU的负载情况。系…

INIT与init_array

INIT与init array 1.so执行JNI_OnLoad之前&#xff0c;还会执行俩个构造函数init 和init array 在so加载时候有这个过程&#xff1a; .init -> .init array -> JNI_Onload -> java_com_xxx 在脱壳的过程中会在一些系统级的.so中下断点比如&#xff1a;fopen&#x…

GUI编程19:贪吃蛇小游戏及GUI总结

视频链接&#xff1a;21、贪吃蛇之界面绘制_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1DJ411B75F?p21&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.游戏中用的的图片素材 1.贪吃蛇游戏的主启动类StartGame&#xff1b; package com.yundait.snake;import j…

缓存的思考与总结

缓存的思考与总结 什么是缓存缓存命中率数据一致性旁路模式 Cache aside双写模式直写模式 write through异步写 Write Behind 旁路和双写 案例 新技术或中间的引入&#xff0c;一定是解决了亟待解决的问题或是显著提升了系统性能&#xff0c;并且这种改变所带来的增幅&#xff…

【开源服务框架】Dubbo

&#x1f384;欢迎来到边境矢梦的csdn博文&#x1f384; &#x1f384;本文主要梳理Java面试中开源服务框架Dubbo会涉及到的知识点 &#x1f384; &#x1f308;我是边境矢梦&#xff0c;一个正在为秋招和算法竞赛做准备的学生&#x1f308; &#x1f386;喜欢的朋友可以关注一…

GAMES101(15节)

Irradiance辐射度量学 辐射度量学在渲染领域&#xff0c;可以帮助理解基于物理的光照模型 radiant energy辐射能量Q&#xff0c;累计总能量&#xff08;单位J joule焦耳&#xff09;&#xff0c;就像太阳能板&#xff0c;光照时间越长接收能量越多&#xff0c;收到的能量总和…

jetlinks物联网平台学习2(加盐算法登陆)

加盐算法 加盐算法加密验证密码是否正确 对于传统的MD5加密&#xff0c;比更传统的直接保存账号密码稍微安全一点。 md5加密是一种hash算法 比如对于123456来说&#xff0c;md5算法结果一定是e10adc3949ba59abbe56e057f20f883e 这个结果是固定的。于是有的人准备一张彩虹表 预先…

ECharts基础使用方法 ---vue

1.安装依赖文件 仔细看项目" README.md " 描述&#xff0c;确定用什么安装 npm npm install echarts --save //官网推荐使用 pnpm pnpm install echarts --save 其他也是 在项目根目录&#xff0c;打开当前目录命令控制栏&#xff0c;输入以上命令并运行 安装成功后…