我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Spring中@Value注入复杂类型怎么用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Spring中@Value注入复杂类型怎么用

这篇文章主要为大家展示了“Spring中@Value注入复杂类型怎么用”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Spring中@Value注入复杂类型怎么用”这篇文章吧。

为什么用,分割的字符串可以注入数组?于是我就去一步一步的断点去走了一遍@value注入属性的过程,才发现了根本原因。

@Value不支持复杂类型封装(数组、Map、对象等)这个说法确实是有问题的,不够严谨,因为在特殊情况下,是可以注入复杂类型的。

先来梳理一下@Value对属性的注入流程

先交代一下我们的代码:

一个yml文件a.yml

test: a,b,c,d

一个Bean A.java

@Component@PropertySource(value = {"classpath:a.yml"},ignoreResourceNotFound = true, encoding = "utf-8")public class A {@Value("${test}")private String[] test;public void test(){System.out.println("test:"+Arrays.toString(test));System.out.println("长度:"+test.length);}}

main方法:

@Configuration@ComponentScan("com.kinyang")public class HelloApp {public static void main(String[] args) {AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(HelloApp.class);A bean = ac.getBean(A.class);bean.test();}}

ok!下面开始分析

1、从AutowiredAnnotationBeanPostProcessor后置处理说起

过多的Spring初始化Bean的流程就不说了,我们直接定位到Bean的属性注入的后置处理器AutowiredAnnotationBeanPostProcessor。

