手写Spring(1):IOC容器创建

17 阅读11分钟

实现AOP - 手写Spring - 廖雪峰的官方网站

实现IOC

实现ResourceResolver

Java的ClassLoader机制可以在指定的Classpath中根据类名加载指定的Class,但遗憾的是,给出一个包名,例如,org.example,它并不能获取到该包下的所有Class,也不能获取子包。要在Classpath中扫描指定包名下的所有Class,包括子包,实际上是在Classpath中搜索所有文件,找出文件名匹配的.class文件。例如,Classpath中搜索的文件org/example/Hello.class就符合包名org.example,我们需要根据文件路径把它变为org.example.Hello,就相当于获得了类名。因此,搜索Class变成了搜索文件。

提供一个ResourceResolver,定义scan()方法来获取扫描到的Resource

public class ResourceResolver {
    String basePackage;

    public ResourceResolver(String basePackage) {
        this.basePackage = basePackage;
    }

    public <R> List<R> scan(Function<Resource, R> mapper) {
        ...
    }
}

过滤Class:

// 定义一个扫描器:
ResourceResolver rr = new ResourceResolver("org.example");
List<String> classList = rr.scan(res -> {
    String name = res.name(); // 资源名称"org/example/Hello.class"
    if (name.endsWith(".class")) { // 如果以.class结尾
        // 把"org/example/Hello.class"变为"org.example.Hello":
        return name.substring(0, name.length() - 6).replace("/", ".").replace("\\", ".");
    }
    // 否则返回null表示不是有效的Class Name:
    return null;
});

实现PropertyResolver

Spring框架中,注入分为@Autowired和@Value两种,第一种依赖bean注入,而第二中只是简单

的属性注入,所以,我们可以创建一个类。存放配置的属性,并提供方法来获取属性

Java中提供了按照key查询的Properties,我们可以将其传入

PropertyResolver

public PropertyResolver(Properties props) {  
    //获取环境变量  
    this.properties.putAll(System.getenv());  
    Set<String> names = props.stringPropertyNames();  
    for (String name : names) {  
        this.properties.put(name, props.getProperty(name));  
    }  
    if (logger.isDebugEnabled()) {  
        List<String> keys = new ArrayList<>(this.properties.keySet());  
        Collections.sort(keys);  
        for (Iterator<String> it = keys.iterator(); it.hasNext();) {  
            String key = it.next();  
            logger.debug("PropertyResolver: {} = {}", key, this.properties.get(key));  
        }  
    }  
  
    converters.put(String.class, s -> s);  
    converters.put(boolean.class, s -> Boolean.parseBoolean(s));  
    converters.put(Boolean.class, s -> Boolean.valueOf(s));  
  
    converters.put(byte.class, s -> Byte.parseByte(s));  
    converters.put(Byte.class, s -> Byte.valueOf(s));  
  
    converters.put(short.class, s -> Short.parseShort(s));  
    converters.put(Short.class, s -> Short.valueOf(s));  
  
    converters.put(int.class, s -> Integer.parseInt(s));  
    converters.put(Integer.class, s -> Integer.valueOf(s));  
  
    converters.put(long.class, s -> Long.parseLong(s));  
    converters.put(Long.class, s -> Long.valueOf(s));  
  
    converters.put(float.class, s -> Float.parseFloat(s));  
    converters.put(Float.class, s -> Float.valueOf(s));  
  
    converters.put(double.class, s -> Double.parseDouble(s));  
    converters.put(Double.class, s -> Double.valueOf(s));  
  
    converters.put(LocalDate.class, s -> LocalDate.parse(s));  
    converters.put(LocalTime.class, s -> LocalTime.parse(s));  
    converters.put(LocalDateTime.class, s -> LocalDateTime.parse(s));  
    converters.put(ZonedDateTime.class, s -> ZonedDateTime.parse(s));  
    converters.put(Duration.class, s -> Duration.parse(s));  
    converters.put(ZoneId.class, s -> ZoneId.of(s));  
}

