说说Spring中@Value注解

@Value注解是Spring3.0后提出来的一个注解。注解内容本身非常之简单,但是它提供的功能却非常之强大。

首先从它的注解本身定义知道,它能使用在:

  • 字段上
  • set方法上
  • 方法入参上
  • 当作元注解

它的功能大致可归类为:

  • 注入普通字符串
  • 书写SpEL表达式(功能强大包括:获取系统属性、调用静态方法、计算、注入bean、调用bean的方法等等~~~)
  • 注入Resource。如:@Value("classpath:com/demo/config.txt") 使用Resource类型接收
  • 注入URL资源。如:@Value("http://www.baidu.com") 使用Resource类型接收

@Value

首先看看@Value注解定义

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
    String value(); // 没有默认值 所以是必填的
}

接下来以一个具体实例,跟踪分析它的执行原理: 

@Configuration
public class RootConfig {

    @Value("#{person.name}")
    private String personName;

    @Bean
    public Person person() {
        return new Person("fsx", 18);
    }
}

之前我们已经讲过,当Bean进行初始化完成之后会populateBean()对它的属性进行赋值,这个时候AutowiredAnnotationBeanPostProcessor这个后置处理器生效,从而对属性进行依赖注入赋值。

AutowiredAnnotationBeanPostProcessor它能够处理@Autowired和@Value注解

注意:因为@Value是BeanPostProcessor来解析的,所以具有容器隔离性(本容器内的Bean使用@Value只能引用到本容器内的值哦~,因为BeanPostProcessor是具有隔离性的)

推荐:所有的@Value都写在根容器(也就是我们常说的Service容器)内,请不要放在web容器里。也就是说,请尽量不要在controller里使用@Value注解,因为业务我们都要求放在service层

三层架构:Controller、Service、Repository务必做到职责分离和松耦合

我们直接从AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues()方法入手:(Spring5.1后为postProcessProperties方法,方法的语义更加清晰些了~)

public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
		implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {

    // 这个方法是InstantiationAwareBeanPostProcessor的,它在给属性赋值的时候会被调用~~
    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {

    	// InjectionMetadata 里包含private final Collection<InjectedElement> injectedElements;表示所有需要注入处理的属性们~~~
    	// 所以最终都是InjectionMetadata去处理~
    	InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    	try {
    		metadata.inject(bean, beanName, pvs);
    	} ...
    	return pvs;
    }

}

InjectionMetadata

用于管理注入元数据的内部类。不建议直接在应用程序中使用。AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor以及org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor这几个最终的注入都是靠它~

本处直接看它的inject方法:

public class InjectionMetadata {
    public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    	Collection<InjectedElement> checkedElements = this.checkedElements;
    	Collection<InjectedElement> elementsToIterate = (checkedElements != null ? checkedElements : this.injectedElements);
    
    	// 遍历,一个一个处理
    	if (!elementsToIterate.isEmpty()) {
    		for (InjectedElement element : elementsToIterate) {
    			element.inject(target, beanName, pvs);
    		}
    	}
    }	
}

总而言之,最终是委托给了InjectedElement 。它有两个内部类的实现:AutowiredFieldElement和AutowiredMethodElement 此处我们只关注字段注入的。

InjectedElement是InjectionMetadata的一个public内部类,并且是抽象的。AutowiredFieldElement和AutowiredMethodElement都是AutowiredAnnotationBeanPostProcessor的private内部类。

另外:CommonAnnotationBeanPostProcessor中有InjectedElement的实现类:LookupElement、ResourceElement、EjbRefElement、WebServiceRefElement等来辅助完成注入~

AutowiredFieldElement

