aspcms网站后台登陆界面模版,怎样给自己的网站做防红连接,网站建设找哪家好,建设四川网站问题复现
在装配对象成员属性时#xff0c;我们常常会使用 Autowired 来装配。但是#xff0c;有时候我们也使用 Value 进行装配。不过这两种注解使用风格不同#xff0c;使用 Autowired 一般都不会设置属性值#xff0c;而 Value 必须指定一个字符串值#xff0c;因为其…问题复现
在装配对象成员属性时我们常常会使用 Autowired 来装配。但是有时候我们也使用 Value 进行装配。不过这两种注解使用风格不同使用 Autowired 一般都不会设置属性值而 Value 必须指定一个字符串值因为其定义做了要求定义代码如下public interface Value {/*** The actual value expression mdash; for example, code#{systemProperties.myProp}/code.*/String value();}另外在比较这两者的区别时我们一般都会因为 Value 常用于 String 类型的装配而误以为 Value 不能用于非内置对象的装配实际上这是一个常见的误区。例如我们可以使用下面这种方式来 Autowired 一个属性成员Value(#{student})
private Student student;其中 student 这个 Bean 定义如下Bean
public Student student(){Student student createStudent(1, xie);return student;
}当然正如前面提及我们使用 Value 更多是用来装配 String而且它支持多种强大的装配方式典型的方式参考下面的示例//注入正常字符串
Value(我是字符串)
private String text; //注入系统参数、环境变量或者配置文件中的值
Value(${ip})
private String ip//注入其他Bean属性其中student为bean的IDname为其属性
Value(#{student.name})
private String name;上面我给你简单介绍了 Value 的强大功能以及它和 Autowired 的区别。那么在使用 Value 时可能会遇到那些错误呢这里分享一个最为典型的错误即使用 Value 可能会注入一个不是预期的值。我们可以模拟一个场景我们在配置文件 application.properties 配置了这样一个属性usernameadmin
passwordpass然后我们在一个 Bean 中分别定义两个属性来引用它们RestController
Slf4j
public class ValueTestController {Value(${username})private String username;Value(${password})private String password;RequestMapping(path user, method RequestMethod.GET)public String getUser(){return username : password;};
}当我们去打印上述代码中的 username 和 password 时我们会发现 password 正确返回了但是 username 返回的并不是配置文件中指明的 admin而是运行这段程序的计算机用户名。很明显使用 Value 装配的值没有完全符合我们的预期。
案例解析
通过分析运行结果我们可以知道 Value 的使用方式应该是没有错的毕竟 password 这个字段装配上了但是为什么 username 没有生效成正确的值接下来我们就来具体解析下。我们首先了解下对于 ValueSpring 是如何根据 Value 来查询“值”的。我们可以先通过方法 DefaultListableBeanFactory#doResolveDependency 来了解 Value 的核心工作流程代码如下Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, Nullable String beanName,Nullable SetString autowiredBeanNames, Nullable TypeConverter typeConverter) throws BeansException {//省略其他非关键代码Class? type descriptor.getDependencyType();//寻找ValueObject value getAutowireCandidateResolver().getSuggestedValue(descriptor);if (value ! null) {if (value instanceof String) {//解析Value值String strVal resolveEmbeddedValue((String) value);BeanDefinition bd (beanName ! null containsBean(beanName) ?getMergedBeanDefinition(beanName) : null);value evaluateBeanDefinitionString(strVal, bd);}//转化Value解析的结果到装配的类型TypeConverter converter (typeConverter ! null ? typeConverter : getTypeConverter());try {return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());}catch (UnsupportedOperationException ex) {//异常处理}}//省略其他非关键代码}可以看到Value 的工作大体分为以下三个核心步骤。
寻找 value 在这步中主要是判断这个属性字段是否标记为 Value依据的方法参考 QualifierAnnotationAutowireCandidateResolver#findValueNullable
protected Object findValue(Annotation[] annotationsToSearch) {if (annotationsToSearch.length 0) { AnnotationAttributes attr AnnotatedElementUtils.getMergedAnnotationAttributes(AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType);//valueAnnotationType即为Valueif (attr ! null) {return extractValue(attr);}}return null;
}解析 Value 的字符串值 如果一个字段标记了 Value则可以拿到对应的字符串值然后就可以根据字符串值去做解析最终解析的结果可能是一个字符串也可能是一个对象这取决于字符串怎么写。 将解析结果转化为要装配的对象的类型 当拿到第二步生成的结果后我们会发现可能和我们要装配的类型不匹配。假设我们定义的是 UUID而我们获取的结果是一个字符串那么这个时候就会根据目标类型来寻找转化器执行转化字符串到 UUID 的转化实际上发生在 UUIDEditor 中public class UUIDEditor extends PropertyEditorSupport {Overridepublic void setAsText(String text) throws IllegalArgumentException {if (StringUtils.hasText(text)) {//转化操作setValue(UUID.fromString(text.trim()));}else {setValue(null);}}//省略其他非关代码}通过对上面几个关键步骤的解析我们大体了解了 Value 的工作流程。结合我们的案例很明显问题应该发生在第二步即解析 Value 指定字符串过程执行过程参考下面的关键代码行String strVal resolveEmbeddedValue((String) value);这里其实是在解析嵌入的值实际上就是“替换占位符”工作。具体而言它采用的是 PropertySourcesPlaceholderConfigurer 根据 PropertySources 来替换。不过当使用 ${username} 来获取替换值时其最终执行的查找并不是局限在 application.property 文件中的。通过调试我们可以看到下面的这些“源”都是替换依据 [ConfigurationPropertySourcesPropertySource {nameconfigurationProperties},
StubPropertySource {nameservletConfigInitParams}, ServletContextPropertySource {nameservletContextInitParams}, PropertiesPropertySource {namesystemProperties}, OriginAwareSystemEnvironmentPropertySource {namesystemEnvironment}, RandomValuePropertySource {namerandom},
OriginTrackedMapPropertySource {nameapplicationConfig: classpath:/application.properties]},
MapPropertySource {namedevtools}]而具体的查找执行我们可以通过下面的代码PropertySourcesPropertyResolver#getProperty来获取它的执行方式Nullable
protected T T getProperty(String key, ClassT targetValueType, boolean resolveNestedPlaceholders) {if (this.propertySources ! null) {for (PropertySource? propertySource : this.propertySources) {Object value propertySource.getProperty(key);if (value ! null) {//查到value即退出 return convertValueIfNecessary(value, targetValueType);}}}return null;
}从这可以看出在解析 Value 字符串时其实是有顺序的查找的源是存在 CopyOnWriteArrayList 中在启动时就被有序固定下来一个一个“源”执行查找在其中一个源找到后就可以直接返回了。如果我们查看 systemEnvironment 这个源会发现刚好有一个 username 和我们是重合的且值不是 pass。 所以讲到这里你应该知道问题所在了吧这是一个误打误撞的例子刚好系统环境变量systemEnvironment中含有同名的配置。实际上对于系统参数systemProperties也是一样的这些参数或者变量都有很多如果我们没有意识到它的存在起了一个同名的字符串作为 Value 的值则很容易引发这类问题。
问题修正
针对这个案例有了源码的剖析我们就可以很快地找到解决方案了。例如我们可以避免使用同一个名称具体修改如下user.nameadmin
user.passwordpass但是如果我们这么改的话其实还是不行的。实际上通过之前的调试方法我们可以找到类似的原因在 systemProperties 这个 PropertiesPropertySource 源中刚好存在 user.name真是无巧不成书。所以命名时我们一定要注意不仅要避免和环境变量冲突也要注意避免和系统变量等其他变量冲突这样才能从根本上解决这个问题。