Gradle系列(3)——Gradle extension(gradle扩展),如何自定义gradle扩展,AppPlugin,AppExtension原理

文章目录

    • 1.什么是Extensions
    • 2.如何自定义Extension
    • 3.问题来了——如何通过自定义Extension给Plugin传递数据
    • 4.BaseAppModuleExtension和AppPlugin部分原理
      • BuildTypes是如何创建并传递数据给AppPlugin的?
      • AppPlugin是如何接收数据的?
      • buildTypeContainer
      • 流程总结
    • 5. 回归初心——扩展是如何传递数据给插件的呢?
    • 6 应用

系列: Gradle系列(2)——如何自定以Gradle 插件

在上一篇Gradle系列(2)——如何自定以Gradle 插件 中,我们了解了什么是 Gradle Plugin以及如何用Gradle Plugin在项目构建中实现更多强力而有趣的功能。尽管在demo中我们已经实现了在整个公司的不同团队通过本地json文件实现平台化的配置,但是仍然存在某个项目需要个性化的配置,或者在构建过程中需要一些灵活便捷的自定义的构建,那么上一篇实现的功能就不能满足我们的需求,于是本篇就引入了如何自定义Gradle Extensions


1.什么是Extensions

  • 我们其实经常接触Gradle Extension

Gradle Extensions 是Gradle Plugin 的一种扩展机制,通过实现自定义的Extension,我们可以在Gradle 脚本(例如build.gradle)中增加自己的配置,例如android(其实例为com.android.build.gradle.internal.dsl.BaseAppModuleExtensioncom.android.build.gradle.LibraryExtension),
Gradle在configuration阶段可以读取这些配置里面的内容。

2.如何自定义Extension

关于这点,百度搜索有许多好多大量海量千篇一律毫无区别的帖子告诉你怎么定义一个自定义扩展并注册,我不再展开,就按照他们写的简单写个小demo

  • 定义一个扩展

public class TestExtension {public String anyString;
}
  • 在Plugin实现类注册这个扩展

public class RocketPlugin implements Plugin<Project>  {@Overridepublic void apply(Project project) {super.apply(project);project.getExtensions().create("anyTest", TestExtension.class);}
}

我们给build.gradle内注册了一个TestExtension的实例,名字为anyTest

  • 使用这个扩展
    在app/library build.gradle内
plugins {id("com.android.application")id("gradle-rocket")
}anyTest{topic = "extension test"
}
  • 获取插件的配置信息

在Plugin实现类内:


public class RocketPlugin implements Plugin<Project>  {@Overridepublic void apply(Project project) {super.apply(project);TestExtension anyTest = project.getExtensions().create("anyTest", TestExtension.class);project.afterEvaluate(new Action<Project>() {@Overridepublic void execute(Project project) {System.out.println("    afterEvaluate :anyTest:"+anyTest.topic);}});}
}

然后同步就可以看到输出:

!请添加图片描述

按照大家的说法,到这一步我们实现了一个简单的扩展。

3.问题来了——如何通过自定义Extension给Plugin传递数据

到上一节,我们实现了一个扩展,简单说明如何定义注册使用扩展,但,这只是一个简陋的扩展,甚至不能称之为扩展,我称之为扩展都感觉十分脸红惭愧甚至觉得丢人。

回想看过的十数篇帖子,不由得内心怒骂好几次:

这TM是扩展?!!这TM是扩展?!!这TM是扩展?!!

你们这些灌水的人给我翻译翻译,什么TM的叫TM的扩展?!!

为什么这个东西不能叫扩展?

我们回到开头的“什么是Extensions”,扩展的核心诉求是在gradle构建脚本(比如build.gradle)中增加我们自己的配置应用到我们的自定义gradle plugin中。而在以上篇章我们的扩展在哪里应用了,可以看到一行代码:

   estExtension anyTest = project.getExtensions().create("anyTest", TestExtension.class);project.afterEvaluate(new Action<Project>() {@Overridepublic void execute(Project project) {System.out.println("    afterEvaluate :anyTest:"+anyTest.topic);}});//外部直接打印System.out.println("    afterEvaluate :anyTest:"+anyTest.topic);