此类中的processInjection()方法中完成了Bean 中@Autowired、@Inject、 @Value 注解的解析并注入的功能。

 此方法中完成了Bean 中@Autowired、@Inject、 @Value 注解的解析并注入的功能public void processInjection(Object bean) throws BeanCreationException {Class<?> clazz = bean.getClass();///  找到 类上所有的需要自动注入的元素// (把@Autowired、@Inject、 @Value注解的字段和方法包装成InjectionMetadata类的对象返回)InjectionMetadata metadata = findAutowiringMetadata(clazz.getName(), clazz, null);try {metadata.inject(bean, null, null);}catch (BeanCreationException ex) {throw ex;}catch (Throwable ex) {throw new BeanCreationException("Injection of autowired dependencies failed for class [" + clazz + "]", ex);}}

2、接着进入InjectionMetadata的inject()方法

inject()方法就是一个循环上面一步解析出来的注解信息,注解的方法或者字段包装后的对象是InjectedElement类型的类,InjectedElement是一个抽象类,他的实现主要有两个:对注解字段生成的是AutowiredFieldElement类,对注解方法生成的是AutowiredMethodElement类。

我们这里只分析@Value注解字段的注入流程,所以下一步会进到AutowiredFieldElement类的inject()方法.

此方法就两大步骤:

  • 获取要注入的value

  • 通过反射,把值去set字段上

其中获取要注入的value过程比较复杂,第二步set值就两行代码搞定

具体逻辑看下面代码上我写的注释

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {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");  这个对我们今天讨论的问题很关键  获取一个 类型转换器TypeConverter typeConverter = beanFactory.getTypeConverter();try {///   获取值(重点,这里把一个TypeConverter传进去了)value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);///  经过上面的方法返回来的 value 就是要注入的值了///  通过断点调试,我们可以发现我们在配置文件yml中配置的 “a,b,c,d”字符串已经变成了一个String[]数组}catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);}synchronized (this) {..... 这里不是我们本次讨论的重点所以就去掉了}}if (value != null) { 这里就是第二步,赋值ReflectionUtils.makeAccessible(field);field.set(bean, value);}}}

从上面代码来看,所有重点就都落到了这行代码

value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);

推断下来resolveDependency方法里应该是读取配置文件字符串,然后将字符串用,分割转换了数组。

那么具体怎么转换的呢?我们继续跟进!

进入resolveDependency()方法,里面逻辑很简单做了一些判断,真正实现其实是doResolveDependency()方法,进行跟进。

根据@Value注解,从配置文件a.yml中解析出配置的内容:“a,b,c,d”

Spring中@Value注入复杂类型怎么用

Spring中@Value注入复杂类型怎么用

到这里我们得到值还是配置文件配置的字符串,并没有变成我们想要的String[]字符串数组类型。

我们继续往下走,下面是获取一个TypeConverter类型转换器,这里的类型转换器是上面传进来的,具体类型SimpleTypeConverter类。

然后通过这个类型转换器的convertIfNecessary方法把,我们的字符串"a,b,c,d"转换成了String[]数组。

Spring中@Value注入复杂类型怎么用

所以我们现在知道了,我们从配置文件获取到的值,通过了Spring转换器,调用了convertIfNecessary方法后,进行了类型自动转换。那么这转换器到底是怎么进行工作的呢?

继续研究~~

那接下来要研究的就是Spring的TypeConverter的工作原理问题了

首先我们这里知道了外面传进来的那个转换器是一个叫SimpleTypeConverter 的转换器。

这转换器是org.springframework.beans.factory.support.AbstractBeanFactory#getTypeConverter方法得到的

@Overridepublic TypeConverter getTypeConverter() {TypeConverter customConverter = getCustomTypeConverter();if (customConverter != null) {return customConverter;}else {/// 如果没有 用户自定的TypeConverter 那就用 默认的SimpleTypeConverter吧// Build default TypeConverter, registering custom editors.SimpleTypeConverter typeConverter = new SimpleTypeConverter(); 注册一些默认的ConversionServicetypeConverter.setConversionService(getConversionService());  再注册一些默认的CustomEditorsregisterCustomEditors(typeConverter);return typeConverter;}}

默认的SimpleTypeConverter里面注册了一些转换器,从debug过程我们可以看到默认是注入了12个PropertyEditor

Spring中@Value注入复杂类型怎么用

这12个PropertyEditor是在哪注入的呢?大家可以看registerCustomEditors(typeConverter)方法,这里就不展开了,我直接说了,是通过ResourceEditorRegistrar类注入进去的。

@Overridepublic void registerCustomEditors(PropertyEditorRegistry registry) {ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver);doRegisterEditor(registry, Resource.class, baseEditor);doRegisterEditor(registry, ContextResource.class, baseEditor);doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor));doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor));doRegisterEditor(registry, File.class, new FileEditor(baseEditor));doRegisterEditor(registry, Path.class, new PathEditor(baseEditor));doRegisterEditor(registry, Reader.class, new ReaderEditor(baseEditor));doRegisterEditor(registry, URL.class, new URLEditor(baseEditor));ClassLoader classLoader = this.resourceLoader.getClassLoader();doRegisterEditor(registry, URI.class, new URIEditor(classLoader));doRegisterEditor(registry, Class.class, new ClassEditor(classLoader));doRegisterEditor(registry, Class[].class, new ClassArrayEditor(classLoader));if (this.resourceLoader instanceof ResourcePatternResolver) {doRegisterEditor(registry, Resource[].class,new ResourceArrayPropertyEditor((ResourcePatternResolver) this.resourceLoader, this.propertyResolver));}}

现在我们回到 SimpleTypeConverter 的convertIfNecessary方法里去,这个方法其实是SimpleTypeConverter的父类TypeConverterSupport的方法,而这个父类方法里调用的又是TypeConverterDelegate类的convertIfNecessary方法(一个比一个懒,哈哈哈就是自己不干活)

最后我们重点来分析TypeConverterDelegate的convertIfNecessary方法。

这个方法内容比较多,但是整体思路就是 根据最后想转换的类型,选择出对应的PropertyEditor或者ConversionService,然后进行类型转换。

从上面的看的注入的12个PropertyEditor中,我们就可以看出来了,我们匹配到的是

这行代码doRegisterEditor(registry, Class[].class, new ClassArrayEditor(classLoader));注入的ClassArrayEditor。

所以我ClassArrayEditor这个类就可以了,这个类就很简单了,主要看setAsText方法

