Gradle 庖丁解牛(构建源头源码浅析)

所谓基础技能就是一些铺垫,既然要说 Gradle 构建系列了,我们有必要知道 Gradle 相关的一些基本命令,方便下面演示时使用(注意:如下命令是针对安装 gradle 并配置环境变量的情况下执行,你如果做 Android 开发的话,Ubuntu 直接将如下所有 gradle 替换为 ./gradlew 即可,这样就可以用包装特性了,至于包装是咋回事后面会说),关于其他命令请呼叫 help 或者查看官方 User Guide 的 Using the Gradle Command-Line

//不懂 Gradle 命令了就常呼叫帮助吧。

gradle --help //或者 gradle -h 或者 gradle -? 。

//和本节相关的,查看当前 Gradle 构建包含哪些 task。

gradle tasks [–all] //–all可以更加详细。

//和本节相关的,运行 task。

gradle [taskName | tN] [taskName | tN] … //支持多个 task 执行,支持驼峰缩写方式,譬如tN,但tN必须全局保持唯一。

//和本节相关的,当我们想修改 Gradle 插件默认定制约束配置时可以查看官方 dsl 文档或者运行如下命令。

gradle properties //结果会列出标准可配置属性列表和当前默认值,我们一般也可用该命令检查自己修改的属性值是否是期望的,譬如 Android 中修改 Project 的编译输出默认目录等。

//查看web版的构建性能耗时分布表,位于build/reports/profile目录下。

gradle taskName --profile

看完和本篇相关的命令总结后来看看前面提到的 gradle 替换 gradlew 执行是咋回事,其实实质可以查看官方 User Guide 的 The Gradle Wrapper,这里给出总结如下:

//项目目录下这四个文件的作用

gradlew (Unix Shell script)

gradlew.bat (Windows batch file)

gradle/wrapper/gradle-wrapper.jar (Wrapper JAR)

gradle/wrapper/gradle-wrapper.properties (Wrapper properties)

其实上面脚本的作用就是对 Gradle 的一个包装,保证任何机器都可以运行这个构建,即使这个机器没有安装 Gradle 或者安装的 Gradle 版本不兼容匹配,如果没装或者不兼容就会根据 gradle-wrapper.properties 里的配置下载特定版本,下载的包放在 $USER_HOME/.gradle/wrapper/dists 目录下,所以我们有时候第一次通过 gradlew 包装脚本执行构建时总会看到正在下载一个 zip 文件,实质就是在下 gradle-wrapper.properties 中指定的 zip 包。

