对Spring有所了解的读者应该知道,Spring的两大核心分别为IOC和AOP。本篇带领大家来分析下 Spring 的核心之一:IOC 容器,帮助大家排查应用中和 Spring 相关的一些问题。

本文采用的Spring源码版本为5.2.2.RELEASE。为了降低难度,本文所说的内容都是基于 xml 的配置的方式,实际使用已经很少人这么做了,至少不是纯 xml 配置,不过从理解源码的角度来看用这种方式是比较合适的。

阅读建议:读者至少需要知道怎么配置 Spring,了解 Spring 中的基础概念,少部分内容我还假设读者使用过 SpringMVC。本文要说的 IOC 总体来说有两处地方最重要,一个是创建 Bean 容器,一个是初始化 Bean,如果读者觉得一次性看完本文压力有点大,那么可以按这个思路分两次消化。读者不一定对 Spring 容器的源码感兴趣,也许附录部分介绍的知识对读者有些许作用。

希望通过本文可以让读者不惧怕阅读 Spring 源码,也希望大家能反馈表述错误或不合理的地方。

引言

使用xml的方式启动spring容器:

public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
}

以上代码可利用xml配置文件启动spring容器,使用Maven结果的小伙伴需要先添加pom依赖。建议读者尽量动手实操,学习效果会更佳;同时也不建议一次性把spring相关的依赖全配置进来。

<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.2.RELEASE</version>
</dependency>

以上spring-context会将spring-aop、spring-beans、spring-core、spring-expression、spring-jcl基础包依赖进来。

继续看代码,ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml"),除了ClassPathXmlApplicationContext,我们还有其他选择来启动容器:

我们先关注颜色高亮的几个接口和类:

ClassPathXmlApplicationContext:构造函数需要一个 xml 配置文件在classpath路径

FileSystemXmlApplicationContext:构造函数需要一个 xml 配置文件在系统中的路径,其他和 ClassPathXmlApplicationContext 基本上一样。

AnnotationConfigApplicationContext 是基于注解来使用的,它不需要配置文件,采用 java 配置类和各种注解来配置,是比较简单的方式,也是大势所趋吧。

不过本文旨在帮助大家理解整个构建流程,所以决定使用 ClassPathXmlApplicationContext 进行分析,先来看个简单例子。

首先,定义一个接口:

public interface MessageService {String getMessage();
}

定义接口实现类:

public class MessageServiceImpl implements MessageService {public String getMessage() {return "hello world";}
}

接下来,我们在 resources 目录新建一个配置文件,文件名随意,通常叫 application.xml 或 application-xxx.xml 就可以了:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byName"><bean id="messageService" class="com.javadoop.example.MessageServiceImpl"/>
</beans>

这样,我们就可以跑起来了:

public class App {public static void main(String[] args) {// 用我们的配置文件来启动一个 ApplicationContextApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");System.out.println("context 启动成功");// 从 context 中取出我们的 Bean,而不是用 new MessageServiceImpl() 这种方式MessageService messageService = context.getBean(MessageService.class);// 这句将输出: hello worldSystem.out.println(messageService.getMessage());}
}

以上例子很简单,也足够引出本文的主题了,就是怎么样通过配置文件来启动 Spring 的 ApplicationContext?也就是我们今天要分析的 IOC 的核心了。ApplicationContext 启动过程中,会负责创建实例 Bean,往各个 Bean 中注入依赖等。

BeanFactory 简介

说到ApplicationContext,我们不得不介绍下它的父接口BeanFactory。从名字上理解,它应该是一个生产bean的工厂,或者说它负责管理和创建各种Bean。前面的说的ApplicationContext其实就是个BeanFactory,我们先看下他们的关系:

ApplicationContext 往下的继承结构前面一张图已经说过了,这里说往上的结构。这张图呢,背下来肯定是不需要的,有几个重点和大家说明下就好。

  1. ApplicationContext 继承了 ListableBeanFactory,这个 Listable 的意思就是,通过这个接口,我们可以获取多个 Bean,大家看源码会发现,最顶层 BeanFactory 接口的方法都是获取单个 Bean 的。

  2. ApplicationContext 继承了 HierarchicalBeanFactory,Hierarchical 单词本身已经能说明问题了,也就是说我们可以在应用中起多个 BeanFactory,然后可以将各个 BeanFactory 设置为父子关系。

  3. AutowireCapableBeanFactory 这个名字中的 Autowire 大家都非常熟悉,它就是用来自动装配 Bean 用的,但是仔细看上图,ApplicationContext 并没有继承它,不过不用担心,不使用继承,不代表不可以使用组合,如果你看到 ApplicationContext 接口定义中的最后一个方法 getAutowireCapableBeanFactory() 就知道了。

  4. ConfigurableListableBeanFactory 也是一个特殊的接口,看图,特殊之处在于它继承了第二层所有的三个接口,而 ApplicationContext 没有。这点之后会用到。

