spring-IOC、spring-DI以及spring-AOP机制,有了这些机制使我们可以专注于业务代码的开发
1.spring bean
首先,我们了解下spring bean是什么。spring Framework最核心的能力是将各种对象交由spring容器管理,让我们在进行业务开发时无需关注各种对象的依赖关系,在这个过程中,spring容器中将java对象都统一转换为bean来管理。
2.spring bean装配方式
spring bean有三种重要的装配机制:
- XML
XML形式装配bean比较固定,但是对于一些特殊场景,比如需要注入jar包中的bean时,XML形式就是最好的方式
- java注解
java注解形式非常灵活,包括@service,@component等,但底层都是@component,更多在于语义上的区分
- java config
之所以将java config(即@configuration)单独拿出来,因为装配时会有后置处理,这个后面会说到
这三种方式我们在使用时更多是综合起来使用,这些方式都是在声明bean,旨在告诉spring容器启动时将这些“豆子”倒进容器中。在spring容器启动过程中,大致可以分为两个过程,即bean的注册和初始化。
bean注册
在了解bean注册之前,首先需要了解到beanDefinition的概念,因为在注册bean的过程中,所有的bean都会封装为beanDefinition,这里记录了每个bean类名,依赖关系等的信息。
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
/**
* bean的生命周期
*/
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
/**
* bean角色
*/
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;
void setParentName(@Nullable String parentName);
/**
* 获取父beanName
*/
@Nullable
String getParentName();
void setBeanClassName(@Nullable String beanClassName);
/**
* 获取bean所属className
*/
@Nullable
String getBeanClassName();
void setScope(@Nullable String scope);
/**
* 获取bean scope
*
*/
@Nullable
String getScope();
void setLazyInit(boolean lazyInit);
/**
* 是否懒加载
*/
boolean isLazyInit();
void setDependsOn(@Nullable String... dependsOn);
/**
* bean依赖项
*/
@Nullable
String[] getDependsOn();
void setAutowireCandidate(boolean autowireCandidate);
/**
* bean是否可以注入
*/
boolean isAutowireCandidate();
void setPrimary(boolean primary);
/**
* 是否优先注入
*/
boolean isPrimary();
/**
* 工厂名称
*/
void setFactoryBeanName(@Nullable String factoryBeanName);
/**
* 获取工厂名称
*/
@Nullable
String getFactoryBeanName();
/**
* 指定工厂方法名称
*/
void setFactoryMethodName(@Nullable String factoryMethodName);
/**
* 获取工厂方法名称
*/
@Nullable
String getFactoryMethodName();
/**
* 构造器参数
*/
ConstructorArgumentValues getConstructorArgumentValues();
/**
* 获取bean属性值
*/
MutablePropertyValues getPropertyValues();
// Read-only attributes
/**
* 是否单例
*/
boolean isSingleton();
boolean isPrototype();
/**
* 是否抽象
*/
boolean isAbstract();
/**
* 获取bean角色
*/
int getRole();
/**
* 获取bean描述
*/
@Nullable
String getDescription();
/**
* 获取beanDefinition来源描述
*/
@Nullable
String getResourceDescription();
@Nullable
BeanDefinition getOriginatingBeanDefinition();
}
了解完beanDefinition的含义,接着看一下spring bean注册过程的时序图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-el3x1r0l-1605251293690)(D:\我的文档\11103976\Desktop\work\share\spring\timeSequence.png)]
可以看到,spring bean在注册过程中经历了很多流程,而ClassPathXmlApplicationContext这个类,作为XML文件的加载类,是spring容器启动的入口。相应地,spring中还有一个AnnotationConfigApplicationContext类,顾名思义,这个类是处理注解bean的入口,不过在处理流程上关键环节都是相同的,本文中我们以ClassPathXmlApplicationContext为主。接下来就开始分析一下spring容器启动的源码。
启动入口
public class Demo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
DemoService demoService = context.getBean(DemoService.class);
System.out.println(demoService.alohaWorld());
}
}
// xml配置
<bean id="demoService" class="com.vivo.vivoshop.bean.DemoServiceImpl"/>
本文中以上示代码作为测试代码,spring-context依赖版本为5.0.0.RELEASE。ApplicationContext作为spring核心上下文,通过继承关系融合了spring各种核心能力,可以看到类图中的BeanFactory接口,通过其接口方法可以访问到spring IOC容器中的各种资源。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fk8QXOX0-1605251293692)(C:\Users\11103976\AppData\Roaming\Typora\typora-user-images\image-20201112163348444.png)]
通过初始化ClassPathXmlApplicationContext类对象,就开始了spring容器启动流程。
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
setConfigLocations
首先执行的就是setConfigLocations方法,源码如下:
/**
* 处理配置文件路径
*/
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
方法中真正的处理逻辑在resolvePath()方法中,resolvePath方法主要是处理一些XML配置文件的变量替换,即“${xxx}”占位符替换为变量。
refresh
refresh()方法是启动过程的核心方法,其中又包含了很多的处理逻辑,在其中进行了bean注册和bean初始化的过程。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备刷新ctx
prepareRefresh();
// 调用子类refreshBeanFactory()方法
// 获取beanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 对beanFactory进行配置
prepareBeanFactory(beanFactory);
try {
// 拓展点,后置处理beanFactory
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
prepareRefresh()方法先做一些准备工作
/**
* Prepare this context for refreshing, setting its startup date and
* active flag as well as performing any initialization of property sources.
*/
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// Initialize any placeholder property sources in the context environment
initPropertySources();
// Validate that all properties marked as required are resolvable
// see ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<>();
}