本文發(fā)表于我的獨(dú)立博客:Geeekr
最近幾天跟同事聊起Spring的一些問(wèn)題,對(duì)一些地方有些疑問(wèn),趁這兩天有點(diǎn)空,看看Spring的源碼,了解下具體的實(shí)現(xiàn)細(xì)節(jié)。本文基于Spring 4.0.5版本。
首先Web項(xiàng)目使用Spring是通過(guò)在web.xml里面配置
org.springframework.web.context.ContextLoaderListener 初始化IOC容器的。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
那就以此為切入點(diǎn)順藤摸瓜。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
ContextLoaderListener 繼承了ContextLoader ,并且實(shí)現(xiàn)ServletContextListener 接口。當(dāng)Server容器(一般指tomcat)啟動(dòng)時(shí),會(huì)收到事件初始化。
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
initWebApplicationContext 方法是在org.springframework.web.context.ContextLoader 類里面。方法太長(zhǎng),分段讀一下。
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " +"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
首先是判斷servletContext 中是否已經(jīng)注冊(cè)了WebApplicationContext ,如果有則拋出異常,避免重復(fù)注冊(cè)。然后就是啟用log,啟動(dòng)計(jì)時(shí)。本方法的關(guān)鍵就在于try代碼塊里的內(nèi)容
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
這里面有幾個(gè)關(guān)鍵的方法。首先看一下createWebApplicationContext()
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
首先determineContextClass() 方法查明具體的Context 類,他會(huì)讀取servletContext 的初始化參數(shù)contextClass ,此參數(shù)我們一半不配置,所以Spring 就會(huì)讀取跟org.springframework.web.context.WebApplicationContext 同一個(gè)包下面的ContextLoader.properties 文件讀取默認(rèn)設(shè)置,反射出org.springframework.web.context.support.XmlWebApplicationContext 類來(lái)。接下來(lái)就是在configureAndRefreshWebApplicationContext() 方法里將新創(chuàng)建的XmlWebApplicationContext 進(jìn)行初始化。首先會(huì)設(shè)置一個(gè)默認(rèn)ID,即org.springframework.web.context.WebApplicationContext: +你項(xiàng)目的ContextPath 。
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default
// value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
} else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
緊接著就是將ServletContext 設(shè)置成XmlWebApplicationContext 的屬性,這樣Spring 就能在上下文里輕松拿到ServletContext 了。
wac.setServletContext(sc);
接下來(lái)就是讀取web.xml 文件中的contextConfigLocation 參數(shù)。如果沒(méi)有配置就會(huì)去讀WEB-INF下的applicationContext.xml 文件。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
并將值設(shè)置(就是我們的Spring配置文件的路徑)進(jìn)XmlWebApplicationContext 中。然后就會(huì)在指定的路徑加載配置文件。
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
接下來(lái)就是customizeContext(sc, wac) 方法,此方法會(huì)根據(jù)用戶配置的globalInitializerClasses 參數(shù)來(lái)初始化一些用戶自定義的屬性,一般我們不配置,所以這里什么也不做。
最后登場(chǎng)的就是最核心的方法了,
wac.refresh();
在這個(gè)方法里,會(huì)完成資源文件的加載、配置文件解析、Bean定義的注冊(cè)、組件的初始化等核心工作,我們一探究竟。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
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) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
次方法是同步的,避免重復(fù)刷新,每個(gè)步驟都放在單獨(dú)的方法內(nèi),流程清晰,是值得學(xué)習(xí)的地方。這里面有個(gè)重要的方法是finishBeanFactoryInitialization(beanFactory); ,里面的內(nèi)容是Spring如何實(shí)例化bean,并注入依賴的,這個(gè)內(nèi)容下一節(jié)講,本節(jié)只說(shuō)明Spring是如何加載class文件的。
首先就是prepareRefresh() 方法。
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
synchronized (this.activeMonitor) {
this.active = 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();
}
此方法做一些準(zhǔn)備工作,如記錄開(kāi)始時(shí)間,輸出日志,initPropertySources(); 和getEnvironment().validateRequiredProperties(); 一般沒(méi)干什么事。
接下來(lái)就是初始化BeanFactory ,是整個(gè)refresh() 方法的核心,其中完成了配置文件的加載、解析、注冊(cè)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
看看它里面都做了些什么?
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
首先refreshBeanFactory() :
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
我們看到會(huì)創(chuàng)建一個(gè)DefaultListableBeanFactory 實(shí)例
DefaultListableBeanFactory beanFactory = createBeanFactory();
再設(shè)置一個(gè)ID
beanFactory.setSerializationId(getId());
然后設(shè)置一些自定義參數(shù):
customizeBeanFactory(beanFactory);
這里面最重要的就是loadBeanDefinitions(beanFactory); 方法了。
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
此方法會(huì)通過(guò)XmlBeanDefinitionReader 加載bean定義。具體的實(shí)現(xiàn)方法是在org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions 方法中定義的。這里設(shè)計(jì)了層層調(diào)用,有好多重載方法,主要就是加載Spring所有的配置文件(可能會(huì)有多個(gè)),以備后面解析,注冊(cè)之用。我一路追蹤到org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(Element root)
protected void doRegisterBeanDefinitions(Element root) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!this.environment.acceptsProfiles(specifiedProfiles)) {
return;
}
}
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(this.readerContext, root, parent);
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
這里創(chuàng)建了一個(gè)BeanDefinitionParserDelegate 示例,解析XML的過(guò)程就是委托它完成的,我們不關(guān)心它是怎樣解析XML的,我們只關(guān)心是怎么加載類的,所以就要看parseBeanDefinitions(root, this.delegate) 方法了。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
我們看到最終解析XML元素的是delegate.parseCustomElement(ele) 方法,最終會(huì)走到一下方法.
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
這里會(huì)根據(jù)不同的XML節(jié)點(diǎn),會(huì)委托NamespaceHandlerSupport 找出合適的BeanDefinitionParser ,如果我們配置了
<context:component-scan
base-package="com.geeekr.service,com.geeekr.dao" />
那么對(duì)應(yīng)BeanDefinitionParser 就是org.springframework.context.annotation.ComponentScanBeanDefinitionParser ,來(lái)看看它的parse 方法。
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
不難看出這里定義了一個(gè)ClassPathBeanDefinitionScanner ,通過(guò)它去掃描包中的類文件,注意:這里是類文件而不是類,因?yàn)楝F(xiàn)在這些類還沒(méi)有被加載,只是ClassLoader能找到這些class的路徑而已。到目前為止,感覺(jué)真想距離我們?cè)絹?lái)越近了。順著繼續(xù)往下摸。進(jìn)入doSacn 方法里,映入眼簾的又是一大坨代碼,但是我們只關(guān)心觀點(diǎn)的部分。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
一眼就能看出是通過(guò)
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
有時(shí)候不得不佩服這些外國(guó)人起名字的功力,把掃描出來(lái)的類叫做candidates(候選人);真是不服不行啊,這種名字真的很容易理解有不有?哈哈,貌似扯遠(yuǎn)了。繼續(xù)往下看。這里只列出方法的主題部分。
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + "/" + this.resourcePattern;
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
先看這兩句:
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + "/" + this.resourcePattern;
假設(shè)我們配置的需要掃描的包名為com.geeekr.service ,那么packageSearchPath 的值就是classpath*:com.geeekr.service/**/*.class ,意思就是com.geeekr.service包(包括子包)下所有class文件;如果配置的是* ,那么packageSearchPath 的值就是classpath*:*/**/*.class 。這里的表達(dá)式是Spring自己定義的。Spring會(huì)根據(jù)這種表達(dá)式找出相關(guān)的class文件。
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
這一句就把相關(guān)class文件加載出來(lái)了,那我們就要看看,Spring究竟是如何把class文件找到的了。首先看看resourcePatternResolver 的定義:
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
進(jìn)入getResources 方法
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Only look for a pattern after a prefix here
// (to not get fooled by a pattern symbol in a strange prefix).
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
這里會(huì)先判斷表達(dá)式是否以classpath*: 開(kāi)頭。前面我們看到Spring已經(jīng)給我們添加了這個(gè)頭,這里當(dāng)然符合條件了。接著會(huì)進(jìn)入findPathMatchingResources 方法。在這里又把**/*.class 去掉了,然后在調(diào)用getResources 方法,然后在進(jìn)入findAllClassPathResources 方法。這里的參數(shù)只剩下包名了例如com/geeekr/service/ 。
protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
if (path.startsWith("/")) {
path = path.substring(1);
}
ClassLoader cl = getClassLoader();
Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
Set<Resource> result = new LinkedHashSet<Resource>(16);
while (resourceUrls.hasMoreElements()) {
URL url = resourceUrls.nextElement();
result.add(convertClassLoaderURL(url));
}
return result.toArray(new Resource[result.size()]);
}
真相大白了,Spring也是用的ClassLoader 加載的class文件。一路追蹤,原始的ClassLoader是Thread.currentThread().getContextClassLoader(); 。到此為止,就拿到class文件了。 Spring會(huì)將class信息封裝成BeanDefinition ,然后再放進(jìn)DefaultListableBeanFactory 的beanDefinitionMap 中。
拿到了class文件后,就要看看Spring是如何裝配bean的了,下一節(jié),繼續(xù)看。
|