001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.jboot.web.json; 017 018import com.alibaba.fastjson.JSON; 019import com.alibaba.fastjson.annotation.JSONField; 020import com.jfinal.json.JFinalJson; 021import com.jfinal.json.JFinalJsonKit; 022import com.jfinal.kit.LogKit; 023import com.jfinal.kit.StrKit; 024import com.jfinal.plugin.activerecord.CPI; 025import com.jfinal.plugin.activerecord.Model; 026import io.jboot.Jboot; 027import io.jboot.db.model.JbootModel; 028import io.jboot.utils.ClassUtil; 029import io.jboot.utils.StrUtil; 030 031import java.lang.reflect.Method; 032import java.lang.reflect.Modifier; 033import java.util.*; 034 035 036public class JbootJson extends JFinalJson { 037 038 private JbootJsonConfig config = Jboot.config(JbootJsonConfig.class); 039 protected static Map<Class<?>, MethodsAndFieldsWrapper> methodAndFieldsCache = new HashMap<>(); 040 041 public JbootJson() { 042 043 //跳过 null 值输出到浏览器,提高传输性能 044 setSkipNullValueField(config.isSkipNullValueField()); 045 046 //设置转换层级 047 setConvertDepth(config.getDepth()); 048 049 //默认设置为 CamelCase 的属性模式 050 if (config.isCamelCaseJsonStyleEnable()) { 051 setModelAndRecordFieldNameConverter((fieldName) -> StrKit.toCamelCase(fieldName, config.isCamelCaseToLowerCaseAnyway())); 052 } 053 054 setToJsonFactory(o -> o instanceof Model ? jbootModelToJson : null); 055 056 if (StrUtil.isNotBlank(config.getTimestampPattern())) { 057 setTimestampPattern(config.getTimestampPattern()); 058 } 059 } 060 061 062 protected JFinalJsonKit.ToJson<Model<?>> jbootModelToJson = (model, depth, ret) -> { 063 if (JFinalJsonKit.checkDepth(depth--, ret)) { 064 return; 065 } 066 067 Map<String, Object> map = new HashMap<>(); 068 069 if (!config.isSkipModelAttrs()) { 070 fillModelAttrsToMap(CPI.getAttrs(model), map); 071 } 072 073 if (!config.isSkipBeanGetters()) { 074 fillBeanToMap(model, map); 075 } 076 077 optimizeMapAttrs(map); 078 079 JFinalJsonKit.mapToJson(map, depth, ret); 080 }; 081 082 083 protected void fillModelAttrsToMap(Map<String, Object> attrs, Map<String, Object> toMap) { 084 if (attrs != null && !attrs.isEmpty()) { 085 for (Map.Entry<String, Object> entry : attrs.entrySet()) { 086 String fieldName = entry.getKey(); 087 if (config.isCamelCaseJsonStyleEnable()) { 088 fieldName = StrKit.toCamelCase(fieldName, config.isCamelCaseToLowerCaseAnyway()); 089 } 090 toMap.put(fieldName, entry.getValue()); 091 } 092 } 093 } 094 095 096 protected void fillBeanToMap(Object bean, Map<String, Object> toMap) { 097 098 MethodsAndFieldsWrapper wrapper = methodAndFieldsCache.get(bean.getClass()); 099 if (wrapper == null) { 100 synchronized (this) { 101 if (wrapper == null) { 102 wrapper = new MethodsAndFieldsWrapper(bean.getClass()); 103 } else { 104 wrapper = methodAndFieldsCache.get(bean.getClass()); 105 } 106 } 107 } 108 109 for (String ignoreField : wrapper.ignoreFields) { 110 toMap.remove(ignoreField); 111 } 112 113 114 for (int i = 0; i < wrapper.fields.size(); i++) { 115 String originalField = wrapper.originalFields.get(i); 116 toMap.remove(originalField); 117 118 Object value = invokeGetterMethod(wrapper.getterMethods.get(i), bean); 119 String field = wrapper.fields.get(i); 120 toMap.put(field, value); 121 } 122 123 } 124 125 126 protected void optimizeMapAttrs(Map<String, Object> map) { 127 } 128 129 protected Object invokeGetterMethod(Method method, Object bean) { 130 try { 131 return method.invoke(bean); 132 } catch (Exception ex) { 133 LogKit.error("can not invoke method: " + ClassUtil.buildMethodString(method), ex); 134 return null; 135 } 136 } 137 138 139 @Override 140 public <T> T parse(String jsonString, Class<T> type) { 141 return JSON.parseObject(jsonString, type); 142 } 143 144 145 public static class MethodsAndFieldsWrapper { 146 147 148 private static boolean hasFastJson = ClassUtil.hasClass("com.alibaba.fastjson.JSON"); 149 150 private List<String> fields = new LinkedList<>(); 151 private List<Method> getterMethods = new LinkedList<>(); 152 private List<String> originalFields = new LinkedList<>(); 153 154 //需要忽略的字段 155 private List<String> ignoreFields = new ArrayList<>(); 156 157 public MethodsAndFieldsWrapper(Class reflectiveClass) { 158 159 Method[] methodArray = reflectiveClass.getMethods(); 160 for (Method method : methodArray) { 161 if (method.getParameterCount() != 0 162 || method.getReturnType() == void.class 163 || !Modifier.isPublic(method.getModifiers()) 164 || "getClass".equals(method.getName()) 165 || method.getDeclaringClass() == JbootModel.class 166 || method.getDeclaringClass() == Model.class 167 || method.getDeclaringClass() == Object.class 168 ) { 169 continue; 170 } 171 172 173 String fieldName = getGetterMethodField(method.getName()); 174 if (fieldName != null) { 175 String attrName = StrKit.firstCharToLowerCase(fieldName); 176 if (isIgnoreFiled(method)) { 177 ignoreFields.add(attrName); 178 } else { 179 originalFields.add(attrName); 180 fields.add(getDefineName(method, attrName)); 181 getterMethods.add(method); 182 } 183 } 184 185 } 186 } 187 188 private String getGetterMethodField(String methodName) { 189 if (methodName.startsWith("get") && methodName.length() > 3) { 190 return methodName.substring(3); 191 } else if (methodName.startsWith("is") && methodName.length() > 2) { 192 return methodName.substring(2); 193 } 194 return null; 195 } 196 197 198 private String getDefineName(Method method, String orginalName) { 199 if (hasFastJson) { 200 JSONField jsonField = method.getAnnotation(JSONField.class); 201 if (jsonField != null && StrUtil.isNotBlank(jsonField.name())) { 202 return jsonField.name(); 203 } 204 } 205 return orginalName; 206 } 207 208 private boolean isIgnoreFiled(Method method) { 209 if (hasFastJson) { 210 JSONField jsonField = method.getAnnotation(JSONField.class); 211 if (jsonField != null && !jsonField.serialize()) { 212 return true; 213 } 214 } 215 return method.getAnnotation(JsonIgnore.class) != null; 216 } 217 } 218}