springMVC 启动加载过程

10 篇文章 0 订阅

spring启动component-scan类扫描加载过程—源码分析
spring通过DispatcherServlet加载:
系统配置:

<servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

 


servlet的规范中,如果load-on-startup被设定了,那么就会被初始化的时候装载,而servlet装载时会调用其init()方法,那么自然是调用DispatcherServlet的init方法,
通过源码一看,竟然没有,但是并不代表真的没有,你会发现在父类的父类中:org.springframework.web.servlet.HttpServletBean有这个方法,


源码如下所示:

 

 

public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}
		// Set bean properties from init parameters.
		try {
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			throw ex;
		}
		// Let subclasses do whatever initialization they like.
		initServletBean();
		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}




注意代码:initServletBean(); 其余的都和加载bean关系并不是特别大,跟踪进去会发现这个方法是在类:org.springframework.web.servlet.FrameworkServlet类中(是DispatcherServlet的父类、HttpServletBean的子类),
内部通过调用initWebApplicationContext()来初始化一个WebApplicationContext,源码片段

 

        protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (this.logger.isInfoEnabled()) {
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		}
		long startTime = System.currentTimeMillis();


		try {
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}
		catch (RuntimeException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}


		if (this.logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		}
	}

接下来需要知道的是如何初始化这个context的(按照使用习惯,其实只要得到了ApplicationContext,就得到了bean的信息,所以在初始化ApplicationCotext的时候,就已经初始化好了bean的信息,至少至少,它初始化好了bean的路径,以及描述信息),所以我们一旦知道ApplicationCotext是怎么初始化的,就基本知道bean是如何加载的了。


代码片段如下:

 

protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;


		...
		
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}


		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			onRefresh(wac);
		}


		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}


		return wac;
	}

因为Root的ApplicationContext的信息还根本没创建,所以主要是看createWebApplicationContext这个方法,进去后,该方法前面部分,都是在设置一些相关的参数,
例如我们需要将WEB容器、以及容器的配置信息设置进去,然后会调用一个refresh()方法,这个方法表面上是用来刷新的,其实也是用来做初始化bean用的,也就是配置修改后,
如果你能调用它的这个方法,就可以重新装载spring的信息,我们看看源码中的片段如下:

 

        protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		
		...
		
		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));


		...


		postProcessWebApplicationContext(wac);
		applyInitializers(wac);
		wac.refresh();
	}


其实这个方法,不论是通过ClassPathXmlApplicationContext还是WEB装载都会调用这里,我们看下ClassPathXmlApplicationContext中调用的部分:

 

        public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
			throws BeansException {
		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}

他们的区别在于,web容器中,用servlet装载了,servlet中包装了一个XmlWebApplicationContext而已,而ClassPathXmlApplicationContext是直接调用的,他们共同点是,
不论是XmlWebApplicationContext、还是ClassPathXmlApplicationContext都继承了类(间接继承),AbstractApplicationContext,这个类中的refresh()方法是共用的,
也就是他们都调用的这个方法来加载bean的,在这个方法中,通过obtainFreshBeanFactory方法来构造beanFactory的

 

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


是不是看到一层调用一层很烦人,其实回过头来想一想,它没一层都有自己的处理动作,毕竟spring不是简单的做一个bean加载,即使是这样,
我们最少也需要做xml解析、类装载和实例化的过程,每个步骤可能都有很多需求,因此分离设计,使得代码更加具有扩展性,我们继续来看obtainFreshBeanFactory方法的描述:

 

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		refreshBeanFactory();
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (logger.isDebugEnabled()) {
			logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
		}
		return beanFactory;
	}

这里很多人可能会不太注意refreshBeanFactory()这个方法,尤其是第一遍看这个代码的,如果你忽略掉,你可能会找不到bean在哪里加载的,前面提到了refresh其实可以用以初始化,
这里也是这样,refreshBeanFactory如果没有初始化beanFactory就是初始化它了,后面你看到的都是getBeanFactory的代码,也就是已经初始化好了,这个refreshBeanFactory方法类AbstractRefreshableApplicationContext中的方法,
它是AbstractApplicationContext的子类,同样不论是XmlWebApplicationContext、还是ClassPathXmlApplicationContext都继承了它,因此都能调用到这个一样的初始化方法,来看看body部分的代码

 

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