private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {
    private final boolean required;
    private volatile boolean cached = false;
    @Nullable
    private volatile Object cachedFieldValue;

    public AutowiredFieldElement(Field field, boolean required) {
        super(field, null);
        this.required = required;
    }

    @Override
    protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
        // member在抽象父类:InjectedElement中定义~~~
        Field field = (Field) this.member;
        Object value;
        if (this.cached) {
            value = resolvedCachedArgument(beanName, this.cachedFieldValue);
        }
        else {
            DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
            desc.setContainingClass(bean.getClass());
            Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
            Assert.state(beanFactory != null, "No BeanFactory available");

            // 此处一般为SimpleTypeConverter,它registerDefaultEditors=true,所以普通类型大都能能通过属性编辑器实现转换的
            TypeConverter typeConverter = beanFactory.getTypeConverter();
            try {
                // 最最最根本的原理,其实在resolveDependency这个方法里,它最终返回的就是一个具体的值,这个value是个Object~
                value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
            } catch (BeansException ex) {
                throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
            }
            synchronized (this) {
                if (!this.cached) {
                    if (value != null || this.required) {
                        this.cachedFieldValue = desc;
                        registerDependentBeans(beanName, autowiredBeanNames);
                        if (autowiredBeanNames.size() == 1) {
                            String autowiredBeanName = autowiredBeanNames.iterator().next();
                            if (beanFactory.containsBean(autowiredBeanName) &&
                                beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
                                this.cachedFieldValue = new ShortcutDependencyDescriptor(
                                    desc, autowiredBeanName, field.getType());
                            }
                        }
                    } else {
                        this.cachedFieldValue = null;
                    }
                    this.cached = true;
                }
            }
        }
        if (value != null) {
            ReflectionUtils.makeAccessible(field);
            field.set(bean, value);
        }
    }
}

DefaultListableBeanFactory#resolveDependency

它是Spring容器整个体系里实现依赖查找的心脏~

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
    ...
    @Override
    @Nullable
    public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
    		@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

    	descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
    	if (Optional.class == descriptor.getDependencyType()) {
    		// 最终它也会走到:doResolveDependency
    		return createOptionalDependency(descriptor, requestingBeanName);
    	}
    	else if (ObjectFactory.class == descriptor.getDependencyType() ||
    			ObjectProvider.class == descriptor.getDependencyType()) {
    		// 直接new一个Provider返回出去~
    		return new DependencyObjectProvider(descriptor, requestingBeanName);
    	}
    	// 兼容jsr330的javax.inject.Provider
    	else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
    		return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
    	}
    	else {
    	// ContextAnnotationAutowireCandidateResolver-> QualifierAnnotationAutowireCandidateResolver 他们能够解决  就直接返回 否则交给doResolveDependency
    		Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
    				descriptor, requestingBeanName);
    		if (result == null) {
    			result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
    		}
    		return result;
    	}
    }
    ...    
    // 咱们此处descriptor:field 'personName'
    // beanName:rootConfig
    // autowiredBeanNames:[]
    // typeConverter:SimpleTypeConverter
    @Nullable
    public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
    		@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {    
    	InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
    	try {
    		// 若你的despriptor是ShortcutDependencyDescriptor,这个就会直接去beanFactory.getBean(this.shortcut, this.requiredType)  提前返回  主要作用是缓存的效果~
    		Object shortcut = descriptor.resolveShortcut(this);
    		if (shortcut != null) {
    			return shortcut;
    		}    
    		// public final class String
    		Class<?> type = descriptor.getDependencyType();
    		// 备注:QualifierAnnotationAutowireCandidateResolver会处理@Qualifier和@Value
    		//QualifierAnnotationAutowireCandidateResolver#getSuggestedValue()
    		//先拿出@Value注解的值  如果为null再去拿Method里这个注解的值~~~ 最终返回~  所以@Value也是可以标注在方法上的
    		// 注意此处:若是@Value  这里返回值肯定是String  但是若是@Autowired此处返回值就可能是对象了~
    		Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
    		if (value != null) {    
    			// 说明这个注入是@Value  因为它是String
    			if (value instanceof String) {
    				// 使用StringValueResolver处理${}占位符~
    				// 所以我们常用的只使用@Value("${xxx}")这样来注入值或者你就是个字面量值,到这一步就已经完事了~解析完成  
    				// 若你是个el表达式  或者文件资源Resource啥的,会继续交给下面的beanExpressionResolver处理,所以它是处理复杂类型的核心~
    				String strVal = resolveEmbeddedValue((String) value);
    				BeanDefinition bd = (beanName != null && containsBean(beanName) ?
    						getMergedBeanDefinition(beanName) : null);    
    				// 此处注意:处理器是BeanExpressionResolver~~~~它是处理@Value表达式的核心方法
    				// 它的默认值是:StandardBeanExpressionResolver#evaluate
    				// 这里面就会解析
    				value = evaluateBeanDefinitionString(strVal, bd);
    			}    
    			// 若我们没有定制,此处为SimpleTypeConverter... 值已经拿到手了,经由转换器以转换 就可以测地的返回喽~~~解析结束
    			TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
    			try {
    				return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
    			}
    			catch (UnsupportedOperationException ex) {
    				// A custom TypeConverter which does not support TypeDescriptor resolution...
    				return (descriptor.getField() != null ?
    						converter.convertIfNecessary(value, type, descriptor.getField()) :
    						converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
    			}
    		}    
    		// ====================下面就不解释了,多bean和required的解释  前面已经分析过了=======================
    		Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
    		if (multipleBeans != null) {
    			return multipleBeans;
    		}    
    		Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
    		if (matchingBeans.isEmpty()) {
    			if (isRequired(descriptor)) {
    				raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
    			}
    			return null;
    		}    
    		String autowiredBeanName;
    		Object instanceCandidate;    
    		if (matchingBeans.size() > 1) {
    			autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
    			if (autowiredBeanName == null) {
    				if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
    					return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
    				}
    				else {
    					// In case of an optional Collection/Map, silently ignore a non-unique case:
    					// possibly it was meant to be an empty collection of multiple regular beans
    					// (before 4.3 in particular when we didn't even look for collection beans).
    					return null;
    				}
    			}
    			instanceCandidate = matchingBeans.get(autowiredBeanName);
    		}
    		else {
    			// We have exactly one match.
    			Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
    			autowiredBeanName = entry.getKey();
    			instanceCandidate = entry.getValue();
    		}    
    		if (autowiredBeanNames != null) {
    			autowiredBeanNames.add(autowiredBeanName);
    		}
    		if (instanceCandidate instanceof Class) {
    			instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
    		}
    		Object result = instanceCandidate;
    		if (result instanceof NullBean) {
    			if (isRequired(descriptor)) {
    				raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
    			}
    			result = null;
    		}
    		if (!ClassUtils.isAssignableValue(type, result)) {
    			throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
    		}
    		return result;
    	}
    	finally {
    		ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
    	}
    }
}

