<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>민수's 기술 블로그</title>
    <link>https://alwayspr.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 24 Jun 2026 17:01:10 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>AlwaysPr</managingEditor>
    <image>
      <title>민수's 기술 블로그</title>
      <url>https://t1.daumcdn.net/cfile/tistory/2236483858EF1D681C</url>
      <link>https://alwayspr.tistory.com</link>
    </image>
    <item>
      <title>Java 21 새로운 기능(2) - preview</title>
      <link>https://alwayspr.tistory.com/54</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;자바21로고.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9lUIB/btssgDi2FW5/suuWWnvnL72n6V2y1t6DL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9lUIB/btssgDi2FW5/suuWWnvnL72n6V2y1t6DL0/img.png&quot; data-alt=&quot;Java&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9lUIB/btssgDi2FW5/suuWWnvnL72n6V2y1t6DL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9lUIB%2FbtssgDi2FW5%2FsuuWWnvnL72n6V2y1t6DL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;800&quot; data-filename=&quot;자바21로고.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Java&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://alwayspr.tistory.com/53&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Java&amp;nbsp;21&amp;nbsp;새로운 기능(1)&amp;nbsp;-&amp;nbsp;main&lt;/a&gt;에 이어 Preview 인 7개의 feature를 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;String Templates&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String을 간단하게 사용하기 위해 이미 많은 언어에서 String Templates을 사용하고 있다. Stirng Templates은 String을 쉽게 표현해서 Java 코드 작성을 간단하게 한다. 이로 인해 text와&amp;nbsp;expressions의&amp;nbsp;가독성이 향상된다.&lt;/p&gt;
&lt;pre id=&quot;code_1693029098508&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;C#             $&quot;{x} plus {y} equals {x + y}&quot;
Visual Basic   $&quot;{x} plus {y} equals {x + y}&quot;
Python         f&quot;{x} plus {y} equals {x + y}&quot;
Scala          s&quot;$x plus $y equals ${x + y}&quot;
Groovy         &quot;$x plus $y equals ${x + y}&quot;
Kotlin         &quot;$x plus $y equals ${x + y}&quot;
JavaScript     `${x} plus ${y} equals ${x + y}`
Ruby           &quot;#{x} plus #{y} equals #{x + y}&quot;
Swift          &quot;\(x) plus \(y) equals \(x + y)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;pre id=&quot;code_1693029707229&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String name = &quot;Joan&quot;;
String info = STR.&quot;My name is \{name}&quot;;
assert info.equals(&quot;My name is Joan&quot;);   // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;\{변수명}으로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693029759483&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Embedded expressions can be strings
String firstName = &quot;Bill&quot;;
String lastName  = &quot;Duck&quot;;
String fullName  = STR.&quot;\{firstName} \{lastName}&quot;;
| &quot;Bill Duck&quot;
String sortName  = STR.&quot;\{lastName}, \{firstName}&quot;;
| &quot;Duck, Bill&quot;

// Embedded expressions can perform arithmetic
int x = 10, y = 20;
String s = STR.&quot;\{x} + \{y} = \{x + y}&quot;;
| &quot;10 + 20 = 30&quot;

// Embedded expressions can invoke methods and access fields
String s = STR.&quot;You have a \{getOfferType()} waiting for you!&quot;;
| &quot;You have a gift waiting for you!&quot;
String t = STR.&quot;Access at \{req.date} \{req.time} from \{req.ipAddress}&quot;;
| &quot;Access at 2022-03-25 15:34 from 8.8.8.8&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 method 호출과 추가적인 연산도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Unnamed&amp;nbsp;Patterns&amp;nbsp;and&amp;nbsp;Variables&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하지 않는 변수를 이름 없는 변수로 지정하여 Java 언어를 향상시킨다. 불필요한 Nested patterns을 제거하여 Record patterns의 가독성을 개선한다. 또한 선언해야 하지만 사용되지 않는 변수를 식별하여, 코드의 유지보수성을 개선한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;pre id=&quot;code_1693030201393&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// type X
... instanceof Point(int x, _)
case Point(int x, _)


// type 명시
... instanceof Point(int x, int _)
case Point(int x, int _)

// Pattern matching 이외에도 자유롭게 사용
int _ = q.remove();