创建了一个beanFactory,然后下面的方法可以通过名称就能看出是“加载bean的定义”,将beanFactory传入,自然要加载到beanFactory中了,createBeanFactory就是实例化一个beanFactory没别的,
我们要看的是bean在哪里加载的,现在貌似还没看到重点,继续跟踪loadBeanDefinitions(DefaultListableBeanFactory)方法

它由AbstractXmlApplicationContext类中的方法实现,web项目中将会由类:XmlWebApplicationContext来实现,其实差不多,主要是看启动文件是在那里而已,
如果在非web类项目中没有自定义的XmlApplicationContext,那么其实功能可以参考XmlWebApplicationContext,可以认为是一样的功能。那么看看loadBeanDefinitions方法如下:

 

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


	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			for (String configLocation : configLocations) {
				reader.loadBeanDefinitions(configLocation);
			}
		}
	}

这里有一个XmlBeanDefineitionReader,是读取XML中spring的相关信息(也就是解析SpringContext.xml的),这里通过getConfigLocations()获取到的就是这个或多个文件的路径,会循环,
通过XmlBeanDefineitionReader来解析,跟踪到loadBeanDefinitions方法里面,会发现方法实现体在XmlBeanDefineitionReader的父类:AbstractBeanDefinitionReader中,代码如下:

 

        public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
		}


		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				int loadCount = loadBeanDefinitions(resources);
				if (actualResources != null) {
					for (Resource resource : resources) {
						actualResources.add(resource);
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
				}
				return loadCount;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			// Can only load single resources by absolute URL.
			Resource resource = resourceLoader.getResource(location);
			int loadCount = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
			}
			return loadCount;
		}
	}

这里大家会疑惑,为啥里面还有一个loadBeanDefinitions,大家要知道,我们目前只解析到我们的springContext.xml在哪里,但是还没解析到springContext.xml的内容是什么,可能有多个spring的配置文件,这里会出现多个Resource,
所以是一个数组(这里如何通过location找到文件部分,在我们找class的时候自然明了,大家先不纠结这个问题)。

接下来有很多层调用,会以此调用:
AbstractBeanDefinitionReader.loadBeanDefinitions(Resources []) 循环Resource数组,调用方法:
XmlBeanDefinitionReader.loadBeanDefinitions(Resource ) 和上面这个类是父子关系,接下来会做:doLoadBeanDefinitions、registerBeanDefinitions的操作,在注册beanDefinitions的时候,其实就是要真正开始解析XML了
步骤一:loadBeanDefinitions

 

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isInfoEnabled()) {
			logger.info("Loading XML bean definitions from " + encodedResource.getResource());
		}


		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<EncodedResource>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}



步骤二:doLoadBeanDefinitions

 

        protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			Document doc = doLoadDocument(inputSource, resource);
			return registerBeanDefinitions(doc, resource);
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		catch (SAXParseException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
		}
		catch (SAXException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"XML document from " + resource + " is invalid", ex);
		}
		catch (ParserConfigurationException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Parser configuration exception parsing XML from " + resource, ex);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"IOException parsing XML document from " + resource, ex);
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Unexpected exception parsing XML document from " + resource, ex);
		}
	}




    步骤三:registerBeanDefinitions

 

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}


	步骤三调用了DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions方法,如下图所示:
	protected void doRegisterBeanDefinitions(Element root) {
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);


		if (this.delegate.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isInfoEnabled()) {
						logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}
		preProcessXml(root);
		parseBeanDefinitions(root, this.delegate);
		postProcessXml(root);
		this.delegate = parent;
	}




中间有解析XML的过程,但是貌似我们不是很关心,我们就关系类是怎么加载的,虽然已经到XML解析部分了,所以主要看parseBeanDefinitions这个方法,
里面会调用到BeanDefinitionParserDelegate类的parseCustomElement方法,用来解析bean的信息:

 

        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的信息,跟踪进去,会发现用了NamespaceHandlerSupport的parse方法,它会根据节点的类型,找到一种合适的解析BeanDefinitionParser(接口),
