五、Spring高级-IOC的深入剖析

1、Spring中的BeanFactory

1.1、BeanFactory类视图

1.2、工厂详解

1.2.1、BeanFactory
  1. BeanFactory 中定义的各种方法如上面方法注释,整个设计还是比较简洁、直观的,其中将近一半是获取 bean 对象的各种方法,另外就是对 bean 属性的获取和判定,该接口仅仅是定义了 IoC 容器的最基本基本形式,具体实现都交由子类来实现。

1.2.2、HierarchicalBeanFactory
  1. HierarchicalBeanFactory 译为中文是“分层的”,它相对于 BeanFactory 增加了对父 BeanFactory 的获取,子容器可以通过接口方法访问父容器,让容器的设计具备了层次性。这种层次性增强了容器的扩展性和灵活性,我们可以通过编程的方式为一个已有的容器添加一个或多个子容器,从而实现一些特殊功能。层次容器有一个特点就是子容器对于父容器来说是透明的,而子容器则能感知到父容器的存在。典型的应用场景就是 Spring MVC,控制层的 bean 位于子容器中,并将业务层和持久层的 bean 所在的容器设置为父容器,这样的设计可以让控制层的 bean 访问业务层和持久层的 bean,反之则不行,从而在容器层面对三层软件结构设计提供支持。

1.2.3、ListableBeanFactory

ListableBeanFactory 引入了获取容器中 bean 的配置信息的若干方法,比如获取容器中 bean 的个数,获取容器中所有 bean 的名称列表,按照目标类型获取 bean 名称,以及检查容器中是否包含指定名称的 bean 等等。Listable 中文译为“可列举的”,对于容器而言,bean 的定义和属性是可以列举的对象。

1.2.4、AutowireCapableBeanFactory
  1. AutowireCapableBeanFactory 提供了创建 bean、自动注入,初始化以及应用 bean 的后置处理器等功能。自动注入让配置变得更加简单,也让注解配置成为可能,Spring 提供了四种自动注入类型:<br /> byName:<br /> 根据名称自动装配。假设 bean A 有一个名为 b 的属性,如果容器中刚好存在一个 bean 的名称为 b,则将该 bean 装配给 bean A b 属性。<br /> byType:<br /> 根据类型自动匹配。假设 bean A 有一个类型为 B 的属性,如果容器中刚好有一个 B 类型的 bean,则使用该 bean 装配 A 的对应属性。<br /> constructor:<br /> 仅针对构造方法注入而言,类似于 byType。如果 bean A 有一个构造方法,构造方法包含一个 B 类型的入参,如果容器中有一个 B 类型的 bean,则使用该 bean 作为入参,如果找不到,则抛出异常。<br /> autodetect:<br /> 根据 bean 的自省机制决定采用 byType 还是 constructor 进行自动装配。如果 bean 提供了默认的构造函数,则采用 byType,否则采用 constructor。<br /> 总结:<br /> <beans/> 元素标签中的 default-autowire 属性可以配置全局自动匹配,default-autowire 默认值为 no,表示不启用自动装配。在实际开发中,XML 配置方式很少启用自动装配功能,而基于注解的配置方式默认采用 byType 自动装配策略。

1.2.5、ConfigurableBeanFactory

ConfigurableBeanFactory 提供配置 Factory 的各种方法,增强了容器的可定制性,定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法。

1.2.6、DefaultListableBeanFactory

DefaultListableBeanFactory 是一个非常重要的类,它包含了 IoC 容器所应该具备的重要功能,是容器完整功能的一个基本实现,XmlBeanFactory 是一个典型的由该类派生出来的 Factory,并且只是增加了加载 XML 配置资源的逻辑,而容器相关的特性则全部由 DefaultListableBeanFactory 来实现。

1.2.7、ApplicationContext

ApplicationContext 是 Spring 为开发者提供的高级容器形式,也是我们初始化 Spring 容器的常用方式,除了简单容器所具备的功能外,ApplicationContext 还提供了许多额外功能来降低开发人员的开发量,提升框架的使用效率。这些额外的功能主要包括:
国际化支持:ApplicationContext 实现了 org.springframework.context.MessageSource 接口,该接口为容器提供国际化消息访问功能,支持具备多语言版本需求的应用开发,并提供了多种实现来简化国际化资源文件的装载和获取。
发布应用上下文事件:ApplicationContext 实现了 org.springframework.context.ApplicationEventPublisher 接口,该接口让容器拥有发布应用上下文事件的功能,包括容器启动、关闭事件等,如果一个 bean 需要接收容器事件,则只需要实现 ApplicationListener 接口即可,Spring 会自动扫描对应的监听器配置,并注册成为主题的观察者。
丰富的资源获取的方式:ApplicationContext 实现了 org.springframework.core.io.support.ResourcePatternResolver 接口,ResourcePatternResolver 的实现类 PathMatchingResourcePatternResolver 让我们可以采用 Ant 风格的资源路径去加载配置文件。

1.2.8、ConfigurableApplicationContext

ConfigurableApplicationContext 中主要增加了 refresh 和 close 两个方法,从而为应用上下文提供了启动、刷新和关闭的能力。其中 refresh 方法是高级容器的核心方法,方法中概括了高级容器初始化的主要流程(包含简单的容器的全部功能,以及高级容器特有的扩展功能)

1.2.9、WebApplicationContext

WebApplicationContext 是为 WEB 应用定制的上下文,可以基于 WEB 容器来实现配置文件的加载,以及初始化工作。对于非 WEB 应用而言,bean 只有 singleton 和 prototype 两种作用域,而在 WebApplicationContext 中则新增了 request、session、globalSession,以及 application 四种作用域。
WebApplicationContext 将整个应用上下文对象以属性的形式放置到 ServletContext 中,所以在 WEB 应用中,我们可以通过 WebApplicationContextUtils 的 getWebApplicationContext(ServletContext sc) 方法,从 ServletContext 中获取到 ApplicationContext 实例。为了支持这一特性,WebApplicationContext 定义了一个常量:

ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + “.ROOT”

并在初始化应用上下文时以该常量为 key,将 WebApplicationContext 实例存放到 ServletContext 的属性列表中,当我们在调用 WebApplicationContextUtils 的 getWebApplicationContext(ServletContext sc) 方法时,本质上是在调用 ServletContext 的 getAttribute(String name) 方法,只不过 Spring 会对获取的结果做一些校验。

1.2.10、高级容器的一些具体实现类型

AnnotationConfigApplicationContext:
是基于注解驱动开发的高级容器类,该类中提供了AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner两个成员,AnnotatedBeanDefinitionReader用于读取注解创建Bean的定义信息,ClassPathBeanDefinitionScanner负责扫描指定包获取Bean的定义信息。

ClasspathXmlApplicationContext:
是基于xml配置的高级容器类,它用于加载类路径下配置文件。

FileSystemXmlApplicationContext:
是基于xml配置的高级容器类,它用于加载文件系统中的配置文件。

AnnotationConfigWebApplicationContext:
是注解驱动开发web应用的高级容器类。

2、Spring中的BeanDefinition

2.1、BeanDefinition类视图

2.2.1、类视图

2.2.2、说明

现实中的容器都是用来装物品的,Spring 的容器也不例外,这里的物品就是 bean。我通常对于 bean 的印象是一个个躺在配置文件中的 标签,或者是被注解的类,但是这些都是 bean 的静态表示,是还没有放入容器的物料,最终(加载完配置,且在 getBean 之前)加载到容器中的是一个个 BeanDefinition 实例。BeanDefinition 的继承关系如下图,RootBeanDefinition、ChildBeanDefinition,以及 GenericBeanDefinition 是三个主要的实现。有时候我们需要在配置时,通过 parent 属性指定 bean 的父子关系,这个时候父 bean 则用 RootBeanDefinition 表示,而子 bean 则用 ChildBeanDefinition 表示。GenericBeanDefinition 自 2.5 版本引入,是对于一般的 bean 定义的一站式服务中心。

