BeanFactory 使用前的准备

上一篇文章 https://www.cnblogs.com/redwinter/p/16165878.html 介绍了自定义标签的使用,完成了AbstractApplicationContext#refresh 第二个方法 的介绍,本文将继续介绍Spring源码的重要方法AbstractApplicationContext#refresh方法的第三个方法:prepareBeanFactory,准备BeanFactory

源码如下:

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Tell the internal bean factory to use the context's class loader etc.
// 设置类加载器
beanFactory.setBeanClassLoader(getClassLoader());
// 设置Spel 表达式解析器,用于属性填充时对值进行表达式解析
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
// 扩展点,添加一个属性编辑器的注册器,也可以使用 CustomEditorConfigurer 进行设置
// 后面在进行属性填充的时候会调用这个属性编辑器进行属性的解析
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment())); // Configure the bean factory with context callbacks.
// 扩展点,添加一个BeanPostProcessor 这里添加这个进行处理,使用前置处理器执行下面忽略的六个Aware接口
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
// 由于上面设置了这六个接口,因此需要忽略掉,不让Spring使用自动装配进行Bean的装配,而是使用BeanPostProcessor
// 的后置处理器的前置方法进行调用,因为如果不忽略,那么自定义的Bean中就会使用Setter注入进行装配,
// spring 这样做是为了统一的进行处理在Bean增强的时候
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class); // BeanFactory interface not registered as resolvable type in a plain factory.
// MessageSource registered (and found for autowiring) as a bean.
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this); // Register early post-processor for detecting inner beans as ApplicationListeners.
// 添加一个事件监听器的装饰器
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this)); // Detect a LoadTimeWeaver and prepare for weaving, if found.
// aop织入 编译器织入、运行期织入、类加载织入
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
// Set a temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
} // Register default environment beans.
// 注册环境信息
if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
}
if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
}
if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
}
}

这个方法中主要做了以下事情:

  • 设置BeanFactory的类加载器。
  • 设置Bean的SPEL表达式的解析器,其作用是对值进行表达式的解析,比如在属性填充时,针对值是Properties或者String类型的时候就会使用el表达式进行解析。
  • 设置属性编辑器的注册器,作用是对属性进行解析,比如在属性填充时,针对字符串String类型的时候进行类型转换,就可以自定义属性编辑器针对性的进行解析操作。
  • 添加一些内置的BeanPostProcessor用于后面对象初始化时调用。
  • 设置环境信息,系统属性,系统环境变量等。

这个方法预留了一些扩展点,比如可以添加自定义的属性编辑器,添加自定义的BeanPostProcessor等。

定制Bean的属性解析器

我们知道在Bean的初始化时是分为两步,一步是属性填充,一步是初始化,在属性填充的时候,Spring会针对属性进行解析,如果属性值对应的类型和传入的值类型不一致,就会进行值的自定义解析,前提是你自定义了属性解析器,否则就会报错:报值的类型转换失败

接下来我们自定义一个属性解析器,比如我现在有个类CustomUser,其中有个属性类型是Address,还有个属性类型是Date,但是我在定义Bean的时候我把address属性设置为xxx_xxx_xxx,表示xxx省xxx市xxx区(县),date属性设置为yyyy-MM-dd HH:mm:ss格式的日期,我要让Spring帮我解析出正确的值出来,话不多说,上代码。

编写CustomUser类以及Address类

  • Address类
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class Address { private String province;
private String city;
private String town; public String getProvince() {
return province;
} public void setProvince(String province) {
this.province = province;
} public String getCity() {
return city;
} public void setCity(String city) {
this.city = city;
} public String getTown() {
return town;
} public void setTown(String town) {
this.town = town;
} // 为了验证重写toString方法
@Override
public String toString() {
return "Address{" +
"province='" + province + '\'' +
", city='" + city + '\'' +
", town='" + town + '\'' +
'}';
}
}
  • CustomUser类
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class CustomUser {
private String name;
private Address address;
private Date date; public Date getDate() {
return date;
} public void setDate(Date date) {
this.date = date;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Address getAddress() {
return address;
} public void setAddress(Address address) {
this.address = address;
} @Override
public String toString() {
return "CustomUser{" +
"name='" + name + '\'' +
", address=" + address +
", date=" + date +
'}';
}
}

编写Address解析器和注册器

  • 解析器(编辑器)
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class AddressPropertyEditor extends PropertyEditorSupport { @Override
public void setAsText(String text) throws IllegalArgumentException {
/**
* 自定义属性编辑器,将属性解析成自定义对象,比如传入的是一个字符串,可以解析成另外一个对象
*/
String[] arr = text.split("_");
Address address = new Address();
address.setProvince(arr[0]);
address.setCity(arr[1]);
address.setTown(arr[2]);
// 设置值
setValue(address);
}
}
  • 注册器
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class AddressPropertyEditorRegistrar implements PropertyEditorRegistrar {
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(Address.class,new AddressPropertyEditor());
}
}