我们注意project.afterEvaluate这个方法,在我的上一篇Gradle系列(2)——如何自定以Gradle 插件 中我简单讲了gradle的生命周期以及监听,afterEvaluate表示,gradle 的configuration阶段已经完成了。而上述代码表示,在项目配置完成后,输出一下我们扩展配置的值

  • 实际问题

由于我们做的是平台化项目,androidharmony双平台打包的,两个平台应用的三方库,签名机制也不一样,所以我需要知道编译的是哪个平台,是否需要支持双平台,所以我添加了一个扩展来配置这个信息,然而我在上述afterEvaluate生命周期去做签名的时候,发现报错(当时忘了截图,大概意思是说buildType已经创建好了,此时修改太晚了)

我们尝试在外部直接打印,你会发现,根本没有值!

那么问题来了——我们这里实现的扩展还有什么用呢?

答案是,没卵用!

你去搜索相关的帖子,千篇一律,都是这样,甚至你看官网,他们的demo也是这样!我请问一下,这样一个不能给插件传送数据,只能看没有实际用处的花架子,它叫扩展吗?

4.BaseAppModuleExtension和AppPlugin部分原理

那么到底在构建脚本怎么通过我们的扩展给插件传递数据呢?我想,我们可以看Android的AppExtension和AppPlugin是如何实现的呀。

注意:

  • com.android.application,com.android.library 实现类分别为com.android.build.gradle.internal.plugins.AppPlugincom.android.build.gradle.internal.plugins.LibraryPlugin

  • build.gradle中声明的“android”扩展的实例根据module是应用的插件不同,其实例可能是com.android.build.gradle.internal.dsl.BaseAppModuleExtensioncom.android.build.gradle.LibraryExtension

BuildTypes是如何创建并传递数据给AppPlugin的?

我们以build.gradle.kts中buildTypes为例:


plugins {id("com.android.application")id("gradle-rocket")
}
android {//。。。create("test") {isMinifyEnabled = falseisDebuggable = truesigningConfigs { }}//。。。
}

其实build.gradle的dsl写法我们可以理解为一种极致的lambda,buildTypes完整写法应该是

buildTypes {create("test", object : Action<ApplicationBuildType> {override fun execute(t: ApplicationBuildType) {t.isMinifyEnabled = falset.isDebuggable = true}})}

我们点进去这个create方法,
在这里插入图片描述

可以看到,这是一个抽象类,点击左侧图标查看源码:

在这里插入图片描述

看得出来这个类应用还是比较广泛的,一时难以确定实现类是哪个。我们转变思路看AppPlugin:

AppPlugin是如何接收数据的?

查看AppPlugin源码


