Java属性和Map映射通用方案

一、背景

有类似JSON的key和实体属性不对应的场景,可以通过JSON类库提供的注解加在属性上填写值别名,然后JSON转换类可以实现正确的转换。

但是如果需求和JSON没半毛钱关系,如实现Map 到对象属性的映射,而且map中的key和属性名还不一致。肿么办?

注意这里的map的value实际适合属性的类型是保持一致的!!!

 

二、解决方案

2.1 注解+反射

注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 需要映射的字段
 */
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Field2Map {
    /**
     * 别名(属性名和map的key不对应时设置)
     */
    String alias() default "";
}

 

实体

@Data
public class MyBean {

    @Field2Map(alias = "alias")
    private String name;

    @Field2Map(alias = "myAge")
    private Integer age;

    @Field2Map
    private String nick;

    private Boolean no;
}

 

封装

import lombok.Data;

import java.lang.reflect.Field;
import java.lang.reflect.Method;


@Data
public class Invoke {
    
    private Field field;
    
    private Method getMethod;
    
    private Method setMethod;
    
    private String key;
}

工具类

package com.chujianyun.web.util;

import com.chujianyun.web.annotation.Field2Map;
import com.chujianyun.web.bean.Invoke;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class InvokeUtil {

    private static Map> cache = new ConcurrentHashMap<>();

    private static List getInvokesUseCacheIfPossible(Class clazz) throws IntrospectionException {
        List invokes;
        if (cache.containsKey(clazz)) {
            invokes = cache.get(clazz);
        } else {
            invokes = getInvokes(clazz);
            cache.put(clazz, invokes);
        }
        return invokes;
    }

    /**
     * 带转Map
     */
    public static  Map toMap(T object) throws InvocationTargetException, IllegalAccessException, IntrospectionException {

        // 这里可以用缓存
        List invokes = getInvokesUseCacheIfPossible(object.getClass());

        if (CollectionUtils.isEmpty(invokes)) {
            return new HashMap<>(0);
        }
        Map data = new HashMap<>(invokes.size());
        for (Invoke invoke : invokes) {
            data.put(invoke.getKey(), invoke.getGetMethod().invoke(object));
        }
        return data;
    }

    /**
     * Map转对象
     */
    public static  T toObject(Map data, Class tClass) throws InvocationTargetException, IllegalAccessException, InstantiationException, IntrospectionException {

        if (MapUtils.isEmpty(data)) {
            return null;
        }

        T object = tClass.newInstance();

        // 这里可以用缓存
        List invokes = getInvokesUseCacheIfPossible(object.getClass());
        if (CollectionUtils.isEmpty(invokes)) {
            return null;
        }

        for (Invoke invoke : invokes) {

            if (data.containsKey(invoke.getKey())) {
                invoke.getSetMethod().invoke(object, data.get(invoke.getKey()));
            }
        }
        return object;
    }

    /**
     * 获取调用对象
     */
    public static List getInvokes(Class clazz) throws IntrospectionException {

        List invokes = new ArrayList<>(2);
        Field[] declaredFields = clazz.getDeclaredFields();

        for (Field field : declaredFields) {
            if (field.isAnnotationPresent(Field2Map.class)) {
                Invoke invoke = new Invoke();
                invoke.setField(field);
                Field2Map field2Map = field.getAnnotation(Field2Map.class);
                String alias = field2Map.alias();
                if (StringUtils.isNotBlank(alias)) {
                    invoke.setKey(alias);
                } else {
                    invoke.setKey(field.getName());
                }
                PropertyDescriptor descriptor = new PropertyDescriptor(field.getName(), clazz);
                invoke.setGetMethod(descriptor.getReadMethod());
                invoke.setSetMethod(descriptor.getWriteMethod());
                invokes.add(invoke);
            }
        }
        return invokes;
    }
}

编写测试类

package com.chujianyun.web.util;

import com.chujianyun.web.bean.Invoke;
import com.chujianyun.web.bean.MyBean;
import org.apache.commons.collections.CollectionUtils;
import org.junit.Assert;
import org.junit.jupiter.api.Test;

import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

class InvokeUtilTest {

    Map data = new HashMap<>();

    {
        data.put("alias", "test");
        data.put("myAge", 22);
        data.put("nick", "tomcat");
    }

    @Test
    public void toMap() throws IllegalAccessException, IntrospectionException, InvocationTargetException {

        MyBean myBean = new MyBean();
        myBean.setAge(22);
        myBean.setName("test");
        myBean.setNick("tomcat");
        myBean.setNo(Boolean.FALSE);

        Map map = InvokeUtil.toMap(myBean);
        Assert.assertEquals(22, map.get("myAge"));
        Assert.assertEquals("test", map.get("alias"));
        Assert.assertEquals("tomcat", map.get("nick"));
        Assert.assertNull(map.get("no"));
    }

    @Test
    public void toObject() throws IntrospectionException, InstantiationException, IllegalAccessException, InvocationTargetException {
        MyBean myBean = InvokeUtil.toObject(data, MyBean.class);
        Assert.assertNotNull(myBean);
        Assert.assertEquals(Integer.valueOf(22), myBean.getAge());
        Assert.assertEquals("test", myBean.getName());
        Assert.assertEquals("tomcat", myBean.getNick());
        Assert.assertEquals("tomcat", myBean.getNick());
        Assert.assertNull(myBean.getNo());
    }


    @Test
    void getInvokes() throws IntrospectionException, InvocationTargetException, IllegalAccessException {


        List invokes = InvokeUtil.getInvokes(MyBean.class);
        MyBean myBean = new MyBean();

        Set result = new HashSet<>(2);
        for (Invoke invoke : invokes) {
            if (data.containsKey(invoke.getKey())) {
                invoke.getSetMethod().invoke(myBean, data.get(invoke.getKey()));
                result.add(invoke.getGetMethod().invoke(myBean));
            }
        }
        int count = CollectionUtils.countMatches(result, each -> data.values().contains(each));
        Assert.assertEquals(count, data.size());
    }
} 
  

测试通过.

上面只是提供一个思考,使用的时候可以在此基础上进行改造。

在工具类或者Service中定义一个缓存,结构如下  Map> cache = new ConcurrentHashMap();

存放解析的类到带有Alias注解属性的调用列表,方便和map直接进行转化。

 

如果存在就不需要再去反射构造调用列表,提高效率。

 

2.2 第三方库

可以使用Orika等属性映射工具来实现类似功能,不过由于个别库需要手动写映射,没有注解这么方便。

 

如果觉得本文对你有帮助,欢迎点赞评论,欢迎关注我,我将努力创作更多更好的文章。

 

你可能感兴趣的:(Java基础,通用方案)