<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>BAE DEV</title>
    <link>https://wanbaep.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 10 Apr 2026 01:44:19 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>wanbaep</managingEditor>
    <item>
      <title>[JPA] SpringBoot 3.X 버전 Querydsl 설정</title>
      <link>https://wanbaep.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SpringBoot 3.X 버전 대에서 Querydsl설정을 위한 gradle 설정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 하고 build 후에 build &amp;gt; generated &amp;gt; sources &amp;gt; annotationProcessor 폴더에 QEntity들이 생성되는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1718666920341&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.4'
    id 'io.spring.dependency-management' version '1.1.4'
}

group = 'jpa.project'
version = '1.0-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'com.h2database:h2'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
    
    implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
    annotationProcessor &quot;com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta&quot;
    annotationProcessor &quot;jakarta.annotation:jakarta.annotation-api&quot;
    annotationProcessor &quot;jakarta.persistence:jakarta.persistence-api&quot;
}

tasks.named('test') {
    useJUnitPlatform()
}

clean {
    delete file('src/main/generated')
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/JPA</category>
      <category>gradle</category>
      <category>QueryDSL</category>
      <category>springboot3.x</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/26</guid>
      <comments>https://wanbaep.tistory.com/26#entry26comment</comments>
      <pubDate>Tue, 18 Jun 2024 08:31:26 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] @OneToOne 연관 관계 Lazy 로딩 안되는 이유 및 해결 방법</title>
      <link>https://wanbaep.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 리소스의 status를 보고 Pod가 비정상 상태라면 데이터를 저장하고 비정상 상태가 지속 시간을 만족하면 알람이 발생되는 로직이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 리소스의 status와 Alarm은 일대일 관계이기 때문에, &lt;code&gt;@OneToOne&lt;/code&gt;관계로 설정을 했고 연관 관계 owner가 아닌 테이블에 mappedBy 설정으로 조회가 되게 설정 후 &lt;code&gt;fetch = FetchType.LAZY&lt;/code&gt; 로 모두 설정해서 Lazy 로딩이 동작될 것으로 예상했지만, 예상햇던 대로 SQL query가 나가지 않는 이슈가 발생했다.&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;Entity에 대한 설정은 아래와 같이 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Alarm Entity&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718632751856&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ToString(exclude = {&quot;k8sEvent&quot;})
@Entity
public class Alarm {

    @Id
    @UuidGenerator
    @Column(name = &quot;alarm_id&quot;)
    private String id;

    @Enumerated(EnumType.STRING)
    private Severity severity;
    private LocalDateTime fireTime;
    private LocalDateTime clearTime;

    private String probableCause;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;event_id&quot;)
    private K8sEvent k8sEvent;

    protected Alarm() {
    }

    @Builder
    public Alarm(Severity severity, LocalDateTime fireTime, LocalDateTime clearTime, String probableCause, K8sEvent k8sEvent) {
        this.severity = severity;
        this.fireTime = fireTime;
        this.clearTime = clearTime;
        this.probableCause = probableCause;
        this.k8sEvent = k8sEvent;
    }

    public static Alarm from(K8sEvent k8sEvent, Severity severity) {
        return Alarm.builder()
                .severity(severity)
                .probableCause(k8sEvent.getReason())
                .fireTime(LocalDateTime.now())
                .k8sEvent(k8sEvent)
                .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;K8sEvent Entity&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718632774335&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@ToString(exclude = {&quot;alarm&quot;})
@Entity
public class K8sEvent {

    @Id
    @UuidGenerator
    @Column(name = &quot;event_id&quot;)
    private String id;
    private String sourceId;
    private String sourceName;
    @Enumerated(EnumType.STRING)
    private K8sType k8sType;
    private String reason;
    private LocalDateTime fireAtDateTime;

    @OneToOne(mappedBy = &quot;k8sEvent&quot;, fetch = FetchType.LAZY)
    private Alarm alarm;

    protected K8sEvent() {
    }
    
    protected K8sEvent(String sourceId, String sourceName, K8sType k8sType, String reason, LocalDateTime fireAtDateTime) {
        this.sourceId = sourceId;
        this.sourceName = sourceName;
        this.k8sType = k8sType;
        this.reason = reason;
        this.fireAtDateTime = fireAtDateTime;
    }

    public static K8sEvent createNewK8sEvent(String sourceId, String sourceName, K8sType k8sType, String reason, LocalDateTime fireAtDateTime) {
        return new K8sEvent(sourceId, sourceName, k8sType, reason, fireAtDateTime);
    }

}&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;Alarm은 연관 관계의 주인으로 &lt;code&gt;event_id&lt;/code&gt; FK를 가지고 있고, K8sEvent에서는 &lt;code&gt;mappedBy&lt;/code&gt;로 연관된 Alarm을 조회할 수 있다. K8sEvent와 관련된 Alarm이 발생된 경우, 중복 알람이 발생되지 않도록 조치가 필요해서 &lt;code&gt;mappedBy&lt;/code&gt;로 선언해 함께 조회될 수 있게 했다. Alarm에서는 K8sEvent에 대해서 Lazy 로딩이 되도록 설정하고 마찬가지로 &lt;code&gt;mappedBy&lt;/code&gt;로 선언된 K8sEvent에서도 K8sEvent만 조회하거나 Alarm이 함께 조회가 필요한 경우 Lazy 로딩이 되도록 설정을 해둔 상태였다.&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;Alarm관련된 데이터만 필요한 경우 Alarm테이블의 데이터를 사용하고, Alarm 해제 시 K8sEvent를 조회해서 삭제하기 위해 사용했고, K8sEvent는 Event를 받아서 상태를 업데이트 하고, Polling을 통해서 K8sEvent에 대한 Alarm이 발생되도록 했다. K8sEvent에 대한 알람 발생 전에 Alarm 연관 엔티티를 조회해서 중복 알람이 있는지 여부를 확인하려고 했다.&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;하지만, 테스트 도중 K8sEvent를 조회하는 경우에 예상하지 못한 SQL Query들이 실행 되고 있었고, 속도가 저하되는 이슈가 있었다.&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;그래서 해당 현상을 좀 더 확인해보고자 아래 테스트 코드로 JPQL과 SQL을 조회해 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 &lt;code&gt;@ToString&lt;/code&gt;으로 조회하는데 연관 관계는 조회하지 않기 때문에, select query가 나가지 않도록 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1718632869824&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootTest
@Transactional
class AlarmTest {

    @PersistenceContext
    private EntityManager em;

    @Test
    public void alarmRetrieveTest() throws Exception {
        saveTestData();

        em.flush();
        em.clear();

        Alarm findAlarm = em.createQuery(&quot;select a from Alarm a&quot;, Alarm.class)
                .getSingleResult();
        System.out.println(&quot;findAlarm = &quot; + findAlarm);
    }

    private void saveTestData() {
        K8sEvent k8sEvent = K8sEvent.createNewK8sEvent(&quot;podId1&quot;, &quot;podName1&quot;, K8sType.POD, &quot;KubePodNotReady&quot;,
                LocalDateTime.now().plusMinutes(5));
        em.persist(k8sEvent);

        Alarm alarm = Alarm.from(k8sEvent, Severity.MINOR);
        em.persist(alarm);
    }
}&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;b&gt;테스트 결과&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목적이 query를 보는 것이었기 때문에 &lt;code&gt;Assertions&lt;/code&gt;로 검증하지 않고 간단하게 출력했다.&lt;/p&gt;
&lt;pre id=&quot;code_1718632946900&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2024-06-17T23:01:49.674+09:00 DEBUG 6606 --- [    Test worker] org.hibernate.SQL                        : 
    /* select
        a 
    from
        Alarm a */ select
            a1_0.alarm_id,
            a1_0.clear_time,
            a1_0.fire_time,
            a1_0.event_id,
            a1_0.probable_cause,
            a1_0.severity 
        from
            alarm a1_0
findAlarm = Alarm(id=48b51d0e-e573-4049-b7aa-6fdfcff6d5a2, severity=MINOR, fireTime=2024-06-17T23:01:49.533174, clearTime=null, probableCause=KubePodNotReady)&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;Alarm Entity를 조회하는 경우에는 Alarm Entity만 조회 되어서 출력되고 K8sEvent는 로딩이 안되는 것을 확인해 볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 K8sEvent를 조회해봤다. (마찬가지로 연관 관계가 조회되지 않기 위해 출력하지 않도록 했다.)&lt;/p&gt;
&lt;pre id=&quot;code_1718633031702&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
    public void k8sEventRetrieveTest() throws Exception {
        saveTestData();
        em.flush();
        em.clear();

        K8sEvent findK8sEvent = em.createQuery(&quot;select k from K8sEvent k&quot;, K8sEvent.class)
                .getSingleResult();
        System.out.println(&quot;findK8sEvent = &quot; + findK8sEvent);
    }&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;b&gt;테스트 결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718633075381&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2024-06-17T23:04:00.301+09:00 DEBUG 6617 --- [    Test worker] org.hibernate.SQL                        : 
    /* select
        k 
    from
        K8sEvent k */ select
            ke1_0.event_id,
            ke1_0.fire_at_date_time,
            ke1_0.k8s_type,
            ke1_0.reason,
            ke1_0.source_id,
            ke1_0.source_name 
        from
            k8s_event ke1_0
2024-06-17T23:04:00.307+09:00 DEBUG 6617 --- [    Test worker] org.hibernate.SQL                        : 
    select
        a1_0.alarm_id,
        a1_0.clear_time,
        a1_0.fire_time,
        a1_0.event_id,
        a1_0.probable_cause,
        a1_0.severity 
    from
        alarm a1_0 
    where
        a1_0.event_id=?
findK8sEvent = K8sEvent(id=6ce0a619-724c-4fa8-aa3c-cc42d8579b09, sourceId=podId1, sourceName=podName1, k8sType=POD, reason=KubePodNotReady, fireAtDateTime=2024-06-17T23:09:00.151917)&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;select query가 K8sEvent에 대해서 먼저 나가고 그 다음 Alarm에 대한 select query가 나가고 있다. 분명 &lt;code&gt;fetch = FetchType.LAZY&lt;/code&gt; 로 잘 설정을 해두었지만, Lazy 로딩으로 동작되지 않고 있었다.&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;blockquote data-ke-style=&quot;style2&quot;&gt;Lazy fetching for one-to-one associations&lt;br /&gt;Notice that we did not declare the unowned end of the association&amp;nbsp;fetch=LAZY. That&amp;rsquo;s because:&lt;br /&gt;not every&amp;nbsp;Person&amp;nbsp;has an associated&amp;nbsp;Author, andthe foreign key is held in the table mapped by&amp;nbsp;Author, not in the table mapped by&amp;nbsp;Person.&lt;br /&gt;Therefore, Hibernate can&amp;rsquo;t tell if the reference from&amp;nbsp;Person&amp;nbsp;to&amp;nbsp;Author&amp;nbsp;is&amp;nbsp;null&amp;nbsp;without fetching the associated&amp;nbsp;Author.&lt;br /&gt;On the other hand, if&amp;nbsp;every&amp;nbsp;Person&amp;nbsp;was an&amp;nbsp;Author, that is, if the association were non-optional, we would not have to consider the possibility of&amp;nbsp;null&amp;nbsp;references, and we would map it like this:&lt;br /&gt;@OneToOne(optional=false, mappedBy = Author_.PERSON, fetch=LAZY) Author author;&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;&lt;a href=&quot;https://docs.jboss.org/hibernate/orm/6.5/introduction/html_single/Hibernate_Introduction.html#one-to-one-fk&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.jboss.org/hibernate/orm/6.5/introduction/html_single/Hibernate_Introduction.html#one-to-one-fk&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718633350567&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;An Introduction to Hibernate 6&quot; data-og-description=&quot;To interact with the database, that is, to execute queries, or to insert, update, or delete data, we need an instance of one of the following objects: a JPA EntityManager, a Hibernate Session, or a Hibernate StatelessSession. The Session interface extends &quot; data-og-host=&quot;docs.jboss.org&quot; data-og-source-url=&quot;https://docs.jboss.org/hibernate/orm/6.5/introduction/html_single/Hibernate_Introduction.html#one-to-one-fk&quot; data-og-url=&quot;https://docs.jboss.org/hibernate/orm/6.5/introduction/html_single/Hibernate_Introduction.html#one-to-one-fk&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kisvL/hyWoIHFJ65/3I50AfvKYktPYiBgRNe7P1/img.png?width=1564&amp;amp;height=1432&amp;amp;face=0_0_1564_1432,https://scrap.kakaocdn.net/dn/fdOh2/hyWoC8v8G2/BPsMtkDPPEFA3xL2Wf6NS1/img.png?width=2529&amp;amp;height=867&amp;amp;face=0_0_2529_867,https://scrap.kakaocdn.net/dn/UNfqV/hyWlmGfGkD/gHtr3vAgwEs5bP7LXUUPkk/img.png?width=1805&amp;amp;height=996&amp;amp;face=0_0_1805_996&quot;&gt;&lt;a href=&quot;https://docs.jboss.org/hibernate/orm/6.5/introduction/html_single/Hibernate_Introduction.html#one-to-one-fk&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.jboss.org/hibernate/orm/6.5/introduction/html_single/Hibernate_Introduction.html#one-to-one-fk&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kisvL/hyWoIHFJ65/3I50AfvKYktPYiBgRNe7P1/img.png?width=1564&amp;amp;height=1432&amp;amp;face=0_0_1564_1432,https://scrap.kakaocdn.net/dn/fdOh2/hyWoC8v8G2/BPsMtkDPPEFA3xL2Wf6NS1/img.png?width=2529&amp;amp;height=867&amp;amp;face=0_0_2529_867,https://scrap.kakaocdn.net/dn/UNfqV/hyWlmGfGkD/gHtr3vAgwEs5bP7LXUUPkk/img.png?width=1805&amp;amp;height=996&amp;amp;face=0_0_1805_996');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;An Introduction to Hibernate 6&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;To interact with the database, that is, to execute queries, or to insert, update, or delete data, we need an instance of one of the following objects: a JPA EntityManager, a Hibernate Session, or a Hibernate StatelessSession. The Session interface extends&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.jboss.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hibernate 에서 하는 설명은 &lt;code&gt;mappedBy&lt;/code&gt; 로 설정된 즉, FK owner가 아닌 Entity에서는 연관 관계에 있는 객체가 null인지 아닌지를 알 수 없기 때문에, Lazy로 설정을 해도 Eager 한번에 데이터를 가져올 수 밖에 없다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) Hibernate에서 작성된 방법 (Primary key 공유 + FK Not Null)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 Hibernate Document에서도 알 수 있듯이, FK가 항상 존재하는 경우에는 이러한 이슈를 해결할 수 있는데, 그 방법은, &lt;code&gt;mappedBy&lt;/code&gt; 위치 즉, FK owner가 아닌 Entity에 &lt;code&gt;optional = false&lt;/code&gt; 설정을 해주고, &lt;code&gt;@MapsId&lt;/code&gt;를 설정해 줘서 이를 해결해 줄 수 있다는 얘기다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이미 테이블의 key는 서로 다른 값을 사용하도록 설정되어 있었고, FK가 not null constriant도 아니었기 때문에 위 방법은 사용할 수 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용 방법은 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1718643477786&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ToString(exclude = {&quot;k8sEvent&quot;})
@Entity
public class Alarm {

    @Id
//    @UuidGenerator
    @Column(name = &quot;alarm_id&quot;)
    private String id;

    @Enumerated(EnumType.STRING)
    private Severity severity;
    private LocalDateTime fireTime;
    private LocalDateTime clearTime;

    private String probableCause;

    @OneToOne(optional = false, fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;event_id&quot;)
    @MapsId
    private K8sEvent k8sEvent;
}

@ToString(exclude = {&quot;alarm&quot;})
@Entity
public class K8sEvent {

    @Id
    @UuidGenerator
    @Column(name = &quot;event_id&quot;)
    private String id;
    private String sourceId;
    private String sourceName;
    @Enumerated(EnumType.STRING)
    private K8sType k8sType;
    private String reason;
    private LocalDateTime fireAtDateTime;

    @OneToOne(optional = false, mappedBy = &quot;k8sEvent&quot;, fetch = FetchType.LAZY)
    private Alarm alarm;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alarm의 Id를 할당하던 &lt;code&gt;@UuidGenerator&lt;/code&gt;가 주석처리 되어 있는데, 이는 Alarm이 K8sEvent의 Id를 사용하기 때문에 별도로 Id를 할당하면 안되기 때문이다. (주석을 해제해도 실행은 되지만, 마찬가지로 동일하게 같은 Id를 사용한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 K8sEvent를 먼저 출력하고, Alarm도 출력되게 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1718643553586&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Test
    public void k8sEventRetrieveTest() throws Exception {
        saveTestData();
        em.flush();
        em.clear();

        K8sEvent findK8sEvent = em.createQuery(&quot;select k from K8sEvent k&quot;, K8sEvent.class)
                .getSingleResult();
        System.out.println(&quot;findK8sEvent = &quot; + findK8sEvent);
        System.out.println(&quot;findK8sEvent.getAlarm() = &quot; + findK8sEvent.getAlarm());

    }&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;b&gt;테스트 결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718643595903&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2024-06-18T01:58:26.957+09:00 DEBUG 6895 --- [    Test worker] org.hibernate.SQL                        : 
    /* select
        k 
    from
        K8sEvent k */ select
            ke1_0.event_id,
            ke1_0.fire_at_date_time,
            ke1_0.k8s_type,
            ke1_0.reason,
            ke1_0.source_id,
            ke1_0.source_name 
        from
            k8s_event ke1_0
findK8sEvent = K8sEvent(id=c78ecce6-9bcf-4410-8d01-a0c293dc6057, sourceId=podId1, sourceName=podName1, k8sType=POD, reason=KubePodNotReady, fireAtDateTime=2024-06-18T02:03:26.814882)
2024-06-18T01:58:26.965+09:00 DEBUG 6895 --- [    Test worker] org.hibernate.SQL                        : 
    select
        a1_0.event_id,
        a1_0.clear_time,
        a1_0.fire_time,
        a1_0.probable_cause,
        a1_0.severity 
    from
        alarm a1_0 
    where
        a1_0.event_id=?
findK8sEvent.getAlarm() = Alarm(id=c78ecce6-9bcf-4410-8d01-a0c293dc6057, severity=MINOR, fireTime=2024-06-18T01:58:26.820097, clearTime=null, probableCause=KubePodNotReady)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K8sEvent를 먼저 출력하고 Alarm을 나중에 읽어오면서, Lazy 로딩이 잘 동작하고 있는 모습이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) OneToMany나 ManyToOne관계로 변경&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 방법으로는 ManyToOne 이나 OneToMany 관계를 활용하는 것이다. List는 null이 아닌 빈 리스트로 표현이 가능하기 때문에, Hibernate가 Proxy객체로 감쌀 수 있다. 따라서 OneToOne에서의 이슈가 발생되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) fetch join 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 방법으로는 Lazy로 query를 2번 날리거나 Eager로 예상치 못한 query가 작동하게 하는 것이 아닌 fetch join을 사용하는 방법이다. K8sEvent를 조회할 때, Alarm도 같이 조회 되도록 fetch join을 사용해서 데이터를 가져오면 select query를 1번만 사용해서 조회를 할 수 있다. 하지만, 성능이 중요한 경우에는 Batch 사이즈 설정 혹은 수동으로 in 절을 작성해서 이를 최적화를 함께 해주는 것이 중요하다.&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;</description>
      <category>BackEnd/JPA</category>
      <category>Fetch</category>
      <category>hibernate</category>
      <category>join</category>
      <category>jpa</category>
      <category>OneToOne</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/25</guid>
      <comments>https://wanbaep.tistory.com/25#entry25comment</comments>
      <pubDate>Tue, 18 Jun 2024 08:20:04 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] Spring Boot 프로젝트 Apache Kafka Binder 예제</title>
      <link>https://wanbaep.tistory.com/24</link>
      <description>&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;Spring Boot에서 Apache Kafka Binder 를 사용할 때, Producer, Consumer 설정을 어떻게 하고 코드는 어떻게 작성하면 되는지 간략하게 요약식으로 적어두려고 한다.&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;dependency 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710508188309&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.cloud:spring-cloud-stream-binder-kafka'
    implementation 'org.springframework.cloud:spring-cloud-starter-stream-kafka'
}&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;Spring Cloud Stream 3.0.0 부터 kafka Stream Binder 를 사용해서 &lt;code&gt;Java 8&lt;/code&gt; 부터 제공하는 Functional programming 스타일을 사용할 수 있다. 이와 관련된 type은 &lt;code&gt;java.util.function.Function&lt;/code&gt; 이나 &lt;code&gt;java.util.function.Consumer 이다.&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;application.yaml 설정&lt;/h4&gt;
&lt;pre id=&quot;code_1710508633271&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  cloud:
    function:
      definition: kafkaConsumer;
    stream:
      kafka:
        bindings:
          producer-out-0:
            producer:
              configuration:
                key:
                  serializer: org.apache.kafka.common.serialization.StringSerializer
          kafkaConsumer-in-0:
            consumer:
              configuration:
                key:
                  deserializer: org.apache.kafka.common.serialization.StringDeserializer
        binder:
          brokers: 127.0.0.1:9092
      bindings:
        producer-out-0:
          destination: test # topic
          contentType: application/json
        kafkaConsumer-in-0:
          destination: test  # topic&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 &lt;code&gt;application.yaml&lt;/code&gt;을 하나씩 살펴보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;spring.cloud.stream.kafka.binder.brokers&lt;/code&gt; 에는 boroker server정보를 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Consumer&lt;/b&gt; &amp;lt;channel-name&amp;gt; = kafkaConsumer-in-0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;spring.cloud.function.definition&lt;/code&gt; 의 값에 consumer에 해당하는 &lt;code&gt;Bean&lt;/code&gt;이름을 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;spring.cloud.stream.bindings.&amp;lt;channel-name&amp;gt;.destination&lt;/code&gt; 에는 consume할 topic 명을 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;spring.cloud.stream.kafka.bindings.&amp;lt;channel-name&amp;gt;.consumer.configuration&lt;/code&gt; 에는 consumer 관련 설정을 작성하는데, 여기서는 &lt;code&gt;key&lt;/code&gt;에 대한 &lt;code&gt;Deserializer&lt;/code&gt;설정을 해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Producer&lt;/b&gt; &amp;lt;channel-name&amp;gt; = producer-out-0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;spring.cloud.stream.bindings.&amp;lt;channel-name&amp;gt;.destination&lt;/code&gt; 에는 producer할 topic 명을 작성한다. (위 예제에서는 consume할 topic과 producer할 topic이 같다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;spring.cloud.stream.kafka.bindings.&amp;lt;channel-name&amp;gt;.producer.configuration&lt;/code&gt; 에는 producer 관련 설정을 작성하는데, 여기서는 &lt;code&gt;key&lt;/code&gt;에 대한 &lt;code&gt;Serializer&lt;/code&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;Consumer Code&lt;/h4&gt;