2.2、Bean的定义信息详解

2.2.1、源码分析

/*
在上一小节我们介绍了RootBeanDefinition,ChildBeanDefinition,GenericBeanDefinition三个类
他们都是由AbstractBeanDefinition派生而来,该抽象类中包含了bean的所有配置项和一些支持程序运
行的属性。以下是类中属性的说明。
*/
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable {
// 常量定义略

  1. /** bean 对应的类实例 */<br /> private volatile Object beanClass;<br /> /** bean的作用域,对应scope属性 */<br /> private String scope = SCOPE_DEFAULT;<br /> /** 是否是抽象类,对应abstract属性 */<br /> private boolean abstractFlag = false;<br /> /** 是否延迟加载,对应lazy-init属性 */<br /> private boolean lazyInit = false;<br /> /** 自动装配模式,对应autowire属性 */<br /> private int autowireMode = AUTOWIRE_NO;<br /> /** 依赖检查,对应dependency-check属性 */<br /> private int dependencyCheck = DEPENDENCY_CHECK_NONE;<br /> /** 对应depends-on,表示一个bean实例化前置依赖另一个bean */<br /> private String[] dependsOn;<br /> /** 对应autowire-candidate属性,设置为false时表示取消当前bean作为自动装配候选者的资格 */<br /> private boolean autowireCandidate = true;<br /> /** 对应primary属性,当自动装配存在多个候选者时,将其作为首选 */<br /> private boolean primary = false;<br /> /** 对应qualifier属性 */<br /> private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<String, AutowireCandidateQualifier>(0);<br /> /** 非配置项:表示允许访问非公开的构造器和方法,由程序设置 */<br /> private boolean nonPublicAccessAllowed = true;<br /> /**<br /> * 非配置项:表示是否允许以宽松的模式解析构造函数,由程序设置<br /> *<br /> * 例如:如果设置为true,则在下列情况时不会抛出异常(示例来源于《Spring源码深度解析》)<br /> * interface ITest{}<br /> * class ITestImpl implements ITest {}<br /> * class Main {<br /> * Main(ITest i){}<br /> * Main(ITestImpl i){}<br /> * }<br /> */<br /> private boolean lenientConstructorResolution = true;<br /> /** 对应factory-bean属性 */<br /> private String factoryBeanName;<br /> /** 对应factory-method属性 */<br /> private String factoryMethodName;<br /> /** 记录构造函数注入属性,对应<construct-arg/>标签 */<br /> private ConstructorArgumentValues constructorArgumentValues;<br /> /** 记录<property/>属性集合 */<br /> private MutablePropertyValues propertyValues;<br /> /** 记录<lookup-method/>和<replaced-method/>标签配置 */<br /> private MethodOverrides methodOverrides = new MethodOverrides();<br /> /** 对应init-method属性 */<br /> private String initMethodName;<br /> /** 对应destroy-method属性 */<br /> private String destroyMethodName;<br /> /** 非配置项:是否执行init-method,由程序设置 */<br /> private boolean enforceInitMethod = true;<br /> /** 非配置项:是否执行destroy-method,由程序设置 */<br /> private boolean enforceDestroyMethod = true;<br /> /** 非配置项:表示是否是用户定义,而不是程序定义的,创建AOP时为true,由程序设置 */<br /> private boolean synthetic = false;<br /> /**<br /> * 非配置项:定义bean的应用场景,由程序设置,角色如下:<br /> * ROLE_APPLICATION:用户<br /> * ROLE_INFRASTRUCTURE:完全内部使用<br /> * ROLE_SUPPORT:某些复杂配置的一部分<br /> */<br /> private int role = BeanDefinition.ROLE_APPLICATION;<br /> /** bean的描述信息,对应description标签 */<br /> private String description;<br /> /** bean定义的资源 */<br /> private Resource resource;<br /> <br /> // 方法定义略<br />}

2.2.2、总结

BeanDefinition 是容器对于bean配置的内部表示,Spring 将各个 bean 的 BeanDefinition 实例注册记录在 BeanDefinitionRegistry 中,该接口定义了对 BeanDefinition 的各种增删查操作,类似于内存数据库,其实现类 SimpleBeanDefinitionRegistry 主要以 Map 作为存储标的。

3、注解驱动执行过程分析

3.1、使用配置类字节码的构造函数

3.1.1、构造函数源码

/
Create a new AnnotationConfigApplicationContext that needs to be populated
through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
*/
public AnnotationConfigApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
/

Create a new AnnotationConfigApplicationContext, deriving bean definitions
from the given annotated classes and automatically refreshing the context.
@param annotatedClasses one or more annotated classes,
e.g. {@link Configuration @Configuration} classes
*/
public AnnotationConfigApplicationContext(Class<?>… annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}

3.1.2、register方法说明
  1. 它是根据传入的配置类字节码解析Bean对象中注解的(包括类上的和类中方法和字段上的注解。如果类没有被注解,那么类中方法和字段上的注解不会被扫描)。使用的是AnnotatedGenericBeanDefinition,里面包含了BeanDefinitionScope两部分信息,其中BeanDefinition是传入注解类的信息,即SpringConfiguration;scope是指定bean的作用范围,默认情况下为单例。<br /> 同时,借助AnnotationConfigUtils类中processCommonDefinitionAnnotations方法判断是否使用了PrimaryLazyDependsOn等注解来决定Bean的加载时机。<br /> ConfigurationClassBeanDefinitionReader类中的registerBeanDefinitionForImportedConfigurationClass方法会把导入的JdbcConfig类注册到容器中。而loadBeanDefinitionsForBeanMethod方法会解析Bean注解,把被Bean注解修饰的方法返回值存入容器。

3.1.3、执行过程分析图

3.2、使用包扫描的构造函数

3.2.1、构造函数源码

/
Create a new AnnotationConfigApplicationContext that needs to be populated
through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
*/
public AnnotationConfigApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
/

Create a new AnnotationConfigApplicationContext, scanning for bean definitions
in the given packages and automatically refreshing the context.
@param basePackages the packages to check for annotated classes
/
public AnnotationConfigApplicationContext(String… basePackages) {
this();
scan(basePackages);
refresh();
}

3.2.2、scan方法说明
  1. 它是根据传入的类路径下(classpath*)的包解析Bean对象中注解的(包括类上以及类成员的),使用的是ClassPathBeanDefinitionScanner类中的doScan方法,该方法最终将得到的BeanDefinitionHolder信息存储到LinkedHashSet中,为后面初始化容器做准备。<br /> doScan中的findCandidateComponents方法调用ClassPathScanningCandidateComponentProvider类中的scanCandidateComponents方法,而此方法又去执行了PathMatchingResourcePatternResolver类中的doFindAllClassPathResources方法,找到指定扫描包的URL(是URL,不是路径。因为是带有file协议的),然后根据磁盘路径读取当前目录及其子目录下的所有类。接下来执行AnnotationConfigUtils类中的processCommonDefinitionAnnotations方法,剩余就和本章节第一小节后面的过程一样了。

3.2.3、执行过程分析图

3.3、注册注解类型过滤器

3.3.1、ClassPathScanningCandidateComponentProvider的registerDefaultFilters方法说明

/*
Register the default filter for {@link Component @Component}.

This will implicitly register all annotations that have the
{@link Component @Component} meta-annotation including the
{@link Repository @Repository}, {@link Service @Service}, and
{@link Controller @Controller} stereotype annotations.

Also supports Java EE 6’s {@link javax.annotation.ManagedBean} and
JSR-330’s {@link javax.inject.Named} annotations, if available.

/
@SuppressWarnings(“unchecked”)
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName(“javax.annotation.ManagedBean”, cl)), false));
logger.trace(“JSR-250 ‘javax.annotation.ManagedBean’ found and supported for component scanning”);
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName(“javax.inject.Named”, cl)), false));
logger.trace(“JSR-330 ‘javax.inject.Named’ annotation found and supported for component scanning”);
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}

3.4、准备和初始化容器

3.4.1、AbstractApplicationContext的refresh方法说明

@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//Prepare this context for refreshing.
//1.准备容器,设置一些初始化信息,例如启动时间。验证必须要的属性等等。
prepareRefresh();

  1. // Tell the subclass to refresh the internal bean factory.<br /> //2.告诉子类刷新内部bean工厂。实际就是重新创建一个Bean工厂<br /> ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  2. // Prepare the bean factory for use in this context.<br /> //3.准备使用创建的这个BeanFactory,添加或者注册到当前Bean工厂一些必要对象。<br /> prepareBeanFactory(beanFactory);
  3. try {<br /> // Allows post-processing of the bean factory in context subclasses.<br /> //4.允许子容器对BeanFactory进行后处理。例如,在web环境中bean的作用范围等等。<br /> postProcessBeanFactory(beanFactory);
  4. // Invoke factory processors registered as beans in the context.<br /> //5.在Singleton的Bean对象初始化前,对Bean工厂进行一些处理<br /> invokeBeanFactoryPostProcessors(beanFactory);
  5. // Register bean processors that intercept bean creation.<br /> //6.注册拦截bean创建的处理器<br /> registerBeanPostProcessors(beanFactory);
  6. // Initialize message source for this context.<br /> //7.初始化消息资源接口的实现类。主要用于处理国际化(i18n)<br /> initMessageSource();
  7. // Initialize event multicaster for this context.<br /> //8、为容器注册一个事件组播器<br /> initApplicationEventMulticaster();
  8. // Initialize other special beans in specific context subclasses.<br /> //9.在AbstractApplicationContext的子类中初始化其他特殊的bean<br /> onRefresh();
  9. // Check for listener beans and register them.<br /> //10.注册应用的监听器。就是注册实现了ApplicationListener接口的监听器bean<br /> registerListeners();
  10. // Instantiate all remaining (non-lazy-init) singletons.<br /> //11.实例化所有剩余的(非lazy init)单例。(就是没有被@Lazy修饰的单例Bean)<br /> finishBeanFactoryInitialization(beanFactory);//十一、
  11. // Last step: publish corresponding event.<br /> //12.完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事件(ContextRefreshedEvent)。<br /> finishRefresh();<br /> }
  12. catch (BeansException ex) {<br /> if (logger.isWarnEnabled()) {<br /> logger.warn("Exception encountered during context initialization - " +<br /> "cancelling refresh attempt: " + ex);<br /> }
  13. // Destroy already created singletons to avoid dangling resources.<br /> destroyBeans();//如果刷新失败那么就会将已经创建好的单例Bean销毁掉
  14. // Reset 'active' flag.<br /> cancelRefresh(ex);//重置context的活动状态
  15. // Propagate exception to caller.<br /> throw ex;//抛出异常<br /> }
  16. finally {<br /> // Reset common introspection caches in Spring's core, since we<br /> // might not ever need metadata for singleton beans anymore...<br /> resetCommonCaches();//重置的Spring内核的缓存。因为可能不再需要metadata给单例Bean了。<br /> }<br /> }<br />}

3.5、实例化和获取Bean对象

3.5.1、AbstractBeanFactory的doGetBean方法说明

protected T doGetBean(
final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
/
获取name对应的真正beanName

因为传入的参数可以是alias,也可能是FactoryBean的name,所以需要进行解析,包含以下内容:
1. 如果是FactoryBean,则去掉修饰符“&”
2. 沿着引用链获取alias对应的最终name
/
final String beanName = this.transformedBeanName(name);

Object bean;

/

检查缓存或者实例工厂中是否有对应的单例

在创建单例bean的时候会存在依赖注入的情况,而在创建依赖的时候为了避免循环依赖
Spring创建bean的原则是不等bean创建完成就会将创建bean的ObjectFactory提前曝光(将对应的ObjectFactory加入到缓存)
一旦下一个bean创建需要依赖上一个bean,则直接使用ObjectFactory对象
/
Object sharedInstance = this.getSingleton(beanName); // 获取单例
if (sharedInstance != null && args == null) {
// 实例已经存在
if (logger.isDebugEnabled()) {
if (this.isSingletonCurrentlyInCreation(beanName)) {
logger.debug(“Returning eagerly cached instance of singleton bean ‘“ + beanName + “‘ that is not fully initialized yet - a consequence of a circular reference”);
} else {
logger.debug(“Returning cached instance of singleton bean ‘“ + beanName + “‘“);
}
}
// 返回对应的实例
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
// 单例实例不存在
if (this.isPrototypeCurrentlyInCreation(beanName)) {
/
只有在单例模式下才会尝试解决循环依赖问题
对于原型模式,如果存在循环依赖,也就是满足this.isPrototypeCurrentlyInCreation(beanName),抛出异常
/
throw new BeanCurrentlyInCreationException(beanName);
}

// 获取parentBeanFactory实例
BeanFactory parentBeanFactory = this.getParentBeanFactory();
// 如果在beanDefinitionMap中(即所有已经加载的类中)不包含目标bean,则尝试从parentBeanFactory中获取
if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
String nameToLookup = this.originalBeanName(name); // 获取name对应的真正beanName,如果是factoryBean,则加上“&”前缀
if (args != null) {
// 递归到BeanFactory中寻找
return (T) parentBeanFactory.getBean(nameToLookup, args);
} else {
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}

// 如果不仅仅是做类型检查,标记bean的状态已经创建,即将beanName加入alreadyCreated集合中
if (!typeCheckOnly) {
this.markBeanAsCreated(beanName);
}

try {
/
将存储XML配置的GenericBeanDefinition实例转换成RootBeanDefinition实例,方便后续处理
如果存在父bean,则同时合并父bean的相关属性
/
final RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
// 检查bean是否是抽象的,如果是则抛出异常
this.checkMergedBeanDefinition(mbd, beanName, args);

// 加载当前bean依赖的bean
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
// 存在依赖,递归实例化依赖的bean
for (String dep : dependsOn) {
if (this.isDependent(beanName, dep)) {
// 检查dep是否依赖beanName,从而导致循环依赖
throw new BeanCreationException(mbd.getResourceDescription(), beanName, “Circular depends-on relationship between ‘“ + beanName + “‘ and ‘“ + dep + “‘“);
}
// 缓存依赖调用
this.registerDependentBean(dep, beanName);
this.getBean(dep);
}
}

// 完成加载依赖的bean后,实例化mbd自身
if (mbd.isSingleton()) {
// scope == singleton
sharedInstance = this.getSingleton(beanName, new ObjectFactory() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
// 清理工作,从单例缓存中移除
destroySingleton(beanName);
throw ex;
}
}
});
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
// scope == prototype
Object prototypeInstance;
try {
// 设置正在创建的状态
this.beforePrototypeCreation(beanName);
// 创建bean
prototypeInstance = this.createBean(beanName, mbd, args);
} finally {
this.afterPrototypeCreation(beanName);
}
// 返回对应的实例
bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
// 其它scope
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException(“No Scope registered for scope name ‘“ + scopeName + “‘“);
}
try {
Object scopedInstance = scope.get(beanName, new ObjectFactory() {
@Override
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
}
});
// 返回对应的实例
bean = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
} catch (IllegalStateException ex) {
throw new BeanCreationException(beanName, “Scope ‘“ + scopeName + “‘ is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton”, ex);
}
}
} catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}

// 检查需要的类型是否符合bean的实际类型,对应getBean时指定的requireType
if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
try {
// 执行类型转换,转换成期望的类型
return this.getTypeConverter().convertIfNecessary(bean, requiredType);
} catch (TypeMismatchException ex) {
if (logger.isDebugEnabled()) {
logger.debug(“Failed to convert bean ‘“ + name + “‘ to required type ‘“ + ClassUtils.getQualifiedName(requiredType) + “‘“, ex);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}
return (T) bean;
}

4、BeanNameGenerator及其实现类

4.1、BeanNameGenerator

BeanNameGenerator接口位于 org.springframework.beans.factory.support 包下面:
/*
Strategy interface for generating bean names for bean definitions.

@author Juergen Hoeller
@since 2.0.3
/
public interface BeanNameGenerator {

  1. /**<br /> * Generate a bean name for the given bean definition.<br /> * @param definition the bean definition to generate a name for<br /> * @param registry the bean definition registry that the given definition<br /> * is supposed to be registered with<br /> * @return the generated bean name<br /> */<br /> String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);

}
它有两个实现类:分别是:
其中DefaultBeanNameGenerator是给资源文件加载bean时使用(BeanDefinitionReader中使用);AnnotationBeanNameGenerator是为了处理注解生成bean name的情况。

