Java(Spring)当中如何利用反射去进行数组/集合的注入(类似Autowired注解的实现)

在看到Spring当中是支持数组/集合/Map去进行注入的,但是我们如果自己要去使用,应该怎么做呢?以前自己确实没做过,这里记录一下。

1. 如何使用反射去进行数组字段的注入?

比如我们定义了如下这样一个字段

    private User[] users;

我们用反射如何去进行实现对这个字段去进行赋值?首先我们可以通过Class.getField去获取到这个users字段。

Field field = App.class.getField("users");

接着呢,我们的想法是什么?反射创建对象对吧!

我们使用如下的代码可以获取到字段的类型(User[])

Class<?> fieldType = field.getType();

那么我们如何去创建一个User[]类型的对象?因为这是个数组类型,我们自然可以拿到它的元素类型,使用如下的代码

Class<?> componentType = fieldType.getComponentType();

这个componentType就可以拿到User的类型Class。接着怎么做?我们如何创建一个数组对象?可以使用JDK提供的Array类给我们提供的相关方法:

Object userArray = Array.newInstance(componentType, 10);

使用上面的代码,userArray就是一个长度为10的User[],那么我们如何对这个数组当中的元素去进行赋值?当然也是使用Array类为我们提供的相关方法。

            for (int i = 0; i < 10; i++) {
                Object user = componentType.getDeclaredConstructor().newInstance();
                Array.set(userArray, i, user);
            }

使用Array.set方法就可以很方便地为数组当中的元素进行赋值了!所以使用反射对User[]类型的字段去进行赋值的完整的代码如下

        Field field = App.class.getField("users");
        Class<?> fieldType = field.getType();

        if (fieldType.isArray()) {
            Class<?> componentType = fieldType.getComponentType();
            Object userArray = Array.newInstance(componentType, 10);
            for (int i = 0; i < 10; i++) {
                Object user = componentType.getDeclaredConstructor().newInstance();
                Array.set(userArray, i, user);
            }
            field.setAccessible(true);
            field.set(new App(), userArray);
        }

2. 如何使用反射去对集合的字段去进行注入?

比如我们定义了如下的字段

    public List<User> users;

我们当然也可以使用反射拿到这个字段,以及拿到这个字段类型

Field field = App.class.getField("users");
Class<?> fieldType = field.getType();

但是问题来了,Java当中对于泛型的实现是使用的类型擦除,也就是所你List<User>,这样一个集合,在底层仍然是使用的List<Object>去进行实现的,对于泛型的检查都是在javac对Java代码在编译层面去进行检查的。

这说明了什么呢?说明了我们通过fieldType是拿不到我们定义的泛型信息的,因为类型被抹除成为Object类型了。我们有什么办法获取到泛型信息吗?既然通过fieldType获取不到,那么我们使用Filed去进行获取嘛,在HotSpot VM当中,Field是有记录泛型的类型的!那么我们如何去进行获取?我们可以使用它的getGenericType方法去进行获取。

Type genericType = field.getGenericType();

对于字段当中的泛型,实际上Type是一个ParameterizedType类型,我们可以将其进行强转试试!

        if (genericType instanceof ParameterizedType) {
            ParameterizedType type = (ParameterizedType) genericType;
            Type[] actualTypeArguments = type.getActualTypeArguments();
        }

我们发现强转之后,它就为我们提供了getActualTypeArguments方法,翻译过来叫做获取真正的泛型参数,这里的Type[]其实就是我们Field的真正的泛型类型数组,为什么是个数组?别忘了还有Map<K,V>这种。

我们这里既然是个Collection,那么肯定只有一个泛型,我们直接获取它的0号元素即可。

Type typeArgument = actualTypeArguments[0];

实际上我们这里拿到的Type就是一个真正的Class对象,我们直接强转为Class!接着就可以使用泛型的具体类型去创建对象了

Class typeArgument = (Class) actualTypeArguments[0];
Object o = typeArgument.getDeclaredConstructor().newInstance();

现在有个问题就是,我们不知道提供的集合是什么类型,比如List/Collection/Set,还是ArrayList/LinkedList等?这里貌似我们就没办法,只能做一层尽可能的枚举,毕竟用户还可以自定义Collection等!

       Collection collection;
        if (field.getType() == List.class || field.getType() == Collection.class) {
            collection = new ArrayList();
        } else if (field.getType() == Set.class) {
            collection = new HashSet();
        } else {
            collection = (Collection) field.getType().getDeclaredConstructor().newInstance();
        }

既然得到了Collection对象,也得到了泛型的User对象了,差的自然就是往Collection当中放元素了,这部分代码暂时忽略掉,您完全可以自己实现!

对于Map类型中的泛型,以及方法参数中的泛型,其实完全类似的,和上述原理类似,这里就不再进行赘述。

3.下面是一个我写的比较详细的注入案例

主要功能就是实现类似Spring当中的@Autowired/@Inject/@Resource去进行注入的情况

/**
 * @author wanna
 * @version v1.0
 */
public class InjectMetadataUtils {

    private final ConfigurableListableBeanFactory beanFactory;