&lt;pre id=&quot;code_1710509862741&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.wanbaep.runner;

import com.wanbaep.model.ClusterResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;

import java.util.function.Consumer;

@Slf4j
@Service
public class KafkaConsumer implements Consumer&amp;lt;Message&amp;lt;ClusterResponse&amp;gt;&amp;gt; {

    @SneakyThrows
    @Override
    public void accept(Message&amp;lt;ClusterResponse&amp;gt; clusterMessage) {
        log.info(&quot;key: {}&quot;, clusterMessage.getHeaders().get(KafkaHeaders.RECEIVED_KEY));
        ClusterResponse clusterResponse = clusterMessage.getPayload();
        log.info(&quot;payload: {}&quot;, clusterResponse);
    }
}&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;code&gt;KafkaConsumer&lt;/code&gt; 클래스는 Java의 &lt;code&gt;Consumer&lt;/code&gt; Type을 상속 받도록 했으며, &lt;code&gt;Message&amp;lt;ClusterResponse&amp;gt;&lt;/code&gt; 타입을 &lt;code&gt;accept&lt;/code&gt;하도록 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;code&gt;accept&lt;/code&gt; method만 &lt;code&gt;Override&lt;/code&gt;해서 어떠한 동작을 할지 작성하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reference 문서에서는 &lt;code&gt;@Bean&lt;/code&gt; 으로 설정하는 예제들만 있는데, 이렇게 별도 클래스로 구분해서 하고자 하는 경우 &lt;code&gt;implements&lt;/code&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;Producer Code&lt;/h4&gt;
&lt;pre id=&quot;code_1710509892654&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.wanbaep.runner;

import com.wanbaep.model.ClusterResponse;
import lombok.AllArgsConstructor;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@AllArgsConstructor
@Service
public class KafkaProducer {
    private static final String bindingName = &quot;producer-out-0&quot;;

    private final StreamBridge streamBridge;