获取键值

解析${abc.xyz:defaultValue}这样的key,我们需要定义一个实体类,传入key和默认值,所以在获取键值(getProperty(String key))的方法中,我们需要判断是否存在默认值(parsePropertyExpr(String key)),如果不存在默认值,则将属性defaultValue置为NULL

转换类型

方法

<T> T convert(Class<?> clazz, String value) {  
    Function<String, Object> fn = this.converters.get(clazz);  
    if (fn == null) {  
        throw new IllegalArgumentException("Unsupported value type: " + clazz.getName());  
    }  
    return (T) fn.apply(value);  
}

存储

Map<Class<?>, Function<String, Object>> converters = new HashMap<>();
键为要转化的对象,所以我们可以通过Function类型来实现注入类型的转换

传入key的思考

如果key所对应的值也存在占位符的使用,那么应该如何处理:

定义一个函数专门进行处理占位符,如果存在占位符,递归重新获取键值,没有则返回原值

函数调用总览

getProperty(String key)
├── parsePropertyExpr(key)
├── getRequiredProperty(key)
│   └── getProperty(key)
├── parseValue(value)
│   ├── parsePropertyExpr(value)
│   ├── getProperty(key, defaultValue)
│   └── getRequiredProperty(key)
└── 返回结果

getProperty(String key, String defaultValue)
├── getProperty(key)
└── parseValue(defaultValue)

getProperty(String key, Class<T> targetType)
├── getProperty(key)
└── convert(targetType, value)

getRequiredProperty(String key, Class<T> targetType)
├── getProperty(key, targetType)
└── Objects.requireNonNull()

核心:getProperty(String key) 调用后先判断是否是占位符格式,如果是,将其转换成指定的实体 类,再次进行判断:如果有默认值->获取默认值,没有则获取属性值

getProperty(String key, Class<> targetType)和getRequiredProperty(String key, Class<> targetType)是对注入的属性值进行类型转换

创建BeanDefinition

我们可以定义BeanDefinitiin这样一个类来管理Bean的信息,包括:名称,声明类型,构造器方法,工厂方法名称,工厂方法,声明类型,Bean的实例,Bean的顺序,对外提供工厂方法和构造器方法的有参构造

定义好类型以后,我们可以设置一个Map<>类型的变量,按照名字存储Bean信息


public class AnnotationConfigApplicationContext {
    Map<String, BeanDefinition> beans;
}

但是如果我们按照名字查找Bean的话,只能返回一个实例或者无实例,使用上存在一定的局限,但如果按照类查询的话,无法定义以class为键的Map类型,因为可能声明类型和实际类型不一定相符,如果不进行标注,可能返回多个实例

@Configuration
public class AppConfig {
    @Bean
    AtomicInteger counter() {
        return new AtomicInteger();
    }
    
    @Bean
    Number bigInt() {
        return new BigInteger("1000000000");
    }
}

所以我们需要定义一个方法,根据类型查找出所有满足条件的BeanDefinition,然后再定义一个方法,如果有唯一标识,则返回特定BeanDefinition

// 根据Type查找若干个BeanDefinition,返回0个或多个:
List<BeanDefinition> findBeanDefinitions(Class<?> type) {
    return this.beans.values().stream()
        // 按类型过滤:
        .filter(def -> type.isAssignableFrom(def.getBeanClass()))
        // 排序:
        .sorted().collect(Collectors.toList());
    }
}

