Java(Spring)当中利用反射去进行数组/集合的注入

在看到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;
    }
}
#Java##学习路径#
全部评论
好嘞,谢谢大佬分享
点赞 回复 分享
发布于 2022-01-13 16:23

相关推荐

ProMonkey2024:5个oc?厉害! 但是有一个小问题:谁问你了?😡我的意思是,谁在意?我告诉你,根本没人问你,在我们之中0人问了你,我把所有问你的人都请来 party 了,到场人数是0个人,誰问你了?WHO ASKED?谁问汝矣?誰があなたに聞きましたか?누가 물어봤어?我爬上了珠穆朗玛峰也没找到谁问你了,我刚刚潜入了世界上最大的射电望远镜也没开到那个问你的人的盒,在找到谁问你之前我连癌症的解药都发明了出来,我开了最大距离渲染也没找到谁问你了我活在这个被辐射蹂躏了多年的破碎世界的坟墓里目睹全球核战争把人类文明毁灭也没见到谁问你了(别的帖子偷来的,现学现卖😋)
点赞 评论 收藏
分享
找不到工作死了算了:没事的,雨英,hr肯主动告知结果已经超越大部分hr了
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务