1,理解控制反轉 以前一直說著這個詞,然後把它等於上ioc這個詞,再等於上代碼里一個bean里依賴了其他bean,不用new,用註解,用xml去描述,就可以了。能用就行了,實際理論的不管也不影響編碼,其實能用了內心也是理解是怎麼回事的,知識理論上說不好而已。 我覺得只要理解一個事情就好了,ioc所謂 ...
1,理解控制反轉
以前一直說著這個詞,然後把它等於上ioc這個詞,再等於上代碼里一個bean里依賴了其他bean,不用new,用註解,用xml去描述,就可以了。能用就行了,實際理論的不管也不影響編碼,其實能用了內心也是理解是怎麼回事的,知識理論上說不好而已。 我覺得只要理解一個事情就好了,ioc所謂的控制翻轉,它控制翻轉的是什麼? 依賴對象的獲得被反轉 把一個bean中對其他bean的依賴這個事提取出來,統一由一個容器管理,解耦了bean管理和業務的代碼(工廠方法模式不就乾這事嘛) 最後,Spring提供了一個IoC容器來管理對象的生命周期、依賴關係,這也是spring的最核心的基礎組件。 其實控制反轉也是一種設計模式,的確,從項目層面抽取了管理bean依賴關係,徹底解耦bean註入和業務代碼,也是一種軟體開發中的思想。 2,IoC容器 它要完成什麼事? 通過解析bean依賴關係的描述,容器需要創建出項目中所有需要管理的bean,然後註入。 那麼拆成打的兩步:1,把依賴關係描述解析成bean;2,把bean註入 但是,既然是bean之間有依賴關係,當生產一個bean時,它依賴的bean也需要已經產生,所以在一個軟體中,觸發開始初始化容器的時候是把項目中用到的bean看成樹結構的樣子,然後一次遞歸的生產bean,註入。有點像目錄結構遍歷。 比如:A依賴B B依賴C,那麼A生產時會觸發B生產,然後觸發C生產。 所以,我們閱讀IoC源碼時也會看到這樣的實現。 3,IoC容器的實現 BeanFactory和ApplicationContext BeanFactory是容器基礎設計,ApplicationContext增加了針對企業軟體一些擴展功能,應該說後者是前者的高級形態。public interface BeanFactory { /** * 用來獲取FactoryBean本身的轉義符 */ String FACTORY_BEAN_PREFIX = "&"; /** * 獲取容器維護的bean是用名稱來指定的 */ Object getBean(String name) throws BeansException; <T> T getBean(String name, Class<T> requiredType) throws BeansException; <T> T getBean(Class<T> requiredType) throws BeansException; Object getBean(String name, Object... args) throws BeansException; <T> T getBean(Class<T> requiredType, Object... args) throws BeansException; /** * 查看容器中是否包含指定name的bean */ boolean containsBean(String name); /** * 判斷指定name的bean是否為單例 */ boolean isSingleton(String name) throws NoSuchBeanDefinitionException; /** * 判斷指定name的bean是否為多例 */ boolean isPrototype(String name) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException; Class<?> getType(String name) throws NoSuchBeanDefinitionException; /** * 獲得bean的所有別名 */ String[] getAliases(String name); }
BeanFactory設計定義了ioc的規範。
DefaultListableBeanFactory <— XmlBeanFactory DefaultListableBeanFactory有容器的完整實現,而XmlBeanFactory是對它的一種擴展,擴展什麼看名字就知道了,支持xml數據源的解析。我們看下XmlBeanFactory的代碼:public class XmlBeanFactory extends DefaultListableBeanFactory { /** * 換了一個reader */ private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } }
XmlBeanFactory在初始化時調用loadBeanDefinitions方法,使用XmlBeanDefinitionReader去解析元數據,這也是最初的一步。
FileSystemXmlApplicationContext源碼:
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext { public FileSystemXmlApplicationContext() { } public FileSystemXmlApplicationContext(ApplicationContext parent) { super(parent); } public FileSystemXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); } public FileSystemXmlApplicationContext(String... configLocations) throws BeansException { this(configLocations, true, null); } public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { this(configLocations, true, parent); } public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException { this(configLocations, refresh, null); } /** * setConfigLocations 將路徑字元串解析到自己存資源配置的configLocations里 * 然後調用refresh(),這也是容器初始化的入口 * */ public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } /** * 這是這個FileSystemXml自己的實現部分,getResourceByPath必然是哪一個父類模版方法需要調用的方法 * 而這個方法自己實現提供一個FileSystemResource出來。 * Resource這個就是將元數據轉化成容器可以統一可以解析的資源,根據不同的元數據類型,必然需要不同的演算法去讀取這些數據。 */ @Override protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); } }
調用AbstractApplicationContext的refresh方法:
標準的模版方法,這裡註意繼承鏈路,嵌套的模版方法調用下來。
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. // 擴展點1 invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. // 擴展點2 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; } } }
調用AbstractApplicationContext 的obtainFreshBeanFactory方法:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { // 這要調用子類的實現=>AbstractRefreshableApplicationContext refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
AbstractRefreshableApplicationContext的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); } }
AbstractXmlApplicationContext的loadBeanDefinitions
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. // 指定Reader 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); } protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
XmlBeanDefinitionReader繼承的基類AbstractBeanDefinitionReader的實現:
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); int counter = 0; for (String location : locations) { counter += loadBeanDefinitions(location); } return counter; } public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); } public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int counter = 0; for (Resource resource : resources) { counter += loadBeanDefinitions(resource); } return counter; } /* *最終調用到的實現,這裡需要產生Resource,既然用FileSystemXmlApplicationContext * 那在讀取文件產生Resource的過程有它自定義的部分,也就是我們在FileSystemXmlApplicationContext中看到的getResourceByPath會被使用。 */ 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了 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); // 這裡是解析Resource的入口 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; } }
回到AbstractApplicationContext調用getResources方法,resourcePatternResolver用預設的PathMatchingResourcePatternResolver
public Resource[] getResources(String locationPattern) throws IOException { return this.resourcePatternResolver.getResources(locationPattern); } public AbstractApplicationContext() { this.resourcePatternResolver = getResourcePatternResolver(); } //預設PathMatchingResourcePatternResolver protected ResourcePatternResolver getResourcePatternResolver() { return new PathMatchingResourcePatternResolver(this); }
PathMatchingResourcePatternResolver的getResources實現
public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); // 一種是classpath開頭 一種沒有 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 // 使用ResourceLoader來產生Resource return new Resource[] {getResourceLoader().getResource(locationPattern)}; } } } // 預設使用DefaultResourceLoader來解析路徑 public PathMatchingResourcePatternResolver(ClassLoader classLoader) { this.resourceLoader = new DefaultResourceLoader(classLoader); } public ResourceLoader getResourceLoader() { return this.resourceLoader; }
DefaultResourceLoader的getResource,而此時返回的Resource是FileSystemResource類型
public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); if (location.startsWith("/")) { // 最終調用子類的getResourceByPath,而這個方法正是FileSystemXmlApplicationContext實現的 return getResourceByPath(location); } else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // Try to parse the location as a URL... URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. return getResourceByPath(location); } } }
可見FileSystemXmlApplicationContext最終體現在產生的Resource不同實現。
Resource相當於定位到資源的抽象,下一步就是不同Resource解析資源。
回到AbstractBeanDefinitionReader的loadBeanDefinitions。定位後下一步就是解析Resource的入口:int loadCount = loadBeanDefinitions(resource);
子類XmlBeanDefinitionReader實現:
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 { // 終於到了java讀取文件熟悉的InputStream了,Recoure->InputStream InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 繼續解析:InputStream -> Document 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(); } } }
調用到DefaultDocumentLoader實現解析xml
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // 繼續解析:InputStream -> Document Document doc = doLoadDocument(inputSource, resource); // 轉化:Document->BeanDefinition // 顯然,從一個Document變成一個個bean的描述(BeanDefinition),就涉及到我們在使用spring時經常用到的bean定義時的各種配置規則,如果查看源碼,也許會更好的理解。 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); } } // 具體documentLoader來實現,DefaultDocumentLoader實現解析xml protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
DefaultDocumentLoader使用JAXP方式解析:
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); } DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource); } protected DocumentBuilder createDocumentBuilder( DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler) throws ParserConfigurationException { DocumentBuilder docBuilder = factory.newDocumentBuilder(); if (entityResolver != null) { docBuilder.setEntityResolver(entityResolver); } if (errorHandler != null) { docBuilder.setErrorHandler(errorHandler); } return docBuilder; }
registerBeanDefinitions方法:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); //BeanDefinitionDocumentReader具體實現 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
BeanDefinitionDocumentReader具體實現:
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); } protected void doRegisterBeanDefinitions(Element root) { // Any nested <beans> elements will cause recursion in this method. In // order to propagate and preserve <beans> default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. BeanDefinitionParserDelegate parent = this.delegate; // 指定好BeanDefinitionParserDelegate 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)) { return; } } } //可擴展入口 preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; } 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); } } //標簽解析 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { //import importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { //alias processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { //bean processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // beans recurse doRegisterBeanDefinitions(ele); } }
如果是beans的標簽則進行了遞歸,查看解析bean標簽的代碼:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // 終於是解析入口了,Element->BeanDefinitionHolder BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. 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)); } } public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return parseBeanDefinitionElement(ele, null); }
這裡的BeanDefinitionHolder包裝了BeanDefinition以及bean和alias:
private final BeanDefinition beanDefinition; private final String beanName; private final String[] aliases;
BeanDefinitionReaderUtils.registerBeanDefinition:
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { // bean id String id = ele.getAttribute(ID_ATTRIBUTE); // bean name String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<String>(); 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); } // beanDefinition產生 此時已經解析了bean中的其他欄位 AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); 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; }
拿到holder後進行註冊:
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { //<