手写Spring源码——实现一个简单的spring framework

这篇文章主要带大家实现一个简单的Spring框架,包含单例、多例bean的获取,依赖注入、懒加载等功能。为了能让大家更好的理解Spring各个功能的实现,将版本由易到难贴出代码,文章内容会持续更新,感兴趣的小伙伴可以持续关注一下。

目录

一、创建Java项目

二、开始实现Spring

1、BeanFactory接口

2、ApplicationContext接口

3、ApplicationContext接口的实现类(Spring容器)

4、Spring容器代码初步实现

创建配置类

创建自定义的组件注解

@Component

@ComponentScan

创建bean的定义

实现Spring的组件扫描功能

创建一个单例bean——UserService

组件扫描功能测试

5、懒加载和bean的作用域

创建自定义注解

@Lazy

@Scope

创建一个非单例bean——UserMapper

根据bean名称获取bean功能测试

6、自动获取bean的名称

7、依赖注入@Autowired

创建@Autowired注解

UserService中注入UserMapper

添加依赖注入的代码

测试依赖注入

8、InitializingBean

创建InitializingBean接口

创建InitializingBean接口的实现类

9、@Configuration + @Bean

创建@Configuration注解

创建@Bean注解

创建一个UserController

创建配置类MyConfig

测试获取bean

9、未完待续


一、创建Java项目

首先,需要创建一个Java工程,名字就叫spring。

创建完成后,如下图,再依次创建三级包

二、开始实现Spring

Spring中最重要也是最基础的类就是Spring容器,Spring容器用于创建管理对象,为了方便实现类型转换功能,给接口设置一个参数化类型(泛型)。

1、BeanFactory接口

BeanFactory是spring容器的顶级接口,创建该接口,在该接口中定义三个重载的获取bean的方法。

package com.example.spring;/*** @author heyunlin* @version 1.0*/
public interface BeanFactory<T> {Object getBean(String beanName);T getBean(Class<T> type);T getBean(String beanName, Class<T> type);
}

2、ApplicationContext接口

ApplicationContext接口扩展自BeanFactory接口

package com.example.spring;/*** @author heyunlin* @version 1.0*/
public interface ApplicationContext<T> extends BeanFactory<T> {}

3、ApplicationContext接口的实现类(Spring容器)

创建一个ApplicationContext接口的实现类,实现接口中定义的所有抽象方法,这就是我们要直接用的Spring容器。

package com.example.spring;/*** @author heyunlin* @version 1.0*/
public class AnnotationConfigApplicationContext<T> implements ApplicationContext<T> {@Overridepublic Object getBean(String beanName) {return null;}@Overridepublic T getBean(Class<T> type) {return null;}@Overridepublic T getBean(String beanName, Class<T> type) {return null;}}

4、Spring容器代码初步实现

首先,组件扫描需要一个扫描路径,可以通过配置类上的@ComponentScan注解指定,如果不指定,则默认为配置类所在的包。

创建配置类

在当前包下创建一个类,配置包扫描路径。

package com.example.spring;/*** @author heyunlin* @version 1.0*/
@ComponentScan("com.example.spring")
public class SpringConfig {}

创建自定义的组件注解

@Component

package com.example.spring;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author heyunlin* @version 1.0*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {String value() default "";
}

@ComponentScan

package com.example.spring;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author heyunlin* @version 1.0*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {String value() default "";
}

创建bean的定义

创建一个BeanDefinition用来保存bean的基本信息,按alt + insert键创建getter/setter和toString方法。

package com.example.spring;/*** @author heyunlin* @version 1.0*/
public class BeanDefinition {/*** bean的类型*/private Class type;/*** bean的作用域*/private String scope;/*** 是否懒加载*/private boolean lazy;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;}public boolean isLazy() {return lazy;}public void setLazy(boolean lazy) {this.lazy = lazy;}@Overridepublic String toString() {return "BeanDefinition{" +"type=" + type +", scope='" + scope + '\'' +", lazy=" + lazy +'}';}}

实现Spring的组件扫描功能

以下代码只是完成了Spring的简单bean扫描功能,可以根据自己的见解进行代码实现。唯一复杂一点的地方是递归的过程,对递归不是很了解的需要去提前学习一下。

修改AnnotationConfigApplicationContext:

1、添加一个属性clazz,用于保存实例化时传递的配置类对象参数;

2、Spring容器中创建用于保存bean的定义BeanDefinition的map;