编写Date解析器和注册器

  • 解析器(编辑器)
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class DatePropertyEditor extends PropertyEditorSupport { private final DateFormat dateFormat; public DatePropertyEditor(DateFormat dateFormat) {
this.dateFormat = dateFormat;
} @Override
public void setAsText(String text) throws IllegalArgumentException {
if (!StringUtils.hasText(text)) {
System.out.println("日期类型的属性不能为空!");
return;
}
try {
Date date = dateFormat.parse(text);
setValue(date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
  • 注册器
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class DatePropertyEditorRegistrar implements PropertyEditorRegistrar {
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(Date.class,new DatePropertyEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")));
}
}

配置注册器到Spring容器中

配置注册器有两种方式,一种是直接在定制BeanFactory的方法中添加注册器,一种是在Spring配置文件中添加

我在Spring容器中先配置CustomUser的信息以及Date日期类型的注册器:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:redwinter="http://www.redwinter.com/schema/redwinter"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.redwinter.com/schema/redwinter http://www.redwinter.com/schema/redwinter.xsd
"> <!--自定义标签-->
<redwinter:dl id ="redwinter" email="abc@qq.com" password="123456" username="redwinter-name"/>
<redwinter:dl id ="redwinter123456" email="123456-abc@qq.com" password="123456" username="redwinter-name"/>
<!--自定义属性编辑器的解析器-->
<bean class="com.redwinter.test.CustomUser">
<property name="name" value="冬玲记忆"/>
<property name="address" value="四川省_成都市_郫都区"/>
<property name="date" value="2022-04-19 19:50:20"/>
</bean>
<!--配置自定义的编辑器注册器-->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<bean class="com.redwinter.test.DatePropertyEditorRegistrar"/>
</list>
</property>
</bean> </beans>

另外还有个Address的注册器我配置在定制BeanFactory的方法中:

@Override
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
// 扩展点 设置不去处理循环依赖或者beanDefinition覆盖
super.setAllowBeanDefinitionOverriding(true);
super.setAllowCircularReferences(true);
super.customizeBeanFactory(beanFactory);
// 添加一个自定义的属性编辑器的注册器
beanFactory.addPropertyEditorRegistrar(new AddressPropertyEditorRegistrar());
}

好了,配置完成,运行试试:

public class BeanCreate {

	@Test
public void classPathXml() {
// ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-test.xml");
ClassPathXmlApplicationContext context = new MyClassPathXmlApplicationContext("classpath:spring-test.xml"); Redwinter redwinter = (Redwinter) context.getBean("redwinter");
System.out.println(redwinter.getEmail()); Redwinter redwinter123456 = (Redwinter) context.getBean("redwinter123456");
System.out.println(redwinter123456.getEmail()); CustomUser bean = context.getBean(CustomUser.class);
System.out.println(bean);
}
}

输出日志:

abc@qq.com
123456-abc@qq.com
CustomUser{name='冬玲记忆', address=Address{province='四川省', city='成都市', town='郫都区'}, date=Tue Apr 19 19:50:20 CST 2022}

说明配置没有问题,输出了想要的结果。

这里有个疑问为什么你知道在编写注册器和解析器的时候需要实现这些类呢?

其实原因很简单,根据源码我们知道Spring默认添加了一个ResourceEditorRegistrar注册器,进去ResourceEditorRegistrar类中发现,他实现了PropertyEditorRegistrar接口,然后重写了registerCustomEditors方法,在这个方法中他添加了很多编辑器:

@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver);
doRegisterEditor(registry, Resource.class, baseEditor);
doRegisterEditor(registry, ContextResource.class, baseEditor);
doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor));
doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor));
doRegisterEditor(registry, File.class, new FileEditor(baseEditor));
doRegisterEditor(registry, Path.class, new PathEditor(baseEditor));
doRegisterEditor(registry, Reader.class, new ReaderEditor(baseEditor));
doRegisterEditor(registry, URL.class, new URLEditor(baseEditor)); ClassLoader classLoader = this.resourceLoader.getClassLoader();
doRegisterEditor(registry, URI.class, new URIEditor(classLoader));
doRegisterEditor(registry, Class.class, new ClassEditor(classLoader));
doRegisterEditor(registry, Class[].class, new ClassArrayEditor(classLoader)); if (this.resourceLoader instanceof ResourcePatternResolver) {
doRegisterEditor(registry, Resource[].class,
new ResourceArrayPropertyEditor((ResourcePatternResolver) this.resourceLoader, this.propertyResolver));
}
}

继续点doRegisterEditor方法发现最终是将这些编辑器加入到了PropertyEditorRegistry接口的默认实现类PropertyEditorRegistrySupport类的customEditors属性中,而且你还在这个类中发现,有很多的编辑器是默认加载进去的:

private void createDefaultEditors() {
this.defaultEditors = new HashMap<>(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(Path.class, new PathEditor());
this.defaultEditors.put(Pattern.class, new PatternEditor());
this.defaultEditors.put(Properties.class, new PropertiesEditor());
this.defaultEditors.put(Reader.class, new ReaderEditor());
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());
this.defaultEditors.put(ZoneId.class, 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()); // The JDK does not contain a default editor for char!
this.defaultEditors.put(char.class, new CharacterEditor(false));
this.defaultEditors.put(Character.class, new CharacterEditor(true)); // Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.
this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true)); // The JDK does not contain default editors for number wrapper types!
// Override JDK primitive number editors with our own CustomNumberEditor.
this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true)); // Only register config value editors if explicitly requested.
if (this.configValueEditorsActive) {
StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
this.defaultEditors.put(String[].class, sae);
this.defaultEditors.put(short[].class, sae);
this.defaultEditors.put(int[].class, sae);
this.defaultEditors.put(long[].class, sae);
}
}

所以为什么我们在定义Bean属性的时候这些默认的属性会自动帮你转换出来,就是这个原因。那么注册器的编写我们也可以直接实现PropertyEditorRegistrar这个接口,然后重写registerCustomEditors方法把自定义的编辑器加入即可。

那编辑器怎么实现呢?

编辑器的话自然也就很简单了,随便点击一个编辑器看下他是怎么实现的,你就可以实现出来了,最终发现这些编辑器都是继承了PropertyEditorSupport这个类,而PropertyEditorSupport这个类实现了PropertyEditor这个接口,那这么方法实现哪个呢?

不着急看源码:

	private Object doConvertTextValue(@Nullable Object oldValue, String newTextValue, PropertyEditor editor) {
try {
editor.setValue(oldValue);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex);
}
// Swallow and proceed.
}
// 调动转换方法,这里就会调用到自定义的属性编辑器中,执行自定义的逻辑转换
editor.setAsText(newTextValue);
return editor.getValue();
}

源码明确写了使用编辑器调用setAsText方法进行新值的转换,然后再去获取getValue得到值,那么说明只需要重写setAsText方法并且将转换的值调用setValue方法即可。

所以我们直接继承PropertyEditorSupport类,然后重写setAsText方法即可实现属性值的解析和转换。

接下来就是分析AbstractApplicationContext#refresh方法的第四个方法和第五个方法,第四个方法postProcessBeanFactory是一个空方法,留给子类实现,第五个方法invokeBeanFactoryPostProcessors 是执行BeanFactoryPostProcessor,这篇就到这里,下一篇文章继续。