// 根据Type查找某个BeanDefinition,如果不存在返回null,如果存在多个返回@Primary标注的一个:
@Nullable
public BeanDefinition findBeanDefinition(Class<?> type) {
    List<BeanDefinition> defs = findBeanDefinitions(type);
    if (defs.isEmpty()) { // 没有找到任何BeanDefinition
        return null;
    }
    if (defs.size() == 1) { // 找到唯一一个
        return defs.get(0);
    }
    // 多于一个时,查找@Primary:
    List<BeanDefinition> primaryDefs = defs.stream().filter(def -> def.isPrimary()).collect(Collectors.toList());
    if (primaryDefs.size() == 1) { // @Primary唯一
        return primaryDefs.get(0);
    }
    if (primaryDefs.isEmpty()) { // 不存在@Primary
        throw new NoUniqueBeanDefinitionException(String.format("Multiple bean with type '%s' found, but no @Primary specified.", type.getName()));
    } else { // @Primary不唯一
        throw new NoUniqueBeanDefinitionException(String.format("Multiple bean with type '%s' found, and multiple @Primary specified.", type.getName()));
    }
}

所以主要流程:

1. 构造函数调用
   ↓
2. 扫描配置类及其关联的 Bean 类名
   → 获取 @ComponentScan 指定的包(默认为 configClass 所在包)
   → 使用 ResourceResolver 扫描这些包下的所有 .class 文件 → 提取全限定类名
   → 同时收集 @Import 导入的配置类名
   ↓
3. 得到一组候选类名 Set<String> classNameSet
   ↓
4. 遍历每个类名,加载 Class 对象
   → Class.forName(className)
   ↓
5. 判断该 Class 是否是“组件”(即是否间接或直接标注了 @Component)
   → 通过 ClassUtils.findAnnotation(clazz, Component.class) 递归查找元注解
   ↓
6. 若是组件:
   → 提取 Bean 名称(默认为首字母小写的类名)
   → 获取合适的构造方法(用于后续实例化)
   → 查找 @Order@Primary@PostConstruct@PreDestroy 等注解信息
   → 创建 BeanDefinition(类型为 clazz,无工厂方法)
   → 注册到 Map<String, BeanDefinition> beans
   ↓
7. 若该组件同时是 @Configuration(即工厂类):
   → 遍历其所有方法
   → 查找带有 @Bean 注解的方法
      → 提取 Bean 名称(默认为方法名)
      → 声明类型 = 方法返回类型(注意:不一定是实际运行时类型!)
      → 工厂方法 = 该 Method 对象
      → 工厂 Bean 名 = 配置类的 Bean 名(用于后续调用实例方法)
      → 读取 @Bean(initMethod="...", destroyMethod="...")
      → 创建 BeanDefinition(带 factoryName + factoryMethod)
      → 注册到 beans Map8. 最终得到完整的 Map<String, BeanDefinition> beans
   ↓
9. (后续步骤,虽未实现但隐含):
   → 按依赖顺序实例化 Bean(先实例化 @Configuration 工厂类)
   → 调用构造方法 或 工厂方法 创建 instance
   → 注入依赖(字段/方法/setter)
   → 调用初始化方法(@PostConstruct / init-method)

创建Bean实例

Spring提供了四种注入方法:构造方法注入,工厂方法注入,Setter注入,字段注入

前两种方法Bean的创建和注入是不可分离的,后两种可以先创建,后注入

因为创建和注入是不可分离的,所以如果遇到循环注入就只能抛出异常

检测循环依赖

if (!this.creatingBeanNames.add(def.getName())) {  
    throw new UnsatisfiedDependencyException(String.format("Circular dependency detected when create bean '%s'", def.getName()));  
}

尝试将当前创建的Bean姓名进行存放,如果已经创建,说明存在循环依赖,抛出异常

逻辑:

createBeanAsSingleton 有三种入口

  1. 创建@Configuration类型的Bean

  2. 创建普通的Bean

  3. 递归执行

第一种方式必然不会循环依赖

第二种方式在执行前会检查instance是否已赋值,所以不会让creatingBeanNames重复

只有第三者情况,即在递归执行createBeanAsSingleton时,才可能存在重复;如果遇到重复的Bean,说明之前的Bean还没创建完成,也就说明有了循环依赖