3、在初始化spring容器时,从指定的路径下开始扫描,地柜扫描当前目录及其子目录,把@Component注解标注的类加载出来,以bean名称为key,bean的信息封装成的BeanDefinition为value保存到一个map中;

  • 在这里只专注于扫描组件 ,只需要@Component和@ComponentScan两个注解;
  • 没有非常复杂的逻辑,暂时不考虑是否懒加载的单例和作用域,默认设置为false和单例;
package com.example.spring;import java.io.File;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;/*** @author heyunlin* @version 1.0*/
public class AnnotationConfigApplicationContext<T> implements ApplicationContext<T> {private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();public final Class<T> clazz;public AnnotationConfigApplicationContext(Class<T> clazz) throws ClassNotFoundException {this.clazz = clazz;// 扫描组件componentScan(clazz);}public Map<String, BeanDefinition> getBeanDefinitionMap() {return beanDefinitionMap;}/*** 扫描组件* @param clazz 配置类的类对象* @throws ClassNotFoundException 类找不到*/private void componentScan(Class<T> clazz) throws ClassNotFoundException {// 如果类上使用了@ComponentScan注解if (clazz.isAnnotationPresent(ComponentScan.class)) {ComponentScan componentScan = clazz.getAnnotation(ComponentScan.class);String value = componentScan.value();if (!"".equals(value)) {String path = value;path = path.replace(".", "/");URL resource = clazz.getClassLoader().getResource(path);File file = new File(resource.getFile());loopFor(file);}}}/*** 递归遍历指定文件/文件夹* @param file 文件/文件夹* @throws ClassNotFoundException 类找不到*/private void loopFor(File file) throws ClassNotFoundException {if (file.isDirectory()) {for (File listFile : file.listFiles()) {if (listFile.isDirectory()) {loopFor(listFile);continue;}toBeanDefinitionMap(listFile);}} else if (file.isFile()) {toBeanDefinitionMap(file);}}/*** 解析bean,并保存到Map<String, BeanDefinition>* @param file 解析的class文件* @throws ClassNotFoundException 类找不到*/private void toBeanDefinitionMap(File file) throws ClassNotFoundException {// 获取类的绝对路径String absolutePath = file.getAbsolutePath();// 处理得到类的全限定名absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));absolutePath = absolutePath.replace("\\", ".");// 通过类加载器加载Class<?> loadClass = clazz.getClassLoader().loadClass(absolutePath);String beanName;if (loadClass.isAnnotationPresent(Component.class)) {// 获取@Component注解上配置的组件名Component component = loadClass.getAnnotation(Component.class);beanName = component.value();// 是否懒加载boolean lazy = false;// 作用域String scope = "singleton";// 保存bean的定义BeanDefinition beanDefinition = new BeanDefinition();beanDefinition.setType(loadClass);beanDefinition.setLazy(lazy);beanDefinition.setScope(scope);beanDefinitionMap.put(beanName, beanDefinition);}}@Overridepublic Object getBean(String beanName) {return null;}@Overridepublic T getBean(Class<T> type) {return null;}@Overridepublic T getBean(String beanName, Class<T> type) {return null;}}

创建一个单例bean——UserService

package com.example.spring;/*** @author heyunlin* @version 1.0*/
@Component("userService")
public class UserService {}

组件扫描功能测试

为了方便获取已经加载到的bean的定义,在AnnotationConfigApplicationContext中临时添加getter方法

public Map<String, BeanDefinition> getBeanDefinitionMap() {return beanDefinitionMap;
}

测试代码:SpringExample

package com.example.spring;import java.util.Map;/*** @author heyunlin* @version 1.0*/
public class SpringExample {public static void main(String[] args) throws ClassNotFoundException {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);Map<String, BeanDefinition> beanDefinitionMap = applicationContext.getBeanDefinitionMap();for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {System.out.println(entry.getKey() + ":" + entry.getValue());}}}

运行SpringExample的main方法,发现成功扫描到了bean

5、懒加载和bean的作用域

上面一节内容已经实现了了一个简单的组件扫描功能,这节内容主要是实现单例bean的懒加载和非懒加载,以及根据bean的作用域singleton和prototype来决定bean的创建、获取方式。

创建自定义注解

@Lazy

package com.example.spring;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author heyunlin* @version 1.0*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {}

@Scope

package com.example.spring;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author heyunlin* @version 1.0*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {String value() default "singleton";
}

当bean上面使用了@Lazy注解时,并且bean的作用域是单例,将执行懒加载的流程,第一次使用bean的时候才创建bean。

那么,如何保证单例bean是“单例”的呢?

其实这个问题很简单,通过一个map缓存单例的bean,当调用getBean()方法获取bean时,判断bean的作用域如果是单例,就从单例池中获取,不用再次创建。

因此,需要先创建一个map保存单例的bean,以bean的名称为key,单例bean对象为value存储起来。

/*** 单例对象池*/
private final Map<String, Object> singletonObjects = new HashMap<>();

在Spring容器初始化过程中,把非懒加载单例的单例bean缓存到singletonObjects中,而懒加载的单例bean是在第一次获取bean的时候创建bean,然后保存到singletonObjects。

package com.example.spring;import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;/*** @author heyunlin* @version 1.0*/
public class AnnotationConfigApplicationContext<T> implements ApplicationContext<T> {/*** 配置类的类对象*/public final Class<T> clazz;/*** 单例对象池*/private final Map<String, Object> singletonObjects = new HashMap<>();private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();public AnnotationConfigApplicationContext(Class<T> clazz) throws ClassNotFoundException {this.clazz = clazz;// 扫描组件componentScan(clazz);}public Map<String, BeanDefinition> getBeanDefinitionMap() {return beanDefinitionMap;}/*** 扫描组件* @param clazz 配置类的类对象* @throws ClassNotFoundException 类找不到*/private void componentScan(Class<T> clazz) throws ClassNotFoundException {// 如果类上使用了@ComponentScan注解if (clazz.isAnnotationPresent(ComponentScan.class)) {ComponentScan componentScan = clazz.getAnnotation(ComponentScan.class);String value = componentScan.value();if (!"".equals(value)) {String path = value;path = path.replace(".", "/");URL resource = clazz.getClassLoader().getResource(path);File file = new File(resource.getFile());loopFor(file);}}}/*** 递归遍历指定文件/文件夹* @param file 文件/文件夹* @throws ClassNotFoundException 类找不到*/private void loopFor(File file) throws ClassNotFoundException {if (file.isDirectory()) {for (File listFile : file.listFiles()) {if (listFile.isDirectory()) {loopFor(listFile);continue;}toBeanDefinitionMap(listFile);}} else if (file.isFile()) {toBeanDefinitionMap(file);}}/*** 解析bean,并保存到Map<String, BeanDefinition>* @param file 解析的class文件* @throws ClassNotFoundException 类找不到*/private void toBeanDefinitionMap(File file) throws ClassNotFoundException {// 获取类的绝对路径String absolutePath = file.getAbsolutePath();// 处理得到类的全限定名absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));absolutePath = absolutePath.replace("\\", ".");// 通过类加载器加载Class<?> loadClass = clazz.getClassLoader().loadClass(absolutePath);String beanName;if (loadClass.isAnnotationPresent(Component.class)) {// 获取@Component注解上配置的组件名Component component = loadClass.getAnnotation(Component.class);beanName = component.value();// 是否懒加载boolean lazy = false;// 作用域String scope = "singleton";// 新增的代码-------------------start// 类上使用了@Scope注解if (loadClass.isAnnotationPresent(Scope.class)) {// 获取@Scope注解Scope annotation = loadClass.getAnnotation(Scope.class);// 单例beanif (isSingleton(annotation.value())) {lazy = loadClass.isAnnotationPresent(Lazy.class);} else {// 非单例scope = annotation.value();}} else {// 类上没有使用@Scope注解,默认是单例的lazy = loadClass.isAnnotationPresent(Lazy.class);}// 新增的代码-------------------end// 保存bean的定义BeanDefinition beanDefinition = new BeanDefinition();beanDefinition.setType(loadClass);beanDefinition.setLazy(lazy);beanDefinition.setScope(scope);beanDefinitionMap.put(beanName, beanDefinition);}}// 新增的代码-------------------start/*** 判断作用域是否单例* @param scope bean的作用域* @return boolean 如果是单例,返回true,否则返回false*/private boolean isSingleton(String scope) {return "singleton".equals(scope);}// 新增的代码-------------------end@Overridepublic Object getBean(String beanName) {// 修改的代码-------------------startBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);//if (!beanDefinitionMap.containsKey(beanName)) {if (beanDefinition == null) {throw new NullPointerException();}return getBean(beanName, beanDefinition);// 修改的代码-------------------end}@Overridepublic T getBean(Class<T> type) {return null;}@Overridepublic T getBean(String beanName, Class<T> type) {return null;}// 新增的代码-------------------start/*** 统一获取bean的方法* @param beanName bean名称* @param beanDefinition BeanDefinition* @return Object 符合条件的bean对象*/private Object getBean(String beanName, BeanDefinition beanDefinition) {String scope = beanDefinition.getScope();// bean的作用域是单例if (isSingleton(scope)) {Object object = singletonObjects.get(beanName);// 懒加载的单例beanif (object == null) {Object bean = createBean(beanDefinition);singletonObjects.put(beanName, bean);}return singletonObjects.get(beanName);}return createBean(beanDefinition);}/*** 创建bean对象* @param beanDefinition bean的定义* @return Object 创建好的bean对象*/private Object createBean(BeanDefinition beanDefinition) {Object bean = null;Class beanType = beanDefinition.getType();try {// 调用无参构造方法初始化Constructor constructor = beanType.getConstructor();bean = constructor.newInstance();} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {e.printStackTrace();}return bean;}// 新增的代码-------------------end}

如上,已经实现了通过bean名称获取bean的方法,接下来简单测试一下

创建一个非单例bean——UserMapper

package com.example.spring;/*** @author heyunlin* @version 1.0*/
@Component("userMapper")
@Scope("prototype")
public class UserMapper {}

根据bean名称获取bean功能测试

package com.example.spring;/*** @author heyunlin* @version 1.0*/
public class SpringExample {public static void main(String[] args) throws ClassNotFoundException {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);Object userService1 = applicationContext.getBean("userService");Object userService2 = applicationContext.getBean("userService");System.out.println(userService1);System.out.println(userService2);System.out.println("----------------------------------------------------");Object userMapper3 = applicationContext.getBean("userMapper");Object userMapper4 = applicationContext.getBean("userMapper");System.out.println(userMapper3);System.out.println(userMapper4);}}

如图,获取到的两个Userservice是同一个对象,而UserMapper因为是非单例的,每次获取bean都会创建一次。

6、自动获取bean的名称

上面我们都是通过@Component注解里的value属性获取bean的名称,每次都要写value属性值。我们平时使用@Component注解一般都不会写组件名,会根据类名来获取。

bean名称的默认取值:

1、当类名是以大驼峰命名法,即首单词的首字母大写,其他字母小写,之后每个单词首字母大写(如UserService、UserMapper),组件名为类名首字母大写;

2、当类名是连续的大写字母开头,(如IUserService),组件名为类名;

根据这个规则来对现有代码进行改造,新增一个getBeanName()方法。

package com.example.spring;import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;/*** @author heyunlin* @version 1.0*/
public class AnnotationConfigApplicationContext<T> implements ApplicationContext<T> {/*** 配置类的类对象*/public final Class<T> clazz;/*** 单例对象池*/private final Map<String, Object> singletonObjects = new HashMap<>();private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();public AnnotationConfigApplicationContext(Class<T> clazz) throws ClassNotFoundException {this.clazz = clazz;// 扫描组件componentScan(clazz);}public Map<String, BeanDefinition> getBeanDefinitionMap() {return beanDefinitionMap;}/*** 扫描组件* @param clazz 配置类的类对象* @throws ClassNotFoundException 类找不到*/private void componentScan(Class<T> clazz) throws ClassNotFoundException {// 如果类上使用了@ComponentScan注解if (clazz.isAnnotationPresent(ComponentScan.class)) {ComponentScan componentScan = clazz.getAnnotation(ComponentScan.class);String value = componentScan.value();if (!"".equals(value)) {String path = value;path = path.replace(".", "/");URL resource = clazz.getClassLoader().getResource(path);File file = new File(resource.getFile());loopFor(file);}}}/*** 递归遍历指定文件/文件夹* @param file 文件/文件夹* @throws ClassNotFoundException 类找不到*/private void loopFor(File file) throws ClassNotFoundException {if (file.isDirectory()) {for (File listFile : file.listFiles()) {if (listFile.isDirectory()) {loopFor(listFile);continue;}toBeanDefinitionMap(listFile);}} else if (file.isFile()) {toBeanDefinitionMap(file);}}/*** 解析bean,并保存到Map<String, BeanDefinition>* @param file 解析的class文件* @throws ClassNotFoundException 类找不到*/private void toBeanDefinitionMap(File file) throws ClassNotFoundException {// 获取类的绝对路径String absolutePath = file.getAbsolutePath();// 处理得到类的全限定名absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));absolutePath = absolutePath.replace("\\", ".");// 通过类加载器加载Class<?> loadClass = clazz.getClassLoader().loadClass(absolutePath);String beanName;if (loadClass.isAnnotationPresent(Component.class)) {// 获取@Component注解上配置的组件名Component component = loadClass.getAnnotation(Component.class);beanName = component.value();/******************新增代码*****************/if ("".equals(beanName)) {beanName = getBeanName(loadClass);}/******************新增代码*****************/// 是否懒加载boolean lazy = false;// 作用域String scope = "singleton";// 类上使用了@Scope注解if (loadClass.isAnnotationPresent(Scope.class)) {// 获取@Scope注解Scope annotation = loadClass.getAnnotation(Scope.class);// 单例beanif (isSingleton(annotation.value())) {lazy = loadClass.isAnnotationPresent(Lazy.class);} else {// 非单例scope = annotation.value();}} else {// 类上没有使用@Scope注解,默认是单例的lazy = loadClass.isAnnotationPresent(Lazy.class);}// 保存bean的定义BeanDefinition beanDefinition = new BeanDefinition();beanDefinition.setType(loadClass);beanDefinition.setLazy(lazy);beanDefinition.setScope(scope);beanDefinitionMap.put(beanName, beanDefinition);}}/******************新增代码*****************//*** 根据类对象获取beanName* @param clazz bean的Class对象* @return String bean名称*/private String getBeanName(Class<?> clazz) {String beanName = clazz.getSimpleName();// 判断是否以双大写字母开头String className = beanName.replaceAll("([A-Z])([A-Z])", "$1_$2");// 正常的大驼峰命名:bean名称为类名首字母大写if (className.indexOf("_") != 1) {beanName = beanName.substring(0, 1).toLowerCase().concat(beanName.substring(1));}
//        else { // 否则,bean名称为类名
//            beanName = beanName;
//        }return beanName;}/******************新增代码*****************//*** 判断作用域是否单例* @param scope bean的作用域* @return boolean 如果是单例,返回true,否则返回false*/private boolean isSingleton(String scope) {return "singleton".equals(scope);}@Overridepublic Object getBean(String beanName) {BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);//if (!beanDefinitionMap.containsKey(beanName)) {if (beanDefinition == null) {throw new NullPointerException();}return getBean(beanName, beanDefinition);}@Overridepublic T getBean(Class<T> type) {return null;}@Overridepublic T getBean(String beanName, Class<T> type) {return null;}/*** 统一获取bean的方法* @param beanName bean名称* @param beanDefinition BeanDefinition* @return Object 符合条件的bean对象*/private Object getBean(String beanName, BeanDefinition beanDefinition) {String scope = beanDefinition.getScope();// bean的作用域是单例if (isSingleton(scope)) {Object object = singletonObjects.get(beanName);// 懒加载的单例beanif (object == null) {Object bean = createBean(beanDefinition);singletonObjects.put(beanName, bean);}return singletonObjects.get(beanName);}return createBean(beanDefinition);}/*** 创建bean对象* @param beanDefinition bean的定义* @return Object 创建好的bean对象*/private Object createBean(BeanDefinition beanDefinition) {Object bean = null;Class beanType = beanDefinition.getType();try {// 调用无参构造方法初始化Constructor constructor = beanType.getConstructor();bean = constructor.newInstance();} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {e.printStackTrace();}return bean;}}

然后删除两个Bean上的@Component的value属性值

package com.example.spring;/*** @author heyunlin* @version 1.0*/
@Component
@Scope("prototype")
public class UserMapper {}
package com.example.spring;/*** @author heyunlin* @version 1.0*/
@Component
public class UserService {}

再次运行测试代码,发现一样能够获取到两个bean,且运行结果和上一节相同。

7、依赖注入@Autowired

这节主要实现简单的字段注入(属性注入),在bean初始化完成后对bean上使用了@Autowired注解的字段设置值,当然了,前提是注入的字段类型是一个bean类型。

创建@Autowired注解

package com.example.spring;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author heyunlin* @version 1.0*/
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {boolean required() default true;
}

UserService中注入UserMapper

package com.example.spring;/*** @author heyunlin* @version 1.0*/
@Component
public class UserService {@Autowiredprivate UserMapper userMapper;
}

添加依赖注入的代码

完善代码,只需要修改AnnotationConfigApplicationContext的createBean()方法

