这个想法的来源是一个数值单位转换的逻辑,数据库中存储以元为单位的数字,前端展示万元,在进行数据库查询的时候并没有直接除以10000拿到查询结果。后面再改的时候又不像改SQL或者重写get或者set方法,又想到了之前学习了SpEL的使用,所以就想结合SpEL实现一个更灵活的自定义序列化器
首先实现SpEL通用表达式处理器:
import cn.hutool.core.lang.Assert;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Map;
/**
* @desc 通用SpEL表达式处理器
*/
public class CommonExpressionHandler {
private static final ExpressionParser expressionParser = new SpelExpressionParser();
/**
* 注册bean解析器
*/
private static final CommonBeanResolver beanResolver = new CommonBeanResolver();
private StandardEvaluationContext evaluationContext;
private boolean already;
/**
* 初始化相关工具
*
* @param beanFactory
*/
public CommonExpressionHandler() {
//注册解析上下文
this.evaluationContext = new StandardEvaluationContext();
this.evaluationContext.setBeanResolver(beanResolver);
}
/**
* 获取指定值
*
* @param expression
* @param clz
* @param <T>
* @return
*/
public <T> T getValue(String expression, Class<T> clz) {
Assert.isTrue(already, "CommonExpressionHandler, 请检查是否执行CommonExpressionHandler.setEvaluationContextVariable方法");
return expressionParser.parseExpression(expression).getValue(evaluationContext, clz);
}
/**
* 获取指定值
*
* @param expression
* @param <T>
* @return
*/
public <T> T getValue(String expression) {
Assert.isTrue(already, "CommonExpressionHandler, 请检查是否执行CommonExpressionHandler.setEvaluationContextVariable方法");
return (T) expressionParser.parseExpression(expression).getValue(evaluationContext);
}
/**
* 给解析上下文填充解析变量
*/
public void setVariable(Map<String, Object> parameters) {
for (String key : parameters.keySet()) {
//填充参数
evaluationContext.setVariable(key, parameters.get(key));
}
already = true;
}
/**
* 给解析上下文填充解析变量
*
* @param key
* @param parameter
* @return
*/
public CommonExpressionHandler addVariable(String key, Object parameter) {
//填充参数
evaluationContext.setVariable(key, parameter);
already = true;
return this;
}
}
自定义Bean解析器,返回相关Bean
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.expression.AccessException;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.Assert;
/**
* @desc bean解析器, 用于获取bean
*/
public class CommonBeanResolver implements BeanResolver {
@Override
public Object resolve(EvaluationContext context, String beanName) throws AccessException {
Object bean;
try {
bean = SpringUtil.getBean(beanName);
} catch (BeansException var4) {
//查不到的话默认拼接Impl后缀在获取一次
bean = SpringUtil.getBean(beanName.concat("Impl"));
if (bean == null) {
throw new AccessException("Could not resolve bean reference against BeanFactory", var4);
}
}
return bean;
}
}
自定义序列化器
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fzznkj.boot.common.annotation.FieldValueParse;
import com.fzznkj.boot.common.resolver.CommonExpressionHandler;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
/**
* @desc 字段值转换序列化处理器
*/
@Slf4j
public class FieldValueParser extends JsonSerializer<Object> implements ContextualSerializer {
private CommonExpressionHandler expressionHandler;
private String fieldName;
private String condition;
private String parseExpression;
public FieldValueParser(String fieldName, String condition, String parseExpression) {
this.expressionHandler = new CommonExpressionHandler();
this.fieldName = fieldName;
this.condition = condition;
this.parseExpression = parseExpression;
}
public FieldValueParser() {
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
expressionHandler.addVariable(fieldName, value);
if (StrUtil.isNotBlank(condition) && !expressionHandler.getValue(condition, Boolean.class)) {
gen.writeObject(value);
return;
}
try {
final Object objValue = expressionHandler.getObjValue(parseExpression);
gen.writeObject(objValue);
} catch (Exception e) {
log.error("获取转换值失败, 将写入原始值: {}, message: {}", value, e.getMessage());
gen.writeObject(value);
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
//为空直接跳过
final FieldValueParse parse = beanProperty.getAnnotation(FieldValueParse.class);
if (parse != null) {
return new FieldValueParser(beanProperty.getName(), parse.condition(), parse.parse());
}
return prov.findValueSerializer(beanProperty.getType(), beanProperty);
}
return prov.findNullValueSerializer(beanProperty);
}
}
定义好注解,内联Jackson序列化注解
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fzznkj.boot.common.parser.EnumNameParser;
import com.fzznkj.boot.common.parser.FieldValueParser;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @desc 字段值转换序列化注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JsonSerialize(using = FieldValueParser.class)
@JacksonAnnotationsInside
public @interface FieldValueParse {
/**
* 条件SpEL
* @return
*/
String condition() default "";
/**
* 转换表达式SpEL
* @return
*/
String parse();
}
使用
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fzznkj.boot.common.annotation.FieldValueParse;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* @desc
*/
@Getter
@Setter
public class PurchaseBusinessOverviewVO {
/*
* 调用Bean进行查询,写入orderName的值
*/
@FieldValueParse(parse = "@orderService.getOrder(#orderId)?.orderName")//service查询
private Long orderId;
/**
* 将枚举值转换为枚举名
*/
@FieldValueParse(parse = "T(com.order.enums.OrderTypeEnum).codeOf(#orderType)?.getName()") //枚举名回显
private Integer orderType;
/**
* 金额(万元)
*/
@FieldValueParse(parse = "#totalProcurementAmount.divide(10000)") //单位转换
private BigDecimal totalProcurementAmount;
/**
* 金额(万元)
*/
@FieldValueParse(parse = "#savedAmount.divide(10000)") //单位转换
private BigDecimal savedAmount;
/**
* 总金额(万元)
*/
@JsonIgnore
private BigDecimal totalAmount;
@FieldValueParse(parse = "#savedRate + '%'") //字符串拼接
private BigDecimal savedRate;
public BigDecimal getSavedRate() {
if (savedAmount == null || totalAmount == null) {
return BigDecimal.ZERO;
}
return savedRate = savedAmount.divide(totalAmount, 2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100));
}
}
这个功能与序列化方式处理枚举字段回显重复了, 这个想法和使用更灵活, 后者定制化的实现更多, 功能更专一。
这个想法其实还比较简单,还有能优化的点, 比如,如果想在SpEL表达式中想要使用其他字段的值该怎么做?比如如何用EL表达式替换掉对象中的getSavedRate()方法。如果想这样实现的话是不是需要提前加载整个对象放在解析上下文中? 先这样, 后面在研究研究。