Skip to content

Commit ff2707d

Browse files
committed
add update fields by reflections
1 parent 03f631d commit ff2707d

File tree

5 files changed

+498
-0
lines changed

5 files changed

+498
-0
lines changed

rest-jpa-update/readme.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,53 @@ Pros:
9595
Cons:
9696
* manual deserialization
9797
* cannot auto add Bean Validation from the box
98+
99+
## Reflection
100+
101+
### Reflection update fields written by ourselves
102+
103+
See test: `/src/test/java/hello/controller/reflection/self/EmployeeReflectionSelfControllerTest.java`
104+
105+
In controller:
106+
```
107+
@RestController
108+
@RequestMapping(EmployeeReflectionSelfController.PATH)
109+
public class EmployeeReflectionSelfController {
110+
111+
public final static String PATH = "/reflection-self-employees";
112+
113+
@Autowired
114+
private EmployeeReflectionSelfService service;
115+
116+
@PatchMapping("/{id}")
117+
public Employee update(@RequestBody Map<String, Object> requestMap, @PathVariable Long id) {
118+
return service.update(id, requestMap);
119+
}
120+
}
121+
```
122+
123+
In service:
124+
```
125+
@Service
126+
public class EmployeeReflectionSelfService {
127+
128+
@Autowired
129+
private EmployeeRepository employeeRepository;
130+
131+
public Employee update(Long id, Map<String, Object> requestMap) {
132+
Optional<Employee> optionalEmployee = employeeRepository.findById(id);
133+
return optionalEmployee
134+
.map(e -> getUpdatedFromMap(e, requestMap))
135+
.orElseThrow(() -> new ResourceNotFoundException("Not exist by id", OBJECT_NOT_FOUND));
136+
}
137+
138+
private Employee getUpdatedFromMap(Employee employeeFromDb, Map<String, Object> requestMap) {
139+
Long id = employeeFromDb.getId();
140+
141+
requestMap.forEach((key, value) -> ReflectionUtils.setFieldContent(employeeFromDb, key, value));
142+
143+
employeeFromDb.setId(id);
144+
return employeeRepository.save(employeeFromDb);
145+
}
146+
}
147+
```
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package hello.controller.reflection.self;
2+
3+
import hello.entity.Employee;
4+
import hello.service.reflection.self.EmployeeReflectionSelfService;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.web.bind.annotation.*;
7+
8+
import java.util.Map;
9+
10+
@RestController
11+
@RequestMapping(EmployeeReflectionSelfController.PATH)
12+
public class EmployeeReflectionSelfController {
13+
14+
public final static String PATH = "/reflection-self-employees";
15+
16+
@Autowired
17+
private EmployeeReflectionSelfService service;
18+
19+
@PatchMapping("/{id}")
20+
public Employee update(@RequestBody Map<String, Object> requestMap, @PathVariable Long id) {
21+
return service.update(id, requestMap);
22+
}
23+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package hello.service.reflection.self;
2+
3+
import hello.entity.Employee;
4+
import hello.exception.ResourceNotFoundException;
5+
import hello.repository.EmployeeRepository;
6+
import hello.util.ReflectionUtils;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.stereotype.Service;
9+
10+
import java.util.Map;
11+
import java.util.Optional;
12+
13+
import static hello.exception.ErrorCode.OBJECT_NOT_FOUND;
14+
15+
@Service
16+
public class EmployeeReflectionSelfService {
17+
18+
@Autowired
19+
private EmployeeRepository employeeRepository;
20+
21+
public Employee update(Long id, Map<String, Object> requestMap) {
22+
Optional<Employee> optionalEmployee = employeeRepository.findById(id);
23+
return optionalEmployee
24+
.map(e -> getUpdatedFromMap(e, requestMap))
25+
.orElseThrow(() -> new ResourceNotFoundException("Not exist by id", OBJECT_NOT_FOUND));
26+
}
27+
28+
private Employee getUpdatedFromMap(Employee employeeFromDb, Map<String, Object> requestMap) {
29+
Long id = employeeFromDb.getId();
30+
31+
requestMap.forEach((key, value) -> ReflectionUtils.setFieldContent(employeeFromDb, key, value));
32+
33+
employeeFromDb.setId(id);
34+
return employeeRepository.save(employeeFromDb);
35+
}
36+
}
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
package hello.util;
2+
3+
import java.lang.reflect.Field;
4+
import java.lang.reflect.InvocationTargetException;
5+
import java.lang.reflect.Method;
6+
import java.util.ArrayList;
7+
import java.util.Arrays;
8+
import java.util.List;
9+
import java.util.Optional;
10+
import java.util.stream.Stream;
11+
12+
public final class ReflectionUtils {
13+
14+
private ReflectionUtils() { }
15+
16+
private static final String GETTER_PREFIX = "get";
17+
private static final String SETTER_PREFIX = "set";
18+
19+
/**
20+
* Get name of getter
21+
*
22+
* @param fieldName fieldName
23+
* @return getter name
24+
*/
25+
public static String getterByFieldName(String fieldName) {
26+
if (isStringNullOrEmpty(fieldName))
27+
return null;
28+
29+
return convertFieldByAddingPrefix(fieldName, GETTER_PREFIX);
30+
}
31+
32+
/**
33+
* Get name of setter
34+
*
35+
* @param fieldName fieldName
36+
* @return setter name
37+
*/
38+
public static String setterByFieldName(String fieldName) {
39+
if (isStringNullOrEmpty(fieldName))
40+
return null;
41+
42+
return convertFieldByAddingPrefix(fieldName, SETTER_PREFIX);
43+
}
44+
45+
/**
46+
* Get the contents of the field with any access modifier
47+
*
48+
* @param obj obj
49+
* @param fieldName fieldName
50+
* @return content of field
51+
*/
52+
public static Object getFieldContent(Object obj, String fieldName) {
53+
if (!isValidParams(obj, fieldName))
54+
return null;
55+
56+
try {
57+
Field declaredField = getFieldAccessible(obj, fieldName);
58+
return declaredField.get(obj);
59+
} catch (IllegalAccessException e) {
60+
throw new IllegalArgumentException("Cannot get field content for field name: " + fieldName, e);
61+
}
62+
}
63+
64+
/**
65+
* Set the contents to the field with any access modifier
66+
*
67+
* @param obj obj
68+
* @param fieldName fieldName
69+
* @param value value
70+
*/
71+
public static void setFieldContent(Object obj, String fieldName, Object value) {
72+
if (!isValidParams(obj, fieldName))
73+
return;
74+
75+
try {
76+
Field declaredField = getFieldAccessible(obj, fieldName);
77+
declaredField.set(obj, value);
78+
} catch (IllegalAccessException e) {
79+
throw new IllegalArgumentException("Cannot set field content for field name: " + fieldName, e);
80+
}
81+
}
82+
83+
/**
84+
* Call a method with any access modifier
85+
*
86+
* @param obj obj
87+
* @param methodName methodName
88+
* @return result of method
89+
*/
90+
public static Object callMethod(Object obj, String methodName) {
91+
if (!isValidParams(obj, methodName))
92+
return null;
93+
94+
try {
95+
Method method = obj.getClass().getMethod(methodName);
96+
method.setAccessible(true);
97+
return method.invoke(obj);
98+
} catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
99+
throw new IllegalArgumentException("Cannot invoke method name: " + methodName, e);
100+
}
101+
}
102+
103+
/**
104+
* Get all fields even from parent
105+
*
106+
* @param clazz clazz
107+
* @return array of fields
108+
*/
109+
public static Field[] getAllFields(Class<?> clazz) {
110+
if (clazz == null) return null;
111+
112+
List<Field> fields = new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()));
113+
if (clazz.getSuperclass() != null) {
114+
// danger! Recursion
115+
fields.addAll(Arrays.asList(getAllFields(clazz.getSuperclass())));
116+
}
117+
return fields.toArray(new Field[] {});
118+
}
119+
120+
/**
121+
* Get the Field from Object even from parent
122+
*
123+
* @param obj obj
124+
* @param fieldName fieldName
125+
* @return {@code Optional}
126+
*/
127+
public static Optional<Field> getField(Object obj, String fieldName) {
128+
if (!isValidParams(obj, fieldName))
129+
return Optional.empty();
130+
131+
Class<?> clazz = obj.getClass();
132+
return getField(clazz, fieldName);
133+
}
134+
135+
/**
136+
* Get the Field from Class even from parent
137+
*
138+
* @param clazz clazz
139+
* @param fieldName fieldName
140+
* @return {@code Optional}
141+
*/
142+
public static Optional<Field> getField(Class<?> clazz, String fieldName) {
143+
if (!isValidParams(clazz, fieldName))
144+
return Optional.empty();
145+
146+
Field[] fields = getAllFields(clazz);
147+
return Stream.of(fields)
148+
.filter(x -> x.getName().equals(fieldName))
149+
.findFirst();
150+
}
151+
152+
/**
153+
* @param clazz clazz
154+
* @param fieldName fieldName
155+
* @return Class
156+
*/
157+
public static Class<?> getFieldType(Class<?> clazz, String fieldName) {
158+
return getFieldWithCheck(clazz, fieldName).getType();
159+
}
160+
161+
/**
162+
* @param clazz clazz
163+
* @param fieldName fieldName
164+
* @return Field
165+
*/
166+
public static Field getFieldWithCheck(Class<?> clazz, String fieldName) {
167+
return ReflectionUtils.getField(clazz, fieldName)
168+
.orElseThrow(() -> {
169+
String msg = String.format("Cannot find field name: '%s' from class: '%s'", fieldName, clazz);
170+
return new IllegalArgumentException(msg);
171+
});
172+
}
173+
174+
/**
175+
* Get the field values with the types already listed according to the field type
176+
*
177+
* @param clazz clazz
178+
* @param fieldName fieldName
179+
* @param fieldValue fieldValue
180+
* @return value cast to specific field type
181+
*/
182+
public static Object castFieldValueByClass(Class<?> clazz, String fieldName, Object fieldValue) {
183+
Field field = getField(clazz, fieldName)
184+
.orElseThrow(() -> new IllegalArgumentException(String.format("Cannot find field by name: '%s'", fieldName)));
185+
186+
Class<?> fieldType = field.getType();
187+
188+
return castFieldValueByType(fieldType, fieldValue);
189+
}
190+
191+
/**
192+
* @param fieldType fieldType
193+
* @param fieldValue fieldValue
194+
* @return casted value
195+
*/
196+
public static Object castFieldValueByType(Class<?> fieldType, Object fieldValue) {
197+
if (fieldType.isAssignableFrom(Boolean.class)) {
198+
if (fieldValue instanceof String) {
199+
return convertStringToBoolean((String) fieldValue);
200+
}
201+
if (fieldValue instanceof Number) {
202+
return !(fieldValue).equals(0);
203+
}
204+
return fieldValue;
205+
}
206+
207+
else if (fieldType.isAssignableFrom(Double.class)) {
208+
if (fieldValue instanceof String) {
209+
return Double.valueOf((String)fieldValue);
210+
}
211+
return ((Number) fieldValue).doubleValue();
212+
}
213+
214+
else if (fieldType.isAssignableFrom(Long.class)) {
215+
if (fieldValue instanceof String) {
216+
return Long.valueOf((String)fieldValue);
217+
}
218+
return ((Number) fieldValue).longValue();
219+
}
220+
221+
else if (fieldType.isAssignableFrom(Float.class)) {
222+
if (fieldValue instanceof String) {
223+
return Float.valueOf((String)fieldValue);
224+
}
225+
return ((Number) fieldValue).floatValue();
226+
}
227+
228+
else if (fieldType.isAssignableFrom(Integer.class)) {
229+
if (fieldValue instanceof String) {
230+
return Integer.valueOf((String)fieldValue);
231+
}
232+
return ((Number) fieldValue).intValue();
233+
}
234+
235+
else if (fieldType.isAssignableFrom(Short.class)) {
236+
if (fieldValue instanceof String) {
237+
return Short.valueOf((String)fieldValue);
238+
}
239+
return ((Number) fieldValue).shortValue();
240+
}
241+
242+
return fieldValue;
243+
}
244+
245+
private static boolean convertStringToBoolean(String s) {
246+
String trim = s.trim();
247+
return !trim.equals("") && !trim.equals("0") && !trim.toLowerCase().equals("false");
248+
}
249+
250+
private static boolean isValidParams(Object obj, String param) {
251+
return (obj != null && !isStringNullOrEmpty(param));
252+
}
253+
254+
private static boolean isStringNullOrEmpty(String fieldName) {
255+
return fieldName == null || fieldName.trim().length() == 0;
256+
}
257+
258+
private static Field getFieldAccessible(Object obj, String fieldName) {
259+
Optional<Field> optionalField = getField(obj, fieldName);
260+
return optionalField
261+
.map(el -> {
262+
el.setAccessible(true);
263+
return el;
264+
})
265+
.orElseThrow(() -> new IllegalArgumentException("Cannot find field name: " + fieldName));
266+
}
267+
268+
private static String convertFieldByAddingPrefix(String fieldName, String prefix) {
269+
return prefix + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
270+
}
271+
}

0 commit comments

Comments
 (0)