4.2、AnnotationBeanNameGenerator

/*
此方法是接口中抽象方法的实现。
该方法分为两个部分:
第一个部分:当指定了bean的名称,则直接使用指定的名称。
第二个部分:当没有指定bean的名称时,则使用当前类的短类名作为bean的唯一标识。
/
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
//判断bean的定义信息是否为基于注解的
if (definition instanceof AnnotatedBeanDefinition) {
//解析注解中的属性,看看有没有指定的bean的唯一标识
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
//返回注解的属性指定的bean的唯一标识
return beanName;
}
}
// 调用方法,使用注解bean名称的命名规则,生成bean的唯一标识
return buildDefaultBeanName(definition, registry);
}

/*
Derive a default bean name from the given bean definition.

The default implementation delegates to {@link #buildDefaultBeanName(BeanDefinition)}.
@param definition the bean definition to build a bean name for
@param registry the registry that the given bean definition is being registered with
@return the default bean name (never {@code null})
*/
protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return buildDefaultBeanName(definition);
}

/
Derive a default bean name from the given bean definition.

The default implementation simply builds a decapitalized version
of the short class name: e.g. “mypackage.MyJdbcDao” -> “myJdbcDao”.

Note that inner classes will thus have names of the form
“outerClassName.InnerClassName”, which because of the period in the
name may be an issue if you are autowiring by name.
@param definition the bean definition to build a bean name for
@return the default bean name (never {@code null})
*/
protected String buildDefaultBeanName(BeanDefinition definition) {
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, “No bean class name set”);
String shortClassName = ClassUtils.getShortName(beanClassName);
return Introspector.decapitalize(shortClassName);
}
ClassUtils的代码节选:
/

