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;
}
}
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:
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.
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.
I was practicing setter & getter in Java. I thought what if my code will through an error if user have set Employee name incorrect i.e. "123" or "#qre23" which can't be someone's name in real .Here is my code, suggest me what to upgrade ?
class MyEmployee {
private int id;
private String name;
public void setName(String name){
this.name = name;
}
public void getName(){
System.out.println("\n Your Employee Name is : " + this.name);
}
}
public class Main {
public static void main(String[] args) {
MyEmployee myEmployee = new MyEmployee();
myEmployee.setName("209");
myEmployee.getName();
}
}
Match the string with the Regex using matches()
public void setName(String name){
if(name.matches("^[a-zA-Z ]*$"))
this.name = name;
else
//throw error
}
You can do it in many different ways depending on what regex you want but this is one way to do it. Also make sure your getter returns a name. Right now, your getter doesn't return the name.
class MyEmployee {
private int id;
private String name;
public void setName(String name){
String regx = "^[\\p{L} .'-]+$";
boolean match = Pattern.matches(regx, name);
if(match){
this.name = name;
}
}
public String getName(){
System.out.println("\n Your Employee Name is : " + this.name);
return this.name;
}
}
I've came upon a problem and I am struggling for 5 hours.
I've created a Java API (right now just for testing) in Eclipse using Jersey and this is my first time creating an API.
I am using Postman to test it.
When calling the GET method to just return a string "Hello" it's working great.
The problem is when I try the POST method that accepts an object of a class Person as an input parameter and also just returns string "Hello" I get Internal Server Error. I know I am not using the Person object right now, but I am just testing the input parameter from Postman and it's not working.
I tried to change the function in the API to be without an input parameter and just to be POST and it works like that, it prints the "Hello", but I need that input parameter for later...
The problem is somewhere around the creation of the object in the xml code in Postman maybe, I don't know.
Any suggestion is welcomed.
Here is the code for the API with the methods get and post
#Path("/employee")
public class API {
#POST
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.APPLICATION_XML)
#Path("/examplepost")
public String examplePost(Person p) {
return "Hello";
}
#GET
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.APPLICATION_XML)
#Path("/exampleget")
public String exampleGet() {
return "Hello";
}
}
This is the Person class:
#XmlRootElement(name = "person")
public class Person {
private String name;
private int age;
private int id;
public Person(String name, int age, int id) {
super();
this.name = name;
this.age = age;
this.id = id;
}
public Person() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Override
public String toString(){
return id+"::"+name+"::"+age;
}
}
And these are the results from Postman
GET Method
POST Method
The problem is that the argument Person p does not have the right class type. the function is consuming an XML not an instance of Person. So you have to use JAXBElement that represents information about an Xml Element
public String examplePost(JAXBElement<Person> p) {
Person person = p.getValue();
return "Hello";
}
[OPTIONAL] Also in PostMan: you have to specify what data you are sending by adding the xml version at the beginning
<?xml version="1.0"?>
<Person>
<!-- Person's attributes -->
</Person>
I am using Jersy for producing Json in an application.
The code snippet which produces Json is as follows
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("sample")
public List<test> displaySampleMessage(#PathParam("id") int id)
{
System.out.println(id);
List<test> sample1 = new ArrayList<>();
test temp1 = new test();
temp1.setName("abc");
sample1.add(temp1);
return sample1;
}
Test is simple java class with the following code
package webServiceTester;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class test
{
private String name;
private int age;
public test()
{
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public test(String name, int age) {
super();
this.name = name;
this.age = age;
}
}
Then when I run this web service I get the following output
I do not t want to get age = 0 here because I have not set the age property of my object.
What is its solution. I want age to be appeared if I set the value otherwise it should not appear..
Use Jakson, here you can find a
JSON example with Jersey + Jackson.
So you can use #JsonSerialize annotation to exclude null or empty fields.
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
or
#JsonSerialize(include=JsonSerialize.Inclusion.NON_EMPTY)
However it's not a good practice to exclude null or empty fields in restful applications because it may lead errors in client side.
I am having a question with Jackson that I think should be simple to solve, but it is killing me.
Let's say I have a java POJO class that looks like this (assume Getters and Setters for me):
class User {
private String name;
private Integer age;
}
And I want to deserialize JSON that looks like this into a User object:
{
"user":
{
"name":"Sam Smith",
"age":1
}
}
Jackson is giving me issues because the User is not the first-level object in the JSON. I could obviously make a UserWrapper class that has a single User object and then deserialize using that but I know there must be a more elegant solution.
How should I do this?
edit: this solution only works for jackson < 2.0
For your case there is a simple solution:
You need to annotate your model class with #JsonRootName(value = "user");
You need to configure your mapper with om.configure(Feature.UNWRAP_ROOT_VALUE, true); (as for 1.9) and om.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); (for version 2).
That's it!
#JsonRootName(value = "user")
public static class User {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(final Integer age) {
this.age = age;
}
#Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
ObjectMapper om = new ObjectMapper();
om.configure(Feature.UNWRAP_ROOT_VALUE, true);
System.out.println(om.readValue("{ \"user\": { \"name\":\"Sam Smith\", \"age\":1 }}", User.class));
this will print:
User [name=Sam Smith, age=1]