Spring IOC 的理解
IoC容器是什么?
IoC文英全称Inversion of Control,即控制反转,我么可以这么理解IoC容器:
“把某些业务对象的的控制权交给一个平台或者框架来同一管理,这个同一管理的平台可以称为IoC
容器。”
我们刚开始学习spring的时候会经常看到的类似下面的这代码:
上面代码中,在创建ApplicationContext实例对象过程中会创建一个spring容器,该容器会读取配置文
件"cjj/models/beans.xml",并统一管理由该文件中定义好的所有bean实例对象,如果要获取某个bean
实例,使用getBean方法就行了。例如我们只需要将Person提前配置在beans.xml文件中(可以理解为
注入),之后我们可以不需使用new Person()的方式创建实例,而是通过容器来获取Person实例,这就
相当于将Person的控制权交由spring容器了,差不多这就是控制反转的概念。
那在创建IoC容器时经历了哪些呢?为此,先来了解下Spring中IoC容器分类,继而根据一个具体的容器
来讲解IoC容器初始化的过程。
Spring中有两个主要的容器系列:
1. 实现BeanFactory接口的简单容器;
2. 实现ApplicationContext接口的高级容器。
ApplicationContext appContext = new
ClassPathXmlApplicationContext("cjj/models/beans.xml");
Person p = (Person)appContext.getBean("person");ApplicationContext比较复杂,它不但继承了BeanFactory的大部分属性,还继承其它可扩展接口,扩
展的了许多高级的属性,其接口定义如下:
public interface ApplicationContext extends EnvironmentCapable,
ListableBeanFactory, //继承于
BeanFactory HierarchicalBeanFactory,//继承于
BeanFactory
MessageSource,
//
ApplicationEventPublisher,// ResourcePatternResolver
//继承ResourceLoader,用于获取resource对象
在BeanFactory子类中有一个DefaultListableBeanFactory类,它包含了基本Spirng IoC容器所具有的
重要功能,开发时不论是使用BeanFactory系列还是ApplicationContext系列来创建容器基本都会使用
到DefaultListableBeanFactory类,可以这么说,在spring中实际上把它当成默认的IoC容器来使用。下
文在源码实例分析时你将会看到这个类。
(注:文章有点长,需要细心阅读,不同版本的spring中源码可能不同,但逻辑几乎是一样的,如果可
以建议还是看源码 ^_^)
回到本文正题上来,关于Spirng IoC容器的初始化过程在《Spirng技术内幕:深入解析Spring架构与设
计原理》一书中有明确的指出,IoC容器的初始化过程可以分为三步:
1. Resource定位(Bean的定义文件定位)
2. 将Resource定位好的资源载入到BeanDefinition
3. 将BeanDefiniton注册到容器中
第一步 Resource定位
Resource是Sping中用于封装I/O操作的接口。正如前面所见,在创建spring容器时,通常要访问XML配
置文件,除此之外还可以通过访问文件类型、二进制流等方式访问资源,还有当需要网络上的资源时可
以通过访问URL,Spring把这些文件统称为Resource,Resource的体系结构如下:
常用的resource资源类型如下:
FileSystemResource:以文件的绝对路径方式进行访问资源,效果类似于Java中的File;
ClassPathResourcee:以类路径的方式访问资源,效果类似于
this.getClass().getResource("/").getPath();ServletContextResource:web应用根目录的方式访问资源,效果类似于
request.getServletContext().getRealPath("");
UrlResource:访问网络资源的实现类。例如file: http: ftp:等前缀的资源对象;
ByteArrayResource: 访问字节数组资源的实现类。
那如何获取上图中对应的各种Resource对象呢?
Spring提供了ResourceLoader接口用于实现不同的Resource加载策略,该接口的实例对象中可以获取
一个resource对象,也就是说将不同Resource实例的创建交给ResourceLoader的实现类来处理。
ResourceLoader接口中只定义了两个方法:
Resource getResource(String location); //通过提供的资源location参数获取Resource实例
ClassLoader getClassLoader(); // 获取ClassLoader,通过ClassLoader可将资源载入JVM
注:ApplicationContext的所有实现类都实现RecourceLoader接口,因此可以直接调用
getResource(参数)获取Resoure对象。不同的ApplicatonContext实现类使用getResource方法取得
的资源类型不同,例如:FileSystemXmlApplicationContext.getResource获取的就是
FileSystemResource实例;ClassPathXmlApplicationContext.gerResource获取的就是
ClassPathResource实例;XmlWebApplicationContext.getResource获取的就是
ServletContextResource实例,另外像不需要通过xml直接使用注解@Configuation方式加载资源的
AnnotationConfigApplicationContext等等。
在资源定位过程完成以后,就为资源文件中的bean的载入创造了I/O操作的条件,如何读取资源中的数
据将会在下一步介绍的BeanDefinition的载入过程中描述。
*第二步 通过返回的*resource对象,进行BeanDefinition的载入
1.什么是BeanDefinition? BeanDefinition与Resource的联系呢?
官方文档中对BeanDefinition的解释如下:
A BeanDefinition describes a bean instance, which has property values, constructor argument
values, and further information supplied by concrete implementations.
它们之间的联系从官方文档描述的一句话:Load bean definitions from the specified resource中可见
一斑。
/**
* Load bean definitions from the specified resource.
* @param resource the resource descriptor
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
*/
int loadBeanDefinitions(Resource resource) throws
BeanDefinitionStoreException;
总之,BeanDefinition相当于一个数据结构,这个数据结构的生成过程是根据定位的resource资源
对象中的bean而来的,这些bean在Spirng IoC容器内部表示成了的BeanDefintion这样的数据结构,
IoC容器对bean的管理和依赖注入的实现都是通过操作BeanDefinition来进行的。
2.如何将BeanDefinition载入到容器?
在Spring中配置文件主要格式是XML,对于用来读取XML型资源文件来进行初始化的IoC 容器而
言,该类容器会使用到AbstractXmlApplicationContext类,该类定义了一个名为
loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的方法用于获取BeanDefinition:// 该方法属于AbstractXmlApplicationContect类
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws
BeansException, IOException {
XmlBeanDefinitionReader beanDefinitionReader = new
XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
this.initBeanDefinitionReader(beanDefinitionReader);
// 用于获取BeanDefinition
this.loadBeanDefinitions(beanDefinitionReader);
}
此方法在具体执行过程中首先会new一个与容器对应的BeanDefinitionReader型实例对象,然后将生成
的BeanDefintionReader实例作为参数传入loadBeanDefintions(XmlBeanDefinitionReader),继续往
下执行载入BeanDefintion的过程。例如AbstractXmlApplicationContext有两个实现类:
FileSystemXmlApplicationContext、ClassPathXmlApplicationContext,这些容器在调用此方法时会
创建一个XmlBeanDefinitionReader类对象专门用来载入所有的BeanDefinition。
下面以XmlBeanDefinitionReader对象载入BeanDefinition为例,使用源码说明载入BeanDefinition的
过程:
// 该方法属于AbstractXmlApplicationContect类protected void
loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException,
IOException {
Resource[] configResources = getConfigResources();//获取所有定位到的
resource资源位置(用户定义)
if (configResources != null) {
reader.loadBeanDefinitions(configResources);//载入resources
}
String[] configLocations = getConfigLocations();//获取所有本地配置文件的位置
(容器自身)
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);//载入resources
}
}
通过上面代码将用户定义的资源以及容器本身需要的资源全部加载到reader中,
reader.loadBeanDefinitions方法的源码如下:// 该方法属于AbstractBeanDefinitionReader类, 父接口BeanDefinitionReader
@Override
public int loadBeanDefinitions(Resource... resources) throws
BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
// 将所有资源全部加载,交给AbstractBeanDefinitionReader的实现子类处理这些
resource
counter += loadBeanDefinitions(resource);
}
return counter;
}
BeanDefinitionReader接口定义了 int loadBeanDefinitions(Resource resource)方法:
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws
BeanDefinitionStoreException;
XmlBeanDefinitionReader 类实现了BeanDefinitionReader接口中的loadBeanDefinitions(Resource)
方法,其继承关系如上图所示。XmlBeanDefinitionReader类中几主要对加载的所有resource开始进行
处理,大致过程是,先将resource包装为EncodeResource类型,然后处理,为生成BeanDefinition对
象做准备,其主要几个方法的源码如下:
public int loadBeanDefinitions(Resource resource) throws
BeanDefinitionStoreException {
// 包装resource为EncodeResource类型
return loadBeanDefinitions(new EncodedResource(resource));
}
// 加载包装后的EncodeResource资源
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());
}try {
// 通过resource对象得到XML文件内容输入流,并为IO的InputSource做准备
InputStream inputStream =
encodedResource.getResource().getInputStream();
try {
// Create a new input source with a byte stream.
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 开始准备 load bean definitions from the specified XML file
return doLoadBeanDefinitions(inputSource,
encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " +
encodedResource.getResource(), ex);
}
}
protected int doLoadBeanDefinitions(InputSource inputSource, Resource
resource)
throws BeanDefinitionStoreException {
try {
// 获取指定资源的验证模式
int validationMode = getValidationModeForResource(resource);
// 从资源对象中加载DocumentL对象,大致过程为:将resource资源文件的内容读入到
document中
// DocumentLoader在容器读取XML文件过程中有着举足轻重的作用!
// XmlBeanDefinitionReader实例化时会创建一个DefaultDocumentLoader型的私有
属性,继而调用loadDocument方法
// inputSource--要加载的文档的输入源
Document doc = this.documentLoader.loadDocument(
inputSource, this.entityResolver, this.errorHandler,
validationMode, this.namespaceAware);
// 将document文件的bean封装成BeanDefinition,并注册到容器
return registerBeanDefinitions(doc, resource);
}
catch ...(略)
}
DefaultDocumentLoader大致了解即可,感兴趣可继续深究,其源码如下:(看完收起,便于阅读下
文)
View Code
上面代码分析到了registerBeanDefinitions(doc, resource)这一步,也就是准备将Document中的Bean
按照Spring bean语义进行解析并转化为BeanDefinition类型,这个方法的具体过程如下:/**
* 属于XmlBeanDefinitionReader类
* Register the bean definitions contained in the given DOM document.
* @param doc the DOM document
* @param resource
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException
*/
public int registerBeanDefinitions(Document doc, Resource resource) throws
BeanDefinitionStoreException {
// 获取到DefaultBeanDefinitionDocumentReader实例
BeanDefinitionDocumentReader documentReader =
createBeanDefinitionDocumentReader();
// 获取容器中bean的数量
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
通过 XmlBeanDefinitionReader 类中的私有属性 documentReaderClass 可以获得一个
DefaultBeanDefinitionDocumentReader 实例对象:
private Class<?> documentReaderClass =
DefaultBeanDefinitionDocumentReader.class;
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return
BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.document
ReaderClass));
}
DefaultBeanDefinitionDocumentReader实现了BeanDefinitionDocumentReader接口,它的
registerBeanDefinitions方法定义如下:
// DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
{
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
// 获取doc的root节点,通过该节点能够访问所有的子节点
Element root = doc.getDocumentElement();
// 处理beanDefinition的过程委托给BeanDefinitionParserDelegate实例对象来完成
BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
// Default implementation is empty.
// Subclasses can override this method to convert custom elements into
standard Spring bean definitions
preProcessXml(root);
// 核心方法,代理
parseBeanDefinitions(root, delegate);postProcessXml(root);
}
上面出现的BeanDefinitionParserDelegate类非常非常重要(需要了解代理技术,如JDK动态代理、
cglib动态代理等)。Spirng BeanDefinition的解析就是在这个代理类下完成的,此类包含了各种对符合
Spring Bean语义规则的处理,比如、、等的检测。
parseBeanDefinitions(root, delegate)方法如下:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate
delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
// 遍历所有节点,做对应解析工作
// 如遍历到<import>标签节点就调用importBeanDefinitionResource(ele)方法对应
处理
// 遍历到<bean>标签就调用processBeanDefinition(ele,delegate)方法对应处理
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) {
// 解析<import>标签
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// 解析<alias>标签
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// 解析<bean>标签,最常用,过程最复杂
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// 解析<beans>标签
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);}
}
这里针对常用的标签中的方法做简单介绍,其他标签的加载方式类似:
/**
* Process the given bean element, parsing the bean definition
* and registering it with the registry.
*/
protected void processBeanDefinition(Element ele,
BeanDefinitionParserDelegate delegate) {
// 该对象持有beanDefinition的name和alias,可以使用该对象完成beanDefinition向容
器的注册
BeanDefinitionHolder bdHolder =
delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 注册最终被修饰的bean实例,下文注册beanDefinition到容器会讲解该方法
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,
getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with
name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new
BeanComponentDefinition(bdHolder));
}
}
parseBeanDefinitionElement(Element ele)方法会调用parseBeanDefinitionElement(ele, null)方法,
并将值返回BeanDefinitionHolder类对象,这个方法将会对给定的标签进行解析,如果在解析标签的
过程中出现错误则返回null。
需要强调一下的是parseBeanDefinitionElement(ele, null)方法中产生了一个抽象类型的BeanDefinition
实例,这也是我们首次看到直接定义BeanDefinition的地方,这个方法里面会将标签中的内容解析到
BeanDefinition中,之后再对BeanDefinition进行包装,将它与beanName,Alias等封装到
BeanDefinitionHolder 对象中,该部分源码如下:
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);
...(略)
String beanName = id;
...(略)
// 从上面按过程走来,首次看到直接定义BeanDefinition !!!
// 该方法会对<bean>节点以及其所有子节点如<property>、<List>、<Set>等做出解析,具
体过程本文不做分析(太多太长)
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele,
beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
...(略)
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName,
aliasesArray);
}
return null;
}
第三步,将BeanDefiniton注册到容器中
最终Bean配置会被解析成BeanDefinition并与beanName,Alias一同封装到BeanDefinitionHolder
类中, 之后beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()),注册
到DefaultListableBeanFactory.beanDefinitionMap中。之后客户端如果要获取Bean对象,Spring容
器会根据注册的BeanDefinition信息进行实例化。
BeanDefinitionReaderUtils类:
public static void registerBeanDefinition(
BeanDefinitionHolder bdHolder, BeanDefinitionRegistry beanFactory)
throws BeansException {
// Register bean definition under primary name.
String beanName = bdHolder.getBeanName(); // 注册beanDefinition!!!
beanFactory.registerBeanDefinition(beanName,
bdHolder.getBeanDefinition());
// 如果有别名的话也注册进去,Register aliases for bean name, if any.
String[] aliases = bdHolder.getAliases();
if (aliases != null) {
for (int i = 0; i < aliases.length; i++) {
beanFactory.registerAlias(beanName, aliases[i]);
}
}
}DefaultListableBeanFactory实现了上面调用BeanDefinitionRegistry接口的
registerBeanDefinition( beanName, bdHolder.getBeanDefinition())方法,这一部分的主要逻辑是向
DefaultListableBeanFactory对象的beanDefinitionMap中存放beanDefinition,当初始化容器进行
bean初始化时,在bean的生命周期分析里必然会在这个beanDefinitionMap中获取beanDefition实
例,有机会成文分析一下bean的生命周期,到时可以分析一下如何使用这个beanDefinitionMap。
registerBeanDefinition( beanName, bdHolder.getBeanDefinition() )方法具体方法如下:
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new
ConcurrentHashMap<String, BeanDefinition>(256);
public void registerBeanDefinition(String beanName, BeanDefinition
beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "Bean definition 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);
}
}
// beanDefinitionMap是个ConcurrentHashMap类型数据,用于存放
beanDefinition,它的key值是beanName
Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!this.allowBeanDefinitionOverriding) {
throw new
BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "]
for bean '" + beanName +
"': there's already [" + oldBeanDefinition + "] bound");
}
else {
if (logger.isInfoEnabled()) {
logger.info("Overriding bean definition for bean '" +
beanName +
"': replacing [" + oldBeanDefinition + "] with [" +
beanDefinition + "]");
}
}
}
else {
this.beanDefinitionNames.add(beanName);
} // 将获取到的BeanDefinition放入Map中,容器操作使用bean时通过这个
HashMap找到具体的BeanDefinition
相关文章:
Spring IOC 的理解
IoC容器是什么? IoC文英全称Inversion of Control,即控制反转,我么可以这么理解IoC容器: “把某些业务对象的的控制权交给一个平台或者框架来同一管理,这个同一管理的平台可以称为IoC 容器。” 我们刚开始学习…...
Linux 学习笔记(七):时间片
一、时间片概念 时间片(timeslice)又称为 “量子”(quantum)或 “处理器片”(processor slice),是分时操作系统分配给每个正在运行的进程微观上的一段 CPU 时间(在抢占内核中是&…...
java并发-ReentrantLock
当多个线程需要同时对共享资源进行操作时,就需要用到线程同步技术。Java中提供了synchronized关键字用于线程同步,而ReentrantLock就是另外一种用于线程同步的技术,本文将介绍ReentrantLock及其使用方法。 ### 1. 概述 ReentrantLock是Java…...
21.模型的访问器和修改器
学习要点: 1.访问器 2.修改器 本节课我们来开始学习数据库模型的访问器和修改器的使用。 一.访问器 1. 访问器:就是在获取数据列表时,拦截属性并对属性进行修改的过程; 2. 比如,我们在输出性别时࿰…...
72 yaffs文件系统挂载慢 sync不起作用
1 引言 最近在开放过程中遇到了一个问题:Linux在启动挂载根文件系统时很慢很慢!而且每次开机都是这样,一下子让人难以理解。 因为,理论上当机器第一次启动,会扫描完整的rootfs的flash区域,从而建立索引&…...
【无标题】春漫乌海湖!
春漫乌海湖! 杨桂林 黄河流经几字弯内蒙古段的第一段便遇见了镶嵌在大漠中的璀璨明珠乌海湖。 谁也不会相信:这里被乌兰布和、库布其、毛乌素三大沙漠重重包围,矿山林立,煤尘喧嚣飞扬的黑色煤都,如今在金色沙海的映衬下,柔润潋滟周…...

Red Hat重置root密码
目录 前言 1、使用rd.break参数重置root密码 2、使用安装盘重置root密码 前言 我们有时会忘记linux系统的root密码,有的不会重置密码只能重置系统了,下面介绍两种重置root密码的方法 1、使用rd.break参数重置root密码 1、启动系统,并在…...
应急响应之日志排查方法,Linux篇
应急响应之日志排查方法,Linux篇 1.Linux系统日志位置2.Linux日志分析方法3.其他日志的分析中间件日志其他服务日志1.Linux系统日志位置 Linux 系统中的日志一般存放在目录“/var/log/”下,具体的日志功能如下 /var/log/wtmp:记录登录进入、退出、数据交换、关机和重启,即…...

Midjourney AI 官方中文版已开启内测申请;OpenAI 正准备向公众发布一款新的开源语言模型。
🚀 Midjourney AI 官方中文版已开启内测申请,搭载在 QQ 频道上,召唤机器人进行作画。 Midjourney AI 官方中文版已开启内测申请,搭载在 QQ 频道上,召唤机器人进行作画。 可调用 MJ 和 Niji 的最新模型和所有参数&…...

DevOps 的道术法器,探寻 DevOps “立体化”实践之旅
引言 随着业务的发展,软件发布迭代的频率越来越高,传统的瀑布型模式已经不能满足快速交付的需求,DevOps 也因此受到持续关注。越来越多的公司开始接受并尝试使用 DevOps,期望能使得软件开发中的构建、测试与发布工作变得更加快捷…...

redis 7.x 缓存双写一致性的解决方案
一 redis缓存双写一致性 1.1 保证redis一致性的原则 1.给缓存设置过期时间,定期清理缓存并写回,是保证最终一致性的解决方案。使用场景:在数据读多写少的情况下作为缓存来使用。 我们可以对已存入缓存的数据设置过期时间,所有…...

真题详解(语法分析输入记号流)-软件设计(八十)
真题详解(求叶子结点数)-软件设计(七十九)https://blog.csdn.net/ke1ying/article/details/130787349?spm1001.2014.3001.5501 极限编程XP最佳实践: 测试先行、 按日甚至按小时为客户提供可运行的版本。 组件图的 插座 和插头…...
ffmpeg-编译汇总01
ffmpeg-编译汇总 ubuntu18.04下编译ffmpeg 所有安装目录 /usr/local 1.nasm编译器编译 (nasm-2.13.03解包) ./configure --prefix/usr/local make -j4 sudo make install 注意:能检测到可以不用设置下面的环境。 安装完成后,为了系统能自动找到nasm程序&…...

素雅的登录界面,简单而优雅
先上效果图: 再上代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><style>*, *::after, *::before {margin: 0;padding: 0;box-sizing: bord…...

Docker数据目录迁移方法
文章目录 前言一、停掉Docker服务?二、迁移docker数据到数据盘目三、备份原数据目录四、添加软链接五、重启docker服务六、确认服务没有问题后,删除备份的目录总结 前言 服务器上安装的docker服务,数据默认存储在/var/lib/docker目录&#x…...
C++——动态规划
动态规划是一种解决复杂问题的算法思想。它通过将问题分解为更小的子问题,并利用子问题的解来构建原问题的解。动态规划通常用于优化问题,其中需要找到最优解或最大值/最小值。 动态规划的核心思想是存储并重复使用子问题的解,以避免重复计算…...

【FAQ】视频编辑服务常见问题及解答
Q1问题描述 1、 访问贴纸等素材的时候提示“网络异常,请重试”怎么办? 2、 使用AI能力时,提示“errorCode:20124 errorMsg:Method not Allowed”? 解决方案 请做以下检查: 1、 在代码中检查鉴权信息是否已设置。如…...

JavaEE(系列8) -- 多线程案例(单例模式)
目录 1. 设计模式 2. 单例模式 -- 饿汉模式 3. 单例模式 -- 懒汉模式 4. 单例模式(懒汉模式-多线程) 1. 设计模式 什么是设计模式? 设计模式好比象棋中的 "棋谱". 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有一些固定的套路. 按照套路…...

深度剖析,如何从底层代码层面理解Selenium和Appium的关联
目录 前言: 一、Selenium和WebDriver 二、Appium和WebDriver 三、Selenium和Appium的底层关联 1. Selenium WebDriver提供底层的浏览器控制机制 2. 利用JSON Wire Protocol通信协议实现通讯机制 四、实例代码 总结: 前言: Selenium和…...

【Three.js】第一、二章 入门指南和基础知识
01.介绍 Three.js 非常庞大,你可以用它做无数的事情。 在第一章中,我们将学习所有基础知识,例如创建第一个场景、渲染、添加对象、选择正确的材料、添加纹理、为所有内容制作动画,甚至将其放到网上。有些人可能会觉得这部分有点…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
土建施工员考试:建筑施工技术重点知识有哪些?
《管理实务》是土建施工员考试中侧重实操应用与管理能力的科目,核心考查施工组织、质量安全、进度成本等现场管理要点。以下是结合考试大纲与高频考点整理的重点内容,附学习方向和应试技巧: 一、施工组织与进度管理 核心目标: 规…...