Spring - @Value function details

preface

For small partners engaged in java development, the spring framework is certainly familiar. Spring provides developers with a very rich api to meet our daily work needs.

If you want to create a bean instance, you can use @ Controller, @ Service, @ Repository, @ Component and other annotations.

If you want to inject an object by dependency, you can use @ Autowired and @ Resource annotations.

If you want to start a transaction, you can use the @ Transactional annotation.

If you want to dynamically read a system property in the configuration file, you can use the @ Value annotation.

Wait, there's a lot more...

Today, let's focus on the @ Value annotation, because it is a very useful but easily ignored annotation. Most people may only use some of its functions, which is a very regrettable thing.

So it's necessary to get to know @ Value again with you today.

1. Start with an example

If it is in the UserService class, you need to inject the system attribute into the userName variable. Usually, we write the following code:

@Service
public class UserService {

    @Value("${susan.test.userName}")
    private String userName;

    public String test() {
        System.out.println(userName);
        return userName;
    }
}

Specify the name of the system property susan.test.userName through the @ Value annotation, which needs to be wrapped in ${}.

In this way, spring will automatically help us inject the corresponding system attribute value into the userName variable.

However, the above function focuses on configuring system properties with the same name in the applicationContext.properties file (configuration file for short):

#Zhang San
susan.test.userName=\u5f20\u4e09

So, does the name really have to be exactly the same?

2. About property names

At this time, some friends may say that the parameter name defined in the @ ConfigurationProperties configuration class can be different from the system property name in the configuration file.

For example, the parameter name defined in the configuration class MyConfig class is userName:

@Configuration
@ConfigurationProperties(prefix = "susan.test")
@Data
public class MyConfig {
    private String userName;
}

The system attribute name configured in the configuration file is:

susan.test.user-name=\u5f20\u4e09

The userName used in the class is different from the user name used in the configuration file. However, after the test, it is found that the function can operate normally.

The system property name in the configuration file is used   Hump identification   or   spring can find the attribute name userName in the configuration class to assign values by combining lowercase letters with an underlined line.

It can be seen that the system attribute name in the configuration file can be different from that in the configuration class. However, there is a premise that the prefix susan.test must be the same.

Can the system attribute names defined in the @ Value annotation be different?

Answer: No. If it is different, an error will be reported directly when starting the project.

In addition, if only the system property name is specified in the @ Value annotation, but it is not actually configured in the configuration file, the same error will be reported as above.

Therefore, the system property name specified in the @ Value annotation must be the same as that in the configuration file.

3. Garbled code problem

  I wonder if the careful friends have found that the attribute value I configured: Zhang San is actually escaped.

susan.test.userName=\u5f20\u4e09

Why do you do this escape?

If Zhang San in Chinese is configured in the configuration file:

susan.test.userName=Zhang San

When you finally get the data, you will find that the userName is garbled:

å¼ ä¸

what?

Why is there garbled code?

A: in the CharacterReader class of springboot, the default encoding format is ISO-8859-1, which is responsible for reading the system properties in the. Properties file. If the system attribute contains Chinese characters, garbled code will appear.

 

So, how to solve the problem of garbled code?

At present, there are mainly three schemes:

  1. Manually convert the attribute value in ISO-8859-1 format to UTF-8 format.

  2. Set the encoding parameter, but this is only useful for the @ PropertySource annotation.

  3. Escape Chinese characters with unicode encoding.

Obviously @ Value does not support encoding parameter, so scheme 2 cannot.

If scheme 1 is used, the specific implementation code is as follows:

@Service
public class UserService {

    @Value(value = "${susan.test.userName}")
    private String userName;

    public String test() {
        String userName1 = new String(userName.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
        System.out.println();
        return userName1;
    }
}

It can really solve the problem of garbled code.

However, if the project contains a large number of Chinese system attribute values, such a special conversion code needs to be added every time. There are a lot of duplicate code. Do you feel a little sick?

I was disgusted.

So, how to solve the problem of code duplication?

A: convert the Chinese content of the attribute value into unicode.

Similar to this:

susan.test.userName=\u5f20\u4e09

This method can also solve the problem of garbled code without disgusting duplicate code. But a little extra conversion work needs to be done, but this conversion is very easy because there are ready-made online conversion tools.

This tool is recommended for conversion: http://www.jsons.cn/unicode/

Here's a little secret: if you use a configuration file in. yml or. yaml format, there will be no Chinese garbled code problem.

Why?

Because the configuration file in. yml or. yaml format will eventually be parsed using the Unicode reader class. In its init method, first read the BOM file header information. If there are UTF8, UTF16BE and UTF16LE in the header information, the corresponding code will be used. If there is no UTF8 code, the default UTF8 code will be used.

