说这一段:
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
看这段需要你理解且不局限下面这两个概念:
JavaBeanhttps://blog.csdn.net/dmw412724/article/details/84787225
ProertyEditorhttps://blog.csdn.net/dmw412724/article/details/84787331
第一行代码:
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
PropertyValue相当于key+value.
ServletConfigPropertyValues父类是MutablePropertyValues,MutablePropertyValues实现了PropertyValues接口。
PropertyValues里面要求对于PropertyValue的操作都是数组格式操作。而MutablePropertyValues则是使用List来实际操作的,在必要的时候才转换成数组。
ServletConfigPropertyValues的构造方法:
// 获取ServletConfig的参数
// springmvc里获得了web.xml里配置DispaterServlet的contextConfigLocation
Enumeration<String> en = config.getInitParameterNames();
while (en.hasMoreElements()) {
String property = en.nextElement();// contextConfigLocation
Object value = config.getInitParameter(property);// classpath:springmvc.xml
addPropertyValue(new PropertyValue(property, value));
if (missingProps != null) {
missingProps.remove(property);
}
}
看下addPropertyValue方法
public MutablePropertyValues addPropertyValue(PropertyValue pv) {
for (int i = 0; i < this.propertyValueList.size(); i++) {
PropertyValue currentPv = this.propertyValueList.get(i);
if (currentPv.getName().equals(pv.getName())) {
pv = mergeIfRequired(pv, currentPv);
setPropertyValueAt(pv, i);
return this;
}
}
this.propertyValueList.add(pv);
return this;
}
上面讲的是:先查看是否包含这个Property,如果以及包含了,那么执行这俩方法:mergeIfRequired(是否有必要合并),setPropertyValueAt(重置),如果不包含,直接添加。
关于setPropertyValueAt没什么可说的,操作List集合而已。mergeIfRequired需要理解Mergeable接口。
public interface Mergeable {
//是否合并
boolean isMergeEnabled();
//合并
Object merge(Object parent);
}
该接口并非是大众接口,它针对的只有下面四个集合/数组类型的子类:
* @see org.springframework.beans.factory.support.ManagedSet
* @see org.springframework.beans.factory.support.ManagedList
* @see org.springframework.beans.factory.support.ManagedMap
* @see org.springframework.beans.factory.support.ManagedProperties
合并集合就是把元素放到一起。
所以mergeIfRequired这个方法就是判断如果可以合并,就把集合的元素放到一起。
本行代码完后:PropertyValues里面会多了一个PropertyValue(contextConfigLocation-classpath:springmvc.xml)
第二行代码:
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
查看forBeanPropertyAccess方法里面,发现就new BeanWrapperImpl.接下来需要了解BeanWrapperImpl 的类树,如下:
BeanWrapperImpl具有上面的那些类和接口的所有功能,我们从上往下逐个说。
PropertyEditorRegistry:注册自定义PropertyEditor的接口。为什么叫自定义呢?因为还有一个默认的defaultPropertyEditor,defaultPropertyEditor不在这里,而是在它的实现类PropertyEditorRegistrySupport里面,查看里面的一个方法:
private void createDefaultEditors() {
this.defaultEditors = new HashMap<Class<?>, PropertyEditor>(64);
// Simple editors, without parameterization capabilities.
// The JDK does not contain a default editor for any of these target types.
this.defaultEditors.put(Charset.class, new CharsetEditor());
this.defaultEditors.put(Class.class, new ClassEditor());
this.defaultEditors.put(Class[].class, new ClassArrayEditor());
this.defaultEditors.put(Currency.class, new CurrencyEditor());
this.defaultEditors.put(File.class, new FileEditor());
this.defaultEditors.put(InputStream.class, new InputStreamEditor());
this.defaultEditors.put(InputSource.class, new InputSourceEditor());
this.defaultEditors.put(Locale.class, new LocaleEditor());
this.defaultEditors.put(Pattern.class, new PatternEditor());
this.defaultEditors.put(Properties.class, new PropertiesEditor());
this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
this.defaultEditors.put(URI.class, new URIEditor());
this.defaultEditors.put(URL.class, new URLEditor());
this.defaultEditors.put(UUID.class, new UUIDEditor());
if (zoneIdClass != null) {
this.defaultEditors.put(zoneIdClass, new ZoneIdEditor());
}
// Default instances of collection editors.
// Can be overridden by registering custom instances of those as custom editors.
this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));
// Default editors for primitive arrays.
this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());
...
...
}
这里面注册了一些:工具类型的,集合类型的,基本数据类型,基本包装类型,基本数据数组类型的等一些PropertyEditor,我们再翻看这些PropertyEditor就会发现主要的setAsText方法都已经重写了,字符串转这些类型的都已经有了。
PropertyEditorRegistrySupport不仅有defaultPropertyEditor的Map,而且实现了PropertyEditorRegistry,还支持对自定义类型PropertyEditor的添加和注册管理。
TypeConverter:定义类型转换的接口,通常与PropertyEditorRegistry一起使用。可以看到里面的接口都是转换模式:把value转成T。
<T> T convertIfNecessary(Object value, Class<T> requiredType) throws TypeMismatchException;
它也有实现类:TypeConverterSupport,但是查看源码,发现TypeConverterSupport近乎是个没有意义的类,而真正起作用的是里面的TypeConverterDelegate。
TypeConverterDelegate是个内部的转换工具类,它不是public的而是包范围的。
通过查看源码可以发现,它里面做了具体的类型转换:字符串类型换成其他数据类型啊,如果是数组该怎么转,如果是map该怎么转,如果是基本数据类型该怎么转等等。这一块就是spring类型转换策略的核心。
PropertyAccessor是操作类属性的接口,是BeanWrapper的基接口。可以看到它规范的方法都是对属性值进行操作的。
Object getPropertyValue(String propertyName) throws BeansException;
void setPropertyValue(String propertyName, Object value) throws BeansException;
void setPropertyValue(PropertyValue pv) throws BeansException;
void setPropertyValues(Map<?, ?> map) throws BeansException;
全是操作接口,也就是说BeanWrapper的一些有关于操作属性设置的方法是从这里来的。这里面的setPropertyValue(String propertyName, Object value) 这个方法相信你会喜欢的,当然前提是接着往下看。
ConfigurablePropertyAccessor也是一个接口,它封装了PropertyAccessor的具体操作方法,改了个名字成ConversionService转换服务,看起来更高大上了。
到了BeanWrapper这里,它已经承载了很多接口了,但是还不满足,它还要再来几个接口。
在Spring里,BeanWrapper是低级JavaBeans基础结构的中央接口,通常不直接使用,而是使用org.springframework.beans.factory.BeanFactory 或者org.springframework.validation.DataBinder来调用,它可以获取和设置属性值,获取属性描述以及查询属性的读写和是否私有,还支持嵌套属性且无限深度。功能简直爆炸!!!
上面我们讲到PropertyAccessor是个操作属性的接口,它其实有实现AbstractPropertyAccessor,AbstractPropertyAccessor其实结合它的父类TypeConverterSupport的一些操作把PropertyAccessor里的大部分set类型的方法给具体实现了,一些get方法留给后来者。
那么BeanWrapperImpl左手实现BeanWrapper,右手继承AbstractPropertyAccessor,此时的它对于javaBean的属性操作能力达到了恐怖如斯的阶段。而在它的构造方法这里,它注册了所有的默认Editors,以及设置了包装类型,如下所示:
public BeanWrapperImpl(Object object) {
registerDefaultEditors();
setWrappedInstance(object);
}
本行代码结束后:会把本Servlet对象(这行代码里的this是DispatcherServlet,因为本方法是DispatcherServlet的实例来调用的)包装成有属性操作能力的类。
第三行代码:
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
这里需要注意的是: ResourceLoader是个接口,查看里面的代码,你会发现里面有个经典的属性:
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
ResourceUtils.CLASSPATH_URL_PREFIX就是"classpath:"
我们在web.xml里配置contextConfigLocation时写的是classpath:springmvc.xml,前面多了一个classpath,实际上原因就和这个有关的.
ResourceLoader是加载资源的策略接口,它的getResource方法明确规定了具体实现必须遵循以下原则:
1.必须能根据完整URL来找到资源。如:file:C:/test.dat
2.必须能根据以classpath开头的URL来找到资源。如:classpath:test.dat
3.能够根据可以访问的文件夹来找到资源。如:WEB-INF/test.dat
本行代码是多态,具体实现是ServletContextResourceLoader,ServletContextResourceLoader里面几乎没有什么东西,但你要知道它继承自DefaultResourceLoader,DefaultResourceLoader又继承自ResourceLoader接口。
DefaultResourceLoader实现了ResourceLoader的getResource方法。
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
上述代码逻辑是:
1.如果location以“/”开头,那就是项目路径。
2.如果以classpath开头,就截取location的后半段拼接到类加载后面(一般都是WEB-INF/classes后面)
3.以URL的方式去寻找
上面所讲的牵扯到了classpath,但和本行代码没有关系,本行代码不会执行getResource这个方法的。
本行代码结束后:只是创建了一个资源加载器。
第四行代码:
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
该行代码不具体深入研究,只说下做了什么。
BeanWrapper注册了一个自定义的Editor,我们前面讲了BeanWrapper拥有了很多默认的PropertyEditor,但是其他类型的还需要添加到自定义Editor的Map里。
本行代码结束后:创建了一个和ResourceLoader相关的PropertyEditor,放到了自定义PropertyEditor的Map里。
第五行代码:
initBeanWrapper(bw);
本行代码什么都没有做,估计为了以后扩展。
第六行代码:
bw.setPropertyValues(pvs, true);
BeanWrapperImpl的setProperty型方法,都是把包装对象的某个属性操作为某个值。
第一行代码结束后已经得出pvs里是PropertyValue(contextConfigLocation,classpath:springmvc.xml)
而第二行代码BeanWrapperImpl包装的是this,是DispatherServlet。
那么这句代码就是相当于DispatherServlet.setContextConfigLocation("classpath:springmvc.xml");
只是一个是主动set,一个是被动等待操作属性.
需要注意的是DispatherServlet并没有contextConfigLocation这个属性,而是在它的父类FrameworkServlet里
本行代码结束以后:
contextConfigLocation属性会被填入一个值classpath:springmvc.xml。
结语:
弄完了。好累啊~~~
至于为什么会喜欢setPropertyValue(String propertyName, Object value),给大家一个简单的例子,一看就知道的。
1.汽车类,有个颜色属性
public class Car {
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
2.人类,人都有一辆车和年龄
public class Men {
private int age;
private Car car ;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
}
3.测试一下:猜猜打印会有结果吗??????
public static void main(String[] args) {
Men men = new Men();
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(men);
bw.setPropertyValue("car", new Car());
bw.setPropertyValue("age", 2);
System.out.println(men.getAge());
System.out.println(men.getCar());
}