Spring - Detailed explanation of @Value function

foreword

For small partners engaged in java development, the spring framework must be 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, etc. annotations.

If you want to dependency inject an object, 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 are many more. . .

Today we will focus on @Valueannotations, because it is a very useful but extremely easily overlooked annotation. Most people may only use part of its functions, which is a very regrettable thing.

So today it is necessary to re-acquaint with you all @Value.

1. Start with an example

If in the UserService class, you need to inject system properties into the userName variable. Normally, we would 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 through @Valuethe annotation susan.test.userName, the name needs to be ${}wrapped.

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

However, the point of the above function is to applicationContext.propertiesconfigure a system property of the same name in a file (short: configuration file):

#张三
susan.test.userName=\u5f20\u4e09

So, do the names really have to be exactly the same?

2. About attribute names

At this time, some friends may say: In the @ConfigurationPropertiesconfiguration class, the defined parameter name 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 property name configured in the configuration file is:

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

The ones used in the class and the ones used in the userNameconfiguration file user-nameare different. But after testing, it was found that the function works properly.

The system property name in the configuration file is used  驼峰标识 or  小写字母加中划线的组合, spring can find the property name userName in the configuration class for assignment.

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

So, @Valuecan the system property names defined in the annotations be different?

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

picture

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 as above will be reported.

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

3, garbled problem

 I don't know if the attentive friends have found that the attribute value I configured: 张三, actually 转义passed.

susan.test.userName=\u5f20\u4e09

Why do this escaping?

If you configure Zhang San in Chinese in the configuration file:

susan.test.userName=张三

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

å¼ ä¸

what?

Why do garbled characters appear?

Answer: In the springboot CharacterReaderclass, the default encoding format is ISO-8859-1that this class is responsible for .propertiesreading system properties in the file. If the system property contains Chinese characters, garbled characters will appear.

picture

 

So, how to solve the garbled problem?

There are currently three main options:

  1. Manually convert attribute values ​​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.

Apparently @Value doesn't support encoding parameter, so option 2 doesn't work.

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 indeed solve the garbled problem.

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

I was disgusted by the inversion.

So, how to solve the code duplication problem?

A: Convert the Chinese content of the attribute value to unicode.

Something like this:

susan.test.userName=\u5f20\u4e09

This method can also solve the problem of garbled code, and there will be no disgusting repeated code. It requires a little extra conversion work, but this conversion is very easy because there are ready-made online conversion tools.

It is recommended to use this tool to convert: http://www.jsons.cn/unicode/

Let me tell you a little secret here by the way: if you use the configuration file in the format .ymlor format, there will be no problem of Chinese garbled characters..yaml

Why is this?

Because the configuration file in .yml or .yaml format will eventually be UnicodeReaderparsed using classes. In its initmethod, the header information of the BOM file is first read. If there are UTF8, UTF16BE, and UTF16LE in the header information, the corresponding encoding is used. , the default UTF8encoding is used.

picture

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

4. Default value

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

why would you said this?

Because the default values ​​of java are often used, they cannot meet our daily work needs.

For example, there is such a requirement: if a system property is configured, userName uses the configured property value. If not configured, userName defaults to susan.

Some friends may think it is possible to do this:

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

Give a default value directly when defining the parameter, but if you think about it carefully, this trick will not work. Because the time to set the default value of userName is earlier than the value of the dependency injection property of the @Value annotation, that is to say, the default value of userName is initialized, and it will be overwritten later.

So, how do you set the default value?

Answer: Use :.

E.g:

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

:Add a symbol after the name of the system property whose default value needs to be set . Next, :set the default value on the right.

It is recommended that when you use @Value, try to set a default value as much as possible. If you don't need a default value, it is better to set it to empty. for example:

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

Why do you say that?

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

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 starts.

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

5. Static variables

We have seen before, how to use the @Value annotation to 成员变量inject into the class 系统属性值.

So, the question is, 静态变量can you automatically inject system property values?

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

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

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

It can be seen that the staticmodified variable will fail to be injected through @Value.

As a curious baby, you must want to ask at this time: How can we inject system property values ​​into static variables?

Answer: This requires the use of the following Sao 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;
    }
}

A method that provides a static parameter setter, injects the property value using @Value on the method, and assigns a value to the static variable in the method at the same time.

Some careful friends may find that the @Value annotation is actually used on the setUserName method here, that is, the corresponding setter method, not on the variable.

Fun, fun, this usage is a bit high-end.

However, under normal circumstances, we generally use lombok's @Data, @Setter, @Getter and other annotations on the pojo entity class, and dynamically add setter or getter methods at compile time, so the scene where @Value is used in methods is actually not many.

6. Variable type

The above contents are all examples of variables of string type. In fact, the @Value annotation also supports the injection of other types of system property values.

6.1. Basic types

As we all know, there are 4 types of basic data types in Java, but let's review them together:

  • Integer: byte, short, int, long

  • Floating point: float, double

  • Boolean: boolean

  • Character type: char

Correspondingly, 8 wrapper classes are provided:

  • Integer: Byte, Short, Integer, Long

  • Float: Float, Double

  • Boolean: Boolean

  • Character type: Character