    @Async
    public void send(String key, ClusterResponse payload) {
        streamBridge.send(bindingName, MessageBuilder
                .withPayload(payload)
                .setHeader(KafkaHeaders.KEY, key)
                .build());
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Producer 코드인데, &lt;code&gt;StreamBridge&lt;/code&gt;를 사용해서 message를 produce하면 된다. &lt;code&gt;application.yaml&lt;/code&gt; 에서 작성한 &amp;lt;channel-name&amp;gt; 이 &lt;code&gt;bindingName&lt;/code&gt;이 되고, &lt;code&gt;org.springframework.messaging.Message&lt;/code&gt; 를 사용해서 메시지를 전송하고 있다. 해당 클래스를 사용한 이유는 Payload만 전송하는 것이 아닌, KafkaHeader를 함께 전송해야 하는 경우가 있기 때문에 사용했다. 위에서는 &lt;code&gt;Kafkaheaders.KEY=key&lt;/code&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;topic 조회 with kafka-console-consumer&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key도 함께 보내고 있기 때문에 같이 조회되도록 &lt;code&gt;--property print.key=true&lt;/code&gt; 설정을 넣어줬다.&lt;/p&gt;
&lt;pre id=&quot;code_1710509961217&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@9716ab3aeb82:~# kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092 --topic test --from-beginning -property print.key=true

# Consumer가 consume한 message
clusterId 0	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 0&quot;,&quot;name&quot;:&quot;name 0&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 1	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 1&quot;,&quot;name&quot;:&quot;name 1&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 2	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 2&quot;,&quot;name&quot;:&quot;name 2&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 3	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 3&quot;,&quot;name&quot;:&quot;name 3&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 4	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 4&quot;,&quot;name&quot;:&quot;name 4&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 5	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 5&quot;,&quot;name&quot;:&quot;name 5&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 6	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 6&quot;,&quot;name&quot;:&quot;name 6&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 7	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 7&quot;,&quot;name&quot;:&quot;name 7&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 8	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 8&quot;,&quot;name&quot;:&quot;name 8&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 9	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 9&quot;,&quot;name&quot;:&quot;name 9&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}

# Producer가 produce한 message
clusterId 0	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 0&quot;,&quot;name&quot;:&quot;name 0&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 1	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 1&quot;,&quot;name&quot;:&quot;name 1&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 2	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 2&quot;,&quot;name&quot;:&quot;name 2&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 3	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 3&quot;,&quot;name&quot;:&quot;name 3&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 4	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 4&quot;,&quot;name&quot;:&quot;name 4&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 5	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 5&quot;,&quot;name&quot;:&quot;name 5&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 6	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 6&quot;,&quot;name&quot;:&quot;name 6&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 7	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 7&quot;,&quot;name&quot;:&quot;name 7&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 8	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 8&quot;,&quot;name&quot;:&quot;name 8&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&quot;}]}
clusterId 9	{&quot;cluster&quot;:{&quot;id&quot;:&quot;clusterId 9&quot;,&quot;name&quot;:&quot;name 9&quot;},&quot;node&quot;:[{&quot;id&quot;:&quot;node1&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;간단하게 key와 payload가 kafka를 통해서 전달되는 것을 확인해 볼 수 있다.&lt;/p&gt;</description>
      <category>Kafka</category>
      <category>consumer</category>
      <category>Kafka</category>
      <category>producer</category>
      <category>Spring Boot</category>
      <category>Spring Cloud Stream</category>
      <category>Stream</category>
      <category>StreamBridge</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/24</guid>
      <comments>https://wanbaep.tistory.com/24#entry24comment</comments>
      <pubDate>Fri, 15 Mar 2024 23:02:10 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] Docker Compose로 Kafka 설치 후 간단한 테스트</title>
      <link>https://wanbaep.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;종종 Spring Boot 로 Kafka를 사용하는 테스트를 간단하게 하는데, zookeeper를 실행하고 kafka를 실행하는 서버 방식으로 하다보니 너무 귀찮았다.&lt;br /&gt;그래서 docker container를 이용해서 kafka를 설치해보고 간단한 Kafka 테스트도 진행해보려고 한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;배포가 아닌 단일호스트에서 간단한 테스트를 위한 용도, 그리고 설치 삭제가 간편한 이점 때문에 docker compose를 사용하고자 한다.&lt;br /&gt;(kafka는 설치가 아닌 실행으로 동작하지만 mysql과 같은 데이터베이스를 구성할때도 container를 활용하면 매우 간편하다)&lt;br /&gt;&lt;br /&gt;docker compose를 먼저 설치한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;docker-compose.yaml 을 작성한다.&lt;br /&gt;작성은 아래 github을 참조해서 작성했다.&lt;br /&gt;&lt;a href=&quot;https://github.com/wurstmeister/kafka-docker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://github.com/wurstmeister/kafka-docker&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;GitHub - wurstmeister/kafka-docker: Dockerfile for Apache Kafka&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Dockerfile for Apache Kafka. Contribute to wurstmeister/kafka-docker development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/wurstmeister/kafka-docker&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bcAXMu/hyVm0W3Gs5/T46en0Jk1jdTPck9n0iuxK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot; data-og-url=&quot;https://github.com/wurstmeister/kafka-docker&quot;&gt;&lt;a href=&quot;https://github.com/wurstmeister/kafka-docker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/wurstmeister/kafka-docker&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bcAXMu/hyVm0W3Gs5/T46en0Jk1jdTPck9n0iuxK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - wurstmeister/kafka-docker: Dockerfile for Apache Kafka&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Dockerfile for Apache Kafka. Contribute to wurstmeister/kafka-docker development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;docker-compose.yaml&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;version: '2'
services:
&amp;nbsp;&amp;nbsp;zookeeper:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image: wurstmeister/zookeeper
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;container_name: zookeeper
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ports:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- &quot;2181:2181&quot;
&amp;nbsp;&amp;nbsp;kafka:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image: wurstmeister/kafka
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;container_name: kafka
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ports:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- &quot;9092:9092&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;environment:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;volumes:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- /var/run/docker.sock:/var/run/docker.sock&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;docker-compuse up -d&lt;br /&gt;d 옵션을 넣어서 백그라운드로 실행한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;docker-compose ps 로 조회&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt; wanbaep &amp;gt; ~/TOY/docker-compose-kafka  docker-compose ps
NAME&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IMAGE&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;COMMAND&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SERVICE&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CREATED&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; STATUS&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PORTS
kafka&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; wurstmeister/kafka&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &quot;start-kafka.sh&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; kafka&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 7 hours ago&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Up 7 hours&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;0.0.0.0:9092-&amp;gt;9092/tcp
zookeeper&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; wurstmeister/zookeeper&amp;nbsp;&amp;nbsp; &quot;/bin/sh -c '/usr/sb&amp;hellip;&quot;&amp;nbsp;&amp;nbsp; zookeeper&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 7 hours ago&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Up 7 hours&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;22/tcp, 2888/tcp, 3888/tcp, 0.0.0.0:2181-&amp;gt;2181/tcp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;mac 피씨의 9092포트와 kafka docker container 내부 포트 9092를 연결하도록 설정했기 때문에 Spring Boot 를 로컬에서 바로 실행해서 연결할 수 있다.&lt;br /&gt;&lt;br /&gt;topic 생성&lt;/p&gt;
&lt;pre id=&quot;code_1708832317302&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; wanbaep ~ &amp;gt; docker exec -it kafka bash
 
root@9716ab3aeb82:~# kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --list

root@9716ab3aeb82:/# kafka-topics.sh --create --topic test --bootstrap-server 127.0.0.1:9092 --replication-factor 1 --partitions 1
Created topic test.

root@9716ab3aeb82:~# kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --list
test&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Producer&lt;/p&gt;
&lt;pre id=&quot;code_1708832420611&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@9716ab3aeb82:/# kafka-console-producer.sh --bootstrap-server 127.0.0.1:9092 --topic test
&amp;gt;hello
&amp;gt;This is producer&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;Consumer&lt;/p&gt;
&lt;pre id=&quot;code_1708832448699&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@9716ab3aeb82:~# kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092 --topic test --from-beginning
hello
This is producer&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;console producer와 consumer를 이용해서 정상적으로 토픽이 생성되고 메시지가 생성, 소비 되는지 간단하게 테스트까지만 해봤다.&lt;/p&gt;</description>
      <category>Kafka</category>
      <category>console</category>
      <category>consumer</category>
      <category>docker-compose</category>
      <category>Kafka</category>
      <category>producer</category>
      <category>Topic</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/23</guid>
      <comments>https://wanbaep.tistory.com/23#entry23comment</comments>
      <pubDate>Sun, 25 Feb 2024 12:42:30 +0900</pubDate>
    </item>
    <item>
      <title>[Kubernetes] minikube 메모리 CPU 설정</title>
      <link>https://wanbaep.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;minikube를 설치하고 시작하면 default 설정으로 cpus=2, memory=4000MB 으로 실행이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1952&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VTZkT/btsEY8Sb10x/81ddvSugtdibJmXChbucq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VTZkT/btsEY8Sb10x/81ddvSugtdibJmXChbucq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VTZkT/btsEY8Sb10x/81ddvSugtdibJmXChbucq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVTZkT%2FbtsEY8Sb10x%2F81ddvSugtdibJmXChbucq1%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;1952&quot; height=&quot;910&quot; data-origin-width=&quot;1952&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;minikube의 현재 설정을 아래 커맨드로 확인해 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 아무 설정도 되어있지 않기 때문에 빈 설정 정보를 볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1708234362697&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;wanbaep ~ &amp;gt; cat ~/.minikube/config/config.json
{}%&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;cpus 와 memory 설정을 변경하기 위해 아래 설정을 추가해보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 실행중인 minikube를 종료하고 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708234458960&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;wanbaep &amp;gt; minikube stop
✋  &quot;minikube&quot; 노드를 중지하는 중 ...
   &quot;minikube&quot;를 SSH로 전원을 끕니다 ...
   1개의 노드가 중지되었습니다.
 wanbaep &amp;gt; minikube delete
   docker 의 &quot;minikube&quot; 를 삭제하는 중 ...
   Deleting container &quot;minikube&quot; ...
   /Users/wanbaep/.minikube/machines/minikube 제거 중 ...
   &quot;minikube&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_1708234553309&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;minikube config set cpus 4
minikube config set memory 6500&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;minikube를 재실행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1952&quot; data-origin-height=&quot;1556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMxcQk/btsEYC0yNX8/5TFZsndLmPKNWEK9VkpAxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMxcQk/btsEYC0yNX8/5TFZsndLmPKNWEK9VkpAxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMxcQk/btsEYC0yNX8/5TFZsndLmPKNWEK9VkpAxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMxcQk%2FbtsEYC0yNX8%2F5TFZsndLmPKNWEK9VkpAxk%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;1952&quot; height=&quot;1556&quot; data-origin-width=&quot;1952&quot; data-origin-height=&quot;1556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치되면서 docker container의 cpus와 memory가 설정한 값으로 실행되는 것을 볼 수 있다.&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;minikube 설정을 확인해본다.&lt;/p&gt;
&lt;pre id=&quot;code_1708234646345&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; wanbaep &amp;gt; cat ~/.minikube/config/config.json
{
    &quot;cpus&quot;: &quot;4&quot;,
    &quot;memory&quot;: &quot;6500&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;</description>
      <category>Kubernetes</category>
      <category>config</category>
      <category>cpus</category>
      <category>Memory</category>
      <category>minikube</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/22</guid>
      <comments>https://wanbaep.tistory.com/22#entry22comment</comments>
      <pubDate>Sun, 18 Feb 2024 14:41:32 +0900</pubDate>
    </item>
    <item>
      <title>[Kubernetes] Kubernetes 환경에 Prometheus Operator 설치하기</title>
      <link>https://wanbaep.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Kubernetes monitoring 을 하기 위한 솔루션으로 prometheus 를 설치하려고 한다.&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;기존에 prometheus를 서버 형태로 설치해본 경험은 있었지만, Kubernetes 환경에서는 서버 형태로 설치하면 metric을 scrape할 대상이 추가될 때마다 수동으로 서버를 재시작 해주기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, Kubernetes 환경에서는 Operator형태의 prometheus를 설치해서 동적으로 metric을 scrape할 대상을 추가할 수 있도록 구성해볼 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치는 &lt;code&gt;helm&lt;/code&gt;을 사용해서 prometheus operator chart를 이용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사이트에서 &lt;code&gt;helm&lt;/code&gt; 명령어 대로 실행을 해본다. (혹시 helm 이 설치되어 있지 않다면 설치부터 진행해준다&amp;hellip; Mac이라서 &lt;code&gt;brew install helm&lt;/code&gt; 으로 설치가 가능하다.. 아닌경우에는 공식 사이트를 참조하면 된다 &lt;a href=&quot;https://helm.sh/docs/intro/install/&quot;&gt;https://helm.sh/docs/intro/install/&lt;/a&gt;)&lt;/p&gt;
&lt;figure id=&quot;og_1693269118186&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Installing Helm&quot; data-og-description=&quot;Learn how to install and get running with Helm.&quot; data-og-host=&quot;helm.sh&quot; data-og-source-url=&quot;https://helm.sh/docs/intro/install/&quot; data-og-url=&quot;https://helm.sh/docs/intro/install/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cVyY5d/hyTL6dGECV/MvhypzfbO4uO5hPD6RkGWK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://helm.sh/docs/intro/install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://helm.sh/docs/intro/install/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cVyY5d/hyTL6dGECV/MvhypzfbO4uO5hPD6RkGWK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Installing Helm&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to install and get running with Helm.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;helm.sh&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693269157932&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

helm install [RELEASE_NAME] prometheus-community/kube-prometheus-stack&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;아래와 같이 helm repository를 먼저 추가해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1693269188061&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
&quot;prometheus-community&quot; has been added to your repositories

$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
&quot;prometheus-community&quot; already exists with the same configuration, skipping&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;helm install 로 chart를 설치하기 전에 namespace를 먼저 생성하고 설치를 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1693269237625&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl create ns monitoring
namespace/monitoring created

$ helm install wb-prometheus prometheus-community/kube-prometheus-stack -nmonitoring
NAME: wb-prometheus
LAST DEPLOYED: Fri Aug 18 08:55:45 2023
NAMESPACE: monitoring
STATUS: deployed
REVISION: 1
NOTES:
kube-prometheus-stack has been installed. Check its status by running:
  kubectl --namespace monitoring get pods -l &quot;release=wb-prometheus&quot;

Visit https://github.com/prometheus-operator/kube-prometheus for instructions on how to create &amp;amp; configure Alertmanager and Prometheus instances using the Operator.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 결과가 나오면서 설치가 진행되며, 전부 정상 설치되는 것은 K8s Resource들을 확인해서 볼 수 있다.&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_1693269311915&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl get all -nmonitoring
NAME                                                         READY   STATUS    RESTARTS   AGE
pod/alertmanager-wb-prometheus-kube-prometh-alertmanager-0   2/2     Running   0          79s
pod/prometheus-wb-prometheus-kube-prometh-prometheus-0       2/2     Running   0          79s
pod/wb-prometheus-grafana-66f6c458c4-tbm9f                   3/3     Running   0          81s
pod/wb-prometheus-kube-prometh-operator-69cb7478b6-n88xh     1/1     Running   0          81s
pod/wb-prometheus-kube-state-metrics-65d9578fdc-xqzrv        1/1     Running   0          81s
pod/wb-prometheus-prometheus-node-exporter-66bc7             1/1     Running   0          81s
pod/wb-prometheus-prometheus-node-exporter-rdwpx             1/1     Running   0          81s
pod/wb-prometheus-prometheus-node-exporter-t694g             1/1     Running   0          81s

NAME                                              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
service/alertmanager-operated                     ClusterIP   None             &amp;lt;none&amp;gt;        9093/TCP,9094/TCP,9094/UDP   79s
service/prometheus-operated                       ClusterIP   None             &amp;lt;none&amp;gt;        9090/TCP                     79s
service/wb-prometheus-grafana                     ClusterIP   10.108.223.14    &amp;lt;none&amp;gt;        80/TCP                       81s
service/wb-prometheus-kube-prometh-alertmanager   ClusterIP   10.105.3.120     &amp;lt;none&amp;gt;        9093/TCP,8080/TCP            81s
service/wb-prometheus-kube-prometh-operator       ClusterIP   10.107.199.199   &amp;lt;none&amp;gt;        443/TCP                      81s
service/wb-prometheus-kube-prometh-prometheus     ClusterIP   10.101.30.107    &amp;lt;none&amp;gt;        9090/TCP,8080/TCP            81s
service/wb-prometheus-kube-state-metrics          ClusterIP   10.98.224.89     &amp;lt;none&amp;gt;        8080/TCP                     81s
service/wb-prometheus-prometheus-node-exporter    ClusterIP   10.97.200.77     &amp;lt;none&amp;gt;        9100/TCP                     81s

NAME                                                    DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
daemonset.apps/wb-prometheus-prometheus-node-exporter   3         3         3       3            3           kubernetes.io/os=linux   81s

NAME                                                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/wb-prometheus-grafana                 1/1     1            1           81s
deployment.apps/wb-prometheus-kube-prometh-operator   1/1     1            1           81s
deployment.apps/wb-prometheus-kube-state-metrics      1/1     1            1           81s

NAME                                                             DESIRED   CURRENT   READY   AGE
replicaset.apps/wb-prometheus-grafana-66f6c458c4                 1         1         1       81s
replicaset.apps/wb-prometheus-kube-prometh-operator-69cb7478b6   1         1         1       81s
replicaset.apps/wb-prometheus-kube-state-metrics-65d9578fdc      1         1         1       81s

NAME                                                                    READY   AGE
statefulset.apps/alertmanager-wb-prometheus-kube-prometh-alertmanager   1/1     79s
statefulset.apps/prometheus-wb-prometheus-kube-prometh-prometheus       1/1     79s&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;Pod 를 살펴보면 prometheus, alertmanager, grafana, prometheus operator, kube-state-metrics, node-exporter 등이 설치되어 있는 것을 볼 수 있다.&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;설치한 chart는 kube-prometheus 인데 관련 구성은 다음과 같이 되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kube-prometheus Package - &lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://github.com/prometheus-operator/kube-prometheus&quot;&gt;https://github.com/prometheus-operator/kube-prometheus&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1693269337320&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - prometheus-operator/kube-prometheus: Use Prometheus to monitor Kubernetes and applications running on Kubernetes&quot; data-og-description=&quot;Use Prometheus to monitor Kubernetes and applications running on Kubernetes - GitHub - prometheus-operator/kube-prometheus: Use Prometheus to monitor Kubernetes and applications running on Kubernetes&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/prometheus-operator/kube-prometheus&quot; data-og-url=&quot;https://github.com/prometheus-operator/kube-prometheus&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cL6ien/hyTL9VONWe/ok6mGoFLkSGBOlSL9tFGnK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/prometheus-operator/kube-prometheus&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/prometheus-operator/kube-prometheus&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cL6ien/hyTL9VONWe/ok6mGoFLkSGBOlSL9tFGnK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - prometheus-operator/kube-prometheus: Use Prometheus to monitor Kubernetes and applications running on Kubernetes&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Use Prometheus to monitor Kubernetes and applications running on Kubernetes - GitHub - prometheus-operator/kube-prometheus: Use Prometheus to monitor Kubernetes and applications running on Kubernetes&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Prometheus Operator&lt;/code&gt;는 Prometheus 관련 요소를 Kubernetes에 배포 및 관리 하는 기능을 제공하는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Prometheus&lt;/code&gt;는 Prometheus 서버를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Alertmanager&lt;/code&gt;는 Prometheus에서 감지된 Alarm을 외부로 notification 보내기 위한 용도이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Node exporter&lt;/code&gt;는 하드웨어, OS 매트릭을 수집하며, Go로 작성된 Plugin 형식 metric collector이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Prometheus Adapter for Kubernetes Metrics API&lt;/code&gt; 는 K8s의 HPA에 사용될 Custom Metric 지표를 생성해서 제공하기 위한 도구이다. 예를 들어 Pod에서 HTTP 요청에 대한 횟수를 Prometheus에 수집하고 이를 Prometheus Adapter를 통해서 Custom Metrics로 변환해서 HPA의 지표로 사용하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;kube-state-metrics&lt;/code&gt;는 Kubernetes API 서버를 수신하고 객체(K8s 객체들) 상태에 대한 지표를 생성하는 서비스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Grafana&lt;/code&gt;는 Prometheus와 같이 데이터 소스를 시각화 하는 서비스이다.&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;구성 패키지들에 대한 자세한 사항은 아래 github 내용을 통해서 확인이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Components included in this package:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;The&amp;nbsp;&lt;a href=&quot;https://github.com/prometheus-operator/prometheus-operator&quot;&gt;Prometheus Operator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Highly available&amp;nbsp;&lt;a href=&quot;https://prometheus.io/&quot;&gt;Prometheus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Highly available&amp;nbsp;&lt;a href=&quot;https://github.com/prometheus/alertmanager&quot;&gt;Alertmanager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/prometheus/node_exporter&quot;&gt;Prometheus node-exporter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes-sigs/prometheus-adapter&quot;&gt;Prometheus Adapter for Kubernetes Metrics APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes/kube-state-metrics&quot;&gt;kube-state-metrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://grafana.com/&quot;&gt;Grafana&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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;code&gt;Minikube&lt;/code&gt;에 Prometheus Operator를 설치했는데, Prometheus나 Alertmanager, Grafana등을 접속해보기 위해서 &lt;code&gt;NodePort&lt;/code&gt;로 변경해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 각 컴포넌트에 대한 &lt;code&gt;service.type&lt;/code&gt;을 &lt;code&gt;NodePort&lt;/code&gt;로 지정하는 yaml파일을 하나 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1693269425715&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;prometheus:
  service:
    type: NodePort
grafana:
  service:
    type: NodePort
alertmanager:
  service:
    type: NodePort&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;그리고 위에서 수행했던 helm install 시에 -f 옵션을 이용해서 value file을 추가한다. 이런 경우 value file에 정의된 값을 우선적으로 적용하게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1693269475334&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install wb-prometheus prometheus-community/kube-prometheus-stack -f prometheus-value.yaml -nmonitoring&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;이미 helm chart에 NodePort인 경우의 Port번호가 할당되어 있으며, 없는 경우 Kubernetes 에서 할당해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1693269517815&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl get svc -nmonitoring
NAME                                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                         AGE
alertmanager-operated                     ClusterIP   None             &amp;lt;none&amp;gt;        9093/TCP,9094/TCP,9094/UDP      10d
prometheus-operated                       ClusterIP   None             &amp;lt;none&amp;gt;        9090/TCP                        10d
wb-prometheus-grafana                     NodePort    10.99.237.208    &amp;lt;none&amp;gt;        80:31575/TCP                    10d
wb-prometheus-kube-prometh-alertmanager   NodePort    10.100.208.186   &amp;lt;none&amp;gt;        9093:30903/TCP,8080:30321/TCP   10d
wb-prometheus-kube-prometh-operator       ClusterIP   10.97.94.108     &amp;lt;none&amp;gt;        443/TCP                         10d
wb-prometheus-kube-prometh-prometheus     NodePort    10.105.53.89     &amp;lt;none&amp;gt;        9090:30090/TCP,8080:30648/TCP   10d
wb-prometheus-kube-state-metrics          ClusterIP   10.107.215.168   &amp;lt;none&amp;gt;        8080/TCP                        10d
wb-prometheus-prometheus-node-exporter    ClusterIP   10.98.254.223    &amp;lt;none&amp;gt;        9100/TCP                        10d&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;code&gt;minikube service&lt;/code&gt; 를 이용해서 로컬호스트에서 접속 가능한 상태로 만들어준다.&lt;/p&gt;
&lt;pre id=&quot;code_1693269619932&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ minikube service -nmonitoring --all

...
|------------|-----------------------------------------|-------------|------------------------|
| NAMESPACE  |                  NAME                   | TARGET PORT |          URL           |
|------------|-----------------------------------------|-------------|------------------------|
| monitoring | alertmanager-operated                   |             | http://127.0.0.1:56946 |
|            |                                         |             | http://127.0.0.1:56947 |
|            |                                         |             | http://127.0.0.1:56948 |
| monitoring | prometheus-operated                     |             | http://127.0.0.1:56950 |
| monitoring | wb-prometheus-grafana                   |             | http://127.0.0.1:56952 |
| monitoring | wb-prometheus-kube-prometh-alertmanager |             | http://127.0.0.1:56954 |
|            |                                         |             | http://127.0.0.1:56955 |
| monitoring | wb-prometheus-kube-prometh-operator     |             | http://127.0.0.1:56957 |
| monitoring | wb-prometheus-kube-prometh-prometheus   |             | http://127.0.0.1:56959 |
|            |                                         |             | http://127.0.0.1:56960 |
| monitoring | wb-prometheus-kube-state-metrics        |             | http://127.0.0.1:56962 |
| monitoring | wb-prometheus-prometheus-node-exporter  |             | http://127.0.0.1:56964 |
|------------|-----------------------------------------|-------------|------------------------|
...&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;prometheus를 설치한 &lt;code&gt;namespace&lt;/code&gt;에 대한 &lt;code&gt;minikube service&lt;/code&gt;를 수행하면 관련 &lt;code&gt;service&lt;/code&gt;들을 찾고 &lt;code&gt;NodePort&lt;/code&gt;인 경우 위에 처럼 접속 가능한 URL을 생성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력된 URL주소를 통해서 해당 서비스에 브라우저로 접속이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c46NF4/btssqvZwA82/TRgV4wwYfKskdBvizXjWt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c46NF4/btssqvZwA82/TRgV4wwYfKskdBvizXjWt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c46NF4/btssqvZwA82/TRgV4wwYfKskdBvizXjWt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc46NF4%2FbtssqvZwA82%2FTRgV4wwYfKskdBvizXjWt1%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;1912&quot; height=&quot;1000&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;1000&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;code&gt;ServiceMonitor&lt;/code&gt;를 이용한 metric scrape을 수행해볼 예정이다.&lt;/p&gt;</description>
      <category>Kubernetes</category>
      <category>minikube</category>
      <category>operator</category>
      <category>prometheus</category>
      <category>Prometheus Operator</category>
      <category>ServiceMonitor</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/21</guid>
      <comments>https://wanbaep.tistory.com/21#entry21comment</comments>
      <pubDate>Tue, 29 Aug 2023 09:42:52 +0900</pubDate>
    </item>
    <item>
      <title>[Kubernetes] Mac OS Minikube 노드 추가하기</title>
      <link>https://wanbaep.tistory.com/20</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;minikube로 K8s cluster설치 후 실습을 해보려는데 최초 설치하는 경우 node가 master하나만 있었기에 worker node를 추가해서 cluster환경을 구성하고 싶었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;찾아본 결과 node를 추가하는 방법은 아주 간단했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;a href=&quot;https://minikube.sigs.k8s.io/docs/commands/node/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://minikube.sigs.k8s.io/docs/commands/node/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;node&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Add, remove, or list additional nodes&quot; data-og-host=&quot;minikube.sigs.k8s.io&quot; data-og-source-url=&quot;https://minikube.sigs.k8s.io/docs/commands/node/&quot; data-og-url=&quot;https://minikube.sigs.k8s.io/docs/commands/node/&quot;&gt;&lt;a href=&quot;https://minikube.sigs.k8s.io/docs/commands/node/&quot; target=&quot;_blank&quot; data-source-url=&quot;https://minikube.sigs.k8s.io/docs/commands/node/&quot;&gt;&lt;div class=&quot;og-image&quot;&gt;&lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;node&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Add, remove, or list additional nodes&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;minikube.sigs.k8s.io&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;minikube node add&lt;/code&gt; 를 입력해서 node를 추가 할 수 있으며 여러 옵션들을 넣을 수 있는데, --worker 옵션은 default 가 true라서 특별히 추가해줄 필요는 없었다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTskQI/btsrsSUV8L6/of7ywuOSEks4ZqXjJmkdc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTskQI/btsrsSUV8L6/of7ywuOSEks4ZqXjJmkdc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTskQI/btsrsSUV8L6/of7ywuOSEks4ZqXjJmkdc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTskQI%2FbtsrsSUV8L6%2Fof7ywuOSEks4ZqXjJmkdc0%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;1574&quot; height=&quot;910&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 node를 추가한 경우 ROLES가 &amp;lt;none&amp;gt; 으로 나오는데, 필요한 경우 label에 role을 추가해서 설정 할 수 있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ kubectl get node minikube -oyaml | grep node-role -A2
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;node-role.kubernetes.io/control-plane: &quot;&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;node.kubernetes.io/exclude-from-external-load-balancers: &quot;&quot;
&amp;nbsp;&amp;nbsp;name: minikube
&amp;nbsp;&amp;nbsp;
$ kubectl get node
NAME&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; STATUS&amp;nbsp;&amp;nbsp; ROLES&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; AGE&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; VERSION
minikube&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Ready&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;control-plane&amp;nbsp;&amp;nbsp; 34m&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; v1.27.3
minikube-m02&amp;nbsp;&amp;nbsp; Ready&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;none&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2m26s&amp;nbsp;&amp;nbsp; v1.27.3
minikube-m03&amp;nbsp;&amp;nbsp; Ready&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;none&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;60s&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; v1.27.3

$ kubectl get node
NAME&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; STATUS&amp;nbsp;&amp;nbsp; ROLES&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; AGE&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; VERSION
minikube&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Ready&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;control-plane&amp;nbsp;&amp;nbsp; 37m&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; v1.27.3
minikube-m02&amp;nbsp;&amp;nbsp; Ready&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;worker&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5m10s&amp;nbsp;&amp;nbsp; v1.27.3
minikube-m03&amp;nbsp;&amp;nbsp; Ready&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;none&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3m44s&amp;nbsp;&amp;nbsp; v1.27.3

$ kubectl get node minikube-m02 -oyaml | grep role
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;node-role.kubernetes.io/worker: &quot;&quot;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;br&gt;&lt;/p&gt;</description>
      <category>Kubernetes</category>
      <category>minikube</category>
      <category>minikube node add</category>
      <category>Node</category>
      <category>node add</category>
      <category>Worker</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/20</guid>
      <comments>https://wanbaep.tistory.com/20#entry20comment</comments>
      <pubDate>Thu, 17 Aug 2023 08:59:59 +0900</pubDate>
    </item>
    <item>
      <title>[Kubernetes] Mac OS에 minikube로 Cluster 설치하기</title>
      <link>https://wanbaep.tistory.com/19</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;minikube는 가벼운 쿠버네티스 구현체로 로컬 머신에 VM을 만들어 하나의 노드로 구성한 클러스터를 생성한다.&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;minikube를 설치하려면 Docker container 혹은 VM 환경이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Docker 를 먼저 설치하고 minikube를 설치해보도록 하겠다.&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;Docker 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 경로에서 Mac OS용 Docker Desktop을 다운로드 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.docker.com/products/docker-desktop/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.docker.com/products/docker-desktop/&lt;/a&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-origin-width=&quot;3446&quot; data-origin-height=&quot;1568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/73PsP/btsqs86L9Xo/Ee5lSrh8SaEdIddaRNW7Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/73PsP/btsqs86L9Xo/Ee5lSrh8SaEdIddaRNW7Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/73PsP/btsqs86L9Xo/Ee5lSrh8SaEdIddaRNW7Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F73PsP%2Fbtsqs86L9Xo%2FEe5lSrh8SaEdIddaRNW7Tk%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;3446&quot; height=&quot;1568&quot; data-origin-width=&quot;3446&quot; data-origin-height=&quot;1568&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;현재 사용중인 Mac OS는 M1 칩이기 때문에 Apple Chip을 선택했다.&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;1664&quot; data-origin-height=&quot;904&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mwk7u/btsqvh2GRvA/KIzZBVCfCZk9hrsWVesUf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mwk7u/btsqvh2GRvA/KIzZBVCfCZk9hrsWVesUf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mwk7u/btsqvh2GRvA/KIzZBVCfCZk9hrsWVesUf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmwk7u%2Fbtsqvh2GRvA%2FKIzZBVCfCZk9hrsWVesUf1%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;1664&quot; height=&quot;904&quot; data-origin-width=&quot;1664&quot; data-origin-height=&quot;904&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dmg파일을 다운로드해서 drag and drop을 하면 Docker 설치가 완료된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTQHHj/btsqj3L13hv/7wPoZi8kkQ3KJFI7wfUj41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTQHHj/btsqj3L13hv/7wPoZi8kkQ3KJFI7wfUj41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTQHHj/btsqj3L13hv/7wPoZi8kkQ3KJFI7wfUj41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTQHHj%2Fbtsqj3L13hv%2F7wPoZi8kkQ3KJFI7wfUj41%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;1824&quot; height=&quot;1224&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Desktop App 실행시 위에 화면이 나오고 Accept를 눌러서 설치를 마저 진행한다.&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;1936&quot; data-origin-height=&quot;1646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UuuEH/btsrjqFpGUz/eNUJ2fFlStsti4KJYlfrck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UuuEH/btsrjqFpGUz/eNUJ2fFlStsti4KJYlfrck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UuuEH/btsrjqFpGUz/eNUJ2fFlStsti4KJYlfrck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUuuEH%2FbtsrjqFpGUz%2FeNUJ2fFlStsti4KJYlfrck%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;1936&quot; height=&quot;1646&quot; data-origin-width=&quot;1936&quot; data-origin-height=&quot;1646&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Finish 를 누르게 되면 설치가 완료되고 docker 실행 시 아래와 같은 화면이 나온다..!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2764&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FkBFe/btsrjsQMNdJ/qq0SBNPdoVQDXNqHnUCnC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FkBFe/btsrjsQMNdJ/qq0SBNPdoVQDXNqHnUCnC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FkBFe/btsrjsQMNdJ/qq0SBNPdoVQDXNqHnUCnC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFkBFe%2FbtsrjsQMNdJ%2Fqq0SBNPdoVQDXNqHnUCnC0%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;2764&quot; height=&quot;1664&quot; data-origin-width=&quot;2764&quot; data-origin-height=&quot;1664&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;하지만,, 위처럼 설치한 경우 docker CLI는 함께 설치되지 않는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 필요한 경우 CLI를 별도로 설치해야 할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.docker.com/products/cli/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.docker.com/products/cli/&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;뭔가.. 따로 설치하기 좀 찜찜했기 때문에&amp;nbsp;결국,, 설치했던 Docker Desktop App을 삭제하고 homebrew로 다시 설치했다. (homebrew 최고..!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691626174980&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install --cask docker&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(--cask 옵션을 추가하면 Docker Desktop 뿐만 아니라 docker-compose, docker-machine 등을 함께 설치해준다.)&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;minikube 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;minikube로 local cluster를 설치할 예정이다. 아래 공식 사이트에서 스펙을 체크하고 install command를 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://minikube.sigs.k8s.io/docs/start/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://minikube.sigs.k8s.io/docs/start/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692227624294&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;minikube start&quot; data-og-description=&quot;minikube is local Kubernetes&quot; data-og-host=&quot;minikube.sigs.k8s.io&quot; data-og-source-url=&quot;https://minikube.sigs.k8s.io/docs/start/&quot; data-og-url=&quot;https://minikube.sigs.k8s.io/docs/start/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://minikube.sigs.k8s.io/docs/start/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://minikube.sigs.k8s.io/docs/start/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;minikube start&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;minikube is local Kubernetes&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;minikube.sigs.k8s.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macOS Apple M1 chip은 ARM based CPU architecture를 사용하고 있기에 아래 처럼 스팩을 선택했다. (그런데 brew를 선택하면 사실 command에 차이는 없다..)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2270&quot; data-origin-height=&quot;1234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ncJMT/btsrjthO8LF/Q9bvDz3vVDp7hflpvHYBj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ncJMT/btsrjthO8LF/Q9bvDz3vVDp7hflpvHYBj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ncJMT/btsrjthO8LF/Q9bvDz3vVDp7hflpvHYBj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FncJMT%2FbtsrjthO8LF%2FQ9bvDz3vVDp7hflpvHYBj0%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;2270&quot; height=&quot;1234&quot; data-origin-width=&quot;2270&quot; data-origin-height=&quot;1234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;minikube 설치 command&lt;/p&gt;
&lt;pre id=&quot;code_1692227904228&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install minikube&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;code&gt;minikube start&lt;/code&gt; 를 입력해서 minikube 를 시작하도록 한다. 이 과정은 위에 install 보다는 살짝 시간이 걸린다.&lt;/p&gt;
&lt;pre id=&quot;code_1692228224471&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;minikube start&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1364&quot; data-origin-height=&quot;1046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ED0Jj/btsroJ5LilJ/LumPmo0vCK8vFzK8FE6sq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ED0Jj/btsroJ5LilJ/LumPmo0vCK8vFzK8FE6sq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ED0Jj/btsroJ5LilJ/LumPmo0vCK8vFzK8FE6sq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FED0Jj%2FbtsroJ5LilJ%2FLumPmo0vCK8vFzK8FE6sq0%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;1364&quot; height=&quot;1046&quot; data-origin-width=&quot;1364&quot; data-origin-height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 &lt;code&gt;minikube start&lt;/code&gt; 를 수행한 내용을 살펴보면 컨트롤 플레인, node resource 설정(CPU, memory), RBAC 구성, Network 관련 설정 등 쿠버네티스 구성에 필요한 정보들을 수행하는 것을 확인할 수 있다.&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;몇 가지 command를 사용해서 cluster 정보 및 자원들을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;978&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0pSdP/btsrjr5or7L/ioytZnKm7UJ8KnqBwr7SY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0pSdP/btsrjr5or7L/ioytZnKm7UJ8KnqBwr7SY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0pSdP/btsrjr5or7L/ioytZnKm7UJ8KnqBwr7SY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0pSdP%2Fbtsrjr5or7L%2FioytZnKm7UJ8KnqBwr7SY0%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;1574&quot; height=&quot;978&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;978&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 정보들을 web에서 dashboard형태로도 확인할 수 있는데, Terminal에서 &lt;code&gt;minikube dashboard&lt;/code&gt;를 입력하면 대시보드가 실행되며 아래 화면이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;876&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W7ZW6/btsrsRBIDOI/GGvHOpNS4ZwhhqiuNIDbh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W7ZW6/btsrsRBIDOI/GGvHOpNS4ZwhhqiuNIDbh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W7ZW6/btsrsRBIDOI/GGvHOpNS4ZwhhqiuNIDbh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW7ZW6%2FbtsrsRBIDOI%2FGGvHOpNS4ZwhhqiuNIDbh0%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;1574&quot; height=&quot;876&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;876&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2872&quot; data-origin-height=&quot;2104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bG99xV/btsrqw6avpj/uTDuKvPzehwpmedkS2Egp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bG99xV/btsrqw6avpj/uTDuKvPzehwpmedkS2Egp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bG99xV/btsrqw6avpj/uTDuKvPzehwpmedkS2Egp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbG99xV%2Fbtsrqw6avpj%2FuTDuKvPzehwpmedkS2Egp0%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;2872&quot; height=&quot;2104&quot; data-origin-width=&quot;2872&quot; data-origin-height=&quot;2104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;minikube 설치는 Container Manager(docker) 설치 후 minikube를 설치하면 매우 간단하게 설치할 수 있다.&lt;/p&gt;</description>
      <category>Kubernetes</category>
      <category>Cluster</category>
      <category>Docker</category>
      <category>kubernetes</category>
      <category>minikube</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/19</guid>
      <comments>https://wanbaep.tistory.com/19#entry19comment</comments>
      <pubDate>Thu, 17 Aug 2023 08:03:14 +0900</pubDate>
    </item>
    <item>
      <title>[JUnit] JUnit4 Exception 발생 후 Mockito verify</title>
      <link>https://wanbaep.tistory.com/18</link>
      <description>&lt;div class=&quot;markdown-body&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 테스트 코드를 작성하던 도중 Exception 발생 시 catch 이후의 flow 를 검증해야 하는 상황이 발생했다.&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;Exception Testing에 사용한 방법은 &lt;b&gt;ExpectedException Rule&lt;/b&gt; 을 사용하고 있었다. &lt;code&gt;ExpectedException Rule&lt;/code&gt; 같은 경우, 발생할(expect) exception 에 대해서 작성해 둘 뿐만 아니라 예상되는 message까지 쉽게 선언 할 수 있어서 좋은 테스트 방식으로 생각하고 있었다.&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;code&gt;Mockito verify&lt;/code&gt; 메소드를 호출하면 될 것으로 예상했는데 막상 테스트 코드를 작성 하고 실행해보니 예상과 다르게 동작했다.&lt;/p&gt;
  &lt;p data-ke-size=&quot;size16&quot;&gt;이를 확인해 보고자 junit wiki를 확인해보니 &lt;code&gt;ExpectedException Rule&lt;/code&gt;의 경우 테스트 중인 메소드가 예외를 던지면 이후의 테스트는 실행되지(유효하지) 않다는 내용이었다. 이러한 점은 사용자에게 혼동을 줄 수 있고 이 때문에 &lt;code&gt;ExpectedException.none()&lt;/code&gt;은 &lt;code&gt;deprecated&lt;/code&gt;되었다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/junit-team/junit4/wiki/Exception-testing&quot;&gt;Exception testing &amp;middot; junit-team/junit4 Wiki&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1640593071678&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - junit-team/junit4: A programmer-oriented testing framework for Java.&quot; data-og-description=&quot;A programmer-oriented testing framework for Java. Contribute to junit-team/junit4 development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/junit-team/junit4/wiki/Exception-testing&quot; data-og-url=&quot;https://github.com/junit-team/junit4&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/PZkXk/hyMRyInBKg/OOTwshyp9eP3hFYED4UHt1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/junit-team/junit4/wiki/Exception-testing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/junit-team/junit4/wiki/Exception-testing&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/PZkXk/hyMRyInBKg/OOTwshyp9eP3hFYED4UHt1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - junit-team/junit4: A programmer-oriented testing framework for Java.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A programmer-oriented testing framework for Java. Contribute to junit-team/junit4 development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
  &lt;p data-ke-size=&quot;size16&quot;&gt;혼동을 줄 수 있다는 점은 Exception 이후의 예상되는 동작이 Test를 실행 한 후 &lt;code&gt;Mockito verify&lt;/code&gt;에 의해 호출되었는가를 판단하기 전에 기대한 Exception 이 발생되어 Test가 Pass 되는 상황을 불러 올 수 있다.(Exception만 판단하기 때문에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명한 문제는 파일의 메타 데이터를 DB에 저장하고 파일 서버에 물리적인 파일을 저장하다가 실패하는 case에 DB를 rollback하는 테스트를 작성 중인 상황에서 발생했다.&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;Exception 발생시 DB를 rollback 하는 code를 아직 추가 하지 않은 상태로 테스트 코드만 먼저 작성해서 rollback code를 &lt;code&gt;Mockito verify&lt;/code&gt;로 검증하는 테스트 코드를 작성해 두었는데, &lt;code&gt;ExpectedException Rule&lt;/code&gt;에서는 Exception발생 여부만 확인하기 때문에 Test가 Pass되고 있었다. 따라서 &lt;code&gt;ExpectedException Rule&lt;/code&gt; Testing 방식은 &lt;b&gt;잘못 작성한 경우 다른 사용자에게 혼동을 줄 수 있고, Exception이후의 flow를 테스트 하기에는 적절하지 않은 테스트 방식이었다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;
  public void fileUpload(file) {
      ...
      try {
          fileDao.save(file metadata);
          minioService.store(file);
      } catch (IOException e) {
          //fileDao.rollback();  //rollback 코드를 추가 하기 전
      }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;
  @Rule
  public ExpectedException exception = ExpectedException.none();

  ...

  @Test
  public void testFileUpload() {
  exception.expect(IOException.class);
      exception.expectMessage(&quot;error message&quot;);

      Mockito.doThrow(IOException.class).when(minioService).store(any());
      fileService.fileUpload(file);  //exception 발생
      Mockito.Verify(fileDao).rollback();  // -&amp;gt; 실제 구현된 rollback 코드가 없기 때문에 Test false가 나올 것으로 예상되지만, Exception 검사만 하기때문에 Pass.
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 예제 코드를 보면, 실제 rollback 코드는 아직 구현되지 않은 상태로 추가가 되어 있지 않은 상태에서 &lt;code&gt;Mockito verfiy&lt;/code&gt;를 호출하면 테스트에 실패할 것으로 예상했다. 하지만 실제 실행했을 때는 &lt;code&gt;fileUpload&lt;/code&gt; method의 &lt;code&gt;minioService.store(file)&lt;/code&gt;에서 &lt;code&gt;IOException&lt;/code&gt;이 발생하며 expect exception이 발생하기 때문에 Test는 pass로 완료된다.&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;따라서 exception 이후의 flow에 대한 검증을 하기위해서는 다른 방법을 사용해야 한다. &lt;code&gt;@Test&lt;/code&gt; annotaion 을 이용한 방법도 마찬가지이다. (&lt;code&gt;Try/Catch&lt;/code&gt; 문구, JUnit 4.13 부터는 &lt;code&gt;assertThrows&lt;/code&gt; 방법도 사용 가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
  &lt;p data-ke-size=&quot;size16&quot;&gt;만약 꼭 &lt;code&gt;ExpectedException Rule&lt;/code&gt; 방식을 사용해서 Exception 이후의 flow를 검증해야 한다면, &lt;code&gt;Try/finally&lt;/code&gt; 를 사용해서 실행되게 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;
  @Test
  public void testFileUpload() {
      exception.expect(IOException.class);
      exception.expectMessage(&quot;error message&quot;);

      Mockito.doThrow(IOException.class).when(minioService).store(any());
      try {
          fileService.fileUpload(file);  //Exception 발생
      } finally {
          Mockito.Verify(fileDao).rollback();  //finally 문구가 먼저 실행
      }
  }
&lt;/code&gt;&lt;/pre&gt;
  &lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위 방식은 의도되는 테스트 작성 방식과는 거리가 있어 보인다. 따라서 &lt;code&gt;deprecated&lt;/code&gt;된 &lt;code&gt;ExpectedException Rule&lt;/code&gt; 방식보다는 다른 Exception Testing 방법을 사용하는 것이 이후의 테스트 코드 관리를 위해서도 좋을 것 같다.&lt;/p&gt;
&lt;/div&gt;</description>
      <category>BackEnd/Test</category>
      <category>Exception</category>
      <category>ExpectedException</category>
      <category>JUnit</category>
      <category>mockito</category>
      <category>verify</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/18</guid>
      <comments>https://wanbaep.tistory.com/18#entry18comment</comments>
      <pubDate>Mon, 27 Dec 2021 17:44:07 +0900</pubDate>
    </item>
    <item>
      <title>Intellij Servlet 프로젝트 생성 후 war 배포하기</title>
      <link>https://wanbaep.tistory.com/17</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 웹 애플리케이션 디렉토리 구조에 대해서 살펴봤었다. 웹 애플리케이션을 war 확장자로 패키징 하고, 이를 Tomcat과 같은 서블릿 컨테이너에 배포해서 서비스를 실행한다고 했었다. 이번 포스팅에서는 직접 war 패키징을 해보고 Tomcat에 수동 배포를 수행해 보려고 한다.&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;a href=&quot;https://wanbaep.tistory.com/16&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://wanbaep.tistory.com/16&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1640571716693&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;웹 애플리케이션 디렉토리 구조&quot; data-og-description=&quot;웹 애플리케이션은 다음과 같은 디렉토리 구조를 가지며 이 구조는 웹 표준 디렉토리 구조이다. WebAppRoot ├── WEB-INF │ ├── classes │ ├── lib │ └── web.xml ├── index.html └── index.&quot; data-og-host=&quot;wanbaep.tistory.com&quot; data-og-source-url=&quot;https://wanbaep.tistory.com/16&quot; data-og-url=&quot;https://wanbaep.tistory.com/16&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cASaHc/hyMRw4sCTp/laJIWb0KZkXRKFvpk1cFOK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cr1DCZ/hyMQi02s6I/QNXYWaRQKvinBlZlnRVK41/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://wanbaep.tistory.com/16&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wanbaep.tistory.com/16&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cASaHc/hyMRw4sCTp/laJIWb0KZkXRKFvpk1cFOK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cr1DCZ/hyMQi02s6I/QNXYWaRQKvinBlZlnRVK41/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;웹 애플리케이션 디렉토리 구조&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;웹 애플리케이션은 다음과 같은 디렉토리 구조를 가지며 이 구조는 웹 표준 디렉토리 구조이다. WebAppRoot ├── WEB-INF │ ├── classes │ ├── lib │ └── web.xml ├── index.html └── index.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wanbaep.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Intellij 에서 Servlet 프로젝트 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;File &amp;gt; New &amp;gt; Project 선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;1346&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lHiwm/btroSWVxxGv/p5A0UfTreCqniaZkzB4D8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lHiwm/btroSWVxxGv/p5A0UfTreCqniaZkzB4D8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lHiwm/btroSWVxxGv/p5A0UfTreCqniaZkzB4D8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlHiwm%2FbtroSWVxxGv%2Fp5A0UfTreCqniaZkzB4D8K%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;563&quot; height=&quot;472&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;1346&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 정보 입력 후 Finish&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;1344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blZk0z/btro13edp7b/09A3W3o24PwRzuk70ZKggK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blZk0z/btro13edp7b/09A3W3o24PwRzuk70ZKggK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blZk0z/btro13edp7b/09A3W3o24PwRzuk70ZKggK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblZk0z%2Fbtro13edp7b%2F09A3W3o24PwRzuk70ZKggK%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;585&quot; height=&quot;490&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;1344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Web Application Framework 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 우클릭 &amp;gt; Add Framework Support... 선택 후 Web Applicaiton 추가&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BSgkX/btroT5dhwtY/kC3khhSRLFROrkqbZCsoeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BSgkX/btroT5dhwtY/kC3khhSRLFROrkqbZCsoeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BSgkX/btroT5dhwtY/kC3khhSRLFROrkqbZCsoeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBSgkX%2FbtroT5dhwtY%2FkC3khhSRLFROrkqbZCsoeK%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;492&quot; height=&quot;313&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;1216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cETjMt/btroTnedMi7/BcN2ET9ITKLqTVlAhWNNN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cETjMt/btroTnedMi7/BcN2ET9ITKLqTVlAhWNNN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cETjMt/btroTnedMi7/BcN2ET9ITKLqTVlAhWNNN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcETjMt%2FbtroTnedMi7%2FBcN2ET9ITKLqTVlAhWNNN0%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;610&quot; height=&quot;481&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;1216&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;Web Application 을 추가 하는 경우 web 디렉토리가 생성되며 WEB-INF/web.xml (Deployment Descriptor) 배치 파일과 index.jsp파일이 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ak8Bc/btroTl8zQYq/hetm3r96rJ5zVEcyzhQ081/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ak8Bc/btroTl8zQYq/hetm3r96rJ5zVEcyzhQ081/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ak8Bc/btroTl8zQYq/hetm3r96rJ5zVEcyzhQ081/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAk8Bc%2FbtroTl8zQYq%2Fhetm3r96rJ5zVEcyzhQ081%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;481&quot; height=&quot;332&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 위해서 index.jsp 파일 body에 문장을 수정해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oRFc6/btroTl1NGsH/1wFkVkc3PcnTn3AEksFrZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oRFc6/btroTl1NGsH/1wFkVkc3PcnTn3AEksFrZk/img.png&quot; data-alt=&quot; index.jsp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oRFc6/btroTl1NGsH/1wFkVkc3PcnTn3AEksFrZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoRFc6%2FbtroTl1NGsH%2F1wFkVkc3PcnTn3AEksFrZk%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;620&quot; height=&quot;243&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; index.jsp&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;war 로 패키징&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;war 로 프로젝트를 패키징 하기 위해 war Archive 설정이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;File &amp;gt; Project Structure... 를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;7.png&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;984&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHMC2c/btroYj2Fmqo/Ds25MkVFdrkvte7hgMNjH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHMC2c/btroYj2Fmqo/Ds25MkVFdrkvte7hgMNjH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHMC2c/btroYj2Fmqo/Ds25MkVFdrkvte7hgMNjH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHMC2c%2FbtroYj2Fmqo%2FDs25MkVFdrkvte7hgMNjH1%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;399&quot; height=&quot;571&quot; data-filename=&quot;7.png&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;984&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;Artifacts &amp;gt; + 선택 &amp;gt; Web Application: Archive 에서 war exploded 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;war 확장자로 archive 생성을 하겠다는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;8.png&quot; data-origin-width=&quot;2050&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dYfCG9/btroVjWIhz9/J10kiFk6wuYIiQ3gHhQeN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dYfCG9/btroVjWIhz9/J10kiFk6wuYIiQ3gHhQeN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dYfCG9/btroVjWIhz9/J10kiFk6wuYIiQ3gHhQeN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdYfCG9%2FbtroVjWIhz9%2FJ10kiFk6wuYIiQ3gHhQeN0%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;2050&quot; height=&quot;1656&quot; data-filename=&quot;8.png&quot; data-origin-width=&quot;2050&quot; data-origin-height=&quot;1656&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;war 확장자 이름을 적당하게 설정해준다. 이 이름은 application service의 명칭이 된다. 나중에 localhost:8080/webtest 가 애플리케이션 web root 가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;9.png&quot; data-origin-width=&quot;2042&quot; data-origin-height=&quot;1648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I59W8/btroTUJkMGa/3k2WMIBxrAmkZTuLjDLtw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I59W8/btroTUJkMGa/3k2WMIBxrAmkZTuLjDLtw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I59W8/btroTUJkMGa/3k2WMIBxrAmkZTuLjDLtw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI59W8%2FbtroTUJkMGa%2F3k2WMIBxrAmkZTuLjDLtw0%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;2042&quot; height=&quot;1648&quot; data-filename=&quot;9.png&quot; data-origin-width=&quot;2042&quot; data-origin-height=&quot;1648&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가한 Artifacts를 빌드한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;10.png&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dicSb8/btroWojcSPX/oq9r1K8x2UitrKOIcKkxzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dicSb8/btroWojcSPX/oq9r1K8x2UitrKOIcKkxzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dicSb8/btroWojcSPX/oq9r1K8x2UitrKOIcKkxzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdicSb8%2FbtroWojcSPX%2Foq9r1K8x2UitrKOIcKkxzk%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;402&quot; height=&quot;244&quot; data-filename=&quot;10.png&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;11.png&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uLfip/btroZEy1Jyp/rshIVW7FyaspD8gkNwiWbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uLfip/btroZEy1Jyp/rshIVW7FyaspD8gkNwiWbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uLfip/btroZEy1Jyp/rshIVW7FyaspD8gkNwiWbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuLfip%2FbtroZEy1Jyp%2FrshIVW7FyaspD8gkNwiWbk%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;455&quot; height=&quot;226&quot; data-filename=&quot;11.png&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 후 생성된 war 파일을 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;12.png&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;346&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/szITc/btroTnkX2jQ/LWi6cE8GXwd77lnPPMrPz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/szITc/btroTnkX2jQ/LWi6cE8GXwd77lnPPMrPz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/szITc/btroTnkX2jQ/LWi6cE8GXwd77lnPPMrPz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FszITc%2FbtroTnkX2jQ%2FLWi6cE8GXwd77lnPPMrPz0%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;493&quot; height=&quot;234&quot; data-filename=&quot;12.png&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;346&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Tomcat 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tomcat 은 간략한 테스트를 위해 tar.gz 를 다운받아 bin/startup.sh 로 실행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 종료시에는 bin/shutdown.sh 를 실행하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;13.png&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8ht21/btroVkBhLvP/Pk2YIQeBBqwMEc34pNkztk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8ht21/btroVkBhLvP/Pk2YIQeBBqwMEc34pNkztk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8ht21/btroVkBhLvP/Pk2YIQeBBqwMEc34pNkztk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8ht21%2FbtroVkBhLvP%2FPk2YIQeBBqwMEc34pNkztk%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;782&quot; height=&quot;392&quot; data-filename=&quot;13.png&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;14.png&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pM1Mm/btroTl8zUBg/YKxJmKLdrGG1ppzKdMuDhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pM1Mm/btroTl8zUBg/YKxJmKLdrGG1ppzKdMuDhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pM1Mm/btroTl8zUBg/YKxJmKLdrGG1ppzKdMuDhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpM1Mm%2FbtroTl8zUBg%2FYKxJmKLdrGG1ppzKdMuDhK%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;782&quot; height=&quot;222&quot; data-filename=&quot;14.png&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;406&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;localhost:8080 으로 접속해서 tomcat이 정상 실행되었는지 확인한다. (기본 포트가 8080으로 설정되어있다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;15.png&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IY5uJ/btroWmZZVBr/02m2ZzJcb2pZzTODMTe7TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IY5uJ/btroWmZZVBr/02m2ZzJcb2pZzTODMTe7TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IY5uJ/btroWmZZVBr/02m2ZzJcb2pZzTODMTe7TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIY5uJ%2FbtroWmZZVBr%2F02m2ZzJcb2pZzTODMTe7TK%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;2136&quot; height=&quot;642&quot; data-filename=&quot;15.png&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Tomcat 에 war 파일 배포&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tomcat 의 webapps 디렉토리 내에 이전에 Build 한 war 패키지를 업로드 해둔다. 그러면 Tomcat이 war파일을 감지하고 해당 디렉토리에 압축해제를 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림을 보면 처음에는 webtest.war 파일만 있다가 이 후 webtest 디렉토리가 추가로 보이는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;16.png&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k1IUz/btroTmM9OLw/TATaJK9dsteMO0FbNaITok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k1IUz/btroTmM9OLw/TATaJK9dsteMO0FbNaITok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k1IUz/btroTmM9OLw/TATaJK9dsteMO0FbNaITok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk1IUz%2FbtroTmM9OLw%2FTATaJK9dsteMO0FbNaITok%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;1426&quot; height=&quot;376&quot; data-filename=&quot;16.png&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;376&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;webtest path로 접속해서 이전에 작성해둔 body내용이 잘 나오는지 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;17.png&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Du846/btro136lFYK/qZQ1r57zXUfpsFlrDMKan1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Du846/btro136lFYK/qZQ1r57zXUfpsFlrDMKan1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Du846/btro136lFYK/qZQ1r57zXUfpsFlrDMKan1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDu846%2Fbtro136lFYK%2FqZQ1r57zXUfpsFlrDMKan1%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;1108&quot; height=&quot;362&quot; data-filename=&quot;17.png&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 잘 보면 하나의 디렉토리 단위로 애플리케이션을 구성한다는 점도 확인 할 수 있다. 그리고 수정된 내용을 추가해서 빌드한 war 파일을 업로드 해두면 Tomcat에서 이를 인지하고 다시 압축해제를 하여 변경된 애플리케이션의 내용이 배포되도록 한다.&lt;/p&gt;</description>
      <category>BackEnd/Jsp Servlet</category>
      <category>IntelliJ</category>
      <category>Servlet</category>
      <category>Tomcat</category>
      <category>War</category>
      <category>배포</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/17</guid>
      <comments>https://wanbaep.tistory.com/17#entry17comment</comments>
      <pubDate>Mon, 27 Dec 2021 11:32:26 +0900</pubDate>
    </item>
    <item>
      <title>웹 애플리케이션 디렉토리 구조</title>
      <link>https://wanbaep.tistory.com/16</link>
      <description>&lt;div class=&quot;markdown-body&quot;&gt;
&lt;p&gt;웹 애플리케이션은 다음과 같은 디렉토리 구조를 가지며 이 구조는 웹 표준 디렉토리 구조이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;WebAppRoot
├── WEB-INF
│   ├── classes
│   ├── lib
│   └── web.xml
├── index.html
└── index.jsp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Intellij나 Eclipse와 같은 에디터에서 웹 애플리케이션을 작성하고 war 패키지로 빌드한 후, 이를 압축 해제 하면 위와 같은 구조가 나오는 것을 확인 할 수 있다.&lt;/p&gt;&lt;br&gt;
&lt;p&gt;주로 Tomcat과 같은 서블릿 컨테이너를 동작시켜 두고 Application Root 디렉토리 위치에 {WebApp}.war 파일을 업로드 해두면, 자동으로 .war 파일 압축을 해제한다. 물론 압축 해제된 내용은 위에 구조와 동일하다.&lt;/p&gt;&lt;br&gt;
&lt;p&gt;각 파일에 대해서 살펴보도록 하자.&lt;/p&gt;&lt;br&gt;
&lt;p&gt;&lt;code&gt;WebAppRoot&lt;/code&gt; 위치에 HTML(.html), JSP, JavaScript(.js), CSS(.css) 와 같은 파일들이 위치한다. 해당 위치에 놓이는 파일들은 웹 애플리케이션이 배치 될 때 그대로 옮겨진다.&lt;/p&gt;&lt;br&gt;
&lt;p&gt;&lt;code&gt;WEB-INF&lt;/code&gt; - 웹 애플리케이션의 설정 파일들이 해당 디렉토리 아래에 포함된다. WEB-INF 내에 있는 파일들은 클라이언트에서 요청할 수 없다. 따라서 html js와 같은 정적 자원은 두지 않는다.&lt;/p&gt;&lt;br&gt;
&lt;p&gt;&lt;code&gt;WEB-INF/classes&lt;/code&gt; - 컴파일된 자바 클래스(.class) 파일이 있으며, src에 선언된 package가 동일하게 생성된다.&lt;/p&gt;&lt;br&gt;
&lt;p&gt;&lt;code&gt;WEB-INF/lib&lt;/code&gt; - 애플리케이션 실행에 필요한 라이브러리 즉, jar 파일들을 모아두는 디렉토리이다. jar은 자바 아카이브 파일이란 의미로 Java + ARchive의 합성어로 jar 로 사용된다.&lt;/p&gt;&lt;br&gt;
&lt;p&gt;&lt;code&gt;WEB-INF/web.xml&lt;/code&gt; - 웹 애플리케이션 배치 설명서 파일이다. 애플리케이션 컴포넌트들의 배치 정보들을 해당 파일에 작성한다. 서블릿 컨테이너는 web.xml 배치 설명서를 참조해서 서블릿, 필터, 리스너 그리고 매개변수 등을 찾거나 실행한다.&lt;/p&gt;&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;웹 애플리케이션 배치 설명서 - Web Application Deployment Descriptor 이며 DD파일 이라고도 불린다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;/div&gt;</description>
      <category>BackEnd/Jsp Servlet</category>
      <category>DD파일</category>
      <category>WEB-INF</category>
      <category>web.xml</category>
      <category>웹 애플리케이션</category>
      <category>웹 애플리케이션 구조</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/16</guid>
      <comments>https://wanbaep.tistory.com/16#entry16comment</comments>
      <pubDate>Tue, 14 Dec 2021 00:34:01 +0900</pubDate>
    </item>
    <item>
      <title>서블릿(Servlet) 프로그래밍</title>
      <link>https://wanbaep.tistory.com/15</link>
      <description>&lt;div class=&quot;mardown-body&quot;&gt;
&lt;h1&gt;서블릿(Servlet) 프로그래밍&lt;/h1&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;먼저 Web Application 의 동작 순서를 아래 그림과 함께 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3680&quot; data-origin-height=&quot;797&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGLQbW/btrnDnZvjh2/ON4rwEVQkFr15DZM2PRjY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGLQbW/btrnDnZvjh2/ON4rwEVQkFr15DZM2PRjY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGLQbW/btrnDnZvjh2/ON4rwEVQkFr15DZM2PRjY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGLQbW%2FbtrnDnZvjh2%2FON4rwEVQkFr15DZM2PRjY1%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;3680&quot; height=&quot;797&quot; data-origin-width=&quot;3680&quot; data-origin-height=&quot;797&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 브라우저에 요청을 입력하면 브라우저는 이를 HTTP Protocol의 요청으로 만들어서 웹 서버에 요청을 보낸다. 웹 서버는 받은 요청에 따라서 프로그램을 실행시킨다. 프로그램은 실행된 결과를 다시 웹 서버에 리턴하고 웹 서버는 프로그램에게 받은 결과를 HTTP Protocol의 응답으로 만들어서 브라우저에 응답한다. 최종적으로 사용자는 요청을 확인 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 과정에서 &lt;b&gt;웹 서버와 프로그램 사이에서 데이터를 주고 받는 규칙&lt;/b&gt;이 있는데 이를 &lt;b&gt;CGI(Common Gateway Interface)&lt;/b&gt; 규칙이라고 한다. &lt;b&gt;1) 웹 서버에 의해 실행되며, 2) CGI 규칙에 따라 웹 서버와 데이터를 주고 받도록 작성된 프로그램을 CGI 프로그램&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CGI 프로그램은 다양한 언어로 작성 될 수 있다. C/C++, Java 와 같은 컴파일 언어 혹은 php, Python 등과 같은 인터프리터 언어 등으로 작성 될 수 있으며, 언어적 특성에 따른 차이는 다양하겠지만 간단하게만 살펴보면 컴파일 언어의 빠른 실행속도와 인터프리터 언어의 변경용이성 정도를 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서 &lt;b&gt;Java로 작성된 CGI프로그램을 서블릿(Servlet)&lt;/b&gt; 이라고 한다. 좀 더 자세하게 이야기 하면, 자바 서블릿은 웹 서버와 직접 데이터를 주고 받지 않고 전문 프로그램에 의해 관리 되는데, 이를 &lt;b&gt;서블릿 컨테이너&lt;/b&gt;라고 한다. 즉, 아래와 같은 구조로 데이터를 주고 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3025&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bptlUl/btrnBy1XN8F/Iz6vsKw0ucUE7wFu0W0B70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bptlUl/btrnBy1XN8F/Iz6vsKw0ucUE7wFu0W0B70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bptlUl/btrnBy1XN8F/Iz6vsKw0ucUE7wFu0W0B70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbptlUl%2FbtrnBy1XN8F%2FIz6vsKw0ucUE7wFu0W0B70%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;3025&quot; height=&quot;792&quot; data-origin-width=&quot;3025&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림을 보면 웹 서버는 서블릿 컨테이너와 &lt;b&gt;CGI규칙&lt;/b&gt;으로 데이터를 주고 받고, 서블릿 컨테이너와 서블릿은 &lt;b&gt;Servlet 규칙&lt;/b&gt;으로 데이터를 주고 받는다. 여기서 우리가 서블릿 프로그래밍을 한다는 것은 &lt;b&gt;Servlet&lt;/b&gt; 규칙 즉 &lt;b&gt;인터페이스를&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;지금까지의 용어를 정리해보자면, 아래와 같이 정리해 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;서블릿 컨테이너&lt;/code&gt; - 서블릿의 생성, 실행 그리고 소멸 등 LifeCycle 을 관리하는 CGI 프로그램이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;서블릿&lt;/code&gt; - Servlet 인터페이스를 구현한 프로그램으로 서버측 프로그램이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Servlet 이라는 단어는 Server + Applet 의 합성어로 클라이언트에서 서비스를 제공하는 작은 단위의 서버 애플리케이션을 의미한다.&lt;br /&gt;Applet 은 Application과 작은을 의미하는 ~let 접미사의 합성어로 작은 애플리케이션을 의미한다.&lt;/p&gt;
&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;이쯤에서 한가지 더 궁금해지는 단어가 있다. 그러면 WAS(Web Application Server) 는 무엇일까. 위에서 얘기한 서블릿 컨테이너와 서블릿이 WAS인걸까? 결과부터 얘기하자면, 맞다.&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;클라이언트 서버 구조에서 서버쪽 애플리케이션의 LifeCycle 을 관리하는 프로그램을 Application Server 인데, 그 중 &lt;b&gt;서블릿 그리고 서블릿 컨테이너와 같이 웹 기술을 기반으로 동작되는 애플리케이션이 Web Application Server 즉, WAS이다.&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>BackEnd/Jsp Servlet</category>
      <category>cgi</category>
      <category>Servlet</category>
      <category>Servlet Container</category>
      <category>WAS</category>
      <category>서블릿</category>
      <category>서블릿 컨테이너</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/15</guid>
      <comments>https://wanbaep.tistory.com/15#entry15comment</comments>
      <pubDate>Sat, 11 Dec 2021 18:13:19 +0900</pubDate>
    </item>
    <item>
      <title>더 자바 코드를 조작하는 다양한 방법 강의를 들어가며..</title>
      <link>https://wanbaep.tistory.com/14</link>
      <description>&lt;div class=&quot;markdown-body&quot;&gt;
&lt;p&gt;그동안 Java 프로그래밍을 하면서 &lt;code&gt;Constructor, Getter, Setter&lt;/code&gt; 등등 코드를 자동으로 생성해주는 &lt;code&gt;lombok&lt;/code&gt; 을 굉장히 많이 써왔는데, 어떻게 동작하는지 알아본 적은 없었던 것 같다. 더 정확하게 말하면 알아보려고 검색했으나, 무슨말인지 이해를 못했던것이 맞는 것 같다.&lt;/p&gt;
  &lt;br&gt;
&lt;p&gt;이번에는 꼭 반드시 알아내고 말겠다는 생각으로 여러 사람들이 작성해둔 블로그 글들을 천천히 읽어 봤으나.. 잘 이해가 되지 않았다.&lt;/p&gt;
  &lt;br&gt;
&lt;p&gt;그러던 중 우연히 &lt;strong&gt;백기선님의 강의&lt;/strong&gt;를 하나 알게 되었고 너무 듣고 싶은 마음에 나도모르게 바로 결제를 해버렸다.&lt;/p&gt;
  &lt;br&gt;
  &lt;p&gt;결제 하자마자 &lt;code&gt;lombok&lt;/code&gt; 이 어떻게 동작하는지 보려고 애노테이션 프로세서부터 들었던 것 같다. 하지만, 좀 더 자세하고 정확하게 이해하려면 강의 순서대로 들으며 나름대로 나만의 언어로 정리가 필요할 것 같았다. 그래서 강의 순서대로 들으면서 내가 이해한 내용으로 정리해 보려고 한다.&lt;/p&gt;
  &lt;br&gt;
&lt;p&gt;강의 순서는 아래와 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;JVM 이해하기&lt;/li&gt;
&lt;li&gt;바이트코드 조작&lt;/li&gt;
&lt;li&gt;리플렉션&lt;/li&gt;
&lt;li&gt;다이나믹 프록시&lt;/li&gt;
&lt;li&gt;애노테이션 프로세서&lt;/li&gt;
&lt;/ol&gt;
  &lt;br&gt;
&lt;p&gt;강의 링크: &lt;a href=&quot;https://www.inflearn.com/course/the-java-code-manipulation&quot;&gt;https://www.inflearn.com/course/the-java-code-manipulation&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>Language/Java</category>
      <category>lombok</category>
      <category>애노테이션 프로세서</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/14</guid>
      <comments>https://wanbaep.tistory.com/14#entry14comment</comments>
      <pubDate>Mon, 9 Aug 2021 22:34:38 +0900</pubDate>
    </item>
    <item>
      <title>HTTP 프로토콜 살펴보기</title>
      <link>https://wanbaep.tistory.com/13</link>
      <description>&lt;div class=&quot;markdown-body&quot;&gt;
&lt;h1&gt;&lt;b&gt;HTTP 프로토콜&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP(Hyper-Text Transfer Protocol) 은 &lt;b&gt;웹 브라우저와 웹 서버 간의 통신 프로토콜&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;HTTP 프로토콜은 단순히 HTML 페이지나 이미지 파일 전송을 넘어서 원격 컴퓨터에 로딩되어 있는 &lt;b&gt;함수나 객체의 메서드를 호출할 때도 사용&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;&lt;i&gt;HTTP 프로토콜을 응용하거나 확장한 다양한 기술들이 있는데 여기서는 종류만 살짝 살펴보고 넘어가자.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RESTful(REpresentational State Transfer), SOAP (Simple Object Access Protocol)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트와 서버 사이에 서비스를 요청하고 응답을 하는 방식을 나타내는데, 두 가지 모두 HTTP 프로토콜을 응용하거나 확장한 기술이다. (관련한 내용은 추후에 정리해봐야겠다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WebDAV (World Wide Web DIstributed Authoring and Versioning)&lt;/b&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;b&gt;CalDAV (Calendaring Extensions to WebDAV)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캘린더의 정보를 주고받기 위한 프로토콜이다. 기본적으로 HTTP를 기반으로 &lt;code&gt;iCalendar&lt;/code&gt; 포맷을 통해 데이터를 주고받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebDAV를 응용한 CalDAV 기술은 캘린더 데이터를 보다 쉽게 편집하고 공유할 수 있는 기술이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 기술들을 정확하게 이해하기 위해서는 기본이 되는 HTTP 프로토콜에 대해서 이해하는 것이 필수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 HTTP 요청, 응답의 형태를 확인해보자.&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;b&gt;HTTP 요청&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 요청 메시지 형태&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;
GET /url HTTP/1.1	# Request Line
Host : www.tistory.com	# Request Header
User-Agent : Mozilla/5.0 (Macint ...	# Request Header

{ &quot;data&quot; : 1 }	# Message Body&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 요청은 &lt;code&gt;Request-Line&lt;/code&gt;, &lt;code&gt;Request Header&lt;/code&gt;로 이루어져 있으며 필요한 경우 &lt;code&gt;Message Body&lt;/code&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;b&gt;Request-Line (요청 라인)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 메시지의 첫 라인은 &lt;b&gt;메서드,&lt;/b&gt; &lt;b&gt;요청하는 자원&lt;/b&gt; 그리고 &lt;b&gt;프로토콜 버전&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;&lt;b&gt;Request-Line의 형식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;GET / HTTP/1.1 &amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(윈도우에서는 &lt;code&gt;&amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;/code&gt; 를 붙이고, 리눅스 맥 OS 등 유닉스 계열에서는 &lt;code&gt;&amp;lt;LF&amp;gt;&lt;/code&gt; 만 사용해서 개행을 표시)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메서드&lt;/b&gt; - 요청하는 자원에 대해 웹 서버에게 내리는 명령&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종류: GET, POST, HEAD, PUT, DELETE, TRACE, CONNECT, PATCH, OPTIONS 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요청 자원(URI)&lt;/b&gt; - 요청하는 자원의 식별자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML, 이미지, 동영상 애플리케이션 등이 있는 &lt;b&gt;가상의 경로&lt;/b&gt;, 웹 서버는 이 (URI)식별자를 이용해서 관련 자원을 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP 버전&lt;/b&gt; - 요청 정보가 어떤 버전인지 웹 서버에게 알려주기 위한 정보&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Request Header (요청 헤더)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 요청을 처리할 때 클라이언트에서 알려주는 정보를 참고하는데 이를 헤더라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Request Header 형식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;헤더 이름 : 헤더 값&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;
Host : www.tistory.com
User-Agent : Mozilla/5.0 (Macint ... &amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헤더 이름과 값을 &lt;code&gt;:&lt;/code&gt; 구분자를 사용해서 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;헤더의 종류&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;일반 헤더(General-header)&lt;/code&gt; - 요청이나 응답 모두에 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;요청 헤더 또는 응답 헤더(Request, Response header)&lt;/code&gt; - 요청 또는 응답 둘 중 하나에만 적용할 수 있는 헤더.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;엔티티 헤더(Entity-header)&lt;/code&gt; - 보내거나 받는 본문 데이터를 설명하는 헤더.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헤더 종류는 HTTP 표준 문서에 정의되어 있다. (&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc2616#section-5.3&quot;&gt;Request Header 표준 정의&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;요청 헤더 중 User-Agent 는 클라이언트의 정보를 서버에게 알려주는 헤더이기 때문에 중요하다. 웹 서버에서는 이 헤더를 분석해서 요청자의 OS와 브라우저를 구분한다.&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;HTTP 요청 내용 중 마지막 라인은 요청 헤더의 끝을 표시하는 &lt;b&gt;공백 라인이다.&lt;/b&gt; GET 요청의 경우 공백 라인으로 끝나고, POST 요청의 경우 공백 라인 다음에 서버에 보낼 데이터 &lt;code&gt;Message Body&lt;/code&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;b&gt;HTTP 응답&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 응답 메시지 형태&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;
HTTP/1.1 200 OK		# Status-Line
Content-Type : text/html;charset=UTF-8	# Response Header
Content-Language : ko-KR	# Response Header
Content-Length : 1011	# Response Header

&amp;lt;!DOCTYPE html&amp;gt;		# Message Body
&amp;lt;html lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 응답 메시지는 &lt;code&gt;Status-Line&lt;/code&gt;, &lt;code&gt;Response Header&lt;/code&gt; 그리고 &lt;code&gt;Message Body&lt;/code&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;b&gt;Status-Line (상태 라인)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 라인에 상태 정보를 나타내며 프로토콜 버전과 상태 코드, 설명으로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP 응답 상태 코드&lt;/b&gt; - 상태 코드의 첫 번째 숫자는 응답의 종류를 구분하는 역할을 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;1XX&lt;/code&gt;: 요청을 받았고 처리중이라는 정보를 알려주는 상태 코드 들이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2XX&lt;/code&gt;: 성공, 요청을 성공적으로 받았고 잘 처리했다는 의미이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;3XX&lt;/code&gt;: 요청에 대한 응답을 처리 완료 하기 위해서 추가 동작이 필요하다는 의미로 리다이렉션 하며, 브라우저는 해당 응답을 받으면 재요청을 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;4XX&lt;/code&gt;: Client Error 로 요청이 잘못 되었거나 실행될 수 없는 경우에 해당된다. (유효하지 않은 요청)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;5XX&lt;/code&gt;: Server Error 로 유효한 요청을 받았으나 서버에서 처리되지 못한 경우다.&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;&lt;b&gt;Response Header (응답 헤더)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답 데이터를 처리할 때 참고하도록 서버에서 웹 브라우저에게 알려주는 정보다. 특히 &lt;code&gt;Content-Type&lt;/code&gt; 헤더는 서버가 웹 브라우저에게 보내는 데이터의 형식을 나타내며, 웹 브라우저는 &lt;code&gt;Content-Type&lt;/code&gt; 값을 보고 HTML 페이지를 출력할지, Json 형태의 데이터를 출력할지, 다운로드 창을 띄울지 아니면 외부 프로그램을 실행할지 등등을 결정한다. 추가로 Content-Length는 웹 브라우저에게 보내는 데이터 &lt;code&gt;Message-Body&lt;/code&gt;의 크기(Byte)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 Response Header의 경우도 표준 문서에서 확인 가능하다. (&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc2616#section-6.2&quot;&gt;Response Header 종류&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Message Body&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Response Header&lt;/code&gt;와 &lt;code&gt;Message Body&lt;/code&gt;를 구분하기 위해 HTTP 요청 메시지와 동일하게 &lt;code&gt;Response Header&lt;/code&gt; 뒤에 &lt;b&gt;공백 라인&lt;/b&gt;이 오고 그 뒤에 &lt;code&gt;Message Body&lt;/code&gt; 가 시작된다.&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 HTTP 프로토콜의 요청과 응답 메시지들을 살펴 봤는데, 메시지의 정확한 규칙을 한번 짚고 넘어가는 좋은 계기가 되었다. 이후에는 HTTP Request의 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;b&gt;참고문헌&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엄진영 지음 /&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;열혈강의 자바 웹 개발 워크북 (1장 웹 애플리케이션의 이해)&lt;/i&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;/ 프리렉 / 초판 6쇄 2018년 08월 09일&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7231&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;rfc7231&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&amp;amp;ejkGb=KOR&amp;amp;barcode=9788965400677&quot;&gt;http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&amp;amp;ejkGb=KOR&amp;amp;barcode=9788965400677&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1625586813641&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;자바 웹 개발 워크북 - 교보문고&quot; data-og-description=&quot;MVC 아키텍처, 마이바티스, 스프링으로 만드는 실무형 개발자 로드맵 | 더 자바답게, 원리로 이해하는 자바 웹 개발 워크북이 책은 자바 웹 개발 기초에서 프레임워크를 사용하는 실무 내용까지 &quot; data-og-host=&quot;www.kyobobook.co.kr&quot; data-og-source-url=&quot;http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&amp;amp;ejkGb=KOR&amp;amp;barcode=9788965400677&quot; data-og-url=&quot;http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&amp;amp;ejkGb=KOR&amp;amp;barcode=9788965400677&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xWmCw/hyKN70qCuZ/ElqvKF3f6yLmatgplosho1/img.jpg?width=458&amp;amp;height=626&amp;amp;face=0_0_458_626&quot;&gt;&lt;a href=&quot;http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&amp;amp;ejkGb=KOR&amp;amp;barcode=9788965400677&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&amp;amp;ejkGb=KOR&amp;amp;barcode=9788965400677&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xWmCw/hyKN70qCuZ/ElqvKF3f6yLmatgplosho1/img.jpg?width=458&amp;amp;height=626&amp;amp;face=0_0_458_626');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;자바 웹 개발 워크북 - 교보문고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MVC 아키텍처, 마이바티스, 스프링으로 만드는 실무형 개발자 로드맵 | 더 자바답게, 원리로 이해하는 자바 웹 개발 워크북이 책은 자바 웹 개발 기초에서 프레임워크를 사용하는 실무 내용까지&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/Jsp Servlet</category>
      <category>http</category>
      <category>http protocol</category>
      <category>http request</category>
      <category>HTTP response</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/13</guid>
      <comments>https://wanbaep.tistory.com/13#entry13comment</comments>
      <pubDate>Wed, 7 Jul 2021 00:48:50 +0900</pubDate>
    </item>
    <item>
      <title>애플리케이션의 변화 과정 (데스크톱 to 웹 애플리케이션)</title>
      <link>https://wanbaep.tistory.com/12</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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데스크톱 애플리케이션&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인용 &lt;b&gt;데스크톱에서 실행되는 애플리케이션&lt;/b&gt;으로 로컬 피씨로 프로그램을 다운받아 설치하고 실행하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 피씨의 모든 자원을 사용 할 수 있으며, &lt;b&gt;MS Office 프로그램이나 게임&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;&lt;b&gt;장점&lt;/b&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;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변경사항이 생길 때마다 다시 배포&lt;/b&gt;해야 하므로 배포가 번거롭다. &amp;rarr; 배포 문제는 &lt;b&gt;자동 갱신&lt;/b&gt;을 통해 해결이 가능하다. (애플리케이션을 실행할 때 먼저 서버에 갱신된 버전이 있는지 확인하여 다운로드 후 재설치하는 방식)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터베이스&lt;/b&gt;에 접속이 필요한 애플리케이션은 &lt;b&gt;보안에 취약&lt;/b&gt;하다. &amp;rarr; 애플리케이션에서 바로 데이터베이스에 접속하는 구조이기 때문에 접속 정보가 노출될 우려가 있다.&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;b&gt;클라이언트 서버 애플리케이션&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;애플리케이션의 기능을 클라이언트와 서버로 분리&lt;/b&gt;한 애플리케이션을 의미 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 데이터베이스에 접속 &amp;rarr; 보안이 강화된다. (&lt;b&gt;데스크톱 애플리케이션의 단점을 커버한다&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서는 여러 클라이언트를 동시에 처리해주기 위해 &lt;b&gt;멀티 스레드 구조&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;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 변경이나 추가에 대해 유연하게 대처가 가능하다. &amp;rarr; 새로운 기능을 추가하는 경우, 서버 쪽의 내용만 변경해도 되는 경우 클라이언트는 변경 사항이 없기 때문에 재설치할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트의 변경 사항이 있는 경우에는 데스크톱 애플리케이션과 마찬가지로 클라이언트 배포를 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 통신을 위한 &lt;b&gt;네트워크 프로그래밍&lt;/b&gt;을 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다중 클라이언트 요청을 동시에 처리하기 위한 &lt;b&gt;스레드 프로그래밍&lt;/b&gt;도 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;클라이언트 서버 아키텍처의 진화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) 전통적인 클라이언트 서버 아키텍처&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버는 데이터 처리&lt;/b&gt;를 맡고 &lt;b&gt;클라이언트는 UI와 비즈니스 처리를 담당&lt;/b&gt;한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;333&quot; data-filename=&quot;전통적인서버클라이언트구조.png&quot; width=&quot;467&quot; height=&quot;124&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRO9Bg/btq7L0SEaAg/kSBuDtfEUL9nJkn8nemnQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRO9Bg/btq7L0SEaAg/kSBuDtfEUL9nJkn8nemnQ0/img.png&quot; data-alt=&quot;전통적인 클라이언트 서버 아키텍처&amp;amp;amp;nbsp;&amp;amp;amp;nbsp;(출처: 자바 웹 개발 워크북 / 엄진영 저)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRO9Bg/btq7L0SEaAg/kSBuDtfEUL9nJkn8nemnQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRO9Bg%2Fbtq7L0SEaAg%2FkSBuDtfEUL9nJkn8nemnQ0%2Fimg.png&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;333&quot; data-filename=&quot;전통적인서버클라이언트구조.png&quot; width=&quot;467&quot; height=&quot;124&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;전통적인 클라이언트 서버 아키텍처&amp;nbsp;&amp;nbsp;(출처: 자바 웹 개발 워크북 / 엄진영 저)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데스크톱 애플리케이션의 &lt;b&gt;데이터 처리 부분을 공통화하여 서버로 이관&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;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 PC에 분산되어 있는 자료를 하나의 서버에서 관리 함으로써 &lt;b&gt;자료의 중복이나 불일치 성을 해소할 수 있다&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;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 변경 시 PC에 다시 설치해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 클라이언트 서버 환경에서는 클라이언트가 DBMS에 바로 접속하기 때문에 보안 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) 개선된 클라이언트 서버 아키텍처&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클라이언트는 사용자와의 상호작용을 처리하는 UI를 담당&lt;/b&gt;하고 &lt;b&gt;데이터 처리, 비즈니스 처리 부분을 서버로 이관&lt;/b&gt;한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;333&quot; data-filename=&quot;개선된클라이언트서버구조.png&quot; width=&quot;700&quot; height=&quot;122&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mn9u4/btq7NzUNksS/ZaetSGKX314uq91droqlVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mn9u4/btq7NzUNksS/ZaetSGKX314uq91droqlVK/img.png&quot; data-alt=&quot;개선된 클라이언트 서버 아키텍처&amp;amp;amp;nbsp;&amp;amp;amp;nbsp;(출처: 자바 웹 개발 워크북 / 엄진영 저)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mn9u4/btq7NzUNksS/ZaetSGKX314uq91droqlVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmn9u4%2Fbtq7NzUNksS%2FZaetSGKX314uq91droqlVK%2Fimg.png&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;333&quot; data-filename=&quot;개선된클라이언트서버구조.png&quot; width=&quot;700&quot; height=&quot;122&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;개선된 클라이언트 서버 아키텍처&amp;nbsp;&amp;nbsp;(출처: 자바 웹 개발 워크북 / 엄진영 저)&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;클라이언트에서는 데이터를 입력받는 화면을 제공하고, 데이터를 형식에 맞춰서 서버에 보내거나 서버로부터 받은 데이터를 클라이언트에 보여주는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비즈니스 처리 부분을 담당하는 서버를 &lt;b&gt;애플리케이션 서버&lt;/b&gt;라고 하는데, 역할은 크게 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션 서버는 클라이언트로부터 요청을 받아 로직에 따라 동작하며 DBMS와 연결되어 데이터를 처리한다.&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 DB에 직접 접속하지 않기 때문에 DB 접속 정보가 노출되지 않는다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;웹 기술을 활용한 클라이언트 서버&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;웹 애플리케이션 서버 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트와의 통신은 웹 서버가 처리함 &amp;rarr; 네트워크 및 멀티 스레드 프로그래밍 부분을 웹 서버가 처리한다.&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-origin-width=&quot;1110&quot; data-origin-height=&quot;333&quot; data-filename=&quot;기존cs환경.png&quot; width=&quot;497&quot; height=&quot;149&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TjUTZ/btq7L0SEbVu/Zrwm95YkjYPMUSjg0m2MEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TjUTZ/btq7L0SEbVu/Zrwm95YkjYPMUSjg0m2MEk/img.png&quot; data-alt=&quot;기존 클라이언트 서버 구조 (출처: 자바 웹 개발 워크북 / 엄진영 저)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TjUTZ/btq7L0SEbVu/Zrwm95YkjYPMUSjg0m2MEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTjUTZ%2Fbtq7L0SEbVu%2FZrwm95YkjYPMUSjg0m2MEk%2Fimg.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;333&quot; data-filename=&quot;기존cs환경.png&quot; width=&quot;497&quot; height=&quot;149&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;기존 클라이언트 서버 구조 (출처: 자바 웹 개발 워크북 / 엄진영 저)&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;418&quot; data-filename=&quot;웹환경.png&quot; width=&quot;493&quot; height=&quot;186&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWkBZN/btq7I0yPIq7/WdZLKNEcKXq2VjTGvG02AK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWkBZN/btq7I0yPIq7/WdZLKNEcKXq2VjTGvG02AK/img.png&quot; data-alt=&quot;웹 구조 (출처: 자바 웹 개발 워크북 / 엄진영 저)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWkBZN/btq7I0yPIq7/WdZLKNEcKXq2VjTGvG02AK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWkBZN%2Fbtq7I0yPIq7%2FWdZLKNEcKXq2VjTGvG02AK%2Fimg.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;418&quot; data-filename=&quot;웹환경.png&quot; width=&quot;493&quot; height=&quot;186&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;웹 구조 (출처: 자바 웹 개발 워크북 / 엄진영 저)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML과 함께 CSS, JavaScript를 이용하여 역동적인 UI를 구성할 수 있고, &lt;b&gt;표준 웹 프로토콜인 HTTP를 이용하여 데이터를 송수신함으로써 다양한 플랫폼 간에 연결을 지원한다.&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;&lt;b&gt;기존의 클라이언트 서버 환경과 다른 점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) 배치&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 환경에서는 &lt;b&gt;비즈니스 로직과 UI 로직을 모두 서버에서 배치&lt;/b&gt;하기 때문에 기능 추가 및 변경 시 서버 쪽만 변경된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존의 클라이언트 서버 환경은 비즈니스 처리 부분을 서버에 배치하고 UI 처리 부분을 클라이언트에 배치했다. 그로 인해 UI변경 시 클라이언트를 배포해야 한다.&lt;/li&gt;
&lt;li&gt;(&lt;i&gt;배치란, 클라이언트에서 서비스를 요청했을 때 톰캣 서버가 애플리케이션을 실행할 수 있도록 설치하는 것이다. 웹 애플리케이션을 실행하려면 톰캣 서버에 배치(deployment) 해야 한다.&lt;/i&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 실행할 때마다 UI 로직을 내려받아야 하기 때문에 &lt;b&gt;네트워크 오버헤드가 발생&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;&lt;b&gt;(2) 실행&lt;/b&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;b&gt;(3) 개발&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 환경에서는 웹 브라우저와 &lt;b&gt;웹 서버가 네트워크 프로그래밍, 멀티 스레드 프로그래밍을 대신 처리&lt;/b&gt;한다.&lt;/p&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;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1575&quot; data-origin-height=&quot;377&quot; data-filename=&quot;웹브라우저_웹서버.png&quot; width=&quot;555&quot; height=&quot;133&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eh5MQC/btq7I1Ewtyh/QXPrRVSf6wvAKywGPF9HZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eh5MQC/btq7I1Ewtyh/QXPrRVSf6wvAKywGPF9HZk/img.png&quot; data-alt=&quot;개발 관점에서 본 웹 브라우저, 웹 서버 그리고 애플리케이션 서버 (출처: 자바 웹 개발 워크북 / 엄진영 저)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eh5MQC/btq7I1Ewtyh/QXPrRVSf6wvAKywGPF9HZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feh5MQC%2Fbtq7I1Ewtyh%2FQXPrRVSf6wvAKywGPF9HZk%2Fimg.png&quot; data-origin-width=&quot;1575&quot; data-origin-height=&quot;377&quot; data-filename=&quot;웹브라우저_웹서버.png&quot; width=&quot;555&quot; height=&quot;133&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;개발 관점에서 본 웹 브라우저, 웹 서버 그리고 애플리케이션 서버 (출처: 자바 웹 개발 워크북 / 엄진영 저)&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;&lt;b&gt;웹 애플리케이션의 등장 이유&lt;/b&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;b&gt;웹 애플리케이션의 문제점과 개선방안&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&lt;/b&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;b&gt;해결 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ajax (Asynchronous JavaScript and XML)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 화면에서 데이터만 바뀔 때, 서버에서 UI 전체가 아닌 데이터만 받아오는 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;&lt;b&gt;참고문헌&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엄진영 지음 / &lt;i&gt;열혈강의 자바 웹 개발 워크북 (1장 웹 애플리케이션의 이해)&lt;/i&gt; / 프리렉 / 초판 6쇄 2018년 08월 09일&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&amp;amp;ejkGb=KOR&amp;amp;barcode=9788965400677&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&amp;amp;ejkGb=KOR&amp;amp;barcode=9788965400677&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1624202570841&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;자바 웹 개발 워크북 - 교보문고&quot; data-og-description=&quot;MVC 아키텍처, 마이바티스, 스프링으로 만드는 실무형 개발자 로드맵 | 더 자바답게, 원리로 이해하는 자바 웹 개발 워크북이 책은 자바 웹 개발 기초에서 프레임워크를 사용하는 실무 내용까지 &quot; data-og-host=&quot;www.kyobobook.co.kr&quot; data-og-source-url=&quot;http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&amp;amp;ejkGb=KOR&amp;amp;barcode=9788965400677&quot; data-og-url=&quot;http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&amp;amp;ejkGb=KOR&amp;amp;barcode=9788965400677&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/2Jx3O/hyKCBHZWPf/ccwxKNfKkAkD0Ut8Cylykk/img.jpg?width=458&amp;amp;height=626&amp;amp;face=0_0_458_626&quot;&gt;&lt;a href=&quot;http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&amp;amp;ejkGb=KOR&amp;amp;barcode=9788965400677&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&amp;amp;ejkGb=KOR&amp;amp;barcode=9788965400677&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/2Jx3O/hyKCBHZWPf/ccwxKNfKkAkD0Ut8Cylykk/img.jpg?width=458&amp;amp;height=626&amp;amp;face=0_0_458_626');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;자바 웹 개발 워크북 - 교보문고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MVC 아키텍처, 마이바티스, 스프링으로 만드는 실무형 개발자 로드맵 | 더 자바답게, 원리로 이해하는 자바 웹 개발 워크북이 책은 자바 웹 개발 기초에서 프레임워크를 사용하는 실무 내용까지&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/Jsp Servlet</category>
      <category>데스크톱 애플리케이션</category>
      <category>서버 클라이언트</category>
      <category>애플리케이션</category>
      <category>애플리케이션 변화 과정</category>
      <category>웹</category>
      <category>웹 애플리케이션</category>
      <category>클라이언트 서버</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/12</guid>
      <comments>https://wanbaep.tistory.com/12#entry12comment</comments>
      <pubDate>Mon, 21 Jun 2021 00:27:18 +0900</pubDate>
    </item>
    <item>
      <title>[Database] MySQL 외부 IP 에서 접근하기</title>
      <link>https://wanbaep.tistory.com/11</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;WAS는 윈도우에서 개발하고 MySQL 데이터베이스는 리눅스(우분투)에 띄워서 개발하는 중에 MySQL에 접속되지 않는 현상이 있었다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 리눅스 피씨의 MySQL에 새로운 사용자를 모든 IP에 대해 허용하도록 권한을 설정해서 생성했다.&lt;/p&gt;
&lt;pre id=&quot;code_1623852237287&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE USER 'baetest'@'%' IDENTIFIED by 'password';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 웹 어플리케이션 에서 jdbc url을 localhost에서 접속했던 방법에서 url의 localhost만 리눅스 피씨의 IP로 변경해서 설정했다.&lt;/p&gt;
&lt;pre id=&quot;code_1623852294605&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jdbc.url=jdbc:mysql://[리눅스피씨 IP]:3306/workbook_db&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;그러나 실행해본 결과 아래와 같은 Exception 이 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;com.mysql.cj.jdbc.exceptions.&lt;b&gt;CommunicationsException&lt;/b&gt;: Communications link failure&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Communications link failure 와 같은 메세지를 보니 MySQL에 통신 오류가 된 것으로 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인해본 결과 MySQL 설치 후 default로 localhost만 접속 가능하게 설정되어 있었다.&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;b&gt;Ubuntu에서 mysqld 연결 수신대기 상태 확인&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1623852575094&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo netstat -ntlp | grep mysqld
tcp    0    0    127.0.0.1:3306    0.0.0.0: *    LISTEN    8864/mysqld&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 mysql server의 listen ip 대역이 localhost(127.0.0.1:3306)만 수신 가능하도록 제한되어 있는 상태이다.&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;b&gt;my.cnf 파일 수정&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1623852683146&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ vi /etc/mysql/my.cnf

// MySQL 버전이 변경되면서 path가 변경된 것 같다. 위에 혹은 아래 경로의 cnf 파일 수정

$ vi /etc/mysql/mysql.conf.d/.. &amp;lt;- 재확인 필요&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 IP에 대해서 수신 가능하도록 변경 하기 위해서는 my.cnf의 bind-address=127.0.0.1을 주석 처리하고 bind-address=0.0.0.0으로 변경해야 한다. bind-address 를 0.0.0.0 으로 변경 하는 것은 외부에서의 접근을 허용한다는 의미이다.&lt;/p&gt;
&lt;pre id=&quot;code_1623852727716&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#bind-address=127.0.0.1
bind-address=0.0.0.0
#주석처리만 해도 bind-address=0.0.0.0 과 같은 의미가 된다.&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;&lt;b&gt;MySQL 재시작&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 변경 후 적용되도록 재시작이 필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1623852764865&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ service mysql restart
...
$ sudo netstat -ntlp | grep mysqld
tcp    0    0    0.0.0.0:3306    0.0.0.0: *    LISTEN    8864/mysqld&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;특정 IP 대역 설정하는 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL의 bind-address 를 특정 IP 로 설정하고 재시작 하는 경우 실행이 되지 않았다. 따라서 현재까지 확인한 방법으로는 Listen IP 는 0.0.0.0 으로 열어두고 사용자의 IP를 설정해서 접근을 제한 하는 방법을 사용하면 특정 IP 대역 설정이 가능하다.&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;특정 혹은 외부 IP 에 대해서 접속 허용을 제한하는 것은 사용자를 추가할 때만 많이 봤던 내용인데, MySQL에서 접속 가능한 IP 대역을 설정해주고 있는지는 처음 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/Database</category>
      <category>Database</category>
      <category>MySQL</category>
      <category>MySQL 외부 IP</category>
      <category>외부 IP</category>
      <category>특정 ip</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/11</guid>
      <comments>https://wanbaep.tistory.com/11#entry11comment</comments>
      <pubDate>Wed, 16 Jun 2021 23:24:56 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] 옵저버 패턴(Observer Pattern)</title>
      <link>https://wanbaep.tistory.com/10</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Observer Pattern 정의&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 객체(Subject, Observable)의 상태가 바뀌면 그 객체에 의존하는 다른 객체들(Observers)한테 연락(notify)이 가고 자동으로 내용이 갱신(update)되는 방식으로 일대다 (one-to-many) 의존성을 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;관찰 대상인 주제(Subject)가 변경되면, 등록된 Observer 들에게 notify를 주고, Observer들은 이벤트를 받아 처리&lt;/b&gt;한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Observer Pattern 구조&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 Head First Design Pattern 에 나오는 기상청에서 받아온 데이터를 출력해주는 프로그램에 Observer Pattern을 적용한 예제이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;835&quot; data-filename=&quot;observer.png&quot; width=&quot;618&quot; height=&quot;400&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCteCk/btq7nF9SQkK/2GKyXpH9e9tZhyEtWQXgw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCteCk/btq7nF9SQkK/2GKyXpH9e9tZhyEtWQXgw1/img.png&quot; data-alt=&quot;Observer Pattern Example Class Diagram&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCteCk/btq7nF9SQkK/2GKyXpH9e9tZhyEtWQXgw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCteCk%2Fbtq7nF9SQkK%2F2GKyXpH9e9tZhyEtWQXgw1%2Fimg.png&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;835&quot; data-filename=&quot;observer.png&quot; width=&quot;618&quot; height=&quot;400&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;Observer Pattern Example Class Diagram&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;726&quot; data-filename=&quot;observer-seq.png&quot; width=&quot;345&quot; height=&quot;357&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfdPP0/btq7myiTuMk/bazJKuEXgEQGpTkjkasJcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfdPP0/btq7myiTuMk/bazJKuEXgEQGpTkjkasJcK/img.png&quot; data-alt=&quot;Observer Pattern Example Sequece Diagram&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfdPP0/btq7myiTuMk/bazJKuEXgEQGpTkjkasJcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfdPP0%2Fbtq7myiTuMk%2FbazJKuEXgEQGpTkjkasJcK%2Fimg.png&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;726&quot; data-filename=&quot;observer-seq.png&quot; width=&quot;345&quot; height=&quot;357&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;Observer Pattern Example Sequece Diagram&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;Subject인 WeatherData 클래스가 있고 서로 다른 Display를 보여주는 DisplayElement(Observer)인 StatisticsDisplay와 CurrentConditionDisplay가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 WeatherData 클래스의 메서드 registerObserver를 사용해서 각 Observer들을 등록해 두면, WeatherData에서 변경이 생길 때마다 notifyObservers 메서드 내부에서 ArrayList&amp;lt;Observer&amp;gt; 들의 update를 호출해주고, 각 Observer들은 이를 처리하는 구조이다.&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;실생활에 빗댄 예제 중 Head First Design Pattern 책에서 해주는 설명 중 신문사 구독 예제 설명이 이해하기 가장 쉬웠다. Subject(Publisher) 와 Observer(Subscriber)로 구성된 신문사와 구독자의 관계를 살펴보면,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소비자A는 신문사에 구독을 신청한다.&lt;/li&gt;
&lt;li&gt;신문이 나오면 신문사는 소비자A에게 신문을 배달한다.&lt;/li&gt;
&lt;li&gt;소비자A가 신문사에 구독 서비스를 취소한다.&lt;/li&gt;
&lt;li&gt;신문이 나와도 소비자A는 신문을 배달받지 못한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 code level로 다시 살펴보면,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;registerObserver method를 통해서 observers list Observer (소비자A)객체를 저장한다.&lt;/li&gt;
&lt;li&gt;notifyObservers method를 통해서 observers list 들의 update method들을 호출한다.&lt;/li&gt;
&lt;li&gt;Observer (소비자A) 객체에 대해서 removeObserver method를 통해서 observers list에서 제거한다.&lt;/li&gt;
&lt;li&gt;notifyObservers method가 호출되어도 Observer (소비자A)는 update method가 호출되지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;옵저버 패턴에서는 Subject (Observable)와 Observer가 느슨하게 결합되어 있는 객체 디자인을 제공한다고 하는데, 그 이유를 알아보자.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subject 입장에서는 Observer에 대해 아는 것은 Observer가 특정 인터페이스를 구현한다는 사실뿐이다. (Observer Interface)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observer에 대해서 언제든지 새로 추가할 수 있다. (제거 또한 마찬가지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 형식의 Observer를 추가하려고 할 때도 Subject를 전혀 변경할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 Interface Observer에 대해서 update method를 호출하기로 미리 약속해두었기 때문에, 해당 부분만 맞춰주면, 다른 부분의 변경사항에 대해서는 Subject와 Observer가 서로 영향을 미치지 않는 느슨하게 결합된 구조를 갖게 된다는 것을 의미한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Observer 패턴 예제 코드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Head First Design Pattern에 수록된 예제 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Subject Interface&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623766141003&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}

public interface Observer {
    public void update(float temp, float humidity, float pressure);
}&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;Subject 인터페이스와 Observer 인터페이스를 정의해 둔다. Subject 인터페이스에는 Observer를 등록하기 위한 registerObserver 메서드와 제거를 위한 removeObserver를 가지고 있다. 또한, Subject내에서 변경 등에 대해 발생을 Observer들에게 알리기 위해 notifyObservers 메서드가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observer 인터페이스에는 Update 메서드가 있는데, 현재 메서드 인자는 Subject object를 전달하는 것이 아닌 값들을 넣어서 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Subject의 구현체 WeatherData 클래스&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623766187960&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.ArrayList;

public class WeatherData implements Subject {
    private ArrayList&amp;lt;Observer&amp;gt; observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList&amp;lt;&amp;gt;();
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        int i = observers.indexOf(o);
        if(i &amp;gt;= 0) {
            observers.remove(i);
        }
    }

    @Override
    public void notifyObservers() {
        for(int i = 0; i &amp;lt; observers.size(); i++) {
            Observer observer = observers.get(i);
            observer.update(temperature, humidity, pressure);
        }
    }

    public void measurementsChanged() {
        notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}&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;Subject 구현체에서는 Observer를 멤버 변수로 관리한다. 그리고 registerObserver에서 인자로 받아온 Observer를 추가하고 반대로 removeObserver에서는 Observer를 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setMeasurements() -&amp;gt; measurementsChanged() -&amp;gt; notifyObservers() 순서로 변경 시마다 모든 Observer의 update를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주목해야 할 점은 Observer 인터페이스를 Composition 하고 있기 때문에, Observer 인터페이스를 구현한 구현체는 모두 해당 Subject에 등록할 수 있다는 점이다. 즉, 새로운 Observer가 추가되어도 Subject에는 변경 사항이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Display Interface&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623766236292&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface DisplayElement {
    public void display();
}&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;b&gt;Observer 구현체 1&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623766258890&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class StatisticsDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private Subject weatherData;

    public StatisticsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        this.weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println(&quot;Current conditions: &quot; + temperature + &quot;F degrees and&quot; + humidity + &quot;% humidity&quot;);
    }

    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        display();
    }
}&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;&lt;b&gt;Observer 구현체 2&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623766283805&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CurrentConditionDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private Subject weatherData;

    public CurrentConditionDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println(&quot;Current conditions: &quot; + temperature + &quot;F degrees and&quot; + humidity + &quot;% humidity&quot;);
    }

    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        display();
    }
}&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;&lt;b&gt;Observer 패턴을 사용하는 Client (Main)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623766351498&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);

        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);

        weatherData.removeObserver(currentConditionDisplay);
        weatherData.setMeasurements(20, 30, 11.2f);

        weatherData.registerObserver(currentConditionDisplay);
        weatherData.setMeasurements(10, 20, 5.8f);
    }
}&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;&lt;b&gt;자바 내장 옵저버 패턴 사용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java.util 패키지에 Observable, Observer를 이용해서 이를 구현할 수 있다.&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;b&gt;Observable 구현체&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623766386983&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.Observable;