他们预先被spring注册好了,放在一个HashMap中,例如我们在spring 的annotation扫描中,通常会配置:<context:component-scan base-package="com.xxx" />

此时根据名称“component-scan”就会找到对应的解析器来解析,而与之对应的就是ComponentScanBeanDefinitionParser的parse方法,这地方已经很明显有扫描bean的概念在里面了,
这里的parse获取到后,中间有一个非常非常关键的步骤那就是定义了ClassPathBeanDefinitionScanner来扫描类的信息,它扫描的是什么?是加载的类还是class文件呢?
答案是后者,为何,因为有些类在初始化化时根本还没被加载,ClassLoader根本还没加载,只是ClassLoader可以找到这些class的路径而已

 

        public BeanDefinition parse(Element element, ParserContext parserContext) {
		String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
		basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
		String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
				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;
	}

注意这里的scanner创建后,最关键的是doScan的功能,解析XML我想来看这个的不是问题,如果还不熟悉可以先看看,那么我们得到了类似:com.xxx这样的信息,就要开始扫描类的列表,
那么再哪里扫描呢?这里的doScan返回了一个Set<BeanDefinitionHolder>我们感到希望就在不远处,进去看看doScan方法。

 

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


我们看到这么大一坨代码,其实我们目前不关心的代码,暂时可以不管,我们就看怎么扫描出来的,可以看出最关键的扫描代码是:findCandidateComponents(String basePackage)方法,
也就是通过每个basePackage去找到有那些类是匹配的,我们这里假如配置了com.abc,或配置了 * 两种情况说明。

 

    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);
							if (isCandidateComponent(sbd)) {
								if (debugEnabled) {
									logger.debug("Identified candidate component class: " + resource);
								}
								candidates.add(sbd);
							}
							else {
								if (debugEnabled) {
									logger.debug("Ignored because not a concrete top-level class: " + resource);
								}
							}
						}
						else {
							if (traceEnabled) {
								logger.trace("Ignored because not matching any filter: " + resource);
							}
						}
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to read candidate component class: " + resource, ex);
					}
				}
				else {
					if (traceEnabled) {
						logger.trace("Ignored because not readable: " + resource);
					}
				}
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
		}
		return candidates;
	}

是已经拿到了类的定义,会组装信息,如果我们配置了 com.abc会组装为:classpath*:com/abc/**/*.class ,如果配置是 * ,那么将会被组装为classpath*:*/**/*.class ,
但是这个好像和我们用的东西不太一样,java中也没见这种URL可以获取到,spring到底是怎么搞的呢?就要看如下代码

 

Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);

它竟然神奇般的通过这个路径获取到了URL,你一旦跟踪你会发现,获取出来的全是.class的路径,包括jar包中的相关class路径,
这里有些细节,我们先不说,先看下这个resourcePatternResolover是什么类型的,看到定义部分是:

 

        private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); 

做了一个测试,用一个简单main方法写了一段:

 

        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();  
	Resource[] resources = resourcePatternResolver.getResources("classpath*:com/abc/**/*.class");

获取出来的果然是那样,可以猜测,这个和ClassLoader的getResource方法有关系了,因为太类似了,我们跟踪进去看下:

 

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

这个CLASSPATH_ALL_URL_PREFIX就是字符串 classpath*: , 我们传递参数进来的时候一致,继续走findPathMatchingResources方法,好了,越来越接近真相了。

 

        protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
		String rootDirPath = determineRootDir(locationPattern);
		String subPattern = locationPattern.substring(rootDirPath.length());
		Resource[] rootDirResources = getResources(rootDirPath);
		Set<Resource> result = new LinkedHashSet<Resource>(16);
		for (Resource rootDirResource : rootDirResources) {
			rootDirResource = resolveRootDirResource(rootDirResource);
			URL rootDirURL = rootDirResource.getURL();
			if (equinoxResolveMethod != null) {
				if (rootDirURL.getProtocol().startsWith("bundle")) {
					rootDirURL = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirURL);
					rootDirResource = new UrlResource(rootDirURL);
				}
			}
			if (rootDirURL.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirURL, subPattern, getPathMatcher()));
			}
			else if (ResourceUtils.isJarURL(rootDirURL) || isJarResource(rootDirResource)) {
				result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirURL, subPattern));
			}
			else {
				result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
			}
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
		}
		return result.toArray(new Resource[result.size()]);
	}