  5. 请先不用花时间在其他的接口和类上,先理解我说的这几点就可以了。

然后,请读者打开编辑器,翻一下 BeanFactory、ListableBeanFactory、HierarchicalBeanFactory、AutowireCapableBeanFactory、ApplicationContext 这几个接口的代码,大概看一下各个接口中的方法,大家心里要有底,限于篇幅,我就不贴代码介绍了。

启动过程

下面将会是冗长的代码分析,记住,一定要自己打开源码来看,不然纯看是很累的。

第一步,我们肯定要从 ClassPathXmlApplicationContext 的构造方法说起。

public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {private Resource[] configResources;// 如果已经有 ApplicationContext 并需要配置成父子关系,那么调用这个构造方法public ClassPathXmlApplicationContext(ApplicationContext parent) {super(parent);}...public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)throws BeansException {super(parent);// 根据提供的路径,处理成配置文件数组(以分号、逗号、空格、tab、换行符分割)setConfigLocations(configLocations);if (refresh) {refresh(); // 核心方法}}...
}

接下来,就是 refresh(),这里简单说下为什么是 refresh(),而不是 init() 这种名字的方法。因为 ApplicationContext 建立起来以后,其实我们是可以通过调用 refresh() 这个方法重建的,refresh() 会将原来的 ApplicationContext 销毁,然后再重新执行一次初始化操作。

往下看,refresh() 方法里面调用了那么多方法,就知道肯定不简单了,请读者先看个大概,细节之后会详细说。

