1.内省api
1.什么是内省:
内省是java语言针对bean属性,事件的一种缺省处理方法,spring源码中也经常会出现相关的api。
javaBean是一种特殊的类,主要用于信息传递,这种类中的方法主要用于私有字段的访问,且方法名符合某种命名规则,事实上,内省机制也是通过反射来实现的。
反射的功能是比内省强大的。
在Java内省中,用到的基本上就是上述几个类。
内省api的一般的做法是通过类 Introspector 的 getBeanInfo方法来获取某个对象的 BeanInfo 信息,然后通过 BeanInfo 来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的 取值/赋值 方法,然后我们就可以通过反射机制来调用这些方法,这就是内省机制。
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
@Test
public void testIntrospect1() throws IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(User.class, Object.class);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
logger.info("{}",propertyDescriptor.getPropertyType());
logger.info("{}",propertyDescriptor.getReadMethod());
logger.info("{}",propertyDescriptor.getWriteMethod());
}
}
// 2.操纵bean的指定属性:age
@Test
public void testIntrospect2() throws Exception {
User user = new User();
PropertyDescriptor pd = new PropertyDescriptor("age", User.class);
// 得到属性的写方法,为属性赋值
Method method = pd.getWriteMethod();
method.invoke(user, 24);
// 获取属性的值
method = pd.getReadMethod();
System.out.println(method.invoke(user, null));
}
@Test
public void testBeanUtil() throws Exception {
User user = new User();
// 赋值
BeanUtils.setProperty(user,"name","tom");
BeanUtils.setProperty(user,"age",10);
logger.info("user->{}",user);
// 获取值
logger.info("the user's name is ->{}.",BeanUtils.getProperty(user,"name"));
}
二、更强的反射工具
在spring中,我们除了能看到内省相关的api,看到的更多的可能是反射api了,当然针对原生api的复杂性,spring同样进行了封装,让其使用起来更简单。
spring给我们提供了强大的反射工具BeanWrapper,下边的例子展示了该类如何配合BeanDefinition对其进行了实例化:
1、bean的创建
@Test
public void testCreate() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1、通过任意形式捕获beanDefinition
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName("com.ydlclass.User");
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue("name","lily");
propertyValues.addPropertyValue("age",12);
beanDefinition.setPropertyValues(propertyValues);
// 2、通过权限定名称获得Class
Class<?> aClass = Class.forName(beanDefinition.getBeanClassName());
// 3、使用BeanWrapper包裹实例,使其更方便使用反射方法
BeanWrapper beanWrapper = new BeanWrapperImpl(aClass);
beanWrapper.setPropertyValues(beanDefinition.getPropertyValues());
Object bean = beanWrapper.getWrappedInstance();
logger.info("The bean is [{}]",bean);
}
我们可以看到BeanWrapperImpl仅仅需要一个Class就能十分友好的结合beanDefinition进行构建和赋值,而不需要通过复杂的反射获取构造器进行实例化,获取字段对象进行赋值,当然这仅仅是api封装的功劳,原理还是那些东西。
2、批量构造
我们可以使用如下的方法进行批量构造:
@Test
public void testBatchCreate() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1、通过任意形式捕获beanDefinition
SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(registry);
xmlReader.loadBeanDefinitions("classpath:spring.xml");
// 2、通过反射实例化
String[] definitionNames = registry.getBeanDefinitionNames();
for (String definitionName : definitionNames) {
BeanDefinition beanDefinition = registry.getBeanDefinition(definitionName);
String beanClassName = beanDefinition.getBeanClassName();
Class<?> aClass = Class.forName(beanClassName);
// 3、使用BeanWrapper包裹实例,使其更方便使用反射方法
BeanWrapperImpl beanWrapper = new BeanWrapperImpl(aClass);
DefaultConversionService conversionService = new DefaultConversionService();
// 转换器在这里呦:使用lamdba表达式写一个转换器
conversionService.addConverter((Converter<TypedStringValue, Integer>) source -> Integer.valueOf(Objects.requireNonNull(source.getValue())));
beanWrapper.setConversionService(conversionService);
beanWrapper.setPropertyValues(beanDefinition.getPropertyValues());
Object bean = beanWrapper.getWrappedInstance();
System.out.println(bean);
}
}
3、ResolvableType
该类可以封装Java类型,提供对超类类型、接口和泛型参数的访问,以及最终解析为类的能力,这是非常常见的一个类,他能及其方便的简化对反射api的调用,该类在spring中的使用率非常高。
ResolvableType可以从字段、方法参数、方法返回类型或类中获得。这个类上的大多数方法本身都会返回一个ResolvableType,以便于链式调用。
@Test
public void testTypeResolvableType() throws NoSuchFieldException {
ResolvableType type = ResolvableType.forField(DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects"));
// 获取类型
logger.info(type.getType().getTypeName());
// 获取泛型
logger.info(Arrays.toString(type.getGenerics()));
logger.info(Arrays.toString(type.getInterfaces()));
logger.info(Arrays.toString(type.resolveGenerics()));
// 获取来源
Class<?> resolve = type.resolve();
logger.info(type.getRawClass().getName());
}
2、类型转化
我们从xml中搜集到的所有数据都是【字符串】,但是实际的类中的成员变量可能是数字,数组,集合,或者是复杂的引用数据类型,所以spring给我们提供了强大的转换服务(conversionService接口)。
public interface ConversionService {
boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
@Nullable
<T> T convert(@Nullable Object source, Class<T> targetType);
// 将给定的{@code source}转换为指定的{@code targetType}。
Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
}
我们不妨看看DefaultConversionService的源码,更多核心的功能是在器父类中实现的,在构造实例时,他会默认传入大量可用转化器:
public class DefaultConversionService extends GenericConversionService {
@Nullable
private static volatile DefaultConversionService sharedInstance;
public DefaultConversionService() {
// 添加大量的默认的转换器
addDefaultConverters(this);
}
// 类似单例的获取方式
public static ConversionService getSharedInstance() {
DefaultConversionService cs = sharedInstance;
if (cs == null) {
synchronized (DefaultConversionService.class) {
cs = sharedInstance;
if (cs == null) {
cs = new DefaultConversionService();
sharedInstance = cs;
}
}
}
return cs;
}
// 添加适合大多数环境的转换器
public static void addDefaultConverters(ConverterRegistry converterRegistry) {
addScalarConverters(converterRegistry);
addCollectionConverters(converterRegistry);
converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new StringToTimeZoneConverter());
converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
//...还有好多
}
// 增加通用的转换器,例如集合、数组、对象等
public static void addCollectionConverters(ConverterRegistry converterRegistry) {
ConversionService conversionService = (ConversionService) converterRegistry;
converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService));
converterRegistry.addConverter(new CollectionToArrayConverter(conversionService));
converterRegistry.addConverter(new StringToCollectionConverter(conversionService));
converterRegistry.addConverter(new CollectionToObjectConverter(conversionService));
converterRegistry.addConverter(new ObjectToCollectionConverter(conversionService));
//...还有好多
}
// 新增标量的转化器,主要是字符串数字类型
private static void addScalarConverters(ConverterRegistry converterRegistry) {
converterRegistry.addConverterFactory(new NumberToNumberConverterFactory());
converterRegistry.addConverterFactory(new StringToNumberConverterFactory());
converterRegistry.addConverter(Number.class, String.class, new ObjectToStringConverter());
converterRegistry.addConverter(new StringToPropertiesConverter());
converterRegistry.addConverter(new PropertiesToStringConverter());
converterRegistry.addConverter(new StringToUUIDConverter());
//...还有好多
}
}
我们可以编写如下的测试用例,可以将字符串转换为数字和列表:
@Test
public void testConvertInteger(){
String source = "100";
ConversionService conversionService = new DefaultConversionService();
if(conversionService.canConvert(String.class,Integer.class)){
Integer target = conversionService.convert(source, Integer.class);
logger.info("The number is {}.", target);
}
}
@Test
public void testConvertList(){
String source = "100,12,23,54,56";
ConversionService conversionService = new DefaultConversionService();
if(conversionService.canConvert(String.class, List.class)){
List target = conversionService.convert(source, List.class);
logger.info("The number is {}.", target);
}
}
这种类型转换的能力非常有用,我们从【xml到java对象】,从【前端参数到java对象】,都需要这样的强大能力。
2、独立编写转化器
我们也可以实现自己的转化器,我们不妨写一个【从字符串到User类】的转化器,我们的转换器需要实现GenericConverter接口,这个可以仿照其他的转换器写:
我们可以先看看GenericConverter接口:
public interface GenericConverter {
// 返回目标类型和源类型的一个set
Set<ConvertiblePair> getConvertibleTypes();
// 新的方法
Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
// 定义了一个 source-to-target class pair.
final class ConvertiblePair {
private final Class<?> sourceType;
private final Class<?> targetType;
... 其他内容省略
}
}
我们需要实现GenericConverter的两个方法:
public class UserConvert implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
// 返回一个Set集合,其中的元素为一个只包含object(obj)的不可变集合
return Collections.singleton(new GenericConverter.ConvertiblePair(String.class, User.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
// 我们假设字符串的类型如下: name | age
assert source != null;
String[] nameAndAge = source.toString().split("\\|");
if(nameAndAge.length == 2){
return new User(nameAndAge[0].trim(),Integer.valueOf(nameAndAge[1].trim()));
} else {
throw new RuntimeException("转化出现异常.");
}
}
}
在当前的测试用例中,我们将我们的convert添加到conversionService中:
@Test
public void testConvertUser(){
String source = "Tom | 23";
// 这里必须使用子类的类型,接口并不提供addConverter方法
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new UserConvert());
if(conversionService.canConvert(String.class, User.class)){
User target = conversionService.convert(source, User.class);
logger.info("The user is {}.", target);
}
}
// 结果
19:51:03.210 [main] INFO com.ydlclass.ToolsTest - The user is User{name='Tom', age=23}.
3.转换器源码:
spring在考虑转换的时候会考虑到 由 a -> b的转换,还会考虑到由a 到 b 的子类的转换。所以在spring中,转换器的结构是如下所示:
这一对对应的转换器们(因为能处理一对类型转换可能存在多个转换器),内部使用一个双端队列Deque来存储,保证顺序。
private static class ConvertersForPair {
// 内部维护的队列
private final Deque<GenericConverter> converters = new ConcurrentLinkedDeque<>();
public void add(GenericConverter converter) {
this.converters.addFirst(converter);
}
@Nullable
public GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
for (GenericConverter converter : this.converters) {
// 此处表明,如果我们有特殊的需求,还可以实现ConditionalGenericConverter,实现特殊的匹配规则,连边中的converter可以有不同的匹配规则,
// 当然通常情况下会返回第一个
if (!(converter instanceof ConditionalGenericConverter genericConverter) ||
genericConverter.matches(sourceType, targetType)) {
return converter;
}
}
return null;
}
}
private static class Converters {
// 存取通用的转换器,并不限定转换类型,一般用于兜底
private final Set<GenericConverter> globalConverters = new CopyOnWriteArraySet<>();
// 指定了类型对,对应的转换器们的映射关系。
// ConvertiblePair:表示一对,包含sourceType和targetType
// ConvertersForPair:这一对对应的转换器们(因为能处理一对类型转换可能存在多个转换器),内部使用一个双端队列Deque来存储,保证顺序
private final Map<ConvertiblePair, ConvertersForPair> converters = new ConcurrentHashMap<>(256);
public void add(GenericConverter converter) {
// 获得他的类型对儿
Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
if (convertibleTypes == null) {
// 如果没有限定转换类型,添加到globalConverters
this.globalConverters.add(converter);
}
else {
// 如果已经存在转换类型,我们写的都在这里
for (ConvertiblePair convertiblePair : convertibleTypes) {
// 找到与之匹配的加进去,这里是个链表
getMatchableConverters(convertiblePair).add(converter);
}
}
}
@Nullable
public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
// 搜索完整的类型层次结构,父类--->
// 比如想要搜索【虎猫 -> 老虎】,但如过虎猫有父类(猫)
// 我们还需检索【猫 -> 老虎】
List<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType());
List<Class<?>> targetCandidates = getClassHierarchy(targetType.getType());
for (Class<?> sourceCandidate : sourceCandidates) {
for (Class<?> targetCandidate : targetCandidates) {
// 所有的类型都要匹配
ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
// 找到一个就返回
GenericConverter converter = getRegisteredConverter(sourceType, targetType, convertiblePair);
if (converter != null) {
return converter;
}
}
}
return null;
}
@Nullable
private GenericConverter getRegisteredConverter(TypeDescriptor sourceType,
TypeDescriptor targetType, ConvertiblePair convertiblePair) {
// 根据convertiblePair获取ConvertersForPair
ConvertersForPair convertersForPair = this.converters.get(convertiblePair);
if (convertersForPair != null) {
GenericConverter converter = convertersForPair.getConverter(sourceType, targetType);
if (converter != null) {
return converter;
}
}
// 检查是否能匹配兜底的全局转换器
for (GenericConverter globalConverter : this.globalConverters) {
if (((ConditionalConverter) globalConverter).matches(sourceType, targetType)) {
return globalConverter;
}
}
return null;
}
}