java解决调用死环导致栈溢出

场景:最近写了个StringUtils,继承了org.apache.commons.lang3.StringUtils,在这个蛮好用的字符串工具类上做一些扩展,扩展到toString的时候,我写了一个safeToString方法,传入一个对象,无论你传的是啥都能给你变打印字符串并且不报错,问题就遇到了,假设我对象自己重写了toString方法,我当然要用对象自己的toString,那么我就写了,但是出现了一个问题,就是,如果对象重写的toString里调用的我们这个工具类的safeToString去打印,那就会出现栈溢出,那就需要破解这个死环,所以设计了一个modMap来检测出调用环

在这里插入图片描述


import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 字符串工具类
 * 继承org.apache.commons.lang3.StringUtils
 *
 * @author humorchen
 * @date 2022/8/11 16:20
 */
public class StringUtils extends org.apache.commons.lang3.StringUtils {
    /**
     * urf8编码
     */
    public static final String UTF8 = "utf8";
    /**
     * 空字符串
     */
    public static final String EMPTY_STRING = "";
    /**
     * null的字符串
     */
    public static final String NULL_STRING = "null";
    /**
     * 默认字符串编码
     */
    public static final String DEFAULT_CHARSET = UTF8;
    /**
     * 防止死循环
     */
    private static final Map<Object, Integer> modMap = new ConcurrentHashMap<>();
    /**
     * 日志
     */
    private static Logger logger = LoggerFactory.getLogger(StringUtils.class);


    /**
     * 压缩成url编码的字符串(默认utf8)
     *
     * @param str
     * @return
     */
    public static String urlEncode(String str) {
        return urlEncode(str, DEFAULT_CHARSET);
    }

    /**
     * 压缩成url编码的字符串
     *
     * @param str
     * @param charset
     * @return
     */
    public static String urlEncode(String str, String charset) {
        if (isEmpty(str)) {
            return EMPTY_STRING;
        }
        String ret = EMPTY_STRING;
        try {
            ret = URLEncoder.encode(str, charset);
        } catch (UnsupportedEncodingException e) {
            logger.error("urlEncode error", e);
        }
        return ret;
    }

    /**
     * url字符串解码(默认utf8)
     *
     * @param str
     * @return
     */
    public static String urlDecode(String str) {
        return urlDecode(str, DEFAULT_CHARSET);
    }

    /**
     * url字符串解码
     *
     * @param str
     * @param charset
     * @return
     */
    public static String urlDecode(String str, String charset) {
        if (isEmpty(str)) {
            return EMPTY_STRING;
        }
        String ret = EMPTY_STRING;
        try {
            ret = URLDecoder.decode(str, charset);
        } catch (UnsupportedEncodingException e) {
            logger.error("urlDecode error", e);
        }
        return ret;
    }

    /**
     * 安全序列化
     *
     * @param obj
     * @return
     */
    public static String safeToString(Object obj) {
        if (obj == null) {
            return NULL_STRING;
        }
        String ret = EMPTY_STRING;
        boolean mod = false;
        try {
            // 重写了且无调用死环就用自己的
            if (isOverrideToString(obj) && modMap.put(obj, 1) == null) {
                mod = true;
                ret = obj.toString();
            } else {
                // 默认json输出
                ret = JSONObject.toJSONString(obj);
            }
        } catch (Exception e) {
            logger.error("safeToString error", e);
        } finally {
            if (mod) {
                modMap.remove(obj);
            }
        }
        return ret;
    }

    /**
     * 是否重写了toString方法
     *
     * @param classInstance
     * @return
     */
    public static boolean isOverrideToString(Object classInstance) {
        return classInstance != null && isOverrideToString(classInstance.getClass());
    }

    /**
     * 是否重写了toString方法
     * (用toString方法和@Overrided判定不准确)
     *
     * @param cls
     * @return
     */
    public static boolean isOverrideToString(Class<?> cls) {
        // 空的和根对象不算重写
        if (cls == null || Object.class.equals(cls)) {
            return false;
        }
        // 是否自己声明了toString方法
        Method[] declaredMethods = cls.getDeclaredMethods();
        for (Method method : declaredMethods) {
            if ("toString".equals(method.getName()) && method.getReturnType().equals(String.class) && method.getParameterTypes().length == 0) {
                return true;
            }
        }
        return false;
    }

}

posted @ 2022-08-23 14:41  HumorChen99  阅读(9)  评论(0编辑  收藏  举报  来源