... } catch (NumberFormatException _) { ...

(int x, int _) -&amp;gt; x + x&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Unnamed&amp;nbsp;Classes&amp;nbsp;and&amp;nbsp;Instance&amp;nbsp;Main&amp;nbsp;Methods&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바를 동작시키기 위해서는 클래스와 함께 `public static void main(String[] args)`이라는 긴 구절을 외워서 사용해야 한다. 이것을 이름 없는 클래스와 간단한 main method로 동작가능하게 한다. 학생들이 대규모 프로그램을 위해 설계된 언어 기능을 이해하지 않고도 첫 번째 프로그램을 작성할 수 있도록 한다&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;pre id=&quot;code_1693030742965&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//as-is
public class HelloWorld { 
    public static void main(String[] args) { 
        System.out.println(&quot;Hello, World!&quot;);
    }
}

//to-be
class HelloWorld { 
    void main() { 
        System.out.println(&quot;Hello, World!&quot;);
    }
}

//unnamed classes
void main() {
    System.out.println(&quot;Hello, World!&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Scoped&amp;nbsp;Values&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scoped Values을 도입하여 Thread 내 또는 Thread 간에 불변 데이터를 공유할 수 있다. 특히 많은 수의 Virtual Thread를 사용할 때 Thread 지역 변수보다 선호된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thread 지역 변수는 몇 가지 설계적 결함이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;제약 없는 변경 가능성 - 가변객체의 단점&lt;/li&gt;
&lt;li&gt;무한한 수명 - remove 메소드를 호출하지 않으면, 계속 살아있고 메모리 누출로 이어질 수 있음&lt;/li&gt;
&lt;li&gt;값 비싼 상속비용 - 많은 수의 Thread를 사용할 때 값비싼 비용이 발생&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 단점을 Scoped Values가 해소해 주며, 안전하고 효율적인 API이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;pre id=&quot;code_1693031494689&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final static ScopedValue&amp;lt;...&amp;gt; V = ScopedValue.newInstance();

// In some method
ScopedValue.where(V, &amp;lt;value&amp;gt;)
           .run(() -&amp;gt; { ... V.get() ... call methods ... });

// In a method called directly or indirectly from the lambda expression
... V.get() ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드의 구조는 Thread가 ScopedValue의 값을 읽을 수 있는 기간을 나타낸다. 불변성과 결합된 이 제한된 수명은 Thread 동작에 대한 추론을 크게 단순화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693031729478&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Server {
    final static ScopedValue&amp;lt;Principal&amp;gt; PRINCIPAL
        = ScopedValue.newInstance();                                  // (1)

    void serve(Request request, Response response) {
        var level     = (request.isAdmin() ? ADMIN : GUEST);
        var principal = new Principal(level);
        ScopedValue.where(PRINCIPAL, principal)                       // (2)
                   .run(() -&amp;gt; Application.handle(request, response));
    }
}

class DBAccess {
    DBConnection open() {
        var principal = Server.PRINCIPAL.get();                       // (3)
        if (!principal.canOpen()) throw new  InvalidPrincipalException();
        return newConnection(...);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 프레임워크를 예로 보면,&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인스턴스화한다.&lt;/li&gt;
&lt;li&gt;Value를 ScopedValue에 담는다.&lt;/li&gt;
&lt;li&gt;사용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Structured&amp;nbsp;Concurrency&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성은 서로 다른 Thread에서 실행 중인 관련 작업 그룹을 하나의 작업 단위로 처리하여 오류 처리 및 취소를 간소화하고, 안정성을 개선하며, 통합 가시성을 향상한다. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Thread 누수 및 취소 지연과 같이 취소/종료로 인해 발생하는 일반적인 위험을 제거할 수 있는 동시 프로그래밍 스타일을 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;pre id=&quot;code_1693032302952&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//as-is
Response handle() throws ExecutionException, InterruptedException {
    Future&amp;lt;String&amp;gt;  user  = esvc.submit(() -&amp;gt; findUser());
    Future&amp;lt;Integer&amp;gt; order = esvc.submit(() -&amp;gt; fetchOrder());
    String theUser  = user.get();   // Join findUser
    int    theOrder = order.get();  // Join fetchOrder
    return new Response(theUser, theOrder);
}

//to-be
Response handle() throws ExecutionException, InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Supplier&amp;lt;String&amp;gt;  user  = scope.fork(() -&amp;gt; findUser());
        Supplier&amp;lt;Integer&amp;gt; order = scope.fork(() -&amp;gt; fetchOrder());

        scope.join()            // Join both subtasks
             .throwIfFailed();  // ... and propagate errors

        // Here, both subtasks have succeeded, so compose their results
        return new Response(user.get(), order.get());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Vector&amp;nbsp;API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지원되는 CPU 아키텍처에서 최적의 Vector 명령어로 런타임에 안정적으로 컴파일되어 동등한 스칼라 계산보다 뛰어난 성능을 달성하는 벡터 계산을 표현하는 API를 도입한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;pre id=&quot;code_1693032487303&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//as-is
void scalarComputation(float[] a, float[] b, float[] c) {
   for (int i = 0; i &amp;lt; a.length; i++) {
        c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
   }
}


//to-be
static final VectorSpecies&amp;lt;Float&amp;gt; SPECIES = FloatVector.SPECIES_PREFERRED;

void vectorComputation(float[] a, float[] b, float[] c) {
    int i = 0;
    int upperBound = SPECIES.loopBound(a.length);
    for (; i &amp;lt; upperBound; i += SPECIES.length()) {
        // FloatVector va, vb, vc;
        var va = FloatVector.fromArray(SPECIES, a, i);
        var vb = FloatVector.fromArray(SPECIES, b, i);
        var vc = va.mul(va)
                   .add(vb.mul(vb))
                   .neg();
        vc.intoArray(c, i);
    }
    for (; i &amp;lt; a.length; i++) {
        c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VetorSpecies를 정적 final 필드에 저장하여 사용하면 런타임 컴파일러가 값을 상수로 취급하여 벡터 계산을 더 잘 최적화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Foreign&amp;nbsp;Function&amp;nbsp;&amp;amp;&amp;nbsp;Memory&amp;nbsp;API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 프로그램이 Java 런타임 외부의 코드 및 데이터와 상호 운용할 수 있는 API이 다. 이 API는 외부 함수(즉, JVM 외부의 코드)를 효율적으로 호출하고 외부 메모리(즉, JVM에서 관리하지 않는 메모리)에 안전하게 액세스함으로써 Java 프로그램이 JNI의 취약성과 위험 없이 네이티브 라이브러리를 호출하고 네이티브 데이터를 처리할 수 있게 해 준다. 해당 API는 &lt;a href=&quot;https://cr.openjdk.org/~pminborg/panama/21/v1/javadoc/java.base/java/lang/foreign/package-summary.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;java.lang.foreign&lt;/a&gt; 패키지에 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;pre id=&quot;code_1693032959640&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Foreign memory access
 MemorySegment segment = Arena.ofAuto().allocate(10 * 4, 1);
 for (int i = 0 ; i &amp;lt; 10 ; i++) {
     segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
 }

// Deterministic deallocationCopy URL
try (Arena arena = Arena.ofConfined()) {
    MemorySegment segment = arena.allocate(10 * 4);
    for (int i = 0 ; i &amp;lt; 10 ; i++) {
        segment.setAtIndex(ValueLayout.JAVA_INT, i, i);
    }
}

// Foreign function access
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle strlen = linker.downcallHandle(
    stdlib.find(&quot;strlen&quot;).get(),
    FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);

try (Arena arena = Arena.ofConfined()) {
    MemorySegment cString = arena.allocateUtf8String(&quot;Hello&quot;);
    long len = (long)strlen.invoke(cString); // 5
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>FFM</category>
      <category>Java21</category>
      <category>scopedValue</category>
      <category>stringtemplates</category>
      <category>structured-concurrency</category>
      <category>자바21</category>
      <author>AlwaysPr</author>
      <guid isPermaLink="true">https://alwayspr.tistory.com/54</guid>
      <comments>https://alwayspr.tistory.com/54#entry54comment</comments>
      <pubDate>Mon, 28 Aug 2023 08:00:04 +0900</pubDate>
    </item>
    <item>
      <title>Java 21 새로운 기능(1) - main</title>
      <link>https://alwayspr.tistory.com/53</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Java_Logo.png&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;2500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I8ExY/btsrauz1s5r/kJBVpkIarOBa7pLryF0rk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I8ExY/btsrauz1s5r/kJBVpkIarOBa7pLryF0rk1/img.png&quot; data-alt=&quot;Java&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I8ExY/btsrauz1s5r/kJBVpkIarOBa7pLryF0rk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI8ExY%2Fbtsrauz1s5r%2FkJBVpkIarOBa7pLryF0rk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4000&quot; height=&quot;2500&quot; data-filename=&quot;Java_Logo.png&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;2500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Java&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023.09.19에 LTS 버전인 Java 21이 출시될 예정이다. Preview를 포함해서 &lt;a href=&quot;https://openjdk.org/projects/jdk/21/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;총 15개의 feature&lt;/a&gt;가 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시간에는 정식 릴리즈된 feature들을 살펴보고, 다음 시간엔 &lt;a href=&quot;https://alwayspr.tistory.com/54&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Preview&lt;/a&gt;를 살펴보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Virtual&amp;nbsp;Threads&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Virtual Threads는 높은 처리량의 동시 애플리케이션을 작성, 유지 관리, 모니터링하는데 드는 비용을 획기적으로 줄여주는 경량 스레드이다. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Thread Per Reuqest 스타일로 작성된 서버 애플리케이션을 최적의 하드웨어로 확장할 수 있다. 또한 `java.lang.Thread API`를 사용하는 기존 코드를 최소한의 변경으로 가상 스레드를 사용할 수 있게 하위호환성을 잘 유지한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;pre id=&quot;code_1691898181832&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기본 Thread
new Thread(()  -&amp;gt; System.out.println(&quot;Hello World&quot;)).start();

// Virtual Thread
Thread.startVirtualThread(() -&amp;gt; System.out.println(&quot;Hello World&quot;)).start();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 간단하게 Virtual Thread를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691899226956&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000).forEach(i -&amp;gt; {
        executor.submit(() -&amp;gt; {
            Thread.sleep(Duration.ofSeconds(1));
            return i;
        });
    });
}  // executor.close() is called implicitly, and waits&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 Virtual Thread를 통해서 10,000 번 1초 sleep을 하는 코드이다. 기존의 Thread와는 다르게 소수의 OS Thread를 사용해서 처리할 수 있다. JEP에서는 아마 1개의 OS Thread만을 사용해서 처리할 수 있을 거라고 한다. 만약&amp;nbsp; Executors.newCachedThreadPool()`를 사용했다면 10,000개의 OS Thread를 생성하려 시도하면서 문제가 생겼을 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Virtual&amp;nbsp;Thread는&amp;nbsp;일반적인&amp;nbsp;서버&amp;nbsp;애플리케이션의&amp;nbsp;처리량을&amp;nbsp;개선하는데&amp;nbsp;도움이&amp;nbsp;된다. 왜냐하면&amp;nbsp;대부분의&amp;nbsp;어플리케이션이&amp;nbsp;DB,&amp;nbsp;API,&amp;nbsp;Network&amp;nbsp;호출처럼&amp;nbsp;Block&amp;nbsp;된&amp;nbsp;채&amp;nbsp;기다리고&amp;nbsp;있는&amp;nbsp;작업이&amp;nbsp;많기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Pattern Matching for switch&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;switch  문 및 expression에 대한 Pattern Matching으로 Java 프로그래밍 언어를 개선한다. Pattern Matching을 switch로 확장하면 각각 특정 동작이 있는 여러 패턴에 대해 expression을 테스트할 수 있으므로 복잡한 데이터 지향 쿼리를 간결하고 안전하게 표현할 수 있다. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;case label에 패턴 매칭을 사용하여 switch 문 및 statement의 표현력과 적용성을 확장할 수 있다. 그리고 기존과 다른 점은 case 문에 null을 허용한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;pre id=&quot;code_1691898254457&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Prior to Java 16
if (obj instanceof String) {
    String s = (String)obj;
    ... use s ...
}

// As of Java 16
if (obj instanceof String s) {
    ... use s ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위는 Java 16 전후의 feature이며, Pattern matching 하여 s를 변수로 곧바로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691898275372&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Prior to Java 21
static String formatter(Object obj) {
    String formatted = &quot;unknown&quot;;
    if (obj instanceof Integer i) {
        formatted = String.format(&quot;int %d&quot;, i);
    } else if (obj instanceof Long l) {
        formatted = String.format(&quot;long %d&quot;, l);
    } else if (obj instanceof Double d) {
        formatted = String.format(&quot;double %f&quot;, d);
    } else if (obj instanceof String s) {
        formatted = String.format(&quot;String %s&quot;, s);
    }
    return formatted;
}

// As of Java 21
static String formatterPatternSwitch(Object obj) {
    return switch (obj) {
        case Integer i -&amp;gt; String.format(&quot;int %d&quot;, i);
        case Long l    -&amp;gt; String.format(&quot;long %d&quot;, l);
        case Double d  -&amp;gt; String.format(&quot;double %f&quot;, d);
        case String s  -&amp;gt; String.format(&quot;String %s&quot;, s);
        default        -&amp;gt; obj.toString();
    };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pattern&amp;nbsp;matching&amp;nbsp;+&amp;nbsp;switch&amp;nbsp;문을&amp;nbsp;활용하여&amp;nbsp;코드를&amp;nbsp;간결하고&amp;nbsp;안전하게&amp;nbsp;표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691898294722&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Prior to Java 21
static void testFooBarOld(String s) {
    if (s == null) {
        System.out.println(&quot;Oops!&quot;);
        return;
    }
    switch (s) {
        case &quot;Foo&quot;, &quot;Bar&quot; -&amp;gt; System.out.println(&quot;Great&quot;);
        default           -&amp;gt; System.out.println(&quot;Ok&quot;);
    }
}

// As of Java 21
static void testFooBarNew(String s) {
    switch (s) {
        case null         -&amp;gt; System.out.println(&quot;Oops&quot;);
        case &quot;Foo&quot;, &quot;Bar&quot; -&amp;gt; System.out.println(&quot;Great&quot;);
        default           -&amp;gt; System.out.println(&quot;Ok&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null을&amp;nbsp;별도로&amp;nbsp;처리하지않고,&amp;nbsp;case를&amp;nbsp;통해&amp;nbsp;활용할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691898316840&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// As of Java 21
static void testStringOld(String response) {
    switch (response) {
        case null -&amp;gt; { }
        case String s -&amp;gt; {
            if (s.equalsIgnoreCase(&quot;YES&quot;))
                System.out.println(&quot;You got it&quot;);
            else if (s.equalsIgnoreCase(&quot;NO&quot;))
                System.out.println(&quot;Shame&quot;);
            else
                System.out.println(&quot;Sorry?&quot;);
        }
    }
}

// As of Java 21
static void testStringNew(String response) {
    switch (response) {
        case null -&amp;gt; { }
        case String s
        when s.equalsIgnoreCase(&quot;YES&quot;) -&amp;gt; {
            System.out.println(&quot;You got it&quot;);
        }
        case String s
        when s.equalsIgnoreCase(&quot;NO&quot;) -&amp;gt; {
            System.out.println(&quot;Shame&quot;);
        }
        case String s -&amp;gt; {
            System.out.println(&quot;Sorry?&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`when` 키워드를 사용하여 Pattern에 대한 세분화된 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Record&amp;nbsp;Patterns&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Record Patterns을 사용하여 Record 값을 분해해 Java 프로그래밍 언어를 향상하며, Record Patterns 및 Type Patterns을 활용하여 강력하고 선언적이며 구성 가능한 형식의 데이터 탐색과 처리를 제공한다. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Record Class의 인스턴스를 분해하여 Pattern matching을 확장해 보다 정교한 데이터 쿼리로 활용할 수 있고, 더 나아가 &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;중첩된 패턴을 사용하여 보다 세밀하게 데이터 쿼리로 활용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;pre id=&quot;code_1691898377009&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;record Point(int x, int y) {}

// As of Java 16
static void printSum(Object obj) {
    if (obj instanceof Point p) {
        int x = p.x();
        int y = p.y();
        System.out.println(x+y);
    }
}

// As of Java 21
static void printSum(Object obj) {
    if (obj instanceof Point(int x, int y)) {
        System.out.println(x+y);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java&amp;nbsp;21에서는&amp;nbsp;Pattern&amp;nbsp;matching&amp;nbsp;후&amp;nbsp;Record의&amp;nbsp;필드를&amp;nbsp;간단하게&amp;nbsp;다룰&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;코드가&amp;nbsp;간결해지고,&amp;nbsp;명시적인걸&amp;nbsp;알&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1691898414556&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;record Point(int x, int y) {}

enum Color { RED, GREEN, BLUE }

record ColoredPoint(Point p, Color c) {}

record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 클래스를 가지고 아래를 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691898434464&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// As of Java 21
static void printUpperLeftColoredPoint(Rectangle r) {
    if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
         System.out.println(ul.c());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 예제와 동일한 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691898450551&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// As of Java 21
static void printColorOfUpperLeftPoint(Rectangle r) {
    if (r instanceof Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr)) {
        System.out.println(c);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Patterns&amp;nbsp;matching&amp;nbsp;후&amp;nbsp;첫번째&amp;nbsp;`ColoredPoint`를&amp;nbsp;보면&amp;nbsp;`Point`를&amp;nbsp;인자로&amp;nbsp;가지고&amp;nbsp;있는&amp;nbsp;것을&amp;nbsp;알&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;이처럼&amp;nbsp;Record&amp;nbsp;patterns&amp;nbsp;안에&amp;nbsp;다른&amp;nbsp;Patterns을&amp;nbsp;중첩하여&amp;nbsp;외부&amp;nbsp;Record와&amp;nbsp;내부&amp;nbsp;Record를&amp;nbsp;한&amp;nbsp;번에&amp;nbsp;분해할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691898491550&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Rectangle r = new Rectangle(new ColoredPoint(new Point(x1, y1), c1), new ColoredPoint(new Point(x2, y2), c2));

static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) {
    if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c), var lr)) {
        System.out.println(&quot;Upper-left corner: &quot; + x);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중첩된&amp;nbsp;패턴을&amp;nbsp;사용하면&amp;nbsp;중첩된&amp;nbsp;생성자의&amp;nbsp;구조를&amp;nbsp;반영하는&amp;nbsp;코드로&amp;nbsp;위처럼 분해하여&amp;nbsp;제공할&amp;nbsp;수&amp;nbsp;있으며,&amp;nbsp;`var`&amp;nbsp;키워드&amp;nbsp;또한&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691898514485&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// As of Java 21
record Pair(Object x, Object y) {}

Pair p = new Pair(42, 42);

if (p instanceof Pair(String s, String t)) {
    System.out.println(s + &quot;, &quot; + t);
} else {
    System.out.println(&quot;Not a pair of strings&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중첩 패턴은 당연히 타입이 일치하지 않을 수 있으며 위처럼 else 문을 통하여 제어가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sequenced&amp;nbsp;Collections&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서를&amp;nbsp;가진&amp;nbsp;Collection을&amp;nbsp;표현하는&amp;nbsp;새로운&amp;nbsp;인터페이스이다.&amp;nbsp;첫 번째&amp;nbsp;요소와&amp;nbsp;마지막&amp;nbsp;요소에&amp;nbsp;접근하고&amp;nbsp;해당&amp;nbsp;요소를&amp;nbsp;역순으로&amp;nbsp;처리하기&amp;nbsp;위한&amp;nbsp;일관된&amp;nbsp;API를&amp;nbsp;제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1691898594035&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface SequencedCollection&amp;lt;E&amp;gt; extends Collection&amp;lt;E&amp;gt; {
    // new method
    SequencedCollection&amp;lt;E&amp;gt; reversed();
    // methods promoted from Deque
    void addFirst(E);
    void addLast(E);
    E getFirst();
    E getLast();
    E removeFirst();
    E removeLast();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SequencedCollectionDiagram20220216.png&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ptiDG/btsq2kycdnh/4g6DckKznnSIlaK95HSeu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ptiDG/btsq2kycdnh/4g6DckKznnSIlaK95HSeu0/img.png&quot; data-alt=&quot;sequenceCollection&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ptiDG/btsq2kycdnh/4g6DckKznnSIlaK95HSeu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FptiDG%2Fbtsq2kycdnh%2F4g6DckKznnSIlaK95HSeu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;840&quot; data-filename=&quot;SequencedCollectionDiagram20220216.png&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;sequenceCollection&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Generational ZGC&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Z Garbage Collector(ZGC)를 확장하여 young and old object의 세대를 분리하여 유지함으로써 애플리케이션 성능을 개선한다. 이렇게 하면 ZGC가 대체로 빨리 죽는 young object를 더 자주 수집할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;pre id=&quot;code_1691898702628&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ java -XX:+UseZGC -XX:+ZGenerational ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Key Encapsulation Mechanism API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공개&amp;nbsp;키&amp;nbsp;암호화를&amp;nbsp;사용하여&amp;nbsp;대칭&amp;nbsp;키를&amp;nbsp;보호하는&amp;nbsp;암호화&amp;nbsp;기술인&amp;nbsp;Key&amp;nbsp;Encapsulation&amp;nbsp;Mechanism(KEM)을&amp;nbsp;위한&amp;nbsp;API이다.&lt;br /&gt;Java의 기존 암호화 API 중 어떤 것도 KEM을 자연스럽게 표현할 수 없었고, 타사 보안 제공업체에서  이미 표준 KEM API에 대한 필요성을 증명해 왔기에 KEM 클래스 구현이 필요했다. 또한 KEM은 양자 공격을 방어하는 데 중요한 도구가 될 걸로 보인다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691901555981&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class KEM {

    public static KEM getInstance(String alg)
        throws NoSuchAlgorithmException;
    public static KEM getInstance(String alg, Provider p)
        throws NoSuchAlgorithmException;
    public static KEM getInstance(String alg, String p)
        throws NoSuchAlgorithmException, NoSuchProviderException;

    public static final class Encapsulated {
        public Encapsulated(SecretKey key, byte[] encapsulation, byte[] params);
        public SecretKey key();
        public byte[] encapsulation();
        public byte[] params();
    }

    public static final class Encapsulator {
        String providerName();
        int secretSize();           // Size of the shared secret
        int encapsulationSize();    // Size of the key encapsulation message
        Encapsulated encapsulate();
        Encapsulated encapsulate(int from, int to, String algorithm);
    }

    public Encapsulator newEncapsulator(PublicKey pk)
            throws InvalidKeyException;
    public Encapsulator newEncapsulator(PublicKey pk, SecureRandom sr)
            throws InvalidKeyException;
    public Encapsulator newEncapsulator(PublicKey pk, AlgorithmParameterSpec spec,
                                        SecureRandom sr)
            throws InvalidAlgorithmParameterException, InvalidKeyException;

    public static final class Decapsulator {
        String providerName();
        int secretSize();           // Size of the shared secret
        int encapsulationSize();    // Size of the key encapsulation message
        SecretKey decapsulate(byte[] encapsulation) throws DecapsulateException;
        SecretKey decapsulate(byte[] encapsulation, int from, int to,
                              String algorithm)
                throws DecapsulateException;
    }

    public Decapsulator newDecapsulator(PrivateKey sk)
            throws InvalidKeyException;
    public Decapsulator newDecapsulator(PrivateKey sk, AlgorithmParameterSpec spec)
            throws InvalidAlgorithmParameterException, InvalidKeyException;

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Deprecate the Windows 32-bit x86 Port for Removal&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;향후 Release에서 제거를 목적으로 Windows 32비트 x86 포트가 deprecate 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Prepare to Disallow the Dynamic Loading of Agents&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agent가 실행 중인 JVM에 동적으로 로드될 때 경고를 발생시킨다. 이러한 경고는 기본적으로 무결성을 개선하기 위해 기본적으로 Agent의 동적 로드를 허용하지 않는 향후 릴리스에 대해 사용자를 준비시키는 것을 목표로 한다. startup 시 agent를 로드하는 Serviceability tools은 모든 릴리스에서 경고를 발행하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://alwayspr.tistory.com/54&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Java 21 새로운 기능(2) - preview&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1694342661847&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Java 21 새로운 기능(2) - preview&quot; data-og-description=&quot;Java 21 new feature (1) - main에 이어 Preview 인 7개의 feature를 알아보자. String Templates String을 간단하게 사용하기 위해 이미 많은 언어에서 String Templates을 사용하고 있다. C# $&amp;quot;{x} plus {y} equals {x + y}&amp;quot; Visual B&quot; data-og-host=&quot;alwayspr.tistory.com&quot; data-og-source-url=&quot;https://alwayspr.tistory.com/54&quot; data-og-url=&quot;https://alwayspr.tistory.com/54&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ebeDTc/hyTSA0wHuv/JxupyDwl4e8WA3aMMTTK30/img.png?width=800&amp;amp;height=500&amp;amp;face=0_0_800_500,https://scrap.kakaocdn.net/dn/bBsM4g/hyTSzURZ5l/NLRzO3VRLP9nWRmVGyxEr1/img.png?width=800&amp;amp;height=500&amp;amp;face=0_0_800_500,https://scrap.kakaocdn.net/dn/ru8VT/hyTSAM1VZD/peldDU7E8KDSoXvnl8DttK/img.png?width=1280&amp;amp;height=800&amp;amp;face=0_0_1280_800&quot;&gt;&lt;a href=&quot;https://alwayspr.tistory.com/54&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://alwayspr.tistory.com/54&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ebeDTc/hyTSA0wHuv/JxupyDwl4e8WA3aMMTTK30/img.png?width=800&amp;amp;height=500&amp;amp;face=0_0_800_500,https://scrap.kakaocdn.net/dn/bBsM4g/hyTSzURZ5l/NLRzO3VRLP9nWRmVGyxEr1/img.png?width=800&amp;amp;height=500&amp;amp;face=0_0_800_500,https://scrap.kakaocdn.net/dn/ru8VT/hyTSAM1VZD/peldDU7E8KDSoXvnl8DttK/img.png?width=1280&amp;amp;height=800&amp;amp;face=0_0_1280_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java 21 새로운 기능(2) - preview&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Java 21 new feature (1) - main에 이어 Preview 인 7개의 feature를 알아보자. String Templates String을 간단하게 사용하기 위해 이미 많은 언어에서 String Templates을 사용하고 있다. C# $&quot;{x} plus {y} equals {x + y}&quot; Visual B&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;alwayspr.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>Java21</category>
      <category>pattern matching</category>
      <category>record pattern</category>
      <category>virtual thread</category>
      <category>ZGC</category>
      <category>가상스레드</category>
      <category>레코드매칭</category>
      <category>자바</category>
      <category>자바21</category>
      <category>패턴매칭</category>
      <author>AlwaysPr</author>
      <guid isPermaLink="true">https://alwayspr.tistory.com/53</guid>
      <comments>https://alwayspr.tistory.com/53#entry53comment</comments>
      <pubDate>Sun, 13 Aug 2023 12:53:37 +0900</pubDate>
    </item>
    <item>
      <title>Web applications and Project Loom (번역)</title>
      <link>https://alwayspr.tistory.com/51</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://spring.io/blog/2023/02/27/web-applications-and-project-loom&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;Web applications and Project Loom&lt;/span&gt;&lt;/a&gt;를 번역한 글입니다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Project Loom은 &quot;간편한 사용성(easy-to-use), 높은 처리량(high-throughput), 가벼운 동시성(lightweight concurrency)&quot;을 JRE에 제공하는 것을 목표로 합니다. Project Loom이 도입한 기능 중 하나는 Virtual thread입니다. 이 블로그 게시물에서는 Apache Tomcat에서 배포한 몇 가지 간단한 웹 애플리케이션을 사용하여 Virtual thread가 웹 애플리케이션에 어떤 의미를 주는지 살펴보겠습니다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;높은&amp;nbsp;처리량&amp;nbsp;/&amp;nbsp;경량화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 실험은 Tomcat 표준 Thread pool 사용에 따른 overhaed와 Virtual thread(Loom) 사용에 따른 overhead를 비교합니다.&amp;nbsp;&amp;nbsp;테스트에 사용된 환경은 이 게시물의 마지막에 자세히 설명되어 있습니다.&amp;nbsp;&amp;nbsp;다양한 크기의 응답과 동시성 요청에 대한 RPS(초당 평균 요청) 성능을 실험했습니다. 결과는 다음 그래프와 같습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lWrZJ/btr3gMY7sNN/LCdGGzFUFYyOfk2CRHxbN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lWrZJ/btr3gMY7sNN/LCdGGzFUFYyOfk2CRHxbN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lWrZJ/btr3gMY7sNN/LCdGGzFUFYyOfk2CRHxbN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlWrZJ%2Fbtr3gMY7sNN%2FLCdGGzFUFYyOfk2CRHxbN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;721&quot; height=&quot;694&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;요청을 처리하기 위해 새로운 Virtual thread를 생성하는 overhead가 Thread pool에서 Platform thread를 가져오는 overhead보다 낮다는 결과를 보여줍니다.&lt;br&gt;&lt;br&gt;Thread pool 테스트에서 예상치 못한 결과는 응답 body의 크기가 작고 동시 사용자가 2명일 때가 한 명의 동시 사용자보다 RPS 수가 더 낮다는 점입니다. 확인 결과, Executor에게 전달되는 task과 task의 run() 메서드를 호출하는 Executor 사이에 추가 지연이 발생하는 것으로 확인되었습니다. 동시 사용자가 4명일 때는 이 차이가 줄어들었고, 8명일 때는 거의 사라졌습니다.&lt;br&gt;&lt;br&gt;사용 가능한 프로세서 코어수보다 더 많은 동시 작업이 있을 때 높은 수준의 동시성에서 Vircual thread Executor의 성능이 다시 향상되는 것으로 나타났습니다. 이는 더 작은 응답 body를 사용한 테스트에서 더욱 두드러졌습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;간편한 사용성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 실험에서는 표준 Thread pool과 함께 Servlet 비동기 I/O를 사용하여 얻은 성능과 Virtual Thread 기반 Executor와 함께 간단한 블로킹 I/O를 사용하여 얻은 성능을 비교했습니다. 여기서 Vircual Thread의 잠재적 이점은 단순성(simplicity)입니다. 블로킹 읽기 / 쓰기는 특히 오류 처리를 고려할 때 같은 조건의 Servlet 비동기 읽기 / 쓰기보다 훨씬 더 간단합니다.&lt;br&gt;&lt;br&gt;Servlet 비동기 I/O는 응답에 지연이 있는 외부 서비스에 접근하는 데 자주 사용됩니다. 테스트 웹 애플리케이션은 Service 클래스에서 이를 시뮬레이션했습니다. Virtual Thread 기반 Executor와 함께 사용된 Servlet은 블로킹 스타일로 서비스에 액세스 한 반면, 표준 Thread pool과 함께 사용된 Servlet은 Servlet 비동기 API를 사용하여 서비스에 액세스 했습니다. 네트워크 I/O는 포함되지 않았지만 결과에 영향을 미치지는 않았을 것입니다.&lt;br&gt;&lt;br&gt;초기 테스트에서는 당연히 블로킹 방식과 비동기 방식 간에 측정 가능한 차이가 없었는데, 5초 지연이 대부분을 시간을 차지했기 때문입니다. 지연의 영향이 없는 차이를 살펴보기 위해 지연을 0으로 줄이고 처리량 테스트와 유사한 테스트 세트를 실행했습니다. 결과는 다음 그래프에 나와 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k9bkZ/btr3cCDsU4f/iuqhU5VUSacpWAwsHgjoi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k9bkZ/btr3cCDsU4f/iuqhU5VUSacpWAwsHgjoi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k9bkZ/btr3cCDsU4f/iuqhU5VUSacpWAwsHgjoi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk9bkZ%2Fbtr3cCDsU4f%2FiuqhU5VUSacpWAwsHgjoi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;606&quot; height=&quot;341&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;341&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;다시 우리는 Virtual Thread가 일반적으로 성능이 더 뛰어나다는 것을 알 수 있습니다. 그 차이는 동시성이 낮을 때와 테스트에 사용 가능한 프로세서 코어 수를 초과하는 동시성이 요구될 때 가장 두드러집니다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Virtual Thread 기반 Executor와 Tomcat 표준 Thread pool의 차이는 위의 그래프에서 보이는 것만큼 뚜렷하지 않습니다. 이 테스트는 각 접근 방식과 관련된 overhead를 측정하기 위해 설계되었으며, 실제 애플리케이션을 대표하지 않습니다. 실제 애플리케이션에서 테스트에 나타난 차이는 요청을 완료하는 데 걸리는 시간과 비교할 때 무시해도 될 정도입니다.&lt;br&gt;&lt;br&gt;Tomcat 표준 Thread pool과 Virtual Thread 기반 Executor 간 성능 차이의 주요 원인은 Thread pool 사용 시 Queue에서 task를 추가하고 제거할 때 발생하는 경합입니다. 그리고 이는 Tomcat의 구현을 최적화함으로써 Thread pool queue의 경합을 줄여 처리량을 개선할 수 있어보입니다.&lt;br&gt;&lt;br&gt;상대적 성능에 영향을 미치는 두 번째 요소는 Context switching입니다. Virtual thread의 Context switching 비용이 표준 Thread pool의 Thread보다 저렴합니다. 그래서 사용 가능한 프로세서 코어 수 이상이 요구되는 동시성이 두 번째 실험에서 나타난 성능 차이를 설명할 수 있는 원인일 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Virtual thread 기반 Executor를 사용하는 것은 Tomcat 표준 Thread pool에 대한 실행 가능한 대안입니다. Virtual thread로 전환하면 컨테이너 overhead 측면에서 얻을 수 있는 이점은 미미합니다.&lt;br&gt;&lt;br&gt;Servlet 비동기 API, 리액티브 프로그래밍, 기타 비동기 API로 전환하지 않은 웹 애플리케이션인 클래식 Spring MVC와 같은 블로킹 기반의 웹 어플리케이션은 Virtual Thread 기반 Exctuor로 전환하면 확장성(scalability)이 약간 개선될 수 있습니다. 웹 애플리케이션에 따라 웹 애플리케이션 코드의 큰 변경 없이 이러한 개선 사항을 달성할 수 있습니다.&lt;br&gt;&lt;br&gt;Servlet 비동기 API, 리액티브 프로그래밍, 기타 비동기 API를 사용하도록 전환한 웹 애플리케이션은 Virtual Thread 기반 Executor로 전환해도 유의미한 차이(긍정적이든 부정적이든)를 얻지 못할 가능성이 높습니다.&lt;br&gt;&lt;br&gt;장기적으로 볼 때 Virtual Thread의 가장 큰 장점은 애플리케이션 코드의 간소화입니다. 현재 Servlet 비동기 API, 리액티브 프로그래밍, 기타 비동기 API를 사용해야 하는 일부 사용 사례는 블로킹 I/O와 Virtual Thread를 사용하여 충족할 수 있을 것입니다. 여기서 주의할 점은 애플리케이션이 다른 외부 서비스를 여러 번 호출해야 하는 경우가 많다는 것입니다. 이 작업은 병렬로 수행하는 것이 가장 효율적이며&amp;nbsp;&lt;a href=&quot;https://projectreactor.io/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;Project&amp;nbsp;Reactor&lt;/span&gt;&lt;/a&gt;와&amp;nbsp;같은&amp;nbsp;프레임워크가&amp;nbsp;이를&amp;nbsp;최고&amp;nbsp;수준으로&amp;nbsp;지원하지만,&amp;nbsp;이에&amp;nbsp;상응하는&amp;nbsp;JRE의&amp;nbsp;솔루션(구조적&amp;nbsp;동시성)은&amp;nbsp;아직&amp;nbsp;incubator&amp;nbsp;단계에&amp;nbsp;있으며&amp;nbsp;여러&amp;nbsp;future를&amp;nbsp;조정하는&amp;nbsp;것을&amp;nbsp;목표로&amp;nbsp;할&amp;nbsp;뿐&amp;nbsp;가장&amp;nbsp;편리한&amp;nbsp;방식으로&amp;nbsp;서로를&amp;nbsp;기준으로&amp;nbsp;선언하거나&amp;nbsp;구성하는&amp;nbsp;것은&amp;nbsp;아닙니다.&lt;br&gt;&lt;br&gt;마지막으로, Project Loom은 아직 preview 모드에 있습니다. 프로덕션 환경에서 Virtual Thread 사용을 고려하기에는 아직 이르지만, 지금이 바로 Project Loom과 Virtual Thread 사용 계획을 세워 JRE에서 Virtual Thread를 일반적으로 사용할 수 있게 될 때를 대비해야 합니다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트&amp;nbsp;환경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 환경은 다음과 같이 구성되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;Apache&amp;nbsp;Tomcat&amp;nbsp;&lt;a href=&quot;https://github.com/apache/tomcat/tree/11.0.0-M1&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;11.0.0-M1&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;The &lt;a href=&quot;https://github.com/apache/tomcat/blob/11.0.0-M1/modules/loom/src/main/java/org/apache/catalina/core/LoomExecutor.java&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;Loom executor&lt;/span&gt;&lt;/a&gt; from Tomcat's Loom module&lt;/li&gt;
 &lt;li&gt;OpenJDK 21, early access, build 1&lt;/li&gt;
 &lt;li&gt;&lt;a href=&quot;https://github.com/wg/wrk&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;wrk 4.2.0&lt;/span&gt;&lt;/a&gt; (built from source)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트는 Intel i7-6950X processor, 32GB RAM 최신버전의 Ubuntu 22.04.1 LTS 머신에서 수행되었습니다.&lt;br&gt;&lt;br&gt;테스트 간의 차이점을 극대화하고, 일반적인 overhead를 최소화하기 위해 기본 설정에서 다음의 구성들을 변경했습니다.&lt;br&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;loopback 인터페이스를 사용하며, 하나의 머신에서 테스트를 실행하여 네트워크 overhead를 최소화합니다.&lt;/li&gt;
 &lt;li&gt;요청량이 많을 때 상당한 Disk I/O의 원인이 되는 액세스 로그를 비활성화합니다.&lt;/li&gt;
 &lt;li&gt;maxKeepAliveRequests&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;을&lt;/span&gt;&lt;/span&gt; -1로 설정하여 TCP 연결 설정 / 해지에 소요되는 시간을 줄입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한&amp;nbsp;&lt;a href=&quot;https://github.com/markt-asf/loom-blog&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;테스트&amp;nbsp;웹&amp;nbsp;애플리케이션&lt;/span&gt;&lt;/a&gt;은 일반적인 overhead를 최소화하고, 테스트 간의 차이점을 강조하도록 설계되었습니다.&lt;br&gt;&lt;br&gt;사용된 server.xml 파일은 다음과 같습니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;html&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;Server port=&quot;8005&quot; shutdown=&quot;SHUTDOWN&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;lt;Listener className=&quot;org.apache.catalina.startup.VersionLoggerListener&quot; /&amp;gt;

&amp;nbsp;&amp;nbsp;&amp;lt;Service name=&quot;Catalina&quot;&amp;gt;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Executor
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;className=&quot;org.apache.catalina.core.LoomExecutor&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name=&quot;loomExecutor&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/&amp;gt;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Connector 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol=&quot;org.apache.coyote.http11.Http11NioProtocol&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port=&quot;8080&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;maxKeepAliveRequests=&quot;-1&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/&amp;gt;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Connector
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;executor=&quot;loomExecutor&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol=&quot;org.apache.coyote.http11.Http11NioProtocol&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port=&quot;8081&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;maxKeepAliveRequests=&quot;-1&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/&amp;gt;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Engine name=&quot;Catalina&quot; defaultHost=&quot;localhost&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Host name=&quot;localhost&quot;&amp;nbsp;&amp;nbsp;appBase=&quot;webapps&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;unpackWARs=&quot;true&quot; autoDeploy=&quot;true&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/Host&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/Engine&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;lt;/Service&amp;gt;
&amp;lt;/Server&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br&gt;사용된 setenv.sh 파일은 다음과 같습니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;JAVA_OPTS=--enable-preview&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: 139px; top: 4180.33px;&quot;&gt; 
 &lt;div class=&quot;gtx-trans-icon&quot;&gt;
   &amp;nbsp; 
 &lt;/div&gt; 
&lt;/div&gt;</description>
      <category>Java</category>
      <category>LOOM</category>
      <category>loom project</category>
      <category>thread pool</category>
      <category>virtual thread</category>
      <category>Web application</category>
      <author>AlwaysPr</author>
      <guid isPermaLink="true">https://alwayspr.tistory.com/51</guid>
      <comments>https://alwayspr.tistory.com/51#entry51comment</comments>
      <pubDate>Sun, 12 Mar 2023 21:52:38 +0900</pubDate>
    </item>
    <item>
      <title>lombok.config 옵션</title>
      <link>https://alwayspr.tistory.com/50</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;how-to-use-java-lombok-plugin-thumbnail.webp&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QB9uT/btr1TTmiH5N/OA0h1890RQR80qYwx3NPvk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QB9uT/btr1TTmiH5N/OA0h1890RQR80qYwx3NPvk/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QB9uT/btr1TTmiH5N/OA0h1890RQR80qYwx3NPvk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQB9uT%2Fbtr1TTmiH5N%2FOA0h1890RQR80qYwx3NPvk%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;260&quot; data-filename=&quot;how-to-use-java-lombok-plugin-thumbnail.webp&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;lombok.config&lt;/b&gt;에서 사용할 수 있는 옵션은 lombok jar를 &lt;a href=&quot;https://projectlombok.org/download&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다운로드&lt;/a&gt;한 다음 아래 명령어를 작성하면 얻을 수 있다. &lt;i&gt;&lt;s&gt;문서에 있을 법한데...&lt;/s&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1678010004324&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java -jar lombok.jar config -g --verbose&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 직접 jar를 다운로드하고, 위의 명령어까지 치기에는 번거롭기에 정리해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;v1.18.26, v1.18.24&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 두 버전은 변경 사항이 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1678010478128&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;##
## Key : checkerframework
## Type: checkerframework-version
##
## If set with the version of checkerframework.org (in major.minor, or just 'true' for the latest supported version), create relevant checkerframework.org annotations for code lombok generates (default: false).
##
## Examples:
#
# clear checkerframework
# checkerframework = major.minor (example: 3.2 - and no higher than 4.0) or true or false
#

##
## Key : config.stopBubbling
## Type: boolean
##
## Tell the configuration system it should stop looking for other configuration files (default: false).
##
## Examples:
#
# clear config.stopBubbling
# config.stopBubbling = [false | true]
#

##
## Key : lombok.accessors.capitalization
## Type: enum (lombok.core.configuration.CapitalizationStrategy)
##
## Which capitalization strategy to use when converting field names to accessor names and vice versa (default: basic).
##
## Examples:
#
# clear lombok.accessors.capitalization
# lombok.accessors.capitalization = [BASIC | BEANSPEC]
#

##
## Key : lombok.accessors.chain
## Type: boolean
##
## Generate setters that return 'this' instead of 'void' (default: false).
##
## Examples:
#
# clear lombok.accessors.chain
# lombok.accessors.chain = [false | true]
#

##
## Key : lombok.accessors.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Accessors is used.
##
## Examples:
#
# clear lombok.accessors.flagUsage
# lombok.accessors.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.accessors.fluent
## Type: boolean
##
## Generate getters and setters using only the field name (no get/set prefix) (default: false).
##
## Examples:
#
# clear lombok.accessors.fluent
# lombok.accessors.fluent = [false | true]
#

##
## Key : lombok.accessors.makeFinal
## Type: boolean
##
## Generate getters, setters and with-ers with the 'final' modifier (default: false).
##
## Examples:
#
# clear lombok.accessors.makeFinal
# lombok.accessors.makeFinal = [false | true]
#

##
## Key : lombok.accessors.prefix
## Type: list of string
##
## Strip this field prefix, like 'f' or 'm_', from the names of generated getters, setters, and with-ers.
##
## Examples:
#
# clear lombok.accessors.prefix
# lombok.accessors.prefix += &amp;lt;text&amp;gt;
# lombok.accessors.prefix -= &amp;lt;text&amp;gt;
#

##
## Key : lombok.addGeneratedAnnotation
## Type: boolean
##
## Generate @javax.annotation.Generated on all generated code (default: false). Deprecated, use 'lombok.addJavaxGeneratedAnnotation' instead.
##
## Examples:
#
# clear lombok.addGeneratedAnnotation
# lombok.addGeneratedAnnotation = [false | true]
#

##
## Key : lombok.addJavaxGeneratedAnnotation
## Type: boolean
##
## Generate @javax.annotation.Generated on all generated code (default: follow lombok.addGeneratedAnnotation).
##
## Examples:
#
# clear lombok.addJavaxGeneratedAnnotation
# lombok.addJavaxGeneratedAnnotation = [false | true]
#

##
## Key : lombok.addLombokGeneratedAnnotation
## Type: boolean
##
## Generate @lombok.Generated on all generated code (default: false).
##
## Examples:
#
# clear lombok.addLombokGeneratedAnnotation
# lombok.addLombokGeneratedAnnotation = [false | true]
#

##
## Key : lombok.addNullAnnotations
## Type: nullity-annotation-library
##
## Generate some style of null annotation for generated code where this is relevant. (default: none).
##
## Examples:
#
# clear lombok.addNullAnnotations
# lombok.addNullAnnotations = none | javax | eclipse | jetbrains | netbeans | androidx | android.support | checkerframework | findbugs | spring | jml | CUSTOM:com.foo.my.nonnull.annotation:com.foo.my.nullable.annotation
#

##
## Key : lombok.addSuppressWarnings
## Type: boolean
##
## Generate @java.lang.SuppressWarnings(&quot;all&quot;) on all generated code (default: true).
##
## Examples:
#
# clear lombok.addSuppressWarnings
# lombok.addSuppressWarnings = [false | true]
#

##
## Key : lombok.allArgsConstructor.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @AllArgsConstructor is used.
##
## Examples:
#
# clear lombok.allArgsConstructor.flagUsage
# lombok.allArgsConstructor.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.anyConstructor.addConstructorProperties
## Type: boolean
##
## Generate @ConstructorProperties for generated constructors (default: false).
##
## Examples:
#
# clear lombok.anyConstructor.addConstructorProperties
# lombok.anyConstructor.addConstructorProperties = [false | true]
#

##
## Key : lombok.anyConstructor.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if any of the XxxArgsConstructor annotations are used.
##
## Examples:
#
# clear lombok.anyConstructor.flagUsage
# lombok.anyConstructor.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.anyConstructor.suppressConstructorProperties
## Type: boolean
##
## Suppress the generation of @ConstructorProperties for generated constructors (default: false).
##
## Examples:
#
# clear lombok.anyConstructor.suppressConstructorProperties
# lombok.anyConstructor.suppressConstructorProperties = [false | true]
#

##
## Key : lombok.builder.className
## Type: string
##
## Default name of the generated builder class. A * is replaced with the name of the relevant type (default = *Builder).
##
## Examples:
#
# clear lombok.builder.className
# lombok.builder.className = &amp;lt;text&amp;gt;
#

##
## Key : lombok.builder.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Builder is used.
##
## Examples:
#
# clear lombok.builder.flagUsage
# lombok.builder.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.cleanup.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Cleanup is used.
##
## Examples:
#
# clear lombok.cleanup.flagUsage
# lombok.cleanup.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.copyableAnnotations
## Type: list of type-name
##
## Copy these annotations to getters, setters, with methods, builder-setters, etc.
##
## Examples:
#
# clear lombok.copyableAnnotations
# lombok.copyableAnnotations += &amp;lt;fully.qualified.Type&amp;gt;
# lombok.copyableAnnotations -= &amp;lt;fully.qualified.Type&amp;gt;
#

##
## Key : lombok.data.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Data is used.
##
## Examples:
#
# clear lombok.data.flagUsage
# lombok.data.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.delegate.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Delegate is used.
##
## Examples:
#
# clear lombok.delegate.flagUsage
# lombok.delegate.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.equalsAndHashCode.callSuper
## Type: enum (lombok.core.configuration.CallSuperType)
##
## When generating equals and hashCode for classes that extend something (other than Object), either automatically take into account superclass implementation (call), or don't (skip), or warn and don't (warn). (default = warn).
##
## Examples:
#
# clear lombok.equalsAndHashCode.callSuper
# lombok.equalsAndHashCode.callSuper = [CALL | SKIP | WARN]
#

##
## Key : lombok.equalsAndHashCode.doNotUseGetters
## Type: boolean
##
## Don't call the getters but use the fields directly in the generated equals and hashCode method (default = false).
##
## Examples:
#
# clear lombok.equalsAndHashCode.doNotUseGetters
# lombok.equalsAndHashCode.doNotUseGetters = [false | true]
#

##
## Key : lombok.equalsAndHashCode.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @EqualsAndHashCode is used.
##
## Examples:
#
# clear lombok.equalsAndHashCode.flagUsage
# lombok.equalsAndHashCode.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.experimental.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if an experimental feature is used.
##
## Examples:
#
# clear lombok.experimental.flagUsage
# lombok.experimental.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.extensionMethod.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @ExtensionMethod is used.
##
## Examples:
#
# clear lombok.extensionMethod.flagUsage
# lombok.extensionMethod.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.extern.findbugs.addSuppressFBWarnings
## Type: boolean
##
## Generate @edu.umd.cs.findbugs.annotations.SuppressFBWarnings on all generated code (default: false).
##
## Examples:
#
# clear lombok.extern.findbugs.addSuppressFBWarnings
# lombok.extern.findbugs.addSuppressFBWarnings = [false | true]
#

##
## Key : lombok.fieldDefaults.defaultFinal
## Type: boolean
##
## If true, fields, in any file (lombok annotated or not) are marked as final. Use @NonFinal to override this.
##
## Examples:
#
# clear lombok.fieldDefaults.defaultFinal
# lombok.fieldDefaults.defaultFinal = [false | true]
#

##
## Key : lombok.fieldDefaults.defaultPrivate
## Type: boolean
##
## If true, fields without any access modifier, in any file (lombok annotated or not) are marked as private. Use @PackagePrivate or an explicit modifier to override this.
##
## Examples:
#
# clear lombok.fieldDefaults.defaultPrivate
# lombok.fieldDefaults.defaultPrivate = [false | true]
#

##
## Key : lombok.fieldDefaults.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @FieldDefaults is used.
##
## Examples:
#
# clear lombok.fieldDefaults.flagUsage
# lombok.fieldDefaults.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.fieldNameConstants.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @FieldNameConstants is used.
##
## Examples:
#
# clear lombok.fieldNameConstants.flagUsage
# lombok.fieldNameConstants.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.fieldNameConstants.innerTypeName
## Type: identifier-name
##
## The default name of the inner type generated by @FieldNameConstants. (default: 'Fields').
##
## Examples:
#
# clear lombok.fieldNameConstants.innerTypeName
# lombok.fieldNameConstants.innerTypeName = &amp;lt;javaIdentifier&amp;gt;
#

##
## Key : lombok.fieldNameConstants.uppercase
## Type: boolean
##
## The default name of the constants inside the inner type generated by @FieldNameConstants follow the variable name precisely. If this config key is true, lombok will uppercase them as best it can. (default: false).
##
## Examples:
#
# clear lombok.fieldNameConstants.uppercase
# lombok.fieldNameConstants.uppercase = [false | true]
#

##
## Key : lombok.getter.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Getter is used.
##
## Examples:
#
# clear lombok.getter.flagUsage
# lombok.getter.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.getter.lazy.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Getter(lazy=true) is used.
##
## Examples:
#
# clear lombok.getter.lazy.flagUsage
# lombok.getter.lazy.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.getter.noIsPrefix
## Type: boolean
##
## If true, generate and use getFieldName() for boolean getters instead of isFieldName().
##
## Examples:
#
# clear lombok.getter.noIsPrefix
# lombok.getter.noIsPrefix = [false | true]
#

##
## Key : lombok.helper.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Helper is used.
##
## Examples:
#
# clear lombok.helper.flagUsage
# lombok.helper.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.jacksonized.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Jacksonized is used.
##
## Examples:
#
# clear lombok.jacksonized.flagUsage
# lombok.jacksonized.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.apacheCommons.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @CommonsLog is used.
##
## Examples:
#
# clear lombok.log.apacheCommons.flagUsage
# lombok.log.apacheCommons.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.custom.declaration
## Type: custom-log-declaration
##
## Define the generated custom logger field.
##
## Examples:
#
# clear lombok.log.custom.declaration
# lombok.log.custom.declaration = my.cool.Logger my.cool.LoggerFactory.createLogger()(TOPIC,TYPE)
#

##
## Key : lombok.log.custom.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @CustomLog is used.
##
## Examples:
#
# clear lombok.log.custom.flagUsage
# lombok.log.custom.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.fieldIsStatic
## Type: boolean
##
## Make the generated logger fields static (default: true).
##
## Examples:
#
# clear lombok.log.fieldIsStatic
# lombok.log.fieldIsStatic = [false | true]
#

##
## Key : lombok.log.fieldName
## Type: identifier-name
##
## Use this name for the generated logger fields (default: 'log').
##
## Examples:
#
# clear lombok.log.fieldName
# lombok.log.fieldName = &amp;lt;javaIdentifier&amp;gt;
#

##
## Key : lombok.log.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if any of the log annotations is used.
##
## Examples:
#
# clear lombok.log.flagUsage
# lombok.log.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.flogger.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Flogger is used.
##
## Examples:
#
# clear lombok.log.flogger.flagUsage
# lombok.log.flogger.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.javaUtilLogging.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Log is used.
##
## Examples:
#
# clear lombok.log.javaUtilLogging.flagUsage
# lombok.log.javaUtilLogging.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.jbosslog.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @JBossLog is used.
##
## Examples:
#
# clear lombok.log.jbosslog.flagUsage
# lombok.log.jbosslog.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.log4j.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Log4j is used.
##
## Examples:
#
# clear lombok.log.log4j.flagUsage
# lombok.log.log4j.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.log4j2.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Log4j2 is used.
##
## Examples:
#
# clear lombok.log.log4j2.flagUsage
# lombok.log.log4j2.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.slf4j.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Slf4j is used.
##
## Examples:
#
# clear lombok.log.slf4j.flagUsage
# lombok.log.slf4j.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.xslf4j.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @XSlf4j is used.
##
## Examples:
#
# clear lombok.log.xslf4j.flagUsage
# lombok.log.xslf4j.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.noArgsConstructor.extraPrivate
## Type: boolean
##
## Generate a private no-args constructor for @Data and @Value (default: false).
##
## Examples:
#
# clear lombok.noArgsConstructor.extraPrivate
# lombok.noArgsConstructor.extraPrivate = [false | true]
#

##
## Key : lombok.noArgsConstructor.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @NoArgsConstructor is used.
##
## Examples:
#
# clear lombok.noArgsConstructor.flagUsage
# lombok.noArgsConstructor.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.nonNull.exceptionType
## Type: enum (lombok.core.configuration.NullCheckExceptionType)
##
## The type of the exception to throw if a passed-in argument is null (Default: NullPointerException).
##
## Examples:
#
# clear lombok.nonNull.exceptionType
# lombok.nonNull.exceptionType = [NullPointerException | IllegalArgumentException | Assertion | JDK | Guava]
#

##
## Key : lombok.nonNull.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @NonNull is used.
##
## Examples:
#
# clear lombok.nonNull.flagUsage
# lombok.nonNull.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.onX.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if onX is used.
##
## Examples:
#
# clear lombok.onX.flagUsage
# lombok.onX.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.requiredArgsConstructor.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @RequiredArgsConstructor is used.
##
## Examples:
#
# clear lombok.requiredArgsConstructor.flagUsage
# lombok.requiredArgsConstructor.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.setter.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Setter is used.
##
## Examples:
#
# clear lombok.setter.flagUsage
# lombok.setter.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.singular.auto
## Type: boolean
##
## If true (default): Automatically singularize the assumed-to-be-plural name of your variable/parameter when using @Singular.
##
## Examples:
#
# clear lombok.singular.auto
# lombok.singular.auto = [false | true]
#

##
## Key : lombok.singular.useGuava
## Type: boolean
##
## Generate backing immutable implementations for @Singular on java.util.* types by using guava's ImmutableList, etc. Normally java.util's mutable types are used and wrapped to make them immutable.
##
## Examples:
#
# clear lombok.singular.useGuava
# lombok.singular.useGuava = [false | true]
#

##
## Key : lombok.sneakyThrows.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @SneakyThrows is used.
##
## Examples:
#
# clear lombok.sneakyThrows.flagUsage
# lombok.sneakyThrows.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.standardException.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @StandardException is used.
##
## Examples:
#
# clear lombok.standardException.flagUsage
# lombok.standardException.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.superBuilder.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @SuperBuilder is used.
##
## Examples:
#
# clear lombok.superBuilder.flagUsage
# lombok.superBuilder.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.synchronized.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Synchronized is used.
##
## Examples:
#
# clear lombok.synchronized.flagUsage
# lombok.synchronized.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.toString.callSuper
## Type: enum (lombok.core.configuration.CallSuperType)
##
## When generating toString for classes that extend something (other than Object), either automatically take into account superclass implementation (call), or don't (skip), or warn and don't (warn). (default = skip).
##
## Examples:
#
# clear lombok.toString.callSuper
# lombok.toString.callSuper = [CALL | SKIP | WARN]
#

##
## Key : lombok.toString.doNotUseGetters
## Type: boolean
##
## Don't call the getters but use the fields directly in the generated toString method (default = false).
##
## Examples:
#
# clear lombok.toString.doNotUseGetters
# lombok.toString.doNotUseGetters = [false | true]
#

##
## Key : lombok.toString.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @ToString is used.
##
## Examples:
#
# clear lombok.toString.flagUsage
# lombok.toString.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.toString.includeFieldNames
## Type: boolean
##
## Include the field names in the generated toString method (default = true).
##
## Examples:
#
# clear lombok.toString.includeFieldNames
# lombok.toString.includeFieldNames = [false | true]
#

##
## Key : lombok.toString.onlyExplicitlyIncluded
## Type: boolean
##
## Include only fields/methods explicitly marked with @ToString.Include. Otherwise, include all non-static, non-dollar-named fields (default = false).
##
## Examples:
#
# clear lombok.toString.onlyExplicitlyIncluded
# lombok.toString.onlyExplicitlyIncluded = [false | true]
#

##
## Key : lombok.utilityClass.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @UtilityClass is used.
##
## Examples:
#
# clear lombok.utilityClass.flagUsage
# lombok.utilityClass.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.val.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if 'val' is used.
##
## Examples:
#
# clear lombok.val.flagUsage
# lombok.val.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.value.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Value is used.
##
## Examples:
#
# clear lombok.value.flagUsage
# lombok.value.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.var.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if 'var' is used.
##
## Examples:
#
# clear lombok.var.flagUsage
# lombok.var.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.with.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @With is used.
##
## Examples:
#
# clear lombok.with.flagUsage
# lombok.with.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.withBy.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @WithBy is used.
##
## Examples:
#
# clear lombok.withBy.flagUsage
# lombok.withBy.flagUsage = [WARNING | ERROR | ALLOW]
#&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;v1.18.22&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1678011084760&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;##
## Key : checkerframework
## Type: checkerframework-version
##
## If set with the version of checkerframework.org (in major.minor, or just 'true' for the latest supported version), create relevant checkerframework.org annotations for code lombok generates (default: false).
##
## Examples:
#
# clear checkerframework
# checkerframework = major.minor (example: 3.2 - and no higher than 4.0) or true or false
#

##
## Key : config.stopBubbling
## Type: boolean
##
## Tell the configuration system it should stop looking for other configuration files (default: false).
##
## Examples:
#
# clear config.stopBubbling
# config.stopBubbling = [false | true]
#

##
## Key : lombok.accessors.chain
## Type: boolean
##
## Generate setters that return 'this' instead of 'void' (default: false).
##
## Examples:
#
# clear lombok.accessors.chain
# lombok.accessors.chain = [false | true]
#

##
## Key : lombok.accessors.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Accessors is used.
##
## Examples:
#
# clear lombok.accessors.flagUsage
# lombok.accessors.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.accessors.fluent
## Type: boolean
##
## Generate getters and setters using only the field name (no get/set prefix) (default: false).
##
## Examples:
#
# clear lombok.accessors.fluent
# lombok.accessors.fluent = [false | true]
#

##
## Key : lombok.accessors.prefix
## Type: list of string
##
## Strip this field prefix, like 'f' or 'm_', from the names of generated getters and setters.
##
## Examples:
#
# clear lombok.accessors.prefix
# lombok.accessors.prefix += &amp;lt;text&amp;gt;
# lombok.accessors.prefix -= &amp;lt;text&amp;gt;
#

##
## Key : lombok.addGeneratedAnnotation
## Type: boolean
##
## Generate @javax.annotation.Generated on all generated code (default: false). Deprecated, use 'lombok.addJavaxGeneratedAnnotation' instead.
##
## Examples:
#
# clear lombok.addGeneratedAnnotation
# lombok.addGeneratedAnnotation = [false | true]
#

##
## Key : lombok.addJavaxGeneratedAnnotation
## Type: boolean
##
## Generate @javax.annotation.Generated on all generated code (default: follow lombok.addGeneratedAnnotation).
##
## Examples:
#
# clear lombok.addJavaxGeneratedAnnotation
# lombok.addJavaxGeneratedAnnotation = [false | true]
#

##
## Key : lombok.addLombokGeneratedAnnotation
## Type: boolean
##
## Generate @lombok.Generated on all generated code (default: false).
##
## Examples:
#
# clear lombok.addLombokGeneratedAnnotation
# lombok.addLombokGeneratedAnnotation = [false | true]
#

##
## Key : lombok.addNullAnnotations
## Type: nullity-annotation-library
##
## Generate some style of null annotation for generated code where this is relevant. (default: none).
##
## Examples:
#
# clear lombok.addNullAnnotations
# lombok.addNullAnnotations = none | javax | eclipse | jetbrains | netbeans | androidx | android.support | checkerframework | findbugs | spring | jml | CUSTOM:com.foo.my.nonnull.annotation:com.foo.my.nullable.annotation
#

##
## Key : lombok.addSuppressWarnings
## Type: boolean
##
## Generate @java.lang.SuppressWarnings(&quot;all&quot;) on all generated code (default: true).
##
## Examples:
#
# clear lombok.addSuppressWarnings
# lombok.addSuppressWarnings = [false | true]
#

##
## Key : lombok.allArgsConstructor.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @AllArgsConstructor is used.
##
## Examples:
#
# clear lombok.allArgsConstructor.flagUsage
# lombok.allArgsConstructor.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.anyConstructor.addConstructorProperties
## Type: boolean
##
## Generate @ConstructorProperties for generated constructors (default: false).
##
## Examples:
#
# clear lombok.anyConstructor.addConstructorProperties
# lombok.anyConstructor.addConstructorProperties = [false | true]
#

##
## Key : lombok.anyConstructor.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if any of the XxxArgsConstructor annotations are used.
##
## Examples:
#
# clear lombok.anyConstructor.flagUsage
# lombok.anyConstructor.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.anyConstructor.suppressConstructorProperties
## Type: boolean
##
## Suppress the generation of @ConstructorProperties for generated constructors (default: false).
##
## Examples:
#
# clear lombok.anyConstructor.suppressConstructorProperties
# lombok.anyConstructor.suppressConstructorProperties = [false | true]
#

##
## Key : lombok.builder.className
## Type: string
##
## Default name of the generated builder class. A * is replaced with the name of the relevant type (default = *Builder).
##
## Examples:
#
# clear lombok.builder.className
# lombok.builder.className = &amp;lt;text&amp;gt;
#

##
## Key : lombok.builder.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Builder is used.
##
## Examples:
#
# clear lombok.builder.flagUsage
# lombok.builder.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.cleanup.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Cleanup is used.
##
## Examples:
#
# clear lombok.cleanup.flagUsage
# lombok.cleanup.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.copyableAnnotations
## Type: list of type-name
##
## Copy these annotations to getters, setters, with methods, builder-setters, etc.
##
## Examples:
#
# clear lombok.copyableAnnotations
# lombok.copyableAnnotations += &amp;lt;fully.qualified.Type&amp;gt;
# lombok.copyableAnnotations -= &amp;lt;fully.qualified.Type&amp;gt;
#

##
## Key : lombok.data.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Data is used.
##
## Examples:
#
# clear lombok.data.flagUsage
# lombok.data.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.delegate.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Delegate is used.
##
## Examples:
#
# clear lombok.delegate.flagUsage
# lombok.delegate.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.equalsAndHashCode.callSuper
## Type: enum (lombok.core.configuration.CallSuperType)
##
## When generating equals and hashCode for classes that extend something (other than Object), either automatically take into account superclass implementation (call), or don't (skip), or warn and don't (warn). (default = warn).
##
## Examples:
#
# clear lombok.equalsAndHashCode.callSuper
# lombok.equalsAndHashCode.callSuper = [CALL | SKIP | WARN]
#

##
## Key : lombok.equalsAndHashCode.doNotUseGetters
## Type: boolean
##
## Don't call the getters but use the fields directly in the generated equals and hashCode method (default = false).
##
## Examples:
#
# clear lombok.equalsAndHashCode.doNotUseGetters
# lombok.equalsAndHashCode.doNotUseGetters = [false | true]
#

##
## Key : lombok.equalsAndHashCode.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @EqualsAndHashCode is used.
##
## Examples:
#
# clear lombok.equalsAndHashCode.flagUsage
# lombok.equalsAndHashCode.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.experimental.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if an experimental feature is used.
##
## Examples:
#
# clear lombok.experimental.flagUsage
# lombok.experimental.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.extensionMethod.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @ExtensionMethod is used.
##
## Examples:
#
# clear lombok.extensionMethod.flagUsage
# lombok.extensionMethod.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.extern.findbugs.addSuppressFBWarnings
## Type: boolean
##
## Generate @edu.umd.cs.findbugs.annotations.SuppressFBWarnings on all generated code (default: false).
##
## Examples:
#
# clear lombok.extern.findbugs.addSuppressFBWarnings
# lombok.extern.findbugs.addSuppressFBWarnings = [false | true]
#

##
## Key : lombok.fieldDefaults.defaultFinal
## Type: boolean
##
## If true, fields, in any file (lombok annotated or not) are marked as final. Use @NonFinal to override this.
##
## Examples:
#
# clear lombok.fieldDefaults.defaultFinal
# lombok.fieldDefaults.defaultFinal = [false | true]
#

##
## Key : lombok.fieldDefaults.defaultPrivate
## Type: boolean
##
## If true, fields without any access modifier, in any file (lombok annotated or not) are marked as private. Use @PackagePrivate or an explicit modifier to override this.
##
## Examples:
#
# clear lombok.fieldDefaults.defaultPrivate
# lombok.fieldDefaults.defaultPrivate = [false | true]
#

##
## Key : lombok.fieldDefaults.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @FieldDefaults is used.
##
## Examples:
#
# clear lombok.fieldDefaults.flagUsage
# lombok.fieldDefaults.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.fieldNameConstants.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @FieldNameConstants is used.
##
## Examples:
#
# clear lombok.fieldNameConstants.flagUsage
# lombok.fieldNameConstants.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.fieldNameConstants.innerTypeName
## Type: identifier-name
##
## The default name of the inner type generated by @FieldNameConstants. (default: 'Fields').
##
## Examples:
#
# clear lombok.fieldNameConstants.innerTypeName
# lombok.fieldNameConstants.innerTypeName = &amp;lt;javaIdentifier&amp;gt;
#

##
## Key : lombok.fieldNameConstants.uppercase
## Type: boolean
##
## The default name of the constants inside the inner type generated by @FieldNameConstants follow the variable name precisely. If this config key is true, lombok will uppercase them as best it can. (default: false).
##
## Examples:
#
# clear lombok.fieldNameConstants.uppercase
# lombok.fieldNameConstants.uppercase = [false | true]
#

##
## Key : lombok.getter.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Getter is used.
##
## Examples:
#
# clear lombok.getter.flagUsage
# lombok.getter.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.getter.lazy.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Getter(lazy=true) is used.
##
## Examples:
#
# clear lombok.getter.lazy.flagUsage
# lombok.getter.lazy.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.getter.noIsPrefix
## Type: boolean
##
## If true, generate and use getFieldName() for boolean getters instead of isFieldName().
##
## Examples:
#
# clear lombok.getter.noIsPrefix
# lombok.getter.noIsPrefix = [false | true]
#

##
## Key : lombok.helper.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Helper is used.
##
## Examples:
#
# clear lombok.helper.flagUsage
# lombok.helper.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.jacksonized.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Jacksonized is used.
##
## Examples:
#
# clear lombok.jacksonized.flagUsage
# lombok.jacksonized.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.apacheCommons.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @CommonsLog is used.
##
## Examples:
#
# clear lombok.log.apacheCommons.flagUsage
# lombok.log.apacheCommons.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.custom.declaration
## Type: custom-log-declaration
##
## Define the generated custom logger field.
##
## Examples:
#
# clear lombok.log.custom.declaration
# lombok.log.custom.declaration = my.cool.Logger my.cool.LoggerFactory.createLogger()(TOPIC,TYPE)
#

##
## Key : lombok.log.custom.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @CustomLog is used.
##
## Examples:
#
# clear lombok.log.custom.flagUsage
# lombok.log.custom.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.fieldIsStatic
## Type: boolean
##
## Make the generated logger fields static (default: true).
##
## Examples:
#
# clear lombok.log.fieldIsStatic
# lombok.log.fieldIsStatic = [false | true]
#

##
## Key : lombok.log.fieldName
## Type: identifier-name
##
## Use this name for the generated logger fields (default: 'log').
##
## Examples:
#
# clear lombok.log.fieldName
# lombok.log.fieldName = &amp;lt;javaIdentifier&amp;gt;
#

##
## Key : lombok.log.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if any of the log annotations is used.
##
## Examples:
#
# clear lombok.log.flagUsage
# lombok.log.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.flogger.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Flogger is used.
##
## Examples:
#
# clear lombok.log.flogger.flagUsage
# lombok.log.flogger.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.javaUtilLogging.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Log is used.
##
## Examples:
#
# clear lombok.log.javaUtilLogging.flagUsage
# lombok.log.javaUtilLogging.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.jbosslog.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @JBossLog is used.
##
## Examples:
#
# clear lombok.log.jbosslog.flagUsage
# lombok.log.jbosslog.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.log4j.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Log4j is used.
##
## Examples:
#
# clear lombok.log.log4j.flagUsage
# lombok.log.log4j.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.log4j2.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Log4j2 is used.
##
## Examples:
#
# clear lombok.log.log4j2.flagUsage
# lombok.log.log4j2.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.slf4j.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Slf4j is used.
##
## Examples:
#
# clear lombok.log.slf4j.flagUsage
# lombok.log.slf4j.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.xslf4j.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @XSlf4j is used.
##
## Examples:
#
# clear lombok.log.xslf4j.flagUsage
# lombok.log.xslf4j.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.noArgsConstructor.extraPrivate
## Type: boolean
##
## Generate a private no-args constructor for @Data and @Value (default: false).
##
## Examples:
#
# clear lombok.noArgsConstructor.extraPrivate
# lombok.noArgsConstructor.extraPrivate = [false | true]
#

##
## Key : lombok.noArgsConstructor.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @NoArgsConstructor is used.
##
## Examples:
#
# clear lombok.noArgsConstructor.flagUsage
# lombok.noArgsConstructor.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.nonNull.exceptionType
## Type: enum (lombok.core.configuration.NullCheckExceptionType)
##
## The type of the exception to throw if a passed-in argument is null (Default: NullPointerException).
##
## Examples:
#
# clear lombok.nonNull.exceptionType
# lombok.nonNull.exceptionType = [NullPointerException | IllegalArgumentException | Assertion | JDK | Guava]
#

##
## Key : lombok.nonNull.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @NonNull is used.
##
## Examples:
#
# clear lombok.nonNull.flagUsage
# lombok.nonNull.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.onX.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if onX is used.
##
## Examples:
#
# clear lombok.onX.flagUsage
# lombok.onX.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.requiredArgsConstructor.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @RequiredArgsConstructor is used.
##
## Examples:
#
# clear lombok.requiredArgsConstructor.flagUsage
# lombok.requiredArgsConstructor.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.setter.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Setter is used.
##
## Examples:
#
# clear lombok.setter.flagUsage
# lombok.setter.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.singular.auto
## Type: boolean
##
## If true (default): Automatically singularize the assumed-to-be-plural name of your variable/parameter when using @Singular.
##
## Examples:
#
# clear lombok.singular.auto
# lombok.singular.auto = [false | true]
#

##
## Key : lombok.singular.useGuava
## Type: boolean
##
## Generate backing immutable implementations for @Singular on java.util.* types by using guava's ImmutableList, etc. Normally java.util's mutable types are used and wrapped to make them immutable.
##
## Examples:
#
# clear lombok.singular.useGuava
# lombok.singular.useGuava = [false | true]
#

##
## Key : lombok.sneakyThrows.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @SneakyThrows is used.
##
## Examples:
#
# clear lombok.sneakyThrows.flagUsage
# lombok.sneakyThrows.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.standardException.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @StandardException is used.
##
## Examples:
#
# clear lombok.standardException.flagUsage
# lombok.standardException.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.superBuilder.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @SuperBuilder is used.
##
## Examples:
#
# clear lombok.superBuilder.flagUsage
# lombok.superBuilder.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.synchronized.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Synchronized is used.
##
## Examples:
#
# clear lombok.synchronized.flagUsage
# lombok.synchronized.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.toString.callSuper
## Type: enum (lombok.core.configuration.CallSuperType)
##
## When generating toString for classes that extend something (other than Object), either automatically take into account superclass implementation (call), or don't (skip), or warn and don't (warn). (default = skip).
##
## Examples:
#
# clear lombok.toString.callSuper
# lombok.toString.callSuper = [CALL | SKIP | WARN]
#

##
## Key : lombok.toString.doNotUseGetters
## Type: boolean
##
## Don't call the getters but use the fields directly in the generated toString method (default = false).
##
## Examples:
#
# clear lombok.toString.doNotUseGetters
# lombok.toString.doNotUseGetters = [false | true]
#

##
## Key : lombok.toString.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @ToString is used.
##
## Examples:
#
# clear lombok.toString.flagUsage
# lombok.toString.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.toString.includeFieldNames
## Type: boolean
##
## Include the field names in the generated toString method (default = true).
##
## Examples:
#
# clear lombok.toString.includeFieldNames
# lombok.toString.includeFieldNames = [false | true]
#

##
## Key : lombok.utilityClass.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @UtilityClass is used.
##
## Examples:
#
# clear lombok.utilityClass.flagUsage
# lombok.utilityClass.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.val.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if 'val' is used.
##
## Examples:
#
# clear lombok.val.flagUsage
# lombok.val.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.value.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Value is used.
##
## Examples:
#
# clear lombok.value.flagUsage
# lombok.value.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.var.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if 'var' is used.
##
## Examples:
#
# clear lombok.var.flagUsage
# lombok.var.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.with.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @With is used.
##
## Examples:
#
# clear lombok.with.flagUsage
# lombok.with.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.withBy.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @WithBy is used.
##
## Examples:
#
# clear lombok.withBy.flagUsage
# lombok.withBy.flagUsage = [WARNING | ERROR | ALLOW]
#&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;v1.18.20&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1678011119056&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;##
## Key : checkerframework
## Type: checkerframework-version
##
## If set with the version of checkerframework.org (in major.minor, or just 'true' for the latest supported version), create relevant checkerframework.org annotations for code lombok generates (default: false).
##
## Examples:
#
# clear checkerframework
# checkerframework = major.minor (example: 3.2 - and no higher than 4.0) or true or false
#

##
## Key : config.stopBubbling
## Type: boolean
##
## Tell the configuration system it should stop looking for other configuration files (default: false).
##
## Examples:
#
# clear config.stopBubbling
# config.stopBubbling = [false | true]
#

##
## Key : lombok.accessors.chain
## Type: boolean
##
## Generate setters that return 'this' instead of 'void' (default: false).
##
## Examples:
#
# clear lombok.accessors.chain
# lombok.accessors.chain = [false | true]
#

##
## Key : lombok.accessors.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Accessors is used.
##
## Examples:
#
# clear lombok.accessors.flagUsage
# lombok.accessors.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.accessors.fluent
## Type: boolean
##
## Generate getters and setters using only the field name (no get/set prefix) (default: false).
##
## Examples:
#
# clear lombok.accessors.fluent
# lombok.accessors.fluent = [false | true]
#

##
## Key : lombok.accessors.prefix
## Type: list of string
##
## Strip this field prefix, like 'f' or 'm_', from the names of generated getters and setters.
##
## Examples:
#
# clear lombok.accessors.prefix
# lombok.accessors.prefix += &amp;lt;text&amp;gt;
# lombok.accessors.prefix -= &amp;lt;text&amp;gt;
#

##
## Key : lombok.addGeneratedAnnotation
## Type: boolean
##
## Generate @javax.annotation.Generated on all generated code (default: false). Deprecated, use 'lombok.addJavaxGeneratedAnnotation' instead.
##
## Examples:
#
# clear lombok.addGeneratedAnnotation
# lombok.addGeneratedAnnotation = [false | true]
#

##
## Key : lombok.addJavaxGeneratedAnnotation
## Type: boolean
##
## Generate @javax.annotation.Generated on all generated code (default: follow lombok.addGeneratedAnnotation).
##
## Examples:
#
# clear lombok.addJavaxGeneratedAnnotation
# lombok.addJavaxGeneratedAnnotation = [false | true]
#

##
## Key : lombok.addLombokGeneratedAnnotation
## Type: boolean
##
## Generate @lombok.Generated on all generated code (default: false).
##
## Examples:
#
# clear lombok.addLombokGeneratedAnnotation
# lombok.addLombokGeneratedAnnotation = [false | true]
#

##
## Key : lombok.addNullAnnotations
## Type: nullity-annotation-library
##
## Generate some style of null annotation for generated code where this is relevant. (default: none).
##
## Examples:
#
# clear lombok.addNullAnnotations
# lombok.addNullAnnotations = none | javax | eclipse | jetbrains | netbeans | androidx | android.support | checkerframework | findbugs | spring | jml | CUSTOM:com.foo.my.nonnull.annotation:com.foo.my.nullable.annotation
#

##
## Key : lombok.addSuppressWarnings
## Type: boolean
##
## Generate @java.lang.SuppressWarnings(&quot;all&quot;) on all generated code (default: true).
##
## Examples:
#
# clear lombok.addSuppressWarnings
# lombok.addSuppressWarnings = [false | true]
#

##
## Key : lombok.allArgsConstructor.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @AllArgsConstructor is used.
##
## Examples:
#
# clear lombok.allArgsConstructor.flagUsage
# lombok.allArgsConstructor.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.anyConstructor.addConstructorProperties
## Type: boolean
##
## Generate @ConstructorProperties for generated constructors (default: false).
##
## Examples:
#
# clear lombok.anyConstructor.addConstructorProperties
# lombok.anyConstructor.addConstructorProperties = [false | true]
#

##
## Key : lombok.anyConstructor.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if any of the XxxArgsConstructor annotations are used.
##
## Examples:
#
# clear lombok.anyConstructor.flagUsage
# lombok.anyConstructor.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.anyConstructor.suppressConstructorProperties
## Type: boolean
##
## Suppress the generation of @ConstructorProperties for generated constructors (default: false).
##
## Examples:
#
# clear lombok.anyConstructor.suppressConstructorProperties
# lombok.anyConstructor.suppressConstructorProperties = [false | true]
#

##
## Key : lombok.builder.className
## Type: string
##
## Default name of the generated builder class. A * is replaced with the name of the relevant type (default = *Builder).
##
## Examples:
#
# clear lombok.builder.className
# lombok.builder.className = &amp;lt;text&amp;gt;
#

##
## Key : lombok.builder.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Builder is used.
##
## Examples:
#
# clear lombok.builder.flagUsage
# lombok.builder.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.cleanup.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Cleanup is used.
##
## Examples:
#
# clear lombok.cleanup.flagUsage
# lombok.cleanup.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.copyableAnnotations
## Type: list of type-name
##
## Copy these annotations to getters, setters, with methods, builder-setters, etc.
##
## Examples:
#
# clear lombok.copyableAnnotations
# lombok.copyableAnnotations += &amp;lt;fully.qualified.Type&amp;gt;
# lombok.copyableAnnotations -= &amp;lt;fully.qualified.Type&amp;gt;
#

##
## Key : lombok.data.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Data is used.
##
## Examples:
#
# clear lombok.data.flagUsage
# lombok.data.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.delegate.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Delegate is used.
##
## Examples:
#
# clear lombok.delegate.flagUsage
# lombok.delegate.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.equalsAndHashCode.callSuper
## Type: enum (lombok.core.configuration.CallSuperType)
##
## When generating equals and hashCode for classes that extend something (other than Object), either automatically take into account superclass implementation (call), or don't (skip), or warn and don't (warn). (default = warn).
##
## Examples:
#
# clear lombok.equalsAndHashCode.callSuper
# lombok.equalsAndHashCode.callSuper = [CALL | SKIP | WARN]
#

##
## Key : lombok.equalsAndHashCode.doNotUseGetters
## Type: boolean
##
## Don't call the getters but use the fields directly in the generated equals and hashCode method (default = false).
##
## Examples:
#
# clear lombok.equalsAndHashCode.doNotUseGetters
# lombok.equalsAndHashCode.doNotUseGetters = [false | true]
#

##
## Key : lombok.equalsAndHashCode.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @EqualsAndHashCode is used.
##
## Examples:
#
# clear lombok.equalsAndHashCode.flagUsage
# lombok.equalsAndHashCode.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.experimental.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if an experimental feature is used.
##
## Examples:
#
# clear lombok.experimental.flagUsage
# lombok.experimental.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.extensionMethod.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @ExtensionMethod is used.
##
## Examples:
#
# clear lombok.extensionMethod.flagUsage
# lombok.extensionMethod.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.extern.findbugs.addSuppressFBWarnings
## Type: boolean
##
## Generate @edu.umd.cs.findbugs.annotations.SuppressFBWarnings on all generated code (default: false).
##
## Examples:
#
# clear lombok.extern.findbugs.addSuppressFBWarnings
# lombok.extern.findbugs.addSuppressFBWarnings = [false | true]
#

##
## Key : lombok.fieldDefaults.defaultFinal
## Type: boolean
##
## If true, fields, in any file (lombok annotated or not) are marked as final. Use @NonFinal to override this.
##
## Examples:
#
# clear lombok.fieldDefaults.defaultFinal
# lombok.fieldDefaults.defaultFinal = [false | true]
#

##
## Key : lombok.fieldDefaults.defaultPrivate
## Type: boolean
##
## If true, fields without any access modifier, in any file (lombok annotated or not) are marked as private. Use @PackagePrivate or an explicit modifier to override this.
##
## Examples:
#
# clear lombok.fieldDefaults.defaultPrivate
# lombok.fieldDefaults.defaultPrivate = [false | true]
#

##
## Key : lombok.fieldDefaults.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @FieldDefaults is used.
##
## Examples:
#
# clear lombok.fieldDefaults.flagUsage
# lombok.fieldDefaults.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.fieldNameConstants.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @FieldNameConstants is used.
##
## Examples:
#
# clear lombok.fieldNameConstants.flagUsage
# lombok.fieldNameConstants.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.fieldNameConstants.innerTypeName
## Type: identifier-name
##
## The default name of the inner type generated by @FieldNameConstants. (default: 'Fields').
##
## Examples:
#
# clear lombok.fieldNameConstants.innerTypeName
# lombok.fieldNameConstants.innerTypeName = &amp;lt;javaIdentifier&amp;gt;
#

##
## Key : lombok.fieldNameConstants.uppercase
## Type: boolean
##
## The default name of the constants inside the inner type generated by @FieldNameConstants follow the variable name precisely. If this config key is true, lombok will uppercase them as best it can. (default: false).
##
## Examples:
#
# clear lombok.fieldNameConstants.uppercase
# lombok.fieldNameConstants.uppercase = [false | true]
#

##
## Key : lombok.getter.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Getter is used.
##
## Examples:
#
# clear lombok.getter.flagUsage
# lombok.getter.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.getter.lazy.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Getter(lazy=true) is used.
##
## Examples:
#
# clear lombok.getter.lazy.flagUsage
# lombok.getter.lazy.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.getter.noIsPrefix
## Type: boolean
##
## If true, generate and use getFieldName() for boolean getters instead of isFieldName().
##
## Examples:
#
# clear lombok.getter.noIsPrefix
# lombok.getter.noIsPrefix = [false | true]
#

##
## Key : lombok.helper.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Helper is used.
##
## Examples:
#
# clear lombok.helper.flagUsage
# lombok.helper.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.jacksonized.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Jacksonized is used.
##
## Examples:
#
# clear lombok.jacksonized.flagUsage
# lombok.jacksonized.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.apacheCommons.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @CommonsLog is used.
##
## Examples:
#
# clear lombok.log.apacheCommons.flagUsage
# lombok.log.apacheCommons.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.custom.declaration
## Type: custom-log-declaration
##
## Define the generated custom logger field.
##
## Examples:
#
# clear lombok.log.custom.declaration
# lombok.log.custom.declaration = my.cool.Logger my.cool.LoggerFactory.createLogger()(TOPIC,TYPE)
#

##
## Key : lombok.log.custom.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @CustomLog is used.
##
## Examples:
#
# clear lombok.log.custom.flagUsage
# lombok.log.custom.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.fieldIsStatic
## Type: boolean
##
## Make the generated logger fields static (default: true).
##
## Examples:
#
# clear lombok.log.fieldIsStatic
# lombok.log.fieldIsStatic = [false | true]
#

##
## Key : lombok.log.fieldName
## Type: identifier-name
##
## Use this name for the generated logger fields (default: 'log').
##
## Examples:
#
# clear lombok.log.fieldName
# lombok.log.fieldName = &amp;lt;javaIdentifier&amp;gt;
#

##
## Key : lombok.log.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if any of the log annotations is used.
##
## Examples:
#
# clear lombok.log.flagUsage
# lombok.log.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.flogger.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Flogger is used.
##
## Examples:
#
# clear lombok.log.flogger.flagUsage
# lombok.log.flogger.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.javaUtilLogging.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Log is used.
##
## Examples:
#
# clear lombok.log.javaUtilLogging.flagUsage
# lombok.log.javaUtilLogging.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.jbosslog.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @JBossLog is used.
##
## Examples:
#
# clear lombok.log.jbosslog.flagUsage
# lombok.log.jbosslog.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.log4j.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Log4j is used.
##
## Examples:
#
# clear lombok.log.log4j.flagUsage
# lombok.log.log4j.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.log4j2.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Log4j2 is used.
##
## Examples:
#
# clear lombok.log.log4j2.flagUsage
# lombok.log.log4j2.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.slf4j.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Slf4j is used.
##
## Examples:
#
# clear lombok.log.slf4j.flagUsage
# lombok.log.slf4j.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.log.xslf4j.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @XSlf4j is used.
##
## Examples:
#
# clear lombok.log.xslf4j.flagUsage
# lombok.log.xslf4j.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.noArgsConstructor.extraPrivate
## Type: boolean
##
## Generate a private no-args constructor for @Data and @Value (default: false).
##
## Examples:
#
# clear lombok.noArgsConstructor.extraPrivate
# lombok.noArgsConstructor.extraPrivate = [false | true]
#

##
## Key : lombok.noArgsConstructor.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @NoArgsConstructor is used.
##
## Examples:
#
# clear lombok.noArgsConstructor.flagUsage
# lombok.noArgsConstructor.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.nonNull.exceptionType
## Type: enum (lombok.core.configuration.NullCheckExceptionType)
##
## The type of the exception to throw if a passed-in argument is null (Default: NullPointerException).
##
## Examples:
#
# clear lombok.nonNull.exceptionType
# lombok.nonNull.exceptionType = [NullPointerException | IllegalArgumentException | Assertion | JDK | Guava]
#

##
## Key : lombok.nonNull.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @NonNull is used.
##
## Examples:
#
# clear lombok.nonNull.flagUsage
# lombok.nonNull.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.onX.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if onX is used.
##
## Examples:
#
# clear lombok.onX.flagUsage
# lombok.onX.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.requiredArgsConstructor.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @RequiredArgsConstructor is used.
##
## Examples:
#
# clear lombok.requiredArgsConstructor.flagUsage
# lombok.requiredArgsConstructor.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.setter.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Setter is used.
##
## Examples:
#
# clear lombok.setter.flagUsage
# lombok.setter.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.singular.auto
## Type: boolean
##
## If true (default): Automatically singularize the assumed-to-be-plural name of your variable/parameter when using @Singular.
##
## Examples:
#
# clear lombok.singular.auto
# lombok.singular.auto = [false | true]
#

##
## Key : lombok.singular.useGuava
## Type: boolean
##
## Generate backing immutable implementations for @Singular on java.util.* types by using guava's ImmutableList, etc. Normally java.util's mutable types are used and wrapped to make them immutable.
##
## Examples:
#
# clear lombok.singular.useGuava
# lombok.singular.useGuava = [false | true]
#

##
## Key : lombok.sneakyThrows.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @SneakyThrows is used.
##
## Examples:
#
# clear lombok.sneakyThrows.flagUsage
# lombok.sneakyThrows.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.superBuilder.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @SuperBuilder is used.
##
## Examples:
#
# clear lombok.superBuilder.flagUsage
# lombok.superBuilder.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.synchronized.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Synchronized is used.
##
## Examples:
#
# clear lombok.synchronized.flagUsage
# lombok.synchronized.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.toString.callSuper
## Type: enum (lombok.core.configuration.CallSuperType)
##
## When generating toString for classes that extend something (other than Object), either automatically take into account superclass implementation (call), or don't (skip), or warn and don't (warn). (default = skip).
##
## Examples:
#
# clear lombok.toString.callSuper
# lombok.toString.callSuper = [CALL | SKIP | WARN]
#

##
## Key : lombok.toString.doNotUseGetters
## Type: boolean
##
## Don't call the getters but use the fields directly in the generated toString method (default = false).
##
## Examples:
#
# clear lombok.toString.doNotUseGetters
# lombok.toString.doNotUseGetters = [false | true]
#

##
## Key : lombok.toString.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @ToString is used.
##
## Examples:
#
# clear lombok.toString.flagUsage
# lombok.toString.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.toString.includeFieldNames
## Type: boolean
##
## Include the field names in the generated toString method (default = true).
##
## Examples:
#
# clear lombok.toString.includeFieldNames
# lombok.toString.includeFieldNames = [false | true]
#

##
## Key : lombok.utilityClass.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @UtilityClass is used.
##
## Examples:
#
# clear lombok.utilityClass.flagUsage
# lombok.utilityClass.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.val.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if 'val' is used.
##
## Examples:
#
# clear lombok.val.flagUsage
# lombok.val.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.value.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @Value is used.
##
## Examples:
#
# clear lombok.value.flagUsage
# lombok.value.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.var.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if 'var' is used.
##
## Examples:
#
# clear lombok.var.flagUsage
# lombok.var.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.with.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @With is used.
##
## Examples:
#
# clear lombok.with.flagUsage
# lombok.with.flagUsage = [WARNING | ERROR | ALLOW]
#

##
## Key : lombok.withBy.flagUsage
## Type: enum (lombok.core.configuration.FlagUsageType)
##
## Emit a warning or error if @WithBy is used.
##
## Examples:
#
# clear lombok.withBy.flagUsage
# lombok.withBy.flagUsage = [WARNING | ERROR | ALLOW]
#&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;figure id=&quot;og_1678011155149&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Configuration system&quot; data-og-description=&quot;&quot; data-og-host=&quot;projectlombok.org&quot; data-og-source-url=&quot;https://projectlombok.org/features/configuration&quot; data-og-url=&quot;https://projectlombok.org/features/configuration&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://projectlombok.org/features/configuration&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://projectlombok.org/features/configuration&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Configuration system&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;projectlombok.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: 422px; top: 381px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Java</category>
      <category>Lombok</category>
      <category>lombok.config</category>
      <author>AlwaysPr</author>
      <guid isPermaLink="true">https://alwayspr.tistory.com/50</guid>
      <comments>https://alwayspr.tistory.com/50#entry50comment</comments>
      <pubDate>Sun, 5 Mar 2023 19:01:39 +0900</pubDate>
    </item>
    <item>
      <title>Spring Batch 5 뭐가 달라졌나?</title>
      <link>https://alwayspr.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Batch 4.0에서 약 5년의 세월이 흘러 Spring Batch 5.0으로 메이저 버전이 업데이트되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 기능들이 개선되고, 생겼는지 알아보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Batch5는 Spring framework 6을 의존하기 때문에 최소 Java 17이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에 3rd Party 의존성은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Framework 6&lt;/li&gt;
&lt;li&gt;Spring Integration 6&lt;/li&gt;
&lt;li&gt;Spring Data 3&lt;/li&gt;
&lt;li&gt;Spring AMQP 3&lt;/li&gt;
&lt;li&gt;Spring for Apache Kafka 3&lt;/li&gt;
&lt;li&gt;Micrometer 1.10&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다양한 JobParemeter Type&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어(?) 다양한 JobParemeter type을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.x 버전까지는 4개의 Type(Long, Double, String, Date)만을 지원했다. 하지만 5.0부터는 JobParameter를 커스텀해서 사용할 수 있다. 아래는 JobParameter의 변경사항인데, Generic이 들어간 걸 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-02-19 오후 9.52.12.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz6stM/btrZLTVkvuv/zjPkXzFAdhg0917pzJ5i71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz6stM/btrZLTVkvuv/zjPkXzFAdhg0917pzJ5i71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz6stM/btrZLTVkvuv/zjPkXzFAdhg0917pzJ5i71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz6stM%2FbtrZLTVkvuv%2FzjPkXzFAdhg0917pzJ5i71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;220&quot; data-filename=&quot;스크린샷 2023-02-19 오후 9.52.12.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.x까지는 아래처럼 jobParameter를 적용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1676810678714&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;parameterName(parameterType)=parameterValue&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5부터는 다음과 같이 적용해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1676810705253&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;parameterName=parameterValue,parameterType,identificationFlag&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실예로 들면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676810737414&quot; class=&quot;angelscript&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;schedule.date=2022-12-12,java.time.LocalDate&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위는 &lt;span&gt;기본적으로 적용된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;DefaultJobParametersConverter의 결과물이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보다 명시적인 방식을 원한다면 JsonJobParametersConverter를 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676810910087&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;parameterName='{&quot;value&quot;: &quot;parameterValue&quot;, &quot;type&quot;:&quot;parameterType&quot;, &quot;identifying&quot;: &quot;booleanValue&quot;}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 BATCH_JOB_EXECUTION_PARAMS DDL이 수정되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-02-19 오후 9.52.03.png&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGyJHG/btrZLSbabol/NUT4pi56zrERIKTHzsyV0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGyJHG/btrZLSbabol/NUT4pi56zrERIKTHzsyV0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGyJHG/btrZLSbabol/NUT4pi56zrERIKTHzsyV0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGyJHG%2FbtrZLSbabol%2FNUT4pi56zrERIKTHzsyV0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;593&quot; height=&quot;319&quot; data-filename=&quot;스크린샷 2023-02-19 오후 9.52.03.png&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;319&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;숨겨진 자동 설정 명시적으로 변경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StepBuilderFactory, JobBuilderFactory가 Deprecated 되었다. 해당 Factory를 사용하여 Step과 Job을 구성하는 건 일반적으로 사용하는 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1676812429066&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableBatchProcessing
public class MyJobConfig {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Bean
    public Job myJob(Step step) {
        return this.jobBuilderFactory.get(&quot;myJob&quot;)
                .start(step)
                .build();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이는 사용자가 문서를 읽어 인지하지 않는 한 Builder에서 JobRepository가 생성되고 설정된다는 사실을 숨기고 있다.&lt;br /&gt;그래서&amp;nbsp;아래와&amp;nbsp;같이&amp;nbsp;JobRepository를&amp;nbsp;명시적으로&amp;nbsp;제공하는&amp;nbsp;방식을&amp;nbsp;사용하길&amp;nbsp;권장한다.&lt;/p&gt;
&lt;pre id=&quot;code_1676812394232&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableBatchProcessing
public class MyJobConfig {

    @Bean
    public Job myJob(JobRepository jobRepository, Step step) {
        return new JobBuilder(&quot;myJob&quot;, jobRepository)
                .start(step)
                .build();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;transactionManager 또한 위와 같은 이유로 명시적으로 사용하길 권장하고 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1676813185080&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Sample with v4
@Configuration
@EnableBatchProcessing
public class MyStepConfig {

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Bean
    public Step myStep() {
        return this.stepBuilderFactory.get(&quot;myStep&quot;)
                .tasklet(..) // or .chunk()
                .build();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676813200003&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Sample with v5
@Configuration
@EnableBatchProcessing
public class MyStepConfig {

    @Bean
    public Tasklet myTasklet() {
       return new MyTasklet();
    }

    @Bean
    public Step myStep(JobRepository jobRepository, Tasklet myTasklet, PlatformTransactionManager transactionManager) {
        return new StepBuilder(&quot;myStep&quot;, jobRepository)
                .tasklet(myTasklet, transactionManager) // or .chunk(chunkSize, transactionManager)
                .build();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Batch 팀에서는 불편함을 감수하더라도 명시적인 코드 작성에 손을 들어줬다. &lt;a href=&quot;https://github.com/spring-projects/spring-batch/issues/4188&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;더보기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DefaultBatchConfiguration 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@EnableBatchProcessing가 JobRepository, JobLauncher, StepScope, JobScope 등의 Bean을 등록하고 마법을 부리는 것을 대신할&amp;nbsp;Configuration class가 추가되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 DefaultBatchConfiguration를&amp;nbsp; 상속하여 사용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1676815352927&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
class MyJobConfiguration extends DefaultBatchConfiguration {

	@Bean
	public Job job(JobRepository jobRepository) {
		return new JobBuilder(&quot;myJob&quot;, jobRepository)
				//define job flow as needed
				.build();
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DefaultBatchConfiguration에서는 getDataSource, getTransactionManager, getCharset 등의 다양한 protected method 들을 제공하고 있으며, 자식 클래스에서 이를 Override 하여 간편하게 Batch 설정을 커스터마이즈 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1676815805694&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
class MyJobConfiguration extends DefaultBatchConfiguration {

	@Bean
	public Job job(JobRepository jobRepository) {
		return new JobBuilder(&quot;job&quot;, jobRepository)
				// define job flow as needed
				.build();
	}

	@Override
	protected Charset getCharset() {
		return StandardCharsets.ISO_8859_1;
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⛔️ 주의할 점은 @EnableBatchProcessing과 DefaultBatchConfiguration를 함께 사용하면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@EnableBatchProcessing&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@EnableBatchProcessing Annotation이 4.x버전까지 modular만 제공하는 것에 다양한 옵션을 제공한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dataSourceRef&lt;/li&gt;
&lt;li&gt;transactionManagerRef&lt;/li&gt;
&lt;li&gt;executionContextSerializerRef&lt;/li&gt;
&lt;li&gt;charset&lt;/li&gt;
&lt;li&gt;tablePrefix&lt;/li&gt;
&lt;li&gt;maxVarCharLength&lt;/li&gt;
&lt;li&gt;incrementerFactoryRef&lt;/li&gt;
&lt;li&gt;lobHandlerRef&lt;/li&gt;
&lt;li&gt;clobType&lt;/li&gt;
&lt;li&gt;isolationLevelForCreate&lt;/li&gt;
&lt;li&gt;taskExecutorRef&lt;/li&gt;
&lt;li&gt;conversionServiceRef&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GraalVM native 지원&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GraalVM을 사용하여 Spring Batch 애플리케이션을 기본적으로 컴파일하는 데 필요한 Ahead-Of-Time process 및 Runtime hint를 제공하여 성능이 크게 개선되었다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;graalvm_native_support.png&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPP7Sq/btrZK0HbNLs/eyVZxMkI2otpXYfel1WEJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPP7Sq/btrZK0HbNLs/eyVZxMkI2otpXYfel1WEJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPP7Sq/btrZK0HbNLs/eyVZxMkI2otpXYfel1WEJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPP7Sq%2FbtrZK0HbNLs%2FeyVZxMkI2otpXYfel1WEJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;931&quot; height=&quot;603&quot; data-filename=&quot;graalvm_native_support.png&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고&lt;/h3&gt;
&lt;figure id=&quot;og_1676809707277&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Batch - Reference Documentation&quot; data-og-description=&quot;If a group of Steps share similar configurations, then it may be helpful to define a &amp;ldquo;parent&amp;rdquo; Step from which the concrete Steps may inherit properties. Similar to class inheritance in Java, the &amp;ldquo;child&amp;rdquo; Step combines its elements and attributes wit&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/index-single.html#whatsNew&quot; data-og-url=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/index-single.html#whatsNew&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hsvjZ/hyRFZu2FEp/M5QtQfghKpDo0OrOB4kcjk/img.png?width=642&amp;amp;height=355&amp;amp;face=0_0_642_355,https://scrap.kakaocdn.net/dn/xXna9/hyRF7s4R1p/KoZ5WswzieD2cVKbK674e1/img.png?width=663&amp;amp;height=343&amp;amp;face=0_0_663_343,https://scrap.kakaocdn.net/dn/bPjSPz/hyRHqYRkhv/NpGpqqG6KM1yexbR5DnNP1/img.png?width=738&amp;amp;height=294&amp;amp;face=0_0_738_294&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/index-single.html#whatsNew&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/index-single.html#whatsNew&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hsvjZ/hyRFZu2FEp/M5QtQfghKpDo0OrOB4kcjk/img.png?width=642&amp;amp;height=355&amp;amp;face=0_0_642_355,https://scrap.kakaocdn.net/dn/xXna9/hyRF7s4R1p/KoZ5WswzieD2cVKbK674e1/img.png?width=663&amp;amp;height=343&amp;amp;face=0_0_663_343,https://scrap.kakaocdn.net/dn/bPjSPz/hyRHqYRkhv/NpGpqqG6KM1yexbR5DnNP1/img.png?width=738&amp;amp;height=294&amp;amp;face=0_0_738_294');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Batch - Reference Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;If a group of Steps share similar configurations, then it may be helpful to define a &amp;ldquo;parent&amp;rdquo; Step from which the concrete Steps may inherit properties. Similar to class inheritance in Java, the &amp;ldquo;child&amp;rdquo; Step combines its elements and attributes wit&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1676809710170&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring | Home&quot; data-og-description=&quot;Cloud Your code, any cloud&amp;mdash;we&amp;rsquo;ve got you covered. Connect and scale your services, whatever your platform.&quot; data-og-host=&quot;spring.io&quot; data-og-source-url=&quot;https://spring.io/blog/2022/11/24/spring-batch-5-0-goes-ga&quot; data-og-url=&quot;https://spring.io&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cibmbz/hyRF3RKrr9/xRB7bBR6LY976bGuy4C7Wk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bRcgJX/hyRGai2v3x/StSn2OamKmF60d4UMo3LiK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://spring.io/blog/2022/11/24/spring-batch-5-0-goes-ga&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://spring.io/blog/2022/11/24/spring-batch-5-0-goes-ga&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cibmbz/hyRF3RKrr9/xRB7bBR6LY976bGuy4C7Wk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bRcgJX/hyRGai2v3x/StSn2OamKmF60d4UMo3LiK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring | Home&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Cloud Your code, any cloud&amp;mdash;we&amp;rsquo;ve got you covered. Connect and scale your services, whatever your platform.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1676811194048&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Spring Batch 5.0 Migration Guide&quot; data-og-description=&quot;Spring Batch is a framework for writing offline and batch applications using Spring and Java - spring-projects/spring-batch&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/spring-projects/spring-batch/wiki/Spring-Batch-5.0-Migration-Guide&quot; data-og-url=&quot;https://github.com/spring-projects/spring-batch/wiki/Spring-Batch-5.0-Migration-Guide&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Wbkkt/hyRHyikdhZ/cOZwUseAnvE3fP53maDA81/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-batch/wiki/Spring-Batch-5.0-Migration-Guide&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/spring-projects/spring-batch/wiki/Spring-Batch-5.0-Migration-Guide&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Wbkkt/hyRHyikdhZ/cOZwUseAnvE3fP53maDA81/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Batch 5.0 Migration Guide&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Batch is a framework for writing offline and batch applications using Spring and Java - spring-projects/spring-batch&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1676813139135&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Deprecate Job/Step builder factories &amp;middot; Issue #4188 &amp;middot; spring-projects/spring-batch&quot; data-og-description=&quot;JobBuilderFactory was reported to be of no real added value compared to JobBuilder. In fact, it only sets a single property on the builder it creates, which is the job repository. While this is not...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/spring-projects/spring-batch/issues/4188&quot; data-og-url=&quot;https://github.com/spring-projects/spring-batch/issues/4188&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/M9DLs/hyRF1NcucQ/zP8tn2H9JjbsAKIttpFHiK/img.png?width=1200&amp;amp;height=600&amp;amp;face=82_127_1072_528&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-batch/issues/4188&quot; data-source-url=&quot;https://github.com/spring-projects/spring-batch/issues/4188&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/M9DLs/hyRF1NcucQ/zP8tn2H9JjbsAKIttpFHiK/img.png?width=1200&amp;amp;height=600&amp;amp;face=82_127_1072_528');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Deprecate Job/Step builder factories &amp;middot; Issue #4188 &amp;middot; spring-projects/spring-batch&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JobBuilderFactory was reported to be of no real added value compared to JobBuilder. In fact, it only sets a single property on the builder it creates, which is the job repository. While this is not...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Spring</category>
      <category>@EnableBatchProcessing</category>
      <category>Batch</category>
      <category>DefaultBatchConfiguration</category>
      <category>java17</category>
      <category>JobBuilder</category>
      <category>Spring Batch</category>
      <category>Spring batch5</category>
      <category>StepBuilder</category>
      <category>스프링배치</category>
      <category>스프링배치5</category>
      <author>AlwaysPr</author>
      <guid isPermaLink="true">https://alwayspr.tistory.com/49</guid>
      <comments>https://alwayspr.tistory.com/49#entry49comment</comments>
      <pubDate>Sun, 19 Feb 2023 23:24:32 +0900</pubDate>
    </item>
    <item>
      <title>10분만에 구현하는 CircuitBreaker</title>
      <link>https://alwayspr.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring AOP를 활용하여 CircuitBreaker를 구현해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Annotation 기반의 AOP를 활용하기 위해 Annotation과 Aspect를 다음과 같이 만들어주자.&lt;/p&gt;
&lt;pre id=&quot;code_1674908883438&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CircuitBreaker {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674909336880&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Aspect
@Component
public class CircuitBreakerAspect {

    @Around(&quot;@annotation(com.ms.circuitbreaker.CircuitBreaker)&quot;)
    public Object round(ProceedingJoinPoint joinPoint) throws Throwable {
    	// TODO 세부구현
        return joinPoint.proceed();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CircuitBreaker를 적용할 코드도 하나 만들자&lt;/p&gt;
&lt;pre id=&quot;code_1674908978818&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class FooService {

    @CircuitBreaker
    public String getFoo() {
        return &quot;foo&quot;;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패 개수와 마지막 실패 시간을 가지고 있는 metadata와 Exception을 만들자&lt;/p&gt;
&lt;pre id=&quot;code_1674966420340&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CircuitBreakerMetadata {

        private final AtomicInteger failures = new AtomicInteger();

        private volatile long lastFailure;

        CircuitBreakerMetadata() {
        }

        private long getLastFailure() {
            return this.lastFailure;
        }

        private void setLastFailure(long lastFailure) {
            this.lastFailure = lastFailure;
        }

        private AtomicInteger getFailures() {
            return this.failures;
        }

    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674966500630&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class CircuitBreakerOpenException extends RuntimeException {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 뼈대는 만들어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 CircuitBreaker 동작을 Aspect에서 구현해 보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항은 간단하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. @CircuitBreaker가 선언된 곳에서 에러가 연속으로 5회를 넘게 되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 1초 동안 CircuitBreaker가 발동한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 에러 없이 성공하면 실패 개수를 초기화한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674967507904&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Aspect
@Component
public class CircuitBreakerAspect {

    private static final int THRESHOLD = 5;
    
    private static final long HALF_OPEN_AFTER = 1000;
    
    private static final ConcurrentMap&amp;lt;Object, CircuitBreakerMetadata&amp;gt; METADATA_MAP = new ConcurrentHashMap&amp;lt;&amp;gt;();

    @Around(&quot;@annotation(com.ms.circuitbreaker.CircuitBreaker)&quot;)
    public Object round(ProceedingJoinPoint joinPoint) throws Throwable {

        final Object target = joinPoint.getTarget();

        CircuitBreakerMetadata metadata = METADATA_MAP.get(target);

        if (metadata == null) {
            METADATA_MAP.putIfAbsent(target, new CircuitBreakerMetadata());
            metadata = METADATA_MAP.get(target);
        }

        if (metadata.getFailures().get() &amp;gt;= THRESHOLD &amp;amp;&amp;amp;
                System.currentTimeMillis() - metadata.getLastFailure() &amp;lt; HALF_OPEN_AFTER) {
            throw new CircuitBreakerOpenException();
        }

        try {
            final Object result = joinPoint.proceed();
            metadata.getFailures().set(0);
            return result;
        } catch (Exception e) {
            metadata.getFailures().incrementAndGet();
            metadata.setLastFailure(System.currentTimeMillis());
            throw e;
        }

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CircuitBrekaer를 구현해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에서 제공하는 @CircuitBreaker를 사용하다가 어떻게 구현되어있는지 보니 생각에 비해 간단/간결하게 작성되어 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 위에서 작성한 코드는 spring-integration의 RequestHandlerCircuitBreakerAdvice 클래스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프레임워크와 라이브러리가 제공해 준다고 해서 그저 가져다 쓰기보다는 어떻게 구현되었는지 궁금해하고 찾다 보면 더 좋은 개발자가 되리라 생각한다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>aop</category>
      <category>circuitbreaker</category>
      <category>Spring</category>
      <author>AlwaysPr</author>
      <guid isPermaLink="true">https://alwayspr.tistory.com/48</guid>
      <comments>https://alwayspr.tistory.com/48#entry48comment</comments>
      <pubDate>Sun, 29 Jan 2023 14:00:21 +0900</pubDate>
    </item>
    <item>
      <title>WebFlux에서 micrometer로 모니터링 데이터 수집하기</title>
      <link>https://alwayspr.tistory.com/46</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;환경설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 gradle을 사용했고,&amp;nbsp; 아래와 같이 webflux, actuator, prometheus 등의 dependency 설정이 필요하다&lt;/p&gt;
&lt;pre class=&quot;sml&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 application.properties에 아래 코드를 통하여 엔드포인트에 노출시켜 주도록 하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;management.endpoints.web.exposure.include=health,info,metrics,prometheus
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;micrometer를 사용하면 기본적으로 JVM, Disk, CPU, HTTP 등의 다양한 값들을 측정할 수 있지만 특정 API, Code 들의 속도 측정을 위주로 문서를 작성해보려 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1.&amp;nbsp; HTTP 속도 측정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Server 기준&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 아래처럼 간단한 API를 만들었고, 300~2000ms의 sleep을 걸었다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@RestController
public class FooController {

    @GetMapping(&quot;/test&quot;)
    public Mono&amp;lt;String&amp;gt; test() throws InterruptedException {
        int millis = ThreadLocalRandom.current().nextInt(300, 2000);
        Thread.sleep(millis);
        return Mono.just(&quot;test&quot;);

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terminal에서 &lt;i&gt;curl localhost:8080/test&lt;/i&gt; 을 충분히 호출해 준 뒤&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;curl localhost:8080/actuator/prometheus&lt;/i&gt; 를 호출하여 지표를 보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1642128605422&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# HELP http_server_requests_seconds
# TYPE http_server_requests_seconds summary
http_server_requests_seconds_count{exception=&quot;None&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,uri=&quot;/test&quot;,} 5.0
http_server_requests_seconds_sum{exception=&quot;None&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,uri=&quot;/test&quot;,} 3.690574182

# HELP http_server_requests_seconds_max
# TYPE http_server_requests_seconds_max gauge
http_server_requests_seconds_max{exception=&quot;None&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,uri=&quot;/test&quot;,} 1.048827297&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 http 호출 수, 총 시간, max 시간을 제공해주고 있다. 만약 백분위 지표나 그 외의 지표를 알고 싶다면 application.properties에 코드를 한 줄 추가해 주면 된다.(&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/2.7.7/reference/html/actuator.html#actuator.metrics.customizing.per-meter-properties&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 지표들은 태그들을 충분히 제공하기 때문에 uri, status, method 별로 데이터를 시각화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Client 기준&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller로 들어오는 지표와 반대로 Webclient를 통해 외부로 나가는 데이터도 측정할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 아래처럼 Controller, Service를 작성해 보자&lt;/p&gt;
&lt;pre id=&quot;code_1642465087837&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
public class FooController {

    private final FooService fooService;

    @GetMapping(&quot;/test&quot;)
    public Mono&amp;lt;String&amp;gt; test() throws InterruptedException {
        int millis = ThreadLocalRandom.current().nextInt(300, 2000);
        Thread.sleep(millis);
        return Mono.just(&quot;test&quot;);
    }

    @GetMapping(&quot;/call&quot;)
    public Mono&amp;lt;String&amp;gt; call() {
        return fooService.call();
    }

}


@Service
public class FooService {

    private final WebClient webClient;

    public FooService(WebClient.Builder builder) {
        this.webClient = builder
                .baseUrl(&quot;http://localhost:8080/test&quot;)
                .build();
    }

    public Mono&amp;lt;String&amp;gt; call() {
        return webClient.get()
                .retrieve()
                .bodyToMono(String.class);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;/call&lt;/i&gt;을 호출하게 되면 Webclient로 &lt;i&gt;/test&lt;/i&gt;를 호출하게 되는 간단한 구조이다. ( &lt;i&gt;/call -&amp;gt; /test&lt;/i&gt; )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Service에서 주의 깊게 봐야 할 코드는 생성자 부분이다. WebClient.Builder는 WebClientAutoConfiguration#webClientBuilder에서 생성된 Prototype Sope의 빈을 DI 받는다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Bean
@Scope(&quot;prototype&quot;)
@ConditionalOnMissingBean
public WebClient.Builder webClientBuilder(ObjectProvider&amp;lt;WebClientCustomizer&amp;gt; customizerProvider) {
   WebClient.Builder builder = WebClient.builder();
   customizerProvider.orderedStream().forEach((customizer) -&amp;gt; customizer.customize(builder));
   return builder;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webClientBuilder의 파라미터로는 WebClientMetricsConfiguration#metricsWebClientCustomizer를 DI 받아 WebClient filter로 등록을 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돌고 돌아왔는데, 한마디로 말하면 &lt;b&gt;WebClient.Builder를 DI 받아서 사용하면 기본적으로 metric을 사용할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebClient로 호출되면 아래처럼 prometheus에 metric이 수집된다.&lt;/p&gt;
&lt;pre id=&quot;code_1642466539519&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# HELP http_client_requests_seconds Timer of WebClient operation
# TYPE http_client_requests_seconds summary
http_client_requests_seconds_count{clientName=&quot;localhost&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,uri=&quot;/test&quot;,} 6.0
http_client_requests_seconds_sum{clientName=&quot;localhost&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,uri=&quot;/test&quot;,} 10.821525809
# HELP http_client_requests_seconds_max Timer of WebClient operation
# TYPE http_client_requests_seconds_max gauge
http_client_requests_seconds_max{clientName=&quot;localhost&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,uri=&quot;/test&quot;,} 1.097316922&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.&amp;nbsp; Annotation을 통한 측정&lt;/h2&gt;
&lt;pre id=&quot;code_1643074863214&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Timed(extraTags = {&quot;timedKey&quot;, &quot;timedValue&quot;}, percentiles = {0.95, 0.99})
@Counted(extraTags = {&quot;countedKey&quot;, &quot;countedValue&quot;})
@GetMapping(&quot;/test&quot;)
public Mono&amp;lt;String&amp;gt; test() throws InterruptedException {
    int millis = ThreadLocalRandom.current().nextInt(300, 2000);
    Thread.sleep(millis);
    return Mono.just(&quot;test&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Annotation을 이용하면 method 별로 좀 더 디테일하게 지표를 측정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extraTags는 key, value로 꼭 이루어져야 하며, 해당 Annotation은 다양한 기능을 제공한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 가능한 이유는 WebFluxMetricsAutoConfiguration에서 자동설정된 MetricsWebFilter#record가 동작하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 측정된 지표들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1643074948977&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# HELP http_server_requests_seconds
# TYPE http_server_requests_seconds summary
http_server_requests_seconds{exception=&quot;None&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,timedKey=&quot;timedValue&quot;,uri=&quot;/test&quot;,quantile=&quot;0.95&quot;,} 1.811939328
http_server_requests_seconds{exception=&quot;None&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,timedKey=&quot;timedValue&quot;,uri=&quot;/test&quot;,quantile=&quot;0.99&quot;,} 1.811939328
http_server_requests_seconds_count{exception=&quot;None&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,timedKey=&quot;timedValue&quot;,uri=&quot;/test&quot;,} 2.0
http_server_requests_seconds_sum{exception=&quot;None&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,timedKey=&quot;timedValue&quot;,uri=&quot;/test&quot;,} 3.068237006
# HELP http_server_requests_seconds_max
# TYPE http_server_requests_seconds_max gauge
http_server_requests_seconds_max{exception=&quot;None&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,timedKey=&quot;timedValue&quot;,uri=&quot;/test&quot;,} 1.849908941&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.&amp;nbsp; 특정 파이프라인의 속도 측정&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reactor에서 제공하는 metircs 메소드를 사용하면 특정 파이프라인의 지표를 측정할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@GetMapping(&quot;/test&quot;)
public Mono&amp;lt;String&amp;gt; test() throws InterruptedException {
    return Mono.just(&quot;test&quot;)
            .map(this::sleepAndConcat);
}

private String sleepAndConcat(String it){
    int millis = ThreadLocalRandom.current().nextInt(300, 2000);
    try {
        Thread.sleep(millis);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return it + it;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mono에 들어가 있는 String을 더하는 간단한 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#sleepAndConcat에는 일부로 다양한 시간을 측정하기 위해 랜덤 하게 시간을 기다리게 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#sleepAndConcat에서 소요되는 시간을 측정하려면 Mono#metrics를 사용해 주면 간단하게 측정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@GetMapping(&quot;/test&quot;)
public Mono&amp;lt;String&amp;gt; test() throws InterruptedException {
    return Mono.just(&quot;test&quot;)
            .map(this::sleepAndConcat)
            .metrics();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1644717664399&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# HELP reactor_flow_duration_seconds Times the duration elapsed between a subscription and the onComplete termination of a sequence that did emit some elements
# TYPE reactor_flow_duration_seconds summary
reactor_flow_duration_seconds_count{exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 5.0
reactor_flow_duration_seconds_sum{exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 4.807904757
# HELP reactor_flow_duration_seconds_max Times the duration elapsed between a subscription and the onComplete termination of a sequence that did emit some elements
# TYPE reactor_flow_duration_seconds_max gauge
reactor_flow_duration_seconds_max{exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 1.901454296&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의&amp;nbsp;name을&amp;nbsp;주지&amp;nbsp;않으면&amp;nbsp;reactor_flow_duration를&amp;nbsp;default&amp;nbsp;값으로&amp;nbsp;보여준다.&lt;br /&gt;이는&amp;nbsp;Mono#name&amp;nbsp;을&amp;nbsp;사용하여&amp;nbsp;아래처럼&amp;nbsp;정의할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673356184602&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/test&quot;)
public Mono&amp;lt;String&amp;gt; test() {
    return Mono.just(&quot;test&quot;)
            .map(this::sleepAndConcat)
            .name(&quot;ms_test_metrics&quot;)
            .metrics();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673356393791&quot; class=&quot;pgsql&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# HELP ms_test_metrics_flow_duration_seconds_max Times the duration elapsed between a subscription and the onComplete termination of a sequence that did emit some elements
# TYPE ms_test_metrics_flow_duration_seconds_max gauge
ms_test_metrics_flow_duration_seconds_max{exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 1.786340917
# HELP ms_test_metrics_flow_duration_seconds Times the duration elapsed between a subscription and the onComplete termination of a sequence that did emit some elements
# TYPE ms_test_metrics_flow_duration_seconds summary
ms_test_metrics_flow_duration_seconds_count{exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 7.0
ms_test_metrics_flow_duration_seconds_sum{exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 7.508261709&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이에&amp;nbsp;더해&amp;nbsp;파라미터별로&amp;nbsp;측정하고&amp;nbsp;싶다거나&amp;nbsp;특정&amp;nbsp;데이터를&amp;nbsp;기준으로&amp;nbsp;데이터를&amp;nbsp;수집하고&amp;nbsp;싶으면&amp;nbsp;Mono#tag를&amp;nbsp;사용하면&amp;nbsp;된다.&lt;/p&gt;
&lt;pre id=&quot;code_1673356278524&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/test&quot;)
public Mono&amp;lt;String&amp;gt; test() {
    return Mono.just(&quot;test&quot;)
            .map(this::sleepAndConcat)
            .name(&quot;ms_test_metrics&quot;)
            .tag(&quot;KEY&quot;, UUID.randomUUID().toString())
            .metrics();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673356312924&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# HELP ms_test_metrics_flow_duration_seconds_max Times the duration elapsed between a subscription and the onComplete termination of a sequence that did emit some elements
# TYPE ms_test_metrics_flow_duration_seconds_max gauge
ms_test_metrics_flow_duration_seconds_max{KEY=&quot;6d58e54a-f7b7-49ad-974b-c1e18ba7676d&quot;,exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 0.522620334
ms_test_metrics_flow_duration_seconds_max{KEY=&quot;b8d05f99-861d-4253-a59f-582e2e9230ac&quot;,exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 1.125683875
ms_test_metrics_flow_duration_seconds_max{KEY=&quot;be098a82-c39c-4a8e-92d3-87009cfd83c3&quot;,exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 1.216874791
# HELP ms_test_metrics_flow_duration_seconds Times the duration elapsed between a subscription and the onComplete termination of a sequence that did emit some elements
# TYPE ms_test_metrics_flow_duration_seconds summary
ms_test_metrics_flow_duration_seconds_count{KEY=&quot;6d58e54a-f7b7-49ad-974b-c1e18ba7676d&quot;,exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 1.0
ms_test_metrics_flow_duration_seconds_sum{KEY=&quot;6d58e54a-f7b7-49ad-974b-c1e18ba7676d&quot;,exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 0.522620334
ms_test_metrics_flow_duration_seconds_count{KEY=&quot;b8d05f99-861d-4253-a59f-582e2e9230ac&quot;,exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 1.0
ms_test_metrics_flow_duration_seconds_sum{KEY=&quot;b8d05f99-861d-4253-a59f-582e2e9230ac&quot;,exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 1.125683875
ms_test_metrics_flow_duration_seconds_count{KEY=&quot;be098a82-c39c-4a8e-92d3-87009cfd83c3&quot;,exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 1.0
ms_test_metrics_flow_duration_seconds_sum{KEY=&quot;be098a82-c39c-4a8e-92d3-87009cfd83c3&quot;,exception=&quot;&quot;,status=&quot;completed&quot;,type=&quot;Mono&quot;,} 1.216874791&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>Metrics</category>
      <category>micrometer</category>
      <category>Prometheus</category>
      <category>Reactor</category>
      <category>Webflux</category>
      <category>마이크로미터</category>
      <category>모니터링</category>
      <category>웹플럭스</category>
      <category>프로메테우스</category>
      <author>AlwaysPr</author>
      <guid isPermaLink="true">https://alwayspr.tistory.com/46</guid>
      <comments>https://alwayspr.tistory.com/46#entry46comment</comments>
      <pubDate>Wed, 11 Jan 2023 19:39:11 +0900</pubDate>
    </item>
    <item>
      <title>2022년 회고</title>
      <link>https://alwayspr.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 쓰는 회고글이다. 2022년을 의미 있게 보냈고, 2023년을 맞이하기 위해 회고글을 작성하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 계기로 내년엔 블로그 작성에 좀 더 신경써보려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;본인확인기관 라이센스 취득&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;1622&quot;&gt;&lt;a href=&quot;https://biz.chosun.com/stock/finance/2022/10/06/MN3ZROKWYZGIRGG5I5MLJDUJAU/&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biP1cg/btrUV3PCe30/Dym89DNqT4Q4NeKD8pNHt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiP1cg%2FbtrUV3PCe30%2FDym89DNqT4Q4NeKD8pNHt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1962&quot; height=&quot;1622&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;1622&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 본인확인기관 / 전자서명인정사업자 라이센스를 취득하기 위해 준비가 한창이었고, 팀장님의 소소한 프로젝트라는 말에 속아(?) 서버 개발에 참여하게 되었다. 서버 개발은 나를 포함한 두 명이서 진행하게 되었고, 팀원 한분이 입사한 지 얼마 안 되어 필자가 리딩을 하게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막상 참여해보니 코딩 자체가 양이 많거나 난이도가 있다기 보단 전반적인 프로세스를 이해하는데 애를 많이 먹었다. 회원가입, 기기변경, 계좌개설, 회원탈퇴 등의 굵직한 로직에 자연스레 녹아들어 가야 했기 때문이다. 또한 리딩의 경험이 많지 않았기에 부담을 느꼈다. 회사에서 필자가 경험한 선배 개발자들은 같이 일하는 구성원의 성장을 위해 많은 고민과 노력을 하셨다. 그런데 이제 그 역할을 내가 잘할 수 있을까? 란 의구심이 계속 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 고군분투하며 심사까지 통과하고 10월에 위의 기사와 함께 라이센스 취득에 성공하였다. 처음엔 필자를 낚은(?) 팀장님 원망도 종종 했지만, 그 덕에 나의 한계선을 돌파했다는 생각도 들었다. 또한 위에서 언급한 굵직한 로직들을 알게 되어 회사의 전반적인 이해도가 상승하여 업무에 도움이 되었다. 무엇보다도 같이 일한 팀원이 많이 배우고 도움이 되었다고 말씀해주셔서 큰 격려가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;if kakao 발표&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1948&quot; data-origin-height=&quot;1708&quot;&gt;&lt;a href=&quot;https://if.kakao.com/2022/session/36&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lBNDT/btrUTZ0ZXPr/nofjjD4Uw2atBCv65ScrBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlBNDT%2FbtrUTZ0ZXPr%2FnofjjD4Uw2atBCv65ScrBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1948&quot; height=&quot;1708&quot; data-origin-width=&quot;1948&quot; data-origin-height=&quot;1708&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2018년에 ifkakao를 참석하면서 큰 무대에서 기술 발표를 하고 싶다는 꿈을 가졌었다. 4년이라는 시간이 흘러 그 기회가 왔다. 사내 게시판에 ifkakao 연사 모집글이 올라왔고, 큰 고민 없이 지원했다. 그런데 갑작스러운 코로나 확진 증가로 인하여 코엑스에서 오프라인으로 진행하던걸 온라인으로 바뀌게 되어 중간에 그만둘까라는 고민도 했지만 어찌 되었든 얻는 게 있을 거라 생각하고 준비를 하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발표 준비를 하면서 깊게 공부하거나 생각하지 못한 부분이 발견되었다. 예를 들면 백엔드 이외의 DB나 Infra 쪽이다. 아무래도 담당팀에서 잘해주시니 관심을 못 가진 것 같다. 그러나 이러한 부분까지도 발표에 필요하니 자연스레 많이 배우고 성장한 것 같다.&amp;nbsp; 담당팀에서도 적극적으로 지원을 해주셔서 양질의 발표자료를 만드는데 큰 도움이 되었다. 이외에도 주변 동료분들이 많이 도움을 주셨다. 발표 내용에 대해서도 좋은 피드백을 주시고, 발표 자료 PPT의 디자인을 봐주시고, 마지막으로 촬영 당일날 화장까지 도와주셨다. (우리 회사엔 좋은 동료가 참 많다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 수십만 원 상당의 1:1 스피치 교육을 지원해 주었다. 준비된 스크립트를 가지고 강사님 앞에서 발표를 하였는데 이때 스크립트, 말투, 전달력 등을 코칭해 주셨다. 큰 기대는 안 했는데, 발표뿐만 아니라 일상생활에서도 활용할만한 부분이 많은 것 같아 큰 도움이 되었다. &lt;s&gt;사투리에 대해서 피드백이 많았는데, 역시나 촬영할 때 까지도 고쳐지지 않았다 &lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번의 리허설 촬영과 본 촬영을 끝으로 ifkakao 발표를 마쳤다. 배우고 성장한 것도 많지만 아쉬움이 남는 것도 사실이다. ifkakao가 시작이라고 생각한다. 다음 발표엔 좀 더 좋은 주제와 전달력으로 찾아가길 희망한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;iOS 개발&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;964&quot;&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/%EA%BF%80%EB%A1%9C%EB%A6%AC/id1662476376&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F9gRh/btrVZWakzgr/Jk86oHlIVkZuD99oNeCsRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF9gRh%2FbtrVZWakzgr%2FJk86oHlIVkZuD99oNeCsRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1006&quot; height=&quot;964&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;964&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2022년에 의외의 성과는 iOS 개발을 한 것이다. XCode에서부터 Swift 모든 게 낯설었지만, 그래도 개발 경험이 있어서 그런지 금방 적응해서 앱 하나를 만들었다. 새로운 언어를 배우고 낯선 개발을 경험해본 것이 기존에 사용하던 언어와 개발 방식에 대해 다시 생각해볼 기회를 마련해 줬다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실용주의 프로그래머라는 책에선가 1년에 한 번은 새로운 언어를 배우라고 했던 기억이 있다. 내년에는 Go를 한번 공부해보고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기술 서적&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 팀은 기술서적 읽는 것을 즐기고, 그에 대한 토론과 논의를 좋아하는 것 같다. 그래서 올해도 다양한 책들을 함께 읽고 의견을 나누는 자리가 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 만들면서 배우는 클린 아키텍처&lt;br /&gt;- 구글 엔지니어는 이렇게 일한다&lt;br /&gt;-&amp;nbsp;도메인&amp;nbsp;주도&amp;nbsp;설계&amp;nbsp;첫걸음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 책들을 같이 읽고 나누었고, 3 권 다 선한 영향을 준 책이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이펙티브&amp;nbsp;자바&amp;nbsp;Effective&amp;nbsp;Java&amp;nbsp;3/E&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 엘레강트&amp;nbsp;오브젝트&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 에릭 에반스 DDD&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터&amp;nbsp;중심&amp;nbsp;애플리케이션&amp;nbsp;설계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 책들은 개인적으로 읽은 책인데, 과거에 비해 많이 못 읽은 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023년에는 쌓아둔 책들을 다 읽는 한 해가 되었으면 좋겠다. 또한 그 책에서 얻는 인사이트를 블로그에 작성할 예정이다.&lt;/p&gt;</description>
      <category>Diary</category>
      <category>2022</category>
      <category>개발자</category>
      <category>이프카카오</category>
      <category>회고</category>
      <author>AlwaysPr</author>
      <guid isPermaLink="true">https://alwayspr.tistory.com/47</guid>
      <comments>https://alwayspr.tistory.com/47#entry47comment</comments>
      <pubDate>Fri, 30 Dec 2022 23:19:10 +0900</pubDate>
    </item>
    <item>
      <title>JPA를 이용하여 cursor 기반 페이징 구현</title>
      <link>https://alwayspr.tistory.com/45</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;언젠가부터 &lt;b&gt;무한 스크롤&lt;/b&gt;을 이용한 페이징 방식이 우리들에 스며들기 시작했다. 이는 과거의 1, 2, 3... 의 페이지를 클릭하여 다음 콘텐츠를 보는 것이 아니라, 페이스북처럼 마지막 콘텐츠를 보게 되면 다음 페이지가 로딩되어 보이는 것을 의미한다. 그러나 SNS에서는 일반적으로 사용하는 Offset 기반의 페이징을 사용하게 되면 문제가 생길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Offset Based Pagination&lt;/h3&gt;
&lt;pre id=&quot;code_1577366399138&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM BOARDS ORDER BY ID DESC LIMIT 0, 10;
SELECT * FROM BOARDS ORDER BY ID DESC LIMIT 10, 10;
SELECT * FROM BOARDS ORDER BY ID DESC LIMIT 20, 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 쿼리를 해석해보면 최신 게시글의 0~10, 10~20, 20~30을 조회한다. 그러나 SNS처럼 실시간으로 많은 글이 올라오는 환경에서는 중복해서 컨텐츠가 노출될 수 있다. 이유는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 쿼리가 실행된 후 그 사이에 콘텐츠 하나가 추가가 되었다면, 두 번째 쿼리를 조회할 때 중복될 수가 있다. 즉 첫 번째 쿼리의 마지막 요소와 두 번째 쿼리의 첫 번째 요소는 동일한 결과가 나타나게 된다. 계속해서 이러한 문제가 생긴다면 사용자에게 부정적인 인식을 심어주게 될 것이다. 이러한 이슈를 Cursor 기반의 페이징으로 풀어나갈 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Cursor Based Pagination&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Cursor 기반의 페이징은&amp;nbsp;&lt;/span&gt;간단하게 말하자면 리스트를 조회할 때 내가 읽은 마지막 요소를 알려줌으로써 그 뒤의 값을 조회하는 것을 의미한다.&lt;/p&gt;
&lt;pre id=&quot;code_1577606312110&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM BOARDS ORDER BY ID DESC LIMIT 10;

SELECT * FROM BOARDS 
WHERE 
	ID &amp;lt; {이전에 조회한 마지막 id} 
ORDER BY 
	ID DESC LIMIT 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 쿼리를 해석해보면 최신 10개의 게시글을 조회하고, 이후에는 이전에 조회한 마지막 ID보다 작은 10개를 가져오게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID가 1 ~ 30인 30개의 게시글이 있다고 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초에는 ID가 30 ~ 21 인 게시글이 조회된다. 두번째 조회 시에 where절에 21을 넣게 되어 20~11이 조회된다. 세 번째도 비슷한 원리로 11을 넣게 되면 10~1이 조회된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원리는 이해했으니 &lt;a href=&quot;https://github.com/viviennes7/jpa-cursor-example&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Java코드&lt;/a&gt;로 풀어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1577366854485&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String title;

    private String contents;

    private LocalDateTime createAt;
	//...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id, 제목, 내용, 생성일이 있는 간략한 게시글이다. 여기서 키포인트는 ID는 &lt;b&gt;순차적&lt;/b&gt;이어야 한다는 것이다. 그래야 간편하게 Cursor 구현이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1577606957273&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/boards&quot;)
public class BoardController {

    private static final int DEFAULT_SIZE = 10;

    private final BoardService boardService;

    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }

    @GetMapping
    public CursorResult&amp;lt;Board&amp;gt; getBoards(Long cursorId, Integer size) {
        if (size == null) size = DEFAULT_SIZE;
        return this.boardService.get(cursorId, PageRequest.of(0, size));
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1577607312829&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CursorResult&amp;lt;T&amp;gt; {
    private List&amp;lt;T&amp;gt; values;
    private Boolean hasNext;

    public CursorResult(List&amp;lt;T&amp;gt; values, Boolean hasNext) {
        this.values = values;
        this.hasNext = hasNext;
    }
	//...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Controller 또한 간단하다. &lt;/span&gt;파라미터로 cursorId와 size를 받게된다. 여기서 주목해야 될 점은 PageRequest.of()의 첫 번째 파라미터는 무조건 0으로, 즉 최초의 페이지로 처리를 해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Client 개발자와 소통할 때 다음 조회할 리스트가 존재하는지 내려주면 좀 더 효율적이다. 왜냐하면 불필요하게 한번 더 리스트를 조회할 필요가 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 CursorResult에서는 게시글 정보와 다음 리스트가 존재하는지의 여부를 알려주는 hasNext를 내려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1577607705168&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class BoardService {

    private final BoardRepository boardRepository;

    public BoardService(BoardRepository boardRepository) {
        this.boardRepository = boardRepository;
    }

    CursorResult&amp;lt;Board&amp;gt; get(Long cursorId, Pageable page) {
        final List&amp;lt;Board&amp;gt; boards = getBoards(cursorId, page);
        final Long lastIdOfList = boards.isEmpty() ?
                null : boards.get(boards.size() - 1).getId();

        return new CursorResult&amp;lt;&amp;gt;(boards, hasNext(lastIdOfList));
    }

    private List&amp;lt;Board&amp;gt; getBoards(Long id, Pageable page) {
        return id == null ?
                this.boardRepository.findAllByOrderByIdDesc(page) :
                this.boardRepository.findByIdLessThanOrderByIdDesc(id, page);
    }

    private Boolean hasNext(Long id) {
        if (id == null) return false;
        return this.boardRepository.existsByIdLessThan(id);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 최초로 조회한 경우(cursorId가 null인 경우)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;findAllByOrderByIdDesc()를 통하여 size만큼의 최신 게시글을 조회한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 최초가 아닌 경우 (유효한 cursorId가 들어오는 경우)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;findByIdLessThanOrderByIdDesc()를 통하여 커서보다 낮은 게시글을 조회한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 다음에 조회될 게시글이 있는지 여부&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;existsByIdLessThan()를 통하여 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 이해가 잘안된다면 &lt;a href=&quot;https://github.com/viviennes7/jpa-cursor-example/blob/master/src/test/java/com/ms/jpacursorexample/BoardServiceTest.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Test Code&lt;/a&gt;를 작성해 놓았으니 실행하여 확인해보면 좀 더 이해하기 편할 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ID가 순차적이지 않으면 어떻게 될까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떠한 이유로 ID가 순차적이지 않고 UUID와 같이 비순차적이면 위의 방법은 크게 의미가 없어진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 생성일을 기준으로 가져오는 방식을 생각해볼 수 있지만, 동일한 생성일을 가질 경우 데이터가 누락될 가능성이 있다. 그래서 ID값을 활용하여 이를 예방시켜보자.&lt;/p&gt;
&lt;pre id=&quot;code_1577608901804&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM BOARD 
ORDER BY 
	CREATE_AT DESC, ID DESC LIMIT 10;

SELECT * FROM BOARD 
WHERE 
	(CREATE_AT = {CREATE_AT} &amp;amp;&amp;amp; ID &amp;lt; {ID}) OR 
	(CREATE_AT &amp;lt; {CREATE_AT}) 
ORDER BY 
	CREATE_AT DESC, ID DESC LIMIT 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 쿼리를 사용하면 ID가 순차적이지 않은 경우에도 Cursor Based Pagination을 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: 403px; top: 4043.36px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Spring</category>
      <category>cursor</category>
      <category>java</category>
      <category>JPA</category>
      <category>Page</category>
      <category>Spring</category>
      <author>AlwaysPr</author>
      <guid isPermaLink="true">https://alwayspr.tistory.com/45</guid>
      <comments>https://alwayspr.tistory.com/45#entry45comment</comments>
      <pubDate>Sun, 29 Dec 2019 17:50:27 +0900</pubDate>
    </item>
    <item>
      <title>Spring WebFlux는 어떻게 적은 리소스로 많은 트래픽을 감당할까?</title>
      <link>https://alwayspr.tistory.com/44</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Boot1VsBoot2.png&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqCUNI/btqwi20NiHH/7Bcr6naBosEyFgbbv96E21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqCUNI/btqwi20NiHH/7Bcr6naBosEyFgbbv96E21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqCUNI/btqwi20NiHH/7Bcr6naBosEyFgbbv96E21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqCUNI%2Fbtqwi20NiHH%2F7Bcr6naBosEyFgbbv96E21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;786&quot; data-filename=&quot;Boot1VsBoot2.png&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;786&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림은  &lt;a href=&quot;https://dzone.com/articles/raw-performance-numbers-spring-boot-2-webflux-vs-s&quot;&gt;DZone&lt;/a&gt; 게시글 중 하나인 Spring WebFlux를 이용한 Boot2와 Spring MVC를 이용한 Boot1을 비교한 그래프이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 그래프에서는 두 가지 특징을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로는 유저가 적을 때에는 성능에 별반 차이가 없다. 두 번째로는 유저가 늘어나면 늘어날수록 극명한 성능 차이를 보여주고 있다. 어떻게 이런 차이가 일어날 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본글은 아래의 구성을 가지고 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;I/O&lt;/li&gt;
&lt;li&gt;Event-Driven&lt;/li&gt;
&lt;li&gt;Spring Framework&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 1과 2를 통해서 원리를 알아본 다음 3에서 이를 접목시켜서 위의 그래프가 나올수 있는 이유를 설명할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;I / O&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Why was Spring WebFlux created?&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Part of the answer is the need for a non-blocking web stack to handle concurrency with a small number of threads and scale with fewer hardware resources. &amp;ndash; Spring Document&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심만 말하자면 non-blocking을 통해서 적은 수의 리소스로 동시성을 다룬다는 것이다. I/O의 원리부터 시작해서 non-blocking에 대해서도 이해해보도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://eunhyejung.github.io/assets/contents/content02.PNG&quot; alt=&quot;io&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 I/O 요청을 할 때 CPU가 I/O Controller에 요청을 하고 I/O Controller가 파일을 다 가져오면 그것을 Memory에 적재시키고 CPU에게 완료되었다고 알려준다. 즉 큰 그림은 CPU -&amp;gt; I/O Controller -&amp;gt; CPU의 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 CPU가 I/O를 직접 가져오는 것이 아니라, 작은 CPU라고 불리는 I/O Controller가 수행한다는 이야기이다. 좀 더 나아가면 작업을 단순히 위임시키고 작업이 완료되는 동안에는 다른 일을 수행할 수 있다는 말이다. 이러한 예처럼 I/O를 처리하는데 몇 가지 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Blocking I/O&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory&amp;amp;fname=http%3A%2F%2Fcfile9.uf.tistory.com%2Fimage%2F991CE83359A87B6D342482&quot; alt=&quot;blocking io&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 I/O 모델이며 여러분들이 Spring MVC와 RDBMS를 사용하고 있으면 대부분 이 모델을 사용하고 있을 것으로 예상된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Application에서 I/O 요청을 한 후 완료되기 전까지는 Application이 Block이 되어 다른 작업을 수행할 수 없다. 이는 해당 자원이 효율적으로 사용되지 못하고 있음을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 생각을 해보면 여러분들의 Application들은 Blocking 방식임에도 불구하고 마치 Block이 안된듯이 동작하는 것처럼 보인다. 이것은 여러분들이 Single Thread를 기반으로 하는 것이 아닌 Multi Thread를 기반으로 동작하기 때문이다. Block 되는 순간 다른 Thread가 동작함으로써 Block의 문제를 해소하였다. 그러나 Thread 간의 전환(Context Switching)에 드는 비용이 존재하므로 여러 개의 I/O를 처리하기 위해 여러 개의 Thread를 사용하는 것은 비효율적으로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Synchronous Non-Blocking I/O&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory&amp;amp;fname=http%3A%2F%2Fcfile10.uf.tistory.com%2Fimage%2F99245C3359A87B9533B7DA&quot; alt=&quot;non blocking io&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Application에서 I/O를 요청 후 바로 return되어 다른 작업을 수행하다가 특정 시간에 데이터가 준비가 다되었는지 상태를 확인한다. 데이터의 준비가 끝날 때까지 틈틈이 확인을 하다가 완료가 되었으면 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주기적으로 체크하는 방식을 &lt;b&gt;폴링(Polling)&lt;/b&gt; 이라고 한다. 그러나 이러한 방식은 작업이 완료되기 전까지 주기적으로 호출하기 때문에 불필요하게 자원을 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Asynchronous Non-blocking I/O&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory&amp;amp;fname=http%3A%2F%2Fcfile10.uf.tistory.com%2Fimage%2F99D8873359A87C3D1001BD&quot; alt=&quot;async non blocking io&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;I/O 요청을 한 후 Non-Blocking I/O와 마찬가지고 즉시 리턴된다. 허나, 데이터 준비가 완료되면 이벤트가 발생하여 알려주거나, 미리 등록해놓은 callback을 통해서 이후 작업이 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 두 I/O의 문제였던 Blocking이나 Polling이 없기 때문에 자원을 보다 더 효율적으로 사용할 수 있다.&lt;br /&gt;(이후로는 편의상 Non-Blocking I/O라고 하겠다.)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/viviennes7/io-example/blob/master/src/test/java/com/ms/ioexample/io/IOTest.java&quot;&gt;Java 코드&lt;/a&gt;를 통해서 이를 이해해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Blocking I/O&lt;/h3&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Test
public void blocking() {
    final RestTemplate restTemplate = new RestTemplate();

    final StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    for (int i = 0; i &amp;lt; 3; i++) {
        final ResponseEntity&amp;lt;String&amp;gt; response =
                restTemplate.exchange(THREE_SECOND_URL, HttpMethod.GET, HttpEntity.EMPTY, String.class);
        assertThat(response.getBody()).contains(&quot;success&quot;);
    }

    stopWatch.stop();

    System.out.println(stopWatch.getTotalTimeSeconds());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring의 HTTP 요청 라이브러리인 &lt;code&gt;RestTemplate&lt;/code&gt;을 사용하여 3초가 걸리는 API를 3번 호출하였다. 결과는 여러분도 알다시피 9.xx초가 나온다. 이유는 I/O가 요청 중일 때에는 아무 작업도 할 수 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Non Blocking I/O&lt;/h3&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Test
public void nonBlocking3() throws InterruptedException {
    final StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for (int i = 0; i &amp;lt; LOOP_COUNT; i++) {
        this.webClient
                .get()
                .uri(THREE_SECOND_URL)
                .retrieve()
                .bodyToMono(String.class)
                .subscribe(it -&amp;gt; {
                    count.countDown();
                    System.out.println(it);
                });
    }

    count.await(10, TimeUnit.SECONDS);
    stopWatch.stop();
    System.out.println(stopWatch.getTotalTimeSeconds());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebFlux에서 제공하는 &lt;code&gt;WebClient&lt;/code&gt;를 사용해서 위와 동일하게 3초가 걸리는 API를 호출하였다. for문 안의 변수인 &lt;code&gt;LOOP_COUNT&lt;/code&gt;는 100으로 코드상에서 설정되어있다. 3초 걸리는 API를 100번 호출한다 하더라도 3.xx초 밖에 걸리지 않는다. 더 나아가서 &lt;code&gt;LOOP_COUNT&lt;/code&gt;를 1000으로 변경하더라도 필자의 컴퓨터에서는 4.xx초 밖에 걸리지 않는다. Blocking I/O와 비교해봤을 때 정말 효율적이라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, Blocking을 위처럼 많은 요청을 동시에 처리하려면 그 만큼의 Thread이 생성되어야 한다. 그러나 이렇게 처리한다 해도 Context Swiching에 의한 오버헤드가 존재할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Event-Driven&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시장 조사 기관 가트너는 비즈니스 업계가 주목해야 할 2018 10대 전략기술 트렌드를 발표했고, Event-Driven가 포함되어 있다. 또한 Event-Driven을 토대로 많은 프레임워크와 라이브러리가 발전하고 있다. Spring WebFlux, Node.js, Vert.x 등이 그에 따른 예이다. 우리가 자주 접하는 기술들에 어떻게 스며들어 접목이 되었는지에 대해서 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Event-Driven Programming은 프로그램 실행 흐름이 이벤트(ex : 마우스 클릭, 키 누르기 또는 다른 프로그램의 메시지와 같은 사용자 작업)에 의해 결정되는 프로그래밍 패러다임이다. Event가 발생할 때 이를 감지하고 적합한 이벤트 핸들러를 사용하여 이벤트를 처리하도록 설계됐다. 순차적으로 진행되는 과거의 프로그래밍 방식과는 달리 유저에 의해 종잡을 수 없이 진행되는 GUI(Graphical User Interface)가 발전됨에 따라 Event-Driven 방식은 더욱더 많이 쓰이게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 우리 자주 접할 수 있는 방식은 아래와 같은 Click Event이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://derickbailey.com/wp-content/uploads/2015/09/edit-click.jpeg&quot; alt=&quot;click event&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 Java를 이용하여 Click Event를 구현한 예이다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;JButton button = new JButton();
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println(&quot;clicked&quot;);
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 Lambda Expression 사용하면 아래처럼 간결하게 표현이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;JButton button = new JButton();
button.addActionListener(e -&amp;gt; System.out.println(&quot;clicked&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 Java 이외의 타 언어에서도 자연스럽게 &lt;code&gt;Listener&lt;/code&gt;에 등록하여 Event를 구현하고 있다. 그런데 Button은 어떻게 유저에 의해 Click이 되었는지 &lt;b&gt;인지&lt;/b&gt;할 수 있을까? 단순히 &lt;code&gt;Listener&lt;/code&gt;에 등록하기만 하면 자동적으로 인지할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거로 돌아가 C언어로 키보드 Event를 핸들링하는 코드를 작성해보자.&lt;/p&gt;
&lt;pre class=&quot;c++ cpp arduino&quot;&gt;&lt;code&gt;int main(void){
    char key;
    while(1){
        key = getch();      // (1) 

        switch (key) {      // (2)
            case 1 : 실행문; break;
            case 2 : 실행문; break;
            case 3 : 실행문; break;
            case 4 : 실행문; break;
            default : 실행문; break;
        }
    }
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 두 가지 경우로 되어있다.&lt;br /&gt;(1) 무한루프를 돌면서 사용자에 의해 Key가 눌러지는 것을 감지한다.&lt;br /&gt;(2) 감지된 값을 토대로 해야 할 일을 알맞은 곳에서 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 일반화시켜 말하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Event Loop가 돌면서 Event를 감지한 뒤 Event Handler 또는 Event Listener에게 보내서 작업을 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java Swing에서는 내부적으로 어떻게 Event를 처리할까?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;무제.png&quot; data-origin-width=&quot;775&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vWQSU/btqx8S97Gku/f9pjBBDV9Vh94LmCJ2KpK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vWQSU/btqx8S97Gku/f9pjBBDV9Vh94LmCJ2KpK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vWQSU/btqx8S97Gku/f9pjBBDV9Vh94LmCJ2KpK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvWQSU%2Fbtqx8S97Gku%2Ff9pjBBDV9Vh94LmCJ2KpK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;775&quot; height=&quot;588&quot; data-filename=&quot;무제.png&quot; data-origin-width=&quot;775&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 c언어로 작성한 코드와 거의 동일하지 않은가? 단지 Event Queue만 추가되었을 뿐이다. 그러나 코드는 한편 간결해졌다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;JButton button = new JButton();
button.addActionListener(e -&amp;gt; System.out.println(&quot;clicked&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일일이 Event를 제어했던 과거와는 달리 요즘은 이를 단순히 Listener에 행위만 등록해주면 간편하게 Event를 제어할 수 있다. 이는 Event Handle만이 관심의 대상이고 이에 집중할 수 있게 한다. 달리 말하면 Evevt Loop를 돌면서 요청을 감지하고 적합한 Handler에 위임해주는 부수적인 부분은 언어 레벨에서 처리를 해준다는 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 일반화하면 아래와 같은 이미지를 그릴 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://deepakpol.files.wordpress.com/2015/09/event-loop.png&quot; alt=&quot;Event-Driven&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Event-Driven이라는 키워드가 언급되면 위의 이미지를 기억에서 꺼내면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이러한 Event 처리는 Server에도 적합하다. 왜냐하면 HTTP Request라는 &lt;b&gt;Event&lt;/b&gt;가 발생하기 때문이다. 그래서 Node.js, Spring WebFlux, Vert.x 등은 Event-Driven 형태로 Architecture가 구현되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring Framework&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;diagram-reactive-1290533f3f01ec9c57baf2cc9ea9fa2f.svg&quot; data-origin-width=&quot;198&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sMFrh/btqCMoJkmiO/C1DKIoWLdkPTUKoeO4GOuK/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sMFrh/btqCMoJkmiO/C1DKIoWLdkPTUKoeO4GOuK/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sMFrh/btqCMoJkmiO/C1DKIoWLdkPTUKoeO4GOuK/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsMFrh%2FbtqCMoJkmiO%2FC1DKIoWLdkPTUKoeO4GOuK%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;198&quot; height=&quot;150&quot; data-filename=&quot;diagram-reactive-1290533f3f01ec9c57baf2cc9ea9fa2f.svg&quot; data-origin-width=&quot;198&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 그림처럼 Spring은 Reactive Stack과 Servlet Stack 두 가지 형태를 제공한다. 또한 Reactive Stack은 non-blocking I/O를 이용해서 많은 양의 동시성 연결을 다룰 수 있다고 한다. 과거로 돌아가서 Servlet Stack의 문제점을 파악하고 이를 어떻게 Reactive Stack으로 해결했는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring MVC&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스레드풀.png&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p5S5V/btqwg3zeAQP/tNjcoOvjL3LBOIRPa7Z61k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p5S5V/btqwg3zeAQP/tNjcoOvjL3LBOIRPa7Z61k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p5S5V/btqwg3zeAQP/tNjcoOvjL3LBOIRPa7Z61k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp5S5V%2Fbtqwg3zeAQP%2FtNjcoOvjL3LBOIRPa7Z61k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1488&quot; height=&quot;846&quot; data-filename=&quot;스레드풀.png&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 그림처럼 유저들로부터 HTTP 요청이 들어올 때 요청들은 Queue를 통하게 된다. Thread pool이 수용할 수 있는 수(thread pool size)의 요청까지만 동시적으로 작업이 처리되고 만약 넘게 된다면 큐에서 대기하게 된다. 즉 하나의 요청은 하나의 Thread를 요구한다. (one request per thread model)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thread pool은 다음과 같다. Thread를 생성하는 비용이 크기 때문에 미리 생성하여 재사용함으로써 효율적으로 사용한다. 그렇다고 과도하게 많은 Thread를 생성하는 것이 아니라 서버 성능에 맞게 Thread의 최대 수치를 제한시킨다. 참고로 tomcat default thread size는 200이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약 대량의 트래픽이 들어와 thread pool size를 지속적으로 초과하게 된다면 어떻게 될까?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://onacloud.co.za/wp-content/uploads/2016/07/queue-480x270.png&quot; alt=&quot;queue&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;설정해놓은 thread pool size를 넘게 되면 위 그림처럼 작업이 처리될 때까지 Queue에서 계속해서 기다려야 한다. 그래서 전체의 대기시간이 늘어난다. 이런 현상을 &lt;b&gt;Thread pool hell이라고&lt;/b&gt; 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 사진은 Linkedin의 Thread pool hell 현상에 대한 그래프이다. Thread pool이 감당할 수 있는 요청수를 넘는 순간부터는 평소보다 수배나 많은 지연시간을 보여준다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://image.slidesharecdn.com/theplayframeworkatlinkedin-final-130604033451-phpapp01/95/the-play-framework-at-linkedin-8-638.jpg&quot; alt=&quot;thread pool hell&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Thread pool이 감당할 수 있을 때까진 빠른 처리속도를 보이지만, 넘는 순간부터는 지연시간이 급격하게 늘어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 작업이 늦게 처리되는 부분에 대해서도 우린 고민해 볼 필요가 있다. 독자분들이 만든 코드가 보통 왜 느려지는가? 특수한 경우를 제외하면 DB, Network 등의 I/O가 일어나는 부분에서 아마 시간을 많이 소비했을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명했듯이 I/O 작업은 CPU가 관여하지 않는다. I/O Controller가 데이터를 읽어오고 이를 전달받을 뿐이다. 위에서 I/O를 처리하는 3가지 방식을 소개했는데 가장 효율이 좋은 방법은 마지막에 설명한 Asynchronous Non-blocking I/O이라고 하였다. Blocking 방식은 I/O Controller가 데이터를 읽는 동안 CPU가 아무 일도 할 수가 없고, Non-Blokcing방식은 polling 때문에 불필요하게 CPU를 소비한다고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에서도 Non-blocking I/O를 이용해서 효율적으로 작업을 처리할 수 있는 방법을 제공한다. 그 수단이 WebFlux이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring WebFlux&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://workwiththebest.intraway.com/wp-content/uploads/sites/4/2018/01/TRM-10-FINAL.png&quot; alt=&quot;webflux event driven&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위는 전반적인 WebFlux의 구조이다. 사용자들에 의해 요청이 들어오면 Event Loop를 통해서 작업이 처리가 된다. one request per thread model과의 차이점은 다수의 요청을 적은 Thread로도 커버할 수 있다. worker thread default size는 서버의 core 개수로 설정이 되어있다. 즉 서버의 core가 4개라면 worker thread는 4개라는 말이며 이 적은 Thread를 통해서도 traffic을 감당할 수 있다. 위에서 하나의 Thread로 3초가 걸리는 API 1000개를 호출했음에도 4초밖에 안 걸렸다는 걸 상기시키면 이해에 도움이 될 것이다. 또한 비슷한 Architecture를 가진 Node.js가 이미 증명을 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇듯 Non Blocking 방식을 활용하면 좀 더 효율적으로 I/O를 제어할 수 있고 성능에도 좋은 영향을 미친다. 특히나 유행하는 MSA에서는 수많은 Microservice가 거미줄처럼 서로를 네트워크를 통해서 호출하고 있다. 즉 많은 수의 Network I/O가 발생할 텐데 이를 Non Blocking I/O를 통해 좀 더 성능을 끌어올릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 물론 제한된 점이 있다. WebFlux로 성능을 최대치로 끌어올리려면 모든 I/O 작업이 Non Blocking 기반으로 동작해야 된다. Blocking이 되는 곳이 있다면 안 하느니만 못한 상황이 되어버린다.&lt;br /&gt;예를 들어 멀티코어로 가정을 해보자. 그럼 처리할 수 있는 Thread는 2개인데 Blocking이 걸리는 API를 열명 이서 동시에 호출한다면 결국엔 Spring MVC처럼 8명이 I/O 작업이 끝날 때까지 기다려야 하는 구조가 되어버리기 때문이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;What if you do need to use a blocking library? Both Reactor and RxJava provide the publishOn operator to continue processing on a different thread. That means there is an easy escape hatch. Keep in mind, however, that blocking APIs are not a good fit for this concurrency model. - Spring Document&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대안으로 &lt;code&gt;publicOn()&lt;/code&gt;을 사용하라고 하지만 마지막 문장 Blocking은 이 모델에 적합하지 않다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 진영에는 아쉽게도 DB connection을 non-blokcing으로 지원하는 라이브러리가 널리 보급되어 잘 사용되지는 않고 있다. 다만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/r2dbc/r2dbc-postgresql&quot;&gt;R2DBC&lt;/a&gt;처럼 개발이 진행 중인 라이브러리, 최근에 release된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/jasync-sql/jasync-sql&quot;&gt;jasync sql&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;등이 있으며, MongoDB, Redis 등의 NoSQL은 지원중이다. (피드백 주신 정상혁님 감사합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 소수의 Thread에 의해서 수많은 요청을 처리하고, 순서대로 작업이 처리되는 것이 아니라 Event에 기반하여 실타래가 엉킨 것처럼 작업이 처리되기 때문에 트래킹 하기에 힘이 들다는 문제가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 &lt;b&gt;성능이 좋으니 무조건 WebFlux를 사용해야 할까?&lt;/b&gt;&lt;br /&gt;지금까지 필자의 글에 혹했다면 위의 질문처럼 잘못된 생각을 할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://3.bp.blogspot.com/-3i759KJap_U/We6baQQFc2I/AAAAAAAAfTs/0G7gLgD2BWsmVbPluFFoeGhViOafX1QqgCLcBGAs/s1600/Boot1VsBoot2.png&quot; alt=&quot;webflux-performance&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 그림은 Spring MVC나 Spring WebFlux 둘 다 성능이 동일한 구간이 있다. 서버의 성능이 좋으면 좋아질수록 해당 구간은 더 늘어날 것이다. 그렇기에 만약 여러분의 환경이 해당 구간이라면 굳이 사용할 필요가 없다. 또한 &lt;a href=&quot;https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-framework-choice&quot;&gt;Spring Document&lt;/a&gt;에서는 동기 방식이 코드 작성, 이해, 디버깅하기 더 쉽다고 한다. 이 말은 즉 높은 생산성을 가진다는 말과 같은 것으로 보인다. 그렇기에 이해타산을 잘 따져서 선택해야 할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 우리는 이제 왜 성능이 동일한 구간이 생기는 지를 알 수 있다. 저 구간은 바로 Thread Pool이 감당할 수 있을 정도의 요청이었기에 비동기적으로 잘 수행하다가 이후에는 Queue에 쌓여 점점 성능이 느려졌던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'Spring WebFlux는 어떻게 적은 리소스로 많은 트래픽을 감당할까?'란 궁금증을 시작으로 여기까지 왔다. 이에 대한 답은 I/O를 Non Blockkng을 이용하여 잘 사용하는 것과 Request를 Event-Driven을 통해서 효율적으로 처리하기 때문에 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/viviennes7/io-example&quot;&gt;Blocking / Non Blocking example Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-reactive-spring-web&quot;&gt;Web on Reactive Stack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://spring.io/&quot;&gt;Spring.IO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dzone.com/articles/raw-performance-numbers-spring-boot-2-webflux-vs-s&quot;&gt;Dzone WebFlux Benchmarking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eunhyejung.github.io/os/2018/06/29/operatingsystem-study02.html&quot;&gt;컴퓨터 시스템 동작원리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.techsuda.com/archives/10174&quot;&gt;가트너 10대 기술 트렌드&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://news.samsung.com/kr/%EC%9A%94%EC%A6%98-%EC%A0%9C%EC%9D%BC-%ED%95%AB%ED%95%98%EB%8B%A4%EB%8A%94-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%93%9C%EB%A6%AC%EB%B8%90-%EB%88%84%EA%B5%AC%EB%83%90-%EB%84%8C&quot;&gt;요즘 제일 '핫'하다는 이벤트-드리븐... 누구냐, 넌! | SAMSUNG NEWSROOM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://deepakpol.wordpress.com/2015/09/29/event-driven-and-reactive-architecture/&quot;&gt;Event Driven and Reactive Architecture ‒ Deepak Pol's Blog &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.sys4u.co.kr/pages/viewpage.action?pageId=7767390&quot;&gt;Blocking, Non-Blocking / Synchronous, Asynchronous&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://asfirstalways.tistory.com/348&quot;&gt;blocking, non-blocking and Async&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://brainbackdoor.tistory.com/26&quot;&gt;blocking vs non-blocking / synchronous vs asynchronous &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.javaworld.com/article/2073477/customize-swingworker-to-improve-swing-guis.html&quot;&gt;Java Swing Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.slideshare.net/arawnkr/reactive-web-servlet-async-nonblocking-io-73838876&quot;&gt;Reactive Web - Servlet &amp;amp; Async, Non-blocking I/O &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://workwiththebest.intraway.com/white-paper/Testing-Reactive-Microservices-With-Spring-Webflux/&quot;&gt;Testing Reactive Microservices With Spring&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://spring.io/blog/2019/04/12/going-reactive-with-spring-coroutines-and-kotlin-flow&quot;&gt;Going Reactive with Spring, Coroutines and Kotlin Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://news.samsung.com/kr/%EC%9A%94%EC%A6%98-%EC%A0%9C%EC%9D%BC-%ED%95%AB%ED%95%98%EB%8B%A4%EB%8A%94-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%93%9C%EB%A6%AC%EB%B8%90-%EB%88%84%EA%B5%AC%EB%83%90-%EB%84%8C&quot;&gt;요즘 제일 '핫'하다는 이벤트-드리븐&amp;hellip; 누구냐, 넌! | SAMSUNG NEWSROOM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://deepakpol.wordpress.com/2015/09/29/event-driven-and-reactive-architecture/&quot;&gt;Event Driven and Reactive Architecture &amp;ndash; Deepak Pol's Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dbse-teaching.github.io/isee2018-SOverS/2018/05/25/System-Design.html&quot;&gt;Observer Pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://asfirstalways.tistory.com/348&quot;&gt;blocking, non-blocking and Async&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://brainbackdoor.tistory.com/26&quot;&gt;blocking vs non-blocking / synchronous vs asynchronous&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.sys4u.co.kr/pages/viewpage.action?pageId=7767390&quot;&gt;http://wiki.sys4u.co.kr/pages/viewpage.action?pageId=7767390&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.slideshare.net/arawnkr/reactive-web-servlet-async-nonblocking-io-73838876&quot;&gt;Reactive Web - Servlet &amp;amp; Async, Non-blocking I/O&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://workwiththebest.intraway.com/white-paper/Testing-Reactive-Microservices-With-Spring-Webflux/&quot;&gt;Testing Reactive Microservices With Spring-Webflux - Work with the Best&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://spring.io/blog/2019/04/12/going-reactive-with-spring-coroutines-and-kotlin-flow&quot;&gt;Going Reactive with Spring, Coroutines and Kotlin Flow &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring</category>
      <category>blocking</category>
      <category>eventdriven</category>
      <category>NIO</category>
      <category>nonblocking</category>
      <category>r2dbc</category>
      <category>reactive</category>
      <category>Reactor</category>
      <category>Spring</category>
      <category>SpringBoot2</category>
      <category>Webflux</category>
      <author>AlwaysPr</author>
      <guid isPermaLink="true">https://alwayspr.tistory.com/44</guid>
      <comments>https://alwayspr.tistory.com/44#entry44comment</comments>
      <pubDate>Sat, 22 Jun 2019 11:16:29 +0900</pubDate>
    </item>
  </channel>
</rss>