  It should be noted that the problem of garbled code generally occurs in the local environment because the. properties configuration file is directly read locally. In dev, test, production and other environments, if the system parameter values are obtained from the configuration centers such as zookeeper, apollo and nacos, another logic is followed, and there will be no garbled code.

4. Default value

Sometimes, the default value is a big headache for us.

But why do you say that?

Because we often use the default value of java, it can not meet our daily work needs.

For example, there is a requirement: if the system attribute is configured, userName will use the configured attribute value. If not configured, userName uses the default value susan.

Some friends may think this can be done:

@Value(value = "${susan.test.userName}")
private String userName = "susan";

Give a default Value directly when defining parameters, but it won't work if you think about it carefully. Because the time to set the default Value of userName is earlier than the dependency injection attribute Value of @ Value annotation, that is to say, after userName initializes the default Value, it will be overwritten later.

So, how to set the default value?

Answer: use:.

For example:

@Value(value = "${susan.test.userName:susan}")
private String userName;

After the system attribute name for which the default value needs to be set, add the: symbol. Next, set the default value on the right.

It is recommended that you set a default Value when using @ Value. If you don't need a default Value, you'd rather set an empty Value. For example:

@Value(value = "${susan.test.userName:}")
private String userName;

Why do you say that?

If there is such a scenario: the UserService class is included in the business layer, which is referenced by both api services and job services. However, the userName of @ Value in UserService class is only useful in api service, and this attribute is not used in job service at all.

For the job service, if the system property with the same name is not configured in the. properties file, an error will be reported when the service is started.

I've stepped on this pit many times before. Therefore, it is recommended that you set a default Value for the parameter when using @ Value annotation to prevent similar problems.

5. static variable

We have seen how to use the @ Value annotation to inject system attribute values into class member variables.

So, the question is, can static variables be automatically injected into system attribute values?

Let's take a look. If the above userName is defined as static:

@Value("${susan.test.userName}")
private static String userName;

The program can start normally, but the value of userName obtained is null.

It can be seen that variables modified by static will fail to be injected through @ Value.

As a curious baby, you must want to ask: how can you inject system attribute values into static variables?

A: you need to use the following code:

@Service
public class UserService {

    private static String userName;

    @Value("${susan.test.userName}")
    public void setUserName(String userName) {
        UserService.userName = userName;
    }

    public String test() {
        return userName;
    }
}

Provide a setter method of static parameters, inject attribute values with @ Value on this method, and assign values to static variables in this method at the same time.

Some careful friends may find that the @ Value annotation is actually used on the setUserName method, that is, the corresponding setter method, rather than on variables.

Interesting, interesting, this usage is a little high-end.

However, generally, we will use lombok's @ Data, @ setter, @ getter and other annotations on pojo entity classes to dynamically add setter or getter methods during compilation, so @ Value is not used in many scenarios.

6. Variable type

The above contents are exemplified by variables of string type. In fact, the @ Value annotation also supports the injection of other types of system attribute values.

6.1 basic types

As we all know, there are 4 categories and 8 types of basic data types in Java. Let's review:

  • Integer: byte, short, int, long

  • Floating point type: float, double

  • boolean: boolean

  • Character type: char

Correspondingly, eight packaging categories are provided:

  • Integer: Byte, Short, integer, Long

  • Floating point type: Float, Double

  • Boolean: Boolean

  • Character type: character

@The Value annotation supports these 8 basic types and corresponding wrapper classes, for example:

@Value("${susan.test.a:1}")
private byte a;

@Value("${susan.test.b:100}")
private short b;

@Value("${susan.test.c:3000}")
private int c;

@Value("${susan.test.d:4000000}")
private long d;

@Value("${susan.test.e:5.2}")
private float e;

@Value("${susan.test.f:6.1}")
private double f;

@Value("${susan.test.g:false}")
private boolean g;

@Value("${susan.test.h:h}")
private char h;

@Value("${susan.test.a:1}")
private byte a1;

@Value("${susan.test.b:100}")
private Short b1;

@Value("${susan.test.c:3000}")
private Integer c1;

@Value("${susan.test.d:4000000}")
private Long d1;

@Value("${susan.test.e:5.2}")
private Float e1;

@Value("${susan.test.f:6.1}")
private Double f1;

@Value("${susan.test.g:false}")
private Boolean g1;

@Value("${susan.test.h:h}")
private Character h1;

With these common data types, we can play happily when defining variable types without additional conversion.

6.2 array

However, it is not enough to use only the above basic types, especially in many scenarios where batch data processing is required. At this time, you can use arrays, which are used frequently in daily development.

When defining an array, we can write as follows:

@Value("${susan.test.array:1,2,3,4,5}")
private int[] array;

spring uses comma separated parameter values by default.

If separated by spaces, for example:

@Value("${susan.test.array:1 2 3 4 5}")
private int[] array;

spring will automatically remove the blank space, resulting in only one value in the data: 12345. Be careful not to make a mistake.

By the way, there are many ways to define an array. For example, in the above list, my data are: 1,2,3,4,5.

If we define the array as short, int, long, char and string types, spring can inject attribute values normally.

However, if the array is defined as float and double types, an error will be reported directly when starting the project.