The @Value annotation has very good support for 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 commonly used data types, we can have a lot of fun when defining variable types without having to do additional conversions.

6.2. Array

But only using the above basic types is not enough, especially in many scenarios that require batch processing of data. It can be used at this time 数组, and it is frequently used in daily development.

We can write this when defining an array:

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

Spring uses commas to separate 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 spaces, resulting in only one value in the data: 12345. Be careful not to make a mistake.

By the way, when defining an array, there are still a lot of doorways in it. For example, in the above column, my data is: 1, 2, 3, 4, 5.

If we define the array as: short, int, long, char, string type, spring can inject property values ​​normally.

But if the array is defined as: float, double type, an error will be reported directly when starting the project.

picture

 Guys, did your jaw drop?

It stands to reason that 1, 2, 3, 4, and 5 can be represented by float and double. Why do I get an error?

If you use a wrapper class for int, such as:

@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 type of attribute values, which must be exactly the same. 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, obviously neither can convert the string to int.

6.3. Collection class

Having basic types and arrays really makes things easier for us. However, for data processing, it is far from enough to use only the array data structure. Let me introduce other commonly used data structures.

6.3.1、List

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

Let's see how List injects property 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 a project with high hopes and are about to use this feature, you find that an error is reported.

picture

what?

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

So, how to solve this problem?

Some say use @ConfigurationProperties.

A MyConfig class needs to be defined:

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

 Then write this in the place of the call:

@Service
public class UserService {

    @Autowired
    private MyConfig myConfig;

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

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

Answer: No.

So, the question is, how to use @Value to achieve this function?

A: Use spring's EL expressions.

The definition of List is changed to:

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

EL expressions using #curly braces.

Then change the configuration file to:

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

The same as the configuration file when defining the array.

6.3.2、Set

Set is also a collection of saved data. It is special, and the data saved in it will not be repeated.

We can define Set like this:

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

The configuration file is like this:

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

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

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

How to set the default value empty for List or Set?

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

The specific code is as follows:

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

The result is not as expected:

picture

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

Well, then I add null after the : sign, it's alright, right?

The Set collection is also not empty, but contains a collection of "null" strings.

This doesn't work, that doesn't work, what should I do?

Answer: The emptymethod using EL expressions.

The specific code is as follows:

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

After running, the result is correct:

picture

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

Here, I would like to remind you that the expression of this judgment is relatively complex, and it is very easy to make mistakes by handwriting it by yourself. It is recommended to copy and paste it and modify it according to actual needs.

6.3.3、Map

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

We can define Map like this:

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

The configuration file is like this:

susan.test.map={"name":"苏三", "age":"18"}

This usage is slightly different from the above.

The code to set the default value is as follows:

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

7. EL high-end gameplay

We have seen the use of spring EL expressions earlier, which are especially useful when setting empty default values.

In fact, the emptymethod is just its very common usage, and there are more high-end usages. If you don’t believe me, let’s take a look.

7.1. Injecting beans

In the past, when we injected beans, we generally used @Autowired or @Resource annotations. E.g:

@Service
public class RoleService {
    public String getRoleName() {
        return "管理员";
    }
}

@Service
public class UserService {

    @Autowired
    private RoleService roleService;

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

But what I'm telling you is that the @Value annotation can also inject beans, it does this:

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

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

7.2, bean variables and methods

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

Defined in the RoleService class: member variables, constants, methods, static methods.

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

    public String getRoleName() {
        return "管理员";
    }

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

Write this in the place of 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, you can inject: member variables, constants, methods, and values ​​obtained by static methods into the corresponding member variables.

Do you feel suddenly enlightened? With these, we can achieve more functions through the @Value annotation, not only limited to injecting system properties.

7.3, static class

The previous content is based on beans, but sometimes we need to call static classes, such as: Math, xxxUtil and other static tool class methods, what should we do?

Answer: Use T in parentheses.

Example 1:

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

System path separators can be injected into path.

Example 2:

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

A random number can be injected into randomValue.

7.4, logical operation

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

Concatenating strings:

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

Logical judgment:

@Value("#{roleService.DEFAULT_AGE > 16 and roleService.roleName.equals('苏三')}")
private String operation;

Trinary operation:

@Value("#{roleService.DEFAULT_AGE > 16 ? roleService.roleName: '苏三' }")
private String realRoleName;

There are many, many more features, so I won't list them all.

EL expressions are too powerful, if you are interested in this aspect, you can chat with me privately.

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

Balabala said so many awesome usages of @Value above, and in the final analysis, it is the usage of ${}sum #{}.

The following focuses on the difference between ${} and #{}, which may be a topic of concern to many small partners.

8.1、${}

Mainly used to get the system property value in the configuration file.

E.g:

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

:The default value can be set through . If the configuration of susan.test.userName is not 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 the project is started.

8.2、#{}

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

@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 it is to call a static method of a class, you need to add T (package name + method name).

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

Well, today's content is introduced here, I hope it will be helpful to you.

Guess you like

Origin blog.csdn.net/qq_34272760/article/details/121237460