public void setAsText(String text) throws IllegalArgumentException {if (StringUtils.hasText(text)) {///  这里通过StringUtils 把字符串,转换成 String数组String[] classNames = StringUtils.commaDelimitedListToStringArray(text);Class<?>[] classes = new Class<?>[classNames.length];for (int i = 0; i < classNames.length; i++) {String className = classNames[i].trim();classes[i] = ClassUtils.resolveClassName(className, this.classLoader);}setValue(classes);}else {setValue(null);}}

这个方法里通过

Spring的字符串工具类StringUtils的commaDelimitedListToStringArray(text)方法把字符串转换成了数组,方法里就是通过 “,” 进行分割的。

到此为止,我们知道了@Value为什么可以把“,”分割的字符串注册到数组中了吧。

其实@Value可以注入URI、Class、File、Resource等等类型,@Value可以注入什么类型完全取决于能不能找到处理 String 到 注入类型的转换器。

上面列出来的12个其实不是全部默认的,系统还有47个其他的转换器,只不过是上面的12个优先级比较高而已,其实还有下面的40多个转换器,所以你看@Value可以注入的类型还会很多的。

private void createDefaultEditors() {this.defaultEditors = new HashMap<>(64);// Simple editors, without parameterization capabilities.// The JDK does not contain a default editor for any of these target types.this.defaultEditors.put(Charset.class, new CharsetEditor());this.defaultEditors.put(Class.class, new ClassEditor());this.defaultEditors.put(Class[].class, new ClassArrayEditor());this.defaultEditors.put(Currency.class, new CurrencyEditor());this.defaultEditors.put(File.class, new FileEditor());this.defaultEditors.put(InputStream.class, new InputStreamEditor());this.defaultEditors.put(InputSource.class, new InputSourceEditor());this.defaultEditors.put(Locale.class, new LocaleEditor());this.defaultEditors.put(Path.class, new PathEditor());this.defaultEditors.put(Pattern.class, new PatternEditor());this.defaultEditors.put(Properties.class, new PropertiesEditor());this.defaultEditors.put(Reader.class, new ReaderEditor());this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());this.defaultEditors.put(URI.class, new URIEditor());this.defaultEditors.put(URL.class, new URLEditor());this.defaultEditors.put(UUID.class, new UUIDEditor());this.defaultEditors.put(ZoneId.class, new ZoneIdEditor());// Default instances of collection editors.// Can be overridden by registering custom instances of those as custom editors.this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));// Default editors for primitive arrays.this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());// The JDK does not contain a default editor for char!this.defaultEditors.put(char.class, new CharacterEditor(false));this.defaultEditors.put(Character.class, new CharacterEditor(true));// Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));// The JDK does not contain default editors for number wrapper types!// Override JDK primitive number editors with our own CustomNumberEditor.this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));// Only register config value editors if explicitly requested.if (this.configValueEditorsActive) {StringArrayPropertyEditor sae = new StringArrayPropertyEditor();this.defaultEditors.put(String[].class, sae);this.defaultEditors.put(short[].class, sae);this.defaultEditors.put(int[].class, sae);this.defaultEditors.put(long[].class, sae);}}

重点来了,分析了这么久了,那么,如果我们想注册一个我们自定义的类该如何操作呢???

好了,既然知道了@Value的注入的原理和中间类型转换的过程,那我们就知道该从哪里下手了,那就是写一个我们自己的PropertyEditor,然后注册到Spring的类型转换器中。

先明确一下我们的需求,就是在yml配置文件中,配置字符串,然后通过@Value注入为一个自定义的对象。

我们的自定义对象 Car.java

public class Car {private String color;private String name;// 省略 get set方法}

yml配置文件,配置car: 红色|法拉利,我们这里用|分割

test: a,b,c,dcar: 红色|法拉利

用于测试的Bean A.java

@Component@PropertySource(value = {"classpath:a.yml"},ignoreResourceNotFound = true, encoding = "utf-8")public class A {@Value("${test}")private String[] test;@Value("${car}")private Car car;public void test(){System.out.println("test:"+Arrays.toString(test));System.out.println("长度:"+test.length);System.out.println("自定的Car 居然通过@Value注册成功了");System.out.println(car.toString());}}

下面就是写我们的PropertyEditor然后注册到Spring的Spring的类型转换器中。

  • 自定义 一个 propertyEditor类:CarPropertyEditor,

  • 这里不要直接去实现PropertyEditor接口,那样太麻烦了,因为有很多接口要实现

  • 我们这里通过继承PropertyEditorSupport类,通过覆盖关键方法来做

  • 主要是两个方法 setAsText 和 getAsText 方法

public class CarPropertyEditor extends PropertyEditorSupport {@Overridepublic void setAsText(String text) throws IllegalArgumentException {///  这实现我们的 字符串 转 自定义对象的 逻辑if (StringUtils.hasText(text)) {String[] split = text.split("\\|");Car car = new Car();car.setColor(split[0]);car.setName(split[1]);setValue(car);}else {setValue(null);}}@Overridepublic String getAsText() {Car value = (Car) getValue();return (value != null ? value.toString() : "");}}

那么如何注册到Spring的Spring的类型转换器中呢?

这个也简单,ConfigurableBeanFactory 接口有一个

void registerCustomEditor(Class<?> requiredType, Class<? extends PropertyEditor> propertyEditorClass);方法就是用于注册CustomEditor的。

所以我们写一个BeanFactory的后置处理器就可以了。

@Componentpublic class MyCustomEditorConfigurer implements BeanFactoryPostProcessor, Ordered {private int order = Ordered.LOWEST_PRECEDENCE;  // default: same as non-Ordered@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {///  把我们自定义的 转换器器注册进去beanFactory.registerCustomEditor(Car.class, CarPropertyEditor.class);}@Overridepublic int getOrder() {return this.order;}}

下面我运行一下程序,看看结果吧:

@Configuration@ComponentScan("com.kinyang")public class HelloApp {public static void main(String[] args) {AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(HelloApp.class);A bean = ac.getBean(A.class);bean.test();}}

Spring中@Value注入复杂类型怎么用

搞定!!!

以上是“Spring中@Value注入复杂类型怎么用”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网行业资讯频道!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Spring中@Value注入复杂类型怎么用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Spring中@Value注入复杂类型怎么用

这篇文章主要为大家展示了“Spring中@Value注入复杂类型怎么用”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Spring中@Value注入复杂类型怎么用”这篇文章吧。为什么用,分割的字符
2023-06-22

在Spring-Boot中怎么使用@Value注解注入集合类

这篇文章主要讲解了“在Spring-Boot中怎么使用@Value注解注入集合类”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“在Spring-Boot中怎么使用@Value注解注入集合类”吧
2023-06-20

Python中的复杂数据类型怎么使用

这篇文章主要介绍“Python中的复杂数据类型怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Python中的复杂数据类型怎么使用”文章能帮助大家解决问题。一、序列:序列是基类类型,序列扩展类
2023-06-30

mybatis怎么利用resultMap复杂类型list映射

这篇文章主要介绍“mybatis怎么利用resultMap复杂类型list映射”,在日常操作中,相信很多人在mybatis怎么利用resultMap复杂类型list映射问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家
2023-06-20

Spring依赖注入多种类型数据的代码怎么写

这篇文章主要介绍“Spring依赖注入多种类型数据的代码怎么写”,在日常操作中,相信很多人在Spring依赖注入多种类型数据的代码怎么写问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Spring依赖注入多种类
2023-06-29

使用Spring MVC如何实现将对象注入枚举类型中

今天就跟大家聊聊有关使用Spring MVC如何实现将对象注入枚举类型中,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。首先:这是一个枚举类:/** * 新闻类别 * @autho
2023-05-31

Spring怎么使用注解进行引用类型的自动装

这篇文章主要讲解了“Spring怎么使用注解进行引用类型的自动装”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Spring怎么使用注解进行引用类型的自动装”吧!一.案例分级简单解析:配置类替
2023-07-05

Python中的变量类型标注怎么用

这篇文章主要讲解了“Python中的变量类型标注怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python中的变量类型标注怎么用”吧!一、概述1、描述变量类型注解是用来对变量和函数的参
2023-07-06

Java中复合数据类型怎么用

这篇文章主要为大家展示了“Java中复合数据类型怎么用”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Java中复合数据类型怎么用”这篇文章吧。1、Java字符串在 Java 中字符串被作为 St
2023-06-25

Spring中的类型转换器怎么定义使用

这篇文章主要讲解了“Spring中的类型转换器怎么定义使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Spring中的类型转换器怎么定义使用”吧!1.类型转换器作用类型的转换赋值2.自定义
2023-07-04

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录