  Guys, did you lose your chin?

Normally, 1,2,3,4,5 can be expressed by float and double. Why do you report an error?

If the wrapper class of int is used, for example:

@Value("${susan.test.array:1,2,3,4,5}")
private Integer[] array;

The above exception will also be reported when starting the project.

In addition, when defining an array, you must pay attention to the types of attribute values, which must be completely consistent. If the following occurs:

@Value("${susan.test.array:1.0,abc,3,4,5}")
private int[] array;

The attribute value contains 1.0 and abc, which obviously cannot convert the string to int.

6.3 collection class

With basic types and arrays, it really makes us more convenient. However, for data processing, it is not enough to use only the data structure of array. Let's introduce other common data structures.

6.3.1,List

List is a variant of array, its length is variable, and the length of array is fixed.

Let's see how List injects attribute values:

@Value("${susan.test.list}")
private List<String> list;

The most important thing is to look at the configuration file:

susan.test.list[0]=10
susan.test.list[1]=11
susan.test.list[2]=12
susan.test.list[3]=13

When you start the project with hope and prepare to use this function, you find that you have reported an error.

what?

It seems that @ Value does not support this direct List injection.

So, how to solve this problem?

Some people say @ ConfigurationProperties.

You need to define a MyConfig class:

@Configuration
@ConfigurationProperties(prefix = "susan.test")
@Data
public class MyConfig {
    private List<String> list;
}

  Then write this at the call:

@Service
public class UserService {

    @Autowired
    private MyConfig myConfig;

    public String test() {
        System.out.println(myConfig.getList());
        return null;
    }
}

This method can indeed complete List injection. However, it can only show that the @ ConfigurationProperties annotation is powerful and has a half dime relationship with @ Value?

A: No.

So, here's the problem. How to implement this function with @ Value?

Answer: use spring's EL expression.

Change the definition of List to:

@Value("#{'${susan.test.list}'.split(',')}")
private List<String> list;

EL expression with # sign and braces.

Then change the configuration file to:

susan.test.list=10,11,12,13

It is the same as the configuration file when defining the array.

6.3.2,Set

Set is also a set for saving data. It is special. The data saved in it will not be repeated.

We can define Set as follows:

@Value("#{'${susan.test.set}'.split(',')}")
private Set<String> set;

The configuration file is as follows:

susan.test.set=10,11,12,13

The usage of Set is very similar to that of List.

But in order to prove the uniqueness of this section, I'm going to say something new.

How to Set the default value of List or Set to null?

Some friends may say: This is not easy. Just add a: sign after the $expression of @ Value.

The specific codes are as follows:

@Value("#{'${susan.test.set:}'.split(',')}")
private Set<String> set;

The result is not quite the same as expected:

  Why is the Set set not empty, but a Set containing an empty string?

Well, I'll add null after the: number. Is it always OK?

The Set collection is not empty, but a collection containing a "null" string.

Neither can this nor that. What should I do?

Answer: use the empty method of EL expression.

The specific codes are as follows:

@Value("#{'${susan.test.set:}'.empty ? null : '${susan.test.set:}'.split(',')}")
private Set<String> set;

After running, the result is correct:

