在春天,当实体类反射到一个Bean中时,如何指定setter方法的调用顺序?

huangapple go评论62阅读模式
英文:

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方法调用顺序

Spring setter方法顺序

Spring - 为什么初始化在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 setter method 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()方法在agegender字段设置之后被调用。

英文:

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 &lt; 30) {
            this.describe = &quot;young &quot; + this.gender;
        } else if ( this.age &lt;= 55 &amp;&amp; this.age &gt;= 30) {
            this.describe = &quot;middle-aged &quot; + this.gender;
        } else {
            this.describe = &quot;old &quot; + 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 &quot;1.8.0_121&quot;

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(&quot;user&quot;, User.class));
        }
    }
    
    @Configuration
    class Configs {
        @Bean
        public String name() { return &quot;the name&quot;; }
        @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 &quot;User{&quot; +
                    &quot;name=&#39;&quot; + name + &#39;\&#39;&#39; +
                    &quot;, age=&quot; + age +
                    &#39;}&#39;;
        }
    }

------
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]:
&gt; 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 &quot;order&quot;. No &quot;order&quot; 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&lt;User&gt; 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&lt;?&gt;, boolean)` use `Class#getDeclaredMethods` to gets the array of all methods in User:

	private static Method[] getDeclaredMethods(Class&lt;?&gt; clazz, boolean defensive) {
		Assert.notNull(clazz, &quot;Class must not be null&quot;);
		Method[] result = declaredMethodsCache.get(clazz);
		if (result == null) {
			try {
				Method[] declaredMethods = clazz.getDeclaredMethods();
				List&lt;Method&gt; 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(&quot;Failed to introspect Class [&quot; + clazz.getName() +
						&quot;] from ClassLoader [&quot; + clazz.getClassLoader() + &quot;]&quot;, 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&#39;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&#39;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 xmlymlconfiguration 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 &#39;BO&#39; 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 = &quot;&quot;;
            if (this.age &lt; 30) {
                describe = &quot;young &quot; + gender;
            } else if ( this.age &lt;= 55 &amp;&amp; this.age &gt;= 30) {
                describe = &quot;middle-aged &quot; + gender;
            } else {
                describe = &quot;old &quot; + 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>



huangapple
  • 本文由 发表于 2023年2月8日 10:54:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/75380973.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定