在看到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;
}
}