gradlew 有一个非常人性化和牛逼的特点,解决了几种坑爹的问题,譬如团队开发保证 Gradle 构建版本一致性、gradle-wrapper.properties 下载地址方便切换到公司内部服务器等(甚至可以先通过 gradle.properties 文件配置公司服务器的帐号密码或者验证信息,然后在 gradle-wrapper.properties 中配置distributionUrl=https://username:password@somehost/path/to/gradle-distribution.zip,这样就可以达到公司内网加认证下载的双重安全了)。

gradlew 由来的最终原理其实是通过 Gradle 预置的 Wrapper (继承自 DefaultTask )来实现的(AS 内部默认实现了自动生成而已),譬如:

task createWrapper(type: Wrapper) {

gradleVersion = ‘3.4’

}

运行 gradle createWrapper 就能生成上面描述的几个文件。而关于 Gradle 的 Wrapper 可以参考 DSL Reference 的 WrapperJavaDoc 的 Wrapper API,里面提供了一堆属性设置下载地址、解压路径、 gradleVersion 等,你也可以在 distributionUrl 中通过 ${gradleVersion} 来使用你设置的 DSL 变量,这里暂时不再展开。

说完命令我们接着说说 Groovy、DSL、Gradle 之间的关系,首先一定要明确,Groovy 不是 DSL,而是通用的编程语言,类似 Java、C++ 等,就是一种语言;但 Groovy 对编写 DSL 提供了很牛逼的支持,这些支持都源自 Groovy 自己语法的特性,比如闭包特性、省略分号特性、有参方法调用省略括弧特性、属性默认实现 getter、setter 方法特性等,当然,作为 Android 开发来说,Gradle 构建 Android 应用实质也是基于 Groovy 编写的 DSL,DSL 存在的意义就在于简化编写和阅读。

而 Gradle 的实质就是一个基于 Groovy 的框架了,也就是说我们得按照他的约束来玩了,和我们平时 Android 开发使用框架类似,一旦引入框架,我们就得按照框架的写法来规规矩矩的编写。Gradle 这个框架只负责流程约定,处理细节是我们自己的事,就像我们编译 Android App 时基于 Gradle 框架流程引入 apply plugin: 'com.android.application' 构建插件一样,具体做事是我们插件再约束的,插件又对我们简化了配置,我们只用基于 Gradle 框架和相关插件进行构建配置。

了所以简单粗暴的解释就是, Groovy 是一门语言,DSL 就是一种特定领域的配置文件,Gradle 就是基于 Groovy 的一种框架,就像我们以前做 Android 开发使用 Ant 构建一样,build.xml 就可以粗略的理解为 Ant 的 DSL 配置,所以我们编写 build.xml 时会相对觉得挺轻松(和后来的 Gradle 还是没法比的)。

搞清了他们之间的关系后我们就要深入看看为啥是这样了,因为关于 Gradle 构建现在中文网上的教程要么是教你如何配置 DSL 属性,要么就是教你如何使用 Groovy 去拓展自己的特殊 task,或者是教你怎么编写插件,却几乎没人提到 Gradle 这个构建框架的本质,所以使用起来总是心里发慌,有种不受控制的感觉。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

3 Gradle 源码源头分析

===================

还记得我们前一小节讲的 gradlew 么,追踪溯源就从它开始,以前我也是出于好奇就去看了下它,发现这个 shell 脚本前面其实就是干了一堆没事干的事,大招就在它的最后一句,如下:

exec “ J A V A C M D " " JAVACMD" " JAVACMD""{JVM_OPTS[@]}” -classpath “ C L A S S P A T H " o r g . g r a d l e . w r a p p e r . G r a d l e W r a p p e r M a i n " CLASSPATH" org.gradle.wrapper.GradleWrapperMain " CLASSPATH"org.gradle.wrapper.GradleWrapperMain"@”

对的,大招就是 GradleWrapperMain,这货还支持通过 “$@” 传递参数,想想我们平时执行的 gradlew 命令,这下明白了吧,既然这样我们就拿他开涮,去看看他的源码,如下:

public class GradleWrapperMain {

//执行 gradlew 脚本命令时触发调用的入口。

public static void main(String[] args) throws Exception {

//不多说,正如上面分析 gradlew 作用一样,去工程目录下获取 wrapper.jar 和 properties 文件。

File wrapperJar = wrapperJar();

File propertiesFile = wrapperProperties(wrapperJar);

File rootDir = rootDir(wrapperJar);

//解析gradlew 输入传递的参数等,反正就是一堆准备工作

CommandLineParser parser = new CommandLineParser();

//憋大招的两行

WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile);

wrapperExecutor.execute(

args,

new Install(logger, new Download(logger, “gradlew”, wrapperVersion()), new PathAssembler(gradleUserHome)),

new BootstrapMainStarter());

}

}

上面 WrapperExecutor.forWrapperPropertiesFile 方法实质就是通过 java Properties 去解析 gradle/wrapper/gradle-wrapper.properties 文件里的配置,譬如 distributionUrl 等;接着执行 wrapperExecutor.execute 方法,args 参数就是我们执行 gradlew 脚本时传递的参数,Install 实例就是管理本地本项目是否有 wrapper 指定版本的 gradle,木有就去下载解压等等,BootstrapMainStarter 实例就是 wrapper 执行 gradle 真实入口的地方,所以我们接着看看 execute 这个方法,如下:

public void execute(String[] args, Install install, BootstrapMainStarter bootstrapMainStarter) throws Exception {

//config 就是 gradle-wrapper.properties 解析的配置

File gradleHome = install.createDist(config);

bootstrapMainStarter.start(args, gradleHome);

}

到这里如果本地没有 wrapper 包装的 Gradle,就会下载解压等,然后准备一堆货,货备足后就调用了前面说的 BootstrapMainStarter 的 start 方法,如下:

public class BootstrapMainStarter {

public void start(String[] args, File gradleHome) throws Exception {

File gradleJar = findLauncherJar(gradleHome);

URLClassLoader contextClassLoader = new URLClassLoader(new URL[]{gradleJar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent());

Thread.currentThread().setContextClassLoader(contextClassLoader);

Class<?> mainClass = contextClassLoader.loadClass(“org.gradle.launcher.GradleMain”);

Method mainMethod = mainClass.getMethod(“main”, String[].class);

mainMethod.invoke(null, new Object[]{args});

if (contextClassLoader instanceof Closeable) {

((Closeable) contextClassLoader).close();

}

}

}

不解释,快上车,真的 Gradle 要现身了,Wrapper 的使命即将终结,我们把重点转到 org.gradle.launcher.GradleMain 的 main 方法,如下:

public class GradleMain {

public static void main(String[] args) throws Exception {

new ProcessBootstrap().run(“org.gradle.launcher.Main”, args);

}

}

GG了,莫慌,我们的重点不是看懂 Gradle 的每一句代码,我们需要捡自己需要的重点,这货设置各种 ClassLoader 后最终还是调用了 org.gradle.launcher.Main 的 run 方法,实质就是 EntryPoint 类的 run 方法,因为 Main 类是 EntryPoint 类的实现类,而 EntryPoint 的 run 方法最主要做的事情就是创建了一个回调监听接口,然后调用了 Main 重写的 doAction 方法,所以我们去到 Main 的 doAction 看看,如下:

public class Main extends EntryPoint {

protected void doAction(String[] args, ExecutionListener listener) {

createActionFactory().convert(Arrays.asList(args)).execute(listener);

}

CommandLineActionFactory createActionFactory() {

return new CommandLineActionFactory();

}

}

这货实质调用了 CommandLineActionFactory 实例的 convert 方法得到 Action 实例,然后调用了 Action 的 execute 方法,我去,真特么绕的深,这弯溜的,我们会发现 CommandLineActionFactory 里的 convert 方法实质除过 log 记录准备外干的惟一一件事就是创建其内部类 WithLogging 的对象,这时候我们可以发现 Action 的 execute 方法实质就是调用了 WithLogging 的 execute 实现,如下:

public void execute(ExecutionListener executionListener) {

//executionListener 是前面传入的回调实现实例

//各种解析config,譬如参数的–内容等等,不是我们的重点

//各种初始化、log启动等等,不是我们的重点

//大招!!!外面new WithLogging实例时传入的参数!!!

action.execute(executionListener);

}

这不,最终还是执行了 `new ExceptionReportingAction(

new JavaRuntimeValidationAction(

new ParseAndBuildAction(loggingServices, args)),

new BuildExceptionReporter(loggingServices.get(StyledTextOutputFactory.class), loggingConfiguration, clientMetaData())));对象的 execute 方法(上面的 action 就是这个对象),关于这个对象的创建我们只用关注new JavaRuntimeValidationAction(

new ParseAndBuildAction(loggingServices, args))` 这个参数即可,这也是一个 Action,实例化后在 ExceptionReportingAction 的 execute 调用了他的 execute,而 ParseAndBuildAction 的 execute 又被 JavaRuntimeValidationAction 的 execute 触发,有点包装模式的感觉,所以我们直接关注 ParseAndBuildAction 的实例化和 execute 方法,因为其他不是我们的重点,如下:

private class ParseAndBuildAction implements Action {

public void execute(ExecutionListener executionListener) {

List actions = new ArrayList();

//给你一个默认的 help 和 version 的 CommandLineAction 加入 actions 列表

actions.add(new BuiltInActions());

//创建一个 GuiActionsFactory 和 BuildActionsFactory 加入 actions 列表

createActionFactories(loggingServices, actions);

//依据参数给各个添加到列表的 CommandLineAction 对象进行配置

CommandLineParser parser = new CommandLineParser();

for (CommandLineAction action : actions) {

action.configureCommandLineParser(parser);

}

//依据这几个参数获取创建一个可用的 Action<? super ExecutionListener>

Action<? super ExecutionListener> action;

try {

ParsedCommandLine commandLine = parser.parse(args);

//如果输入的命令中包含 gui 参数则创建 GuiActionsFactory 的 action 备用。

//如果输入的命令中包含 help 或者 version 参数则创建 BuiltInActions 的 action 备用。

//其他参数的则创建 BuildActionsFactory 的 action 备用。

action = createAction(actions, parser, commandLine);

} catch (CommandLineArgumentException e) {

action = new CommandLineParseFailureAction(parser, e);

}

//执行我们创建的备用 action。。。。

action.execute(executionListener);

}

}

既然我们是追主线分析(关于执行命令中带 help、version、gui 的情况我们就不分析了,也比较简单,当我们执行 gradle –help 或者 gradle –gui 时打印的 help 或者弹出的 GUI 是 BuiltInActions 或者 GuiActionsFactory ,比较简单,不作分析),我们看核心主线 BuildActionsFactory 的 createAction 方法和通过 createAction 方法生成的 Runnable 的 run 方法即可(所谓的主线就是我们执行 gradle taskName 命令走的流程,譬如 gradle asseambleDebug 等),如下是 BuildActionsFactory 的 createAction 方法:

public Runnable createAction(CommandLineParser parser, ParsedCommandLine commandLine) {

//命令各种转换包装

Parameters parameters = parametersConverter.convert(commandLine, new Parameters());

//三种判断,哪个中了就返回,runXXX系列方法实质都调用了runBuildAndCloseServices方法,只是参数不同而已

if (parameters.getDaemonParameters().isEnabled()) {

return runBuildWithDaemon(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);

}

if (canUseCurrentProcess(parameters.getDaemonParameters())) {

return runBuildInProcess(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);

}

return runBuildInSingleUseDaemon(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);

}

既然上面都判断最后调用都是类同的,那我们就假设调用了 runBuildInProcess 方法吧,如下:

private Runnable runBuildInProcess(StartParameter startParameter, DaemonParameters daemonParameters, ServiceRegistry loggingServices) {

//创建client,这是个神奇的设计思路,大招!

ServiceRegistry globalServices = ServiceRegistryBuilder.builder()

.displayName(“Global services”)

.parent(loggingServices)

.parent(NativeServices.getInstance())

.provider(new GlobalScopeServices(startParameter.isContinuous()))

.build();

//上面说的,BuildActionsFactory的createAction方法最后都是调用这个方法,只是传递参数不同而已!

return runBuildAndCloseServices(startParameter, daemonParameters, globalServices.get(BuildExecuter.class), globalServices);

}

此刻您可憋住了,别小看这么简单的一个方法,这玩意麻雀虽小五脏俱全啊,在我第一次看这部分源码时是懵逼的,好在看见了相关类的注释才恍然大悟,至于为啥我们现在来分析下。先说说 globalServices 对象的构建吧,其实和 createGlobalClientServices() 这个方法类似,随意咯,我们就随便看个,如下:

private ServiceRegistry createGlobalClientServices() {

return ServiceRegistryBuilder.builder()

.displayName(“Daemon client global services”)

.parent(NativeServices.getInstance())

.provider(new GlobalScopeServices(false))

.provider(new DaemonClientGlobalServices())

.build();

}

看起来就是构造了一个 clientSharedServices 对象,然后交给下面的 runBuildAndCloseServices 方法使用,对的,就是

必看视频!获取2024年最新Java开发全套学习资料 备注Java

这样的,只是这个 createGlobalClientServices() 方法真的很懵逼,ServiceRegistryBuilder 的 build() 方法实质是实例化了一个 DefaultServiceRegistry 对象,然后通过构造方法传递了 parent(NativeServices.getInstance()) 实例,通过 addProvider(provider) 方法传递了 provider(new GlobalScopeServices(false)) 和 provider(new DaemonClientGlobalServices()) 实例,巧妙的地方就在 DefaultServiceRegistry 类的注释上面,大家一定要先看注释,ServiceRegistryBuilder 的 build() 最后调用的是 DefaultServiceRegistry 对象的 addProvider 方法,实质调用的是 DefaultServiceRegistry 的 findProviderMethods(provider) 方法,如下:

private void findProviderMethods(Object target) {

Class<?> type = target.getClass();

RelevantMethods methods = getMethods(type);

//把target自己和所有父类中以create开头的方法通过new DecoratorMethodService(target, method)包装加入到ownServices列表。

for (Method method : methods.decorators) {

ownServices.add(new DecoratorMethodService(target, method));

Spring全套教学资料

Spring是Java程序员的《葵花宝典》,其中提供的各种大招,能简化我们的开发,大大提升开发效率!目前99%的公司使用了Spring,大家可以去各大招聘网站看一下,Spring算是必备技能,所以一定要掌握。

目录:

部分内容:

Spring源码

  • 第一部分 Spring 概述
  • 第二部分 核心思想
  • 第三部分 手写实现 IoC 和 AOP(自定义Spring框架)
  • 第四部分 Spring IOC 高级应用
    基础特性
    高级特性
  • 第五部分 Spring IOC源码深度剖析
    设计优雅
    设计模式
    注意:原则、方法和技巧
  • 第六部分 Spring AOP 应用
    声明事务控制
  • 第七部分 Spring AOP源码深度剖析
    必要的笔记、必要的图、通俗易懂的语言化解知识难点

脚手框架:SpringBoot技术

它的目标是简化Spring应用和服务的创建、开发与部署,简化了配置文件,使用嵌入式web服务器,含有诸多开箱即用的微服务功能,可以和spring cloud联合部署。

Spring Boot的核心思想是约定大于配置,应用只需要很少的配置即可,简化了应用开发模式。

  • SpringBoot入门
  • 配置文件
  • 日志
  • Web开发
  • Docker
  • SpringBoot与数据访问
  • 启动配置原理
  • 自定义starter

微服务架构:Spring Cloud Alibaba

同 Spring Cloud 一样,Spring Cloud Alibaba 也是一套微服务解决方案,包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

  • 微服务架构介绍
  • Spring Cloud Alibaba介绍
  • 微服务环境搭建
  • 服务治理
  • 服务容错
  • 服务网关
  • 链路追踪
  • ZipKin集成及数据持久化
  • 消息驱动
  • 短信服务
  • Nacos Confifig—服务配置
  • Seata—分布式事务
  • Dubbo—rpc通信

Spring MVC

目录:

部分内容:

组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

  • 微服务架构介绍
  • Spring Cloud Alibaba介绍
  • 微服务环境搭建
  • 服务治理
  • 服务容错
  • 服务网关
  • 链路追踪
  • ZipKin集成及数据持久化
  • 消息驱动
  • 短信服务
  • Nacos Confifig—服务配置
  • Seata—分布式事务
  • Dubbo—rpc通信

[外链图片转存中…(img-m7MW3aRP-1716441469040)]

[外链图片转存中…(img-wLh3Pn8m-1716441469041)]

Spring MVC

目录:

[外链图片转存中…(img-l4w0bxY2-1716441469041)]

[外链图片转存中…(img-32wUzK3e-1716441469042)]

[外链图片转存中…(img-7j4fB6ae-1716441469043)]

部分内容:

[外链图片转存中…(img-7HFYsRYK-1716441469043)]

[外链图片转存中…(img-liAiHAj7-1716441469043)]

  • 25
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值