创建时先创建配置类,再创建普通Bean

核心逻辑:通过递归循环调用依赖创建进行注入,如果构造函数或者工厂方法的参数又进行了依赖

注入,可以再次调用方法创建实例返回,通过反射获取构造函数及其参数,创建实例,完成依赖注

private Object createBeanAsEarlySingleton(BeanDefinition def) {  
        log.debug("Try create bean '{}' as early singleton: {}", def.getName(), def.getBeanClass().getName());  
        //检测循环依赖  
        if (!this.creatingBeanNames.add(def.getName())) {  
            throw new UnsatisfiedDependencyException(String.format("Circular dependency detected when create bean '%s'", def.getName()));  
        }  
        //创建方式:构造函数或工厂方法  
        //Executable:统一处理构造函数和工厂方法  
        Executable  createFn=null;  
        if (def.getFactoryName()== null){  
            createFn=def.getConstructor();  
        }else {  
            createFn=def.getFactoryMethod();  
        }  
        // 创建参数:  
        final Parameter[] parameters = createFn.getParameters();  
        final Annotation[][] parametersAnnos = createFn.getParameterAnnotations();  
        Object[] args = new Object[parameters.length];  
        for (int i = 0; i < parameters.length; i++) {  
            final Parameter param = parameters[i];  
            final Annotation[] paramAnnos = parametersAnnos[i];  
            final Value value = ClassUtils.getAnnotation(paramAnnos, Value.class);  
            final Autowired autowired = ClassUtils.getAnnotation(paramAnnos, Autowired.class);  
  
            // @Configuration类型的Bean是工厂,不允许使用@Autowired创建:  
            final boolean isConfiguration = isConfigurationDefinition(def);  
            if (isConfiguration && autowired != null) {  
                throw new BeanCreationException(  
                        String.format("Cannot specify @Autowired when create @Configuration bean '%s': %s.", def.getName(), def.getBeanClass().getName()));  
            }  
  
            // 参数需要@Value或@Autowired两者之一:  
            if (value != null && autowired != null) {  
                throw new BeanCreationException(  
                        String.format("Cannot specify both @Autowired and @Value when create bean '%s': %s.", def.getName(), def.getBeanClass().getName()));  
            }  
            if (value == null && autowired == null) {  
                throw new BeanCreationException(  
                        String.format("Must specify @Autowired or @Value when create bean '%s': %s.", def.getName(), def.getBeanClass().getName()));  
            }  
            // 参数类型:  
            final Class<?> type = param.getType();  
            if (value != null) {  
                // 参数是@Value:  
                args[i] = this.propertyResolver.getRequiredProperty(value.value(), type);  
            } else {  
                // 参数是@Autowired:  
                String name = autowired.name();  
                boolean required = autowired.value();  
                // 依赖的BeanDefinition:  
                BeanDefinition dependsOnDef = name.isEmpty() ? findBeanDefinition(type) : findBeanDefinition(name, type);  
                // 检测required==true?  
                if (required && dependsOnDef == null) {  
                    throw new BeanCreationException(String.format("Missing autowired bean with type '%s' when create bean '%s': %s.", type.getName(),  
                            def.getName(), def.getBeanClass().getName()));  
                }  
                if (dependsOnDef != null) {  
                    // 获取依赖Bean:  
                    Object autowiredBeanInstance = dependsOnDef.getInstance();  
                    if (autowiredBeanInstance == null && !isConfiguration) {  
                        // 当前依赖Bean尚未初始化,递归调用初始化该依赖Bean:  
                        autowiredBeanInstance = createBeanAsEarlySingleton(dependsOnDef);  
                    }  
                    args[i] = autowiredBeanInstance;  
                } else {  
                    args[i] = null;  
                }  
            }  
        }  
  
        //创建Bean实例  
        Object instance=null;  
        //构造函数创建  
        if(def.getFactoryName()== null){  
            try {  
                //注入过程:创建Bean实例  
                instance = def.getConstructor().newInstance(args);  
            }catch (Exception e){  
                throw new BeanCreationException(e);  
            }  
        }else {  
//            获取配置类实例:通过 getBean(def.getFactoryName()) 获取定义了 @Bean 方法的配置类实例  
//            调用工厂方法:使用 def.getFactoryMethod().invoke(configInstance, args) 调用 @Bean 注解的方法来创建 Bean 实例  
//            异常处理:如果调用过程中发生异常,包装成 BeanCreationException 抛出  
//            设置实例:通过 def.setInstance(instance) 将创建好的实例设置到 BeanDefinition 中  
//            返回实例:最终返回创建的 Bean 实例  
            // 用@Bean方法创建:  
            Object configInstance = getBean(def.getFactoryName());  
            try {  
                instance = def.getFactoryMethod().invoke(configInstance, args);  
            } catch (Exception e) {  
                throw new BeanCreationException(String.format("Exception when create bean '%s': %s", def.getName(), def.getBeanClass().getName()), e);  
            }  
        }  
        //设置实例  
        def.setInstance(instance);  
        return def.getInstance();  
  
    }