QualifierAnnotationAutowireCandidateResolver实现了AutowireCandidateResolver,对要自动绑定的field或者参数和bean definition根据@qualifier注解进行匹配,当然也支持javax.inject.Qualifier。同时也支持通过@value注解来绑定表达式的值。

从上面分析知道,当把@Value的占位符替换完成后,最终都会交给beanExpressionResolver由它来统一处理:包括根据beanName获取bean、SpEL计算等等~~~

BeanExpressionResolver

策略接口,用于通过将值作为表达式进行评估来解析值(如果适用)。它持有Bean工厂~

// @since 3.0
public interface BeanExpressionResolver {
    // value此时还是复杂类型,比如本例的#{person.name}
    // BeanExpressionContext:持有beanFactory和scope的引用而已~
    @Nullable
    Object evaluate(@Nullable String value, BeanExpressionContext evalContext) throws BeansException;
}

它的唯一实现类StandardBeanExpressionResolver(当然我们是可以自己实现的,比如后面我们自定义@Value功能,通过继承StandardBeanExpressionResolver来扩展实现。

StandardBeanExpressionResolver

语言解析器的标准实现,支持解析SpEL语言。

public class StandardBeanExpressionResolver implements BeanExpressionResolver {

    // 因为SpEL是支持自定义前缀、后缀的   此处保持了和SpEL默认值的统一
    // 它的属性值事public的   so你可以自定义~
    /** Default expression prefix: "#{". */
    public static final String DEFAULT_EXPRESSION_PREFIX = "#{";
    /** Default expression suffix: "}". */
    public static final String DEFAULT_EXPRESSION_SUFFIX = "}";
    private String expressionPrefix = DEFAULT_EXPRESSION_PREFIX;
    private String expressionSuffix = DEFAULT_EXPRESSION_SUFFIX;
    
    private ExpressionParser expressionParser; // 它的最终值是SpelExpressionParser    
    // 每个表达式都对应一个Expression,这样可以不用重复解析了~~~
    private final Map<String, Expression> expressionCache = new ConcurrentHashMap<>(256);
    // 每个BeanExpressionContex都对应着一个取值上下文~~~
    private final Map<BeanExpressionContext, StandardEvaluationContext> evaluationCache = new ConcurrentHashMap<>(8);
    
    // 匿名内部类   解析上下文。  和TemplateParserContext的实现一样。个人觉得直接使用它更优雅
    // 和ParserContext.TEMPLATE_EXPRESSION 这个常量也一毛一样
    private final ParserContext beanExpressionParserContext = new ParserContext() {
    	@Override
    	public boolean isTemplate() {
    		return true;
    	}
    	@Override
    	public String getExpressionPrefix() {
    		return expressionPrefix;
    	}
    	@Override
    	public String getExpressionSuffix() {
    		return expressionSuffix;
    	}
    };
    
    // 空构造函数:默认就是使用的SpelExpressionParser  下面你也可以自己set你自己的实现~
    public StandardBeanExpressionResolver() {
    	this.expressionParser = new SpelExpressionParser();
    }
    public void setExpressionParser(ExpressionParser expressionParser) {
    	Assert.notNull(expressionParser, "ExpressionParser must not be null");
    	this.expressionParser = expressionParser;
    }    
    // 解析代码相对来说还是比较简单的,毕竟复杂的解析逻辑都是SpEL里边~  这里只是使用一下而已~
    @Override
    @Nullable
    public Object evaluate(@Nullable String value, BeanExpressionContext evalContext) throws BeansException {
    	if (!StringUtils.hasLength(value)) {
    		return value;
    	}
    	try {
    		Expression expr = this.expressionCache.get(value);
    		if (expr == null) {
    			// 注意:此处isTemplte=true
    			expr = this.expressionParser.parseExpression(value, this.beanExpressionParserContext);
    			this.expressionCache.put(value, expr);
    		}    
    		// 构建getValue计算时的执行上下文~~~
    		// 做种解析BeanName的ast为;org.springframework.expression.spel.ast.PropertyOrFieldReference
    		StandardEvaluationContext sec = this.evaluationCache.get(evalContext);
    		if (sec == null) {
    			// 此处指定的rootObject为:evalContext   --> BeanExpressionContext 
    			sec = new StandardEvaluationContext(evalContext);
    			// 此处新增了4个,加上一个默认的   所以一共就有5个属性访问器了
    			// 这样我们的SpEL就能访问BeanFactory、Map、Enviroment等组件了~
    			// BeanExpressionContextAccessor表示调用bean的方法~~~~(比如我们此处就是使用的它)  最终执行者为;BeanExpressionContext   它持有BeanFactory的引用嘛~
    			// 如果是单村的Bean注入,最终使用的也是BeanExpressionContextAccessor 目前没有找到BeanFactoryAccessor的用于之地~~~
    			// addPropertyAccessor只是:addBeforeDefault 所以只是把default的放在了最后,我们手动add的还是保持着顺序的~
    			// 注意:这些属性访问器是有先后顺序的,具体看下面~~~
    			sec.addPropertyAccessor(new BeanExpressionContextAccessor());
    			sec.addPropertyAccessor(new BeanFactoryAccessor());
    			sec.addPropertyAccessor(new MapAccessor());
    			sec.addPropertyAccessor(new EnvironmentAccessor());    
    			// setBeanResolver不是接口方法,仅仅辅助StandardEvaluationContext 去获取Bean
    			sec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory()));
    			sec.setTypeLocator(new StandardTypeLocator(evalContext.getBeanFactory().getBeanClassLoader()));    
    			// 若conversionService不为null,就使用工厂的。否则就使用SpEL里默认的DefaultConverterService那个  
    			// 最后包装成TypeConverter给set进去~~~
    			ConversionService conversionService = evalContext.getBeanFactory().getConversionService();
    			if (conversionService != null) {
    				sec.setTypeConverter(new StandardTypeConverter(conversionService));
    			}    
    			// 这个很有意思,是一个protected的空方法,因此我们发现若我们自己要自定义BeanExpressionResolver,完全可以继承自StandardBeanExpressionResolver
    			// 因为我们绝大多数情况下,只需要提供更多的计算环境即可~~~~~
    			customizeEvaluationContext(sec);
    			this.evaluationCache.put(evalContext, sec);
    		}
    		return expr.getValue(sec);
    	} catch (Throwable ex) {
    		throw new BeanExpressionException("Expression parsing failed", ex);
    	}
    }
    
    //Spring留给我们扩展的SPI	
    protected void customizeEvaluationContext(StandardEvaluationContext evalContext) {
    }
}

