# my-spring **Repository Path**: rong-kang/my-spring ## Basic Information - **Project Name**: my-spring - **Description**: 从0到1手写一个简化版Spring框架 —— begin:2023-04-12 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-04-12 - **Last Updated**: 2024-05-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: Java, Spring, 源码 ## README [TOC] # 1. Bean对象的定义、注册和获取 简单实现了Bean对象的定义、注册和获取(使用了设计模式中的行为型模式——模板方法模式)。 Bean注册到容器中不需要传递一个以及实例化的Bean对象(而是传递一个Class类),而是放进容器的Bean均为单例。 在工厂获取Bean的时候,获取的是单例的Bean,也就是单例Map中存储的Bean。 如果单例Map中不存在这个Bean,则会根据Bean的名字,从Bean定义信息的Map中获取Bean的定义类(BeanDefinition)。 然后通过其Class类创建Bean,并且放进单例Map中。 --- # 2. 使用Cglib实现构造函数含参数的实例化策略 前面的Bean对象定义、注册和获取都是基于无参实现的,也就是注入到容器中的Bean必须是无参构造,如果想要使用有参构造,那么程序就会发生报错。 想要创建含有参数的构造函数的Bean对象,有两种方法,第一种就是使用JDK的`DeclaredConstructor`;另一种就是使用Cglib动态创建Bean对象。 由于有两种方法,为了更好拓展,使用了设计模式中的行为模式——策略模式来设计。 如果想要修改创建Bean的方式,只需要修改`AbstractAutowireCapableBeanFactory`的私有成员变量`instantiationStrategy`为JDK实现即可。 在程序中`CglibSubclassingInstantiationStrategy`和`SimpleInstantiationStrategy`实现了`InstantiationStrategy`,其中前者是Cglib实现,后者为Jdk实现。 并且在`InstantiationStrategy`中,提供了一个含构造参数的获取Bean的方法,并且对获取Bean的方法进行了封装。 --- # 3. 注入属性和依赖对象 进一步将将创建对象的实例化细分,注入的属性不再局限于int、long、String等,还包括没有被实例化的对象。 定义一个`BeanRefernence`引用对象,里面只有一个简单的Bean名称,这个当作一个属性中被注入的Bean。 并且还创建一个`PropertyValues`的集合存放各种属性。`PropertyValues`集合存放的是`PropertyValue`类。 该类只有两个成员变量,分别是属性名称和属性值。可以说是一个key-value的类。 `BeanDefinition`也做了修改,`BeanDefinition`新增了一个`PropertyValues`的成员变量,代表这个Bean的属性信息。 在`AbstractAutowireCapableBeanFactory`的`createBean`中,会首先创建一个Bean,但是这个Bean只是被实例化了,成员变量并没有被赋值。 这时候调用`applyPropertyValues`方法进行成员变量赋值。 遍历`BeanDefinition`中的`PropertyValues`,如果发现`PropertyValue`的`value`是`BeanReference`类型,说明这个value是一个Bean,需要在容器中寻找,于是就需要在容器中获取该Bean。 > 但是,一定一定要注意,要先将这个Bean注册到容器中,否则,会抛出异常说这个Bean没有被定义 之后,通过hutool的工具类,对bean的成员变量赋值即可。 > 注意,这里并没有考虑到循环依赖这种现象。 --- # 4. 实现资源加载器通过加载资源注册Bean 前面的实现中,都需要手动完成Bean对象的定义、注册和属性填充。 真正的Spring框架将Bean的注册、属性的填充、Bean的定义都整合到Spring配置文件中。 往现有的MySpring框架中添加一个资源加载器`ResourceLoader`,它的实现类为`DefaultResourceLoader`,让MySpring框架可以用来读取ClassPath、本地文件(File)和远程云文件(HTTP文件)的配置内容。 并且定义了一个`Resource`接口,里面有一个方法`getInputStream`,获取输入流。 对于`Resource`接口,有三个实现类,分别对应了ClassPath、本地文件(File)和远程云文件(HTTP文件),分别为`ClassPathResource`、`FileSystemResource`、`UrlResource`。 为了读取配置文件注册Bean,定义了一个`BeanDefinitionReader`接口,主要有3个方法 1. `getRegistry`:获取Bean定义注册器 2. `getResourceLoader`:获取资源加载器 3. `loadBeanDefinitions`:加载Bean定义 其中`AbstractBeanDefinitionReader`实现了前两个方法,这是单一职责的体现。 而第三个方法以及其重载由`XmlBeanDefinitionReader`实现。 在`loadBeanDefinitions`获取到`Resource`的输入流后 通过`doLoadBeanDefinitions`方法利用`hutool`的`readXML`读取XML配置文件。 通过解析标签后定义一个Bean对象,并且解析标签构造其属性进而完成属性填充。 --- # 5. 实现上下文和MySpring框架的组件扩展 前面实现了通过资源加载器加载配置文件中的Bean,这其中BeanFactory的定义、Bean的注册、Bean属性的填充等都是需要手动来完成。 ```java // 使用UserService 填充属性 (uId, userDao) PropertyValues propertyValues = new PropertyValues(); propertyValues.addPropertyValue(new PropertyValue("uId", "10001")); propertyValues.addPropertyValue(new PropertyValue("userDao", new BeanReference("userDao"))); // 使用UserService注册Bean对象 BeanDefinition userServiceBeanDefinition = new BeanDefinition(UserService.class, propertyValues); beanFactory.registerBeanDefinition("userService", userServiceBeanDefinition); ``` 引入上下文,使得用户使用MySpring的时候,只需要使用`ClassPathXmlApplicationContext`定义好配置文件的地址。 接下来的BeanFactory的定义、Bean的注册、Bean属性的填充等都会自动化完成。 ![image-20230414205613166](./assets/image-20230414205613166.png) `ClassPathXmlApplicationContext`是最后提供给用户使用的,而其他的则是体现单一职责,使用模板模式使得每一个类只实现一个方法。 其中最重要的是`refresh`方法,在`ClassPathXmlApplicationContext`的构造方法里会调用该次方法。 在`refresh`方法中,完成了BeanFactory的创建、BeanFactory的获取、Bean对象实例化的注册、提前实例化单例Bean等工作,实现了自动化。 为了给Bean对象实例化过程添加扩展机制,使得开发者可以开发属于自己的Spring的技术组件,提供了`BeanFactoryProcessor`和`BeanProcessor`。 `BeanFactoryProcessor`需要使用者实现这个接口,实现里面的`postProcessBeanFactory`方法,并将这个类放入MySpring容器中。这个方法在所有的`BeanDefinition`加载完毕后,并且将Bean对象实例化之前,提供修改`BeanDefinition`属性的机制。这个方法的调用是在`refresh`方法中获取`BeanFactory`后被调用。 而`BeanProcessor`也是一个需要被用户实现,实现里面的`postProcessBeforeInitialization`方法和`postProcessAfterInitialization`方法,两个方法的被调用时机如下: 1. 在 Bean 对象执行初始化方法之前,执行此方法 2. Bean 对象执行初始化方法之后,执行此方法 这两个方法,在使用`getBean`方法从MySpring容器中获取Bean实例的时候,如果从容器中无法获取该单例Bean, 那么就会创建该Bean,也就是`createBean`方法。 在`createBean`方法会调用这两个方法,在创建完成该Bean实例并且为其他的成员变量赋值完成后,就会调用`initializeBean`方法,执行Bean对象的初始化方法和BeanPostProcessor 接口的前置和后置处理方法。 ```java private Object initializeBean(String beanName, Object bean, BeanDefinition beanDefinition){ // 执行BeanPostProcessor Before前置处理 Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName); //待完成内容 invokeInitMethods(beanName, wrappedBean, beanDefinition); // 执行BeanPostProcessor 后置处理 wrappedBean = applyBeanPostProcessorsAfterInitialization(bean, beanName); return wrappedBean; } ``` --- # 6. Bean对象的初始化和销毁 在MySpring中,继续完善了Bean的生命周期。提供了两种Bean初始化和Bean销毁的方法 1. 通过XML文件配置 2. 通过实现`InitializingBean`和`DisposableBean`实现 1. 两个接口均提供了初始化方法和销毁放啊,需要实现类实现这个方法 两种方式的实现过程都大同小异。需要继续完善`BeanDefinition`的定义,新增两个成员变量`initMethodName`和`destroyMethodName`来表示初始化方法和销毁方法的方法名,后续就可以通过反射调用该方法,完成其生命周期。 **对于Bean初始化方法**,会在`AbstractAutowireCapableBeanFactory` 的`createBean`方法中完成Bean的实例化和属性填充之后调用`initializeBean`方法,在`initializeBean`里面会调用`invokeInitMethods`来完成初始化方法。 1. 首先,会先判断Bean是否实现了`InitializingBean`接口 - 如果是,则强转为`InitializingBean`并调用`afterPropertiesSet`方法。 2. 从`BeanDefinition`中获取初始化方法的方法名 3. 如果方法名不为空,说明在配置文件中指定了初始化的方法 4. 通过`BeanDefinition`的`Class`获取到这个方法名的方法`Method` 5. 然后通过反射调用该初始化方法 **而对于Bean的销毁方法**,就需要在`ConfigurableApplicationContext`接口定义两个方法。 1. `registerShutdownHook`:使用Java虚拟机提供的机制,在虚拟机关闭的时候调用close方法 > `Runtime.getRuntime().addShutdownHook()` 是Java虚拟机提供的一种机制。它允许开发者在JVM关闭之前执行一些必要的操作,比如保存程序的状态、释放资源等。 > > 当JVM准备关闭时,会以异步方式启动所有已注册的shutdown hook线程,并且在这些线程执行完毕后才会完全停止JVM。因此,通过使用 `addShutdownHook()` 方法,我们可以确保在JVM关闭之前执行一些必要的清理操作,从而避免潜在的数据损坏或其他不良影响。 > > 例如,如果你的程序创建了一个临时文件,在程序结束时需要将该文件删除,那么你就可以使用 `addShutdownHook()` 方法来注册一个钩子线程,在JVM关闭时清理该文件。这样即使程序意外终止,也能保证临时文件得到正确地清理,从而避免可能出现的问题。 2. `close`:调用方法会调用Bean的销毁方法。 **销毁方法相对于初始化方法来说,需要进行一层封装,将其封装成一个`DisposableBeanAdapter`适配器,然后存进Map中,当虚拟机关闭之前就会从容器中拿出这适配器,一个个调用适配器的`destroy`方法进行调用其适配器方法**。 --- # 7. 感知容器对象 对于目前实现的MySpring框架中,实现了Bean对象的定义和注册、Bean对象过程执行`BeanFactoryPostProcessor`、`BeanPostProcessor`等等。 想要获取MySpring容器中提供的BeanFctory、ApplicationContext、BeanClassLoader等进行功能拓展,就需要在容器中提供一种感知容器对象的接口。 定义一个`Aware`接口,该接口并没有定义任何方法,只是一种感知标记性接口。再分别定义四个接口继承这个`Aware`接口 1. `BeanClassLoaderAware`:感知BeanClassLoader 2. `ApplicationContextAware`: 感知应用程序上下文 3. `BeanFactoryAware`:感知BeanFactory 4. `BeanNameAware`:感知BeanName 需要一个包装处理器类`ApplicationContextAwareProcessor`,它的主要作用是获取ApplicationContext,因为不能在创建Bean的时候直接获取到ApplicationContext属性,所以需要在`refresh`方法中,写入一个包装类,再调用`postProcessBeforeInitialization`方法的时候获取到`AplicationContext`属性。 而想要获取`ClassLoader`、`BeanFactory`、`BeanName`则在`createBean`中调用的`initializeBean`方法判断bean实例是否实现了`Aware`接口,进而判断是否实现`BeanFactoryAware`、`BeanFactoryAware`和`BeanNameAware`。 这样就能获取到容器的对象了。 --- # 8. 对象作用域和FactoryBean > 在 Spring 中,**对象作用域指的是 Spring 容器中管理的 bean 对象的生命周期和可访问范围**。Spring 框架提供了以下几种常见的对象作用域: > > 1. Singleton:单例模式,一个 bean 实例在 Spring 容器中仅创建一次,并在整个应用程序中共享。 > 2. Prototype:原型模式,每次请求获取 bean 时都会创建一个新的实例。 > 3. Request:请求作用域,每个 HTTP 请求都将创建一个新的 bean 实例,在该 HTTP 请求处理期间可以访问该实例。 > 4. Session:会话作用域,每个 HTTP 会话(通常指用户与 Web 应用程序之间的交互)都将创建一个新的 bean 实例,在该会话期间可以访问该实例。 > 5. GlobalSession:全局会话作用域,类似于 Session 作用域,但仅适用于基于 portlet 的 web 应用程序。 > > 使用不同的对象作用域可以帮助我们更好地管理对象的生命周期和访问范围,从而提高应用程序的性能和可维护性。 在MySpring中,在BeanDefinition新增几个属性和方法 1. `scope`:作用域,默认为singleton 2. `setScope`:设置作用域 3. `isSingleton`:是否是Singleton类型 4. `isPrototype`:是否是原型类型 在配置文件中,当我们设置需要设置对象作用域的时候,需要指定参数`scope`,不指定默认为Singgleton ```xml ``` xml文件配置完成之后,很自然地想到需要读取这个`scope`,给Bendifinition赋值 于是对`XmlBeanDefinistionReader`的`doLoadBeanDefinitions`方法进行修改,获取`scop`标签 1. 如果该标签不为空,则给BeanDefinition赋值 2. 否则,不赋值,也就是默认为Singleton Bean实例的创建,是在`getBean`的时候才会进行Bean实例的创建的 在`AbstractBeanFactory`的`doGetBean`方法,会先从单例Map中根据BeanName获取Bean实例,如果没有获取到,则会调用`createBean`方法进行创建Bean实例。里面有一个非常关键的一步,创建出Bean实例后,如果这个Bean是单例的,那么就会将这个Bean注册到单例Map中,从而实现了单例。 也就是说,如果不是单例的,那么每次创建完只是直接返回,并不会将该对象存储下来,所以每次返回的都是新对象。 这就完成了对象作用域的实现。 > 在Spring中,FactoryBean是一个特殊的bean,它的作用是创建其他的bean。FactoryBean可以用来解决某些情况下,创建bean的过程需要复杂的处理逻辑,或者需要在运行时动态地确定所要创建的bean类型的问题。 > > FactoryBean接口定义了两个方法:getObject()和getObjectType()。其中getObject()方法用于创建bean实例,而getObjectType()方法则返回该工厂创建的bean的类型。通过这种方式,我们可以让Spring容器在运行时根据具体的情况来动态地创建不同类型的bean,并且可以在创建bean之前进行一些必要的初始化操作。 在MySpring中实现FactoryBean,首先需要定义一个`FactoryBean`的接口,这是一个由BeanFactory中使用的对象的接口,如果这些对象本身就是工厂,也就是一个Bean实现了这个接口。那么它将被用作公开的对象的工厂,则不是直接作为将自己公开的bean实例。 并且定义一个`FactoryBeanRegistrySupport`继承`DefaultSingletonBeanRegistry`。 里面有三个方法 1. `getCachedObjectForFactoryBean`:根据Bean的名字,从Map中获取 2. `getObjectFromFactoryBean`:从FactoryBean中获取对象,需要判断FactoryBean是不是单例的; - 如果是单例的,则需要从Map中获取,这样获取的数据都是一样的 - 如果不是单例的,则每次都是新的对象 3. `doGetObjectFromFactoryBean`:调用FactoryBean的`getObject`方法,创建一个新的对象。 在`AbstractBeanFactory`的`doGetBean`会先从单例中获取Bean,如果Bean不存在就会创建Bean。 无论存不存在,都会调用`getObjectForBeanInstance`方法 1. 首先会判断Bean对象是否是`FactoryBean`实现类,如果不是就可以直接返回这个Bean 2. 如果是,则会根据beanName,从FactroyBean中获取Bean,如果没有这个Bean,Factory会创建一个这个Bean并存进map 3. 之后将这个新Bean返回,也就是代理后的Bean --- # 9. 容器事件和事件监听器 采用观察者模式,完成容器事件和事件的监听器。 `ApplicationEvent`是一个定义和实现事件的类,这是一个具备事件功能的抽象类。 里面定义了一个构建一个原型事件的构造方法。 ```java public ApplicationEvent(Object source){ super(source); } ``` `ApplicationContextEvent`是一个抽象观察者类,后续的具体观察者都需要继承这个抽象类,可以共给用户使用。 在MySpring中实现了两种具体抽象这类 1. `ContextClosedEvent`:监听关闭事件 2. `ContextRefreshedEvent`:监听刷新事件 构建应用程序事件侦听器实现的接口`ApplicationListener` 1. `onApplicationEvent`这里构建一个应用事件 构建抽象主题类(事件广播器)`ApplicationEventMulticaster` 1. `addApplicationListener`:添加要通知的所有事件的侦听器 2. `removeApplicationListener`:从通知列表删除侦听器 3. `multicastEvent`:将给点的应用程序事件多播到适当监听器(推送) 在MySpring中,为了避免所有直接实现类实现接口的处理细节,需要用一个抽象类`AbstractApplicationEventMulticaster`实现`ApplicationEventMulticaster`接口的部分方法 1. `addApplicationListener` 2. `removeApplicationListener` 后续实现类只需要继承`AbstractApplicationEventMulticaster`即可。 然后完成重写`multicastEvent`方法即可。这个方法的作用是将给定的应用程序事件广播到适当监听器(推送) 除此之外,还需要定义一个事件发布的定义和实现接口`ApplicationEventPublisher`,定义一个发布事件的抽象方法`publishEvent`。 在MySpring中,容器事件和事件监听的流程如下: 1. 用户可以通过实现`ApplicationContextEvent`接口,将该类定义为一个事件。 2. 除此之外,还需要定义这个事件的监听器类,需要实现`ApplicationListener`接口。重写`onApplicationEvent`方法。 3. 接着将`ApplicationListener`接口的实现类放进MySpring容器中,也就是在配置文件中进行配置。 4. 在容器创建的时候,调用`refresh`方法,`refresh`方法里会调用`initApplicationEventMulticaster`方法完成初始化事件发布者。 5. 接着调用`registerListeners`注册事件监听器,从MySpring容器中获取所有`ApplicationListener`的Bean(也就是第三步放进容器中的Bean),将这些Bean放进`ApplicationEventMulticaster`中,从而完成注册 6. 完成后,就会调用`finishRefresh`完成发布容器刷新完成事件,在这个方法里面会调用`publishEvent`方法,在`publishEvent`方法里面调用`multicastEvent`,完成事件的处理。 7. 容器创建完成后,当用户往容器中发布一个事件,也就是调用容器的`publishEvent`方法,就会像第6步一样完成事件的处理。 8. 同样的在容器关闭的时候会调用`close`方法,在`close`中或调用容器关闭事件`publishEvent(new ContextClosedEvent(this));` --- # 10.基于JDK、Cglib实现AOP切面 分别定义三个接口 1. `ClassFilter`:接口匹配类,定义`matches`方法执行静态检查给定方法是否匹配 2. `Pointcut`:定义接口,定义了三个方法 - `getClassFilter`:返回该切点的ClassFilter - `getMethodMatcher`:返回该切入点的MethodMatcher 3. `MethodMatcher`:方法匹配接口,定义`matches`方法,切入点应该应用于给定的接口还是目标类?询问候选目标类是否应用将建议应用于给定的目标类 `AspectJExpressionPointcut`类实现上面三个接口,同时使用aspectj包提供的表达式验证方法,这个类的目的是用来实现切点表达式类的。 `AdvisedSupport`类则是包装了被代理对象、方法拦截器、方法匹配器的类,目的是为了方便Proxy实现类中使用。 `AopProxy`是代理抽象的实现,其实现类分别有Cglib和JDK。 1. `JdkDynamicAopProxy`是JDK实现的代理类 2. `Cglib2AopProxy`是Cglib代理类 --- # 11. 将AOP扩展到Bean生命周期 `Spring的XML配置如下:` ```xml ``` 在Spring的AOP实现中,会将AOP相关类注入到Spring容器中,以便后续的使用。 会分别向容器中注入`自定义的前置增强`、`方法前置拦截器(记录拦截的方法需要进行哪些增强操作)`、`AOP的定义(表达式、前置拦截器)`。 有了这些之后,当我们在容器中想要获取某一个Bean时候,会调用`org.lrk.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean方法` 由于创建的是代理对象,而不是之前流程中的普通对象,需要在创建Bean的之前进行特殊化处理。 ```java protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException { Object bean = null; try { // 判断返回的是不是代理对象 bean = resolveBeforeInstantiation(beanName, beanDefinition); if (null != bean){ return bean; } bean = createBeanInstance(beanDefinition, beanName, args); // 这时候初始化的Bean的实例的属性还为空,需要调用该方法给这个Bean的成员变量赋值 applyPropertyValues(beanName, bean, beanDefinition); // 执行Bean对象的初始化方法和BeanPostProcessor 接口的前置和后置处理方法 bean = initializeBean(beanName, bean, beanDefinition); } catch (Exception e) { throw new BeansException("创建Bean失败", e); } // 注册实现了DisposableBean接口的对象 registerDisposableBeanIfNecessary(beanName, bean, beanDefinition); // 创建了Bean实例之后,如果是单例 注册到单例容器中 if (beanDefinition.isSingleton()) { registerSingleton(beanName, bean); } return bean; } ``` 在`resolveBeforeInstantiation` 方法中会调用`applyBeanPostProcessorBeforeInstantiation` 方法获取容器中的AOP对象也就是`AspectJExpressionPointcutAdvisor` ,创建出代理对象,并放到容器中。 ```java /** * 特殊化处理(代理对象) */ protected Object resolveBeforeInstantiation(String beanName, BeanDefinition beanDefinition){ // 判断返回的是不是代理对象,如果返回的对象不为null,则说明这是一个代理对象 Object bean = applyBeanPostProcessorBeforeInstantiation(beanDefinition.getBeanClass(), beanName); if (null != bean){ // 进行后置处理 bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); } return bean; } /** * 获取代理对象,如果返回为null说明存在代理对象 */ public Object applyBeanPostProcessorBeforeInstantiation(Class beanClass, String beanName) throws BeansException { for (BeanPostProcessor processor : getBeanPostProcessors()) { if (processor instanceof InstantiationAwareBeanPostProcessor) { Object result = ((InstantiationAwareBeanPostProcessor)processor).postProcessBeforeInstantiation(beanClass, beanName); if (null != result) return result; } } return null; } ``` --- # 12. 自动扫描Bean对象注册 在Spring的XML配置文件中配置下面的语句,即刻自动扫描注册`org.lrk.springframework.property_test`下的Bean。 ```xml ``` 在这包下需要注入容器的Bean使用`Component`表示这是一个Spring的Bean即刻。 原理也很简单,在读取Spring的XML配置文件的时候,先判断一下是否有`component-scan`标签 如果有则扫描对应的包,将这个包的类中有`Component`注解的Bean注册进来。 ```java // 解析 context:component-scan 标签,扫描包中的类并提取相关信息,用于组装 BeanDefinition org.dom4j.Element componentScan = root.element("component-scan"); if (null != componentScan) { String scanPath = componentScan.attributeValue("base-package"); if (StrUtil.isEmpty(scanPath)) { throw new BeansException("The value of base-package attribute can not be empty or null"); } scanPackage(scanPath); } ``` --- # 13. 处理配置文件占位符 ```xml ``` 对于处理占位符比如`${token}`这种情况,Spring框架是会自定义一个前置处理,在Bean实例化之前会调用前置处理,然后在前置处理这一步将占位符进行替换。 ```java public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { try { DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); Resource resource = resourceLoader.getResource(location); Properties properties = new Properties(); properties.load(resource.getInputStream()); String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames(); for (String beanName : beanDefinitionNames) { BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); PropertyValues propertyValues = beanDefinition.getPropertyValues(); for (PropertyValue propertyValue : propertyValues.getPropertyValues()) { Object value = propertyValue.getValue(); if (!(value instanceof String)) { continue; } String strVal = (String) value; StringBuilder buffer = new StringBuilder(strVal); int startIdx = strVal.indexOf(DEFAULT_PLACEHOLDER_PREFIX); int stopIdx = strVal.indexOf(DEFAULT_PLACEHOLDER_SUFFIX); if (stopIdx != -1 && startIdx != -1 && startIdx < stopIdx) { // 截取占位符关键词 String propKey = strVal.substring(startIdx + 2, stopIdx); String propVal = properties.getProperty(propKey); buffer.replace(startIdx, stopIdx + 1, propVal); propertyValues.addPropertyValue(new PropertyValue(propertyValue.getName(), buffer.toString())); } } } }catch (IOException e) { throw new BeansException("Could not load properties from " + location, e); } } ``` --- # 14. 通过注解注入属性信息 `@Autowired` 与`@Value` 是Spring框架提供的注入属性的注解 在`org.lrk.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean`方法中进行创建Bean的操作,完成实例化Bean后,在填充属性之前,调用`org.lrk.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeApplyingPropertyValues`方法。 在这个方法中会调用`org.lrk.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues` 方法,加载Bean中被`Value`与`Autowired`注解的成员变量,将其从IOC容器中获取出来,再给字段赋值。 ***那么`org.lrk.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor`这个Bean啥时候被放进IOC容器的呢?*** 在`org.lrk.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan` 完成扫描包之后,会将`org.lrk.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor`放进IOC容器。而`doScan`这个方法是用来扫描包下带有`Component`注解的类,并将其加载进容器。也就是说需要在spring.xml开启Bean自动扫描。 ```xml ``` ---