public class WeatherData extends Observable {
    private ArrayList&amp;lt;Observer&amp;gt; observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList&amp;lt;&amp;gt;();
    }

    public void measurementsChanged() {
        setChanged();
        notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}&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;바뀐 점은 이전에 있던, registerObserver, removeObserver, notifyObservers 메서드 정의가 없어진 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 measurementsChanged에서 Observable 클래스의 메서드인 setChanged()와 notifyObservers를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setChanged는 Observable 객체가 변경이 있는지 말 그대로 changed를 true로 변경하는 함수다.&lt;/p&gt;
&lt;pre id=&quot;code_1623766407749&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Observable 코드
protected synchronized void setChanged() {
    changed = true;
}&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;notifyObservers는 notifyObservers()와 notifyObservers(Object arg) 메서드가 있는데, notifyObservers()를 호출하면 결국 내부에서 notfiyObservers(null)을 호출한다. (Object arg를 통해서 Observer들에게 보내고 싶은 데이터를 객체로 보낼 수 있다. 위에 예제에서는 데이터를 주고받기 위한 객체를 별도로 할당하지 않고 Subject에서 데이터를 가져와서 사용했다.)&lt;/p&gt;
&lt;pre id=&quot;code_1623766426337&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//간략하게 변경한 Observable의 notifyObservers 메서드
public void notifyObservers(Object arg) {
    if (!changed) return;
    clearChanged();
    for (Observer observer : Observers) {
				observer.update(this, arg);
		}
}&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;notfiyObservers(Object arg) 메서드에서는 changed 가 ture인지 보고 Observer들의 update 메서드를 호출해준다. (실제로는 changed를 확인하는 과정에서 thread safe 하도록 작동하기 위해 synchronized를 추가해주고, Vector로 선언되어 있는 Observer 객체들을 Object 배열로 변경해서 update메서드를 호출하고 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Observer 구현체&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623766447120&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Observable;
import java.util.Observer;

public class StatisticsDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private Observable weatherData;

    public StatisticsDisplay(Observable weatherData) {
        this.weatherData = weatherData;
        this.weatherData.addObserver(this);
    }

    @Override
    public void display() {
        System.out.println(&quot;Current conditions: &quot; + temperature + &quot;F degrees and&quot; + humidity + &quot;% humidity&quot;);
    }

    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherData) {
            WeatherData weatherObj = (WeatherData) o;
            this.temperature = weatherObj.getTemperature();
            this.humidity = weatherObj.getHumidity();
            display();
        }
    }
}&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;Observer 인터페이스에 정의된 update 메서드를 구현해야 하기 때문에 코드가 변경된다.&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;b&gt;Deprecated 된 Observable, Observer&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observer 패턴을 사용하려 하면 Deprecated 되었다고 나온다. (java 9 버전부터 Deprecated 되었다.) java 문서를 보면, Observer, Observable에서 지원하는 모델은 제한적이며, Observable에서 실행되는 알림이 전달될 순서가 지정되지 않으며 (무조건 등록된 순서대로 전달), 상태 변경이 notification과 일대일로 대응되지 않을 수 있다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 java.beans 나 java.util.concurrent, Flow API를 사용하라고 얘기하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 나중에 한번 살펴봐야 할 것 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1623766502805&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Deprecated(since=&quot;9&quot;)
public classObservable
extendsObject&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고문헌&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에릭 프리먼, 엘리자베스 프리먼, 케이시 시에라, 버트 베이츠 / 서환수 역 /&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;Head First Design Patterns /&lt;/i&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;한빛미디어 / 초판 19쇄 발행 2021년 1월 11일&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/9/docs/api/java/util/Observable.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Java 9 Class Observable&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SW 설계/Design Pattern</category>
      <category>Observer Pattern</category>
      <category>옵저버 패턴</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/10</guid>
      <comments>https://wanbaep.tistory.com/10#entry10comment</comments>
      <pubDate>Tue, 15 Jun 2021 23:28:12 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] 전략 패턴(Strategy Pattern)</title>
      <link>https://wanbaep.tistory.com/9</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Strategy Pattern 이란&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Behavior 패턴 중 하나로 &lt;b&gt;알고리즘 군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.&lt;/b&gt; Strategy를 활용하면 알고리즘을 사용하는 클라이언트와 &lt;b&gt;독립적으로 알고리즘을 변경&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;독립적으로 알고리즘을 변경할 수 있다는 말은 알고리즘을 사용하는 클라이언트에서 어떠한 concrete 객체를 사용하는지 모르지만, Interface에 정의된 대로 사용할 수 있음을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 예제에 적용해서 이야기해보면, Duck 추상 클래스에서 어떠한 concrete 한 FlyBehavior 나 QuackBehavior 객체를 사용하는지 모르지만, Interface로 정의해 둔 FlyBehavior, QuackBehavior에 정의된 대로 사용할 수 있다. Duck 추상 클래스는 어떠한 concrete 객체를 사용하는지 모르기 때문에 이 부분을 캡슐화되어 있다고 한다.&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;b&gt;Strategy Pattern 이 사용되는 Case&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 알고리즘이 다양하고 변형되어야 하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 사용되어야 할 행동, 알고리즘이 runtime에 결정되어야 하는 경우&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;b&gt;Strategy Pattern 예제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;Strategy Pattern 예제 클래스 다이어그램으로 Class 들 간의 관계를 확인해보도록 하자.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;2038&quot; data-origin-height=&quot;958&quot; data-filename=&quot;strategy_added.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vuufI/btq6ArFnM3H/LawLOdTg2lbAYZCYgC9s70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vuufI/btq6ArFnM3H/LawLOdTg2lbAYZCYgC9s70/img.png&quot; data-alt=&quot;Strategy Pattern Class Diagram Exmple&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vuufI/btq6ArFnM3H/LawLOdTg2lbAYZCYgC9s70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvuufI%2Fbtq6ArFnM3H%2FLawLOdTg2lbAYZCYgC9s70%2Fimg.png&quot; data-origin-width=&quot;2038&quot; data-origin-height=&quot;958&quot; data-filename=&quot;strategy_added.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;Strategy Pattern Class Diagram Exmple&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;위의 Strategy Class Diagram을 간략하게 설명해보면, Duck 추상 클래스가 있고 이를 상속받는 concrete Duck 클래스들이 있으며 각각 클래스에서 어떻게 보이는지를 display 메서드를 정의하고 있다. (display의 경우에는 concrete Duck들 마다 전부 다르게 override 해주어야 하기 때문에 상속한 sub-class에서 ovrride 메서드를 구현한다.)&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;각각의 Duck들의 다양한 부분인 FlyBehavior와 QuackBehavior는 별도의 Interface로 정의하고 이에 대한 concrete 한 행동 클래스들이 구현되어 있다. 그리고 Duck 추상 클래스와 각 행동에 대한 Interface는 composition(구성) 관계를 맺고 있다. (&lt;u&gt;composition 관계를 맺는다는 것은 Duck 추상 클래스의 멤버 변수로 가지고 있다는 것을 의미한다.&lt;/u&gt;) 이 부분이 객체지향 원칙인 &lt;b&gt;Inheritance 보다 Composition을 사용&lt;/b&gt;하라 라는 부분에 해당한다. 이렇게 Duck 추상 클래스를 직접 Inheritance 하지 않고, 행동 Interface 들을 composition 함으로써 다형성을 이용해 각 구현체가 정의되기 때문에, Duck 추상 클래스에서는 실제 구현체에 대해서 알지 못해도 된다. (캡슐화와 시스템의 유연성이 크게 향상될&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;위의 Strategy Class Diagram의 예제를 Strategy Pattern을 적용한 결과에 대해서 위에서 언급한 적절한 case가 맞는지 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 알고리즘이 다양하고 변형되어야 하는 경우&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FlyBehavior 클래스와 QuackBehavior 클래스가 다양하고 Duck 마다 다르게 적용되어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 사용되어야 할 행동, 알고리즘이 runtime에 결정되어야 하는 경우&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 Duck 클래스마다 실행하는 행동들이 초기에 결정될 수 도 있지만, 이를 runtime에 동적으로 변경할 수 있다. 이는 각 행동 클래스들이 Duck 클래스와 composition 관계에 있기 때문에 가능하며, setter 함수로 이를 설정할 수 있다.&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;&lt;b&gt;Strategy Pattern의 중요 부분&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Strategy Pattern에서 핵심은&lt;/b&gt; 알고리즘을 사용할 &lt;b&gt;클라이언트와 알고리즘 군을 정의하는 부분의 관계&lt;/b&gt;이다. 위에서 언급한 Inheritance 보다 Composition을 사용하라 라는 의미를 나름대로 정리해 보았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;알고리즘을 직접 구현하고 있기 때문에 캡슐화되어 있지 않다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클라이언트가 concrete한 알고리즘과 composition 관계로 구성되는 것은 좋지 않다.