    /*** 创建bean对象* @param beanDefinition bean的定义* @return Object 创建好的bean对象*/private Object createBean(BeanDefinition beanDefinition) {Object bean = null;Class beanType = beanDefinition.getType();try {// 调用无参构造方法初始化Constructor constructor = beanType.getConstructor();bean = constructor.newInstance();// 新增:处理字段注入// 获取bean的所有自定义属性Field[] fields = beanType.getDeclaredFields();// 给@Autowired注解标注的属性设置值for (Field field : fields) {if (field.isAnnotationPresent(Autowired.class)) {// 获取bean,并设置到@Autowired注入的属性中Object autowiredBean = getBean(field.getName());field.setAccessible(true);field.set(bean, autowiredBean);}}} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {e.printStackTrace();}return bean;}

测试依赖注入

还是之前的代码,通过debug运行main方法,发现UserMapper确实有值。

完善依赖注入代码

上面的代码只是简单的通过bean名称获取bean,当找不到对应bean名称的bean时,会尝试通过类型查找,因此,在这里需要实现通过bean类型获取bean的方法。

实现通过bean类型获取bean

    @Overridepublic T getBean(Class<T> type) {if (type == null) {throw new IllegalStateException("bean类型不能为空!");}// 保存指定类型的bean的个数AtomicInteger count = new AtomicInteger();// 保存同一类型的beanMap<String, BeanDefinition> objectMap = new HashMap<>();for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {String beanName = entry.getKey();BeanDefinition beanDefinition = entry.getValue();Class beanType = beanDefinition.getType();if (beanType.equals(type)) {count.addAndGet(1);objectMap.put(beanName, beanDefinition);}}if (count.get() == 0 || count.get() > 1) {throw new IllegalStateException();}return (T) getBean((String) objectMap.keySet().toArray()[0], (BeanDefinition) objectMap.values().toArray()[0]);}

修改通过bean名称获取bean的方法

    @Overridepublic Object getBean(String beanName) {if (!beanDefinitionMap.containsKey(beanName)) {return null;}return getBean(beanName, beanDefinitionMap.get(beanName));}

这样的话,当我们通过bean名称获取到的bean为null时,在尝试通过类型查找,当通过类型查找到多个bean时会抛出异常。

/*** 创建bean对象* @param beanDefinition bean的定义* @return Object 创建好的bean对象*/private Object createBean(BeanDefinition beanDefinition) {Object bean = null;Class beanType = beanDefinition.getType();// 获取所有构造方法Constructor[] constructors = beanType.getConstructors();try {/*** 推断构造方法* 1、没有提供构造方法:调用默认的无参构造* 2、提供了构造方法:*   - 构造方法个数为1*     - 构造方法参数个数为0:无参构造*     - 构造方法参数个数不为0:传入多个为空的参数*   - 构造方法个数 > 1:推断失败,抛出异常*/if (isEmpty(constructors)) {// 无参构造方法Constructor constructor = beanType.getConstructor();bean = constructor.newInstance();} else if (constructors.length == 1) {Constructor constructor = constructors[0];// 得到构造方法参数个数int parameterCount = constructor.getParameterCount();if (parameterCount == 0) {// 无参构造方法bean = constructor.newInstance();} else {// 多个参数的构造方法Object[] array = new Object[parameterCount];bean = constructor.newInstance(array);}} else {throw new IllegalStateException();}// 获取bean的所有自定义属性Field[] fields = beanType.getDeclaredFields();// 处理字段注入for (Field field : fields) {if (field.isAnnotationPresent(Autowired.class)) {// 获取bean// 通过bean名称获取beanObject autowiredBean = getBean(field.getName());// 没有找到对应名称的bean,尝试通过bean类型查找if (autowiredBean == null) {autowiredBean = getBean(beanType);}// 并设置到@Autowired注入的属性中field.setAccessible(true);field.set(bean, autowiredBean);}}// 调用InitializingBean的afterPropertiesSet()方法if (bean instanceof InitializingBean) {Method afterPropertiesSet = beanType.getDeclaredMethod("afterPropertiesSet");afterPropertiesSet.invoke(bean);}} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {e.printStackTrace();}return bean;}

8、InitializingBean

InitializingBean是Spring bean生命周期的重要钩子方法之一,在Spring初始化完成后调用。

我们创建一个InitializingBean的实现类,同时使用@Component将该实现类声明为bean。

创建InitializingBean接口

package com.example.spring;/*** @author heyunlin* @version 1.0*/
public interface InitializingBean {void afterPropertiesSet();
}

创建InitializingBean接口的实现类

package com.example.spring;import com.example.spring.annotation.Component;/*** @author heyunlin* @version 1.0*/
@Component
public class InitializingBeanImpl implements InitializingBean {@Overridepublic void afterPropertiesSet() {System.out.println("执行InitializingBean.afterPropertiesSet()");}}

在创建bean的时候判断该bean是否InitializingBean接口的实现类,如果是,调用其afterPropertiesSet()方法,代码很简单。

/*** 创建bean对象* @param beanDefinition bean的定义* @return Object 创建好的bean对象*/private Object createBean(BeanDefinition beanDefinition) {Object bean = null;Class beanType = beanDefinition.getType();try {// 调用无参构造方法初始化Constructor constructor = beanType.getConstructor();bean = constructor.newInstance();// 获取bean的所有自定义属性Field[] fields = beanType.getDeclaredFields();// 处理字段注入for (Field field : fields) {if (field.isAnnotationPresent(Autowired.class)) {// 获取bean,并设置到@Autowired注入的属性中Object autowiredBean = getBean(field.getName());field.setAccessible(true);field.set(bean, autowiredBean);}}// 新增:调用InitializingBean的afterPropertiesSet()方法if (bean instanceof InitializingBean) {Method afterPropertiesSet = beanType.getDeclaredMethod("afterPropertiesSet");afterPropertiesSet.invoke(bean);}} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {e.printStackTrace();}return bean;}

9、@Configuration + @Bean

上面的代码完成了@Component注解的隐式声明的bean的扫描,接下来完成@Bean注解配置的bean扫描。(@Configuration注解标注的类中,@Bean注解标注方法的返回值是bean的类型,方法名就是bean的名称)

创建@Configuration注解

package com.example.spring;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author heyunlin* @version 1.0*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Configuration {}

创建@Bean注解

package com.example.spring;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author heyunlin* @version 1.0*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {String value() default "";
}

创建一个UserController

package com.example.spring;/*** @author heyunlin* @version 1.0*/
public class UserController {}

创建配置类MyConfig

package com.example.spring;/*** @author heyunlin* @version 1.0*/
@Configuration
public class MyConfig {@Beanpublic UserController userController() {return new UserController();}}

测试获取bean

package com.example.spring;/*** @author heyunlin* @version 1.0*/
public class SpringExample {public static void main(String[] args) throws ClassNotFoundException {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);Object userController1 = applicationContext.getBean("userController");Object userController2 = applicationContext.getBean("userController");System.out.println(userController1);System.out.println(userController2);}}

运行代码,发现确实获取到了两个相同的bean

9、未完待续

文章和代码持续更新中,敬请期待~

手写Spring Framework源码https://gitee.com/he-yunlin/spring.git

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习笔记|认识数码管|控制原理|数码管实现0-9的显示|段码跟位码|STC32G单片机视频开发教程(冲哥)|第九集:数码管静态显示

文章目录 1.认识数码管2.控制原理十进制转换为任意进制其它进制转十进制 3.数码管实现0-9的显示1.用数组定义0-9的内码段码跟位码的区别2.尝试用延时实现0-9的循环显示3.用按键控制数字的加或者减。 总结课后练习&#xff1a; 1.认识数码管 数码管按段数可分为七段数码管和八段…

树莓派4B上安装Gitlab

参考连接&#xff1a; 树莓派上使用 GitLab 搭建专业 Git 服务 | 树莓派实验室 gitlab reconfigure 卡住 ruby_block[wait for redis service socket] action run_芹菜学长的博客-CSDN博客 以及用到了讯飞星火 系统版本信息 1.进入 giblab安装页面gitlab/gitlab-ce - Instal…

BATPowerShell实现本地文件自动上传FTP服务器

运维工作中&#xff0c;经常需要一些脚本来实现自动化&#xff0c;今天分享本地文件自动上传FTP的两种解决办法&#xff1a; 一、使用BAT自动上传FTP 使用批处理&#xff08;BAT&#xff09;命令文件将本地文件夹内容上传到FTP服务器需要使用Windows自带的命令行工具&#xf…

QT基础使用:组件和代码关联(信号和槽)

自动关联 ui文件在设计环境下&#xff0c;能看到的组件可以使用鼠标右键选择“转到槽”就是开始组件和动作关联。 在自动关联这个过程中软件自动动作的部分 需要对前面头文件进行保存&#xff0c;才能使得声明的函数能够使用。为了方便&#xff0c;自动关联时先对所有文件…

javaee idea创建maven项目,然后创建servlet

idea创建maven项目 参考我的上一篇博客点击查看 创建servlet 步骤一 引入依赖 步骤二 新建directory并设置mark directory as 步骤三 新建package和servlet

达梦数据库分区表介绍

概述 本文将对达梦数据库分区表概念、创建、维护进行介绍。 1.分区表概念 1.1 分区表使用场景 近几年&#xff0c;随着移动支付快速发展&#xff0c;银行交易系统中【移动小微支付场景】使用越来越多&#xff0c;系统中流水账单表数据量巨大&#xff0c;往往上TB。 为了提高…

Redis使用

环境配置 代码实现 Java public CoursePublish getCoursePublishCache(Long courseId){//查询缓存Object jsonObj redisTemplate.opsForValue().get("course:" courseId);if(jsonObj!null){String jsonString jsonObj.toString();System.out.println("从缓…

构建与应用大数据环境:从搭建到开发与组件使用的全面指南

文章目录 环境搭建开发与组件使用性能优化与监控安全与隐私总结 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 ✨收录专栏&#xff1a;大数据系列 ✨文章内容&#xff1a; &#x1f91d;希望作者…

物种气候生态位动态量化与分布特征模拟

在全球气候快速变化的背景下&#xff0c;理解并预测生物种群如何应对气候变化&#xff0c;特别是它们的地理分布如何变化&#xff0c;已经变得至关重要。利用R语言进行物种气候生态位动态量化与分布特征模拟&#xff0c;不仅可以量化描述物种对环境的需求和适应性&#xff0c;预…

使用 wxPython 和 pymupdf进行 PDF 加密

PDF 文件是一种常见的文档格式&#xff0c;但有时候我们希望对敏感信息进行保护&#xff0c;以防止未经授权的访问。在本文中&#xff0c;我们将使用 Python 和 wxPython 库创建一个简单的图形用户界面&#xff08;GUI&#xff09;应用程序&#xff0c;用于对 PDF 文件进行加密…

Apache Paimon 实时数据湖 Streaming Lakehouse 的存储底座

摘要&#xff1a;本文整理自阿里云开源大数据表存储团队负责人&#xff0c;阿里巴巴高级技术专家李劲松&#xff08;之信&#xff09;&#xff0c;在 Streaming Lakehouse Meetup 的分享。内容主要分为四个部分&#xff1a; 流计算邂逅数据湖 Paimon CDC 实时入湖 Paimon 不止…

MLCC产生噪音的原因及解决方案

1.内部构造及工作原理 MLCC是Multilayer Ceramic Capacitor多层片式陶瓷电容 决定电容容值大小的主要参数&#xff1a; 真空介电率 相对介电常数K&#xff1a;和MLCC使用材料有关的常数 有效面积S 介电层厚度d 堆叠层数N 所以面积越大堆叠层数越多的MLCC容值越高 2.MLCC产生啸…

SpringCloud教程 | 第二篇: 服务消费者(rest+ribbon)

在上一篇文章&#xff0c;讲了服务的注册和发现。在微服务架构中&#xff0c;业务都会被拆分成一个独立的服务&#xff0c;服务与服务的通讯是基于http restful的。Spring cloud有两种服务调用方式&#xff0c;一种是ribbonrestTemplate&#xff0c;另一种是feign。在这一篇文章…

提高企业会计效率,选择Manager for Mac(企业会计软件)

作为一家企业&#xff0c;良好的财务管理是保持业务运转的关键。而选择一款适合自己企业的会计软件&#xff0c;能够帮助提高会计效率、减少错误和节约时间。在众多的选择中&#xff0c;Manager for Mac(企业会计软件)是一款值得考虑的优秀软件。 首先&#xff0c;Manager for…

【原创】jmeter并发测试计划

bankQPS 创建线程组 设置并发参数 HTTP请求GET 添加HTTP请求 GET请求 查看结果树 HTTP请求 POST 添加HTTP请求 参数必须设置头信息格式&#xff1a; 添加HTTP头信息 查看结果树 可以选择&#xff0c;仅查看错误日志 汇总报告

基于微信小程序的宠物领养平台的设计与实现(Java+spring boot+微信小程序+MySQL)

获取源码或者论文请私信博主 演示视频&#xff1a; 基于微信小程序的宠物领养平台的设计与实现&#xff08;Javaspring boot微信小程序MySQL&#xff09; 使用技术&#xff1a; 前端&#xff1a;html css javascript jQuery ajax thymeleaf 微信小程序 后端&#xff1a;Java…

【项目经理】项目管理杂谈

杂谈 1. 走上管理岗位&#xff0c;别再自己埋头干了2. 如何更好地管理项目进度3. 管理是“管事”而不是“管人”4. 让领导欣赏的十个沟通技巧在这里插入图片描述 1. 走上管理岗位&#xff0c;别再自己埋头干了 2. 如何更好地管理项目进度 3. 管理是“管事”而不是“管人” 4. 让…

(动态规划) 剑指 Offer 48. 最长不含重复字符的子字符串 ——【Leetcode每日一题】

❓剑指 Offer 48. 最长不含重复字符的子字符串 难度&#xff1a;中等 请从字符串中找出一个最长的不包含重复字符的子字符串&#xff0c;计算该最长子字符串的长度。 示例 1: 输入: “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”&#xff0c;所以其长度为…

用Python写一个武侠游戏

前言 在本教程中&#xff0c;我们将使用Python写一个武侠类的游戏&#xff0c;大的框架全部搭好了&#xff0c;很多元素都可以自己添加&#xff0c;让游戏更丰富 &#x1f4dd;个人主页→数据挖掘博主ZTLJQ的主页 个人推荐python学习系列&#xff1a; ☄️爬虫JS逆向系列专栏 -…

JavaScript设计模式(一)——构造器模式、原型模式、类模式

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…