Miscellaneous class utility methods.
Mainly for internal use within the framework.

@author Juergen Hoeller
@author Keith Donald
@author Rob Harrop
@author Sam Brannen
@since 1.1
@see TypeUtils
@see ReflectionUtils
*/
public abstract class ClassUtils {

  1. /** Suffix for array class names: {@code "[]"}. */<br /> public static final String ARRAY_SUFFIX = "[]";
  2. /** Prefix for internal array class names: {@code "["}. */<br /> private static final String INTERNAL_ARRAY_PREFIX = "[";
  3. /** Prefix for internal non-primitive array class names: {@code "[L"}. */<br /> private static final String NON_PRIMITIVE_ARRAY_PREFIX = "[L";
  4. /** The package separator character: {@code '.'}. */<br /> private static final char PACKAGE_SEPARATOR = '.';
  5. /** The path separator character: {@code '/'}. */<br /> private static final char PATH_SEPARATOR = '/';
  6. /** The inner class separator character: {@code '$'}. */<br /> private static final char INNER_CLASS_SEPARATOR = '$';
  7. /** The CGLIB class separator: {@code "$$"}. */<br /> public static final String CGLIB_CLASS_SEPARATOR = "$$";
  8. /** The ".class" file suffix. */<br /> public static final String CLASS_FILE_SUFFIX = ".class";<br /> <br /> /**<br /> * Get the class name without the qualified package name.<br /> * @param className the className to get the short name for<br /> * @return the class name of the class without the package name<br /> * @throws IllegalArgumentException if the className is empty<br /> */<br /> public static String getShortName(String className) {<br /> Assert.hasLength(className, "Class name must not be empty");<br /> int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);<br /> int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR);<br /> if (nameEndIndex == -1) {<br /> nameEndIndex = className.length();<br /> }<br /> String shortName = className.substring(lastDotIndex + 1, nameEndIndex);<br /> shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);<br /> return shortName;<br /> }<br /> <br /> //其余代码略<br />}

4.3、DefaultBeanNameGenerator

/*
Default implementation of the {@link BeanNameGenerator} interface, delegating to
{@link BeanDefinitionReaderUtils#generateBeanName(BeanDefinition, BeanDefinitionRegistry)}.

@author Juergen Hoeller
@since 2.0.3
*/
public class DefaultBeanNameGenerator implements BeanNameGenerator {

  1. @Override<br /> public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {<br /> return BeanDefinitionReaderUtils.generateBeanName(definition, registry);<br /> }<br />}<br />DefaultBeanNameGenerator类将具体的处理方式委托给了,BeanDefinitionReaderUtils#generateBeanName(BeanDefinition, BeanDefinitionRegistry)方法处理。 

以下是代码节选:
public abstract class BeanDefinitionReaderUtils {

