Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to use @ Value injection complex type in Spring

2025-02-25 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly shows you "how to use @ Value injection complex types in Spring". The content is simple and clear. I hope it can help you solve your doubts. Let me lead you to study and learn this article "how to use @ Value injection complex types in Spring".

Why is it that segmented strings can be injected into arrays? So I went to the step-by-step breakpoint to go through the process of injecting attributes into the @ value, and found the root cause.

The statement that @ Value does not support complex type encapsulation (arrays, Map, objects, etc.) is indeed problematic and not rigorous enough, because complex types can be injected in special cases.

Let's first sort out the @ Value injection process for attributes.

Let's first explain our code:

A yml file a.yml

Test: a,b,c,d

A 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 ("length:" + test.length);}}

Main method:

@ 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! Let's start the analysis.

1. Start with the post-processing of AutowiredAnnotationBeanPostProcessor

Not to mention the process of initializing Bean with too much Spring, we go directly to the post processor AutowiredAnnotationBeanPostProcessor of Bean's attribute injection.

The processInjection () method in this class completes the parsing and injection of @ Autowired, @ Inject, @ Value annotations in Bean.

This method completes the parsing and injection of @ Autowired, @ Inject, @ Value annotations in Bean public void processInjection (Object bean) throws BeanCreationException {Class clazz = bean.getClass () / / find all the elements on the class that need to be automatically injected / / (return @ Autowired, @ Inject, @ Value annotated fields and methods as objects of the InjectionMetadata class) 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. Then enter the inject () method of InjectionMetadata

The inject () method is a loop of the annotation information parsed in the previous step. The object wrapped by the annotation method or field is a class of InjectedElement type. InjectedElement is an abstract class. There are two main implementations of it: the AutowiredFieldElement class is generated for the annotation field, and the AutowiredMethodElement class is generated for the annotation method.

We only analyze the injection flow of @ Value annotation fields here, so the next step is to go to the inject () method of the AutowiredFieldElement class.

There are two major steps in this method:

Get the value to be injected

Through reflection, put the value on the set field

The process of obtaining the value to be injected is complicated, and the second step is to complete the set value with two lines of code.

For specific logic, take a look at the comments I wrote on the following code

Protected void inject (Object bean, @ Nullable String beanName, @ Nullable PropertyValues pvs) throws Throwable {Field field = (Field) this.member; Object value; if (this.cached) {/ get value = resolvedCachedArgument (beanName, this.cachedFieldValue) from the cache first } else {/ if it is not in the cache, go to the following logic processing: DependencyDescriptor desc = new DependencyDescriptor (field, this.required); desc.setContainingClass (bean.getClass ()) Set autowiredBeanNames = new LinkedHashSet (1); Assert.state (beanFactory! = null, "No BeanFactory available") This is critical to the problem we are discussing today. Get a type converter TypeConverter typeConverter = beanFactory.getTypeConverter () Try {/ get the value (key point, a TypeConverter is passed in here) value = beanFactory.resolveDependency (desc, beanName, autowiredBeanNames, typeConverter) / the value returned by the above method is the value to be injected / debugged through the breakpoint We can find that the string "arecrenerbjcpend" that we configured in the configuration file yml has been changed into a String [] array} catch (BeansException ex) {throw new UnsatisfiedDependencyException (null, beanName, new InjectionPoint (field), ex) } synchronized (this) {. This is not the focus of our discussion, so we removed}} if (value! = null) {this is the second step, assign ReflectionUtils.makeAccessible (field) Field.set (bean, value);}

Judging from the above code, all the emphasis falls on this line of code.

Value = beanFactory.resolveDependency (desc, beanName, autowiredBeanNames, typeConverter)

It is inferred that the resolveDependency method should read the configuration file string, and then use the string to split the converted array.

So how exactly did you convert it? Let's keep following up!

Enter the resolveDependency () method, in which the logic is very simple to make some judgments, the real implementation is actually the doResolveDependency () method, follow up.

According to the @ Value annotation, the content of the configuration is parsed from the configuration file a.yml: "a _

At this point, we get that the value is still the string configured by the configuration file, and it does not become the type of String [] string array we want.

Let's move on to get a TypeConverter type converter, where the type converter is passed in from above, the specific type SimpleTypeConverter class.

Then, using the convertIfNecessary method of this type converter, we convert our string "a _ d" to String [] array.

So now we know that the value we got from the configuration file passed the Spring converter, and after calling the convertIfNecessary method, the type was automatically converted. So how exactly does this converter work?

Continue to study ~ ~

Then the next thing to study is the working principle of Spring's TypeConverter.

First of all, we know here that the converter coming in from outside is a converter called SimpleTypeConverter.

The converter is obtained by the org.springframework.beans.factory.support.AbstractBeanFactory#getTypeConverter method.

@ Override public TypeConverter getTypeConverter () {TypeConverter customConverter = getCustomTypeConverter (); if (customConverter! = null) {return customConverter;} else {/ if there is no user-defined TypeConverter, then use the default SimpleTypeConverter bar / / Build default TypeConverter, registering custom editors. SimpleTypeConverter typeConverter = new SimpleTypeConverter (); register some default ConversionService typeConverter.setConversionService (getConversionService ()); then register some default CustomEditors registerCustomEditors (typeConverter); return typeConverter;}}

Some converters are registered in the default SimpleTypeConverter. From the debug process, we can see that 12 PropertyEditor are injected by default.

Where did these 12 PropertyEditor be injected? You can look at the registerCustomEditors (typeConverter) method, so I won't expand it here. I'll say it directly, it's injected into it through the ResourceEditorRegistrar class.

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));}}