    @Overridepublic void refresh() throws BeansException, IllegalStateException {//加锁,避免refresh和destory方法同时执行synchronized (this.startupShutdownMonitor) {// 准备刷新 contextprepareRefresh();// 创建 beanFactory = DefaultListableBeanFactoryConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 准备 bean factory  注册一些特殊的beanprepareBeanFactory(beanFactory);try {// 预留给BeanFactoryPostProcessor的具体实现类扩展(此处为空实现)// 此时bean已加载、注册,但未实例化postProcessBeanFactory(beanFactory);// 激活各种BeanFactory处理器invokeBeanFactoryPostProcessors(beanFactory);// 注册BeanPostProcessor处理器,拦截Bean创建registerBeanPostProcessors(beanFactory);// 初始化消息源initMessageSource();//初始化事件广播器initApplicationEventMulticaster();// 预留给特殊的bean初始化(此处为空实现,在初始化 singleton beans 之前)onRefresh();// 注册事件监听器(实现 ApplicationListener 接口)registerListeners();// 初始化所有剩余的bean(除延迟加载的外)finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// clear已经创建好的单例bean,释放资源destroyBeans();//重置active标志为falsecancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...// 重置Spring的公共反射元数据缓存,反射、注解、解析类型、类加载器resetCommonCaches();}}}

下面,我们开始来肢解这个 refresh() 方法。

创建 Bean 容器前的准备工作prepareRefresh()

protected void prepareRefresh() {// Switch to active.this.startupDate = System.currentTimeMillis();this.closed.set(false);this.active.set(true);if (logger.isDebugEnabled()) {if (logger.isTraceEnabled()) {logger.trace("Refreshing " + this);}else {logger.debug("Refreshing " + getDisplayName());}}//初始化属性源,留给子类覆盖initPropertySources();// 验证必要的属性是否放入环境中,没有则抛异常// 例如工程师自定义设置VAR,用户如果没有在环境变量中设置VAR,则程序不能继续运行getEnvironment().validateRequiredProperties();// 存储 ApplicationListeners...if (this.earlyApplicationListeners == null) {this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);}else {// Reset local application listeners to pre-refresh state.this.applicationListeners.clear();this.applicationListeners.addAll(this.earlyApplicationListeners);}// 准备ApplicationEventsthis.earlyApplicationEvents = new LinkedHashSet<>();}

创建 Bean 容器obtainFreshBeanFactory()

注意,这个方法是全文最重要的部分之一,这里将会初始化 BeanFactory、加载 Bean、注册 Bean 等等。

当然,这步结束后,Bean 并没有完成初始化。这里指的是 Bean 实例并未在这一步生成。

//AbstractApplicationContext.java 638

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {//刷新BeanFactoryrefreshBeanFactory();return getBeanFactory();}

//AbstractRefreshableApplicationContext.java 124

protected final void refreshBeanFactory() throws BeansException {//当前applicationContext中是否有BeanFactoryif (hasBeanFactory()) {//销毁bean,调用destroy,再clear相关容器destroyBeans();//关闭BeanFactory(置空)closeBeanFactory();}try {//创建DefaultListableBeanFactoryDefaultListableBeanFactory beanFactory = createBeanFactory();//getId() 当前applicationContext的对象地址//org.springframework.context.support.ClassPathXmlApplicationContext@1efbd816beanFactory.setSerializationId(getId());//设置bean是否允许被覆盖、是否允许循环引用customizeBeanFactory(beanFactory);//加载bean到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);}}

看到这里的时候,我觉得读者就应该站在高处看 ApplicationContext 了,ApplicationContext 继承自 BeanFactory,但是它不应该被理解为 BeanFactory 的实现类,而是说其内部持有一个实例化的 BeanFactory(DefaultListableBeanFactory)。以后所有的 BeanFactory 相关的操作其实是委托给这个实例来处理的。

为什么选择实例化 DefaultListableBeanFactory ?把之前的继承图再拿过来大家再仔细看一下:

我们可以看到 ConfigurableListableBeanFactory 只有一个实现类 DefaultListableBeanFactory,而且 DefaultListableBeanFactory 还通过实现右边的 AbstractAutowireCapableBeanFactory 通吃了右路。所以结论就是,DefaultListableBeanFactory 基本上是最牛的 BeanFactory 了,这也是为什么这边会使用这个类来实例化的原因。

在继续往下之前,我们需要先了解 BeanDefinition。我们说 BeanFactory 是 Bean 容器,那么 Bean 又是什么呢?

这里的 BeanDefinition 就是我们所说的 Spring 的 Bean,我们自己定义的各个 Bean 其实会转换成一个个 BeanDefinition 存在于 Spring 的 BeanFactory 中。

所以,如果有人问你 Bean 是什么的时候,你要知道 Bean 在代码层面上可以认为是 BeanDefinition 的实例。

BeanDefinition 中保存了我们的 Bean 信息,比如这个 Bean 指向的是哪个类、是否是单例的、是否懒加载、这个 Bean 依赖了哪些 Bean 等等。

BeanDefinition 接口定义

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {// 默认只提供 sington 和 prototype 两种,// 很多读者可能知道还有 request, session, globalSession, application, websocket 这几种,// 不过,它们属于基于 web 的扩展。String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;// 比较不重要,直接跳过吧int ROLE_APPLICATION = 0;int ROLE_SUPPORT = 1;int ROLE_INFRASTRUCTURE = 2;// 设置父 Bean,这里涉及到 bean 继承,不是 java 继承。请参见附录的详细介绍// 一句话就是:继承父 Bean 的配置信息而已void setParentName(String parentName);// 获取父 BeanString getParentName();// 设置 Bean 的类名称,将来是要通过反射来生成实例的void setBeanClassName(String beanClassName);// 获取 Bean 的类名称String getBeanClassName();// 设置 bean 的 scopevoid setScope(String scope);String getScope();// 设置是否懒加载void setLazyInit(boolean lazyInit);boolean isLazyInit();// 设置该 Bean 依赖的所有的 Bean,注意,这里的依赖不是指属性依赖(如 @Autowire 标记的),// 是 depends-on="" 属性设置的值。用于指定初始化顺序void setDependsOn(String... dependsOn);// 返回该 Bean 的所有依赖String[] getDependsOn();// 设置该 Bean 是否可以注入到其他 Bean 中,只对根据类型注入有效,// 如果根据名称注入,即使这边设置了 false,也是可以的void setAutowireCandidate(boolean autowireCandidate);// 该 Bean 是否可以注入到其他 Bean 中boolean isAutowireCandidate();// 主要的。同一接口的多个实现,如果不指定名字的话,Spring 会优先选择设置 primary 为 true 的 beanvoid setPrimary(boolean primary);// 是否是 primary 的boolean isPrimary();// 如果该 Bean 采用工厂方法生成,指定工厂名称。对工厂不熟悉的读者,请参加附录// 一句话就是:有些实例不是用反射生成的,而是用工厂模式生成的void setFactoryBeanName(String factoryBeanName);// 获取工厂名称String getFactoryBeanName();// 指定工厂类中的 工厂方法名称void setFactoryMethodName(String factoryMethodName);// 获取工厂类中的 工厂方法名称String getFactoryMethodName();// 构造器参数ConstructorArgumentValues getConstructorArgumentValues();// Bean 中的属性值,后面给 bean 注入属性值的时候会说到MutablePropertyValues getPropertyValues();// 是否 singletonboolean isSingleton();// 是否 prototypeboolean isPrototype();// 如果这个 Bean 是被设置为 abstract,那么不能实例化,// 常用于作为 父bean 用于继承,其实也很少用......boolean isAbstract();int getRole();String getDescription();String getResourceDescription();BeanDefinition getOriginatingBeanDefinition();
}

有了 BeanDefinition 的概念以后,我们再往下看 refreshBeanFactory() 方法中的剩余部分:

customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);

customizeBeanFactory

customizeBeanFactory(beanFactory) 比较简单,就是配置是否允许 BeanDefinition 覆盖、是否允许循环引用。

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {if (this.allowBeanDefinitionOverriding != null) {// 是否允许 Bean 定义覆盖beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}if (this.allowCircularReferences != null) {// 是否允许 Bean 间的循环依赖beanFactory.setAllowCircularReferences(this.allowCircularReferences);}
}

默认情况下,Spring 允许循环依赖。但如果你在 A 的构造方法中依赖 B,在 B 的构造方法中依赖 A 是不行的。

加载 Bean: loadBeanDefinitions

接下来是最重要的 loadBeanDefinitions(beanFactory) 方法了,这个方法将根据配置,加载各个 Bean,然后放到 BeanFactory 中。

读取配置的操作在 XmlBeanDefinitionReader 中,其负责加载配置、解析。

// AbstractXmlApplicationContext.java 121

/** 我们可以看到,此方法将通过一个 XmlBeanDefinitionReader 实例来加载各个 Bean。*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {// 给这个 BeanFactory 实例化一个 XmlBeanDefinitionReaderXmlBeanDefinitionReader 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));// 初始化 BeanDefinitionReader,其实这个是提供给子类覆写的,// 我看了一下,没有类覆写这个方法,我们姑且当做不重要吧initBeanDefinitionReader(beanDefinitionReader);// 重点来了,继续往下loadBeanDefinitions(beanDefinitionReader);
}

现在还在这个类中,接下来用刚刚初始化的 Reader 开始来加载 xml 配置,这块代码读者可以选择性跳过,不是很重要。也就是说,下面这个代码块,读者可以很轻松地略过。

// AbstractXmlApplicationContext.java 120

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);}
}// 上面虽然有两个分支,不过第二个分支很快通过解析路径转换为 Resource 以后也会进到这里
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {Assert.notNull(resources, "Resource array must not be null");int counter = 0;// 注意这里是个 for 循环,也就是每个文件是一个 resourcefor (Resource resource : resources) {// 继续往下看counter += loadBeanDefinitions(resource);}// 最后返回 counter,表示总共加载了多少的 BeanDefinitionreturn counter;
}// XmlBeanDefinitionReader 
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {return loadBeanDefinitions(new EncodedResource(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());}// 用一个 ThreadLocal 来存放配置文件资源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();}}
}// 还在这个文件中
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {// 这里就不看了,将 xml 文件转换为 Document 对象Document doc = doLoadDocument(inputSource, resource);// 继续return registerBeanDefinitions(doc, resource);}catch (...
}
// 还在这个文件中
// 返回值:返回从当前配置文件加载了多少数量的 Bean
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 90
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext;logger.debug("Loading bean definitions");Element root = doc.getDocumentElement();// 从 xml 根节点开始解析文件doRegisterBeanDefinitions(root);
}

经过漫长的链路,一个配置文件终于转换为一颗 DOM 树了,注意,这里指的是其中一个配置文件,不是所有的,读者可以看到上面有个 for 循环的。下面开始从根节点开始解析:

doRegisterBeanDefinitions:

// DefaultBeanDefinitionDocumentReader 116
protected void doRegisterBeanDefinitions(Element root) {// 我们看名字就知道,BeanDefinitionParserDelegate 必定是一个重要的类,它负责解析 Bean 定义,// 这里为什么要定义一个 parent? 看到后面就知道了,是递归问题,// 因为 <beans /> 内部是可以定义 <beans /> 的,所以这个方法的 root 其实不一定就是 xml 的根节点,也可以是嵌套在里面的 <beans /> 节点,从源码分析的角度,我们当做根节点就好了BeanDefinitionParserDelegate parent = this.delegate;this.delegate = createDelegate(getReaderContext(), root, parent);if (this.delegate.isDefaultNamespace(root)) {// 这块说的是根节点 <beans ... profile="dev" /> 中的 profile 是否是当前环境需要的,// 如果当前环境配置的 profile 不包含此 profile,那就直接 return 了,不对此 <beans /> 解析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;
}

preProcessXml(root) 和 postProcessXml(root) 是给子类用的钩子方法,鉴于没有被使用到,也不是我们的重点,我们直接跳过。

接下来,看核心解析方法 parseBeanDefinitions(root, this.delegate) :

// default namespace 涉及到的就四个标签 <import />、<alias />、<bean /> 和 <beans />,
// 其他的属于 custom 的
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)) {// 解析 default namespace 下面的几个元素parseDefaultElement(ele, delegate);}else {// 解析其他 namespace 的元素delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}
}

从上面的代码,我们可以看到,对于每个配置来说,分别进入到 parseDefaultElement(ele, delegate); 和 delegate.parseCustomElement(ele); 这两个分支了。

parseDefaultElement(ele, delegate) 代表解析的节点是 <import /><alias /><bean /><beans /> 这几个。

这里的四个标签之所以是 default 的,是因为它们是处于这个 namespace 下定义的:

http://www.springframework.org/schema/beans

又到初学者科普时间,不熟悉 namespace 的读者请看下面贴出来的 xml,这里的第二行 xmlns 就是咯。

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"default-autowire="byName">

而对于其他的标签,将进入到 delegate.parseCustomElement(element) 这个分支。如我们经常会使用到的 <mvc /><task /><context /><aop />等。

这些属于扩展,如果需要使用上面这些 ”非 default“ 标签,那么上面的 xml 头部的地方也要引入相应的 namespace 和 .xsd 文件的路径,如下所示。同时代码中需要提供相应的 parser 来解析,如 MvcNamespaceHandler、TaskNamespaceHandler、ContextNamespaceHandler、AopNamespaceHandler 等。

假如读者想分析 <context:property-placeholder location="classpath:xx.properties" /> 的实现原理,就应该到 ContextNamespaceHandler 中找答案。

继续看看处理 default 标签的方法:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {// 处理 <import /> 标签importBeanDefinitionResource(ele);}else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {// 处理 <alias /> 标签定义// <alias name="fromName" alias="toName"/>processAliasRegistration(ele);}else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {// 处理 <bean /> 标签定义,这也算是我们的重点吧processBeanDefinition(ele, delegate);}else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {// 如果碰到的是嵌套的 <beans /> 标签,需要递归doRegisterBeanDefinitions(ele);}
}

processBeanDefinition 解析 bean 标签

下面是 processBeanDefinition 解析 <bean /> 标签:

// DefaultBeanDefinitionDocumentReader  306

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {// 将 <bean /> 节点中的信息提取出来,然后封装到一个 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));}
}

继续往下看怎么解析之前,我们先看下 <bean /> 标签中可以定义哪些属性:

Property 
class类的全限定名
name可指定 id、name(用逗号、分号、空格分隔)
scope作用域
constructor arguments指定构造参数
properties设置属性的值
autowiring modeno(默认值)、byName、byType、 constructor
lazy-initialization mode是否懒加载(如果被非懒加载的bean依赖了那么其实也就不能懒加载了)
initialization methodbean 属性设置完成后,会调用这个方法
destruction methodbean 销毁后的回调方法

上面表格中的内容我想大家都非常熟悉吧,如果不熟悉,那就是你不够了解 Spring 的配置了。

简单地说就是像下面这样子:

<bean id="exampleBean" name="name1, name2, name3" class="com.javadoop.ExampleBean"scope="singleton" lazy-init="true" init-method="init" destroy-method="cleanup"><!-- 可以用下面三种形式指定构造参数 --><constructor-arg type="int" value="7500000"/><constructor-arg name="years" value="7500000"/><constructor-arg index="0" value="7500000"/><!-- property 的几种情况 --><property name="beanOne"><ref bean="anotherExampleBean"/></property><property name="beanTwo" ref="yetAnotherBean"/><property name="integerProperty" value="1"/>
</bean>

当然,除了上面举例出来的这些,还有 factory-bean、factory-method、<lockup-method /><replaced-method /><meta /><qualifier /> 这几个,大家是不是熟悉呢?自己检验一下自己对 Spring 中 bean 的了解程度。

有了以上这些知识以后,我们再继续往里看怎么解析 bean 元素,是怎么转换到 BeanDefinitionHolder 的。

// BeanDefinitionParserDelegate 404

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {return parseBeanDefinitionElement(ele, null);
}public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {String id = ele.getAttribute(ID_ATTRIBUTE);String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);List<String> aliases = new ArrayList<String>();// 将 name 属性的定义按照 “逗号、分号、空格” 切分,形成一个 别名列表数组,// 当然,如果你不定义 name 属性的话,就是空的了// 我在附录中简单介绍了一下 id 和 name 的配置,大家可以看一眼,有个20秒就可以了if (StringUtils.hasLength(nameAttr)) {String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);aliases.addAll(Arrays.asList(nameArr));}String beanName = id;// 如果没有指定id, 那么用别名列表的第一个名字作为beanNameif (!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);}// 根据 <bean ...>...</bean> 中的配置创建 BeanDefinition,然后把配置中的信息都设置到实例中,// 细节后面细说,先知道下面这行结束后,一个 BeanDefinition 实例就出来了。AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);// 到这里,整个 <bean /> 标签就算解析结束了,一个 BeanDefinition 就形成了。if (beanDefinition != null) {// 如果都没有设置 id 和 name,那么此时的 beanName 就会为 null,进入下面这块代码产生// 如果读者不感兴趣的话,我觉得不需要关心这块代码,对本文源码分析来说,这些东西不重要if (!StringUtils.hasText(beanName)) {try {if (containingBean != null) {// 按照我们的思路,这里 containingBean 是 null 的beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);}else {// 如果我们不定义 id 和 name,那么我们引言里的那个例子://   1. beanName 为:com.javadoop.example.MessageServiceImpl#0//   2. beanClassName 为:com.javadoop.example.MessageServiceImplbeanName = this.readerContext.generateBeanName(beanDefinition);String beanClassName = beanDefinition.getBeanClassName();if (beanClassName != null &&beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {// 把 beanClassName 设置为 Bean 的别名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);// 返回 BeanDefinitionHolderreturn new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}return null;
}

然后,我们再看看怎么根据配置创建 BeanDefinition 实例的:

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);// 设置 BeanDefinition 的一堆属性,这些属性定义在 AbstractBeanDefinition 中parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));/*** 下面的一堆是解析 <bean>......</bean> 内部的子元素,* 解析出来以后的信息都放到 bd 的属性中*/// 解析 <meta />parseMetaElements(ele, bd);// 解析 <lookup-method />parseLookupOverrideSubElements(ele, bd.getMethodOverrides());// 解析 <replaced-method />parseReplacedMethodSubElements(ele, bd.getMethodOverrides());// 解析 <constructor-arg />parseConstructorArgElements(ele, bd);// 解析 <property />parsePropertyElements(ele, bd);// 解析 <qualifier />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;
}

到这里,我们已经完成了根据 <bean /> 配置创建了一个 BeanDefinitionHolder 实例。注意,是一个。

我们回到解析 <bean /> 的入口方法:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {// 将 <bean /> 节点转换为 BeanDefinitionHolder,就是上面说的一堆BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {// 如果有自定义属性的话,进行相应的解析,先忽略bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// 我们把这步叫做 注册Bean 吧BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to register bean definition with name '" +bdHolder.getBeanName() + "'", ele, ex);}// 注册完成后,发送事件,本文不展开说这个getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}
}

大家再仔细看一下这块吧,我们后面就不回来说这个了。这里已经根据一个 <bean /> 标签产生了一个 BeanDefinitionHolder 的实例,这个实例里面也就是一个 BeanDefinition 的实例和它的 beanName、aliases 这三个信息,注意,我们的关注点始终在 BeanDefinition 上:

public class BeanDefinitionHolder implements BeanMetadataElement {private final BeanDefinition beanDefinition;private final String beanName;private final String[] aliases;
...

然后我们准备注册这个 BeanDefinition,最后,把这个注册事件发送出去。

下面,我们开始说说注册 Bean 吧。

注册 Bean

// BeanDefinitionReaderUtils 160

public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {// 真正注册bean定义String beanName = definitionHolder.getBeanName();registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());// 如果有别名,用map保存别名和beanName映射// 获取的时候,会先将 alias 转换为 beanName,然后再查找String[] aliases = definitionHolder.getAliases();if (aliases != null) {for (String alias : aliases) {registry.registerAlias(beanName, alias);}}}

别名注册的放一边,毕竟它很简单,我们看看怎么注册 Bean。

// DefaultListableBeanFactory 914

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);}}BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);//存在重复名称的beanif (existingDefinition != null) {//不允许override,则抛异常if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);}else if (existingDefinition.getRole() < beanDefinition.getRole()) {// role越小,优先级越高 小覆盖大if (logger.isInfoEnabled()) {logger.info("Overriding user-defined bean definition for bean '" + beanName +"' with a framework-generated bean definition: replacing [" +existingDefinition + "] with [" + beanDefinition + "]");}}else if (!beanDefinition.equals(existingDefinition)) {// 不相等时,beanDefinition覆盖existingDefinitionif (logger.isDebugEnabled()) {logger.debug("Overriding bean definition for bean '" + beanName +"' with a different definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}else {// 相等时,实际仍是beanDefinition覆盖existingDefinitionif (logger.isTraceEnabled()) {logger.trace("Overriding bean definition for bean '" + beanName +"' with an equivalent definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}this.beanDefinitionMap.put(beanName, beanDefinition);}else {if (hasBeanCreationStarted()) {// Cannot modify startup-time collection elements anymore (for stable iteration)synchronized (this.beanDefinitionMap) {this.beanDefinitionMap.put(beanName, beanDefinition);List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);updatedDefinitions.addAll(this.beanDefinitionNames);updatedDefinitions.add(beanName);this.beanDefinitionNames = updatedDefinitions;removeManualSingletonName(beanName);}}else {// 启动注册阶段,正常进入此分支//保存beanDefinition到IOC容器中this.beanDefinitionMap.put(beanName, beanDefinition);//按beanName顺序存储this.beanDefinitionNames.add(beanName);//remove手工注册bean的情况removeManualSingletonName(beanName);}this.frozenBeanDefinitionNames = null;}if (existingDefinition != null || containsSingleton(beanName)) {resetBeanDefinition(beanName);}}

总结一下,到这里已经初始化了 Bean 容器,<bean /> 配置也相应的转换为了一个个 BeanDefinition,然后注册了各个 BeanDefinition 到注册中心,并且发送了注册事件。

 

 

 

 

 

 

 

 

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 6.1课堂笔记

    CSS发展历程 从HTML被发明开始, 样式就以各种形式存在 不同的浏览器结合他们各自的样式语言为用户提供页面效果的控制 最初的HTML只包含很少的显示属性 随着HTML的成长 为了满足页面设计者的要求 HTML 添加了很多显示功能 但是随着这些功能的增加 HTML变得越来越杂乱 而且HTML页…...

    2024/4/26 13:21:17
  2. Java中IO流以数组为单位遍历容易出现的问题

    IO流中以数组为单位遍历读取容易出现的错误铛铛铛!我们在IO流的联系中通常会使用到以数组为单位的输入流,对于这样的的输入流我们知道,要想查看其中的内容,无非是遍历出来。但是当我们进行遍历的时候往往会忽视一个问题,下面给大家举个例子来理解这个问题,对于例子当中的…...

    2024/4/24 9:55:49
  3. 决策树-学习笔记整理

    **## 决策树 决策树:是一种树形结构,其中每个内部节点表示一个属性上的判断,每个分支代表一个判断结果的输出,最后每个叶节点代表一种分类结果,本质是一颗由多个判断节点组成的树。 熵 物理学上,熵 Entropy 是“混乱”程度的量度。系统越有序,熵值越低;系统越混乱或者分…...

    2024/4/24 9:55:48
  4. Video banking 帮助银行扩展服务范围,国内市场和海外市场冰火两重天

    在中国市场,视频银行服务已经过了被热捧、吸人眼球的营销噱头。随着中国人民银行《关于落实个人银行账户分类管理制度的通知》新规于2016年12月1日开始实施,视频银行业务快速下滑。大部分银行的VTM(Video Teller Machine)上取消了开户/发卡等业务,仅保留一些签约、转账等业…...

    2024/4/24 9:55:47
  5. Kali Linux渗透测试——免杀基础

    笔记内容参考安全牛课堂苑房弘老师的Kali Linux渗透测试教程文章目录一、基础概念(一)恶意软件(二)防护手段(三)免杀技术二、工具介绍(一)Msfvenom(二)Veil-evasion(三)Backdoor-factory 一、基础概念 (一)恶意软件 在用户非自愿情况下执行安装,具有控制、窃取、…...

    2024/4/24 9:55:46
  6. 对不起navicat我投入了DataGrid的怀抱

    一 前言 今天的内容是知识追寻者想给大家安利一个软件为DataGrid, 没错,他就是idea,pycharm们的兄弟,都是属于jetbrains家族,知识追寻者放弃navicat的使用了,对不起navicat , 因为 datagrid 华丽的黑色背景我放弃了你,因为datagrid的高效的查询性能和自动化的功能键,放弃…...

    2024/4/24 9:55:45
  7. 北邮学渣大学四年自学走来,这些私藏的实用工具/学习网站我贡献出来了

    点赞再看,养成习惯,微信搜索【敖丙】关注这个互联网苟且偷生的工具人。 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点、资料以及我的系列文章。##前言 在大学的时候我们有大量的业余时间,我们可以拿出一部分时间去自学,也可以自学你感兴趣的…...

    2024/5/6 18:53:07
  8. WebSocket的故事(五)—— Springboot中,实现网页聊天室之自定义消息代理

    前言最近,偶然在掘金上发现了一个大牛写的这篇文章,感觉作者写的非常好,防止以后找不到了,这里转载记录一下,方便以后使用。概述WebSocket的故事系列计划分五大篇六章,旨在由浅入深的介绍WebSocket以及在Springboot中如何快速构建和使用WebSocket提供的能力。本系列计划包…...

    2024/4/28 23:07:12
  9. WebSocket的故事(六)—— Springboot中,实现更灵活的WebSocket

    前言最近,偶然在掘金上发现了一个大牛写的这篇文章,感觉作者写的非常好,防止以后找不到了,这里转载记录一下,方便以后使用。概述WebSocket的故事系列计划分五大篇六章,旨在由浅入深的介绍WebSocket以及在Springboot中如何快速构建和使用WebSocket提供的能力。本系列计划包…...

    2024/4/28 10:37:34
  10. Redis要点总结

    文章目录1. 缓存穿透1.1 带来的问题1.2 解决办法1.2.1 缓存空值1.2.2 BloomFilter1.2.3 如何选择2. 缓存击穿2.1 带来的问题2.2 解决办法3. 缓存雪崩3.1 解决办法3.1.1 事前:使用集群缓存,保证缓存服务的高可用3.1.2 事中:使用 ehcache 本地缓存 + Hystrix 限流&降级 ,避…...

    2024/4/24 9:55:41
  11. 混沌变换及Logistic映射

    引言 : 混沌算法? 如果一个系统的演变过程对初始的状态十分敏感,就把这个系统称为是混沌系统。 在1972年12月29日,美国麻省理工教授、混沌学开创人之一E.N.洛仑兹在美国科学发展学会第139次会议上发表了题为《蝴蝶效应》的论文,提出一个貌似荒谬的论 断:在巴西一只蝴蝶翅…...

    2024/4/24 9:55:40
  12. 2020年疫情过后 上班问题积累

    🐴🐴🐴Spring Boot 数据库连接池 HikariCP https://www.cnblogs.com/michael-xiang/p/10747934.html 🐴🐴🐴和druid的区别 https://tech.souyunku.com/?p=10931 🐴🐴🐴java的基本数据类型占据的内存大小 https://www.cnblogs.com/frankielf0921/p/9305335.h…...

    2024/4/24 9:55:40
  13. Hive知识点(三)--基本命令

    Hive常用命令1.基本命令2.Hive中数据库存放在HDFS的位置3.Hive中数据库切换4.Hive中数据库控制台显示设置4.1全局的修改4.2局部的修改5.Hive中指定数据库建表6.Linux界面执行Hive语句7.Hive的日志存放8.问题点 1.基本命令 #首先启动Hadoop su - hadoop app/hadoop/sbin/start-a…...

    2024/4/24 9:55:38
  14. 听说你的大疆Snail不能运动起来???

    在准备RoboMaster对抗赛的过程中,遇到了一些奇奇怪怪的问题,所以将其总结并记录下来,用来警戒后学者重蹈覆辙。 最近在倒腾大疆的电机6020、6623、3508、Snail等,发现最难并且遇到最多的问题的是大疆Snail这款电机,所以将一些心得记录下来。 大疆的这款Snail的电机,开初设…...

    2024/4/24 9:55:37
  15. STACKOVERFLOW 创始人关于如何高效编程的清单

    前言“无我编程”发生在开发阶段,表现为技术团队经常通过同级评审的方式来发现软件中的缺陷。目的是让所有人(包括作者)都参与寻找缺陷,而不是证明软件产品里没有缺陷。人们会交换各自手上的代码,相互进行评审,并且大家都有这样的共识:代码的原始作者会犯错误,而作为评…...

    2024/4/24 9:55:36
  16. @Before和@After的使用

    @before的作用就是在一个类中最先执行的方法 @after的作用就是在一个类中最后执行的方法 这样就可以把一些重复执行的代码抽取出来 , 这样我们就不用书写这些的重复的部分了 例如下面的这段代码 , 这是进行查询 ,当我们书写增删改时候还得全部写很麻烦@Testpublic void testFin…...

    2024/4/16 9:11:13
  17. 类的加载过程

    类的加载阶段 此时jvm会选用不用的类加载器(ClassLoader)进行加载我们的类,通常将class文件以二进制流的方式读入到内存当中,生成Class对象 类的连接阶段 此阶段又分为3个小阶段 1)验证:启动项目时,遇到的java版本不匹配的的问题都是在此过程出现的;还会检验二进制流是…...