Spring 源码(5)BeanFactory使用的准备及自定义属性值解析器的更多相关文章

  1. Spring源码分析——BeanFactory体系之抽象类、类分析(二)

    上一篇分析了BeanFactory体系的2个类,SimpleAliasRegistry和DefaultSingletonBeanRegistry——Spring源码分析——BeanFactory体系之 ...

  2. Spring源码分析——BeanFactory体系之抽象类、类分析(一)

    上一篇介绍了BeanFactory体系的所有接口——Spring源码分析——BeanFactory体系之接口详细分析,本篇就接着介绍BeanFactory体系的抽象类和接口. 一.BeanFactor ...

  3. Spring源码阅读-BeanFactory体系结构分析

    BeanFactory是Spring中非常重要的一个类,搞懂了它,你就知道了bean的初始化和摧毁过程,对于深入理解IOC有很大的帮助. BeanFactory体系结构 首先看一下使用IDEA生成的继 ...

  4. Spring源码分析——BeanFactory体系之接口详细分析

    Spring的BeanFactory的继承体系堪称经典.这是众所周知的!作为Java程序员,不能错过! 前面的博文分析了Spring的Resource资源类Resouce.今天开始分析Spring的I ...

  5. Spring源码学习笔记之基于ClassPathXmlApplicationContext进行bean标签解析

    bean 标签在spring的配置文件中, 是非常重要的一个标签, 即便现在boot项目比较流行, 但是还是有必要理解bean标签的解析流程,有助于我们进行 基于注解配置, 也知道各个标签的作用,以及 ...

  6. Spring源码学习之BeanFactory体系结构

    一.BeanFactory BeanFactory是Spring IOC容器的鼻祖,是IOC容器的基础接口,所有的容器都是从它这里继承实现而来.可见其地位.BeanFactory提供了最基本的IOC容 ...

  7. Spring源码学习

    Spring源码学习--ClassPathXmlApplicationContext(一) spring源码学习--FileSystemXmlApplicationContext(二) spring源 ...

  8. spring源码-aop源码-5.1

    一.aop的源码部分还是有点复杂的,但是为了更好的理解,我这里会省去很多不必要的逻辑实现过程.主要方向还是更好的理解整体代码的实现过程. 二.说明重点:aop的过程主要过程有两点:第一点,发现正确和适 ...

  9. Spring源码系列 — Bean生命周期

    前言 上篇文章中介绍了Spring容器的扩展点,这个是在Bean的创建过程之前执行的逻辑.承接扩展点之后,就是Spring容器的另一个核心:Bean的生命周期过程.这个生命周期过程大致经历了一下的几个 ...

随机推荐

  1. hutool包里的ObjectUtil.isNull和ObjectUtil.isEmpty的区别

    大家都用过hutool包把,包路径为:cn.hutool.core.util,最近再使用的过程中一直没高明白ObjectUtil.isEmpty和ObjectUtil.isNull两者到底有那些区别, ...

  2. 关于Gradle 6.x及以上版本发布到仓库有很多CheckSum文件,想去除?

    写在前边 今天写的这个博客和平时的博客有点区别,大多数都是告诉你解决问题的,而本文不完全是. 经常使用Gradle的都知道 Gradle有两个发布制品到Maven仓库的插件:maven 与 maven ...

  3. 『现学现忘』Docker基础 — 35、实战:自定义CentOS镜像

    目录 1.前提说明 2.编写Dockerfile文件 3.构建镜像 4.运行镜像 5.列出镜像的变更历史 1)目标:自定义镜像wokong_centos. 2)所用到的保留字指令: FROM:基础镜像 ...

  4. 分库分表之后分布式如何保证ID全局唯一性

    分库分表之后分布式如何保证ID全局唯一性 韩师学子--小倪 2018-07-21 23:35:38 8139 收藏 3分类专栏: Mysql版权                         分库分 ...

  5. 什么是B+树??

    上一篇中,我们了解了B树,辣么..B+树又是什么呢?? 一:定义:B+树是基于B树的,是B树的变形,也是一种多路搜索树.查询性能更加出色. 1.每个父节点元素出现在子节点中,是子节点的最大或最小元素. ...

  6. vue3.0的更新和defineProperty优化?

    放弃 Object.defineProperty ,使用更快的原生 Proxy (访问对象拦截器, 也成代理器) 提速, 降低内存使用, Tree-shaking更友好 支持IE11等 使用Types ...

  7. volatile 变量和 atomic 变量有什么不同?

    Volatile 变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不 能保证原子性.例如用 volatile 修饰 count 变量那么 count++ 操作就不是原子 性的. 而 A ...

  8. Spring Boot 自动配置原理是什么?

    注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自动配置的核心,首先它得是一个配置文件,其次根据类路径下是否有这个 ...

  9. 面试问题之C++语言:类模板声明与定义为何不能分开

    C++中每个对象所占用的空间大小,是在编译的时候就确定的,在模板类没有真正的被使用之前,编译器是无法知道,模板类中使用模板类型的对象的所占用的空间的大小的.只有模板被真正使用的时候,编译器才知道,模板 ...

  10. 区分 BeanFactory 和 ApplicationContext?

    BeanFactory ApplicationContext 它使用懒加载 它使用即时加载 它使用语法显式提供资源对象 它自己创建和管理资源对象 不支持国际化 支持国际化 不支持基于依赖的注解 支持基 ...