首先Web项目使用Spring是通过在web.xml里面配置
org.springframework.web.context.ContextLoaderListener初始化IOC容器的。

<listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

那就以此为切入点顺藤摸瓜。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener

ContextLoaderListener继承了ContextLoader,并且实现ServletContextListener接口。当Server容器(一般指tomcat)启动时,会收到事件初始化。

/*** Initialize the root web application context.*/
@Override
public void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());
}

initWebApplicationContext方法是在org.springframework.web.context.ContextLoader类里面。方法太长,分段读一下。

/*** Initialize Spring's web application context for the given servlet context,* using the application context provided at construction time, or creating a new one* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.* @param servletContext current servlet context* @return the new WebApplicationContext* @see #ContextLoader(WebApplicationContext)* @see #CONTEXT_CLASS_PARAM* @see #CONFIG_LOCATION_PARAM*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " +"check whether you have multiple ContextLoader* definitions in your web.xml!");}Log logger = LogFactory.getLog(ContextLoader.class);servletContext.log("Initializing Spring root WebApplicationContext");if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");}long startTime = System.currentTimeMillis();try {// Store context in local instance variable, to guarantee that// it is available on ServletContext shutdown.if (this.context == null) {this.context = createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent ->// determine parent for root web application context, if any.ApplicationContext parent = loadParentContext(servletContext);cwac.setParent(parent);}configureAndRefreshWebApplicationContext(cwac, servletContext);}}servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {currentContext = this.context;}else if (ccl != null) {currentContextPerThread.put(ccl, this.context);}if (logger.isDebugEnabled()) {logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");}if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");}return this.context;}catch (RuntimeException ex) {logger.error("Context initialization failed", ex);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throw ex;}catch (Error err) {logger.error("Context initialization failed", err);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);throw err;}
}

首先是判断servletContext中是否已经注册了WebApplicationContext,如果有则抛出异常,避免重复注册。然后就是启用log,启动计时。本方法的关键就在于try代码块里的内容

这里面有几个关键的方法。首先看一下createWebApplicationContext()

/*** Instantiate the root WebApplicationContext for this loader, either the* default context class or a custom context class if specified.* <p>This implementation expects custom contexts to implement the* {@link ConfigurableWebApplicationContext} interface.* Can be overridden in subclasses.* <p>In addition, {@link #customizeContext} gets called prior to refreshing the* context, allowing subclasses to perform custom modifications to the context.* @param sc current servlet context* @return the root WebApplicationContext* @see ConfigurableWebApplicationContext*/
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {Class<?> contextClass = determineContextClass(sc);if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Custom context class [" + contextClass.getName() +"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");}return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

首先determineContextClass()方法查明具体的Context类,他会读取servletContext的初始化参数contextClass,此参数我们一半不配置,所以Spring就会读取跟org.springframework.web.context.WebApplicationContext同一个包下面的ContextLoader.properties文件读取默认设置,反射出org.springframework.web.context.support.XmlWebApplicationContext类来。接下来就是在configureAndRefreshWebApplicationContext()方法里将新创建的XmlWebApplicationContext进行初始化。首先会设置一个默认ID,即org.springframework.web.context.WebApplicationContext:+你项目的ContextPath。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {// The application context id is still set to its original default value// -> assign a more useful id based on available informationString idParam = sc.getInitParameter(CONTEXT_ID_PARAM);if (idParam != null) {wac.setId(idParam);}else {// Generate default id...wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(sc.getContextPath()));}}wac.setServletContext(sc);String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);if (configLocationParam != null) {wac.setConfigLocation(configLocationParam);}// The wac environment's #initPropertySources will be called in any case when the context// is refreshed; do it eagerly here to ensure servlet property sources are in place for// use in any post-processing or initialization that occurs below prior to #refreshConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(sc, null);}customizeContext(sc, wac);wac.refresh();
}

紧接着就是将ServletContext设置成XmlWebApplicationContext的属性,这样Spring就能在上下文里轻松拿到ServletContext了。

wac.setServletContext(sc);

接下来就是读取web.xml文件中的contextConfigLocation参数。如果没有配置就会去读WEB-INF下的applicationContext.xml文件。

<context-param><param-name>contextConfigLocation</param-name><param-value>classpath:beans.xml</param-value>
</context-param>

并将值设置(就是我们的Spring配置文件的路径)进XmlWebApplicationContext中。然后就会在指定的路径加载配置文件。

String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {wac.setConfigLocation(configLocationParam);
}

接下来就是customizeContext(sc, wac)方法,此方法会根据用户配置的globalInitializerClasses参数来初始化一些用户自定义的属性,一般我们不配置,所以这里什么也不做。

最后登场的就是最核心的方法了,

wac.refresh();

在这个方法里,会完成资源文件的加载、配置文件解析、Bean定义的注册、组件的初始化等核心工作,我们一探究竟。

@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.
        prepareRefresh();// Tell the subclass to refresh the internal bean factory.ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);// Initialize message source for this context.
            initMessageSource();// Initialize event multicaster for this context.
            initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.
            onRefresh();// Check for listener beans and register them.
            registerListeners();// Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.
            finishRefresh();}catch (BeansException ex) {// Destroy already created singletons to avoid dangling resources.
            destroyBeans();// Reset 'active' flag.
            cancelRefresh(ex);// Propagate exception to caller.throw ex;}}
}

此方法是同步的,避免重复刷新,每个步骤都放在单独的方法内,流程清晰,是值得学习的地方。这里面有个重要的方法是finishBeanFactoryInitialization(beanFactory);,里面的内容是Spring如何实例化bean,并注入依赖的,这个内容下一节讲,本节只说明Spring是如何加载class文件的。

首先就是prepareRefresh()方法。

/*** Prepare this context for refreshing, setting its startup date and* active flag as well as performing any initialization of property sources.*/
protected void prepareRefresh() {this.startupDate = System.currentTimeMillis();this.closed.set(false);this.active.set(true);if (logger.isInfoEnabled()) {logger.info("Refreshing " + this);}// Initialize any placeholder property sources in the context environment
    initPropertySources();// Validate that all properties marked as required are resolvable// see ConfigurablePropertyResolver#setRequiredProperties
    getEnvironment().validateRequiredProperties();// Allow for the collection of early ApplicationEvents,// to be published once the multicaster is available...this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}

此方法做一些准备工作,如记录开始时间,输出日志,initPropertySources();和getEnvironment().validateRequiredProperties();一般没干什么事。

接下来就是初始化BeanFactory,是整个refresh()方法的核心,其中完成了配置文件的加载、解析、注册

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

看看它里面都做了些什么?

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

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

我们看到会创建一个DefaultListableBeanFactory实例

DefaultListableBeanFactory beanFactory = createBeanFactory();

再设置一个ID

beanFactory.setSerializationId(getId());

然后设置一些自定义参数:

customizeBeanFactory(beanFactory);

这里面最重要的就是loadBeanDefinitions(beanFactory);方法了。

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

此方法会通过XmlBeanDefinitionReader加载bean定义。具体的实现方法是在org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions方法中定义的。这里设计了层层调用,有好多重载方法,主要就是加载Spring所有的配置文件(可能会有多个),以备后面解析,注册之用。我一路追踪到org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(Element root)

/*** Register each bean definition within the given root {@code <beans/>} element.*/
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;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;
}

这里创建了一个BeanDefinitionParserDelegate示例,解析XML的过程就是委托它完成的,我们不关心它是怎样解析XML的,我们只关心是怎么加载类的,所以就要看parseBeanDefinitions(root, this.delegate)方法了。

/*** Parse the elements at the root level in the document:* "import", "alias", "bean".* @param root the DOM root element of the document*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {NodeList nl = root.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (node instanceof Element) {Element ele = (Element) node;if (delegate.isDefaultNamespace(ele)) {parseDefaultElement(ele, delegate);}else {delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}
}

我们看到最终解析XML元素的是delegate.parseCustomElement(ele)方法,最终会走到一下方法.

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {String namespaceUri = getNamespaceURI(ele);NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

这里会根据不同的XML节点,会委托NamespaceHandlerSupport找出合适的BeanDefinitionParser,如果我们配置了

<context:component-scanbase-package="com.geeekr.service,com.geeekr.dao" />

那么对应BeanDefinitionParser就是org.springframework.context.annotation.ComponentScanBeanDefinitionParser,来看看它的parse方法。

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);// Actually scan for bean definitions and register them.ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);registerComponents(parserContext.getReaderContext(), beanDefinitions, element);return null;
}

不难看出这里定义了一个ClassPathBeanDefinitionScanner,通过它去扫描包中的类文件,注意:这里是类文件而不是类,因为现在这些类还没有被加载,只是ClassLoader能找到这些class的路径而已。到目前为止,感觉真想距离我们越来越近了。顺着继续往下摸。进入doSacn方法里,映入眼帘的又是一大坨代码,但是我们只关心观点的部分。

/*** Perform a scan within the specified base packages,* returning the registered bean definitions.* <p>This method does <i>not</i> register an annotation config processor* but rather leaves this up to the caller.* @param basePackages the packages to check for annotated classes* @return set of beans registered if any for tooling registration purposes (never {@code null})*/
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;
}