public class AppPluginextends AbstractAppPlugin<com.android.build.api.dsl.ApplicationExtension,ApplicationAndroidComponentsExtension,ApplicationVariantBuilderImpl,ApplicationVariantImpl> {@Injectpublic AppPlugin(ToolingModelBuilderRegistry registry,SoftwareComponentFactory componentFactory,BuildEventsListenerRegistry listenerRegistry) {super(registry, componentFactory, listenerRegistry);}@NonNull@Overrideprotected BaseExtension createExtension(@NonNull DslServices dslServices,@NonNull GlobalScope globalScope,@NonNullDslContainerProvider<DefaultConfig, BuildType, ProductFlavor, SigningConfig>dslContainers,@NonNull NamedDomainObjectContainer<BaseVariantOutput> buildOutputs,@NonNull ExtraModelInfo extraModelInfo) {ApplicationExtensionImpl applicationExtension =dslServices.newDecoratedInstance(ApplicationExtensionImpl.class, dslServices, //..忽略一万行代码//创建“android”,注入到所有Extensions中return project.getExtensions().create("android",BaseAppModuleExtension.class,dslServices,globalScope,buildOutputs,dslContainers.getSourceSetManager(),extraModelInfo,applicationExtension);}}

AppPluginLibraryPlugin代码都相当简单,主要区别在各自的泛型以及createExtensionandroid的实现类不同。逻辑都在其父类com.android.build.gradle.internal.plugins.BasePlugin,这个类也实现了Plugin接口,我们主要看apply方法

@Overridepublic final void apply(@NonNull Project project) {CrashReporting.runAction(() -> {basePluginApply(project);pluginSpecificApply(project);project.getPluginManager().apply(AndroidBasePlugin.class);});}

接着进入basePluginApply

private void basePluginApply(@NonNull Project project) {// We run by default in headless mode, so the JVM doesn't steal focus.System.setProperty("java.awt.headless", "true");this.project = project;//...configuratorService.recordBlock(ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,project.getPath(),null,this::configureProject);configuratorService.recordBlock(ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,project.getPath(),null,this::configureExtension);configuratorService.recordBlock(ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,project.getPath(),null,this::createTasks);}

这3方法都比较重要,但我们要看configureExtension

private void configureExtension() {DslServices dslServices = globalScope.getDslServices();final NamedDomainObjectContainer<BaseVariantOutput> buildOutputs =project.container(BaseVariantOutput.class);project.getExtensions().add("buildOutputs", buildOutputs);//1.创建BuildType 工厂类variantFactory = createVariantFactory(projectServices, globalScope);variantInputModel =new LegacyVariantInputManager(dslServices,variantFactory.getVariantType(),new SourceSetManager(project,isPackagePublished(),dslServices,new DelayedActionsExecutor()));//2.子类中重载的方法                       	extension =createExtension(dslServices, globalScope, variantInputModel, buildOutputs, extraModelInfo);//3.把扩展添加到全局作用域       globalScope.setExtension(extension);androidComponentsExtension = createComponentExtension(dslServices, variantApiOperations);variantManager =new VariantManager(globalScope,project,projectServices.getProjectOptions(),extension,variantApiOperations,variantFactory,variantInputModel,projectServices);registerModels(registry,globalScope,variantInputModel,extension,extraModelInfo);// create default Objects, signingConfig first as its used by the BuildTypes.//创建默认的buildType和签名variantFactory.createDefaultComponents(variantInputModel);createAndroidTestUtilConfiguration();}

这段代码我们需要了解的有三处:

  • createVariantFactory创建了我们的buildTypes工厂
  • 调用熟悉的createExtension创建我们的扩展实例,就是在上面提到子类中重载的方法。
  • 把扩展实例添加到全局作用域
  • 创建默认的buildType和签名

那么我们接下来createVariantFactory是怎么创建的,继而看我们的BuildType到底怎么创建的。

    protected abstract VariantFactory<VariantBuilderT, VariantT> createVariantFactory(@NonNull ProjectServices projectServices, @NonNull GlobalScope globalScope);

这是个抽象方法,我们看实现类

在这里插入图片描述

能看到AppPlugin,这个类是BaseAppModuleExtension的父类

@NonNull@Overrideprotected ApplicationVariantFactory createVariantFactory(@NonNull ProjectServices projectServices, @NonNull GlobalScope globalScope) {return new ApplicationVariantFactory(projectServices, globalScope);}

最终到了ApplicationVariantFactory


class ApplicationVariantFactory(projectServices: ProjectServices,globalScope: GlobalScope
) : AbstractAppVariantFactory<ApplicationVariantBuilderImpl, ApplicationVariantImpl>(projectServices,globalScope
) {//。。。
}

这个类的父类是AbstractAppVariantFactory,我们先记住这个类,返回到configureExtension,查看variantFactory.createDefaultComponents(variantInputModel);

    fun createDefaultComponents(dslContainers: DslContainerProvider<DefaultConfig, BuildType, ProductFlavor, SigningConfig>)

这是VariantFactory接口的抽象方法,接着看实现类AbstractAppVariantFactory

    override fun createDefaultComponents(dslContainers: DslContainerProvider<DefaultConfig, BuildType, ProductFlavor, SigningConfig>) {// must create signing config first so that build type 'debug' can be initialized// with the debug signing config.//创建默认的debug签名dslContainers.signingConfigContainer.create(BuilderConstants.DEBUG)//创建默认的debug和release buildTypedslContainers.buildTypeContainer.create(BuilderConstants.DEBUG)dslContainers.buildTypeContainer.create(BuilderConstants.RELEASE)}

这个方法里做了两件事:

  • 创建默认的debug签名
  • 创建默认的debug和releae buildType

这就解释了为什么我们项目为什么不配置签名也能安装,以及两个默认的buidType从何而来。(这段代码救了我的命,在做平台化Flavor和签名的时候要配置我们的签名密钥,创建SigningConfig遇到问题,就是看这段代码找到了解决方法)

好,我们接着往下看dslContainers.buildTypeContainer.create(BuilderConstants.DEBUG)

在这里插入图片描述

是不是回到了远点,又是NamedDomainObjectContainer

buildTypeContainer

那么我们的buildTypeContainer从何而来呢?

我们要看dslContainers;

createDefaultComponents(),从方法形参可以看到它的类型是DslContainerProvider

往回退到configureExtension(),可以看到dslContainers就是variantInputModel;在configureExtension开始的地方创建的:

variantInputModel =new LegacyVariantInputManager(dslServices,variantFactory.getVariantType(),new SourceSetManager(project,isPackagePublished(),dslServices,new DelayedActionsExecutor()));

我们再看这个LegacyVariantInputManager的代码:


class LegacyVariantInputManager(dslServices: DslServices,variantType: VariantType,sourceSetManager: SourceSetManager
) : AbstractVariantInputManager<DefaultConfig, BuildType, ProductFlavor, SigningConfig>(dslServices,variantType,sourceSetManager
) {override val buildTypeContainer: NamedDomainObjectContainer<BuildType> =dslServices.domainObjectContainer(BuildType::class.java, BuildTypeFactory(dslServices))override val productFlavorContainer: NamedDomainObjectContainer<ProductFlavor> =dslServices.domainObjectContainer(ProductFlavor::class.java,ProductFlavorFactory(dslServices))override val signingConfigContainer: NamedDomainObjectContainer<SigningConfig> =dslServices.domainObjectContainer(SigningConfig::class.java,SigningConfigFactory(dslServices,getBuildService(dslServices.buildServiceRegistry,AndroidLocationsBuildService::class.java).get().getDefaultDebugKeystoreLocation()))override val defaultConfig: DefaultConfig = dslServices.newInstance(DefaultConfig::class.java,BuilderConstants.MAIN,dslServices)
}

这里我特意多粘贴了点代码,是不是看着很熟悉,有productFlavorsigningConfigContainer, defaultConfig

我们接着看buildTypeContainer = dslServices.domainObjectContainer,它是接口DslServices的方法,唯一实现类DslServicesImpl


class DslServicesImpl constructor(projectServices: ProjectServices,override val sdkComponents: Provider<SdkComponentsBuildService>
) : BaseServicesImpl(projectServices), DslServices {override fun <T> domainObjectContainer(type: Class<T>,factory: NamedDomainObjectFactory<T>): NamedDomainObjectContainer<T> =projectServices.objectFactory.domainObjectContainer(type, factory)}

继续点进去domainObjectContainer,他是接口ObjectFactory的方法,它的有效实现类是DefaultObjectFactory,查看它的方法:

@Overridepublic <T> NamedDomainObjectContainer<T> domainObjectContainer(Class<T> elementType, NamedDomainObjectFactory<T> factory) {return domainObjectCollectionFactory.newNamedDomainObjectContainer(elementType, factory);}

继续点进去仍然是个接口DomainObjectCollectionFactory:

public interface DomainObjectCollectionFactory {<T> NamedDomainObjectContainer<T> newNamedDomainObjectContainer(Class<T> elementType, NamedDomainObjectFactory<T> factory);
}

接着看它唯一实现类DefaultDomainObjectCollectionFactory:

是不是很绕啊,确实这么绕,山高水远,道阻且长,说实话

BaePlugin


public class DefaultDomainObjectCollectionFactory implements DomainObjectCollectionFactory {@Overridepublic <T> NamedDomainObjectContainer<T> newNamedDomainObjectContainer(Class<T> elementType, NamedDomainObjectFactory<T> factory) {Instantiator instantiator = instantiatorFactory.decorateLenient();return Cast.uncheckedCast(instantiator.newInstance(FactoryNamedDomainObjectContainer.class, elementType, instantiator, new DynamicPropertyNamer(), factory, mutationGuard, collectionCallbackActionDecorator));}}

看到这里就没有必要继续往下看了,其实已经能看出来了,这里其实就是反射创建了FactoryNamedDomainObjectContainer的实例,那我们接着看它


public class FactoryNamedDomainObjectContainer<T> extends AbstractNamedDomainObjectContainer<T> {public FactoryNamedDomainObjectContainer(Class<T> type, Instantiator instantiator, Namer<? super T> namer, NamedDomainObjectFactory<T> factory, MutationGuard crossProjectConfiguratorMutationGuard, CollectionCallbackActionDecorator collectionCallbackActionDecorator) {super(type, instantiator, namer, collectionCallbackActionDecorator);this.factory = factory;this.crossProjectConfiguratorMutationGuard = crossProjectConfiguratorMutationGuard;}@Overrideprotected T doCreate(String name) {return factory.create(name);}
}

最终,我们找到了dslContainers.buildTypeContainer的实现类NamedDomainObjectContainer

(这里我特意贴了构造函数和一个方法,先记住,然后往后看)

它并没有实现create方法,我们接着看它的父类


public abstract class AbstractNamedDomainObjectContainer<T> extends DefaultNamedDomainObjectSet<T> implements NamedDomainObjectContainer<T>, HasPublicType {@Overridepublic T create(String name) {assertMutable("create(String)");return create(name, Actions.doNothing());}@Overridepublic T create(String name, Closure configureClosure) {assertMutable("create(String, Closure)");return create(name, ConfigureUtil.configureUsing(configureClosure));}@Overridepublic T create(String name, Action<? super T> configureAction) throws InvalidUserDataException {assertMutable("create(String, Action)");assertCanAdd(name);//创建BuildType实例T object = doCreate(name);//添加到集合中add(object);//传递给我们的回调,让我们自己配置configureAction.execute(object);return object;}}

可以看到实现了NamedDomainObjectContainer的3个create方法,最主要的是第三个方法,我们再次回顾一下build.gradle里面

buildTypes {create("test", object : Action<ApplicationBuildType> {override fun execute(t: ApplicationBuildType) {t.isMinifyEnabled = falset.isDebuggable = true}})}

对比一下上面的方法,是不是觉得柳暗花明了😎😎😎

我们知道,create创建的就是BuildType实例(它实现了ApplicationBuildType),这里主要做了3件事:

  • 创建BuildType实例
  • 添加到缓存集合中
  • 传递给我们的回调,由我们自己去配置

看到希望了,我们接着往下看,我们的BuildType是怎么创建出来的,doCreate是个抽象方法,FactoryNamedDomainObjectContainer:

@Overrideprotected T doCreate(String name) {return factory.create(name);}

看到这个方法,我震惊了🙃🙃🙃我费尽心思追了这么久想看factory是什么终于找到了FactoryNamedDomainObjectContainer,你告诉我创建BuildType的还不是它?🤦‍♂️可是我能有什么办法啊,为了维护世界和平,为了沉没成本,只能继续看啊!

那我们看factory是哪里来的,记得我说前面特意贴的构造函数吗,竟然是构造函数传进来的,那我就返回往前看,发现我们来时的路上每一个方法的参数都是它的身影,直到LegacyVariantInputManager

    override val buildTypeContainer: NamedDomainObjectContainer<BuildType> =dslServices.domainObjectContainer(BuildType::class.java, BuildTypeFactory(dslServices))

看它的第二个参数点进去:


public class BuildTypeFactory implements NamedDomainObjectFactory<BuildType> {@NonNull private DslServices dslServices;public BuildTypeFactory(@NonNull DslServices dslServices) {this.dslServices = dslServices;}@NonNull@Overridepublic BuildType create(@NonNull String name) {return dslServices.newInstance(BuildType.class, name, dslServices);}
}

诺,就是这里了,后面我实在不想浪费时间继续贴了,相信大家已经看腻了,总结六个字——反射创建实例


流程总结

ok,到这一步,我们就明白了:

  • BuildType是由FactoryNamedDomainObjectContainer创建的;
  • 实例是由BuildTypeFactory反射创建的;
  • BuildType实例创建后添加到了FactoryNamedDomainObjectContainer,然后又给到我们的回调由我们自己配置

AppPlugin创建默认BuildType和我们自己在build.gradle 添加方式是一样的。

5. 回归初心——扩展是如何传递数据给插件的呢?

我们回溯上篇章的内容,找到各个类的从属关系:AppExtension持有ApplicationVariantFactory,后者在前者实例化时通过构造函数注入,它又持有DslServiceImpl… :

  • AppExtension -> ApplicationVariantFactory ->DslServiceImpl->FactoryNamedDomainObjectContainer->BuildType,
  • 而 ApplicationVariantFactory 和 DslServiceImpl 是BasePlugin的成员变量

简单的结构图:

在这里插入图片描述

总结

所以我们可以直观的理解为,ApplicationVariantFactory 就是注入A品牌Extension的一个callback,build.gralde里面看似调用AppExtension的函数,实则调用AppPlugin的成员。

经过千难万险,坎坷曲折,一路不停的追踪代码,最终我们得出这样一个很简单额结论,是不是很开心呢😂

6 应用

好,既然我们已经明白了这个原理,那么我们就可以在我们插件中应用。

改造我们的扩展:


public class RocketExtensions {//多平台支持。如若支持,会默认配置多个flavorprivate RocketConfigs mExtensionContainer;public RocketExtensions(BoosterConfigs container) {mExtensionContainer = container;}public void setMultiFlavor(boolean multi) {mExtensionContainer.setMultiFlavor(multi);}
}

新增一个配置类RocketConfigs:


public class RocketConfigs {public boolean isMultiFlavor;public void setMultiFlavor(boolean multi) {System.out.println("setMultiFlavor:" + multi);this.isMultiFlavor = multi;//doSth}
}

插件类:


public class BoosterPlugin implements Plugin {@Overridepublic void apply(Project project) {super.apply(project);//初始化配置类RocketConfigs container = new BoosterConfigs();//注入扩展project.getExtensions().create("rocket", RocketExtensions.class, container);}
}

然后发布,在应用的build.gradle里使用:


plugins {id("com.android.application")id("gradle-rocket")
}rocket {setMultiFlavor(true)
}
android{//...
}

同步一下就可以看到build输出

在这里插入图片描述


为了看明白这块儿代码真的费尽心思,弯弯绕绕,脑袋都晕了,不过最终搞懂这些东西还是非常开心的。

好了,本篇到此结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/823222.shtml

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

相关文章

HDLbits 刷题 -- Mux256to1

Create a 1-bit wide, 256-to-1 multiplexer. The 256 inputs are all packed into a single 256-bit input vector. sel0 should select in[0], sel1 selects bits in[1], sel2 selects bits in[2], etc. Expected solution length: Around 1 line. 译&#xff1a; 创建一个…

CESS 受邀出席香港Web3.0标准化协会第一次理事会议,共商行业未来

2024 年 4 月 5 日&#xff0c;CESS&#xff08;Cumulus Encrypted Storage System&#xff09;作为香港 Web3.0 标准化协会的副理事会成员&#xff0c;于香港出席了 2024 年度第一次理事会会议。此次会议汇聚了来自不同领域的知名企业和专家&#xff08;参会代表名单见文末&am…

Linux——守护进程

在这篇文章中我介绍了关于tcp网络套接字&#xff0c;关于网络套接字编程的问题我会再次讲述一点东西&#xff0c;然后介绍关于守护进程的知识。 1. 关于网络套接字编程的一些问题 在进行套接字编程时我们一定是得先有套接字&#xff0c;并且我们在使用socket的一些接口时&…

MyBatis 源码分析 - 映射文件解析过程

1.简介 在上一篇文章中&#xff0c;我详细分析了 MyBatis 配置文件的解析过程。由于上一篇文章的篇幅比较大&#xff0c;加之映射文件解析过程也比较复杂的原因。所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来&#xff0c;独立成文&#xff0c;于是就有了本篇文章…

新标准日本语 课后练习

自学错误可能较多&#xff0c;听力题不需要听力的就没听录音 第二十課 スミスさんはピアノを弾くことができます 練習&#xff11;&#xff0d;&#xff11; &#xff11;張さんは日本の歌を歌うことができます 张先生会唱日本歌 &#xff12;小野さんは自転車に乗ることがで…

设计模式代码实战-模版方法模式

1、问题描述 小明喜欢品尝不同类型的咖啡&#xff0c;她发现每种咖啡的制作过程有一些相同的步骤&#xff0c;他决定设计一个简单的咖啡制作系统&#xff0c;使用模板方法模式定义咖啡的制作过程。系统支持两种咖啡类型&#xff1a;美式咖啡&#xff08;American Coffee&#…

RAID 5实训

Raid 5 配置实训 实验结构&#xff1a; 实验步骤 环境准备&#xff0c;Linux操作虚拟机下添加3个及以上的磁盘用于raid 10阵列构建所用&#xff08;本次实验4个&#xff0c;一个用于备用&#xff09;创建raid 5 磁盘阵列格式化并挂载磁盘阵列存储速度检查是否提升安全性能检查…

DevExpress WinForms中文教程 - 如何通过UI测试自动化增强应用可靠性?(二)

DevExpress WinForm拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜任…

C++进阶——继承

前言&#xff1a;从这篇文章开始&#xff0c;我们进入C进阶知识的分享&#xff0c;在此之前&#xff0c;我们需要先来回顾一个知识&#xff1a; C语言有三大特性&#xff0c;分别是封装、继承和多态&#xff0c;而我们前边所分享的各种容器类&#xff0c;迭代器等&#xff0c;…

数据结构----链表算法题目

1.移除链表的元素 这个题目我们有多种解决方案 &#xff08;1&#xff09;思路A&#xff1a;遍历整串数据&#xff0c;如果是我们想要删除的数据&#xff0c;就让这个数字后面的数字全部向前移动直到整传数字全部遍历完成&#xff1b;这个方法的时间复杂度是N的平方&#xff…

ARM作业day8

温湿度数据采集应用&#xff1a; 由上图可知&#xff1a; 控制温湿度采集模块的引脚是PF14&#xff08;串行时钟线&#xff09;和PF15&#xff08;串行数据线&#xff09;&#xff1a;控制温湿度采集模块的总线是AHB4&#xff0c;通过GPIOF串口和RCC使能完成初始化操作。 控制…

MAC-OS低版本升级到高版本——亲测有效

关于MAC-OS 10.13.6 升级到10.15的实战 一.MAC 欧司如何查看他的系统版本 查看信息如图 二.,去官网下载新的MAC OS 系统 官网地址&#xff1a;如何下载和安装 macOS - 官方 Apple 支持 (中国) 三.点击安装 四.具体步骤可以参考官网 在兼容的 Mac 电脑上下载并安装最新或以前…

最新版idea 合并分支方法

前言 以下是最新版的idea2024&#xff0c;如果有人找不到按键可能是因为版本不同。 操作步骤 看右小角我的分支是submit&#xff0c;现在我要将test合并到我的submit分支上 找到test分支&#xff0c;选择update&#xff0c;这一步会拉取相应分支内容等同于pull 选择merge 选…

I2C,UART,SPI(STM32、51单片机)

目录 基本理论知识&#xff1a; 并行通信/串行通信&#xff1a; 异步通信/同步通信&#xff1a; 半双工通信/全双工通信: UART串口&#xff1a; I2C串口&#xff1a; SPI串口&#xff1a; I2C在单片机中的应用&#xff1a; 软件模拟&#xff1a; 51单片机&#xff1a;…

PHP-file_get_contents(练习1)

[题目信息]&#xff1a; 题目名称题目难度PHP-file_get_contents(练习1)1 [题目考点]&#xff1a; file_get_contents() 把整个文件读入一个字符串中。 该函数是用于把文件的内容读入到一个字符串中的首选方法。如果服务器操作系统支持&#xff0c;还会使用内存映射技术来增…

同城O2O系统开发实战:外卖送餐APP的技术架构与实现

今天&#xff0c;我们将深入探讨同城O2O系统开发实战中&#xff0c;外卖送餐APP的技术架构与实现。 一、概述 外卖送餐APP是一种典型的O2O应用&#xff0c;通过移动互联网技术&#xff0c;将用户与商家连接起来&#xff0c;实现用户在线订餐&#xff0c;商家配送服务的模式。…

Git分布式版本控制系统——在IDEA中使用Git(一)

一、在IDEA中配置Git 本质上还是使用的本地安装的Git软件&#xff0c;所以需要在IDEA中配置Git 打开IDEA的设置页面&#xff0c;按照下图操作 二、在IDEA中使用Git获取仓库 1、本地初始化仓库 2、从远程仓库克隆 方法一&#xff1a; 方法二&#xff1a; 三、.gitignore文件…

简单的网站-表白墙(前后端交互)

提交信息后&#xff0c;就得到了下面的一行话 但是存在一些问题 在一个网站中&#xff0c;服务器起到的最主要的效果&#xff0c;就是 “存储数据” 因此服务器这边往往也就需要能够提供两种风格的接口。存数据 、取数据 二、实现前后端交互 1&#xff09;先规定此处请求和响…

2024-04-11最新dubbo+zookeeper下载安装,DEMO展示

dubbozookeeper下载安装 下载zookeeper&#xff1a; 下载地址 解压&#xff0c;并进入bin目录&#xff0c;启动 如果闪退可以编辑脚本&#xff0c;在指定位置加上暂停脚本 报错内容说没有conf/zoo.cfg&#xff0c;就复制zoo_sample.cfg重命名为zoo.cfg 再次启动脚本&#x…

前端网络 --- http缓存

什么是http缓存&#xff1f; 1、HTTP 缓存会存储与请求关联的响应&#xff0c;并将存储的响应复用于后续请求。 2、缓存的原理是在首次请求后保存一份请求资源的响应副本&#xff0c;当用户再次发起相同请求时&#xff0c;判断缓存是否命中&#xff0c;如果命中则将前面的响应…