如上,整个@Value的解析过程至此就全部完成了。可能有小伙伴会问:怎么不见Resource这种注入呢?其实,从上面不难看出,这个是ConversionService去做的事,它能够把一个字符串转换成Resource对象,仅此而已

总得来说@Value它自己做的事本身还是非常单一的:依赖注入,只是它把众多功能都很好的像插件一样插拔进来了,从而对用户很友好的显示了显它的神通广大~

需要注意的是,在整个依赖的解析过程中,有两个非常重要的接口:BeanExpressionResolver和AutowireCandidateResolver都扮演着重要角色。

自定义扩展@Value的功能

因为Spring上下文默认是这么注册的beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));,所以我们的思路应该是替换掉它。

@Value中使用${}读取不存在的key时,不报错而是原样输出的问题

我们通用的一个观念是这样的:若使用@Value("${app.full2}")给字段赋值,若key不存在启动应该报错,这样才符合我们预期。但是Spring默认是启动并没有报错,而且给我原样输出了。

解析占位符这款发生在:DefaultListableBeanFactory#resolveDependency中,里面有一句代码是:

@Override
public String resolveEmbeddedValue(String value) {
    if (value == null) {
        return null;
    }
    String result = value;
    for (StringValueResolver resolver : this.embeddedValueResolvers) {
        result = resolver.resolveStringValue(result);
        if (result == null) {
            return null;
        }
    }
    return result;
}

