<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Sue's devlog</title>
    <link>https://suaring.tistory.com/</link>
    <description>개발 공부 로그</description>
    <language>ko</language>
    <pubDate>Mon, 29 Jun 2026 00:48:49 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>suaring</managingEditor>
    <image>
      <title>Sue's devlog</title>
      <url>https://tistory1.daumcdn.net/tistory/4965111/attach/4eb38d9e1bcf4b558a803d60da9cb730</url>
      <link>https://suaring.tistory.com</link>
    </image>
    <item>
      <title>[Kotlin] 코틀린 기초(1) : 변수, Null Safety, 타입</title>
      <link>https://suaring.tistory.com/140</link>
      <description>&lt;h1&gt;&lt;b&gt; &amp;nbsp;코틀린(Kotlin)이란?&lt;/b&gt;&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcjXke/dJMb99SVtVE/dkIeuGGlKLF6tGM6Rr8pl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcjXke/dJMb99SVtVE/dkIeuGGlKLF6tGM6Rr8pl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcjXke/dJMb99SVtVE/dkIeuGGlKLF6tGM6Rr8pl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcjXke%2FdJMb99SVtVE%2FdkIeuGGlKLF6tGM6Rr8pl1%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;550&quot; height=&quot;309&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 JetBrains에서 2011년에 공개한 &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;b&gt;Java와의 호환성&lt;/b&gt;&amp;nbsp;: JVM 위에서 동작하며 Java와 100% 호환된다. 기존 Java 프로젝트에 코틀린을 바로 도입하거나, Java 라이브러리를 그대로 가져와 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;간결한 코드&lt;/b&gt;&amp;nbsp;: Java의 장황한 코드(보일러플레이트)를 대폭 줄여, 적은 코드로 동일한 기능을 구현하여 생산성을 높인다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;안전성 (Null Safety)&lt;/b&gt;&amp;nbsp;: NPE를 컴파일 시점에 방지하여 안정적인 개발이 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다양한 활용성&lt;/b&gt;&amp;nbsp;: 안드로이드 앱 개발, 백엔드 서버 개발(Spring 등), 멀티플랫폼 개발에 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;b&gt; &amp;nbsp;변수&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 변수 선언 시 가변 여부(var / val)를 명확히 표시해주어야 한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val num1 = 10L
    var num2 = 20L
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;val&lt;/b&gt;&amp;nbsp;&lt;b&gt;(Value)&lt;/b&gt; : 불변 변수로 초기화 후 값 변경이 불가능하다. (자바의 final)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;var (Variable)&lt;/b&gt; : 가변 변수로 초기화 후 값 변경이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Tip&lt;/b&gt; : 모든 변수는 우선 val로 선언하고 꼭 필요한 경우에만 var로 변경하는 것이 클린 코드 유지에 유리하다.&lt;/p&gt;
&lt;/blockquote&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;✅&amp;nbsp; 타입 추론과 명시적 타입&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자바와 다르게 타입을 지정해주지 않아도 컴파일 시점에 추론한다. 타입을 명시하고 싶다면 변수 뒤에&amp;nbsp;:타입을 붙여준다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val num1 = 10L // Long으로 추론
    val num2: Long = 10L // 명시적 타입 선언
}
&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 class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val num1 // This variable must either have an explicit type or be initialized.
    val num2: Long
}
&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 class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val num1: Long
    println(num1) // Variable 'num1' must be initialized.
}
&lt;/code&gt;&lt;/pre&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;✅ 기본 타입&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에는 reference type(ex.&amp;nbsp;Long)과 primitive type(ex.&amp;nbsp;long)이 있다. reference type을 사용하여 연산하는 경우 boxing, unboxing이 일어나면서 불필요한 객체 생성이 발생한다. 그러나&amp;nbsp;&lt;b&gt;코틀린에는 reference type과 primitive type에 구분이 없다&lt;/b&gt;. 코틀린에서는 실행 시점에 boxing, unboxing을 고려하지 않아도 되도록 가장 효율적인 방식으로 상황에 따라 처리한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    // 1. Primitive 타입으로 변환되는 경우 (최적화)
    val a: Long = 100L
    val b: Long = 200L
    val sum = a + b // 자바의 long + long 연산과 동일하게 동작

    // 2. Reference 타입(Wrapper)이 불가피한 경우
    val nullableSum: Long? = sum // null 허용 시 자바의 Long 객체로 생성
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;b&gt; &amp;nbsp;Null Safety : 코틀린의 핵심&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 가장 큰 특징은 &lt;b&gt;Nullable&lt;/b&gt;과 &lt;b&gt;Non-nullable&lt;/b&gt; 타입을 엄격히 구분한다는 점이다. 코틀린에서는 기본적으로 모든 변수는 null이 들어갈 수 없다. 만약 null이 들어올 수 있는 변수라면 선언 시 nullable 표시를 해주어야 한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val num: Long? = 1_000L
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⚠️&amp;nbsp;주의할 점&lt;/b&gt; 코틀린이 알아서 최적화해주지만, Nullable(?) 타입을 남용하면 자바와 마찬가지로 내부적으로 Boxing이 발생하여 성능 저하가 생길 수 있다. 성능이 중요한 루프문 등에서는 최대한 non-nullable 타입을 사용하는 것이 좋다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅&amp;nbsp;&lt;b&gt;Safe Call과 Elvis 연산자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도로 null 체크를 하지 않는다면 nullable 인자의 경우 바로 함수를 호출할 수 없으므로 Safe Call 또는 Elvis 연산자를 사용해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Safe Call(?.)&lt;/b&gt; : null이 아닐 때만 실행하고, null이면 전체 결과가 null이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;val str: String? = &quot;ABC&quot;
println(str?.length)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Elvis 연산자(?:)&lt;/b&gt; : 앞의 연산 결과가 null이면 뒤에 지정한 기본값을 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;val str: String? = &quot;ABC&quot;
str?.length ?: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅&amp;nbsp;&lt;b&gt;null 아님 단언(!!)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 null이 아님이 확실할 떄만 사용한다. 만약 null일 경우 런타임에 NPE가 발생하므로 주의해야한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun ignoreNull(str: String?): Boolean {
	return str!!.startsWith(&quot;A&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅&amp;nbsp;&lt;b&gt;플랫폼 타입&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 코드에서 @Nullable이나 @NotNull 어노테이션이 없어 코틀린이 null 가능성을 알 수 없는 타입으로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바와 코틀린을 함께 사용할 때 가장 주의해야 하는 부분이다. 코틀린에서 Non-nullable로 간주했다가 런타임에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NPE가 발생할 수 있다. 자바 라이브러리 사용 시 반드시 라이브러리 내부를 확인하고 널 가능성을 처리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt; &amp;nbsp;타입&lt;/b&gt;&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 기본 타입&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Byte&lt;/li&gt;
&lt;li&gt;Short&lt;/li&gt;
&lt;li&gt;Int&lt;/li&gt;
&lt;li&gt;Long&lt;/li&gt;
&lt;li&gt;Double&lt;/li&gt;
&lt;li&gt;Float&lt;/li&gt;
&lt;li&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;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;val num1 = 3 // Int
val num2 = 3L // Long
val num3 = 3.0f // Float
val num4 = 3.0 // Double
&lt;/code&gt;&lt;/pre&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;기본 타입 변환&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자바와 달리 기본 타입간의 암시적 형변환을 허용하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;val num1 = 4
// val num2: Long = num2 // Type mismatch
val num3: Long = num1.toLong() // 명시적 형변환
&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;변수가 nullable이라면 적절한 처리가 필요하다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;val num1: Int? = 3
val num2: Long = num1?.toLong() ?: 0L
&lt;/code&gt;&lt;/pre&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;2. 타입 캐스팅&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 instanceof는 코틀린에서 is로 사용된다. is로 타입을 확인하면 스마트 캐스트가 일어나므로 별도의 형변환 코드가 필요 없다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun printNameIfPerson(obj: Any){
    if (obj is Person) {
        // obj는 Person 타입으로 간주됨 (스마트 캐스트)
        println(person.name)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;instanceof &amp;rarr; is&lt;/li&gt;
&lt;li&gt;!(instanceof) -&amp;gt; !is&lt;/li&gt;
&lt;li&gt;(Person) obj -&amp;gt; obj as Person&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;nullable 변수에 대하여 타입 캐스팅 시, NPE를 방지하려면 as?를 붙여야 한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    printNameIfPerson(null)
}

fun printNameIfPerson(obj: Any?) {
	val person = obj as? Person // null이 아니면 형변환, null이면 null 반환
    println(person?.name)
}
&lt;/code&gt;&lt;/pre&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;3. 코틀린의 3가지 특이한 타입&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Any&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바의 Object. 모든 객체의 최상위 타입 (Primitive Type 포함)&lt;/li&gt;
&lt;li&gt;Any 자체로는 null을 포함할 수 없음 (Any? 사용해야함)&lt;/li&gt;
&lt;li&gt;equals, hashCode, toString 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Unit&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바의 void와 동일한 역할&lt;/li&gt;
&lt;li&gt;void와 다르게 Unit은 그 자체로 타입 인자로 사용 가능하다&lt;/li&gt;
&lt;li&gt;함수형 프로그래밍에서 Unit은 단 하나의 인스턴스만 갖는 타입을 의미. 즉, 코틀린의 Unit은 실제 존재하는 타입이라는 것을 표현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Nothing&lt;/b&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;/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;4. String&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 자바와 달리 문자열 안에서 변수를 바로 사용할 수 있어 가독성이 뛰어나다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;val person = Person(&quot;수아&quot;, 100);
val str = &quot;사람의 이름은 ${person.name}이고, 나이는 ${person.age}세 입니다.&quot;

val name = &quot;수아&quot;
println(&quot;이름: $name&quot;) // 중괄호 생략 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅&amp;nbsp;Tip : 중괄호는 생략이 가능하지만 생략하지 않는 것이 &lt;b&gt;가독성, 일괄 변환, 정규식 활용&lt;/b&gt; 측면에서 좋다.&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;여러 줄에 걸친 문자열을 작성해야 할 때&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;&quot;&quot;&quot;
	어쩌구저쩌구..
	${name}
&quot;&quot;&quot;.trimIndent() // 인덴트 제거
&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 class=&quot;processing&quot;&gt;&lt;code&gt;val str = &quot;ABC&quot;
println(str[0])
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;b&gt; &amp;nbsp;연산자&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 산술 연산자, 논리 연산자 등은 자바와 동일하지만, 몇가지 차이점이 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅&amp;nbsp;동등성, 동일성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;== : 동등성(값이 같은지) 비교. 내부적으로 equals() 호출 (자바의 equals)&lt;/li&gt;
&lt;li&gt;=== : 동일성 비교. 주소값을 비교한다. (자바의 ==)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 코틀린에 있는 특이한 연산자&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;in / !in : 컬렉션이나 범위에 포함되는지 확인&lt;/li&gt;
&lt;li&gt;a..b : a부터 b까지의 범위 객체를 생성한다.&lt;/li&gt;
&lt;li&gt;a[i] : a에서 특정 인덱스 i로 값을 가져온다.&lt;/li&gt;
&lt;li&gt;a[i] = b : a의 특정 인덱스 i에 b를 넣는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 연산자 오버로딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서는 객체마다 연산자를 직접 정의할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
	val money1 = Money(1_000L)
	val money2 = Money(2_000L)
	println(money1 + money2) // 연산자 오버로딩
}

data class Money(
	val amount: Long
) {
	operator fun plus(other: Money): Money {
		return Money(this.amount + other.amount)	
	}
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Kotlin</category>
      <author>suaring</author>
      <guid isPermaLink="true">https://suaring.tistory.com/140</guid>
      <comments>https://suaring.tistory.com/140#entry140comment</comments>
      <pubDate>Sat, 21 Feb 2026 21:51:16 +0900</pubDate>
    </item>
    <item>
      <title>[Programmers Level 2] 올바른 괄호</title>
      <link>https://suaring.tistory.com/139</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;figure id=&quot;og_1660493345272&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;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12909&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lOXkk/hyPq1g4gtk/Mlt41vkxYUvknhrA2hhL80/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cKfq69/hyPq0WNyEx/m9psnXnUhTmqt6l9uR3ibk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12909&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12909&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lOXkk/hyPq1g4gtk/Mlt41vkxYUvknhrA2hhL80/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cKfq69/hyPq0WNyEx/m9psnXnUhTmqt6l9uR3ibk/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;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.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;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;첫번째 시도&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넘어온 괄호 문자열을 split으로 자른 후, 열린 괄호일 경우 스택에 push, 닫는 괄호일 경우 pop해서 스택이 비어있는지 여부를 반환하는 방법으로 시도했다.&lt;/p&gt;
&lt;pre id=&quot;code_1660493403052&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    boolean solution(String s) {

        String[] temp = s.split(&quot;&quot;);
        Stack&amp;lt;String&amp;gt; stack = new Stack&amp;lt;&amp;gt;();
        
        // 닫는 괄호로 시작하거나 여는 괄호로 끝나면 false 반환
        if (temp[0].equals(&quot;)&quot;) || temp[temp.length-1].equals(&quot;(&quot;)) {
            return false;
        }
        
        for (int i = 0 ; i &amp;lt; temp.length ; i ++) {
            if (temp[i].equals(&quot;(&quot;)) { // 여는 괄호일 경우 stack에 push
                stack.push(temp[i]);
            } else { // 닫는 괄호일 경우 pop
                
                if (stack.isEmpty()) {
                    return false;
                }
                
                stack.pop();    
                
            }
        }

        return stack.isEmpty();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;효율성 점수 빵점..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;267&quot; data-origin-height=&quot;165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nKvwX/btrJBFFpT5s/p4scBNqKC1vjEIwzVg1Vu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nKvwX/btrJBFFpT5s/p4scBNqKC1vjEIwzVg1Vu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nKvwX/btrJBFFpT5s/p4scBNqKC1vjEIwzVg1Vu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnKvwX%2FbtrJBFFpT5s%2Fp4scBNqKC1vjEIwzVg1Vu0%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;267&quot; height=&quot;165&quot; data-origin-width=&quot;267&quot; data-origin-height=&quot;165&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;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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;두번째 시도&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;split 하는 과정을 생략하고 String의 charAt 메서드를 사용해서 괄호를 비교하는 방법을 사용했다. split 메서드 실행 시 문자열을 분리하고, 배열에 삽입하는 과정까지 들어가기 때문에 시간복잡도는 O(n^2)이 되어서 그런 것 같다!&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;그리고 for문에 들어가기 전 문자열 맨 앞의 괄호가 닫힌 괄호일 경우, 문자열 맨 뒤의 괄호가 열린 괄호일 경우 바로 false를 리턴하는 방법이 효율성을 높여줄 줄 알았는데 아니었다..^^ 비교 해보니까 효율성 테스트 시간에 + 3 ~ 4ms 로 나와서 해당 코드는 제거했다.&lt;/p&gt;
&lt;pre id=&quot;code_1660493545976&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    boolean solution(String s) {

        // split으로 String 배열 사용 시 효율성 0점 &amp;gt; charAt() 사용

        Stack&amp;lt;Character&amp;gt; stack = new Stack&amp;lt;&amp;gt;();
        
        for (int i = 0 ; i &amp;lt; s.length() ; i ++) {
            
            if (s.charAt(i) == '(') { // 여는 괄호일 경우 stack에 push
                stack.push(s.charAt(i));
            } else { // 닫는 괄호일 경우 pop
                
                if (stack.isEmpty()) { // stack에 열린 괄호가 없으면 바로 false 반환
                    return false;
                }
                
                stack.pop();    
                
            }
        }

        return stack.isEmpty();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TY6LV/btrJBFSYgu9/vk5FQ9TP3RCdTk4fcANVJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TY6LV/btrJBFSYgu9/vk5FQ9TP3RCdTk4fcANVJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TY6LV/btrJBFSYgu9/vk5FQ9TP3RCdTk4fcANVJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTY6LV%2FbtrJBFSYgu9%2Fvk5FQ9TP3RCdTk4fcANVJK%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;300&quot; height=&quot;182&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Algorithm</category>
      <category>알고리즘</category>
      <category>자바</category>
      <category>코딩테스트</category>
      <category>코테</category>
      <category>프로그래머스</category>
      <author>suaring</author>
      <guid isPermaLink="true">https://suaring.tistory.com/139</guid>
      <comments>https://suaring.tistory.com/139#entry139comment</comments>
      <pubDate>Mon, 15 Aug 2022 01:26:09 +0900</pubDate>
    </item>
    <item>
      <title>[Programmers Level 2] 전화번호 목록</title>
      <link>https://suaring.tistory.com/138</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/h2&gt;
&lt;figure id=&quot;og_1660371388478&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;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42577&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/7M8k5/hyPq7nep3B/5n6ptpwom1Ic37KRetFdZ0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42577&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42577&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/7M8k5/hyPq7nep3B/5n6ptpwom1Ic37KRetFdZ0/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;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;첫번째 시도&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시를 사용할 방법이 떠오르지 않아서 일단 배열의 반복문을 사용해서 시도해봤다.&lt;/p&gt;
&lt;pre id=&quot;code_1660370035758&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    public boolean solution(String[] phone_book) {
        
        /*
            1. 길이 순으로 정렬
            2. 가장 짧은 길이의 번호를 기준으로 각 문자열의 substring을 비교
            3. 루프를 한번 돌고, 다음 인덱스로 넘어가서 반복
        */
        
        // 문자열 길이 순으로 정렬, 짧은 문자열이 긴 문자열로 시작하는 경우는 없음
        Arrays.sort(phone_book, new Comparator&amp;lt;String&amp;gt;() {
            @Override
            public int compare(String s1, String s2) {
                return s1.length() - s2.length();
            }
        });
        
        // 가장 짧은 길이의 번호를 기준으로 각 문자열의 substring을 비교
        for (int i = 0 ; i &amp;lt; phone_book.length-1 ; i ++) {
            
            // 현재 인덱스에 있는 문자열 저장
            String s = phone_book[i];
            
            for (int j = i+1 ; j &amp;lt; phone_book.length ; j ++) {
                
                // 접두어가 있으면 바로 false 반환
                if (phone_book[j].startsWith(s)) {
                    return false;
                }
            }
        }
        
        // for문 다 돌고나서도 접두어가 없으면 true 반환
        return true;
        
        
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4hiJ9/btrJAHDvxjP/KWVB341xAA5iKOobq2oyt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4hiJ9/btrJAHDvxjP/KWVB341xAA5iKOobq2oyt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4hiJ9/btrJAHDvxjP/KWVB341xAA5iKOobq2oyt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4hiJ9%2FbtrJAHDvxjP%2FKWVB341xAA5iKOobq2oyt0%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;268&quot; height=&quot;162&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;효율성 테스트 4개 중 2개 빼고 통과..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3, 4번에서 계속 시간초과 뜬다ㅡ.ㅡ&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;힌트를 찾아본 결과 for문을 한번만 쓰고, 비교도 바로 뒤 문자열과만 비교하면 된다고 한다고 해서 다시 시도!&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;두번째 시도&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힌트 참고해서 다시 풀었음! 이중 for문을 쓰던걸 하나로 줄여서 시간 복잡도가 O(n^2)에서 O(n)으로 줄었따  &lt;/p&gt;
&lt;pre id=&quot;code_1660371197294&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    public boolean solution(String[] phone_book) {
        
        /*
            1. 문자열 배열 정렬 (문자열 &amp;gt; 길이 순으로 정렬)
            2. 뒤의 문자열이 앞의 문자열로 시작하면 바로 false 반환
            3. 루프를 한번 돌고, 다음 인덱스로 넘어가서 반복
        */
        
        // 문자열 배열 정렬
        Arrays.sort(phone_book);
        
        // System.out.println(Arrays.toString(phone_book));
        
        // 가장 짧은 길이의 번호를 기준으로 각 문자열의 substring을 비교
        for (int i = 0 ; i &amp;lt; phone_book.length-1 ; i ++) {
            
            // 현재 인덱스에 있는 문자열 저장
            String s = phone_book[i];
            
            if (phone_book[i+1].startsWith(phone_book[i])){
                return false;
            }
        }
        
        return 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;문자열 배열을 정렬을 이해하면 쉽게 풀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬 시 문자열 &amp;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;1459&quot; data-origin-height=&quot;625&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQjFdT/btrJAL0E8Ih/tgv1delRS6BmjGz45qMVS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQjFdT/btrJAL0E8Ih/tgv1delRS6BmjGz45qMVS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQjFdT/btrJAL0E8Ih/tgv1delRS6BmjGz45qMVS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQjFdT%2FbtrJAL0E8Ih%2Ftgv1delRS6BmjGz45qMVS1%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;1459&quot; height=&quot;625&quot; data-origin-width=&quot;1459&quot; data-origin-height=&quot;625&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;232&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lIHSx/btrJEL5mllL/6k6RAFP98QUlSpaaKm6w1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lIHSx/btrJEL5mllL/6k6RAFP98QUlSpaaKm6w1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lIHSx/btrJEL5mllL/6k6RAFP98QUlSpaaKm6w1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlIHSx%2FbtrJEL5mllL%2F6k6RAFP98QUlSpaaKm6w1k%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;232&quot; height=&quot;154&quot; data-origin-width=&quot;232&quot; data-origin-height=&quot;154&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;</description>
      <category>Algorithm</category>
      <author>suaring</author>
      <guid isPermaLink="true">https://suaring.tistory.com/138</guid>
      <comments>https://suaring.tistory.com/138#entry138comment</comments>
      <pubDate>Sat, 13 Aug 2022 15:27:45 +0900</pubDate>
    </item>
    <item>
      <title>AWS EC2 스프링 부트 프로젝트 gradlew test 오류 해결</title>
      <link>https://suaring.tistory.com/137</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  상황&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트로 작성한 프로젝트를 배포하기 위해 ec2 서버에 올리는 과정에서 github에 push하고 clone까지는 되었으나 &lt;b&gt;./gradlew test &lt;/b&gt;입력&amp;nbsp;시 오류가 발생했다.. 에러 뜬 상황을 자세히 보니 constructor, getter 부분에서 오류가 나길래 lombok 문제인 것 같았다. (사실 에러 메세지를 제대로 확인하지 않았을 때는 자바를 인식하지 못하는 줄 알고 환경변수 설정을 했지만 오히려 다른 에러가 발생했다..ㅎㅎㅎ)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1789&quot; data-origin-height=&quot;211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/owllE/btrEUh3V29k/jT61qZTzu1yy8IanNEEWKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/owllE/btrEUh3V29k/jT61qZTzu1yy8IanNEEWKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/owllE/btrEUh3V29k/jT61qZTzu1yy8IanNEEWKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FowllE%2FbtrEUh3V29k%2FjT61qZTzu1yy8IanNEEWKK%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;971&quot; height=&quot;115&quot; data-origin-width=&quot;1789&quot; data-origin-height=&quot;211&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  해결&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트 프로젝트에 lombok 플러그인을 설치했기 때문에 기존 build.gradle에는 implementation lombok만 해주었었다.&lt;/p&gt;
&lt;pre id=&quot;code_1655367330295&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation('org.projectlombok:lombok')&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;lombok은 컴파일 시에만 필요하기 때문에 implementation -&amp;gt; compileOnly로 수정하고, 플러그인의 역할을 해주는annotationProcessor 설정을 추가했다. test에서도 문제가 발생할 수 있으니 관련 설정을 모두 추가했다.&lt;/p&gt;
&lt;pre id=&quot;code_1655367349040&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;compileOnly('org.projectlombok:lombok')
annotationProcessor('org.projectlombok:lombok')
testCompileOnly('org.projectlombok:lombok')
testAnnotationProcessor('org.projectlombok:lombok')&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;build.gradle 파일을 수정하고 github에 push한 뒤 putty에서 pull해서 다시 실행하니 테스트 성공!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QbiqN/btrEZE31wwH/zcVxx64aDbaKe17KD5czjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QbiqN/btrEZE31wwH/zcVxx64aDbaKe17KD5czjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QbiqN/btrEZE31wwH/zcVxx64aDbaKe17KD5czjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQbiqN%2FbtrEZE31wwH%2FzcVxx64aDbaKe17KD5czjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;721&quot; height=&quot;92&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;92&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;a href=&quot;https://github.com/jojoldu/freelec-springboot2-webservice/issues/762&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;&lt;/p&gt;</description>
      <category>devlog</category>
      <author>suaring</author>
      <guid isPermaLink="true">https://suaring.tistory.com/137</guid>
      <comments>https://suaring.tistory.com/137#entry137comment</comments>
      <pubDate>Thu, 16 Jun 2022 18:41:46 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] MVC 1편 (10) - 실용적인 스프링 MVC</title>
      <link>https://suaring.tistory.com/136</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  &lt;b&gt;@RequestMapping&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서는 애노테이션을 활용한 매우 유연하고 실용적인 컨트롤러를 사용할 수 있다. 가장 우선순위가 높은 핸들러 매핑과 핸들러 어댑터는 애노테이션 기반의 컨트롤러를 지원하는 각각 RequestMappingHandlerMapping과 RequestMappingAdapter이다. 실무에서는 이 방식의 컨트롤러를 사용한다!&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;회원 등록 폼&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648726404309&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Controller
public class SpringMemberFormControllerV1 {

   @RequestMapping(&quot;/springmvc/v1/members/new-form&quot;)
    public ModelAndView process() {
  	return new ModelAndView(&quot;new-form&quot;);  
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@Controller&lt;/b&gt; : 스프링이 자동으로 스프링 빈으로 등록하고, 스프링 MVC에서 애노테이션 기반 컨트롤러로 인식한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@RequestMapping&lt;/b&gt; : 요청 정보를 매핑한다. 해당 URL이 호출되면 이 메서드가 호출된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ModelAndView&lt;/b&gt; : 모델과 뷰 정보를 담아서 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;&lt;b&gt;컨트롤러 통합&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RequestMapping은 메서드 단위에 적용되기 때문에 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;하나의 클래스에 각 컨트롤러의 메서드&lt;/b&gt;&lt;/span&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;MemberController&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648726730876&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Controller
@RequestMapping(&quot;/springmvc/v2/members&quot;) // 중복을 줄일 수 있음!
public class SpringMemberControllerV2 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping(&quot;/new-form&quot;)
    public ModelAndView newForm() { // 반환 타입 ModelAndView
        return new ModelAndView(&quot;new-form&quot;);
    }

    @RequestMapping(&quot;/save&quot;)
    public ModelAndView save(HttpServletRequest request, HttpServletResponse response) {

        String username = request.getParameter(&quot;username&quot;);
        int age = Integer.parseInt(request.getParameter(&quot;age&quot;));

        Member member = new Member(username, age);
        memberRepository.save(member);

        ModelAndView mv = new ModelAndView(&quot;save-result&quot;);
        mv.addObject(&quot;member&quot;,member);
        return mv;
    }

    @RequestMapping
    public ModelAndView members() {

        List&amp;lt;Member&amp;gt; members = memberRepository.findAll();
        ModelAndView mv = new ModelAndView(&quot;members&quot;);
        mv.addObject(&quot;members&quot;, members); // Map을 사용하는 것보다 간단하게 값을 넘겨줄 수 있음

        return mv;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중복되는 뷰 이름은 클래스 레벨의 @RequestMapping에 두어 중복을 줄일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;&lt;b&gt;실용적인 방식&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 주로 사용하는 방법이다.&lt;/p&gt;
&lt;pre id=&quot;code_1648727118929&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Controller
@RequestMapping(value = &quot;/springmvc/v3/members&quot;)
public class SpringMemberControllerV3 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @GetMapping(&quot;/new-form&quot;) // 데이터 단순 조회 -&amp;gt; GET
    public String newForm() { // String으로 반환하면 뷰 이름으로 인식됨
        return &quot;new-form&quot;;
    }

    @PostMapping(&quot;/save&quot;) // 데이터의 변경 발생 -&amp;gt; POST
    public String save(
            @RequestParam(&quot;username&quot;) String username, // 파라미터를 직접 받을 수 있음
            @RequestParam(&quot;age&quot;) int age, // 타입 변환까지 자동으로 해줌
            Model model) {

        Member member = new Member(username, age);
        memberRepository.save(member);

        model.addAttribute(&quot;member&quot;,member);
        return &quot;save-result&quot;;
    }

    @GetMapping
    public String members(Model model) {

        List&amp;lt;Member&amp;gt; members = memberRepository.findAll();
        model.addAttribute(&quot;members&quot;, members);
        return &quot;members&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Model 파라미터&lt;/b&gt; : Model을 메서드의 파라미터로 받을 수 있다. 각 메서드는 데이터를 addAttribute() 메서드를 사용해서 Model에 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ViewName&lt;/b&gt; : 각 메서드를 실행하고 ModelView를 반환하는 것이 아닌, 뷰의 논리 이름을 반환&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@ReqeustParam&lt;/b&gt; : 매개변수로 지정한 이름의 요청 파라미터를 변수로 가져온다. == request.getParameter(&quot;username&quot;)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@GetMapping, @PostMapping&lt;/b&gt; : URL을 매핑하는 동시에 HTTP 메서드도 함께 구분할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;강의 링크&lt;/b&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1648726016589&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;스프링 MVC 1편  - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의&quot; data-og-description=&quot;웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., - &quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wQhNd/hyNPPpXZGv/SrVF03Mo4EPkEMkk95ZwcK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/MgMQg/hyNPL8WOYG/eZjUT8slKjyXmxZfVymMCK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wQhNd/hyNPPpXZGv/SrVF03Mo4EPkEMkk95ZwcK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/MgMQg/hyNPL8WOYG/eZjUT8slKjyXmxZfVymMCK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500');&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;스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의&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.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>suaring</author>
      <guid isPermaLink="true">https://suaring.tistory.com/136</guid>
      <comments>https://suaring.tistory.com/136#entry136comment</comments>
      <pubDate>Thu, 31 Mar 2022 20:52:05 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] MVC 1편 (9) - 스프링 MVC 프레임워크 구조</title>
      <link>https://suaring.tistory.com/135</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;&lt;b&gt;스프링 MVC 구조&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOmeVO/btrx2SwsVlu/vZbeWxdAcLHJTTaTYaTWOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOmeVO/btrx2SwsVlu/vZbeWxdAcLHJTTaTYaTWOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOmeVO/btrx2SwsVlu/vZbeWxdAcLHJTTaTYaTWOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOmeVO%2Fbtrx2SwsVlu%2FvZbeWxdAcLHJTTaTYaTWOk%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;787&quot; height=&quot;363&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✅&amp;nbsp;&lt;b&gt;동작 순서&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;핸들러 조회&lt;/b&gt;: 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러를 조회한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;핸들러 어댑터 조회&lt;/b&gt;: 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;핸들러 어댑터 실행&lt;/b&gt;: 핸들러 어댑터를 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;핸들러 실행&lt;/b&gt;: 핸들러 어댑터가 실제 핸들러를 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ModelAndView 반환&lt;/b&gt;: 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;viewResolver 호출&lt;/b&gt;: 뷰 리졸버를 찾고 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;View 반환&lt;/b&gt; : 뷰 리졸버가 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;뷰 렌더링&lt;/b&gt; : 뷰를 통해 뷰를 렌더링한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;&lt;b&gt;간단한 핸들러 매핑과 핸들러 어댑터 사용해보기&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;OldController&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648723725263&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component(&quot;/spirngmvc/old-controller&quot;)
public class OldController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println(&quot;OldController.handleRequest&quot;);
        return null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ModelAndView를 반환하는 스프링의 과거 버전 인터페이스이다.&lt;/li&gt;
&lt;li&gt;이 컨트롤러는 &lt;b&gt;/spirngmvc/old-controller&lt;/b&gt;라는 이름의 스프링 빈으로 등록되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;핸들러가 호출되기 위해서는 두가지가 필요하다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;rarr; 핸들러 매핑, 핸들러 어댑터&lt;/b&gt;&lt;/span&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;&amp;nbsp;&lt;b&gt;스프링 부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;HandlerMapping&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648724551652&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0 = RequestMappingHandlerMapping // 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping // 스프링 빈으로 찾을 때 사용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;HandlerAdapter&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648724572970&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0 = RequestMappingHandlerAdapter // 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter // HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter // Controller 인터페이스(애노테이션X, 과거에 사용) 처리&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&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;size18&quot;&gt;&lt;b&gt;1️⃣ 핸들러 매핑으로 핸들러 조회&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;HandlerMapping을 순서대로 실행해서 핸들러를 찾는다.&lt;/li&gt;
&lt;li&gt;빈 이름으로 핸들러를 찾아주는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;BeanNameUrlHandlerMapping&lt;/b&gt;&lt;/span&gt;이 실행되고, 핸들러인 OldController를 반환한다.&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;size18&quot;&gt;&lt;b&gt;2️⃣ 핸들러 어댑터 조회&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;HandlerAdapter의 supports()를 순서대로 호출한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;SimpleControllerHandlerAdapter&lt;/span&gt;&lt;/b&gt;가 Controller 인터페이스를 지원하므로 반환된다.&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;size18&quot;&gt;&lt;b&gt;&lt;span&gt;3️⃣ 핸들러 어댑터 실행&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- Dispatcher 서블릿이 조회한 SimpleControllerHandlerAdapter를 실행하면서 핸들러 정보도 함께 넘겨준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- 어댑터는 핸들러를 내부에서 실행하고, 결과를 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp;&lt;b&gt;간단한 뷰와 뷰 리졸버 사용해보기&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;OldController&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648725053137&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return new ModelAndView(&quot;new-form&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;View를 사용할 수 있도록 코드 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✅&amp;nbsp;&lt;b&gt;ViewResolver 설정 정보&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648725146664&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;application.properties에 설정 정보를 추가한다.&lt;/li&gt;
&lt;li&gt;스프링 부트는 InternalResourceViewResolver를 사용하는데 이때 이 설정 정보를 사용해서 뷰 객체를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✅&amp;nbsp;&lt;b&gt;스프링 부트가 자동 등록하는 뷰 리졸버&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;ViewResolver&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648725615046&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1 = BeanNameViewResolver // 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능에 사용)
2 = InternalResourceViewResolver // JSP를 처리할 수 있는 뷰를 반환한다.&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;span&gt;&amp;nbsp;&lt;b&gt;과정&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1️⃣ 핸들러 어댑터 호출&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;핸들러 어댑터를 통해 new-form이라는 논리 뷰 이름을 획득한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2️⃣ ViewResolver 호출&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;new-form이라는 뷰 이름으로 viewResolver를 순서대로 호출한다.&lt;/li&gt;
&lt;li&gt;new-form이라는 이름으로 등록된 스프링 빈이 없으므로 InternalResourceViewResolver가 호출된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3️⃣ InternalResourceViewResolver&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;InternalResourceView를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4️⃣ 뷰 렌더링&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;view.render()가 호출되고 InternalResourceView는 forward()를 사용해서 JSP를 실행한다.&lt;/li&gt;
&lt;/ul&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;강의 링크&lt;/b&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1648723139801&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;스프링 MVC 1편  - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의&quot; data-og-description=&quot;웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., - &quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wQhNd/hyNPPpXZGv/SrVF03Mo4EPkEMkk95ZwcK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/MgMQg/hyNPL8WOYG/eZjUT8slKjyXmxZfVymMCK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wQhNd/hyNPPpXZGv/SrVF03Mo4EPkEMkk95ZwcK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/MgMQg/hyNPL8WOYG/eZjUT8slKjyXmxZfVymMCK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500');&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;스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의&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.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>MVC</category>
      <category>Spring</category>
      <category>김영한</category>
      <category>스프링</category>
      <category>웹</category>
      <author>suaring</author>
      <guid isPermaLink="true">https://suaring.tistory.com/135</guid>
      <comments>https://suaring.tistory.com/135#entry135comment</comments>
      <pubDate>Thu, 31 Mar 2022 20:26:45 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] MVC 1편 (8) - MVC 프레임워크 만들기 v4,v5</title>
      <link>https://suaring.tistory.com/134</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;v1, v2, v3&lt;/b&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1648598970012&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;[Spring] MVC 1편 (7) - MVC 프레임워크 만들기 v1, v2, v3&quot; data-og-description=&quot; &amp;nbsp;프론트 컨트롤러 ✅ 프론트 컨트롤러 도입 전 프론트 컨트롤러 도입 전에는 각 컨트롤러에서 공통 로직을 구현해줘야 했다. ✅ 프론트 컨트롤러 도입 후 모든 요청 메세지는 공통 로직이 구&quot; data-og-host=&quot;suaring.tistory.com&quot; data-og-source-url=&quot;https://suaring.tistory.com/133?category=913012&quot; data-og-url=&quot;https://suaring.tistory.com/133&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vrE8l/hyNSfGXfya/I6sLoGzVnhRobm5Vqmrdd1/img.png?width=721&amp;amp;height=378&amp;amp;face=0_0_721_378,https://scrap.kakaocdn.net/dn/baiyVP/hyNR9tb1BJ/4kTaKIGk8EDfG3GPSbtfW0/img.png?width=721&amp;amp;height=378&amp;amp;face=0_0_721_378,https://scrap.kakaocdn.net/dn/72cCI/hyNQ0q6EdU/Lu1PO3A6RgNteMWL3naiYK/img.png?width=720&amp;amp;height=486&amp;amp;face=0_0_720_486&quot;&gt;&lt;a href=&quot;https://suaring.tistory.com/133?category=913012&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://suaring.tistory.com/133?category=913012&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vrE8l/hyNSfGXfya/I6sLoGzVnhRobm5Vqmrdd1/img.png?width=721&amp;amp;height=378&amp;amp;face=0_0_721_378,https://scrap.kakaocdn.net/dn/baiyVP/hyNR9tb1BJ/4kTaKIGk8EDfG3GPSbtfW0/img.png?width=721&amp;amp;height=378&amp;amp;face=0_0_721_378,https://scrap.kakaocdn.net/dn/72cCI/hyNQ0q6EdU/Lu1PO3A6RgNteMWL3naiYK/img.png?width=720&amp;amp;height=486&amp;amp;face=0_0_720_486');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring] MVC 1편 (7) - MVC 프레임워크 만들기 v1, v2, v3&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;프론트 컨트롤러 ✅ 프론트 컨트롤러 도입 전 프론트 컨트롤러 도입 전에는 각 컨트롤러에서 공통 로직을 구현해줘야 했다. ✅ 프론트 컨트롤러 도입 후 모든 요청 메세지는 공통 로직이 구&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;suaring.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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  MVC&amp;nbsp;프레임워크&amp;nbsp;만들기&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✅ v4 - 단순하고 실용적인 컨트롤러&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v3 컨트롤러는 서블릿의 종속성을 제거하고 뷰 경로의 중복을 제거하여 잘 설계된 컨트롤러이다. 그러나 개발자 입장에서 항상 컨트롤러에서 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;ModelView 객체를 생성해서 반환해야 하는 부분이 번거롭다&lt;/b&gt;&lt;/span&gt;. 이번 버전에서는 조금 더 실용적인 방법으로 리팩토링 해본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqvuJf/btrx06mJCUY/KF0ZndsElUdI21B8tBpC6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqvuJf/btrx06mJCUY/KF0ZndsElUdI21B8tBpC6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqvuJf/btrx06mJCUY/KF0ZndsElUdI21B8tBpC6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqvuJf%2Fbtrx06mJCUY%2FKF0ZndsElUdI21B8tBpC6k%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;716&quot; height=&quot;425&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;425&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 구조는 v3와 같지만, 각 컨트롤러는 이제 ModelView를 반환하지 않고 viewName 즉, 뷰의 논리 이름만 반환한다.&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;ControllerV4 인터페이스&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648640302708&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ControllerV4 {

 String process(Map&amp;lt;String, String&amp;gt; paramMap, Map&amp;lt;String, Object&amp;gt; model);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 컨트롤러는 뷰의 이름만 반환하기 때문에 반환타입을 String으로 설정한다.&lt;/li&gt;
&lt;li&gt;또 각 컨트롤러가 객체를 저장할 model 맵을 파라미터로 전달한다.&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;style2&quot;&gt;회원 저장 컨트롤러&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648641132591&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MemberSaveControllerV4 implements ControllerV4 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public String process(Map&amp;lt;String, String&amp;gt; paramMap, Map&amp;lt;String, Object&amp;gt; model) {

        String username = paramMap.get(&quot;username&quot;);
        int age = Integer.parseInt(paramMap.get(&quot;age&quot;));

        Member member = new Member(username, age);
        memberRepository.save(member);

        model.put(&quot;member&quot;, member);
        return &quot;save-result&quot;;
    }
}&lt;/code&gt;&lt;/pre&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;FrontControllerV4&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648641417627&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;String, String&amp;gt; paramMap = createParamMap(request);
Map&amp;lt;String, Object&amp;gt; model = new HashMap&amp;lt;&amp;gt;(); //추가

String viewName = controller.process(paramMap, model);

MyView view = viewResolver(viewName);
view.render(model, request, response);&lt;/code&gt;&lt;/pre&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;컨트롤러의 process() 메서드를 실행하고, 반환받은 viewName으로 논리 이름을 물리 이름으로 바꾼 MyView 객체를 생성해서 render()를 호출한다.&lt;/li&gt;
&lt;/ul&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;✅ v5 - 유연한 컨트롤러&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 v3, v4 컨트롤러를 모두 사용하고 싶다면? 우리가 개발한 프론트 컨트롤러는 &lt;span style=&quot;color: #000000;&quot;&gt;한가지 방식의 컨트롤러 인터페이스만 사용&lt;/span&gt;할 수 있다. 여기에 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;어댑터 패턴을 적용&lt;/b&gt;&lt;/span&gt;하면 다양한 방식의 컨트롤러를 사용할 수 있게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ditmLy/btrx5zBKPhM/GuWOkTH9RaUINXhKRtxVIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ditmLy/btrx5zBKPhM/GuWOkTH9RaUINXhKRtxVIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ditmLy/btrx5zBKPhM/GuWOkTH9RaUINXhKRtxVIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FditmLy%2Fbtrx5zBKPhM%2FGuWOkTH9RaUINXhKRtxVIk%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;898&quot; height=&quot;417&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;프론트 컨트롤러는 핸들러(컨트롤러) 매핑 정보에서 요청 URL를 기반으로 핸들러를 조회한다. 다음으로 조회한 핸들러를 지원할 수 있는 핸들러 어댑터를 핸들러 어댑터 목록에서 찾는다. &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이제 프론트 컨트롤러는 컨트롤러를 직접 호출하지 않고, &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;핸들러 어댑터가 대신 호출&lt;/b&gt;&lt;/span&gt;해준다. 프론트 컨트롤러는 핸들러 어댑터가 반환해준 넘어온 ModelView 객체를 가지고 논리 이름을 물리 이름으로 바꾸어 렌더링해주면 된다.&lt;/span&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;MyHandlerAdapter&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648697624901&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface MyHandlerAdapter {

    boolean supports(Object handler);

    ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException, ServletException;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;boolean supports(Object handler)&lt;/b&gt;&amp;nbsp;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;어댑터가 해당 컨트롤러를 처리할 수 있으면 true를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ModelView handler(HttpServletRequest request, HttpServletResponse response, Object handler)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;어댑터는 컨트롤러를 호출하고 그 결과로 ModelView를 반환한다.&lt;/li&gt;
&lt;li&gt;컨트롤러가 ModelView를 반환하지 않는 경우, 어댑터가 직접 생성해서 반환한다.&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;style2&quot;&gt;ControllerV3HandlerAdapter&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648698118732&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ControllerV3HandlerAdapter implements MyHandlerAdapter {

    @Override
    public boolean supports(Object handler) {
        return (handler instanceof ControllerV3); // ControllerV3의 구현체일 경우 true 반환
    }

    @Override
    public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException, ServletException {

        ControllerV3 controller = (ControllerV3) handler; // supports 메서드로 확인해주고 handle을 호출하기 때문에 캐스팅해도 괜찮음

        Map&amp;lt;String, String&amp;gt; paramMap = createParamMap(request);
        ModelView mv = controller.process(paramMap);
        return mv;
    }

    private Map&amp;lt;String, String&amp;gt; createParamMap(HttpServletRequest request) {

        Map&amp;lt;String, String&amp;gt; paramMap = new HashMap&amp;lt;&amp;gt;();
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -&amp;gt; paramMap.put(paramName, request.getParameter(paramName)));
        return paramMap;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ModelView를 리턴하는 ControllerV3를 지원하는 핸들러 어댑터이다.&lt;/li&gt;
&lt;li&gt;먼저 매개변수로 들어오는 핸들러를 처리할 수 있는 핸들러 어댑터인지 검사한다. (ControllerV3의 구현체인지 확인)&lt;/li&gt;
&lt;li&gt;처리할 있는 핸들러라면 HTTP 요청으로부터 paramMap을 생성하고, 이것을 컨트롤러에게 넘겨주면서 컨트롤러를 실행한다.&lt;/li&gt;
&lt;li&gt;컨트롤러의 process를 호출한 후 반환값인 ModelView를 프론트 컨트롤러에 전달한다.&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;style2&quot;&gt;ControllerV4HandlerAdapter&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648722493087&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ControllerV4HandlerAdapter implements MyHandlerAdapter {

    @Override
    public boolean supports(Object handler) {
        return (handler instanceof ControllerV4);
    }

    @Override
    public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException, ServletException {

        ControllerV4 controller = (ControllerV4) handler;
        Map&amp;lt;String, String&amp;gt; paramMap = createParamMap(request);
        HashMap&amp;lt;String, Object&amp;gt; model = new HashMap&amp;lt;&amp;gt;();

        String viewName = controller.process(paramMap, model);

        ModelView mv = new ModelView(viewName);
        mv.setModel(model);

        return mv;
    }

    private Map&amp;lt;String, String&amp;gt; createParamMap(HttpServletRequest request) {

        Map&amp;lt;String, String&amp;gt; paramMap = new HashMap&amp;lt;&amp;gt;();
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -&amp;gt; paramMap.put(paramName, request.getParameter(paramName)));
        return paramMap;
    }
}&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;blockquote data-ke-style=&quot;style2&quot;&gt;FrontControllerV5 - 핸들러 매핑 정보, 핸들러 어댑터 목록&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648702228364&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class FrontControllerServletV5 extends HttpServlet {

   // private Map&amp;lt;String, ControllerV4&amp;gt; controllerMap = new HashMap&amp;lt;&amp;gt;(); 기존 컨트롤러 맵
    private final Map&amp;lt;String, Object&amp;gt; handlerMappingMap = new HashMap&amp;lt;&amp;gt;(); // Map의 value 타입을 Object로 설정해서 모든 컨트롤러 지원
    private final List&amp;lt;MyHandlerAdapter&amp;gt; handlerAdapters = new ArrayList&amp;lt;&amp;gt;();

    public FrontControllerServletV5() {
        initHandlerMappingMap();
        initHandlerAdapters();
    }

    private void initHandlerMappingMap() {
        handlerMappingMap.put(&quot;/front-controller/v5/v3/members/new-form&quot;, new MemberFormControllerV3());
        handlerMappingMap.put(&quot;/front-controller/v5/v3/members/save&quot;, new MemberSaveControllerV3());
        handlerMappingMap.put(&quot;/front-controller/v5/v3/members&quot;, new MemberListControllerV3());
        
        handlerMappingMap.put(&quot;/front-controller/v5/v4/members/new-form&quot;, new MemberFormControllerV4());
        handlerMappingMap.put(&quot;/front-controller/v5/v4/members/save&quot;, new MemberSaveControllerV4());
        handlerMappingMap.put(&quot;/front-controller/v5/v4/members&quot;, new MemberListControllerV4());
    }

    private void initHandlerAdapters() {
        handlerAdapters.add(new ControllerV3HandlerAdapter());
        handlerAdapters.add(new ControllerV4HandlerAdapter());
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼저 기존처럼 핸들러 매핑 맵을 생성한다. 이때 특정 Controller 인터페이스로 Value 값을 설정하지 않고, Object로 설정해서 모든 컨트롤러가 Value로 들어올 수 있다.&lt;/li&gt;
&lt;li&gt;핸들러 어댑터를 담아놓을 리스트를 생성한다.&lt;/li&gt;
&lt;li&gt;FrontController는 생성될 때 handlerMappingMap과 handlerAdapters를 가지고 있다.&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;style2&quot;&gt;FrontControllerV5 - 메서드&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648702780941&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    Object handler = getHandler(request);

    if (handler == null) {
        response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    MyHandlerAdapter adapter = getHandlerAdapter(handler);
    ModelView mv = adapter.handle(request, response, handler);

    String viewName = mv.getViewName();
    MyView view = viewResolver(viewName);
    view.render(mv.getModel(), request, response);

}

private Object getHandler(HttpServletRequest request) { // 매핑 정보를 사용해서 핸들러를 찾음
    String requestURI = request.getRequestURI();
    return handlerMappingMap.get(requestURI);
}

private MyHandlerAdapter getHandlerAdapter(Object handler) { // 리스트에서 for문으로 handler를 support하는 어댑터를 찾음
    for (MyHandlerAdapter adapter : handlerAdapters) {
        if (adapter.supports(handler)) {
          return adapter;
        }
    }
    throw new IllegalArgumentException(&quot;handler adapter를 찾을 수 없습니다. handler = &quot; + handler);
}

private MyView viewResolver(String viewName) {
    return new MyView(&quot;/WEB-INF/views/&quot; + viewName + &quot;.jsp&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;service() 메서드가 실행되면 request로 들어온 URL를 기반으로 핸들러 매핑 맵에서 컨트롤러를 찾는다.&lt;/li&gt;
&lt;li&gt;어댑터 목록에서 루프를 돌려 핸들러를 지원하는지 확인하고, 지원하는 어댑터를 가져온다.&lt;/li&gt;
&lt;li&gt;어댑터의 handle() 메서드를 호출하고 반환된 ModelView를 렌더링한다.&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;&amp;rarr; 이제 어댑터를 사용하기 때문에 컨트롤러 뿐만 아니라, &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;어댑터가 지원하기만 하면 어떤 것이든 URL에 매핑하여 사용&lt;/b&gt;&lt;/span&gt;할 수 있다! (확장성 up)&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;강의 링크&lt;/b&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1648599012899&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;스프링 MVC 1편  - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의&quot; data-og-description=&quot;웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., - &quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wQhNd/hyNPPpXZGv/SrVF03Mo4EPkEMkk95ZwcK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/MgMQg/hyNPL8WOYG/eZjUT8slKjyXmxZfVymMCK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wQhNd/hyNPPpXZGv/SrVF03Mo4EPkEMkk95ZwcK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/MgMQg/hyNPL8WOYG/eZjUT8slKjyXmxZfVymMCK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500');&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;스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의&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.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>MVC</category>
      <category>Spring</category>
      <category>김영한</category>
      <category>스프링</category>
      <category>웹</category>
      <author>suaring</author>
      <guid isPermaLink="true">https://suaring.tistory.com/134</guid>
      <comments>https://suaring.tistory.com/134#entry134comment</comments>
      <pubDate>Thu, 31 Mar 2022 19:37:58 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] MVC 1편 (7) - MVC 프레임워크 만들기 v1, v2, v3</title>
      <link>https://suaring.tistory.com/133</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;&lt;b&gt;프론트 컨트롤러&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✅ 프론트 컨트롤러 도입 전&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAB7ag/btrxYa2ZLqL/mlMv7U5yelmzsOjAEsycHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAB7ag/btrxYa2ZLqL/mlMv7U5yelmzsOjAEsycHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAB7ag/btrxYa2ZLqL/mlMv7U5yelmzsOjAEsycHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAB7ag%2FbtrxYa2ZLqL%2FmlMv7U5yelmzsOjAEsycHK%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;720&quot; height=&quot;486&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&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 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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CB1Yr/btrxVlEu1mp/lkBZKs6XZJdPl5a4sCaDr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CB1Yr/btrxVlEu1mp/lkBZKs6XZJdPl5a4sCaDr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CB1Yr/btrxVlEu1mp/lkBZKs6XZJdPl5a4sCaDr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCB1Yr%2FbtrxVlEu1mp%2FlkBZKs6XZJdPl5a4sCaDr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;721&quot; height=&quot;378&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&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;li&gt;프론트 컨트롤러는 요청에 맞는 컨트롤러를 찾아 호출해준다.&lt;/li&gt;
&lt;li&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;b&gt;스프링 웹 MVC의 핵심도 FrontController&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 스프링 웹 MVC의 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;DispatcherServlet&lt;/b&gt;&lt;/span&gt;이 FrontController 패턴으로 구현되어 있음!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;&lt;b&gt;MVC 프레임워크 직접 만들어보기&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✅ v1 - 프론트 컨트롤러 도입&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qmBZB/btrxWJED21v/InDIkv2egsjWg2BUKbCn0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qmBZB/btrxWJED21v/InDIkv2egsjWg2BUKbCn0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qmBZB/btrxWJED21v/InDIkv2egsjWg2BUKbCn0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqmBZB%2FbtrxWJED21v%2FInDIkv2egsjWg2BUKbCn0k%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;717&quot; height=&quot;392&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 요청이 들어오면 프론트 컨트롤러는 요청의 URL을 바탕으로 매핑 정보에서 요청 URL과 맞는 컨트롤러를 찾아 호출한다. 각 컨트롤러는 dispatcher.forward()를 사용해서 뷰를 호출한다.&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;ControllerV1 인터페이스&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648558395962&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ControllerV1 {

    void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}&lt;/code&gt;&lt;/pre&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;프론트 컨트롤러는 이 인터페이스의 process() 메서드를 호출하여 로직의 일관성을 가져갈 수 있다.&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;style2&quot;&gt;FrontControllerV1&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648558620184&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@WebServlet(name = &quot;frontControllerServletV1&quot;, urlPatterns = &quot;/front-controller/v1/*&quot;) // v1의 어떤 url이 들어와도 프론트 컨트롤러 서블릿이 무조건 먼저 호출
public class FrontControllerServletV1 extends HttpServlet {

    private Map&amp;lt;String, ControllerV1&amp;gt; controllerMap = new HashMap&amp;lt;&amp;gt;();

    // 서블릿이 생성될 때 url의 매핑 정보를 가지고 있음 (Map의 형태: key=url, value=각 컨트롤러 구현 객체)
    public FrontControllerServletV1() {
        controllerMap.put(&quot;/front-controller/v1/members/new-form&quot;, new MemberFormControllerV1());
        controllerMap.put(&quot;/front-controller/v1/members/save&quot;, new MemberSaveControllerV1());
        controllerMap.put(&quot;/front-controller/v1/members&quot;, new MemberListControllerV1());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println(&quot;FrontControllerServletV1.service&quot;); // 실제로 실행되는지 확인하기 위해서는 로그 찍어보기

        String requestURI = request.getRequestURI();// 요청으로 들어오는 URI를 그대로 받을 수 있음

        ControllerV1 controller = controllerMap.get(requestURI); // 다형성 활용 (인터페이스 타입으로 컨트롤러 가져옴)
        if(controller == null) { // 들어온 URI가 없을 때 예외 처리
             response.setStatus(HttpServletResponse.SC_NOT_FOUND);
             return;
        }

	// 인터페이스의 메서드 호출
        controller.process(request, response);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;urlPatterns를 &quot;/front-controller/v1/*&quot;로 설정하면 /front-controller/v1를 포함한 모든 하위 요청은 이 서블릿으로 들어온다.&lt;/li&gt;
&lt;li&gt;controllerMap에는&lt;span style=&quot;color: #000000;&quot;&gt; &lt;b&gt;각 컨트롤러의 url 매핑 정보&lt;/b&gt;&lt;/span&gt;를 저장하고, 이는 서블릿이 생성될 때 가지고 있는다.&lt;/li&gt;
&lt;li&gt;request.getRequestURI()로 &lt;b&gt;요청으로 들어온 URI를 key로 사용해 controllerMap에서 컨트롤러를 인터페이스 타입으로 가져온다.&lt;/b&gt; (다형성)&lt;/li&gt;
&lt;li&gt;만약 컨트롤러 중 요청 URI와 맞는 &lt;b&gt;컨트롤러가 없으면 NOT_FOUND 상태 코드&lt;/b&gt;를 반환한다.&lt;/li&gt;
&lt;li&gt;컨트롤러가 있으면 컨트롤러에 공통으로 구현되어 있는 &lt;b&gt;process() 메서드를 호출&lt;/b&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;&amp;rarr; 프론트 컨트롤러를 도입했고, Controller의 인터페이스를 도입해서 더 확장성 있고&lt;span style=&quot;color: #ee2323;&quot;&gt; &lt;b&gt;일관적으로 로직을 처리&lt;/b&gt;&lt;/span&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;v&lt;/b&gt;2 - View 분리&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648559203465&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String viewPath = &quot;/WEB-INF/views/new-form.jsp&quot;;
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 컨트롤러에서 뷰로 이동하는 부분에 중복이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9ohea/btrxVGhjx1W/CN2EA44gwBBwgkUGyp6q8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9ohea/btrxVGhjx1W/CN2EA44gwBBwgkUGyp6q8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9ohea/btrxVGhjx1W/CN2EA44gwBBwgkUGyp6q8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9ohea%2FbtrxVGhjx1W%2FCN2EA44gwBBwgkUGyp6q8k%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;718&quot; height=&quot;461&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 컨트롤러에서 직접 JSP를 forward 해주었지만, 이제 Controller는 MyView 객체를 생성해서 FrontController에게 넘겨주기만 하면 FrontController에서 뷰로 이동하는 로직을 실행한다.&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;MyView&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648559457544&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MyView {

    private String viewPath;

    public MyView(String viewPath) {
        this.viewPath = viewPath;
    }

    // 서블릿에서 jsp로 이동해서 렌더링 되도록 동작
    public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MyView 클래스에는 뷰의 경로를 저장할 변수와 JSP를 forward해주는 render() 메서드를 구현한다.&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;ControllerV2 인터페이스&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648559570740&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ControllerV2 {

    MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각 컨트롤러는 로직을 수행하고 MyView 객체를 생성해서 반환한다. 이때 각 뷰의 경로를 입력한다.&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;FrontControllerV2&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648560883718&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

// 나머지 부분 v1과 동일

    MyView myView = controller.process(request, response);
    myView.render(request, response);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Map에서 가져온 컨트롤러의 호출 결과로 MyView 객체를 받는다. &lt;b&gt;view.render()를 호출하면 각 컨트롤러에 맞는 forward 로직이 수행&lt;/b&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;&amp;rarr; 이제 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;각 컨트롤러가 직접 JSP를 forward 하지 않고, 프론트 컨트롤러에서 처리&lt;/b&gt;&lt;/span&gt;하기 때문에 각 컨트롤러에 중복으로 들어갔던 forward 코드를 작성해주지 않아도 된다.&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;&lt;b&gt;✅ v3 - 모델 추가&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;b&gt;서블릿 종속성 제거&lt;/b&gt; : 컨트롤러 입장에서 request와 response 객체가 필요하지 않다. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;요청 파라미터 정보를 자바의 Map으로 대신 넘기도록 하면 컨트롤러는 서블릿 기술을 몰라도 된다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;뷰 이름 중복 제거&lt;/b&gt; : 경로를 지정하는 부분과 파일의 확장자 부분을 작성하는 부분이 중복된다. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;각 컨트롤러는 논리 이름만 반환&lt;/b&gt;&lt;/span&gt;하도록 하고, &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;물리 위치의 이름은 프론트 컨트롤러에서 처리&lt;/b&gt;&lt;/span&gt;하도록 하자. (유지보수 편리)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZUuJ1/btrxWBmwPiT/QmSZekgWyOPFCGdKLiujb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZUuJ1/btrxWBmwPiT/QmSZekgWyOPFCGdKLiujb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZUuJ1/btrxWBmwPiT/QmSZekgWyOPFCGdKLiujb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZUuJ1%2FbtrxWBmwPiT%2FQmSZekgWyOPFCGdKLiujb1%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;717&quot; height=&quot;436&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 컨트롤러에서 서블릿에 종속적인 HttpServletRequest를 사용했고, Model도 request.setAttribute()를 통해 데이터를 보관하고 뷰에 전달했다. 이번 버전에서는 서블릿의 종속성을 제거하기 위해 Model을 직접 만들고, 뷰의 논리 이름을 물리 이름으로 바꿔주는 객체도 생성해볼 것이다.&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;ModelView&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648562164344&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ModelView {

    private String viewName;
    private Map&amp;lt;String, Object&amp;gt; model = new HashMap&amp;lt;&amp;gt;(); // key=이름, value=객체

    public ModelView(String viewName) {
        this.viewName = viewName;
    }

    public String getViewName() {
        return viewName;
    }

    public void setViewName(String viewName) {
        this.viewName = viewName;
    }

    public Map&amp;lt;String, Object&amp;gt; getModel() {
        return model;
    }

    public void setModel(Map&amp;lt;String, Object&amp;gt; model) {
        this.model = model;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰의 이름과 뷰를 렌더링할 때 필요한 model 객체를 가지고 있다.&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;style2&quot;&gt;ControllerV3&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648562239846&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ControllerV3 {

    ModelView process(Map&amp;lt;String, String&amp;gt; paramMap);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이제 컨트롤러는 서블릿 기술을 전혀 사용하지 않는다. &amp;gt; 구현 매우 단순, 테스트하기도 쉬워짐&lt;/li&gt;
&lt;li&gt;HttpServletRequest가 제공하는 파라미터는 프론트 컨트롤러에서 paramMap에 담아준다.&lt;/li&gt;
&lt;li&gt;각 컨트롤러는 로직 수행 후 ModelView를 반환한다.&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;style2&quot;&gt;회원 저장 컨트롤러&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648562449732&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MemberSaveControllerV3 implements ControllerV3 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public ModelView process(Map&amp;lt;String, String&amp;gt; paramMap) {

        // 기존 방식이었던 getParameter() 메서드를 사용하는 부분은 front controller에서 처리해줌
        String username = paramMap.get(&quot;username&quot;);
        int age = Integer.parseInt(paramMap.get(&quot;age&quot;));

        Member member = new Member(username, age);
        memberRepository.save(member);

        ModelView mv = new ModelView(&quot;save-result&quot;);
        mv.getModel().put(&quot;member&quot;, member);
        return mv;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이제 컨트롤러에서는 paramMap에서 요청 파라미터의 값을 꺼내서 비즈니스 로직을 수행한다.&lt;/li&gt;
&lt;li&gt;컨트롤러에서 ModelView 객체를 생성하는데, 이때 생성자의 매개변수로 논리 이름을 입력한다.&lt;/li&gt;
&lt;li&gt;ModelView 객체의 model에 뷰를 렌더링 할 때 필요한 객체를 담고 반환한다.&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;style2&quot;&gt;FrontControllerV3&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648562937015&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // 생략

    Map&amp;lt;String, String&amp;gt; paramMap = createParamMap(request);
    // request, response 대신 paraMap을 넘겨줘야 함
    ModelView mv = controller.process(paramMap);

    String viewName = mv.getViewName();

    MyView view = viewResolver(viewName);// 논리 이름을 물리 이름으로 바꿔줌
    view.render(mv.getModel(), request, response);

} // service

private MyView viewResolver(String viewName) {
    return new MyView(&quot;/WEB-INF/views/&quot; + viewName + &quot;.jsp&quot;);
}

private Map&amp;lt;String, String&amp;gt; createParamMap(HttpServletRequest request) {

    Map&amp;lt;String, String&amp;gt; paramMap = new HashMap&amp;lt;&amp;gt;();

    request.getParameterNames().asIterator()
            .forEachRemaining(paramName -&amp;gt; paramMap.put(paramName, request.getParameter(paramName)));

    return paramMap;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HttpServletRequest에서 파라미터 정보를 꺼내서 paramMap에 넣어둔다.&lt;/li&gt;
&lt;li&gt;이때 생성된 &lt;b&gt;paramMap은 컨트롤러가 호출될 때 전달&lt;/b&gt;된다.&lt;/li&gt;
&lt;li&gt;viewResolver 메서드는 &lt;b&gt;논리 이름을 받아 물리 뷰 경로로 변환&lt;/b&gt;하고, 실제 물리 경로가 있는 &lt;b&gt;MyView 객체를 반환&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;render()를 수행하게 되면 뷰 렌더링할 때 필요한 데이터가 담긴 &lt;b&gt;모델 Map&lt;/b&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;style2&quot;&gt;MyView&lt;/blockquote&gt;
&lt;pre id=&quot;code_1648563575827&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MyView {
	
    // ...
	
    public void render(Map&amp;lt;String, Object&amp;gt; model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        modelToRequestAttribute(model, request);
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }

    // model에 있는 데이터를 request의 attribute로 설정 -&amp;gt; JSP 사용 시 필요
    private void modelToRequestAttribute(Map&amp;lt;String, Object&amp;gt; model, HttpServletRequest request) {
        model.forEach((key, value) -&amp;gt; request.setAttribute(key,value));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MyView에서는 넘어온 모델 Map을 가지고 각 데이터에 대해 request.setAttribute를 수행한다.&lt;/li&gt;
&lt;/ul&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;강의 링크&lt;/b&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1648559150027&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;스프링 MVC 1편  - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의&quot; data-og-description=&quot;웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., - &quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wQhNd/hyNPPpXZGv/SrVF03Mo4EPkEMkk95ZwcK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/MgMQg/hyNPL8WOYG/eZjUT8slKjyXmxZfVymMCK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wQhNd/hyNPPpXZGv/SrVF03Mo4EPkEMkk95ZwcK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/MgMQg/hyNPL8WOYG/eZjUT8slKjyXmxZfVymMCK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500');&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;스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의&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.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>MVC</category>
      <category>Spring</category>
      <category>김영한</category>
      <category>스프링</category>
      <category>웹</category>
      <author>suaring</author>
      <guid isPermaLink="true">https://suaring.tistory.com/133</guid>
      <comments>https://suaring.tistory.com/133#entry133comment</comments>
      <pubDate>Tue, 29 Mar 2022 23:32:19 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] MVC 1편 (6) - MVC 패턴 적용</title>
      <link>https://suaring.tistory.com/132</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  MVC 패턴 사용 이유?&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;너무 많은 역할&lt;/b&gt; : 하나의 파일에서 비즈니스 로직을 수행하고 뷰 렌더링까지 처리하면 유지보수가 어렵다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;변경의 라이프 사이클&lt;/b&gt; : 비즈니스 로직과 뷰 렌더링의 변경의 라이프 사이클이 다르므로 한 파일에 두면 유지보수하기 좋지 않다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기능 특화&lt;/b&gt; : JSP는 뷰 템플릿으로 화면을 렌더링하는데 최적화 되어있다.&lt;/li&gt;
&lt;/ul&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; &lt;span&gt; Model-View-Controller&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;563&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGX8EZ/btrxUZaq6iF/pLBlI2C6urMkTQmkKha6EK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGX8EZ/btrxUZaq6iF/pLBlI2C6urMkTQmkKha6EK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGX8EZ/btrxUZaq6iF/pLBlI2C6urMkTQmkKha6EK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGX8EZ%2FbtrxUZaq6iF%2FpLBlI2C6urMkTQmkKha6EK%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;720&quot; height=&quot;563&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;563&quot;/&gt;&lt;/span&gt;&lt;/figure&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;b&gt;컨트롤러&lt;/b&gt; : HTTP 요청을 받아 파라미터를 검증하고, 비즈니스 로직을 실행한다. 뷰에 전달할 결과 데이터를 조회해서 모델에 담는 역할을 한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모델&lt;/b&gt; : 컨트롤러가 담은 데이터를 뷰에 전달한다. 뷰는 덕분에 비즈니스 로직이나 데이터 접근을 몰라도 되고, 화면을 렌더링하는 일에 집중할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;뷰&lt;/b&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;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;※참고※&lt;/b&gt;&lt;/span&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &lt;span&gt; 서블릿을 컨트롤러로, JSP를 뷰로 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://suaring.tistory.com/131&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;기존에 구현했던 웹 애플리케이션&lt;/a&gt;을 MVC 패턴을 적용해서 리팩토링 해보자!&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;✅ 회원 등록 폼 - 컨트롤러 (Servlet)&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648554840009&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@WebServlet(name =  &quot;mvcMemberFormServlet&quot;,urlPatterns = &quot;/servlet-mvc/members/new-form&quot;)
public class MvcMemberFormServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String viewPath = &quot;/WEB-INF/views/new-form.jsp&quot;;
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);// controller에서 view로 이동할 때 사용
        dispatcher.forward(request, response);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;dispatcher.forward()&lt;/b&gt; : 다른 서블릿 또는 JSP로 이동할 수 있는 기능이다. 서버 내부에서 다시 호출이 발생한다. (리다이렉션 X)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;※ 참고 ※&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;redirect vs forward&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- redirect: 웹 브라우저에서 서버로 호출이 두번 발생. 처음 호출 + 리다이렉션 시 재호출, 클라이언트가 인지 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- forward: 웹 브라우저에서 서버로 호출은 한번만 발생. 서버 내부에서 여러 호출이 발생, 클라이언트가 인지 불가&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;✅ 회원 등록 폼 - 뷰 (JSP)&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648555050428&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;%@ page contentType=&quot;text/html;charset=UTF-8&quot; language=&quot;java&quot; %&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;form action=&quot;save&quot; method=&quot;post&quot;&amp;gt;
    username: &amp;lt;input type=&quot;text&quot; name=&quot;username&quot; /&amp;gt;
    age:      &amp;lt;input type=&quot;text&quot; name=&quot;age&quot; /&amp;gt;
    &amp;lt;button type=&quot;submit&quot;&amp;gt;전송&amp;lt;/button&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSP 파일이 /WEB-INF 경로에 있으면 외부에서 직접 JSP를 호출할 수 없다. 컨트롤러를 통해 호출하려면 이 경로에 파일을 둔다.&lt;/li&gt;
&lt;/ul&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;✅ 회원 저장 - 컨트롤러 (Servlet)&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648555342607&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@WebServlet(name = &quot;mvcMemberSaveServlet&quot;, urlPatterns = &quot;/servlet-mvc/members/save&quot;)
public class MvcMemberSaveServlet extends HttpServlet {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String username = request.getParameter(&quot;username&quot;);
        int age = Integer.parseInt(request.getParameter(&quot;age&quot;));

        // 인스턴스 생성해서 멤버 저장소에 저장
        Member member = new Member(username, age);
        memberRepository.save(member);

        // model에 데이터 보관
        request.setAttribute(&quot;member&quot;, member);

        String viewPath = &quot;/WEB-INF/views/save-result.jsp&quot;;
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HttpServletRequest 객체를 Model로 사용한다.&lt;/li&gt;
&lt;li&gt;request.setAttribute() 메서드를 사용해서 비즈니스 로직을 수행한 후 결과를 보관하고, 뷰로 전달한다.&lt;/li&gt;
&lt;li&gt;이때 key=이름, value=member 형식으로 저장된다.&lt;/li&gt;
&lt;/ul&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;✅ 회원 저장 -&amp;nbsp; 뷰 (JSP)&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648555496619&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;ul&amp;gt;
    &amp;lt;!--(Member)(request.getAttribute(&quot;member&quot;)).getId()와 똑같은 역할--&amp;gt;
    &amp;lt;li&amp;gt;id=${member.id}&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;username=${member.username}&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;age=${member.age} &amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSP가 제공하는 ${ } 문법을 사용해서 request의 attribute의 데이터를 편리하게 조회할 수 있다.&lt;/li&gt;
&lt;/ul&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;✅ 회원 목록 - 컨트롤러 (Servlet)&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648555655973&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Member&amp;gt; members = memberRepository.findAll();

request.setAttribute(&quot;members&quot;, members);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;List를 생성해서 모델에 보관한다.&lt;/li&gt;
&lt;/ul&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;/b&gt; -&amp;nbsp; 뷰 (JSP)&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648555823647&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;%@ taglib prefix=&quot;c&quot; uri=&quot;http://java.sun.com/jsp/jstl/core&quot;%&amp;gt;

&amp;lt;c:forEach var=&quot;item&quot; items=&quot;${members}&quot;&amp;gt; &amp;lt;!--model의 attribure 이름으로 가져옴--&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;td&amp;gt;${item.id}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;${item.username}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;${item.age}&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
&amp;lt;/c:forEach&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모델에 담아둔 리스트를 JSP가 제공하는 taglib 기능을 사용해 for문을 돌린 것 처럼 출력했다.&lt;/li&gt;
&lt;li&gt;members 리스트에서 member를 순서대로 꺼내 item 변수에 담아 출력하는 과정을 반복한다.&lt;/li&gt;
&lt;li&gt;이 기능을 사용하기 위해서는 첫줄처럼 선언해주어야 한다.&lt;/li&gt;
&lt;li&gt;JSP와 같은 뷰 템플릿은 이렇게 화면을 렌더링하는데 특화된 다양한 기능을 제공한다.&lt;/li&gt;
&lt;/ul&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; &lt;span&gt;&lt;span&gt; 서블릿과 JSP로 MVC 패턴 구현 시 한계&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러와 뷰 렌더링 역할을 명확하게 구분했지만, &lt;u&gt;&lt;b&gt;컨트롤러에 중복되는 코드가 많다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648556801268&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String viewPath = &quot;/WEB-INF/views/new-form.jsp&quot;;
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;forward 중복 &lt;/b&gt;: dispatcher를 가져와 view로 이동하기 위한 코드가 항상 중복 호출되어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;viewPath 중복 &lt;/b&gt;: 경로 부분(prefix)과 확장자 부분(suffix)이 중복된다. 만약 폴더 전체를 바꾸거나 템플릿 엔진을 바꾸는 등 변경이 이루어지면 전체 코드를 수정해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용하지 않는 코드 &lt;/b&gt;: request 객체는 모델을 사용할 때에는 사용했지만 response 객체는 아예 사용하지 않았다. 또 이를 사용하는 코드는 테스트 케이스를 작성하기도 어렵다.&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;rarr; 공통 처리가 어렵다&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제들을 해결하기 위해서는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;컨트롤러 호출전에 공통 기능을 처리&lt;/b&gt;&lt;/span&gt;해주면 된다. 모든 요청은 수문장 역할을 하는 객체(프론트 컨트롤러)를 통해서 들어와서 뒤에 있는 컨트롤러가 호출되도록 하자!&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;강의 링크&lt;/b&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1648556075618&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;스프링 MVC 1편  - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의&quot; data-og-description=&quot;웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., - &quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wQhNd/hyNPPpXZGv/SrVF03Mo4EPkEMkk95ZwcK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/MgMQg/hyNPL8WOYG/eZjUT8slKjyXmxZfVymMCK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wQhNd/hyNPPpXZGv/SrVF03Mo4EPkEMkk95ZwcK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/MgMQg/hyNPL8WOYG/eZjUT8slKjyXmxZfVymMCK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500');&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;스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의&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.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>MVC</category>
      <category>Spring</category>
      <category>김영한</category>
      <category>스프링</category>
      <category>웹</category>
      <author>suaring</author>
      <guid isPermaLink="true">https://suaring.tistory.com/132</guid>
      <comments>https://suaring.tistory.com/132#entry132comment</comments>
      <pubDate>Tue, 29 Mar 2022 21:14:58 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] MVC 1편 (5) - ​ 서블릿, JSP를 사용한 회원 관리 웹 애플리케이션</title>
      <link>https://suaring.tistory.com/131</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;&lt;b&gt;회원 관리 웹 애플리케이션 요구사항&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;회원 정보&lt;/b&gt; : username(이름), age(나이)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기능 요구사항&lt;/b&gt; : 회원 저장, 회원 조회&lt;/li&gt;
&lt;/ul&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;  서블릿으로 구현하기&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✅ 회원 등록 폼 - Servlet&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648550857700&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@WebServlet(name = &quot;memberFormServlet&quot;, urlPatterns = &quot;/servlet/members/new-form&quot;)
public class MemberFormServlet extends HttpServlet {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 응답으로 html 받기위해 설정
        response.setContentType(&quot;text/html&quot;);
        response.setCharacterEncoding(&quot;utf-8&quot;);

        PrintWriter w = response.getWriter();

        // form 작성시 form action 경로로 이동
        w.write(&quot;&amp;lt;!DOCTYPE html&amp;gt;\n&quot; +
                &quot;&amp;lt;html&amp;gt;\n&quot; +
                &quot;&amp;lt;head&amp;gt;\n&quot; +
                &quot; &amp;lt;meta charset=\&quot;UTF-8\&quot;&amp;gt;\n&quot; + &quot; &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;\n&quot; +
                &quot;&amp;lt;/head&amp;gt;\n&quot; +
                &quot;&amp;lt;body&amp;gt;\n&quot; +
                &quot;&amp;lt;form action=\&quot;/servlet/members/save\&quot; method=\&quot;post\&quot;&amp;gt;\n&quot; +
                &quot; username: &amp;lt;input type=\&quot;text\&quot; name=\&quot;username\&quot; /&amp;gt;\n&quot; +
                &quot; age: &amp;lt;input type=\&quot;text\&quot; name=\&quot;age\&quot; /&amp;gt;\n&quot; +
                &quot; &amp;lt;button type=\&quot;submit\&quot;&amp;gt;전송&amp;lt;/button&amp;gt;\n&quot; +
                &quot;&amp;lt;/form&amp;gt;\n&quot; +
                &quot;&amp;lt;/body&amp;gt;\n&quot; +
                &quot;&amp;lt;/html&amp;gt;\n&quot;);

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고객에게 보여줄 Form 화면을 웹 브라우저에 전달한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✅ 회원 저장&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;- Servlet&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648551008853&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@WebServlet(name = &quot;memberSaveServlet&quot;, urlPatterns = &quot;/servlet/members/save&quot;)
public class MemberSaveServlet extends HttpServlet {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        System.out.println(&quot;MemberSaveServlet.service&quot;);
        // Form을 통해 넘어온 정보를 변수에 입력
        String username = request.getParameter(&quot;username&quot;);
        int age = Integer.parseInt(request.getParameter(&quot;age&quot;));

        // 인스턴스 생성해서 멤버 저장소에 저장
        Member member = new Member(username, age);
        memberRepository.save(member);

	// 결과를 HTML로 반환
        response.setContentType(&quot;text/html&quot;);
        response.setCharacterEncoding(&quot;utf-8&quot;);

        PrintWriter w = response.getWriter();
        
        // 동적 HTML 생성
        w.write(&quot;&amp;lt;html&amp;gt;\n&quot; +
                &quot;&amp;lt;head&amp;gt;\n&quot; +
                &quot; &amp;lt;meta charset=\&quot;UTF-8\&quot;&amp;gt;\n&quot; +
                &quot;&amp;lt;/head&amp;gt;\n&quot; +
                &quot;&amp;lt;body&amp;gt;\n&quot; +
                &quot;성공\n&quot; +
                &quot;&amp;lt;ul&amp;gt;\n&quot; +
                &quot; &amp;lt;li&amp;gt;id=&quot;+member.getId()+&quot;&amp;lt;/li&amp;gt;\n&quot; +
                &quot; &amp;lt;li&amp;gt;username=&quot;+member.getUsername()+&quot;&amp;lt;/li&amp;gt;\n&quot; +
                &quot; &amp;lt;li&amp;gt;age=&quot;+member.getAge()+&quot;&amp;lt;/li&amp;gt;\n&quot; +
                &quot;&amp;lt;/ul&amp;gt;\n&quot; +
                &quot;&amp;lt;a href=\&quot;/index.html\&quot;&amp;gt;메인&amp;lt;/a&amp;gt;\n&quot; +
                &quot;&amp;lt;/body&amp;gt;\n&quot; +
                &quot;&amp;lt;/html&amp;gt;&quot;);

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Form을 통해 입력된 정보를 request.getParameter() 메서드를 사용해 가져와 Member 객체를 생성한다.&lt;/li&gt;
&lt;li&gt;이를 이용해서 비즈니스 로직을 수행하고 동적으로 HTML을 생성해서 전달한다.&lt;/li&gt;
&lt;/ul&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;span&gt;&amp;nbsp;&lt;/span&gt;- Servlet&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648551285634&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@WebServlet(name = &quot;memberListServlet&quot;, urlPatterns = &quot;/servlet/members&quot;)
public class MemberListServlet extends HttpServlet {

    MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        List&amp;lt;Member&amp;gt; memberList = memberRepository.findAll();

        response.setContentType(&quot;text/html&quot;);
        response.setCharacterEncoding(&quot;utf-8&quot;);

        PrintWriter w = response.getWriter();
        w.write(&quot;&amp;lt;html&amp;gt;&quot;);
        w.write(&quot;&amp;lt;head&amp;gt;&quot;);
        w.write(&quot; &amp;lt;meta charset=\&quot;UTF-8\&quot;&amp;gt;&quot;);
        w.write(&quot; &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;&quot;);
        w.write(&quot;&amp;lt;/head&amp;gt;&quot;);
        w.write(&quot;&amp;lt;body&amp;gt;&quot;);
        w.write(&quot;&amp;lt;a href=\&quot;/index.html\&quot;&amp;gt;메인&amp;lt;/a&amp;gt;&quot;);
        w.write(&quot;&amp;lt;table&amp;gt;&quot;);
        w.write(&quot; &amp;lt;thead&amp;gt;&quot;);
        w.write(&quot; &amp;lt;th&amp;gt;id&amp;lt;/th&amp;gt;&quot;);
        w.write(&quot; &amp;lt;th&amp;gt;username&amp;lt;/th&amp;gt;&quot;);
        w.write(&quot; &amp;lt;th&amp;gt;age&amp;lt;/th&amp;gt;&quot;);
        w.write(&quot; &amp;lt;/thead&amp;gt;&quot;);
        w.write(&quot; &amp;lt;tbody&amp;gt;&quot;);

 	// memberRepository에 저장된 모든 멤버를 동적으로 출력
        for (Member member : memberList) {
            w.write(&quot; &amp;lt;tr&amp;gt;&quot;);
            w.write(&quot; &amp;lt;td&amp;gt;&quot; + member.getId() + &quot;&amp;lt;/td&amp;gt;&quot;);
            w.write(&quot; &amp;lt;td&amp;gt;&quot; + member.getUsername() + &quot;&amp;lt;/td&amp;gt;&quot;);
            w.write(&quot; &amp;lt;td&amp;gt;&quot; + member.getAge() + &quot;&amp;lt;/td&amp;gt;&quot;);
            w.write(&quot; &amp;lt;/tr&amp;gt;&quot;);
        }
        w.write(&quot; &amp;lt;/tbody&amp;gt;&quot;);
        w.write(&quot;&amp;lt;/table&amp;gt;&quot;); w.write(&quot;&amp;lt;/body&amp;gt;&quot;);
        w.write(&quot;&amp;lt;/html&amp;gt;&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동적으로 HTML을 생성할 때 for문을 사용할 수 있다.&lt;/li&gt;
&lt;/ul&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;span&gt; 템플릿 엔진&lt;/span&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;서블릿을 이용해서 동적으로 HTML을 생성할 수 있다.&lt;/li&gt;
&lt;li&gt;하지만 자바 코드에서 HTML 코드를 한줄한줄 적는 것은 번거롭고 디버깅이 굉장히 힘들다.&lt;/li&gt;
&lt;li&gt;이를 해결하기 위해 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;템플릿 엔진&lt;/b&gt;&lt;/span&gt;을 사용할 수 있다! e.g. JSP, Thymeleaf, Freemarker, Velocity&lt;/li&gt;
&lt;li&gt;템플릿 엔진을 사용하면 HTML 코드에서 필요한 곳만 코드를 적용해서 동적으로 변경할 수 있다.&lt;/li&gt;
&lt;/ul&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;  JSP로 구현하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main/webapp 폴더 밑에 있는 파일은 기본적으로 호출된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✅ JSP 라이브러리 추가&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648552059914&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//JSP 추가 시작
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'javax.servlet:jstl'
//JSP 추가 끝&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSP를 사용하기 위해 위 코드를 build.gradle에 추가해주어야 한다.&lt;/li&gt;
&lt;/ul&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;✅ 회원 등록 폼 - JSP&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648552169406&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;%@ page contentType=&quot;text/html;charset=UTF-8&quot; language=&quot;java&quot; %&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;form action=&quot;/jsp/members/save.jsp&quot; method=&quot;post&quot;&amp;gt;
    username: &amp;lt;input type=&quot;text&quot; name=&quot;username&quot; /&amp;gt;
    age: &amp;lt;input type=&quot;text&quot; name=&quot;age&quot; /&amp;gt;
    &amp;lt;button type=&quot;submit&quot;&amp;gt;전송&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫줄은 JSP 문서임을 나타낸다.&lt;/li&gt;
&lt;li&gt;HTML만 전달하는 회원 등록 폼에는 자바 코드가 들어가지 않고 HTML 코드와 거의 같다.&lt;/li&gt;
&lt;/ul&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;✅ 회원 저장 - JSP&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648552494040&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;%@ page import=&quot;hello.servlet.domain.member.Member&quot; %&amp;gt;
&amp;lt;%@ page import=&quot;hello.servlet.domain.member.MemberRepository&quot; %&amp;gt;
&amp;lt;%@ page contentType=&quot;text/html;charset=UTF-8&quot; language=&quot;java&quot; %&amp;gt;
&amp;lt;%
    MemberRepository memberRepository = MemberRepository.getInstance();
    // 넘어온 정보 변수에 입력
    String username = request.getParameter(&quot;username&quot;);
    int age = Integer.parseInt(request.getParameter(&quot;age&quot;));
    // 인스턴스 생성해서 멤버 저장소에 저장
    Member member = new Member(username, age);
    memberRepository.save(member);
%&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
성공
&amp;lt;ul&amp;gt;
    &amp;lt;li&amp;gt;id=&amp;lt;%=member.getId()%&amp;gt; &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;username=&amp;lt;%=member.getUsername()%&amp;gt; &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;age=&amp;lt;%=member.getAge()%&amp;gt; &amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;a href=&quot;/index.html&quot;&amp;gt;메인&amp;lt;/a&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSP 에서 자바 코드는 &amp;lt;%~~%&amp;gt; 안에 적어준다.&lt;/li&gt;
&lt;li&gt;자바 코드를 출력하려면 &amp;lt;%=~~%&amp;gt; 안에 적어준다.&lt;/li&gt;
&lt;li&gt;서블릿과 같이 request와 response를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&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;✅ 회원 목록 - JSP&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648552722152&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;%@ page import=&quot;java.util.List&quot; %&amp;gt;
&amp;lt;%@ page import=&quot;hello.servlet.domain.member.MemberRepository&quot; %&amp;gt;
&amp;lt;%@ page import=&quot;hello.servlet.domain.member.Member&quot; %&amp;gt;
&amp;lt;%@ page contentType=&quot;text/html;charset=UTF-8&quot; language=&quot;java&quot; %&amp;gt;
&amp;lt;%
    MemberRepository memberRepository = MemberRepository.getInstance();
    List&amp;lt;Member&amp;gt; members = memberRepository.findAll();
%&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;
&amp;lt;a href=&quot;/index.html&quot;&amp;gt;메인&amp;lt;/a&amp;gt;
&amp;lt;table&amp;gt;
    &amp;lt;thead&amp;gt;
    &amp;lt;th&amp;gt;id&amp;lt;/th&amp;gt;
    &amp;lt;th&amp;gt;username&amp;lt;/th&amp;gt;
    &amp;lt;th&amp;gt;age&amp;lt;/th&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
    &amp;lt;%
        for (Member member : members) {
            out.write(&quot; &amp;lt;tr&amp;gt;&quot;);
            out.write(&quot; &amp;lt;td&amp;gt;&quot; + member.getId() + &quot;&amp;lt;/td&amp;gt;&quot;);
            out.write(&quot; &amp;lt;td&amp;gt;&quot; + member.getUsername() + &quot;&amp;lt;/td&amp;gt;&quot;);
            out.write(&quot; &amp;lt;td&amp;gt;&quot; + member.getAge() + &quot;&amp;lt;/td&amp;gt;&quot;);
            out.write(&quot; &amp;lt;/tr&amp;gt;&quot;);
        }
    %&amp;gt;
    &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 코드를 실행하고, 결과 List를 사용해서 for문을 이용해 동적으로 HTML을 출력한다.&lt;/li&gt;
&lt;/ul&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;  서블릿, JSP의 한계&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서블릿을 사용하는 경우 &lt;b&gt;뷰 화면을 위한 HTML 코드와 자바 코드가 섞여있어&lt;/b&gt; 지저분하고 복잡했다.&lt;/li&gt;
&lt;li&gt;JSP를 사용하면 뷰를 생성하는 &lt;b&gt;HTML 코드를 기본적으로 사용&lt;/b&gt;하면서 중간중간 &lt;b&gt;동적으로 변경이 필요한 부분에만 자바 코드를 적용&lt;/b&gt;했다.&lt;/li&gt;
&lt;li&gt;하지만 두가지 방법 모두 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;뷰와 비즈니스 로직을 한 파일 안에서 구현&lt;/b&gt;&lt;/span&gt;하기 때문에 각각 너무 많은 역할을 한다. (유지보수 어려움)&lt;/li&gt;
&lt;li&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;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;rarr;&lt;/b&gt;&lt;/span&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;MVC 패턴 등장!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;강의 링크&lt;/b&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1648552429468&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;스프링 MVC 1편  - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의&quot; data-og-description=&quot;웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., - &quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wQhNd/hyNPPpXZGv/SrVF03Mo4EPkEMkk95ZwcK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/MgMQg/hyNPL8WOYG/eZjUT8slKjyXmxZfVymMCK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wQhNd/hyNPPpXZGv/SrVF03Mo4EPkEMkk95ZwcK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/MgMQg/hyNPL8WOYG/eZjUT8slKjyXmxZfVymMCK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500');&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;스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의&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.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>jsp</category>
      <category>Spring</category>
      <category>김영한</category>
      <category>서블릿</category>
      <category>스프링</category>
      <category>웹</category>
      <author>suaring</author>
      <guid isPermaLink="true">https://suaring.tistory.com/131</guid>
      <comments>https://suaring.tistory.com/131#entry131comment</comments>
      <pubDate>Tue, 29 Mar 2022 20:26:40 +0900</pubDate>
    </item>
  </channel>
</rss>