&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;알고리즘을 구현하는 객체를 가지고 있으며 해당 알고리즘에 대해 캡슐화되어 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트가 알고리즘군에 대한 Interface와 composition 관계로 구성되도록 한다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트의 알고리즘을 Interface를 구현한 알고리즘으로 변경 가능하다. (다형성을 이용한 동적인 구조)&lt;/li&gt;
&lt;li&gt;알고리즘을 구현하는 객체를 가지고 있으며 해당 알고리즘에 대해 캡슐화되어 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;해당 자료는 &quot;&lt;i&gt;&lt;b&gt;Head First Design Pattern&lt;/b&gt;&lt;/i&gt;&quot; 책을 참조해서 작성되었습니다.&lt;/blockquote&gt;</description>
      <category>SW 설계/Design Pattern</category>
      <category>Strategy Pattern</category>
      <category>전략 패턴</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/9</guid>
      <comments>https://wanbaep.tistory.com/9#entry9comment</comments>
      <pubDate>Sun, 6 Jun 2021 17:45:49 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] 디자인 패턴이란?</title>
      <link>https://wanbaep.tistory.com/8</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;디자인 패턴(Desgin Pattern) 이란&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특정 상황에서 빈번하게 발생하는 구조적 문제를 해결한 디자인 방법을 의미&lt;/b&gt;한다. 객체 지향 프로그래밍에서 구조적 문제를 잘 해결할 수 있는 모범사례(Best Practice) 들을 모아 놓은 것으로 이해할 수 있다.&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;b&gt;디자인 패턴을 사용하는 이유&lt;/b&gt;&lt;/h4&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;Best Practice를 모아둔 것이기 때문에 좋은 모방책이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 code를 이해하는데 도움이 많이 된다. 즉, 디자인 패턴을 바탕으로 만들어진 API를 쉽게 이해할 수 있다는 장점이 있다.&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;b&gt;GoF의 디자인 패턴 카테고리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인 패턴 카테고리는 목적에 따라서 크게 3가지로 분류된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;생성 패턴(Creational Pattern) - 유연하게 객체를 생성함으로써 문제를 해결하는 방법. 객체를 사용하는 부분과 생성하는 부분을 나눈다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Factory Method&lt;/li&gt;