同样的代码不同的现象,问题就出现在这里resolver.resolveStringValue(result)。这里总结一下,给AbstractBeanFactory设置处理器的地方有两个:

第一处:

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean {

    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    	...
    	// 若没有指定EmbeddedValueResolver, 这里就用个匿名函数实现  做个保底嘛~~~
    	if (!beanFactory.hasEmbeddedValueResolver()) {
    		beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
    			@Override
    			public String resolveStringValue(String strVal) {
    				return getEnvironment().resolvePlaceholders(strVal);
    			}
    		});
    	}
    	//...
    }
	
}

第二处:

//@since 3.1
public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfigurer implements BeanNameAware, BeanFactoryAware {
    protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) {
    	...
    	// Spring3.0后  加载完配置文件后,会把这个处理器放进Bean工厂里面去。
    	// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
    	beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
    }
}

它有两个子类:PropertyPlaceholderConfigurer,它的StringValueResolver实现为一个内部类实现的。另一个子类PropertySourcesPlaceholderConfigurer,它的实现StringValueResolver为一个lambda表达式。

它俩有个共同点:最终的解析都依赖于PropertyPlaceholderHelper并且,并且ignoreUnresolvablePlaceholders属性均为默认的fasle。

所以得出结论:若你手动配置过上面两个PlaceholderConfigurerSupport子类Bean,并且没有改变过ignoreUnresolvablePlaceholders这个值,那你最终会使用它们去解析${}占位符,从而如果找不到key就启动报错了。

但是若你没有手动配置过,那将最终交给AbstractBeanFactory的那个内部类处理,也就是这句话:return getEnvironment().resolvePlaceholders(strVal);而它最终解析如下:

public abstract class AbstractEnvironment implements ConfigurableEnvironment {

    // 两个接口方法。显然AbstractBeanFactory默认实现为这个方法,而并非Required的~~~
    @Override
    public String resolvePlaceholders(String text) {
        return this.propertyResolver.resolvePlaceholders(text);
    }
    @Override
    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        return this.propertyResolver.resolveRequiredPlaceholders(text);
    }
}

public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
    // 调用的resolvePlaceholders这个方法,所以默认情况下  即使key不存在  也是没关系的
    @Override
    public String resolvePlaceholders(String text) {
        if (this.nonStrictHelper == null) {
            this.nonStrictHelper = createPlaceholderHelper(true);
        }
        return doResolvePlaceholders(text, this.nonStrictHelper);
    }
    @Override
    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        if (this.strictHelper == null) {
            this.strictHelper = createPlaceholderHelper(false);
        }
        return doResolvePlaceholders(text, this.strictHelper);
    }
}

如上,就能解释了为何有时候你使用@Value找不到key就启动报错,有时候却原样输出呢? 这就是其根本原因。

关于SpringBoot环境下,默认情况下都是key必须存在的,否则启动报错。原因如下:

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PropertyPlaceholderAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

}

而在spring.factories文件里配置有它: 

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

 总之:使用@Value注入时请保证key是存在的,否则建议请使用defaultValue语法处理

 

posted @   残城碎梦  阅读(1087)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
历史上的今天:
2021-11-28 你知道为什么HashMap是线程不安全的吗?
2021-11-28 Java序列化与反序列化三连问:是什么?为什么要?如何做?
2021-11-28 什么情况用ArrayList or LinkedList呢?
2021-11-28 你能谈谈HashMap怎样解决hash冲突吗
2021-11-28 谈谈这几个常见的多线程面试题
2021-11-28 你能说说进程与线程的区别吗
2021-11-28 谈谈 Redis 的过期策略
点击右上角即可分享
微信分享提示