This article focuses on application-level implementation without delving into underlying principles. The project framework is already set up, using Spring Boot 1.5, MyBatis for database operations, Oracle as the database, and Vue with Node.js for the frontend. Due to specific requirements, communication between multiple projects is necessary. One approach involves calling a fixed project on a single server, while another involves calling a project deployed across multiple servers.
Method One
Calling one project from another uses the RestTemplate class provided by Spring. This approach was adopted because the current project has multiple data sources, and other databases have limited connection resources. To address this, the called service was made into a standalone service accessible by other systems. The implementation involves retrieving parameters through reflection, obtaining the corresponding service implementation class, and executing CRUD operations via Mapper.java. Parameters are passed using Map<String, String>, and the called service parses the data using a custom JSON utility class that handles single objects, lists, and paginated data. After thorough testing, this method successfully met the requirements and resolved existing issues.
The JSON parsing utility class:
@Slf4j
@Component
@Scope(value = "singleton")
public class DataParserUtil implements InitializingBean {
private DataParserUtil() {}
private static RestTemplate restTemplate = new RestTemplate();
private static String apiPrefix;
@Value("${api.prefix}")
private String apiConfig;
@Override
public void afterPropertiesSet() throws Exception {
DataParserUtil.apiPrefix = this.apiConfig;
}
private static <T> String validateParams(Map<String, Object> paramMap, T obj) {
if (Objects.equal(paramMap, null)) {
throw new CustomException("A000001", "Parameter map error");
}
if (Objects.equal(obj, null)) {
throw new CustomException("A000001", "Object type error");
}
String methodName = (String) paramMap.get("method");
String serviceId = (String) paramMap.get("service");
if (StringUtils.isBlank(methodName) || StringUtils.isBlank(serviceId)) {
throw new CustomException("A000001", "Missing required parameters");
}
return methodName;
}
private static <T> List<? extends Object> parseList(String methodName, String resultStr, T obj) {
return JsonUtil.parseArray(resultStr, "data", obj.getClass());
}
public static final <T> Page<T> getPageData(Map<String, Object> paramMap, final Class<T> clazz) {
T obj = getInstance(clazz);
String methodName = validateParams(paramMap, obj);
String resultStr = sendRequest(paramMap, methodName);
Page<T> pages = new Page<>();
String pageData = JsonUtil.getSubJson(resultStr, "data");
if (Objects.equal("[]", pageData)) {
pages.setPageNum(1);
pages.setPageSize(10);
pages.setTotal(0);
pages.setPages(0);
} else {
pages.setPageNum(JsonUtil.parse(pageData, "pageNum"));
pages.setPageSize(JsonUtil.parse(pageData, "pageSize"));
pages.setTotal(JsonUtil.parse(pageData, "total"));
pages.setPages(JsonUtil.parse(pageData, "pages"));
List<? extends Object> parsedList = parseList(methodName, pageData, obj);
if (!Objects.equal(parsedList, null)) {
for (Object item : parsedList) {
pages.add((T) item);
}
}
}
return pages;
}
public static final <T> List<T> getListData(Map<String, Object> paramMap, final Class<T> clazz) {
List<T> resultList = new ArrayList<>();
T obj = getInstance(clazz);
String methodName = validateParams(paramMap, obj);
String resultStr = sendRequest(paramMap, methodName);
List<? extends Object> parsedList = parseList(methodName, resultStr, obj);
if (!Objects.equal(parsedList, null)) {
for (Object item : parsedList) {
resultList.add((T) item);
}
}
return resultList;
}
public static final <T> T getSingleData(Map<String, Object> paramMap, final Class<T> clazz) {
T obj = getInstance(clazz);
String methodName = validateParams(paramMap, obj);
String resultStr = sendRequest(paramMap, methodName);
obj = (T) JsonUtil.parse(resultStr, "data", clazz);
return obj;
}
private static String sendRequest(Map<String, Object> paramMap, String methodName) {
String response = restTemplate.postForObject(apiPrefix, paramMap, String.class);
Integer code = JsonUtil.parse(response, "code");
if (!Objects.equal(code, 20000)) {
String message = JsonUtil.parse(response, "msg");
log.error(methodName + "-sendRequest-Error: {}", message);
throw new CustomException("A000001", methodName + "-sendRequest-Error: " + message);
}
return response;
}
private static final <T> T getInstance(final Class<T> clazz) {
T obj = null;
try {
obj = (T) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException error) {
log.error(clazz.toString() + "-getInstance-Error: " + error.getMessage(), error);
throw new CustomException("A000001", clazz.toString() + "-getInstance-Error");
}
return obj;
}
}
The JSON utility class:
public class JsonUtil {
public static final String toJson(final Object obj) {
return JSON.toJSONString(obj);
}
public static final String toJson(final Object obj, SerializerFeature... features) {
return JSON.toJSONString(obj, features);
}
public static final String toJson(final Object obj, final boolean format) {
return JSON.toJSONString(obj, format);
}
public static final String toJson(final Object obj, final String[] fields, final boolean ignore, SerializerFeature... features) {
if (fields == null || fields.length < 1) {
return toJson(obj);
}
if (features == null) {
features = new SerializerFeature[] { SerializerFeature.QuoteFieldNames };
}
return JSON.toJSONString(obj, new PropertyFilter() {
@Override
public boolean apply(Object object, String name, Object value) {
for (int i = 0; i < fields.length; i++) {
if (name.equals(fields[i])) {
return !ignore;
}
}
return ignore;
}
}, features);
}
@SuppressWarnings("unchecked")
public static final <T> T parse(final String json, final String path) {
String[] keys = path.split(",");
JSONObject obj = JSON.parseObject(json);
for (int i = 0; i < keys.length - 1; i++) {
obj = obj.getJSONObject(keys[i]);
}
return (T) obj.get(keys[keys.length - 1]);
}
public static final <T> T parse(final String json, final Class<T> clazz) {
return JSON.parseObject(json, clazz);
}
public static final <T> T parse(final String json, final String path, final Class<T> clazz) {
String[] keys = path.split(",");
JSONObject obj = JSON.parseObject(json);
for (int i = 0; i < keys.length - 1; i++) {
obj = obj.getJSONObject(keys[i]);
}
String inner = obj.getString(keys[keys.length - 1]);
return parse(inner, clazz);
}
public static final <T> List<T> parseArray(final Object obj, final String[] fields, boolean ignore, final Class<T> clazz, final SerializerFeature... features) {
String json = toJson(obj, fields, ignore, features);
return parseArray(json, clazz);
}
public static final <T> List<T> parseArray(final String json, final Class<T> clazz) {
return JSON.parseArray(json, clazz);
}
public static final <T> List<T> parseArray(final String json, final String path, final Class<T> clazz) {
String[] keys = path.split(",");
JSONObject obj = JSON.parseObject(json);
for (int i = 0; i < keys.length - 1; i++) {
obj = obj.getJSONObject(keys[i]);
}
String inner = obj.getString(keys[keys.length - 1]);
List<T> result = parseArray(inner, clazz);
return result;
}
public static final String correctJson(final String invalidJson) {
String content = invalidJson.replace("\\\"", "\"").replace("\"{", "{").replace("}\"", "}");
return content;
}
public static final String formatJson(String json) {
Map<?, ?> map = (Map<?, ?>) JSON.parse(json);
return JSON.toJSONString(map, true);
}
public static final String getSubJson(String json, String path) {
String[] keys = path.split(",");
JSONObject obj = JSON.parseObject(json);
for (int i = 0; i < keys.length - 1; i++) {
obj = obj.getJSONObject(keys[i]);
}
return obj != null ? obj.getString(keys[keys.length - 1]) : null;
}
}
This approach effectively solves the issue of inter-project communication and fits the current project's needs. A challenge was handling paginated data, which required an extra conversion step. This was easily managed.
Method Two
For projects deployed across multiple servers, the first method is insufficient. Instead, Feign is used for communication. This method allows for data synchronization between projects.
An example of the implementation includes defining a Feign interface, its fallback class, and a service class that utilizes the Feign interface. The actual call is made through the callFixDataService class. Configuration allows multiple addresses separated by commas. This method fully meets business requirements and enables calling a project deployed on multiple servers.