<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>추측보다는 측정</title>
    <link>https://vixxcode.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 12 Jul 2026 01:12:37 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>글을 쓰는 개발자</managingEditor>
    <image>
      <title>추측보다는 측정</title>
      <url>https://tistory1.daumcdn.net/tistory/4369219/attach/ec65ef512baf4029a5c95888b72bd540</url>
      <link>https://vixxcode.tistory.com</link>
    </image>
    <item>
      <title>[Jenkins Pipeline] Environment Setting</title>
      <link>https://vixxcode.tistory.com/276</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;간혹 Jenkins pipeline 내에서 환경변수를 저장하고 싶을 때가 있다.&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;webhook 으로부터 &quot;example,hello,world&quot; 와 같이 &quot;,&quot;가 seperator 형식인 String으로 전달되었다고 가정하자.&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;이 때, 배열로 변환하고 다음 stage에서 사용해야하는 상황에서 넘기는 방법은 다양하지만 가장 대표적인 방법은 환경변수로 저장하는 방법이 있다.&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;pre id=&quot;code_1683446633220&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;stage('set env') {
	steps {
    	script {
            def list = value.split(&quot;,&quot;)
            env.MY_LIST = list // &amp;lt;-- 환경변수에 저장하는 방법
        }
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;env.YOUR_ENV_NAME = VALUE&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JVM/Jenkins</category>
      <category>Jenkins</category>
      <author>글을 쓰는 개발자</author>
      <guid isPermaLink="true">https://vixxcode.tistory.com/276</guid>
      <comments>https://vixxcode.tistory.com/276#entry276comment</comments>
      <pubDate>Sun, 7 May 2023 17:05:19 +0900</pubDate>
    </item>
    <item>
      <title>Jenkins Pipeline Docker Usage</title>
      <link>https://vixxcode.tistory.com/275</link>
      <description>&lt;h1&gt;Plugin Install&lt;/h1&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;좌측에 &lt;b&gt;Jenkins 관리&lt;/b&gt; 클릭&lt;/li&gt;
&lt;li&gt;System Configuration &amp;gt; 플러그인 관리 클릭&lt;/li&gt;
&lt;li&gt;좌측 Avaulable plugins 클릭 후 검색창에 &lt;b&gt;Docker API Plugin&lt;/b&gt; 타이핑&lt;/li&gt;
&lt;li&gt;checkbox 클릭 후 설치 진행&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Docker Pipeline CLI&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Credentials&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 Docker Pipeline CLI 를 쓰는 이유는 사내 docker registry 혹은 docker hub 에 이미지를 푸시하기 위해 사용된다. 그렇기에 해당 registry에 접근하기 위한 username &amp;amp; password 등록이 필요하다.&lt;/p&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;Username&lt;/li&gt;
&lt;li&gt;Password&lt;/li&gt;
&lt;li&gt;ID&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 존재하며 여기서 Username은 레지스트리 아이디, Password 는 레지스트리 비밀번호, ID는 젠킨스에서 사용할 키 이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;키 등록 방법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Manage Credentials&lt;/li&gt;
&lt;li&gt;System -&amp;gt; Global credentials &amp;lt;Default 일 경우&amp;gt; -&amp;gt; 우측에 Add Credentials 클릭 -&amp;gt; Kind ( Username with password) 설정 후 위에서 말한 Username, Password, ID 등록&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;pipeline&lt;/h2&gt;
&lt;pre class=&quot;flix&quot;&gt;&lt;code&gt;
stage('docker registry access') {
    steps {
        script {
            docker.withRegistry(&quot;&amp;lt;DOMAIN&amp;gt;&quot;,&quot;&amp;lt;YOUR_CREDENTIALS_ID&amp;gt;&quot;) { // -- 1
                def dockerImage = docker.build(&quot;&amp;lt;DOMAIN&amp;gt;/&amp;lt;REPOSITORY&amp;gt;/&amp;lt;IMAGE_NAME&amp;gt;:&amp;lt;TAG&amp;gt;&quot;, &quot; .&quot;) // -- 2
                dockerImage.push() // -- 3
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;withRegistry&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker registry 접근하기 위해서는 로그인이 필요한 상황인데 이를 도와주는 인터페이스이다.&lt;br /&gt;위에서 등록한 로그인하고자 하는 레지스트리 도메인은 좌측에, credentialsId 를 우측에 작성하면 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;docker.build(&quot;image 관련 정보 (이미지이름, 태그, 레지스트리 도메인, 레포지토리)&quot;, &quot;빌드 시 추가적인 arg 명령어를 작성할 수 있는 곳&quot;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략적으로 다시 설명하자면 좌측에는 이미지 이름과 태그 등 어떻게 등록할 것인가에 대한 내용을, 우측에는 추가적인 명령어를 작성하고자 했을 때 사용하면 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;3&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;dockerImage.push()&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build를 하게 되면 이미지 객체를 가지게 되는데 이에 대한 내용을 레지스트리에 등록하는 명령어이다.&lt;br /&gt;여기서 push 안에 force 파라미터에 대한 boolean 값을 설정할 수 있는데 이는 제대로 동작하지 않는 것 같다. 기본적으로 force = true 이며 false 로 진행해도 true로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 만일 force = true 의 동작을 미리 하고 싶다면 다음과 같이 구현하면 될 것 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;validation&lt;/h2&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;stage('validate') {
    steps {
        script {
            docker.withRegistry('&amp;lt;DOMAIN&amp;gt;','&amp;lt;YOUR_CREDENTIALS_ID&amp;gt;') {
                def valid = false
                try {
                    sh &quot;docker pull &amp;lt;IMAGE&amp;gt;:&amp;lt;TAG&amp;gt;&quot;
                } catch(Exception e) {
                    valid = true
                }
                if (!valid) {
                    error(&quot;already ${IMAGE}:${TAG} exists&quot;)
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원리는 간단하다. 푸쉬하고자 하는 image 와 tag 를 미리 레지스트리에 pull하여 해당 이미지 존재 유무를 판단하고 이에 대해서 error를 일으킬 것인지 아니면 그래도 동작하게 할 것인지 돌아가게 하면 된다.&lt;/p&gt;</description>
      <category>JVM/Jenkins</category>
      <category>CLI</category>
      <category>docker</category>
      <category>Jenkins</category>
      <author>글을 쓰는 개발자</author>
      <guid isPermaLink="true">https://vixxcode.tistory.com/275</guid>
      <comments>https://vixxcode.tistory.com/275#entry275comment</comments>
      <pubDate>Wed, 3 May 2023 08:30:18 +0900</pubDate>
    </item>
    <item>
      <title>[Reflections] 하위 클래스를 어떻게 하면 가져올 수 있을까?</title>
      <link>https://vixxcode.tistory.com/274</link>
      <description>&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;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;라면 레시피 관련 클래스가 있다고 생각해봅시다.&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;- 빨간 국물 라면&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;- 비빔면&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;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;- 삼양라면&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;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;pre id=&quot;code_1679460917643&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@RequiredArgsConstructor
public enum RamenRecipe {
    RED_SHIN(RamenType.RED, Brand.N, &quot;신라면&quot;),
    // ...
    RED_SAM(RamenType.RED, Brand.S, &quot;삼양라면&quot;);
    private final RamenType type;
    private final Brand brand;
    private final String description;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 적을 때는 별 문제가 없으며 description 이란 필드가 단순히 설명으로 되어 있으니 상관은 없지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 description 대신&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- receipe의 내용을 담고 있는 클래스를 맵핑&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- suppier를 통해 라면을 생성하는 람다식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등 다양한 옵션이 붙게 되면 하나 하나의 enum의 크기가 커지게 되고 이러한 갯수가 많아지면 관리하기 힘들어진다는 단점이 존재합니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;enum 필드에 대한 getterMethod 인터페이스 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1679462098602&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Recipe {
    RamenType getType();

    Brand getBrand();

    String getDescription();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;각 관심사에 맞는 enum 클래스 생성&lt;/h3&gt;
&lt;pre id=&quot;code_1679462136539&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@RequiredArgsConstructor
public enum SamRecipe implements Recipe{
    RED_SAM_1(RamenType.RED, Brand.S, &quot;삼양라면&quot;),
    RED_SAM_2(RamenType.RED, Brand.S, &quot;삼양라면&quot;),
    RED_SAM_3(RamenType.RED, Brand.S, &quot;삼양라면&quot;),
    RED_SAM_4(RamenType.RED, Brand.S, &quot;삼양라면&quot;);
    private final RamenType type;
    private final Brand brand;
    private final String description;
}&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_1679462148888&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@RequiredArgsConstructor
public enum ShinRecipe implements Recipe{
    RED_SHIN_0(RamenType.RED, Brand.N, &quot;신라면&quot;),
    RED_SHIN_1(RamenType.RED, Brand.N, &quot;신라면&quot;),
    RED_SHIN_2(RamenType.RED, Brand.N, &quot;신라면&quot;),
    RED_SHIN_3(RamenType.RED, Brand.N, &quot;신라면&quot;),
    RED_SHIN_4(RamenType.RED, Brand.N, &quot;신라면&quot;);
    private final RamenType type;
    private final Brand brand;
    private final String description;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;aggregation class 구성&lt;/h3&gt;
&lt;pre id=&quot;code_1679462729662&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class RecipeAggregation {
    public static Collection&amp;lt;Recipe&amp;gt; values;
    private static final String PACKAGE_NAME = &quot;com.vixx.springground.ramen&quot;;
    private static final Reflections reflections = new Reflections(PACKAGE_NAME);
    static {
        final List&amp;lt;Recipe&amp;gt; recipes = new LinkedList&amp;lt;&amp;gt;();
        reflections.getSubTypesOf(Recipe.class).forEach(clazz -&amp;gt; recipes.addAll(Arrays.asList(clazz.getEnumConstants())));
        values = recipes;
    }
}&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트&lt;/h3&gt;
&lt;pre id=&quot;code_1679462754663&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Test
    void name() {
        final Collection&amp;lt;Recipe&amp;gt; values = RecipeAggregation.values;
        Assertions.assertThat(values.size()).isEqualTo(9);
        values.forEach(System.out::println);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1679462764047&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RED_SAM_1
RED_SAM_2
RED_SAM_3
RED_SAM_4
RED_SHIN_0
RED_SHIN_1
RED_SHIN_2
RED_SHIN_3
RED_SHIN_4&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;읽으시면서 부족한 점이 있거나 수정할 부분이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>JVM/JAVA</category>
      <author>글을 쓰는 개발자</author>
      <guid isPermaLink="true">https://vixxcode.tistory.com/274</guid>
      <comments>https://vixxcode.tistory.com/274#entry274comment</comments>
      <pubDate>Wed, 22 Mar 2023 14:04:04 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Thread 이해해보기</title>
      <link>https://vixxcode.tistory.com/269</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;글을 앞서 소개하기 앞서서 글을 적는 데 도움이 되었던 사이트 링크를 먼저 남깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://bugs.openjdk.org/browse/JDK-8277131&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://bugs.openjdk.org/browse/JDK-8277131&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/issues/23443&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/spring-projects/spring-framework/issues/23443&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://spring.io/blog/2022/10/11/embracing-virtual-threads&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://spring.io/blog/2022/10/11/embracing-virtual-threads&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;Java Thread 의 변화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loom Project 가 JDK 19 에 들어가면서 Virtual Thread, Concurrency 에 대한 내용이 추가가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 버전에서의 Java 에서는 Virtual Thread 가 아닌 OS Thread를 생성하는 형식으로 구성되어 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Virtual Thread 특성은 가벼운 스레드로서 생성하는 비용을 많이 줄일 수 있다는 특성이 있으며 concurrent 애플리케이션의 높은 처리량이 관찰된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 이번 패치는 자바의 concurrency model을 바꾸는 것은 아니며 병행처리에 대한 자바 model을 바꾸는 것은 아니다.&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;p data-ke-size=&quot;size16&quot;&gt;기본적으로 자바는 멀티 스레드이며, 각각의 스레드는 지역 변수 저장하거나 메소드 call을 조정하기 위한 스택을 제공해줬다. 그 뿐만 아니라 Exception이 같은 스레드에서 나거나 했을 때 같은 context를 공유하며 개발자는 이를 통해 어떤 일이 일어났는 지 스레드의 call stack을 debugging 하는 데 용이한 구조를 가졌다.&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;이 말을 thread-per-request style 이라 하며 이러한 방식은 이해하기 쉬우며, 프로그램하기 쉽다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_1.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boLNj9/btrRn01VyGt/okzuw1K8XOoMWmm37WnIek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boLNj9/btrRn01VyGt/okzuw1K8XOoMWmm37WnIek/img.png&quot; data-alt=&quot; Thread-Per-Request&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boLNj9/btrRn01VyGt/okzuw1K8XOoMWmm37WnIek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboLNj9%2FbtrRn01VyGt%2Fokzuw1K8XOoMWmm37WnIek%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;600&quot; height=&quot;424&quot; data-filename=&quot;edited_1.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; Thread-Per-Request&lt;/figcaption&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;서버 어플리케이션의 확장성은 Little's law 의해 처리되며, 해당 법칙은 latency, concurrency, 그리고 처리량과 관련되어 있다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 한 번 들어보자. 평균 latency가 50ms 이고 초당 200개의 요청을 처리한다고 했을 때 동시에 처리할 수 있는 갯수는 10개의 요청들이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;20 * x = 200&amp;nbsp;&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 20 은 50ms을 1초 기준으로 환산한 값이며 x는 Little's Law 에서의 람다 역할, 200은 L 을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 초당 처리량이 2000개, 20000개 점점 늘어날수록 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;불행하게도, JDK에 구현된 스레드는 OS 스레드로 구성되어 있어서 이용가능한 스레드의 수는 제한된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS 스레드는 비용이 비싸서 우리는 해당 스레드를 많이 생성할 수 없으며, 이러한 상황은 thread-per-style 과 부적절하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 숫자가 늘어남에 따라 CPU 나 네트워크 연결에 대하여 제한적이게 된다. 그래서 이러한 것을 다른 방식으로 풀어냈는데 우리가 흔히 아는 pooling 방식으로 스레드 생성에 대한 비용을 줄이고 스레드의 총 갯수를 증가하지 않는 방식으로 되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_2.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgdF1T/btrRn5PCC4I/oZUedwLJoaHyTrh87PGPAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgdF1T/btrRn5PCC4I/oZUedwLJoaHyTrh87PGPAK/img.png&quot; data-alt=&quot;Asynchronous&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgdF1T/btrRn5PCC4I/oZUedwLJoaHyTrh87PGPAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgdF1T%2FbtrRn5PCC4I%2FoZUedwLJoaHyTrh87PGPAK%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;600&quot; height=&quot;424&quot; data-filename=&quot;edited_2.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Asynchronous&lt;/figcaption&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;하지만 우리는 안다. thread-per-request 방식 말고 다른 방식이 있다는 것을 바로 asynchronous style 방식이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;asynchrous 방식은 thread-per-style 과 같이 하나의 스레드로 처음부터 끝까지 하는 것에 비해, &amp;nbsp;fine-grained sharing of threads(스레드의 세분화된 공유)는 오직 수행할 때에만 스레드를 수행하며, 이러한 방식은 많은 스레드 사용 없이 많은 수의 요청건을 처리한다.&amp;nbsp; OS Thread 부족함으로 인한 처리량의 제한은 제거되지만, 비용은 비싸다. 왜냐하면 흔히 아는 asynchronous style을 알아야 한다. 이 말은 람다 표현식, 순차적 파이프 형식을 구성해야 한다.&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;비동기 방식은 각 순간마다 다른 스레드를 사용하기 때문에 하나의 스레드에 여러 개의 요청에 대한 콜 스택이 쌓이게 된다. 이러한 방식은 Stack trace가 불 필요하게 된다는 것을 의미한다. 이유는 디버깅하기 힘든 구조를 가졌기 때문이다.&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 thread가 도입된 방식이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_3.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCubQ1/btrRlselfmD/qDA2RlN6LaQnQCJWSBV1M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCubQ1/btrRlselfmD/qDA2RlN6LaQnQCJWSBV1M1/img.png&quot; data-alt=&quot;Virtual Thread&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCubQ1/btrRlselfmD/qDA2RlN6LaQnQCJWSBV1M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCubQ1%2FbtrRlselfmD%2FqDA2RlN6LaQnQCJWSBV1M1%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;600&quot; height=&quot;424&quot; data-filename=&quot;edited_3.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Virtual Thread&lt;/figcaption&gt;
&lt;/figure&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;운영체제는 한정된 램보다 더 많은 크기의 메모리가 존재한다고 생각하게 하며 이러한 방식은 Java 런타임 때 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 thread는 비동기 방식과 유사하게 OS Thread를 연산할 때에만 소비한다.&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;그래서 이전과 다르게 주의할 점이 있다. 바로 pooling을 해서는 안된다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 virtual thread는 요청마다 생성된다. 대부분 스레드들은 짧은 수명과 얇은 콜 스택을 가진다. 사용 예는 http 요청이나 JDBC Query Call 이다. 이와 다르게 Platform Thread는 무겁고 비싸며 반드시 pooling 방식을 유지해야한다.&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;blockquote data-ke-style=&quot;style3&quot;&gt;One OS Thread = Many Virtual Thread at time&lt;br /&gt;즉 하나의 OS Thread는 여러개의 Virtual Thread와 연관되며 공유하는 관계다.&lt;/blockquote&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;virtual thread는 OS Thread보다 결코 빠른 것은 아니다.&lt;/li&gt;
&lt;li&gt;동시에 많은 요청을 처리해야하는 상황에서 유리하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pooling 하지 않는다는 점에서도 많은 이점을 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 virtual thread는 스레드를 공유하면서 사용하지 않고 요청마다 생성하는 형태로 사용하기 때문에 ThreadLocal에 값을 저장하여 사용할 때 다른 요청에서 쓸 걱정하지 않아도 된다는 큰 이점이 있기 때문이다.&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 thread의 스택은 자바의 GC heap에 저장된다. 애플리케이션이 동작하면서 해당 스택은 줄거나 늘어날 수 있다.&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 thread는 많은 상황에서 스택을 변경하고 재사용할 수 있는 반면 asynchronous pipeline은 항상 새 객체를 할당해야 하므로 virtual 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 thread 스택의 내부 표현이 훨씬 더 간결해질 것으로 예상한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Unlike platform thread stacks, virtual thread stacks are not GC roots, so the references contained in them are not traversed in a stop-the-world pause by garbage collectors, such as G1, that perform concurrent heap scanning.&lt;br /&gt;&lt;br /&gt;virtual thread stack은 가비지 수집기에 의한 stop the world 일시중지에서 순회되지 않는다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;nbsp;since the virtual thread can never be interrupted or unblocked. Of course, the virtual thread will not be garbage collected if it is running or if it is blocked and could ever be unblocked.&lt;br /&gt;&lt;br /&gt;virtual thread는 결코 interrupt 나 unblock을 일으키지 않는다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Little's Law&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;nbsp;L = &amp;lambda;W&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;L : 총 처리량&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lambda;: 대기열에 있는 요청 수&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;W: 대기 시간&amp;nbsp;&lt;/b&gt;&lt;/p&gt;</description>
      <category>JVM/JAVA</category>
      <category>Java</category>
      <category>thread</category>
      <author>글을 쓰는 개발자</author>
      <guid isPermaLink="true">https://vixxcode.tistory.com/269</guid>
      <comments>https://vixxcode.tistory.com/269#entry269comment</comments>
      <pubDate>Mon, 10 Oct 2022 08:34:07 +0900</pubDate>
    </item>
    <item>
      <title>[Java] record 이해해보기</title>
      <link>https://vixxcode.tistory.com/266</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;쓰는 방식&lt;/h2&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public record MyRecordVo(
        Long id,
        String name,
        String phone
) {
    public String region() {
        return Arrays.stream(phone.split(&quot;-&quot;)).findFirst().orElseThrow(RuntimeException::new);
    }
}&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;record로 설정한다.&lt;/li&gt;
&lt;li&gt;( ) 괄호 안에 원하는 타입과 필드 이름을 설정한다.&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;특성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Record.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Record.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-8.10&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-8.10&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본적으로 정의된 타입과 이름에 따라 public 접근제어자와 return type 그리고 method name이 정해진다.&lt;/li&gt;
&lt;li&gt;해당 값들은 immutable 하다 ( 즉 Vo 에 쓰기 적절하다)&lt;/li&gt;
&lt;li&gt;암묵적으로 final 제어자가 붙게 된다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;동일한 키워드가 레코드 선언의 수정자로 두 번 이상 나타나거나 레코드 선언에 public, protected 및 private 중 하나 이상의 액세스 수정자가 있는 경우 컴파일 시간 오류&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;equals, hashCode, toString 들은 멤버 변수에 맞게 정의된다.&lt;/li&gt;
&lt;li&gt;생성자를 정확히 하나만 지정하는 이유는 해당 변수 값들을 validate 하기 위함이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;public void record(MyRecordVo vo) {
    log.info(&quot;record id = {}, name = {}, phone = {}&quot;, vo.id(), vo.name(), vo.phone());
    Class&amp;lt;?&amp;gt; superclass = vo.getClass().getSuperclass();
    log.info(&quot;classes = {}&quot;, superclass);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;record로 정의한 java의 경우 상위 클래스가 뭔지 알아보기 위해 찍어봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;18:20:51.011&amp;nbsp;[Test&amp;nbsp;worker]&amp;nbsp;INFO&amp;nbsp;co&lt;a href=&quot;http://m.playground.springplayground.java.RecordService&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;m.playground.springplayground.java.RecordService&lt;/a&gt;&amp;nbsp;-&amp;nbsp;classes&amp;nbsp;=&amp;nbsp;class&amp;nbsp;java.lang.Record&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 클래스들은 extends를 하지 않는 이상 superClass는 Object 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 record로 설정한 경우 &lt;span style=&quot;background-color: #fcfcfc; color: #666666;&quot;&gt;&amp;nbsp;java.lang.Record &lt;span style=&quot;color: #000000;&quot;&gt;이다&lt;/span&gt;&lt;/span&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;span style=&quot;background-color: #fcfcfc; color: #666666;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Record&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public abstract class Record {
    
    protected Record() {}

    
    @Override
    public abstract boolean equals(Object obj);

    
    @Override
    public abstract int hashCode();

    
    @Override
    public abstract String toString();
}&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;p data-ke-size=&quot;size16&quot;&gt;그렇기에 record로 설정한 경우 extends 를 사용할 수 없다는 것을 유념해야 한다.&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;위에서 말한 것 처럼 Value Object로 사용할 경우 이용하기에 적절하다. 그 이유는 VO 의 경우 값이 바뀌면 안되고, 수정해서는 안되는 것이 기본이므로 왠만한 Vo 의 경우 record를 사용하기에 적절하다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>JVM/Spring</category>
      <category>Record</category>
      <author>글을 쓰는 개발자</author>
      <guid isPermaLink="true">https://vixxcode.tistory.com/266</guid>
      <comments>https://vixxcode.tistory.com/266#entry266comment</comments>
      <pubDate>Thu, 6 Oct 2022 15:52:10 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Generic 에 대한 이해</title>
      <link>https://vixxcode.tistory.com/265</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Generic은 왜 도입이 되었고 이점은 무엇인가?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Oracle 참고&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/java/generics/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/javase/tutorial/java/generics/index.html&lt;/a&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Fortunately, some bugs are easier to detect than others. Compile-time bugs, for example, can be detected early on; you can use the compiler's error messages to figure out what the problem is and fix it, right then and there. Runtime bugs, however, can be much more problematic; they don't always surface immediately, and when they do, it may be at a point in the program that is far removed from the actual cause of the problem.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서에서 말하길 Generic 도입으로 인하여 Compile 시점에서 에러 또는 버그를 찾을 수 있게 되었고, 이로 인해 개발적 생산성이 높아지는 기대를 얻을 수 있었다.&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;그렇기에 Generic의 도입은 많은 버그를 잡아주는 포인트였다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴파일 에러를 발견할 수 있는 이점&lt;/li&gt;
&lt;li&gt;casting 하지 않아도 된다는 이점&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;Convention&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;E - Element (used extensively by the Java Collections Framework)&lt;/li&gt;
&lt;li&gt;K - Key&lt;/li&gt;
&lt;li&gt;N - Number&lt;/li&gt;
&lt;li&gt;T - Type&lt;/li&gt;
&lt;li&gt;V - Value&lt;/li&gt;
&lt;li&gt;S,U,V etc. - 2nd, 3rd, 4th types&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;Type Parameter 와 Type Arguments의 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 Type Parameter와 Type Arguments를 혼용하여 쓰인다. 하지만 엄연히 차이점이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Box&amp;lt;T&amp;gt; 에서 T는 Type Parameter 이고 Box&amp;lt;String&amp;gt; 에서 String 은 Type Arguments 이다.&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;h2 data-ke-size=&quot;size26&quot;&gt;static, 일반 배열에 사용할 수 없다.&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 쓸 수 없을까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓸 수 없는 이유는 generic은 인스턴스변수로 간주되기 때문에 static 와 같이 인스턴스가 아닌 경우 사용할 수가 없다.&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;배열 또한 사용할 수 없는 것은 new 연산자 때문인데, 컴파일 시점에 generic type을 알아야 하기 때문이다.&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;다만 generic 배열 타입의 참조변수를 선언하는 것은 가능하다. generic 배열을 생성하는 것이 불가능하다는 것을 말하는 것이다.&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;h2 data-ke-size=&quot;size26&quot;&gt;? extends T, ? super T&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java 코드를 보다 보면 위와 같은 형식을 자주 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extends 와 super 연산자는 우리가 아는 것과 비슷하게 동작을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;? extends T&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;T 와 그 sub class 에 대해서만 접근을 가능하게 하는 역할을 한다.&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6ewCM/btrOGvb6NzK/FjfTruR7yQDfHSUly6Ztmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6ewCM/btrOGvb6NzK/FjfTruR7yQDfHSUly6Ztmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6ewCM/btrOGvb6NzK/FjfTruR7yQDfHSUly6Ztmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6ewCM%2FbtrOGvb6NzK%2FFjfTruR7yQDfHSUly6Ztmk%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;512&quot; height=&quot;197&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDx915/btrODeinrl8/ADs8WpkdFz0Sb8mYykNxqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDx915/btrODeinrl8/ADs8WpkdFz0Sb8mYykNxqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDx915/btrODeinrl8/ADs8WpkdFz0Sb8mYykNxqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDx915%2FbtrODeinrl8%2FADs8WpkdFz0Sb8mYykNxqk%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;512&quot; height=&quot;370&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시와 같이 &amp;lt;T extends CheckType&amp;gt; 으로 설정하면 CheckType 포함하여 그 하위클래스들만 접근할 수 있게 된다. 그렇기에 Object를 넣었을 때 Compile Error가 나는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;? super T&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/java/generics/lowerBounded.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/javase/tutorial/java/generics/lowerBounded.html&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;우리는 예를 들어 숫자를 더하는 함수를 만드는 데 Integer 뿐만 아니라 Number 까지 허용하는 것을 구현하고 싶을 때 사용할 수 있다.&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;우리가 흔히 볼 수 있는 곳은 sort 이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;static &amp;lt;T&amp;gt; void sort(List&amp;lt;T&amp;gt; list, Comparator&amp;lt;? super T&amp;gt; c)&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Generic Method&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드의 선언부에 generic type이 선언된 메서드를 generic method라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;331&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/exodgG/btrOC4UAxVU/eQQKxJofCFzkD6ws9vljp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/exodgG/btrOC4UAxVU/eQQKxJofCFzkD6ws9vljp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/exodgG/btrOC4UAxVU/eQQKxJofCFzkD6ws9vljp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FexodgG%2FbtrOC4UAxVU%2FeQQKxJofCFzkD6ws9vljp0%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;631&quot; height=&quot;331&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;331&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 경우에는 접근제어자가 생략되어 있는데 접근 제어자를 쓰고나서 generic method에서 사용하고자 하는 generic을 선언하면 된다. 여기서 선언된 generic의 경우에는 클래스에서 선언한 generic과 별개로 움직인다는 것만 알면 된다.&lt;/p&gt;</description>
      <category>JVM/JAVA</category>
      <category>Generic</category>
      <category>generic method</category>
      <category>Java</category>
      <author>글을 쓰는 개발자</author>
      <guid isPermaLink="true">https://vixxcode.tistory.com/265</guid>
      <comments>https://vixxcode.tistory.com/265#entry265comment</comments>
      <pubDate>Thu, 6 Oct 2022 15:51:52 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Annotation 의 동작의 이해</title>
      <link>https://vixxcode.tistory.com/264</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;a href=&quot;https://docs.oracle.com/javase/tutorial/java/annotations/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/javase/tutorial/java/annotations/&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Java Annotation&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Annotations, a form of metadata, provide data about a program that is not part of the program&lt;/blockquote&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;컴파일러를 위한 정보&lt;/b&gt; - 어노테이션은 컴파일러가 에러나 supress warnings를 찾기 위해 사용된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Compile-time 그리고 deployment-time 가공과정&lt;/b&gt; - 소프트웨어 도구들은 어노테이션 정보들을 활용하여 코드, XML, JSON 등을 생성한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;런타임 처리&lt;/b&gt; - 몇몇 어노테이션은 런타임 시간에 검사되는 데 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Declaring an Annotation Type&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 프로젝트를 진행하면서 어떤 클래스에 중요한 정보를 저장해야 할 때 다음과 같이 표현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1667115148652&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MyClass {
// Author: Park Suhan
// Date: 2022/10/01
// Current Version: 1.5.6
// Last Modified: 2022/10/30
// By: Park Suhan
// Reviewers: Mike, Sandy, Alice
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 어노테이션을 가지고 메타데이터를 더한다면, 다음과 같이 정의할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1667115310486&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@interface ClassRecord {
	String author();
    String date();
    double currentVersion() default 1.0;
    String lastModified() default &quot;N/A&quot;;
    String lastModifiedBy() default &quot;N/A&quot;;
    String[] reviewers();
}&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;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 class=&quot;nix&quot;&gt;&lt;code&gt;@ClassRecord(
        author = &quot;Park Suhan&quot;,
        date = &quot;2022/10/01&quot;,
        currentVersion = 1.56,
        lastModified = &quot;2022/10/30&quot;,
        lastModifiedBy = &quot;Park Suhan&quot;,
        reviewers = {&quot;Alice&quot;, &quot;Sandy&quot;, &quot;Mike&quot;}
)
public class MyClass {
}&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;이 때 약간의 팁을 주자면 해당 어노데이션은 문서의 성향이 있으며 Javadoc을 원한다면 다음과 같이 추가해주면 더 좋다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Documented
public @interface ClassRecord {
    // 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Predefined Annotation Types&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Deprecated, @Override, @SuppressWarnings&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;@Deprecated&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Deprecated 어노테이션은 해당 클래스 또는 메소드를 더 이상 사용하지 않을 것이라고 표현하는 메타데이터이다. 컴파일러가&amp;nbsp; @Deprecated 어노테이션 표시된 클래스, 메소드 등을 프로그램에서 사용할 때 사용하지 말라고 경고문을 날린다.&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;@Override&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Override 어노테이션은 컴파일러에게 부모 클래스로부터 상속받은 메소드라는 정보를 준다. @Override 어노테이션은 의무적으로 사용해야 하는 것은 아니지만, 사용할 경우 에러를 예방할 수 있다.&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;@SuppressWarnings&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SuppressWarnings 어노테이션은 특정 경고 메시지를 없애는 데 사용된다.&amp;nbsp; 해당 사용되는 곳은 컬렉션 프레임워크를 보면 심심치 않게 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@SuppressWarnings(&quot;unchecked&quot;)
public &amp;lt;T&amp;gt; T[] toArray(T[] a) {
    if (a.length &amp;lt; size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length &amp;gt; size)
        a[size] = null;
    return a;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 컴파일러 경고는 하나의 카테고리에 속하게 되는데 &quot;unchecked&quot;, &quot;deprecation&quot;이 이에 속한다.&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;@SafeVarags&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SafeVarags 어노테이션은 메소드나 생성자에 지원되는 어노테이션으로 코드가&amp;nbsp;varargs&amp;nbsp;매개변수에&amp;nbsp;대해&amp;nbsp;잠재적으로&amp;nbsp;안전하지&amp;nbsp;않은&amp;nbsp;작업을&amp;nbsp;수행하지&amp;nbsp;않음을&amp;nbsp;나타냅니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/java-safevarargs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.baeldung.com/java-safevarargs&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;@FunctionalInterface&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FunctionalInterface 어노테이션은 람다를 사용할 때 컴파일 에러를 체크하기 위해 사용되는 어노테이션으로서 메소드를 하나만 정의하도록 설정하는 어노테이션입니다. 두개 이상이 정의되었을 때 에러 표시가 나면서 실수를 줄일 수 있게 해줍니다.&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;Annotation을 지원하는 Annotation&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Rentention&lt;/h3&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;RetentionPolicy.SOURCE&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;ndash; 소스 코드 상까지는 유지되며 그 이후 (컴파일러 이후) 부터는 효력이 없음&lt;/li&gt;
&lt;li&gt;RetentionPolicy.CLASS&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;ndash; 컴파일 시점까지 유지되며,&amp;nbsp; 그 이후 부터는 무시(Java Virtual Machine (JVM)에 의해)&lt;/li&gt;
&lt;li&gt;RetentionPolicy.RUNTIME&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;ndash; JVM시점 부터 유지되며 런타임때 사용가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Documented&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaDoc 관련 문서화할 때 사용되는 어노테이션&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;@Target&lt;/h3&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;ElementType.ANNOTATION_TYPE&lt;span&gt;&amp;nbsp; -&amp;gt;&lt;/span&gt;&amp;nbsp;annotation type.&lt;/li&gt;
&lt;li&gt;ElementType.CONSTRUCTOR&lt;span&gt; -&amp;gt;&lt;/span&gt;&amp;nbsp;constructor.&lt;/li&gt;
&lt;li&gt;ElementType.FIELD&lt;span&gt; -&amp;gt;&lt;/span&gt;&amp;nbsp;field or property.&lt;/li&gt;
&lt;li&gt;ElementType.LOCAL_VARIABLE&lt;span&gt; -&amp;gt;&lt;/span&gt;&amp;nbsp;local variable.&lt;/li&gt;
&lt;li&gt;ElementType.METHOD&lt;span&gt; -&amp;gt;&lt;/span&gt;&amp;nbsp;method-level annotation.&lt;/li&gt;
&lt;li&gt;ElementType.PACKAGE&lt;span&gt; -&amp;gt;&lt;/span&gt;&amp;nbsp;package declaration.&lt;/li&gt;
&lt;li&gt;ElementType.PARAMETER&lt;span&gt; -&amp;gt;&lt;/span&gt;&amp;nbsp;parameters of a method.&lt;/li&gt;
&lt;li&gt;ElementType.TYPE&lt;span&gt; -&amp;gt;&lt;/span&gt;&amp;nbsp;any element of a class.&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;@Inherited&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;h3 data-ke-size=&quot;size23&quot;&gt;@Repeatable&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;h4 data-ke-size=&quot;size20&quot;&gt;Name.java&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 어노테이션은 값을 저장하는 어노테이션으로 중복해서 사용할 수 있는 어노테이션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복된 값을 가져올 때는 Names.class를 사용하라는 표시로 @Repeatable를 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Target(ElementType.TYPE)
@Repeatable(Names.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {
    String value();
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Names.java&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Name 어노테이션들을 관리하는 어노테이션으로 사용하는 코드에서 해당 어노테이션을 통해 정보를 가져온다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Names {
    Name[] value();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Name(&quot;A&quot;)
@Name(&quot;B&quot;)
@Name(&quot;C&quot;)
public class TargetName {
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;public class Runner {
    public static void main(String[] args) {
        TargetName targetName = new TargetName();
        Names names = targetName.getClass().getAnnotation(Names.class);
        Name[] nameArray = targetName.getClass().getAnnotationsByType(Name.class);
        Name[] values = names.value();
        for (Name value : values) {
            System.out.println(value);
        }
        System.out.println(Arrays.toString(nameArray));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;@com.playground.springplayground.annotation.Name(&quot;A&quot;) @com.playground.springplayground.annotation.Name(&quot;B&quot;) @com.playground.springplayground.annotation.Name(&quot;C&quot;) [@com.playground.springplayground.annotation.Name(&quot;A&quot;), @com.playground.springplayground.annotation.Name(&quot;B&quot;), @com.playground.springplayground.annotation.Name(&quot;C&quot;)]&lt;/blockquote&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;Name을 관리하는 Names를 통해 가져오는 방법&lt;/li&gt;
&lt;li&gt;Name을 지정하여 Array로 받는 방법&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;Spring&amp;nbsp; Annotation&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@SpringBootApplication&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
   
   @AliasFor(annotation = EnableAutoConfiguration.class)
   Class&amp;lt;?&amp;gt;[] exclude() default {};
   
   @AliasFor(annotation = EnableAutoConfiguration.class)
   String[] excludeName() default {};
   
   @AliasFor(annotation = ComponentScan.class, attribute = &quot;basePackages&quot;)
   String[] scanBasePackages() default {};

   @AliasFor(annotation = ComponentScan.class, attribute = &quot;basePackageClasses&quot;)
   Class&amp;lt;?&amp;gt;[] scanBasePackageClasses() default {};

   
   @AliasFor(annotation = ComponentScan.class, attribute = &quot;nameGenerator&quot;)
   Class&amp;lt;? extends BeanNameGenerator&amp;gt; nameGenerator() default BeanNameGenerator.class;

   @AliasFor(annotation = Configuration.class)
   boolean proxyBeanMethods() default true;

}&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;@Target(ElementType.TYPE)&lt;/h4&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;h4 data-ke-size=&quot;size20&quot;&gt;@Retention(RetentionPolicy.RUNTIME)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM 동작하는 시점에 적용되며, 흔히 런타임 시점에서 동작한다고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;@Inherited&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용된 어노테이션의 A 클래스가 존재한다고 했을 때 A 클래스를 상속한 B 클래스에서 A 클래스에 적용된 메타 데이터를 사용할 수 있게 해주는 것이 @Inherited 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;@Inherited 를 적용한 클래스&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyInherit {
    String value() default &quot;hi&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;@Inherited 를 적용하지 않은 클래스&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public @interface NoInherit {
    String value() default &quot;noInherit&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;size18&quot;&gt;적용한 클래스&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@NoInherit
@MyInherit
public class AClass {
}&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;size18&quot;&gt;테스트할 클래스&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class BClass extends AClass{
}
&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실행 결과&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;public class Runner {
    public static void main(String[] args) {
        final BClass bClass = new BClass();
        Annotation[] annotations = bClass.getClass().getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;@com.playground.springplayground.annotation.inherited.MyInherit(&quot;hi&quot;)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보시는 바와 같이 @Inherited 어노테이션을 적용한 곳에서만 값을 가져올 수 있는 것을 볼 수가 있다.&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;@AliasFor&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@AliasFor 에 정의된 어노테이션의 메타데이터 값을 가져오는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@AliasFor(annotation = EnableAutoConfiguration.class)
Class&amp;lt;?&amp;gt;[] exclude() default {};&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;&quot;해당 부분은 SpringBootApplication 어노테이션에 있는 exclude method의 값은 EnableAutoConfiguration 어노테이션의 exclude 값의 역할을 부여한다.&quot; 라고 이해하면 된다.&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;h4 data-ke-size=&quot;size20&quot;&gt;MyRequestMapping.java&lt;/h4&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping
public @interface MyRequestMapping {
    @AliasFor(annotation = RequestMapping.class, attribute = &quot;value&quot;)
    String route() default &quot;&quot;;
    @AliasFor(annotation = RequestMapping.class, attribute = &quot;method&quot;)
    RequestMethod[] define() default {};

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MyRequestMapping은 RequestMapping의 일부분을 재정의한 어노테이션으로서 RequestMapping의 value 부분은 route로 재정의하였으며, method 부분은 define을 재정의하였다.&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;@RestController
public class AnnoController {
    @MyRequestMapping(define = RequestMethod.GET, route = &quot;/web/annotation&quot;)
    public String view() {
        return &quot;view&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;&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;해당 부분을 테스트 해보면 view가 정상적으로 찍히는 것을 알 수 있다.&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;나중에 하면서 부족하거나 필요한 지식은 추가해나갈 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽어주셔서 감사합니다.&lt;/p&gt;</description>
      <category>JVM/JAVA</category>
      <category>Annotation</category>
      <category>Java</category>
      <author>글을 쓰는 개발자</author>
      <guid isPermaLink="true">https://vixxcode.tistory.com/264</guid>
      <comments>https://vixxcode.tistory.com/264#entry264comment</comments>
      <pubDate>Thu, 6 Oct 2022 15:51:35 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] @EventListener</title>
      <link>https://vixxcode.tistory.com/263</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;참고 글: &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/event/EventListener.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/event/EventListener.html&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구조 파악하기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없는 다이어그램.drawio.svg&quot; data-origin-width=&quot;1740&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZv28q/btrN4D4RCdZ/2WLzbeCWfBUfA2Y832PLW1/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZv28q/btrN4D4RCdZ/2WLzbeCWfBUfA2Y832PLW1/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZv28q/btrN4D4RCdZ/2WLzbeCWfBUfA2Y832PLW1/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZv28q%2FbtrN4D4RCdZ%2F2WLzbeCWfBUfA2Y832PLW1%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;860&quot; height=&quot;350&quot; data-filename=&quot;제목 없는 다이어그램.drawio.svg&quot; data-origin-width=&quot;1740&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@EventListener를 사용하는 방법은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;pubsub.svg&quot; data-origin-width=&quot;1051&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/54qbk/btrN5Gs081A/7qRLGUEEbSFmoOs2zM9NUk/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/54qbk/btrN5Gs081A/7qRLGUEEbSFmoOs2zM9NUk/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/54qbk/btrN5Gs081A/7qRLGUEEbSFmoOs2zM9NUk/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F54qbk%2FbtrN5Gs081A%2F7qRLGUEEbSFmoOs2zM9NUk%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;1051&quot; height=&quot;82&quot; data-filename=&quot;pubsub.svg&quot; data-origin-width=&quot;1051&quot; data-origin-height=&quot;82&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;EventPublisher&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class EventPublisher {
    private final ApplicationEventPublisher publisher;
    public void publish(PublicEvent event) {
        publisher.publishEvent(event);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;EventSubscriber&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class EventSubscriber {
    private final EventService service;

    @Async
    @EventListener
    public void subscribe(PublicEvent event) throws InterruptedException {
        service.doing(event);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SurfaceService&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Slf4j
@Service
@RequiredArgsConstructor
public class SurfaceService {
    private final EventPublisher publisher;

    public void surface(PublicEvent event) {
        log.info(&quot;start surface&quot;);
        publisher.publish(event);
        log.info(&quot;finish surface&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;호출 시&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;2022-10-09 20:51:01.160 INFO 14161 --- [nio-8080-exec-1] c.p.s.pubsub.SurfaceService : start surface 2022- 2022-10-09 20:51:01.161 INFO 14161 --- [nio-8080-exec-1] c.p.s.pubsub.SurfaceService : finish surface 2022-10-09 20:51:03.168 INFO 14161 --- [cTaskExecutor-1] c.p.s.pubsub.EventService : eventName = hi&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Async만 써도 되는 데 굳이 왜 Pub Sub을 따로 구성하는 이유가 뭘까?&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;Subscriber 는 A만 알면 되는데 그 이상의 정보를 넘기는 순간 애플리케이션 레이어 및 로직들이 꼬이게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 Publisher에서 특정 정보만 다듬어서 Subscriber에만 넘김으로서 느슨한 결합을 유지하며 레이어도 유지할 수 있게 된다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;simplepubsub.svg&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oL57a/btrOcfnCHMI/0i5FpdbbuBAwLVood8D9F0/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oL57a/btrOcfnCHMI/0i5FpdbbuBAwLVood8D9F0/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oL57a/btrOcfnCHMI/0i5FpdbbuBAwLVood8D9F0/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoL57a%2FbtrOcfnCHMI%2F0i5FpdbbuBAwLVood8D9F0%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;931&quot; height=&quot;306&quot; data-filename=&quot;simplepubsub.svg&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;306&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;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;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;</description>
      <category>JVM/Spring</category>
      <category>async</category>
      <category>EventListener</category>
      <category>Spring</category>
      <author>글을 쓰는 개발자</author>
      <guid isPermaLink="true">https://vixxcode.tistory.com/263</guid>
      <comments>https://vixxcode.tistory.com/263#entry263comment</comments>
      <pubDate>Thu, 6 Oct 2022 15:51:14 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] @Async 비동기로 처리하는 방법</title>
      <link>https://vixxcode.tistory.com/262</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;초기설정&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@SpringBootApplication
@EnableAsync
public class SpringplaygroundApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringplaygroundApplication.class, args);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로 @EnableAsync을 설정해야 한다. 그래야 비동기로 동작하는 데 필요한 Bean 들이 등록이 된다.&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;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Slf4j
@Service
@Async
public class AsyncService {
    public void myAsyncCall() throws InterruptedException {
        Thread.sleep(1000L);
        log.info(&quot;async call = {}&quot;, LocalDateTime.now());
    }
}&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Slf4j
@Service
@RequiredArgsConstructor
public class MyAsyncService {

    private final AsyncService asyncService;
    
    public void call() throws InterruptedException {
        log.info(&quot;start time = {}&quot;, LocalDateTime.now());
        asyncCall();
        log.info(&quot;end time = {}&quot;, LocalDateTime.now());
    }

    public void asyncCall() throws InterruptedException {
        asyncService.myAsyncCall();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 해당 Async 클래스를 사용하는 서비스를 호출했을 때 다음과 같은 로그가 남는다.&lt;/p&gt;
&lt;pre id=&quot;code_1664794088847&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2022-10-03 19:28:10.392  INFO 16905 start time = 2022-10-03T19:28:10.392891
2022-10-03 19:28:10.395  INFO 16905 end time = 2022-10-03T19:28:10.395125
2022-10-03 19:28:11.400  INFO 16905 async call = 2022-10-03T19:28:11.400748&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;서비스에 따라 ThreadPoolTaskExecutor 설정을 다르게 하고 싶을 수 있다.&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 class=&quot;reasonml&quot;&gt;&lt;code&gt;@Configuration
public class ThreadConfiguration {

    @Bean(name = &quot;first&quot;)
    public ThreadPoolTaskExecutor asyncFirstExecutor() {
        final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(4);
        executor.setThreadNamePrefix(&quot;first&quot;);
        executor.initialize();
        return executor;
    }

    @Bean(name = &quot;second&quot;)
    public ThreadPoolTaskExecutor asyncSecondExecutor() {
        final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(4);
        executor.setThreadNamePrefix(&quot;second&quot;);
        executor.initialize();
        return executor;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ThreadPoolTaskExecutor 설정 값은 이와 같이 구성할 수 있으며 추가적인 API는 검색을 통해 찾아보시면 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 해당 Bean이름을 편의를 위해 이름을 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 해당 이름을 @Async(&quot;first&quot;) 이와 같이 구성해주면 그에 대한 설정을 쓸 수가 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Slf4j
@Service
@Async(&quot;first&quot;)
public class AsyncService {
    public void myAsyncCall() throws InterruptedException {
        Thread.sleep(1000L);
        log.info(&quot;async call = {}&quot;, LocalDateTime.now());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1664794493767&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2022-10-03 19:28:31.543  INFO 16905 --- [nio-8080-exec-4] c.p.s.async.MyAsyncService               : start time = 2022-10-03T19:28:31.543265
2022-10-03 19:28:31.543  INFO 16905 --- [nio-8080-exec-4] c.p.s.async.MyAsyncService               : end time = 2022-10-03T19:28:31.543708
2022-10-03 19:28:32.551  INFO 16905 --- [        second2] c.p.s.async.SecondAsyncService           : async call2 = 2022-10-03T19:28:32.551425
2022-10-03 19:28:32.551  INFO 16905 --- [         first2] c.p.springplayground.async.AsyncService  : async call = 2022-10-03T19:28:32.551425&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그랬을 때 스레드 이름이 second 와 first 각각 다르게 찍히는 것을 볼 수가 있다.&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;h3 data-ke-size=&quot;size23&quot;&gt;@EnableAsync&lt;/h3&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
    Class&amp;lt;? extends Annotation&amp;gt; annotation() default Annotation.class;

    boolean proxyTargetClass() default false;

    AdviceMode mode() default AdviceMode.PROXY;

    int order() default Integer.MAX_VALUE;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 @Import를 통해 AsyncConfigurationSelector.class 빈 정보들을 가져온다.&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;AsyncConfigurationSelector&lt;/h3&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class AsyncConfigurationSelector extends AdviceModeImportSelector&amp;lt;EnableAsync&amp;gt; {
    private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = &quot;org.springframework.scheduling.aspectj.AspectJAsyncConfiguration&quot;;

    public AsyncConfigurationSelector() {
    }

    @Nullable
    public String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                return new String[]{ProxyAsyncConfiguration.class.getName()};
            case ASPECTJ:
                return new String[]{&quot;org.springframework.scheduling.aspectj.AspectJAsyncConfiguration&quot;};
            default:
                return null;
        }
    }
}&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;여기서 ProxyAsyncConfiguration.class 정보를 보도록 하자&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ProxyAsyncConfiguration&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Configuration(
    proxyBeanMethods = false
)
@Role(2)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
    public ProxyAsyncConfiguration() {
    }

    @Bean(
        name = {&quot;org.springframework.context.annotation.internalAsyncAnnotationProcessor&quot;}
    )
    @Role(2)
    public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
        Assert.notNull(this.enableAsync, &quot;@EnableAsync annotation metadata was not injected&quot;);
        AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
        bpp.configure(this.executor, this.exceptionHandler);
        Class&amp;lt;? extends Annotation&amp;gt; customAsyncAnnotation = this.enableAsync.getClass(&quot;annotation&quot;);
        if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, &quot;annotation&quot;)) {
            bpp.setAsyncAnnotationType(customAsyncAnnotation);
        }

        bpp.setProxyTargetClass(this.enableAsync.getBoolean(&quot;proxyTargetClass&quot;));
        bpp.setOrder((Integer)this.enableAsync.getNumber(&quot;order&quot;));
        return bpp;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 클래스를 통해 @EnableAsync 설정한 정보들을 이용한다.&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;AsyncAnnotationBeanPostProcessor&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 ProxyAsyncConfiguration에서 볼 수 있다시피 AsyncAnnotationBeanPostProcessor를 통해&amp;nbsp; Advice로 동작하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;protected Advice buildAdvice(@Nullable Supplier&amp;lt;Executor&amp;gt; executor, @Nullable Supplier&amp;lt;AsyncUncaughtExceptionHandler&amp;gt; exceptionHandler) {
    AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor((Executor)null);
    interceptor.configure(executor, exceptionHandler);
    return interceptor;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;interceptor를 등록하는 과정이며 자세한 것은 검색해보시길 바란다.&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;AsyncExecutionAspectSupport&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;protected Object doSubmit(Callable&amp;lt;Object&amp;gt; task, AsyncTaskExecutor executor, Class&amp;lt;?&amp;gt; returnType) {
   if (CompletableFuture.class.isAssignableFrom(returnType)) {
      return executor.submitCompletable(task);
   }
   else if (org.springframework.util.concurrent.ListenableFuture.class.isAssignableFrom(returnType)) {
      return ((org.springframework.core.task.AsyncListenableTaskExecutor) executor).submitListenable(task);
   }
   else if (Future.class.isAssignableFrom(returnType)) {
      return executor.submit(task);
   }
   else if (void.class == returnType) {
      executor.submit(task);
      return null;
   }
   else {
      throw new IllegalArgumentException(
            &quot;Invalid return type for async method (only Future and void supported): &quot; + returnType);
   }
}&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;h2 data-ke-size=&quot;size26&quot;&gt;언제 사용하면 좋을까?&lt;/h2&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;</description>
      <category>JVM/Spring</category>
      <category>async</category>
      <category>EnableAsync</category>
      <category>비동기</category>
      <author>글을 쓰는 개발자</author>
      <guid isPermaLink="true">https://vixxcode.tistory.com/262</guid>
      <comments>https://vixxcode.tistory.com/262#entry262comment</comments>
      <pubDate>Sat, 1 Oct 2022 18:22:46 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] @Scheduled, @EnableScheduling</title>
      <link>https://vixxcode.tistory.com/261</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@EnableScheduling 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반드시 @Scheduled를 사용할려면 @EnableScheduling을 다음과 같이 설정해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1664617102628&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootApplication
@EnableScheduling
public class SpringplaygroundApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringplaygroundApplication.class, args);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Scheduled 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Scheduled는 다음 아래와 같이 3가지 설정을 통해 구현할 수 있으며, 원하는 내용에 어노테이션을 설정하면 된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;cron&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cron은 흔히 우리가 linux에서 설정하는 cron 형식과 유사하며 사용하는 방법은 다음 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1664617159283&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@Slf4j
public class ScheduleService {
    @Scheduled(cron = &quot;10 * * * * *&quot;) //10초로 끝날 때마다 호출
    public void cronCall() {
        log.info(&quot;cron execute&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1664617173282&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2022-10-01 18:36:10.005  INFO 7847 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : cron execute
2022-10-01 18:37:10.004  INFO 7847 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : cron execute
2022-10-01 18:38:10.005  INFO 7847 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : cron execute
2022-10-01 18:39:10.005  INFO 7847 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : cron execute&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;CronExpression 에 정의 되어 있는 cron 예시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664617324556&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;@yearly&quot;, &quot;0 0 0 1 1 *&quot;,
&quot;@annually&quot;, &quot;0 0 0 1 1 *&quot;,
&quot;@monthly&quot;, &quot;0 0 0 1 * *&quot;,
&quot;@weekly&quot;, &quot;0 0 0 * * 0&quot;,
&quot;@daily&quot;, &quot;0 0 0 * * *&quot;,
&quot;@midnight&quot;, &quot;0 0 0 * * *&quot;,
&quot;@hourly&quot;, &quot;0 0 * * * *&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@Slf4j
public class ScheduleService {
    @Scheduled(cron = &quot;@hourly&quot;) // 매 정각마다
    public void cronCall() {
        log.info(&quot;cron execute&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;실제 위의 표현식은 제대로 동작을 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1664618481735&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2022-10-01 18:54:35.361  INFO 8103 --- [           main] c.p.s.SpringplaygroundApplication        : Started SpringplaygroundApplication in 1.115 seconds (JVM running for 1.315)
2022-10-01 19:00:00.004  INFO 8103 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : cron execute&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;cron 순서는 다음과 같다&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;* &amp;lt;li&amp;gt;second&amp;lt;/li&amp;gt; 초
* &amp;lt;li&amp;gt;minute&amp;lt;/li&amp;gt; 분
* &amp;lt;li&amp;gt;hour&amp;lt;/li&amp;gt; 시간
* &amp;lt;li&amp;gt;day of month&amp;lt;/li&amp;gt; 일
* &amp;lt;li&amp;gt;month&amp;lt;/li&amp;gt; 월
* &amp;lt;li&amp;gt;day of week&amp;lt;/li&amp;gt; 주&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;fixedDelay&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fixedDelay는 이전 작업이 끝나고 일정시간 마다 도는 것을 설정하는 필드다.&lt;/p&gt;
&lt;pre id=&quot;code_1664617567051&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@Slf4j
public class ScheduleService {
    @Scheduled(fixedDelay = 1L, timeUnit = TimeUnit.SECONDS)
    public void publicCall() throws InterruptedException {
        Thread.sleep(500L);
        log.info(&quot;public call fixedDelay = 1's&quot;);
    }
}&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_1664617618804&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2022-10-01 18:46:39.827  INFO 7915 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedDelay = 1's
2022-10-01 18:46:41.334  INFO 7915 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedDelay = 1's
2022-10-01 18:46:42.840  INFO 7915 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedDelay = 1's
2022-10-01 18:46:44.349  INFO 7915 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedDelay = 1's
2022-10-01 18:46:45.859  INFO 7915 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedDelay = 1's
2022-10-01 18:46:47.366  INFO 7915 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedDelay = 1's&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;* Execute the annotated method with a fixed period between the end of the
* last invocation and the start of the nex&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;p data-ke-size=&quot;size16&quot;&gt;예시에서 스레드를 0.5초 쉬게 한 이유도 정확히 이전 작업이 끝나고 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;참고로 timeUnit은 기본은 MiliSeconds 이다.&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;그러면 public Method 에서만 동작할까?&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;pre id=&quot;code_1664617786108&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@Slf4j
public class ScheduleService {
    @Scheduled(fixedDelay = 1500L)
    private void privateCall() {
        log.info(&quot;private call fixedDelay = 1.5's&quot;);
    }
}&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_1664617818325&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2022-10-01 18:49:20.443  INFO 8038 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : private call fixedDelay = 1.5's
2022-10-01 18:49:21.949  INFO 8038 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : private call fixedDelay = 1.5's
2022-10-01 18:49:23.451  INFO 8038 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : private call fixedDelay = 1.5's
2022-10-01 18:49:24.957  INFO 8038 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : private call fixedDelay = 1.5's
2022-10-01 18:49:26.463  INFO 8038 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : private call fixedDelay = 1.5's&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 private method 에서도 동작하는 것을 볼 수가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;fixedRate&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fixedRate의 경우에는 시작을 기준으로 일정 간격마다 동작하는 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1664617874361&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@Slf4j
public class ScheduleService {
    @Scheduled(fixedRate = 1000L)
    public void fixedRateCall() throws InterruptedException {
        Thread.sleep(500L);
        log.info(&quot;public call fixedRate = 1's&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1664617913597&quot; class=&quot;yaml&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2022-10-01 18:51:24.293  INFO 8080 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedRate = 1's
2022-10-01 18:51:25.296  INFO 8080 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedRate = 1's
2022-10-01 18:51:26.298  INFO 8080 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedRate = 1's
2022-10-01 18:51:27.293  INFO 8080 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedRate = 1's
2022-10-01 18:51:28.298  INFO 8080 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedRate = 1's
2022-10-01 18:51:29.294  INFO 8080 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedRate = 1's
2022-10-01 18:51:30.294  INFO 8080 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedRate = 1's
2022-10-01 18:51:31.297  INFO 8080 --- [   scheduling-1] c.p.s.schedule.ScheduleService           : public call fixedRate = 1's&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;fixedRate는 fixedDelay랑 다르게 일정 주기성을 가지고 정확히 1초마다 돌아가는 것을 볼 수가 있다.&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;* Execute the annotated method with a fixed period between invocations.
* &amp;lt;p&amp;gt;The time unit is milliseconds by default but can be overridden via
* {@link #timeUnit}.&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;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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@EnableScheduling 으로 필요한 Bean들을 등록한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SchedulingConfiguration&lt;/h4&gt;
&lt;pre id=&quot;code_1664618787607&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration(
    proxyBeanMethods = false
)
@Role(2)
public class SchedulingConfiguration {
    public SchedulingConfiguration() {
    }

    @Bean(
        name = {&quot;org.springframework.context.annotation.internalScheduledAnnotationProcessor&quot;}
    )
    @Role(2)
    public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
        return new ScheduledAnnotationBeanPostProcessor();
    }
}&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;여기서 ScheduledAnnotationBeanPostProcessor를 빈으로 등록하는 과정을 거친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ScheduledAnnotationBeanPostProcessor&lt;/h4&gt;
&lt;pre id=&quot;code_1664618841910&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    public ScheduledAnnotationBeanPostProcessor() {
        this.registrar = new ScheduledTaskRegistrar();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자에서 ScheduledTaskRegistrar()를 새로 생성하는 과정을 거치며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ScheduledTaskRegistrar&lt;/h4&gt;
&lt;pre id=&quot;code_1664618904332&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    public void setTaskScheduler(TaskScheduler taskScheduler) {
        Assert.notNull(taskScheduler, &quot;TaskScheduler must not be null&quot;);
        this.taskScheduler = taskScheduler;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;taskScheduler를 주입받으며 생성하게 되는데 taskScheduler는&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-autoconfigure-metadata.properties&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration.ConditionalOnClass=org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에 정의되어 있는 ThreadPoolTaskScheduler를 빈으로 생성하고 이를 주입 받는 형태로 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;그럼 @Scheduled을 붙인 메소드는 어떻게 동작하게 될까?&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ScheduledAnnotationBeanPostProcessor 의 이름에서 느껴지겠지만 빈 후처리기를 통해 생성된 빈에 스케줄 관련 후처리를 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664619360429&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (!(bean instanceof AopInfrastructureBean) &amp;amp;&amp;amp; !(bean instanceof TaskScheduler) &amp;amp;&amp;amp; !(bean instanceof ScheduledExecutorService)) {
            Class&amp;lt;?&amp;gt; targetClass = AopProxyUtils.ultimateTargetClass(bean);
            if (!this.nonAnnotatedClasses.contains(targetClass) &amp;amp;&amp;amp; AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
                Map&amp;lt;Method, Set&amp;lt;Scheduled&amp;gt;&amp;gt; annotatedMethods = MethodIntrospector.selectMethods(targetClass, (method) -&amp;gt; {
                    Set&amp;lt;Scheduled&amp;gt; scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);
                    return !scheduledAnnotations.isEmpty() ? scheduledAnnotations : null;
                });
                if (annotatedMethods.isEmpty()) {
                    this.nonAnnotatedClasses.add(targetClass);
                    if (this.logger.isTraceEnabled()) {
                        this.logger.trace(&quot;No @Scheduled annotations found on bean class: &quot; + targetClass);
                    }
                } else {
                    annotatedMethods.forEach((method, scheduledAnnotations) -&amp;gt; {
                        scheduledAnnotations.forEach((scheduled) -&amp;gt; {
                            this.processScheduled(scheduled, method, bean);
                        });
                    });
                    if (this.logger.isTraceEnabled()) {
                        this.logger.trace(annotatedMethods.size() + &quot; @Scheduled methods processed on bean '&quot; + beanName + &quot;': &quot; + annotatedMethods);
                    }
                }
            }

            return bean;
        } else {
            return bean;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 보시면 @Scheduled 검사 여부에 따라 적합한 후보군이 있으면 이에 따라&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;protected void processScheduled(Scheduled scheduled, Method method, Object bean)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 메소드를 호출하고 bean을 반환하는 작업을 진행하게 된다. 그렇게 되면 우리가 원하는 Schedule 설정이 동작하게 된다.&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;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Override
public void afterPropertiesSet() {
   scheduleTasks();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 scheduleTasks() 는 어디서 돌아갈까? 바로 아래글을 보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;그러면 실질적인 동작하는 클래스는 어디?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task를 관리하는 클래스는 &lt;b&gt;ScheduledTaskRegistrar&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;관리하는 Task 는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CronTask&lt;/li&gt;
&lt;li&gt;FixedDelayTask&lt;/li&gt;
&lt;li&gt;FixedRateTask&lt;/li&gt;
&lt;li&gt;etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등 다양한 Task 들이 존재하고 이에 대한 처리를&amp;nbsp; &lt;b&gt;ScheduledTaskRegistrar &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;아까 위에서 scheduleTasks()에서 실질적인 동작들이 돌아간다고 말을 했는데 그 중 작은 부분을 보도록 하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664620994837&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Date startTime = new Date(this.taskScheduler.getClock().millis() + task.getInitialDelay());
scheduledTask.future = this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), startTime, task.getInterval());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 FixedDelay 를 설정했을 때 돌아가는 코드로서 @Scheduled 에 설정한 값들을 저장하고 그 값을 활용하여 ScheduleFuture에 등록함으로써 일정주기마다 동작하도록 설정하는 코드다.&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;그 이외에도 FixedRate, Cron도 이와 유사하게 동작하므로 실제로 보는 것을 추천한다.&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;h2 data-ke-size=&quot;size26&quot;&gt;어떤 상황에서 써야 할까?&lt;/h2&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;가령 일정 시간마다 데이터 최신화가 필요하지만 실패해도 상관없는 경우에 사용하거나, 일정시간마다 캐시를 Invalidation하는 작업을 하거나 등의 상황에서 사용하면 유용하고, 쉽게 사용할 수 있지 않을까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JVM/Spring</category>
      <category>@EnableScheduling</category>
      <category>@Scheduled</category>
      <category>cron</category>
      <category>FixedDelay</category>
      <category>FixedRate</category>
      <author>글을 쓰는 개발자</author>
      <guid isPermaLink="true">https://vixxcode.tistory.com/261</guid>
      <comments>https://vixxcode.tistory.com/261#entry261comment</comments>
      <pubDate>Sat, 1 Oct 2022 18:20:33 +0900</pubDate>
    </item>
  </channel>
</rss>