springmvc源码阅读之启动加载(2)-----------初始化参数

说这一段: 

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());
		
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值