<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Java</title>
	<atom:link href="https://blog.gmem.cc/category/work/java/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Mon, 06 Apr 2026 12:46:48 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>A Comprehensive Study of Kotlin for Java Developers</title>
		<link>https://blog.gmem.cc/comprehensive-study-kotlin-java-developers</link>
		<comments>https://blog.gmem.cc/comprehensive-study-kotlin-java-developers#comments</comments>
		<pubDate>Sun, 29 Sep 2024 12:52:40 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=40127</guid>
		<description><![CDATA[<p>Introduction Purpose of the Study Understanding the Motivations for Learn Kotlin In the rapidly evolving field of software development, staying abreast of <a class="read-more" href="https://blog.gmem.cc/comprehensive-study-kotlin-java-developers">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/comprehensive-study-kotlin-java-developers">A Comprehensive Study of Kotlin for Java Developers</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">Introduction</span></div>
<div class="blog_h2"><span class="graybg">Purpose of the Study</span></div>
<div class="blog_h3"><span class="graybg">Understanding the Motivations for Learn Kotlin</span></div>
<p>In the rapidly evolving field of software development, staying abreast of emerging technologies is essential for maintaining a competitive edge. Kotlin, a statically typed programming language developed by JetBrains, has garnered significant attention since its release. For developers proficient in Java—especially versions 8 and earlier—exploring Kotlin offers an opportunity to enhance coding efficiency, embrace modern programming paradigms, and address some of the limitations inherent in older versions of Java.</p>
<p>The motivations for learning Kotlin are multifaceted:</p>
<ol style="list-style-type: undefined;">
<li>Modern Language Features: Kotlin introduces contemporary features such as null safety, data classes, and coroutines, which streamline coding practices and reduce common programming errors.</li>
<li>Interoperability: Kotlin is fully interoperable with Java, allowing for seamless integration into existing Java projects and the use of established Java libraries.</li>
<li>Industry Adoption: Major companies, including Google, have endorsed Kotlin for Android development, signaling a shift in industry standards and practices.</li>
</ol>
<div class="blog_h3"><span class="graybg">Setting Goals for Mastering Kotlin with a Java Background</span></div>
<p>As a Java developer delving into Kotlin, it's important to set clear, achievable goals to maximize the benefits of this study:</p>
<ol style="list-style-type: undefined;">
<li>Comprehensive Understanding: Gain a thorough grasp of Kotlin's syntax, features, and best practices.</li>
<li>Comparative Analysis: Identify and understand the similarities and differences between Kotlin and Java to leverage existing knowledge effectively.</li>
<li>Practical Application: Apply Kotlin concepts in real-world scenarios, including Android development and server-side applications.</li>
<li>Interoperability Proficiency: Learn how to integrate Kotlin code with Java, enabling the use of both languages within the same project seamlessly.</li>
<li>Code Optimization: Utilize Kotlin's features to write more concise, efficient, and maintainable code compared to traditional Java approaches.</li>
</ol>
<p>By setting these goals, the study aims to provide a structured pathway to not only learn Kotlin but also to enhance overall programming proficiency.</p>
<div class="blog_h2"><span class="graybg">Overview of Java and Kotlin</span></div>
<div class="blog_h3"><span class="graybg">Brief History and Evolution of Java up to Version 21</span></div>
<p>Java has been a cornerstone in the software development industry since its inception by Sun Microsystems in 1995. Over the years, it has undergone significant transformations, introducing features that address the evolving needs of developers and the industry at large. Below is a chronological overview of Java's evolution up to Java 21, the latest version as of October 2023.</p>
<p>Java 1.0 to Java 1.4 (1996 - 2002): Establishing the Foundation</p>
<ol style="list-style-type: undefined;">
<li>Java 1.0 (1996): The initial release provided the basic framework of the language, focusing on portability and network computing.</li>
<li>Java 1.1 to 1.4: These versions introduced inner classes, JDBC, JavaBeans, the Collections Framework, and enhanced performance and security features.</li>
</ol>
<p>Java 5 (2004): Embracing Modern Programming Concepts</p>
<ol style="list-style-type: undefined;">
<li>Generics: Allowed for type-safe collections.</li>
<li>Annotations: Provided metadata that could be processed by the compiler or at runtime.</li>
<li>Enhanced for-loop: Simplified iteration over collections and arrays.</li>
<li>Autoboxing/Unboxing: Automated conversion between primitive types and their corresponding object wrapper classes.</li>
</ol>
<p>Java 6 and Java 7 (2006 - 2011): Performance and Usability Enhancements</p>
<ol style="list-style-type: undefined;">
<li>Java 6: Focused on performance improvements and included updates to the JVM and core libraries.</li>
</ol>
<ol style="list-style-type: undefined;">
<li>Java 7:
<ol>
<li>Diamond Operator (&lt;&gt;): Simplified the use of generics.</li>
<li>Try-with-Resources: Enhanced exception handling and resource management.</li>
<li>Strings in Switch Statements: Allowed strings to be used in switch cases.</li>
</ol>
</li>
</ol>
<p>Java 8 (2014): A Paradigm Shift with Functional Programming</p>
<ol style="list-style-type: undefined;">
<li>Lambda Expressions: Introduced functional programming concepts, enabling more concise code.</li>
<li>Stream API: Provided a powerful way to process collections in a functional style.</li>
<li>Optional Class: Addressed null references by providing a container object which may or may not contain a non-null value.</li>
<li>Date and Time API: Offered a new set of classes under java.time package for date and time manipulation.</li>
</ol>
<p>Java 9 (2017): Modularization and JShell</p>
<ol style="list-style-type: undefined;">
<li>Project Jigsaw (Modules): Introduced the Java Platform Module System, allowing for better encapsulation and modularization of code.</li>
<li>JShell (REPL): Provided an interactive Read-Eval-Print Loop tool for rapid prototyping.</li>
</ol>
<p>Java 10 (2018): Local Variable Type Inference</p>
<ol style="list-style-type: undefined;">
<li>var Keyword: Enabled local variable type inference, allowing the compiler to infer the type of a variable from its initializer.</li>
</ol>
<p>Java 11 (2018): Long-Term Support and New Features</p>
<ol style="list-style-type: undefined;">
<li>Standardized HTTP Client API: Introduced a new HTTP client under java.net.http.</li>
<li>String Methods Enhancements: Added methods like isBlank(), lines(), strip(), repeat().</li>
<li>Removal of JavaFX: Decoupled JavaFX from the JDK.</li>
</ol>
<p>Java 12 to Java 15 (2019 - 2020): Incremental Improvements</p>
<ol style="list-style-type: undefined;">
<li>Java 12: Switch Expressions (Preview): Enhanced switch statements to be used as expressions.</li>
<li>Java 13: Text Blocks (Preview): Simplified the inclusion of multi-line strings.</li>
<li>Java 14:
<ol>
<li>Records (Preview): Introduced a compact syntax for declaring data classes.</li>
<li>Helpful NullPointerExceptions: Improved the detail in NullPointerException messages.</li>
</ol>
</li>
</ol>
<p>Java 15:</p>
<ol style="list-style-type: undefined;">
<li>Sealed Classes (Preview): Restricted which classes can extend or implement a class or interface.</li>
<li>Z Garbage Collector (Product Feature): Low-latency garbage collector moved from experimental to production.</li>
</ol>
<p>Java 16 and Java 17 (2021): Pattern Matching and Sealed Classes</p>
<ol style="list-style-type: undefined;">
<li>Java 16:
<ol>
<li>Pattern Matching for instanceof: Simplified the use of instanceof with pattern variables.</li>
<li>Records: Moved from preview to a standard feature.</li>
</ol>
</li>
<li>Java 17 (Long-Term Support Release):
<ol>
<li>Sealed Classes: Finalized as a standard feature.</li>
<li>Removal of Deprecated Features: Eliminated older features like the Applet API.</li>
<li>Enhanced Pseudorandom Number Generators: Introduced new interfaces and implementations for PRNGs.</li>
</ol>
</li>
</ol>
<p>Java 18 and Java 19 (2022): Incubator and Preview Features</p>
<ol style="list-style-type: undefined;">
<li>Java 18:
<ol>
<li>UTF-8 by Default: Standardized UTF-8 as the default character set.</li>
<li>Simple Web Server: Provided a command-line tool for starting a minimal web server.</li>
</ol>
</li>
<li>Java 19:
<ol>
<li>Virtual Threads (Preview): Part of Project Loom, introduced lightweight threads for concurrent programming.</li>
<li>Structured Concurrency (Incubator): Simplified multithreaded programming by treating multiple tasks running in different threads as a single unit.</li>
</ol>
</li>
</ol>
<p>Java 20 and Java 21 (2023): Advancements in Performance and Productivity</p>
<ol style="list-style-type: undefined;">
<li>Java 20:
<ol>
<li>Scoped Values (Incubator): Allowed for the sharing of immutable data within and across threads.</li>
<li>Record Patterns (Second Preview): Enhanced pattern matching for records.</li>
</ol>
</li>
<li>Java 21 (Latest LTS as of October 2023):
<ol>
<li>Virtual Threads (Standard Feature): Finalized virtual threads for high-throughput concurrent applications.</li>
<li>Sequenced Collections: Introduced interfaces to represent collections with a defined encounter order.</li>
<li>String Templates (Preview): Provided a new way to create and process strings with embedded expressions.</li>
<li>Pattern Matching for Switch (Standard Feature): Finalized pattern matching in switch expressions and statements.</li>
</ol>
</li>
</ol>
<div class="blog_h3"><span class="graybg">Brief History of Kotlin</span></div>
<p>In 2010 JetBrains began the development of Kotlin, aiming to create a language that could improve developer productivity and happiness. The primary motivations were:</p>
<ol style="list-style-type: undefined;">
<li>Conciseness: Reduce boilerplate code common in Java.</li>
<li>Safety: Introduce features like null safety to prevent common errors.</li>
<li>Interoperability: Ensure seamless integration with Java code and libraries.</li>
<li>Tooling Support: Leverage JetBrains' expertise in IDE development to provide excellent tooling from the outset.</li>
</ol>
<p>In July 2011, JetBrains publicly announced Kotlin, revealing their plans to create a new language for the JVM.</p>
<p>Kotlin 1.0 (February 2016):</p>
<ol style="list-style-type: undefined;">
<li>First Stable Release: Marked the language as production-ready after years of development and refinement.</li>
<li>Core Features: Included null safety, extension functions, data classes, and higher-order functions.</li>
<li>Interoperability: Ensured 100% compatibility with Java, allowing developers to call Kotlin code from Java and vice versa</li>
<li>Google I/O Announcement: Google declared official support for Kotlin on Android, making it a first-class language for Android app development. Led to a significant surge in Kotlin adoption within the Android community.</li>
</ol>
<p>Kotlin 1.1 (March 2017):</p>
<ol style="list-style-type: undefined;">
<li>Coroutines (Experimental): Introduced coroutines for asynchronous programming, allowing developers to write non-blocking code more easily.</li>
<li>JavaScript Target: Enabled compilation of Kotlin code to JavaScript, facilitating cross-platform development.</li>
</ol>
<p>Kotlin 1.2 (November 2017):</p>
<ol style="list-style-type: undefined;">
<li>Multiplatform Projects (Experimental): Allowed sharing code between JVM and JavaScript platforms, paving the way for true cross-platform applications.</li>
<li>Improved Compilation: Enhanced compiler performance and incremental compilation support.</li>
</ol>
<p>Kotlin 1.3 (October 2018):</p>
<ol style="list-style-type: undefined;">
<li>Coroutines Become Stable: Solidified coroutines as a core feature, providing a powerful tool for asynchronous and concurrent programming.</li>
<li>Kotlin/Native: Enabled compilation to native binaries, expanding Kotlin's reach to platforms like iOS, Windows, Linux, and macOS without the need for a virtual machine.</li>
<li>Contracts: Introduced experimental support for contracts, allowing for more precise code analysis.</li>
</ol>
<p>Kotlin 1.4 (August 2020):</p>
<ol style="list-style-type: undefined;">
<li>Multiplatform Enhancements: Improved the multiplatform project support, making it more stable and easier to use.</li>
<li>Compiler Improvements: Focused on performance, resulting in faster compilation times and better IDE responsiveness.</li>
<li>Standard Library Updates: Added new functions and classes to the standard library, enhancing functionality.</li>
</ol>
<p>Kotlin 1.5 (May 2021):</p>
<ol style="list-style-type: undefined;">
<li>Language Features: Introduced JVM records, sealed interfaces, and inline classes.</li>
<li>Stability: Many experimental features were promoted to stable status.</li>
</ol>
<p>Kotlin 1.6 (November 2021):</p>
<ol style="list-style-type: undefined;">
<li>Standard Library Enhancements: Improved existing APIs and added new ones.</li>
<li>Performance: Continued focus on compiler and runtime performance optimizations.</li>
</ol>
<p>Kotlin 1.7 (June 2022):</p>
<ol style="list-style-type: undefined;">
<li>Context Receivers (Experimental): Added support for context-dependent declarations.</li>
<li>K2 Compiler (Alpha): Began work on a new frontend compiler aimed at performance improvements and better tooling.</li>
</ol>
<p>Kotlin 1.8 (January 2023):</p>
<ol style="list-style-type: undefined;">
<li>Incremental Updates: Brought further enhancements to the language and tooling.</li>
<li>Kotlin Multiplatform Mobile (KMM): Progressed towards stabilizing shared code between Android and iOS.</li>
</ol>
<p>Kotlin 1.9 (July 2023):</p>
<ol style="list-style-type: undefined;">
<li>K2 Compiler Advances: Continued development of the new compiler, improving compilation times and error diagnostics.</li>
<li>Language Features: Added new experimental features and made existing ones more stable</li>
</ol>
<p>Kotlin 2.x (May 2024):</p>
<p>Kotlin 2.0 brings significant improvements and new features that make it a more powerful, expressive, and developer-friendly language. Here are some of the significant features introduced in Kotlin 2.0:</p>
<ol style="list-style-type: undefined;">
<li>K2 Compiler (New Generation Compiler)
<ol>
<li>Improved Performance: The new K2 compiler is designed to be faster and more efficient, significantly reducing compilation times.</li>
<li>Improved Error Reporting: K2 enhances error diagnostics, providing more detailed and user-friendly error messages.</li>
<li>Unified Backend: It unifies the backend of Kotlin/Native, Kotlin/JVM, and Kotlin/JS, allowing developers to work with different platforms more seamlessly.</li>
<li>Modular and Extensible: The new architecture allows for easier integration of third-party tools, opening doors for more customization and extension.</li>
</ol>
</li>
<li>Context Receivers
<ol>
<li>This feature allows adding additional context to functions without explicitly passing it through parameters. It simplifies code that requires multiple receivers, such as in DSLs (Domain Specific Languages) and other multi-receiver scenarios.<br />
<pre class="crayon-plain-tag">fun  withDatabaseContext(block: Database.() -&gt; T): T { 
    return Database().block()
}

val result = withDatabaseContext {
    // Inside this lambda, `this` refers to a `Database` instance.
    query("SELECT * FROM users")
}</pre>
</li>
</ol>
</li>
<li>Builder Inference Improvements: Kotlin 2.0 improves type inference in builder-style APIs, making code more concise and readable. This is particularly useful for libraries like coroutines and UI libraries that leverage builder patterns.</li>
<li>Explicit API Mode for Kotlin Libraries: The explicit API mode helps developers build public libraries with stricter controls. It enforces that every member of a public API must explicitly declare its visibility and type, making the API more predictable and well-documented.</li>
<li>Improved Multiplatform Support
<ol>
<li>Multiplatform Compose: Kotlin 2.0 enhances Kotlin Multiplatform projects, especially for shared UI development. It supports libraries like Compose Multiplatform for building UIs that run on multiple platforms with the same codebase.</li>
<li>Better Gradle Integration: The new version comes with improved Gradle tooling and support for managing multiple targets more easily.</li>
</ol>
</li>
<li>New Sealed Interface Features: Kotlin 2.0 allows sealed interfaces, which, like sealed classes, restrict which classes can implement them. This improves safety in scenarios where specific hierarchies are needed.<br />
<pre class="crayon-plain-tag">sealed interface Operation
class Add(val value: Int) : Operation
class Subtract(val value: Int) : Operation</pre>
</li>
<li>
<p>Collection Literals and Destructuring in Loops: </p>
<ol>
<li>
<p>Kotlin 2.0 introduces collection literals to make collection initialization more concise.</p>
</li>
<li>
<p>Destructuring in loops is improved, allowing better handling of pairs and other data structures in iteration contexts.</p>
</li>
</ol>
</li>
<li>Value Classes (Refined): Kotlin 2.0 continues to improve value classes (formerly inline classes), ensuring that they are more efficient and flexible. Value classes can be used in cases where a small, immutable data holder is needed without the overhead of full object allocation.</li>
<li>Unit Testing Enhancements: Kotlin 2.0 improves unit testing capabilities for Kotlin Multiplatform projects, providing better tools and frameworks for cross-platform test sharing and execution.</li>
<li>Enhanced Coroutines:
<ol>
<li>Kotlin 2.0 further enhances Kotlin’s popular coroutine library with better integration, new debugging tools, and optimizations for more efficient asynchronous programming.</li>
<li>Structured Concurrency Improvements: Kotlin 2.0 adds additional safeguards and features to make structured concurrency even more robust.</li>
</ol>
</li>
<li>Function Interfaces: Kotlin 2.0 introduces Function Interfaces (aka SAM conversions), which allow developers to convert interfaces with a single abstract method into lambda expressions. This is especially useful when interoperating with Java libraries that heavily use functional interfaces.</li>
<li>Improved Null Safety Features: Kotlin 2.0 strengthens null-safety features, particularly in interoperability with Java. Improved type-checking mechanisms ensure that fewer null-pointer exceptions occur when interacting with non-null Kotlin code and nullable Java code.</li>
<li>Incremental Compilation Improvements: Kotlin 2.0 improves the incremental compilation process, reducing build times even for large projects and making the development experience smoother.</li>
<li>Backward Compatibility with Kotlin 1.x: Kotlin 2.0 is designed to be backward compatible with Kotlin 1.x, allowing gradual migration of projects without major breaking changes.</li>
</ol>
<div class="blog_h1"><span class="graybg">Kotlin Syntax Basics</span></div>
<div class="blog_h2"><span class="graybg">Main Function Declaration</span></div>
<p>In both Java and Kotlin, the main function serves as the entry point of the application. However, the syntax and structure differ between the two languages.</p>
<div class="blog_h3"><span class="graybg">Java Main Method</span></div>
<p>In Java, the main method must be declared within a class and must be public, static, and void, accepting a String[] argument:</p>
<pre class="crayon-plain-tag">public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, Java!");
    }
}</pre>
<div class="blog_h3"><span class="graybg">Kotlin Main Function</span></div>
<p>In Kotlin, the main function does not need to be part of a class and can be declared at the top level: </p>
<pre class="crayon-plain-tag">fun main(args: Array) {
    println("Hello, Kotlin!")
}</pre>
<div class="blog_h2"><span class="graybg">Variables and Data Types </span></div>
<p>Kotlin introduces a more concise and expressive way to declare variables, emphasizing immutability and type inference.</p>
<div class="blog_h3"><span class="graybg">Mutable (var) vs Immutable (val) Variables</span></div>
<ol style="list-style-type: undefined;">
<li>val (Immutable): Declares a read-only variable whose value cannot be changed once assigned.</li>
<li>var (Mutable): Declares a variable whose value can be changed.</li>
</ol>
<p>For example:</p>
<pre class="crayon-plain-tag">val name = "Alice"   // Immutable variable
var age = 30         // Mutable variable</pre>
<p>Attempting to reassign name will result in a compile-time error. age can be reassigned to a different value.</p>
<div class="blog_h3"><span class="graybg">constants</span></div>
<p>In Kotlin, you can define constants using val and const val. Both are used for read-only values, but they differ in when and how their values are initialized and how they can be used.</p>
<p>val:</p>
<ol>
<li>Declares a read-only property or variable.</li>
<li>The value is assigned at runtime.</li>
<li>Can be used anywhere in the code.</li>
<li>Can hold any type, including objects.</li>
</ol>
<p>const val:</p>
<ol>
<li>Declares a compile-time constant.</li>
<li>The value is assigned at compile time.</li>
<li>Must be a top-level or member of an object or companion object.</li>
<li>Can only hold primitive types and String.</li>
</ol>
<p>Example:</p>
<pre class="crayon-plain-tag">const val MAX_COUNT = 100
const val APP_NAME = "MyApplication"

object Constants {
    const val PI = 3.1415926535
    const val E = 2.7182818284
}


class MathUtils {
    companion object {
        const val GOLDEN_RATIO = 1.6180339887
    }
}

// Accessing the constant
val ratio = MathUtils.GOLDEN_RATIO</pre>
<div class="blog_h3"><span class="graybg">Type Inference</span></div>
<p>Kotlin can infer the type of a variable from the assigned value, so specifying the type explicitly is optional. Examples:</p>
<pre class="crayon-plain-tag">val number = 42               // Type inferred as Int
val pi = 3.1415               // Type inferred as Double
var message: String = "Hello" // Type explicitly specified</pre>
<div class="blog_h3"><span class="graybg">Primitive Types and Their Equivalents</span></div>
<p>Kotlin does not have primitive types in the same way Java does. All types are objects. However, the compiler optimizes to use primitive types where possible for performance.</p>
<ol style="list-style-type: undefined;">
<li>Numeric Types: Byte, Short, Int, Long, Float, Double</li>
<li>Other Types: Char, Boolean, String</li>
</ol>
<p>Example: </p>
<pre class="crayon-plain-tag">val count: Int = 10
// Or with type inference
val count = 10</pre>
<div class="blog_h2"><span class="graybg">Null Safety</span></div>
<p>One of the most significant features of Kotlin is its approach to null safety, which helps prevent the dreaded NullPointerException.</p>
<div class="blog_h3"><span class="graybg">Nullable and Non-Nullable Types</span></div>
<p><span style="background-color: #c0c0c0;">By default, variables in Kotlin cannot hold a null value.</span> To allow a variable to hold null, you need to declare it as nullable by adding a <pre class="crayon-plain-tag">?</pre> after the type.</p>
<pre class="crayon-plain-tag">var nonNullable: String = "Hello"
// nonNullable = null // Compile-time error

var nullable: String? = "Hello"
nullable = null       // Allowed</pre>
<div class="blog_h3"><span class="graybg">Safe Calls</span></div>
<p>To safely access properties or methods of a nullable variable, use the safe call operator <pre class="crayon-plain-tag">?.</pre>. </p>
<pre class="crayon-plain-tag">// If nullable is not null, length will hold the length of the string.
// If nullable is null, length will be null.
val length = nullable?.length</pre>
<div class="blog_h3"><span class="graybg">Elvis Operator<br /></span></div>
<p>The Elvis operator <pre class="crayon-plain-tag">?:</pre> in Kotlin provides a concise way to handle nullable expressions by specifying a default value when an expression evaluates to null. It's particularly useful when you want to assign a value that might be null but have an alternative ready if it is.</p>
<pre class="crayon-plain-tag">// If expression is not null, result will be the value of expression.
// If expression is null, result will be defaultValue.
val result = expression ?: defaultValue</pre>
<div class="blog_h3"><span class="graybg">Non-Null Assertion Operator</span></div>
<p>The non-null assertion operator !! in Kotlin is used to explicitly assert that a nullable variable or expression is not null. If it is null, the operator will throw a <pre class="crayon-plain-tag">KotlinNullPointerException</pre> at runtime.</p>
<pre class="crayon-plain-tag">// If nullableValue is not null, nonNullableValue will hold its value.
// If nullableValue is null, a KotlinNullPointerException is thrown.
val nonNullableValue = nullableValue!!</pre>
<div class="blog_h3"><span class="graybg">Safe Casting</span></div>
<p>Use <pre class="crayon-plain-tag">as?</pre> for safe casting that returns null if the cast is unsuccessful.</p>
<pre class="crayon-plain-tag">val obj: Any = "Kotlin"
val str: String? = obj as? String</pre>
<div class="blog_h3"><span class="graybg">Comparison with Java </span></div>
<p>In Java, all object references can be null, and there's no language-level enforcement to prevent NullPointerException. </p>
<pre class="crayon-plain-tag">String message = null;
int length = message.length(); // Throws NullPointerException</pre>
<p>To address issues related to null values, Java 8 introduced the <pre class="crayon-plain-tag">Optional</pre> class in the java.util package. Optional is a container object that may or may not contain a non-null value. It provides methods to handle the presence or absence of a value without directly using null.</p>
<p>Example of Using Optional:</p>
<pre class="crayon-plain-tag">import java.util.Optional;

Optional optionalMessage = Optional.ofNullable(getMessage());

if (optionalMessage.isPresent()) {
    int length = optionalMessage.get().length();
} else {
    int length = 0;
}</pre>
<p> Or using a functional style:</p>
<pre class="crayon-plain-tag">int length = optionalMessage.map(String::length).orElse(0);</pre>
<p>Limitations of Optional in Java:</p>
<ol style="list-style-type: undefined;">
<li>Not a Language-Level Feature: Optional is a library class, not a language construct. It doesn't enforce null safety at the type system level.</li>
<li>Limited Usage: It's primarily intended for return types and not recommended for fields or method parameters, limiting its scope.</li>
<li>Verbosity: Using Optional can make the code more verbose compared to Kotlin's null handling.</li>
<li>Performance Overhead: Wrapping values in Optional can introduce performance overhead due to additional object creation.</li>
</ol>
<div class="blog_h2"><span class="graybg">String Templates</span></div>
<p>Kotlin allows embedding variables and expressions within strings using string templates.</p>
<ol>
<li>Variable Interpolation: Use <pre class="crayon-plain-tag">$variableName</pre></li>
<li>Expression Interpolation: Use <pre class="crayon-plain-tag">${expression}</pre></li>
</ol>
<p>Example:</p>
<pre class="crayon-plain-tag">val name = "Alice"
val greeting = "Hello, $name!"
println(greeting) // Output: Hello, Alice!

val age = 30
println("In 5 years, ${name} will be ${age + 5} years old.")
// Output: In 5 years, Alice will be 35 years old.</pre>
<div class="blog_h2"><span class="graybg">Kotlin Operators Overview</span></div>
<p>In Kotlin, operators can be classified into various categories, similar to Java. However, there are some key differences. Below is a comprehensive table of Kotlin operators and their usage. Where applicable, significant differences between Kotlin and Java are highlighted.</p>
<div class="blog_h3"><span class="graybg">Assignment Operators</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;"><strong>Operator</strong></td>
<td style="text-align: center;"><strong>Description</strong></td>
<td style="text-align: center;"><strong>Kotlin Example</strong></td>
<td style="text-align: center;"><strong>Difference with Java</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td>=</td>
<td>Simple assignment</td>
<td><pre class="crayon-plain-tag">a = b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>+=</td>
<td>Add and assign</td>
<td><pre class="crayon-plain-tag">a += b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>-=</td>
<td>Subtract and assign</td>
<td><pre class="crayon-plain-tag">a -= b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>*=</td>
<td>Multiply and assign</td>
<td><pre class="crayon-plain-tag">a *= b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>/=</td>
<td>Divide and assign</td>
<td><pre class="crayon-plain-tag">a /= b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>%=</td>
<td>Modulus and assign</td>
<td><pre class="crayon-plain-tag">a %= b</pre></td>
<td>No difference</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Arithmetic Operators</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;"><strong>Operator</strong></td>
<td style="text-align: center;"><strong>Description</strong></td>
<td style="text-align: center;"><strong>Kotlin Example</strong></td>
<td style="text-align: center;"><strong>Difference with Java</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td>+</td>
<td>Addition</td>
<td><pre class="crayon-plain-tag">a + b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>-</td>
<td>Subtraction</td>
<td><pre class="crayon-plain-tag">a - b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>*</td>
<td>Multiplication</td>
<td><pre class="crayon-plain-tag">a * b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>/</td>
<td>Division</td>
<td><pre class="crayon-plain-tag">a / b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>%</td>
<td>Modulus</td>
<td><pre class="crayon-plain-tag">a % b</pre></td>
<td>No difference</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Comparison Operators</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;"><strong>Operator</strong></td>
<td style="text-align: center;"><strong>Description</strong></td>
<td style="text-align: center;"><strong>Kotlin Example</strong></td>
<td style="text-align: center;"><strong>Difference with Java</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td>==</td>
<td>Equal to</td>
<td><pre class="crayon-plain-tag">a == b</pre></td>
<td>Kotlin compares values, Java compares references</td>
</tr>
<tr>
<td>!=</td>
<td>Not equal to</td>
<td><pre class="crayon-plain-tag">a != b</pre></td>
<td>Kotlin compares values, Java compares references</td>
</tr>
<tr>
<td>&gt;</td>
<td>Greater than</td>
<td><pre class="crayon-plain-tag">a &gt; b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>&lt;</td>
<td>Less than</td>
<td><pre class="crayon-plain-tag">a &lt; b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>&gt;=</td>
<td>Greater than or equal to</td>
<td><pre class="crayon-plain-tag">a &gt;= b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>&lt;=</td>
<td>Less than or equal to</td>
<td><pre class="crayon-plain-tag">a &lt;= b</pre></td>
<td>No difference</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Logical Operators</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;"><strong>Operator</strong></td>
<td style="text-align: center;"><strong>Description</strong></td>
<td style="text-align: center;"><strong>Kotlin Example</strong></td>
<td style="text-align: center;"><strong>Difference with Java</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td>&amp;&amp;</td>
<td>Logical AND</td>
<td><pre class="crayon-plain-tag">a &amp;&amp; b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>||</td>
<td>Logical OR</td>
<td><pre class="crayon-plain-tag">a || b</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>!</td>
<td>Logical NOT</td>
<td><pre class="crayon-plain-tag">!a</pre></td>
<td>No difference</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Bitwise Operators</span></div>
<p>Unlike Java, Kotlin does not have specific bitwise operators (<code>&amp;</code>, <code>|</code>, etc.). Instead, it uses functions like <code>and()</code>, <code>or()</code>, <code>xor()</code>, <code>inv()</code> for bitwise operations.</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;"><strong>Operator</strong></td>
<td style="text-align: center;"><strong>Description</strong></td>
<td style="text-align: center;"><strong>Kotlin Example</strong></td>
<td style="text-align: center;"><strong>Difference with Java</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td>and()</td>
<td>Bitwise AND</td>
<td><pre class="crayon-plain-tag">a.and(b)</pre></td>
<td>Kotlin uses a function instead of the <code>&amp;</code> operator</td>
</tr>
<tr>
<td>or()</td>
<td>Bitwise OR</td>
<td><pre class="crayon-plain-tag">a.or(b)</pre></td>
<td>Kotlin uses a function instead of the <code>|</code> operator</td>
</tr>
<tr>
<td>xor()</td>
<td>Bitwise XOR</td>
<td><pre class="crayon-plain-tag">a.xor(b)</pre></td>
<td>Kotlin uses a function instead of the <code>^</code> operator</td>
</tr>
<tr>
<td>inv()</td>
<td>Bitwise inversion</td>
<td><pre class="crayon-plain-tag">a.inv()</pre></td>
<td>Kotlin uses a function instead of the <code>~</code> operator</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Other Operators</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;"><strong>Operator</strong></td>
<td style="text-align: center;"><strong>Description</strong></td>
<td style="text-align: center;"><strong>Kotlin Example</strong></td>
<td style="text-align: center;"><strong>Difference with Java</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td>in</td>
<td>Checks if a value is in a collection</td>
<td><pre class="crayon-plain-tag">x in array</pre></td>
<td>No Java equivalent</td>
</tr>
<tr>
<td>!in</td>
<td>Checks if a value is not in a collection</td>
<td><pre class="crayon-plain-tag">x !in array</pre></td>
<td>No Java equivalent</td>
</tr>
<tr>
<td>is</td>
<td>Checks if an object is of a certain type</td>
<td><pre class="crayon-plain-tag">x is String</pre></td>
<td>Kotlin uses <code>is</code> instead of <code>instanceof</code> in Java</td>
</tr>
<tr>
<td>as</td>
<td>Type casting</td>
<td><pre class="crayon-plain-tag">x as String</pre></td>
<td>Kotlin uses <code>as</code> for type casting</td>
</tr>
<tr>
<td>* (spread)</td>
<td>Spread operator (used to pass multiple arguments)</td>
<td><pre class="crayon-plain-tag">foo(*args)</pre></td>
<td>Kotlin uses the spread operator to pass arrays or varargs, while Java requires manual array expansion.</td>
</tr>
<tr>
<td>?.</td>
<td>Safe call operator (used to handle nullability)</td>
<td><pre class="crayon-plain-tag">a?.length</pre></td>
<td>No Java equivalent (in Java, null checks are required)</td>
</tr>
<tr>
<td>?:</td>
<td>Elvis operator (provides default value if null)</td>
<td><pre class="crayon-plain-tag">a ?: "default"</pre></td>
<td>No Java equivalent (in Java, null checks and ternary are needed)</td>
</tr>
<tr>
<td>!!</td>
<td>Not-null assertion (throws exception if value is null)</td>
<td><pre class="crayon-plain-tag">a!!</pre></td>
<td>No direct equivalent in Java (Java requires manual null checking and exception handling)</td>
</tr>
<tr>
<td>++</td>
<td>Increment (pre/post)</td>
<td><pre class="crayon-plain-tag">a++</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>--</td>
<td>Decrement (pre/post)</td>
<td><pre class="crayon-plain-tag">a--</pre></td>
<td>No difference</td>
</tr>
<tr>
<td>..</td>
<td>Range operator</td>
<td><pre class="crayon-plain-tag">1..5</pre></td>
<td>No direct Java equivalent</td>
</tr>
<tr>
<td>::</td>
<td>Callable reference (method or constructor)</td>
<td><pre class="crayon-plain-tag">::foo</pre></td>
<td>Similar to Java method references</td>
</tr>
<tr>
<td>[]</td>
<td>Index access</td>
<td><pre class="crayon-plain-tag">array[0]</pre></td>
<td>Same as Java</td>
</tr>
<tr>
<td>()</td>
<td>Invoke operator</td>
<td><pre class="crayon-plain-tag">myFunction()</pre></td>
<td>No direct Java equivalent (Kotlin allows operator overloading)</td>
</tr>
</tbody>
</table>
<p>Understanding these operators, especially those unique to Kotlin, like the spread operator and null-safe operators, will make coding more efficient and error-free compared to Java.</p>
<div class="blog_h2"><span class="graybg">Kotlin Keywords Overview</span></div>
<p>Kotlin reserves certain keywords for defining syntax elements like functions, classes, and more. These keywords cannot be used as identifiers (such as variable or function names) unless escaped with backticks. Below is a table listing all Kotlin keywords and their purposes.</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;"><strong>Keyword</strong></td>
<td style="text-align: center;"><strong>Description</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td>abstract</td>
<td>Used to declare an abstract class or function.</td>
</tr>
<tr>
<td>annotation</td>
<td>Defines an annotation class.</td>
</tr>
<tr>
<td>as</td>
<td>Used for type casting.</td>
</tr>
<tr>
<td>break</td>
<td>Terminates the nearest enclosing loop.</td>
</tr>
<tr>
<td>by</td>
<td>Used for delegation and property delegation.</td>
</tr>
<tr>
<td>catch</td>
<td>Handles exceptions in a try block.</td>
</tr>
<tr>
<td>class</td>
<td>Defines a class.</td>
</tr>
<tr>
<td>companion</td>
<td>Declares a companion object within a class.</td>
</tr>
<tr>
<td>const</td>
<td>Declares compile-time constants.</td>
</tr>
<tr>
<td>continue</td>
<td>Skips the current iteration of the nearest enclosing loop.</td>
</tr>
<tr>
<td>crossinline</td>
<td>Prevents non-local returns from lambda expressions.</td>
</tr>
<tr>
<td>data</td>
<td>Declares a data class.</td>
</tr>
<tr>
<td>do</td>
<td>Used with `while` to create a do-while loop.</td>
</tr>
<tr>
<td>else</td>
<td>Specifies the alternative branch in an if-expression.</td>
</tr>
<tr>
<td>enum</td>
<td>Declares an enum class.</td>
</tr>
<tr>
<td>external</td>
<td>Marks a declaration as implemented in native code.</td>
</tr>
<tr>
<td>false</td>
<td>A boolean literal value representing "false".</td>
</tr>
<tr>
<td>final</td>
<td>Prevents a class or function from being overridden.</td>
</tr>
<tr>
<td>for</td>
<td>Used to create a for loop.</td>
</tr>
<tr>
<td>fun</td>
<td>Defines a function.</td>
</tr>
<tr>
<td>if</td>
<td>Specifies a conditional expression.</td>
</tr>
<tr>
<td>in</td>
<td>Checks if a value belongs to a range or collection.</td>
</tr>
<tr>
<td>inline</td>
<td>Used to request that a function be inlined.</td>
</tr>
<tr>
<td>inner</td>
<td>Declares an inner class that holds a reference to its outer class.</td>
</tr>
<tr>
<td>interface</td>
<td>Defines an interface.</td>
</tr>
<tr>
<td>is</td>
<td>Checks if a value is of a specific type.</td>
</tr>
<tr>
<td>lateinit</td>
<td>Delays initialization of a variable.</td>
</tr>
<tr>
<td>noinline</td>
<td>Prevents inlining of lambda expressions in inline functions.</td>
</tr>
<tr>
<td>null</td>
<td>A special literal representing "null".</td>
</tr>
<tr>
<td>object</td>
<td>Declares an object, which is a singleton.</td>
</tr>
<tr>
<td>open</td>
<td>Allows a class or function to be overridden.</td>
</tr>
<tr>
<td>operator</td>
<td>Marks a function as an operator for operator overloading.</td>
</tr>
<tr>
<td>out</td>
<td>Defines covariance in generics.</td>
</tr>
<tr>
<td>override</td>
<td>Overrides a function or property from a superclass or interface.</td>
</tr>
<tr>
<td>package</td>
<td>Declares the package for the file.</td>
</tr>
<tr>
<td>private</td>
<td>Defines the visibility of a declaration to be within the containing class or file.</td>
</tr>
<tr>
<td>protected</td>
<td>Defines visibility to be within the class and its subclasses.</td>
</tr>
<tr>
<td>public</td>
<td>Defines visibility to be accessible from anywhere.</td>
</tr>
<tr>
<td>return</td>
<td>Exits from the nearest enclosing function.</td>
</tr>
<tr>
<td>sealed</td>
<td>Declares a sealed class, which restricts subclassing to within the same file.</td>
</tr>
<tr>
<td>super</td>
<td>Refers to the superclass's implementation.</td>
</tr>
<tr>
<td>this</td>
<td>Refers to the current instance of a class.</td>
</tr>
<tr>
<td>throw</td>
<td>Throws an exception.</td>
</tr>
<tr>
<td>true</td>
<td>A boolean literal value representing "true".</td>
</tr>
<tr>
<td>try</td>
<td>Starts a block of code that may throw an exception.</td>
</tr>
<tr>
<td>typealias</td>
<td>Defines a new name for an existing type.</td>
</tr>
<tr>
<td>val</td>
<td>Declares a read-only property or local variable.</td>
</tr>
<tr>
<td>var</td>
<td>Declares a mutable property or local variable.</td>
</tr>
<tr>
<td>vararg</td>
<td>Allows a function to accept a variable number of arguments.</td>
</tr>
<tr>
<td>when</td>
<td>Acts as a replacement for the switch statement.</td>
</tr>
<tr>
<td>where</td>
<td>Specifies constraints on type parameters.</td>
</tr>
<tr>
<td>while</td>
<td>Starts a while loop.</td>
</tr>
</tbody>
</table>
<p>These keywords are essential for understanding the Kotlin language syntax. By knowing their purpose, you can write cleaner and more efficient Kotlin code. Some keywords like <pre class="crayon-plain-tag">val</pre>, <pre class="crayon-plain-tag">var</pre>, and <pre class="crayon-plain-tag">fun</pre> have no direct equivalent in Java, showcasing Kotlin's unique features.</p>
<div class="blog_h1"><span class="graybg">Control Flow Constructs</span></div>
<p>Control flow constructs are essential in any programming language as they dictate the order in which statements are executed. Kotlin offers a rich set of control flow statements that are both expressive and concise. In this section, we'll explore how Kotlin handles conditional statements and loops, highlighting the differences and similarities with Java.</p>
<div class="blog_h2"><span class="graybg">Conditional Statements</span></div>
<p>Kotlin provides powerful conditional statements, including <pre class="crayon-plain-tag">if</pre> expressions and the versatile <pre class="crayon-plain-tag">when</pre> expression. These constructs allow for more expressive and concise code compared to Java's traditional if-else and switch statements.</p>
<div class="blog_h3"><span class="graybg">if and else Expressions</span></div>
<p>In Kotlin, <span style="background-color: #c0c0c0;">if and else are expressions, meaning they return a value</span>. This allows you to assign the result of an if expression directly to a variable, enhancing code conciseness.</p>
<pre class="crayon-plain-tag">val result = if (condition) {
    // Block of code
    valueIfTrue
} else {
    // Block of code
    valueIfFalse
}</pre>
<p>Example: </p>
<pre class="crayon-plain-tag">val max = if (a &gt; b) a else b</pre>
<p>Equivalent Java Code:</p>
<pre class="crayon-plain-tag">int max = (a &gt; b) ? a : b;</pre>
<p>Kotlin allows for multiple else if branches within an if expression.</p>
<pre class="crayon-plain-tag">val grade = if (score &gt;= 90) {
    "A"
} else if (score &gt;= 80) {
    "B"
} else if (score &gt;= 70) {
    "C"
} else if (score &gt;= 60) {
    "D"
} else {
    "F"
}</pre>
<p>Equivalent Java Code:</p>
<pre class="crayon-plain-tag">String grade;
if (score &gt;= 90) {
    grade = "A";
} else if (score &gt;= 80) {
    grade = "B";
} else if (score &gt;= 70) {
    grade = "C";
} else if (score &gt;= 60) {
    grade = "D";
} else {
    grade = "F";
}</pre>
<div class="blog_h3"><span class="graybg">The When expression </span></div>
<p>Kotlin's when expression is a powerful and flexible construct that can replace Java's switch statement. Starting from Java 14, Java introduced switch expressions, which bring some of the capabilities of Kotlin's when to Java.</p>
<p>Syntax of Kotlin's when Expression:</p>
<pre class="crayon-plain-tag">when (expression) {
    value1 -&gt; result1
    value2, value3 -&gt; result2
    in range -&gt; result3
    !in range -&gt; result4
    is Type -&gt; result5
    else -&gt; defaultResult
}</pre>
<p>Example:</p>
<pre class="crayon-plain-tag">fun determineResponse(input: Any): String {
    return when (input) {
        // Matching a specific value
        1 -&gt; "You entered one."
        
        // Matching multiple values
        2, 3 -&gt; "You entered two or three."

        // Matching a value within a range
        in 4..10 -&gt; "Your number is in the range of 4 to 10."

        // Matching a value outside of a range
        !in 11..20 -&gt; "Your number is not in the range of 11 to 20."

        // Matching by type
        is String -&gt; "You entered a string."

        // Default case
        else -&gt; "I don't know what you entered."
    }
}</pre>
<p>Java 14 introduced switch expressions, which can <span style="background-color: #c0c0c0;">return a value and use the arrow syntax -&gt; </span></p>
<pre class="crayon-plain-tag">int day = 3;

String dayName = switch (day) {
    case 1 -&gt; "Monday";
    case 2 -&gt; "Tuesday";
    case 3 -&gt; "Wednesday";
    case 4 -&gt; "Thursday";
    case 5 -&gt; "Friday";
    case 6, 7 -&gt; "Weekend";
    default -&gt; "Invalid day";
};

System.out.println(dayName); // Output: Wednesday</pre>
<p>Kotlin's when can perform type checks using <pre class="crayon-plain-tag">is</pre>. Java 16 introduced Pattern Matching for instanceof, and Java 17 enhanced pattern matching in switch statements (preview feature). </p>
<pre class="crayon-plain-tag">// Kotlin Example:
fun describe(obj: Any): String =
    when (obj) {
        is Int -&gt; "Integer"
        is String -&gt; "String of length ${obj.length}"
        is Boolean -&gt; "Boolean"
        else -&gt; "Unknown"
    }

println(describe("Hello")) // Output: String of length 5</pre><br />
<pre class="crayon-plain-tag">// Java Example with Pattern Matching (instanceof) (Java 16 and Later):
public String describe(Object obj) {
    if (obj instanceof Integer) {
        return "Integer";
    } else if (obj instanceof String s) {
        return "String of length " + s.length();
    } else if (obj instanceof Boolean) {
        return "Boolean";
    } else {
        return "Unknown";
    }
}

// Java Example with Pattern Matching in switch (Java 17 Preview):
public String describe(Object obj) {
    return switch (obj) {
        case Integer i -&gt; "Integer";
        case String s -&gt; "String of length " + s.length();
        case Boolean b -&gt; "Boolean";
        default -&gt; "Unknown";
    };
}</pre>
<div class="blog_h2"><span class="graybg">Loops</span></div>
<p>Kotlin offers loops that are similar to Java's but with enhanced features and more concise syntax.</p>
<div class="blog_h3"><span class="graybg">for Loop</span></div>
<p>Kotlin's for loop is designed to iterate over any iterable, including ranges, arrays, and collections.</p>
<p>Iterating Over Ranges with Kotlin:</p>
<pre class="crayon-plain-tag">// Iterating Over Ranges
for (i in 1..5) {
    print("$i ") // Output: 1 2 3 4 5
}</pre>
<p>Java Equivalent Using for Loop: </p>
<pre class="crayon-plain-tag">for (int i = 1; i &lt;= 5; i++) {
    System.out.print(i + " "); // Output: 1 2 3 4 5
}</pre>
<p>Java Equivalent Using Streams (Java 8 and Later) :</p>
<pre class="crayon-plain-tag">IntStream.rangeClosed(1, 5).forEach(i -&gt; System.out.print(i + " "));
// Output: 1 2 3 4 5</pre>
<p>Iterating Over Collections with Kotlin:</p>
<pre class="crayon-plain-tag">val fruits = listOf("Apple", "Banana", "Cherry")

for (fruit in fruits) {
    println(fruit)
}</pre>
<p>Java enhanced for loop:</p>
<pre class="crayon-plain-tag">List fruits = List.of("Apple", "Banana", "Cherry");

for (String fruit : fruits) {
    System.out.println(fruit);
}</pre>
<div class="blog_h3"><span class="graybg">while and do-while Loops</span></div>
<p>These loops function similarly in both Kotlin and Java. </p>
<pre class="crayon-plain-tag">var count = 5
while (count &gt; 0) {
    println(count)
    count--
}</pre>
<div class="blog_h3"><span class="graybg">Ranges and Progressions</span></div>
<p>Kotlin provides range expressions that simplify loop constructs.</p>
<pre class="crayon-plain-tag">// Exclusive Range:
for (i in 1 until 5) {
    print("$i ") // Output: 1 2 3 4
}

// Downward Range:
for (i in 5 downTo 1) {
    print("$i ") // Output: 5 4 3 2 1
}


// Stepped Range:
for (i in 1..10 step 2) {
    print("$i ") // Output: 1 3 5 7 9
}</pre>
<p>Java can use IntStream with methods like range, rangeClosed, and custom steps. </p>
<pre class="crayon-plain-tag">IntStream.iterate(1, i -&gt; i + 2)
    .limit(5)
    .forEach(i -&gt; System.out.print(i + " "));
// Output: 1 3 5 7 9</pre>
<div class="blog_h3"><span class="graybg">break and continue with Labels </span></div>
<p>Kotlin allows labeled break and continue statements for controlling nested loops.</p>
<pre class="crayon-plain-tag">outer@ for (i in 1..5) {
    for (j in 1..5) {
        if (i * j &gt; 10) break@outer
        println("i = $i, j = $j")
    }
}</pre>
<p>Java Equivalent Using for labeled loops:</p>
<pre class="crayon-plain-tag">outer: // Label for the outer loop
for (int i = 1; i &lt;= 5; i++) {
    for (int j = 1; j &lt;= 5; j++) {
        if (i * j &gt; 10) {
            break outer; // Break the outer loop using the label
        }
        System.out.println("i = " + i + ", j = " + j);
    }
}</pre>
<div class="blog_h2"><span class="graybg">Exception Handling</span></div>
<p>Exception handling in Kotlin is similar to Java but with some key differences.</p>
<div class="blog_h3"><span class="graybg">Try-Catch Blocks</span></div>
<pre class="crayon-plain-tag">try {
    // Code that may throw an exception
} catch (e: ExceptionType) {
    // Handle exception
} finally {
    // Optional finally block
}</pre>
<p>Example:</p>
<pre class="crayon-plain-tag">try {
    val result = numerator / denominator
} catch (e: ArithmeticException) {
    println("Cannot divide by zero")
} finally {
    println("Execution completed")
}</pre>
<div class="blog_h3"><span class="graybg">Checked vs. Unchecked Exceptions </span></div>
<p>Kotlin:</p>
<ol style="list-style-type: undefined;">
<li>All exceptions are unchecked.</li>
<li>No need to declare exceptions with throws.</li>
<li>Reduces boilerplate code.</li>
</ol>
<p>Java:</p>
<ol style="list-style-type: undefined;">
<li>Differentiates between checked and unchecked exceptions.</li>
<li>Checked exceptions must be declared or handled.</li>
<li>Can lead to verbose code with try-catch blocks.</li>
</ol>
<div class="blog_h3"><span class="graybg">Try-with-Resources </span></div>
<p>Java 7 introduced the try-with-resources statement to manage resources automatically.</p>
<pre class="crayon-plain-tag">try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}</pre>
<p>Kotlin provides the <pre class="crayon-plain-tag">use</pre> extension function for resource management.</p>
<pre class="crayon-plain-tag">BufferedReader(FileReader("file.txt")).use { reader -&gt;
    var line = reader.readLine()
    while (line != null) {
        println(line)
        line = reader.readLine()
    }
}</pre>
<p>The use function ensures the resource is closed after use. </p>
<div class="blog_h3"><span class="graybg">Exception Handling with Coroutines</span></div>
<p>Kotlin's coroutines provide advanced exception handling mechanisms. <span style="background-color: #c0c0c0;">Exceptions in coroutines can be handled within the coroutine or propagated to the caller</span>.</p>
<pre class="crayon-plain-tag">import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            // Coroutine code that may throw an exception
        } catch (e: Exception) {
            // Handle exception
        }
    }
    job.join()
}</pre>
<p>Java's Approach to Asynchronous Exception Handling:  Java uses CompletableFuture and ExecutorService for asynchronous programming, with exception handling mechanisms.</p>
<pre class="crayon-plain-tag">CompletableFuture future = CompletableFuture.runAsync(() -&gt; {
    try {
        // Asynchronous code
    } catch (Exception e) {
        // Handle exception
    }
});

future.join();</pre>
<div class="blog_h1"><span class="graybg">Functions in Kotlin</span></div>
<p>Functions are fundamental building blocks in Kotlin, and they come with a variety of features that enhance code readability, conciseness, and expressiveness. In this section, we'll explore how functions in Kotlin differ from those in Java.</p>
<div class="blog_h2"><span class="graybg">Function Declaration and Calling</span></div>
<div class="blog_h3"><span class="graybg">Basic Function Syntax</span></div>
<p>In Kotlin, functions are declared using the <pre class="crayon-plain-tag">fun</pre> keyword, followed by the function name, parameter list, and return type:</p>
<pre class="crayon-plain-tag">fun functionName(parameter1: Type1, parameter2: Type2): ReturnType {
    // function body
    return result
}

// Example
fun add(a: Int, b: Int): Int {
    return a + b
}</pre>
<div class="blog_h3"><span class="graybg">Single-Expression Functions</span></div>
<p>For functions that return a single expression, Kotlin allows you to simplify the syntax using the equals sign =.</p>
<pre class="crayon-plain-tag">fun multiply(a: Int, b: Int): Int = a * b
// If the return type can be inferred, you can omit it:
fun multiply(a: Int, b: Int) = a * b</pre>
<div class="blog_h3"><span class="graybg">Comparison with Java</span></div>
<p>In Java, methods must be declared within a class, and the syntax is more verbose.</p>
<pre class="crayon-plain-tag">public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}</pre>
<p>Starting from Java 8, you can define static methods in interfaces and use lambda expressions, but you still need to define methods within a class or interface.</p>
<p>Java Example with Lambda (Java 8 and Later):</p>
<pre class="crayon-plain-tag">BiFunction&lt;Integer, Integer, Integer&gt; add = (a, b) -&gt; a + b;
int sum = add.apply(5, 3);</pre>
<div class="blog_h2"><span class="graybg">Default and Named Arguments</span></div>
<div class="blog_h3"><span class="graybg">Default Arguments</span></div>
<p>Kotlin allows you to specify default values for function parameters. If an argument is not provided, the default value is used.</p>
<pre class="crayon-plain-tag">fun greet(name: String = "Guest") {
    println("Hello, $name!")
}

greet()            // Output: Hello, Guest!
greet("Alice")     // Output: Hello, Alice!</pre>
<div class="blog_h3"><span class="graybg">Named Arguments </span></div>
<p>When calling a function, you can specify the names of the parameters, allowing you to pass arguments in any order and enhance code readability.</p>
<pre class="crayon-plain-tag">fun displayInfo(name: String, age: Int, country: String) {
    println("$name is $age years old from $country.")
}

displayInfo(age = 30, country = "USA", name = "Bob")</pre>
<div class="blog_h3"><span class="graybg">Comparison with Java</span></div>
<p>In Java, prior to version 15, there is no direct support for default or named arguments in methods. You typically overload methods to achieve similar functionality.</p>
<p>Java Example with Method Overloading:</p>
<pre class="crayon-plain-tag">public void greet() {
    greet("Guest");
}

public void greet(String name) {
    System.out.println("Hello, " + name + "!");
}</pre>
<p>Named arguments are not supported in Java. However, starting from Java 15, the introduction of Records allows for more concise data carriers, but they don't provide named arguments for methods.</p>
<pre class="crayon-plain-tag">// Example Using Records in Java 15
public record Person(String name, int age) {}

public class Main {
    public static void main(String[] args) {
        // Creating an instance of Person
        Person person = new Person("Alice", 30);
        displayInfo(person);
    }

    public static void displayInfo(Person person) {
        System.out.println(person.name() + " is " + person.age() + " years old.");
    }
}</pre>
<div class="blog_h2"><span class="graybg">Extension Functions</span></div>
<p>Extension functions in Kotlin allow you to add new functions to existing classes without inheriting from them or using design patterns like Decorator.</p>
<pre class="crayon-plain-tag">fun String.isPalindrome(): Boolean {
    return this == this.reversed()
}

val word = "level"
println(word.isPalindrome()) // Output: true</pre>
<div class="blog_h3"><span class="graybg">Comparison with Java</span></div>
<p>Java does not support extension functions directly. To achieve similar functionality, you would create utility classes with static methods.</p>
<p>Java Example:</p>
<pre class="crayon-plain-tag">public class StringUtils {
    public static boolean isPalindrome(String s) {
        return s.equals(new StringBuilder(s).reverse().toString());
    }
}

String word = "level";
System.out.println(StringUtils.isPalindrome(word)); // Output: true</pre>
<p>With Java 8 and later, you can use default methods in interfaces to provide implementations, but this requires modifying the interface and doesn't allow adding methods to existing classes like String. </p>
<div class="blog_h2"><span class="graybg">Higher-Order Functions and Lambdas</span></div>
<p>Kotlin treats functions as first-class citizens, meaning you can store functions in variables, pass them as parameters, and return them from other functions.</p>
<div class="blog_h3"><span class="graybg">Higher-Order Functions</span></div>
<p>A higher-order function is a function that takes functions as parameters or returns a function.</p>
<pre class="crayon-plain-tag">fun operate(a: Int, b: Int, operation: (Int, Int) -&gt; Int): Int {
    return operation(a, b)
}

// ::add syntax stands for a function reference
val sum = operate(4, 5, ::add)
println(sum) // Output: 9

// Using lambda expression
// Trailing Lambda Syntax: if the last parameter of the function is a lambda, Kotlin allows 
// you to move the lambda outside of the parentheses for improved readability:
val product = operate(4, 5) { x, y -&gt; x * y }
println(product) // Output: 20</pre>
<div class="blog_h3"><span class="graybg">Lambdas and Anonymous Functions</span></div>
<p>Lambda expressions provide a concise way to represent functions.</p>
<pre class="crayon-plain-tag">val lambdaName: (InputType) -&gt; ReturnType = { arguments -&gt; body }</pre>
<p>Example:</p>
<pre class="crayon-plain-tag">val square: (Int) -&gt; Int = { number -&gt; number * number }
println(square(6)) // Output: 36</pre>
<div class="blog_h3"><span class="graybg">Trailing Lambda</span></div>
<p>Kotlin provides a concise and expressive syntax for passing lambda expressions as function parameters. One of the features that enhance code readability is the trailing lambda syntax. This allows you to <span style="background-color: #c0c0c0;">pass a lambda expression after the function call parentheses</span>, which can make your code more readable, especially when working with higher-order functions.</p>
<pre class="crayon-plain-tag">// Standard syntax
functionName(parameters..., { lambda_parameters -&gt; lambda_body })

// Trailing lambda syntax
functionName(parameters...) { lambda_parameters -&gt; lambda_body }

// If the lambda is the only parameter
functionName { lambda_parameters -&gt; lambda_body }</pre>
<p>Examples:</p>
<pre class="crayon-plain-tag">fun performOperation(x: Int, operation: (Int) -&gt; Int): Int {
    return operation(x)
}

// Standard Syntax
val result = performOperation(5, { num -&gt; num * num })
println(result) // Output: 25


// Trailing Lambda Syntax
val result = performOperation(5) { num -&gt;
    num * num
}
println(result) // Output: 25</pre>
<p>If a function takes only a lambda parameter, you can omit the parentheses entirely.</p>
<pre class="crayon-plain-tag">fun repeatAction(times: Int, action: () -&gt; Unit) {
    for (i in 1..times) {
        action()
    }
}

repeatAction(3) {
    println("Hello, World!")
}
// Output:
// Hello, World!
// Hello, World!
// Hello, World!</pre>
<p>Kotlin's standard library provides many functions that take lambdas as parameters. Trailing lambdas can make these calls more readable.</p>
<pre class="crayon-plain-tag">val numbers = listOf(1, 2, 3, 4, 5)

// Standard syntax
val evenNumbers = numbers.filter({ it % 2 == 0 })

// Trailing lambda syntax
val evenNumbers = numbers.filter { it % 2 == 0 }

println(evenNumbers) // Output: [2, 4]


val result = numbers
    .filter { it &gt; 2 }
    .map { it * it }
    .also { println("Squared numbers: $it") }</pre>
<div class="blog_h3"><span class="graybg">Using the it Keyword in Lambda </span></div>
<p>In lambdas with a single parameter, you can omit the parameter declaration and use the implicit <pre class="crayon-plain-tag">it</pre> variable.</p>
<pre class="crayon-plain-tag">val squares = numbers.map { it * it }
println(squares) // Output: [1, 4, 9, 16, 25]
 </pre>
<div class="blog_h3"><span class="graybg">Inline Functions</span></div>
<p>Kotlin allows you to declare functions as <pre class="crayon-plain-tag">inline</pre> to reduce overhead associated with higher-order functions.</p>
<pre class="crayon-plain-tag">inline fun performOperation(a: Int, b: Int, operation: (Int, Int) -&gt; Int): Int {
    return operation(a, b)
}</pre>
<div class="blog_h3"><span class="graybg">Comparison with Java </span></div>
<p>Java 8 introduced lambda expressions and functional interfaces, enabling functional programming paradigms.</p>
<pre class="crayon-plain-tag">BiFunction&lt;Integer, Integer, Integer&gt; add = (a, b) -&gt; a + b;
int sum = add.apply(4, 5);
System.out.println(sum); // Output: 9</pre>
<p>Java doesn't support higher-order functions in the same way as Kotlin. You can pass functional interfaces as parameters, but the syntax is more verbose. </p>
<pre class="crayon-plain-tag">public int operate(int a, int b, BiFunction&lt;Integer, Integer, Integer&gt; operation) {
    return operation.apply(a, b);
}

int product = operate(4, 5, (x, y) -&gt; x * y);
System.out.println(product); // Output: 20</pre>
<div class="blog_h2"><span class="graybg">Tail Recursion</span></div>
<p>Kotlin supports tail recursive functions using the <pre class="crayon-plain-tag">tailrec</pre> modifier, which optimizes recursive calls to prevent stack overflow errors.</p>
<pre class="crayon-plain-tag">tailrec fun factorial(n: Int, accumulator: Int = 1): Int {
    return if (n &lt;= 1) accumulator else factorial(n - 1, n * accumulator)
}

println(factorial(5)) // Output: 120</pre>
<p>The tailrec modifier in Kotlin transforms a recursive function into an iterative one during compilation, optimizing it to prevent stack overflow issues that can occur with deep recursion. This is achieved by performing tail call optimization (TCO). Here's a breakdown of the exact effect of the tailrec keyword:</p>
<ol>
<li>Tail Call Optimization (TCO): In Kotlin, <span style="background-color: #c0c0c0;">a function call is considered a tail call if it's the last operation to be executed in a function</span>. If the recursive call is in the tail position (i.e., it is the last thing the function does before returning), Kotlin replaces the recursive call with a loop during compilation, avoiding the need for additional stack frames for each recursive call.</li>
<li>Stack Frame Elimination: Normally, every function call adds a new stack frame, which can lead to stack overflow errors for deep recursion. The tailrec modifier removes the need for these additional frames by reusing the current function’s stack frame.</li>
<li>Iterative Approach: When you mark a function with tailrec, the compiler rewrites the recursive function into a loop under the hood. This makes the recursion as efficient as a traditional loop, avoiding the overhead of managing recursion in the stack.</li>
</ol>
<div class="blog_h3"><span class="graybg">Comparison with Java</span></div>
<p>Java does not have built-in support for tail call optimization. Recursive functions can lead to stack overflow errors if not carefully managed.</p>
<div class="blog_h2"><span class="graybg">Local Functions and Closures</span></div>
<p>Kotlin allows you to define functions inside other functions, and these inner functions can access variables from the outer function.</p>
<pre class="crayon-plain-tag">fun greeting(): () -&gt; Unit {
    val message = "Hello"
    fun sayHello() {
        println(message)
    }
    return ::sayHello
}

val greet = greeting()
greet() // Output: Hello</pre>
<p>A function inside another function is called a Closure because it "close over" its surrounding environment, meaning it captures and remembers the state of variables from the outer function, even after that outer function has finished executing.</p>
<div class="blog_h3"><span class="graybg"> Comparison with Java</span></div>
<p>Java supports lambda expressions and anonymous inner classes but does not support defining named local functions inside methods.</p>
<div class="blog_h2"><span class="graybg">Function Literals with Receiver</span></div>
<p>Kotlin allows you to define lambda expressions that have a receiver object, enabling a DSL-like syntax.</p>
<pre class="crayon-plain-tag">// The lambda (builderAction) is an extension function on StringBuilder that doesn't return any value (Unit).
// Unit is a special type that is used to indicate the absence of a meaningful return value from a function. 
// It is similar to void in languages like Java or C, but it is treated as a real type in Kotlin, and it has
// only one value, which is Unit
fun buildString(builderAction: StringBuilder.() -&gt; Unit): String {
    val sb = StringBuilder()
    sb.builderAction()
    return sb.toString()
}

val result = buildString {
    append("Hello, ")
    append("World!")
}

println(result) // Output: Hello, World!</pre>
<p>The <pre class="crayon-plain-tag">{ ... }</pre> after buildString is Kotlin's trailing lambda syntax for better readability. </p>
<div class="blog_h3"><span class="graybg">Comparison with Java</span></div>
<p>Java does not support function literals with receivers. Achieving similar functionality would require more verbose code and design patterns.</p>
<div class="blog_h2"><span class="graybg">Operator Overloading</span></div>
<p>Kotlin allows you to provide implementations for predefined operators for your own types by overloading them. </p>
<pre class="crayon-plain-tag">// In Kotlin, a data class is a special type of class that is primarily used to hold data. 
// It automatically generates useful methods like equals(), hashCode(), toString(), and copy() for you, 
// based on the properties you define. This makes it especially handy when creating simple classes whose 
// main purpose is to store state (i.e., the values of its properties).
data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point) = Point(x + other.x, y + other.y)
}

val p1 = Point(2, 3)
val p2 = Point(4, 5)
val sum = p1 + p2
println(sum) // Output: Point(x=6, y=8)</pre>
<div class="blog_h3"><span class="graybg">Comparison with Java</span></div>
<p>Java does not support operator overloading, except for the + operator for string concatenation. You would need to define methods like add to achieve similar functionality. </p>
<div class="blog_h2"><span class="graybg">Coroutines</span></div>
<p>Although coroutines are covered in detail in a later section, it's worth mentioning that Kotlin functions can be declared with the <pre class="crayon-plain-tag">suspend</pre> modifier to support asynchronous operations.</p>
<pre class="crayon-plain-tag">suspend fun fetchData(): String {
    // Simulate a long-running operation
    delay(1000)
    return "Data fetched"
}</pre>
<p>suspend functions are special functions in Kotlin that <span style="background-color: #c0c0c0;">can be paused and resumed at a later time without blocking the thread they are running on</span>.</p>
<p>To use the fetchData() function, you'll <span style="background-color: #c0c0c0;">need to call it within a coroutine scope, as suspend functions can only be called from within another suspend function or a coroutine</span>.</p>
<pre class="crayon-plain-tag">import kotlinx.coroutines.*

fun main() {
    // Start a new coroutine:  This creates a coroutine scope that blocks the main thread 
    // until all coroutines inside it complete.
    runBlocking {
        println("Fetching data...")
        val result = fetchData() // Calls the suspend function
        println(result) // Prints: Data fetched
    }
}</pre>
<div class="blog_h3"><span class="graybg">Comparison with Java</span></div>
<p>Starting from Java 19, Virtual Threads (Project Loom) have been introduced to support lightweight concurrency. However, as of Java 21, coroutines as language constructs are not available. Asynchronous programming in Java is typically handled using CompletableFuture, reactive streams, or third-party libraries.</p>
<p>Java Example with CompletableFuture:</p>
<pre class="crayon-plain-tag">CompletableFuture future = CompletableFuture.supplyAsync(() -&gt; {
    // Simulate long-running operation
    Thread.sleep(1000);
    return "Data fetched";
});

future.thenAccept(System.out::println);</pre>
<div class="blog_h2"><span class="graybg">Special Kotlin Functions</span></div>
<p>Kotlin provides several special functions like <pre class="crayon-plain-tag">with</pre>, <pre class="crayon-plain-tag">apply</pre>, <pre class="crayon-plain-tag">run</pre>, <pre class="crayon-plain-tag">let</pre>, and more to simplify common operations. These functions primarily help manage scope, allow concise object configuration, and reduce boilerplate code. Let's explore these functions with examples.</p>
<div class="blog_h3"><span class="graybg">with</span></div>
<p>The <pre class="crayon-plain-tag">with</pre> function is used to call multiple functions on the same object without repeating its name. It is typically used for operating on an object in a block of code.</p>
<pre class="crayon-plain-tag">data class User(val name: String, var age: Int, var city: String)

fun main() {
    val user = User("John", 25, "New York")
    with(user) {
        println(name)  // Access `name` directly
        age += 1
        println("Updated age: $age")
    }
}</pre>
<p>Output:</p>
<pre class="crayon-plain-tag">John
Updated age: 26</pre>
<p>In the example, <pre class="crayon-plain-tag">with</pre> allows access to the properties of <pre class="crayon-plain-tag">user</pre> without explicitly referring to the object.</p>
<div class="blog_h3"><span class="graybg">apply</span></div>
<p>The <pre class="crayon-plain-tag">apply</pre> function is used to initialize or configure an object. It returns the object itself after applying the configuration.</p>
<pre class="crayon-plain-tag">val user = User("John", 25, "New York").apply {
    age = 26
    city = "San Francisco"
}
println(user)  // Output: User(name=John, age=26, city=San Francisco)</pre>
<p>The <pre class="crayon-plain-tag">apply</pre> function is commonly used for initializing or setting properties in a concise manner.</p>
<div class="blog_h3"><span class="graybg">let</span></div>
<p>The <pre class="crayon-plain-tag">let</pre> function is useful for performing operations on a non-null object and is often used in combination with the safe-call operator (<pre class="crayon-plain-tag">?.</pre>).</p>
<pre class="crayon-plain-tag">val name: String? = "John"
name?.let {
    println("Hello, $it")  // Output: Hello, John
}</pre>
<p>If <pre class="crayon-plain-tag">name</pre> is not null, <pre class="crayon-plain-tag">let</pre> executes the block and prints the value.</p>
<div class="blog_h3"><span class="graybg">run</span></div>
<p>The <pre class="crayon-plain-tag">run</pre> function is similar to <pre class="crayon-plain-tag">let</pre>, but instead of returning the object, it returns the result of the lambda expression. It's great for scoping and executing code in a context.</p>
<pre class="crayon-plain-tag">val user = User("John", 25, "New York")
val result = user.run {
    "User's name is $name, age is $age"
}
println(result)  // Output: User's name is John, age is 25</pre>
<p>In this example, <pre class="crayon-plain-tag">run</pre> returns the result of the expression, not the object itself.</p>
<div class="blog_h3"><span class="graybg">also</span></div>
<p>The <pre class="crayon-plain-tag">also</pre> function is similar to <pre class="crayon-plain-tag">apply</pre>, but it is used to perform additional actions such as logging, side-effects, or validation without affecting the object’s state. It returns the object itself.</p>
<pre class="crayon-plain-tag">val user = User("John", 25, "New York").also {
    println("User created: $it")
}
println(user)  // Output: User(name=John, age=25, city=New York)</pre>
<p><pre class="crayon-plain-tag">also</pre> is often used when you need to perform operations like logging or debugging without changing the object.</p>
<div class="blog_h3"><span class="graybg">takeIf and takeUnless</span></div>
<p>The <pre class="crayon-plain-tag">takeIf</pre> function returns the object if the provided predicate is true; otherwise, it returns null. The opposite is <pre class="crayon-plain-tag">takeUnless</pre>, which returns the object if the predicate is false.</p>
<pre class="crayon-plain-tag">val user = User("John", 25, "New York")

val result = user.takeIf { it.age &gt;= 18 }  // Returns user if age &gt;= 18
println(result)  // Output: User(name=John, age=25, city=New York)

val resultNull = user.takeUnless { it.age &lt; 18 }  // Returns user if age &gt;= 18
println(resultNull)  // Output: User(name=John, age=25, city=New York)</pre>
<p>Both <pre class="crayon-plain-tag">takeIf</pre> and <pre class="crayon-plain-tag">takeUnless</pre> are useful for performing conditional operations on an object.</p>
<p>These special functions in Kotlin provide a powerful way to write concise and expressive code. By using these functions effectively, you can avoid boilerplate code and make your programs easier to understand and maintain.</p>
<div class="blog_h1"><span class="graybg">Object-Oriented Programming</span></div>
<p>Object-Oriented Programming (OOP) is a paradigm centered around objects and classes, enabling developers to model real-world entities and relationships in code. Both Kotlin and Java are object-oriented languages, but Kotlin introduces several enhancements and syntactic sugar to make OOP more concise and expressive. This section explores how Kotlin handles OOP concepts compared to Java. </p>
<div class="blog_h2"><span class="graybg">Classes and Objects</span></div>
<div class="blog_h3"><span class="graybg">Class Declaration</span></div>
<p>In Kotlin, classes are declared using the <pre class="crayon-plain-tag">class</pre> keyword, and you can define properties and methods within them. Unlike Java, <span style="background-color: #c0c0c0;">Kotlin does not require you to place each class in a separate file or match the filename with the class name</span>.</p>
<p>Example:</p>
<pre class="crayon-plain-tag">class Person {
    var name: String = ""
    var age: Int = 0

    fun greet() {
        println("Hello, my name is $name.")
    }
}</pre>
<p>Equivalent Java code:</p>
<pre class="crayon-plain-tag">public class Person {
    private String name = "";
    private int age = 0;

    public void greet() {
        System.out.println("Hello, my name is " + name + ".");
    }
}</pre>
<div class="blog_h3"><span class="graybg">Primary Constructors </span></div>
<p>Kotlin introduces the concept of primary constructors, which are <span style="background-color: #c0c0c0;">declared in the class header and can initialize properties directly</span>.</p>
<pre class="crayon-plain-tag">// val name: String declares an immutable property.
// var age: Int declares a mutable property.
// Properties are initialized through the constructor.
class Person(val name: String, var age: Int) {
    fun greet() {
        println("Hello, my name is $name.")
    }
}

// Instantiate a person
val personInstance = Person(nameArgument, ageArgument)</pre>
<p>Equivalent Java code: </p>
<pre class="crayon-plain-tag">public class Person {
    private final String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public void greet() {
        System.out.println("Hello, my name is " + name + ".");
    }
    
    // Getters and setters omitted for brevity
}

// Instantiate a person
Person personInstance = new Person(nameArgument, ageArgument);</pre>
<div class="blog_h3"><span class="graybg">Initializer Blocks</span></div>
<p>In Kotlin, initializer blocks can be used to execute code during object creation.</p>
<pre class="crayon-plain-tag">class Person(val name: String, var age: Int) {
    init {
        println("Person initialized with name = $name and age = $age")
    }
}</pre>
<div class="blog_h3"><span class="graybg">Secondary Constructors</span></div>
<p>Kotlin allows secondary constructors for additional initialization logic. </p>
<pre class="crayon-plain-tag">class Person {
    var name: String
    var age: Int

    constructor(name: String) {
        this.name = name
        this.age = 0
    }

    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }
}</pre>
<p>Please note that Secondary Constructors are less common due to the flexibility of default parameters.</p>
<div class="blog_h3"><span class="graybg">Properties and Fields </span></div>
<p>In Kotlin, properties are a central concept that combines a field (to hold data) and optional accessors (getters and setters) into a single, concise syntax. This approach simplifies code and reduces boilerplate compared to Java, where you typically need to declare private fields and provide public getter and setter methods separately.</p>
<pre class="crayon-plain-tag">//  declares a mutable property name of type String with an initial value of "Unknown".
var name: String = "Unknown"</pre>
<p>For a mutable property declared with <pre class="crayon-plain-tag">var</pre>, Kotlin automatically generates:</p>
<ol style="list-style-type: undefined;">
<li>A getter method to retrieve the property's value.</li>
<li>A setter method to set or modify the property's value.</li>
</ol>
<p>For an immutable property declared with <pre class="crayon-plain-tag">val</pre>, Kotlin only generates a getter. </p>
<p>You can customize the getter and setter if you need additional logic when accessing or modifying the property. Syntax for Custom Accessors:</p>
<pre class="crayon-plain-tag">var propertyName: Type = initialValue
    get() {
        // Custom getter logic
    }
    set(value) {
        // Custom setter logic
    }</pre>
<p>Within the getter and setter, <pre class="crayon-plain-tag">field</pre> is a special backing field identifier provided by Kotlin. It refers to the actual storage of the property's value.</p>
<pre class="crayon-plain-tag">var name: String = "Unknown"
    get() = field
    set(value) {
        field = value.capitalize()
    }</pre>
<p>In Java, we always need to explicitly implement getter and setter:</p>
<pre class="crayon-plain-tag">private String name = "Unknown";

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = capitalize(name);
}

private String capitalize(String value) {
    // Capitalize logic
}</pre>
<div class="blog_h2"><span class="graybg">Inheritance and Interfaces</span></div>
<div class="blog_h3"><span class="graybg">Inheritance</span></div>
<p>I<span style="background-color: #c0c0c0;">n Kotlin, classes are final by default</span>. To allow a class to be subclassed, you must mark it with the <pre class="crayon-plain-tag">open</pre> keyword.</p>
<pre class="crayon-plain-tag">open class Animal {
    open fun sound() {
        println("Some sound")
    }
}

class Dog : Animal() {
    override fun sound() {
        println("Bark")
    }
}</pre>
<p>In Java, classes and methods are open for extension by default unless marked as final:</p>
<pre class="crayon-plain-tag">public class Animal {
    public void sound() {
        System.out.println("Some sound");
    }
}

public class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Bark");
    }
}</pre>
<div class="blog_h3"><span class="graybg">Interfaces </span></div>
<p>Kotlin interfaces<span style="background-color: #c0c0c0;"> can contain both abstract methods and method implementations</span>.</p>
<pre class="crayon-plain-tag">interface Movable {
    fun move()
    fun stop() {
        println("Stopped moving")
    }
}

class Vehicle : Movable {
    override fun move() {
        println("Vehicle is moving")
    }
}</pre>
<p>From Java 8,  interfaces can provide default implementations:</p>
<pre class="crayon-plain-tag">public interface Movable {
    void move();
    
    default void stop() {
        System.out.println("Stopped moving");
    }
}

public class Vehicle implements Movable {
    @Override
    public void move() {
        System.out.println("Vehicle is moving");
    }
}</pre>
<div class="blog_h2"><span class="graybg">Multiple Inheritance of Behavior</span></div>
<p>Kotlin allows a class to implement multiple interfaces, and if <span style="background-color: #c0c0c0;">there are conflicts, you must override the conflicting methods</span>.</p>
<pre class="crayon-plain-tag">interface A {
    fun show() {
        println("A")
    }
}

interface B {
    fun show() {
        println("B")
    }
}

class C : A, B {
    override fun show() {
        super<a>.show()
        super<b>.show()
    }
}

val c = C()
c.show()
// Output:
// A
// B
</b></a></pre>
<p>In Java you need to do something similar to solve the conflict:</p>
<pre class="crayon-plain-tag">public interface A {
    default void show() {
        System.out.println("A");
    }
}

public interface B {
    default void show() {
        System.out.println("B");
    }
}

public class C implements A, B {
    @Override
    public void show() {
        A.super.show();
        B.super.show();
    }
}</pre>
<div class="blog_h2"><span class="graybg">Data Classes</span></div>
<p>Data classes in Kotlin are designed to hold data. The compiler automatically generates <pre class="crayon-plain-tag">equals()</pre>, <pre class="crayon-plain-tag">hashCode()</pre>, <pre class="crayon-plain-tag">toString()</pre>, and <pre class="crayon-plain-tag">copy()</pre> methods.</p>
<pre class="crayon-plain-tag">data class User(val name: String, val age: Int)

val user1 = User("Alice", 30)
println(user1) // Output: User(name=Alice, age=30)

val user2 = user1.copy(age = 31)
println(user2) // Output: User(name=Alice, age=31)</pre>
<p>Java Equivalent Prior to Records:</p>
<pre class="crayon-plain-tag">public class User {
    private final String name;
    private final int age;
    
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // Getters, equals(), hashCode(), and toString() methods
    // Need to be manually implemented or generated by the IDE
}</pre>
<p>Java Equivalent with Records ( Java 16+ ):</p>
<pre class="crayon-plain-tag">public record User(String name, int age) {}

User user1 = new User("Alice", 30);
System.out.println(user1); // Output: User[name=Alice, age=30]

User user2 = new User(user1.name(), 31);
System.out.println(user2); // Output: User[name=Alice, age=31]</pre>
<div class="blog_h2"><span class="graybg">Sealed Classes and Interfaces</span></div>
<p>Sealed classes and interfaces restrict the hierarchy to a finite set of subclasses, known at compile time.</p>
<pre class="crayon-plain-tag">sealed class Result

// Declares a data class Success that inherits from Result
data class Success(val data: String) : Result()
data class Error(val exception: Exception) : Result()
// Declares a singleton object Loading that inherits from Result.
object Loading : Result()</pre>
<p>Java 17 introduced sealed classes and interfaces. </p>
<pre class="crayon-plain-tag">public sealed class Result permits Success, Error, Loading {}

public final class Success extends Result {
    private final String data;
    // Constructor, getters, etc.
}

public final class Error extends Result {
    private final Exception exception;
    // Constructor, getters, etc.
}

public final class Loading extends Result {}</pre>
<div class="blog_h2"><span class="graybg">Visibility Modifiers</span></div>
<p>Kotlin offers several visibility modifiers:</p>
<ol style="list-style-type: undefined;">
<li><span style="background-color: #c0c0c0;">public (default): Visible everywhere.</span></li>
<li><span style="background-color: #c0c0c0;">internal: Visible within the same module.</span></li>
<li>protected: Visible to subclasses.</li>
<li>private: Visible within the containing declaration.</li>
</ol>
<p>Example:</p>
<pre class="crayon-plain-tag">class Example {
    private val x = 1
    internal val y = 2
    protected val z = 3
    val w = 4 // Public by default
}</pre>
<p>Java Visibility Modifiers: </p>
<ol style="list-style-type: undefined;">
<li>public: Visible everywhere.</li>
<li>protected: Visible within the package and subclasses.</li>
<li><span style="background-color: #c0c0c0;">Package-private (default): Visible within the package.</span></li>
<li>private: Visible within the class.</li>
</ol>
<div class="blog_h2"><span class="graybg">Nested and Inner Classes</span></div>
<div class="blog_h3"><span class="graybg">Nested Classes</span></div>
<p>In Kotlin, <span style="background-color: #c0c0c0;">a nested class is static by default</span>. It does not hold a reference to the outer class.</p>
<pre class="crayon-plain-tag">class Outer {
    class Nested {
        fun hello() = "Hello from Nested"
    }
}

val message = Outer.Nested().hello()
println(message) // Output: Hello from Nested</pre>
<p>Java static netsted class:</p>
<pre class="crayon-plain-tag">public class Outer {
    public static class Nested {
        public String hello() {
            return "Hello from Nested";
        }
    }
}

String message = new Outer.Nested().hello();
System.out.println(message); // Output: Hello from Nested</pre>
<div class="blog_h3"><span class="graybg">Inner Classes</span></div>
<p>An inner class in Kotlin holds a reference to the outer class and can access its members.</p>
<pre class="crayon-plain-tag">class Outer(val name: String) {
    inner class Inner {
        fun greet() = "Hello from $name's Inner"
    }
}

val outer = Outer("Kotlin")
val message = outer.Inner().greet()
println(message) // Output: Hello from Kotlin's Inner</pre>
<p>Java Inner Class:</p>
<pre class="crayon-plain-tag">public class Outer {
    private String name;

    public Outer(String name) {
        this.name = name;
    }

    public class Inner {
        public String greet() {
            return "Hello from " + name + "'s Inner";
        }
    }
}

Outer outer = new Outer("Java");
String message = outer.new Inner().greet();
System.out.println(message); // Output: Hello from Java's Inner</pre>
<div class="blog_h3"><span class="graybg">Nested/Inner Data Class</span></div>
<p>Data classes can also be nested or inner class:</p>
<pre class="crayon-plain-tag">// Nested
data class Person(val name: String, val age: Int) {
    data class Address(val street: String, val city: String)
}

fun main() {
    val address = Person.Address("Main St", "Springfield")
    val person = Person("John Doe", 30)

    println(person)  // Output: Person(name=John Doe, age=30)
    println(address) // Output: Address(street=Main St, city=Springfield)
}


// Inner
data class Person(val name: String, val age: Int) {
    inner data class Address(val street: String, val city: String) {
        fun getFullAddress(): String {
            return "$name lives at $street, $city"  // Accessing outer class (Person) properties
        }
    }
}

fun main() {
    val person = Person("John Doe", 30)
    val address = person.Address("Main St", "Springfield")

    println(address.getFullAddress())  // Output: John Doe lives at Main St, Springfield
}</pre>
<div class="blog_h2"><span class="graybg">Object Declarations and Companion Objects</span></div>
<div class="blog_h3"><span class="graybg">Singleton Objects</span></div>
<p>Kotlin provides a concise way to create singleton objects using <pre class="crayon-plain-tag">object</pre> declarations.</p>
<pre class="crayon-plain-tag">object Singleton {
    fun greet() = "Hello from Singleton"
}

println(Singleton.greet()) // Output: Hello from Singleton</pre>
<p> In Java, you typically use a class with a private constructor and a static instance.</p>
<pre class="crayon-plain-tag">public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }

    public String greet() {
        return "Hello from Singleton";
    }
}

System.out.println(Singleton.getInstance().greet()); // Output: Hello from Singleton</pre>
<div class="blog_h3"><span class="graybg">Companion Objects</span></div>
<p>In Kotlin, companion objects can hold static members and factory methods.</p>
<pre class="crayon-plain-tag">class MyClass {
    companion object {
        const val CONSTANT = 100
        fun create(): MyClass = MyClass()
    }
}

println(MyClass.CONSTANT) // Output: 100
val instance = MyClass.create()</pre>
<p>Java uses static members and methods.</p>
<pre class="crayon-plain-tag">public class MyClass {
    public static final int CONSTANT = 100;

    public static MyClass create() {
        return new MyClass();
    }
}

System.out.println(MyClass.CONSTANT); // Output: 100
MyClass instance = MyClass.create();</pre>
<div class="blog_h2"><span class="graybg">Delegation</span></div>
<div class="blog_h3"><span class="graybg">Class Delegation</span></div>
<p>Kotlin supports delegation of interface implementation to another object.</p>
<pre class="crayon-plain-tag">interface Printer {
    fun print()
}

class ConsolePrinter : Printer {
    override fun print() {
        println("Printing to console")
    }
}

class PrinterManager(printer: Printer) : Printer by printer

val manager = PrinterManager(ConsolePrinter())
manager.print() // Output: Printing to console</pre>
<p>The syntax <pre class="crayon-plain-tag">Printer by printer</pre>in the above example is called <span style="background-color: #c0c0c0;">delegation in Kotlin. It means that PrinterManager will automatically forward (delegate) all calls to the print() method to the printer object that was passed in</span>.</p>
<p>Java does not have built-in support for class delegation. You would need to implement the methods and delegate calls manually.</p>
<pre class="crayon-plain-tag">public interface Printer {
    void print();
}

public class ConsolePrinter implements Printer {
    @Override
    public void print() {
        System.out.println("Printing to console");
    }
}

public class PrinterManager implements Printer {
    private final Printer printer;

    public PrinterManager(Printer printer) {
        this.printer = printer;
    }

    @Override
    public void print() {
        printer.print();
    }
}

PrinterManager manager = new PrinterManager(new ConsolePrinter());
manager.print(); // Output: Printing to console</pre>
<div class="blog_h3"><span class="graybg">Property Delegation</span></div>
<p>Kotlin also allows delegation of property getters and setters.</p>
<pre class="crayon-plain-tag">import kotlin.properties.Delegates

class User {
    // A mutable property (var) named name of type String
    // Use Kotlin's Delegates.observable delegate to manage the behavior of the name property. 
    //   This function allows you to observe changes to the property and take some action 
    //   whenever the property's value is changed.
    // The initial value of the name property is set to ""
    // The last parameter of Delegates.observable is a
    var name: String by Delegates.observable("") { prop, old, new -&gt;
        println("Property '${prop.name}' changed from '$old' to '$new'")
    }
}

val user = User()
user.name = "Alice" // Output: Property 'name' changed from '' to 'Alice'</pre>
<div class="blog_h2"><span class="graybg">Abstract Classes </span></div>
<p>Abstract classes in Kotlin are declared using the abstract keyword and can contain abstract members that must be implemented by subclasses.</p>
<pre class="crayon-plain-tag">abstract class Vehicle {
    abstract fun drive()
    fun stop() {
        println("Vehicle stopped")
    }
}

class Car : Vehicle() {
    override fun drive() {
        println("Car is driving")
    }
}

val car = Car()
car.drive() // Output: Car is driving
car.stop()  // Output: Vehicle stopped</pre>
<p>Equivalent Java code:</p>
<pre class="crayon-plain-tag">public abstract class Vehicle {
    public abstract void drive();
    
    public void stop() {
        System.out.println("Vehicle stopped");
    }
}

public class Car extends Vehicle {
    @Override
    public void drive() {
        System.out.println("Car is driving");
    }
}

Car car = new Car();
car.drive(); // Output: Car is driving
car.stop();  // Output: Vehicle stopped</pre>
<div class="blog_h2"><span class="graybg">Enum Classes</span></div>
<p>Enum classes represent a fixed set of constants.</p>
<pre class="crayon-plain-tag">enum class Direction {
    NORTH, SOUTH, EAST, WEST
}

val dir = Direction.NORTH
println(dir) // Output: NORTH</pre>
<p>Java Enum Class:</p>
<pre class="crayon-plain-tag">public enum Direction {
    NORTH, SOUTH, EAST, WEST;
}

Direction dir = Direction.NORTH;
System.out.println(dir); // Output: NORTH</pre>
<div class="blog_h2"><span class="graybg">Inline Classes and Value Classes </span></div>
<p>Kotlin introduces inline classes (now known as value classes) to create type-safe wrappers without runtime overhead.</p>
<p>Kotlin Value Class (Kotlin 1.5 and Later):</p>
<pre class="crayon-plain-tag">@JvmInline
value class Email(val address: String)

fun sendEmail(email: Email) {
    // ...
}

val email = Email("test@example.com")
sendEmail(email)</pre>
<p>In ths example above, Email is a wrapper around String but without additional allocation.</p>
<div class="blog_h2"><span class="graybg">Object Equality</span></div>
<p>Kotlin distinguishes between structural equality (==) and referential equality (===).</p>
<ol>
<li><pre class="crayon-plain-tag">a == b</pre> checks if the values are equal (calls equals()).</li>
<li><pre class="crayon-plain-tag">a === b</pre> checks if the references are the same.</li>
</ol>
<p>Java Equivalent:</p>
<ol style="list-style-type: undefined;">
<li><pre class="crayon-plain-tag">a.equals(b)</pre> checks value equality.</li>
<li><pre class="crayon-plain-tag">a == b</pre> checks reference equality.</li>
</ol>
<div class="blog_h1"><span class="graybg">Coroutines</span></div>
<div class="blog_h2"><span class="graybg">Introduction to Coroutines</span></div>
<p>Coroutines are a powerful feature in Kotlin that facilitate asynchronous and non-blocking programming. They are lightweight threads that allow you to write asynchronous code in a sequential and readable manner.</p>
<div class="blog_h3"><span class="graybg">The Problem with Traditional Asynchronous Code</span></div>
<p>In traditional asynchronous programming, especially in Java, handling asynchronous tasks often leads to:</p>
<ol style="list-style-type: undefined;">
<li>Callback Hell: Nested callbacks that make code hard to read and maintain.</li>
<li>Complex Thread Management: Manual handling of threads, synchronization, and locking mechanisms.</li>
</ol>
<div class="blog_h3"><span class="graybg">Kotlin's Solution: Coroutines</span></div>
<p>Kotlin coroutines simplify asynchronous programming by:</p>
<ol>
<li>Suspending Functions: Functions that can suspend execution without blocking the thread.</li>
<li>Structured Concurrency: Managing coroutines in a structured way to avoid leaks and ensure proper cancellation.</li>
</ol>
<div class="blog_h3"><span class="graybg">How Coroutines Differ from Traditional Threading</span></div>
<ol style="list-style-type: undefined;">
<li>Lightweight: Coroutines are much lighter than threads. You can run thousands of coroutines without significant overhead.</li>
<li>Non-blocking: <span style="background-color: #c0c0c0;">Suspending a coroutine doesn't block the underlying thread</span>, allowing other coroutines to run.</li>
<li>Simplified Syntax: Coroutines allow writing asynchronous code sequentially, improving readability.</li>
</ol>
<div class="blog_h2"><span class="graybg">Coroutine Builders</span></div>
<p>Coroutine builders are functions that help you create and start coroutines.</p>
<div class="blog_h3"><span class="graybg">launch</span></div>
<p>Starts a new coroutine without blocking the current thread. It returns a Job that can be used to manage the coroutine.</p>
<pre class="crayon-plain-tag">import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        delay(1000L)
        println("World!")
    }
    println("Hello,")
}
// Output:
// Hello,
// World!</pre>
<div class="blog_h3"><span class="graybg">async</span></div>
<p>Starts a new coroutine and returns a <pre class="crayon-plain-tag">Deferred</pre> result (similar to <pre class="crayon-plain-tag">Future</pre> in Java).</p>
<pre class="crayon-plain-tag">import kotlinx.coroutines.*

fun main() = runBlocking {
    val deferred = async {
        delay(1000L)
        "Result"
    }
    println("Waiting for result...")
    val result = deferred.await()
    println("Result: $result")
}
// Output:
// Waiting for result...
// Result: Result</pre>
<div class="blog_h3"><span class="graybg">runBlocking</span></div>
<p>Bridges the gap between regular blocking code and suspending code. It blocks the current thread until its coroutine completes.</p>
<pre class="crayon-plain-tag">import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Start")
    delay(1000L)
    println("End")
}
// Output:
// Start
// End</pre>
<div class="blog_h2"><span class="graybg">Suspending Functions</span></div>
<p>Functions marked with the suspend keyword that can suspend execution without blocking the thread. This kind of functions can only be called from within a coroutine or another suspending function.</p>
<pre class="crayon-plain-tag">suspend fun fetchData(): String {
    delay(1000L) // Simulate long-running task
    return "Data"
}

fun main() = runBlocking {
    val data = fetchData()
    println("Fetched: $data")
}</pre>
<div class="blog_h2"><span class="graybg">Coroutine Scope and Context</span></div>
<div class="blog_h3"><span class="graybg">Coroutine Scope</span></div>
<p>Coroutine scope defines the lifecycle of coroutines and provides context for them:</p>
<ol>
<li>GlobalScope: Lives for the entire lifetime of the application:
<ol>
<li>Coroutines launched in this scope live for the entire lifetime of the application.</li>
<li>These coroutines are not bound to any specific lifecycle (like an activity or a function) and keep running unless explicitly canceled or when the application terminates.</li>
<li>It should be avoided in most cases because it doesn't allow proper lifecycle management, which could lead to memory leaks or unintended behavior.</li>
</ol>
</li>
<li>CoroutineScope: Custom scope for structured concurrency:
<ol>
<li>It is used to create structured concurrency. This means coroutines launched within this scope are bound to its lifecycle, and if the scope is canceled, all the coroutines within it are also canceled.</li>
<li>CoroutineScope can be manually created, or it can be inherited from a parent scope like in runBlocking.</li>
<li>It is preferred for organizing coroutines so they can be properly canceled, ensuring that resources are not leaked.</li>
</ol>
</li>
</ol>
<p>Example of GlobalScope:</p>
<pre class="crayon-plain-tag">GlobalScope.launch {
    println("Running in GlobalScope")
}</pre>
<p> In this example, the coroutine runs independently of any lifecycle and will only stop when canceled or the application terminates.</p>
<p>Example of CoroutineScope inherited from a parent scope:</p>
<pre class="crayon-plain-tag">fun main() = runBlocking {
    launch { // Inherits parent scope
        println("Coroutine in runBlocking scope")
    }
}</pre>
<p><pre class="crayon-plain-tag">runBlocking</pre> creates a special scope, used mainly in testing or main functions, where the code inside runBlocking is run synchronously.</p>
<p>Example of a custom scope: </p>
<pre class="crayon-plain-tag">class MyClass {
    private val scope = CoroutineScope(Dispatchers.Default)

    fun startTask() {
        scope.launch {
            println("Running in a custom CoroutineScope")
        }
    }

    fun stopTask() {
        scope.cancel()  // Cancels all coroutines in this scope
    }
}</pre>
<div class="blog_h3"><span class="graybg">Coroutine Context</span></div>
<p>A coroutine context contains information like the job, dispatcher, and exception handler.</p>
<p>A dispatcher defines which thread or thread pool a coroutine will be executed on. Dispatchers help distribute and manage tasks efficiently based on their requirements, such as computational intensity, input/output operations, or user interface tasks:</p>
<ol>
<li>Dispatchers.Default:
<ol>
<li>Used for CPU-intensive tasks (e.g., complex calculations, sorting, etc.).</li>
<li>It uses a shared pool of background threads optimized for CPU-bound operations.</li>
</ol>
</li>
<li>Dispatchers.IO:
<ol>
<li>Designed for I/O operations like reading from or writing to files, network requests, or database interactions.</li>
<li>It uses a pool of threads optimized for blocking I/O operations to prevent CPU starvation.</li>
</ol>
</li>
<li>Dispatchers.Main:
<ol>
<li>Used for UI-related work, typically on the main thread of an application (for example, in Android development).</li>
<li>It ensures that tasks interacting with the user interface are executed on the main thread to avoid UI lag or inconsistencies.</li>
</ol>
</li>
</ol>
<p>An example of using a dispatcher:</p>
<pre class="crayon-plain-tag">fun main() = runBlocking {
    launch(Dispatchers.IO) {
        val data = fetchData()
        println("Data: $data")
    }
}</pre>
<div class="blog_h2"><span class="graybg">Structured Concurrency</span></div>
<p>Structured concurrency is a design principle in Kotlin Coroutines that ensures that coroutines are executed within a structured scope, with clear parent-child relationships. This design makes it easier to manage their lifecycle, handle exceptions, and ensure that resources are properly released.</p>
<p>In simple terms, structured concurrency ensures that coroutines have a well-defined lifecycle. The parent coroutine will not complete until all of its child coroutines have finished, and if the parent coroutine is canceled, all of its child coroutines will also be automatically canceled. This structure helps avoid common pitfalls such as leaking coroutines or leaving background tasks running indefinitely.</p>
<div class="blog_h3"><span class="graybg">Key Benefits of Structured Concurrency</span></div>
<ol style="list-style-type: undefined;">
<li>Automatic Cancellation: <span style="background-color: #c0c0c0;">When a parent coroutine is canceled, all its child coroutines are canceled automatically.</span> This prevents coroutines from running longer than necessary and ensures that resources (like memory or network connections) are freed up.</li>
<li>Avoiding Leaked Coroutines: <span style="background-color: #c0c0c0;">Coroutines are tied to a scope, and the lifecycle of that scope dictates the lifetime of the coroutines</span>. This avoids situations where coroutines continue running even after their associated tasks or parent coroutine is no longer needed, preventing resource leaks.</li>
<li>Lifecycle Management:<span style="background-color: #c0c0c0;"> The parent coroutine waits for its child coroutines to finish</span>. This ensures a predictable and manageable lifecycle for the coroutines, avoiding orphaned coroutines that can be hard to trace and manage.</li>
</ol>
<p>Example:</p>
<pre class="crayon-plain-tag">suspend fun fetchData(): String = coroutineScope {
    // Launch two async coroutines to fetch data concurrently
    val data1 = async { /* Simulate fetching data 1 */ "Data1" }
    val data2 = async { /* Simulate fetching data 2 */ "Data2" }

    // Wait for both data to be fetched and concatenate the results
    data1.await() + data2.await()
}</pre>
<p>Explanation:</p>
<ol>
<li>coroutineScope: The coroutineScope function creates a scope that ensures all the coroutines launched within it (like async or launch) are completed before it returns. This ensures that fetchData() will only return when both data1 and data2 have finished fetching their data.</li>
<li>async: The async function launches a new coroutine concurrently to execute a block of code. It's similar to launch, but it returns a Deferred result, which you can await to get the result.</li>
<li>await: This suspends the parent coroutine until the result of the child coroutine (created by async) is available. In this case, the result is the fetched data.</li>
<li>Structured Concurrency in Action:
<ol>
<li>Both data1 and data2 are launched concurrently.</li>
<li>The coroutineScope ensures that the parent coroutine waits for both data1.await() and data2.await() to finish before proceeding.</li>
<li>If an exception occurs in one of the async blocks, or if the parent coroutine is canceled, both data1 and data2 coroutines will be automatically canceled.</li>
</ol>
</li>
</ol>
<div class="blog_h3"><span class="graybg">How Structured Concurrency Works Under the Hood</span></div>
<ol style="list-style-type: undefined;">
<li>Parent-Child Relationship: Every coroutine has <span style="background-color: #c0c0c0;">a parent-child relationship when launched within a scope. The coroutineScope establishes this relationship</span>.</li>
<li>Exception Propagation: <span style="background-color: #c0c0c0;">If a child coroutine fails with an exception, that exception is propagated to its parent</span>, and the entire coroutine scope is canceled unless handled explicitly. This ensures that errors are not silently ignored.</li>
<li>Job Hierarchy: Coroutines form a hierarchy of jobs, with a parent job being responsible for managing the completion of its children. Cancellation of the parent job cascades down to its children.</li>
</ol>
<div class="blog_h2"><span class="graybg">Channels and Flow</span></div>
<p>Both Channels and Flows provide ways to deal with asynchronous streams of data in Kotlin coroutines, but they have different use cases, behavior, and underlying mechanisms. Here's a breakdown of each, followed by the key differences.</p>
<div class="blog_h3"><span class="graybg">Channels</span></div>
<ol>
<li>Channels are similar to queues and provide a way for coroutines to send and receive data.</li>
<li>Channels allow bi-directional communication between producer and consumer coroutines, where one coroutine can send data and another coroutine can receive it.</li>
<li>Channels can be hot, meaning the data is produced whether or not the consumer is actively receiving it.</li>
<li>When a channel is closed (using channel.close()), it signals that no further values will be sent, but the consumer can still receive the remaining values until the channel is empty.</li>
</ol>
<p>Example:</p>
<pre class="crayon-plain-tag">import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel

fun main() = runBlocking {
    val channel = Channel()

    // Launching a coroutine to send data to the channel
    launch {
        for (x in 1..5) channel.send(x * x)  // Sending values (1^2, 2^2, ..., 5^2)
        channel.close()  // Close the channel to indicate no more data
    }

    // Receiving data from the channel
    for (y in channel) {
        println(y)  // Prints 1, 4, 9, 16, 25
    }
}</pre>
<div class="blog_h3"><span class="graybg">Flow</span></div>
<ol>
<li>A Flow is a cold asynchronous data stream that emits values sequentially.</li>
<li>Flows are <span style="background-color: #c0c0c0;">unidirectional and typically represent a one-way stream of data from producer to consumer</span>.</li>
<li>Flows are <span style="background-color: #c0c0c0;">cold, meaning the data is only produced when a consumer starts collecting the flow</span>. <span style="background-color: #c0c0c0;">If no one is collecting the flow, the producer is inactive</span>.</li>
<li>Flows are much more declarative and follow a pattern similar to reactive streams. They emit data using the emit() function and are consumed using collect().</li>
<li>Flow APIs handle backpressure and cancellation transparently.</li>
</ol>
<p>Example:</p>
<pre class="crayon-plain-tag">import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun simpleFlow(): Flow = flow {
    for (i in 1..3) {
        delay(100)  // Simulate asynchronous work
        emit(i)  // Emit values (1, 2, 3)
    }
}

fun main() = runBlocking {
    simpleFlow().collect { value -&gt;
        println(value)  // Prints 1, 2, 3
    }
}</pre>
<div class="blog_h2"><span class="graybg">Comparison with Java Threading</span></div>
<div class="blog_h3"><span class="graybg">Traditional Java Threading</span></div>
<ol>
<li>Threads: Heavyweight, managed by the OS.</li>
<li>Synchronization: Requires explicit handling of synchronization, locks, and potential deadlocks.</li>
<li>Asynchronous APIs: Use of Future, Callable, ExecutorService, and CompletableFuture. </li>
</ol>
<p>Example:</p>
<pre class="crayon-plain-tag">public class ThreadExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -&gt; {
            try {
                Thread.sleep(1000);
                System.out.println("World!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        System.out.println("Hello,");
    }
}
// Output may vary due to thread scheduling:
// Hello,
// World!</pre>
<div class="blog_h3"><span class="graybg">Java's CompletableFuture (Java 8 and Later)</span></div>
<p>CompletableFuture is for building asynchronous computation stages.</p>
<pre class="crayon-plain-tag">import java.util.concurrent.*;

public class CompletableFutureExample {
    public static void main(String[] args) throws Exception {
        CompletableFuture future = CompletableFuture.supplyAsync(() -&gt; {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Result";
        });
        System.out.println("Waiting for result...");
        String result = future.get();
        System.out.println("Result: " + result);
    }
}
// Output:
// Waiting for result...
// Result: Result</pre>
<div class="blog_h2"><span class="graybg">Comparison with Java Virtual Threads</span></div>
<p>Java Virtual Threads are a feature introduced under Project Loom, available as a preview in Java 19 and beyond. Virtual Threads aim to provide lightweight, high-throughput threading by decoupling the notion of a Java thread from an operating system thread. Key features include:</p>
<ol>
<li>Lightweight Threads: Virtual Threads are managed by the JVM rather than the OS, allowing for millions of threads.</li>
<li>Familiar APIs: Use the same java.lang.Thread API, making it easier for developers to adopt.</li>
<li>Better Resource Utilization: Improves scalability and performance for applications with high concurrency needs.</li>
<li>Compatibility: Works with existing Java code and libraries.</li>
</ol>
<p>Example:</p>
<pre class="crayon-plain-tag">public class VirtualThreadExample {
    public static void main(String[] args) throws Exception {
        Thread.startVirtualThread(() -&gt; {
            try {
                Thread.sleep(1000);
                System.out.println("World!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("Hello,");
        Thread.sleep(1500); // Ensure the virtual thread has time to execute
    }
}</pre>
<p>&nbsp;</p>
<div class="blog_h1"><span class="graybg">Generics &amp; Variance<br /></span></div>
<div class="blog_h2"><span class="graybg">Generics </span></div>
<p>Generics are a powerful feature in Kotlin that allow you to write flexible and reusable code. By using generics, you can define classes, methods, and interfaces that work with any type while preserving type safety. Generics enable you to create algorithms or data structures that can handle multiple types without having to write the same logic multiple times for different types.</p>
<div class="blog_h3"><span class="graybg">Basic Syntax of Generics</span></div>
<p>A generic class or function in Kotlin can accept a type parameter, which allows the user to specify the actual type when using that class or function. The type parameter is usually denoted by a single capital letter, commonly T, but you can use any name you like.</p>
<pre class="crayon-plain-tag">// Defining a generic class Box with a type parameter T
class Box(val value: T)

fun main() {
    // Creating a Box that holds an Int
    val intBox: Box = Box(42)
    println(intBox.value)  // Output: 42

    // Creating a Box that holds a String
    val stringBox: Box = Box("Hello")
    println(stringBox.value)  // Output: Hello
}</pre>
<div class="blog_h3"><span class="graybg">Generic Functions</span></div>
<p>Just like classes, functions in Kotlin can also be made generic by introducing type parameters in the function definition. Here's how you can define and use a generic function: </p>
<pre class="crayon-plain-tag">// Defining a generic function that works with any type
fun  printBoxContent(box: Box) {
    println("Box contains: ${box.value}")
}

fun main() {
    val intBox = Box(42)
    val stringBox = Box("Hello")
    
    // Calling the generic function with different types
    printBoxContent(intBox)    // Output: Box contains: 42
    printBoxContent(stringBox)  // Output: Box contains: Hello
}</pre>
<div class="blog_h3"><span class="graybg">Type Constraints in Generics</span></div>
<p>Sometimes, you might want to restrict the types that can be used as generic arguments. Kotlin allows you to apply type constraints to limit the types that can be passed to a generic type parameter.</p>
<p>For example, you can limit a generic type to only accept subtypes of a particular class or implement a specific interface. This is done using the where keyword or inline directly after the type parameter with the <pre class="crayon-plain-tag">:</pre> symbol.</p>
<pre class="crayon-plain-tag">// Defining a generic class with a type constraint
class Container(val value: T)

fun main() {
    val intContainer = Container(42)   // Allowed because Int is a subtype of Number
    val doubleContainer = Container(3.14)  // Allowed because Double is a subtype of Number

    // val stringContainer = Container("Hello")  // Error: String is not a subtype of Number
}</pre>
<div class="blog_h3"><span class="graybg">Type Erasure in Generics</span></div>
<p>Like Java, Kotlin uses type erasure for generics, meaning that the generic type information is only available at compile time and is erased at runtime. This means you cannot directly check the type of a generic parameter at runtime.</p>
<p>For instance, trying to check the type of a generic parameter like this will not work:</p>
<pre class="crayon-plain-tag">fun  checkType(value: T) {
    if (value is List) {
        // Error: Cannot check for List at runtime due to type erasure
    }
}</pre>
<div class="blog_h3"><span class="graybg">Generics with Multiple Type Parameters </span></div>
<p>You can also define classes or functions with multiple generic type parameters, allowing even more flexibility.</p>
<pre class="crayon-plain-tag">// Defining a class with two generic type parameters
class PairBox&lt;T1, T2&gt;(val first: T1, val second: T2)

fun main() {
    val pair = PairBox(1, "One")
    println("First: ${pair.first}, Second: ${pair.second}")  // Output: First: 1, Second: One
}</pre>
<div class="blog_h2"><span class="graybg">Reified Keyword</span></div>
<p>In Kotlin, generics are usually erased at runtime, which means that type information is not available during runtime. However, in some cases, you may need to <span style="background-color: #ffffff;"><span style="background-color: #c0c0c0;">retain the generic type information</span> for certain operations</span>, such as casting or checking the type of an object. This is where the <pre class="crayon-plain-tag">reified</pre> keyword comes into play.</p>
<p>By using the <pre class="crayon-plain-tag">reified</pre> keyword in combination with an <pre class="crayon-plain-tag">inline</pre> function, Kotlin allows you to retain the type information at runtime. Let’s explore how this works:</p>
<div class="blog_h3"><span class="graybg">Usage of Reified Keyword</span></div>
<p>Normally, without <pre class="crayon-plain-tag">reified</pre>, you can’t access the type of generic parameters at runtime because of type erasure. However, using <pre class="crayon-plain-tag">reified</pre>, you can perform type checks and casts directly:</p>
<pre class="crayon-plain-tag">inline fun isTypeOf(value: Any): Boolean {
    return value is T
}

fun main() {
    val result = isTypeOf("Hello")
    println(result) // Output: true
}</pre>
<p>In this example, the <pre class="crayon-plain-tag">isTypeOf</pre> function is an <pre class="crayon-plain-tag">inline</pre> function with a <pre class="crayon-plain-tag">reified</pre> generic type <pre class="crayon-plain-tag">T</pre>. The reified type allows Kotlin to know what type <pre class="crayon-plain-tag">T</pre> is during runtime, which is not possible with regular generics.</p>
<div class="blog_h3"><span class="graybg">Practical Use of Reified</span></div>
<p>One practical use of <pre class="crayon-plain-tag">reified</pre> is to simplify code that involves type casting. Without <pre class="crayon-plain-tag">reified</pre>, you would need to pass the class type explicitly, which makes the code more verbose. Let’s look at the difference:</p>
<p>Without Reified:</p>
<pre class="crayon-plain-tag">fun  getClassName(clazz: Class): String {
    return clazz.simpleName
}

fun main() {
    val className = getClassName(String::class.java)
    println(className) // Output: String
}</pre>
<p>With Reified:</p>
<pre class="crayon-plain-tag">inline fun  getClassName(): String {
    return T::class.java.simpleName
}

fun main() {
    val className = getClassName()
    println(className) // Output: String
}</pre>
<p>As you can see, by using <pre class="crayon-plain-tag">reified</pre>, the function signature becomes simpler, and there is no need to explicitly pass the class type. Kotlin can infer it automatically at runtime.</p>
<div class="blog_h3"><span class="graybg">Limitations of Reified</span></div>
<p>It’s important to note that the <pre class="crayon-plain-tag">reified</pre> keyword can only be used in <pre class="crayon-plain-tag">inline</pre> functions. This is because the type information is only preserved during runtime if the function is inlined. Otherwise, Kotlin would still erase the type information as part of type erasure.</p>
<p>Here’s an attempt to use <pre class="crayon-plain-tag">reified</pre> in a non-inline function, which would cause a compilation error:</p>
<pre class="crayon-plain-tag">fun  getClassName(): String { // Error: Reified type parameter T is not allowed in non-inline functions
    return T::class.java.simpleName
}</pre>
<p>To summarize, <pre class="crayon-plain-tag">reified</pre> provides a powerful way to retain type information at runtime in Kotlin, particularly when dealing with generics. Its most useful applications involve type checks and casts, making the code more concise and easier to read.</p>
<div class="blog_h2"><span class="graybg">Variance</span></div>
<p>In the context of programming languages that support generics (such as Kotlin, Java, Scala, etc.), variance  is a concept that describes how subtyping between more complex types (like generics or parameterized types) relates to subtyping between their components (like their type parameters).</p>
<p>Specifically, variance determines whether one generic type can be considered a subtype or supertype of another generic type based on their type parameters. For example, if you have two types <pre class="crayon-plain-tag">A</pre> and <pre class="crayon-plain-tag">B</pre> where A is a subtype of B, variance answers the question: is <pre class="crayon-plain-tag">Box</pre> a subtype of <pre class="crayon-plain-tag">Box<b></b></pre>?</p>
<div class="blog_h3"><span class="graybg">Covariance</span></div>
<p>The <pre class="crayon-plain-tag">out</pre> keyword in Kotlin indicates covariance (协变). Covariance allows a generic type to preserve the subtype relationship of its type parameters. <span style="background-color: #c0c0c0;">If type A is a subtype of type B, then <pre class="crayon-plain-tag">Box</pre> will be a subtype of <pre class="crayon-plain-tag">Box<b></b></pre> if the generic type is covariant</span>. <span style="background-color: #c0c0c0;">Covariance allows you to read values from a generic type but restricts you from modifying it</span>.</p>
<pre class="crayon-plain-tag">// Define a covariant Box class
class Box(val value: T)

fun main() {
    val intBox: Box = Box(42)
    val anyBox: Box = intBox  // This is allowed due to covariance (Int is a subtype of Any)

    // You can safely read from anyBox
    println(anyBox.value)  // Output: 42

    // However, you cannot modify anyBox because it's covariant
    // anyBox.value = "Hello"  // Error: Val cannot be reassigned
}</pre>
<p>Here, Box is covariant in <pre class="crayon-plain-tag">T</pre>. If <pre class="crayon-plain-tag">Int</pre> is a subtype of <pre class="crayon-plain-tag">Any</pre>, then <pre class="crayon-plain-tag">Box</pre> is considered a subtype of <pre class="crayon-plain-tag">Box</pre>, because you can safely read an <pre class="crayon-plain-tag">Any</pre> from a <pre class="crayon-plain-tag">Box</pre>.</p>
<p>Think of covariant types as producers. For example, a <pre class="crayon-plain-tag">Box</pre> can produce Strings, and since <pre class="crayon-plain-tag">String</pre> is a subtype of <pre class="crayon-plain-tag">Any</pre>, a <pre class="crayon-plain-tag">Box</pre> can also be treated as a <pre class="crayon-plain-tag">Box</pre>(since you can read values of type <pre class="crayon-plain-tag">Any</pre> from it).</p>
<div class="blog_h3"><span class="graybg">Contravariance </span></div>
<p>Contravariance is the opposite of covariance. If type <pre class="crayon-plain-tag">A</pre> is a subtype of type <pre class="crayon-plain-tag">B</pre>, then <pre class="crayon-plain-tag">Box<b></b></pre> will be a subtype of <pre class="crayon-plain-tag">Box</pre>if the generic type is contravariant. Contravariance allows you to write values to a generic type but restricts you from reading them.</p>
<pre class="crayon-plain-tag">// Define a contravariant Box class
class Box {
    fun setValue(value: T) {
        println("Setting value: $value")
    }
}

fun main() {
    val anyBox: Box = Box()
    val stringBox: Box = anyBox  // This is allowed due to contravariance (String is a subtype of Any)

    // You can safely write a String to anyBox, because it's contravariant
    stringBox.setValue("Hello, World!")  // Output: Setting value: Hello, World!

    // However, you cannot read from stringBox because it's contravariant
    // val value: String = stringBox.value  // Error: Cannot read from a contravariant type
}</pre>
<p>In this example, <pre class="crayon-plain-tag">Box</pre> is contravariant. If <pre class="crayon-plain-tag">String</pre> is a subtype of <pre class="crayon-plain-tag">Any</pre>, then <pre class="crayon-plain-tag">Box</pre> is considered a subtype of <pre class="crayon-plain-tag">Box</pre>, because you can safely write a <pre class="crayon-plain-tag">String</pre> into a <pre class="crayon-plain-tag">Box</pre> , but you cannot safely read from it since you don’t know the exact type.</p>
<p>Think of contravariant types as consumers. A <pre class="crayon-plain-tag">Box</pre> can accept any type, so it can accept a <pre class="crayon-plain-tag">String</pre>. Hence, it can be treated as a <pre class="crayon-plain-tag">Box</pre>.</p>
<div class="blog_h3"><span class="graybg">Invariance </span></div>
<p>Invariance means that there is no relationship between the subtyping of the type parameters and the subtyping of the generic types themselves. In other words, if type <pre class="crayon-plain-tag">A</pre> is a subtype of type <pre class="crayon-plain-tag">B</pre>, there is no relationship between <pre class="crayon-plain-tag">Box</pre> and <pre class="crayon-plain-tag">Box<b></b></pre>. They are considered independent.Invariant types are neither covariant nor contravariant; they are strict about the type they accept.</p>
<pre class="crayon-plain-tag">// Define an invariant Box class
class Box(val value: T)

fun main() {
    val intBox: Box = Box(42)
    // val anyBox: Box = intBox  // Error: Type mismatch

    // You can both read and write values, but they must be of the exact type
    val anotherIntBox: Box = Box(100)
    println(anotherIntBox.value)  // Output: 100

    // If we had a setter (not shown here), it would only accept Int
    // anotherIntBox.setValue("Hello")  // Error: Type mismatch
}</pre>
<p>Here, <pre class="crayon-plain-tag">Box</pre> is invariant. You cannot pass a <pre class="crayon-plain-tag">Box</pre> to a function that expects a <pre class="crayon-plain-tag">Box</pre> , even though <pre class="crayon-plain-tag">Int</pre> is a subtype of <pre class="crayon-plain-tag">Any</pre>.</p>
<p>Invariant types are both producers and consumers of their exact type. You can both read and write values, but they must always be of the specific type <pre class="crayon-plain-tag">T</pre>. There is no flexibility in treating it as another type.</p>
<div class="blog_h1"><span class="graybg">Collections and Functional Operations </span></div>
<p>Collections are fundamental data structures that allow developers to store and manipulate groups of objects. Kotlin provides a rich set of collection APIs and functional operations that enhance productivity and code readability. In this section, we'll explore Kotlin's collections, compare them with Java's collection framework, and delve into functional operations that simplify common programming tasks.</p>
<div class="blog_h2"><span class="graybg">Overview of Collections in Kotlin</span></div>
<p> Kotlin collections are divided into two categories:</p>
<ol>
<li>Immutable Collections: Read-only collections that cannot be modified after creation.</li>
<li>Mutable Collections: Collections that can be modified, allowing addition, removal, and updating of elements.</li>
</ol>
<div class="blog_h3"><span class="graybg">Immutable Collections</span></div>
<p>Immutable collections are preferred in functional programming paradigms as they prevent accidental modification and promote thread safety. </p>
<p>Common Immutable Collection Types:</p>
<ol>
<li>List: Ordered collection of elements.</li>
<li>Set: Unordered collection of unique elements.</li>
<li>Map: Collection of key-value pairs.</li>
</ol>
<p>Example:</p>
<pre class="crayon-plain-tag">val numbers: List = listOf(1, 2, 3)</pre>
<div class="blog_h3"><span class="graybg">Mutable Collections </span></div>
<p>Mutable collections can be modified after creation.</p>
<p>Common Mutable Collection Types:</p>
<ol>
<li>MutableList</li>
<li>MutableSet</li>
<li>MutableMap</li>
</ol>
<p>Example:</p>
<pre class="crayon-plain-tag">val mutableNumbers: MutableList = mutableListOf(1, 2, 3)
mutableNumbers.add(4)</pre>
<div class="blog_h3"><span class="graybg">Creating Collections</span></div>
<pre class="crayon-plain-tag">val fruits = listOf("Apple", "Banana", "Cherry")

val mutableFruits = mutableListOf("Apple", "Banana")
mutableFruits.add("Cherry")


val numbers = setOf(1, 2, 3, 2)
println(numbers) // Output: [1, 2, 3]

val mutableNumbers = mutableSetOf(1, 2, 3)
mutableNumbers.add(4)


val countryCodes = mapOf("US" to "United States", "CA" to "Canada")

val mutableCountryCodes = mutableMapOf("US" to "United States")
mutableCountryCodes["CA"] = "Canada"</pre>
<div class="blog_h2"><span class="graybg">Functional Operations on Collections</span></div>
<p>Kotlin provides a plethora of functional operations that make working with collections more expressive and concise. These operations are inspired by functional programming paradigms.</p>
<div class="blog_h3"><span class="graybg">Common Functional Operations</span></div>
<ol style="list-style-type: undefined;">
<li>map: Transforms each element.<br />
<pre class="crayon-plain-tag">val numbers = listOf(1, 2, 3)
val squares = numbers.map { it * it }
println(squares) // Output: [1, 4, 9] </pre>
</li>
<li>filter: Filters elements based on a predicate.<br />
<pre class="crayon-plain-tag">val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers) // Output: [2, 4]</pre>
</li>
<li>reduce: Reduces the collection to a single value.<br />
<pre class="crayon-plain-tag">val numbers = listOf(1, 2, 3, 4)
val sum = numbers.reduce { acc, num -&gt; acc + num }
println(sum) // Output: 10 </pre>
</li>
<li>fold: Similar to reduce but with an initial value.<br />
<pre class="crayon-plain-tag">val numbers = listOf(1, 2, 3, 4)
val product = numbers.fold(1) { acc, num -&gt; acc * num }
println(product) // Output: 24</pre>
</li>
<li>forEach: Performs an action on each element.</li>
<li>groupBy: Groups elements by a key.<br />
<pre class="crayon-plain-tag">data class Person(val name: String, val city: String)

val people = listOf(
    Person("Alice", "New York"),
    Person("Bob", "Paris"),
    Person("Charlie", "New York")
)

val peopleByCity = people.groupBy { it.city }
println(peopleByCity)
// Output:
// {
//   New York=[Person(name=Alice, city=New York), Person(name=Charlie, city=New York)],
//   Paris=[Person(name=Bob, city=Paris)]
// }</pre>
</li>
<li>flatMap: Maps each element to a collection and flattens the results.<br />
<pre class="crayon-plain-tag">val numbers = listOf(1, 2, 3)
val expandedNumbers = numbers.flatMap { listOf(it, it * 10) }
println(expandedNumbers) // Output: [1, 10, 2, 20, 3, 30]</pre>
</li>
<li>partition: Splits the collection into two based on a predicate.</li>
</ol>
<div class="blog_h2"><span class="graybg">Sequences</span></div>
<p>Sequences are lazily evaluated collections in Kotlin. They are useful when working with large datasets or when the operations are computationally intensive.</p>
<p>You can create a sequence from a collection:</p>
<pre class="crayon-plain-tag">val numbers = listOf(1, 2, 3, 4, 5)
val numberSequence = numbers.asSequence()</pre>
<p>Or generate one:</p>
<pre class="crayon-plain-tag">val naturalNumbers = generateSequence(1) { it + 1 }
val firstTenNumbers = naturalNumbers.take(10).toList()
println(firstTenNumbers) // Output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]</pre>
<p>Example of using a sequence:</p>
<pre class="crayon-plain-tag">// Even though we start with a range of 1 to 1,000,000, only the necessary computations
// are performed due to lazy evaluation.
val numbers = (1..1_000_000).asSequence()
    .map { it * 2 }
    .filter { it % 3 == 0 }
    .take(10)
    .toList()
println(numbers) // Output: [6, 12, 18, 24, 30, 36, 42, 48, 54, 60]</pre>
<div class="blog_h3"><span class="graybg">Benefits of Sequences</span></div>
<ol>
<li>Lazy Evaluation: Operations are evaluated as needed, which can improve performance.</li>
<li>Avoids Intermediate Collections: Reduces memory overhead. </li>
</ol>
<div class="blog_h2"><span class="graybg">Collection Builders</span></div>
<p>Kotlin provides collection builders for creating collections in a functional style.</p>
<pre class="crayon-plain-tag">val numbers = buildList {
    add(1)
    add(2)
    addAll(listOf(3, 4, 5))
}
println(numbers) // Output: [1, 2, 3, 4, 5]</pre>
<div class="blog_h2"><span class="graybg">Parallel Processing</span></div>
<p>Kotlin sequences are not inherently parallel. However, Kotlin coroutines can be used for asynchronous and parallel operations. For example:</p>
<pre class="crayon-plain-tag">import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers

fun main() = runBlocking {
    val numbers = (1..5).toList()
    val squares = numbers.map { number -&gt;
        async(Dispatchers.Default) {
            number * number
        }
    }.awaitAll()
    println(squares) // Output: [1, 4, 9, 16, 25]
}</pre>
<div class="blog_h2"><span class="graybg">Comparison with Java Collections</span></div>
<p>Java's collection framework is robust and has evolved over time, especially with the introduction of Streams in Java 8.</p>
<div class="blog_h3"><span class="graybg">Java Collections</span></div>
<ol>
<li>Immutable Collections: Introduced in Java 9 with List.of(), Set.of(), Map.of().</li>
<li>Mutable Collections: The standard ArrayList, HashSet, HashMap, etc.</li>
</ol>
<p>Example of Immutable List in Java:</p>
<pre class="crayon-plain-tag">List numbers = List.of(1, 2, 3);</pre>
<div class="blog_h3"><span class="graybg">Functional Operations in Java</span></div>
<p>Java 8 introduced Streams ( Also lazily loaded ) , which provide functional operations on collections.</p>
<pre class="crayon-plain-tag">List numbers = Arrays.asList(1, 2, 3, 4, 5);
List squares = numbers.stream()
    .map(n -&gt; n * n)
    .collect(Collectors.toList());
System.out.println(squares); // Output: [1, 4, 9, 16, 25]</pre>
<p>Sequences in Kotlin</p>
<ol style="list-style-type: undefined;">
<li>Similar to Java Streams but more integrated into the language.</li>
<li>No Need for Explicit Conversion: Collections can be seamlessly converted to sequences.</li>
</ol>
<p>Streams in Java</p>
<ol style="list-style-type: undefined;">
<li>Separate API: Requires conversion using stream() method.</li>
<li>Terminal Operations: Must perform a terminal operation to execute the stream pipeline.</li>
</ol>
<div class="blog_h1"><span class="graybg">Interop Between Kotlin and Java</span></div>
<p>One of the key strengths of Kotlin is its seamless interoperability with Java. This means you can:</p>
<ol>
<li>Call Kotlin code from Java.</li>
<li>Call Java code from Kotlin.</li>
<li>Use existing Java libraries and frameworks in Kotlin projects.</li>
<li>Migrate codebases incrementally from Java to Kotlin.</li>
</ol>
<p>In this section, we'll explore how Kotlin and Java interoperate, covering various aspects such as calling functions, handling nullability, working with classes, exceptions, and more.</p>
<div class="blog_h2"><span class="graybg">Calling Kotlin from Java </span></div>
<div class="blog_h3"><span class="graybg">Basic Function Calls</span></div>
<p>Kotlin functions can be called from Java code without any special effort. </p>
<p>Kotlin Function:</p>
<pre class="crayon-plain-tag">// File: Utils.kt
package cc.gmem.utils

fun greet(name: String): String {
    return "Hello, $name!"
}</pre>
<p>Calling from Java:</p>
<pre class="crayon-plain-tag">import cc.gmem.utils.UtilsKt;

public class Main {
    public static void main(String[] args) {
        String message = UtilsKt.greet("Alice");
        System.out.println(message); // Output: Hello, Alice!
    }
}</pre>
<p>Explanation:</p>
<ol>
<li>By default, top-level functions in Kotlin are compiled into a class named after the file with the suffix Kt.</li>
<li>In this case, the class is UtilsKt, and the function greet is a static method. </li>
</ol>
<div class="blog_h3"><span class="graybg">Using @JvmName Annotation </span></div>
<p>You can customize the generated class name using the @file:JvmName annotation.</p>
<pre class="crayon-plain-tag">@file:JvmName("UtilFunctions")
package cc.gmem.utils

fun greet(name: String): String {
    return "Hello, $name!"
}</pre>
<p> For the function above you can call it from Java:</p>
<pre class="crayon-plain-tag">import cc.gmem.utils.UtilFunctions;

public class Main {
    public static void main(String[] args) {
        String message = UtilFunctions.greet("Alice");
        System.out.println(message);
    }
}</pre>
<div class="blog_h3"><span class="graybg">Static Methods and Companion Objects</span></div>
<p>Kotlin's companion objects can be used to expose static members to Java.</p>
<pre class="crayon-plain-tag">class MathUtils {
    companion object {
        fun add(a: Int, b: Int): Int {
            return a + b
        }
    }
}</pre>
<p> For the function above you can call it from Java:</p>
<pre class="crayon-plain-tag">public class Main {
    public static void main(String[] args) {
        int sum = MathUtils.Companion.add(5, 3);
        System.out.println(sum); // Output: 8
    }
}</pre>
<div class="blog_h3"><span class="graybg">Using @JvmStatic </span></div>
<p>To make the method appear as a static method in Java, use the @JvmStatic annotation.</p>
<pre class="crayon-plain-tag">class MathUtils {
    companion object {
        @JvmStatic
        fun add(a: Int, b: Int): Int {
            return a + b
        }
    }
}</pre>
<p>Calling from Java:</p>
<pre class="crayon-plain-tag">public class Main {
    public static void main(String[] args) {
        int sum = MathUtils.add(5, 3);
        System.out.println(sum);
    }
}</pre>
<div class="blog_h3"><span class="graybg">Kotlin Properties</span></div>
<p>Kotlin properties are compiled to getter and setter methods in Java.</p>
<pre class="crayon-plain-tag">class Person(var name: String, val age: Int)</pre>
<p>Accessing from Java:</p>
<pre class="crayon-plain-tag">public class Main {
    public static void main(String[] args) {
        Person person = new Person("Alice", 30);
        System.out.println(person.getName()); // Getter for 'name'
        person.setName("Bob"); // Setter for 'name'
        System.out.println(person.getAge()); // Getter for 'age' (no setter since it's 'val')
    }
}</pre>
<div class="blog_h3"><span class="graybg">Handling Nullability</span></div>
<p>Kotlin's null safety affects how types are represented in Java.</p>
<pre class="crayon-plain-tag">fun getName(): String? {
    return null
}</pre><br />
<pre class="crayon-plain-tag">public class Main {
    public static void main(String[] args) {
        String name = UtilsKt.getName();
        if (name != null) {
            System.out.println(name.length());
        } else {
            System.out.println("Name is null");
        }
    }
}</pre>
<div class="blog_h2"><span class="graybg">Calling Java from Kotlin</span></div>
<p>Kotlin can seamlessly use existing Java classes and methods.</p>
<div class="blog_h3"><span class="graybg">Basic Class Usage</span></div>
<pre class="crayon-plain-tag">public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}</pre>
<p>Using in Kotlin:</p>
<pre class="crayon-plain-tag">fun main() {
    val calculator = Calculator()
    val sum = calculator.add(5, 3)
    println(sum) // Output: 8
}</pre>
<div class="blog_h3"><span class="graybg">Static Members </span></div>
<pre class="crayon-plain-tag">public class MathUtils {
    public static int multiply(int a, int b) {
        return a * b;
    }
}</pre>
<p>Using in Kotlin: </p>
<pre class="crayon-plain-tag">fun main() {
    val product = MathUtils.multiply(4, 5)
    println(product) // Output: 20
}</pre>
<div class="blog_h3"><span class="graybg">Nullability and Platform Types </span></div>
<p>When calling Java code from Kotlin, types are treated as platform types, which can be nullable or non-nullable.</p>
<pre class="crayon-plain-tag">public String getName() {
    return null;
}</pre>
<p>Using in Kotlin:</p>
<pre class="crayon-plain-tag">fun main() {
    val name = getName()
    println(name.length) // May throw NullPointerException
    val name1: String? = getName() // Safe: Allows null
}</pre>
<p>Since getName() is a platform type, Kotlin does not enforce null checks. It's up to the developer to handle potential nulls. </p>
<div class="blog_h3"><span class="graybg">Handling Checked Exceptions</span></div>
<p>Kotlin does not have checked exceptions. When calling Java methods that throw checked exceptions, you need to handle them manually.</p>
<pre class="crayon-plain-tag">public void readFile(String path) throws IOException {
    // ...
}</pre>
<p>Using in Kotlin:</p>
<pre class="crayon-plain-tag">fun main() {
    try {
        readFile("file.txt")
    } catch (e: IOException) {
        e.printStackTrace()
    }
}</pre>
<p>Please note: You need to catch the exception explicitly; the compiler does not enforce it.</p>
<div class="blog_h2"><span class="graybg">Java Annotations and Kotlin</span></div>
<div class="blog_h3"><span class="graybg">@NotNull and @Nullable</span></div>
<p>Kotlin recognizes Java's nullability annotations to improve null safety.</p>
<pre class="crayon-plain-tag">public @Nullable String getName() {
    return null;
}</pre>
<p>Using in Kotlin: </p>
<pre class="crayon-plain-tag">fun main() {
    val name = getName()
    if (name != null) {
        println(name.length)
    } else {
        println("Name is null")
    }
}</pre>
<p>Kotlin treats @Nullable types as nullable. </p>
<div class="blog_h3"><span class="graybg">@JvmOverloads</span></div>
<p>Kotlin functions with default parameters can generate overloads for Java using @JvmOverloads.</p>
<pre class="crayon-plain-tag">class Greeter {
    @JvmOverloads
    fun greet(name: String = "Guest") {
        println("Hello, $name!")
    }
}</pre>
<p>Calling from Java:</p>
<pre class="crayon-plain-tag">public class Main {
    public static void main(String[] args) {
        Greeter greeter = new Greeter();
        greeter.greet();          // Calls greet() with default parameter
        greeter.greet("Alice");   // Calls greet(String)
    }
}</pre>
<div class="blog_h3"><span class="graybg">@JvmField</span></div>
<p>By default, Kotlin properties are accessed via getters and setters. To expose a public field directly to Java, use @JvmField.</p>
<pre class="crayon-plain-tag">class Constants {
    @JvmField
    val MAX_COUNT = 100
}</pre>
<p> Accessing from Java:</p>
<pre class="crayon-plain-tag">public class Main {
    public static void main(String[] args) {
        int max = Constants.MAX_COUNT;
        System.out.println(max); // Output: 100
    }
}</pre>
<div class="blog_h2"><span class="graybg">Annotation Processing</span></div>
<p>Kotlin supports Java's annotation processing tools.</p>
<ol>
<li>Using Annotations: You can use annotations like @Entity, @Autowired, etc., in Kotlin classes.</li>
<li>Annotation Processors: Tools like Dagger, Hibernate, and Spring Data work with Kotlin code.</li>
</ol>
<p>Example:</p>
<pre class="crayon-plain-tag">@Entity
data class User(
    @Id val id: Long,
    val name: String
)</pre>
<div class="blog_h2"><span class="graybg">Differences in Language Features </span></div>
<div class="blog_h3"><span class="graybg">SAM Conversions</span></div>
<p>About Single Abstract Method (SAM) Interfaces:</p>
<ol>
<li>In Java, functional interfaces can be implemented using lambda expressions.</li>
<li>Kotlin supports SAM conversions for Java interfaces but not for Kotlin interfaces.</li>
</ol>
<pre class="crayon-plain-tag">public interface Runnable {
    void run();
}</pre>
<p>Using in Kotlin: </p>
<pre class="crayon-plain-tag">fun main() {
    // You can pass a lambda to a Java SAM interface in Kotlin.
    val runnable = Runnable { println("Running") }
    runnable.run()
}</pre>
<div class="blog_h3"><span class="graybg">Kotlin Data Classes in Java</span></div>
<p>Kotlin data classes generate equals(), hashCode(), toString(), and copy() methods.</p>
<p>Kotlin Data Class:</p>
<pre class="crayon-plain-tag">data class User(val name: String, val age: Int)</pre>
<p>Using in Java:</p>
<pre class="crayon-plain-tag">public class Main {
    public static void main(String[] args) {
        User user = new User("Alice", 30);
        System.out.println(user.getName()); // Access properties via getters
        System.out.println(user); // Calls toString()
    }
}</pre>
<p>Plese note that the <pre class="crayon-plain-tag">copy()</pre> function is not directly accessible in Java.</p>
<div class="blog_h3"><span class="graybg">Type Aliases</span></div>
<p>Kotlin's typealias declarations are not visible in Java.</p>
<pre class="crayon-plain-tag">typealias StringMap = Map&lt;String, String&gt;

fun getMap(): StringMap {
    return mapOf("key" to "value")
}</pre>
<p>Java usage:</p>
<pre class="crayon-plain-tag">public class Main {
    public static void main(String[] args) {
        Map&lt;String, String&gt; map = UtilsKt.getMap();
        System.out.println(map);
    }
}</pre>
<div class="blog_h3"><span class="graybg">Extension Functions</span></div>
<p>Kotlin extension functions are not directly accessible from Java.</p>
<p>Kotlin Extension Function:</p>
<pre class="crayon-plain-tag">fun String.isPalindrome(): Boolean {
    return this == this.reversed()
}</pre>
<p>Using in Java:</p>
<pre class="crayon-plain-tag">public class Main {
    public static void main(String[] args) {
        String word = "level";
        // Extension functions are compiled into static methods with the receiver type as the first parameter
        boolean result = StringExtensionsKt.isPalindrome(word);
        System.out.println(result); // Output: true
    }
}</pre>
<div class="blog_h3"><span class="graybg">Coroutines Interoperability</span></div>
<p>Kotlin coroutines can be used from Java code, but it's more complex. </p>
<p>Kotlin Suspended Function:</p>
<pre class="crayon-plain-tag">suspend fun fetchData(): String {
    delay(1000L)
    return "Data"
}</pre>
<p>Using in Java:</p>
<pre class="crayon-plain-tag">public class Main {
    public static void main(String[] args) {
        // Calling suspend functions from Java requires handling Continuation, making it less straightforward.
        Continuation continuation = new Continuation() {
            @Override
            public CoroutineContext getContext() {
                return EmptyCoroutineContext.INSTANCE;
            }

            @Override
            public void resumeWith(Object result) {
                if (result instanceof Result) {
                    Result res = (Result) result;
                    System.out.println(res.getOrNull());
                }
            }
        };

        FetchDataKt.fetchData(continuation);
    }
}</pre>
<div class="blog_h1"><span class="graybg">Practical Applications</span></div>
<p>Having explored various advanced features of Kotlin, including coroutines, delegation, generics and variance, collections, and interoperability with Java, let's look at how these features are applied in real-world scenarios.</p>
<div class="blog_h2"><span class="graybg">Android Development with Kotlin</span></div>
<div class="blog_h3"><span class="graybg">Kotlin as the Preferred Language for Android</span></div>
<p>Kotlin is officially supported by Google as the preferred language for Android app development. It offers concise syntax, null safety, and powerful features that improve developer productivity.</p>
<div class="blog_h3"><span class="graybg">Using Coroutines for Asynchronous Tasks</span></div>
<p>Problem: Android apps often perform operations that can block the main thread, such as network requests or database operations. Blocking the main thread can lead to a poor user experience.</p>
<p>Solution with Coroutines: Use coroutines to perform asynchronous tasks without blocking the main thread.</p>
<p>Example:</p>
<pre class="crayon-plain-tag">class MainActivity : AppCompatActivity() {

    private val viewModel: DataViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Launches a coroutine tied to the lifecycle of the activity.
        lifecycleScope.launch {
            val data = viewModel.fetchData()
            updateUI(data)
        }
    }

    private fun updateUI(data: String) {
        // Update UI elements with the fetched data
    }
}

class DataViewModel : ViewModel() {
    // Switches the coroutine context to a background thread for I/O operations.
    suspend fun fetchData(): String = withContext(Dispatchers.IO) {
        // Simulate network call
        delay(1000)
        "Data from server"
    }
}</pre>
<div class="blog_h3"><span class="graybg">Leveraging Extensions and Delegation </span></div>
<p>Problem: Reducing boilerplate code and improving code organization in Android applications.</p>
<p>Solution with Extensions and Delegation</p>
<ol>
<li>Extensions: Add utility functions to existing classes without inheritance.</li>
<li>Delegation: Use property delegation for shared preferences.</li>
</ol>
<p>Example:</p>
<pre class="crayon-plain-tag">// Extension function for Toast messages
fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, message, duration).show()
}

// Usage in an Activity
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        showToast("Welcome to the app!")
    }
}

// Delegated property for SharedPreferences
class UserPreferences(context: Context) {
    private val prefs: SharedPreferences by lazy {
        context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
    }

    var userName: String?
        get() = prefs.getString("user_name", null)
        set(value) = prefs.edit().putString("user_name", value).apply()
}</pre>
<div class="blog_h3"><span class="graybg">Handling Null Safety and Interoperability</span></div>
<p>Problem: Dealing with null references and integrating Java libraries in Android apps.</p>
<p>Solution:</p>
<ol>
<li>Utilize Kotlin's null safety features to prevent crashes.</li>
<li>Interoperate with Java libraries seamlessly.</li>
</ol>
<p>Example:</p>
<pre class="crayon-plain-tag">// Using null safety
val intentData: String? = intent.getStringExtra("data")
intentData?.let {
    // Use the data safely
}

// Interoperating with a Java library
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val date = dateFormat.parse("2023-10-01")</pre>
<div class="blog_h2"><span class="graybg">Server-Side Development</span></div>
<div class="blog_h3"><span class="graybg">Building RESTful APIs with Ktor</span></div>
<p>Ktor is an asynchronous framework for building microservices and web applications.</p>
<pre class="crayon-plain-tag">import io.ktor.application.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*

fun main() {
    embeddedServer(Netty, port = 8080) {
        routing {
            get("/hello") {
                call.respondText("Hello, World!", ContentType.Text.Plain)
            }
            post("/data") {
                val data = call.receive()
                call.respondText("Received: $data", ContentType.Text.Plain)
            }
        }
    }.start(wait = true)
}</pre>
<div class="blog_h3"><span class="graybg">Database Access with Exposed</span></div>
<pre class="crayon-plain-tag">// Define the table
object Users : Table() {
    val id = integer("id").autoIncrement()
    val name = varchar("name", 50)
    val age = integer("age")
    override val primaryKey = PrimaryKey(id)
}

// Perform database operations
transaction {
    // Insert a new user
    Users.insert {
        it[name] = "Alice"
        it[age] = 30
    }

    // Query users
    val userList = Users.selectAll().map {
        it[Users.name] to it[Users.age]
    }
}</pre>
<div class="blog_h3"><span class="graybg">Concurrency with Coroutines </span></div>
<p>Leverage coroutines for high-throughput server applications. Example:</p>
<pre class="crayon-plain-tag">suspend fun handleRequest(request: Request): Response = coroutineScope {
    val data = async { fetchDataFromDatabase() }
    val computation = async { performComputation() }

    // Wait for both tasks to complete
    Response(data.await(), computation.await())
}</pre>
<div class="blog_h3"><span class="graybg">Testing and Mocking in Kotlin</span></div>
<p>mockk library can be used for mocking:</p>
<pre class="crayon-plain-tag">class UserServiceTest {

    private val repository = mockk()
    private val userService = UserService(repository)

    //  In Kotlin, you can use backticks (``)* to enclose function names, allowing you to include spaces, 
    //  special characters, or even keywords that are normally not allowed in regular function identifiers. 
    @Test
    fun `should return user when found`() = runBlocking {
        val user = User(1, "Alice")
        coEvery { repository.findUser(1) } returns user

        val result = userService.getUser(1)

        assertEquals(user, result)
        coVerify { repository.findUser(1) }
    }
}</pre>
<div class="blog_h2"><span class="graybg">Integrating Kotlin with Spring Framework</span></div>
<div class="blog_h3"><span class="graybg">Build Configuration</span></div>
<p>Gradle (build.gradle.kts)</p>
<pre class="crayon-plain-tag">plugins {
    id("org.springframework.boot") version "3.1.0"
    id("io.spring.dependency-management") version "1.1.0"
    kotlin("jvm") version "1.9.10"
    kotlin("plugin.spring") version "1.9.10"
    kotlin("plugin.jpa") version "1.9.10"
}

group = "cc.gmem"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    // Add other dependencies as needed
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("io.mockk:mockk:1.13.5")
}

tasks.withType {
    useJUnitPlatform()
}</pre>
<div class="blog_h3"><span class="graybg">Creating a RESTful Web Service</span></div>
<p>Application Entry Point:</p>
<pre class="crayon-plain-tag">package cc.gmem.demo

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class DemoApplication

fun main(args: Array) {
    runApplication(*args)
}</pre>
<div class="blog_h3"><span class="graybg">Creating a Controller</span></div>
<pre class="crayon-plain-tag">package cc.gmem.demo.controller

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController

data class Greeting(val id: Long, val content: String)

@RestController
class GreetingController {

    @GetMapping("/greeting/{name}")
    fun greeting(@PathVariable name: String): Greeting {
        return Greeting(id = 1, content = "Hello, $name!")
    }
}</pre>
<div class="blog_h3"><span class="graybg">Running the Application</span></div>
<pre class="crayon-plain-tag">./gradlew bootRun</pre>
<div class="blog_h3"><span class="graybg">Dependency Injection and Bean Configuration</span></div>
<p>Kotlin encourages constructor injection due to its concise syntax.</p>
<pre class="crayon-plain-tag">package cc.gmem.demo.service

import org.springframework.stereotype.Service

@Service
class UserService(private val userRepository: UserRepository) {

    fun findAllUsers(): List = userRepository.findAll()
}</pre>
<div class="blog_h3"><span class="graybg">Database Access with Spring Data JPA </span></div>
<p>Entity definition:</p>
<pre class="crayon-plain-tag">package cc.gmem.demo.model

import jakarta.persistence.*

@Entity
data class User(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    val name: String,
    val email: String
)</pre>
<p>Repository interface:</p>
<pre class="crayon-plain-tag">package cc.gmem.demo.repository

import cc.gmem.demo.model.User
import org.springframework.data.jpa.repository.JpaRepository

interface UserRepository : JpaRepository&lt;User, Long&gt; {
    fun findByEmail(email: String): User?
}</pre>
<div class="blog_h3"><span class="graybg">Using the Repository in a Service</span></div>
<pre class="crayon-plain-tag">package cc.gmem.demo.service

import cc.gmem.demo.model.User
import cc.gmem.demo.repository.UserRepository
import org.springframework.stereotype.Service

@Service
class UserService(private val userRepository: UserRepository) {

    fun getUserByEmail(email: String): User? = userRepository.findByEmail(email)
}</pre>
<div class="blog_h3"><span class="graybg">Controller Endpoint</span></div>
<pre class="crayon-plain-tag">package cc.gmem.demo.controller

import cc.gmem.demo.model.User
import cc.gmem.demo.service.UserService
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/users")
class UserController(private val userService: UserService) {

    @GetMapping("/{email}")
    fun getUser(@PathVariable email: String): User? {
        return userService.getUserByEmail(email)
    }
}</pre>
<div class="blog_h3"><span class="graybg">Defining Beans with @Configuration</span></div>
<pre class="crayon-plain-tag">package cc.gmem.demo.config

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import cc.gmem.demo.service.CustomService

@Configuration
class AppConfig {

    @Bean
    fun customService(): CustomService = CustomService()
}</pre>
<div class="blog_h3"><span class="graybg">Asynchronous Request Handling </span></div>
<p>Spring Framework supports coroutines and reactive programming.</p>
<p>Add kotlinx-coroutines-reactor as a dependency:</p>
<pre class="crayon-plain-tag">implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")</pre>
<p>Using suspend Functions in Controllers:</p>
<pre class="crayon-plain-tag">import kotlinx.coroutines.delay
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class AsyncController {

    @GetMapping("/async")
    suspend fun getAsyncData(): String {
        delay(1000) // Simulate non-blocking delay
        return "Async Response"
    }
}</pre>
<div class="blog_h3"><span class="graybg">Testing Spring Applications in Kotlin</span></div>
<p>Writing Tests with JUnit 5:</p>
<pre class="crayon-plain-tag">package cc.gmem.demo.service

import cc.gmem.demo.model.User
import cc.gmem.demo.repository.UserRepository
import io.mockk.*
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest

@SpringBootTest
class UserServiceTest {

    private val userRepository = mockk()
    private val userService = UserService(userRepository)

    @Test
    fun `should return user when email exists`() {
        val email = "test@example.com"
        val user = User(id = 1, name = "Test User", email = email)

        every { userRepository.findByEmail(email) } returns user

        val result = userService.getUserByEmail(email)

        assertEquals(user, result)
        verify { userRepository.findByEmail(email) }
    }
}</pre>
<div class="blog_h2"><span class="graybg">Spring Reactive in Kotlin</span></div>
<p>Spring WebFlux is the reactive web framework in the Spring ecosystem, designed for building non-blocking, event-driven applications. When combined with Kotlin's coroutines, Spring WebFlux provides an elegant, efficient way to handle asynchronous operations.</p>
<p>In this section, we'll explore how to set up reactive routes in Kotlin using Spring WebFlux and coroutines. We'll focus on declarative routing with <pre class="crayon-plain-tag">coRouter</pre>, handling asynchronous data flows, and writing clean, functional code in Kotlin.</p>
<div class="blog_h3"><span class="graybg">What is Reactive Programming?</span></div>
<p>Reactive programming is a programming paradigm focused on building asynchronous, non-blocking systems that are scalable and resilient. Unlike traditional blocking I/O, reactive programming allows your application to <span style="background-color: #c0c0c0;">handle a large number of requests efficiently by reacting to incoming data streams as they arrive, rather than waiting for blocking operations to complete</span>.</p>
<p>Spring WebFlux is the reactive counterpart of Spring MVC, and it is built on the Project Reactor library, which provides the core reactive API.</p>
<div class="blog_h3"><span class="graybg">Key Concepts in Spring WebFlux</span></div>
<p>Spring WebFlux introduces several new concepts for building reactive applications:</p>
<ul>
<li>Mono - Represents a single asynchronous value or an empty value.</li>
<li>Flux - Represents a stream of asynchronous values, zero or more.</li>
<li>Non-blocking I/O - WebFlux runs on top of a non-blocking I/O framework such as Netty or Undertow.</li>
<li>Coroutines - Kotlin’s coroutines allow us to write asynchronous, non-blocking code in a declarative style.</li>
</ul>
<p>In Kotlin, using coroutines makes reactive programming more intuitive by handling asynchronous tasks in a sequential, readable manner.</p>
<div class="blog_h3"><span class="graybg">Setting up Reactive Routes in Kotlin</span></div>
<p>One of the core features of Spring WebFlux is the ability to define reactive routes using Kotlin DSL with <pre class="crayon-plain-tag">coRouter</pre>. This allows for a clean, functional approach to route definition.</p>
<p>Here’s how you can define reactive routes:</p>
<pre class="crayon-plain-tag">import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.function.server.coRouter

@Configuration
class RoutesConfig {

    @Bean
    fun apiRouter(handler: ApiHandler) = coRouter {
        "/api".nest {
            GET("/items", handler::getAllItems)
            GET("/items/{id}", handler::getItemById)
            POST("/items", handler::createItem)
        }
    }
}</pre>
<p>In this example, the <pre class="crayon-plain-tag">coRouter</pre> function is used to define reactive routes in a Kotlin DSL style:</p>
<ul>
<li>The GET and POST methods define routes that map to specific paths, like <pre class="crayon-plain-tag">/items</pre> and <pre class="crayon-plain-tag">/items/{id}</pre>.</li>
<li><pre class="crayon-plain-tag">handler::getAllItems</pre> refers to handler functions that will process incoming requests asynchronously.</li>
<li><pre class="crayon-plain-tag">nest</pre> is used to group multiple routes under a common path, such as <pre class="crayon-plain-tag">/api</pre>.</li>
</ul>
<p>This approach replaces traditional annotation-based controllers with a more declarative and functional style, making the code more concise and readable.</p>
<div class="blog_h3"><span class="graybg">Kotlin Coroutines in WebFlux</span></div>
<p>Spring WebFlux, when used with Kotlin, leverages coroutines for asynchronous processing. This helps eliminate the complexity of callbacks and makes reactive code look more like traditional blocking code while retaining its non-blocking nature.</p>
<p>For instance, here’s a handler function written with coroutines:</p>
<pre class="crayon-plain-tag">import kotlinx.coroutines.reactor.awaitSingle
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse

class ApiHandler(private val service: ItemService) {

    suspend fun getAllItems(request: ServerRequest): ServerResponse {
        val items = service.getAllItems().collectList().awaitSingle()
        return ServerResponse.ok().bodyValueAndAwait(items)
    }

    suspend fun getItemById(request: ServerRequest): ServerResponse {
        val id = request.pathVariable("id")
        val item = service.getItemById(id).awaitSingleOrNull()
        return item?.let { ServerResponse.ok().bodyValueAndAwait(it) }
            ?: ServerResponse.notFound().buildAndAwait()
    }

    suspend fun createItem(request: ServerRequest): ServerResponse {
        val newItem = request.awaitBody()
        val savedItem = service.saveItem(newItem).awaitSingle()
        return ServerResponse.ok().bodyValueAndAwait(savedItem)
    }
}

// Implementation of ItemService

import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

data class Item(val id: String, val name: String, val price: Double)

interface ItemService {
    fun getAllItems(): Flux&lt;Item&gt;
    fun getItemById(id: String): Mono&lt;Item&gt;
    fun saveItem(item: Item): Mono&lt;Item&gt;
}

class ItemServiceImpl : ItemService {

    private val items = mutableListOf(
        Item("1", "Laptop", 999.99),
        Item("2", "Smartphone", 599.99),
        Item("3", "Tablet", 299.99)
    )

    override fun getAllItems(): Flux&lt;Item&gt; {
        return Flux.fromIterable(items)
    }

    override fun getItemById(id: String): Mono&lt;Item&gt; {
        val item = items.find { it.id == id }
        return if (item != null) {
            Mono.just(item)
        } else {
            Mono.empty()
        }
    }

    override fun saveItem(item: Item): Mono&lt;Item&gt; {
        items.add(item)
        return Mono.just(item)
    }
}</pre>
<p>In this code:</p>
<ul>
<li>awaitSingle() is used to await the result of a <pre class="crayon-plain-tag">Mono</pre> without blocking the thread.</li>
<li>suspend functions are used to declare that these functions will be executed asynchronously in a non-blocking manner using coroutines.</li>
<li>Handlers return a <pre class="crayon-plain-tag">ServerResponse</pre> object, which is a reactive response object in WebFlux.</li>
</ul>
<p>Kotlin’s coroutine support in WebFlux allows you to avoid callback hell and write non-blocking code in a sequential, easy-to-read style.</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/comprehensive-study-kotlin-java-developers">A Comprehensive Study of Kotlin for Java Developers</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/comprehensive-study-kotlin-java-developers/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Jenkins插件开发</title>
		<link>https://blog.gmem.cc/jenkins-plugin-development</link>
		<comments>https://blog.gmem.cc/jenkins-plugin-development#comments</comments>
		<pubDate>Fri, 02 Aug 2019 01:37:16 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=28177</guid>
		<description><![CDATA[<p>插件开发环境（Maven） 通常我们基于Maven来开发Jenkins插件。 添加Jenkins仓库 修改Maven配置文件，添加： [crayon-69d57ac53ea41880642223/] 父POM 插件项目的父POM通常设置为： [crayon-69d57ac53ea46667623971/] 此POM为构件插件提供了合理的缺省配置， 修改属性以定制配置项： [crayon-69d57ac53ea48118745801/] maven-hpi-plugin 这是一个Maven插件，用于构建Jenkins插件。 hpi:create 此目标用于创建一个新的插件项目骨架。目前已经废弃，应当考虑基于原型生成插件项目骨架 hpi:hpi 目标完整名称：org.jenkins-ci.tools:maven-hpi-plugin:3.7:hpi。构建Jenkins插件的WAR包。 此目标的必需参数： 参数 说明 minimumJavaVersion  运行此插件需要的最低Java版本 <a class="read-more" href="https://blog.gmem.cc/jenkins-plugin-development">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/jenkins-plugin-development">Jenkins插件开发</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">插件开发环境（Maven）</span></div>
<p>通常我们基于Maven来开发Jenkins插件。</p>
<div class="blog_h2"><span class="graybg">添加Jenkins仓库</span></div>
<p>修改Maven配置文件，添加：</p>
<pre class="crayon-plain-tag">&lt;profile&gt;
    &lt;id&gt;jenkins&lt;/id&gt;
    &lt;repositories&gt;
        &lt;repository&gt;
            &lt;id&gt;repo.jenkins-ci.org&lt;/id&gt;
            &lt;url&gt;http://repo.jenkins-ci.org/public/&lt;/url&gt;
        &lt;/repository&gt;
    &lt;/repositories&gt;
    &lt;pluginRepositories&gt;
        &lt;pluginRepository&gt;
            &lt;id&gt;repo.jenkins-ci.org&lt;/id&gt;
            &lt;url&gt;http://repo.jenkins-ci.org/public/&lt;/url&gt;
        &lt;/pluginRepository&gt;
    &lt;/pluginRepositories&gt;
&lt;/profile&gt;</pre>
<div class="blog_h2"><span class="graybg">父POM</span></div>
<p>插件项目的父POM通常设置为：</p>
<pre class="crayon-plain-tag">&lt;parent&gt;
    &lt;groupId&gt;org.jenkins-ci.plugins&lt;/groupId&gt;
    &lt;artifactId&gt;plugin&lt;/artifactId&gt;
    &lt;version&gt;3.43&lt;/version&gt;
    &lt;relativePath /&gt;
  &lt;/parent&gt;</pre>
<p>此POM为构件插件提供了合理的缺省配置， 修改属性以定制配置项：</p>
<pre class="crayon-plain-tag">&lt;properties&gt;
    &lt;jenkins.version&gt;2.60.1&lt;/jenkins.version&gt;
    &lt;java.level&gt;8&lt;/java.level&gt;
  &lt;/properties&gt;</pre>
<div class="blog_h2"><span class="graybg">maven-hpi-plugin</span></div>
<p>这是一个Maven插件，用于构建Jenkins插件。</p>
<div class="blog_h3"><span class="graybg">hpi:create</span></div>
<p>此目标用于创建一个新的插件项目骨架。目前已经废弃，应当考虑基于原型生成插件项目骨架</p>
<div class="blog_h3"><span class="graybg">hpi:hpi</span></div>
<p>目标完整名称：org.jenkins-ci.tools:maven-hpi-plugin:3.7:hpi。构建Jenkins插件的WAR包。</p>
<p>此目标的必需参数：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>
<p>minimumJavaVersion </p>
</td>
<td>运行此插件需要的最低Java版本</td>
</tr>
</tbody>
</table>
<p>此目标主要的可选参数：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>compatibleSinceVersion</td>
<td>当前插件版本，在配置上兼容的最低插件版本号</td>
</tr>
<tr>
<td>maskClasses</td>
<td>
<p>空白符分隔的Java包前缀，插件不希望使用Jenkins Core提供的这些类</p>
<p>每个包前缀应当以 . 结尾</p>
</td>
</tr>
<tr>
<td>globalMaskClasses</td>
<td>
<p>类似于maskClasses，但是作用于Jenkins Core和所有插件的边界</p>
<p>主要供哪些提供JavaEE API的插件使用，例如提供JPA API的database插件。其它依赖于database插件的插件，亦可通过container类加载器看到JPA API</p>
</td>
</tr>
<tr>
<td>hpiName</td>
<td>生成的HPI的名称</td>
</tr>
<tr>
<td>pluginFirstClassLoader</td>
<td>设置为true，改变类加载器优先级，让<span style="background-color: #c0c0c0;">插件自带的类优先</span>于插件依赖的其它插件的类加载</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">hpi:hpl</span></div>
<p>类似hpi:hpi，但是打包为Debug布局。</p>
<div class="blog_h3"><span class="graybg">hpi:run</span></div>
<p>目标完整名称：org.jenkins-ci.tools:maven-hpi-plugin:3.7:run。使用当前插件来运行Jenkins。</p>
<p>此目标的必需参数：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>
<p>minimumJavaVersion </p>
</td>
<td>运行此插件需要的最低Java版本</td>
</tr>
</tbody>
</table>
<p>此目标主要的可选参数：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>consoleForceReload</td>
<td>如果设置为true，在输入控制台上输入新行，则Web上下文自动重启</td>
</tr>
<tr>
<td>defaultPort</td>
<td>默认HTTP端口</td>
</tr>
<tr>
<td>hudsonHome<br />jenkinsHome</td>
<td>
<p>$JENKINS_HOME的位置，启动的Jenkins服务器以此目录为工作空间</p>
</td>
</tr>
<tr>
<td>jenkinsCoreId</td>
<td>Jenkins Core 包的groupId:artifactId</td>
</tr>
<tr>
<td>reload</td>
<td>可选值automatic | manual，设置为automatic则文件改变后自动reload，否则输入控制台上输入新行后reload</td>
</tr>
<tr>
<td>maskClasses</td>
<td>参考HPI</td>
</tr>
<tr>
<td>pluginFirstClassLoader</td>
<td>参考HPI </td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">调试插件</span></div>
<p>可以通过mvnDebug进行Jenkins插件的单步跟踪。</p>
<pre class="crayon-plain-tag">mvnDebug hpi:run

# 或者
export MAVEN_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n"
mvn hpi:run</pre>
<p>使用系统属性来调整Jenkins的HTTP端口，以及上下文路径：</p>
<pre class="crayon-plain-tag">mvn hpi:run -Djetty.port=8090  -Dhpi.prefix=/jenkins</pre>
<div class="blog_h1"><span class="graybg">插件开发环境（Gradle）</span></div>
<p>现在可以通过Gradle JPI plugin，基于Gradle开发Jenkins插件。</p>
<div class="blog_h2"><span class="graybg">Gradle配置</span></div>
<div class="blog_h3"><span class="graybg">说明</span></div>
<pre class="crayon-plain-tag">plugins {
  id 'org.jenkins-ci.jpi' version '0.33.0'
}

group = 'org.jenkins-ci.plugins'
version = '1.2.0-SNAPSHOT'
description = 'A description of your plugin'

jenkinsPlugin {
    // 依赖的Jenkins核心版本
    coreVersion = '1.420'

    // 插件的标识符，默认为项目名去掉-plugin后缀
    shortName = 'hello-world'

    // 人类可读的名称
    displayName = 'Hello World plugin built with Gradle'

    // 在Jenkins Wiki上的插件页面
    url = 'http://wiki.jenkins-ci.org/display/JENKINS/SomePluginPage'

    // 可选的插件源码库地址
    gitHubUrl = 'https://github.com/jenkinsci/some-plugin'

    // 是否让插件Classloader优先于核心Classloader加载类
    pluginFirstClassLoader = true

    // 不希望看到的核心的类前缀
    maskClasses = 'groovy.grape org.apache.commons.codec'

    // 当前版本的配置和什么版本之后的兼容
    compatibleSinceVersion = '1.1.0'

    // 开发服务器的工作目录
    workDir = file('/tmp/jenkins')

    // 部署此插件到什么地方
    repoUrl = 'https://repo.jenkins-ci.org/releases'

    // 部署此插件的快照版本到什么地方调试插件
    snapshotRepoUrl = 'https://repo.jenkins-ci.org/snapshots'

    // 是否禁用测试的依赖注入，用于检查Jelly语法等
    disabledTestInjection = false

    // 本地化任务输出目录
    localizerOutputDir = "${project.buildDir}/generated-src/localizer"

    // 是否配置中心仓库、本地仓库、Jenkins中心仓库
    configureRepositories = false

    // 是否配置部署仓库
    configurePublishing = false

    // 插件扩展名，hpi或者jpi
    fileExtension = 'hpi'

    // 开发者列表
    developers {
        developer {
            id 'abayer'
            name 'Andrew Bayer'
            email 'andrew.bayer@gmail.com'
        }
    }

    // License信息
    licenses {
        license {
            name 'Apache License, Version 2.0'
            url 'https://www.apache.org/licenses/LICENSE-2.0.txt'
            distribution 'repo'
            comments 'A business-friendly OSS license'
        }
    }
}

dependencies {
    // 依赖其它插件，目标插件的类在编译期可见
    jenkinsPlugins 'org.jenkinsci.plugins:git:1.1.15'
    // 可选依赖
    optionalJenkinsPlugins 'org.jenkins-ci.plugins:ant:1.2'
    // 测试依赖
    jenkinsTest 'org.jenkins-ci.main:maven-plugin:1.480'
    // 为server任务安装额外的插件
    jenkinsServer 'org.jenkins-ci.plugins:ant:1.2'
}</pre>
<div class="blog_h3"><span class="graybg">实例</span></div>
<pre class="crayon-plain-tag">plugins {
    id 'groovy'
    id "org.jenkins-ci.jpi" version "0.33.0"
}

sourceCompatibility = 1.8

group 'io.jenkins.plugins'
version '1.0-SNAPSHOT'
description = "This plugin provides pipeline steps for ALCM."

repositories {
    maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
    maven { url 'https://repo.jenkins-ci.org/public/' }
    mavenLocal()
}

jenkinsPlugin {
    coreVersion = "2.164.1"
    displayName = "PA ALCM Steps"
    url = "http://wiki.jenkins-ci.org/display/JENKINS/ALCMSteps"
    shortName = "alcm-steps"
    workDir = file('/tmp/jenkins')
    fileExtension = 'jpi'
    pluginFirstClassLoader = true
}

dependencies {
    compile 'org.codehaus.groovy:groovy-all:2.3.11'
    compile 'cc.gmem.yun.alcm:client:1.0-SNAPSHOT'
    compile 'org.jenkins-ci.plugins:structs:1.19'
    testCompile group: 'junit', name: 'junit', version: '4.12'
    jenkinsTest 'org.jenkins-ci.plugins.workflow:workflow-step-api:2.19'
    jenkinsTest 'org.jenkins-ci.plugins.workflow:workflow-cps:2.39'
    jenkinsTest 'org.jenkins-ci.plugins.workflow:workflow-job:2.11.2'
    jenkinsTest 'org.jenkins-ci.plugins.workflow:workflow-basic-steps:2.6'
    jenkinsTest 'org.jenkins-ci.plugins.workflow:workflow-durable-task-step:2.13'
    jenkinsTest 'org.jenkins-ci.plugins.workflow:workflow-api:2.30'
    jenkinsTest 'org.jenkins-ci.plugins.workflow:workflow-support:3.3'
    jenkinsTest 'org.jenkins-ci.plugins:script-security:1.39'
    jenkinsTest 'org.jenkins-ci.plugins:scm-api:2.2.6'
} </pre>
<div class="blog_h2"><span class="graybg">Gradle任务</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">任务</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>gradle jpi</td>
<td>构建出插件</td>
</tr>
<tr>
<td>gradle publishToMavenLocal</td>
<td>构建并安装到本地Maven仓库</td>
</tr>
<tr>
<td>gradle publish</td>
<td>部署到Jenkins的Maven仓库，以便在Update Center可见</td>
</tr>
<tr>
<td>gradle server</td>
<td>启动本地Jenkins实例便于调试：<pre class="crayon-plain-tag">-Djenkins.httpPort=8082</pre> </td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">调试插件</span></div>
<pre class="crayon-plain-tag"># 调试
./gradlew server -Dorg.gradle.debug=true

# 定制端口
/gradlew server -Dorg.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

# 禁用Jelly缓存
./gradlew -Dstapler.jelly.noCache=false server</pre>
<div class="blog_h1"><span class="graybg">插件代码骨架</span></div>
<p>Jenkins提供了一系列插件的原型，我们可以根据基于这些原型生成自己的插件的骨架代码：</p>
<pre class="crayon-plain-tag">mvn -U -P jenkins archetype:generate -Dfilter="io.jenkins.archetypes:"

# 非交互式
mvn archetype:generate  -P jenkins -B -DarchetypeGroupId=io.jenkins.archetypes \
    -DarchetypeArtifactId=hello-world-plugin \
    -DarchetypeVersion=1.5 \
    -DgroupId=cc.gmem.yun.alcm -DartifactId=alcm-steps -Dversion=1.0-SNAPSHOT \
    -Dpackage=cc.gmem.yun.alcm.pipeline.jenkins.steps</pre>
<p>从列表中选择一个合适的原型，然后根据提示填写各项参数即可。</p>
<div class="blog_h2"><span class="graybg">原型列表</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">原型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>io.jenkins.archetypes:empty-plugin</td>
<td>仅仅包含POM和空白的源码树</td>
</tr>
<tr>
<td>io.jenkins.archetypes:global-configuration-plugin</td>
<td>全局配置插件的示例</td>
</tr>
<tr>
<td>io.jenkins.archetypes:hello-world-plugin</td>
<td>包含POM和一个示例的Build Step</td>
</tr>
</tbody>
</table>
<p>本章后续内容均针对基于hello-world-plugin生成的代码骨架。</p>
<div class="blog_h2"><span class="graybg">项目结构</span></div>
<p>从原型生成的插件目录结构如下：</p>
<pre class="crayon-plain-tag">├── alcm-steps.iml
├── pom.xml
├── src
│   ├── main
│   │   ├── java      # 插件源代码
│   │   │   └── cc
│   │   │       └── gmem
│   │   │           └── yun
│   │   │               └── alcm
│   │   │                   └── pipeline
│   │   │                       └── jenkins
│   │   │                           └── steps
│   │   │                               └── HelloWorldBuilder.java # 插件类
│   │   ├── resources  # Jelly/Groovy视图文件
│   │   │   ├── index.jelly  # 插件说明信息 
│   │   │   ├── cc
│   │   │   │   └── gmem
│   │   │   │       └── yun
│   │   │   │           └── alcm
│   │   │   │               └── pipeline
│   │   │   │                   └── jenkins
│   │   │   │                       └── steps
│   │   │   │                           ├── HelloWorldBuilder
│   │   │   │                           │   ├── config.jelly # 插件配置页面模板
│   │   │   │                           │   ├── config.properties # 模板变量值
│   │   │   │                           │   ├── config_zh_CN.properties # 模板变量值（国际化）
│   │   │   │                           │   ├── help-name.html # 字段name的帮助信息
│   │   │   │                           │   ├── help-name_zh_CN.html
│   │   │   │                           │   ├── help-useFrench.html
│   │   │   │                           │   └── help-useFrench_zh_CN.html
│   │   └── webapp  # 插件的静态资源</pre>
<div class="blog_h2"><span class="graybg">配置界面</span></div>
<p>该插件会为Jenkins Job提供一个Step，此Step的配置界面如下：<a href="https://blog.gmem.cc/wp-content/uploads/2019/08/hello-world-plugin-config.png"><img class="aligncenter size-full wp-image-28219" src="https://blog.gmem.cc/wp-content/uploads/2019/08/hello-world-plugin-config.png" alt="hello-world-plugin-config" width="925" height="208" /></a></p>
<p>Name字段的名称为name，点击后面的问号，显示的内容是help-name.html中的。 </p>
<div class="blog_h2"><span class="graybg">代码分析</span></div>
<div class="blog_h3"><span class="graybg">HelloWorldBuilder</span></div>
<p>此类是插件的实现。它继承Builder，实现了SimpleBuildStep。</p>
<p>SimpleBuildStep是诸如Builder、Publisher之类的Step，可以在构建过程的任何时机调用多次。这些Step应当遵循：</p>
<ol>
<li>不去实现BuildStep.prebuild方法，因为此方法假设了一种特定的执行顺序</li>
<li>不去实现BuildStep.getProjectActions方法，因为如果此Step不是项目的静态配置的一部分，则它可能永不会被调用</li>
<li>实现BuildStep.getRequiredMonitorService，且返回BuildStepMonitor.NONE，因为只对仅调用一次的Step有意义</li>
<li>不去实现DependencyDeclarer</li>
<li>不假设Executor.currentExecutor为非空，不使用Computer.currentComputer</li>
</ol>
<p>从抽象类Builder继承的方法如下：</p>
<pre class="crayon-plain-tag">public abstract class Builder extends BuildStepCompatibilityLayer implements Describable&lt;Builder&gt;, ExtensionPoint {
    
    public boolean prebuild(Build build, BuildListener listener) {
        return true;
    }

    // 由于Builder通常不依赖于它前面的步骤的结果，因此默认返回NONE
    public BuildStepMonitor getRequiredMonitorService() {
        return BuildStepMonitor.NONE;
    }

    public Descriptor&lt;Builder&gt; getDescriptor() {
        return Jenkins.getInstance().getDescriptorOrDie(getClass());
    }

    public static DescriptorExtensionList&lt;Builder,Descriptor&lt;Builder&gt;&gt; all() {
        return Jenkins.getInstance().&lt;Builder,Descriptor&lt;Builder&gt;&gt;getDescriptorList(Builder.class);
    }
}</pre>
<p>HelloWorldBuilder是插件的实现，实现插件不需要继承plugin类（这种方式已经废弃），推荐的方式是实现扩展点，并通过@hudson.Extension注解来注册（以便Jenkins自动发现）。</p>
<p>关于这种扩展的方式，需要注意：</p>
<ol>
<li>一般来说，你的插件应该继承已有的扩展点（实现ExtensionPoint的类），并在静态内部类中继承对应的描述符类，这些描述符类是hudson.model.Descriptor的子类</li>
<li>注解@Extension必须放在上述内部类之上，这样Jenkins才能发现扩展</li>
</ol>
<pre class="crayon-plain-tag">package cc.gmem.yun.alcm.pipeline.jenkins.steps;

import hudson.Launcher;
import hudson.Extension;
import hudson.FilePath;
import hudson.util.FormValidation;
import hudson.model.AbstractProject;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.Builder;
import hudson.tasks.BuildStepDescriptor;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;

import javax.servlet.ServletException;
import java.io.IOException;
import jenkins.tasks.SimpleBuildStep;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundSetter;

public class HelloWorldBuilder extends Builder implements SimpleBuildStep {

    private final String name;
    private boolean useFrench;

    // 绑定对象时使用此构造器
    @DataBoundConstructor
    public HelloWorldBuilder(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public boolean isUseFrench() {
        return useFrench;
    }

    @DataBoundSetter
    public void setUseFrench(boolean useFrench) {
        this.useFrench = useFrench;
    }

    /**
     * SimpleBuildStep的核心方法，执行此Step
     * @param run 此Step所属的Build
     * @param workspace 此Build的工作区，用于文件系统操作
     * @param launcher 用于启动进程
     * @param listener 用于发送输出
     * @throws InterruptedException 如果此Step被中断
     * @throws IOException 出现其它错误，更“礼貌”的错误是AbortException
     */
    public void perform(Run&lt;?, ?&gt; run, FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedException, IOException {
        if (useFrench) {
            listener.getLogger().println("Bonjour, " + name + "!");
        } else {
            listener.getLogger().println("Hello, " + name + "!");
        }
    }

    // 此注解为Jenkins扩展定义唯一的标识符，驼峰式大小写，尽量简短。在流水线脚本中引用此Step时即使用此标识符
    // 标识符不需要全局唯一，只需要在一个扩展点内是唯一的即可
    @Symbol("greet")
    // 标记字段、方法或类，以便Huson能自动发现定位到ExtensionPoint的实现
    @Extension
    // Build / Publisher的描述符
    // Descriptor是可配置实例（Describable）的元数据，也作为Describable的工厂
    public static final class DescriptorImpl extends BuildStepDescriptor&lt;Builder&gt; {

        // 校验Project配置表单中的字段
        // 下面的方法检查name字段，参数value注入name的值，可以用@QueryParameter注入其它参数
        // 如果要校验useFrench字段，则需要实现doCheckUsdeFrench方法
        public FormValidation doCheckName(@QueryParameter String value, @QueryParameter boolean useFrench)
                throws IOException, ServletException {
            if (value.length() == 0)
                // 引用资源束
                return FormValidation.error(Messages.HelloWorldBuilder_DescriptorImpl_errors_missingName());
            if (value.length() &lt; 4)
                return FormValidation.warning(Messages.HelloWorldBuilder_DescriptorImpl_warnings_tooShort());
            if (!useFrench &amp;&amp; value.matches(".*[éáàç].*")) {
                return FormValidation.warning(Messages.HelloWorldBuilder_DescriptorImpl_warnings_reallyFrench());
            }
            return FormValidation.ok();
        }

        // 判断当前Step是否能用于目标类型的项目
        @Override
        public boolean isApplicable(Class&lt;? extends AbstractProject&gt; aClass) {
            return true;
        }

        // 此Step的显示名称
        @Override
        public String getDisplayName() {
            return Messages.HelloWorldBuilder_DescriptorImpl_DisplayName();
        }

    }

}</pre>
<div class="blog_h3"><span class="graybg">config.jelly</span></div>
<p>此文件是Project配置页面的模板：</p>
<pre class="crayon-plain-tag">&lt;?jelly escape-by-default='true'?&gt;
&lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"&gt;
    &lt;!-- 文本表单字段，${%引用资源束中的条目 --&gt;
    &lt;f:entry title="${%Name}" field="name"&gt;
        &lt;f:textbox /&gt;
    &lt;/f:entry&gt;
    &lt;f:advanced&gt;
        &lt;f:entry title="${%French}" field="useFrench"
                 description="${%FrenchDescr}"&gt;
            &lt;f:checkbox /&gt;
        &lt;/f:entry&gt;
    &lt;/f:advanced&gt;
&lt;/j:jelly&gt;</pre>
<p>模板使用的资源束为config.properties。 </p>
<div class="blog_h3"><span class="graybg">HelloWorldBuilderTest</span></div>
<p>这个类包含了单元测试：</p>
<pre class="crayon-plain-tag">package cc.gmem.yun.alcm.pipeline.jenkins.steps;

import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Label;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;

public class HelloWorldBuilderTest {

    // 标记此字段为TestRule
    // JUnit的TestRule决定一个或一组测试方法如何运行、如何报告
    @Rule
    public JenkinsRule jenkins = new JenkinsRule();

    final String name = "Bobby";

    @Test
    public void testConfigRoundtrip() throws Exception {
        // 启动一个临时Jenkins服务器，并创建自由项目
        FreeStyleProject project = jenkins.createFreeStyleProject();
        // 添加一个构建步骤
        project.getBuildersList().add(new HelloWorldBuilder(name));
        // 加载配置页面，不经修改的提交
        project = jenkins.configRoundtrip(project);
        // 判断相等
        jenkins.assertEqualDataBoundBeans(new HelloWorldBuilder(name), project.getBuildersList().get(0));
    }

    @Test
    public void testConfigRoundtripFrench() throws Exception {
        FreeStyleProject project = jenkins.createFreeStyleProject();
        HelloWorldBuilder builder = new HelloWorldBuilder(name);
        builder.setUseFrench(true);
        project.getBuildersList().add(builder);
        project = jenkins.configRoundtrip(project);

        HelloWorldBuilder lhs = new HelloWorldBuilder(name);
        lhs.setUseFrench(true);
        jenkins.assertEqualDataBoundBeans(lhs, project.getBuildersList().get(0));
    }

    @Test
    public void testBuild() throws Exception {
        FreeStyleProject project = jenkins.createFreeStyleProject();
        HelloWorldBuilder builder = new HelloWorldBuilder(name);
        project.getBuildersList().add(builder);
        // 执行构建，并断言构建成功
        FreeStyleBuild build = jenkins.buildAndAssertSuccess(project);
        // 断言本次构建的控制台输出包括指定字样
        jenkins.assertLogContains("Hello, " + name, build);
    }

    @Test
    public void testBuildFrench() throws Exception {

        FreeStyleProject project = jenkins.createFreeStyleProject();
        HelloWorldBuilder builder = new HelloWorldBuilder(name);
        builder.setUseFrench(true);
        project.getBuildersList().add(builder);

        FreeStyleBuild build = jenkins.buildAndAssertSuccess(project);
        jenkins.assertLogContains("Bonjour, " + name, build);
    }

    // 在流水线中使用此Step
    @Test
    public void testScriptedPipeline() throws Exception {
        String agentLabel = "my-agent";
        // 在本机创建一个新的Slave（Agent），等待其上线
        jenkins.createOnlineSlave(Label.get(agentLabel));
        // 定义一个流水线任务
        WorkflowJob job = jenkins.createProject(WorkflowJob.class, "test-scripted-pipeline");
        // 流水线定义，注意通过@Symbol指定的标识符来引用本插件定义的Step
        String pipelineScript
                = "node {\n"
                + "  greet '" + name + "'\n"
                + "}";
        // CPS，即continuation passing style，是运行Groovy脚本的一种方式
        // 允许应用程序被随时暂停、重启继续
        job.setDefinition(new CpsFlowDefinition(pipelineScript, true));
        WorkflowRun completedBuild = jenkins.assertBuildStatusSuccess(job.scheduleBuild2(0));
        String expectedString = "Hello, " + name + "!";
        jenkins.assertLogContains(expectedString, completedBuild);
    }

}</pre>
<div class="blog_h1"><span class="graybg">编写插件</span></div>
<div class="blog_h2"><span class="graybg">依赖注入</span></div>
<p>Jenkins插件的依赖注入，可以基于<a href="https://github.com/google/guice">Google Guice</a>（一个轻量级的IoC框架）实现。下面是一个例子：</p>
<pre class="crayon-plain-tag">// 需要被注入的接口及其实现
public interace MySvc {
    public void myServiceMethod()
}

public class MySvcImpl implements MySvc {
   public void myServiceMethod() {
       System.out.println("It works!");
   }
}

// IoC配置类
public class MyGuiceModule extends com.google.inject.AbstractModule {
    @Override
    public void configure() {
        bind(MySvc.class).to(MySvcImpl.class).in(com.google.inject.Singleton.class);
    }
}</pre>
<p>下面的插件类会被注入MySvc：</p>
<pre class="crayon-plain-tag">import com.google.inject.Guice;
import com.google.inject.Inject; 
 
public class MyPublisher extends Publisher {
    private transient MySvc mySvc;

    private String someJobConfig
 
    @DataBoundConstructor
    public MyPublisher(String someJobConfig) {
        this.someJobConfig = someJobConfig;
    }
 
 
    @Inject
    public void setMySvc(MySvc mySvc) {
        this.mySvc = mySvc;
    }
 
 
    @Override
    public boolean perform(AbstractBuild&lt;?, ?&gt; build, Launcher launcher, BuildListener listener) {
        if (mySvc == null) {
            // 进行注入
            Guice.createInjector(new MyModule()).injectMembers(this);
        }
        mySvc.myServiceMethod();
    }
}</pre>
<div class="blog_h2"><span class="graybg">启动进程</span></div>
<p>每当构建时， <pre class="crayon-plain-tag">perform(Build, Launcher, BuildListener)</pre>方法会被调用，你可以利用Launcher来执行任意命令：</p>
<pre class="crayon-plain-tag">launcher.launch(cmd, env, out, workDir)

// 启动进程
launcher.launch("dir", new String[0], listener.getLogger(), build.getProject().getWorkspace());


// 获取返回值
Proc proc = launcher.launch("dir", build.getEnvVars(), listener.getLogger(), build.getProject().getWorkspace());
int exitCode = proc.join();</pre>
<div class="blog_h2"><span class="graybg">分布式调用</span></div>
<p>Jenkins使用一种分布式机制来运行任务 —— <span style="background-color: #c0c0c0;">运行在Master节点上的线程，能够发送闭包到远程机器上，并且在闭包执行完毕后，取回结果</span>。</p>
<p>支持分布式执行的闭包示例如下： </p>
<pre class="crayon-plain-tag">private static class GetSystemProperties extends MasterToSlaveCallable&lt;Properties,RuntimeException&gt; {
    public Properties call() {
        return System.getProperties();
    }
    private static final long serialVersionUID = 1L;
}</pre>
<p>关键之处在于需要实现hudson.remoting.Callable，此接口声明了返回值、异常类型。</p>
<p>下面的调用将闭包发送给Slave执行：</p>
<pre class="crayon-plain-tag">Properties systemProperties = channel.call(new GetSystemProperties());</pre>
<div class="blog_h3"><span class="graybg">隐式远程调用</span></div>
<p>Jenkins提供了一些关键的抽象，将其分布式执行的特性隐藏起来。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">抽象</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>hudson.FilePath</td>
<td>用于代替java.io.File，可以指向位于Master或任何Slave上的目录或文件</td>
</tr>
<tr>
<td>hudson.Launcher</td>
<td>类似于 java.lang.ProcessBuilder，但是可以在远程JVM上启动进程</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">视图开发</span></div>
<div class="blog_h3"><span class="graybg">使用自定义图片</span></div>
<p>你需要把图片放在src/main/webapp下。在jelly或者Java页面上获取图片的方法是：</p>
<pre class="crayon-plain-tag">public String getIconPath() {
  PluginWrapper wrapper = Hudson.getInstance().getPluginManager().getPlugin([YOUR-PLUGIN-MAIN-CLASS].class);
  return Hudson.getInstance().getRootUrl() + "plugin/"+ wrapper.getShortName()+"/";
}</pre>
<div class="blog_h3"><span class="graybg">插入空格</span></div>
<p>要在jelly页面添加空格，只需要： <pre class="crayon-plain-tag">&lt;st:nbsp/&gt;</pre></p>
<div class="blog_h1"><span class="graybg">插件实例</span></div>
<p>本章分析一些真实的Jenkins插件的代码。</p>
<div class="blog_h2"><span class="graybg">Publisher + Recorder</span></div>
<p>Publisher可以在构建完成后，触发任意的动作。Recorder可以为每次构建记录一些统计信息。</p>
<p>Discard Old Build plugin是这种扩展的例子，用于在构建完毕后，检查并删除过期的构建历史。本节分析其代码：</p>
<pre class="crayon-plain-tag">package org.jenkinsci.plugins.discardbuild;

import hudson.Extension;
import hudson.Launcher;
import hudson.model.*;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.util.RunList;
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DiscardBuildPublisher extends Recorder {
    // 插件参数
    /**
     * If not -1, history is only kept up to this days.
     */
    private final int daysToKeep;

    // 从表单绑定插件参数
    @DataBoundConstructor
    public DiscardBuildPublisher( String daysToKeep) {
        this.daysToKeep = parse(daysToKeep);
    }


    private void deleteOldBuildsByDays(AbstractBuild&lt;?, ?&gt; build, BuildListener listener, int daysToKeep) {
        ArrayList&lt;Run&lt;?, ?&gt;&gt; list = updateBuildsList(build, listener);
        try {
            Calendar cal = getCurrentCalendar();
            cal.add(Calendar.DAY_OF_YEAR, -daysToKeep);
            for (Run&lt;?, ?&gt; r : list) {
                if (r.getTimestamp().before(cal)) {
                    discardBuild(r, "it is older than daysToKeep", listener); //$NON-NLS-1$
                }
            }
        } catch (IOException e) {
            e.printStackTrace(listener.error("")); //$NON-NLS-1$
        }
    }


    private ArrayList&lt;Run&lt;?, ?&gt;&gt; updateBuildsList(AbstractBuild&lt;?, ?&gt; build, BuildListener listener) {
        RunList&lt;Run&lt;?, ?&gt;&gt; builds = new RunList&lt;Run&lt;?, ?&gt;&gt;();
        ArrayList&lt;Run&lt;?, ?&gt;&gt; list = new ArrayList&lt;Run&lt;?, ?&gt;&gt;();
        // 获取本次Build对应的Job
        Job&lt;?, ?&gt; job = (Job&lt;?, ?&gt;) build.getParent();
        // 获取Job的所有构建
        builds = (RunList&lt;Run&lt;?, ?&gt;&gt;) job.getBuilds();
        list = discardLastBuilds(build, listener, builds);
        return list;
    }

    @Override
    public boolean perform(AbstractBuild&lt;?, ?&gt; build, Launcher launcher, BuildListener listener) {
        listener.getLogger().println("Discard old builds..."); //$NON-NLS-1$
        deleteOldBuildsByDays(build, listener, daysToKeep);
        return true;
    }

    private void discardBuild(Run&lt;?, ?&gt; history, String reason, BuildListener listener) throws IOException {
        listener.getLogger().printf("#%d is removed because %s\n", history.getNumber(), reason); //$NON-NLS-1$
        history.delete();
    }


    @Override
    public DescriptorImpl getDescriptor() {
        return (DescriptorImpl) super.getDescriptor();
    }

    // 描述符
    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor&lt;Publisher&gt; {

        @SuppressWarnings("rawtypes")
        public boolean isApplicable(Class&lt;? extends AbstractProject&gt; aClass) {
            return true;
        }

        /**
         * This human readable name is used in the configuration screen.
         */
        public String getDisplayName() {
            return Messages.DiscardHistoryBuilder_description();
        }
    }

    public BuildStepMonitor getRequiredMonitorService() {
        return BuildStepMonitor.NONE;
    }
}</pre>
<div class="blog_h2"><span class="graybg">Trigger </span></div>
<p>这个扩展点用于触发某个Job的构建。</p>
<p>Files Found Trigger是这种扩展的例子，能够轮询一个或多个目录，当发现特定的文件后，自动触发构建。本节分析其代码。</p>
<pre class="crayon-plain-tag">package hudson.plugins.filesfoundtrigger;

import static hudson.Util.fixNull;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.kohsuke.stapler.DataBoundConstructor;

import com.google.common.collect.ImmutableList;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
import com.thoughtworks.xstream.mapper.Mapper;

import antlr.ANTLRException;
import hudson.Extension;
import hudson.model.BuildableItem;
import hudson.model.Item;
import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;
import hudson.util.RobustReflectionConverter;

public final class FilesFoundTrigger extends Trigger&lt;BuildableItem&gt; {

  private static final Logger LOGGER = Logger.getLogger(FilesFoundTrigger.class.getName());

  private static final AtomicLong logCounter = new AtomicLong();

  // 从什么Slave节点上检查文件是否存在，如果为空则检查master节点
  private final String node;

  // 检查的目录
  private final String directory;

  // 期望存在的文件的pattern
  private final String files;

  // 忽略的文件的pattern
  private final String ignoredFiles;

  // 仅仅当发现文件数量大于此数字时才触发
  private final String triggerNumber;

  // 额外的文件pattern
  private final ArrayList&lt;FilesFoundTriggerConfig&gt; additionalConfigs;

  /**
   * 构造函数
   * 
   * @param spec
   *          轮询文件系统的Cron表达式
   * @param configs
   *          文件模式列表
   * @throws ANTLRException
   *           无法解析Cron表达式抛出此异常
   */
  @DataBoundConstructor
  public FilesFoundTrigger(String spec, List&lt;FilesFoundTriggerConfig&gt; configs) throws ANTLRException {
    // Trigger天生基于Cron进行检查
    super(spec);

    ArrayList&lt;FilesFoundTriggerConfig&gt; configsCopy = new ArrayList&lt;FilesFoundTriggerConfig&gt;(fixNull(configs));
    FilesFoundTriggerConfig firstConfig;
    if (configsCopy.isEmpty()) {
      firstConfig = new FilesFoundTriggerConfig(null, "", "", "", "1");
    } else {
      firstConfig = configsCopy.remove(0);
    }
    this.node = firstConfig.getNode();
    this.directory = firstConfig.getDirectory();
    this.files = firstConfig.getFiles();
    this.ignoredFiles = firstConfig.getIgnoredFiles();
    this.triggerNumber = firstConfig.getTriggerNumber();
    if (configsCopy.isEmpty()) {
      configsCopy = null;
    }
    this.additionalConfigs = configsCopy;
  }


  public List&lt;FilesFoundTriggerConfig&gt; getConfigs() {
    ImmutableList.Builder&lt;FilesFoundTriggerConfig&gt; builder = ImmutableList.builder();
    builder.add(new FilesFoundTriggerConfig(node, directory, files, ignoredFiles, triggerNumber));
    if (additionalConfigs != null) {
      builder.addAll(additionalConfigs);
    }
    return builder.build();
  }

  // Cron每次触发的逻辑
  @Override
  public void run() {
    long counter = logCounter.incrementAndGet();
    for (FilesFoundTriggerConfig config : getConfigs()) {
      FilesFoundTriggerConfig expandedConfig = config.expand();
      try {
        FileSearch.Result result = FileSearch.perform(expandedConfig);
        int triggerNumber = Integer.parseInt(expandedConfig.getTriggerNumber());
        boolean triggerBuild = result.files.size() &gt;= triggerNumber;
        if (triggerBuild) {
          // 触发构建
          job.scheduleBuild(0, new FilesFoundTriggerCause(expandedConfig));
          return;
        }
      } catch (NumberFormatException e) {
        LOGGER.log(Level.FINE, "{0} - Result: Invalid trigger number (build not triggered)", counter);
      } catch (InterruptedException e) {
        LOGGER.log(Level.FINE, "{0} - Result: Thread interrupted (build not triggered)", counter);
        Thread.currentThread().interrupt();
      }
    }
  }


  // 将当前插件注册为Trigger扩展
  @Extension
  public static final class DescriptorImpl extends TriggerDescriptor {

    @Override
    public boolean isApplicable(Item item) {
      return item instanceof BuildableItem;
    }

    @Override
    public String getDisplayName() {
      return Messages.DisplayName();
    }
  }
}</pre>
<div class="blog_h1"><span class="graybg">单元测试</span></div>
<p>Jenkins提供一系列JUnit周边工具来简化单元测试。支持的特性包括：</p>
<ol>
<li>启动内嵌的Servlet容器，允许通过浏览器访问Jenkins页面，进行测试</li>
<li>HtmlUnit用于简化UI测试</li>
<li>为每个测试用例准备隔离的Jenkins实例</li>
<li>测试代码可以直接访问Jenkins对象模型，并可直接执行一些操作，而不需要通过浏览器</li>
<li>基于注解的、声明式的测试用例环境说明</li>
</ol>
<div class="blog_h2"><span class="graybg">HowTos</span></div>
<div class="blog_h3"><span class="graybg">JenkinsRule</span></div>
<p>下面的例子示例了如何基于JenkinsRule进行单元测试：</p>
<pre class="crayon-plain-tag">import org.jvnet.hudson.test.JenkinsRule;
import org.apache.commons.io.FileUtils;
import hudson.model.*;
import hudson.tasks.Shell;
import org.junit.Test;
import org.junit.Rule;
public class AppTest {
  @Rule public JenkinsRule j = new JenkinsRule();
  @Test public void first() throws Exception {
    // 自由风格项目
    FreeStyleProject project = j.createFreeStyleProject();
    // 添加一个构建步骤
    project.getBuildersList().add(new Shell("echo hello"));
    // 得到某个构建步骤
    Shell shell = project.getBuildersList().get(Shell.class);
    // 调度构建并获得结果
    FreeStyleBuild build = project.scheduleBuild2(0).get();
    System.out.println(build.getDisplayName() + " completed");
    // 读取日志文件内容
    String s = FileUtils.readFileToString(build.getLogFile());
    assertThat(s, contains("+ echo hello"));
  }
}</pre>
<div class="blog_h3"><span class="graybg">Stubbing</span></div>
<p>某些情况下，你希望在不依赖于完整Jenkins实例的前提下进行快速测试，这时可以使用仿冒：</p>
<pre class="crayon-plain-tag">AbstractBuild build = Mockito.mock(AbstractBuild.class);
Mockito.when(build.getResult()).thenReturn(Result.FAILURE);</pre>
<div class="blog_h3"><span class="graybg">HTML抓取</span></div>
<p>用于测试Jenkins生成的HTML页面：</p>
<pre class="crayon-plain-tag">HtmlPage page = j.createWebClient().goTo("computer/test/");
HtmlElement navbar = page.getElementById("left-top-nav");
assertEquals(1,navbar.selectNodes(".//a").size());</pre>
<div class="blog_h3"><span class="graybg">提交表单 </span></div>
<pre class="crayon-plain-tag">HtmlPage configPage = j.createWebClient().goTo("configure");
HtmlForm form = configPage.getFormByName("config");
form.submit((HtmlButton)last(form.getHtmlElementsByTagName("button")));</pre>
<div class="blog_h3"><span class="graybg">设置环境变量</span></div>
<pre class="crayon-plain-tag">public void setEnvironmentVariables() throws IOException {
    EnvironmentVariablesNodeProperty prop = new EnvironmentVariablesNodeProperty();
    EnvVars envVars = prop.getEnvVars();
    envVars.put("sampleEnvVarKey", "sampleEnvVarValue");
    j.jenkins.getGlobalNodeProperties().add(prop);
}</pre>
<div class="blog_h3"><span class="graybg">测试安全性为</span></div>
<p>下面的代码允许任何用户名登陆，只要输入的密码和用户名一致：</p>
<pre class="crayon-plain-tag">j.jenkins.setSecurityRealm(j.createDummySecurityRealm());</pre>
<div class="blog_h3"><span class="graybg">自定义Builder</span></div>
<p>要实现一个一次性的Builder，可以选择继承TestBuilder：</p>
<pre class="crayon-plain-tag">FreeStyleProject project = j.createFreeStyleProject();
project.getBuildersList().add(new TestBuilder() {
    public boolean perform(AbstractBuild&lt;?, ?&gt; build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
        // 向工作区中的文件中写入数据
        build.getWorkspace().child("abc.txt").write("hello","UTF-8");
        return true;
    }
});
 
project.scheduleBuild2(0);</pre>
<div class="blog_h3"><span class="graybg">线程协调</span></div>
<p>Jenkins中构建时异步发生的，如果需要在测试代码中进行同步，可以使用OneShotEvent：</p>
<pre class="crayon-plain-tag">final OneShotEvent buildStarted = new OneShotEvent();
 
FreeStyleProject project = j.createFreeStyleProject();
project.getBuildersList().add(new TestBuilder() {
    public boolean perform(AbstractBuild&lt;?, ?&gt; build, Launcher launcher,
        BuildListener listener) throws InterruptedException, IOException {
        // 构建真正开始后，发起信号
        buildStarted.signal();
        return true;
    }
});
 
project.scheduleBuild2(0);
// 阻塞直到信号到达
buildStarted.block(); </pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">依赖其它插件</span></div>
<p>如果希望使用其它Jenkins插件提供的类，只需要将目标插件声明为Maven依赖即可。很多插件专门提供基础功能，供其它插件依赖，例如<a href="https://wiki.jenkins.io/display/JENKINS/Git+Client+Plugin">Git Client Plugin</a>。</p>
<p>应当尽量依赖插件（而不是自己引入jar依赖），因为hpi打包时，依赖的插件（以及这些插件的传递性依赖）会自动排除，可以<span style="background-color: #c0c0c0;">减小压缩包的大小</span>。</p>
<div class="blog_h2"><span class="graybg">类冲突问题</span></div>
<div class="blog_h3"><span class="graybg">共享库引起的冲突</span></div>
<p>共享库没有ClassLoader隔离机制，Jenkins Core的同名类会优先使用，从而导致冲突。</p>
<p>解决办法是，将存在冲突的部分代码封装到插件中，然后共享库调用插件。</p>
<div class="blog_h3"><span class="graybg">插件引起的冲突</span></div>
<p>插件具有类隔离机制，通过适当的配置即可避免和Jenkins Core的类冲突。但是，在基于JenkinsRule进行测试时，这种类隔离机制不生效，需要修改JenkinsRule的源码。</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/jenkins-plugin-development">Jenkins插件开发</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/jenkins-plugin-development/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>OpenAPI学习笔记</title>
		<link>https://blog.gmem.cc/openapi</link>
		<comments>https://blog.gmem.cc/openapi#comments</comments>
		<pubDate>Fri, 12 Jul 2019 09:08:45 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Go]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Work]]></category>
		<category><![CDATA[OpenAPI]]></category>
		<category><![CDATA[Swagger]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=28075</guid>
		<description><![CDATA[<p>简介 OpenAPI是一套API规范（ OpenAPI Specification ，OAS），用于定义RESTful API的接口。OpenAPI最初来自SmartBear的Swagger规范。 OpenAPI 目前的版本是3.0，当前Swagger和OpenAPI的关系是： OpenAPI是一套规范 Swagger是实现OpenAPI规范的工具集，包括： Swagger Editor：允许你使用YAML语言在浏览器中编写规范，并实时查看生成的API文档 Swagger UI：从OAS兼容的API动态生成一套Web的美观的API文档 Swagger Codegen：用于生成OpenAPI的客户端库（SDK）、服务器桩代码、文档 Swagger Parser：从Java解析Open API定义的独立库 Swagger Core：一个Java库，用于创建、消费、使用OpenAPI定义 Swagger <a class="read-more" href="https://blog.gmem.cc/openapi">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/openapi">OpenAPI学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>OpenAPI是一套API规范（ OpenAPI Specification ，OAS），用于定义RESTful API的接口。OpenAPI<span style="background-color: #c0c0c0;">最初来自SmartBear的Swagger规范</span>。</p>
<p>OpenAPI 目前的版本是3.0，当前Swagger和OpenAPI的关系是：</p>
<ol>
<li>OpenAPI是一套规范</li>
<li>Swagger是实现OpenAPI规范的工具集，包括：
<ol>
<li>Swagger Editor：允许你使用YAML语言在浏览器中编写规范，并实时查看生成的API文档</li>
<li>Swagger UI：从OAS兼容的API动态生成一套Web的美观的API文档</li>
<li>Swagger Codegen：用于生成OpenAPI的客户端库（SDK）、服务器桩代码、文档</li>
<li>Swagger Parser：从Java解析Open API定义的独立库</li>
<li>Swagger Core：一个Java库，用于创建、消费、使用OpenAPI定义</li>
<li>Swagger Inspector：从现有的API生成OpenAPI定义、验证API的测试工具</li>
<li>SwaggerHub：OpenAPI的API设计、文档平台</li>
</ol>
</li>
</ol>
<p>Swagger并非唯一支持OpenAPI的工具，到<a href="https://github.com/OAI/OpenAPI-Specification/blob/master/IMPLEMENTATIONS.md">OpenAPI-Specification</a>的GitHub页面可以看到相关工具的列表。</p>
<div class="blog_h1"><span class="graybg">Swagger 2.0</span></div>
<div class="blog_h2"><span class="graybg">API示例</span></div>
<pre class="crayon-plain-tag">{
  // 基本信息
  "swagger": "2.0",
  "info": {
    "title": "Kubernetes",
    "version": "v1.12.1"
  },
  // API端点列表
  "paths": {
    // 端点
    "/api/": {
      // 操作
      "get": {
        "description": "get available API versions",
        // 支持的MIME类型
        "consumes": [
          "application/json",
          "application/yaml",
          "application/vnd.kubernetes.protobuf"
        ],
        "produces": [
          "application/json",
          "application/yaml",
          "application/vnd.kubernetes.protobuf"
        ],
        // 支持的协议
        "schemes": [
          "https"
        ],
        "tags": [
          "core"
        ],
        // 操作的唯一标识
        "operationId": "getCoreAPIVersions",
        // 请求参数说明
        "parameters": [
          {
            "type": "string",
            "description": "Username ",
            "name": "username",
            // 这是URL路径变量
            "in": "path",
            "required": true
          },
          {
            "name": "user",
            // 这是请求体参数，通常是映射到模型的JSON
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "$ref": "#/definitions/models.User"
            }
          },
          {
            "type": "integer",
            "name": "size",
            // 这是请求参数
            "in": "query"
          }
        ],
        // 响应说明，每个状态码对应一个元素
        "responses": {
          "200": {
            "description": "OK",
            // 响应的Schema
            "schema": {
              "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.APIVersions"
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    }
  }
  // Schema定义列表
  "definitions": {
    // Schema名以域名倒写形式
    "io.k8s.apimachinery.pkg.apis.meta.v1.APIVersions": {
      "description": "APIVersions lists the versions that are available, to allow clients to discover the API at /api, which is the root path of the legacy v1 API.",
      // 必须属性
      "required": [
        "versions",
        "serverAddressByClientCIDRs"
      ],
      // 属性规格列表
      "properties": {
        "apiVersion": {
          "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
          "type": "string"
        },
        "kind": {
          "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
          "type": "string"
        },
        "serverAddressByClientCIDRs": {
          "description": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR"
          }
        },
        "versions": {
          "description": "versions are the api versions that are available.",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      // K8S扩展字段
      "x-kubernetes-group-version-kind": [
        {
          "group": "",
          "kind": "APIVersions",
          "version": "v1"
        }
      ]
    }
  }
}</pre>
<div class="blog_h2"><span class="graybg">转换为OAS3.0</span></div>
<div class="blog_h3"><span class="graybg">swagger2openapi</span></div>
<p>这是一个Node.js开发的工具，可以将Swagger 2.0转换为OAS 3.0，执行下面的命令安装：</p>
<pre class="crayon-plain-tag">npm install -g swagger2openapi</pre>
<p>调用格式：<pre class="crayon-plain-tag">swagger2openapi source-spec.json [options]</pre></p>
<p>常用选项说明：</p>
<pre class="crayon-plain-tag">--resolveInternal    # 是否解析内部引用
--warnProperty       # 警告扩展配置，默认x-s2o-warning
-e, --encoding       # 输入输出编码，默认utf8
-f, --fatal          # 如果引用解析失败，则终止转换
-i, --indent         # JSON缩进，默认4
-o, --outfile        # 输出到文件而非标准输出
-p, --patch          # 尝试修复源定义中的错误
-r, --resolve        # 是否解析外部引用
-t, --targetVersion  # OAS版本，默认3.0.0
-u, --url            # 源的URL
-w, --warnOnly       # 遇到不可修复错误时发出警告而非报错
-y, --yaml           # 输出为YAML格式而非JSON</pre>
<div class="blog_h2"><span class="graybg">API文档生成</span></div>
<p>通过某些工具，可以从既有代码中生成Swagger API文档。</p>
<div class="blog_h3"><span class="graybg">swag</span></div>
<p><a href="https://github.com/swaggo/swag">该工具</a>能够将Go Annotations转换为Swagger 2.0文档，为多种流行的Go Web框架（例如Gin）提供了插件，从而快速和既有Web项目集成。</p>
<p>执行下面的命令安装到GOPATH下：</p>
<pre class="crayon-plain-tag">go get -u github.com/swaggo/swag/cmd/swag</pre>
<p>在项目根目录，使用<pre class="crayon-plain-tag">swag init</pre>命令可以解析Go代码中的注解并且生成docs目录、docs/docs.go。你需要提供General API annotations，如果这些注解没有存放在根目录的main.go文件中，需要用<pre class="crayon-plain-tag">-g</pre>来指定Go文件路径：</p>
<pre class="crayon-plain-tag">swag init -g pkg/route/routers.go</pre>
<p>使用swag init命令之后，你需要导入生成的docs包以及swaggo的另外两个包：</p>
<pre class="crayon-plain-tag">_ "github.com/gmemcc/myproject/docs"
import "github.com/swaggo/gin-swagger" // gin-swagger middleware
import "github.com/swaggo/files" // swagger embed files</pre>
<p>General API annotations可用字段：</p>
<pre class="crayon-plain-tag">// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/

// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io

// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html

// @host localhost:8080
// @BasePath /api/v1
// @query.collection.format multi

// @securityDefinitions.basic BasicAuth

// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization

// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information

// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information

// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information

// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information

// @x-extension-openapi {"example": "value on a json format"}</pre>
<p>示例：</p>
<pre class="crayon-plain-tag">// @BasePath /myproject/apis/v2
// @version 2.0.0
// @title Myproject API
// @description my project
// @contact.name alex
// @contact.email myproject@gmem.cc
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html</pre>
<p>swag init生成的docs包，导出了变量<pre class="crayon-plain-tag">SwaggerInfo</pre>，通过此变量你可以编程式的设置各种字段（和上面这种注释方式等效）：</p>
<pre class="crayon-plain-tag">package main

import (
	"github.com/gin-gonic/gin"
	"github.com/swaggo/files"
	"github.com/swaggo/gin-swagger"
	
	"./docs" // docs is generated by Swag CLI, you have to import it.
)

func main() {
	// programmatically set swagger info
	docs.SwaggerInfo.Title = "Swagger Example API"
	docs.SwaggerInfo.Description = "This is a sample server Petstore server."
	docs.SwaggerInfo.Version = "1.0"
	docs.SwaggerInfo.Host = "petstore.swagger.io"
	docs.SwaggerInfo.BasePath = "/v2"
	docs.SwaggerInfo.Schemes = []string{"http", "https"}</pre>
<p>为了在当前Web服务（的HTTP服务器）中查看API文档，需要注册路由，以gin为例： </p>
<pre class="crayon-plain-tag">r := gin.New()

	// use ginSwagger middleware to serve the API docs
	r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

	r.Run()
}</pre>
<p>要生成API，你需要在控制器代码中添加 API Operation annotations。这些注解需要加在controller代码中，所谓controller代码，就是包含所有路由处理函数的包，以gin为例：</p>
<pre class="crayon-plain-tag">package controller

import (
	"fmt"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/swaggo/swag/example/celler/httputil"
	"github.com/swaggo/swag/example/celler/model"
)

// 下面就是一份API Operation annotations
// ShowAccount godoc
// @Summary Show a account
// @Description get string by ID
// @ID get-string-by-int
// @Accept  json
// @Produce  json
// @Param id path int true "Account ID"
// @Success 200 {object} model.Account
// @Header 200 {string} Token "qwerty"
// @Failure 400,404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Failure default {object} httputil.DefaultError
// 这个仅仅影响生成的Swagger API文档，你还需要调用gin的接口，注册下面这个处理函数（控制器）的路由
// @Router /accounts/{id} [get]
func (c *Controller) ShowAccount(ctx *gin.Context) {
	id := ctx.Param("id")
	aid, err := strconv.Atoi(id)
	if err != nil {
		httputil.NewError(ctx, http.StatusBadRequest, err)
		return
	}
	account, err := model.AccountOne(aid)
	if err != nil {
		httputil.NewError(ctx, http.StatusNotFound, err)
		return
	}
	ctx.JSON(http.StatusOK, account)
}</pre>
<p>这些注解仅仅影响生成的Swagger API文档，不会对Web服务的逻辑产生任何影响。</p>
<p>运行Web服务，可以在http://localhost:port/swagger/index.html查看Swagger 2.0 API文档。访问/swagger/doc.json可以获得JSON格式的API</p>
<div class="blog_h1"><span class="graybg">OAS</span></div>
<p>OAS是REST API的描述格式，在一个OpenAPI文件中，你可以定义完整的接口规格，包括：</p>
<ol>
<li>可用API端点列表，针对每个端点允许的操作</li>
<li>操作的输入、输出参数</li>
<li>身份验证方法</li>
<li>附属信息，包括使用条款、License</li>
</ol>
<p>关于OAS的编写，需要注意：</p>
<ol>
<li>可以用YAML、JSON两种格式编写</li>
<li>所有关键字都是大小写敏感的</li>
</ol>
<div class="blog_h2"><span class="graybg">示例</span></div>
<pre class="crayon-plain-tag"># OpenAPI的版本，可用版本  3.0.0, 3.0.1, 3.0.2
openapi: 3.0.0
info:
  title: '标题'
  description: '描述'
  version: '此OAS的版本，示例0.1.9'

# 提供此API的服务器地址列表
servers:
  - url: http://api.example.com/v1
    description: Optional server description, e.g. Main (production) server
  - url: http://staging-api.example.com
    description: Optional server description, e.g. Internal staging server for testing

# API 端点列表
paths:
  # 端点 /users
  /users:
    # 端点/users的GET方法
    get:
      # 描述性信息
      summary: Returns a list of users.
      description: Optional extended description in CommonMark or HTML.
      # 响应规格说明
      responses:
        # 200响应说明
        '200':    # status code
          description: A JSON array of user names
          # 200响应是JSON格式
          content:
            application/json:
              # JSON的Schema
              schema: 
                # 数组类型
                type: array
                items: 
                  type: string
    # 端点/users的POST方法
    post:
      summary: Creates a user.
      # 请求体
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                username:
                  type: string
      responses: 
        '201':
          description: Created
  # 端点/user/{userId}的POST方法
  # {} 中的是路径变量
  /user/{userId}:
    get:
      summary: Returns a user by ID.
      # 参数说明
      parameters:
        - name: userId
          # 这是一个路径变量
          in: path
          required: true
          description: The ID of the user to return.
          # Schema中可以包含字段的验证规则
          schema:
            type: integer
            format: int64
            minimum: 1
      responses:
        '200':
          description: A user object.
          content:
            application/json:
              schema:
                # 对象类型
                type: object
                # 包含属性声明
                properties:
                  id:
                    type: integer
                    format: int64
                    example: 4
                  name:
                    type: string
                    example: Jessica Smith
        # 异常处理
        '400':
          description: The specified user ID is invalid (not a number).
        '404':
          description: A user with the specified ID was not found.
        default:
          description: Unexpected error</pre>
<div class="blog_h2"><span class="graybg">Schema定义和引用</span></div>
<pre class="crayon-plain-tag">components:
  # 定义了一个名为User的Schema
  schemas:
    User:
      properties:
        id:
          type: integer
        name:
          type: string
      # User包含两个属性，都是必须属性
      required:  
        - id
        - name</pre>
<p>下面的API引用上述Schema：</p>
<pre class="crayon-plain-tag">paths:
  /users/{userId}:
    get:
      summary: Returns a user by ID.
      parameters:
        - in: path
          name: userId
          required: true
          type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                # 引用Schema
                $ref: '#/components/schemas/User'</pre>
<div class="blog_h2"><span class="graybg">API服务器和Base URL</span></div>
<p>OAS中的所有API端点，相对于Base URL，假设Base URL为https://api.example.com/v1则端点/users的访问路径为https://api.example.com/v1/users。</p>
<p>Open API 3.0允许在servers数组中声明多个Base URL。</p>
<div class="blog_h3"><span class="graybg">服务器URL模板</span></div>
<p>API服务器的URL中，可以包含变量：</p>
<pre class="crayon-plain-tag">- url: https://{customerId}.saas-app.com:{port}/v2
    variables:
      customerId:
        default: demo
        description: Customer ID assigned by the service provider
      port:
        enum:
          - '443'
          - '8443'
        default: '443'</pre>
<div class="blog_h3"><span class="graybg">覆盖服务器URL</span></div>
<p>你可以在路径级别，甚至操作（方法） 级别，覆盖servers数组：</p>
<pre class="crayon-plain-tag">/ping:
    get:
      servers:
        - url: https://echo.example.com
          description: Override base path for the GET /ping operation</pre>
<div class="blog_h3"><span class="graybg">服务器相对URL </span></div>
<p>servers数组中的URL可以是相对URL，这种情况下，URL相对于提供OpenAPI定义的Web服务器。</p>
<p>举例来说，如果Open API定义存放在http://localhost:3001/openapi.yaml，则servers元素/v2解析为http://localhost:3001/v2 </p>
<div class="blog_h2"><span class="graybg">媒体类型</span></div>
<p>请求或者响应的媒体类型，在content元素下声明：</p>
<pre class="crayon-plain-tag"># 200响应，媒体类型为JSON
paths:
  /employees:
    get:
      summary: Returns a list of employees.
      responses:
        '200':      # Response
          description: OK
          content:  # Response body
            application/json:  # Media type
              schema:          # Must-have
                type: object   # Data type</pre>
<div class="blog_h3"><span class="graybg">多种媒体类型</span></div>
<p>可以指定多种媒体类型：</p>
<pre class="crayon-plain-tag">paths:
  /employees:
    get:
      responses:
        '200':
          description: OK
          content:
            # JSON
            application/json:
             schema: 
               $ref: '#/components/schemas/Employee'
            # XML
            application/xml:
             schema: 
               $ref: '#/components/schemas/Employee'</pre>
<div class="blog_h3"><span class="graybg">通配媒体类型 </span></div>
<p>如果需要为多种媒体类型指定相同的Schema，可以使用<pre class="crayon-plain-tag">*/*</pre>或者<pre class="crayon-plain-tag">application/*</pre>这样的通配符： </p>
<pre class="crayon-plain-tag">paths:
  /info/logo:
    get:
      responses:
        '200':
          description: OK
          content:
            image/*:
             schema: 
               type: string
               format: binary</pre>
<div class="blog_h2"><span class="graybg">路径和操作</span></div>
<p>在OAS中，路径即端点（资源），操作即HTTP方法。</p>
<div class="blog_h3"><span class="graybg">路径</span></div>
<p>路径可以包括路径变量，例如：</p>
<pre class="crayon-plain-tag">/users/{id}
/organizations/{orgId}/members/{memberId}
/report.{format}</pre>
<p>路径的description支持多行文本，还允许使用Markdown语法。</p>
<div class="blog_h3"><span class="graybg">操作</span></div>
<p>OpenAPI 3.0支持的HTTP方法包括：get, post, put, patch, delete, head, options,  trace。每个路径可以支持多个操作。</p>
<p>OpenAPI 3.0支持通过路径、查询字符串、请求头、Cookie传递参数。对于POST/PUT/PATCH请求，还可以通过请求体传递数据。</p>
<div class="blog_h3"><span class="graybg">查询字符串参数</span></div>
<p>这种参数不得定义在路径中，下面是错误的用法：</p>
<pre class="crayon-plain-tag">paths:
  /users?role={role}:</pre>
<p>下面则是正确的用法：</p>
<pre class="crayon-plain-tag">paths:
  /users:
    get:
      parameters:
        - in: query
          name: role
          schema:
            type: string
            enum: [user, poweruser, admin]
          required: true</pre>
<div class="blog_h3"><span class="graybg">operationId</span></div>
<p>你可以为操作指定一个标识符：</p>
<pre class="crayon-plain-tag">/users:
  get:
    operationId: getUsers</pre>
<p>此标识符必须在OAS中是唯一的。 operationId的使用场景包括：</p>
<ol>
<li>某些代码生成器使用operationId生成对应的方法名</li>
</ol>
<div class="blog_h2"><span class="graybg">描述参数</span></div>
<p>参数需要定义在operation或path的parameters字段下。每个参数包括名字、位置、数据类型（通过schema或content定义）等属性。</p>
<div class="blog_h3"><span class="graybg">参数位置</span></div>
<p>参数可以通过以下HTTP元素传递：</p>
<ol>
<li>路径参数</li>
<li>查询参数</li>
<li>请求头参数</li>
<li>Cookie参数 </li>
</ol>
<div class="blog_h3"><span class="graybg">路径参数</span></div>
<pre class="crayon-plain-tag">/users/{id}:
    get:
      parameters:
        - in: path
          # 必须和路径变量{}中的一样
          name: id   
          required: true
          schema:
            type: integer
            minimum: 1
          description: The user ID</pre>
<p>路径参数可以是数组，或者对象。这种参数在URL中的串行化形式可以是：</p>
<ol>
<li>路径风格展开，分号分隔，示例：<pre class="crayon-plain-tag">/map/point;x=50;y=20</pre></li>
<li>标签展开，点号前缀，示例：<pre class="crayon-plain-tag">/color.R=100.G=200.B=150</pre></li>
<li>简单形式，逗号分隔，示例：<pre class="crayon-plain-tag">/users/12,34,56</pre></li>
</ol>
<p>具体使用哪种串行化风格，通过style、explode关键字指定。</p>
<div class="blog_h3"><span class="graybg">查询参数</span></div>
<p>这是最常见的参数形式，出现在URL的?后面。</p>
<pre class="crayon-plain-tag">parameters:
        - in: query
          name: offset
          schema:
            type: integer
        - in: query
          name: limit
          schema:
            type: integer</pre>
<p>RFC 3986规定<pre class="crayon-plain-tag">:/?#[]@!$&amp;'()*+,;=</pre>为URI特殊字符。如果查询参数中包含这些字符，必须以%HEX形式编码。</p>
<div class="blog_h3"><span class="graybg">请求头参数</span></div>
<pre class="crayon-plain-tag">paths:
  /ping:
    get:
      summary: Checks if the server is alive
      parameters:
        - in: header
          name: X-Request-ID
          schema:
            type: string
            format: uuid
          required: true</pre>
<div class="blog_h3"><span class="graybg">Cookie参数 </span></div>
<pre class="crayon-plain-tag">parameters:
        - in: cookie
          name: debug
          schema:
            type: integer
            enum: [0, 1]
            default: 0
        - in: cookie
          name: csrftoken
          schema:
            type: string</pre>
<div class="blog_h3"><span class="graybg">参数默认值</span></div>
<p>使用default关键字可以为optional参数提供默认值：</p>
<pre class="crayon-plain-tag">parameters:
  - in: query
    name: offset
    schema:
      type: integer
      minimum: 0
      default: 0
    required: false </pre>
<div class="blog_h3"><span class="graybg">schema和content</span></div>
<p>要描述复杂参数的内容，可以使用schema或者content。这两个关键字是互斥的，用于不同的场景下。</p>
<p>大部分情况下，可以考虑使用schema，使用schema可以描述原始类型、串行化为字符串的对象、简单数组。对象、数组的串行化方法在style、explode关键字中定义：</p>
<pre class="crayon-plain-tag">- in: query
  name: color
  schema:
    type: array
    items:
      type: string
  # 串行化为 color=blue,black,brown 形式
  style: form
  explode: false</pre>
<p>style、explode无法满足的复杂串行化需求，可以使用content：</p>
<pre class="crayon-plain-tag">parameters:
  - in: query
    name: filter
    content:
      # 细粒度的控制如何串行化为JSON
      application/json:
        schema:
          type: object
          properties:
            type:
              type: string
            color:
              type: string</pre>
<div class="blog_h3"><span class="graybg">枚举类型参数</span></div>
<p>可以为某个参数指定可选值的集合，这些值的类型必须和参数类型匹配：</p>
<pre class="crayon-plain-tag">parameters:
  - in: query
    name: status
    schema:
      type: string
      enum:
        - available
        - pending
        - sold</pre>
<p><span class="graybg">常量参数 —— 仅仅包含一个枚举值的参数</span>：</p>
<pre class="crayon-plain-tag">parameters:
  - in: query
    name: rel_date
    required: true
    schema:
      type: string
      enum:
        - now</pre>
<div class="blog_h3"><span class="graybg">可空参数</span></div>
<p>OpenAPI 3.0允许仅仅有名字，而没有值的参数，在URL中表现为<pre class="crayon-plain-tag">/path?parm</pre>的形式：</p>
<pre class="crayon-plain-tag">parameters:
  - in: query
    name: metadata
    schema:
      type: boolean
    allowEmptyValue: true</pre>
<p>你也可以在schema中声明nullable属性：</p>
<pre class="crayon-plain-tag">schema:
  type: integer
  format: int32
  nullable: true</pre>
<div class="blog_h3"><span class="graybg">废弃参数 </span></div>
<p>可以将一个参数标记为已经废弃：</p>
<pre class="crayon-plain-tag">- in: query
  name: format
  required: true
  schema:
    type: string
    enum: [json, xml, yaml]
  deprecated: true</pre>
<div class="blog_h3"><span class="graybg">公共参数</span></div>
<p>所谓公共参数，即一个path下，所有方法都使用的参数定义：</p>
<pre class="crayon-plain-tag">/user/{id}:
  parameters:
    - in: path
      name: id
      schema:
        type: integer
      required: true
      description: The user ID
  get:
    ...
  patch:
    ...
  delete:</pre>
<div class="blog_h2"><span class="graybg">身份验证和授权</span></div>
<p>OpenAPI支持以多种身份验证方式保护API：</p>
<ol>
<li>基于Authorization头的身份认证：
<ol>
<li>不记名令牌（Bearer）</li>
<li>基本认证</li>
</ol>
</li>
<li>请求头、Cookie、查询字符串中的API Key</li>
<li>OAuth2</li>
<li>OpenID连接发现</li>
</ol>
<div class="blog_h3"><span class="graybg">定义securitySchemes</span></div>
<pre class="crayon-plain-tag">securitySchemes:

  BasicAuth:
    type: http
    scheme: basic

  BearerAuth:
    type: http
    scheme: bearer

  ApiKeyAuth:
    type: apiKey
    in: header
    name: X-API-Key

  OpenID:
    type: openIdConnect
    openIdConnectUrl: https://example.com/.well-known/openid-configuration

  OAuth2:
    type: oauth2
    flows:
      authorizationCode:
        authorizationUrl: https://example.com/oauth/authorize
        tokenUrl: https://example.com/oauth/token
        scopes:
          read: Grants read access
          write: Grants write access
          admin: Grants access to admin operations</pre>
<div class="blog_h3"><span class="graybg">应用到方法</span></div>
<p>为方法添加security字段：</p>
<pre class="crayon-plain-tag">security:
  - ApiKeyAuth: []
  - OAuth2:
      - read
      - write </pre>
<div class="blog_h2"><span class="graybg">引用</span></div>
<p>引用Schema：</p>
<pre class="crayon-plain-tag">schema: 
  $ref: '#/components/schemas/User'</pre>
<p>本地引用：<pre class="crayon-plain-tag">#/components/schemas/user</pre></p>
<p>远程引用：</p>
<ol>
<li>当前服务器下其它文件：<pre class="crayon-plain-tag">$ref: 'document.json'</pre></li>
<li>文件中的元素：<pre class="crayon-plain-tag">$ref: 'document.json#/myElement'</pre></li>
<li>其它服务器中的元素：<pre class="crayon-plain-tag">$ref:  http://path/to/your/resource.json#myElement</pre></li>
</ol>
<div class="blog_h1"><span class="graybg">代码生成</span></div>
<div class="blog_h2"><span class="graybg">openapi-generator</span></div>
<p><a href="https://github.com/OpenAPITools/openapi-generator">OpenAPITools/openapi-generator</a>项目，用于从OpenAPI Spec（2和3版本）自动生成客户端库、服务器代码存根、文档以及配置文件。</p>
<p>openapi-generator广泛的支持各种常用语言和框架，对于Go来说，支持的Web框架包括Gin、Echo等。</p>
<div class="blog_h2"><span class="graybg">Go</span></div>
<div class="blog_h3"><span class="graybg">deepmap/oapi-codegen</span></div>
<p>用于生成OpenAPI 3.0的Go样板代码，以Echo为默认的Web框架。该库致力于尽量的简单化，而非通用化，它不会为所有OpenAPI Schemas生成强类型的Go代码。</p>
<p>默认情况下oapi-codegen会生成包括客户端、服务器、类型定义、内嵌Swagger Spec在内的所有代码。对应命令行选项<br /><pre class="crayon-plain-tag">-generate=types,client,server,spec</pre>。</p>
<p>下面我们看看从宠物商店的OpenAPI Spec生成Go样板代码：</p>
<pre class="crayon-plain-tag">openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  description: A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification
  termsOfService: http://swagger.io/terms/
  contact:
    name: Swagger API Team
    email: apiteam@swagger.io
    url: http://swagger.io
  license:
    name: Apache 2.0
    url: https://www.apache.org/licenses/LICENSE-2.0.html
servers:
  - url: http://petstore.swagger.io/api
paths:
  /pets:
    get:
      description: |
        Returns all pets from the system that the user has access to
        Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia.
        Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien.
      operationId: findPets
      parameters:
        - name: tags
          in: query
          description: tags to filter by
          required: false
          style: form
          schema:
            type: array
            items:
              type: string
        - name: limit
          in: query
          description: maximum number of results to return
          required: false
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: pet response
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      description: Creates a new pet in the store. Duplicates are allowed
      operationId: addPet
      requestBody:
        description: Pet to add to the store
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewPet'
      responses:
        '200':
          description: pet response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /pets/{id}:
    get:
      description: Returns a user based on a single ID, if the user does not have access to the pet
      operationId: find pet by id
      parameters:
        - name: id
          in: path
          description: ID of pet to fetch
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: pet response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      description: deletes a single pet based on the ID supplied
      operationId: deletePet
      parameters:
        - name: id
          in: path
          description: ID of pet to delete
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '204':
          description: pet deleted
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Pet:
      allOf:
        - $ref: '#/components/schemas/NewPet'
        - type: object
          required:
          - id
          properties:
            id:
              type: integer
              format: int64

    NewPet:
      type: object
      required:
        - name  
      properties:
        name:
          type: string
        tag:
          type: string    

    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string</pre>
<p>通过下面的命令生成样板代码：</p>
<pre class="crayon-plain-tag">go get github.com/deepmap/oapi-codegen/cmd/oapi-codegen
oapi-codegen petstore-expanded.yaml  &gt; petstore.gen.go</pre>
<p> OpenAPI的<pre class="crayon-plain-tag">/components/schemas</pre>段中定义了可复用对象类型，oapi-codegen会生成对应的Go结构：</p>
<pre class="crayon-plain-tag">// Type definition for component schema "Error"
type Error struct {
    Code    int32  `json:"code"`
    Message string `json:"message"`
}

// Type definition for component schema "NewPet"
type NewPet struct {
    Name string  `json:"name"`
    Tag  *string `json:"tag,omitempty"`
}

// Type definition for component schema "Pet"
type Pet struct {
    // Embedded struct due to allOf(#/components/schemas/NewPet)
    NewPet
    // Embedded fields due to inline allOf schema
    Id int64 `json:"id"`
}

// Type definition for component schema "Pets"
type Pets []Pet</pre>
<p>对于在handler中定义的inline类型，只会生成内联的、匿名的Go结构。这会导致重复的代码，应该避免。</p>
<p>对于paths中的每一个元素，都会生成一个Go Handler函数：</p>
<pre class="crayon-plain-tag">type ServerInterface interface {
    //  (GET /pets)
    FindPets(ctx echo.Context, params FindPetsParams) error
    //  (POST /pets)
    AddPet(ctx echo.Context) error
    //  (DELETE /pets/{id})
    DeletePet(ctx echo.Context, id int64) error
    //  (GET /pets/{id})
    FindPetById(ctx echo.Context, id int64) error
}</pre>
<p>请求参数通过以下方式传递：</p>
<ol>
<li>大部分情况下，函数被编解码到echo.Context中</li>
<li>路径变量作为Handler函数的参数</li>
<li>其它请求头参数、查询参数、Cookie参数被存放在params变量中，例如：<br />
<pre class="crayon-plain-tag">// Parameters object for FindPets
type FindPetsParams struct {
   Tags  *[]string `json:"tags,omitempty"`
   Limit *int32   `json:"limit,omitempty"`  // 可选参数，作为指针
}</pre>
</li>
</ol>
<p>使用命令行选项<pre class="crayon-plain-tag">-generate server</pre>可以为Echo生成Handlers注册函数： </p>
<pre class="crayon-plain-tag">func RegisterHandlers(router codegen.EchoRouter, si ServerInterface) {
    wrapper := ServerInterfaceWrapper{
        Handler: si,
    }
    router.GET("/pets", wrapper.FindPets)
    router.POST("/pets", wrapper.AddPet)
    router.DELETE("/pets/:id", wrapper.DeletePet)
    router.GET("/pets/:id", wrapper.FindPetById)
}</pre>
<p>使用下面的代码注册Handlers到Echo服务器：</p>
<pre class="crayon-plain-tag">func SetupHandler() {
    var myApi PetStoreImpl  // 这是你的服务器端实现
    e := echo.New()
    petstore.RegisterHandlers(e, &amp;myApi)
    ...
}</pre>
<p>类似的，使用命令行选项<pre class="crayon-plain-tag">-generate chi-server</pre>可以生成Chi或net/http的Handlers注册函数。</p>
<p>OpenAPI默认隐含additionalProperties=true，也就是说请求中提供的任何没有明确定义的字段都应该被接受。由于在Go语言中处理这种动态属性需要大量样板代码，oapi-codegen默认假设additionalProperties=false，要改变此默认行为你需要修改OpenAPI Schema：</p>
<pre class="crayon-plain-tag">NewPet:
      required:
        - name
      properties:
        name:
          type: string
        tag:
          type: string
      additionalProperties:
        type: string</pre>
<p>这样会生成如下结构：</p>
<pre class="crayon-plain-tag">// NewPet defines model for NewPet.
type NewPet struct {
	Name                 string            `json:"name"`
	Tag                  *string           `json:"tag,omitempty"`
	AdditionalProperties map[string]string `json:"-"`
}</pre>
<p>使用命令行选项<pre class="crayon-plain-tag">-generate=client</pre>可以生成客户端代码：</p>
<pre class="crayon-plain-tag">// The interface specification for the client above.
type ClientInterface interface {

	// FindPets request
	FindPets(ctx context.Context, params *FindPetsParams, reqEditors ...RequestEditorFn) (*http.Response, error)

	// AddPet request with JSON body
	AddPet(ctx context.Context, body NewPet, reqEditors ...RequestEditorFn) (*http.Response, error)

	// DeletePet request
	DeletePet(ctx context.Context, id int64, reqEditors ...RequestEditorFn) (*http.Response, error)

	// FindPetById request
	FindPetById(ctx context.Context, id int64, reqEditors ...RequestEditorFn) (*http.Response, error)
}


// Client which conforms to the OpenAPI3 specification for this service.
type Client struct {
    // The endpoint of the server conforming to this interface, with scheme,
    // https://api.deepmap.com for example.
    Server string

    // HTTP client with any customized settings, such as certificate chains.
    Client http.Client

    // A callback for modifying requests which are generated before sending over
    // the network.
    RequestEditors []func(ctx context.Context, req *http.Request) error
}</pre>
<p>每个OpenAPI Schema中定义的操作都对应了一个客户端函数。 </p>
<div class="blog_h3"><span class="graybg"><a id="openapi-gen"></a>openapi-gen</span></div>
<p>此工具专门用于从K8S模型类生成Open API模型（以Go结构的形式存放在GetOpenAPIDefinitions函数中）。</p>
<p>下载并安装：</p>
<pre class="crayon-plain-tag">git clone https://github.com/kubernetes/kube-openapi.git

export GOPROXY=https://goproxy.io
export GO111MODULE=on
go install cmd/openapi-gen/openapi-gen.go</pre>
<p>要为指定的包生成模型，执行命令：</p>
<pre class="crayon-plain-tag"># 输入包的导入路径
openapi-gen -i git.pacloud.io/pks/helm-operator/pkg/apis/pks/v1 
            # 输出包的导入路径
            -p git.pacloud.io/pks/helm-operator/pkg/apis/pks/v1 
            # 生成的函数所在文件的名称前缀
            -O zz_generated.openapi</pre>
<p>你的CRD通常会引用K8S核心库中的模型，因此需要同时为它们生成模型：</p>
<pre class="crayon-plain-tag">openapi-gen -i k8s.io/api/core/v1 -p git.pacloud.io/pks/helm-operator/pkg/apis/core/v1
openapi-gen -i k8s.io/apimachinery/pkg/apis/meta/v1 -p git.pacloud.io/pks/helm-operator/pkg/apis/meta/v1</pre>
<p>使用Operator Framework开发CRD的控制器时，你可以使用注释<pre class="crayon-plain-tag">+k8s:openapi-gen=true</pre>来生成Open API模型，生成的Schema示例如下：</p>
<div class="blog_h3"><span class="graybg">生成Swagger Spec</span></div>
<p>下面的代码调用GetOpenAPIDefinitions函数，生成Swagger 2.0.0 API定义的JSON：</p>
<pre class="crayon-plain-tag">package main

import (
	"encoding/json"
	"fmt"
	pksv1 "git.pacloud.io/pks/helm-operator/pkg/apis/pks/v1"
	"github.com/go-openapi/spec"
	"k8s.io/kube-openapi/pkg/common"
	"log"
	"os"
	"strings"
)

func main() {
	if len(os.Args) &lt;= 1 {
		log.Fatal("version required")
	}
	version := os.Args[1]
	if !strings.HasPrefix(version, "v") {
		version = "v" + version
	}
	oAPIDefs := pksv1.GetOpenAPIDefinitions(func(name string) spec.Ref {
		return spec.MustCreateRef("#/definitions/" + common.EscapeJsonPointer(swaggify(name)))
	})
	defs := spec.Definitions{}
	for defName, val := range oAPIDefs {
		defs[swaggify(defName)] = val.Schema
	}

	swagger := spec.Swagger{
		SwaggerProps: spec.SwaggerProps{
			Swagger:     "2.0",
			Definitions: defs,
			Paths:       &amp;spec.Paths{Paths: map[string]spec.PathItem{}},
			Info: &amp;spec.Info{
				InfoProps: spec.InfoProps{
					Title:   "Helm Release",
					Version: version,
				},
			},
		},
	}
	jsonBytes, err := json.MarshalIndent(swagger, "", "  ")
	if err != nil {
		log.Fatal(err.Error())
	}
	fmt.Println(string(jsonBytes))
}

func swaggify(name string) string {
	name = strings.Replace(name, "git.pacloud.io/pks/helm-operator/pkg/apis", "yun.gmem.cc", -1)
	parts := strings.Split(name, "/")
	hostParts := strings.Split(parts[0], ".")
	for i, j := 0, len(hostParts)-1; i &lt; j; i, j = i+1, j-1 {
		hostParts[i], hostParts[j] = hostParts[j], hostParts[i]
	}
	parts[0] = strings.Join(hostParts, ".")
	return strings.Join(parts, ".")
}</pre>
<p>生成的Swagger API，仅仅包含模型信息，也就是definitions字段，但是不包含API端点（paths）。</p>
<p>如果需要生成API端点，则需要启动一个API Server并将需要生成API端点的模型注册进去：</p>
<pre class="crayon-plain-tag">// k8s.io/*包的API非常不稳定，本样例基于kubernetes 1.13.9
package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	pkscorev1 "git.pacloud.io/pks/helm-operator/pkg/apis/core/v1"
	pksmetav1 "git.pacloud.io/pks/helm-operator/pkg/apis/meta/v1"
	pksv1 "git.pacloud.io/pks/helm-operator/pkg/apis/pks/v1"
	"github.com/go-openapi/spec"
	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/watch"
	openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
	"k8s.io/apiserver/pkg/registry/rest"
	genericapiserver "k8s.io/apiserver/pkg/server"
	genericoptions "k8s.io/apiserver/pkg/server/options"
	"k8s.io/klog"
	"k8s.io/kube-openapi/pkg/builder"
	"k8s.io/kube-openapi/pkg/common"
	"net"
	"os"
)

// 这个Storage是资源CRUD操作的提供者
type StandardStorage struct {
	cfg ResourceInfo
}

// 强制它实现以下接口
var _ rest.GroupVersionKindProvider = &amp;StandardStorage{}
var _ rest.Scoper = &amp;StandardStorage{}
var _ rest.StandardStorage = &amp;StandardStorage{}

// GroupVersionKindProvider
func (r *StandardStorage) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
	return r.cfg.gvk
}

// Scoper
func (r *StandardStorage) NamespaceScoped() bool {
	return r.cfg.namespaceScoped
}

// Getter
func (r *StandardStorage) New() runtime.Object {
	return r.cfg.obj
}

func (r *StandardStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	return r.New(), nil
}

func (r *StandardStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	return r.New(), nil
}

// Lister
func (r *StandardStorage) NewList() runtime.Object {
	return r.cfg.list
}

func (r *StandardStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
	return r.NewList(), nil
}

// CreaterUpdater
func (r *StandardStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	return r.New(), true, nil
}

// GracefulDeleter
func (r *StandardStorage) Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
	return r.New(), true, nil
}

// CollectionDeleter
func (r *StandardStorage) DeleteCollection(ctx context.Context, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
	return r.NewList(), nil
}

// Watcher
func (r *StandardStorage) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) {
	return nil, nil
}

type ResourceInfo struct {
	gvk             schema.GroupVersionKind
	obj             runtime.Object
	list            runtime.Object
	namespaceScoped bool
}

type TypeInfo struct {
	GroupVersion    schema.GroupVersion
	Resource        string
	Kind            string
	NamespaceScoped bool
}
type Config struct {
	Scheme             *runtime.Scheme
	Codecs             serializer.CodecFactory
	Info               spec.InfoProps
	OpenAPIDefinitions []common.GetOpenAPIDefinitions
	Resources          []TypeInfo
}

func (c *Config) GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
	out := map[string]common.OpenAPIDefinition{}
	for _, def := range c.OpenAPIDefinitions {
		for k, v := range def(ref) {
			out[k] = v
		}
	}
	return out
}
func RenderOpenAPISpec(cfg Config) (string, error) {

	// 总是需要向Scheme注册corev1、metav1
	metav1.AddToGroupVersion(cfg.Scheme, schema.GroupVersion{Version: "v1"})
	unversioned := schema.GroupVersion{Group: "", Version: "v1"}
	cfg.Scheme.AddUnversionedTypes(unversioned,
		&amp;metav1.Status{},
		&amp;metav1.APIVersions{},
		&amp;metav1.APIGroupList{},
		&amp;metav1.APIGroup{},
		&amp;metav1.APIResourceList{},
	)

	// API Server选项
	options := genericoptions.NewRecommendedOptions("/registry/pks.yun.gmem.cc", cfg.Codecs.LegacyCodec(), &amp;genericoptions.ProcessInfo{})
	options.SecureServing.BindPort = 6445
	options.Etcd = nil
	options.Authentication = nil
	options.Authorization = nil
	options.CoreAPI = nil
	options.Admission = nil
	// 自动生成的服务器证书的存放目录
	options.SecureServing.ServerCert.CertDirectory = "/tmp/helm-operator"

	// 启动的API Server，监听的地址
	publicAddr := "localhost"
	ips := []net.IP{net.ParseIP("127.0.0.1")}

	// 尝试自动生成服务器证书
	if err := options.SecureServing.MaybeDefaultWithSelfSignedCerts(publicAddr, nil, ips); err != nil {
		klog.Fatal(err)
	}

	// API Server配置
	serverConfig := genericapiserver.NewRecommendedConfig(cfg.Codecs)
	if err := options.ApplyTo(serverConfig, cfg.Scheme); err != nil {
		return "", err
	}
	serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(cfg.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(cfg.Scheme))
	serverConfig.OpenAPIConfig.Info.InfoProps = cfg.Info
	//                             完成配置，  创建服务器
	genericServer, err := serverConfig.Complete().New("helm-operator-server", genericapiserver.NewEmptyDelegate())
	if err != nil {
		return "", err
	}

	// 这里处理本服务器需要Serve的资源列表
	table := map[schema.GroupVersion]map[string]rest.Storage{}
	{
		for _, ti := range cfg.Resources {
			// 对于每一种资源，根据其GV寻找存储库
			var resmap map[string]rest.Storage
			if m, found := table[ti.GroupVersion]; found {
				resmap = m
			} else {
				// 如果找不到，则创建存储库（每个GV一个存储库）
				resmap = map[string]rest.Storage{}
				table[ti.GroupVersion] = resmap
			}

			gvk := ti.GroupVersion.WithKind(ti.Kind)
			// 创建这种资源的一个对象
			obj, err := cfg.Scheme.New(gvk)
			if err != nil {
				return "", err
			}
			// 创建这种资源的列表对象
			list, err := cfg.Scheme.New(ti.GroupVersion.WithKind(ti.Kind + "List"))
			if err != nil {
				return "", err
			}

			// 为资源创建存储，并Put到它的GV的存储库中
			resmap[ti.Resource] = &amp;StandardStorage{ResourceInfo{
				// GVK信息
				gvk: gvk,
				// 资源和资源列表的原型
				obj:  obj,
				list: list,
				// 提示此资源是否命名空间化
				namespaceScoped: ti.NamespaceScoped,
			}}
		}
	}
	for gv, resmap := range table {
		// 为每个组创建API组信息
		apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(gv.Group, cfg.Scheme, metav1.ParameterCodec, cfg.Codecs)
		storage := map[string]rest.Storage{}
		for r, s := range resmap {
			storage[r] = s
		}
		apiGroupInfo.VersionedResourcesStorageMap[gv.Version] = storage

		// 并安装到此服务器
		if err := genericServer.InstallAPIGroup(&amp;apiGroupInfo); err != nil {
			return "", err
		}
	}
	// 构件API规范，也就是Swagger 2.0 Spec
	spec, err := builder.BuildOpenAPISpec(genericServer.Handler.GoRestfulContainer.RegisteredWebServices(), serverConfig.OpenAPIConfig)
	if err != nil {
		return "", err
	}
	data, err := json.MarshalIndent(spec, "", "  ")
	if err != nil {
		return "", err
	}
	return string(data), nil
}

func main() {
	flag.Parse()
	if len(os.Args) &lt;= 1 {
		panic("version required")
	}
	version := os.Args[1]

	scheme := runtime.NewScheme()
	// 将我们需要处理的类型加入到scheme
	scheme.AddKnownTypeWithName(schema.GroupVersion{Group: "pks.yun.gmem.cc", Version: "v1"}.WithKind("Release"), &amp;pksv1.Release{})
	scheme.AddKnownTypeWithName(schema.GroupVersion{Group: "pks.yun.gmem.cc", Version: "v1"}.WithKind("ReleaseList"), &amp;pksv1.Release{})
	scheme.AddKnownTypeWithName(schema.GroupVersion{Group: "pks.yun.gmem.cc", Version: "v1"}.WithKind("WatchEvent"), &amp;pksv1.Release{})

	spec, err := RenderOpenAPISpec(Config{
		Info: spec.InfoProps{
			Version: version,
			Title:   "Helm Operator OpenAPI",
		},
		Scheme: scheme,
		Codecs: serializer.NewCodecFactory(scheme),
		OpenAPIDefinitions: []common.GetOpenAPIDefinitions{
			pkscorev1.GetOpenAPIDefinitions,
			pksmetav1.GetOpenAPIDefinitions,
			pksv1.GetOpenAPIDefinitions,
		},
		Resources: []TypeInfo{
			{
				GroupVersion:    schema.GroupVersion{Group: "pks.yun.gmem.cc", Version: "v1"},
				Kind:            "Release",
				Resource:        "Release",
				NamespaceScoped: true,
			},
		},
	})
	if err != nil {
		klog.Fatal(err.Error())
	}
	fmt.Println(spec)
}</pre>
<div class="blog_h2"><span class="graybg">Java</span></div>
<div class="blog_h3"><span class="graybg">Gradle</span></div>
<p>可以使用下面的插件：</p>
<pre class="crayon-plain-tag">plugins {
  id 'org.hidetake.swagger.generator' version '2.18.1'
}</pre>
<p>根据API规格的版本，选择依赖：</p>
<pre class="crayon-plain-tag">dependencies {
  swaggerCodegen 'io.swagger:swagger-codegen-cli:2.4.2'             // Swagger Codegen V2
  swaggerCodegen 'io.swagger.codegen.v3:swagger-codegen-cli:3.0.5'  // Swagger Codegen V3
  swaggerCodegen 'org.openapitools:openapi-generator-cli:3.3.4'     // penAPI Generator
}</pre>
<p>控制代码生成的配置片段：</p>
<pre class="crayon-plain-tag">swaggerSources {
  petstore {
    inputFile = file('petstore.yaml')
    code {
      language = 'spring'
      configFile = file('config.json')
    }
  }
}</pre>
<p>执行命令：<pre class="crayon-plain-tag">./gradlew generateSwaggerCode</pre>，将自动生成代码到<pre class="crayon-plain-tag">build/swagger-code-petstore</pre>目录。</p>
<p>configFile字段可以指定一个外部（本质上就是<pre class="crayon-plain-tag">swagger-codegen-cli generate</pre>的）配置文件，格式如下：</p>
<pre class="crayon-plain-tag">{
  "library": "spring-mvc",
  "modelPackage": "example.model",
  "apiPackage": "example.api",
  "invokerPackage": "example"
}</pre>
<p>对于language=java，常用配置项列表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">配置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>sortParamsByRequiredFlag</td>
<td>排序风发参数，将必须参数放在前面</td>
</tr>
<tr>
<td>ensureUniqueParams</td>
<td>是否一个操作内，保证参数名的唯一性</td>
</tr>
<tr>
<td>allowUnicodeIdentifiers</td>
<td>是否允许Unicode标识符</td>
</tr>
<tr>
<td>modelPackage</td>
<td>生成的模型类的包名</td>
</tr>
<tr>
<td>apiPackage</td>
<td>生成的API类的包名</td>
</tr>
<tr>
<td>invokerPackage</td>
<td>生成的代码的根包名</td>
</tr>
<tr>
<td>groupId</td>
<td rowspan="4">生成的POM的信息</td>
</tr>
<tr>
<td>artifactId</td>
</tr>
<tr>
<td>artifactVersion</td>
</tr>
<tr>
<td>artifactDescription</td>
</tr>
<tr>
<td>sourceFolder</td>
<td>源码目录</td>
</tr>
<tr>
<td>localVariablePrefix</td>
<td>局部变量前缀</td>
</tr>
<tr>
<td>serializableModel</td>
<td>生成的模型是否实现Serializable接口</td>
</tr>
<tr>
<td>fullJavaUtil</td>
<td>如果生成Java代码，那么是否用全限定名称引用java.util下的类</td>
</tr>
<tr>
<td>withXml</td>
<td>生成的类是否添加序列化为XML的支持</td>
</tr>
<tr>
<td>dateLibrary</td>
<td>
<p>Java日期库：</p>
<p style="padding-left: 30px;">joda 用于遗留应用<br />legacy 使用java.util.Date，用于遗留应用<br />java8-localdatetime   使用LocalDateTime，用于遗留应用<br />java8 使用Java 8原生的JSR310<br />threetenbp  低版本的JSR310垫片</p>
</td>
</tr>
<tr>
<td>java8</td>
<td>使用Java 8提供的API</td>
</tr>
<tr>
<td>disableHtmlEscaping</td>
<td>使用JSON时禁止HTML转译，如果<span style="background-color: #c0c0c0;">使用byte[]字段应该设置为true</span></td>
</tr>
<tr>
<td>useGzipFeature</td>
<td>是否使用Gzip编码请求</td>
</tr>
<tr>
<td>useRuntimeException</td>
<td>是否使用RuntimeException代替Exception</td>
</tr>
<tr>
<td>library</td>
<td>
<p>库模板，对于language=java，默认okhttp-gson。可选：</p>
<p style="padding-left: 30px;">jersey1，基于JacksonJSON串行化<br />jersey2，基于JacksonJSON串行化<br />feign，基于JacksonJSON串行化<br />okhttp-gson，基于GSON串行化<br />retrofit2 基于OKHttp 3.x，基于GSON串行化<br />resttemplate 基于Spring RestTemplate，基于JacksonJSON串行化</p>
</td>
</tr>
</tbody>
</table>
<p>  </p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/openapi">OpenAPI学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/openapi/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Maven插件开发</title>
		<link>https://blog.gmem.cc/maven-plugin-development</link>
		<comments>https://blog.gmem.cc/maven-plugin-development#comments</comments>
		<pubDate>Wed, 10 Jul 2019 03:17:38 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=28005</guid>
		<description><![CDATA[<p>简介 Maven本身只是一套框架，它的功能基于全部依赖于插件来实现。因此，掌握插件开发深度定制Maven的必修课。 插件本身也是Maven构件，构件标识的命名约定是[crayon-69d57ac5408f9472666149-i/]。不要命名为[crayon-69d57ac5408fd491576610-i/]，这种命名模式由官方插件（组标识[crayon-69d57ac5408ff151697888-i/]）保留。 每个Maven插件包含一或多个Mojo，每个Mojo实现了一个插件目标（goal），Mojo通常编写为Java类。插件就是一系列Mojo的集合。 插件必须在自己的JAR包的[crayon-69d57ac540901121770842-i/]中提供描述符信息，你不需要手工编写描述符，使用Maven插件工具注解即可自动生成。 入门 本章我们编写一个最简单的插件，它不需要参数，仅仅简单的打印一段消息。 第一个Mojo 可以将mojo理解为插件的一个目标（goal）的实现类。你需要使用@Mojo注解来声明一个类是mojo： [crayon-69d57ac540904873433385/] 抽象类AbstractMojo包含了实现mojo所需的基础代码，我们仅仅需要实现execute方法就可以了。execute方法可以抛出两种异常： MojoExecutionException：表示意外错误发生，控制台会显示BUILD ERROR消息 MojoFailureException：表示非意外的错误发生，例如编译是白。控制台会显示BUILD FAILURE消息 构建插件 插件本身也是Maven项目，因此你需要编写POM，示例： [crayon-69d57ac540906724575703/] 使用插件 另创建一个Maven项目，添加以下配置：  [crayon-69d57ac540908173191139/] 然后调用我们的插件： <a class="read-more" href="https://blog.gmem.cc/maven-plugin-development">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/maven-plugin-development">Maven插件开发</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Maven本身只是一套框架，它的功能基于全部依赖于插件来实现。因此，掌握插件开发深度定制Maven的必修课。</p>
<p>插件本身也是Maven构件，构件标识的命名约定是<pre class="crayon-plain-tag">&lt;yourplugin&gt;-maven-plugin</pre>。不要命名为<pre class="crayon-plain-tag">maven-&lt;yourplugin&gt;-plugin</pre>，这种命名模式由官方插件（组标识<pre class="crayon-plain-tag">org.apache.maven.plugins</pre>）保留。</p>
<p><span style="background-color: #c0c0c0;">每个Maven插件包含一或多个Mojo，每个Mojo实现了一个插件目标（goal），Mojo通常编写为Java类</span>。插件就是一系列Mojo的集合。</p>
<p>插件必须在自己的JAR包的<pre class="crayon-plain-tag">META-INF/maven/plugin.xml</pre>中提供描述符信息，你不需要手工编写描述符，使用Maven插件工具注解即可自动生成。</p>
<div class="blog_h1"><span class="graybg">入门</span></div>
<p>本章我们编写一个最简单的插件，它不需要参数，仅仅简单的打印一段消息。</p>
<div class="blog_h2"><span class="graybg">第一个Mojo</span></div>
<p>可以将mojo理解为插件的一个目标（goal）的实现类。你需要使用@Mojo注解来声明一个类是mojo：</p>
<pre class="crayon-plain-tag">package sample.plugin;
 
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
 
@Mojo( name = "sayhi")
public class GreetingMojo extends AbstractMojo
{
    public void execute() throws MojoExecutionException
    {
        getLog().info( "Hello, world." );
    }
}</pre>
<p>抽象类AbstractMojo包含了实现mojo所需的基础代码，我们仅仅需要实现execute方法就可以了。execute方法可以抛出两种异常：</p>
<ol>
<li>MojoExecutionException：表示意外错误发生，控制台会显示BUILD ERROR消息</li>
<li>MojoFailureException：表示非意外的错误发生，例如编译是白。控制台会显示BUILD FAILURE消息</li>
</ol>
<div class="blog_h2"><span class="graybg">构建插件</span></div>
<p>插件本身也是Maven项目，因此你需要编写POM，示例：</p>
<pre class="crayon-plain-tag">&lt;project&gt;
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
 
  &lt;groupId&gt;sample.plugin&lt;/groupId&gt;
  &lt;artifactId&gt;hello-maven-plugin&lt;/artifactId&gt;
  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
  &lt;!-- 打包方式必须指定为maven-plugin --&gt;
  &lt;packaging&gt;maven-plugin&lt;/packaging&gt;
 
  &lt;name&gt;Sample Parameter-less Maven Plugin&lt;/name&gt;
 
  &lt;dependencies&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.apache.maven&lt;/groupId&gt;
      &lt;artifactId&gt;maven-plugin-api&lt;/artifactId&gt;
      &lt;version&gt;3.0&lt;/version&gt;
    &lt;/dependency&gt;
 
    &lt;!-- 插件注解 --&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.apache.maven.plugin-tools&lt;/groupId&gt;
      &lt;artifactId&gt;maven-plugin-annotations&lt;/artifactId&gt;
      &lt;version&gt;3.4&lt;/version&gt;
      &lt;scope&gt;provided&lt;/scope&gt;
    &lt;/dependency&gt;
  &lt;/dependencies&gt;
&lt;/project&gt;</pre>
<div class="blog_h2"><span class="graybg">使用插件</span></div>
<p>另创建一个Maven项目，添加以下配置： </p>
<pre class="crayon-plain-tag">&lt;build&gt;
  &lt;plugins&gt;
    &lt;plugin&gt;
      &lt;groupId&gt;sample.plugin&lt;/groupId&gt;
      &lt;artifactId&gt;hello-maven-plugin&lt;/artifactId&gt;
      &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
    &lt;/plugin&gt;
  &lt;/plugins&gt;
&lt;/build&gt;</pre>
<p>然后调用我们的插件：</p>
<pre class="crayon-plain-tag"># mvn groupId:artifactId:version:goal
mvn sample.plugin:hello-maven-plugin:1.0-SNAPSHOT:sayhi</pre>
<div class="blog_h2"><span class="graybg">简化命令行</span></div>
<p>上面调用插件的mvn命令参数非常冗长，缩短的方式有几种：</p>
<ol>
<li>如果希望执行本地仓库中，插件的最新版本，则version可以省略</li>
<li>可以为插件分配快捷前缀，如果插件命名遵循了<pre class="crayon-plain-tag">${prefix}-maven-plugin</pre>格式则prefix自动为前缀。调用命令可以简化为<pre class="crayon-plain-tag">mvn sample.plugin:hello:sayhi</pre></li>
<li>可以将组标识添加到Maven的settings.xml的pluginGroups，这样就可以进一步省略组标识：<br />
<pre class="crayon-plain-tag">&lt;pluginGroups&gt;
    &lt;pluginGroup&gt;sample.plugin&lt;/pluginGroup&gt;
&lt;/pluginGroups&gt;</pre></p>
<p>调用命令简化为<pre class="crayon-plain-tag">mvn hello:sayhi</pre></p>
</li>
</ol>
<div class="blog_h2"><span class="graybg">附到生命周期</span></div>
<p>在目标项目中增加如下配置：</p>
<pre class="crayon-plain-tag">&lt;build&gt;
  &lt;plugins&gt;
    &lt;plugin&gt;
      &lt;groupId&gt;sample.plugin&lt;/groupId&gt;
      &lt;artifactId&gt;hello-maven-plugin&lt;/artifactId&gt;
      &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
      &lt;executions&gt;
        &lt;execution&gt;
          &lt;phase&gt;compile&lt;/phase&gt;
          &lt;goals&gt;
            &lt;goal&gt;sayhi&lt;/goal&gt;
          &lt;/goals&gt;
        &lt;/execution&gt;
      &lt;/executions&gt;
    &lt;/plugin&gt;
  &lt;/plugins&gt;
&lt;/build&gt;</pre>
<p>则在构建生命周期的compile阶段，sayhi目标自动执行。</p>
<div class="blog_h2"><span class="graybg">Mojo原型</span></div>
<p>你可以从原型创建插件项目，避免手写样板代码： </p>
<pre class="crayon-plain-tag">mvn archetype:generate \
    -DgroupId=sample.plugin \
    -DartifactId=hello-maven-plugin \
    -DarchetypeGroupId=org.apache.maven.archetypes \
    -DarchetypeArtifactId=maven-archetype-plugin</pre>
<div class="blog_h1"><span class="graybg">参数化Mojo</span></div>
<p>Mojo可以支持参数，目标项目的<span style="background-color: #c0c0c0;">POM中可以声明传入的参数值</span>，你也可以在命令行中以-D<span style="background-color: #c0c0c0;">系统属性的风格传入参数值</span>。</p>
<p>要定义Mojo参数，只需要为Mojo类的字段添加注解：</p>
<pre class="crayon-plain-tag">@Parameter( property = "sayhi.greeting", defaultValue = "Hello World!" )
private String greeting;</pre>
<p>上面的代码定义了一个参数greeting，并给定了默认值。此参数对应的系统属性为sayhi.greeting。</p>
<p>参数值可以使用<pre class="crayon-plain-tag">${project.version}</pre>这样的表达式，来<span style="background-color: #c0c0c0;">引用目标项目的属性</span>。</p>
<div class="blog_h3"><span class="graybg">POM配置参数值</span></div>
<pre class="crayon-plain-tag">&lt;plugin&gt;
  &lt;groupId&gt;sample.plugin&lt;/groupId&gt;
  &lt;artifactId&gt;hello-maven-plugin&lt;/artifactId&gt;
  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
  &lt;configuration&gt;
    &lt;!-- 这里使用参数的字段名而非系统属性 --&gt;
    &lt;greeting&gt;Welcome&lt;/greeting&gt;
  &lt;/configuration&gt;
&lt;/plugin&gt;</pre>
<div class="blog_h2"><span class="graybg">插件参数表达式</span></div>
<p>在插件参数表达式中，你可以使用以下对象：</p>
<table class="fixed-word-wrap full-width" border="1">
<tbody>
<tr>
<td style="width: 30%; text-align: center;"><strong>对象</strong></td>
<td style="text-align: center;"><strong>说明</strong></td>
</tr>
<tr>
<td>session</td>
<td rowspan="2">当前<pre class="crayon-plain-tag">MavenSession</pre>对象 </td>
</tr>
<tr>
<td>session.*</td>
</tr>
<tr>
<td>localRepository</td>
<td><pre class="crayon-plain-tag">MavenSession.getLocalRepository()</pre></td>
</tr>
<tr>
<td>reactorProjects</td>
<td><pre class="crayon-plain-tag">MavenSession.getProjects()</pre></td>
</tr>
<tr>
<td>repositorySystemSession</td>
<td><pre class="crayon-plain-tag">MavenSession.getRepositorySession()</pre></td>
</tr>
<tr>
<td>project</td>
<td rowspan="3"><pre class="crayon-plain-tag">MavenSession.getCurrentProject()</pre></td>
</tr>
<tr>
<td>project.*</td>
</tr>
<tr>
<td>pom.*</td>
</tr>
<tr>
<td>executedProject</td>
<td><pre class="crayon-plain-tag">MavenProject.getExecutionProject()</pre></td>
</tr>
<tr>
<td>settings</td>
<td rowspan="2"><pre class="crayon-plain-tag">MavenSession.getSettings()</pre></td>
</tr>
<tr>
<td>settings.*</td>
</tr>
<tr>
<td>basedir</td>
<td><pre class="crayon-plain-tag">MavenSession.getExecutionRootDirectory()</pre>，为空则取<br /><pre class="crayon-plain-tag">System.getProperty( "user.dir" )</pre></td>
</tr>
<tr>
<td>mojoExecution</td>
<td rowspan="3">当前MojoExecution对象</td>
</tr>
<tr>
<td>mojo</td>
</tr>
<tr>
<td>mojo.*</td>
</tr>
<tr>
<td>plugin</td>
<td rowspan="2"><pre class="crayon-plain-tag">MojoExecution.getMojoDescriptor().getPluginDescriptor() </pre></td>
</tr>
<tr>
<td>plugin.*</td>
</tr>
<tr>
<td>*</td>
<td>还可以直接使用系统属性、项目属性</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">类型转换规则</span></div>
<p>配置中的文本转换为Mojo参数字段的规则如下：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">参数字段类型</td>
<td style="text-align: center;">文本表示</td>
</tr>
</thead>
<tbody>
<tr>
<td>布尔型</td>
<td>true或者false</td>
</tr>
<tr>
<td>整数</td>
<td>包括byte, Byte, int, Integer, long, Long, short, Short，支持负号</td>
</tr>
<tr>
<td>浮点数</td>
<td>包括double, Double, float,  Float，支持科学计数法</td>
</tr>
<tr>
<td>java.util.Date</td>
<td>
<p>支持格式：</p>
<p style="padding-left: 30px;">yyyy-MM-dd HH:mm:ss.S a  例如2005-10-06 2:22:55.1 PM<br />yyyy-MM-dd HH:mm:ssa  例如2005-10-06 2:22:55PM</p>
</td>
</tr>
<tr>
<td>java.io.File</td>
<td>文件系统路径，例如c:\temp</td>
</tr>
<tr>
<td>java.net.URL</td>
<td>URL，例如https://gmem.cc</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">复杂参数</span></div>
<div class="blog_h3"><span class="graybg">多值参数</span></div>
<p>Mojo字段可以使用<span style="background-color: #c0c0c0;">数组、集合类</span>：</p>
<pre class="crayon-plain-tag">@Parameter
private String[] myArray;</pre>
<p>对应POM配置示例： </p>
<pre class="crayon-plain-tag">&lt;myArray&gt;
    &lt;param&gt;value1&lt;/param&gt;
    &lt;param&gt;value2&lt;/param&gt;
&lt;/myArray&gt;</pre>
<p><span style="background-color: #c0c0c0;">映射类型</span>的字段POM配置示例，参数名为myMap： </p>
<pre class="crayon-plain-tag">&lt;myMap&gt;
    &lt;key1&gt;value1&lt;/key1&gt;
    &lt;key2&gt;value2&lt;/key2&gt;
&lt;/myMap&gt;</pre>
<p><span style="background-color: #c0c0c0;">Properties类型</span>的POM配置示例，参数名为myProperties：</p>
<pre class="crayon-plain-tag">&lt;myProperties&gt;
  &lt;property&gt;
    &lt;name&gt;propertyName1&lt;/name&gt;
    &lt;value&gt;propertyValue1&lt;/value&gt;
  &lt;property&gt;
  &lt;property&gt;
    &lt;name&gt;propertyName2&lt;/name&gt;
    &lt;value&gt;propertyValue2&lt;/value&gt;
  &lt;property&gt;
&lt;/myProperties&gt;</pre>
<div class="blog_h3"><span class="graybg">对象参数 </span></div>
<p>Mojo参数字段也可以是任意对象类型： </p>
<pre class="crayon-plain-tag">@Parameter
private MyObject myObject;</pre>
<p>对应POM配置示例：  </p>
<pre class="crayon-plain-tag">&lt;myObject&gt;
    &lt;myField&gt;test&lt;/myField&gt;
&lt;/myObject&gt;</pre>
<div class="blog_h1"><span class="graybg">测试和调试</span></div>
<div class="blog_h2"><span class="graybg">单元测试</span></div>
<p>用于测试单个Mojo的功能，并仿冒Maven的其它部分。</p>
<div class="blog_h3"><span class="graybg">使用PlexusTestCase</span></div>
<p>你可以像编写普通Junit测试用例那样，进行Mojo单元测试。但是，大部分情况下Mojo都需要被注入一个Maven项目的引用，可以<pre class="crayon-plain-tag">extends PlexusTestCase</pre>达成变量注入的目的。</p>
<div class="blog_h3"><span class="graybg">maven-plugin-testing-harness</span></div>
<p>此工具专门用于测试<pre class="crayon-plain-tag">org.apache.maven.reporting.AbstractMavenReport#execute()</pre></p>
<div class="blog_h2"><span class="graybg">集成测试</span></div>
<p>在一个真实的Maven Build中测试Mojo，你需要提供一个测试用的Maven项目，插件需要安装到本地仓库。</p>
<div class="blog_h3"><span class="graybg">maven-verifier</span></div>
<p>Maven验证器为你的JUnit测试用例提供以下功能：</p>
<ol>
<li>启动Maven</li>
<li>根据构建的输出、日志信息进行断言</li>
<li>从src/test/resources目录抽取出一个Maven项目，并存放到临时工作目录中，针对此项目进行测试</li>
<li>操控本地仓库</li>
</ol>
<p>Maven项目本身也在用此验证器进行集成测试，下面是一些说明如何使用该验证器的代码片段：</p>
<pre class="crayon-plain-tag">public class TrivialMavenVerifierTest extends TestCase {
    public void testMyPlugin() throws Exception {
        // 从/src/test/resources/中抽取测试用的Maven项目
        File testDir = ResourceExtractor.simpleExtractResources( getClass(), "/my-dummy-maven-project" );
 
        Verifier verifier;
 
        // 首选需要保证，此test项目产生的构件已经从本地仓库移除
        verifier = new Verifier( testDir.getAbsolutePath() );
        verifier.deleteArtifact( "org.apache.maven.its.itsample", "parent", "1.0", "pom" );
        verifier.deleteArtifact( "org.apache.maven.its.itsample", "checkstyle-test", "1.0", "jar" );
        verifier.deleteArtifact( "org.apache.maven.its.itsample", "checkstyle-assembly", "1.0", "jar" );
 
        // 执行Maven命令
        List cliOptions = new ArrayList();
        cliOptions.add( "-N" );
        verifier.executeGoal( "install" );
 
        // 验证上述命令是否成功
        verifier.verifyErrorFreeLog();
 
        // 清理，准备下一个命令的执行
        verifier.resetStreams();
    }
}</pre>
<div class="blog_h3"><span class="graybg">maven-invoker-plugin</span></div>
<p>这是一个插件，可以调用Maven，并提供一些BeanShell或Groovy的测试脚本： </p>
<pre class="crayon-plain-tag">&lt;build&gt;
  &lt;plugins&gt;
    &lt;plugin&gt;
      &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
      &lt;artifactId&gt;maven-invoker-plugin&lt;/artifactId&gt;
      &lt;version&gt;1.10&lt;/version&gt;
      &lt;configuration&gt;
        &lt;projectsDirectory&gt;src/it&lt;/projectsDirectory&gt;
        &lt;pomIncludes&gt;
          &lt;pomInclude&gt;**/pom.xml&lt;/pomInclude&gt;
        &lt;/pomIncludes&gt;
        &lt;postBuildHookScript&gt;verify&lt;/postBuildHookScript&gt;
      &lt;/configuration&gt;
      &lt;executions&gt;
        &lt;execution&gt;
          &lt;goals&gt;
            &lt;goal&gt;run&lt;/goal&gt;
          &lt;/goals&gt;
        &lt;/execution&gt;
      &lt;/executions&gt;
    &lt;/plugin&gt;
    ...
  &lt;/plugins&gt;
&lt;/build&gt;</pre>
<div class="blog_h2"><span class="graybg">调试</span></div>
<div class="blog_h3"><span class="graybg">远程调试</span></div>
<p>要调试Maven插件，可以使用远程调试的方式。</p>
<p>你需要用<pre class="crayon-plain-tag">mvnDebug</pre>而非mvn来调用Maven，此命令默认会监听8000端口，等待Debugger的连接。</p>
<p>示例：</p>
<pre class="crayon-plain-tag">mvnDebug cc.gmem.yun.maven.plugins:maven-archetype-plugin:3.1.1:create-from-project \
    -Darchetype.properties=archetype.properties -Darchetype.postPhase=package -Darchetype.partialArchetype=true

# Preparing to execute Maven in debug mode
# Listening for transport dt_socket at address: 8000</pre>
<div class="blog_h3"><span class="graybg">本地调试</span></div>
<p>配合Intellij IDEA等开发工具，可以很容易实现本地调试。</p>
<p>在你的插件项目中，创建一个Maven的运行配置，设置环境变量：</p>
<pre class="crayon-plain-tag">MAVEN_DEBUG_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000"</pre>
<p>然后Maven的Command line调用你的插件的Mojo，配置好了点击调试按钮即可。 </p>
<div class="blog_h1"><span class="graybg">Maven插件工具注解</span></div>
<div class="blog_h2"><span class="graybg">所有注解示例</span></div>
<pre class="crayon-plain-tag">import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.InstantiationStrategy;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Settings;

       // 此Mojo对应的目标的名称
@Mojo( name = "&lt;goal-name&gt;",
       aggregator = &lt;false|true&gt;, 
       configurator = "&lt;role hint&gt;",
       // 执行策略
       executionStrategy = "&lt;once-per-session|always&gt;",
       inheritByDefault = &lt;true|false&gt;,
       // 实例化策略
       instantiationStrategy = InstantiationStrategy.&lt;strategy&gt;,
       // 如果用户没有在POM中明确设置此Mojo绑定到的phase，那么绑定一个MojoExecution到那个phase
       defaultPhase = LifecyclePhase.&lt;phase&gt;,
       requiresDependencyResolution = ResolutionScope.&lt;scope&gt;,
       requiresDependencyCollection = ResolutionScope.&lt;scope&gt;,
       // 提示此Mojo需要被直接调用（而非绑定到生命周期阶段）
       requiresDirectInvocation = &lt;false|true&gt;,
       // 提示此Mojo不能在离线模式下运行
       requiresOnline = &lt;false|true&gt;,
       // 提示此Mojo必须在一个Maven项目内运行
       requiresProject = &lt;true|false&gt;,
       // 提示此Mojo是否线程安全，线程安全的Mojo支持在并行构建中被并发的调用
       threadSafe = &lt;false|true&gt; ) // (since Maven 3.0)

// 何时执行此Mojo
@Execute( goal = "&lt;goal-name&gt;",           // 如果提供goal，则隔离执行此Mojo
          phase = LifecyclePhase.&lt;phase&gt;, // 在此生命周期阶段自动执行此Mojo
          lifecycle = "&lt;lifecycle-id&gt;" )  // 在此生命周期中执行此Mojo
public class MyMojo
    extends AbstractMojo
{
    
    @Parameter( name = "parameter",
                // 在POM中可使用别名来配置参数
                alias = "myAlias",
                property = "a.property",
                defaultValue = "an expression, possibly with ${variables}",
                readonly = &lt;false|true&gt;,
                required = &lt;false|true&gt; )
    private String parameter;
 
    @Component( role = MyComponentExtension.class,
                hint = "..." )
    private MyComponent component;
 
 
    @Parameter( defaultValue = "${session}", readonly = true )
    private MavenSession session;
 
    @Parameter( defaultValue = "${project}", readonly = true )
    private MavenProject project;
 
    @Parameter( defaultValue = "${mojoExecution}", readonly = true )
    private MojoExecution mojo;
 
    @Parameter( defaultValue = "${plugin}", readonly = true )
    private PluginDescriptor plugin;
 
    @Parameter( defaultValue = "${settings}", readonly = true )
    private Settings settings;
 
    @Parameter( defaultValue = "${project.basedir}", readonly = true )
    private File basedir;
 
    @Parameter( defaultValue = "${project.build.directory}", readonly = true )
    private File target;
 
    public void execute()
    {
    }
}</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/maven-plugin-development">Maven插件开发</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/maven-plugin-development/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Maven原型系统</title>
		<link>https://blog.gmem.cc/maven-archetypes</link>
		<comments>https://blog.gmem.cc/maven-archetypes#comments</comments>
		<pubDate>Tue, 02 Jul 2019 01:27:30 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Maven]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=27831</guid>
		<description><![CDATA[<p>简介 原型（Archetype）是Maven的模板工具箱。利用原型，可以很容易的应用组织在软件实现方面的最佳实践，减轻搭建项目框架的负担。 原型机制支持“增量“方式，也就是说，你通过原型生成一个项目后，可以再次用另外一个原型来生成额外的内容到此项目中。这种增量机制允许将项目的不同方面/部分封装到多个原型中，增强可复用、可组合性。 创建项目 从Maven原型创建项目，需要使用maven-archetype-plugin插件。 引用原型仓库 一般情况下Maven原型都存放在远程仓库中，Maven中心仓库默认可用，你也可以修改Maven的settings.xml添加别的原型仓库： [crayon-69d57ac5411c0327494654/] 选择原型 交互式调用archetype:generate时，maven-archetype-plugin会提示你从某个仓库选择原型，输入对应的序号即可。 过滤原型 使用filter可以减少原型列表的长度： [crayon-69d57ac5411c4670842386/] 输入配置 选择原型后，maven-archetype-plugin会提示你输入一系列属性的值，最终生成新项目。 属性填充 生成项目时，你需要提供新项目的GAV、package，原型还可能规定了一系列其它必须属性，这些属性会用来： 填充Velocity模板中的变量 如果目录、文件名包含[crayon-69d57ac5411c6399150785-i/]，则被替换为对应的属性值 [crayon-69d57ac5411c8042506226-i/]被替换为项目的ArtifactId 在多模块项目中，[crayon-69d57ac5411ca648277089-i/]被替换为根项目的ArtifactId 开发原型 <a class="read-more" href="https://blog.gmem.cc/maven-archetypes">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/maven-archetypes">Maven原型系统</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>原型（Archetype）是Maven的模板工具箱。利用原型，可以很容易的应用组织在软件实现方面的最佳实践，减轻搭建项目框架的负担。</p>
<p>原型机制支持“增量“方式，也就是说，你通过原型生成一个项目后，可以<span style="background-color: #c0c0c0;">再次用另外一个原型来生成额外的内容</span>到此项目中。这种增量机制允许将项目的不同方面/部分封装到多个原型中，增强可复用、可组合性。</p>
<div class="blog_h1"><span class="graybg">创建项目</span></div>
<p>从Maven原型创建项目，需要使用maven-archetype-plugin插件。</p>
<div class="blog_h2"><span class="graybg">引用原型仓库</span></div>
<p>一般情况下Maven原型都存放在远程仓库中，Maven中心仓库默认可用，你也可以修改Maven的settings.xml添加别的原型仓库：</p>
<pre class="crayon-plain-tag">&lt;repository&gt;
  &lt;id&gt;archetype&lt;/id&gt;
  &lt;url&gt;https://m2.gmem.cc/path/to/repo/&lt;/url&gt;
&lt;/repository&gt;

&lt;!-- 如果需要身份验证 --&gt;
&lt;server&gt;
  &lt;id&gt;archetype&lt;/id&gt;
  &lt;username&gt;user.name&lt;/username&gt;
  &lt;password&gt;s3cr3t&lt;/password&gt;
&lt;/server&gt;</pre>
<div class="blog_h2"><span class="graybg">选择原型</span></div>
<p>交互式调用archetype:generate时，maven-archetype-plugin会提示你从某个仓库选择原型，输入对应的序号即可。</p>
<div class="blog_h3"><span class="graybg">过滤原型</span></div>
<p>使用filter可以减少原型列表的长度：</p>
<pre class="crayon-plain-tag">mvn archetype:generate -Dfilter=org.apache:struts </pre>
<div class="blog_h2"><span class="graybg">输入配置</span></div>
<p>选择原型后，maven-archetype-plugin会提示你输入一系列属性的值，最终生成新项目。</p>
<div class="blog_h2"><span class="graybg">属性填充</span></div>
<p>生成项目时，你需要提供新项目的GAV、package，原型还可能规定了一系列其它必须属性，这些属性会用来：</p>
<ol>
<li>填充Velocity模板中的变量</li>
<li>如果目录、文件名包含<pre class="crayon-plain-tag">__property__</pre>，则被替换为对应的属性值</li>
<li><pre class="crayon-plain-tag">__artifactId__</pre>被替换为项目的ArtifactId</li>
<li>在多模块项目中，<pre class="crayon-plain-tag">__rootArtifactId__</pre>被替换为根项目的ArtifactId</li>
</ol>
<div class="blog_h1"><span class="graybg">开发原型</span></div>
<p>你可以从零开始，开发一个原型，也可以从现有的项目反向生成原型。</p>
<div class="blog_h2"><span class="graybg">ArchetypeDescriptor</span></div>
<p>原型描述符用于存储原型的元数据，它存放在原型JAR的 META-INF/maven/archetype-metadata.xml中。下面是一个例子：</p>
<pre class="crayon-plain-tag">&lt;archetype-descriptor xmlns="https://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.1.0" 
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.1.0 
                      http://maven.apache.org/xsd/archetype-descriptor-1.1.0.xsd"
  &lt;!-- 原型的名称--&gt;
          &lt;!-- 是否偏原型 --&gt;
  name=.. partial=.. &gt;
  &lt;!-- 用户必须提供的属性列表 --&gt;
  &lt;requiredProperties&gt;
    &lt;requiredProperty key=.. &gt;
      &lt;defaultValue/&gt;
      &lt;validationRegex/&gt;
    &lt;/requiredProperty&gt;
  &lt;/requiredProperties&gt;
 
  &lt;!-- 文件集定义 --&gt;
  &lt;fileSets&gt;
    &lt;!-- 文件集说明了在生成项目的过程中，如何使用原型JAR中的文件 --&gt;
    &lt;!-- filtered 此文件集是否可以被过滤，如果true，则意味着这些文件被用作Velocity模板。否则原样拷贝 --&gt;
    &lt;!-- packaged 此文件集是否应该被生成/拷贝到package属性所指定的目录层次中 --&gt;
    &lt;!-- encoding 文件的编码方式 --&gt;
    &lt;fileSet filtered=.. packaged=.. encoding=.. &gt;
      &lt;directory/&gt; &lt;!-- 此文件集位于的目录 --&gt;
      &lt;includes&gt;&lt;include&gt;&lt;/include&gt;&lt;/includes&gt;  &lt;!-- 包含的文件列表，Ant语法 --&gt;
      &lt;excludes/&gt; &lt;!-- 排除的文件列表，Ant语法 --&gt;
    &lt;/fileSet&gt;
  &lt;/fileSets&gt;
 
  &lt;!-- 模块定义 --&gt;
  &lt;modules&gt;
    &lt;module id=.. dir=.. name=.. &gt;
      &lt;fileSets&gt;
        &lt;fileSet filtered=.. packaged=.. encoding=.. &gt;
          &lt;directory/&gt;
          &lt;includes/&gt;
          &lt;excludes/&gt;
        &lt;/fileSet&gt;
      &lt;/fileSets&gt;
      &lt;!-- 模块可以嵌套 --&gt;
      &lt;modules&gt;
        &lt;module&gt;...recursion...&lt;module&gt;
      &lt;/modules&gt;
    &lt;/module&gt;
  &lt;/modules&gt;
&lt;/archetype-descriptor&gt; </pre>
<p>下面是原型描述符的最小化示例：</p>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;archetype-descriptor name="basic"&gt;
  &lt;requiredProperties&gt;
    &lt;requiredProperty key="property-with-default"&gt;
      &lt;defaultValue&gt;default-value&lt;/defaultValue&gt;
    &lt;/requiredProperty&gt;
    &lt;requiredProperty key="property-without-default"/&gt;
  &lt;/requiredProperties&gt;
  &lt;fileSets&gt;
    &lt;fileSet filtered="true" packaged="true"&gt;
      &lt;directory&gt;src/main/java&lt;/directory&gt;
      &lt;includes&gt;
        &lt;include&gt;**/*.java&lt;/include&gt;
      &lt;/includes&gt;
    &lt;/fileSet&gt;
  &lt;/fileSets&gt;
&lt;/archetype-descriptor&gt;</pre>
<div class="blog_h2"><span class="graybg">从项目创建</span></div>
<p>本节我们演示一下，如何从一个现有的项目，反向生成原型。</p>
<div class="blog_h3"><span class="graybg">创建示例项目</span></div>
<p>首先创建一个空白的Maven项目：</p>
<pre class="crayon-plain-tag">mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.0 \
    -DgroupId=cc.gmem.study.mvn -DartifactId=archetype-proto -DinteractiveMode=false</pre>
<div class="blog_h3"><span class="graybg">创建原型</span></div>
<p>进入示例项目，生成原型：</p>
<pre class="crayon-plain-tag">cd JavaEE/projects/idea/archetype-study/archetype-proto/
mvn archetype:create-from-project</pre>
<p>如果没有错误，生成的原型项目位于target/generated-sources/archetype目录：</p>
<pre class="crayon-plain-tag">.
└── archetype                                         # 原型项目的根目录
    ├── pom.xml                                       # 原型项目的POM
    ├── src
    │   ├── main
    │   │   └── resources
    │   │       ├── archetype-resources               # 原始项目的所有内容，亦即从此原型生成的项目的所有内容
    │   │       │   ├── __artifactId__.iml
    │   │       │   ├── .idea
    │   │       │   └── src
    │   │       │       └── main
    │   │       │           └── java
    │   │       │               └── App.java          # 注意包结构消失了
    │   │       │   └── pom.xml
    │   │       └── META-INF
    │   │           └── maven
    │   │               └── archetype-metadata.xml    # 原型元数据
    │   └── test
    │       └── resources
    │           └── projects
    │               └── basic
    │                   ├── archetype.properties      # 用于测试原型
    │                   └── goal.txt
    └── target</pre>
<div class="blog_h3"><span class="graybg">排除文件</span></div>
<p>可以看到，上面的例子将原始项目的IDE元数据文件也放入到原型中了，这不是我们期望的，可以使用属性文件排除。</p>
<p>在原始项目中添加属性文件：</p>
<pre class="crayon-plain-tag">excludePatterns=.idea/**</pre>
<p>然后重新生成原型：</p>
<pre class="crayon-plain-tag">mvn clean  archetype:create-from-project -Darchetype.properties=archetype.properties</pre>
<p>可以看到.idea目录被排除掉了。</p>
<div class="blog_h3"><span class="graybg">消失的包前缀</span></div>
<p>从上面的例子还可以看到，生成的原型项目中App.java的包消失了。</p>
<p>实际上消失的是包前缀，它的值等于传递给create-from-project的package属性，如果不传递，则使用原始项目的包前缀。</p>
<p>生成的原型元数据中有如下片段：</p>
<pre class="crayon-plain-tag">&lt;fileSet filtered="true" packaged="true" encoding="UTF-8"&gt;
  &lt;directory&gt;src/main/java&lt;/directory&gt;
  &lt;includes&gt;
    &lt;include&gt;**/*.java&lt;/include&gt;
  &lt;/includes&gt;
&lt;/fileSet&gt;</pre>
<p>其中package=true含义是，从原型生成项目时，archetype-resources/src/main/java中的内容会被包裹在$package指定的目录结构中。</p>
<div class="blog_h3"><span class="graybg">抽取属性</span></div>
<p>在文件中添加一个配置：</p>
<pre class="crayon-plain-tag">application=App</pre>
<p>它的含义是，搜索原始项目的路径、文件中的文本，将App都更换为Application变量。对于本示例项目来说，其效果是：</p>
<ol>
<li>App.java的文件名替换为<pre class="crayon-plain-tag">__application__.java</pre></li>
<li>App.java中的类名被替换为<pre class="crayon-plain-tag">${application} </pre></li>
</ol>
<p>这是一种简单粗暴的查找替换，使用时务必小心。 </p>
<div class="blog_h2"><span class="graybg">测试原型</span></div>
<p>仍然使用上个例子，首先，构建并安装原型：</p>
<pre class="crayon-plain-tag">mvn clean  archetype:create-from-project \
    -Darchetype.properties=archetype.properties -Darchetype.postPhase=install</pre>
<p>本地原型目录会自动更新。</p>
<p>基于该原型创建一个新项目：</p>
<pre class="crayon-plain-tag">mvn archetype:generate -B -DarchetypeGroupId=cc.gmem.study.mvn \
    -DarchetypeArtifactId=archetype-proto-archetype -DarchetypeVersion=1.0-SNAPSHOT \
    -DgroupId=cc.gmem -DartifactId=project -Dversion=1.0-SNAPSHOT \
                              # 传递自定义属性
    -Dpackage=cc.gmem.project -Dapplication=ProjectApp</pre>
<p>检查生成的项目， 可以看到目录结构如下：</p>
<pre class="crayon-plain-tag">.
└── project
    ├── archetype-metadata.yaml
    ├── pom.xml
    └── src
        └── main
            └── java
                └── cc
                    └── gmem
                        └── project
                            ├── ProjectApp.java
                            └── service
                                └── ProjectAppService.java </pre>
<div class="blog_h1"><span class="graybg">maven-archetype-plugin</span></div>
<p>要在Maven中使用原型来生成项目骨架，必须依赖于maven-archetype-plugin。此插件依赖Java 7+版本，主流IDE内置了该插件。</p>
<p>maven-archetype-plugin使用<a href="/velocity-study-note">Velocity</a>作为模板引擎。</p>
<div class="blog_h2"><span class="graybg">maven-archetype</span></div>
<p>这是Maven原型项目的packaging，这种打包方式的生命周期绑定以下目标</p>
<ol>
<li>archetype:jar，绑定到package阶段，生成原型的JAR包</li>
<li>archetype:integration-test，绑定到integration-test阶段</li>
<li>archetype:update-local-catalog，绑定到install阶段，更新本地原型目录</li>
</ol>
<div class="blog_h2"><span class="graybg">generate </span></div>
<p>从原型生成项目骨架：</p>
<ol>
<li>如果从原型创建新的Maven项目，则输出到名为项目artifactId的目录</li>
<li>如果使用一个<span style="background-color: #c0c0c0;">偏原型（partial archetype）</span>更新已有项目，则输出到当前目录</li>
</ol>
<p>用户需要从原型目录中选择一个原型，从远程仓库将原型拉取下来，然后生成项目。调用示例：</p>
<pre class="crayon-plain-tag"># -B表示批处理模式，等价于-DinteractiveMode=false
mvn archetype:generate -B -DarchetypeGroupId=org.apache.maven.archetypes \
    -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.1 \
    -DgroupId=com.company -DartifactId=project -Dversion=1.0-SNAPSHOT \
    -Dpackage=com.company.project</pre>
<p>其中通过-D传递的，是目标的参数。</p>
<div class="blog_h3"><span class="graybg">目标参数</span></div>
<p>可用参数包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>interactiveMode</td>
<td>是否以交互的方式生成</td>
</tr>
<tr>
<td>archetypeCatalog</td>
<td>
<p>从哪个原型目录中读取原型：</p>
<ol>
<li>local 本地仓库</li>
<li>remote 远程仓库，Maven中心仓库或者其镜像</li>
<li>internal 内部目录</li>
</ol>
</td>
</tr>
<tr>
<td>archetypeGroupId</td>
<td rowspan="3">从什么原型生成，原型的坐标</td>
</tr>
<tr>
<td>archetypeArtifactId</td>
</tr>
<tr>
<td>archetypeVersion</td>
</tr>
<tr>
<td>filter</td>
<td>为显示的原型列表提供过滤器</td>
</tr>
<tr>
<td>goals</td>
<td>在新生成的项目上执行的目标</td>
</tr>
<tr>
<td>outputDirectory</td>
<td>输出目录</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">create-from-project</span></div>
<p>该目标用于从现有的项目（以下称原始项目）反向生成原型。它会读取项目中的源文件、资源文件，你通过.property指定的原型属性文件，并产生一个原型项目。原始项目中的文本文件会被转换为Velocity模板。</p>
<div class="blog_h3"><span class="graybg">目标参数</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">参数/属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>
<p>archetypeFilteredExtentions<br />archetype.filteredExtentions</p>
</td>
<td>检查哪些扩展名，以获取项目的文本文件</td>
</tr>
<tr>
<td>
<p>archetypeLanguages<br />archetype.languages</p>
</td>
<td>从什么目录来查项目的源码主包</td>
</tr>
<tr>
<td>
<p>archetypePostPhase<br />archetype.postPhase</p>
</td>
<td>
<p>在生成原型后，针对原型执行什么目标</p>
<p>默认package，可选package, integration-test, install, deploy</p>
</td>
</tr>
<tr>
<td>defaultEncoding<br />archetype.encoding</td>
<td>Velocity模板的编码方式。默认UTF-8</td>
</tr>
<tr>
<td>interactive</td>
<td>是否交互式的生成原型</td>
</tr>
<tr>
<td>keepParent<br />archetype.keepParent</td>
<td>从原型创建新的项目时，其POM中的parent引用和原始项目一致</td>
</tr>
<tr>
<td>outputDirectory</td>
<td>原型项目的输出目录</td>
</tr>
<tr>
<td>packageName</td>
<td>项目包名，原始项目文本文件中出现的packageName会被替换为Velocity变量</td>
</tr>
<tr>
<td>partialArchetype<br />archetype.partialArchetype</td>
<td>是否生成为偏模板</td>
</tr>
<tr>
<td>preserveCData<br />archetype.preserveCData</td>
<td>使用CDATA preservation来创建Velocity模板</td>
</tr>
<tr>
<td>propertyFile<br />archetype.properties</td>
<td>
<p>包含了此插件配置的属性文件，如果提供该参数，则创建会从中读取属性。默认值archetype.properties</p>
<p>属性分为两类：标准属性、定制属性</p>
<p>标准属性包括：</p>
<ol>
<li>package</li>
<li>archetype.languages。默认值java, xml, txt, groovy, cs, mdo, aj, jsp, gsp, vm, html, xhtml, properties, .classpath, .project</li>
<li>groupId  artifactId version 生成的项目的默认GAV</li>
<li>excludePatterns 不包含在生成的原型中的路径列表，逗号分隔</li>
<li>archetype.filteredExtensions 。默认值java, groovy, csharp, aspectj</li>
</ol>
<p>定制属性：你可以指定自己的定制属性，需要了解：</p>
<ol>
<li>定制属性的名称不能包含点号（Velocity限制）</li>
<li>如果设置了定制属性NAME=VALUE，则该插件会搜索项目文本文件中的VALUE，并全部替换为Velicoty变量NAME</li>
</ol>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">crawl</span></div>
<p>爬取文件系统中的Maven仓库，产生一个Maven原型目录文件。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/maven-archetypes">Maven原型系统</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/maven-archetypes/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>在Kubernetes中管理和使用Jenkins</title>
		<link>https://blog.gmem.cc/jenkins-ops-on-k8s-clusters</link>
		<comments>https://blog.gmem.cc/jenkins-ops-on-k8s-clusters#comments</comments>
		<pubDate>Thu, 20 Jun 2019 05:45:26 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[PaaS]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=27629</guid>
		<description><![CDATA[<p>前言 如何在云原生环境下进行CI/CD，我们先前有一些经验： 使用Jenkins + Jenkins的Kubernete插件 在K8S中按需、动态创建执行CI/CD流水线的Agent 开发Jenkins共享库，简化编写流水线的难度 为每套环境（development staging production，除了production允许有多套）创建独立的命名空间，对应K8S的namespace和Jenkins的folder 利用一个特殊的Jenkins Job来复制、创建新的环境 先前我们搭建的云原生平台主要供内部使用，所以不注重产品化。虽然环境可以复制，但是每个Jenkins Job还是依赖于人工通过Jenkins UI创建。 这种人肉管理Job的方式很不方便，现在做的产品化PaaS平台（以下简称平台）也不希望使用Jenkins UI，仅仅想将它作为流水线执行引擎的一种实现。 Jenkins REST API 除了Web UI之外，Jenkins还提供了RESTful风格的API，平台可以通过这些API和Jenkins交互，API的端点路径位于/api。 <a class="read-more" href="https://blog.gmem.cc/jenkins-ops-on-k8s-clusters">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/jenkins-ops-on-k8s-clusters">在Kubernetes中管理和使用Jenkins</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">前言</span></div>
<p>如何在云原生环境下进行CI/CD，我们先前有一些经验：</p>
<ol>
<li>使用Jenkins + Jenkins的Kubernete插件</li>
<li>在K8S中按需、动态创建执行CI/CD流水线的Agent</li>
<li>开发Jenkins共享库，简化编写流水线的难度</li>
<li>为每套环境（development staging production，除了production允许有多套）创建独立的命名空间，对应K8S的namespace和Jenkins的folder</li>
<li>利用一个特殊的Jenkins Job来复制、创建新的环境</li>
</ol>
<p>先前我们搭建的云原生平台主要供内部使用，所以不注重产品化。虽然环境可以复制，但是每个Jenkins Job还是依赖于人工通过Jenkins UI创建。</p>
<p>这种人肉管理Job的方式很不方便，现在做的产品化PaaS平台（以下简称平台）也不希望使用Jenkins UI，仅仅想将它作为流水线执行引擎的一种实现。</p>
<div class="blog_h1"><span class="graybg">Jenkins REST API</span></div>
<p>除了Web UI之外，Jenkins还提供了<a href="https://wiki.jenkins.io/display/JENKINS/Remote+access+API">RESTful风格的API</a>，平台可以通过这些API和Jenkins交互，API的端点路径位于<a href="https://ci.gmem.cc/api/">/api</a>。</p>
<p>目前，REST API有三种风格：</p>
<ol>
<li>XML API，载荷格式为XML，支持XPath查询。API端点路径位于<a href="https://ci.gmem.cc/api/xml?tree=jobs[name],views[name,jobs[name]]">https://ci.gmem.cc/api/xml?...</a></li>
<li>JSON API，载荷格式JSON，支持JSONP</li>
<li>Python API，载荷格式可以通过<pre class="crayon-plain-tag">eval(urllib.urlopen("...").read())</pre>转换为Python对象</li>
</ol>
<p>通过REST API，可以创建Jenkins Job、复制Jenkins Job、管理构建队列，以及重启Jenkins服务器。 </p>
<div class="blog_h2"><span class="graybg">API封装</span></div>
<p>直接调用REST API比较麻烦，以下语言已经有了对API的封装，也就是Jenkins 客户端库：</p>
<ol>
<li>Ruby：<a href="https://rubygems.org/gems/jenkins_api_client">Jenkins API Client</a></li>
<li>Java：<a href="https://github.com/cdancy/jenkins-rest">jenkins-rest<br /> </a></li>
<li>Python：<a href="http://pypi.python.org/pypi/jenkinsapi">JeninsAPI</a></li>
</ol>
<div class="blog_h1"><span class="graybg">job-dsl-plugin</span></div>
<p>使用Jenkins REST API，你的应用程序就可以管理Jenkins服务器，创建Jenkins Job了。但是，社区还有更具K8S风格的解决方案 —— Jenkins Operator，本章先讨论它的依赖job-dsl-plugin。</p>
<p><a href="https://github.com/jenkinsci/job-dsl-plugin">Jenkins Job DSL / Plugin</a>项目包含两个部分：</p>
<ol>
<li>一个DSL，允许用户用Groovy语言来定义一个Job</li>
<li>一个Jenkins 插件，根据用户定义的上述DSL，生成Jenkins Job</li>
</ol>
<p>此插件的目的是，尽可能简化Jenkins Job的创建，它允许用户使用编程的方式来配置Job，并且把所有Job的通用元素隐藏在DSL背后。</p>
<p>举例来说，你有个项目，需要单元测试、构建、集成测试、部署四个任务，那么基于DSL只需要编写：</p>
<pre class="crayon-plain-tag">def gitUrl = 'git://github.com/jenkinsci/job-dsl-plugin.git'

job('PROJ-unit-tests') {
    scm {
        git(gitUrl)
    }
    triggers {
        scm('*/15 * * * *')
    }
    steps {
        maven('-e clean test')
    }
}

job('PROJ-sonar') {
    scm {
        git(gitUrl)
    }
    triggers {
        cron('15 13 * * *')
    }
    steps {
        maven('sonar:sonar')
    }
}

job('PROJ-integration-tests') {
    scm {
        git(gitUrl)
    }
    triggers {
        cron('15 1,13 * * *')
    }
    steps {
        maven('-e clean integration-test')
    }
}

job('PROJ-release') {
    scm {
        git(gitUrl)
    }
    steps {
        maven('-B release:prepare release:perform')
        shell('cleanup.sh')
    }
}</pre>
<div class="blog_h2"><span class="graybg">特性</span></div>
<p>job-dsl-plugin的特性包括：</p>
<ol>
<li>可以通过DSL直接控制XML，因此任何config.xml可以存在的片段都可以通过DSL操控</li>
<li>DSL提供了很多助手方法，方便通用的Job配置，包括scm、trigger、steps</li>
<li>DSL可以直接编写在Job里</li>
<li>DSL可以放在SCM中，并且通过标准的SCM触发机制拉取</li>
</ol>
<div class="blog_h2"><span class="graybg">基本用法</span></div>
<p>使用job-dsl-plugin的步骤如下：</p>
<ol>
<li>创建一个 Free-style Project类型的Jenkins Job，用于运行DSL，这种Job叫做<span style="background-color: #c0c0c0;">Seed Job</span></li>
<li>配置Seed Job，添加Process Job DSLs类型的Build Step，在其中编写DSL</li>
<li>运行Seed Job来生成新的、实际有用的Job</li>
</ol>
<div class="blog_h2"><span class="graybg">DSL</span></div>
<p>本节列出job-dsl-plugin的常用DSL。完整的DSL API请参考<a href="https://jenkinsci.github.io/job-dsl-plugin/">官方文档</a>。</p>
<div class="blog_h3"><span class="graybg">Job</span></div>
<p>下面的API用于创建不同类型的Job：</p>
<pre class="crayon-plain-tag">// freeStyleJob的别名
job(String name, Closure closure = null)          

freeStyleJob(String name, Closure closure = null) 

buildFlowJob(String name, Closure closure = null) 

ivyJob(String name, Closure closure = null)       

matrixJob(String name, Closure closure = null)    

mavenJob(String name, Closure closure = null)     

multiJob(String name, Closure closure = null)     

workflowJob(String name, Closure closure = null)  

multibranchWorkflowJob(String name, Closure closure = null)</pre>
<p>上述DSL都返回一个Job对象，并且可以后续继续修改Job： </p>
<pre class="crayon-plain-tag">def myJob = freeStyleJob('SimpleJob')
myJob.with {
    description 'A Simple Job'
}</pre>
<div class="blog_h3"><span class="graybg">View</span></div>
<p>下面的API用于创建视图：</p>
<pre class="crayon-plain-tag">listView(String name, Closure closure = null)            

sectionedView(String name, Closure closure = null)       

nestedView(String name, Closure closure = null)          

deliveryPipelineView(String name, Closure closure = null)

buildPipelineView(String name, Closure closure = null)   

buildMonitorView(String name, Closure closure = null)    

categorizedJobsView(String name, Closure closure = null)</pre>
<div class="blog_h3"><span class="graybg">Folder</span></div>
<p>如果安装（现在的版本默认已经安装）了CloudBees Folders Plugin插件，下面的API可以用于创建目录：</p>
<pre class="crayon-plain-tag">folder(String name, Closure closure = null)</pre>
<p>目录内的对象可以用路径的形式创建：</p>
<pre class="crayon-plain-tag">folder('project-a')

freeStyleJob('project-a/compile')

listView('project-a/pipeline')

folder('project-a/testing')</pre>
<div class="blog_h3"><span class="graybg">Queue</span></div>
<p>下面的API用于调度一个Job：</p>
<pre class="crayon-plain-tag">queue(String jobName)
queue(Job job)</pre>
<div class="blog_h3"><span class="graybg">File IO</span></div>
<p>下面的API可以用于读取工作区中的文件：</p>
<pre class="crayon-plain-tag">InputStream streamFileFromWorkspace(String filePath)
String readFileFromWorkspace(String filePath)
String readFileFromWorkspace(String jobName, String filePath)</pre>
<div class="blog_h3"><span class="graybg">Logging</span></div>
<p>任何DSL脚本都可以访问名为out的变量，调用它可以打印日志到构建日志（Console Output）中：</p>
<pre class="crayon-plain-tag">out.println('Hello from a Job DSL script!')</pre>
<p>如果希望输出信息到Jenkins日志，可以：</p>
<pre class="crayon-plain-tag">import java.util.logging.Logger

Logger logger = Logger.getLogger('org.example.jobdsl')
logger.info('Hello from a Job DSL script!') </pre>
<div class="blog_h3"><span class="graybg">Configure</span></div>
<p>如果Job DSL不支持某种Option，你可以使用Configure Block来扩展DSL的能力。</p>
<pre class="crayon-plain-tag">job('example') {
    ...
    configure { project -&gt;
        project / buildWrappers / EnvInjectPasswordWrapper {
            injectGlobalPasswords(true)
        }
    }
}</pre>
<p>Configure Block允许你对配置文件config.xml（<span style="background-color: #c0c0c0;">Jenkins的各种对象的配置都放在自己独立目录的config.xml中</span>）进行直接访问。此块的闭包接收到一个<a href="http://docs.groovy-lang.org/latest/html/gapi/groovy/util/Node.html">groovy.util.Node</a>参数，表示一个XML节点，具体是什么节点取决于Configure Block所在的上下文：</p>
<pre class="crayon-plain-tag">job('example-1') {
    configure { node -&gt;
        // node为project
    }
}

mavenJob('example-2') {
    configure { node -&gt;
        // node为maven2-moduleset
    }
}

listView('example') {
    configure { node -&gt;
        // node为hudson.model.ListView
    }
}</pre>
<p>你可以使用groovy.util.Node的API操控Jenkins对象的配置元素，但是代码会很丑陋。DSL提供了特殊的语法以简化XML操作，需要注意：</p>
<ol>
<li>所有查询返回NodeList，因此像操控第一个元素的话需要调用 nodes[0]</li>
<li>操作符<pre class="crayon-plain-tag">+</pre>用于添加兄弟节点</li>
<li>不能访问不存在的子元素</li>
<li>如果元素名和Groovy关键字、Groovy操作符、任何上层上下文对象的属性（例如properties）冲突，则必须使用引号：<br />
<pre class="crayon-plain-tag">configure {
    //   和上下文对象属性冲突  带有点号
    it / 'properties' / 'com.example.Test' {
        'switch'('on')
    }
}</pre>
</li>
<li>为了简化操作，两个操作符被重写：
<ol>
<li><pre class="crayon-plain-tag">/</pre> 根据名称查找子节点，如果子节点不存在，则会创建。可以指定子节点属性以匹配第一个带有属性的子节点</li>
<li><pre class="crayon-plain-tag">&lt;&lt;</pre>添加为子节点，右操作数可以是字符串，为子节点名称；也可以是closure，其行为类似于NodeBuilder</li>
</ol>
</li>
</ol>
<p>下面是一些例子：</p>
<pre class="crayon-plain-tag">// 简单的例子
configure {
    // it是groovy.util.Node对象，表示Job的config.xml的根元素project
    def aNode = it
    // anotherNode是project的子元素
    def anotherNode = aNode / 'blockBuildWhenDownstreamBuilding'
    // 设置此元素的值
    anotherNode.setValue('true')

    // 链式调用，注意 / 的操作符优先级很低
    (it / 'blockBuildWhenUpstreamBuilding').setValue('true')
}


// 添加权限
job('example') {
    configure { project -&gt;
        def matrix = project / 'properties' / 'hudson.security.AuthorizationMatrixProperty' {
            permission('hudson.model.Item.Configure:jill')
            permission('hudson.model.Item.Configure:jack')
        }
        matrix.appendNode('permission', 'hudson.model.Run.Delete:jryan')
    }
}
/*
对应XML：
&lt;project&gt;
    &lt;properties&gt;
        &lt;hudson.security.AuthorizationMatrixProperty&gt;
            &lt;permission&gt;hudson.model.Item.Configure:jill&lt;/permission&gt;
            &lt;permission&gt;hudson.model.Item.Configure:jack&lt;/permission&gt;
            &lt;permission&gt;hudson.model.Run.Delete:jryan&lt;/permission&gt;
        &lt;/hudson.security.AuthorizationMatrixProperty&gt;
    &lt;/properties&gt;
&lt;/project&gt;
对应DSL：
job('example') {
    authorization {
        permission(Permissions.ItemConfigure, 'jill')
        permission(Permissions.ItemConfigure, 'jack')
        permission(Permissions.RunDelete, 'jryan')
    }
}
*/


// 配置日志轮换插件
job('example') {
    configure { project -&gt;
        // Doesn't take into account existing node
        project &lt;&lt; logRotator {
            daysToKeep(-1)
            numToKeep(10)
            artifactDaysToKeep(-1)
            artifactNumToKeep(-1)
        }

        // Alters existing value
        (project / logRotator / daysToKeep).value = 2
    }
}
/*
对应XML：
&lt;project&gt;
    &lt;logRotator&gt;
        &lt;daysToKeep&gt;2&lt;/daysToKeep&gt;
        &lt;numToKeep&gt;10&lt;/numToKeep&gt;
        &lt;artifactDaysToKeep&gt;-1&lt;/artifactDaysToKeep&gt;
        &lt;artifactNumToKeep&gt;-1&lt;/artifactNumToKeep&gt;
    &lt;/logRotator&gt;
&lt;/project&gt;
对应DSL：
job('example') {
    logRotator(2, 10, -1, -1)
}
*/


// 配置电子邮件提醒
def emailTrigger = {
    trigger {
        email {
            recipientList '$PROJECT_DEFAULT_RECIPIENTS'
            subject '$PROJECT_DEFAULT_SUBJECT'
            body '$PROJECT_DEFAULT_CONTENT'
            sendToDevelopers true
            sendToRequester false
            includeCulprits false
            sendToRecipientList true
        }
    }
}

job('example') {
    configure { project -&gt;
        project / publishers &lt;&lt; 'hudson.plugins.emailext.ExtendedEmailPublisher' {
              recipientList 'Engineering@company.com'
              configuredTriggers {
                  'hudson.plugins.emailext.plugins.trigger.FailureTrigger' emailTrigger
                  'hudson.plugins.emailext.plugins.trigger.FixedTrigger' emailTrigger
              }
              contentType 'default'
              defaultSubject '$DEFAULT_SUBJECT'
              defaultContent '$DEFAULT_CONTENT'
        }
    }
}
/*
对应XML：
&lt;project&gt;
    &lt;publishers&gt;
        &lt;hudson.plugins.emailext.ExtendedEmailPublisher&gt;
            &lt;recipientList&gt;Engineering@company.com&lt;/recipientList&gt;
            &lt;configuredTriggers&gt;
                &lt;hudson.plugins.emailext.plugins.trigger.FailureTrigger&gt;
                    &lt;email&gt;
                        &lt;recipientList&gt;$PROJECT_DEFAULT_RECIPIENTS&lt;/recipientList&gt;
                        &lt;subject&gt;$PROJECT_DEFAULT_SUBJECT&lt;/subject&gt;
                        &lt;body&gt;$PROJECT_DEFAULT_CONTENT&lt;/body&gt;
                        &lt;sendToDevelopers&gt;true&lt;/sendToDevelopers&gt;
                        &lt;sendToRequester&gt;false&lt;/sendToRequester&gt;
                        &lt;includeCulprits&gt;false&lt;/includeCulprits&gt;
                        &lt;sendToRecipientList&gt;true&lt;/sendToRecipientList&gt;
                    &lt;/email&gt;
                &lt;/hudson.plugins.emailext.plugins.trigger.FailureTrigger&gt;
                &lt;hudson.plugins.emailext.plugins.trigger.FixedTrigger&gt;
                    &lt;email&gt;
                        &lt;recipientList&gt;$PROJECT_DEFAULT_RECIPIENTS&lt;/recipientList&gt;
                        &lt;subject&gt;$PROJECT_DEFAULT_SUBJECT&lt;/subject&gt;
                        &lt;body&gt;$PROJECT_DEFAULT_CONTENT&lt;/body&gt;
                        &lt;sendToDevelopers&gt;true&lt;/sendToDevelopers&gt;
                        &lt;sendToRequester&gt;false&lt;/sendToRequester&gt;
                        &lt;includeCulprits&gt;true&lt;/includeCulprits&gt;
                        &lt;sendToRecipientList&gt;true&lt;/sendToRecipientList&gt;
                    &lt;/email&gt;
                &lt;/hudson.plugins.emailext.plugins.trigger.FixedTrigger&gt;
            &lt;/configuredTriggers&gt;
            &lt;contentType&gt;default&lt;/contentType&gt;
            &lt;defaultSubject&gt;$DEFAULT_SUBJECT&lt;/defaultSubject&gt;
            &lt;defaultContent&gt;$DEFAULT_CONTENT&lt;/defaultContent&gt;
        &lt;/hudson.plugins.emailext.ExtendedEmailPublisher&gt;
    &lt;/publishers&gt;
&lt;/project&gt;
对应DSL：
job('example') {
    publishers {
        extendedEmail('Engineering@company.com') {
            trigger(triggerName: 'Failure', recipientList: '$PROJECT_DEFAULT_RECIPIENTS')
            trigger(triggerName: 'Fixed', recipientList: '$PROJECT_DEFAULT_RECIPIENTS')
        }
    }
}
*/


// 为Job添加Shell Step
job('example') {
    configure { project -&gt;
        project / builders / 'hudson.tasks.Shell' {
            command 'echo "Hello" &gt; ${WORKSPACE}/out.txt'
        }
    }
}
/*
对应XML：
&lt;project&gt;
    &lt;builders&gt;
        &lt;hudson.tasks.Shell&gt;
            &lt;command&gt;echo "Hello" &gt; ${WORKSPACE}/out.txt&lt;/command&gt;
        &lt;/hudson.tasks.Shell&gt;
    &lt;/builders&gt;
&lt;/project&gt;
对应DSL：
job('example') {
    steps {
        shell 'echo "Hello" &gt; ${WORKSPACE}/out.txt'
    }
}
*/


// 配置Gradle
job('example') {
    configure { project -&gt;
        project / builders &lt;&lt; 'hudson.plugins.gradle.Gradle' {
            description ''
            switches '-Dtiming-multiple=5'
            tasks 'test'
            rootBuildScriptDir ''
            buildFile ''
            useWrapper 'true'
            wrapperScript 'gradlew'
        }
    }
}
/*
对应XML：
&lt;project&gt;
    &lt;builders&gt;
        &lt;hudson.plugins.gradle.Gradle&gt;
            &lt;description/&gt;
            &lt;switches&gt;-Dtiming-multiple=5&lt;/switches&gt;
            &lt;tasks&gt;test&lt;/tasks&gt;
            &lt;rootBuildScriptDir/&gt;
            &lt;buildFile/&gt;
            &lt;useWrapper&gt;true&lt;/useWrapper&gt;
            &lt;wrapperScript&gt;gradlew&lt;/wrapperScript&gt;
        &lt;/hudson.plugins.gradle.Gradle&gt;
    &lt;/builders&gt;
&lt;/project&gt;
对应DSL：
job('example') {
    steps {
        gradle('test', '-Dtiming-multiple-5', true) {
            it / wrapperScript 'gradlew'
        }
    }
}
*/


// 配置SVN
job('example') {
    configure { project -&gt;
        project.remove(project / scm) // remove the existing 'scm' element
        project / scm(class: 'hudson.scm.SubversionSCM') {
            locations {
                'hudson.scm.SubversionSCM_-ModuleLocation' {
                    remote 'http://svn.apache.org/repos/asf/tomcat/maven-plugin/trunk'
                    local '.'
                }
            }
            excludedRegions ''
            includedRegions ''
            excludedUsers ''
            excludedRevprop ''
            excludedCommitMessages ''
            workspaceUpdater(class: "hudson.scm.subversion.UpdateUpdater")
        }
    }
}
/*
对应XML：
&lt;project&gt;
    &lt;scm class="hudson.scm.SubversionSCM"&gt;
        &lt;locations&gt;
            &lt;hudson.scm.SubversionSCM_-ModuleLocation&gt;
                &lt;remote&gt;http://svn.apache.org/repos/asf/tomcat/maven-plugin/trunk&lt;/remote&gt;
                &lt;local&gt;.&lt;/local&gt;
            &lt;/hudson.scm.SubversionSCM_-ModuleLocation&gt;
        &lt;/locations&gt;
        &lt;excludedRegions/&gt;
        &lt;includedRegions/&gt;
        &lt;excludedUsers/&gt;
        &lt;excludedRevprop/&gt;
        &lt;excludedCommitMessages/&gt;
        &lt;workspaceUpdater class="hudson.scm.subversion.UpdateUpdater"/&gt;
    &lt;/scm&gt;
&lt;/project&gt;
对应DSL：
job('example') {
    scm {
        svn('http://svn.apache.org/repos/asf/tomcat/maven-plugin/trunk')
    }
}
*/


// 配置GIT
def gitConfigWithSubdir(subdir, remote) {
    { node -&gt;
        // use remote name given
        node / 'userRemoteConfigs' / 'hudson.plugins.git.UserRemoteConfig' / name(remote)

        // use local dir given
        node / 'extensions' &lt;&lt; 'hudson.plugins.git.extensions.impl.RelativeTargetDirectory' {
            relativeTargetDir subdir
        }

        // clean after checkout
        node / 'extensions' &lt;&lt; 'hudson.plugins.git.extensions.impl.CleanCheckout'()
    }
}

job('example') {
    scm {
        git(
            'git@server:account/repo1.git',
            'remoteB/master',
            gitConfigWithSubdir('repo1', 'remoteB')
        )
    }
}
/*
对应XML：
&lt;project&gt;
    &lt;scm class='hudson.plugins.git.GitSCM'&gt;
        &lt;userRemoteConfigs&gt;
            &lt;hudson.plugins.git.UserRemoteConfig&gt;
                &lt;url&gt;git@server:account/repo1.git&lt;/url&gt;
                &lt;name&gt;remoteB&lt;/name&gt;
            &lt;/hudson.plugins.git.UserRemoteConfig&gt;
        &lt;/userRemoteConfigs&gt;
        &lt;branches&gt;
            &lt;hudson.plugins.git.BranchSpec&gt;
                &lt;name&gt;remoteB/master&lt;/name&gt;
            &lt;/hudson.plugins.git.BranchSpec&gt;
        &lt;/branches&gt;
        &lt;extensions&gt;
            &lt;hudson.plugins.git.extensions.impl.RelativeTargetDirectory&gt;
                &lt;relativeTargetDir&gt;repo1&lt;/relativeTargetDir&gt;
            &lt;/hudson.plugins.git.extensions.impl.RelativeTargetDirectory&gt;
            &lt;hudson.plugins.git.extensions.impl.CleanCheckout/&gt;
        &lt;/extensions&gt;
    &lt;/scm&gt;
&lt;/project&gt;
对应DSL：
job('example') {
    scm {
        git {
            remote {
                name 'remoteB'
                url 'git@server:account/repo1.git'
            }
            extensions {
                relativeTargetDirectory('repo1')
                cleanAfterCheckout()
            }
        }
    }
}
*/


// 配置Pre-requisite前置步骤
job('example') {
    configure { project -&gt;
        project / builders / 'dk.hlyh.ciplugins.prereqbuildstep.PrereqBuilder' {
            projects('project-A,project-B')
            warningOnly(false)
        }
    }
}
/*
对应XML：
&lt;project&gt;
    &lt;builders&gt;
        &lt;dk.hlyh.ciplugins.prereqbuildstep.PrereqBuilder&gt;
            &lt;projects&gt;project-A,project-B&lt;/projects&gt;
            &lt;warningOnly&gt;false&lt;/warningOnly&gt;
        &lt;/dk.hlyh.ciplugins.prereqbuildstep.PrereqBuilder&gt;
    &lt;/builders&gt;
&lt;/project&gt;
对应DSL：
job('example') {
    steps {
        prerequisite('project-A, project-B')
    }
}
*/


// 配置块重用
def switchOn = {
    it / 'properties' / 'com.example.Test' {
        'switch'('on')
    }
}
job('example-1') {
    configure switchOn
}

Closure switchOnOrOff(String value) {
    return {
        it / 'properties' / 'com.example.Test' {
            'switch'(value)
        }
    }
}
job('example-1') {
    configure switchOnOrOff('on')
}


// 根据属性选择子元素、添加孙子元素两个
job('example') {
    configure {
        def scm = it / scm(class: 'org.MyScm')
        scm &lt;&lt; 'aChild' {
            serverUrl('http://example.org/product-a')
        }
        scm &lt;&lt; 'aChild' {
            serverUrl('http://example.org/product-b')
        }
    }
}
/*
&lt;project&gt;
    &lt;scm class='org.MyScm'&gt;
        &lt;aChild&gt;
            &lt;serverUrl&gt;http://example.org/product-a&lt;/serverUrl&gt;
        &lt;/aChild&gt;
        &lt;aChild&gt;
            &lt;serverUrl&gt;http://example.org/product-b&lt;/serverUrl&gt;
        &lt;/aChild&gt;
    &lt;/scm&gt;
&lt;/project&gt;
*/</pre>
<div class="blog_h3"><span class="graybg">__FILE__</span></div>
<p>使用此变量可以获得被执行的DSL脚本的位置： </p>
<pre class="crayon-plain-tag">println("script directory: ${new File(__FILE__).parent.absolutePath}")</pre>
<div class="blog_h3"><span class="graybg">SEED_JOB</span></div>
<p>通过此变量可以访问运行当前DSL的种子任务： </p>
<pre class="crayon-plain-tag">job('example') {
    quietPeriod(SEED_JOB.quietPeriod)
}</pre>
<div class="blog_h1"><span class="graybg">JCasC</span></div>
<p>有了job-dsl-plugin之后，你仍然需要手工登陆到Jenkins UI来创建种子任务。可以考虑安装<a href="https://github.com/jenkinsci/configuration-as-code-plugin/">Jenkins Configuration as Code</a>插件，该插件允许你编写YAML来配置Jenkins。</p>
<p>下面是JCasC配置Jenkins安全的片段：</p>
<pre class="crayon-plain-tag">jenkins:
  securityRealm:
    ldap:
      configurations:
        - groupMembershipStrategy:
            fromUserRecord:
              attributeName: "memberOf"
          inhibitInferRootDN: false
          rootDN: "dc=acme,dc=org"
          server: "ldaps://ldap.acme.org:1636"</pre>
<div class="blog_h2"><span class="graybg">起步</span></div>
<ol>
<li>安装Jenkins和本插件</li>
<li>配置环境变量CASC_JENKINS_CONFIG，它可以是：
<ol>
<li>指向包含一系列配置文件的目录，例如/var/jenkins_home/casc_configs</li>
<li>指向配置文件</li>
<li>指向URL</li>
</ol>
</li>
<li>在Manage Jenkins ⇨ Configuration as Code管理本插件</li>
</ol>
<div class="blog_h2"><span class="graybg">配置</span></div>
<p>本节给出一个样例，更多的配置示例参考<a href="https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/demos">官方文档</a>。</p>
<pre class="crayon-plain-tag">jenkins:
  # 安全配置
  securityRealm:
    ldap:
      configurations:
        - groupMembershipStrategy:
            fromUserRecord:
              attributeName: "memberOf"
          inhibitInferRootDN: false
          rootDN: "dc=acme,dc=org"
          server: "ldaps://ldap.acme.org:1636"
  # 节点配置
  nodes:
    - permanent:
        name: "static-agent"
        remoteFS: "/home/jenkins"
        launcher:
          jnlp:

  slaveAgentPort: 50000
  agentProtocols:
    - "jnlp2"
# 开发工具配置
tool:
  git:
    installations:
      - name: git
        home: /usr/local/bin/git
unclassified:
  mailer:
    adminAddress: admin@acme.org
    replyToAddress: do-not-reply@acme.org
    # Note that this does not work right now
    #smtpHost: smtp.acme.org
    smtpPort: 4441

# 凭证信息
credentials:
  system:
    domainCredentials:
      credentials:
        - certificate:
            scope: SYSTEM
            id: ssh_private_key
            keyStoreSource:
              fileOnMaster:
                keyStoreFile: /docker/secret/id_rsa</pre>
<div class="blog_h2"><span class="graybg">Jenkins Reload</span></div>
<p>触发Jenkins Reload配置的方式有：</p>
<ol>
<li>通过Jenkins UI： Manage Jenkins ⇨ Configuration ⇨ Reload existing configuration</li>
<li>通过Jenkins CLI</li>
<li>发送POST请求到JENKINS_URL/configuration-as-code/reload，可能需要凭证信息</li>
</ol>
<div class="blog_h1"><span class="graybg">kubernetes-operator</span></div>
<p>这是一个Operator，能够在K8S上对Jenkins进行全面的管理。特性包括：</p>
<ol>
<li>Pipeline as Code</li>
<li>支持基于Groovy脚本或者JCasC进行扩展</li>
<li>安全加固</li>
</ol>
<div class="blog_h2"><span class="graybg">安装</span></div>
<div class="blog_h3"><span class="graybg">Operator</span></div>
<p>首先需要安装CRD：</p>
<pre class="crayon-plain-tag"># kubectl apply -f https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: jenkins.jenkins.io
spec:
  group: jenkins.io
  names:
    kind: Jenkins
    listKind: JenkinsList
    plural: jenkins
    singular: jenkins
  scope: Namespaced
  versions:
    - name : v1alpha2
      served: true
      storage: true
    - name : v1alpha1
      served: true
      storage: false</pre>
<p>然后安装此CRD的Operator：</p>
<pre class="crayon-plain-tag"># kubectl apply -f https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/deploy/all-in-one-v1alpha2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins-operator
spec:
  replicas: 1
  selector:
    matchLabels:
      name: jenkins-operator
  template:
    metadata:
      labels:
        name: jenkins-operator
    spec:
      serviceAccountName: jenkins-operator
      containers:
        - name: jenkins-operator
          image: virtuslab/jenkins-operator:v0.1.0
          command:
          - jenkins-operator
          args: []
          imagePullPolicy: IfNotPresent
          env:
            - name: WATCH_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: OPERATOR_NAME
              value: "jenkins-operator"</pre>
<div class="blog_h3"><span class="graybg">Jenkins</span></div>
<p>要安装一个Jenkins服务，只需要创建Jenkins类型的CR即可，Jenkins Operator会自动完成其它工作：</p>
<pre class="crayon-plain-tag">apiVersion: jenkins.io/v1alpha2
kind: Jenkins
metadata:
  name: example
spec:
  master:
    containers:
    - name: jenkins-master
      image: jenkins/jenkins:lts
      imagePullPolicy: Always
      livenessProbe:
        failureThreshold: 12
        httpGet:
          path: /login
          port: http
          scheme: HTTP
        initialDelaySeconds: 80
        periodSeconds: 10
        successThreshold: 1
        timeoutSeconds: 5
      readinessProbe:
        failureThreshold: 3
        httpGet:
          path: /login
          port: http
          scheme: HTTP
        initialDelaySeconds: 30
        periodSeconds: 10
        successThreshold: 1
        timeoutSeconds: 1
      resources:
        limits:
          cpu: 1500m
          memory: 3Gi
        requests:
          cpu: "1"
          memory: 500Mi
  seedJobs:
  - id: jenkins-operator
    targets: "cicd/jobs/*.jenkins"
    description: "Jenkins Operator repository"
    repositoryBranch: master
    repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git</pre>
<p>等Jenkins的Pod可用后，执行下面的命令获取凭证信息：</p>
<pre class="crayon-plain-tag">kubectl get secret jenkins-operator-credentials-example -o 'jsonpath={.data.user}' | base64 -d
kubectl get secret jenkins-operator-credentials-example -o 'jsonpath={.data.password}' | base64 -d</pre>
<div class="blog_h2"><span class="graybg">编写种子任务和流水线 </span></div>
<p>Jenkins Operator依赖以下插件：</p>
<ol>
<li>job-dsl-plugin，使用该插件提供的“种子任务”、DSL</li>
<li> kubernetes-credentials-provider，在K8S中配置部署密钥</li>
</ol>
<p>默认情况下Jenkins Operator期望你的项目下有如下目录：</p>
<pre class="crayon-plain-tag">cicd/
├── jobs
│   └── build.jenkins
└── pipelines
    └── build.jenkins</pre>
<p>其中jobs包含Jenkins Job的定义，基于Job DSL语法：</p>
<pre class="crayon-plain-tag">#!/usr/bin/env groovy

// 定义一个流水线任务
pipelineJob('build-jenkins-operator') {
    // 显示名称
    displayName('Build jenkins-operator')
    // 任务定义
    definition {
        cpsScm {
            // 代码库信息
            scm {
                git {
                    remote {
                        url('https://github.com/jenkinsci/kubernetes-operator.git')
                        credentials('jenkins-operator')
                    }
                    branches('*/master')
                }
            }
            // 引用实际构建流水线
            scriptPath('cicd/pipelines/build.jenkins')
        }
    }
}</pre>
<p>而pipelines则存放实际的构建流水线，例如： </p>
<pre class="crayon-plain-tag">#!/usr/bin/env groovy

def label = "build-jenkins-operator-${UUID.randomUUID().toString()}"
def home = "/home/jenkins"
def workspace = "${home}/workspace/build-jenkins-operator"
def workdir = "${workspace}/src/github.com/jenkinsci/kubernetes-operator/"

// 在此模板所定义的Pod中执行流水线
podTemplate(label: label,
        containers: [
                // 必须有一个名为jnlp的容器，负责和Jenkins服务器的交互
                containerTemplate(name: 'jnlp', image: 'jenkins/jnlp-slave:alpine'),
                // 这个容器则负责运行构建工具
                containerTemplate(name: 'go', image: 'golang:1-alpine', command: 'cat', ttyEnabled: true),
        ],
        envVars: [
                envVar(key: 'GOPATH', value: workspace),
        ],
        ) {
    // 在上述Pod中
    node(label) {
        // 切换工作目录
        dir(workdir) {
            // 然后在go容器中完成构建
            stage('Init') {
                timeout(time: 3, unit: 'MINUTES') {
                    checkout scm
                }
                container('go') {
                    sh 'apk --no-cache --update add make git gcc libc-dev'
                }
            }

            stage('Dep') {
                container('go') {
                    sh 'make dep'
                }
            }

            stage('Test') {
                container('go') {
                    sh 'make test'
                }
            }

            stage('Build') {
                container('go') {
                    sh 'make build'
                }
            }
        }
    }
}</pre>
<div class="blog_h2"><span class="graybg">配置种子任务</span></div>
<p>除了在工程目录中提供上述种子任务、流水线定义，你还需要配置Jenkins CR，告知Operator到什么地方获取种子任务、流水线： </p>
<pre class="crayon-plain-tag">apiVersion: jenkins.io/v1alpha2
kind: Jenkins
metadata:
  name: example
spec:
  seedJobs:
  - id: jenkins-operator
    targets: "cicd/jobs/*.jenkins"
    description: "Jenkins Operator repository"
    repositoryBranch: master
    repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git
  - id: jenkins-operator-ssh
    # 如果Git仓库是私有的，可以使用基于SSH的身份验证
    credentialType: basicSSHUserPrivateKey
    credentialID: k8s-ssh
    # 也可以使用基于用户名密码的身份验证
  - id: jenkins-operator-user-pass
    credentialType: usernamePassword
    credentialID: k8s-user-pass

# 上述k8s-ssh、k8s-user-pass必须配置为K8S Secret：

apiVersion: v1
kind: Secret
metadata:
  name: k8s-ssh
data:
  privateKey: |
    -----BEGIN RSA PRIVATE KEY-----
    MIIJKAIBAAKCAgEAxxDpleJjMCN5nusfW/AtBAZhx8UVVlhhhIKXvQ+dFODQIdzO
    oDXybs1zVHWOj31zqbbJnsfsVZ9Uf3p9k6xpJ3WFY9b85WasqTDN1xmSd6swD4N8
    ...
  username: github_user_name


apiVersion: v1
kind: Secret
metadata:
  name: k8s-user-pass
data:
  username: github_user_name
  password: password_or_token</pre>
<div class="blog_h2"><span class="graybg">安装插件</span></div>
<p>要为Jenkins安装插件，可以配置CR，增加spec.master.plugins字段：</p>
<pre class="crayon-plain-tag">apiVersion: jenkins.io/v1alpha2
kind: Jenkins
metadata:
  name: example
spec:
  master:
   plugins:
   - name: simple-theme-plugin
     version: 0.5.1</pre>
<div class="blog_h2"><span class="graybg">定制Jenkins</span></div>
<p>定制Jenkins配置，可以使用Groovy脚本或者JCasC。所有定制配置信息都存放在Secret  jenkins-operator-user-configuration-&lt;cr_name&gt;中：</p>
<pre class="crayon-plain-tag"># kubectl get configmap jenkins-operator-user-configuration-&lt;cr_name&gt; -o yaml

apiVersion: v1
data:
  # 使用Groovy脚本陪孩子
  1-configure-theme.groovy: |2
    import jenkins.*
    import jenkins.model.*
    import hudson.*
    import hudson.model.*
    import org.jenkinsci.plugins.simpletheme.ThemeElement
    import org.jenkinsci.plugins.simpletheme.CssTextThemeElement
    import org.jenkinsci.plugins.simpletheme.CssUrlThemeElement
    
    # 获取Jenkins实例对象
    Jenkins jenkins = Jenkins.getInstance()

    # 获取插件配置对象
    def decorator = Jenkins.instance.getDescriptorByType(org.codefirst.SimpleThemeDecorator.class)

    List&lt;ThemeElement&gt; configElements = new ArrayList&lt;&gt;();
    configElements.add(new CssTextThemeElement("DEFAULT"));
    configElements.add(new CssUrlThemeElement("https://cdn.rawgit.com/afonsof/jenkins-material-theme/gh-pages/dist/material-light-green.css"));
    decorator.setElements(configElements);
    decorator.save();
    # 保存配置
    jenkins.save()
  # 使用JCasC也可以
  1-system-message.yaml: |2
    jenkins:
      systemMessage: "Configuration as Code integration works!!!"
      adminAddress: "${SECRET_JENKINS_ADMIN_ADDRESS}"
kind: ConfigMap
metadata:
  name: jenkins-operator-user-configuration-&lt;cr_name&gt;
  namespace: default</pre>
<div class="blog_h1"><span class="graybg">总结</span></div>
<p>本文调研的这些技术中，除了Jenkins REST API以外都是面向Jenkins的最终用户的，不适合进行二次开发。</p>
<p>job-dsl-plugin提供的DSL的确具有其价值，它简化了Jenkins API的复杂度，可以避免编写繁琐的XML操控代码。所以，通过Jenkins REST API创建基于job-dsl-plugin的种子任务，并立即触发来生成Jenkins流水线任务是一种可行的方案。</p>
<p>对于最终用户来说，job-dsl-plugin的DSL还是太过复杂，应当进一步简化、屏蔽技术细节。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/jenkins-ops-on-k8s-clusters">在Kubernetes中管理和使用Jenkins</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/jenkins-ops-on-k8s-clusters/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Byte Buddy学习笔记</title>
		<link>https://blog.gmem.cc/byte-buddy-study-note</link>
		<comments>https://blog.gmem.cc/byte-buddy-study-note#comments</comments>
		<pubDate>Mon, 03 Jun 2019 07:18:57 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=27315</guid>
		<description><![CDATA[<p>简介 Byte Buddy是一个JVM的运行时代码生成器，你可以利用它创建任何类，且不像JDK动态代理那样强制实现一个接口。Byte Buddy还提供了简单的API，便于手工、通过Java Agent，或者在构建期间修改字节码。 Java反射API可以做很多和字节码生成器类似的工作，但是它具有以下缺点： 相比硬编码的方法调用，使用 反射 API 非常慢 反射 API 能绕过类型安全检查 比起JDK动态代理、cglib、Javassist，Byte Buddy在性能上具有优势。 入门 创建新类型 下面是一个最简单的例子： [crayon-69d57ac542138256862938/] ByteBuddy利用Implementation接口来表示一个动态定义的方法，FixedValue.value就是该接口的实例。 完全实现Implementation比较繁琐，因此实际情况下会使用MethodDelegation代替。使用MethodDelegation，你可以在一个POJO中实现方法拦截器： [crayon-69d57ac54213c477431428/] <a class="read-more" href="https://blog.gmem.cc/byte-buddy-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/byte-buddy-study-note">Byte Buddy学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Byte Buddy是一个JVM的运行时代码生成器，你可以利用它创建任何类，且不像JDK动态代理那样强制实现一个接口。Byte Buddy还提供了简单的API，便于手工、通过Java Agent，或者在构建期间修改字节码。</p>
<p>Java反射API可以做很多和字节码生成器类似的工作，但是它具有以下缺点：</p>
<ol>
<li>相比硬编码的方法调用，使用 反射 API 非常慢</li>
<li>反射 API 能绕过类型安全检查</li>
</ol>
<p>比起JDK动态代理、cglib、Javassist，Byte Buddy在性能上具有优势。</p>
<div class="blog_h1"><span class="graybg">入门</span></div>
<div class="blog_h2"><span class="graybg">创建新类型</span></div>
<p>下面是一个最简单的例子：</p>
<pre class="crayon-plain-tag">Class&lt;?&gt; dynamicType = new ByteBuddy()
  // 指定父类
  .subclass(Object.class)
   // 根据名称来匹配需要拦截的方法
  .method(ElementMatchers.named("toString"))
  // 拦截方法调用，返回固定值
  .intercept(FixedValue.value("Hello World!"))
  // 产生字节码
  .make()
  // 加载类
  .load(getClass().getClassLoader())
  // 获得Class对象
  .getLoaded();

assertThat(dynamicType.newInstance().toString(), is("Hello World!"));</pre>
<p>ByteBuddy利用Implementation接口来表示一个动态定义的方法，FixedValue.value就是该接口的实例。</p>
<p>完全实现Implementation比较繁琐，因此实际情况下会使用MethodDelegation代替。使用MethodDelegation，你可以在一个POJO中实现方法拦截器：</p>
<pre class="crayon-plain-tag">public class GreetingInterceptor {
  // 方法签名随意
  public Object greet(Object argument) {
    return "Hello from " + argument;
  }
}

Class&lt;? extends java.util.function.Function&gt; dynamicType = new ByteBuddy()
  // 实现一个Function子类
  .subclass(java.util.function.Function.class)
  .method(ElementMatchers.named("apply"))
  // 拦截Function.apply调用，委托给GreetingInterceptor处理
  .intercept(MethodDelegation.to(new GreetingInterceptor()))
  .make()
  .load(getClass().getClassLoader())
  .getLoaded();

assertThat((String) dynamicType.newInstance().apply("Byte Buddy"), is("Hello from Byte Buddy"));</pre>
<p>编写拦截器时，你可以指定一些注解，ByteBuddy会自动注入：</p>
<pre class="crayon-plain-tag">public class GeneralInterceptor {
  // 提示ByteBuddy根据被拦截方法的实际类型，对此拦截器的返回值进行Cast
  @RuntimeType
  //                      所有入参的数组
  public Object intercept(@AllArguments Object[] allArguments,
  //                      被拦截的原始方法
                          @Origin Method method) {
  }
}</pre>
<div class="blog_h2"><span class="graybg">修改已有类型</span></div>
<p>上面的两个例子中，我们利用ByteBuddy创建了指定接口的新子类型，ByteBuddy也可以用来修改已存在的。</p>
<p>ByteBuddy提供了便捷的创建Java Agent的API，本节的例子就是通过Java Agent方式来修改已存在的Java类型：public class TimerAgent {</p>
<pre class="crayon-plain-tag">public static void premain(String arguments, 
                             Instrumentation instrumentation) {
    new AgentBuilder.Default()
      // 匹配被拦截方法
      .type(ElementMatchers.nameEndsWith("Timed"))
      .transform(
          (builder, type, classLoader, module) -&gt; 
              builder.method(ElementMatchers.any()) .intercept(MethodDelegation.to(TimingInterceptor.class))
      ).installOn(instrumentation);
  }
}

public class TimingInterceptor {
  @RuntimeType
  public static Object intercept(@Origin Method method, 
                                 // 调用该注解后的Runnable/Callable，会导致调用被代理的非抽象父方法
                                 @SuperCall Callable&lt;?&gt; callable) {
    long start = System.currentTimeMillis();
    try {
      return callable.call();
    } finally {
      System.out.println(method + " took " + (System.currentTimeMillis() - start));
    }
  }
}</pre>
<div class="blog_h1"><span class="graybg">API </span></div>
<div class="blog_h2"><span class="graybg">创建类</span></div>
<div class="blog_h3"><span class="graybg">subclass</span></div>
<p>调用此方法可以创建一个目标类的子类：</p>
<pre class="crayon-plain-tag">DynamicType.Unloaded&lt;?&gt; dynamicType = new ByteBuddy()
  .subclass(Object.class)
  .name("example.Type")  // 子类的名称
  .make();</pre>
<p>如果不指定子类名称，Byte Buddy会有一套自动的策略来生成。你还可以指定子类命名策略：</p>
<pre class="crayon-plain-tag">DynamicType.Unloaded&lt;?&gt; dynamicType = new ByteBuddy()
  .with(new NamingStrategy.AbstractBase() {
    @Override
    public String subclass(TypeDescription superClass) {
        return "i.love.ByteBuddy." + superClass.getSimpleName();
    }
  })
  .subclass(Object.class)
  .make();</pre>
<div class="blog_h3"><span class="graybg">加载类</span></div>
<p>上节创建的DynamicType.Unloaded，代表一个尚未加载的类，你可以通过ClassLoadingStrategy来加载这种类。 </p>
<p>如果不指定ClassLoadingStrategy，Byte Buffer根据你提供的ClassLoader来推导出一个策略，内置的策略定义在枚举ClassLoadingStrategy.Default中：</p>
<ol>
<li>WRAPPER：创建一个新的Wrapping类加载器</li>
<li>CHILD_FIRST：类似上面，但是子加载器优先负责加载目标类</li>
<li>INJECTION：利用反射机制注入动态类型</li>
</ol>
<p>示例：</p>
<pre class="crayon-plain-tag">Class&lt;?&gt; type = new ByteBuddy()
  .subclass(Object.class)
  .make()
  .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
  .getLoaded();</pre>
<div class="blog_h2"><span class="graybg">修改类</span></div>
<div class="blog_h3"><span class="graybg">redefine</span></div>
<p>重定义一个类时，Byte Buddy 可以对一个已有的类<span style="background-color: #c0c0c0;">添加属性和方法</span>，或者<span style="background-color: #c0c0c0;">删除已经存在的方法实现</span>。新添加的方法，如果<span style="background-color: #c0c0c0;">签名和原有方法一致，则原有方法会消失</span>。</p>
<div class="blog_h3"><span class="graybg">rebase</span></div>
<p>类似于redefine，但是原有的方法不会消失，而是被重命名，添加后缀<pre class="crayon-plain-tag">$original</pre>，例如类：</p>
<pre class="crayon-plain-tag">class Foo {
  String bar() { return "bar"; }
}</pre>
<p>在rebase之后，会变成：</p>
<pre class="crayon-plain-tag">class Foo {
  String bar() { return "foo" + bar$original(); }
  private String bar$original() { return "bar"; }
}</pre>
<div class="blog_h3"><span class="graybg">重新加载类</span></div>
<p>得益于JVM的HostSwap特性，已加载的类可以被重新定义：</p>
<pre class="crayon-plain-tag">// 安装Byte Buddy的Agent，除了通过-javaagent静态安装，还可以：
ByteBuddyAgent.install();
Foo foo = new Foo();
new ByteBuddy()
  .redefine(Bar.class)
  .name(Foo.class.getName())
  .make()
  .load(Foo.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());

assertThat(foo.m(), is("bar"));</pre>
<p>可以看到，即使时已经存在的对象，也会受到类Reloading的影响。</p>
<p>当前HostSwap具有限制：</p>
<ol>
<li>类再重新载入前后，必须具有相同的Schema，也就是<span style="background-color: #c0c0c0;">方法、字段不能减少（可以增加）</span></li>
<li>不支持具有静态初始化块的类</li>
</ol>
<div class="blog_h2"><span class="graybg">操控未加载类</span></div>
<p>Byte Buddy提供了类似于Javassist的、操控未加载类的API。它在TypePool中维护类型的元数据TypeDescription：</p>
<pre class="crayon-plain-tag">// 获取默认类型池
TypePool typePool = TypePool.Default.ofClassPath();
new ByteBuddy()
  .redefine(typePool.describe("foo.Bar").resolve(), // 根据名称进行解析类
            // ClassFileLocator用于定位到被修改类的.class文件
            ClassFileLocator.ForClassLoader.ofClassPath())
  .defineField("qux", String.class) // 定义一个新的字段
  .make()
  .load(ClassLoader.getSystemClassLoader());
assertThat(Bar.class.getDeclaredField("qux"), notNullValue());</pre>
<div class="blog_h2"><span class="graybg">拦截方法</span></div>
<div class="blog_h3"><span class="graybg">匹配方法</span></div>
<p>Byte Buddy提供了很多用于匹配方法的DSL：</p>
<pre class="crayon-plain-tag">class Foo {
  public String bar() { return null; }
  public String foo() { return null; }
  public String foo(Object o) { return null; }
}
 
Foo dynamicFoo = new ByteBuddy()
  .subclass(Foo.class)
  // 匹配由Foo.class声明的方法
  .method(isDeclaredBy(Foo.class)).intercept(FixedValue.value("One!"))
  // 匹配名为foo的方法
  .method(named("foo")).intercept(FixedValue.value("Two!"))
  // 匹配名为foo，入参数量为1的方法
  .method(named("foo").and(takesArguments(1))).intercept(FixedValue.value("Three!"))
  .make()
  .load(getClass().getClassLoader())
  .getLoaded()
  .newInstance();</pre>
<div class="blog_h3"><span class="graybg">委托方法</span></div>
<p>使用MethodDelegation可以将方法调用委托给任意POJO。Byte Buddy<span style="background-color: #c0c0c0;">不要求Source（被委托类）、Target类的方法名一致</span>：</p>
<pre class="crayon-plain-tag">class Source {
  public String hello(String name) { return null; }
}
 
String helloWorld = new ByteBuddy()
  .subclass(Source.class)
  .method(named("hello")).intercept(MethodDelegation.to(Target.class))
  .make()
  .load(getClass().getClassLoader())
  .getLoaded()
  .newInstance()
  .hello("World");</pre>
<p>Target的实现可以如下： </p>
<pre class="crayon-plain-tag">class Target {
  public static String hello(String name) {
    return "Hello " + name + "!";
  }
} </pre>
<p>也可以如下：  </p>
<pre class="crayon-plain-tag">class Target {
  public static String intercept(String name) { return "Hello " + name + "!"; }
  public static String intercept(int i) { return Integer.toString(i); }
  public static String intercept(Object o) { return o.toString(); }
}</pre>
<p>前一个实现很好理解，那么后一个呢，Byte Buddy到底会委托给哪个方法？Byte Buddy遵循一个最接近原则：</p>
<ol>
<li>intercept(int)因为参数类型不匹配，直接Pass</li>
<li>另外两个方法参数都匹配，但是 intercept(String)类型更加接近，因此会委托给它</li>
</ol>
<div class="blog_h3"><span class="graybg">参数绑定</span></div>
<p>你可以在Target的方法中使用注解进行参数绑定：</p>
<pre class="crayon-plain-tag">void foo(Object o1, Object o2)
// 等价于
void foo(@Argument(0) Object o1, @Argument(1) Object o2)</pre>
<p>全部注解如下表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">注解</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>@Argument</td>
<td>绑定单个参数</td>
</tr>
<tr>
<td>@AllArguments</td>
<td>绑定所有参数的数组</td>
</tr>
<tr>
<td>@This</td>
<td>当前被拦截的、动态生成的那个对象</td>
</tr>
<tr>
<td>@Super</td>
<td>当前被拦截的、动态生成的那个对象的父类对象</td>
</tr>
<tr>
<td>@Origin</td>
<td>
<p>可以绑定到以下类型的参数：</p>
<p style="padding-left: 30px;">Method 被调用的原始方法<br />Constructor 被调用的原始构造器<br />Class 当前动态创建的类<br />MethodHandle<br />MethodType<br />String  动态类的toString()的返回值<br />int  动态方法的修饰符</p>
</td>
</tr>
<tr>
<td>@DefaultCall</td>
<td>调用默认方法而非super的方法</td>
</tr>
<tr>
<td>@SuperCall</td>
<td>用于调用父类版本的方法</td>
</tr>
<tr>
<td>@Super</td>
<td>注入父类型对象，可以是接口，从而调用它的任何方法</td>
</tr>
<tr>
<td>@RuntimeType</td>
<td>可以用在返回值、参数上，提示ByteBuddy禁用严格的类型检查</td>
</tr>
<tr>
<td>@Empty</td>
<td>注入参数的类型的默认值</td>
</tr>
<tr>
<td>@StubValue</td>
<td>注入一个存根值。对于返回引用、void的方法，注入null；对于返回原始类型的方法，注入0</td>
</tr>
<tr>
<td>@FieldValue</td>
<td>注入被拦截对象的一个字段的值</td>
</tr>
<tr>
<td>@Morph</td>
<td>类似于@SuperCall，但是允许指定调用参数</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">添加字段</span></div>
<pre class="crayon-plain-tag">Class&lt;? extends UserType&gt; dynamicUserType = new ByteBuddy()
  .subclass(UserType.class)
  .defineField("interceptor", Interceptor.class, Visibility.PRIVATE);</pre>
<p>方法调用也可以委托给字段（而非外部对象）：</p>
<pre class="crayon-plain-tag">Class&lt;? extends UserType&gt; dynamicUserType = new ByteBuddy()
  .subclass(UserType.class)
    .method(not(isDeclaredBy(Object.class)))
    .intercept(MethodDelegation.toField("interceptor")); </pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/byte-buddy-study-note">Byte Buddy学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/byte-buddy-study-note/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>如何开发Java Agent</title>
		<link>https://blog.gmem.cc/how-to-develop-a-java-agent</link>
		<comments>https://blog.gmem.cc/how-to-develop-a-java-agent#comments</comments>
		<pubDate>Mon, 03 Jun 2019 06:36:06 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=27301</guid>
		<description><![CDATA[<p>简介 Java Instrumentation API 此API由java.lang.instrument包提供，其核心是Instrumentation接口，它提供了探测（instrument）Java代码的基本服务，可用于实现性能监控、Profiler、事件记录器等功能。获取Instrumentation实例的方法有两种： 如果JVM启动时静态加载了Agent，那么Instrumentation被传递给Agent类的[crayon-69d57ac54266a354921609-i/]方法 如果JVM在运行时动态加载了Agent，那么Instrumentation被传递给Agent类的[crayon-69d57ac54266f865812042-i/]方法 常用方法 Instrumentation接口最常用的方法包括： 方法 说明 addTransformer 为instrumentation引擎提供一个转换器 getAllLoadedClasses 获取当前已经加载的所有类的列表 retransformClasses 通过添加字节码来修改已加载的类 removeTransformer 移除转换器 redefineClasses 直接替换类 Java <a class="read-more" href="https://blog.gmem.cc/how-to-develop-a-java-agent">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/how-to-develop-a-java-agent">如何开发Java Agent</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<div class="blog_h2"><span class="graybg">Java Instrumentation API</span></div>
<p>此API由java.lang.instrument包提供，其核心是Instrumentation接口，它提供了探测（instrument）Java代码的基本服务，可用于实现性能监控、Profiler、事件记录器等功能。获取Instrumentation实例的方法有两种：</p>
<ol>
<li>如果JVM启动时静态加载了Agent，那么Instrumentation被传递给Agent类的<pre class="crayon-plain-tag">premain</pre>方法</li>
<li>如果JVM在运行时动态加载了Agent，那么Instrumentation被传递给Agent类的<pre class="crayon-plain-tag">agentmain</pre>方法</li>
</ol>
<div class="blog_h3"><span class="graybg">常用方法<br /></span></div>
<p>Instrumentation接口最常用的方法包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>addTransformer</td>
<td>为instrumentation引擎提供一个转换器</td>
</tr>
<tr>
<td>getAllLoadedClasses</td>
<td>获取当前已经加载的所有类的列表</td>
</tr>
<tr>
<td>retransformClasses</td>
<td>通过添加字节码来修改已加载的类</td>
</tr>
<tr>
<td>removeTransformer</td>
<td>移除转换器</td>
</tr>
<tr>
<td>redefineClasses</td>
<td>直接替换类</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Java Agent</span></div>
<p>Java Agent本质上就是一个Jar包，它会调用 Instrumentation API，来<span style="background-color: #c0c0c0;">修改已经加载到JVM中的字节码</span>。Java Agent具有两种加载方式。</p>
<div class="blog_h3"><span class="graybg">静态加载</span></div>
<p>这种方式下，在应用程序的任何代码被执行之前，就加载Agent以修改字节码。静态加载需要使用JVM的-javaagent参数：</p>
<pre class="crayon-plain-tag">java -javaagent:agent.jar -jar application.jar
# 可以同时加载多个Agents
java -javaagent:agentA.jar -javaagent:agentB.jar application.jar</pre>
<div class="blog_h3"><span class="graybg">动态加载 </span></div>
<p>这种方式下，Agent可以在运行时动态按需的加载。动态加载需要调用<a href="https://docs.oracle.com/javase/7/docs/jdk/api/attach/spec/com/sun/tools/attach/package-summary.html">Java Attach API</a>，下面是个例子：</p>
<pre class="crayon-plain-tag">// 根据PID查找目标JVM，并连接到JVM
VirtualMachine jvm = VirtualMachine.attach(jvmPid);
// 加载Agent
jvm.loadAgent(agentFile.getAbsolutePath());
// 取消到JVM的连接
jvm.detach();</pre>
<div class="blog_h1"><span class="graybg">基础</span></div>
<div class="blog_h2"><span class="graybg">创建入口点</span></div>
<p>Agent类就是一个普通的Java类，不需要特殊签名或者实现接口。根据加载方式的不同，你需要添加下面的方法：</p>
<pre class="crayon-plain-tag">public static void premain(
  String agentArgs, Instrumentation inst) {
    LOGGER.info("[Agent] In premain method");
    String className = "com.baeldung.instrumentation.application.MyAtm";
    // 添加转换器
    transformClass(className,inst);
}
public static void agentmain(
  String agentArgs, Instrumentation inst) {
  
    LOGGER.info("[Agent] In agentmain method");
    String className = "com.baeldung.instrumentation.application.MyAtm";
    transformClass(className,inst);
}</pre>
<div class="blog_h2"><span class="graybg">编写转换器</span></div>
<p>转换器实现ClassFileTransformer接口的transform方法，进行字节码编辑。我们通常会利用<a href="https://www.baeldung.com/javassist">javassist</a>来操控字节码。</p>
<p>下面的例子，修改HttpURLConnection类，以便对JVM访问的每个URL进行审计：</p>
<pre class="crayon-plain-tag">public class MyClassTransformer implements ClassFileTransformer {
  @Override
  public byte[] transform( 
      final ClassLoader loader, 
      final String className,
      final Class&lt;?&gt; classBeingRedefined, 
      final ProtectionDomain protectionDomain,
      final byte[] classfileBuffer ) throws IllegalClassFormatException {
    // 仅仅操作HttpURLConnection类
    if (className.endsWith("sun/net/www/protocol/http/HttpURLConnection")) {
      try {
        // 从ClassPool获得CtClass对象
        final ClassPool classPool = ClassPool.getDefault();
        final CtClass clazz = classPool.get("sun.net.www.protocol.http.HttpURLConnection");
        // 修改构造函数，在其结尾添加日志记录逻辑    
        for (final CtConstructor constructor: clazz.getConstructors()) {
          constructor.insertAfter("System.out.println(this.getURL());");
        }
        // 返回字节码，并且detachCtClass对象
        byte[] byteCode = clazz.toBytecode();
        clazz.detach();
              
        return byteCode;
      } catch (final NotFoundException | CannotCompileException | IOException ex) {
        ex.printStackTrace();
      }
    }
    // 如果返回null则字节码不会被修改
    return null;
  }
}</pre>
<div class="blog_h2"><span class="graybg">创建Agent清单</span></div>
<p>你需要在Jar的MANIFEST.MF文件中适当的属性：</p>
<pre class="crayon-plain-tag">Agent-Class: cc.gmem.agent.MyInstrumentationAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: cc.gmem.agent.MyInstrumentationAgent</pre>
<div class="blog_h1"><span class="graybg">示例</span></div>
<p>本章提供一个例子，该例子没有直接使用Instrumentation API，而是调用<a href="/byte-buddy-study-note">Byte Buddy</a>提供的更简便的API。 在该例子中，我们拦截IntelliJ IDE的DesktopLayout.getInfo方法，实现Tool Window的宽度、高度固定化。</p>
<div class="blog_h2"><span class="graybg">Agent入口点</span></div>
<pre class="crayon-plain-tag">package cc.gmem.intellij;

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.implementation.MethodDelegation;

import java.lang.instrument.Instrumentation;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

public class IntellijUIAgent {

    public static void premain( String arguments, Instrumentation instrumentation ) throws Exception {
        new AgentBuilder.Default()
            // 需要拦截的类
            .type( named( "com.intellij.openapi.wm.impl.DesktopLayout" ) )
            .transform( ( builder, type, classLoader, module ) -&gt; {
                // 需要拦截的方法
                return builder.method( named( "getInfo" ).and( takesArguments( String.class, boolean.class ) ) )
                              // to可以指定一个对象，也可以指定一个类。后者的话，拦截器方法必须是static
                              .intercept( MethodDelegation.withDefaultConfiguration().to( createaInterceptor( classLoader, arguments ) ) );
            } )
            // 将无法拦截的错误信息打印到控制台
            .with( AgentBuilder.Listener.StreamWriting.toSystemOut().withErrorsOnly() )
            // 使用REBASE方式，否则可能出现None of [XXX] allows for delegation from YYY
            .with( AgentBuilder.TypeStrategy.Default.REBASE )
            .installOn( instrumentation );
        System.out.println( "Intellij UI Agent by Alex Wong" );
    }
    // 注意不要通过new操作符直接创建DesktopLayoutInterceptor，原因是
    // Intellij使用独立的ClassLoader com.intellij.util.lang.UrlClassLoader来加载它自己的类库
    // 你new的时候使用的是系统类加载器
    private static Object createaInterceptor( ClassLoader classLoader, String arguments ) {
        try {
            String[] weights = arguments.split( "," );
            float leftWeight = Float.parseFloat( weights[0] );
            float bottomWeight = Float.parseFloat( weights[1] );
            Class&lt;?&gt; cls = classLoader.loadClass( "cc.gmem.intellij.DesktopLayoutInterceptor" );
            return cls.getConstructor( float.class, float.class ).newInstance( leftWeight, bottomWeight );
        } catch ( Exception e ) {
            throw new RuntimeException( e );
        }
    }
}</pre>
<div class="blog_h2"><span class="graybg">拦截器类</span> </div>
<pre class="crayon-plain-tag">package cc.gmem.intellij;

import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.impl.WindowInfoImpl;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

public class DesktopLayoutInterceptor {

    private final float leftWeight;

    private final float bottomWeight;

    public DesktopLayoutInterceptor( float leftWeight, float bottomWeight ) {
        this.leftWeight = leftWeight;
        this.bottomWeight = bottomWeight;
    }

    @RuntimeType
    public Object getInfo( @Origin Method method, @SuperCall Callable&lt;?&gt; callable ) {
        WindowInfoImpl info = null;
        try {
            info = (WindowInfoImpl) callable.call();
        } catch ( Exception e ) {
            e.printStackTrace();
        }
        if ( info.getAnchor() == ToolWindowAnchor.LEFT ) {
            info.setWeight( leftWeight );
        } else if ( info.getAnchor() == ToolWindowAnchor.BOTTOM ) {
            info.setWeight( bottomWeight );
        }
        return info;
    }
}</pre>
<div class="blog_h2"><span class="graybg">使用Agent</span></div>
<pre class="crayon-plain-tag"># 注意向Agent传参的方式
-javaagent:target/ui-agent-1.0-SNAPSHOT.jar=0.28,0.4</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/how-to-develop-a-java-agent">如何开发Java Agent</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/how-to-develop-a-java-agent/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SOFAStack学习笔记</title>
		<link>https://blog.gmem.cc/sofastack-study-note</link>
		<comments>https://blog.gmem.cc/sofastack-study-note#comments</comments>
		<pubDate>Sun, 05 May 2019 08:10:37 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Go]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[PaaS]]></category>
		<category><![CDATA[SOFA]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=26795</guid>
		<description><![CDATA[<p>简介 SOFAStack（Scalable Open Financial Architecture Stack，可扩展开放金融架构栈）是蚂蚁金服开源的技术栈，国内多家金融和互联网公司在生产环境使用了此技术栈。 SOFABoot 基于Spring Boot，额外提供了以下特性： 健康检查（Readiness探针）：在Spring Boot Liveness探针的基础上增加Readiness探针，仅当此探针成功后实例才能接受流量 类（加载器）隔离，基于SOFAArk，实现业务代码的类、SOFA中间件相关的类的隔离，避免了类冲突 日志空间隔离：将SOFA中间件日志和业务代码产生的日志分开 和其他SOFA中间件便捷的集成：提供各种SOFA中间件的Starter 模块化：可以为同一JVM中运行的不同SOFABoot模块提供独立的Spring ApplicationContext，可以规避BeanId的冲突 SOFABoot需要JDK 7+和Maven 3.2.5来完成构建。 起步 创建项目 <a class="read-more" href="https://blog.gmem.cc/sofastack-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/sofastack-study-note">SOFAStack学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>SOFAStack（Scalable Open Financial Architecture Stack，可扩展开放金融架构栈）是蚂蚁金服开源的技术栈，国内多家金融和互联网公司在生产环境使用了此技术栈。</p>
<div class="blog_h1"><span class="graybg">SOFABoot</span></div>
<p>基于Spring Boot，额外提供了以下特性：</p>
<ol>
<li>健康检查（<span style="background-color: #c0c0c0;">Readiness探针</span>）：在Spring Boot Liveness探针的基础上增加Readiness探针，仅当此探针成功后实例才能接受流量</li>
<li><span style="background-color: #c0c0c0;">类（加载器）隔离</span>，基于SOFAArk，实现业务代码的类、SOFA中间件相关的类的隔离，避免了类冲突</li>
<li><span style="background-color: #c0c0c0;">日志空间隔离</span>：将SOFA中间件日志和业务代码产生的日志分开</li>
<li>和其他SOFA中间件便捷的集成：提供各种<span style="background-color: #c0c0c0;">SOFA中间件的Starter</span></li>
<li>模块化：可以为同一JVM中运行的不同SOFABoot模块提供<span style="background-color: #c0c0c0;">独立的Spring ApplicationContext</span>，可以规避BeanId的冲突</li>
</ol>
<p>SOFABoot需要JDK 7+和Maven 3.2.5来完成构建。</p>
<div class="blog_h2"><span class="graybg">起步</span></div>
<div class="blog_h3"><span class="graybg">创建项目</span></div>
<p>通过IntelliJ IDEA的Spring Initializer来创建项目骨架，依赖选择Web。然后修改POM，将parent改为：</p>
<pre class="crayon-plain-tag">&lt;parent&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;sofaboot-dependencies&lt;/artifactId&gt;
    &lt;version&gt;${sofa.boot.version}&lt;/version&gt;
&lt;/parent&gt;</pre>
<p>并添加依赖： </p>
<pre class="crayon-plain-tag">&lt;!-- 提供健康检查能力 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;healthcheck-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
     &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
     &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p>SOFABoot要求提供必要的参数：</p>
<pre class="crayon-plain-tag">spring.application.name=Learing SOFABoot
logging.path=./logs</pre>
<div class="blog_h3"><span class="graybg">执行探针</span></div>
<p>启动上述项目后，运行命令<pre class="crayon-plain-tag">curl http://localhost:8080/actuator/health</pre>，可以看到服务的健康状况，正常情况下输出<pre class="crayon-plain-tag">{"status":"UP"}</pre></p>
<p>运行命令<pre class="crayon-plain-tag">curl -s http://localhost:8080/actuator/readiness | jq .</pre>可以获得Readiness探针执行结果的细节：</p>
<pre class="crayon-plain-tag">{
  "details": {
    "diskSpace": {
      "details": {
        "threshold": 10485760,
        "free": 166560387072,
        "total": 358796750848
      },
      "status": "UP"
    },
    "SOFABootReadinessHealthCheckInfo": {
      "status": "UP"
    }
  },
  "status": "UP"
}</pre>
<div class="blog_h3"><span class="graybg">查看日志</span></div>
<p>检查logs目录，可以看到日志根据来源的不同，分割到logs、infra等目录中。 </p>
<div class="blog_h3"><span class="graybg">单元测试</span></div>
<p>你可以使用SpringRunner进行单元测试，但是如果在项目中使用了SOFABoot的类隔离特性，则必须使用SofaBootRunner、SofaJUnit4Runner进行单元测试，并引入依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;test-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p>单元测试用例的例子：</p>
<pre class="crayon-plain-tag">@SpringBootTest
@RunWith(SpringRunner.class)
public class SofaBootWithModulesTest {
    // 引用SOFABoot模块发布的服务
    @SofaReference
    private SampleJvmService sampleJvmService;

    @Test
    public void test() {
        Assert.assertEquals("Hello, jvm service xml implementation.", sampleJvmService.message());
    }
} </pre>
<div class="blog_h3"><span class="graybg">模块化开发</span></div>
<p>从2.4版本开始SOFABoot引入了基于Spring上下文隔离的模块化能力。SOFABoot定义了三个模块化隔离级别：</p>
<ol>
<li>代码组织上的模块化：开发阶段分多个工程开发，例如使用Dubbo的团队，通常都会将API拆分为独立的Maven模块。这种隔离对运行时没有任何影响</li>
<li>基于Spring上下文隔离的模块化“类似于1，代码和配置分散在不同工程中。但是在运行时会启动多个Spring上下文（它们有共同的父上下文），DI仅仅在子上下文内发生。可以规避BeanId冲突问题</li>
<li>基于类加载器隔离的模块化：每个模块都使用独立的类加载器，可以规避模块依赖了冲突的类版本的问题</li>
</ol>
<p>SOFABoot模块化开发属于第二种模块化形式 —— 基于 Spring 上下文隔离的模块化，每个模块使用独立的Spring上下文。</p>
<p>每个SOFABoot模块包含<span style="background-color: #c0c0c0;">Java代码、Spring配置文集、SOFA模块标识</span>等信息，打包形式为JAR。不同模块之间不能通过DI来引用，需要转而使用SOFA服务。SOFABoot支持两种形式的服务（发布/引用）：</p>
<ol>
<li><span style="background-color: #c0c0c0;">JVM服务</span>发布和引用：解决同一SOFABoot 应用内各 SOFABoot 模块之间的调用问题</li>
<li><span style="background-color: #c0c0c0;">RPC服务</span>发布和引用：解决多个 SOFABoot 应用之间的远程调用问题</li>
</ol>
<p>由于相互之间没有Bean依赖，SOFABoot模块可以并行的启动，这可以提升应用启动速度。</p>
<p>这种模块化开发，和微服务的理念是违背的，各模块仍然需要共享单个JVM的资源。</p>
<p>为了体验SOFABoot的模块化开发，我们直接使用其源码附带的示例应用：</p>
<pre class="crayon-plain-tag">git clone https://github.com/alipay/sofa-boot.git
tree sofa-boot/sofaboot-samples/sofaboot-sample-with-isle -L 1
# .
# ├── service-consumer    服务的消费者
# ├── service-facade      服务的API
# ├── service-provider    服务的提供者
# ├── sofa-boot-run       启动包含模块的SOFABoot应用</pre>
<p>service-facade中定义的接口如下：</p>
<pre class="crayon-plain-tag">public interface SampleJvmService {
    String message();
}</pre>
<p>service-provider将上面的接口发布为JVM服务。发布方式有三种：</p>
<ol>
<li>注解方式：<br />
<pre class="crayon-plain-tag">//           唯一性的ID，不设置默认为空串
@SofaService(uniqueId = "annotationImpl")
public class SampleJvmServiceAnnotationImpl implements SampleJvmService {
    @Override
    public String message() {
        String message = "Hello, jvm service annotation implementation.";
        System.out.println(message);
        return message;
    }
}</pre>
</li>
<li>
<p>XML方式： </p>
<pre class="crayon-plain-tag">public class SampleJvmServiceImpl implements SampleJvmService {
    private String message;

    @Override
    public String message() {
        System.out.println(message);
        return message;
    }
}</pre>
<p>需要配合XML配置： </p>
<pre class="crayon-plain-tag">&lt;bean id="sampleJvmService" class="com.alipay.sofa.isle.sample.SampleJvmServiceImpl"&gt;
    &lt;property name="message" value="Hello, jvm service xml implementation."/&gt;
&lt;/bean&gt;

&lt;sofa:service ref="sampleJvmService" interface="com.alipay.sofa.isle.sample.SampleJvmService"&gt;
    &lt;sofa:binding.jvm/&gt;
&lt;/sofa:service&gt;</pre>
</li>
<li>编程式：<br />
<pre class="crayon-plain-tag">@Component
public class PublishServiceWithClient implements ClientFactoryAware {
    private ClientFactory clientFactory;
    
    @PostConstruct
    public void init() {
        ServiceClient serviceClient = clientFactory.getClient(ServiceClient.class);
        ServiceParam serviceParam = new ServiceParam();
        serviceParam.setInstance(new SampleJvmServiceImpl( "Hello, jvm service service client implementation."));
        serviceParam.setInterfaceType(SampleJvmService.class);
        serviceParam.setUniqueId("serviceClientImpl");
        // 发布服务
        serviceClient.service(serviceParam);
    }

    @Override
    public void setClientFactory(ClientFactory clientFactory) {
        this.clientFactory = clientFactory;
    }
} </pre>
</li>
</ol>
<p>你需要在sofa-module.properties中声明模块名称、模块依赖：
<pre class="crayon-plain-tag"># service-provider
Module-Name=com.alipay.sofa.service-provider

# service-consumer
Module-Name=com.alipay.sofa.service-consumer
Require-Module=com.alipay.sofa.service-provider</pre>
<p>service-consumer负责消费service-provider发布的服务：</p>
<pre class="crayon-plain-tag">public class JvmServiceConsumer implements ClientFactoryAware {
    private ClientFactory    clientFactory;

    // 引用基于XML配置的JVM服务
    @Autowired
    private SampleJvmService sampleJvmService;
    // 引用基于注解配置的JVM服务，使用uniqueId
    @SofaReference(uniqueId = "annotationImpl")
    private SampleJvmService sampleJvmServiceByFieldAnnotation;

    public void init() {
        sampleJvmService.message();
        sampleJvmServiceByFieldAnnotation.message();

        // 编程式客户端
        ReferenceClient referenceClient = clientFactory.getClient(ReferenceClient.class);
        ReferenceParam referenceParam = new ReferenceParam&lt;&gt;();
        referenceParam.setInterfaceType(SampleJvmService.class);
        referenceParam.setUniqueId("serviceClientImpl");
        SampleJvmService sampleJvmServiceClientImpl = referenceClient.reference(referenceParam);
        sampleJvmServiceClientImpl.message();
    }

    @Override
    public void setClientFactory(ClientFactory clientFactory) {
        this.clientFactory = clientFactory;
    }
}</pre>
<div class="blog_h3"><span class="graybg">使用SOFA栈</span></div>
<p>如果需要在SOFABoot项目中使用SOFA中间件，需要依赖相应的Starter： </p>
<pre class="crayon-plain-tag">&lt;!-- SOFARPC --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;rpc-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;!-- SOFATracer --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;tracer-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;!-- SOFALookout --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;lookout-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p> 如果需要在SOFABoot项目中使用扩展组件，需要依赖相应的Starter： </p>
<pre class="crayon-plain-tag">&lt;!-- 健康检查扩展 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;healthcheck-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;


&lt;!-- 模块化隔离扩展 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;isle-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;


&lt;!-- 类隔离扩展 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;sofa-ark-springboot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;


&lt;!-- 测试扩展 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;test-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p>如果要使用SOFAArk提供的类加载器隔离功能，则需要依赖相应的ARK插件，并替换到对应的Starter：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">ARK插件</td>
<td style="text-align: center;">ArtifactId</td>
</tr>
</thead>
<tbody>
<tr>
<td>SOFARPC</td>
<td>rpc-sofa-boot-plugin</td>
</tr>
<tr>
<td>SOFATracer</td>
<td>tracer-sofa-boot-plugin</td>
</tr>
</tbody>
</table>
<p>ARK插件能够让业务应用的类、SOFA中间件的类（以及它依赖的类）使用不同的类加载器，从而避免类冲突问题。</p>
<div class="blog_h2"><span class="graybg">健康检查</span></div>
<p>Spring Boot <span style="background-color: #c0c0c0;">Actuator提供的HealthIndicator接口可以提供Liveness健康检查</span>，SOFABoot在此基础上增加了Readiness检查功能。使用SOFA中间件时，最好配合SOFABoot的健康检查，以实现优雅上线，避免未准备好的实例过早加入服务池。</p>
<p>健康检查扩展提供了多个扩展点，允许用户定制其行为。</p>
<p>健康检查相关配置属性：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">配置属性</td>
<td style="text-align: center;">说明</td>
<td style="width: 80px; text-align: center;">默认值</td>
</tr>
</thead>
<tbody>
<tr>
<td>com.alipay.sofa.healthcheck.skip.all</td>
<td>是否跳过整个 Readiness Check 阶段</td>
<td>false</td>
</tr>
<tr>
<td>com.alipay.sofa.healthcheck.skip.component</td>
<td>是否跳过 SOFA 中间件的 Readiness Check</td>
<td>false</td>
</tr>
<tr>
<td>com.alipay.sofa.healthcheck.skip.indicator</td>
<td>是否跳过 HealthIndicator 的 Readiness Check</td>
<td>false</td>
</tr>
<tr>
<td>com.alipay.sofa.healthcheck.component.check.retry.count</td>
<td>组件健康检查重试次数</td>
<td>20</td>
</tr>
<tr>
<td>com.alipay.sofa.healthcheck.component.check.retry.interval</td>
<td>组件健康检查重试间隔时间</td>
<td>1000ms</td>
</tr>
<tr>
<td>com.alipay.sofa.healthcheck.module.check.retry.count</td>
<td>sofaboot 模块健康检查重试次数</td>
<td>0</td>
</tr>
<tr>
<td>com.alipay.sofa.healthcheck.module.check.retry.interval</td>
<td>sofaboot 模块健康检查重试间隔时间</td>
<td>1000ms</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">异步Bean初始化</span></div>
<p>SOFABoot支持异步的执行Bean的初始化方法，要使用该特性，引入依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;runtime-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p>并且为目标Bean提供配置属性async-init：</p>
<pre class="crayon-plain-tag">&lt;bean id="testBean" class="com.alipay.sofa.runtime.beans.TimeWasteBean" init-method="init" async-init="true"/&gt;</pre>
<p>SOFABoot在独立的线程池中进行Bean的异步初始化，相关配置属性：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">配置属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>com.alipay.sofa.boot.asyncInitBeanCoreSize</td>
<td>线程池默认线程数</td>
</tr>
<tr>
<td>com.alipay.sofa.boot.asyncInitBeanMaxSize</td>
<td>线程池最大线程数</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">模块隔离</span></div>
<div class="blog_h3"><span class="graybg">JAR包格式</span></div>
<p>SOFABoot模块的JAR包遵循如下约定：</p>
<ol>
<li>包含文件sofa-module.properties，定义模块名称、模块之间的依赖关系等元数据</li>
<li>META-INF/spring目录下的任意Spring配置文件都会作为本模块的配置而加载</li>
</ol>
<div class="blog_h3"><span class="graybg">模块元数据</span></div>
<p>模块元数据声明在sofa-module.properties文件中，包含以下配置属性：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 200px; text-align: center;">配置属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Module-Name</td>
<td>SOFABoot模块名称，唯一标识，Java包路径形式</td>
</tr>
<tr>
<td>Spring-Parent</td>
<td>指定一个模块的名称，将其Spring上下文设置为当前模块的父上下文，这样就可以解除对目标模块的隔离</td>
</tr>
<tr>
<td>Require-Module</td>
<td>逗号分隔的，依赖的其它模块列表</td>
</tr>
<tr>
<td>Module-Profile</td>
<td>
<p>所属的Profile，通过Spring配置属性<pre class="crayon-plain-tag">com.alipay.sofa.boot.active-profile</pre>，可以指定哪些SOFABoot Profile启用（逗号分隔多个值）</p>
<p>只有其Profile启用的模块才激活（也就是启动）</p>
<p>此外，在Spring配置文件中，可以嵌套beans元素，子元素可以指定profile属性，来指定仅当特定Profile启用时才激活的Bean：</p>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd"
       default-autowire="byName"&gt;       

    &lt;beans profile="dev"&gt;
        &lt;bean id="devBeanId" class="com.alipay.cloudenginetest.sofaprofilesenv.DemoBean"&gt;
        &lt;/bean&gt;
    &lt;/beans&gt;

    &lt;beans profile="test"&gt;
        &lt;bean id="testBeanId" class="com.alipay.cloudenginetest.sofaprofilesenv.DemoBean"&gt;
        &lt;/bean&gt;
    &lt;/beans&gt;
&lt;/beans&gt;</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">集成SOFARPC</span></div>
<p>参考<a href="#sofagrpc-with-sofoboot">SOFARPC - 起步 - 整合SOFABoot</a>一节。 
<div class="blog_h1"><span class="graybg">SOFAArk</span></div>
<p>SOFAArk 是一款基于 Java 实现的轻量级类隔离容器，提供类隔离和应用（模块）合并部署能力。</p>
<p>在大型软件开发过程中，通常会推荐<span style="background-color: #c0c0c0;">底层功能插件化，业务功能模块化</span>的开发模式，以期达到低耦合、高内聚、功能复用的优点。SOFAArk 提供了一套插件化、模块化的开发规范，它的能力包括：</p>
<ol>
<li>定义类加载模型，运行时底层插件、业务应用（模块）的类相互隔离，每个插件和应用（模块）由不同的 ClassLoader 加载，可以有效避免相互之间的包冲突</li>
<li>定义了插件开发规范，可以基于Maven将多个第三方JAR打包为<span style="background-color: #c0c0c0;">Ark Plugin（插件）</span></li>
<li>定义模块开发规范，可以基于Maven将应用打包为<span style="background-color: #c0c0c0;">Ark Biz（模块）</span></li>
<li>提供针对Plugin、Biz的标准API，提供事件、扩展点支持</li>
<li>多Biz合并部署，打包为扁平的可执行JAR</li>
<li>支持在运行时通过API或配置中心来动态安装/写在Biz</li>
</ol>
<div class="blog_h2"><span class="graybg">架构</span></div>
<p>Ark 包是<span style="background-color: #c0c0c0;">满足特定目录格式要求的可执行扁平</span>（Flat，也就是打包了所有依赖）Jar，使用Maven 插件 <pre class="crayon-plain-tag">sofa-ark-maven-plugin</pre>可以将<span style="background-color: #c0c0c0;">单个或多个应用打包</span>成标准格式的 Ark 包，Ark包包含三类构件：</p>
<ol>
<li>Ark Container：负责 Ark 包启动、运行时的管理，<span style="background-color: #c0c0c0;">Ark Plugin 和 Ark Biz 运行在 SOFAArk 容器之上</span>。Ark Container具备<span style="background-color: #c0c0c0;">管理插件和应用的能力</span>，<span style="background-color: #c0c0c0;">容器启动成功后，会自动解析类路径包含的 Ark Plugin 和 Ark Biz 依赖，完成隔离加载并按优先级依次启动它们</span></li>
<li>Ark Plugin，使用Maven插件<pre class="crayon-plain-tag">sofa-ark-maven-plugin</pre>可以将单个或多个普通Jar包打包为Ark Plugin。Ark Plugin有一个配置文件，其中包含：插件类导入导出配置、资源导入导出配置、插件启动优先级等信息。SOFAArk使用独立的PluginClassLoader来加载插件</li>
<li>Ark Biz，使用Maven插件<pre class="crayon-plain-tag">sofa-ark-maven-plugin</pre>可以将业务应用打包为Biz包。<span style="background-color: #c0c0c0;">每个Ark包可以包含多个Biz，他们按优先级依次启动，通过JVM服务交互</span></li>
</ol>
<p>执行Ark包时，Ark Container优先启动，然后启动Plugin，最后启动Biz。它们的逻辑关系图如下：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2019/05/sofa-ark-arch.png"><img class="aligncenter  wp-image-26907" src="https://blog.gmem.cc/wp-content/uploads/2019/05/sofa-ark-arch.png" alt="sofa-ark-arch" width="876" height="793" /></a></p>
<div class="blog_h2"><span class="graybg">类冲突问题</span></div>
<p>在解决类冲突（两个运行在单个JVM中的模块依赖某个类的两个不兼容版本）方面，SOFAArk比OSGI简单的多。SOFAArk 提出了一种特殊的包结构Ark Plugin，在存在<span style="background-color: #c0c0c0;">包冲突时，用户可以使用 Maven 插件将若干冲突包打包成 Plugin</span>，运行时由<span style="background-color: #c0c0c0;">独立的 PluginClassLoader 加载</span>，从而解决包冲突。</p>
<div class="blog_h2"><span class="graybg">合并部署</span></div>
<p>将复杂项目拆分到多个工程的主要动机包括避免VCS提交冲突、规避技术栈导致的依赖冲突。SOFA支持模块化，可以<span style="background-color: #c0c0c0;">将多个业务模块打包为Biz，在运行时合并部署到单个JVM，各模块基于同一的API进行交互</span>。</p>
<div class="blog_h3"><span class="graybg">静态合并</span></div>
<p>这种模式下，Biz之间的依赖可以通过Maven管理，当应用打为扁平化JAR时其依赖的Biz可以合并进来。<span style="background-color: #c0c0c0;">每个Biz使用独立的BizClassLoader加载</span>，<span style="background-color: #c0c0c0;">Biz之间通过JVM服务（SofaService/SofaRefernece）进行交互</span>。</p>
<div class="blog_h3"><span class="graybg">动态合并</span></div>
<p>这种模式下，Biz可以在运行时，通过API或者配置中心（ZooKeeper）来动态部署/卸载。</p>
<p>这里需要提到主应用（Master Biz）的概念，其实不管静态/动态合并，此概念都是存在的。如果Ark包打了单个Biz则它就是主应用，如果打了多个Biz包则需要配置指定主应用。主应用不得卸载。</p>
<p>通常情况下，各模块的实现放在动态Biz中，供主应用调用。主应用通过两种方式部署/卸载动态Biz：</p>
<ol>
<li>调用SOFAArk的API</li>
<li>使用SOFAArk的Config插件，对接ZooKeeper配置中心。此插件会解析配置并控制动态Biz的部署/卸载</li>
</ol>
<div class="blog_h2"><span class="graybg">插件开发</span></div>
<p>Ark Plugin可以导入、导出类或资源：</p>
<ol>
<li>
<p>导入类：插件启动时，<span style="background-color: #c0c0c0;">优先委托给导出该类的插件负责加载</span>，如果加载不到，才会尝试从本插件内部加载</p>
</li>
<li>
<p>导出类：其<span style="background-color: #c0c0c0;">他插件如果导入了该类，优先从本插件加载</span></p>
</li>
<li>
<p>导入资源：插件在查找资源时，优先委托给导出该资源的插件负责加载，如果加载不到，才会尝试从本插件内部加载</p>
</li>
<li>
<p>导出资源：其他插件如果导入了该资源，优先从本插件加载</p>
</li>
</ol>
<p>SOFAArk的代码库提供了一个样例项目。其结构如下：</p>
<pre class="crayon-plain-tag">├── sample-ark-plugin
│   ├── common    # 此模块包含了插件导出类
│   ├── plugin    # 包含插件服务的实现、PluginActivator接口实现
│   ├── pom.xml</pre>
<div class="blog_h3"><span class="graybg">插件配置</span></div>
<p>plugin项目的POM中包含如下配置：</p>
<pre class="crayon-plain-tag">&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
            &lt;artifactId&gt;sofa-ark-plugin-maven-plugin&lt;/artifactId&gt;
            &lt;version&gt;${project.version}&lt;/version&gt;
            &lt;executions&gt;
                &lt;execution&gt;
                    &lt;id&gt;default-cli&lt;/id&gt;
                    &lt;goals&gt;
                        &lt;goal&gt;ark-plugin&lt;/goal&gt;
                    &lt;/goals&gt;

                    &lt;configuration&gt;

                        &lt;!-- Ark 容器启动插件的入口类，最多只能配置一个。一般在此执行初始化操作，比如发布插件服务 --&gt;
                        &lt;activator&gt;com.alipay.sofa.ark.sample.activator.SamplePluginActivator&lt;/activator&gt;

                        &lt;!-- 导出的包、类、资源列表 --&gt;
                        &lt;exported&gt;
                            &lt;packages&gt;
                                &lt;package&gt;com.alipay.sofa.ark.sample.common&lt;/package&gt;
                            &lt;/packages&gt;
                            &lt;classes&gt;
                                &lt;class&gt;com.alipay.sofa.ark.sample.facade.SamplePluginService&lt;/class&gt;
                            &lt;/classes&gt;
                        &lt;/exported&gt;

                        &lt;!-- 打包后的插件的存放位置，默认${project.build.directory} --&gt;
                        &lt;outputDirectory&gt;../target&lt;/outputDirectory&gt;

                    &lt;/configuration&gt;
                &lt;/execution&gt;

            &lt;/executions&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;</pre>
<p>此配置声明了当前插件的一切必要信息。</p>
<div class="blog_h3"><span class="graybg">发布服务</span></div>
<p>SamplePluginActivator负责在插件启动时，<span style="background-color: #c0c0c0;">发布JVM服务</span>：</p>
<pre class="crayon-plain-tag">package com.alipay.sofa.ark.sample.activator;

import com.alipay.sofa.ark.exception.ArkRuntimeException;
import com.alipay.sofa.ark.sample.facade.SamplePluginService;
import com.alipay.sofa.ark.sample.impl.SamplePluginServiceImpl;
import com.alipay.sofa.ark.spi.model.PluginContext;
import com.alipay.sofa.ark.spi.service.PluginActivator;

public class SamplePluginActivator implements PluginActivator {

    // 插件启动时的回调
    public void start(PluginContext context) throws ArkRuntimeException {
        // 通过插件上下文发布服务
        context.publishService(SamplePluginService.class, new SamplePluginServiceImpl());
    }

    // 插件停止时的回调
    public void stop(PluginContext context) throws ArkRuntimeException {
        System.out.println("stopping in ark plugin activator");
    }

}</pre>
<div class="blog_h3"><span class="graybg">订阅服务</span></div>
<p>除了发布服务之外，你还可以<span style="background-color: #c0c0c0;">引用其他插件或Ark容器发布的服务</span>：</p>
<pre class="crayon-plain-tag">public class SamplePluginServiceImpl implements SamplePluginService {

    // 引用Ark容器发布的，事件管理服务
    @ArkInject
    private EventAdminService eventAdminService;

    public String service() {
        return "I'm a sample plugin service published by ark-plugin";
    }

    public void sendEvent(ArkEvent arkEvent) {
        eventAdminService.sendEvent(arkEvent);
    }
}</pre>
<div class="blog_h2"><span class="graybg">打Ark包</span></div>
<p>使用sofa-ark-maven-plugin也可以把普通的Spring Boot项目打为Ark包：</p>
<pre class="crayon-plain-tag">&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
            &lt;artifactId&gt;sofa-ark-maven-plugin&lt;/artifactId&gt;
            &lt;executions&gt;
                &lt;execution&gt;
                    &lt;id&gt;default-cli&lt;/id&gt;
                    
                    &lt;!-- 此目标生成可执行Ark包 --&gt;
                    &lt;goals&gt;
                        &lt;goal&gt;repackage&lt;/goal&gt;
                    &lt;/goals&gt;
                    
                    &lt;configuration&gt;
                        &lt;!-- 可执行Ark包的输出目录 --&gt;
                        &lt;outputDirectory&gt;./target&lt;/outputDirectory&gt;
                        &lt;!-- 可以指定Ark包的Maven坐标的classifier字段 --&gt;
                        &lt;arkClassifier&gt;executable-ark&lt;/arkClassifier&gt;
                    &lt;/configuration&gt;
                &lt;/execution&gt;
            &lt;/executions&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;</pre>
<div class="blog_h2"><span class="graybg">运行Ark包</span></div>
<div class="blog_h3"><span class="graybg">SpringBoot</span></div>
<p>添加以下依赖即可：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;sofa-ark-springboot-starter&lt;/artifactId&gt;
    &lt;version&gt;${sofa.ark.version}&lt;/version&gt;
&lt;/dependency&gt;</pre>
<div class="blog_h3"><span class="graybg">独立Java工程 </span></div>
<p>需要添加依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;sofa-ark-support-starter&lt;/artifactId&gt;
    &lt;version&gt;${sofa.ark.version}&lt;/version&gt;
&lt;/dependency&gt;</pre>
<p>并且在入口点方法中启动Ark容器：</p>
<pre class="crayon-plain-tag">public class Application{
    public static void main(String[] args) { 
        SofaArkBootstrap.launch(args);
    }
}</pre>
<div class="blog_h3"><span class="graybg">单元测试</span></div>
<p>SOFAArk提供了org.junit.runner.Runner的实现类ArkJUnit4Runner，用于集成JUnit4单元测试框架：</p>
<pre class="crayon-plain-tag">@RunWith(ArkJUnit4Runner.class)
public class JUnitTest {

    @Test
    public void test() {
        Assert.assertTrue(true);
    }</pre>
<p>上面的用例会在Ark容器之上运行。 </p>
<div class="blog_h3"><span class="graybg">集成测试</span></div>
<p>SOFAArk提供了org.junit.runner.Runner的实现类ArkBootRunner，用于在Spring Boot下进行集成测试：</p>
<pre class="crayon-plain-tag">@RunWith(ArkBootRunner.class)
@SpringBootTest(classes = SpringbootDemoApplication.class)
public class IntegrationTest {

    // 可以注入依赖
    @Autowired
    private SampleService sampleService;

    @Test
    public void test() {
        sampleService.service();
    }

} </pre>
<div class="blog_h1"><span class="graybg">SOFARPC</span></div>
<p>这是一个Java的RPC框架，提供了负载均衡，流量转发，链路追踪，链路数据透传，故障剔除等特性，<span style="background-color: #c0c0c0;">兼容 bolt，RESTful，dubbo，H2C协议</span>。</p>
<p>SOFARPC的基本工作原理和Dubbo类似：</p>
<ol>
<li>如果当前应用需要发布RPC服务，那么SOFARPC会将服务注册到服务注册中心</li>
<li>如果当前应用需要调用RPC服务，那么SOFARPC会到注册中心订阅相关服务的元数据。注册中心会即时的推送元数据更新，例如服务的端点信息</li>
</ol>
<div class="blog_h2"><span class="graybg">独立运行</span></div>
<p>需要加入依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;sofa-rpc-all&lt;/artifactId&gt;
    &lt;version&gt;5.6.0-SNAPSHOT&lt;/version&gt;
&lt;/dependency&gt;</pre>
<p>需要发布的服务接口： </p>
<pre class="crayon-plain-tag">public interface HelloService {
    String sayHello(String string);
}</pre>
<p>接口实现HelloServiceImpl这里略去。</p>
<p>SOFARPC服务器：</p>
<pre class="crayon-plain-tag">public class QuickStartServer {

    public static void main(String[] args) {

        ServerConfig serverConfig = new ServerConfig()
            .setProtocol("bolt") // 设置一个协议，默认bolt
            .setPort(12200) // 设置一个端口，默认12200
            .setDaemon(false); // 非守护线程方式运行

        // 使用ZK作为注册表
        RegistryConfig registryConfig = new RegistryConfig()
            .setProtocol("zookeeper")
            .setAddress("127.0.0.1:2181");

        ProviderConfig providerConfig = new ProviderConfig()
            .setInterfaceId(HelloService.class.getName()) // 指定接口
            .setRef(new HelloServiceImpl()) // 指定实现
            .setServer(serverConfig)  // 指定服务端
            .setRegistry(registryConfig);  // 服务注册表

        providerConfig.export(); // 发布服务
    }
}</pre>
<p>SOFARPC客户端： </p>
<pre class="crayon-plain-tag">public class QuickStartClient {

    private final static Logger LOGGER = LoggerFactory.getLogger(QuickStartClient.class);

    public static void main(String[] args) {

        ConsumerConfig consumerConfig = new ConsumerConfig()
            .setInterfaceId(HelloService.class.getName()) // 指定接口
            .setProtocol("bolt") // 指定协议
            .setDirectUrl("bolt://127.0.0.1:12200") // 指定服务端的直连地址
            .setConnectTimeout(10 * 1000);

        HelloService helloService = consumerConfig.refer();

        helloService.sayHello("world");
    }
}</pre>
<div class="blog_h2"><span class="graybg"><a id="sofagrpc-with-sofoboot"></a>整合SOFABoot</span></div>
<p>SpringBoot应用的名字必须配置：</p>
<pre class="crayon-plain-tag">spring.application.name=test </pre>
<p>你需要为SOFABoot工程引入SOFARPC的starter：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
     &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
     &lt;artifactId&gt;rpc-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p>你可以以POJO的形式定义服务接口和它的实现。</p>
<div class="blog_h3"><span class="graybg">XML方式</span></div>
<p>发布服务时，可以使用下面的XML配置：</p>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:sofa="http://sofastack.io/schema/sofaboot"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://sofastack.io/schema/sofaboot   http://sofastack.io/schema/sofaboot.xsd"
       default-autowire="byName"&gt;
    
    &lt;!-- 服务的Bean定义 --&gt;
    &lt;bean id="personServiceImpl" class="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceImpl"/&gt;

    &lt;!-- 发布SOFARPC服务 --&gt;
    &lt;sofa:service ref="personServiceImpl" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService"&gt;
        &lt;!-- 绑定的通信协议  --&gt;
        &lt;sofa:binding.bolt&gt;
            &lt;sofa:global-attrs timeout="3000" address-wait-time="2000"/&gt;  &lt;!-- 调用超时；地址等待时间 --&gt;
            &lt;sofa:route target-url="127.0.0.1:22000"/&gt;  &lt;!-- 直连到提供者，不走负载均衡 --&gt;
            &lt;sofa:method name="sayName" timeout="3000"/&gt; &lt;!-- 方法级别超时配置 --&gt;
        &lt;/sofa:binding.bolt&gt;
        &lt;sofa:binding.rest/&gt;
    &lt;/sofa:service&gt;

    &lt;!-- 订阅SOFARPC服务 --&gt;
    &lt;sofa:reference id="personReferenceBolt" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService"&gt;
        &lt;sofa:binding.bolt/&gt;
    &lt;/sofa:reference&gt;

    &lt;sofa:reference id="personReferenceRest" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService"&gt;
        &lt;sofa:binding.rest/&gt;
    &lt;/sofa:reference&gt;

&lt;/beans&gt;</pre>
<div class="blog_h3"><span class="graybg">注解方式</span></div>
<pre class="crayon-plain-tag">// 发布服务
@SofaService(interfaceType = AnnotationService.class, 
               bindings = { 
                 @SofaServiceBinding(bindingType = "bolt"),
                 @SofaServiceBinding(bindingType = "bolt") 
               })
@Component
public class AnnotationServiceImpl implements AnnotationService {
    @Override
    public String sayAnnotation(String stirng) {
        return stirng;
    }
}

// 订阅服务
@Component
public class AnnotationClientImpl {

    @SofaReference(interfaceType = AnnotationService.class, 
                     binding = @SofaReferenceBinding(bindingType = "bolt"))
    private AnnotationService annotationService;

    public String sayClientAnnotation(String str) {

        String result = annotationService.sayAnnotation(str);

        return result;
    }
}</pre>
<div class="blog_h2"><span class="graybg">使用过滤器</span></div>
<p>SOFARPC支持过滤器。实现过滤器非常简单：</p>
<pre class="crayon-plain-tag">public class PersonServiceFilter extends Filter {
    @Override
    public SofaResponse invoke(FilterInvoker invoker, SofaRequest request) throws SofaRpcException {
        // 前置钩子
        System.out.println("PersonFilter before");
        try {
            // 执行RPC调用
            return invoker.invoke(request);
        } finally {
        // 后置钩子
            System.out.println("PersonFilter after");
        }
    }
}</pre>
<p>过滤器需要配置才能生效。</p>
<div class="blog_h2"><span class="graybg">Bolt协议</span></div>
<p>Bolt是一个高性能的TCP协议，其性能比HTTP好。</p>
<div class="blog_h3"><span class="graybg">调用类型</span></div>
<p>Bolt支持<span style="background-color: #c0c0c0;">同步、异步、回调、单向</span>等多种调用类型：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 120px; text-align: center;">调用类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Synchronous</td>
<td>默认的调用类型，调用发起后，当前线程会阻塞以等待结果</td>
</tr>
<tr>
<td>Asynchronous</td>
<td>
<p>调用发起后，当前线程立即处理后续逻辑，当调用结果到达后SOFAGRPC会缓存之，你可以异步的调用API以获得结果</p>
<p>XML配置：</p>
<pre class="crayon-plain-tag">&lt;sofa:reference interface="com.example.demo.SampleService" id="sampleService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:global-attrs type="future"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre>
<p>注解配置： </p>
<pre class="crayon-plain-tag">@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", invokeType = "future"))
private SampleService sampleService;</pre>
<p>编程式，使用Spring的情况下： </p>
<pre class="crayon-plain-tag">BoltBindingParam boltBindingParam = new BoltBindingParam();
boltBindingParam.setType("future");</pre>
<p>编程式，不使用Spring的情况下： </p>
<pre class="crayon-plain-tag">ConsumerConfig consumerConfig = new ConsumerConfig()
    .setInterfaceId(SampleService.class.getName())
    .setRegistry(registryConfig)
    .setProtocol("bolt")
    .setInvokeType("future");</pre>
<p>要获得异步响应，调用：</p>
<pre class="crayon-plain-tag">// 第一个参数为超时，第二个参数提示是否删除线程上下文中的调用结果缓存
String result = (String)SofaResponseFuture.getResponse(0, true);</pre>
<p>JDK的Future对象也可以得到：</p>
<pre class="crayon-plain-tag">Future future = SofaResponseFuture.getFuture(true);</pre>
</td>
</tr>
<tr>
<td>Callback</td>
<td>
<p>调用类型名称：callback
<p>类似Asynchronous，不需要等待结果。当结果到达后，自动调用注册的回调函数。你需要实现如下的回调接口：</p>
<pre class="crayon-plain-tag">/**
 * 面向用户的Rpc请求结果监听器
 *
 */
public interface SofaResponseCallback {
    /**
     * SOFA RPC 会在调用成功的响应到达后回调此方法
     *
     * @param appResponse 响应对象
     * @param methodName 被调用的方法
     * @param request 请求对象
     */
    void onAppResponse(Object appResponse, String methodName, RequestBase request);

    /**
     * SOFA RPC 会在服务器端异常时回调此方法
     */
    void onAppException(Throwable throwable, String methodName, RequestBase request);

    /**
     * SOFA RPC 会在框架异常时回调此方法
     *
     */
    void onSofaException(SofaRpcException sofaException, String methodName, RequestBase request);
}</pre>
<p>然后，你可以使用下面的方式注册回调接口。</p>
<p>XML方式：</p>
<pre class="crayon-plain-tag">&lt;!-- 回调Bean --&gt;
&lt;bean id="sampleCallback" class="com.example.demo.SampleCallback"/&gt;
&lt;sofa:reference interface="com.example.demo.SampleService" id="sampleService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;!--                               引用回调Bean                 --&gt;
        &lt;sofa:global-attrs type="callback" callback-ref="sampleCallback"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre>
<p>注解方式：</p>
<pre class="crayon-plain-tag">@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt",
            invokeType = "callback",
            callbackRef = "sampleCallback"))
private SampleService sampleService;</pre>
<p> 编程式，使用Spring的情况下： </p>
<pre class="crayon-plain-tag">BoltBindingParam boltBindingParam = new BoltBindingParam();
boltBindingParam.setType("callback");
boltBindingParam.setCallbackClass("com.example.demo.SampleCallback");</pre>
<p> 编程式，不使用Spring的情况下：  </p>
<pre class="crayon-plain-tag">ConsumerConfig consumerConfig = new ConsumerConfig()
    .setInterfaceId(SampleService.class.getName())
    .setRegistry(registryConfig)
    .setProtocol("bolt")
    .setInvokeType("callback")
    .setOnReturn(new SampleCallback());</pre>
<p>你还可以在调用期间临时的设置：</p>
<pre class="crayon-plain-tag">RpcInvokeContext.getContext().setResponseCallback(new SampleCallback());</pre>
</td>
</tr>
<tr>
<td>Oneway</td>
<td>
<p>调用类型名称：oneway
<p>发送请求后就不管了，不在乎结果如何的情况下使用</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">超时控制</span></div>
<p>使用Bolt协议时默认的超时是3s。你可以在多个级别设置超时。</p>
<p>在服务级别设置：</p>
<pre class="crayon-plain-tag">&lt;sofa:reference interface="com.example.demo.SampleService" id="sampleService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:global-attrs timeout="2000"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre><br />
<pre class="crayon-plain-tag">@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", timeout = 2000)) 
private SampleService sampleService;</pre>
<p>在方法级别设置： </p>
<pre class="crayon-plain-tag">&lt;sofa:reference interface="com.example.demo.SampleService" id="sampleService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:method name="hello" timeout="2000"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre>
<div class="blog_h3"><span class="graybg">泛化调用</span></div>
<p>SOFARPC允许消费者在不知道服务接口的情况下发起调用。前提条件是：</p>
<ol>
<li>使用Bolt作为通信协议</li>
<li>使用Hessian 2作为串行化协议 </li>
</ol>
<div class="blog_h3"><span class="graybg">串行化协议</span></div>
<p>SOFARPC支持Hessian 2、protobuf两个串行化协议，默认使用前者。如果要修改，参考：</p>
<pre class="crayon-plain-tag">sofa:service ref="sampleService" interface="com.alipay.sofarpc.demo.SampleService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:global-attrs serialize-type="protobuf"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:service&gt;
&lt;!-- 提供者/消费者都需要设置 --&gt;
&lt;sofa:reference interface="com.alipay.sofarpc.demo.SampleService" id="sampleServiceRef" jvm-first="false"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:global-attrs serialize-type="protobuf"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre>
<div class="blog_h3"><span class="graybg">定制线程池</span></div>
<pre class="crayon-plain-tag">&lt;bean id="helloService" class="com.alipay.sofa.rpc.quickstart.HelloService"/&gt;

&lt;!-- 自定义一个线程池 --&gt;
&lt;bean id="customExecutor" class="com.alipay.sofa.rpc.server.UserThreadPool" init-method="init"&gt;
    &lt;property name="corePoolSize" value="10" /&gt;
    &lt;property name="maximumPoolSize" value="10" /&gt;
    &lt;property name="queueSize" value="0" /&gt;
&lt;/bean&gt;

&lt;sofa:service ref="helloService" interface="XXXService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;!-- 引用线程池 --&gt;
        &lt;sofa:global-attrs thread-pool-ref="customExecutor"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:service&gt;</pre>
<p>或者，使用注解方式：</p>
<pre class="crayon-plain-tag">@SofaService(bindings = {@SofaServiceBinding(bindingType = "bolt", userThreadPool = "customThreadPool")})
public class SampleServiceImpl implements SampleService {
}</pre>
<div class="blog_h2"><span class="graybg">RESTful协议 </span></div>
<p>SOFARPC支持RESTful协议，使用此协议时，你需要为服务接口添加JAX-RS注解：</p>
<pre class="crayon-plain-tag">@Path("sample")
public interface SampleService {
    @GET
    @Path("hello")
    String hello();
}</pre>
<p>发布服务的方式如下：</p>
<pre class="crayon-plain-tag">@Service
//                                           设置绑定类型
@SofaService(bindings = {@SofaServiceBinding(bindingType = "rest")})
public class RestfulSampleServiceImpl implements SampleService {
    @Override
    public String hello() {
        return "Hello";
    }
}</pre>
<p>这种服务直接可以通过浏览器访问： http://localhost:8341/sample/hello</p>
<p>在SOFARPC客户端，消费服务的方式如下：</p>
<pre class="crayon-plain-tag">@SofaReference(binding = @SofaReferenceBinding(bindingType = "rest"))
private SampleService sampleService;</pre>
<div class="blog_h2"><span class="graybg">注册中心 </span></div>
<p>SOFARPC支持多种注册中心。当前bolt、rest、duboo传输协议均支持ZooKeeper作为注册中心，bolt、rest还支持本地文件系统作为注册中心（主要用于测试）。</p>
<div class="blog_h3"><span class="graybg">SOFARegistry</span></div>
<p>当前SOFARPC（SOFARPC: 5.5.2, SOFABoot: 2.6.3）已经支持SOFARegistry注册中心：</p>
<pre class="crayon-plain-tag">com.alipay.sofa.rpc.registry.address=sofa://127.0.0.1:9603</pre>
<div class="blog_h3"><span class="graybg">Zookeeper</span></div>
<p>要使用此注册中心，配置：</p>
<pre class="crayon-plain-tag">com.alipay.sofa.rpc.registry.address=zookeeper://127.0.0.1:2181
# 指定身份验证信息
com.alipay.sofa.rpc.registry.address=zookeeper://xxx:2181?file=/home/admin/registry&amp;scheme=digest&amp;addAuth=sofazk:rpc1</pre>
<div class="blog_h3"><span class="graybg">本地文件系统 </span></div>
<p>要使用此注册中心，配置：</p>
<pre class="crayon-plain-tag">com.alipay.sofa.rpc.registry.address=local:///home/admin/registry/localRegistry.reg</pre>
<div class="blog_h2"><span class="graybg">直接调用</span></div>
<p>服务消费者可以直接指定服务提供者的地址： </p>
<pre class="crayon-plain-tag">ConsumerConfig consumer = new ConsumerConfig()        
            .setInterfaceId(HelloService.class.getName())        
            .setRegistry(registryConfig)        
            .setDirectUrl("bolt://127.0.0.1:12201");</pre><br />
<pre class="crayon-plain-tag">&lt;sofa:reference interface="com.alipay.sample.HelloService" id="helloService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:route target-url="127.0.0.1:12200"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre><br />
<pre class="crayon-plain-tag">@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", directUrl = "127.0.0.1:12220"))
private SampleService sampleService;</pre>
<p>而不经过SOFARPC的负载均衡系统。</p>
<div class="blog_h2"><span class="graybg">负载均衡 </span></div>
<p>目前支持的负载均衡算法：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 120px; text-align: center;">算法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>random</td>
<td>随即选取服务提供者，默认</td>
</tr>
<tr>
<td>localPref</td>
<td>如果存在本地可用的服务提供者，优先使用之</td>
</tr>
<tr>
<td>roundRobin</td>
<td>轮询算法</td>
</tr>
<tr>
<td>consistentHash</td>
<td>一致性哈希算法，每个相同方法级别的请求路由到同一节点</td>
</tr>
<tr>
<td>weightRoundRobin</td>
<td>加权的轮询</td>
</tr>
</tbody>
</table>
<p>配置负载均衡算法的方式如下： </p>
<pre class="crayon-plain-tag">&lt;sofa:reference interface="com.example.demo.SampleService" id="sampleService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:global-attrs loadBalancer="roundRobin"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre>
<div class="blog_h2"><span class="graybg">重试</span></div>
<p>重试的配置方式如下：</p>
<pre class="crayon-plain-tag">&lt;sofa:reference jvm-first="false" id="retriesServiceReferenceBolt" interface="com.alipay.sofa.rpc.samples.retries.RetriesService"&gt;
   &lt;sofa:binding.bolt&gt;
     &lt;sofa:global-attrs retries="2"/&gt;
   &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre><br />
<pre class="crayon-plain-tag">@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", retries = 2))
private SampleService sampleService;</pre>
<div class="blog_h2"><span class="graybg">分布式追踪</span></div>
<p>SOFARPC目前支持以下Tracer：</p>
<ol>
<li>SOFATracer，内置集成</li>
<li>Skywalking</li>
</ol>
<p>如果要禁用分布式追踪特性，可以配置：</p>
<pre class="crayon-plain-tag">com.alipay.sofa.rpc.defaultTracer=</pre>
<div class="blog_h2"><span class="graybg">容错性</span></div>
<p>SOFARPC支持内置的容错机制，还可以和Hystrix进行集成。</p>
<div class="blog_h3"><span class="graybg">自动剔除</span></div>
<p>运行机制：</p>
<ol>
<li>单机故障剔除会<span style="background-color: #c0c0c0;">统计一个时间窗口内的调用次数和异常次数</span>，并计算<span style="background-color: #c0c0c0;">每个服务对应ip的异常率和该服务的平均异常率</span></li>
<li>当达到ip<span style="background-color: #c0c0c0;">异常率大于服务平均异常率到一定比例</span>时，会对<span style="background-color: #c0c0c0;">服务+ip的维度进行权重降级</span></li>
<li>如果该服务+ip维度的<span style="background-color: #c0c0c0;">权重并没有降为0，那么当该服务+ip维度的调用情况正常时，则会对其进行权重恢复</span></li>
<li>整个计算和调控过程异步进行，不会阻塞调用</li>
</ol>
<p>使用这种单机故障剔除，需要进行如下配置：</p>
<pre class="crayon-plain-tag">FaultToleranceConfig faultToleranceConfig = new FaultToleranceConfig();
        faultToleranceConfig.setRegulationEffective(true);
        faultToleranceConfig.setDegradeEffective(true);
        // 时间窗口 20s
        faultToleranceConfig.setTimeWindow(20);
        // 如果被判定为故障，则权重掉1/2
        faultToleranceConfig.setWeightDegradeRate(0.5);

FaultToleranceConfigManager.putAppConfig("appName", faultToleranceConfig);</pre>
<div class="blog_h3"><span class="graybg">集成Hystrix</span></div>
<p>基于Hystrix的熔断能力，目前还不健全。要使用此特性，添加Hystrix的依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
        &lt;groupId&gt;com.netflix.hystrix&lt;/groupId&gt;
        &lt;artifactId&gt;hystrix-core&lt;/artifactId&gt;
        &lt;version&gt;1.5.12&lt;/version&gt;
&lt;/dependency&gt;</pre>
<p>然后显式启用Hystrix（导致Hystrix过滤器被加载）： </p>
<pre class="crayon-plain-tag">// 全局开启
RpcConfigs.putValue(HystrixConstants.SOFA_HYSTRIX_ENABLED, true);

// 对特定 Consumer 开启
ConsumerConfig consumerConfig = new ConsumerConfig()
        .setInterfaceId(HelloService.class.getName())
        .setParameter(HystrixConstants.SOFA_HYSTRIX_ENABLED, String.valueOf(true));</pre>
<p>FallbackFactory接口用于注入Hystrix的Fallback，当Hystrix遇到异常、超时、线程池拒绝、熔断等情况时，指定执行此Fallback（降级）逻辑：</p>
<pre class="crayon-plain-tag">// 可以直接使用默认的 FallbackFactory 直接注入 Fallback 实现
SofaHystrixConfig.registerFallback(consumerConfig, new HelloServiceFallback());

// 也可以自定义 FallbackFactory 直接注入 FallbackFactory
SofaHystrixConfig.registerFallbackFactory(consumerConfig, new HelloServiceFallbackFactory());</pre>
<div class="blog_h2"><span class="graybg">优雅关闭</span></div>
<p>SOFARPC注册了JVM的ShutdownHook，用于实现优雅的资源清理。 </p>
<div class="blog_h1"><span class="graybg">SOFARegistry</span></div>
<p>这是一个服务注册中心，和ZooKeeper、Etcd等项目对比有自己的特点：</p>
<ol>
<li>高度可扩容：数据分片存储，理论上可以支持无限水平扩容</li>
<li>低延迟：基于SOFABolt框架，实现TCP长连接下的变更推送。主流注册中心都是这种架构</li>
<li>高可用：在CAP中保证AP，<span style="background-color: #c0c0c0;">放弃严格一致性，尽可能在网络分区时保证可用性</span>。这和ZooKeeper、Etcd不同。需要注意的是，<span style="background-color: #c0c0c0;">Envoy xDS协议也是最终一致性的</span></li>
</ol>
<p>在架构上，SOFARegistry引入4种角色：</p>
<ol>
<li>Client：通过客户端JAR包接入注册中心</li>
<li>SessionServer：直接处理Client的服务发布/订阅请求，可以无限扩容。<span style="background-color: #c0c0c0;">客户端仅和SessionServer交互</span></li>
<li>DataServer：负责存储服务的元数据，使用一致性哈希分片存储，支持多副本，可以无限扩容</li>
<li>MetaServer：维护SessionServer、DataServer集群的一致性列表，在节点发生变更时发出通知</li>
</ol>
<div class="blog_h2"><span class="graybg">术语列表</span></div>
<div class="blog_h3"><span class="graybg">RPC通用术语</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">术语</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>服务<br />Service</td>
<td>通过网络提供的、具有特定业务逻辑处理能力的软件功能</td>
</tr>
<tr>
<td>服务提供者<br />Service Provider</td>
<td>通过网络提供服务的计算机节点</td>
</tr>
<tr>
<td>服务消费者<br />Service Consumer</td>
<td>通过网络调用服务的计算机节点。一个计算机节点可以既作为一些服务的提供者，又作为一些服务的消费者</td>
</tr>
<tr>
<td>服务发现<br />Service Discovery</td>
<td>服务消费者获取服务提供者的网络地址的过程</td>
</tr>
<tr>
<td>服务注册中心<br />Service Registry</td>
<td>一种提供服务发现功能的软件系统，帮助服务消费者获取服务提供者的网络地址</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg"> 专门术语</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">术语</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>数据（Data）</td>
<td>在服务发现场景下，特指服务提供者的网络地址及其它附加信息。其他场景下，也可以表示任意发布到 SOFARegistry 的信息</td>
</tr>
<tr>
<td>单元（Zone）</td>
<td>单元化架构关键概念，在服务发现场景下，单元是一组发布与订阅的集合，发布及订阅服务时需指定单元名</td>
</tr>
<tr>
<td>发布者（Publisher）</td>
<td>发布数据到 SOFARegistry 的节点。在服务发现场景下，服务提供者就是“服务提供者的网络地址及其它附加信息”的发布者</td>
</tr>
<tr>
<td>订阅者（Subscriber）</td>
<td>从 SOFARegistry 订阅数据的节点。在服务发现场景下，服务消费者就是“服务提供者的网络地址及其它附加信息”的订阅者</td>
</tr>
<tr>
<td>数据标识（DataId）</td>
<td>用来标识数据的字符串。在服务发现场景下，通常由服务接口名、协议、版本号等信息组成，作为服务的标识</td>
</tr>
<tr>
<td>分组标识（GroupId）</td>
<td>用于为数据归类的字符串，可以作为数据标识的命名空间，即只有 DataId、GroupId、InstanceId 都相同的服务，才属于同一服务</td>
</tr>
<tr>
<td>实例 ID（InstanceId）</td>
<td>实例 ID，可以作为数据标识的命名空间，即只有DataId、GroupId、InstanceId都相同的服务，才属于同一服务</td>
</tr>
<tr>
<td>会话服务器（SessionServer）</td>
<td>SOFARegistry 内部负责跟客户端建立 TCP 长连接、进行数据交互的一种服务器角色</td>
</tr>
<tr>
<td>数据服务器（DataServer）</td>
<td>SOFARegistry 内部负责数据存储的一种服务器角色。</td>
</tr>
<tr>
<td>元信息服务器（MetaServer）</td>
<td>SOFARegistry 内部基于 Raft 协议，负责集群内一致性协调的一种服务器角色。</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">起步</span></div>
<div class="blog_h3"><span class="graybg">部署服务器</span></div>
<p>支持独立部署、集成部署方式。后者比较简单，单节点部署，可以用于测试。</p>
<pre class="crayon-plain-tag">wget https://github.com/alipay/sofa-registry/releases/download/v5.2.0/registry-integration-5.2.0.tar.gz
mkdir registry-integration
tar -zxvf registry-integration-5.2.0.tar.gz -C registry-integration
cd registry-integration

# 启动脚本位于
bin/startup.sh</pre>
<p>启动服务器后，执行下面的命令查看各服务器端角色的健康状况：</p>
<pre class="crayon-plain-tag"># 查看meta角色的健康检测接口：
curl http://localhost:9615/health/check
# {"success":true,"message":"... raftStatus:Leader"}

# 查看data角色的健康检测接口：
curl http://localhost:9622/health/check
# {"success":true,"message":"... status:WORKING"}

# 查看session角色的健康检测接口：
curl http://localhost:9603/health/check
# {"success":true,"message":"..."}</pre>
<div class="blog_h3"><span class="graybg">使用客户端</span></div>
<p>引入依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;    
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;registry-client-all&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p>下面的代码演示了如何发布数据到SOFARegistry上： </p>
<pre class="crayon-plain-tag">// 构建客户端实例
RegistryClientConfig config = DefaultRegistryClientConfigBuilder.start()
    // 服务器连接地址，任意一个Session节点都可以
    .setRegistryEndpoint("127.0.0.1").setRegistryEndpointPort(9603).build();
DefaultRegistryClient registryClient = new DefaultRegistryClient(config);
registryClient.init();

// 构造发布者注册表
// dataId是此客户端发布的服务的唯一标识
String dataId = "com.alipay.test.demo.service:1.0@DEFAULT";
PublisherRegistration registration = new PublisherRegistration(dataId);

// 将注册表注册进客户端并发布数据
registryClient.register(registration, "10.10.1.1:12200?xx=yy");</pre>
<p>下面的代码演示了如何从SOFARegistry订阅数据：</p>
<pre class="crayon-plain-tag">// 构建客户端实例
RegistryClientConfig config = DefaultRegistryClientConfigBuilder.start()
    .setRegistryEndpoint("127.0.0.1").setRegistryEndpointPort(9603).build();
DefaultRegistryClient registryClient = new DefaultRegistryClient(config);
registryClient.init();

// 创建 SubscriberDataObserver 
SubscriberDataObserver subscriberDataObserver = new SubscriberDataObserver() {
  	public void handleData(String dataId, UserData userData) {
                // 在这里处理接收到的数据
    		System.out.println("receive data success, dataId: " + dataId + ", data: " + userData);
  	}
};

// 构造订阅者注册表
String dataId = "com.alipay.test.demo.service:1.0@DEFAULT";
SubscriberRegistration registration = new SubscriberRegistration(dataId, subscriberDataObserver);
// 订阅的范围：zone, dataCenter, global
registration.setScopeEnum(ScopeEnum.global);

// 将注册表注册进客户端并订阅数据，订阅到的数据会以回调的方式通知 SubscriberDataObserver
registryClient.register(registration);</pre>
<p>有数据更新后，服务器会推送并回调，你需要在回调中处理 UserData：</p>
<pre class="crayon-plain-tag">public interface UserData {
    // 以Zone分组的数据
    Map&lt;String, List&gt; getZoneData();
    // 当前Zone
    String getLocalZone();
}</pre>
<div class="blog_h1"><span class="graybg">SOFATracer</span></div>
<p>遵循OpenTracing规范的Tracer。可以将将一个Trace的链路信息打印到日志或发送给Zipkin进行展示。 特性包括：</p>
<ol>
<li>基于<a href="https://github.com/LMAX-Exchange/disruptor">Disruptor</a>实现高性能的Trace日志落盘。支持两种打印类型：
<ol>
<li>摘要日志：每一次调用均会落地磁盘的日志</li>
<li>统计日志：每隔一定时间间隔进行统计输出的日志</li>
</ol>
</li>
<li>支持日志自清除和轮换</li>
<li>集成SLF4J的MDC，修改日志配置即可输出当前Trace上下文的TraceId 和 SpanId</li>
<li>已开发多种开源项目的埋点：
<ol>
<li>Spring MVC</li>
<li>基于标准JDBC接口的数据库连接池，包括DBCP、Druid、c3p0、tomcat、HikariCP、BoneCP</li>
<li>HttpClient</li>
<li>RestTemplate</li>
<li>OkHttp</li>
<li>Dubbo</li>
<li>OpenFeign</li>
<li>Redis、消息中间件的埋点仍然在开发中 </li>
</ol>
</li>
</ol>
<div class="blog_h2"><span class="graybg">术语列表</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">术语</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>TraceId</td>
<td>
<p>在SOFATracer 中代表一次请求的唯一标识，此 ID 一般<span style="background-color: #c0c0c0;">由集群中第一个处理请求的系统产生</span>，并在分布式调用下通过网络传递到下一个被请求系统</p>
<p>TraceId的示例：</p>
<pre class="crayon-plain-tag"># 启动Trace的那个服务器的IP地址，十六进制
#        Trace产生的时间戳
#                      自增序列
#                           当前进程ID   
0ad1348f 1403169275002 1003 56696</pre>
</td>
</tr>
<tr>
<td>SpanId</td>
<td>SpanId 代表了本次请求在整个调用链路中的位置或者说层次，比如 A 系统在处理一个请求的过程中依次调用了 B，C，D 三个系统，那么这三次调用的的 SpanId 分别是：0.1，0.2，0.3。如果 B 系统继续调用了 E，F 两个系统，那么这两次调用的 SpanId 分别是：0.1.1，0.1.2</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">配置</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 38%; text-align: center;">配置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>logging.path</td>
<td>
<p>日志输出目录
<p>SOFATracer 会优先输出到 logging.path 目录下；如果没有配置日志输出目录，那默认输出到 ${user.home}</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.disableDigestLog</td>
<td>
<p>是否关闭所有集成 SOFATracer 组件摘要日志打印</p>
<p>默认false</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.disableConfiguration[${logType}]</td>
<td>
<p>关闭指定 ${logType} 的 SOFATracer 组件摘要日志打印。${logType}是指具体的日志类型，如：spring-mvc-digest.log</p>
<p>默认false</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.tracerGlobalRollingPolicy</td>
<td>
<p>SOFATracer 日志的滚动策略</p>
<p>.yyyy-MM-dd：按照天滚动，默认<br />.yyyy-MM-dd_HH：按照小时滚动</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.tracerGlobalLogReserveDay</td>
<td>
<p>SOFATracer 日志的保留天数</p>
<p>默认保留 7 天</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.statLogInterval</td>
<td>
<p>统计日志的时间间隔，单位：秒</p>
<p>默认 60 秒统计日志输出一次</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.baggageMaxLength</td>
<td>
<p>透传数据能够允许存放的最大长度</p>
<p>默认值 1024</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.zipkin.enabled</td>
<td>
<p>是否开启 SOFATracer 远程上报数据到 Zipkin</p>
<p>true：开启上报；false：关闭上报。默认不上报</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.zipkin.baseUrl</td>
<td>
<p>SOFATracer 远程上报数据到 Zipkin 的地址，com.alipay.sofa.tracer.zipkin.enabled=true时配置此地址才有意义</p>
<p>格式：http://${host}:${port}</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.springmvc.filterOrder</td>
<td>SOFATracer 集成在 SpringMVC 的 Filter 生效的 Order</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.springmvc.urlPatterns</td>
<td>
<p>SOFATracer 集成在 SpringMVC 的 Filter 生效的 URL Pattern 路径</p>
<p>默认/*，全部生效</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">埋点植入</span></div>
<p>&nbsp;</p>
<div class="blog_h2"><span class="graybg">SLF4J集成</span></div>
<p>SLF4J 提供了 MDC （Mapped Diagnostic Contexts）功能，支持用户定义和修改日志的输出格式以及内容。SOFATracer支持基于MDC来输出当前Trace上下文的TraceId、SpanId。</p>
<p>需要引入SLF4J的API，以及日志实现包的Maven依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
    &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;!-- Logback --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-logging&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;!-- Log4j2 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-log4j2&lt;/artifactId&gt;
    &lt;!--SOFABoot没有管控log4j2 版本 --&gt;
    &lt;version&gt;1.4.2.RELEASE&lt;/version&gt;
&lt;/dependency&gt;</pre>
<p>配置日志输出的PatternLayout：</p>
<pre class="crayon-plain-tag">&lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss.SSS} %5p  [%X{SOFA-TraceId}, %X{SOFA-SpanId}] -- %m%n&lt;/pattern&gt;</pre>
<p>%X表示，在调用appender输出日志时，将此占位符替换为当前线程上下文（MDC）中的SOFA-TraceId、SOFA-SpanId变量。</p>
<div class="blog_h2"><span class="graybg">异步传递追踪上下文</span></div>
<div class="blog_h3"><span class="graybg">Runnable</span></div>
<p>如果用户启动新线程来处理业务，则基于线程本地变量传递的Trace信息就丢失了。为了将SOFATracer日志上下文从父线程传递到子线程，可以考虑用SofaTracerRunnable：</p>
<pre class="crayon-plain-tag">//                         使用SOFATracer提供的包装器
Thread thread = new Thread(new SofaTracerRunnable(new Runnable() {
            @Override
            public void run() {
                // 异步业务逻辑
            }
        }));
thread.start();</pre>
<div class="blog_h3"><span class="graybg">Callable</span></div>
<p>基于java.util.concurrent.Callable发动新线程时类似：</p>
<pre class="crayon-plain-tag">ExecutorService executor = Executors.newCachedThreadPool();
//                                                            使用SOFATracer提供的包装器
SofaTracerCallable&lt;Object&gt; sofaTracerSpanSofaTracerCallable = new SofaTracerCallable&lt;Object&gt;(new Callable&lt;Object&gt;() {
    @Override
    public Object call() throws Exception {
        // 异步业务逻辑
        return ...;
    }
});
Future&lt;Object&gt; futureResult = executor.submit(sofaTracerSpanSofaTracerCallable);
// ...
Object objectReturn = futureResult.get();</pre>
<div class="blog_h2"><span class="graybg">采样模式</span></div>
<p>SOFATracer支持两种采样模式：</p>
<ol>
<li> 基于BitSet实现的固定采样率的采样模式</li>
<li>用户自定义实现采样的采样模式</li>
</ol>
<div class="blog_h3"><span class="graybg">固定采样率</span></div>
<p>进行以下配置即可：</p>
<pre class="crayon-plain-tag"># 采样率0-100之间
com.alipay.sofa.tracer.samplerPercentage=100
# 采样模式类型名称
com.alipay.sofa.tracer.samplerName=PercentageBasedSampler</pre>
<p>&nbsp;</p>
<div class="blog_h1"><span class="graybg">SOFALookout</span></div>
<p>此项目解决的是监控领域的问题，提供指标的埋点、收集、加工、存储、查询等服务，分为客户端、服务器两个部分。</p>
<p>此项目已经快一年没有更新。</p>
<div class="blog_h1"><span class="graybg">SOFAMesh</span></div>
<p>扩展了Istio项目，并做了以下改进：</p>
<ol>
<li>将数据平面的Envoy替换为SOFAMosn</li>
<li><span style="background-color: #c0c0c0;">下沉Mixer的功能到数据平面，提升性能</span></li>
<li>扩展Pilot，支持更多的服务发现机制（服务注册表），包括SOFARPC、Dubbo</li>
</ol>
<p>SOFARPC、Dubbo之类的入侵式框架可以在Istio中运行，但是无法对其进行任何管理、监控。SOFAMesh解决了这些痛点。</p>
<div class="blog_h1"><span class="graybg">SOFAMosn</span></div>
<p>MOSN（Modular Observable Smart Network，模块化可观察智能网络…）是Envoy的替代品，其出现的主要原因不是Envoy不行，而是阿里系不愿引入C++技术栈。</p>
<p>MOSN增加了对SOFARPC、Dubbo协议的支持，后者仍然在开发中。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/sofastack-study-note">SOFAStack学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/sofastack-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Bazel学习笔记</title>
		<link>https://blog.gmem.cc/bazel-study-note</link>
		<comments>https://blog.gmem.cc/bazel-study-note#comments</comments>
		<pubDate>Mon, 07 Jan 2019 08:46:20 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=24313</guid>
		<description><![CDATA[<p>简介 Bazel是Google开源的，类似于Make、Maven或Gradle的构建和测试工具。它使用可读性强的、高层次的构建语言，支持多种编程语言，以及为多种平台进行交叉编译。 Bazel的优势： 高层次的构建语言：更加简单，Bazel抽象出库、二进制、脚本、数据集等概念，不需要编写调用编译器或链接器的脚本 快而可靠：能够缓存所有已经完成的工作步骤，并且跟踪文件内容、构建命令的变动情况，避免重复构建。此外Bazel还支持高度并行构建、增量构建 多平台支持：可以在Linux/macOS/Windows上运行，可以构建在桌面/服务器/移动设备上运行的应用程序 可扩容性：处理10万以上源码文件时仍然能保持速度 可扩展性：支持Android、C/C++、Java、Objective-C、Protocol Buffer、Python…还支持扩展以支持其它语言 如何工作 当运行构建或者测试时，Bazel会： 加载和目标相关的BUILD文件 分析输入及其依赖，应用指定的构建规则，产生一个Action图。这个图表示需要构建的目标、目标之间的关系，以及为了构建目标需要执行的动作。Bazel依据此图来跟踪文件变动，并确定哪些目标需要重新构建 针对输入执行构建动作，直到最终的构建输出产生出来 如何使用 当你需要构建或者测试一个项目时，通常执行以下步骤： 下载并安装Bazel 创建一个工作空间。Bazel从此工作空间寻找构建输入和BUILD文件，同时也将构建输出存放在（指向）工作空间（的符号链接中） 编写BUILD文件，以及可选的WORKSPACE文件，告知Bazel需要构建什么，如何构建。此文件基于Starlark这种DSL 从命令行调用Bazel命令，构建、测试或者运行项目 概念和术语 Workspace <a class="read-more" href="https://blog.gmem.cc/bazel-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/bazel-study-note">Bazel学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Bazel是Google开源的，类似于Make、Maven或Gradle的构建和测试工具。它使用可读性强的、高层次的构建语言，支持多种编程语言，以及为多种平台进行交叉编译。</p>
<p>Bazel的优势：</p>
<ol>
<li>高层次的构建语言：更加简单，Bazel抽象出<span style="background-color: #c0c0c0;">库、二进制、脚本、数据集等</span>概念，不需要编写调用编译器或链接器的脚本</li>
<li>快而可靠：能够缓存所有已经完成的工作步骤，并且跟踪文件内容、构建命令的变动情况，避免重复构建。此外Bazel还支持高度<span style="background-color: #c0c0c0;">并行构建、增量构建</span></li>
<li>多平台支持：可以在Linux/macOS/Windows上运行，可以构建在桌面/服务器/移动设备上运行的应用程序</li>
<li>可扩容性：处理10万以上源码文件时仍然能保持速度</li>
<li>可扩展性：支持Android、C/C++、Java、Objective-C、Protocol Buffer、Python…还支持扩展以支持其它语言</li>
</ol>
<div class="blog_h2"><span class="graybg">如何工作</span></div>
<p>当运行构建或者测试时，Bazel会：</p>
<ol>
<li>加载和目标相关的<span style="background-color: #c0c0c0;">BUILD</span>文件</li>
<li>分析输入及其依赖，应用指定的<span style="background-color: #c0c0c0;">构建规则</span>，产生一个Action图。这个图表示需要构建的目标、目标之间的关系，以及为了构建目标需要执行的动作。Bazel依据此图来跟踪文件变动，并确定哪些目标需要重新构建</li>
<li>针对输入执行构建动作，直到最终的构建输出产生出来</li>
</ol>
<div class="blog_h2"><span class="graybg">如何使用</span></div>
<p>当你需要构建或者测试一个项目时，通常执行以下步骤：</p>
<ol>
<li>下载并安装Bazel</li>
<li>创建一个工作空间。Bazel从此工作空间寻找构建输入和BUILD文件，同时也将构建输出存放在（指向）工作空间（的符号链接中）</li>
<li>编写BUILD文件，以及可选的WORKSPACE文件，告知Bazel需要构建什么，如何构建。此文件基于<span style="background-color: #c0c0c0;">Starlark这种DSL</span></li>
<li>从命令行调用Bazel命令，构建、测试或者运行项目</li>
</ol>
<div class="blog_h2"><span class="graybg">概念和术语</span></div>
<div class="blog_h3"><span class="graybg">Workspace</span></div>
<p>工作空间是一个目录，它包含：</p>
<ol>
<li>构建目标所需要的源码文件，以及相应的BUILD文件</li>
<li>指向构建结果的符号链接</li>
<li>WORKSPACE文件，可以为空，可以包含对外部依赖的引用</li>
</ol>
<div class="blog_h3"><span class="graybg">Package</span></div>
<p>包是工作空间中主要的代码组织单元，其中包含一系列相关的文件（主要是代码）以及描述这些文件之间关系的BUILD文件</p>
<p>包是工作空间的子目录，它的<span style="background-color: #c0c0c0;">根目录必须包含文件BUILD.bazel或BUILD</span>。<span style="background-color: #c0c0c0;">除了那些具有BUILD文件的子目录——子包——</span>以外，其它子目录属于包的一部分</p>
<div class="blog_h3"><span class="graybg">Target</span></div>
<p>包是一个容器，它的元素定义在BUILD文件中，包括：</p>
<ol>
<li>规则（Rule），指定输入集和输出集之间的关系，声明从输入产生输出的必要步骤。<span style="background-color: #c0c0c0;">一个规则的输出可以是另外一个规则的输入</span></li>
<li>文件（File），可以分为两类：
<ol>
<li>源文件</li>
<li>自动生成的文件（Derived files），由构建工具依据规则生成</li>
</ol>
</li>
<li>包组：一组包，<span style="background-color: #c0c0c0;">包组用于限制特定规则的可见性</span>。包组由函数package_group定义，参数是包的列表和包组名称。你可以在规则的visibility属性中引用包组，声明那些包组可以引用当前包中的规则</li>
</ol>
<p>任何包生成的文件都属于当前包，不能为其它包生成文件。但是可以<span style="background-color: #c0c0c0;">从其它包中读取输入</span></p>
<div class="blog_h3"><span class="graybg">Label</span></div>
<p>引用一个目标时需要使用“标签”。标签的规范化表示：<pre class="crayon-plain-tag">@project//my/app/main:app_binary</pre>， 冒号前面是所属的包名，后面是目标名。如果不指定目标名，则默认以包路径最后一段作为目标名，例如：</p>
<pre class="crayon-plain-tag">//my/app
//my/app:app</pre>
<p>这两者是等价的。在BUILD文件中，引用当前包中目标时，包名部分可以省略，因此下面四种写法都可以等价：</p>
<pre class="crayon-plain-tag"># 当前包为my/app
//my/app:app
//my/app
:app
app</pre>
<p>在BUILD文件中，引用当前包中定义的<span style="background-color: #c0c0c0;">规则</span>时，<span style="background-color: #c0c0c0;">冒号不能省略</span>。引用当前包中<span style="background-color: #c0c0c0;">文件</span>时，<span style="background-color: #c0c0c0;">冒号可以省略</span>。 例如：<pre class="crayon-plain-tag">generate.cc</pre>。</p>
<p>但是，<span style="background-color: #c0c0c0;">从其它包引用时、从命令行引用时，都必须使用完整的标签</span>：<pre class="crayon-plain-tag">//my/app:generate.cc</pre></p>
<p>@project这一部分通常不需要使用，引用外部存储库中的目标时，project填写外部存储库的名字。</p>
<div class="blog_h3"><span class="graybg">Rule</span></div>
<p>规则指定<span style="background-color: #c0c0c0;">输入和输出之间的关系</span>，并且说明<span style="background-color: #c0c0c0;">产生输出的步骤</span>。</p>
<p>规则有很多类型。每个规则都具有一个名称属性，<span style="background-color: #c0c0c0;">此名称亦即目标名称</span>。对于某些规则，此名称就是产生的输出的文件名。</p>
<p>在BUILD中声明规则的语法时：</p>
<pre class="crayon-plain-tag">规则类型(
    name = "...",
    其它属性 = ...
)</pre>
<div class="blog_h3"><span class="graybg">BUILD文件</span></div>
<p>BUILD文件定义了包的所有元数据。其中的语句被<span style="background-color: #c0c0c0;">从上而下的逐条解释</span>，某些语句的顺序很重要， 例如<span style="background-color: #c0c0c0;">变量必须先定义</span>后使用，但是<span style="background-color: #c0c0c0;">规则声明的顺序无所谓</span>。</p>
<p>BUILD文件仅能包含ASCII字符，且不得声明函数、使用for/if语句，你可以在Bazel扩展——扩展名为.bzl的文件中声明函数、控制结构。并在BUILD文件中用load语句加载Bazel扩展：</p>
<pre class="crayon-plain-tag">load("//foo/bar:file.bzl", "some_library")</pre>
<p>上面的语句加载foo/bar/file.bzl并添加其中定义的符号some_libraray到当前环境中，load语句可以用来加载规则、函数、常量（字符串、列表等）。</p>
<p><span style="background-color: #c0c0c0;">load语句必须出现在顶级作用域</span>，不能出现在函数中。第一个参数说明扩展的位置，你可以为导入的符号设置别名。</p>
<p>规则的类型，一般以编程语言为前缀，例如cc，java，后缀通常有：</p>
<ol>
<li>*_binary 用于构建目标语言的可执行文件</li>
<li>*_test 用于自动化测试，其目标是可执行文件，如果测试通过应该退出0</li>
<li>*_library 用于构建目标语言的库 </li>
</ol>
<div class="blog_h3"><span class="graybg">Dependency</span></div>
<p>目标A依赖B，就意味着A在<span style="background-color: #c0c0c0;">构建或执行期间需要B</span>。所有目标的依赖关系构成非环有向图（DAG）称为依赖图。</p>
<p>距离为1的依赖称为直接依赖，大于1的依赖则称为传递性依赖。</p>
<p>依赖分为以下几种：</p>
<ol>
<li>srcs依赖：直接被当前规则消费的文件</li>
<li>deps依赖：独立编译的模块，为当前规则提供头文件、符号、库、数据</li>
<li>data依赖：不属于源码，不影响目标如何构建，但是目标在运行时可能依赖之</li>
</ol>
<div class="blog_h1"><span class="graybg">安装</span></div>
<div class="blog_h2"><span class="graybg">Bazel</span></div>
<div class="blog_h3"><span class="graybg">Ubuntu</span></div>
<p>参考下面的步骤安装Bazel：</p>
<pre class="crayon-plain-tag">echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add -

sudo apt-get update &amp;&amp; sudo apt-get install bazel</pre>
<p>可以用如下命令升级到最新版本的Bazel：</p>
<pre class="crayon-plain-tag">sudo apt-get install --only-upgrade bazel</pre>
<div class="blog_h2"><span class="graybg">Bazelisk</span></div>
<p>这是基于Go语言编写的Bazel启动器，它会为你的工作区下载最适合的Bazel，并且透明的将命令转发给该Bazel。</p>
<p>由于Bazellisk提供了和Bazel一样的接口，因此通常直接将其命名为bazel：</p>
<pre class="crayon-plain-tag">sudo wget -O /usr/local/bin/bazel https://github.com/bazelbuild/bazelisk/releases/download/v0.0.8/bazelisk-linux-amd64
sudo chmod +x /usr/local/bin/bazel </pre>
<div class="blog_h1"><span class="graybg">入门</span></div>
<div class="blog_h2"><span class="graybg">构建C++项目</span></div>
<div class="blog_h3"><span class="graybg">示例项目</span></div>
<p>执行下面的命令下载示例项目：</p>
<pre class="crayon-plain-tag">git clone https://github.com/bazelbuild/examples/</pre>
<p>你可以看到stage1、stage2、stage3这几个WORKSPACE：</p>
<pre class="crayon-plain-tag">examples
└── cpp-tutorial
    ├──stage1
    │  ├── main
    │  │   ├── BUILD
    │  │   └── hello-world.cc
    │  └── WORKSPACE
    ├──stage2
    │  ├── main
    │  │   ├── BUILD
    │  │   ├── hello-world.cc
    │  │   ├── hello-greet.cc
    │  │   └── hello-greet.h
    │  └── WORKSPACE
    └──stage3
       ├── main
       │   ├── BUILD
       │   ├── hello-world.cc
       │   ├── hello-greet.cc
       │   └── hello-greet.h
       ├── lib
       │   ├── BUILD
       │   ├── hello-time.cc
       │   └── hello-time.h
       └── WORKSPACE</pre>
<p>本节后续内容会依次使用到这三个WORKSPACE。</p>
<div class="blog_h3"><span class="graybg">通过Bazel构建</span></div>
<p>第一步是创建工作空间。工作空间中包含以下特殊文件：</p>
<ol>
<li>WORKSPACE，此文件位于根目录中，将当前目录<span style="background-color: #c0c0c0;">定义为Bazel工作空间</span></li>
<li>BUILD，告诉Bazel项目的不同部分如何构建。工作空间中<span style="background-color: #c0c0c0;">包含BUILD文件的目录称为包</span></li>
</ol>
<p>当Bazel构建项目时，<span style="background-color: #c0c0c0;">所有的输入和依赖都必须位于工作空间中</span>。除非被链接，不同工作空间的文件相互独立没有关系。</p>
<p>每个BUILD文件包含若干Bazel指令，其中最重要的指令类型是构建<span style="background-color: #c0c0c0;">规则（Build Rule）</span>，构建规则说明如何产生期望的输出——例如可执行文件或库。<span style="background-color: #c0c0c0;"> BUILD中的每个构建规则也称为目标（Target）</span>，<span style="background-color: #c0c0c0;">目标指向若干源文件和依赖，也可以指向其它目标</span>。</p>
<p>下面是stage1的BUILD文件：</p>
<pre class="crayon-plain-tag">cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)</pre>
<p>这里定义了一个名为hello-world的目标，它使用了内置的cc_binary规则。该规则告诉Bazel，从源码hello-world.cc构建一个自包含的可执行文件。</p>
<p>执行下面的命令可以触发构建：</p>
<pre class="crayon-plain-tag">#   //main: BUILD文件相对于工作空间的位置
#          hello-world 是BUILD文件中定义的目标
bazel build //main:hello-world</pre>
<p>构建完成后，工作空间根目录会出现bazel-bin等目录，它们都是指向$HOME/.cache/bazel某个后代目录的符号链接。执行：</p>
<pre class="crayon-plain-tag">bazel-bin/main/hello-world</pre>
<p>可以运行构建好的二进制文件。</p>
<div class="blog_h3"><span class="graybg">查看依赖图</span></div>
<p>Bazel会根据BUILD中的声明产生一张依赖图，并根据这个依赖图实现精确的增量构建。</p>
<p>要查看依赖图，先安装：</p>
<pre class="crayon-plain-tag">sudo apt install graphviz xdot</pre>
<p>然后执行：</p>
<pre class="crayon-plain-tag">bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' --output graph | xdot</pre>
<div class="blog_h3"><span class="graybg">指定多个目标 </span></div>
<p>大型项目通常会划分为多个包、多个目标，以实现更快的增量构建、并行构建。工作空间stage2包含单个包、两个目标：</p>
<pre class="crayon-plain-tag"># 首先构建hello-greet库，cc_library是内建规则
cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    # 头文件
    hdrs = ["hello-greet.h"],
)

# 然后构建hello-world二进制文件
cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        # 提示Bazel，需要hello-greet才能构建当前目标
        # 依赖当前包中的hello-greet目标
        ":hello-greet",
    ],
)</pre>
<div class="blog_h3"><span class="graybg">使用多个包</span></div>
<p>工作空间stage3更进一步的划分出新的包，提供打印时间的功能：</p>
<pre class="crayon-plain-tag">cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    # 让当前目标对于工作空间的main包可见。默认情况下目标仅仅被当前包可见
    visibility = ["//main:__pkg__"],
)</pre><br />
<pre class="crayon-plain-tag">cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        # 依赖当前包中的hello-greet目标
        ":hello-greet",
        # 依赖工作空间根目录下的lib包中的hello-time目标
        "//lib:hello-time",
    ],
)</pre>
<div class="blog_h3"><span class="graybg">如何引用目标</span></div>
<p>在BUILD文件或者命令行中，你都使用标签（Label）来引用目标，其语法为：</p>
<pre class="crayon-plain-tag">//path/to/package:target-name

# 当引用当前包中的其它目标时，可以：
//:target-name
# 当引用当前BUILD文件中其它目标时，可以：
:target-name</pre>
<div class="blog_h1"><span class="graybg">目录布局</span></div>
<pre class="crayon-plain-tag">workspace-name&gt;/                          # 工作空间根目录
  bazel-my-project =&gt; &lt;...my-project&gt;     # execRoot的符号链接，所有构建动作在此目录下执行
  bazel-out =&gt; &lt;...bin&gt;                   # outputPath的符号链接
  bazel-bin =&gt; &lt;...bin&gt;                   # 最近一次写入的二进制目录的符号链接，即$(BINDIR)
  bazel-genfiles =&gt; &lt;...genfiles&gt;         # 最近一次写入的genfiles目录的符号链接，即$(GENDIR)



/home/user/.cache/bazel/                  # outputRoot，所有工作空间的Bazel输出的根目录
  _bazel_$USER/                           # outputUserRoot，当前用户的Bazel输出的根目录
    install/
      fba9a2c87ee9589d72889caf082f1029/   # installBase，Bazel安装清单的哈希值
        _embedded_binaries/               # 第一次运行时从Bazel可执行文件的数据段解开的可执行文件或脚本
    7ffd56a6e4cb724ea575aba15733d113/     # outputBase，某个工作空间根目录的哈希值
      action_cache/                       # Action cache目录层次
      action_outs/                        # Action output目录
      command.log                         # 最近一次Bazel命令的stdout/stderr输出
      external/                           # 远程存储库被下载、链接到此目录
      server/                             # Bazel服务器将所有服务器有关的文件存放在此
        jvm.out                           # Bazel服务器的调试输出
      execroot/                           # 所有Bazel Action的工作目录
        &lt;workspace-name&gt;/                 # Bazel构建的工作树
          _bin/                           # 助手工具链接或者拷贝到此
          bazel-out/                      # outputPath，构建的实际输出目录
            local_linux-fastbuild/        # 每个独特的BuildConfiguration实例对应一个子目录
              bin/                        # 单个构建配置二进制输出目录，$(BINDIR)
                foo/bar/_objs/baz/        # 命名为//foo/bar:baz的cc_*规则的Object文件所在目录
                  foo/bar/baz1.o          # //foo/bar:baz1.cc对应的Object文件
                  other_package/other.o   # //other_package:other.cc对应的Object文件
                foo/bar/baz               # //foo/bar:baz这一cc_binary生成的构件
                foo/bar/baz.runfiles/     # //foo/bar:baz生成的二进制构件的runfiles目录
                  MANIFEST
                  &lt;workspace-name&gt;/
                    ...
              genfiles/                   # 单个构建配置生成的源文件目录，$(GENDIR)
              testlogs/                   # Bazel的内部测试运行器将日志文件存放在此
              include/                    # 按需生成的include符号链接树，符号链接bazel-include指向这里
            host/                         # 本机的BuildConfiguration
        &lt;packages&gt;/                       # 构建引用的包，对于此包来说，它就像一个正常的WORKSPACE </pre>
<div class="blog_h1"><span class="graybg">Starlark</span></div>
<p>Bazel配置文件使用Starlark（原先叫Skylark）语言，具有短小、简单、线程安全的特点。</p>
<p>这种语言的语法和Python很类似，Starlark是Python2/Python3的子集。不支持的Python特性包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">不支持的特性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>隐含字符串连接</td>
<td>需要明确使用 + 操作符</td>
</tr>
<tr>
<td>链式比较操作符</td>
<td>例如：1 &lt; x &lt; 5</td>
</tr>
<tr>
<td>class</td>
<td>使用struct函数</td>
</tr>
<tr>
<td>import</td>
<td>使用load语句</td>
</tr>
<tr>
<td>is</td>
<td>使用==代替</td>
</tr>
<tr>
<td colspan="2">以下关键字：while、yield、try、raise、except、finally 、global、nonlocal</td>
</tr>
<tr>
<td colspan="2">以下数据类型：float、set</td>
</tr>
<tr>
<td colspan="2">生成器、生成器表达式</td>
</tr>
<tr>
<td colspan="2">lambda以及嵌套函数</td>
</tr>
<tr>
<td colspan="2">绝大多数内置函数、方法</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">数据类型</span></div>
<p>Starlark支持的数据类型包括：None、bool、dict、function、int、list、string，以及两种Bazel特有的类型：depset、struct。</p>
<div class="blog_h2"><span class="graybg">代码示例</span></div>
<pre class="crayon-plain-tag"># 定义一个数字
number = 18

# 定义一个字典
people = {
    "Alice": 22,
    "Bob": 40,
    "Charlie": 55,
    "Dave": 14,
}

names = ", ".join(people.keys())

# 定义一个函数
def greet(name):
  """Return a greeting."""
  return "Hello {}!".format(name)
# 调用函数
greeting = greet(names)


def fizz_buzz(n):
  """Print Fizz Buzz numbers from 1 to n."""
  # 循环结构
  for i in range(1, n + 1):
    s = ""
    # 分支结构
    if i % 3 == 0:
      s += "Fizz"
    if i % 5 == 0:
      s += "Buzz"
    print(s if s else i)</pre>
<div class="blog_h1"><span class="graybg">变量</span></div>
<p>你可以在BUILD文件中声明和使用变量。使用变量可以减少重复的代码：</p>
<pre class="crayon-plain-tag">COPTS = ["-DVERSION=5"]

cc_library(
  name = "foo",
  copts = COPTS,
  srcs = ["foo.cc"],
)

cc_library(
  name = "bar",
  copts = COPTS,
  srcs = ["bar.cc"],
  deps = [":foo"],
)</pre>
<div class="blog_h2"><span class="graybg">跨BUILD变量</span></div>
<p>如果要声明跨越多个BUILD文件共享的变量，<span style="background-color: #c0c0c0;">必须把变量放入.bzl文件中</span>，然后通过load加载bzl文件。</p>
<div class="blog_h2"><span class="graybg">Make变量</span></div>
<p>所谓Make变量，是一类特殊的、可展开的字符串变量，这种变量类似Shell中变量替换那样的展开。</p>
<p>Bazel提供了：</p>
<ol>
<li>预定义变量，可以在任何规则中使用</li>
<li>自定义变量，在规则中定义。仅仅在依赖该规则的那些规则中，可以使用这些变量</li>
</ol>
<div class="blog_h3"><span class="graybg">使用Make变量</span></div>
<p>仅仅那些标记为Subject to 'Make variable' substitution的规则属性，才可以使用Make变量。例如：</p>
<pre class="crayon-plain-tag"># 使用Make变量FOO
my_attr = "prefix $(FOO) suffix"
# 如果变量FOO的值为bar，则实际my_attr的值为prefix bar suffix</pre>
<p>如果要使用$字符，需要用<pre class="crayon-plain-tag">$$</pre>代替。 </p>
<div class="blog_h3"><span class="graybg">一般预定义变量</span></div>
<p>执行命令：<pre class="crayon-plain-tag">bazel info --show_make_env [build options]</pre>可以查看所有预定义变量的列表。</p>
<p>任何规则可以使用以下变量：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 180px; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>COMPILATION_MODE</td>
<td>编译模式：fastbuild、dbg、opt</td>
</tr>
<tr>
<td>BINDIR</td>
<td>目标体系结构的二进制树的根目录</td>
</tr>
<tr>
<td>GENDIR</td>
<td>目标体系结构的生成代码树的根目录</td>
</tr>
<tr>
<td>TARGET_CPU</td>
<td>目标体系结构的CPU</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">genrule预定义变量</span></div>
<p>下表中的变量可以在genrule规则的cmd属性中使用：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 180px; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>OUTS</td>
<td>genrule的outs列表，如果只有一个输出文件，可以用<pre class="crayon-plain-tag">$@</pre></td>
</tr>
<tr>
<td>SRCS</td>
<td>genrule的srcs列表，如果只有一个输入文件，可以用<pre class="crayon-plain-tag">$&lt;</pre></td>
</tr>
<tr>
<td>@D</td>
<td>
<p>输出目录，如果：</p>
<ol>
<li>outs仅仅包含一个文件名，则展开为包含该文件的目录</li>
<li>outs包含多个文件，则此变量展开为在genfiles树中，当前包的根目录</li>
</ol>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">输入输出路径变量</span></div>
<p>下表中的变量以Bazel的Label为参数，获取包的某类输入/输出路径：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 180px; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>execpath</td>
<td rowspan="2">
<p>获取指定标签对应的规则（此规则必须仅仅输出单个文件）或文件（必须是单个文件），位于execroot下的对应路径</p>
<p>对于项目myproject，<span style="background-color: #c0c0c0;">所有构建动作</span>在工作空间根目录下的符号链接bazel-myproject对应的目录下执行，此目录即execroot。源码empty.source被链接到bazel-myproject/testapp/empty.source，因此其execpath为testapp/empty.source</p>
<p>对于目标：</p>
<pre class="crayon-plain-tag">cc_binary(
  name = "app",
  srcs = ["app.cc"]
)</pre>
<p>执行构建：<pre class="crayon-plain-tag">bazel build //testapp:app</pre>时： </p>
<pre class="crayon-plain-tag">$(execpath :app)  # bazel-out/host/bin/testapp/app
$(execpath empty.source) # testapp/empty.source </pre>
</td>
</tr>
<tr>
<td>execpaths</td>
</tr>
<tr>
<td>rootpath</td>
<td rowspan="2">
<p>获取runfiles路径，二进制文件通过此路径在运行时寻找其依赖
<p>对于上面的//testapp:app目标：</p>
<pre class="crayon-plain-tag">$(rootpath :app)  # testapp/app
$(rootpath empty.source)  # testapp/empty.source </pre>
</td>
</tr>
<tr>
<td>rootpaths</td>
</tr>
<tr>
<td>location</td>
<td rowspan="2">
<p>根据当前所声明的属性，等价于execpath或rootpath
<p>对于上面的//testapp:app目标：</p>
<pre class="crayon-plain-tag">$(location :app) # bazel-out/host/bin/testapp/app
$(location empty.source) # testapp/empty.source </pre>
</td>
</tr>
<tr>
<td>locations</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">一般规则</span></div>
<div class="blog_h2"><span class="graybg">规则列表</span></div>
<div class="blog_h3"><span class="graybg">filegroup</span></div>
<p>为一组目标指定一个名字，你可以从其它规则中方便的引用这组目标。
<p>Bazel鼓励使用filegroup，而不是直接引用目录。Bazel构建系统不能完全了解目录中文件的变化情况，因而文件发生变化时，可能不会进行重新构建。而使用filegroup，即使联用glob，目录中所有文件仍然能够被构建系统正确的监控。</p>
<p>示例：</p>
<pre class="crayon-plain-tag">filegroup(
    name = "exported_testdata",
    srcs = glob([
        "testdata/*.dat",
        "testdata/logs/**/*.log",
    ]),
)</pre>
<p>要引用filegroup，只需要使用标签：</p>
<pre class="crayon-plain-tag">cc_library(
    name = "my_library",
    srcs = ["foo.cc"],
    data = [
        "//my_package:exported_testdata",
        "//my_package:mygroup",
    ],
)</pre>
<div class="blog_h3"><span class="graybg">test_suite</span></div>
<p>定义一组测试用例，给出一个有意义的名称，便于在特定时机  —— 例如迁入代码、执行压力测试 —— 时执行这些测试用例。</p>
<p>示例：</p>
<pre class="crayon-plain-tag"># 匹配当前包中所有small测试
test_suite(
    name = "small_tests",
    tags = ["small"],
)
# 匹配不包含flaky标记的测试
test_suite(
    name = "non_flaky_test",
    tags = ["-flaky"],
)
# 指定测试列表
test_suite(
    name = "smoke_tests",
    tests = [
        "system_unittest",
        "public_api_unittest",
    ],
)</pre>
<div class="blog_h3"><span class="graybg">alias</span></div>
<p>为规则设置一个别名：</p>
<pre class="crayon-plain-tag">filegroup(
    name = "data",
    srcs = ["data.txt"],
)
# 定义别名
alias(
    name = "other",
    actual = ":data",
)</pre>
<div class="blog_h3"><span class="graybg">config_setting</span></div>
<p>通过匹配<span style="background-color: #c0c0c0;">以Bazel标记或平台约束来表达的“配置状态”</span>，config_setting能够触发可配置的属性。</p>
<p>下面这个例子，匹配针对ARM平台的构建：</p>
<pre class="crayon-plain-tag">config_setting(
    name = "arm_build",
    values = {"cpu": "arm"},
)</pre>
<p>下面的例子，匹配任何定义了宏FOO=bar的针对X86平台的调试（-c dbg）构建：</p>
<pre class="crayon-plain-tag">config_setting(
    name = "x86_debug_build",
    values = {
        "cpu": "x86",
        "compilation_mode": "dbg",
        "define": "FOO=bar"
    },
)</pre>
<p>下面的库，通过select来声明可配置属性：</p>
<pre class="crayon-plain-tag">cc_binary(
    name = "mybinary",
    srcs = ["main.cc"],
    deps = select({
        # 如果config_settings arm_build匹配正在进行的构建，则依赖arm_lib这个目标
        ":arm_build": [":arm_lib"],
        # 如果config_settings x86_debug_build匹配正在进行的构建，则依赖x86_devdbg_lib
        ":x86_debug_build": [":x86_devdbg_lib"],
        # 默认情况下，依赖generic_lib
        "//conditions:default": [":generic_lib"],
    }),
) </pre>
<div class="blog_h3"><span class="graybg">genrule</span></div>
<p>一般性的规则 —— 使用用户指定的Bash命令，生成一个或多个文件。使用genrule理论上可以实现任何构建行为，例如压缩JavaScript代码。但是在执行C++、Java等构建任务时，最好使用相应的专用规则，更加简单。</p>
<p>不要使用genrule来运行测试，如果需要一般性的测试规则，可以考虑使用sh_test。</p>
<p>genrule在一个Bash shell环境下执行，当任意一个命令或管道失败（set -e -o pipefail），整个规则就失败。你不应该在genrule中访问网络。</p>
<p>示例：</p>
<pre class="crayon-plain-tag">genrule(
    name = "foo",
    # 不需要输入
    srcs = [],
    # 生成一个foo.h
    outs = ["foo.h"],
    # 运行当前规则所在包下的一个Perl脚本
    cmd = "./$(location create_foo.pl) &gt; \"$@\"",
    tools = ["create_foo.pl"],
) </pre>
<div class="blog_h1"><span class="graybg">C++规则</span></div>
<div class="blog_h2"><span class="graybg">规则列表</span></div>
<div class="blog_h3"><span class="graybg">cc_binary</span></div>
<p>隐含输出：</p>
<ol>
<li>name.stripped，仅仅当显式要求才会构建此输出，针对生成的二进制文件运行strip -g以驱除debug符号。额外的strip选项可以通过命令行--stripopt=-foo传入</li>
<li>name.dwp，仅仅当显式要求才会构建此输出，如果启用了 Fission ，则此文件包含用于远程调试的调试信息，否则是空文件</li>
</ol>
<p>属性列表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>目标的名称</td>
</tr>
<tr>
<td>deps</td>
<td>
<p>需要链接到此二进制目标的其它库的列表，以Label引用</p>
<p>这些库可以是cc_library或objc_library定义的目标</p>
</td>
</tr>
<tr>
<td>srcs</td>
<td>
<p>C/C++源文件列表，以Label引用</p>
<p>这些文件是C/C++源码文件或头文件，可以是自动生成的或人工编写的。</p>
<p>所有cc/c/cpp文件都会被编译。<span style="background-color: #c0c0c0;">如果某个声明的文件在其它规则的outs列表中，则当前规则自动依赖于那个规则</span></p>
<p>所有.h文件都不会被编译，仅仅供源码文件包含之。所有.h/.cc等文件都可以包含<span style="background-color: #c0c0c0;">srcs中声明的</span>、<span style="background-color: #c0c0c0;">deps中声明的目标的hdrs中声明</span>的头文件。也就是说，任何#include的文件要么在此属性中声明，要么在依赖的cc_library的hdrs属性中声明</p>
<p>如果某个规则的名称出现在srcs列表中，则当前规则自动依赖于那个规则：</p>
<ol>
<li>如果那个规则的输出是C/C++源文件，则它们被编译进当前目标</li>
<li>如果那个规则的输出是库文件，则被链接到当前目标</li>
</ol>
<p>允许的文件类型：</p>
<ol>
<li>C/C++源码，扩展名.c, .cc, .cpp, .cxx, .c++, .C</li>
<li>C/C++头文件，扩展名.h, .hh, .hpp, .hxx, .inc</li>
<li>汇编代码，扩展名.S</li>
<li>归档文件，扩展名.a, .pic.a</li>
<li>共享库，扩展名.so, .so.version，version为soname版本号</li>
<li>对象文件，扩展名.o, .pic.o</li>
<li>任何能够产生上述文件的规则</li>
</ol>
</td>
</tr>
<tr>
<td>copts</td>
<td>
<p>字符串列表</p>
<p>为C++编译器提供的选项，在编译目标之前，这些选项按顺序添加到COPTS。这些选项仅仅影响当前目标的编译，而<span style="background-color: #c0c0c0;">不影响其依赖</span>。选项中的任何路径都<span style="background-color: #c0c0c0;">相对于当前工作空间而非当前包</span></p>
<p>也可以在bazel build时通过--copts选项传入，例如：</p>
<pre class="crayon-plain-tag">--copt "-DENVOY_IGNORE_GLIBCXX_USE_CXX11_ABI_ERROR=1" </pre>
</td>
</tr>
<tr>
<td>defines</td>
<td>
<p>字符串列表
<p>为C++编译器传递宏定义，实际上会前缀以-D并添加到COPTS。与copts属性不同，这些宏定义会添加到当前目标，以及<span style="background-color: #c0c0c0;">所有依赖它的目标</span></p>
</td>
</tr>
<tr>
<td>includes</td>
<td>
<p>字符串列表</p>
<p>为C++编译器传递的头文件包含目录，实际上会前缀以-isystem并添加到COPTS。与copts属性不同，这些头文件包含会影响当前目标，以及<span style="background-color: #c0c0c0;">所有依赖它的目标</span></p>
<p>如果不清楚有何副作用，可以<span style="background-color: #c0c0c0;">传递-I到copts</span>，而不是使用当前属性</p>
</td>
</tr>
<tr>
<td>linkopts </td>
<td>
<p>字符串列表</p>
<p>为C++链接器传递选项，在链接二进制文件之前，此属性中的每个字符串被添加到LINKOPTS</p>
<p>此属性列表中，任何不以$和-开头的项，都被认为是deps中声明的<span style="background-color: #c0c0c0;">某个目标的Label，目标产生的文件会添加到链接选项</span>中</p>
</td>
</tr>
<tr>
<td>linkshared</td>
<td>
<p>布尔，默认False。用于创建共享库</p>
<p>要创建共享库，指定属性linkshared = True，对于GCC来说，会添加选项-shared。生成的结果适合被Java这类应用程序加载</p>
<p>需要注意，这里创建的共享库<span style="background-color: #c0c0c0;">绝不会被链接到依赖它的二进制文件</span>，而只适用于被其它程序手工的加载。因此，不能代替cc_library</p>
<p>如果同时指定<pre class="crayon-plain-tag">linkopts=['-static']</pre>和linkshared=True，你会得到一个完全自包含的单元。如果同时指定linkstatic=True和linkshared=True会得到一个基本是完全自包含的单元</p>
</td>
</tr>
<tr>
<td>linkstatic</td>
<td>
<p>布尔，默认True</p>
<p>对于cc_binary和cc_test，以静态形式链接二进制文件。对于cc_binary此选项默认True，其它目标默认False</p>
<p>如果当前目标是binary或test，此选项提示构建工具，<span style="background-color: #c0c0c0;">尽可能链接到用户库的.a版本而非.so</span>版本。某些系统库可能仍然需要动态链接，原因是没有静态库，这导致最终的输出仍然使用动态链接，不是完全静态的</p>
<p>链接一个可执行文件时，实际上有三种方式：</p>
<ol>
<li>STATIC，使用完全静态链接特性。所有依赖都被静态链接，GCC命令示例：<br />
<pre class="crayon-plain-tag">gcc -static foo.o libbar.a libbaz.a -lm</pre>
</li>
<li>STATIC，所有用户库静态链接（如果存在静态库版本），但是系统库（除去C/C++运行时库）动态链接，GCC命令示例：<br />
<pre class="crayon-plain-tag"># 此方式可以由linkstatic=True 启用
gcc foo.o libfoo.a libbaz.a -lm </pre>
</li>
<li>DYNAMIC，所有依赖被动态链接（如果存在动态库版本），GCC命令示例：<br />
<pre class="crayon-plain-tag"># 此方式可以由linkstatic=False 启用
gcc foo.o libfoo.so libbaz.so -lm </pre>
</li>
</ol>
<p>对于cc_library来说，linkstatic属性的含义不同。对于C++库来说：</p>
<ol>
<li>linkstatic=True表示仅仅允许静态链接，也就是不产生.so文件</li>
<li>linkstatic=False表示允许动态链接，同时产生.a和.so文件</li>
</ol>
</td>
</tr>
<tr>
<td>malloc </td>
<td>
<p>指向标签，默认@bazel_tools//tools/cpp:malloc</p>
<p>覆盖默认的malloc依赖，默认情况下C++二进制文件链接到//tools/cpp:malloc，这是一个空库，这导致实际上链接到libc的malloc</p>
</td>
</tr>
<tr>
<td>nocopts</td>
<td>
<p>字符串</p>
<p>从C++编译命令中移除匹配的选项，此属性的值是正则式，任何匹配正则式的、已经存在的COPTS被移除 </p>
</td>
</tr>
<tr>
<td>stamp </td>
<td>
<p>整数，默认-1</p>
<p>用于将构建信息嵌入到二进制文件中，可选值：</p>
<ol>
<li>stamp = 1，将构建信息嵌入，目标二进制仅仅在其依赖变化时重新构建</li>
<li>stamp = 0，总是将构建信息替换为常量值，有利于构建结果缓存</li>
<li>stamp = -1 ，由--[no]stamp标记控制是否嵌入</li>
</ol>
</td>
</tr>
<tr>
<td>toolchains </td>
<td>
<p>标签列表</p>
<p>提供构建变量（Make variables，这些变量可以被当前目标使用）的工具链的标签列表 </p>
</td>
</tr>
<tr>
<td>win_def_file</td>
<td>
<p>标签</p>
<p>传递给链接器的Windows DEF文件。在Windows上，此属性可以在链接共享库时导出符号 </p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">cc_import</span></div>
<p>导入预编译好的C/C++库。</p>
<p>属性列表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>hdrs</td>
<td>此预编译库对外发布的头文件列表，依赖此库的规则（dependent rule）会直接将这些头文件包含在源码列表中</td>
</tr>
<tr>
<td>alwayslink</td>
<td>
<p>布尔，默认False</p>
<p>如果为True，则依赖此库的二进制文件会将此静态库归档中的对象文件链接进去，就算某些对象文件中的符号并没有被二进制文件使用</p>
</td>
</tr>
<tr>
<td>interface_library</td>
<td>用于链接共享库时使用的接口（导入）库</td>
</tr>
<tr>
<td>shared_library</td>
<td>共享库，Bazel保证在运行时可以访问到共享库</td>
</tr>
<tr>
<td>static_library</td>
<td>静态库</td>
</tr>
<tr>
<td>system_provided</td>
<td>提示运行时所需的共享库由操作系统提供，如果为True则应该指定interface_library，shared_library应该为空</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">cc_library</span></div>
<p>对于所有cc_*规则来说，构建所需的任何头文件都要在hdrs或srcs中声明。</p>
<p>对于cc_library规则，在hdrs声明的头文件构成库的公共接口。这些头文件可以被当前库的hdrs/srcs中的文件直接包含，也可以被依赖（deps）当前库的其它cc_*的hdrs/srcs直接包含。<span style="background-color: #c0c0c0;">位于srcs中的头文件，则仅仅能被当前库的</span>hdrs/srcs包含。</p>
<p>cc_binary和cc_test不会暴露接口，因此它们没有hdrs属性。</p>
<p>属性列表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>库的名称</td>
</tr>
<tr>
<td>deps</td>
<td>需要链接到（into）当前库的其它库</td>
</tr>
<tr>
<td>srcs</td>
<td>头文件和源码列表</td>
</tr>
<tr>
<td>hdrs</td>
<td>导出的头文件列表</td>
</tr>
<tr>
<td>copts/nocopts</td>
<td>传递给C++编译命令的参数</td>
</tr>
<tr>
<td>defines</td>
<td>宏定义列表</td>
</tr>
<tr>
<td>include_prefix</td>
<td>hdrs中头文件的路径前缀</td>
</tr>
<tr>
<td>includes</td>
<td>
<p>字符串列表</p>
<p>需要添加到编译命令的包含文件列表</p>
</td>
</tr>
<tr>
<td>linkopts</td>
<td>链接选项</td>
</tr>
<tr>
<td>linkstatic</td>
<td>是否生成动态库</td>
</tr>
<tr>
<td>strip_include_prefix</td>
<td>
<p>字符串</p>
<p>需要脱去的头文件路径前缀，也就是说使用hdrs中头文件时，要把这个前缀去除，路径才匹配</p>
</td>
</tr>
<tr>
<td>textual_hdrs</td>
<td>
<p>标签列表</p>
<p>头文件列表，这些头文件是不能独立编译的。依赖此库的目标，直接以文本形式包含这些头文件到它的源码列表中，这样才能正确编译这些头文件</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">常见用例</span></div>
<div class="blog_h3"><span class="graybg">通配符</span></div>
<p>可以使用Glob语法为目标添加多个文件：</p>
<pre class="crayon-plain-tag">cc_library(
    name = "build-all-the-files",
    srcs = glob(["*.cc"]),
    hdrs = glob(["*.h"]),
)</pre>
<div class="blog_h3"><span class="graybg">传递性依赖</span></div>
<p>如果源码依赖于某个头文件，则该源码的规则需要dep头文件的库，仅仅直接依赖才需要声明：</p>
<pre class="crayon-plain-tag"># 三明治依赖面包
cc_library(
    name = "sandwich",
    srcs = ["sandwich.cc"],
    hdrs = ["sandwich.h"],
    # 声明当前包下的目标为依赖
    deps = [":bread"],
)
# 面包依赖于面粉，三明治间接依赖面粉，因此不需要声明
cc_library(
    name = "bread",
    srcs = ["bread.cc"],
    hdrs = ["bread.h"],
    deps = [":flour"],
)

cc_library(
    name = "flour",
    srcs = ["flour.cc"],
    hdrs = ["flour.h"],
)</pre>
<div class="blog_h3"><span class="graybg">添加头文件路径 </span></div>
<p>有些时候你不愿或不能将头文件放到工作空间的include目录下，现有的库的include目录可能不符合</p>
<div class="blog_h3"><span class="graybg">导入已编译库</span></div>
<p>导入一个库，用于静态链接：</p>
<pre class="crayon-plain-tag">cc_import(
  name = "mylib",
  hdrs = ["mylib.h"],
  static_library = "libmylib.a",
  # 如果为1则libmylib.a总会链接到依赖它的二进制文件
  alwayslink = 1,
)</pre>
<p>导入一个库，用于共享链接（UNIX）： </p>
<pre class="crayon-plain-tag">cc_import(
  name = "mylib",
  hdrs = ["mylib.h"],
  shared_library = "libmylib.so",
)</pre>
<p>通过接口库（Interface library）链接到共享库（Windows）：</p>
<pre class="crayon-plain-tag">cc_import(
  name = "mylib",
  hdrs = ["mylib.h"],
  # mylib.lib是mylib.dll的导入库，此导入库会传递给链接器
  interface_library = "mylib.lib",
  # mylib.dll在运行时需要，链接时不需要
  shared_library = "mylib.dll",
)</pre>
<p>在二进制目标中选择链接到共享库还是静态库（UNIX）：</p>
<pre class="crayon-plain-tag">cc_import(
  name = "mylib",
  hdrs = ["mylib.h"],
  # 同时声明共享库和静态库
  static_library = "libmylib.a",
  shared_library = "libmylib.so",
)

# 此二进制目标链接到静态库
cc_binary(
  name = "first",
  srcs = ["first.cc"],
  deps = [":mylib"],
  linkstatic = 1, # default value
)

# 此二进制目标链接到共享库
cc_binary(
  name = "second",
  srcs = ["second.cc"],
  deps = [":mylib"],
  linkstatic = 0,
)</pre>
<div class="blog_h3"><span class="graybg">包含外部库</span></div>
<p>你可以在WORKSPACE中调用new_*存储库函数，来从网络中下载依赖。下面的例子下载Google Test库：</p>
<pre class="crayon-plain-tag"># 下载归档文件，并让其在工作空间的存储库中可用
new_http_archive(
    name = "gtest",
    url = "https://github.com/google/googletest/archive/release-1.7.0.zip",
    sha256 = "b58cb7547a28b2c718d1e38aee18a3659c9e3ff52440297e965f5edffe34b6d0",
    # 外部库的构建规则编写在gtest.BUILD
    # 如果此归档文件已经自带了BUILD文件，则可以调用不带new_前缀的函数
    build_file = "gtest.BUILD",
    # 去除路径前缀
    strip_prefix = "googletest-release-1.7.0",
)</pre>
<p>构建此外部库的规则如下：</p>
<pre class="crayon-plain-tag">cc_library(
    name = "main",
    srcs = glob(
        # 前缀去除，原来是googletest-release-1.7.0/src/*.cc
        ["src/*.cc"],
        # 排除此文件
        exclude = ["src/gtest-all.cc"]
    ),
    hdrs = glob([
        # 前缀去除
        "include/**/*.h",
        "src/*.h"
    ]),
    copts = [
        # 前缀去除，原来是external/gtest/googletest-release-1.7.0/include
        "-Iexternal/gtest/include"
    ],
    # 链接到pthread
    linkopts = ["-pthread"],
    visibility = ["//visibility:public"],
)</pre>
<div class="blog_h3"><span class="graybg">使用外部库</span></div>
<p>沿用上面的例子，下面的目标使用gtest编写测试代码：</p>
<pre class="crayon-plain-tag">cc_test(
    name = "hello-test",
    srcs = ["hello-test.cc"],
    # 前缀去除
    copts = ["-Iexternal/gtest/include"],
    deps = [
        # 依赖gtest存储库的main目标
        "@gtest//:main",
        "//lib:hello-greet",
    ],
)</pre>
<div class="blog_h1"><span class="graybg">外部依赖</span></div>
<p>Bazel允许依赖其它项目中定义的目标，这些<span style="background-color: #c0c0c0;">来自其它项目的依赖叫做“外部依赖“</span>。当前工作空间的<span style="background-color: #c0c0c0;">WORKSPACE文件声明从何处下载外部依赖的源码</span>。</p>
<p>外部依赖可以有自己的1-N个BUILD文件，其中定义自己的目标。当前项目可以使用这些目标。例如下面的两个项目结构：</p>
<pre class="crayon-plain-tag">/
  home/
    user/
      project1/
        WORKSPACE
        BUILD
        srcs/
          ...
      project2/
        WORKSPACE
        BUILD
        my-libs/</pre>
<p>如果project1需要依赖定义在project2/BUILD中的目标:foo，则可以在其WORKSPACE中<span style="background-color: #c0c0c0;">声明一个存储库（repository）</span>，名字为project2，位于/home/user/project2。然后，可以在BUILD中通过标签<span style="background-color: #c0c0c0;">@project2//:foo</span>引用目标foo。</p>
<p>除了依赖来自文件系统其它部分的目标、下载自互联网的目标以外，用户还可以<span style="background-color: #c0c0c0;">编写自己的存储库规则（repository rules ）以实现更复杂的行为</span>。</p>
<p>WORKSPACE的语法格式和BUILD相同，但是允许使用<a href="https://docs.bazel.build/versions/master/be/workspace.html">不同的规则集</a>。</p>
<p>Bazel会把外部依赖下载到<pre class="crayon-plain-tag">$(bazel info output_base)/external</pre>目录中，要删除掉外部依赖，执行：</p>
<pre class="crayon-plain-tag">bazel clean --expunge</pre>
<div class="blog_h2"><span class="graybg">外部依赖类型</span></div>
<div class="blog_h3"><span class="graybg">Bazel项目</span></div>
<p>可以使用local_repository、git_repository或者http_archive这几个规则来引用。</p>
<p>引用本地Bazel项目的例子：</p>
<pre class="crayon-plain-tag">local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
)</pre>
<p>在BUILD中，引用coworkers_project中的目标//foo:bar时，使用标签@coworkers_project//foo:bar </p>
<div class="blog_h3"><span class="graybg">非Bazel项目</span></div>
<p>可以使用new_local_repository、new_git_repository或者new_http_archive这几个规则来引用。你需要自己编写BUILD文件来构建这些项目。</p>
<p>引用本地非Bazel项目的例子：</p>
<pre class="crayon-plain-tag">new_local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
    build_file = "coworker.BUILD",
)</pre><br />
<pre class="crayon-plain-tag">cc_library(
    name = "some-lib",
    srcs = glob(["**"]),
    visibility = ["//visibility:public"],
)&amp;nbsp;</pre>
<p>在BUILD文件中，使用标签@coworkers_project//:some-lib引用上面的库。 </p>
<div class="blog_h3"><span class="graybg">外部包</span></div>
<p>对于Maven仓库，可以使用规则maven_jar/maven_server来下载JAR包，并将其作为Java依赖。</p>
<div class="blog_h2"><span class="graybg">依赖拉取</span></div>
<p>默认情况下，执行bazel Build时会按需自动拉取依赖，你也可以禁用此特性，并使用bazel fetch预先手工拉取依赖。</p>
<div class="blog_h2"><span class="graybg">使用代理</span></div>
<p>Bazel可以使用HTTPS_PROXY或HTTP_PROXY定义的代理地址。</p>
<div class="blog_h2"><span class="graybg">依赖缓存</span></div>
<p>Bazel会缓存外部依赖，当WORKSPACE改变时，会重新下载或更新这些依赖。</p>
<div class="blog_h1"><span class="graybg">.bazelrc</span></div>
<p>Bazel命令接收大量的参数，其中一部分很少变化，这些不变的配置项可以存放在.bazelrc中。</p>
<div class="blog_h2"><span class="graybg">位置</span></div>
<p>Bazel按以下顺序寻找.bazelrc文件：</p>
<ol>
<li>除非指定--nosystem_rc，否则寻找/etc/bazel.bazelrc</li>
<li>除非指定--noworkspace_rc，否则寻找工作空间根目录的.bazelrc</li>
<li>除非指定--nohome_rc，否则寻找当前用户的$HOME/.bazelrc</li>
</ol>
<div class="blog_h2"><span class="graybg">语法</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>import</td>
<td>导入其它bazelrc文件，例如：<pre class="crayon-plain-tag">import %workspace%/tools/bazel.rc</pre></td>
</tr>
<tr>
<td>默认参数</td>
<td>
<p>可以提供以下行：</p>
<p>startup ... 启动参数<br />common... 适用于所有命令的参数<br /><em>command</em>...为某个子命令指定参数，例如buildquery、</p>
<p>以上三类行，都可以出现多次</p>
</td>
</tr>
<tr>
<td>
<p>--config</p>
</td>
<td>
<p>用于定义一组参数的组合，在调用bazel命令时指定--config=memcheck，可以引用名为memcheck的参数组。此参数组的定义示例：</p>
<pre class="crayon-plain-tag">build:memcheck --strip=never --test_timeout=3600</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">扩展</span></div>
<p>所谓Bazel扩展，是扩展名为.bzl的文件。你可以使用load语句加载扩展中定义的符号到BUILD中。
<div class="blog_h2"><span class="graybg">构建阶段</span></div>
<p>一次Bazel构建包含三个阶段：</p>
<ol>
<li>加载阶段：加载、eval本次构建需要的所有扩展、所有BUILD文件。宏在此阶段执行，规则被实例化。BUILD文件中<span style="background-color: #c0c0c0;">调用的宏/函数，在此阶段执行函数体</span>，其结果是<span style="background-color: #c0c0c0;">宏里面实例化的规则被填充到BUILD文件</span>中</li>
<li>分析阶段：<span style="background-color: #c0c0c0;">规则的代码——也就是它的implementation函数被执行</span>，导致规则的Action被实例化，Action描述如何从输入产生输出</li>
<li>执行阶段：执行Action，产生输出，<span style="background-color: #c0c0c0;">测试也在此阶段执行</span></li>
</ol>
<p>Bazel会并行的读取/解析/eval BUILD文件和.bzl文件。每个文件在每次构建最多被读取一次，eval的结果被缓存并重用。每个文件在它<span style="background-color: #c0c0c0;">的全部依赖被解析之后</span>才eval。加载一个.bzl文件没有副作用，仅仅是定义值和函数</p>
<div class="blog_h2"><span class="graybg">宏</span></div>
<p>宏（Macro）是一种函数，用来实例化（instantiates）规则。如果BUILD文件太过重复或复杂，可以考虑使用宏，以便减少代码。<span style="background-color: #c0c0c0;">宏的函数在BUILD文件被读取时就立即执行</span>。BUILD被读取（eval）之后，宏被替换为它生成的规则。bazel query只会列出生成的规则而非宏。</p>
<p>编写宏时需要注意：</p>
<ol>
<li>所有实例化规则的公共函数，都必须具有一个无默认值的name参数</li>
<li>公共函数应当具有docstring</li>
<li>在BUILD文件中，调用宏时name参数必须是关键字参数</li>
<li>宏所生成的规则的name属性，必须以调用宏的name参数作为后缀</li>
<li>大部分情况下，可选参数应该具有默认值None</li>
<li>应当具有可选的visibility参数</li>
</ol>
<div class="blog_h3"><span class="graybg">示例</span></div>
<p>要在宏中实例化原生规则（Native rules，不需要load即可使用的那些规则），可以<span style="background-color: #c0c0c0;">使用native模块</span>：</p>
<pre class="crayon-plain-tag"># 该宏实例化一个genrule规则
def file_generator(name, arg, visibility=None):
  // 生成一个genrule规则
  native.genrule(
    name = name,
    outs = [name + ".txt"],
    cmd = "$(location generator) %s &gt; $@" % arg,
    tools = ["//test:generator"],
    visibility = visibility,
  )</pre>
<p>使用上述宏的BUILD文件：</p>
<pre class="crayon-plain-tag">load("//path:generator.bzl", "file_generator")

file_generator(
    name = "file",
    arg = "some_arg",
)</pre>
<p>执行下面的命令查看宏展开后的情况：</p>
<pre class="crayon-plain-tag"># bazel query --output=build //label

genrule(
  name = "file",
  tools = ["//test:generator"],
  outs = ["//test:file.txt"],
  cmd = "$(location generator) some_arg &gt; $@",
)</pre>
<div class="blog_h2"><span class="graybg">规则</span></div>
<p><a href="https://docs.bazel.build/versions/master/skylark/rules.html">规则（Rule）</a>比宏更强大，能够对Bazel内部特性进行访问，并可以完全控制Bazel。</p>
<p>规则定义了为了产生输出，需要在输入上执行的一系列动作。例如，C++二进制文件规则以一系列.cpp文件为输入，针对输入调用g++，输出一个可执行文件。注意，从Bazel的角度来说，不但cpp文件是输入，g++、C++库也是输入。当编写自定义规则时，你需要注意，将执行Action所需的库、工具作为输入看待。</p>
<p>Bazel内置了一些规则，这些规则叫原生规则，例如cc_library、cc_library，对一些语言提供了基础的支持。通过编写自定义规则，你可以实现对任何语言的支持。</p>
<p>定义在.bzl中的规则，用起来就像原生规则一样 —— 规则的目标具有标签、可以出现在bazel query。</p>
<p>规则在分析阶段的行为，由它的implementation函数决定。此函数不得调用任何外部工具，它只是注册在执行阶段需要的Action。</p>
<div class="blog_h3"><span class="graybg">自定义规则</span></div>
<p>在.bzl文件中，你可以调用rule创建自定义规则，并将其保存到全局变量：</p>
<pre class="crayon-plain-tag">def _empty_impl(ctx):
    # 分析阶段此函数被执行
    print("This rule does nothing")

empty = rule(implementation = _empty_impl)</pre>
<p>然后，规则可以通过load加载到BUILD文件：</p>
<pre class="crayon-plain-tag">load("//empty:empty.bzl", "empty")

# 实例化规则
empty(name = "nothing")</pre>
<div class="blog_h3"><span class="graybg">规则属性 </span></div>
<p>属性即实例化规则时需要提供的参数，例如srcs、deps。在自定义规则的时候，你可以列出所有属性的名字和Schema：</p>
<pre class="crayon-plain-tag">sum = rule(
    implementation = _impl,
    attrs = {
        # 定义一个整数属性，一个列表属性
        "number": attr.int(default = 1),
        "deps": attr.label_list(),
    },
)</pre>
<p>实例化规则的时候，你需要以参数的形式指定属性：</p>
<pre class="crayon-plain-tag">sum(
    name = "my-target",
    deps = [":other-target"],
)

sum(
    name = "other-target",
)</pre>
<p>如果实例化规则的时候，没有指定某个属性的值（且没指定默认值），规则的实现函数会在ctx.attr中看到一个占位符，此占位符的值取决于属性的类型。</p>
<p>使用default为属性指定默认值，使用 mandatory=True 声明属性必须提供。</p>
<div class="blog_h3"><span class="graybg">默认属性</span></div>
<p>任何规则自动具有以下属性：deprecation, features, name, tags, testonly, visibility。</p>
<p>任何测试规则具有以下额外属性：args, flaky, local, shard_count, size, timeout。</p>
<div class="blog_h3"><span class="graybg">特殊属性</span></div>
<p>有两类特殊属性需要注意：</p>
<ol>
<li>依赖属性：例如attr.label、attr.label_list，用于声明拥有此属性的目标所依赖的其它目标</li>
<li>输出属性：例如attr.output、attr.output_list，声明目标的输出文件，较少使用</li>
</ol>
<p>上面两类属性的值都是Label类型。 </p>
<div class="blog_h3"><span class="graybg">隐含依赖</span></div>
<p>具有默认值的依赖属性，称为隐含依赖（implicit dependency）。如果要硬编码规则和工具（例如编译器）之间的关系，可通过隐含依赖。从规则的角度来看，这些工具仍然属于输入，就像源代码一样。</p>
<div class="blog_h3"><span class="graybg">私有属性</span></div>
<p>某些情况下，我们会为规则添加具有默认值的属性，同时还想禁止用户修改属性值，这种情况下可以使用私有属性。</p>
<p>私有属性以下划线 _ 开头，必须具有默认值。</p>
<div class="blog_h3"><span class="graybg">目标</span></div>
<p>实例化规则不会返回值，但是会定义一个新的目标。</p>
<div class="blog_h3"><span class="graybg">规则实现</span></div>
<p>任何规则都需要提供一个实现函数。提供在分析阶段需要严格执行的逻辑。此函数不能有任何读写行为，仅仅用于注册Action。</p>
<p>实现函数具有唯一性入参  —— <a href="https://docs.bazel.build/versions/master/skylark/lib/ctx.html">规则上下文</a>，通常将其命名为ctx。通过规则上下文你可以：</p>
<ol>
<li>访问规则属性</li>
<li>获得输入输出文件的handle</li>
<li>创建Actions</li>
<li>通过providers向依赖于当前规则的其它规则传递信息</li>
</ol>
<div class="blog_h3"><span class="graybg">ctx</span></div>
<p>规则上下文对象的具有以下主要方法或属性：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">方法/属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>action</td>
<td>废弃，使用ctx.actions.run()或ctx.actions.run_shell()代替</td>
</tr>
<tr>
<td>actions.run</td>
<td>
<p>创建一个调用可执行文件的Action，参数：Bazel加载阶段</p>
<p>outputs 此动作的输出文件列表<br />inputs 此动作输入文件的列表/depset<br />executable 执行此动作需要调用的可执行文件<br />tools 执行此动作需要的工具的列表/depset<br />arguments 传递给可执行文件的参数列表<br />mnemonic 动作的描述<br />progress_message 动作执行时，显示给用户的信息<br />use_default_shell_env 是否在内建Shell环境下运行可执行文件<br />env 环境变量字典<br />execution_requirements 调度此动作需要的信息<br />input_manifests 输入runfiles元数据，通常由resolve_command生成</p>
<p>示例：</p>
<pre class="crayon-plain-tag">def _impl(ctx):
    # The list of arguments we pass to the script.
    args = [ctx.outputs.out.path] + [f.path for f in ctx.files.chunks]

    # 调用可执行文件
    ctx.actions.run(
        inputs = ctx.files.chunks,
        outputs = [ctx.outputs.out],
        arguments = args,
        progress_message = "Merging into %s" % ctx.outputs.out.short_path,
        executable = ctx.executable._merge_tool,
    )
// 规则定义
concat = rule(
    implementation = _impl,
    attrs = {
        "chunks": attr.label_list(allow_files = True),
        "out": attr.output(mandatory = True),
        "_merge_tool": attr.label(
            executable = True,
            cfg = "host",
            allow_files = True,
            default = Label("//actions_run:merge"),
        ),
    },
) </pre>
</td>
</tr>
<tr>
<td>actions.run_shell</td>
<td>
<p>创建一个执行Shell脚本的Action
<p>示例：</p>
<p><pre class="crayon-plain-tag">def _impl(ctx):
    output = ctx.outputs.out
    input = ctx.file.file

    # 访问inputs中声明的文件
    ctx.actions.run_shell(
        inputs = [input],
        outputs = [output],
        progress_message = "Getting size of %s" % input.short_path,
        command = "stat -L -c%%s '%s' &gt; '%s'" % (input.path, output.path),
    )

# 规则定义
size = rule(
    implementation = _impl,
    attrs = {"file": attr.label(mandatory = True, allow_single_file = True)},
    outputs = {"out": "%{name}.size"},
)</pre>
</td>
</tr>
<tr>
<td>actions.write</td>
<td>此Action写入内容到文件</td>
</tr>
<tr>
<td>actions.declare_file</td>
<td>此Action创建新的文件</td>
</tr>
<tr>
<td>actions.do_nothing</td>
<td>不做任何事情的Action</td>
</tr>
<tr>
<td>ctx.attr</td>
<td>用于访问属性值的结构 </td>
</tr>
<tr>
<td>bin_dir</td>
<td>二进制目录的根</td>
</tr>
<tr>
<td>genfiles_dir</td>
<td>genfiles目录的根</td>
</tr>
<tr>
<td>build_file_path</td>
<td>相对于源码目录根的，当前BUILD文件的路径</td>
</tr>
<tr>
<td>executable </td>
<td>一个结构，可以引用任何通过<pre class="crayon-plain-tag">attr.label(executable=True)</pre>定义的规则属性</td>
</tr>
<tr>
<td>expand_location </td>
<td>
<p>展开input中定义的所有$(location //x)为目标x的真实路径。仅仅对当前规则的直接依赖、明确列在targets属性中的目标使用</p>
<p><pre class="crayon-plain-tag">string ctx.expand_location(input, targets=[])</pre>
</td>
</tr>
<tr>
<td>features</td>
<td>列出此规则明确启用的特性列表 </td>
</tr>
<tr>
<td>file</td>
<td>
<p>此结构包含任何通过<pre class="crayon-plain-tag">attr.labe(allow_single_file=True)</pre>定义的属性所指向的文件。此结构的字段名即文件属性名，结构字段值是file或Node类型</p>
<p>此结构是表达式<pre class="crayon-plain-tag">list(ctx.attr.&lt;ATTR&gt;.files)[0]</pre>的快捷方式</p>
</td>
</tr>
<tr>
<td>fragments</td>
<td>用于访问目标配置中的配置片断（configuration fragments ） </td>
</tr>
<tr>
<td>host_configuration</td>
<td>返回主机配置的configuration对象。configuration包含构建所在的运行环境信息 </td>
</tr>
<tr>
<td>host_fragments </td>
<td>用于访问host配置中的配置片断（configuration fragments ）  </td>
</tr>
<tr>
<td>label</td>
<td>当前正在分析的目标的标签 </td>
</tr>
<tr>
<td>outputs</td>
<td>一个包含所有预声明的输出文件的伪结构 </td>
</tr>
<tr>
<td>resolve_command</td>
<td>
<p>解析一个命令，返回(inputs, command, input_manifests)元组：</p>
<p>inputs，表示解析后的输入列表<br />command，解析后的命令的argv列表<br />input_manifests，执行命令需要的runfiles元数据</p>
</td>
</tr>
<tr>
<td>resolve_tools </td>
<td>解析工具，返回(inputs, input_manifests)元组 </td>
</tr>
<tr>
<td>runfiles </td>
<td>创建一个Runfiles</td>
</tr>
<tr>
<td>toolchains</td>
<td>此规则需要的工具链 </td>
</tr>
<tr>
<td>var </td>
<td>配置变量的字典</td>
</tr>
<tr>
<td>workspace_name </td>
<td>当前工作空间的名称 </td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">存储库规则</span></div>
<p>存储库规则用于定义外部存储库。<span style="background-color: #c0c0c0;">外部存储库是一种规则</span>，这种规则只能用在WORKSPACE文件中，可以在<span style="background-color: #c0c0c0;">Bazel加载阶段</span>启用<span style="background-color: #c0c0c0;">非封闭性</span>（ non-hermetic，所谓封闭是指自包含，不依赖于外部环境）操作。每个外部存储库都创建自己的WORKSPACE，具有自己的BUILD文件和构件。</p>
<p>外部存储库可以用来：</p>
<ol>
<li>加载第三方依赖，例如Maven打包的库</li>
<li>为运行构件的主机生成特化的BUILD文件</li>
</ol>
<p>在bzl文件中，调用repository_rule函数可以创建一个存储库规则，你需要将其存放在全局变量中：</p>
<pre class="crayon-plain-tag">local_repository = repository_rule(
    # 实现函数
    implementation=_impl,
    local=True,
    # 属性列表
    attrs={"path": attr.string(mandatory=True)}) </pre>
<p>每个存储库规则都必须提供实现函数，其中包含<span style="background-color: #c0c0c0;">在Bazel加载阶段需要执行</span>的严格的逻辑。该函数具有唯一的入参repository_ctx：</p>
<pre class="crayon-plain-tag">def _impl(repository_ctx):
  # 你可以通过repository_ctx访问属性值、调用非密封性函数（例如查找、执行二进制文件，创建或下载文件到存储库）
  repository_ctx.symlink(repository_ctx.attr.path, "") </pre>
<p>引用存储库中规则时，可以使用<pre class="crayon-plain-tag">@REPO_NAMAE//package:target</pre>这样的标签。</p>
<div class="blog_h3"><span class="graybg">repository_ctx</span></div>
<p>存储库规则上下文对象的具有以下主要方法或属性：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">方法/属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>attr</td>
<td>用于访问所有属性的结构</td>
</tr>
<tr>
<td>download</td>
<td>
<pre class="crayon-plain-tag">struct repository_ctx.download(url, output='', sha256='', executable=False)</pre></p>
<p> 下载文件到输出路径，返回包含字段sha256的结构</p>
</td>
</tr>
<tr>
<td>download_and_extract</td>
<td>下载并解压</td>
</tr>
<tr>
<td>execute</td>
<td>执行指定的命令</td>
</tr>
<tr>
<td>file</td>
<td>以指定的内容在存储库目录下生成文件</td>
</tr>
<tr>
<td>name</td>
<td>此规则生成的外部存储库的名称</td>
</tr>
<tr>
<td>path</td>
<td>返回字符串/路径/标签对应的实际路径</td>
</tr>
<tr>
<td>symlink</td>
<td>在文件系统中创建符号链接<br />
<pre class="crayon-plain-tag"># from 符号链接的源，string/Label/path类型
# to 相对于存储库目录的符号链接文件的路径
None repository_ctx.symlink(from, to)</pre>
</td>
</tr>
<tr>
<td>template</td>
<td>使用模板创建文件</td>
</tr>
<tr>
<td>which</td>
<td>返回指定程序的路径</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">命令</span></div>
<div class="blog_h2"><span class="graybg">bazel</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">analyze-profile</td>
<td>分析构建配置数据（build profile data）</td>
</tr>
<tr>
<td class="blog_h3">aquery</td>
<td>针对post-analysis操作图执行查询</td>
</tr>
<tr>
<td class="blog_h3">build</td>
<td>构建指定的目标：<br />
<pre class="crayon-plain-tag"># 构建foo/bar包中的wiz目标
bazel build //foo/bar:wiz
# 构建foo/bar包中的bar目标
bazel build //foo/bar
# 构建foo/bar包中的所有规则
bazel build //foo/bar:all
# 构建foo目录下所有子代包的所有规则
bazel build //foo/...
bazel build //foo/...:all
# 构建foo目录下所有子代包的所有目标（规则和文件）
bazel build //foo/...:*
bazel build //foo/...:all-targets</pre></p>
<p>如果目标标签不以<pre class="crayon-plain-tag">//</pre>开头，则相对于当前目录。如果当前目录是foo则bar:wiz等价于//foo/bar:wiz</p>
<p>Bazel支持通过符号链接来寻找子包，除了：</p>
<ol>
<li>那些指向输出目录的子目录的符号链接，例如bazel-bin</li>
<li>包含了名为DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN文件的目录</li>
</ol>
<p>指定了<pre class="crayon-plain-tag">tags = ["manual"]</pre>的目标必须手工构建，无法通过...、:*、:all等自动构建</p>
<p>常用选项：</p>
<p style="padding-left: 30px;">--loading_phase_threads   加载阶段使用的线程数量，可以防止并发太多导致下载缓慢，进而超时</p>
</td>
</tr>
<tr>
<td class="blog_h3">canonicalize-flags</td>
<td>规范化Bazel标记</td>
</tr>
<tr>
<td class="blog_h3">clean</td>
<td>清除输出文件，可选的停止服务器</td>
</tr>
<tr>
<td class="blog_h3">cquery</td>
<td>针对post-analysis依赖图查询</td>
</tr>
<tr>
<td class="blog_h3">dump</td>
<td>输出Bazel服务器的内部状态</td>
</tr>
<tr>
<td class="blog_h3">info</td>
<td>输出Bazel服务器的运行时信息</td>
</tr>
<tr>
<td class="blog_h3">fetch</td>
<td>
<p>拉取某个目标的外部依赖</p>
<p>使用<pre class="crayon-plain-tag">--fetch=false</pre>标记可以禁止在构建时进行自动的外部依赖（本地系统依赖除外）抓取，通过local_repository、new_local_repository声明的“本地”外部存储库，总是会抓取</p>
<p>如果禁用了自动抓取，你需要在以下时机手工抓取：</p>
<ol>
<li>第一次构建之前</li>
<li>每当新增了外部依赖之后</li>
</ol>
<p>示例：</p>
<pre class="crayon-plain-tag"># 抓取两个外部依赖
bazel fetch //foo:bar //bar:baz
# 抓取工作空间的全部外部依赖
bazel fetch //...</pre>
<p><strong><span style="background-color: #c0c0c0;">存储库缓存</span></strong></p>
<p>Bazel会避免反复抓取同一个文件，即使：</p>
<ol>
<li>多个工作空间使用同一外部依赖</li>
<li>外部存储库的定义改变了，但是需要下载的还是那个文件</li>
</ol>
<p>Bazel在本地文件系统维护外部存储库的缓存，默认位置在~/.cache/bazel/_bazel_$USER/cache/repos/v1/。可以使用选项--repository_cache指定不同的缓存位置。缓存可以被所有命名空间、所有Bazel版本共享</p>
<p><strong><span style="background-color: #c0c0c0;">避免下载</span></strong></p>
<p>你可以指定--distdir选项，其值是一个只读的目录，bazel会在目录中寻找文件，而非去网络上下载。匹配方式是URL中的Basename + 文件哈希。如果不指定哈希值，则Bazel不会去--distdir寻找文件</p>
</td>
</tr>
<tr>
<td class="blog_h3">mobile-install</td>
<td>在移动设备上安装目标</td>
</tr>
<tr>
<td class="blog_h3">query</td>
<td>执行依赖图查询</td>
</tr>
<tr>
<td class="blog_h3">run</td>
<td>运行指定的目标</td>
</tr>
<tr>
<td class="blog_h3">shutdown</td>
<td>关闭Bazel服务器</td>
</tr>
<tr>
<td class="blog_h3">test</td>
<td>构建并运行指定的测试目标</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/bazel-study-note">Bazel学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/bazel-study-note/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>