  In fact, List has similar problems, and this method can also be used to solve problems.

Here's a warm reminder. The expression of the judgment is complex, and it's easy to make mistakes by handwriting. It's recommended to change it according to the actual needs after copying and pasting.

6.3.3,Map

Another common collection is map, which supports saving data in the form of key/value pairs, and data with the same key will not appear.

We can define a Map as follows:

@Value("#{${susan.test.map}}")
private Map<String, String> map;

The configuration file is as follows:

susan.test.map={"name":"Su San", "age":"18"}

This usage is slightly different from the above.

The code for setting the default value is as follows:

@Value("#{'${susan.test.map:}'.empty ? null : '${susan.test.map:}'}")
private Map<String, String> map;

7. EL high end play

We have seen the usage of spring EL expressions earlier, which is particularly useful when setting empty default values.

In fact, the empty method is just a very common use, and there are more high-end uses. If you don't believe it, let's have a look.

7.1. bean injection

In the past, we used to inject bean s with @ Autowired or @ Resource annotations. For example:

@Service
public class RoleService {
    public String getRoleName() {
        return "administrators";
    }
}

@Service
public class UserService {

    @Autowired
    private RoleService roleService;

    public String test() {
        System.out.println(roleService.getRoleName());
        return null;
    }
}

But what I want to tell you is that the @ Value annotation can also be injected into bean s. It does this:

@Value("#{roleService}")
private RoleService roleService;

In this way, a bean with id roleService can be injected.

7.2. bean variables and methods

Through EL expression, @ Value annotation can be injected into bean. Now that you can get the bean instance, you can go further.

The RoleService class defines: member variables, constants, methods, and static methods.

@Service
public class RoleService {
    public static final int DEFAULT_AGE = 18;
    public int id = 1000;

    public String getRoleName() {
        return "administrators";
    }

    public static int getParentId() {
        return 2000;
    }
}

Write this in the call:

@Service
public class UserService {

    @Value("#{roleService.DEFAULT_AGE}")
    private int myAge;

    @Value("#{roleService.id}")
    private int id;

    @Value("#{roleService.getRoleName()}")
    private String myRoleName;

    @Value("#{roleService.getParentId()}")
    private String myParentId;

    public String test() {
        System.out.println(myAge);
        System.out.println(id);
        System.out.println(myRoleName);
        System.out.println(myParentId);
        return null;
    }
}

In the UserService class, @ Value can be used to inject the values obtained from member variables, constants, methods and static methods into the corresponding member variables.

Do you feel suddenly enlightened? With these, we can realize more functions through @ Value annotation, not limited to injecting system attributes.

7.3 static class

The previous contents are based on bean s, but sometimes we need to call the methods of static classes, such as Math, xxutil and other static tool classes. What should we do?

A: use T with parentheses.

Example 1:

@Value("#{T(java.io.File).separator}")
private String path;

The path separator of the system can be injected into the path.

Example 2:

@Value("#{T(java.lang.Math).random()}")
private double randomValue;

You can inject a random number into randomValue.

7.4 logic operation

Through the above introduction, we can get the values of variables and methods of most classes. But with these values, it's not enough. Can we add some logic to the EL expression?

Splice string:

@Value("#{roleService.roleName + '' + roleService.DEFAULT_AGE}")
private String value;

Logical judgment:

@Value("#{roleservice. Default_age > 16 and roleservice. Rolename. Equals ('su San ')} ")
private String operation;

Ternary operation:

@Value("#{roleservice. Default_age > 16? (roleservice. Rolename: 'Su San'} ")
private String realRoleName;

There are many functions, I won't list them one by one.

The EL expression is too powerful. If you are interested in this aspect, you can talk to me privately.

8. The difference between ${} and #{}

Balabala mentioned so many awesome uses of @ Value above. In the final analysis, it is the use of ${} and #{}.

Let's focus on the difference between ${} and #{}, which may be a topic of concern to many small partners.

8.1,${}

It is mainly used to obtain the system attribute value in the configuration file.

For example:

@Value(value = "${susan.test.userName:susan}")
private String userName;

With: you can set default values. If the configuration of susan.test.userName cannot be found in the configuration file, the default value is used during injection.

If the configuration of susan.test.userName is not found in the configuration file and the default value is not set, an error will be reported when starting the project.

8.2,#{}

It is mainly used to obtain the properties of the bean or call a method of the bean through the EL expression of spring. There are also static constants and static methods that call classes.

@Value("#{roleService.DEFAULT_AGE}")
private int myAge;

@Value("#{roleService.id}")
private int id;

@Value("#{roleService.getRoleName()}")
private String myRoleName;

@Value("#{T(java.lang.Math).random()}")
private double randomValue;

If you are calling a static method of a class, you need to add t (package name + method name).

For example: T(java.lang.Math).

Well, that's all for today. I hope it will be helpful to you.

Keywords: Java Spring Back-end

Added by Scott_J on Tue, 09 Nov 2021 21:21:05 +0200