&lt;li&gt;Abstract Factory&lt;/li&gt;
&lt;li&gt;Builder&lt;/li&gt;
&lt;li&gt;Prototype&lt;/li&gt;
&lt;li&gt;Singleton&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;구조 패턴(Structural Pattern) - 상속과 같은 방법을 사용해서 클래스와 객체를 구성함으로써 문제를 해결하는 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Adapter&lt;/li&gt;
&lt;li&gt;Bridge&lt;/li&gt;
&lt;li&gt;Composite&lt;/li&gt;
&lt;li&gt;Decorator&lt;/li&gt;
&lt;li&gt;Facade&lt;/li&gt;
&lt;li&gt;Proxy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;행동 패턴(Behavioral Pattern) - 클래스에 책임을 할당해서 문제를 해결하는 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Interpreter&lt;/li&gt;
&lt;li&gt;Template&lt;/li&gt;
&lt;li&gt;Command&lt;/li&gt;
&lt;li&gt;Iterator&lt;/li&gt;
&lt;li&gt;Mediator&lt;/li&gt;
&lt;li&gt;Observer&lt;/li&gt;
&lt;li&gt;State&lt;/li&gt;
&lt;li&gt;Strategy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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>SW 설계/Design Pattern</category>
      <category>design pattern</category>
      <category>디자인 패턴</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/8</guid>
      <comments>https://wanbaep.tistory.com/8#entry8comment</comments>
      <pubDate>Sat, 5 Jun 2021 21:52:07 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 01. Java 란 무엇인가?</title>
      <link>https://wanbaep.tistory.com/7</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 를 제대로 공부해본 적은 없다. 대학교 3학년 시절 안드로이드 어플리케이션을 만들기 위해서 교수님이 Head First Java를 참조하여 만들어 주신 강의자료를 본게 전부이다.&lt;br /&gt;물론 C++ 을 이미 사용하고 있었기 때문에 기본적인 문법을 익히는 방법은 어렵지 않았다.&lt;br /&gt;하지만, 어떠한 방식으로 동작하는지 정확하게 알지 못했고 기본을 모를 땐 빨리 밑으로 내려가 공부를 시작하는게 가장 빠른 길이라는 생각이 들었다. 그래서 &quot;Java의 정석&quot; 책을 보면서 처음부터 공부하고자 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;java-는-무엇인가&quot;&gt;Java 란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;Java 란 &lt;/b&gt;&lt;/span&gt;&lt;b&gt;썬 마이크로시스템즈 에서 처음 개발한&lt;/b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt; 객체지향 프로그래밍 언어이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 는 가전제품 SW 를 개발하기 위해 처음 개발 된 언어이다. 가전제품이나 PDA와 같은 소형기기에 사용될 목적이었으나 여러 종류의 운영체제를 사용하는 컴퓨터들의 통신(인터넷)이 등장하면서 개발 방향을 바꾸었다고 한다. 썬에서 개발한 뒤 Oracle 에 인수되었다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바언어의 특징들을 나열해 보면, 운영체제 독립적이다, 객체지향 언어이다, 멀티쓰레드를 지원한다, 네트워크와 분산처리를 제공한다, 동적로딩을 지원한다, 자동 메모리 관리(Garbage Collection)를 지원한다 등이 있다.&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 장단점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바 프로그램은 JVM 위에서 실행되기 때문에 운영체제에 독립적이다.&lt;/li&gt;