初始化Bean

在创建Bean过程中,我们实现了强依赖注入,接下来实现字段和Setter注入

使用Setter方法和字段注入时,要注意一点,就是不仅要在当前类查找,还要在父类查找,因为有些@Autowired写在父类,所有子类都可使用,这样更方便。注入弱依赖代码如下:

// 在当前类及父类进行字段和方法注入:
void injectProperties(BeanDefinition def, Class<?> clazz, Object bean) {
    // 在当前类查找Field和Method并注入:
    for (Field f : clazz.getDeclaredFields()) {
        tryInjectProperties(def, clazz, bean, f);
    }
    for (Method m : clazz.getDeclaredMethods()) {
        tryInjectProperties(def, clazz, bean, m);
    }
    // 在父类查找Field和Method并注入:
    Class<?> superClazz = clazz.getSuperclass();
    if (superClazz != null) {
        // 递归调用:
        injectProperties(def, superClazz, bean);
    }
}

// 注入单个属性
void tryInjectProperties(BeanDefinition def, Class<?> clazz, Object bean, AccessibleObject acc) {
    ...
}

注入完依赖之后,在对BeanDifinition进行遍历,执行init方法

实现BeanPostProcessor

BeanPostProcessor的出现改变了这一切。Spring允许用户自定义一种特殊的Bean,即实现了BeanPostProcessor接口,它有什么用呢?其实就是替换Bean。

例子:

@Configuration
@ComponentScan
public class AppConfig {

    public static void main(String[] args) {
        var ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        // 可以获取到ZonedDateTime:
        ZonedDateTime dt = ctx.getBean(ZonedDateTime.class);
        System.out.println(dt);
        // 错误:NoSuchBeanDefinitionException:
        System.out.println(ctx.getBean(LocalDateTime.class));
    }

    // 创建LocalDateTime实例
    @Bean
    public LocalDateTime localDateTime() {
        return LocalDateTime.now();
    }

    // 实现一个BeanPostProcessor
    @Bean
    BeanPostProcessor replaceLocalDateTime() {
        return new BeanPostProcessor() {
            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                // 将LocalDateTime类型实例替换为ZonedDateTime类型实例:
                if (bean instanceof LocalDateTime) {
                    return ZonedDateTime.now();
                }
                return bean;
            }
        };
    }
}

定义一个代理类继承原始Bean,通过BeanPostProcessor的postProcessBeforeInitialization()

方法将原始Bean替换为代理类,并将BeanDenifition中对应的实现类进行替换

但是由此引发了一个问题,当我们想对这个Bean注入依赖的时候,是注入到原始Bean还是代理Bean上呢,举个例子

