引言
在Spring框架中,依赖注入(Dependency Injection)是核心机制之一,用于管理Bean对象的创建和依赖关系。传统方式依赖XML配置文件手动指定属性值和Bean引用,这种方法在复杂项目中容易导致配置繁琐和维护困难。为简化开发流程,提高代码的可读性和灵活性,Spring引入注解-based注入机制,如@Value用于注入配置值,@Autowired用于注入Bean对象。
本章聚焦于实现注解注入配置值和Bean对象的功能。实现思路源于Bean生命周期的扩展点:Spring的BeanPostProcessor接口允许在Bean实例化后、属性填充前修改属性值。通过继承InstantiationAwareBeanPostProcessor接口,并在postProcessPropertyValues方法中扫描注解,可以在属性填充阶段动态注入值。这种方法避免了XML配置的静态性,利用反射和注解元数据实现自动化注入。
具体步骤包括:
(1)在BeanFactory中注册属性解析器处理占位符;
(2)定义自定义注解;
(3)创建注解处理器扫描并注入属性;
(4)在Bean创建流程中调用处理器,确保注入发生在属性填充前。该设计确保兼容现有Bean生命周期,同时提供可扩展性。
目标
实现基于注解的自动注入功能,包括:
- 使用@Value注解注入配置值(如从属性文件读取的占位符)。
- 使用@Autowired注解注入Bean对象,支持@Qualifier指定Bean名称。 此功能构建于IOC容器自动扫描@Component注解的基础上,取代XML中的<property>标签。</property>
设计方案
注入操作集成到Bean生命周期的属性填充阶段(applyPropertyValues前)。核心组件包括:
- 属性解析器:使用BeanFactoryPostProcessor(如PropertyPlaceholderConfigurer)加载属性文件,并注册StringValueResolver到BeanFactory,用于解析@Value中的占位符。
- 注解处理器:实现InstantiationAwareBeanPostProcessor接口,在postProcessPropertyValues方法中扫描字段注解,注入配置值或Bean实例。
- 注入逻辑:
- 配置值:从BeanFactory解析占位符(如”${token}”),设置到PropertyValues。
- Bean对象:通过BeanFactory.getBean获取实例,支持类型或名称匹配。
- 该方案利用BeanPostProcessor的扩展性,确保注入不干扰核心创建流程。
结构:
1.工程结构
small-spring-step-14
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── aop
│ │ ├── aspectj
│ │ │ └── AspectJExpressionPointcut.java
│ │ │ └── AspectJExpressionPointcutAdvisor.java
│ │ ├── framework
│ │ │ ├── adapter
│ │ │ │ └── MethodBeforeAdviceInterceptor.java
│ │ │ ├── autoproxy
│ │ │ │ └── MethodBeforeAdviceInterceptor.java
│ │ │ ├── AopProxy.java
│ │ │ ├── Cglib2AopProxy.java
│ │ │ ├── JdkDynamicAopProxy.java
│ │ │ ├── ProxyFactory.java
│ │ │ └── ReflectiveMethodInvocation.java
│ │ ├── AdvisedSupport.java
│ │ ├── Advisor.java
│ │ ├── BeforeAdvice.java
│ │ ├── ClassFilter.java
│ │ ├── MethodBeforeAdvice.java
│ │ ├── MethodMatcher.java
│ │ ├── Pointcut.java
│ │ ├── PointcutAdvisor.java
│ │ └── TargetSource.java
│ ├── beans
│ │ ├── factory
│ │ │ ├── annotation
│ │ │ │ ├── Autowired.java
│ │ │ │ ├── AutowiredAnnotationBeanPostProcessor.java
│ │ │ │ ├── Qualifier.java
│ │ │ │ └── Value.java
│ │ │ ├── config
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java
│ │ │ │ ├── BeanFactoryPostProcessor.java
│ │ │ │ ├── BeanPostProcessor.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java
│ │ │ │ ├── InstantiationAwareBeanPostProcessor.java
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ │ ├── DisposableBeanAdapter.java
│ │ │ │ ├── FactoryBeanRegistrySupport.java
│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── Aware.java
│ │ │ ├── BeanClassLoaderAware.java
│ │ │ ├── BeanFactory.java
│ │ │ ├── BeanFactoryAware.java
│ │ │ ├── BeanNameAware.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── DisposableBean.java
│ │ │ ├── FactoryBean.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ ├── InitializingBean.java
│ │ │ ├── ListableBeanFactory.java
│ │ │ └── PropertyPlaceholderConfigurer.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── context
│ │ ├── annotation
│ │ │ ├── ClassPathBeanDefinitionScanner.java
│ │ │ ├── ClassPathScanningCandidateComponentProvider.java
│ │ │ └── Scope.java
│ │ ├── event
│ │ │ ├── AbstractApplicationEventMulticaster.java
│ │ │ ├── ApplicationContextEvent.java
│ │ │ ├── ApplicationEventMulticaster.java
│ │ │ ├── ContextClosedEvent.java
│ │ │ ├── ContextRefreshedEvent.java
│ │ │ └── SimpleApplicationEventMulticaster.java
│ │ ├── support
│ │ │ ├── AbstractApplicationContext.java
│ │ │ ├── AbstractRefreshableApplicationContext.java
│ │ │ ├── AbstractXmlApplicationContext.java
│ │ │ ├── ApplicationContextAwareProcessor.java
│ │ │ └── ClassPathXmlApplicationContext.java
│ │ ├── ApplicationContext.java
│ │ ├── ApplicationContextAware.java
│ │ ├── ApplicationEvent.java
│ │ ├── ApplicationEventPublisher.java
│ │ ├── ApplicationListener.java
│ │ └── ConfigurableApplicationContext.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ ├── stereotype
│ │ └── Component.java
│ └── utils
│ ├── ClassUtils.java
│ └── StringValueResolver.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ ├── IUserService.java
│ └── UserService.java
└── ApiTest.java
2.自动扫描注入占位符配置和对象的类关系:
实现:
1.定义解析字符串接口:
定义StringValueResolver接口,用于解析占位符字符串。
/**
* 字符串值解析器接口。
* 定义了解析字符串的方法,通常用于解析占位符或表达式,
* 将输入字符串转换为解析后的实际值。
*/
public interface StringValueResolver {
String resolveStringValue(String strVal);
}
2.填充属性值到容器:
2.1 新增集合embeddedValueResolvers
:
在 AbstractBeanFactory 中新增了一个集合:
/*
embeddedValueResolvers 集合存储 StringValueResolver 实例,
用于解析占位符字符串(如 ${token})以注入配置值或动态值到Bean的属性或注解中。
*/
private final List<StringValueResolver> embeddedValueResolvers = new ArrayList<>();
在ConfigurableBeanFactory定义方法:
/**
* 添加一个字符串解析器,用于解析嵌入的值,例如注解属性。
*/
void addEmbeddedValueResolver(StringValueResolver valueResolver);
/**
* 解析给定的嵌入值,例如注解属性。
*/
String resolveEmbeddedValue(String value);
并在AbstractBeanFactory 中实现:
@Override
public void addEmbeddedValueResolver(StringValueResolver valueResolver) {
this.embeddedValueResolvers.add(valueResolver);
}
@Override
public String resolveEmbeddedValue(String value) {
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
result = resolver.resolveStringValue(result);
}
return result;
}
2.2 拓展PropertyPlaceholderConfigurer:
优化逻辑并新增valueResolver 相关功能:
/**
* 该类实现了Spring的BeanFactoryPostProcessor接口,
* 用于处理Bean定义中的属性占位符,将${...}形式的占位符替换为属性文件中的实际值
*/
public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor {
/**
* 默认的占位符前缀: {@value}
*/
public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
/**
* 默认的占位符后缀: {@value}
*/
public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
// 属性文件的位置
private String location;
/**
* 实现BeanFactoryPostProcessor接口的方法,在BeanFactory加载完所有Bean定义后执行
* 用于替换Bean定义中的属性占位符
* @param beanFactory 可配置的Bean工厂,用于获取和修改Bean定义
* @throws BeansException 如果处理过程中发生错误
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
// 1. 创建资源加载器,加载指定位置的属性文件
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource(location);
// 2. 读取属性文件内容到 Properties 对象中
Properties properties = new Properties();
properties.load(resource.getInputStream());
// 3. 遍历容器中所有 BeanDefinition
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
// 4. 获取该 BeanDefinition 的所有属性值
PropertyValues propertyValues = beanDefinition.getPropertyValues();
for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {
Object value = propertyValue.getValue();
// 5. 只处理字符串类型的属性值
if (!(value instanceof String)) continue;
// 6. 替换属性值中的占位符(如 ${xxx})
value = resolvePlaceholder((String) value, properties);
// 7. 将替换后的值重新写回到属性列表
propertyValues.addPropertyValue(new PropertyValue(propertyValue.getName(), value));
}
}
// 8. 向 BeanFactory 注册一个字符串解析器
// 这个解析器会在解析 @Value 注解时生效,用于处理占位符
StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(properties);
beanFactory.addEmbeddedValueResolver(valueResolver);
} catch (IOException e) {
// 9. 属性文件加载失败时,抛出 BeansException
throw new BeansException("Could not load properties", e);
}
}
/**
* 解析字符串中的占位符并替换为对应的属性值。
* 占位符格式为 `${key}`,从给定的 Properties 中获取 key 对应的值进行替换。
* 如果字符串中不存在占位符,则返回原字符串。
*/
private String resolvePlaceholder(String value, Properties properties) {
String strVal = value;
StringBuilder buffer = new StringBuilder(strVal);
int startIdx = strVal.indexOf(DEFAULT_PLACEHOLDER_PREFIX);
int stopIdx = strVal.indexOf(DEFAULT_PLACEHOLDER_SUFFIX);
if (startIdx != -1 && stopIdx != -1 && startIdx < stopIdx) {
String propKey = strVal.substring(startIdx + 2, stopIdx);
String propVal = properties.getProperty(propKey);
buffer.replace(startIdx, stopIdx + 1, propVal);
}
return buffer.toString();
}
/**
* 设置属性文件的位置
* @param location 属性文件的路径
*/
public void setLocation(String location) {
this.location = location;
}
private class PlaceholderResolvingStringValueResolver implements StringValueResolver {
private final Properties properties;
public PlaceholderResolvingStringValueResolver(Properties properties) {
this.properties = properties;
}
@Override
public String resolveStringValue(String strVal) {
return PropertyPlaceholderConfigurer.this.resolvePlaceholder(strVal, properties);
}
}
}
3.自定义属性注入注解
/**
* 值注入注解。
* 用于将外部配置或表达式的值注入到字段、方法参数等位置。
*/
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
/**
* 要注入的实际值或表达式,例如 "#{systemProperties.myProp}"。
*/
String value();
}
/**
* 自动注入注解。
* <p>用于标记构造器、字段或方法,表示依赖由 Spring 容器自动装配。
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
public @interface Autowired {
}
/**
* 限定符注解。
* <p>与 @Autowired 配合使用,用于按名称精确指定注入的 Bean。
*/
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
/**
* 指定注入的 Bean 名称。
*/
String value() default "";
}
4. 扫描注解
实现AutowiredAnnotationBeanPostProcessor,处理注解注入。
此处又提到了AutowiredAnnotationBeanPostProcessor,那我们就来加深下记忆:
特性 | BeanPostProcessor | InstantiationAwareBeanPostProcessor | BeanFactoryPostProcessor |
---|---|---|---|
作用对象 | 针对 Bean 实例 进行处理 | 针对 Bean 实例化过程 进行干预 | 针对 BeanFactory(容器) 及 BeanDefinition 进行处理 |
作用阶段 | Bean 实例化 之后(构造方法执行后) | Bean 实例化 之前 + 实例化后、属性填充前 | Bean 实例化 之前(容器启动初期,BeanDefinition 加载后) |
核心方法 | – postProcessBeforeInitialization() – postProcessAfterInitialization() | – postProcessBeforeInstantiation() – postProcessAfterInstantiation() – postProcessProperties() | – postProcessBeanFactory() |
典型用途 | 实例初始化前后增强(如 AOP 代理、添加属性) | 控制实例化过程(如返回代理替代原始对象)、属性注入前处理(如解析 @Autowired ) | 修改 BeanDefinition(如动态注册 Bean、修改属性值)、自定义 BeanFactory 配置 |
执行时机 | 在 InitializingBean.afterPropertiesSet() 或自定义初始化方法前后 | 在 Bean 构造方法调用前、后,以及属性填充前 | 在所有 BeanDefinition 加载完成,但未开始实例化任何 Bean 时 |
常见实现类 | AnnotationAwareAspectJAutoProxyCreator (AOP 代理) | AutowiredAnnotationBeanPostProcessor (处理 @Autowired ) | PropertyPlaceholderConfigurer (处理 @Value 占位符)、ConfigurationClassPostProcessor (处理 @Configuration ) |
是否修改 Bean 定义 | 不修改,仅处理已实例化的 Bean | 不直接修改 BeanDefinition,仅干预实例化过程 | 直接修改 BeanDefinition(如属性、作用域等) |
4.1 前置修改:
又到了这个教程最爱的莫名其妙回马枪的时间了:
BeanFactory添加:
<T> T getBean(Class<T> requiredType) throws BeansException;
所以DefaultListableBeanFactory实现:
@Override
public <T> T getBean(Class<T> requiredType) throws BeansException {
List<String> beanNames = new ArrayList<>();
for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
Class beanClass = entry.getValue().getBeanClass();
if (requiredType.isAssignableFrom(beanClass)) {
beanNames.add(entry.getKey());
}
}
if (1 == beanNames.size()) {
return getBean(beanNames.get(0), requiredType);
}
throw new BeansException(requiredType + "expected single bean but found " + beanNames.size() + ": " + beanNames);
}
同样的AbstractApplicationContext实现:
@Override
public <T> T getBean(Class<T> requiredType) throws BeansException {
return getBeanFactory().getBean(requiredType);
}
InstantiationAwareBeanPostProcessor 添加:
/**
* 在工厂将属性值应用到给定 Bean 之前,对这些属性值进行后置处理。
* 例如,可以在这里检查所有依赖是否已满足,
* 也可以基于 Bean 属性的 setter 方法上的 “@Required” 注解进行依赖校验。
*
* 在 Bean 实例化完成后、执行属性填充操作之前调用此方法。
*
*/
PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException;
而DefaultAdvisorAutoProxyCreator实现了InstantiationAwareBeanPostProcessor;
添加实现:
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException {
return pvs;
}
5.注册注解处理器
由于AutowiredAnnotationBeanPostProcessor并没有标注@Component,所以是无法在类扫描时注入到beanFactory中的,此处需要我们手动进行注册。
是 Spring 框架的内部组件,旨在为所有启用注解扫描的项目提供支持,所以需要在spring内部注册,而不是像上一篇的PropertyPlaceholderConfigurer手动配置为Bean。
在ClassPathBeanDefinitionScanner中手动注册AutowiredAnnotationBeanPostProcessor:
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
// ...
public void doScan(String... basePackages) {
// 扫描并注册BeanDefinition
// ...
registry.registerBeanDefinition("internalAutowiredAnnotationProcessor", new BeanDefinition(AutowiredAnnotationBeanPostProcessor.class));
}
}
虽然是被手动配置为bean的,但是触发条件:注册过程由 或 @ComponentScan 触发,发生在 XmlBeanDefinitionReader的 doLoadBeanDefinitions方法中,此时就会注册一个ClassPathBeanDefinitionScanner,然后将AutowiredAnnotationBeanPostProcessor注册到Bean注册容器中。
6. 在Bean的生命周期中调用属性注入:
AbstractAutowireCapableBeanFactory:
createBean中:
******
try {
// 创建实例
bean = createBeanInstance(beanDefinition, beanName, args);
} catch (Exception e) {
throw new BeansException("Failed to create bean instance for: " + beanName, e);
}
try {
// 在设置 Bean 属性之前,允许 AutowiredAnnotationBeanPostProcessor 修改属性值(@Autowired、@Value)
applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition);
} catch (Exception e) {
throw new BeansException("Failed to process @Autowired/@Value annotations for bean: " + beanName, e);
}
try {
// 注入属性
applyPropertyValues(bean, beanName, beanDefinition);
} catch (Exception e) {
throw new BeansException("Failed to apply property values for bean: " + beanName, e);
}
******
/**
* 在为 Bean 填充属性(依赖注入)之前,允许 BeanPostProcessor 对属性值进行修改。
*
* 这里主要是为了支持类似 @Autowired、@Value、@Resource 等注解驱动的依赖注入,
* 因为它们依赖于 InstantiationAwareBeanPostProcessor 在属性设置前进行解析与赋值。
*
* @param beanName 当前 Bean 的名称
* @param bean 已经实例化好的 Bean 对象(构造方法已调用,但属性还未注入)
* @param beanDefinition Bean 对应的定义信息(包含属性、构造参数等)
*/
protected void applyBeanPostProcessorsBeforeApplyingPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) {
// 遍历当前容器中注册的所有 BeanPostProcessor
for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {
// 只有 InstantiationAwareBeanPostProcessor 才能在属性设置前做处理
if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {
// 调用其 postProcessPropertyValues 方法,让它有机会修改 Bean 的属性值
// 例如 AutowiredAnnotationBeanPostProcessor 会在这里解析 @Autowired 并生成对应的依赖注入 PropertyValue
PropertyValues pvs = ((InstantiationAwareBeanPostProcessor) beanPostProcessor)
.postProcessPropertyValues(beanDefinition.getPropertyValues(), bean, beanName);
// 如果处理器返回了新的属性值集合,则合并到 BeanDefinition 的属性列表中
if (null != pvs) {
for (PropertyValue propertyValue : pvs.getPropertyValues()) {
beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
}
}
}
}
}
测试:
1.测试类:
@Component
public class UserDao {
private static Map<String, String> hashMap = new HashMap<>();
static {
hashMap.put("10001", "CMD137,北京,亦庄");
hashMap.put("10002", "CMD138,上海,尖沙咀");
hashMap.put("10003", "CMD139,天津,东丽区");
}
public String queryUserName(String uId) {
return hashMap.get(uId);
}
}
这里包括了两种类型的注入,一个是占位符注入属性信息 @Value("${token}")
,另外一个是注入对象信息 @Autowired
:
@Component("userService")
public class UserService implements IUserService {
@Value("${token}")
private String token;
@Autowired
private UserDao userDao;
public String queryUserInfo() {
try {
Thread.sleep(new Random(1).nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
return userDao.queryUserName("10001") + "," + token;
}
@Override
public String toString() {
return "UserService#token = { " + token + " }";
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
2.配置文件:
token.properties
token=123_TEST_TOKEN_321
spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean class="com.miniSpring.beans.factory.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:token.properties"/>
</bean>
<component-scan base-package="com.miniSpring.test.bean"/>
</beans>
3. 单元测试:
@Test
public void test_scan() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
IUserService userService = applicationContext.getBean("userService", IUserService.class);
System.out.println("测试结果:" + userService.queryUserInfo());
}
测试结果:CMD137,北京,亦庄,123_TEST_TOKEN_321
Process finished with exit code 0