您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“Spring中@Value注入復雜類型怎么用”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“Spring中@Value注入復雜類型怎么用”這篇文章吧。
為什么用,分割的字符串可以注入數組?于是我就去一步一步的斷點去走了一遍@value注入屬性的過程,才發現了根本原因。
@Value不支持復雜類型封裝(數組、Map、對象等)這個說法確實是有問題的,不夠嚴謹,因為在特殊情況下,是可以注入復雜類型的。
先交代一下我們的代碼:
一個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!下面開始分析
過多的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); } }
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”
到這里我們得到值還是配置文件配置的字符串,并沒有變成我們想要的String[]字符串數組類型。
我們繼續往下走,下面是獲取一個TypeConverter類型轉換器,這里的類型轉換器是上面傳進來的,具體類型SimpleTypeConverter類。
然后通過這個類型轉換器的convertIfNecessary方法把,我們的字符串"a,b,c,d"轉換成了String[]數組。
所以我們現在知道了,我們從配置文件獲取到的值,通過了Spring轉換器,調用了convertIfNecessary方法后,進行了類型自動轉換。那么這轉換器到底是怎么進行工作的呢?
繼續研究~~
那接下來要研究的就是Spring的TypeConverter的工作原理問題了
首先我們這里知道了外面傳進來的那個轉換器是一個叫SimpleTypeConverter 的轉換器。
這轉換器是org.springframework.beans.factory.support.AbstractBeanFactory#getTypeConverter方法得到的
@Override public TypeConverter getTypeConverter() { TypeConverter customConverter = getCustomTypeConverter(); if (customConverter != null) { return customConverter; } else { /// 如果沒有 用戶自定的TypeConverter 那就用 默認的SimpleTypeConverter吧 // Build default TypeConverter, registering custom editors. SimpleTypeConverter typeConverter = new SimpleTypeConverter(); 注冊一些默認的ConversionService typeConverter.setConversionService(getConversionService()); 再注冊一些默認的CustomEditors registerCustomEditors(typeConverter); return typeConverter; } }
默認的SimpleTypeConverter里面注冊了一些轉換器,從debug過程我們可以看到默認是注入了12個PropertyEditor
這12個PropertyEditor是在哪注入的呢?大家可以看registerCustomEditors(typeConverter)方法,這里就不展開了,我直接說了,是通過ResourceEditorRegistrar類注入進去的。
@Override public 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,d car: 紅色|法拉利
用于測試的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 方法
/** * @author KinYang.Lau * @date 2020/12/18 11:00 上午 * * 自定義 一個 propertyEditor, * 這里不要直接去實現PropertyEditor接口,那樣太麻煩了,因為有很多接口要實現 * 我們這里通過繼承PropertyEditorSupport類,通過覆蓋關鍵方法來做 * 主要是兩個方法 setAsText 和 getAsText 方法 */ public class CarPropertyEditor extends PropertyEditorSupport { @Override public 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); } } @Override public String getAsText() { Car value = (Car) getValue(); return (value != null ? value.toString() : ""); } }
那么如何注冊到Spring的Spring的類型轉換器中呢?
這個也簡單,ConfigurableBeanFactory 接口有一個
void registerCustomEditor(Class<?> requiredType, Class<? extends PropertyEditor> propertyEditorClass);方法就是用于注冊CustomEditor的。
所以我們寫一個BeanFactory的后置處理器就可以了。
/** * @author KinYang.Lau * @date 2020/12/18 10:54 上午 */ @Component public class MyCustomEditorConfigurer implements BeanFactoryPostProcessor, Ordered { private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { /// 把我們自定義的 轉換器器注冊進去 beanFactory.registerCustomEditor(Car.class, CarPropertyEditor.class); } @Override public 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注入復雜類型怎么用”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。