@Configuration
@ComponentScan
public class AppConfig {

    public static void main(String[] args) {
        var ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService u = ctx.getBean(UserService.class);
        System.out.println(u.getClass().getSimpleName()); // UserServiceProxy
        u.register("bob@example.com", "bob12345");
    }

    @Bean
    BeanPostProcessor createProxy() {
        return new BeanPostProcessor() {
            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                // 实现事务功能:
                if (bean instanceof UserService u) {
                    return new UserServiceProxy(u);
                }
                return bean;
            }
        };
    }
}

@Component
class UserService {
    public void register(String email, String password) {
        System.out.println("INSERT INTO ...");
    }
}

// 代理类:
class UserServiceProxy extends UserService {
    UserService target;

    public UserServiceProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void register(String email, String password) {
        System.out.println("begin tx");
        target.register(email, password);
        System.out.println("commit tx");
    }
}

我们创建的代理类实现了事务功能,但是方法还是调用的原始Bean,也就是说必须对原始Bean进行

注入,否则在调用原始方法时会进行报错

两条原则:

  1. 一个Bean如果被Proxy替换,则依赖它的Bean应注入Proxy,即上图的MvcController应注入UserServiceProxy
  2. 一个Bean如果被Proxy替换,如果要注入依赖,则应该注入到原始对象,即上图的JdbcTemplate应注入到原始的UserService

基于这个原则,要满足条件1是很容易的,因为只要创建Bean完成后,立刻调用BeanPostProcessor就实现了替换,后续其他Bean引用的肯定就是Proxy了。先改造创建Bean的流程,在创建@Configuration后,接着创建BeanPostProcessor,再创建其他普通Bean:


 // 创建BeanPostProcessor类型的Bean:
    List<BeanPostProcessor> processors = this.beans.values().stream()
            // 过滤出BeanPostProcessor:
            .filter(this::isBeanPostProcessorDefinition)
            // 排序:
            .sorted()
            // 创建BeanPostProcessor实例:
            .map(def -> {
                return (BeanPostProcessor) createBeanAsEarlySingleton(def);
            }).collect(Collectors.toList());
    this.beanPostProcessors.addAll(processors);

替换实例

public Object createBeanAsEarlySingleton(BeanDefinition def) {
    ...

    // 创建Bean实例:
    Object instance = ...;
    def.setInstance(instance);

    // 调用BeanPostProcessor处理Bean:
    for (BeanPostProcessor processor : beanPostProcessors) {
        Object processed = processor.postProcessBeforeInitialization(def.getInstance(), def.getName());
        // 如果一个BeanPostProcessor替换了原始Bean,则更新Bean的引用:
        if (def.getInstance() != processed) {
            def.setInstance(processed);
        }
    }
    return def.getInstance();
}

这时,对这个Bean进行依赖注入会有问题,因为注入的是Proxy而不是原始Bean,怎么办?

这时我们要思考原始Bean去哪了?原始Bean实际上是被BeanPostProcessor给丢了!如果BeanPostProcessor能保存原始Bean,那么,注入前先找到原始Bean,就可以把依赖正确地注入给原始Bean。我们给BeanPostProcessor加一个postProcessOnSetProperty()方法,让它返回原始Bean:

我们可以重写BeanPostProcessor中的 postProcessOnSetProperty()方法,定义一个Map集合保留原始Bean,并返回原始Bean

例如:

// 在AroundProxyBeanPostProcessor中的实现:
public class AroundProxyBeanPostProcessor implements BeanPostProcessor {
    Map<String, Object> originBeans = new HashMap<>();  // 保存了原始Bean的引用
    
    @Override
    public Object postProcessOnSetProperty(Object bean, String beanName) {
        Object origin = this.originBeans.get(beanName);  // 查找原始Bean
        return origin != null ? origin : bean;  // 返回原始Bean或保持当前Bean
    }
}