这里有一个rootDirPath,这个地方有个容易出错的,是如果你配置的是 com.abc,那么rootDirPath部分应该是:classpath*:com/abc/ 而如果配置是 * 那么classpath*: 只有这个结果,
而不是classpath*:*(这里我就不说截取字符串的源码了),回到上一段代码,这里再次调用了getResources(String)方法,又回到前面一个方法,这一次,依然是以classpath*:开头,
所以第一层 if 语句会进去,而第二层不会,为什么?在里面的isPattern() 的实现中是这样写的:

 

public boolean isPattern(String path) {  
		return (path.indexOf('*') != -1 || path.indexOf('?') != -1);  
	}  

在匹配前,做了一个substring的操作,会将“classpath*:”这个字符串去掉,如果是配置的是com.abc就变成了"com/abc/",而如果配置为*,那么得到的就是“” ,也就是长度为0的字符串,
因此在我们的这条路上,这个方法返回的是false,就会走到代码段findAllClassPathResources中,这就是为什么上面提到会有用途的原因,好了,最最最最关键的地方来了哦。例如我们知道了一个com/abc/为前缀,
此时要知道相关的classpath下面有哪些class是匹配的,如何做?自然用ClassLoader,我们看看Spring是不是这样做的:

 

        protected Resource[] findAllClassPathResources(String location) throws IOException {
		String path = location;
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		Set<Resource> result = doFindAllClassPathResources(path);
		if (logger.isDebugEnabled()) {
			logger.debug("Resolved classpath location [" + location + "] to resources " + result);
		}
		return result.toArray(new Resource[result.size()]);
	}

果然不出所料,它也是用ClassLoader,只是它自己提供的getClassLoader()方法,也就是和spring的类使用同一个加载器范围内的,以保证可以识别到一样的classpath,自己模拟的时候,可以用一个类

类名.class.getClassLoader().getResources("");
如果放为空,那么就是获取classpath的相关的根路径(classpath可能有很多,但是根路径,可以被合并),也就是如果你配置的*,获取到的将是这个,也许你在web项目中,你会获取到项目的根路径(classes下面,以及tomcat的lib目录)。
如果写入一个:com/abc/ 那么得到的将是扫描相关classpath下面所有的class和jar包中与之匹配的类名(前缀部分)的路径信息,但是需要注意的是,如果有两层jar包,而你想要扫描的类或者说想要通过spring加载的类在第二层jar包中,
这个方法是获取不到的,这不是spring没有去做这个事情,而是,java提供的getResources方法就是这样的,有朋友问我的时候,正好遇到了类似的事情,另外需要注意的是,getResources这个方法是包含当前路径的一个递归文件查找
(一般环境变量中都会配置 . ),所以如果是一个jar包,你要运行的话,切记放在某个根目录来跑,因为当前目录,就是根目录也会被递归下去,你的程序会被莫名奇怪地慢。
回到上面的代码中,在findPathMatchingResources中我们这里刚刚获取到base的路径列表,也就是所有包含类似com/abc/为前缀的路径,或classpath合并后的目录根路径;此时我们需要下面所有的class,
那么就需要的是递归,这里我就不再跟踪了,大家可以自己去跟踪里面的几个方法调用:doFindPathMatchingJarResources、doFindPathMatchingFileResources 。
几乎不会用到:VfsResourceMatchingDelegate.findMatchingResources,所以主要是上面两个,分别是jar包中的和工程里面的class,跟踪进去会发现,代码会不断递归循环调用目录路径下的class文件的路径信息,
最终会拿到相关的class列表信息,但是这些class还并没有做检测是否有annotation,那是下一步做的事情,但是下一个步骤已经很简单了,因为要检测一个类的annotation
这里大家还可以通过以下简单的方式来测试调用路径的问题:

 

 

 

ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);  
Set<BeanDefinition> beanDefinitions = provider.findCandidateComponents("com/abc");  
for(BeanDefinition beanDefinition : beanDefinitions) {  
    System.out.println(beanDefinition.getBeanClassName()   
                    + "\t" + beanDefinition.getResourceDescription()  
                    + "\t" + beanDefinition.getClass());  
} 

 