  1. public static String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry registry)<br /> throws BeanDefinitionStoreException {<br /> //此方法除了bean的定义信息和定义注册之外,还有一个布尔类型的值,用于确定是内部bean还是顶层bean<br /> return generateBeanName(beanDefinition, registry, false);<br /> }
  2. /**<br /> * 生成bean的唯一标识(默认规则)<br /> */<br /> public static String generateBeanName(<br /> BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)<br /> throws BeanDefinitionStoreException {
  3. String generatedBeanName = definition.getBeanClassName();<br /> if (generatedBeanName == null) {<br /> if (definition.getParentName() != null) {<br /> generatedBeanName = definition.getParentName() + "$child";<br /> }<br /> else if (definition.getFactoryBeanName() != null) {<br /> generatedBeanName = definition.getFactoryBeanName() + "$created";<br /> }<br /> }<br /> if (!StringUtils.hasText(generatedBeanName)) {<br /> throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " +<br /> "'class' nor 'parent' nor 'factory-bean' - can't generate bean name");<br /> }
  4. String id = generatedBeanName;<br /> if (isInnerBean) {<br /> // Inner bean: generate identity hashcode suffix.<br /> id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);<br /> }<br /> else {<br /> // Top-level bean: use plain class name with unique suffix if necessary.<br /> return uniqueBeanName(generatedBeanName, registry);<br /> }<br /> return id;<br /> }
  5. //其他代码略<br />}

5、ScopedProxyMode枚举

/*
Enumerates the various scoped-proxy options.

For a more complete discussion of exactly what a scoped proxy is, see the
section of the Spring reference documentation entitled ‘Scoped beans as
dependencies
‘.

@author Mark Fisher
@since 2.5
@see ScopeMetadata
*/
public enum ScopedProxyMode {

  1. /**<br /> * Default typically equals {@link #NO}, unless a different default<br /> * has been configured at the component-scan instruction level.<br /> */<br /> DEFAULT,
  2. /**<br /> * Do not create a scoped proxy.<br /> * <p>This proxy-mode is not typically useful when used with a<br /> * non-singleton scoped instance, which should favor the use of the<br /> * {@link #INTERFACES} or {@link #TARGET_CLASS} proxy-modes instead if it<br /> * is to be used as a dependency.<br /> */<br /> NO,
  3. /**<br /> * Create a JDK dynamic proxy implementing <i>all</i> interfaces exposed by<br /> * the class of the target object.<br /> */<br /> INTERFACES,
  4. /**<br /> * Create a class-based proxy (uses CGLIB).<br /> */<br /> TARGET_CLASS;

}

6、自定义组件扫描过滤规则

6.1、FilterType枚举

public enum FilterType {

  1. /**<br /> * Filter candidates marked with a given annotation.<br /> * @see org.springframework.core.type.filter.AnnotationTypeFilter<br /> */<br /> ANNOTATION,
  2. /**<br /> * Filter candidates assignable to a given type.<br /> * @see org.springframework.core.type.filter.AssignableTypeFilter<br /> */<br /> ASSIGNABLE_TYPE,
  3. /**<br /> * Filter candidates matching a given AspectJ type pattern expression.<br /> * @see org.springframework.core.type.filter.AspectJTypeFilter<br /> */<br /> ASPECTJ,
  4. /**<br /> * Filter candidates matching a given regex pattern.<br /> * @see org.springframework.core.type.filter.RegexPatternTypeFilter<br /> */<br /> REGEX,
  5. /** Filter candidates using a given custom<br /> * {@link org.springframework.core.type.filter.TypeFilter} implementation.<br /> */<br /> CUSTOM

}

6.2、TypeFilter接口

@FunctionalInterface
public interface TypeFilter {

  1. /**<br /> * 此方法返回一个boolean类型的值。<br /> * 当返回true时,表示加入到spring的容器中。返回false时,不加入容器。<br /> * 参数metadataReader:表示读取到的当前正在扫描的类的信息<br /> * 参数metadataReaderFactory:表示可以获得到其他任何类的信息<br /> */<br /> boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)<br /> throws IOException;

}

6.3、使用Spring提供的过滤规则-AnnotationTypeFilter

/*
@author 黑马程序员
@Company http://www.itheima.com
当我们使用注解驱动开发JavaEE项目时,spring提供的容器分为RootApplicationContext和
ServletApplicationContext。此时我们不希望Root容器创建时把Controller加入到容器中,
就可以使用过滤规则排除@Controller注解配置的Bean对象。
*/
@Configuration
@ComponentScan(value = “com.itheima”,excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class))
public class SpringConfiguration {
}

6.4、自定义过滤规则

6.4.1、场景分析
  1. 在实际开发中,有很多下面这种业务场景:一个业务需求根据环境的不同可能会有很多种实现。针对不同的环境,要加载不同的实现。我们看下面这个案例:<br /> 我们现在是一个汽车销售集团,在成立之初,只是在北京销售汽车,我们的项目研发完成后只在北京部署上线。但随着公司的业务发展,现在全国各地均有销售大区,总部设在北京。各大区有独立的项目部署,但是每个大区的业绩计算和绩效提成的计算方式并不相同。<br /> 例如:<br /> 在华北区销售一台豪华级轿车绩效算5,提成销售额1%,销售豪华级SUV绩效算3,提成是0.5%。<br /> 在西南区销售一台豪华级轿车绩效算3,提成销售额0.5%,销售豪华级SUV绩效算5,提成是1.5%。<br /> 这时,我们如果针对不同大区对项目源码进行删减替换,会带来很多不必要的麻烦。而如果加入一些if/else的判断,显然过于简单粗暴。此时应该考虑采用桥接设计模式,把将涉及到区域性差异的模块功能单独抽取到代表区域功能的接口中。针对不同区域进行实现。并且在扫描组件注册到容器中时,采用哪个区域的具体实现,应该采用配置文件配置起来。而自定义TypeFilter就可以实现注册指定区域的组件到容器中。

6.4.2、代码实现

/*
区域的注解
@author 黑马程序员
@Company http://www.itheima.com
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface District {

  1. /**<br /> * 指定区域的名称<br /> * @return<br /> */<br /> String value() default "";<br />}

/*
销售分成的桥接接口
@author 黑马程序员
@Company http://www.itheima.com
*/
public interface DistrictPercentage {

  1. /**<br /> * 不同车型提成<br /> * @param carType<br /> */<br /> void salePercentage(String carType);<br />}

/*
绩效计算桥接接口
@author 黑马程序员
@Company http://www.itheima.com
*/
public interface DistrictPerformance {

  1. /**<br /> * 计算绩效<br /> * @param carType<br /> */<br /> void calcPerformance(String carType);<br />}

/*
华北区销售分成具体实现
@author 黑马程序员
@Company http://www.itheima.com
*/
@Component(“districtPercentage”)
@District(“north”)
public class NorthDistrictPercentage implements DistrictPercentage {

  1. @Override<br /> public void salePercentage(String carType) {<br /> if("SUV".equalsIgnoreCase(carType)) {<br /> System.out.println("华北区"+carType+"提成1%");<br /> }else if("car".equalsIgnoreCase(carType)){<br /> System.out.println("华北区"+carType+"提成0.5%");<br /> }<br /> }<br />}

/*
华北区销售绩效具体实现
@author 黑马程序员
@Company http://www.itheima.com
*/
@Component(“districtPerformance”)
@District(“north”)
public class NorthDistrictPerformance implements DistrictPerformance {

  1. @Override<br /> public void calcPerformance(String carType) {<br /> if("SUV".equalsIgnoreCase(carType)) {<br /> System.out.println("华北区"+carType+"绩效3");<br /> }else if("car".equalsIgnoreCase(carType)){<br /> System.out.println("华北区"+carType+"绩效5");<br /> }<br /> }<br />}

/*
西南区销售分成具体实现
@author 黑马程序员
@Company http://www.itheima.com
*/
@Component(“districtPercentage”)
@District(“southwest”)
public class SouthwestDistrictPercentage implements DistrictPercentage {

  1. @Override<br /> public void salePercentage(String carType) {<br /> if("SUV".equalsIgnoreCase(carType)) {<br /> System.out.println("西南区"+carType+"提成1.5%");<br /> }else if("car".equalsIgnoreCase(carType)){<br /> System.out.println("西南区"+carType+"提成0.5%");<br /> }<br /> }<br />}

/*
西南区绩效计算具体实现
@author 黑马程序员
@Company http://www.itheima.com
*/
@Component(“districtPerformance”)
@District(“southwest”)
public class SouthwestDistrictPerformance implements DistrictPerformance {

  1. @Override<br /> public void calcPerformance(String carType) {<br /> if("SUV".equalsIgnoreCase(carType)) {<br /> System.out.println("西南区"+carType+"绩效5");<br /> }else if("car".equalsIgnoreCase(carType)){<br /> System.out.println("西南区"+carType+"绩效3");<br /> }<br /> }<br />}

/
spring的配置类
用于替代xml配置
@author 黑马程序员
@Company http://www.itheima.com
*/
@Configuration
@PropertySource(value = “classpath:district.properties”)
@ComponentScan(value = “com.itheima”,
excludeFilters = @ComponentScan.Filter(type=FilterType.CUSTOM,classes = DistrictTypeFilter.class))
public class SpringConfiguration {
}
/

spring的自定义扫描规则
@author 黑马程序员
@Company http://www.itheima.com
/
public class DistrictTypeFilter extends AbstractTypeHierarchyTraversingFilter {

  1. //定义路径校验类对象<br /> private PathMatcher pathMatcher;
  2. //注意:使用@Value注解的方式是获取不到配置值的。<br /> //因为Spring的生命周期里,负责填充属性值的InstantiationAwareBeanPostProcessor 与TypeFilter的实例化过程压根搭不上边。<br />// @Value("${common.district.name}")<br /> private String districtName;
  3. /**<br /> * 默认构造函数<br /> */<br /> public DistrictTypeFilter() {<br /> //1.第一个参数:不考虑基类。2.第二个参数:不考虑接口上的信息<br /> super(false, false);
  4. //借助Spring默认的Resource通配符路径方式<br /> pathMatcher = new AntPathMatcher();
  5. //硬编码读取配置信息<br /> try {<br /> Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("district.properties");<br /> districtName = loadAllProperties.getProperty("common.district.name");<br /> } catch (IOException e) {<br /> // TODO Auto-generated catch block<br /> e.printStackTrace();<br /> }<br /> }
  6. //注意本类将注册为Exclude, 返回true代表拒绝<br /> @Override<br /> protected boolean matchClassName(String className) {<br /> try{<br /> if (!isPotentialPackageClass(className)) {<br /> return false;<br /> }
  7. // 判断当前区域是否和所配置的区域一致, 不一致则阻止载入Spring容器<br /> Class<?> clazz = ClassUtils.forName(className, DistrictTypeFilter.class.getClassLoader());<br /> District districtAnnotation = clazz.getAnnotation(District.class);<br /> if(null == districtAnnotation){<br /> return false;<br /> }<br /> final String districtValue = districtAnnotation.value();<br /> return (!districtName.equalsIgnoreCase(districtValue));<br /> }catch (Exception e){<br /> throw new RuntimeException(e);<br /> }<br /> }
  8. // 潜在的满足条件的类的类名, 指定package下<br /> private static final String PATTERN_STANDARD = ClassUtils<br /> .convertClassNameToResourcePath("com.itheima.service.impl.*");
  9. // 本类逻辑中可以处理的类 -- 指定package下的才会进行逻辑判断,<br /> private boolean isPotentialPackageClass(String className) {<br /> // 将类名转换为资源路径, 以进行匹配测试<br /> final String path = ClassUtils.convertClassNameToResourcePath(className);<br /> return pathMatcher.match(PATTERN_STANDARD, path);<br /> }

}
/*
@author 黑马程序员
@Company http://www.itheima.com
/
public class SpringAnnotationTypeFilterTest {

  1. public static void main(String[] args) {<br /> AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext("config");<br /> DistrictPerformance districtPerformance = ac.getBean("districtPerformance", DistrictPerformance.class);<br /> districtPerformance.calcPerformance("SUV");
  2. DistrictPercentage districtPercentage = ac.getBean("districtPercentage",DistrictPercentage.class);<br /> districtPercentage.salePercentage("car");
  3. }<br />}

7、@Import注解的高级分析

7.1、ImportSelector和ImportBeanDefinitionRegistrar介绍

特别说明:
我们在注入bean对象时,可选的方式有很多种。
例如:
我们自己写的类,可以使用@Component,@Service,@Repository,@Controller等等。
我们导入的第三方库中的类,可以使用@Bean(当需要做一些初始化操作时,比如DataSource),也可以使用@Import注解,直接指定要引入的类的字节码。
但是当我们的类很多时,在每个类上加注解会很繁琐,同时使用@Bean或者@Import写起来也很麻烦。此时我们就可以采用自定义ImportSelector或者ImportBeanDefinitionRegistrar来实现。顺便说一句,在SpringBoot中,@EnableXXX这样的注解,绝大多数都借助了ImportSelector或者ImportBeanDefinitionRegistrar。在我们的spring中,@EnableTransactionManagement就是借助了ImportSelector,而@EnableAspectJAutoporxy就是借助了ImportBeanDefinitionRegistrar。

共同点:
他们都是用于动态注册bean对象到容器中的。并且支持大批量的bean导入。
区别:
ImportSelector是一个接口,我们在使用时需要自己提供实现类。实现类中返回要注册的bean的全限定类名数组,然后执行ConfigurationClassParser类中中的processImports方法注册bean对象的。
ImportBeanDefinitionRegistrar也是一个接口,需要我们自己编写实现类,在实现类中手动注册bean到容器中。

注意事项:
实现了ImportSelector接口或者ImportBeanDefinitionRegistrar接口的类不会被解析成一个Bean注册到容器中。
同时,在注册到容器中时bean的唯一标识是全限定类名,而非短类名。

7.2、自定义ImportSelector

/*
@author 黑马程序员
@Company http://www.itheima.com
/
public interface UserService {
void saveUser();
}

/*
@author 黑马程序员
@Company http://www.itheima.com
/
public class UserServiceImpl implements UserService {

  1. @Override<br /> public void saveUser() {<br /> System.out.println("保存用户");<br /> }<br />}

/
@author 黑马程序员
@Company http://www.itheima.com
*/
@Configuration
@ComponentScan(“com.itheima”)
@Import(CustomeImportSelector.class)
public class SpringConfiguration {
}
/

customeimport.properties配置文件中的内容:
custome.importselector.expression= com.itheima.service.impl.
@author 黑马程序员
@Company http://www.itheima.com
/
public class CustomeImportSelector implements ImportSelector {

  1. private String expression;
  2. public CustomeImportSelector(){<br /> try {<br /> Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("customeimport.properties");<br /> expression = loadAllProperties.getProperty("custome.importselector.expression");<br /> } catch (IOException e) {<br /> // TODO Auto-generated catch block<br /> e.printStackTrace();<br /> }<br /> }
  3. /**<br /> * 生成要导入的bean全限定类名数组<br /> * @param importingClassMetadata<br /> * @return<br /> */<br /> @Override<br /> public String[] selectImports(AnnotationMetadata importingClassMetadata) {<br /> //1.定义扫描包的名称<br /> String[] basePackages = null;<br /> //2.判断有@Import注解的类上是否有@ComponentScan注解<br /> if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {<br /> //3.取出@ComponentScan注解的属性<br /> Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());<br /> //4.取出属性名称为basePackages属性的值<br /> basePackages = (String[]) annotationAttributes.get("basePackages");<br /> }<br /> //5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)<br /> if (basePackages == null || basePackages.length == 0) {<br /> String basePackage = null;<br /> try {<br /> //6.取出包含@Import注解类的包名<br /> basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();<br /> } catch (ClassNotFoundException e) {<br /> e.printStackTrace();<br /> }<br /> //7.存入数组中<br /> basePackages = new String[] {basePackage};<br /> }<br /> //8.创建类路径扫描器<br /> ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);<br /> //9.创建类型过滤器(此处使用切入点表达式类型过滤器)<br /> TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());<br /> //10.给扫描器加入类型过滤器<br /> scanner.addIncludeFilter(typeFilter);<br /> //11.创建存放全限定类名的集合<br /> Set<String> classes = new HashSet<>();<br /> //12.填充集合数据<br /> for (String basePackage : basePackages) {<br /> scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));<br /> }<br /> //13.按照规则返回<br /> return classes.toArray(new String[classes.size()]);<br /> }<br />}

/*
测试类
@author 黑马程序员
@Company http://www.itheima.com
*/
public class SpringCustomeImportSelectorTest {

  1. public static void main(String[] args) {<br /> AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext("config");<br /> String[] names = ac.getBeanDefinitionNames();<br /> for(String beanName : names){<br /> Object obj = ac.getBean(beanName);<br /> System.out.println(beanName+"============"+obj);<br /> }<br /> }<br />}

7.3、自定义ImportBeanDefinitionRegistrar

借助7.2小节的案例代码,只需要把配置改一下:
/*
@author 黑马程序员
@Company http://www.itheima.com
/
@Configuration
@ComponentScan(“com.itheima”)
@Import(CustomeImportDefinitionRegistrar.class)
public class SpringConfiguration {
}

/*
自定义bean导入注册器
@author 黑马程序员
@Company http://www.itheima.com
*/
public class CustomeImportDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

  1. private String expression;
  2. public CustomeImportDefinitionRegistrar(){<br /> try {<br /> Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("customeimport.properties");<br /> expression = loadAllProperties.getProperty("custome.importselector.expression");<br /> } catch (IOException e) {<br /> // TODO Auto-generated catch block<br /> e.printStackTrace();<br /> }<br /> }
  3. @Override<br /> public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {<br /> //1.定义扫描包的名称<br /> String[] basePackages = null;<br /> //2.判断有@Import注解的类上是否有@ComponentScan注解<br /> if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {<br /> //3.取出@ComponentScan注解的属性<br /> Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());<br /> //4.取出属性名称为basePackages属性的值<br /> basePackages = (String[]) annotationAttributes.get("basePackages");<br /> }<br /> //5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)<br /> if (basePackages == null || basePackages.length == 0) {<br /> String basePackage = null;<br /> try {<br /> //6.取出包含@Import注解类的包名<br /> basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();<br /> } catch (ClassNotFoundException e) {<br /> e.printStackTrace();<br /> }<br /> //7.存入数组中<br /> basePackages = new String[] {basePackage};<br /> }<br /> //8.创建类路径扫描器<br /> ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);<br /> //9.创建类型过滤器(此处使用切入点表达式类型过滤器)<br /> TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());<br /> //10.给扫描器加入类型过滤器<br /> scanner.addIncludeFilter(typeFilter);<br /> //11.扫描指定包<br /> scanner.scan(basePackages);<br /> }<br />}

7.4、原理分析

我们写的自定义导入器的解析写在了ConfigurationClassParser类中的processImports方法,以下是源码节选:
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection importCandidates, boolean checkForCircularImports) {

  1. if (importCandidates.isEmpty()) {<br /> return;<br /> }
  2. if (checkForCircularImports && isChainedImportOnStack(configClass)) {<br /> this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));<br /> }<br /> else {<br /> this.importStack.push(configClass);<br /> try {<br /> for (SourceClass candidate : importCandidates) {<br />            //对ImportSelector的处理<br /> if (candidate.isAssignable(ImportSelector.class)) {<br /> // Candidate class is an ImportSelector -> delegate to it to determine imports<br /> Class<?> candidateClass = candidate.loadClass();<br /> ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);<br /> ParserStrategyUtils.invokeAwareMethods(<br /> selector, this.environment, this.resourceLoader, this.registry);<br /> if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {<br />                //如果为延迟导入处理则加入集合当中<br /> this.deferredImportSelectors.add(<br /> new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));<br /> }<br /> else {<br />                //根据ImportSelector方法的返回值来进行递归操作<br /> String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());<br /> Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);<br /> processImports(configClass, currentSourceClass, importSourceClasses, false);<br /> }<br /> }<br /> else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {<br /> // Candidate class is an ImportBeanDefinitionRegistrar -><br /> // delegate to it to register additional bean definitions<br /> Class<?> candidateClass = candidate.loadClass();<br /> ImportBeanDefinitionRegistrar registrar =<br /> BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);<br /> ParserStrategyUtils.invokeAwareMethods(<br /> registrar, this.environment, this.resourceLoader, this.registry);<br /> configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());<br /> }<br /> else {<br />              // 如果当前的类既不是ImportSelector也不是ImportBeanDefinitionRegistar就进行@Configuration的解析处理<br /> // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -><br /> // process it as an @Configuration class<br /> this.importStack.registerImport(<br /> currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());<br /> processConfigurationClass(candidate.asConfigClass(configClass));<br /> }<br /> }<br /> }<br /> catch (BeanDefinitionStoreException ex) {<br /> throw ex;<br /> }<br /> catch (Throwable ex) {<br /> throw new BeanDefinitionStoreException(<br /> "Failed to process import candidates for configuration class [" +<br /> configClass.getMetadata().getClassName() + "]", ex);<br /> }<br /> finally {<br /> this.importStack.pop();<br /> }<br /> }<br /> }

8、自定义PropertySourceFactory实现YAML文件解析

8.1、PropertySourceFactory及DefaultPropertySourceFactory

/*
Strategy interface for creating resource-based {@link PropertySource} wrappers.

@author Juergen Hoeller
@since 4.3
@see DefaultPropertySourceFactory
*/
public interface PropertySourceFactory {

  1. /**<br /> * Create a {@link PropertySource} that wraps the given resource.<br /> * @param name the name of the property source<br /> * @param resource the resource (potentially encoded) to wrap<br /> * @return the new {@link PropertySource} (never {@code null})<br /> * @throws IOException if resource resolution failed<br /> */<br /> PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException;

}
/*
The default implementation for {@link PropertySourceFactory},
wrapping every resource in a {@link ResourcePropertySource}.

@author Juergen Hoeller
@since 4.3
@see PropertySourceFactory
@see ResourcePropertySource
*/
public class DefaultPropertySourceFactory implements PropertySourceFactory {

  1. @Override<br /> public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {<br /> return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));<br /> }

}

8.2、执行过程分析

8.2.1、ResourcePropertySource

/*
DefaultPropertySourceFactory在创建PropertySource对象时使用的是此类的构造函数
在构造时,调用的
/
public class ResourcePropertySource extends PropertiesPropertySource {

  1. /**<br /> * 当我们没有指定名称时,执行的是此构造函数,此构造函数调用的是父类的构造函数<br /> * 通过读取父类的构造函数,得知第二个参数是一个properties文件<br /> * spring使用了一个工具类获取properties文件<br /> */<br /> public ResourcePropertySource(EncodedResource resource) throws IOException {<br /> super(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource));<br /> this.resourceName = null;<br /> }<br /> <br /> //其他方法略<br />}

8.2.2、PropertiesPropertySource


/
此类是ResourcePropertySource的父类
/
public class PropertiesPropertySource extends MapPropertySource {

/

此构造函数中包含两个参数:第一个是名称。第二个是解析好的properties文件
/
@SuppressWarnings({“unchecked”, “rawtypes”})
public PropertiesPropertySource(String name, Properties source) {
super(name, (Map) source);
}

  1. protected PropertiesPropertySource(String name, Map<String, Object> source) {<br /> super(name, source);<br /> }

}

8.2.3、PropertiesLoaderUtils

/*
获取properties的工具类
*/
public abstract class PropertiesLoaderUtils {

  1. private static final String XML_FILE_EXTENSION = ".xml";
  2. /**<br /> * ResourcePropertySource类中的构造函数就是执行了此方法<br /> */<br /> public static Properties loadProperties(EncodedResource resource) throws IOException {<br /> Properties props = new Properties();<br /> fillProperties(props, resource);<br /> return props;<br /> }
  3. /**<br /> * <br /> */<br /> public static void fillProperties(Properties props, EncodedResource resource)<br /> throws IOException {
  4. fillProperties(props, resource, new DefaultPropertiesPersister());<br /> }
  5. /**<br /> * 通过此方法源码,可以看出spring在使用@PropertySource注解时,支持xml和properties文件<br /> */<br /> static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister)<br /> throws IOException {
  6. InputStream stream = null;<br /> Reader reader = null;<br /> try {<br /> String filename = resource.getResource().getFilename();<br /> if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {<br /> stream = resource.getInputStream();<br /> //读取xml文件<br /> persister.loadFromXml(props, stream);<br /> }<br /> else if (resource.requiresReader()) {<br /> reader = resource.getReader();<br /> //读取properties文件<br /> persister.load(props, reader);<br /> }<br /> else {<br /> stream = resource.getInputStream();<br /> persister.load(props, stream);<br /> }<br /> }<br /> finally {<br /> if (stream != null) {<br /> stream.close();<br /> }<br /> if (reader != null) {<br /> reader.close();<br /> }<br /> }<br /> }<br /> //其他代码略<br />}

8.2.4、PropertiesPersister


/*
spring中声明的解析xml和properties文件的接口
*/
public interface PropertiesPersister {

  1. <br /> /**<br /> * Load properties from the given Reader into the given<br /> * Properties object.<br /> * @param props the Properties object to load into<br /> * @param reader the Reader to load from<br /> * @throws IOException in case of I/O errors<br /> */<br /> void load(Properties props, Reader reader) throws IOException;
  2. /**<br /> * Load properties from the given XML InputStream into the<br /> * given Properties object.<br /> * @param props the Properties object to load into<br /> * @param is the InputStream to load from<br /> * @throws IOException in case of I/O errors<br /> * @see java.util.Properties#loadFromXML(java.io.InputStream)<br /> */<br /> void loadFromXml(Properties props, InputStream is) throws IOException;<br /> <br /> //其他代码略<br />}<br />/** <br /> * PropertiesPersister接口的实现类<br /> */<br />public class DefaultPropertiesPersister implements PropertiesPersister {
  3. @Override<br /> public void load(Properties props, Reader reader) throws IOException {<br /> props.load(reader);
  4. @Override<br /> public void loadFromXml(Properties props, InputStream is) throws IOException {<br /> props.loadFromXML(is);<br /> }
  5. //其他代码略<br />}

8.3、自定义PropertySourceFactory

我们通过分析@PropertySource源码,得知默认情况下此注解只能解析properties文件和xml文件,而遇到yaml(yml)文件,解析就会报错。此时就需要我们自己编写一个PropertySourceFactory的实现类,借助yaml解析器,实现yml文件的解析。

8.3.1、编写yml配置文件

jdbc:
driver: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring_day01
username: root
password: 1234

8.3.2、导入yaml解析器的坐标



org.yaml
snakeyaml
1.23

8.3.3、编写自定义PropertySourceFactory

/*
自定义资源文件解析器,用于解析yaml yml文件
@author 黑马程序员
@Company http://www.itheima.com
*/
public class CustomerPropertySourceFactory implements PropertySourceFactory {

  1. /**<br /> * 重写接口中的方法<br /> * @param name<br /> * @param resource<br /> * @return<br /> * @throws IOException<br /> */<br /> @Override<br /> public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {<br /> //1.创建yaml文件解析工厂<br /> YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();<br /> //2.设置资源内容<br /> yaml.setResources(resource.getResource());<br /> //3.解析成properties文件<br /> Properties properties = yaml.getObject();<br /> //4.返回符合spring的PropertySource对象<br /> return name != null ? new PropertiesPropertySource(name,properties) : new PropertiesPropertySource(resource.getResource().getFilename(), properties);
  2. }<br />}

8.3.4、使用@PropertyeSource的factory属性配置自定义工厂

/
@author 黑马程序员
@Company http://www.itheima.com
*/
@Configuration
@Import(JdbcConfig.class)
@PropertySource(value = “classpath:jdbc.yml”,factory = CustomerPropertySourceFactory.class)
@ComponentScan(“com.itheima”)
public class SpringConfiguration {
}
/

连接数据库的配置
@author 黑马程序员
@Company http://www.itheima.com
/
public class JdbcConfig {

  1. @Value("${jdbc.driver}")<br /> private String driver;<br /> @Value("${jdbc.url}")<br /> private String url;<br /> @Value("${jdbc.username}")<br /> private String username;<br /> @Value("${jdbc.password}")<br /> private String password;
  2. @Bean("jdbcTemplate")<br /> public JdbcTemplate createJdbcTemplate(DataSource dataSource){<br /> return new JdbcTemplate(dataSource);<br /> }
  3. @Bean("dataSource")<br /> public DataSource createDataSource(){<br /> System.out.println(driver);<br /> DriverManagerDataSource dataSource = new DriverManagerDataSource();<br /> dataSource.setDriverClassName(driver);<br /> dataSource.setUrl(url);<br /> dataSource.setUsername(username);<br /> dataSource.setPassword(password);<br /> return dataSource;<br /> }<br />}

8.3.5、测试运行结果

/*
@author 黑马程序员
@Company http://www.itheima.com
/
public class SpringAnnotationDrivenTest {

  1. /**<br /> * 测试<br /> * @param args<br /> */<br /> public static void main(String[] args) {<br /> ApplicationContext ac = new AnnotationConfigApplicationContext("config");<br /> }<br />}

9、@Profile注解的使用

9.1、使用场景分析

  1. @Profile注解是spring提供的一个用来标明当前运行环境的注解。我们正常开发的过程中经常遇到的问题是,开发环境是一套环境,测试是一套环境,线上部署又是一套环境。这样从开发到测试再到部署,会对程序中的配置修改多次,尤其是从测试到上线这个环节,让测试的也不敢保证改了哪个配置之后能不能在线上运行。为了解决上面的问题,我们一般会使用一种方法,就是针对不同的环境进行不同的配置,从而在不同的场景中跑我们的程序。<br /> spring中的@Profile注解的作用就体现在这里。在spring使用DI来注入的时候,能够根据当前制定的运行环境来注入相应的bean。最常见的就是使用不同的DataSource了。

9.2、代码实现

9.2.1、自定义不同环境的数据源

package config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;

/*
@author 黑马程序员
@Company http://www.itheima.com
/
public class JdbcConfig {

  1. @Value("${jdbc.driver}")<br /> private String driver;<br /> @Value("${jdbc.url}")<br /> private String url;<br /> @Value("${jdbc.username}")<br /> private String username;<br /> @Value("${jdbc.password}")<br /> private String password;
  2. /**<br /> * @return<br /> */<br /> @Bean("dataSource")<br /> @Profile("dev")<br /> public DruidDataSource createDevDataSource(){<br /> DruidDataSource dataSource = new DruidDataSource();<br /> dataSource.setDriverClassName(driver);<br /> dataSource.setUrl(url);<br /> dataSource.setUsername(username);<br /> dataSource.setPassword(password);<br /> dataSource.setMaxActive(10);<br /> return dataSource;<br /> }
  3. /**<br /> * @return<br /> */<br /> @Bean("dataSource")<br /> @Profile("test")<br /> public DruidDataSource createTestDataSource(){<br /> DruidDataSource dataSource = new DruidDataSource();<br /> dataSource.setDriverClassName(driver);<br /> dataSource.setUrl(url);<br /> dataSource.setUsername(username);<br /> dataSource.setPassword(password);<br /> dataSource.setMaxActive(50);<br /> return dataSource;<br /> }
  4. /**<br /> * @return<br /> */<br /> @Bean("dataSource")<br /> @Profile("produce")<br /> public DruidDataSource createProduceDataSource(){<br /> DruidDataSource dataSource = new DruidDataSource();<br /> dataSource.setDriverClassName(driver);<br /> dataSource.setUrl(url);<br /> dataSource.setUsername(username);<br /> dataSource.setPassword(password);<br /> dataSource.setMaxActive(100);<br /> return dataSource;<br /> }<br />}

9.2.2、编写配置类

/*
@author 黑马程序员
@Company http://www.itheima.com
/
@Configuration
@Import(JdbcConfig.class)
public class SpringConfiguration {
}

9.2.3、编写测试类

/*
@author 黑马程序员
@Company http://www.itheima.com
/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
@ActiveProfiles(“test”)
public class SpringProfileTest {

  1. @Autowired<br /> private DruidDataSource druidDataSource;
  2. @Test<br /> public void testProfile(){<br /> System.out.println(druidDataSource.getMaxActive());<br /> }<br />}

9.2.4、测试结果