    public InjectMetadataUtils(ConfigurableListableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    // 处理字段注入的情况
    public Object handleFieldInject(Field field, boolean required) {
        Value value = AnnotatedElementUtils.getMergedAnnotation(field, Value.class);
        Qualifier qualifier = AnnotatedElementUtils.getMergedAnnotation(field, Qualifier.class);
        // 1.如果是一个@Value注解的话,那么,直接解析占位符就行了...
        if (!Objects.isNull(value)) {
            return handleValueInject(value.value());
        }
        // 2.如果不是一个@Value注解,那么就是一个@Inject注解或者@Autowired注解...只需要解析@Qualifier注解就行了
        Object[] injectBean = new Object[1];

        // --2.1如果这个参数的类型是Map/Collection/Array,那么,需要去进行处理
        if (ClassUtils.isAssignableFrom(Collection.class, field.getType())
                || ClassUtils.isAssignableFrom(Map.class, field.getType()) ||
                field.getType().isArray()) {
            injectBean[0] = handleMultiBeans(field.getType(), field.getGenericType());

            // --2.2如果有Qualifier注解的话,那么按name去进行注入...
        } else if (!Objects.isNull(qualifier)) {
            injectBean[0] = beanFactory.getBean(qualifier.value(), field.getType());
            // --2.3 如果没有Qualifier注解,那么type类型去进行注入
        } else {
            injectBean[0] = beanFactory.getBean(field.getType());
        }
        int checkedIndex = checkRequired(injectBean);
        // 如果没有找到合适的Bean,那么抛出异常...
        if (required && checkedIndex != -1) {
            throw new IllegalStateException("在处理字段" + field + "时遇到了没有容器中没有的Bean,字段类型为" + field.getType());
        }
        return injectBean[0];
    }

    // 处理方法去进行注入的情况,需要对每个参数都去进行注入...
    public Object handleMethodParametersInject(Method method, boolean required) {
        Type[] types = method.getGenericParameterTypes();
        Parameter[] parameters = method.getParameters();
        Class<?>[] parameterTypes = method.getParameterTypes();
        // 要进行注入的参数列表...
        Object[] params = new Object[parameters.length];
        // 获取方法上的@Qualifier注解...
        Qualifier methodQualifier = AnnotatedElementUtils.getMergedAnnotation(method, Qualifier.class);
        // 获取方法上的@Value注解...
        Value methodValue = AnnotatedElementUtils.getMergedAnnotation(method, Value.class);
        // 1.如果方法上有@Qualifier注解,那么优先去进行处理
        if (!Objects.isNull(methodQualifier)) {
            if (parameterTypes.length != 1) {
                throw new NoSupportException("@Qualifier注解不支持标注在参数数量不是1个的方法上");
            }
            // 如果@Qualifier注解标注在只有一个参数的方法上,那么...按照名字去解析
            params[0] = beanFactory.getBean(methodQualifier.name(), parameterTypes[0]);

            // 2.如果方法上有@Value注解,那么也优先去进行处理
        } else if (!Objects.isNull(methodValue)) {
            if (parameterTypes.length != 1) {
                throw new NoSupportException("@Value注解不支持标注在参数数量不是1个的方法上");
            }
            // 如果@Value注解标注在只有一个参数的方法上,那么...直接进行解析占位符
            params[0] = handleValueInject(methodValue.value());
            // 3.如果方法上没标注@Value/@Qualifier注解,那么...遍历所有的参数去进行注入
        } else {
            for (int i = 0; i < parameterTypes.length; i++) {
                handleMethodParameter(params, i, parameterTypes, parameters, types);
            }
        }
        // 如果required=true,就得去检查是否每个属性都存在,如果其中一个不存在(返回值不为-1),那么都会抛出异常...
        int checkedIndex = checkRequired(params);
        if (required && checkedIndex != -1) {
            throw new IllegalStateException("在处理方法" + method + "时遇到了没有容器中没有的Bean,参数类型为" + parameters[checkedIndex]);
        }
        return params;
    }

    private void handleMethodParameter(Object[] params, int i, Class<?>[] parameterTypes, Parameter[] parameters, Type[] types) {
        // 获取参数类型、泛型类型以及Parameter...
        Class<?> parameterType = parameterTypes[i];
        Type type = types[i];
        Parameter parameter = parameters[i];

        // 获取参数上的@Value注解信息
        Value value = AnnotatedElementUtils.getMergedAnnotation(parameter, Value.class);

        // 如果这个参数的类型是Map/Collection/Array,那么,需要去进行处理
        if (ClassUtils.isAssignableFrom(Collection.class, parameterType)
                || ClassUtils.isAssignableFrom(Map.class, parameterType) ||
                parameterType.isArray()) {
            params[i] = handleMultiBeans(parameterType, type);

            // 如果这个参数类型不是Map/Collection/Array类型的话,那么直接解析placeholder/getBean即可
            // 处理方法参数上是@Value注解的话,那么...
        } else if (!Objects.isNull(value)) {
            params[i] = handleValueInject(value.value());
        } else {
            // 如果方法上具体某个参数上标注了Qualifier注解的话...那么需要按照名字去进行注入
            Qualifier qualifier = AnnotatedElementUtils.getMergedAnnotation(parameter, Qualifier.class);
            // 判断是根据name去注入还是根据type去进行注入
            if (Objects.isNull(qualifier)) {
                params[i] = beanFactory.getBean(parameterType);
            } else {
                params[i] = beanFactory.getBean(qualifier.name(), parameterType);
            }
        }
    }

    // 处理要注入多个Bean的情况,支持Array/Collection/Map三种方式,其它的不支持
    public Object handleMultiBeans(Class<?> type, Type genericType) {
        // 如果类型是个数组,那么从容器当中注入全部该类型的元素列表
        if (type.isArray()) {
            return handleArrayBean(type, genericType);
            // 如果类型是个集合类型的话,也是从容器中注入全部该类型的元素列表
        } else if (ClassUtils.isAssignableFrom(Collection.class, type)) {
            return handleCollectionBean(type, genericType);
            //如果类型是个Map类型,key是它的beanName,value是Bean
        } else if (ClassUtils.isAssignableFrom(Map.class, type)) {
            return handleMapBean(type, genericType);
        }
        return null;
    }

    // 处理要注入一个Array的情况,注入某种类型的全部Bean的数组
    private Object handleArrayBean(Class<?> type, Type genericType) {
        Class<?> componentType = type.getComponentType();  // 这个api是获取数组的元素类型
        List<?> beansForType = beanFactory.getBeansForType(componentType);
        // 使用Array类去创建一个目标类型的数组对象
        Object targetArray = Array.newInstance(componentType, beansForType.size());
        for (int i = 0; i < beansForType.size(); i++) {
            Array.set(targetArray, i, beansForType.get(i));
        }
        return targetArray;
    }

    // 处理要注入一个Collection的情况...注入某种类型的全部Bean的Collection
    @SuppressWarnings({"unchecked", "rawtypes"})
    private Object handleCollectionBean(Class<?> type, Type genericType) {
        Collection collection;
        if (type == Collection.class || type == List.class) {
            collection = new ArrayList();
        } else if (type == Set.class) {
            collection = new HashSet();
        } else {
            collection = (Collection) ClassUtils.newInstance(type);
        }
        AssertUtils.notNull(collection, "进行注入的Collection必须提供无参数构造器");
        // 如果提供了泛型参数,那么...按照泛型参数去进行注入
        if (genericType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
            Class targetType = (Class) actualTypeArguments[0];
            List<?> beanNamesForType = beanFactory.getBeansForType(targetType);
            collection.addAll(beanNamesForType);

            // 如果没有泛型参数,那么暂时不支持
        } else {
            
        }
        return collection;
    }

    // 处理需要注入一个Map的情况,Key是beanName,value是beanName对应的Bean
    @SuppressWarnings({"unchecked", "rawtypes"})
    private Object handleMapBean(Class<?> type, Type genericType) {
        Map map;
        if (type == Map.class) {
            map = new HashMap();
        } else {
            map = (Map) ClassUtils.newInstance(type);
        }
        AssertUtils.notNull(map, "Map必须提供无参数构造器");
        if (genericType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
            Class keyType = (Class) actualTypeArguments[0];
            AssertUtils.assertTrue(ClassUtils.isAssignableFrom(CharSequence.class, keyType),
                    "要进行注入Map的Key必须是字符串(CharSequence)类型的");
            Class valueType = (Class) actualTypeArguments[1];
            // 拿到容器中所有该类型的Bean,加入到map当中去...
            for (String name : beanFactory.getBeanNamesForType(valueType)) {
                Object injectBean = beanFactory.getBean(name);
                map.put(name, injectBean);
            }

            // 如果Map没有泛型参数,那么...暂时不支持...
        } else {
            
        }
        return map;
    }

    // 对候选的参数去进行非空的检查,如果有一个为空,那么就会return 非空的参数所在的索引,如果全部都非空,那么return -1
    private int checkRequired(Object[] params) {
        for (int i = 0; i < params.length; i++) {
            // 如果是字符串的话,并且为空串的话...那么
            if (params[i] instanceof CharSequence && StringUtils.isNullOrEmpty(params[i].toString())) {
                return i;
            }
            if (Objects.isNull(params[i])) {
                return i;
            }
        }
        return -1;
    }

    // 处理@Value去进行注入的情况
    private Object handleValueInject(String valueValue) {
        AssertUtils.assertTrue(!StringUtils.isNullOrEmpty(valueValue), "@Value注解不能不配置value属性");
        // 如果必要的话,那么使用嵌入式的值解析器去解析占位符
        return beanFactory.hasEmbeddedValueResolver() ? beanFactory.resolveEmbeddedValue(valueValue) : valueValue;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,653评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,321评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,833评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,472评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,306评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,274评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,658评论 3 385
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,335评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,638评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,697评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,454评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,311评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,699评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,986评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,254评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,647评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,847评论 2 335

推荐阅读更多精彩内容