《Spring技术内幕》笔记-第二章 IoC容器的实现
简单介绍
1,在Spring中,SpringIoC提供了一个主要的JavaBean容器。通过IoC模式管理依赖关系。并通过依赖注入和AOP切面增强了为JavaBean这样子的POJO提供事务管理,生命周期管理等功能。
2,Spring IoC的设计中,主要包括两个基本的容器系列:
-1,BeanFactory系列。该序列实现了容器的基本功能。
-2。ApplicationContext应用上下文。
Spring IoC容器的设计
1,例如以下图,IoC容器的接口设计图。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
2,简单介绍
-1,从BeanFactory到HierarchicalbeanFactory再到ConfigurableBeanFactory是一条基本的BeanFactory设计路径。BeanFactory定义了基本的IoC容器接口。
HierarchicalBeanFactory接口继承了BeanFactory接口,并再其基础上添加了getParentBeanFactory()接口功能,使得BeanFactory具备双亲IoC管理功能。ConfigurableBeanFactory主要定义了对BeanFactory的配置功能。
-2。以ApplicationContext为核心的接口设计。
BeanFactory的应用场景
BeanFactory是最主要的IoC容器。
用户在使用容器的时候能够通过&符来获取FactoryBean本身。FactoryBean跟普通Bean不同。通过BeanFactory类的getBean方法直接获取到的并非该FactoryBean的实例,而是 该FactoryBean中方法getObject返回的对象。
但我们能够通过其他途径获取到该FactoryBean的实例。方法就是在通过 getBean方法获取实例时在參数name前面加上“&”符号就可以。
String FACTORY_BEAN_PREFIX = "&";
1。BeanFactory的主要接口,以及其作用例如以下:
-1。容器推断是否包括指定名字的bean。
boolean containsBean(String name);
-2,推断指定的bean是否是prototype类型。
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
-3。推断指定的bean是否是singleton。
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
-4,推断指定的bean是否与给定类型相匹配。
boolean isTypeMatch(String name, Class<? > targetType) throws NoSuchBeanDefinitionException;
-5,获取指定Bean的类型。
Class<? > getType(String name) throws NoSuchBeanDefinitionException;
-6。指定bean的全部别名。
String[] getAliases(String name);
2,BeanFactory的容器设计原理。
BeanFactory接口提供了使用IoC的规范,而且Spring实现了一系列容器的实现供开发者使用。
以XmlBeanFactory为例。例如以下为XmlBeanFactory的继承结构图。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
下面是XmlBeanFactory的代码,在Spring 4中。该类已经不推荐被使用了。
@Deprecated
@SuppressWarnings({"serial", "all"})
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
/**
* 通过给定的资源创建一个XmlBeanFactory实例。
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
/**
* Create a new XmlBeanFactory with the given input stream,
* which must be parsable using DOM.
*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
该类继承了DefaultListableBeanFactory类。DefaultListableBeanFactory该类实现一个最为基础的IoC容器。
例如以下是一个简单的使用方法举例:
ClassPathResource re = new ClassPathResource("bean.xml");
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader xdr = new XmlBeanDefinitionReader(beanFactory);
xdr.loadBeanDefinitions(re);
System.out.println(xdr.getRegistry());
ApplicationContext的应用场景
1。 ApplicationContext在BeanFactory的基础上添加了非常多新的特性:
-1。支持不同的信息源。ApplicationContext扩展了MessageSource接口,这些为国际化提供服务。
-2,訪问资源。对ResourceLoader和Resource的支持。能够从不同的地方获取到资源。
-3。支持应用事件。
继承ApplicationEventPublisher,从而引入的事件机制。
与Bean的生命周期结合,方便管理Bean。
-4,在ApplicationContext添加附加服务。
2,ApplicationContext设计原理,以FileSystemApplicationContext为例。
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
在实例化该应用中,调用了AbstractApplicationContext的refresh方法。该方法是IoC容器启动的主要方法。
另一个主要方法是:
@Override
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
该方法通过FileSystemResource获取资源路径。
IoC容器的初始化过程
简单来说IoC的初始化是由AbstractApplicationContext的refresh方法实现的。整个启动过程包含三个部分。BeanDefinition的Resource定位、加载和注冊三个基本部分。Spring将三个模块分开。并使用不同的模块完毕。
第一个过程是Resource定位过程。这个Resource定位是指Spring找到我们定义的Bean配置的xml文件。
第二步。BeanDefinition的加载。这个过程是把用户定义好的Bean表示成IoC容器内部的数据结构,而这个数据结构就是BeanDefinition。详细说。BeanDefination实际就是POJO在容器中的抽象,通过这个BeanDefinition定义的数据结构,使IoC容器可以方便的对POJO对象。也就是Bean进行管理。
第三步,是向IoC容器注冊这些BeanDefinition的过程。这个过程通过调用BeanDefinationRegistry接口实现。
这个注冊过程把加载过程(第二步)得到的BeanDefinition向容器注冊。IoC内部,将BeanDefinition注入到一个HashMap中去,IoC容器就是通过HashMap来持有这些BeanDefinition数据的。
一般,IoC的初始化过程,不包含依赖注入。依赖注入一般发生在应用第一次通过getBean向容器获取Bean的时候。
1,BeanDefinition的Resource定位。
在使用DefaultListableBeanFactory的时候,首先须要使用Resource来进行资源定位容器使用的BeanDefinition。
使用ClassPathResource进行资源定位。
ClassPathResource re = new ClassPathResource("bean.xml");
在此处定义的资源文件不能被DefaultListableBeanFactory直接使用,通过BeanDefinitionReader来读取。
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader xdr = new XmlBeanDefinitionReader(beanFactory);
xdr.loadBeanDefinitions(re);
在此处我们发现使用比較麻烦。所以使用ApplicationContext,由于在ApplicationContext中。Spring为我们提供了一系列的资源定位,比方FileSystemXmlApplicationContext。
以FileSystemXmlApplicationContext为例,解说相应的源代码。
该构造函数通过文件路劲来生成相应的FileSystemXmlApplicationContext
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
通过refresh方法来加载BeanDefinition。
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
通过文件系统来获取资源。
@Override
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
2,BeanDefinition的加载和解析。
对IoC容器来说。加载过程相当于把BeanDefinition在IoC容器中转换成一个Spring内部数据结构的过程。IoC对对象的管理都是通过对其持有的BeanDefination进行相关操作来实现的。
这些BeanDefinition是通过HashMap来维护的。
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
如上代码,refresh()方法启动了容器初始化。是加载BeanDefination的入口方法。
refresh()详细的代码结构例如以下:
@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.
//在子类中启动refreshBeanFactory()的地方。
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.
//设置BeanFactory的后置处理。
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
//调用BeanFactory的后置处理器。这些处理器是在Bean定义中向容器注冊的
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
//注冊Bean的后处理器。在Bean的创建过程中调用
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
//对上下文中的消息源初始化
initMessageSource();
// Initialize event multicaster for this context.
//初始化上下文中的时间机制
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//初始化其它Bean
onRefresh();
// Check for listener beans and register them.
//检查监听bean并注冊
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//实例化全部(non-lazy-init) 单例。
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
//公布容器事件。结束
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
//防止Bean资源占用,销毁已经创建的单例。
destroyBeans();
// Reset 'active' flag.
//重置active标志
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
-1。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();
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();
}
-2。启动refreshBeanFactory():
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
/**
* Tell the subclass to refresh the internal bean factory.
* @return the fresh BeanFactory instance
* @see #refreshBeanFactory()
* @see #getBeanFactory()
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
终于调用,AbstractRefreshableApplicationContext中的refreshBeanFactory()方法。
/**
* This implementation performs an actual refresh of this context's underlying
* bean factory, shutting down the previous bean factory (if any) and
* initializing a fresh bean factory for the next phase of the context's lifecycle.
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {//假设已经存在BeanFactory
destroyBeans();//销毁
closeBeanFactory();
}
try { //创建IoC容器
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);//加载BeanFactory。抽象方法
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
这里调用的loadBeanDefinations()是一个抽象方法,详细实如今AbstractXmlApplicationContext。
/**
* Loads the bean definitions via an XmlBeanDefinitionReader.
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see #initBeanDefinitionReader
* @see #loadBeanDefinitions
*/
@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);
}
接着就是调用loadBeanDefinations的地方,首先得到BeanDefinition信息的Resource定位,然后调用XmlBeanDefinitionReader来读取,详细过程托付给BeanDefinitionReader来完毕的。
XML文件则是通过XmlBeanDefinitionReader来加载BeanDefination到容器中。
注:在XmlBeanDefinationReader中找到的loadBeanDefination()方法。与书中的代码块不同。
此处可能是Spring版本号的问题。
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());
}//调用解析XML文件
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 {
//获取XML文档读取
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
documentReader.setEnvironment(this.getEnvironment());
int countBefore = getRegistry().getBeanDefinitionCount();
//详细解析过程
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
BeanDefinition加载过程中,首先调用XML解析器,生成XML解析对象。然后再进行详细的解析。
createBeanDefinitionDocumentReader()方法:
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
获取到详细的解析器。然后托付给BeanDefinitionParserDelegate来完毕详细的解析。例如以下查看DefaultBeanDefinitionDocuementReader中的processBeanDefinition(Element , BeanDefinitionParserDelegate)方法:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
//BeanDefinitionHolder是对BeanDefinition的封装。
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
//向IoC容器注冊解析到的BeanDefinition。
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
BeanDefinitionHolder:
public class BeanDefinitionHolder implements BeanMetadataElement {
private final BeanDefinition beanDefinition;
private final String beanName;
private final String[] aliases;
........
在BeanDefinitionParserDelegate类中,定义了对各种SpringBean类的处理。
查看其相应的parseBeanDefinitionElement方法:
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
String id = ele.getAttribute(ID_ATTRIBUTE);//bean元素中的ID
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);//bean元素中的name
List<String> aliases = new ArrayList<String>();//aliases
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);//具体解析过程,返回AbstractBeanDefinition对象,该对象定义了Bean的基本属性
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name, if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
查看详细的解析代码,parseBeanDefinitionEleent方法:
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
//生成BeanDefinition对象
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//解析属性,并设置Description信息
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//解析bean元素信息
parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//构造函数
parseConstructorArgElements(ele, bd);
//成员变量
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
}
return null;
}
当中相应的各种异常信息,可能我们在编程工作中常常遇到。
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
parsePropertyElement((Element) node, bd);//解析
}
}
}
/**
* Parse a property element.
*/
public void parsePropertyElement(Element ele, BeanDefinition bd) {
String propertyName = ele.getAttribute(NAME_ATTRIBUTE);//获取property名字
if (!StringUtils.hasLength(propertyName)) {
error("Tag 'property' must have a 'name' attribute", ele);
return;
}
this.parseState.push(new PropertyEntry(propertyName));
try {
if (bd.getPropertyValues().contains(propertyName)) {//是否包括
error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
return;
}
Object val = parsePropertyValue(ele, bd, propertyName);//解析
PropertyValue pv = new PropertyValue(propertyName, val);//获取值
parseMetaElements(ele, pv);
pv.setSource(extractSource(ele));
bd.getPropertyValues().addPropertyValue(pv);
}
finally {
this.parseState.pop();
}
}
public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
String elementName = (propertyName != null) ?
"<property> element for property '" + propertyName + "'" :
"<constructor-arg> element";
// Should only have one child element: ref, value, list, etc.
NodeList nl = ele.getChildNodes();//xml文档解析
Element subElement = null;
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
!nodeNameEquals(node, META_ELEMENT)) {
// Child element is what we're looking for.
if (subElement != null) {
error(elementName + " must not contain more than one sub-element", ele);
}
else {
subElement = (Element) node;
}
}
}
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);//是否是ref引用
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);//值引用
if ((hasRefAttribute && hasValueAttribute) ||
((hasRefAttribute || hasValueAttribute) && subElement != null)) {
error(elementName +
" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
}
if (hasRefAttribute) {//ref引用
String refName = ele.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
error(elementName + " contains empty 'ref' attribute", ele);
}
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
ref.setSource(extractSource(ele));
return ref;
}//value 引用
else if (hasValueAttribute) {
TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
valueHolder.setSource(extractSource(ele));
return valueHolder;
}//假设还有子元素。触发对子元素的解析
else if (subElement != null) {
return parsePropertySubElement(subElement, bd);
}
else {
// Neither child element nor "ref" or "value" attribute found.
error(elementName + " must specify a ref or value", ele);
return null;
}
}
3,BeanDefinition在IoC容器中的注冊
BeanDefinition完毕加载和解析过程后。须要对其进行注冊操作。
注冊是在DefaultListableBeanFactory中,通过HashMap来加载Beandefinition的。
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);
调用顺序:XmlBeanDefinitionReader调用loadBeanDefinitions(EncodedResource)方法,然后到registerBeanDefinitions()方法。在该方法中registerBeanDefinitions()方法被调用。在DefaultListableBeanFactory中,实现了BeanDefinitionRegistry接口。
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
synchronized (this.beanDefinitionMap) {//是否存在同样名字
BeanDefinition oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!this.allowBeanDefinitionOverriding) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
" with a framework-generated bean definition ': replacing [" +
oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
else {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
}
else {//注冊bean。将bean的name放入List
this.beanDefinitionNames.add(beanName);
this.frozenBeanDefinitionNames = null;
}
//map中增加
this.beanDefinitionMap.put(beanName, beanDefinition);
}
resetBeanDefinition(beanName);
}
在使用的IoC容器的DefaultListableBeanFactory中已经建立了Bean的配置信息,并且这些BeanDefinition已经能够被容器使用,他们都在beanDefinitionMap中被检索和使用。容器的作用就是对这些信息进行维护和处理。
IoC容器的依赖注入
依赖注入是用户第一次向Ioc容器索要Bean的时候触发的。除非通过lazy-init控制Bean的记载时机。
从DefaultListableBeanFactory的基类AbstractBeanFactory的getBean方法開始查看实现。
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
return doGetBean(name, requiredType, null, false);
}
@Override
public Object getBean(String name, Object... args) throws BeansException {
return doGetBean(name, null, args, false);
}
查看doGetBean方法:
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
//从缓存入手。处理单例bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
if (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 + "'");
}
}
//对FactoryBean的处理,获取FactoryBean的相关实例。
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// Check if bean definition exists in this factory.
//减产是否已经存在相应的BeanDefinition对象
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
//依据Bean的名字获取BeanDefinition
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
//获取当前bean的全部依赖bean
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dependsOnBean : dependsOn) {
if (isDependent(beanName, dependsOnBean)) {
throw new BeanCreationException("Circular depends-on relationship between '" +
beanName + "' and '" + dependsOnBean + "'");
}
registerDependentBean(dependsOnBean, beanName);//注冊依赖bean
getBean(dependsOnBean);//获取bean的递归调用
}
}
// Create bean instance.
//创建单例bean
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {// property bean
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
String scopeName = mbd.getScope();//其它scope
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
}
});
bean = 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;
}
}
// Check if required type matches the type of the actual bean instance.
//检查bean的类型。
if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
try {
return 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;
}
这种方法就是依赖注入的入口,由于他触发了依赖注入。
虽然能够以最简单的方法来描写叙述Spring IoC容器,即Spring容器就是一个HashMap,通过HashMap来管理BeanDefinition对象。
在getBean()的时候,会触发createBean()来进创建须要的Bean对象。
终于的调用,到AbstractAutowireCapableBeanFactory的createBean()方法,代码例如以下:
/**
* Central method of this class: creates a bean instance,
* populates the bean instance, applies post-processors, etc.
* @see #doCreateBean
*/
@Override
protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
if (logger.isDebugEnabled()) {
logger.debug("Creating instance of bean '" + beanName + "'");
}
// Make sure bean class is actually resolved at this point.
//推断须要创建的Bean是否可实例化,这个类是否可通过类装载器来加载
resolveBeanClass(mbd, beanName);
// Prepare method overrides.
try {
mbd.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbd.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}
try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
//假设Bean配置了BeanPostProcessors,则返回代理对象
Object bean = resolveBeforeInstantiation(beanName, mbd);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
//创建bean
Object beanInstance = doCreateBean(beanName, mbd, args);
if (logger.isDebugEnabled()) {
logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
再查看doCreateBean()方法:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {//假设是单例的。则移除缓存中的同name bean
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {//创建Bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class<? > beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
mbd.postProcessed = true;
}
}
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
// Initialize the bean instance.初始化bean。通常在此处发生依赖注入
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
与依赖注入相关的两个方法:createBeanInstance和populateBean。
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
// Make sure bean class is actually resolved at this point.
//确认须要创建实例的类能够实例化
Class<?> beanClass = resolveBeanClass(mbd, beanName);
if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
}
//工厂方法实例化
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
// Shortcut when re-creating the same bean...
boolean resolved = false;
boolean autowireNecessary = false;
if (args == null) {
synchronized (mbd.constructorArgumentLock) {
if (mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
if (resolved) {
if (autowireNecessary) {
return autowireConstructor(beanName, mbd, null, null);
}
else {
return instantiateBean(beanName, mbd);
}
}
//构造函数实例化
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// 使用无參构造函数
return instantiateBean(beanName, mbd);
}
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
//使用CGLIB对bean进行实例化。
try {
Object beanInstance;
final BeanFactory parent = this;
if (System.getSecurityManager() != null) {
beanInstance = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
return getInstantiationStrategy().instantiate(mbd, beanName, parent);
}
}, getAccessControlContext());
}
else {
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
}
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
initBeanWrapper(bw);
return bw;
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
}
}
在Bean实例化后,就是相关的依赖关系。
依赖注入的发生是在BeanWrapper的setPropertyValues中实现。详细实现是在BeanWrapper的子类,BeanWrapperImpl中实现。
以上便是整个IoC过程创建Bean的总体思路。与书中相比,省略了部分代码。
ApplicationContext和Bean的初始化及销毁
ApplicationContext的启动是在AbstractApplicationContext中实现。
相同的销毁操作是在doClose()方法中完毕。
protected void doClose() {
boolean actuallyClose;
synchronized (this.activeMonitor) {
actuallyClose = this.active && !this.closed;
this.closed = true;
}
if (actuallyClose) {
if (logger.isInfoEnabled()) {
logger.info("Closing " + this);
}
LiveBeansView.unregisterApplicationContext(this);
try {
// Publish shutdown event.
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// Stop all Lifecycle beans, to avoid delays during individual destruction.
try {
getLifecycleProcessor().onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
// Destroy all cached singletons in the context's BeanFactory.
destroyBeans();
// Close the state of this context itself.
closeBeanFactory();
// Let subclasses do some final clean-up if they wish...
onClose();
synchronized (this.activeMonitor) {
this.active = false;
}
}
}
Bean的销毁和创建,Spring通过IoC管理Bean的生命周期来实现。
Spring中Bean的生命周期包括:
-1。Bean实例的创建。
-2,为Bean实例设置属性。
-3。调用Bean的初始化方法。
-4,通过IoC获取Bean。
-5,当容器关闭的时候调用bean的销毁方法。
总结
BeanDefinition的定位。对IoC容器来说,它为管理POJO直接的关系提供了帮助,但也要依据Spring的定义规则提供Bean定义信息。在Bean定义方面,Spring为用户提供了非常大的灵活性。在初始化过程中,首先须要定义到这些有效地Bean定义信息。这里Spring使用Resource接口来统一这些信息。而定位由ResourceLoader完毕。
容器的初始化。容器的初始化过程是在refresh()方法中完毕的。这个refresh()相当于容器的初始化函数。在初始化中,比較重要的就是对Bean信息的加载和注冊功能。
《Spring技术内幕》笔记-第二章 IoC容器的实现的更多相关文章
- Mysql技术内幕-笔记-第二章 数据类型
第二章 数据类型 Mysql中尽量不要使用UNSIGNED,因为可能会带来一些意想不到的效果 SHOW CREATE TABLE tablename\G;查看表的创建语句 ZEROFILL会将宽度小于 ...
- Spring技术内幕笔记2--我懒不写了哈哈哈哈。
目录 1.1 关于IOC容器设计的线路区别 1.1.1 BeanFactory 1.1.2 ApplicationContext 2.1 FileSystemXmlApplicationContext ...
- Spring技术内幕:SpringIOC原理学习总结
前一段时候我把Spring技术内幕的关于IOC原理一章看完,感觉代码太多,不好掌握,我特意又各方搜集了一些关于IOC原理的资料,特加深一下印象,以便真正掌握IOC的原理. IOC的思想是:Spring ...
- Java开发工程师(Web方向) - 04.Spring框架 - 第2章.IoC容器
第2章.IoC容器 IoC容器概述 abstract: 介绍IoC和bean的用处和使用 IoC容器处于整个Spring框架中比较核心的位置:Core Container: Beans, Core, ...
- 《spring技术内幕》读书笔记(1)——什么是POJO模式
今天在看<spring技术内幕>,第一章中多次提到了使用POJO来完成开发,就百度了一下,在此保留 1. 什么是POJO POJO的名称有多种,pure old java obje ...
- Spring技术内幕:设计理念和整体架构概述(转)
程序员都很崇拜技术大神,很大一部分是因为他们发现和解决问题的能力,特别是线上出现紧急问题时,总是能够快速定位和解决. 一方面,他们有深厚的技术基础,对应用的技术知其所以然,另一方面,在采坑的过程中不断 ...
- Servlet工作原理解析 《深入分析java web 技术内幕》第九章
参考关于servblet的相关文章 侧重概况:https://blog.csdn.net/levycc/article/details/50728921 ibm的相关:https://www.ibm. ...
- SQL Server技术内幕笔记合集
SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...
- Android群英传笔记——第二章:Android开发工具新接触
Android群英传笔记--第二章:Android开发工具新接触 其实这一章并没什么可讲的,前面的安装Android studio的我们可以直接跳过,如果有兴趣的,可以去看看Google主推-Andr ...
随机推荐
- asp.net mvc 最简单身份验证 [Authorize]通过的标准
[Authorize] public ContentResult Index2() { return Content("验证通过了"); } 经常能够看到某个Controler下的 ...
- fcc html5 css 练习3
行内样式看起来是这样的 <h1 style="color: green"> .pink-text { color: pink !important; } ...
- android学习之路资料集合
版权声明:本文为 stormzhang 原创文章,可以随意转载,但必须在明确位置注明出处!!! 这篇博客背后的故事 一路走来很不容易,刚好知乎上被人邀请回答如何自学android编程, 就借这个机会在 ...
- 支付宝小程序日期选择组件datePicker封装
github 地址 https://github.com/iocool/antminDatePicker 最近在做支付宝小程序(以下简称小程序)开发,发现小程序的日期选择组件很不好用,比如安卓和IOS ...
- Windows2008 Server 常规设置及基本安全策略
一.系统及程序 1.屏幕保护与电源 桌面右键--〉个性化--〉屏幕保护程序屏幕保护程序 选择无更改电源设置 选择高性能选择关闭显示器的时间 关闭显示器 选 从不 保存修改 2.安装IIS 管理工具-- ...
- 【技术累积】【点】【java】【21】序列化二三事
基础概念 把对象等转为二进制进行传输的是序列化,反之为反序列化: 应用场景一般为读写文件,传输数据/接口调用: Externalizable和Serializable java的序列化方式有两种: S ...
- CPU内部组成及原理
CPU,Central Processing Unit,翻译过来叫中央处理器.是一块超大规模的集成电路,是一台计算机的运算核心(Core)和控制核心( Control Unit).电脑中所有操作都由C ...
- Array.prototype.slice.call()的理解
最近在看廖雪峰的JS课程,浏览器中的操作DOM的那一章,有这样一道题. JavaScript Swift HTML ANSI C CSS DirectX <!-- HTML结构 --> & ...
- [nodejs]在mac环境下如何将node更新至最新?
在mac下安装angular-cli时,报出较多错误.初步怀疑是因为node环境版本过低导致. 在mac下,需要执行如下几步将node更新至最新版本,也可以更新到指定版本 1. sudo npm ca ...
- Tomcat启动失败--Several ports (8005, 8080, 8009)
启动Tomcat服务器报错: Several ports (8005, 8080, 8009) required by Tomcat v7.0 Server at localhost are alre ...