SprintBoot 为我们实现了很多的工具集:
常见如下:
maven项目的pom.xml中,添加了org.springframework.boot:spring-boot-maven-plugin
插件,当运行“mvn package”进行打包时,会打包成一个可以直接运行的 JAR 文件,使用“Java -jar”命令就可以直接运行。
一般的maven项目的打包命令,不会把依赖的jar包也打包进去的,只是会放在jar包的同目录下,能够引用就可以了,但是spring-boot-maven-plugin插件,会将依赖的jar包全部打包进去。比如下面这个jar包的BOOT/INF/lib目录下面就包含了所有依赖的jar包
spring-boot-maven-plugin插件,在很大程度上简化了应用的部署,只需要安装了 JRE 就可以运行。
只要在源码中实现:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.4.RELEASE</version>
<configuration>
<mainClass>com.zhengbangnet.app.Startup</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
spring-boot-maven-plugin插件默认在父工程sprint-boot-starter-parent中被指定为repackage,
执行以上命令时会自动触发spring-boot-maven-plugin插件的repackage目标,完后可以在target目录下看到生成的jar,如下图:
包后生成的可执行jar,即可以通过java -jar common.jar命令启动。original这个则是mvn package打包的原始jar,在spring-boot-maven-plugin插件repackage命令操作时重命名为xxx.original,这个是一个普通的jar,可以被引用在其他服务中。
内部结构:
其中BOOT-INF主要是一些启动信息,包含classes和lib文件,classes文件放的是项目里生成的字节文件class和配置文件,lib文件是项目所需要的jar依赖。
META-INF目录下主要是maven的一些元数据信息,MANIFEST.MF文件内容如下:
其中Start-Class是项目的主程序入口,即main方法。Springboot-Boot-Classes和Spring-Boot-Lib指向的是生成的BOOT-INF下的对应位置。
思考一下:问题为什么有时候:报红色呢?
源码如下:
public abstract class AbstractAotMojo extends AbstractDependencyFilterMojo {
/**
* The current Maven session. This is used for toolchain manager API calls.
*/
@Parameter(defaultValue = "${session}", readonly = true)
private MavenSession session;
/**
* The toolchain manager to use to locate a custom JDK.
*/
@Component
private ToolchainManager toolchainManager;
/**
* Skip the execution.
*/
@Parameter(property = "spring-boot.aot.skip", defaultValue = "false")
private boolean skip;
/**
* List of JVM system properties to pass to the AOT process.
*/
@Parameter
private Map<String, String> systemPropertyVariables;
/**
* JVM arguments that should be associated with the AOT process. On command line, make
* sure to wrap multiple values between quotes.
*/
@Parameter(property = "spring-boot.aot.jvmArguments")
private String jvmArguments;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (this.skip) {
getLog().debug("Skipping AOT execution as per configuration");
return;
}
try {
executeAot();
}
catch (Exception ex) {
throw new MojoExecutionException(ex.getMessage(), ex);
}
}
protected abstract void executeAot() throws Exception;
protected void generateAotAssets(URL[] classPath, String processorClassName, String... arguments) throws Exception {
List<String> command = CommandLineBuilder.forMainClass(processorClassName)
.withSystemProperties(this.systemPropertyVariables)
.withJvmArguments(new RunArguments(this.jvmArguments).asArray()).withClasspath(classPath)
.withArguments(arguments).build();
if (getLog().isDebugEnabled()) {
getLog().debug("Generating AOT assets using command: " + command);
}
JavaProcessExecutor processExecutor = new JavaProcessExecutor(this.session, this.toolchainManager);
processExecutor.run(this.project.getBasedir(), command, Collections.emptyMap());
}
protected final void compileSourceFiles(URL[] classPath, File sourcesDirectory, File outputDirectory)
throws Exception {
List<Path> sourceFiles = Files.walk(sourcesDirectory.toPath()).filter(Files::isRegularFile).toList();
if (sourceFiles.isEmpty()) {
return;
}
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) {
List<String> options = new ArrayList<>();
options.add("-cp");
options.add(ClasspathBuilder.build(Arrays.asList(classPath)));
options.add("-d");
options.add(outputDirectory.toPath().toAbsolutePath().toString());
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromPaths(sourceFiles);
Errors errors = new Errors();
CompilationTask task = compiler.getTask(null, fileManager, errors, options, null, compilationUnits);
boolean result = task.call();
if (!result || errors.hasReportedErrors()) {
throw new IllegalStateException("Unable to compile generated source" + errors);
}
}
}
使用如下:
public class FindMainClass extends Task {
private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication";
private String mainClass;
private File classesRoot;
private String property;
public FindMainClass(Project project) {
setProject(project);
}
@Override
public void execute() throws BuildException {
String mainClass = this.mainClass;
if (!StringUtils.hasText(mainClass)) {
mainClass = findMainClass();
if (!StringUtils.hasText(mainClass)) {
throw new BuildException("Could not determine main class given @classesRoot " + this.classesRoot);
}
}
handle(mainClass);
}
private String findMainClass() {
if (this.classesRoot == null) {
throw new BuildException("one of @mainClass or @classesRoot must be specified");
}
if (!this.classesRoot.exists()) {
throw new BuildException("@classesRoot " + this.classesRoot + " does not exist");
}
try {
if (this.classesRoot.isDirectory()) {
return MainClassFinder.findSingleMainClass(this.classesRoot, SPRING_BOOT_APPLICATION_CLASS_NAME);
}
return MainClassFinder.findSingleMainClass(new JarFile(this.classesRoot), "/",
SPRING_BOOT_APPLICATION_CLASS_NAME);
}
catch (IOException ex) {
throw new BuildException(ex);
}
}
private void handle(String mainClass) {
if (StringUtils.hasText(this.property)) {
getProject().setProperty(this.property, mainClass);
}
else {
log("Found main class " + mainClass);
}
}
/**
* Set the main class, which will cause the search to be bypassed.
* @param mainClass the main class name
*/
public void setMainClass(String mainClass) {
this.mainClass = mainClass;
}
/**
* Set the root location of classes to be searched.
* @param classesRoot the root location
*/
public void setClassesRoot(File classesRoot) {
this.classesRoot = classesRoot;
}
/**
* Set the ANT property to set (if left unset, result will be printed to the log).
* @param property the ANT property to set
*/
public void setProperty(String property) {
this.property = property;
}
}
这样我们就可以从ant 中找到main 类了。
这个是Springboot的自动配置处理器:
使用方法如下:
org.springframework.boot.autoconfigureprocessor.AutoConfigureAnnotationProcessor
org.springframework.boot.autoconfigureprocessor.AutoConfigurationImportsAnnotationProcessor
org.springframework.boot.autoconfigureprocessor.ManagementContextConfigurationImportsAnnotationProcessor
实现类就有:
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigureprocessor;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
/**
* An {@link AbstractProcessor} to scan for annotated classes and write the class names to
* a file using the Spring Boot {@code .imports} format.
*
* @author Scott Frederick
*/
abstract class AbstractImportsAnnotationProcessor extends AbstractProcessor {
private final List<String> qualifiedClassNames = new ArrayList<>();
abstract String getImportsFilePath();
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
for (Element element : elements) {
this.qualifiedClassNames.add(element.asType().toString());
}
}
if (roundEnv.processingOver()) {
try {
writeImportsFile();
}
catch (IOException ex) {
throw new IllegalStateException("Failed to write imports file '" + getImportsFilePath() + "'", ex);
}
}
return false;
}
private void writeImportsFile() throws IOException {
if (!this.qualifiedClassNames.isEmpty()) {
Filer filer = this.processingEnv.getFiler();
FileObject file = filer.createResource(StandardLocation.CLASS_OUTPUT, "", getImportsFilePath());
try (Writer writer = new OutputStreamWriter(file.openOutputStream(), StandardCharsets.UTF_8)) {
Collections.sort(this.qualifiedClassNames);
for (String className : this.qualifiedClassNames) {
writer.append(className);
writer.append(System.lineSeparator());
}
}
}
}
}
```java
public class DockerConnectionException extends RuntimeException {
private static final String JNA_EXCEPTION_CLASS_NAME = "com.sun.jna.LastErrorException";
public DockerConnectionException(String host, Exception cause) {
super(buildMessage(host, cause), cause);
}
private static String buildMessage(String host, Exception cause) {
Assert.notNull(host, "Host must not be null");
Assert.notNull(cause, "Cause must not be null");
StringBuilder message = new StringBuilder("Connection to the Docker daemon at '" + host + "' failed");
String causeMessage = getCauseMessage(cause);
if (StringUtils.hasText(causeMessage)) {
message.append(" with error \"").append(causeMessage).append("\"");
}
message.append("; ensure the Docker daemon is running and accessible");
return message.toString();
}
private static String getCauseMessage(Exception cause) {
if (cause.getCause() != null && cause.getCause().getClass().getName().equals(JNA_EXCEPTION_CLASS_NAME)) {
return cause.getCause().getMessage();
}
return cause.getMessage();
}
}
```java
import com.fasterxml.jackson.annotation.JsonCreator;
/**
* A {@link ProgressUpdateEvent} fired as an image is loaded.
*
* @author Phillip Webb
* @since 2.3.0
*/
public class LoadImageUpdateEvent extends ProgressUpdateEvent {
private final String stream;
@JsonCreator
public LoadImageUpdateEvent(String stream, String status, ProgressDetail progressDetail, String progress) {
super(status, progressDetail, progress);
this.stream = stream;
}
/**
* Return the stream response or {@code null} if no response is available.
* @return the stream response.
*/
public String getStream() {
return this.stream;
}
}
Springboot 配置原信息这块:主要是Springboot 配置原信息的插件,源码如下:
public class ConfigurationMetadataGroup implements Serializable {
private final String id;
private final Map<String, ConfigurationMetadataSource> sources = new HashMap<>();
private final Map<String, ConfigurationMetadataProperty> properties = new HashMap<>();
public ConfigurationMetadataGroup(String id) {
this.id = id;
}
/**
* Return the id of the group, used as a common prefix for all properties associated
* to it.
* @return the id of the group
*/
public String getId() {
return this.id;
}
/**
* Return the {@link ConfigurationMetadataSource sources} defining the properties of
* this group.
* @return the sources of the group
*/
public Map<String, ConfigurationMetadataSource> getSources() {
return this.sources;
}
/**
* Return the {@link ConfigurationMetadataProperty properties} defined in this group.
*
* A property may appear more than once for a given source, potentially with
* conflicting type or documentation. This is a "merged" view of the properties of
* this group.
* @return the properties of the group
* @see ConfigurationMetadataSource#getProperties()
*/
public Map<String, ConfigurationMetadataProperty> getProperties() {
return this.properties;
}
}
*/
class ConfigurationMetadataItem extends ConfigurationMetadataProperty {
private String sourceType;
private String sourceMethod;
/**
* The class name of the source that contributed this property. For example, if the
* property was from a class annotated with {@code @ConfigurationProperties} this
* attribute would contain the fully qualified name of that class.
* @return the source type
*/
String getSourceType() {
return this.sourceType;
}
void setSourceType(String sourceType) {
this.sourceType = sourceType;
}
/**
* The full name of the method (including parenthesis and argument types) that
* contributed this property. For example, the name of a getter in a
* {@code @ConfigurationProperties} annotated class.
* @return the source method
*/
String getSourceMethod() {
return this.sourceMethod;
}
void setSourceMethod(String sourceMethod) {
this.sourceMethod = sourceMethod;
}
}
class RawConfigurationMetadata {
private final List<ConfigurationMetadataSource> sources;
private final List<ConfigurationMetadataItem> items;
private final List<ConfigurationMetadataHint> hints;
RawConfigurationMetadata(List<ConfigurationMetadataSource> sources, List<ConfigurationMetadataItem> items,
List<ConfigurationMetadataHint> hints) {
this.sources = new ArrayList<>(sources);
this.items = new ArrayList<>(items);
this.hints = new ArrayList<>(hints);
for (ConfigurationMetadataItem item : this.items) {
resolveName(item);
}
}
List<ConfigurationMetadataSource> getSources() {
return this.sources;
}
ConfigurationMetadataSource getSource(ConfigurationMetadataItem item) {
if (item.getSourceType() == null) {
return null;
}
return this.sources.stream()
.filter((candidate) -> item.getSourceType().equals(candidate.getType())
&& item.getId().startsWith(candidate.getGroupId()))
.max(Comparator.comparingInt((candidate) -> candidate.getGroupId().length())).orElse(null);
}
List<ConfigurationMetadataItem> getItems() {
return this.items;
}
List<ConfigurationMetadataHint> getHints() {
return this.hints;
}
/**
* Resolve the name of an item against this instance.
* @param item the item to resolve
* @see ConfigurationMetadataProperty#setName(String)
*/
private void resolveName(ConfigurationMetadataItem item) {
item.setName(item.getId()); // fallback
ConfigurationMetadataSource source = getSource(item);
if (source != null) {
String groupId = source.getGroupId();
String dottedPrefix = groupId + ".";
String id = item.getId();
if (hasLength(groupId) && id.startsWith(dottedPrefix)) {
String name = id.substring(dottedPrefix.length());
item.setName(name);
}
}
}
private static boolean hasLength(String string) {
return (string != null && !string.isEmpty());
}
}
Springboot 配置处理器的:
源码如下:
private final TypeUtils typeUtils;
private final Elements elements;
private final Messager messager;
private final FieldValuesParser fieldValuesParser;
private final Map<TypeElement, Map<String, Object>> defaultValues = new HashMap<>();
private final String configurationPropertiesAnnotation;
private final String nestedConfigurationPropertyAnnotation;
private final String deprecatedConfigurationPropertyAnnotation;
private final String constructorBindingAnnotation;
private final String defaultValueAnnotation;
private final Set<String> endpointAnnotations;
private final String readOperationAnnotation;
private final String nameAnnotation;
private final String autowiredAnnotation;
MetadataGenerationEnvironment(ProcessingEnvironment environment, String configurationPropertiesAnnotation,
String nestedConfigurationPropertyAnnotation, String deprecatedConfigurationPropertyAnnotation,
String constructorBindingAnnotation, String autowiredAnnotation, String defaultValueAnnotation,
Set<String> endpointAnnotations, String readOperationAnnotation, String nameAnnotation) {
this.typeUtils = new TypeUtils(environment);
this.elements = environment.getElementUtils();
this.messager = environment.getMessager();
this.fieldValuesParser = resolveFieldValuesParser(environment);
this.configurationPropertiesAnnotation = configurationPropertiesAnnotation;
this.nestedConfigurationPropertyAnnotation = nestedConfigurationPropertyAnnotation;
this.deprecatedConfigurationPropertyAnnotation = deprecatedConfigurationPropertyAnnotation;
this.constructorBindingAnnotation = constructorBindingAnnotation;
this.autowiredAnnotation = autowiredAnnotation;
this.defaultValueAnnotation = defaultValueAnnotation;
this.endpointAnnotations = endpointAnnotations;
this.readOperationAnnotation = readOperationAnnotation;
this.nameAnnotation = nameAnnotation;
}
private static FieldValuesParser resolveFieldValuesParser(ProcessingEnvironment env) {
try {
return new JavaCompilerFieldValuesParser(env);
}
catch (Throwable ex) {
return FieldValuesParser.NONE;
}
}
TypeUtils getTypeUtils() {
return this.typeUtils;
}
Messager getMessager() {
return this.messager;
}
/**
* Return the default value of the field with the specified {@code name}.
* @param type the type to consider
* @param name the name of the field
* @return the default value or {@code null} if the field does not exist or no default
* value has been detected
*/
Object getFieldDefaultValue(TypeElement type, String name) {
return this.defaultValues.computeIfAbsent(type, this::resolveFieldValues).get(name);
}
boolean isExcluded(TypeMirror type) {
if (type == null) {
return false;
}
String typeName = type.toString();
if (typeName.endsWith("[]")) {
typeName = typeName.substring(0, typeName.length() - 2);
}
return TYPE_EXCLUDES.contains(typeName);
}
boolean isDeprecated(Element element) {
if (isElementDeprecated(element)) {
return true;
}
if (element instanceof VariableElement || element instanceof ExecutableElement) {
return isElementDeprecated(element.getEnclosingElement());
}
return false;
}
ItemDeprecation resolveItemDeprecation(Element element) {
AnnotationMirror annotation = getAnnotation(element, this.deprecatedConfigurationPropertyAnnotation);
String reason = null;
String replacement = null;
if (annotation != null) {
Map<String, Object> elementValues = getAnnotationElementValues(annotation);
reason = (String) elementValues.get("reason");
replacement = (String) elementValues.get("replacement");
}
reason = (reason == null || reason.isEmpty()) ? null : reason;
replacement = (replacement == null || replacement.isEmpty()) ? null : replacement;
return new ItemDeprecation(reason, replacement);
}
boolean hasConstructorBindingAnnotation(ExecutableElement element) {
return hasAnnotation(element, this.constructorBindingAnnotation);
}
boolean hasAutowiredAnnotation(ExecutableElement element) {
return hasAnnotation(element, this.autowiredAnnotation);
}
boolean hasAnnotation(Element element, String type) {
return getAnnotation(element, type) != null;
}
Springboot gradle plugin:
使用gragle 编译插件:
final class ApplicationPluginAction implements PluginApplicationAction {
@Override
public void execute(Project project) {
JavaApplication javaApplication = project.getExtensions().getByType(JavaApplication.class);
DistributionContainer distributions = project.getExtensions().getByType(DistributionContainer.class);
Distribution distribution = distributions.create("boot");
distribution.getDistributionBaseName()
.convention((project.provider(() -> javaApplication.getApplicationName() + "-boot")));
TaskProvider<CreateStartScripts> bootStartScripts = project.getTasks().register("bootStartScripts",
CreateStartScripts.class,
(task) -> configureCreateStartScripts(project, javaApplication, distribution, task));
CopySpec binCopySpec = project.copySpec().into("bin").from(bootStartScripts);
binCopySpec.setFileMode(0755);
distribution.getContents().with(binCopySpec);
}
private void configureCreateStartScripts(Project project, JavaApplication javaApplication,
Distribution distribution, CreateStartScripts createStartScripts) {
createStartScripts
.setDescription("Generates OS-specific start scripts to run the project as a Spring Boot application.");
((TemplateBasedScriptGenerator) createStartScripts.getUnixStartScriptGenerator())
.setTemplate(project.getResources().getText().fromString(loadResource("/unixStartScript.txt")));
((TemplateBasedScriptGenerator) createStartScripts.getWindowsStartScriptGenerator())
.setTemplate(project.getResources().getText().fromString(loadResource("/windowsStartScript.txt")));
project.getConfigurations().all((configuration) -> {
if ("bootArchives".equals(configuration.getName())) {
distribution.getContents().with(artifactFilesToLibCopySpec(project, configuration));
createStartScripts.setClasspath(configuration.getArtifacts().getFiles());
}
});
createStartScripts.getConventionMapping().map("outputDir",
() -> new File(project.getBuildDir(), "bootScripts"));
createStartScripts.getConventionMapping().map("applicationName", javaApplication::getApplicationName);
createStartScripts.getConventionMapping().map("defaultJvmOpts", javaApplication::getApplicationDefaultJvmArgs);
}
private CopySpec artifactFilesToLibCopySpec(Project project, Configuration configuration) {
CopySpec copySpec = project.copySpec().into("lib").from(artifactFiles(configuration));
copySpec.setFileMode(0644);
return copySpec;
}
private Callable<FileCollection> artifactFiles(Configuration configuration) {
return () -> configuration.getArtifacts().getFiles();
}
@Override
public Class<? extends Plugin<Project>> getPluginClass() {
return ApplicationPlugin.class;
}
private String loadResource(String name) {
try (InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream(name))) {
char[] buffer = new char[4096];
int read;
StringWriter writer = new StringWriter();
while ((read = reader.read(buffer)) > 0) {
writer.write(buffer, 0, read);
}
return writer.toString();
}
catch (IOException ex) {
throw new GradleException("Failed to read '" + name + "'", ex);
}
}
}
public class GradleBuild {
private final Dsl dsl;
private File projectDir;
private String script;
private String settings;
private String gradleVersion;
private String springBootVersion = "TEST-SNAPSHOT";
private GradleVersion expectDeprecationWarnings;
private List<String> expectedDeprecationMessages = new ArrayList<>();
private boolean configurationCache = false;
private Map<String, String> scriptProperties = new HashMap<>();
public GradleBuild() {
this(Dsl.GROOVY);
}
public GradleBuild(Dsl dsl) {
this.dsl = dsl;
}
public Dsl getDsl() {
return this.dsl;
}
void before() throws IOException {
this.projectDir = Files.createTempDirectory("gradle-").toFile();
}
void after() {
this.script = null;
FileSystemUtils.deleteRecursively(this.projectDir);
}
private List<File> pluginClasspath() {
return Arrays.asList(new File("bin/main"), new File("build/classes/java/main"),
new File("build/resources/main"), new File(pathOfJarContaining(LaunchScript.class)),
new File(pathOfJarContaining(ClassVisitor.class)),
new File(pathOfJarContaining(DependencyManagementPlugin.class)),
new File(pathOfJarContaining("org.jetbrains.kotlin.cli.common.PropertiesKt")),
new File(pathOfJarContaining("org.jetbrains.kotlin.compilerRunner.KotlinLogger")),
new File(pathOfJarContaining(KotlinPlugin.class)), new File(pathOfJarContaining(KotlinProject.class)),
new File(pathOfJarContaining("org.jetbrains.kotlin.daemon.client.KotlinCompilerClient")),
new File(pathOfJarContaining(KotlinCompilerPluginSupportPlugin.class)),
new File(pathOfJarContaining(LanguageSettings.class)),
new File(pathOfJarContaining(ArchiveEntry.class)), new File(pathOfJarContaining(BuildRequest.class)),
new File(pathOfJarContaining(HttpClientConnectionManager.class)),
new File(pathOfJarContaining(HttpRequest.class)), new File(pathOfJarContaining(Module.class)),
new File(pathOfJarContaining(Versioned.class)),
new File(pathOfJarContaining(ParameterNamesModule.class)),
new File(pathOfJarContaining(JsonView.class)), new File(pathOfJarContaining(Platform.class)),
new File(pathOfJarContaining(Toml.class)), new File(pathOfJarContaining(Lexer.class)),
new File(pathOfJarContaining("org.graalvm.buildtools.gradle.NativeImagePlugin")),
new File(pathOfJarContaining("org.graalvm.reachability.GraalVMReachabilityMetadataRepository")),
new File(pathOfJarContaining("org.graalvm.buildtools.utils.SharedConstants")));
}
private String pathOfJarContaining(String className) {
try {
return pathOfJarContaining(Class.forName(className));
}
catch (ClassNotFoundException ex) {
throw new IllegalArgumentException(ex);
}
}
private String pathOfJarContaining(Class<?> type) {
return type.getProtectionDomain().getCodeSource().getLocation().getPath();
}
public GradleBuild script(String script) {
this.script = script.endsWith(this.dsl.getExtension()) ? script : script + this.dsl.getExtension();
return this;
}
public void settings(String settings) {
this.settings = settings;
}
public GradleBuild expectDeprecationWarningsWithAtLeastVersion(String gradleVersion) {
this.expectDeprecationWarnings = GradleVersion.version(gradleVersion);
return this;
}
public GradleBuild expectDeprecationMessages(String... messages) {
this.expectedDeprecationMessages.addAll(Arrays.asList(messages));
return this;
}
public GradleBuild configurationCache() {
this.configurationCache = true;
return this;
}
public boolean isConfigurationCache() {
return this.configurationCache;
}
https://www.jb51.net/article/197968.htm
http://t.zoukankan.com/kingsonfu-p-11805455.html