英文:
In spring, when the entity class is reflected into a bean, how to specify the calling order of the setter method?
问题
I have encountered a rather difficult problem, Suppose there is an entity class, the code is as follows:
class Human {
private Integer age; // age of the human
private String describe; // description of the human based on their age
/**
* Setter method for the age of the human
*
* @param age the age of the human
*/
public void setAge(Integer age) {
this.age = age;
}
/**
* Setter method for the description of the human
* The description is determined based on their age
*
* @param gender the gender of the human
*/
public void setDescribe(String gender) {
String describe = "";
if (this.age < 30) {
describe = "young " + gender;
} else if ( this.age <= 55 && this age >= 30) {
describe = "middle-aged " + gender;
} else {
describe = "old " + gender;
}
this.describe = describe;
}
}
如代码所示(仅为示例,属性或类名可以是任意的),如果我使用Spring并使用Spring生成bean,我必须确保首先调用setAge
方法。如何确保这一点?
如果数据库中有一个存储年龄和性别的表,当我使用jpa、mybatis或其他库来映射实体时,如何确保首先调用setAge
方法?
我尝试搜索,但没有找到答案。以下是我搜索的关键字和一些相关答案,但似乎没有解决我的疑惑:
Spring初始化bean,如何确保首先执行setter方法
Spring反射实体类,如何确保首先执行setter方法
spring在其他setter之后调用setter
当jpa反射实体类时setter方法调用顺序
对于tgdavies的评论,如果产品需求是扩展性别描述功能并添加年龄描述,类如下:
class Human {
private Integer age;
private String gender;
public void setAge(Integer age) {
this.age = age;
}
public void setGender(String gender) {
String describe = "";
if (this.age < 30) {
describe = "young " + gender;
} else if ( this.age <= 55 && this age >= 30) {
describe = "middle-aged" + gender;
} else {
describe = "old" + gender;
}
this.gender = describe;
}
}
英文:
I have encountered a rather difficult problem, Suppose there is an entity class, the code is as follows:
class Human {
private Integer age; // age of the human
private String describe; // description of the human based on their age
/**
* Setter method for the age of the human
*
* @param age the age of the human
*/
public void setAge(Integer age) {
this.age = age;
}
/**
* Setter method for the description of the human
* The description is determined based on their age
*
* @param gender the gender of the human
*/
public void setDescribe(String gender) {
String describe = "";
if (this.age < 30) {
describe = "young " + gender;
} else if ( this.age <= 55 && this.age >= 30) {
describe = "middle-aged " + gender;
} else {
describe = "old " + gender;
}
this.describe = describe;
}
}
As shown in the code (just an example, the attribute or class may be arbitrary), if I use spring and use spring to generate beans, I must ensure that method setAge
is called first. How can I ensure this?
If there is a table in the database that stores age and gender, how can I ensure that setAge is called first when I use jpa, mybatis or other libraries to reflect entities?
I tried to search, but I didn't find the answer. The following are the keywords I searched and some related answers, but it doesn't seem to solve my doubts:
Spring init bean, how to ensure that a setter method is executed first
Spring reflection entity class, how to ensure that a setter method is executed first
spring call setter after other setter
When jpa reflects entity classes setter methods call order
Spring - why initialization is called after setter method
For tgdavies comment,if the demand of the product is to expand the gender description function and add the age description,the class like this:
class Human {
private Integer age;
private String gender;
public void setAge(Integer age) {
this.age = age;
}
public void setGender(String gender) {
String describe = "";
if (this.age < 30) {
describe = "young " + gender;
} else if ( this.age <= 55 && this.age >= 30) {
describe = "middle-aged" + gender;
} else {
describe = "old" + gender;
}
this.gender = describe;
}
}
答案1
得分: 2
如果您的数据库中有包含年龄和性别的表,您可以像这样使用:
@Entity
public class Human {
@Id
private String id;
private Integer age;
private String gender;
@Transient
private String describe;
public void setId(String id){
this.id = id;
}
public void setAge(Integer age){
this.age = age;
}
public void setGender(String gender){
this gender = gender;
}
@PostLoad
public void initDescribe(){
if (this.age < 30) {
this.describe = "young " + this.gender;
} else if ( this.age <= 55 && this.age >= 30) {
this.describe = "middle-aged " + this.gender;
} else {
this.describe = "old " + this.gender;
}
}
}
由于describe
字段不在数据库表中,可以使用@Transient
注解进行标记,而@PostLoad
注解将确保initDescribe()
方法在age
和gender
字段设置之后被调用。
英文:
If you have a table in the database containing age and gender you could use something like this:
@Entity
public class Human {
@Id
private String id;
private Integer age;
private String gender;
@Transient
private String describe;
public void setId(String id){
this.id = id;
}
public void setAge(Integer age){
this.age = age;
}
public void setGender(String gender){
this.gender = gender;
}
@PostLoad
public void initDescribe(){
if (this.age < 30) {
this.describe = "young " + this.gender;
} else if ( this.age <= 55 && this.age >= 30) {
this.describe = "middle-aged " + this.gender;
} else {
this.describe = "old " + this.gender;
}
}
}
Since the describe field is not in the database table it can be annotated with @Transient and the @PostLoad annotation will ensure that the initDescribe() method is called after age and gender fields are set.
答案2
得分: 2
Test with:
- spring-core-5.2.12.RELEASE.jar
- spring-beans-5.2.12.RELEASE.jar
- java version "1.8.0_121"
Looking forward to a better solution.
Here is my code for testing and debugging:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
public class RepositoryBean {
public static void main(String[] args) {
ApplicationContext AcContext = new AnnotationConfigApplicationContext(User.class, Configs.class);
System.out.println(AcContext.getBean("user", User.class));
}
}
@Configuration
class Configs {
@Bean
public String name() { return "the name"; }
@Bean
public int age() { return 100; }
}
@Component
class User {
private int age;
private String name;
public User() { }
@Autowired
public void setName(String name) { this.name = name; }
public User(String name, age) {
this.name = name;
this.age = age;
}
public int getAge() { return age; }
public String getName() { return name; }
@Autowired
public void setAge(int age) {
this.age = age;
// text = Objects.requireNonNull(this.name) + age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
I am full of curiosity about this question. I have tried to find the answer from the source code these days (only for spring, other middleware, I have no energy to do it)
First of all, the conclusion is that when spring initializes beans, it cannot control the order in which the setter methods are called(According to the results of my investigation of the source code, there may be omissions. If there is an error in my conclusion, I hope everyone can help point out).
Now, please take a look at the documentation of Class#getDeclaredMethods:
The elements in the returned array are not sorted and are not in any particular order.
Through reflection, when using getDeclaredMethods to get the Method array, there is no "order". No "order" means that it is not sorted according to any rules, but the returned order seems to be regular every time it is called, I use the following code to test:
class User {
private int age;
private String name;
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getName() { return name; }
}
public static void main(String[] args) {
Class<User> c = User.class;
Method[] m = c.getDeclaredMethods();
Arrays.stream(m).forEach(System.out::println);
}
No matter how many times I run it (run about 20 times in a row, and run once in a while later), no matter what the setter method and attribute order in the User class are, the final output result, setAge is before setName:
public int com.anno.User.getAge()
public void com.anno.User.setAge(int)
public java.lang.String com.anno.User.getName()
public void com.anno.User.setName(java.lang.String)
or:
public void com.anno.User.setAge(int)
public int com.anno.User.getAge()
public java.lang.String com.anno.User.getName()
public void com.anno.User.setName(java.lang.String)
Therefore, in spring, when obtaining the method list through reflection, it is impossible to specify the order, and track the entire operation chain, and finally to the execution of the setter method, the method list still maintains the order in which it was obtained.
During the entire initialization process, the processing of sorting the data in the method data is not seen.
The method org.springframework.util.ReflectionUtils#getDeclaredMethods(Class<?>, boolean)
use Class#getDeclaredMethods
to get the array of all methods in User:
private static Method[] getDeclaredMethods(Class<?> clazz, boolean defensive) {
Assert.notNull(clazz, "Class must not be null");
Method[] result = declaredMethodsCache.get(clazz);
if (result == null) {
try {
Method[] declaredMethods = clazz.getDeclaredMethods();
List<Method> defaultMethods = findConcreteMethodsOnInterfaces(clazz);
if (defaultMethods != null) {
result = new Method[declaredMethods.length + defaultMethods.size()];
System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length);
int index = declaredMethods.length;
for (Method defaultMethod : defaultMethods) {
result[index] = defaultMethod;
index++;
}
}
else {
result = declaredMethods;
}
declaredMethodsCache.put(clazz, (result.length == 0 ? EMPTY_METHOD_ARRAY : result));
}
catch (Throwable ex) {
throw an IllegalStateException("Failed to introspect Class [" + clazz.getName() +
"] from ClassLoader [" + clazz.getClassLoader() + "]", ex);
}
}
return (result.length == 0 || !defensive) ? result : result.clone();
}
Other related methods are as follows, please check by yourself:
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata
org.springframework.util.ReflectionUtils#doWithLocalMethods
The final conclusion is that no matter how the setter methods and attributes are arranged in the User entity, setAge will be called first when the bean is initialized.
In other words, the final setter method will only be called in the order of the methods in the method array returned by Class#getDeclaredMethods()
I'm not sure if other libraries provide the ability to specify the order in which the setter methods are called during reflection, so I can only assume that the order in which the setter methods are called cannot be guaranteed.
Some advice:
I think I understand your question, you seem to want to encapsulate the changing part into the setter method, so that even in your application, there are many places referencing this entity (say there are 100 places referencing it), you can just pass Modify the setter method once to make the function effective for all referenced places.
I think there is really no way to control its calling order. Apart from spring, we can't figure out all the implementations of other libraries.
But we can do conversion during the development process, you can look at the concepts of PO (persistant object), BO (business object), DTO (data transfer object), VO (value object).
You can use PO, get the data from the xml, yml, configuration center or other data sources:
class HumanPO {
private Integer age;
private String gender;
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
<details>
<summary>英文:</summary>
Test with:
- spring-core-5.2.12.RELEASE.jar
- spring-beans-5.2.12.RELEASE.jar
- java version "1.8.0_121"
Looking forward to a better solution.
Here is my code for testing and debugging:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
public class RepositoryBean {
public static void main(String[] args) {
ApplicationContext AcContext = new AnnotationConfigApplicationContext(User.class, Configs.class);
System.out.println(AcContext.getBean("user", User.class));
}
}
@Configuration
class Configs {
@Bean
public String name() { return "the name"; }
@Bean
public int age() { return 100; }
}
@Component
class User {
private int age;
private String name;
public User() { }
@Autowired
public void setName(String name) { this.name = name; }
public User(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() { return age; }
public String getName() { return name; }
@Autowired
public void setAge(int age) {
this.age = age;
// text = Objects.requireNonNull(this.name) + age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
------
I am full of curiosity about this question. I have tried to find the answer from the source code these days (only for spring, other middleware, I have no energy to do it)
First of all, the conclusion is that when spring initializes beans, it cannot control the order in which the setter methods are called(According to the results of my investigation of the source code, there may be omissions. If there is an error in my conclusion, I hope everyone can help point out).
Now, please take a look at the documentation of [Class#getDeclaredMethods][1]:
> The elements in the returned array are not sorted and are not in any particular order.
Through reflection, when using getDeclaredMethods to get the Method array, there is no "order". No "order" means that it is not sorted according to any rules, but the returned order seems to be regular every time it is called, i use the following code to test:
class User {
private int age;
private String name;
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getName() { return name; }
}
public static void main(String[] args) {
Class<User> c = User.class;
Method[] m = c.getDeclaredMethods();
Arrays.stream(m).forEach(System.out::println);
}
No matter how many times I run it(run about 20 times in a row, and run once in a while later), no matter what the setter method and attribute order in the User class are, the final output result, setAge is before setName:
public int com.anno.User.getAge()
public void com.anno.User.setAge(int)
public java.lang.String com.anno.User.getName()
public void com.anno.User.setName(java.lang.String)
or:
public void com.anno.User.setAge(int)
public int com.anno.User.getAge()
public java.lang.String com.anno.User.getName()
public void com.anno.User.setName(java.lang.String)
Therefore, in spring, when obtaining the method list through reflection, it is impossible to specify the order, and track the entire operation chain, and finally to the execution of the setter method, the method list still maintains the order in which it was obtained.
During the entire initialization process, the processing of sorting the data in the method data is not seen.
The method `org.springframework.util.ReflectionUtils#getDeclaredMethods(Class<?>, boolean)` use `Class#getDeclaredMethods` to gets the array of all methods in User:
private static Method[] getDeclaredMethods(Class<?> clazz, boolean defensive) {
Assert.notNull(clazz, "Class must not be null");
Method[] result = declaredMethodsCache.get(clazz);
if (result == null) {
try {
Method[] declaredMethods = clazz.getDeclaredMethods();
List<Method> defaultMethods = findConcreteMethodsOnInterfaces(clazz);
if (defaultMethods != null) {
result = new Method[declaredMethods.length + defaultMethods.size()];
System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length);
int index = declaredMethods.length;
for (Method defaultMethod : defaultMethods) {
result[index] = defaultMethod;
index++;
}
}
else {
result = declaredMethods;
}
declaredMethodsCache.put(clazz, (result.length == 0 ? EMPTY_METHOD_ARRAY : result));
}
catch (Throwable ex) {
throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() +
"] from ClassLoader [" + clazz.getClassLoader() + "]", ex);
}
}
return (result.length == 0 || !defensive) ? result : result.clone();
}
Other related methods are as follows, please check by yourself:
- `org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties`
- `org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata`
- `org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata`
- `org.springframework.util.ReflectionUtils#doWithLocalMethods`
The final conclusion is that no matter how the setter methods and attributes are arranged in the User entity, setAge will be called first when the bean is initialized.
**In other words, the final setter method will only be called in the order of the methods in the method array returned by Class#getDeclaredMethods()**
I'm not sure if other libraries provide the ability to specify the order in which the setter methods are called during reflection, so I can only assume that the order in which the setter methods are called cannot be guaranteed.
some advices
------
I think I understand your question, you seem to want to encapsulate the changing part into the setter method, so that even in your application, there are many places referencing this entity (say there are 100 places referencing it), you can just pass
Modify the setter method once to make the function effective for all referenced places.
I think there is really no way to control its calling order. Apart from spring, we can't figure out all the implementations of other libraries.
But we can do conversion during the development process, you can look at the concepts of PO (persistant object)
, BO(business object), DTO(data transfer object), VO(value object).
You can use PO, get the data from the xml、yml、configuration center or other data sources:
class HumanPO {
private Integer age;
private String gender;
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
}
Then, convert it to a business object 'BO' where you can encapsulate logic:
class HumanBO {
private Integer age;
private String gender;
private String describe;
public static HumanBO PoConvertToBo(HumanPO humanPo) {
HumanBO rt = new HumanBO();
rt.setAge(humanPo.getAge());
rt.setGender(humanPo.getGender());
rt.setDescribe(humanPo.getGender());
return rt;
}
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
public String getDescribe() { return describe; }
public void setDescribe(String gender) {
String describe = "";
if (this.age < 30) {
describe = "young " + gender;
} else if ( this.age <= 55 && this.age >= 30) {
describe = "middle-aged " + gender;
} else {
describe = "old " + gender;
}
this.describe = describe;
}
}
Now, you can implement your changing needs by modifying HumanBO.
[1]: https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getDeclaredMethods--
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论