回到findCandidateComponents方法中,isCandidateComponent(MetadataReader metadataReader)方法,这个方法里先循环excludeFilters,再循环includeFilters,excludeFilters默认情况下没有啥内容,
includeFilters默认情况下最少会有一个new AnnotationTypeFilter(Component.class);也就是默认情况下excludeFilters排除内容不会循环,includeFilters包含内容最少会匹配到AnnotationTypeFilter,
调用AnnotationTypeFilter.match方法是其父类AbstractTypeHierarchyTraversingFilter.math()方法,其内部调用matchSelf()调回子类的AnnotationTypeFilter.matchSelf()方法。
该方法中用||连接两个判定分别是hasAnnotation、hasMetaAnnotation,前者判定注解名称本身是否匹配因此Component肯定能匹配上,后者会判定注解的meta注解是否包含,Service、Controller、Repository注解都注解了Component,
因此它们会在后者匹配上。这样match就肯定成立了

 

 

        protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				return false;
			}
		}
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}	


看了这么多,是不是有点晕,没关系,谁第一回看都这样,当你下一次看的时候,有个思路就好了,我这里并没有像UML一样理出他们的层次关系,和调用关系,仅仅针对代码调用逐层来说明,
大家如果初步看就是,由Servlet初始化来创建ApplicationContext,在设置了Servelt相关参数后,获取servlet的配置文件路径或自己指定的配置文件路径(applicationContext.xml或其他的名字,可以一个或多个),
然后通过系列的XML解析,以及针对每种不同的节点类型使用不同的加载方式,其中component-scan用于指定扫描类的对应有一个Scanner,它会通过ClassLoader的getResources方法来获取到class的路径信息,
那么class的路径都能获取到,类的什么还拿不到呢?
step 4下面 看refresh()的核心finishBeanFactoryInitialization(beanFactory);
经过obtainFreshBeanFactory() 这个方法,我们的beanFactory就准备好了,接下来我们主要围绕finishBeanFactoryInitialization(beanFactory)方法,聊聊Spring是如何实例化bean的。

 

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
		// Initialize conversion service for this context.
		if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
				beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
			beanFactory.setConversionService(
					beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
		}


		// Register a default embedded value resolver if no bean post-processor
		// (such as a PropertyPlaceholderConfigurer bean) registered any before:
		// at this point, primarily for resolution in annotation attribute values.
		if (!beanFactory.hasEmbeddedValueResolver()) {
			beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
				@Override
				public String resolveStringValue(String strVal) {
					return getEnvironment().resolvePlaceholders(strVal);
				}
			});
		}


		// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
		String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
		for (String weaverAwareName : weaverAwareNames) {
			getBean(weaverAwareName);
		}


		// Stop using the temporary ClassLoader for type matching.
		beanFactory.setTempClassLoader(null);


		// Allow for caching all bean definition metadata, not expecting further changes.
		beanFactory.freezeConfiguration();


		// Instantiate all remaining (non-lazy-init) singletons.
		beanFactory.preInstantiateSingletons();
	}


这个方法,就是为了实例化非懒加载的单例bean,我们走进 beanFactory.preInstantiateSingletons(); 看一看
(注意,这里实例化单例,而Struts中Action是每次请求都创建,所以Action并不是单例的)

 

 

 

public void preInstantiateSingletons() throws BeansException {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Pre-instantiating singletons in " + this);
		}


		// Iterate over a copy to allow for init methods which in turn register new bean definitions.
		// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
		List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);


		// Trigger initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
					boolean isEagerInit;
					if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
						isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
							@Override
							public Boolean run() {
								return ((SmartFactoryBean<?>) factory).isEagerInit();
							}
						}, getAccessControlContext());
					}
					else {
						isEagerInit = (factory instanceof SmartFactoryBean &&
								((SmartFactoryBean<?>) factory).isEagerInit());
					}
					if (isEagerInit) {
						getBean(beanName);
					}
				}
				else {
					getBean(beanName);
				}
			}
		}


		// Trigger post-initialization callback for all applicable beans...
		for (String beanName : beanNames) {
			Object singletonInstance = getSingleton(beanName);
			if (singletonInstance instanceof SmartInitializingSingleton) {
				final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged(new PrivilegedAction<Object>() {
						@Override
						public Object run() {
							smartSingleton.afterSingletonsInstantiated();
							return null;
						}
					}, getAccessControlContext());
				}
				else {
					smartSingleton.afterSingletonsInstantiated();
				}
			}
		}
	}	

 