&lt;li&gt;Garbage Collection이 있기 때문에 메모리 할당을 프로그래머가 신경쓸 필요가 없다.&lt;/li&gt;
&lt;li&gt;동적 로딩을 지원한다. 실행 시에 모든 클래스가 로드되는 것이 아닌, 필요한 시점에 클래스가 로드 되기 때문에 일부 클래스가 변경되어도 전체를 컴파일하지 않아도 되는 유연한 구조를 가지고 있다.&lt;/li&gt;
&lt;li&gt;멀티쓰레드를 제공하기 때문에 분산처리가 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;속도가 느리다? Java 는 Compile 된 .class(Byte code) 가 JVM이 인식할 수 있는 언어이며, 이를 다시 JVM이 하드웨어 Instruction(binary) 로 변경 한 후 실행하는데, Java는 Interpreter 언어 이기 때문에 명령어 1개 번역 &amp;rarr; 수행 과정으로 실행된다. 그러나 현재는 JIT(Just In Time) Compiler 의 개발로 전체 명령어를 컴파일 한 후 수행되도록 변경되어서 속도가 굉장히 빨라졌다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;하드웨어에 연결하여 제어할 수 없기 때문에(가상 머신 이용 JVM) 직접 하드웨어를 정밀하게 조정해야 하는 프로그램 개발에는 적합하지 않다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;자바언어의-특징&quot; data-ke-size=&quot;size26&quot;&gt;Java 의 특징&lt;/h2&gt;
&lt;h3 id=&quot;1-운영체제-독립적이다&quot; data-ke-size=&quot;size23&quot;&gt;1. 운영체제 독립적이다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java는 JVM 위에서 실행되기 때문에 운영체제와 의존성이 없다. 대신 운영체제마다 JVM은 종속적이기 때문에 수많은 OS에 대한 JVM 개발이 필수적이다.&lt;/p&gt;
&lt;h3 id=&quot;2-객체지향-언어이다&quot; data-ke-size=&quot;size23&quot;&gt;2. 객체지향 언어이다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향 프로그래밍 언어로 객체지향 특성인 상속, 캡슐화, 다형성이 잘 적용되어 있다.&lt;/p&gt;
&lt;h3 id=&quot;3-멀티쓰레드를-지원한다&quot; data-ke-size=&quot;size23&quot;&gt;3. 멀티쓰레드를 지원한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 멀티쓰레드는 운영체제에 따라 구현방법과 처리 방식이 다르다. 그러나 자바에서는 이에 영향을 받지 않으며 쓰레드 스케쥴링을 자바 인터프리터가 실행한다.&lt;/p&gt;
&lt;h3 id=&quot;4-네트워크와-분산처리를-지원한다&quot; data-ke-size=&quot;size23&quot;&gt;4. 네트워크와 분산처리를 지원한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 네트워크 API를 제공하며, 인터넷 대규모 분산 시스템 개발에 효과 적이다. (왜 효과적인건지는 좀 더 알아봐야 겠다)&lt;/p&gt;
&lt;h3 id=&quot;5-동적로딩을-지원한다&quot; data-ke-size=&quot;size23&quot;&gt;5. 동적로딩을 지원한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 시에 모든 클래스가 로드되는 것이 아닌 필요한 시점에 로드되는 형태이다. 따라서, 일부 클래스가 변경되어도 전체 컴파일을 하지 않아도 되기 때문에 유연한 구조를 가지고 있다.&lt;/p&gt;
&lt;h3 id=&quot;6-자동-메모리-관리garbage-collection를-지원한다&quot; data-ke-size=&quot;size23&quot;&gt;6. 자동 메모리 관리(Garbage Collection)를 지원한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바는 프로그램이 실행되는 동안 Garbage Collector가 자동으로 메모리를 관리해 주기 때문에 별도로 메모리 관리가 필요가 없다. C++ 개발자 입장에서는 굉장히 부러운 기능이다. 계속 실행되는 부담?은 있지만 프로그램 개발에 집중 할 수 있게 해주며, 최적화도 가능한 것으로 알고 있다. (이 부분도 좀 더 심도있게 알아볼 필요가 있어 보인다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;java-virtual-machine&quot; data-ke-size=&quot;size26&quot;&gt;Java Virtual Machine (JVM)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM 은 Java byte code를 실행하는 가상 머신이다. (가상 머신이란 &lt;span style=&quot;color: #4d5156;&quot;&gt;실재하는 컴퓨터 상에 소프트웨어로 논리적으로 만들어낸 컴퓨터를 말한다.)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 이 JVM 덕분에 자바는 운영체제 독립적이라는 특성을 갖게 된다. 이는 애플리케이션이 실행될 때, OS 위에 JVM이라는 Layer가 하나 생기는 것과 동일하다고 보면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;325&quot; data-origin-height=&quot;470&quot; width=&quot;184&quot; height=&quot;266&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V7urq/btq52oJAUGI/eTNVgV1epWM4uXOO6K4Uf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V7urq/btq52oJAUGI/eTNVgV1epWM4uXOO6K4Uf0/img.png&quot; data-alt=&quot;Java Application 실행 환경&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V7urq/btq52oJAUGI/eTNVgV1epWM4uXOO6K4Uf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV7urq%2Fbtq52oJAUGI%2FeTNVgV1epWM4uXOO6K4Uf0%2Fimg.png&quot; data-origin-width=&quot;325&quot; data-origin-height=&quot;470&quot; width=&quot;184&quot; height=&quot;266&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;Java Application 실행 환경&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;java-애플리케이션이-작성되고-실행되는-과정&quot; data-ke-size=&quot;size26&quot;&gt;Java Applicaiton이 작성되고 실행되는 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바프로그램이 실행되는 순서를 아주 간략하게 살펴보면 아래와 같다. 자바 프로그램(.java)을 작성하고 이를 자바 컴파일러가 bytecode(.class) 로 변환시킨다. class 파일을 자바 인터프리터가 하드웨어가 식별 할 수 있는 언어(binary)로 해석해서 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;300&quot; width=&quot;607&quot; height=&quot;191&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ONNsv/btq57pzKo7Z/UWLxYvzRhfcq2szC9wBr90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ONNsv/btq57pzKo7Z/UWLxYvzRhfcq2szC9wBr90/img.png&quot; data-alt=&quot;Java 실행 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ONNsv/btq57pzKo7Z/UWLxYvzRhfcq2szC9wBr90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FONNsv%2Fbtq57pzKo7Z%2FUWLxYvzRhfcq2szC9wBr90%2Fimg.png&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;300&quot; width=&quot;607&quot; height=&quot;191&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;Java 실행 과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Language/Java</category>
      <category>java</category>
      <category>java virtual machine</category>
      <category>Java 란</category>
      <category>Java 장단점</category>
      <category>Java 특징</category>
      <category>jvm</category>
      <author>wanbaep</author>
      <guid isPermaLink="true">https://wanbaep.tistory.com/7</guid>
      <comments>https://wanbaep.tistory.com/7#entry7comment</comments>
      <pubDate>Sat, 29 May 2021 22:04:36 +0900</pubDate>
    </item>
  </channel>
</rss>