美文网首页
Java(Spring)当中如何利用反射去进行数组/集合的注入(

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

作者: Wannay | 来源:发表于2021-12-24 14:58 被阅读0次

在看到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(Spring)当中如何利用反射去进行数组/集合的注入(

      本文链接:https://www.haomeiwen.com/subject/rwilqrtx.html