因为Struts项目中Action并不满足条件 “不是抽象类, 且是单例, 且不是延迟加载”,所以该方法对我们自定义的Action几乎没有用,我们一直循环直到单例的对象出现,再来看这个代码。


我们把这小段代码提出来单独看

 

 

 

 

for (String beanName : beanNames) { //将加载进来的beanDefinitionNames循环分析
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { //如果不是抽象类, 且是单例, 且不是延迟加载
				if (isFactoryBean(beanName)) { //是否实现FactoryBean接口
					final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName); 
					boolean isEagerInit;
					if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
						isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
							@Override
							public Boolean run() {
								return ((SmartFactoryBean<?>) factory).isEagerInit();
							}
						}, getAccessControlContext());
					}
					else {
						isEagerInit = (factory instanceof SmartFactoryBean &&
								((SmartFactoryBean<?>) factory).isEagerInit());
					}
					if (isEagerInit) {
						getBean(beanName);
					}
				}
				else {
					getBean(beanName);
				}
			}
}

 


判断这个bean是否是抽象类,是否是单例,是否延迟加载
如果不是抽象类, 且是单例, 且不是延迟加载,那么判断是否实现 FactoryBean 接口
如果实现了 FactoryBean,则 getBean(FACTORY_BEAN_PREFIX + beanName),否则 getBean(beanName)

如果我们跟进 getBean 这个方法,发现它调用了 doGetBean 这个方法,我们再跟进,这个方法非常长(这里就不贴出来了)


在这个方法中,你可以不断地去跟进(这里不再做具体展开),你会发现大概的步骤差不多是
创建一个bean的实例
将这个实例封装到BeanWrapper中
















而这里bean的实例化方法,其实是 beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
这个instantiate 方法在 package org.springframework.beans.factory.support; --> SimpleInstantiationStrategy.java
在这之中采用反射机制将对象进行了实例化。


其实还涉及到bean实例化以后,Spring是如何将bean的属性进行注入的,这里暂时不做进一步的展开了。


可以知道的是,最终属性的注入是利用反射机制,通过setter赋值的








 

 

  • 4
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Tomcat是一种开源的Servlet容器,它的启动原理是通过读取配置文件、加载相关类和文件,创建并初始化Web应用程序的运行环境,最后启动HTTP服务器监听客户端请求。 Spring是一个开箱即用的Java企业级应用程序开发框架,它的启动原理是通过读取配置文件,加载配置文件中定义的Bean定义和其他相关配置信息,然后根据这些信息创建和管理应用程序的各个组件。Spring容器在启动过程中会初始化相关的Bean实例,并完成依赖注入等操作,最终将这些组件纳入管理范围,供应用程序使用。 SpringMVC是基于Spring框架的MVC(Model-View-Controller)Web应用程序开发框架,它的启动原理也是跟Spring类似。在SpringMVC启动过程中,首先会读取配置文件,加载配置文件中定义的Controller、View Resolver等组件,并进行相关的初始化操作。然后SpringMVC容器会根据请求的URL路径匹配相应的Controller,执行对应的业务逻辑,最后返回相应的结果。 MyBatis是一种持久层框架,用于简化数据库访问的过程。在MyBatis的启动原理中,先读取配置文件,加载配置的数据库连接信息和SQL映射文件等。然后MyBatis会根据这些配置信息创建数据库连接池,以及初始化SQL语句的映射关系。启动之后,MyBatis会通过数据库连接池建立与数据库的连接,并执行应用程序传递的SQL语句,最后将查询结果返回给应用程序。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值