Now let's go back to the convertIfNecessary method of SimpleTypeConverter, which is actually the method of TypeConverterSupport, the parent class of SimpleTypeConverter, and what is called in this parent method is the convertIfNecessary method of TypeConverterDelegate class (one is lazier than the other, is not working).

Finally, we focus on the convertIfNecessary method of TypeConverterDelegate.

This method has a lot of content, but the overall idea is to select the corresponding PropertyEditor or ConversionService according to the type you want to convert, and then convert the type.

From the 12 PropertyEditor injected above, we can see that what we match is

This line of code doRegisterEditor (registry, Class [] .class, new ClassArrayEditor (classLoader)); injected ClassArrayEditor.

So I ClassArrayEditor this class is fine, this class is very simple, mainly depends on the setAsText method

Public void setAsText (String text) throws IllegalArgumentException {if (StringUtils.hasText (text)) {/ here the string is converted into String array String [] classNames = StringUtils.commaDelimitedListToStringArray (text); Class [] classes = new Class [classNames.length]; for (int I = 0) I < classNames.length; iTunes +) {String className = classNams [I] .trim (); classes [I] = ClassUtils.resolveClassName (className, this.classLoader);} setValue (classes) } else {setValue (null);}}

In this method, through

The commaDelimitedListToStringArray (text) method of Spring's string utility class StringUtils converts a string into an array, which is split by ",".

So far, we know why @ Value can register the split string of "," in the array.

In fact, @ Value can inject URI, Class, File, Resource, and so on, and what type @ Value can inject depends entirely on whether you can find a converter that handles String to injection types.

In fact, not all of the 12 listed above are default. There are 47 other converters in the system, but the above 12 have higher priority. In fact, there are more than 40 converters below, so you can see that there are many types of @ Value that can be injected.

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);}}

The point is, after analyzing it for so long, what should we do if we want to register a class that we customize?

Well, now that we know the principle of @ Value injection and the process of intermediate type conversion, we know where to start, which is to write our own PropertyEditor and register it in Spring's type converter.

To be clear, our requirement is to configure the string in the yml configuration file, and then inject it into a custom object through @ Value.

Our custom object Car.java

Public class Car {private String color; private String name; / / omit get set method}

Yml configuration file, configure car: red | Ferrari, we use here | Segmentation

Test: a _

Bean A.java for testing

@ 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 ("length:" + test.length); System.out.println ("Custom Car has been successfully registered through @ Value"); System.out.println (car.toString ());}}

The following is the type converter that writes our PropertyEditor and then registers it into Spring's Spring.

Customize a propertyEditor class: CarPropertyEditor

Do not directly implement the PropertyEditor interface here, that would be too troublesome, because there are many interfaces to implement

We do this here by inheriting the PropertyEditorSupport class and overriding the key methods

There are mainly two methods: setAsText and getAsText

/ * * @ author KinYang.Lau * @ date 11:00 on 2020-12-18 * * customize a propertyEditor, * do not directly implement the PropertyEditor interface here, that would be too troublesome, because there are many interfaces to implement * We here inherit the PropertyEditorSupport class To do this by overriding the key methods * mainly two methods, setAsText and getAsText methods * / public class CarPropertyEditor extends PropertyEditorSupport {@ Override public void setAsText (String text) throws IllegalArgumentException {/ this implements the logical if (StringUtils.hasText (text)) {String [of our string to the custom object] ] 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 (): ");}}

So how do you register with the type converter in Spring's Spring?

This is also simple, the ConfigurableBeanFactory interface has a

Void registerCustomEditor (Class requiredType, Class

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report