一眼就能看出是通过

Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

有时候不得不佩服这些外国人起名字的功力,把扫描出来的类叫做candidates(候选人);真是不服不行啊,这种名字真的很容易理解有不有?哈哈,貌似扯远了。继续往下看。

/*** Scan the class path for candidate components.* @param basePackage the package to check for annotated classes* @return a corresponding Set of autodetected bean definitions*/
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;
}

先看这两句:

String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + "/" + this.resourcePattern;

假设我们配置的需要扫描的包名为com.geeekr.service,那么packageSearchPath的值就是classpath*:com.geeekr.service/**/*.class,意思就是com.geeekr.service包(包括子包)下所有class文件;如果配置的是*,那么packageSearchPath的值就是classpath*:*/**/*.class。这里的表达式是Spring自己定义的。Spring会根据这种表达式找出相关的class文件。

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

这一句就把相关class文件加载出来了,那我们就要看看,Spring究竟是如何把class文件找到的了。首先看看resourcePatternResolver的定义:这一句就把相关class文件加载出来了,那我们就要看看,Spring究竟是如何把class文件找到的了。首先看看resourcePatternResolver的定义:

private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

进入getResources方法进入getResources方法

@Override
public Resource[] getResources(String locationPattern) throws IOException {Assert.notNull(locationPattern, "Location pattern must not be null");if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {// a class path resource (multiple resources for same name possible)if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {// a class path resource patternreturn findPathMatchingResources(locationPattern);}else {// all class path resources with the given namereturn 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 patternreturn findPathMatchingResources(locationPattern);}else {// a single resource with the given namereturn new Resource[] {getResourceLoader().getResource(locationPattern)};}}
}

这里会先判断表达式是否以classpath*:开头。前面我们看到Spring已经给我们添加了这个头,这里当然符合条件了。接着会进入findPathMatchingResources方法。在这里又把**/*.class去掉了,然后在调用getResources方法,然后在进入findAllClassPathResources方法。这里的参数只剩下包名了例如com/geeekr/service/。

protected Resource[] findAllClassPathResources(String location) throws IOException {String path = location;if (path.startsWith("/")) {path = path.substring(1);}ClassLoader cl = getClassLoader();Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));Set<Resource> result = new LinkedHashSet<Resource>(16);while (resourceUrls.hasMoreElements()) {URL url = resourceUrls.nextElement();result.add(convertClassLoaderURL(url));}return result.toArray(new Resource[result.size()]);
}

真相大白了,Spring也是用的ClassLoader加载的class文件。一路追踪,原始的ClassLoader是Thread.currentThread().getContextClassLoader();。到此为止,就拿到class文件了。
Spring会将class信息封装成BeanDefinition,然后再放进DefaultListableBeanFactory的beanDefinitionMap中。

拿到了class文件后,就要看看Spring是如何装配bean的了,下一节,继续看。

原文链接:

http://geeekr.com/read-spring-source-1-how-to-load-bean/

看看Spring的源码(一)——Bean加载过程相关推荐

  1. spring源码之bean加载(bean解析下篇)

    bean的加载步骤: MyTestBean bean = (MyTestBean) bf.getBean("myTestBean"); 步骤: (1) 转换对应的beanName ...

  2. Spring源码分析4---IOC加载过程补充总结

    原文出自:http://cmsblogs.com IOC 之 获取验证模型 DTD 与 XSD 的区别 DTD(Document Type Definition),即文档类型定义,为 XML 文件的验 ...

  3. springboot集成mybatis源码分析-启动加载mybatis过程(二)

    springboot集成mybatis源码分析-启动加载mybatis过程(二) 1.springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplicati ...

  4. 【Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  5. Mybatis3源码分析(05)-加载Configuration-加载MappedStatement

    2019独角兽企业重金招聘Python工程师标准>>> Mybatis3源码分析(05)-加载Configuration-加载MappedStatement 博客分类: java m ...

  6. Spring 使用注解@DependsOn控制Bean加载顺序

    文章目录 1. 前言 2. 代码实现 1. 前言 默认情况下,Spring加载Bean的顺序是不确定的(或者可以理解为,按编译后的class文件顺序加载).当我们需要控制Bean加载顺序以满足特定的需 ...

  7. Launcher3源码分析(LauncherModel加载数据)

    LauncherModel继承BroadcastReceiver,显然是一个广播接收者.在上一篇Launcher的启动中讲到桌面数据的加载工作是在LauncherModel中执行的,那么它是如何加载数 ...

  8. spring bean加载过程_Spring源码剖析3:Spring IOC容器的加载过程

    本文转自五月的仓颉 https://www.cnblogs.com/xrq730 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https ...

  9. Spring boot 源码:Bean的Scope

    1.美图 2.概述 Scope描述的是Spring容器如何创建新Bean的实例.Spring的Scope有以下几种,通过@Scope注解来实现. Singleton:一个Spring容器中只有一个Be ...

最新文章

  1. 解决手机訪问站点时总体相对屏幕缩小问题?(已解决)
  2. Zookeeper在yarn框架中如何实现避免脑裂的?
  3. 最全的“大数据”学习资源
  4. springboot 多数据源_SpringBoot整合多数据源的巨坑一
  5. php漂亮按钮代码,分享一款金属感十足的按钮样式代码
  6. linux源代码剖析之三fs
  7. 物联网(lot)特定场景最佳实践
  8. VS2019 OpenCV安装与配置教程
  9. Win10电脑不能读取U盘怎么办?不识别U盘怎么解决?
  10. html表格可视化设计器,基于vue-element-ui的一款表格设计器table-making
  11. 12-【istio】-【流量管理】-【流量管理原理】istio Sidecar的两种注入方式、注入原理
  12. 2003英语单词四6级大学六级CET6资料
  13. Sutton and Barto 教材中多臂老虎机(k-armed bandit testbed)模拟
  14. 1024程序员节来了,
  15. Linux内核和用户空间通信的方法
  16. 感谢有你!Apache DolphinScheduler 项目 GitHub star 突破 8k
  17. DTU的功能以及性能
  18. html5 ajax 文件上传,html5+FormData 实现ajax文件上传
  19. Uncaughr SyntaxError:Ivalid or unexpected toke(JS)
  20. 基于微信企业公司小程序设计与实现开题答辩PPT

热门文章

  1. 注意!你的 Navicat 可能被投毒了...
  2. 将Bean放入Spring容器中的五种方式
  3. 太牛了!22岁本科生Github上开源的后台管理系统,太实用(附源码)!
  4. 你这代码写得真丑,满屏的try-catch,全局异常处理不会吗?
  5. 如何优雅的使用和理解线程池
  6. 脱口秀演员入职华为?破案了:人家还是博士后研究员
  7. Transformer大升级!谷歌、OpenAI联合推出分层模型,刷榜ImageNet32刷新SOTA
  8. Python之父:Python 4.0可能不会来了
  9. 理解卷积神经网络中的自注意力机制
  10. 几篇较新的计算机视觉Self-Attention