    2024/4/16 9:11:13
  18. Eclipse将整个项目的HTML文件编码改为UTF-8

    Eclipse的Window -->preference-->General-->ContentTypes 选择Text下的HTML项,在Default encoding栏位输入 UTF-8并且update下,一定要记得update!!!!...

    2024/4/16 9:10:48
  19. 链栈练习题——括号合法性

    括号合法性题目: 给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。 有效字符串需满足: (1)左括号必须用相同类型的右括号闭合。 (2)左括号必须以正确的顺序闭合。 注意空字符串可被认为是有效字符串。 代码如下: #define _CRT_…...

    2024/4/15 5:57:50
  20. IOC的底层原理

    控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理 使用 IOC 目的:为了耦合度降低 IOC的底层原理:xml解析,工厂模式,反射 IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂 Spring提供IOC容器实现两种方式:(两个接口)BeanFactory:IOC容器基本实现,…...

    2024/4/24 9:55:35

最新文章

  1. 03_Redis

    文章目录 Redis介绍安装及使用redis的核心配置数据结构常用命令stringlistsethashzset(sortedset) 内存淘汰策略Redis的Java客户端JedisRedisson Redis 介绍 Redis是一个NoSQL数据库。 NoSQL: not only SQL。表示非关系型数据库&#xff08;不支持SQL标准语法&#xff09;。 …...

    2024/5/7 2:10:43
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/6 9:38:23
  3. STM32-GPIO

    &#x1f913;&#x1f913;&#x1f913; 122.1 2.22.3 344.14.24.34.44.54.64.74.8 56788.18.299.19.2 STM32 第一个外设 1 对我们来说 和IO口没区别 ST公司非叫GPIO 2 2.1 第二个是超频了 F1 72M 这翻转就36 2.2 有cmos 和ttl两种数据手册里给出整个芯片最低电流为150ma 单…...

    2024/5/5 13:38:38
  4. ArcGIS10.8保姆式安装教程

    ArcGIS 10.8是一款非常强大的地理信息系统软件&#xff0c;用于创建、管理、分析和可视化地理数据。以下是ArcGIS 10.8的详细安装教程&#xff1a; 确保系统满足安装要求 在开始安装之前&#xff0c;请确保您的计算机满足以下系统要求&#xff1a; 操作系统&#xff1a;Windo…...

    2024/5/5 8:36:10
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/4 23:54:56
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/4 23:54:56
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/6 9:21:00
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/4 23:55:16
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/4 23:55:17
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/4 23:55:06
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/4 23:55:06
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/4 23:55:16
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/6 21:42:42
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57