<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>404-nut-pound.log</title>
        <link>https://velog.io/</link>
        <description>Java &amp; Vue ...</description>
        <lastBuildDate>Sat, 13 Jan 2024 08:54:18 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>404-nut-pound.log</title>
            <url>https://velog.velcdn.com/images/404-nut-pound/profile/2a2be9a0-1cd4-47f5-90b4-96bc3d815297/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 404-nut-pound.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/404-nut-pound" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Kotlin - 4]]></title>
            <link>https://velog.io/@404-nut-pound/Kotlin-4</link>
            <guid>https://velog.io/@404-nut-pound/Kotlin-4</guid>
            <pubDate>Sat, 13 Jan 2024 08:54:18 GMT</pubDate>
            <description><![CDATA[<h1 id="배열-및-콜렉션">배열 및 콜렉션</h1>
<pre><code class="language-kotlin">val array = arrayOf(1, 2, 3)

for(i in array.indices) {
  println(&quot;${i} - ${array[i]}&quot;)
}

for((index, value) in array.withIndex()) {
  println(&quot;${index} - ${value}&quot;)
}</code></pre>
<ul>
<li><p>콜렉션 객체는 초기화할 때 타입을 명시해야 함</p>
</li>
<li><p>불변 콜렉션</p>
<ul>
<li>List - listOf</li>
<li>Set - setOf</li>
<li>Map - mapOf</li>
</ul>
</li>
<li><p>가변 콜렉션</p>
<ul>
<li>MutableList - mutableListOf(ArrayList)</li>
<li>MutableSet - mutableSetOf(LinkedHashSet)</li>
<li>MutableMap - mutableMapOf()</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">val map = mutableMapOf&lt;Int, String&gt;()
map[1] = &quot;a&quot;
map[2] = &quot;b&quot;

mapOf(1 to &quot;a&quot;, 2 to &quot;b&quot;)</code></pre>
<h1 id="확장-함수">확장 함수</h1>
<ul>
<li>특정 클래스의 내장 함수인 것과 같은 동작을 하는 함수를 추가 정의</li>
<li>동일한 명칭으로 작성된 확장 함수를 호출할 경우 원본 클래스의 함수를 호출함</li>
</ul>
<pre><code class="language-kotlin">fun String.lastChar(): Char {
  return this.charAt(this.length() - 1)
}

fun Int.add(other: Int): Int {
  return this + other
}

infix fun Int.add2(other: Int): Int {
  return this + other
}

1.add(2)  //3
1.add2(2) //3
1 add2 2  //3

//컴파일 시 함수 내부의 코드가 호출된 지점에 그대로 복사됨
//함수 호출에 의한 오버헤드 저감 목적
inline fun Int.add3(other: Int): Int {
  return this + other
}</code></pre>
<h1 id="함수형-프로그래밍">함수형 프로그래밍</h1>
<ul>
<li>함수 파라미터에 함수 타입을 정의 가능</li>
<li>함수 파라미터 마자믹에 함수가 있다면 함수 호출 시 코드 블럭으로 구현하여 호출 가능</li>
<li>Java에서는 람다 밖의 final이 아닌 변수를 사용할 수 없으나 Kotlin은 사용 가능(Closure 기능)</li>
</ul>
<pre><code class="language-kotlin">val isApple: (Fruit) -&gt; Boolean = { fruit: Fruit -&gt; fruit.name == &quot;사과&quot; }

fun filterFruit(fruits: List&lt;Fruit&gt;, filter: (Fruit) -&gt; Boolean): List&lt;Fruit&gt; {
  val results = mutableListOf&lt;Fruit&gt;()

  for(fruit in fruits) {
    if(filter.invoke(fruit)) {
      results.add(fruit)
    }
  }

  return results
}

filterFruits(fruits, isApple)
filterFruits(fruits) {
  //return을 명시하지 않아도 마지막 줄의 결과를 자동 반환
  //여러 줄을 입력할 때 중괄호 필수 아님
  it.name == 사과 //it 키워드로 입력된 람다 파라미터 자체를 지칭
}</code></pre>
<h2 id="stream-함수">Stream 함수</h2>
<ul>
<li>filterIndexed - index와 value를 함께 제공</li>
<li>mapIndexed - index와 entry를 함께 제공</li>
<li>mapNotNull - null이 아닌 값만 반환</li>
<li>sortedByDescending - 주어진 값에 대해 역순 정렬</li>
<li>groupBy - 주어진 값과 일치하는 Map&lt;any, List&lt;any&gt;&gt;를 반환<ul>
<li>파라미터 여러 개를 사용해 key와 value에 해당하는 조건을 각각 작성 가능</li>
</ul>
</li>
<li>associateBy - 특정한 key를 이용한 Map&lt;key, any&gt;를 반환<ul>
<li>파라미터 여러 개를 사용해 key와 value에 해당하는 조건을 각각 작성 가능</li>
</ul>
</li>
<li>flatten - 중첩 콜렉션을 단일 콜렉션으로 단순 변환</li>
</ul>
<h1 id="타입-별칭">타입 별칭</h1>
<ul>
<li><code>typealias</code> 키워드를 통해 별칭 지정 가능</li>
</ul>
<pre><code class="language-kotlin">typealias FruitFilter = (fruit) -&gt; Boolean

fun fruitFilter(frutis: List&lt;Fruit&gt;, filter: FruitFilter): List&lt;Fruit&gt; {

}</code></pre>
<ul>
<li>같은 함수 및 변수를 import할 때는 <code>as</code> 키워드로 별칭을 지정해줘야 함</li>
</ul>
<h1 id="구조-분해">구조 분해</h1>
<ul>
<li>객체 선언과 변수 선언을 동시에 수행</li>
<li>변수 이름에 매칭해주는 것이 아닌 변수 순서에 따라 매핑되기 때문에 코드 작성에 유의</li>
<li><code>componentN</code> 키워드를 이름으로 하는 함수 재정의 가능</li>
</ul>
<pre><code class="language-kotlin">val person = Person()
val name = person.name
val age = person.age

val (name, age) = Person()
//val (age, name) = Person() //age 변수에 원본 클래스의 name 값이 할당 됨</code></pre>
<h1 id="scope-function">Scope Function</h1>
<ul>
<li>특정 코드 블럭 내 변수를 키워드로 대체</li>
</ul>
<table>
<thead>
<tr>
<th></th>
<th><code>it</code></th>
<th><code>this</code></th>
</tr>
</thead>
<tbody><tr>
<td>람다의 결과를 반환</td>
<td><code>let</code></td>
<td><code>run</code></td>
</tr>
<tr>
<td>객체 자체를 반환</td>
<td><code>also</code></td>
<td><code>apply</code></td>
</tr>
</tbody></table>
<ul>
<li>it - 생략 불가능, 별칭 지정 가능</li>
<li>this - 생략 가능</li>
</ul>
<pre><code class="language-kotlin">fun print(person: Person?) {
  person?.let {
    println(it.name)
    println(it.age)
  }
}

person.let {
  it.age
}

person.run {
  this.age
}

person.also {
  it.age
}

person.apply {
  this.age
}

with(person) { //with(객체, 람다 함수)
  println(this.name)
  println(age)
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin - 3]]></title>
            <link>https://velog.io/@404-nut-pound/Kotlin-3</link>
            <guid>https://velog.io/@404-nut-pound/Kotlin-3</guid>
            <pubDate>Fri, 12 Jan 2024 15:52:06 GMT</pubDate>
            <description><![CDATA[<h1 id="클래스">클래스</h1>
<ul>
<li>클래스 생성자는 클래스 이름 옆에 위치<ul>
<li>주 생성자라고 명칭하며 추가 생성자 작성 가능</li>
<li>일반적인 상황에서는 기본값 파라미터 사용을 권장</li>
</ul>
</li>
<li>클래스 정의 시 프로퍼티 항목에 var/val 키워드를 사용하면 getter/setter 함수는 자동 생성<ul>
<li>Java 클래스를 사용할 때도 동일하게 사용 가능</li>
</ul>
</li>
<li>클래스 블록 내 <code>init</code> 키워드를 사용해 클래스 생성 시 로직 작성 가능<ul>
<li>Java의 <code>static {}</code> 블록과 유사</li>
</ul>
</li>
<li>함수를 클래스 프로퍼티와 같이 정의하여 사용 가능</li>
<li>필드 프로퍼티의 custom getter/setter를 사용할 때 <code>field</code>(backing field) 키워드를 사용하여 재정의 가능</li>
</ul>
<pre><code class="language-kotlin">class Person constructor(name: String, age: Int) {

  val name: String = name
  var age: Int = age
}

//간소화
class Person(val name: String, var age: Int)

val person = Person(&quot;abc&quot;, 10)
println(person.name) //getter 접근
person.age = 20      //setter 접근
println(person.age)

class Person(val name: String, var age: Int) {
  init {
    if (age &lt;= 0) {
      ...
    }
  }

  constructor(name: String) : this(name, 1) {
    println(&quot;부 생성자&quot;)
  }

  fun isAdult(): Boolean {
    return this.age &gt;= 20
  }

  //위 코드와 동일한 동작
  val isAdult: Boolean
    get() {
      return this.age &gt;= 20
    }

  //프로퍼티 정의에서 var/val을 지우고 작성해야 함
  val uppercaseName: String = name
    get(): String = field.uppercase()

  var name = name
    set(newValue) {
      field = newValue.uppercase()
    }
}</code></pre>
<h1 id="상속-및-구현">상속 및 구현</h1>
<ul>
<li><code>extends</code>, <code>implement</code> 키워드 대신 클래스 정의 코드 뒤 <code>: SuperClass(생성자)</code>형식으로 입력함<ul>
<li><code>:</code> 입력 시 좌우칸에 띄어쓰기 입력 주의</li>
</ul>
</li>
<li>함수 재정의 시 <code>override</code> 키워드 명시해야 함</li>
<li>하위 클래스에서 상위 클래스의 프로퍼티를 재정의하려면 상위 클래스에서 해당 프로퍼티를 <code>open</code> 키워드로 정의해야 함<ul>
<li>클래스 프로퍼티에는 기본적으로 <code>final</code> 키워드가 사용됨</li>
</ul>
</li>
<li>인터페이스의 프로퍼리를 재정의할 때는 <code>field</code> 키워드 없이 재정의 가능</li>
<li>상위 클래스를 설계할 때 생성자 혹은 초기화 블록에 사용되는 프로퍼티에 <code>open</code> 설정을 하면 안 됨<ul>
<li>동작 순서 상 하위 클래스에서 정의되기 전에 호출하면 의도한 값이 설정되지 않을 수 있음</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">class Animal(
  protected val species: String,
  // protected val legCount: int
  protected open val legCount: int
) {
  abstract fun move()
}

interface Swimmable {
  val swimScore: Int

  fun act() {
    println(&quot;...&quot;)
  }
}

interface Flyable {
  fun act() {
    println(&quot;...&quot;)
  }
}

class Cat(species: String)
 : Animal(species, 4) {
  override fun move() {
    println(&quot;...&quot;)
  }
}

class Penguin(
  species: String
) : Animal(species, 2), Swimmable, Flyable {
  private val wingCount: Int = 2

  override fun move() {
    println(&quot;...&quot;)
  }

  override val legCount: Int
    get() = super.legCount + this.wingCount

  override fun act() {
    super&lt;Swimmable&gt;.act()
    super&lt;Flyable&gt;.act()
  }

  override val swimScore: Int
    get() = super.swimScore //field 키워드 사용없이 가능
}</code></pre>
<h1 id="접근-제어">접근 제어</h1>
<h2 id="기본-접근-제어자">기본 접근 제어자</h2>
<ul>
<li>Java - <code>default</code></li>
<li>Kotlin - <code>public</code></li>
</ul>
<h2 id="package"><code>package</code></h2>
<ul>
<li>Java - <strong><em>같은 패키지</em></strong> 혹은 하위 클래스에서만 접근 가능</li>
<li>Kotlin - <strong><em>선언된 클래스</em></strong> 또는 하위 클래스에서만 접근 가능</li>
</ul>
<h2 id="default"><code>default</code></h2>
<ul>
<li>Java - 같은 패키지에서만 접근 가능</li>
<li>Kotlin - <code>internal</code>로 명칭 변경, 같은 모듈에서만 접근 가능<ul>
<li>모듈 - 프로젝트 단위</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">class Car(
  internal val name: String,
  private var owner: String,
  _price: Int //Prefix로 _를 입력해서 private 표현
) {
  var price = _price
    private set
}</code></pre>
<h1 id="static">Static</h1>
<ul>
<li>Kotlin에는 <code>static</code> 키워드가 없음</li>
<li><code>companion object</code> 키워드를 사용해 클래스 내부의 <code>static</code>과 같은 속성을 가진 객체 생성</li>
<li><code>class</code> 키워드 대신 <code>object</code> 키워드를 사용해 Singleton 객체 생성</li>
</ul>
<pre><code class="language-kotlin">class Person(
  val name: String,
  var age: Int
) {
  companion object Hospital {
    // val MIN_AGE = 0 //런타임 시 값 설정
    const val MIN_AGE = 0 //컴파일 시 값 설정, 기본 타입+String에만 사용 가능

    @JvmStatic
    fun newBaby(name: String): Person  {
      return Person(name, MIN_AGE)
    }
  }
}

//Java
public static void callKotlinStatic() {
  // Person.Companion.newBaby(&quot;&quot;); //companion object의 기본 명칭
  Person.Hospital.newBaby(&quot;&quot;);
  Person.newBaby(&quot;&quot;);
}</code></pre>
<h2 id="익명-클래스">익명 클래스</h2>
<pre><code class="language-kotlin">//Java
interface SomeInterface {
  void actionA();
  void actionB();
}

new SomeInterface() {
  @Override
  void actionA() {
    ...
  }

  @Override
  void actionB() {
    ...
  }
}

//Kotlin
(object : SomeInterface) {
  override fun actionA() {
    ...
  }

  override fun actionB() {
    ...
  }
}</code></pre>
<h1 id="클래스-중첩">클래스 중첩</h1>
<ul>
<li>Java에서는 내부 클래스를 <code>static</code>으로 정의하는 것을 권장함</li>
<li>Kotlin은 <code>static</code> 키워드를 사용할 수 없기 때문에 기본적으로 <code>static</code> 클래스와 같이 설정됨</li>
<li><code>static</code>이 아닌 내부 클래스를 정의하려면 <code>inner</code> 키워드로 정의</li>
</ul>
<pre><code class="language-kotlin">class House(
  var address: String
  var livingRoom: LivingRoom
) {
  inner class LivingRoom(
    area: Double
  ) {
    val address: String
      get() = this@House.address
  }
}</code></pre>
<h1 id="클래스-제어">클래스 제어</h1>
<ul>
<li><code>data class</code> - <code>equals</code>, <code>toString()</code>, <code>hashcode</code> 함수를 자동으로 만들어주는 클래스 정의</li>
<li><code>enum class</code>에 대해 <code>when</code> 분기 처리를할 때 <code>else</code>를 작성하지 않아도 무관</li>
<li><code>sealed class</code>를 사용하여 상속/구현 가능한 클래스를 제한할 수 있음</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin - 2]]></title>
            <link>https://velog.io/@404-nut-pound/Kotlin-2</link>
            <guid>https://velog.io/@404-nut-pound/Kotlin-2</guid>
            <pubDate>Fri, 12 Jan 2024 15:01:18 GMT</pubDate>
            <description><![CDATA[<h1 id="제어문">제어문</h1>
<h2 id="if-else">if else</h2>
<ul>
<li>if 제어문을 return 가능<ul>
<li>Kotlin에서는 삼항 연산자를 사용하지 않음</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">val grade = if(score &gt;= 50) &quot;P&quot; else &quot;F&quot;

//Java
if(score &gt;= 0 &amp;&amp; score &lt;= 100) ...

//Kotlin
if(score in 0..100) ...</code></pre>
<h2 id="switch-case">switch case</h2>
<ul>
<li><code>when</code> 키워드 사용<ul>
<li><code>when</code> 키워드의 조건문 자체를 블럭 내부에서 구현 가능</li>
</ul>
</li>
<li>조건문에는 expression 종류 사용 가능</li>
<li>한 줄에 조건 여러 개 입력 가능</li>
</ul>
<pre><code class="language-kotlin">return when(score) {
  in 90..99 -&gt; &quot;A&quot;
  ...
  8 -&gt; &quot;B&quot; //조건문이 score / 10 인 것으로 고려
  else -&gt; &quot;D&quot;
}

return when {
  number in 90..99 -&gt; &quot;P&quot;
  else &quot;F&quot;
}</code></pre>
<h1 id="반복문">반복문</h1>
<ul>
<li>전체적으로 Java와 Kotlin의 문법은 유사함<ul>
<li>while 사용법은 동일</li>
</ul>
</li>
<li>변화값을 표현하는 특정 키워드 사용<ul>
<li>역순인 경우 2 downTo 0</li>
<li>반복 단위를 설정(i+=2 등)은 step 2 로 표현</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">//for-each
//Java
List numList = List.of(1,2,3);

for(int num : numList) {
  ...
}

//Kotlin
val numList = listOf(1,2,3)
for(num in numList) {
  ...
}

//범위 지정 for
//Java
for(int i = 0; i &lt; 3; i++) {
  ...
}

//Kotlin
for(i in 0..2) {
  ...
}</code></pre>
<h1 id="예외-처리">예외 처리</h1>
<ul>
<li>try-catch-finally 동일하게 사용 가능</li>
<li>if-else와 같이 try 구문을 return 가능</li>
<li>Kotlin에서는 Checked/Unchecked Exception 구분없이 모두 Unchecked로 간주함<ul>
<li>throws 혹은 try-catch 필수 아님</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">//Kotlin에서는 try-with-resource를 사용하지 않음
//Java
try(BufferedReader br = new BufferedReader(...)) {
  br.readLine();
}

//Kotlin
BufferedReader(???).use {
  reader -&gt; reader.readLine()
}</code></pre>
<h1 id="함수-작성">함수 작성</h1>
<ul>
<li><code>public</code> 생략 가능</li>
<li><code>fun</code>이라는 키워드로 함수라고 명시</li>
<li><code>return</code>을 <code>=</code>로 생략 가능</li>
</ul>
<pre><code class="language-kotlin">fun max(a: Int, b: Int): int = if(a &gt; b) a else b</code></pre>
<ul>
<li>함수 파라미터 기본값을 사용하여 <code>Overloading</code> 구현</li>
<li>클래스 인스턴스 생성 시 <code>builder 패턴</code>을 사용하지 않고 <code>named argument</code>로 구현 가능<ul>
<li>자바 코드를 참조할 때는 <code>named argument</code> 사용 불가</li>
</ul>
</li>
</ul>
<pre><code class="language-kotlin">fun someFun(str: String, num: Int = 3, useNeWLine: Boolean = true) {
  ...
}

someFun(&quot;문자열&quot;)
someFun(&quot;문자열&quot;, 1)
someFun(&quot;문자열&quot;, 2, false)
someFun(&quot;문자열&quot;, useNewLine = false)
---
//가변 파라미터 사용
//Java
printAll([&quot;1&quot;, &quot;2&quot;, &quot;3&quot;])

public void printAll(String... params) {
  ...
}

//Kotlin
printAll([&quot;1&quot;, &quot;2&quot;, &quot;3&quot;])
val strList = arrayOf(&quot;1&quot;, &quot;2&quot;, &quot;3&quot;)
printAll(*strList)

fun printAll(vararg params: String) {
  ...
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security AntPathRequestMatcher 오류 처리]]></title>
            <link>https://velog.io/@404-nut-pound/Spring-Security-AntPathRequestMatcher-%EC%98%A4%EB%A5%98-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@404-nut-pound/Spring-Security-AntPathRequestMatcher-%EC%98%A4%EB%A5%98-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Thu, 03 Aug 2023 09:54:16 GMT</pubDate>
            <description><![CDATA[<h1 id="버전-정보">버전 정보</h1>
<ul>
<li>Spring Boot 3.0.9</li>
<li>Spring Security 6.0.5</li>
</ul>
<h1 id="패치-노트">패치 노트</h1>
<p><a href="https://github.com/spring-projects/spring-security/releases/tag/6.0.5" target="_blank">https://github.com/spring-projects/spring-security/releases/tag/6.0.5</a></p>
<ul>
<li>위 내용 중 첫 번째 내용인 <a href="https://github.com/spring-projects/spring-security/issues/13609" target="_blank">Improve RequestMatcher Validation</a> 참조</li>
</ul>
<h1 id="수정-방안">수정 방안</h1>
<h2 id="기존-코드">기존 코드</h2>
<pre><code class="language-java">//권한 확인을 하지 않는 uri
private static final String[] PERMIT_ALL_PATTERNS = new String[] {
  &quot;/v3/api-docs/**&quot;,
  &quot;/configuration/**&quot;,
  &quot;/swagger*/**&quot;,
  &quot;/webjars/**&quot;,
  &quot;/swagger-ui/**&quot;,
  &quot;/docs&quot;,
  &quot;/api/login&quot;,
};

@Bean
public SecurityFilterChain securityFilterChain(
  HttpSecurity httpSecurity,
  HandlerMappingIntrospector handlerMappingIntrospector
) throws Exception {
  return httpSecurity
    .authorizeHttpRequests(request -&gt;
      request
        .requestMatchers(PathRequest.toH2Console())
        .permitAll()
        .requestMatchers(PERMIT_ALL_PATTERN)
        .permitAll()
        .requestMatchers(AntPathRequestMatcher.antMatcher(&quot;/api/**&quot;))
        .authenticated()
    )
    ...
    build();
}</code></pre>
<h2 id="오류-로그">오류 로그</h2>
<pre><code class="language-text">Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method &#39;securityFilterChain&#39; threw exception with message: 
This method cannot decide whether these patterns are Spring MVC patterns or not. If this endpoint is a Spring MVC endpoint, please use requestMatchers(MvcRequestMatcher); otherwise, please use requestMatchers(AntPathRequestMatcher).
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:171)
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:655)
        ... 24 common frames omitte</code></pre>
<h2 id="수정-코드">수정 코드</h2>
<pre><code class="language-java">//권한 확인을 하지 않는 uri
private static final String[] PERMIT_ALL_PATTERNS = new String[] {
  &quot;/v3/api-docs/**&quot;,
  &quot;/configuration/**&quot;,
  &quot;/swagger*/**&quot;,
  &quot;/webjars/**&quot;,
  &quot;/swagger-ui/**&quot;,
  &quot;/docs&quot;,
  &quot;/api/login&quot;,
};

@Bean
public SecurityFilterChain securityFilterChain(
  HttpSecurity httpSecurity,
  HandlerMappingIntrospector handlerMappingIntrospector
) throws Exception {
  return httpSecurity
    .authorizeHttpRequests(request -&gt;
      request
        .requestMatchers(PathRequest.toH2Console())
        .permitAll()
        .requestMatchers( // &lt;== 여기
            Stream
              .of(PERMIT_ALL_PATTERNS)
              .map(AntPathRequestMatcher::antMatcher)
              .toArray(AntPathRequestMatcher[]::new)
          )
        .permitAll()
        .requestMatchers(AntPathRequestMatcher.antMatcher(&quot;/api/**&quot;))
        .authenticated()
    )
    ...
    build();
}</code></pre>
<h1 id="기타-사항">기타 사항</h1>
<ul>
<li>Spring Boot 3 버전 이상부터 <code>WebSecurityCustomizer</code> Bean은 사용하지 않음</li>
<li><code>WebSecurityCustomizer</code>로 구현된 <code>ignore</code> 처리는 위의 코드대로 <code>permitAll</code>로 대체해야 함</li>
<li>현재 어플리케이션에서 <code>OncePerRequestFilter</code> 등과 같은 필터를 구현해서 사용하고 있다면 해당 상속 클래스에 아래 메소드를 구현해 필터 제외 처리를 해야 함<pre><code class="language-java">@Override
protected boolean shouldNotFilter(HttpServletRequest request)
  throws ServletException {
  return Stream
    .of(SHOULD_NOT_FILTER_URI_LIST)
    .anyMatch(request.getRequestURI()::startsWith);
}</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Javascript에서 Transformers Token 길이 계산하기]]></title>
            <link>https://velog.io/@404-nut-pound/Java%EC%97%90%EC%84%9C-Transformers-Token-%EA%B8%B8%EC%9D%B4-%EA%B3%84%EC%82%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@404-nut-pound/Java%EC%97%90%EC%84%9C-Transformers-Token-%EA%B8%B8%EC%9D%B4-%EA%B3%84%EC%82%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 22 Jul 2023 15:41:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 글은 Transformers 기반의 Tokenizer를 Vue CLI에서 구현하는 방법을 소개한다.</p>
</blockquote>
<blockquote>
<p>참조 사이트
<a href="https://github.com/huggingface/transformers/blob/main/README_ko.md" target="_blank">HuggingFace Transformers(Python)</a>
<a href="https://www.npmjs.com/package/@xenova/transformers" target="_blank">@xenova/transformers(Javascript)</a>
<a href="https://djl.ai/extensions/tokenizers/" target="_blank">NLP support with Huggingface tokenizers(Java)</a>
<a href="https://mvnrepository.com/artifact/ai.djl.huggingface/tokenizers/0.23.0" target="_blank">DJL NLP Utilities For Huggingface Tokenizers Maven</a></p>
</blockquote>
<h1 id="의존성-추가">의존성 추가</h1>
<pre><code class="language-json">// package.json의 dependencies에 다음 내용 추가
&quot;@xenova/transformers&quot;: &quot;^2.3.1&quot;,</code></pre>
<h1 id="tokenizer-파일-추가">Tokenizer 파일 추가</h1>
<ul>
<li><code>@xenova/transformers</code> 라이브러리의 tokenizer.json 탐색 순서는 다음과 같다.<ul>
<li><code>location.host</code>/models/<code>인스턴스 생성 파라미터</code>/tokenizer.json</li>
<li><code>location.host</code>/models/<code>인스턴스 생성 파라미터</code>/tokenizer_config.json</li>
<li><a href="https://huggingface.co/">https://huggingface.co/</a> <code>인스턴스 생성 파라미터</code>/resolve/main/tokenizer.json</li>
<li><a href="https://huggingface.co/">https://huggingface.co/</a> <code>인스턴스 생성 파라미터</code>/resolve/main/tokenizer_config.json</li>
</ul>
</li>
<li>따라서 Vue에서 정적 파일을 Context Root로 제공하는 <code>public</code> 폴더에 <code>models</code> 폴더 생성 후 tokenizer.json를 넣어둔다<ul>
<li>빌드 이후에도 자동으로 포함됨</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/404-nut-pound/post/24974c41-498c-4621-bc0e-62018c4bfbd2/image.png" alt=""></p>
<h1 id="tokenizer-인스턴스-생성">Tokenizer 인스턴스 생성</h1>
<pre><code class="language-ts">// 상단에서 인스턴스 생성 파라미터가 탐색 경로가 된다
// public/models/ 이하에 위치한다면 빈 string을 입력
// public/models/tokenizer/ 이하에 위치한다면 &#39;tokenizer&#39;를 입력
const tokenizer = await AutoTokenizer.from_pretrained(&#39;&#39;)</code></pre>
<h1 id="token-길이-계산">Token 길이 계산</h1>
<pre><code class="language-ts">const tokens = await tokenizer(&#39;안녕하세요&#39;)

// tokens 구조
{
  attention_maks: {
    data: BigInt64Array,
    dim: number[],
    size: number,
    type: string
  },
  input_ids: {
    data: BigInt64Array,
    dim: number[],
    size: number,
    type: string
  }  
}

// 토큰 길이는 아래 방법 중 택 1
tokens.attention_mask.size
tokens.attention_mask.data.length
tokens.input_ids.size
tokens.input_ids.data.length</code></pre>
<p>위 함수 이외에도 다양한 기능이 제공된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java에서 Transformers Token 길이 계산하기]]></title>
            <link>https://velog.io/@404-nut-pound/Java%EC%97%90%EC%84%9C-Transformer-Token-%EA%B8%B8%EC%9D%B4-%EA%B3%84%EC%82%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@404-nut-pound/Java%EC%97%90%EC%84%9C-Transformer-Token-%EA%B8%B8%EC%9D%B4-%EA%B3%84%EC%82%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 22 Jul 2023 15:13:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 글은 Transformers 기반의 Tokenizer를 Java에서 구현하는 방법을 소개한다.</p>
</blockquote>
<blockquote>
<p>참조 사이트
<a href="https://github.com/huggingface/transformers/blob/main/README_ko.md" target="_blank">HuggingFace Transformers(Python)</a>
<a href="https://www.npmjs.com/package/@xenova/transformers" target="_blank">@xenova/transformers(Javascript)</a>
<a href="https://djl.ai/extensions/tokenizers/" target="_blank">NLP support with Huggingface tokenizers(Java)</a>
<a href="https://mvnrepository.com/artifact/ai.djl.huggingface/tokenizers/0.23.0" target="_blank">DJL NLP Utilities For Huggingface Tokenizers Maven</a></p>
</blockquote>
<ul>
<li>해당 글에선 Gradle 환경 기준으로 진행함</li>
</ul>
<h1 id="의존성-추가">의존성 추가</h1>
<pre><code class="language-java">// build.gradle의 Dependencies에 추가
implementation &quot;ai.djl.huggingface:tokenizers:0.23.0&quot;</code></pre>
<h1 id="tokenizer-파일-추가">Tokenizer 파일 추가</h1>
<ul>
<li><code>src/resouces/</code> 경로 이하에 사용하고자 하는 Transformer 기반 <code>tokenizer.json</code>을 넣어둔다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/404-nut-pound/post/f08c3724-9bc3-4af2-9864-c06e72d5e5e2/image.png" alt=""></p>
<h1 id="tokenizer-spring-bean-등록">Tokenizer Spring Bean 등록</h1>
<ul>
<li><code>@Configuration</code> 클래스에 작성</li>
</ul>
<pre><code class="language-java">@Bean
public HuggingFaceTokenizer huggingFaceTokenizer() throws IOException {
  ClassPathResource tokenizerResource = new ClassPathResource(
    &quot;docs/tokenizer/tokenizer.json&quot;
  );

  HuggingFaceTokenizer tokenizer = HuggingFaceTokenizer.newInstance(
    tokenizerResource.getInputStream(),
    Map.of(&quot;modelMaxLength&quot;, &quot;2048&quot;)
  );

  return tokenizer;
}</code></pre>
<h1 id="token-길이-계산">Token 길이 계산</h1>
<pre><code class="language-java">int tokenLength = tokenizer
        .encode(&quot;안녕하세요&quot;)
        .getTokens()
        .length;</code></pre>
<p>위 메소드 이외에도 다양한 기능이 제공된다.</p>
<h1 id="컨테이너-및-리눅스-환경-의존성">컨테이너 및 리눅스 환경 의존성</h1>
<ul>
<li><code>DJL NLP Utilities For Huggingface Tokenizers</code> 라이브러리는 <code>libgcc1</code>를 참조하므로 해당 라이브러리가 설치되어 있어야 한다.</li>
</ul>
<pre><code class="language-shell"># Ubuntu 기준
sudo apt install -y libgcc1</code></pre>
<pre><code class="language-Dockerfile"># Dockerfile
FROM ubuntu:20.04

RUN SILENT=1 apt update -qq &amp;&amp; SILENT=1 apt install -qqy openjdk-17-jre-headless libgcc1</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot 3 + JPA/MongoDB + Querydsl 설정]]></title>
            <link>https://velog.io/@404-nut-pound/Spring-Boot-3-JPAMongoDB-Querydsl-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@404-nut-pound/Spring-Boot-3-JPAMongoDB-Querydsl-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Sun, 14 May 2023 14:41:00 GMT</pubDate>
            <description><![CDATA[<p><em>본 글은 build.gradle의 Querydsl 설정에 대해 다룸</em></p>
<h1 id="spring-boot-3-주요-변경점">Spring Boot 3 주요 변경점</h1>
<p><em>본 글의 주제에 맞는 부분만...</em></p>
<blockquote>
</blockquote>
<ul>
<li>Java 17 공식 지원</li>
<li><code>javax</code> 패키지 지원 종료 및 <code>jakarta</code> 패키지 지원 시작</li>
<li>Gradle 7.5 이상 지원<blockquote>
</blockquote>
</li>
</ul>
<h1 id="예제-프로젝트-구성">예제 프로젝트 구성</h1>
<ul>
<li>IDE - VSCode</li>
<li>Java 17</li>
<li>Spring Boot 3.0.6<ul>
<li>Spring Web</li>
<li>DB 구성<ul>
<li>Spring Data JPA<ul>
<li>H2 Embedded</li>
</ul>
</li>
<li>Spring Data MongoDB<ul>
<li>MongoDB 5.0.16</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Gradle 7.6.1</li>
<li>Spring OpenAPI WebMVC 2.1.0</li>
<li>Querydsl 5.0.0<ul>
<li>Spring Boot의 javax 패키지 지원 종료로 jakarta 패키지를 명시해야 함</li>
<li>Querydsl MongoDB의 경우 그대로 사용</li>
</ul>
</li>
</ul>
<h1 id="gradle-설정">Gradle 설정</h1>
<h2 id="jpa">JPA</h2>
<ul>
<li>P6Spy의 경우 1.9.0 버전부터 Spring Boot 3 지원<ul>
<li>예제 프로잭트 내 <code>P6SpyFormatConfig.java</code> 설정으로 SQL 포맷팅</li>
</ul>
</li>
</ul>
<pre><code class="language-java">...
configurations {
  ...
  querydsl.extendsFrom compileClasspath
  ...
}
...
dependencies {
  ...
  implementation &quot;com.querydsl:querydsl-jpa:5.0.0:jakarta&quot;
  annotationProcessor &quot;com.querydsl:querydsl-apt:5.0.0:jakarta&quot;
  annotationProcessor &quot;jakarta.annotation:jakarta.annotation-api&quot;
  annotationProcessor &quot;jakarta.persistence:jakarta.persistence-api&quot;
  implementation &quot;com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0&quot;
  ...
}
...</code></pre>
<h2 id="mongodb">MongoDB</h2>
<ul>
<li>MongoDB 드라이버를 Spring과 Querydsl이 각각 가지고 있어 충돌을 방지하기 위해 한 쪽에서 제외해야 함</li>
<li>본 예제에서는 Querydsl에서 제외</li>
</ul>
<pre><code class="language-java">...
configurations {
  ...
  querydsl.extendsFrom compileClasspath
  ...
}
...
dependencies {
  ...
  implementation(&quot;com.querydsl:querydsl-mongodb:5.0.0&quot;) {
    exclude group: &quot;org.mongodb&quot;, module: &quot;mongo-java-driver&quot;
  }
  annotationProcessor &quot;com.querydsl:querydsl-apt:5.0.0:jakarta&quot;
  annotationProcessor &quot;jakarta.annotation:jakarta.annotation-api&quot;
  annotationProcessor &quot;jakarta.persistence:jakarta.persistence-api&quot;
  ...
}
...</code></pre>
<p>본 예제 프로젝트에서는 Gradle Querydsl 플러그인인 <code>id &quot;com.ewerk.gradle.plugins.querydsl&quot; version &quot;1.0.10&quot;</code>를 사용하지 않는다.</p>
<p>Querydsl의 Qclass 빌드는 <code>compileQuerydsl</code> 태스크 대신 Gradle dependencies에 정의된 2 개의 annotationProcessor로 진행한다.</p>
<p>이 과정은 프로젝트 빌드(Gradle build 태스크 아님)가 진행될 때 같이 진행되며 Qclass는 <code>/bin/generated-sources/annotations/{프로젝트 패키지}/{Entity 클래스 패키지}</code> 위치에 생성된다.</p>
<p><img src="https://velog.velcdn.com/images/404-nut-pound/post/2244606a-2d73-4bc2-944c-cf908ecf57dc/image.png" alt=""></p>
<p>만약 예전의 방식대로 Querydsl 플러그인을 사용하고자 하는 경우에는 아래 2 개의 패키지를 제거해야한다.</p>
<pre><code class="language-java">annotationProcessor &quot;jakarta.annotation:jakarta.annotation-api&quot;
annotationProcessor &quot;jakarta.persistence:jakarta.persistence-api&quot;</code></pre>
<p>제거하지 않으면 Qclass 중복 생성 오류가 발생하여 빌드가 정상적으로 진행되지 않는다.</p>
<p>모든 소스 코드는 다음 링크에서 확인 가능하다.</p>
<p><a href="https://github.com/404-nut-pound/spring-jpa-querydsl-template" target="_blank">JPA 예제 프로젝트</a></p>
<p><a href="https://github.com/404-nut-pound/spring-mongodb-querydsl-template" target="_blank">MongoDB 예제 프로젝트</a></p>
<ul>
<li>MongoDB 프로젝트에는 트랜잭션 처리를 위한 Config와 Docker Compose 파일 및 설정 가이드가 포함되어 있음<ul>
<li><a href="https://github.com/404-nut-pound/spring-mongodb-querydsl-template/tree/main/docs/mongodb" target="_blank">MongoDB 문서</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Svelte 기초]]></title>
            <link>https://velog.io/@404-nut-pound/Svelte-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@404-nut-pound/Svelte-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Wed, 05 Apr 2023 17:03:14 GMT</pubDate>
            <description><![CDATA[<h2 id="svelte">Svelte</h2>
<ul>
<li>2016년 출시</li>
<li>다른 프레임워크에 비해 적은 코드 작성 추구<ul>
<li>가독성 상승, 쉬운 리팩토링 같은 장점</li>
</ul>
</li>
<li>가상 DOM을 사용하지 않음<ul>
<li>빠른 성능을 기대할 수 있음</li>
<li>코드가 HTML과 유사함</li>
</ul>
</li>
<li>CDN 미제공</li>
<li>IE 미지원</li>
</ul>
<h2 id="sveltekit">SvelteKit</h2>
<ul>
<li>Svelte Framework</li>
<li>ReactJs의 Next.js, Vue.js의 Nuxt.js와 같은 관계</li>
<li>SvelteKit 1.0이 출시됨에 따라 Svelte 프로젝트 생성 시 자동으로 SvelteKit을 사용하도록 설정 됨</li>
<li>초기에 번들러로 Rollup이나 WebPack을 쓰는 대신 현재는 Vite가 기본 번들러로 사용됨</li>
<li>앱 구성을 위해 파일 명, 변수 명 등에 예약어가 있음(다른 프레임워크도 동일하잖아?)</li>
</ul>
<h2 id="설치-필요">설치 필요</h2>
<ul>
<li>NodeJs(이왕이면 LTS 버전)</li>
</ul>
<h2 id="프로젝트-초기화">프로젝트 초기화</h2>
<pre><code>npm create svelte@latest &#39;디렉토리 명&#39;</code></pre><ul>
<li>프로젝트 초기 모습
<img src="https://velog.velcdn.com/images/404-nut-pound/post/b118505b-8130-45fd-af8b-51d46bb96245/image.png" alt=""></li>
<li><code>+page.svelte</code>가 index.html 역할</li>
</ul>
<h2 id="변수-선언과-바인딩">변수 선언과 바인딩</h2>
<pre><code class="language-svelte">&lt;script lang=&quot;ts&quot;&gt;
  let name = &#39;World&#39;
&lt;/script&gt;

&lt;h1&gt;Hello {name}!&lt;/h1&gt;
&lt;!-- 동적 바인딩을 하려면 bind: 사용 --&gt;
&lt;input type=&quot;text&quot; bind:value={name} /&gt;
&lt;button on:click={() =&gt; (name = &#39;World&#39;)}&gt;초기화&lt;/button&gt;

&lt;style&gt;
  h1 {
    color: cadetblue;
  }
&lt;/style&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/404-nut-pound/post/a9b800f7-1b6c-4b7b-9340-449309a9eb72/image.webp" alt=""></p>
<h2 id="조건문과-반복문">조건문과 반복문</h2>
<pre><code class="language-svelte">{#if 조건식 혹은 boolean}
코드
{:else if 조건식 혹은 boolean}
코드
{:else}
코드
{/if}

{#each 배열 as 배열 변수명}
코드
{/each}</code></pre>
<h2 id="컴포넌트">컴포넌트</h2>
<pre><code class="language-svelte">&lt;!-- src/routes/+page.svelte --&gt;
&lt;script lang=&quot;ts&quot;&gt;
  import Movie from &#39;./movie.svelte&#39;

  let name = &#39;World&#39;
  let movieList = [&#39;A&#39;, &#39;BB&#39;, &#39;CCC&#39;, &#39;DDDD&#39;]
&lt;/script&gt;

&lt;h1&gt;Hello {name}!&lt;/h1&gt;

&lt;input type=&quot;text&quot; bind:value={name} /&gt;
&lt;button on:click={() =&gt; (name = &#39;World&#39;)}&gt;초기화&lt;/button&gt;

&lt;Movie {movieList} /&gt;

&lt;style&gt;
  h1 {
    color: cadetblue;
  }
&lt;/style&gt;

&lt;!-- movie.svelte --&gt;
&lt;script lang=&quot;ts&quot;&gt;
  export let movieList: string[] = []
&lt;/script&gt;

&lt;ul&gt;
  {#each movieList as movie}
    &lt;li&gt;{movie}&lt;/li&gt;
  {/each}
&lt;/ul&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/404-nut-pound/post/63f62036-4b67-46e0-a4e9-0becc8f36a1a/image.png" alt=""></p>
<h2 id="라우팅">라우팅</h2>
<ul>
<li><p><code>routes</code>(웹 루트 개념) 디렉토리에 <code>+layout.svelte</code> 파일 생성</p>
</li>
<li><p>slot에 대상 페이지(컴포넌트)가 로드 됨</p>
<pre><code class="language-svelte">&lt;a href=&quot;/actor&quot;&gt;영화 배우&lt;/a&gt;

&lt;slot /&gt;</code></pre>
</li>
</ul>
<h2 id="페이지-요청-스크립트">페이지 요청 스크립트</h2>
<ul>
<li><p>라우팅을 통해 특정 컴포넌트로 이동 시 실행되는 함수
<img src="https://velog.velcdn.com/images/404-nut-pound/post/7d27ea32-1577-4f2e-9028-13c1cc7943f6/image.png" alt=""></p>
<pre><code class="language-svelte">&lt;!-- src/routes/actor/+page.svelte --&gt;
&lt;script lang=&quot;ts&quot;&gt;
  export let data: any
&lt;/script&gt;

&lt;ul&gt;
  {#each data.actorList as actor}
    &lt;li&gt;{actor}&lt;/li&gt;
  {/each}
&lt;/ul&gt;

&lt;a href=&quot;/&quot;&gt;홈&lt;/a&gt;

&lt;!-- src/routes/actor/+page.server.js --&gt;
&lt;!-- .server를 추가하면 SSR로 동작함 --&gt;
export async function load() {
  console.log(&#39;asdf&#39;)
  return { actorList: [&#39;톰 크루즈&#39;, &#39;브래드 피드&#39;] }
}</code></pre>
</li>
<li><p><img src="https://velog.velcdn.com/images/404-nut-pound/post/15f64b75-d1f6-4997-bbc1-4e6962737801/image.png" alt=""></p>
</li>
</ul>
<h2 id="api">API</h2>
<ul>
<li>라우팅을 그대로 따라감</li>
<li>GET, POST, PATCH, DELETE 등을 지원</li>
<li><img src="https://velog.velcdn.com/images/404-nut-pound/post/668296bd-2d30-4dec-a603-83a9ae1e4f58/image.png" alt=""></li>
<li><img src="https://velog.velcdn.com/images/404-nut-pound/post/30807cdf-61dd-47f4-8bfe-4241f7c5bc79/image.png" alt=""></li>
</ul>
<h2 id="변수-저장">변수 저장</h2>
<ul>
<li>앱 전체에서 공유하려면 <code>src/routes</code> 이하의 <code>+layout.svelte</code>에 할당하는 것이 적절</li>
</ul>
<pre><code class="language-svelte">&lt;script lang=&quot;ts&quot;&gt;
  import { setContext } from &#39;svelte&#39;
  import { writable } from &#39;svelte/store&#39;

  const user = writable&lt;string&gt;()
  $: user.set(&#39;USER NAME!&#39;)

  setContext(&#39;user&#39;, user)
&lt;/script&gt;

&lt;a href=&quot;/actor&quot;&gt;영화 배우&lt;/a&gt;

&lt;slot /&gt;

&lt;!-- src/routes/+page.svelte --&gt;
&lt;script lang=&quot;ts&quot;&gt;
  import { getContext } from &#39;svelte&#39;
  import Movie from &#39;./movie.svelte&#39;

  const defaultUser = getContext(&#39;user&#39;)
  let userName = $defaultUser
  let movieList = [&#39;몬티 파이튼의 성배&#39;, &#39;아이언 맨&#39;, &#39;올드보이&#39;]
&lt;/script&gt;

&lt;h1&gt;Hello {userName}!&lt;/h1&gt;

&lt;input type=&quot;text&quot; bind:value={userName} /&gt;
&lt;button on:click={() =&gt; (userName = $defaultUser)}&gt;초기화&lt;/button&gt;

&lt;Movie {movieList} /&gt;

&lt;style&gt;
  h1 {
    color: cadetblue;
  }
&lt;/style&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin - 1]]></title>
            <link>https://velog.io/@404-nut-pound/Kotlin-1</link>
            <guid>https://velog.io/@404-nut-pound/Kotlin-1</guid>
            <pubDate>Tue, 28 Mar 2023 13:14:16 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/404-nut-pound/post/bc2e46f6-6aaa-4be4-8e4b-856200467abf/image.png" alt=""></p>
<h1 id="kotlin">Kotlin</h1>
<blockquote>
<p>Kotlin은 JetBrain에서 개발한 JVM 100% 호환가능한 언어이다.</p>
<p>Kotlin은 간결하고 모던한 코드를 지향한다.</p>
<p>Kotlin은 2011년에 출시 됐으며 2017년부터 Android의 공식 언어로 채택됐다.</p>
<p>Kotlin은 JVM과 100% 호환되기 때문에 기존 Java 개발 환경에서 그대로 사용 가능하며 파일 확장자는 <code>kt</code>이다.</p>
</blockquote>
<h1 id="클래스-정의">클래스 정의</h1>
<ul>
<li><code>public class ~~</code>와 같은 키워드 생략 가능</li>
<li><code>fun main()</code> 키워드로 클래스의 main 메소드 구현</li>
<li><code>main()</code> 메소드에 <code>static</code> 키워드 사용하지 않음</li>
<li>클래스를 명시적으로 정의하지 않아도 함수 및 필드 정의 가능</li>
</ul>
<h1 id="변수-정의">변수 정의</h1>
<ul>
<li>기본 스코프는 public</li>
<li>var - variable / val - value</li>
<li>권장 사항은 val로 생성 후 필요 시 var로 변경</li>
<li>변수 타입은 컴파일러가 자동 추론</li>
<li>선언과 동시에 값 입력 시 타입은 생략해도 무관</li>
</ul>
<pre><code class="language-kotlin">var temp1 = 5L // == long temp1 = 5L;
val temp2 = 10L // == final long temp2 = 10L;</code></pre>
<h1 id="함수-정의">함수 정의</h1>
<ul>
<li><code>fun</code> 이름(파라미터 이름: 타입 = 기본값): 반환 타입 {} 형태로 정의</li>
</ul>
<h1 id="primitive--reference-type">Primitive / Reference Type</h1>
<pre><code class="language-kotlin">//Java
long num1 = 5L;
final long num2 = 10L;

//Kotlin
//연산을 사용할 때는 Reference Type 사용 권장
var num1: Long = 5L
val num2: Long = 10L

println(num1 + num2)</code></pre>
<h1 id="null-처리">Null 처리</h1>
<pre><code class="language-kotlin">//nullable 변수는 타입 뒤에 ?를 추가
var num1: Long? = 5L
var num2: Long = 10L

num1 = null
num2 = null //에러

fun someFunction(param: String?): String {
    //함수 매개변수에서 null 가능 여부 확인
    return param.toUpperCase() //에러
}

//null-safe 문법
var str:String? = null
println(str?.length) //null 출력
println(str?.length ?: 0) //0 출력
println(str!!.length) //컴파일러에게 null이 아니라고 명시
---
//Java
@Nullable
public String getName() {
    return name
}

//Kotlin
println(xxx.getName().length) //에러</code></pre>
<h1 id="객체-초기화">객체 초기화</h1>
<pre><code class="language-kotlin">//객체 초기화 시 new 키워드 사용하지 않음
val person = Person(&quot;이름&quot;)</code></pre>
<h1 id="type-관리">Type 관리</h1>
<pre><code class="language-kotlin">val num1 = 5   //int
val num2 = 10L //long

println(num1 + num2) //에러
println(num1.toLong() + num2)

val num3 = 3.0f //float
val num4 = 6.0  //double

println(num3 + num4) //에러
println(num3.toDouble() + num4)
---
//Java
void compare(Object obj) {
    if(obj instanceof Person) {
        Person person = (Person) obj;
    }
}

//Kotlin
fun compare(obj: any) {
    if(obj is Person) { //부정의 뜻은 !is
        val person = obj as Person //타입 캐스팅, nullable은 as?
    }
}</code></pre>
<h2 id="특이-타입">특이 타입</h2>
<h3 id="any">Any</h3>
<ul>
<li>Java의 Object와 같은 타입</li>
<li>null 값 할당하려면 <code>any?</code>와 같이 입력</li>
</ul>
<h3 id="unit">Unit</h3>
<ul>
<li>Java의 void와 같은 타입</li>
<li>return 타입으로 작성하는 Java와 달리 타입 인자로도 사용 가능</li>
<li>함수형 프로그래밍에서는 단 하나의 인스턴스만 갖는 타입으로 표현</li>
</ul>
<h3 id="nothing">Nothing</h3>
<ul>
<li>코드가 정상적으로 끝나지 않는다는 표현</li>
<li>오류만을 반환하는 타입, 함수에 사용</li>
</ul>
<h1 id="string-interpoationindexing">String Interpoation/Indexing</h1>
<pre><code class="language-kotlin">//Java
System.out.println(&quot;%s - %s&quot;.formatted(param1, param2);

String name = &quot;abcd&quot;;
System.out.println(name.charAt(1));

//Kotlin
println(&quot;${param1} - ${param2}&quot;) //중괄호 생략 가능

val name = &quot;abcd&quot;
println(name[1])</code></pre>
<h1 id="연산자">연산자</h1>
<ul>
<li>단항 연산자, 산술 연산자 동일</li>
<li>비교 연산자(&#39;&lt;&#39;, &#39;&gt;&#39;, &#39;&lt;=&#39;, &#39;&gt;=&#39;)는 객체에 사용 시 내부적으로는 <code>compareTo</code> 함수를 사용함</li>
<li>인스턴스의 메모리 주소 비교 시 <code>===</code>, 값 비교 시 <code>==</code> 사용<ul>
<li>Java <code>equals</code> &lt;=&gt; <code>==</code> Kotlin</li>
<li>Java <code>==</code> &lt;=&gt; <code>===</code> Kotlin</li>
<li>Kotlin에서 <code>==</code> 사용 시 내부적으로는 <code>equals</code> 함수를 사용함</li>
</ul>
</li>
</ul>
<h2 id="kotlin-전용-연산자">Kotlin 전용 연산자</h2>
<ul>
<li>in / !in<ul>
<li>콜렉션에 포함/비포함 여부 확인</li>
</ul>
</li>
<li>a..b<ul>
<li>a부터 b까지의 범위 객체를 생성</li>
</ul>
</li>
<li>a[i]<ul>
<li>콜렉션의 요소를 가져옮</li>
</ul>
</li>
<li>a[i] = b<ul>
<li>콜렉션의 요소를 수정함</li>
</ul>
</li>
</ul>
<h2 id="연산자-오버로딩">연산자 오버로딩</h2>
<ul>
<li>클래스 내 특정한 키워드를 이용해 연산자를 오버로딩 가능</li>
</ul>
<pre><code class="language-kotlin">operator fun plus(...): Long {
    return ... + ...
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[리눅스 시스템 자원 조회]]></title>
            <link>https://velog.io/@404-nut-pound/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%9E%90%EC%9B%90-%EC%A1%B0%ED%9A%8C</link>
            <guid>https://velog.io/@404-nut-pound/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%9E%90%EC%9B%90-%EC%A1%B0%ED%9A%8C</guid>
            <pubDate>Wed, 14 Dec 2022 08:14:52 GMT</pubDate>
            <description><![CDATA[<h2 id="cpu">CPU</h2>
<pre><code class="language-shell">#용도에 따라 &#39;%&#39;는 제외해도 무방
top -b -n1 | grep -Po &#39;[0-9.]+ id&#39; | awk &#39;{printf &quot;%.2f%\n&quot;, 100-$1}&#39;
#출력 결과
0.60%</code></pre>
<h2 id="memory">Memory</h2>
<pre><code class="language-shell">#용도에 따라 &#39;%&#39;는 제외해도 무방
#메모리 사용률
free | grep Mem | awk &#39;{printf &quot;%.2f%\n&quot;, $3/$2*100}&#39;
#출력 결과
32.83%

#메모리 총 용량
free | grep Mem | awk &#39;{printf &quot;%.2fGB\n&quot;, $2/1024/1024}&#39;
#출력 결과
23.53GB

#메모리 사용량
free | grep Mem | awk &#39;{printf &quot;%.2fGB\n&quot;, $3/1024/1024}&#39;
#출력 결과
7.73GB</code></pre>
<h2 id="disk">Disk</h2>
<pre><code class="language-shell">#용도에 따라 1024 나누기와 &#39;%&#39;, &#39;GB&#39;는 제외해도 무방
#Disk 사용률
df . | sed -n &#39;1!p&#39; | awk &#39;{total += $2; used += $3;} END {printf &quot;%.2f%\n&quot;, used/total*100}&#39;
#출력 결과
76.33%

#Disk 총 용량
df . | sed -n &#39;1!p&#39; | awk &#39;{sum += $2} END {printf &quot;%.2fGB\n&quot;, sum/1024/1024}&#39;
#출력 결과
97.38GB

#Disk 사용량
df . | sed -n &#39;1!p&#39; | awk &#39;{sum += $3} END {printf &quot;%.2fGB\n&quot;, sum/1024/1024}&#39;
#출력 결과
74.33GB</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[VSCode 활용]]></title>
            <link>https://velog.io/@404-nut-pound/VSCode-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@404-nut-pound/VSCode-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Mon, 03 Oct 2022 14:54:27 GMT</pubDate>
            <description><![CDATA[<h2 id="편집기-탭을-여러-줄로-표시">편집기 탭을 여러 줄로 표시</h2>
<p><img src="https://velog.velcdn.com/images/404-nut-pound/post/97c10e29-fc37-40e7-bbf7-4b33995bb5aa/image.png" alt=""></p>
<p>위와 같이 여러 개의 파일을 열었지만 탭이 한 줄로 표기돼서 확인이 어려울 경우</p>
<p><img src="https://velog.velcdn.com/images/404-nut-pound/post/05a81dba-70ca-4bd1-9fa4-80eb48daa628/image.png" alt=""></p>
<p>설정에서 위 옵션을 활성화</p>
<p>혹은 setting.json에 다음 내용 입력</p>
<p><code>&quot;workbench.editor.wrapTabs&quot;: true,</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바(타입)스크립트의 추가 기능]]></title>
            <link>https://velog.io/@404-nut-pound/%EC%9E%90%EB%B0%94%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EC%B5%9C%EC%8B%A0-%EB%AC%B8%EB%B2%95</link>
            <guid>https://velog.io/@404-nut-pound/%EC%9E%90%EB%B0%94%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EC%B5%9C%EC%8B%A0-%EB%AC%B8%EB%B2%95</guid>
            <pubDate>Sun, 18 Sep 2022 16:41:30 GMT</pubDate>
            <description><![CDATA[<h2 id="모듈화">모듈화</h2>
<blockquote>
</blockquote>
<p>코드 분산 처리 및 번들링을 용이하게 구현하기 위한 문법이다.</p>
<blockquote>
</blockquote>
<p>프론트엔드를 개발할 때 라이브러리를 설치하게 되면 일반적으로 모든 화면에서 해당 라이브러리를 사용하진 않는다.</p>
<blockquote>
</blockquote>
<p>이 때 번들링를 진행하면서 필요하지 않은 라이브러리, 코드들은 가지치기를 하는데 영어로는 Tree-Shaking이라고 한다더라.</p>
<blockquote>
</blockquote>
<p>이 과정을 통해 코드 자체의 가독성도 확보할 수 있고 페이지 로드 속도 증대도 꾀할 수 있다.</p>
<pre><code class="language-js">//es5
module.exports = {
  //변수, 함수 등
}
const 변수명 = require(&#39;라이브러리&#39;);

//es6
//라이브러리 로딩
export default {변수, 함수 등}; //하나의 파일에서 한 번만 가능
import 변수명 from &#39;라이브러리&#39;;

//변수, 함수 로딩
export {변수, 함수 등}; //export는 각각의 항목에 직접 선언 가능
import {변수명} from &#39;파일 상대 경로&#39;;</code></pre>
<h2 id="null-check">Null-Check</h2>
<blockquote>
</blockquote>
<p><code>Null</code>은 개발일을 하다보면 생각보다 매우매우매우 자주 보게되는 단어다.</p>
<blockquote>
</blockquote>
<p>평소에도 코드를 작성할 때 Null 발생 확률을 줄이는 방향을 기조로 작성하고 있다.</p>
<blockquote>
</blockquote>
<p>자바스크립트에서도 마찬가지인데 ESLint라던지 타입스크립트가 많이 도와줘서 어느정도 Null을 배제한 코드를 작성하는 것이 가능해졌다.</p>
<blockquote>
</blockquote>
<p>기본적으로 처리하는 방법은 다음과 같다.</p>
<blockquote>
</blockquote>
<pre><code class="language-js">const div = document.querySelector(&#39;div&#39;);
&gt;
if(div) {
  div.innerText = &#39;abc&#39;
}</code></pre>
<blockquote>
</blockquote>
<p>하지만 뭔가 Cool하지 않다. 모든 null-ish한 변수에 대해 저 작업을 해줘야할까?</p>
<blockquote>
</blockquote>
<p>다른 방법을 보자.</p>
<h2 id="type-assertion">Type Assertion</h2>
<blockquote>
</blockquote>
<p><code>Type Assertion</code>은 <code>타입 단언</code>으로 번역할 수 있는데 이 속성은 어떤 변수가 <code>null-ish한 값이 아니다(반드시 값이 있다)</code>라고 선언하는 것이다.</p>
<blockquote>
</blockquote>
<p>이는 코드 작성 중에는 어느 정도 컴파일 오류를 줄여주거나 하는 효과는 있지만 런타임에서 발생하는 null 오류를 처리해주거나 하진 않는다.</p>
<blockquote>
</blockquote>
<p>따라서 Development 환경에서 사용하고 Production 환경까지는 유지하지 않는 것이 좋다.</p>
<pre><code class="language-js">const div = document.querySelector(&#39;div&#39;);

div!.innerText = &#39;abc&#39; //div가 null 가능함, 타입 단언으로 오류 제거
//위 코드에서 발생하는 이유는 querySelector의 return type이 HtmlElement | null이기 때문
//or
const div = document.querySelector(&#39;div&#39;) as HtmlDivElement;

div.innerText = &#39;abc&#39; //오류 발생하지 않음

class User {
  id: string = &#39;&#39;
  name!: string //기본값 선언 없어도 오류 발생하지 않음
  age: number //생성자에서 취급하지 않으면 오류 발생

  constructor() {
    this.age = 20
  }
}</code></pre>
<h2 id="optional-chaining">Optional Chaining</h2>
<blockquote>
</blockquote>
<p><code>Optional Chaining</code>은 따로 한국어로 번역된 건 없는 것 같다. <code>선택적 체이닝 혹은 선택적 연결</code>이라고 하면 되겠다.</p>
<blockquote>
</blockquote>
<p>해당 옵션은 <code>어떤 변수가 null-ish하다면 이후 코드 실행 중지</code>라는 처리를 해준다.</p>
<blockquote>
</blockquote>
<p>컴파일 오류도 없애주고 런타임 오류도 효과적으로 줄일 수 있는 방법이다.</p>
<pre><code class="language-js">const div = document.querySelector(&#39;div&#39;);

div?.innerText = &#39;abc&#39; //div가 null 가능함, 선택적 체이닝으로 오류 제거
//null이라면 innerText 변경 작업 하지 않음</code></pre>
<h2 id="generic-optionaldefaults">Generic Optional/Defaults</h2>
<blockquote>
</blockquote>
<p>제네릭을 사용하면서 어떤 변수를 Optional 처리하거나 기본 값을 지정해줄 수도 있다.</p>
<blockquote>
</blockquote>
<p>이는 함수 혹은 클래스를 선언할 때 사용 가능하다.</p>
<pre><code class="language-js">//제네릭에 입력 값이 없다면 기본 제네릭 값 지정
function getHtmlElement&lt;T extends HtmlElement = HtmlDivElement&gt;(query: string = &#39;div&#39;) {
  return document.querySelect(query) as T
}</code></pre>
<h2 id="nullish-coalescing">Nullish Coalescing</h2>
<blockquote>
</blockquote>
<p>말 그대로 <code>null-ish한 값의 기본 값을 지정</code>해주는 문법이다. </p>
<pre><code class="language-js">//es6
const a = document.querySelecotor(&#39;div&#39;) ?? document.createElement(&#39;div&#39;)
//화면에 div 항목이 없다면 새로 만들어서 리턴</code></pre>
<blockquote>
</blockquote>
<p>비슷한 문법으로 <code>||</code>가 있는데 이 두 개의 차이점은 다음과 같다</p>
<blockquote>
</blockquote>
<p>?? -&gt; null, undefined와 같은 null-ish한 값을 검사</p>
<blockquote>
</blockquote>
<p>|| -&gt; 0, &#39;&#39;(빈 문자열), false와 같은 falsy한 값을 검사</p>
<pre><code class="language-js">console.log(null ?? &#39;asdf&#39;); //asdf
console.log(null || &#39;asdf&#39;); //asdf

console.log(false ?? &#39;asdf&#39;); //false
console.log(false || &#39;asdf&#39;); //asdf

console.log(0 ?? &#39;asdf&#39;); //0
console.log(0 || &#39;asdf&#39;); //asdf

console.log(&#39;&#39; ?? &#39;asdf&#39;); //&#39;&#39;
console.log(&#39;&#39; || &#39;asdf&#39;); //asdf</code></pre>
<h2 id="객체-병합">객체 병합</h2>
<blockquote>
</blockquote>
<p>여러 개의 객체를 하나의 객체로 병합하는 방법이다.</p>
<blockquote>
</blockquote>
<p>크게 2 가지 방법이 있다.</p>
<pre><code class="language-js">class A {
  id = &#39;a&#39;;
}
class B {
  name = &#39;name&#39;;
}

let a = new A();
let b = new B();

let c = Object.assign({}, a, b);
let d = {...a, ...b};</code></pre>
<h2 id="객체-구조화destructuring">객체 구조화(Destructuring)</h2>
<blockquote>
</blockquote>
<p>일반적으로 객체에서 값을 조회할 때 <code>&#39;객체 이름&#39;.&#39;속성 이름&#39;</code> 같은 방법을 사용하는데 객체 구조화를 사용하면 다음과 같이 객체의 값을 받아올 수 있다.</p>
<pre><code class="language-js">//단일 객체 구조화
class A {
  id: &#39;a&#39;;
}

let { id } = new A(); //new A().id와 같음
//class 말고 object에 대해서도 적용 가능

console.log(id);

//배열 구조화
let arr = [1, 10, 100, 1000];

let [ one, ten, hundred, thousand ] = arr; //배열은 [] 사용

//중첩 객체 구조화
const person = {
  name: &#39;Kim&#39;,
  address: {
    zipCode: &#39;00000&#39;,
    city: &#39;Seoul&#39;
  }
};

const { address: { city } } = person;

console.log(city); // &#39;Seoul&#39;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring RestTemplate을 사용하여 대용량 업로드 처리]]></title>
            <link>https://velog.io/@404-nut-pound/Spring-RestTemplate%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EB%8C%80%EC%9A%A9%EB%9F%89-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@404-nut-pound/Spring-RestTemplate%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EB%8C%80%EC%9A%A9%EB%9F%89-%EC%97%85%EB%A1%9C%EB%93%9C-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Tue, 06 Sep 2022 07:30:46 GMT</pubDate>
            <description><![CDATA[<h2 id="요건">요건</h2>
<blockquote>
</blockquote>
<ul>
<li>화면에서 입력받은 String key 값과 첨부 파일을 원격지 서버에 업로드</li>
<li>첨부 파일의 크기는 약 2 GB 이상</li>
<li><code>Content-Type</code>은 다음과 같음<ul>
<li>화면 -&gt; 백엔드 <code>multipart/form-data</code></li>
<li>백엔드 -&gt; 원격지 서버 <code>multipart/form-data</code></li>
</ul>
</li>
<li>JVM 메모리 옵션은 <code>-Xmx6g</code>로 설정되어 있음</li>
</ul>
<h2 id="사용-코드">사용 코드</h2>
<pre><code class="language-java">MultiValueMap&lt;String, Object&gt; uploadParam = new LinkedMultiValueMap&lt;&gt;();
uploadParam.add(&quot;key&quot;, key);
uploadParam.add(&quot;file&quot;, file.getResource());

//헤더 설정
HttpHeaders uploadheaders = new HttpHeaders();
uploadheaders.setContentType(MediaType.MULTIPART_FORM_DATA);

HttpEntity&lt;?&gt; uploadEntity = new HttpEntity&lt;&gt;(uploadParam, uploadheaders);

RestTemplate restTemplate = new RestTemplate();

ResponseEntity&lt;String&gt; uploadResponseEntity = restTemplate.exchange(
  &quot;http://%s:%d/data/upload&quot;.formatted(
      serverEntity.getServiceServerIp(),
      serverEntity.getServiceServerPort()
    ),
  HttpMethod.POST,
  uploadEntity,
  String.class
);</code></pre>
<h2 id="오류-로그">오류 로그</h2>
<pre><code class="language-sh">c.s.v.config.ExceptionAdviceConfig - Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Required array length 2147483639 + 386 is too large
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Required array length 2147483639 + 386 is too large</code></pre>
<h2 id="원인-분석">원인 분석</h2>
<blockquote>
</blockquote>
<ul>
<li>현재 소스에서는 RestTemplate를 <code>new RestTemplate();</code> 생성자를 이용하여 생성하여 사용 중
<a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html" target="_blank">RestTemplate Javadoc</a></li>
<li>RestTemplate의 기본 생성자는 <code>ClientHttpRequestFactory</code>의 기본값을 사용함
<a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/client/ClientHttpRequestFactory.html" target="_blank">ClientHttpRequestFactory Javadoc</a></li>
</ul>
<h2 id="해결-방안">해결 방안</h2>
<blockquote>
</blockquote>
<ul>
<li><code>ClientHttpRequestFactory</code>의 메소드 중 <code>setBufferRequestBody</code>의 주석 내용<blockquote>
<blockquote>
</blockquote>
<p>Indicate whether this request factory should buffer the request body internally.</p>
<blockquote>
</blockquote>
<p>Default is true. When sending large amounts of data via POST or PUT, it is recommended to change this property to false, so as not to run out of memory.</p>
</blockquote>
</li>
<li>현재 발생하고 있는 오류인 OOM 오류와 연관되어 있는 것으로 판단하여 해당 값을 설정하여 RestTemplate 생성</li>
</ul>
<h2 id="수정-코드">수정 코드</h2>
<pre><code class="language-java">SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
simpleClientHttpRequestFactory.setBufferRequestBody(false);

RestTemplate restTemplate = new RestTemplate(simpleClientHttpRequestFactory);</code></pre>
<h2 id="기타-사항">기타 사항</h2>
<blockquote>
</blockquote>
<ul>
<li>기본 값이 무한대로 설정되어 있는 타임아웃 같은 파라미터도 <code>ClientHttpRequestFactory</code>의 구현체를 통해 설정 가능</li>
<li>자주 사용하게 된다면 Spring Bean으로 등록하여 <code>@Autowired</code>로 주입 가능</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring RestTemplate로 multipart/form-data 전송하기]]></title>
            <link>https://velog.io/@404-nut-pound/Spring-RestTemplate%EB%A1%9C-multipartform-data-%EC%A0%84%EC%86%A1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@404-nut-pound/Spring-RestTemplate%EB%A1%9C-multipartform-data-%EC%A0%84%EC%86%A1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 05 Sep 2022 07:44:27 GMT</pubDate>
            <description><![CDATA[<h2 id="요건">요건</h2>
<blockquote>
</blockquote>
<ul>
<li>화면에서 전달 받은 압축파일을 해제</li>
<li>해제한 파일 중 하나를 다른 서버의 API로 전송<ul>
<li>상대 시스템은 Python 3.8, FastAPI로 구성됨</li>
<li>Parameter는 Binary 파일, String으로 구성</li>
<li>Content-Type은 multipart/form-data</li>
</ul>
</li>
</ul>
<h2 id="테스트-코드">테스트 코드</h2>
<pre><code class="language-java">MultiValueMap&lt;String, Object&gt; params = new LinkedMultiValueMap&lt;&gt;();
params.add(&quot;파라미터1&quot;, new ByteArrayResource(파일 내용 byte[]));
params.add(&quot;파라미터2&quot;, &quot;문자열 값&quot;);

HttpHeaders uploadheaders = new HttpHeaders();
uploadheaders.setContentType(MediaType.MULTIPART_FORM_DATA);

HttpEntity&lt;?&gt; uploadEntity = new HttpEntity&lt;&gt;(params, uploadheaders);

RestTemplate restTemplate = new RestTemplate();

ResponseEntity&lt;SttCommonResultResponseDto&gt; responseEntity = restTemplate.exchange(
  requestUrl,
  HttpMethod.POST,
  uploadEntity,
  SttCommonResultResponseDto.class
);</code></pre>
<h2 id="1차-실행-결과">1차 실행 결과</h2>
<ul>
<li>디버그 로그(해당 코드에서는 파라미터2는 전송하지 않음, 비필수 값)</li>
</ul>
<pre><code class="language-sh">15:51:45.985 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[application/json, application/*+json]
15:51:45.998 [main] DEBUG org.springframework.web.client.RestTemplate - Writing [{파라미터1=[Byte array resource [resource loaded from byte array]]}] as &quot;multipart/form-data&quot;
15:52:50.949 [main] DEBUG org.springframework.web.client.RestTemplate - Response 422 UNPROCESSABLE_ENTITY</code></pre>
<ul>
<li>에러 로그</li>
</ul>
<pre><code class="language-json">{
    &quot;detail&quot;: [{
            &quot;loc&quot;: [&quot;body&quot;, &quot;파라미터1&quot;],
            &quot;msg&quot;: &quot;Expected UploadFile, received: [class &#39;str&#39;]&quot;,
            &quot;type&quot;: &quot;value_error&quot;
        }
    ]
}
</code></pre>
<h2 id="원인-분석불확실함">원인 분석(불확실함)</h2>
<blockquote>
</blockquote>
<ul>
<li>multipart 업로드는 로컬 파일을 업로드 하는데 사용하는 방식</li>
<li>new ByteArrayResource()를 RestTemplate의 파라미터로 설정 시 파일에 대한 정보가 없어 메모리 상의 데이터를 전송함</li>
<li>파일 메타데이터 없이 byte 배열만 전송하여 FastAPI에서 String으로 인식하게 되는 것이 원인인 것으로 판단됨</li>
</ul>
<h2 id="변경-코드">변경 코드</h2>
<pre><code class="language-java">Path path = Paths.get(&quot;Uuid 임시 파일 위치&quot;);

MultiValueMap&lt;String, Object&gt; params = new LinkedMultiValueMap&lt;&gt;();
params.add(&quot;파라미터1&quot;, new PathResource(path));
params.add(&quot;파라미터2&quot;, &quot;문자열 값&quot;);

HttpHeaders uploadheaders = new HttpHeaders();
uploadheaders.setContentType(MediaType.MULTIPART_FORM_DATA);

HttpEntity&lt;?&gt; uploadEntity = new HttpEntity&lt;&gt;(params, uploadheaders);

RestTemplate restTemplate = new RestTemplate();

ResponseEntity&lt;SttCommonResultResponseDto&gt; responseEntity = restTemplate.exchange(
  requestUrl,
  HttpMethod.POST,
  uploadEntity,
  SttCommonResultResponseDto.class
);</code></pre>
<h2 id="2차-실행-결과">2차 실행 결과</h2>
<ul>
<li>디버그 로그(해당 코드에서는 파라미터2는 전송하지 않음, 비필수 값)</li>
</ul>
<pre><code class="language-sh">16:25:10.079 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[application/json, application/*+json]
16:25:10.087 [main] DEBUG org.springframework.web.client.RestTemplate - Writing [{파라미터1=[path [Uuid 임시 파일 위치]]}] as &quot;multipart/form-data&quot;
16:25:38.558 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK</code></pre>
<h2 id="정리">정리</h2>
<blockquote>
</blockquote>
<ul>
<li>Python FastAPI와 통신할 때 multipart/form-data 전송 시 byte 배열을 전송하면 타입 오류가 발생할 수 있음</li>
<li>In-Memory 데이터로 전송하지 않고 임시 파일을 생성해 전송하는 방식을 사용하면 타입 오류를 방지할 수 있음</li>
<li>임시 파일 생성 방식을 사용할 때 Disk Full 이슈를 방지하기 위해 주기적 삭제 혹은 작업 완료 후 삭제 같은 정책이 있어야 함</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트 제네릭]]></title>
            <link>https://velog.io/@404-nut-pound/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%A0%9C%EB%84%A4%EB%A6%AD</link>
            <guid>https://velog.io/@404-nut-pound/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%A0%9C%EB%84%A4%EB%A6%AD</guid>
            <pubDate>Sun, 28 Aug 2022 14:51:28 GMT</pubDate>
            <description><![CDATA[<h2 id="제네릭이란">제네릭이란?</h2>
<blockquote>
</blockquote>
<p>자바스크립트에서는 없던 개념인데 자바 같은 컴파일 언어에서는 비교적 일반적인 문법이다.</p>
<blockquote>
</blockquote>
<p>제네릭은 <code>데이터 타입을 일반화(generalize)한다</code> 라고 보면 되는데 어떤 클래스, 파라미터, 리턴 타입에 대해 타입을 지정해주는 것이다.</p>
<blockquote>
</blockquote>
<p>제네릭을 사용하면 어떤 데이터 타입이 오가는지 보다 명확히 할 수 있어 코딩 중 발생하는 문제를 줄일 수 있고 다양한 데이터 타입에 대해 대응할 수 있어서 코드 양을 줄일 수 있는 효과도 있다.</p>
<h2 id="문법">문법</h2>
<pre><code class="language-ts">function logAndReturn&lt;T&gt;(param: T): T {
  console.log(param);
  return param;
}

const str = logAndReturn(&#39;10&#39;); //str의 데이터 타입은 string
const num = logAndReturn(10); //num의 데이터 타입은 number</code></pre>
<h2 id="union-type과-비교">Union Type과 비교</h2>
<blockquote>
</blockquote>
<p>메소드 파라미터를 Union Type으로 설정하면 제네릭과 마찬가지로 여러가지 데이터 타입에 대해 대응할 수 있지만
return 결과도 Union Type으로 설정되기 때문에 이후 사용하기에 불편한 상황이 발생한다.</p>
<pre><code class="language-ts">function logAndReturn(param: string | number) {
  console.log(param);
  return param;
}

const str = logAndReturn(&#39;10&#39;); //str의 데이터 타입은 string | number
str.split(&#39; &#39;); //split은 string 타입에만 사용할 수 있으므로 error 발생</code></pre>
<h2 id="제네릭-타입-제한">제네릭 타입 제한</h2>
<pre><code class="language-ts">function logAndReturn&lt;T&gt;(param: T[]): T {
  console.log(param.length);
  param.forEach((child) =&gt; {
    console.log(child);
  }
  return param;
}

const str = logandReturn(&#39;abc&#39;); //배열 타입이 아니므로 error 발생
const arr = logAndReturn([&#39;abc&#39;, &#39;def&#39;]);</code></pre>
<pre><code class="language-ts">//인터페이스 상속으로 타입 제한
interface LengthType {
  length: number;
}

function logAndReturn&lt;T extends LengthType&gt;(param: T): T {
  console.log(param);
  return param;
}

const num = logAndReturn(10); //number 항목에는 length 값이 없으므로 error 발생
const str = logAndReturn(&#39;10&#39;);
const arr = logAndReturn([&#39;abc&#39;, 123]);
const obj = logAndReturn({leng: 10}); //object 항목에 length 값이 없으므로 error 발생</code></pre>
<pre><code class="language-ts">//인터페이스의 키에 대한 상속으로 타입 제한
interface Product {
  name: string;
  price: number;
  stock: number;
}

function getItemInfo&lt;T extends keyof Product&gt;(param: T): T {
  console.log(param);
  return param;
}

const name = getItemInfo(&#39;name&#39;);
const amount = getItemInfo(&#39;amount&#39;); //Product 항목에 amount 속성이 없으므로 error 발생</code></pre>
<h2 id="유틸리티-타입">유틸리티 타입</h2>
<blockquote>
</blockquote>
<p>타입스크립트 작성 중 정의되어 있는 타입, 인터페이스에 대해 유동적으로 타입을 정의할 수 있는 방법이다.</p>
<blockquote>
</blockquote>
<p>다양한 타입이 정의돼있으며 공식 문서를 확인하자.</p>
<p>링크 - <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html" target="_blank">타입스크립트 유틸리티 타입</a></p>
<pre><code class="language-ts">interface Parent {
  name: string;
  age: number;
  address: string;
}

//Pick은 타입에서 정의된 특정 항목을 가져오는데 쓰인다.
type child1 = Pick&lt;Parent, &#39;name&#39;|&#39;age&#39;&gt;;

//Omit은 Pick과 반대로 동작한다.
type child2 = Omit&lt;Parent, &#39;address&#39;&gt;;

//Partial은 내부 항목을 Optional 값으로 정의할 수 있다.
type child3 = Partial&lt;Parent&gt;;
===
interface child3 {
  name?: string;
  age?: number;
  address?: string;
}</code></pre>
<pre><code class="language-ts">//mappedType은 기존 타입에서 정의된 항목의 속성을 재정의한다.
type Names = &#39;kim&#39; | &#39;lee&#39; | &#39;park&#39;;
type Ages = { [K in Names]: number}; //string 타입을 number 타입으로 변환

const ages: Ages = {
  kim: &#39;20&#39;, //number 타입이 아니므로 error 발생
  lee: 30,
  park: 40
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트 문법 기초]]></title>
            <link>https://velog.io/@404-nut-pound/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@404-nut-pound/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Fri, 26 Aug 2022 10:36:12 GMT</pubDate>
            <description><![CDATA[<h2 id="변수-타입">변수 타입</h2>
<ul>
<li>Boolean</li>
<li>Number</li>
<li>String</li>
<li>Object</li>
<li>Array</li>
<li>Tuple</li>
<li>Enum</li>
<li>Any</li>
<li>Void</li>
<li>Null</li>
<li>Undefined</li>
<li>Never</li>
</ul>
<h2 id="변수-문법">변수 문법</h2>
<pre><code class="language-ts">//자바스크립트
var str = &#39;hello&#39;;

//타입스크립트
var str: string = &#39;hello&#39;;

//배열 선언
var numbers: number[] = [1, 2, 3];

//제네릭 사용
var arr: Array&lt;string&gt; = [&#39;hello&#39;, &#39;world&#39;];

//타입스크립트 튜플
//각 인덱스 별 타입을 지정한 배열
var address: [string, number] = [&#39;seoul&#39;, 10];

//타입스크립트 객체
var obj: object = {
  //값 유형 제한 없음
};
var person: {name: string, age: number} = {
  name: &#39;kim&#39;,
  age: 10
}</code></pre>
<h2 id="함수-문법">함수 문법</h2>
<pre><code class="language-ts">//파라미터와 리턴 타입 정의
function sum1(a: number, b: number): number {
  return a + b;
}

//옵셔널 파라미터와 기본 값
function sum2(a: number, b?: number = 0): number {
  return a + b;
}</code></pre>
<h2 id="인터페이스-문법">인터페이스 문법</h2>
<pre><code class="language-ts">//기본 문법
interface User {
  name: string;
  age: number;
}

var kim: User = {
  //age를 정의하지 않아 오류 발생
  //옵셔널 설정을 하면 오류 발생하지 않음
  name: &#39;kim&#39;,
}

//함수 인터페이스 정의
interface SumFunc {
  (a: number, b: number): number;
}

var sum1: SumFunc = function (a: number, b: number): number {
  return a + b;
};

var sum2: SumFunc = (a: number, b: number): number =&gt; {
  return a + b;
};</code></pre>
<pre><code class="language-ts">//인터페이스 인덱싱
interface StringArray {
  [index: number]: string;
}

var stringArr: StringArray = [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;];
arr[0] = 10; //타입 오류 발생

interface RegExpArray {
  [key: string]: RegExp;
}

var regExpArr: RegExpArray = {
  cssFile: &#39;*.css&#39;, //정규식이 아니므로 오류 발생
  jsFile: /\.js$/,
};
regExpArr[&#39;javaFile&#39;] = /\.java$/</code></pre>
<pre><code class="language-ts">//인터페이스 상속
interface Person {
  name: string;
  age: number;
}

interface Developer extends Person {
  language: string;
}

var kim: Developer = { //Person에서 정의된 name, age 항목에 대해서도 정의 필요
  language: &#39;Typescript&#39;,
};</code></pre>
<h2 id="파라미터-문법">파라미터 문법</h2>
<pre><code class="language-ts">interface Developer {
  name: string;
  skill: string;
}

interface Person {
  name: string;
  age: number;
}

//Union Type
function someFunction1(worker: Developer | Person) {
  //함수 호출 시 전달한 변수에 대해서만 접근 가능
  worker.name; //공통 요소는 자유 접근 가능
  worker.age; //특정 인터페이스(타입)에만 정의된 속성은 타입 체크 후 접근 가능
}

//Intersection Type
function someFunction2(worker: Developer &amp; Person) {
  //함수에 선언된 파라미터 종류의 모든 변수에 접근 가능
  //별도의 인터페이스(타입)를 정의하는 방식
  worker.name;
  worker.age;
  worker.skill;
}</code></pre>
<h2 id="클래스-문법">클래스 문법</h2>
<pre><code class="language-ts">//ES5
function Person(name, age) {
  this.name = name;
  this.age = age;
}
const kim = new Person(&#39;kim&#39;, 10);

//ES6 + Typescript
class Person {
  name: string;
  age: number;

  constructor() {
    this.name = &#39;&#39;
    this.age = 0
  }

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}
const kim = new Person(&#39;kim&#39;, 10);

//클래스 변수 상속
var user = {name: &#39;kim&#39;, age: 10};
var admin = {};
admin.__proto__ = user; //user에 설정된 변수와 값을 그대로 상속 받음</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Gradle import 설정과 test 실패]]></title>
            <link>https://velog.io/@404-nut-pound/Gradle-import-%EC%84%A4%EC%A0%95%EA%B3%BC-test-%EC%8B%A4%ED%8C%A8</link>
            <guid>https://velog.io/@404-nut-pound/Gradle-import-%EC%84%A4%EC%A0%95%EA%B3%BC-test-%EC%8B%A4%ED%8C%A8</guid>
            <pubDate>Fri, 26 Aug 2022 08:04:49 GMT</pubDate>
            <description><![CDATA[<h2 id="빌드-이후-단위-테스트-오류-발생">빌드 이후 단위 테스트 오류 발생</h2>
<blockquote>
</blockquote>
<p>현재 상황</p>
<blockquote>
</blockquote>
<p>IDE(VSCode)에서는 단위 테스트가 정상적으로 된다.</p>
<blockquote>
</blockquote>
<p>하지만 Gradle Job을 통한 단위 테스트는 실패한다.</p>
<blockquote>
</blockquote>
<p>테스트 결과가 실패인 게 아니라 테스트 Job 자체의 실패가 발생한다.</p>
<h2 id="프로젝트-주요-스택">프로젝트 주요 스택</h2>
<blockquote>
</blockquote>
<p>OpenJDK 17
Spring Boot 2.6.6
Junit 5
Gradle 7.4.1</p>
<blockquote>
</blockquote>
<h2 id="ide-내-단위-테스트-결과">IDE 내 단위 테스트 결과</h2>
<p><img src="https://velog.velcdn.com/images/404-nut-pound/post/addbebcc-8596-4ecd-8d1d-674485c9efc3/image.png" alt=""></p>
<p>모두 테스트가 정상 통과된다.</p>
<p><del>사내 프로젝트다 보니 전체 패키지명은 못 보여드림</del></p>
<h2 id="오류-로그">오류 로그</h2>
<pre><code class="language-js">***ApplicationTests &gt; testMessage() FAILED
    java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
        Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:800
            Caused by: org.springframework.beans.factory.BeanCreationException at ConstructorResolver.java:658
                Caused by: org.springframework.beans.BeanInstantiationException at SimpleInstantiationStrategy.java:185
                    Caused by: java.lang.IllegalStateException at Assert.java:97</code></pre>
<p>해당 오류 메시지를 가지고 여기저기 찾아봐도 나의 문제와 동일한 현상은 없었다.</p>
<p>그중에는 다음과 같은 해결책을 제시했다.</p>
<ol>
<li>JUnit @Test 어노테이션의 패키지 변경</li>
<li><ol>
<li><code>import org.junit.jupiter.api.Test;</code>를 <code>import org.junit.Test;</code>로 변경</li>
</ol>
</li>
<li><ol start="2">
<li>현재 Gradle 구성 상 저런 패키지를 가져올 수 없었다.</li>
</ol>
</li>
<li>test 프로필에 사용하는 H2 DB가 연결 가능한지 확인해라</li>
<li><ol>
<li>test 프로필에 연결돼있는 H2 DB는 Memory DB이다.</li>
</ol>
</li>
<li><ol start="2">
<li>Embedded DB이기 때문에 연결 가능한지 확인해볼 필요가 없다.</li>
</ol>
</li>
</ol>
<h2 id="그렇다면-어디가-문제인가">그렇다면 어디가 문제인가?</h2>
<p>하루동안 고민해봤는데 두 번째 해결책을 다시 보게 됐다.</p>
<p>핵심은 <code>테스트할 때 H2 DB에 연결할 수 있는가?</code>이다. </p>
<p>test 프로필에는 별도로 datasource 항목을 정의해뒀기 때문에 다른 DB에 연결될 일도 없었다.</p>
<pre><code class="language-yml">spring:
  config:
    activate:
      on-profile:
        - test
  datasource:
    url: jdbc:h2:mem:testdb
    username: sa
    password:
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: create

logging:
  level:
    root: info # 테스트 속도 향상을 위해 info로 설정</code></pre>
<p>그렇다면 H2 DB에 연결할 때 필요한 재료(JDBC 드라이버 같은)들은 잘 있을까?</p>
<p>Gradle의 Dependency 설정을 보자</p>
<pre><code class="language-js">dependencies {
  ...
  developmentOnly &quot;com.h2database:h2&quot; //로컬 환경 및 테스트할 때만 사용
  compileOnly &quot;org.mariadb.jdbc:mariadb-java-client&quot; //빌드 이후에 사용
  ...
}</code></pre>
<p>마지막으로 Gradle의 test 태스크는 <code>빌드 및 패키징이 완료된 후 실행</code>한다.</p>
<h2 id="어">어?</h2>
<p>그렇다면 저 <code>developmentOnly</code>를 <code>implementation</code>으로 바꿔서 다시 Gradle test를 해보자.</p>
<p><img src="https://velog.velcdn.com/images/404-nut-pound/post/e9a3b93a-47f0-4990-be51-fea299a37412/image.png" alt=""></p>
<p>아주 가볍게 성공.</p>
<p>그렇다. 나름대로 패키지 구성을 멋지게 한다고 Gradle의 Dependency Scope를 이리저리 구성해놓은게 독이 됐다.</p>
<p>Gradle의 LifeCycle을 생각하지도 않고 단위 테스트에 필요한 Dependency를 개발 환경에만 적용되게 해놓은 것이 원인이었다.</p>
<p>H2 DB에 대한 패키지 외에 Dependency Scope가 다르게 설정돼있는 패키지도 몇몇개 있는데 이걸 다 <code>implementation</code>로 바꿔야할까 싶기도 하다.</p>
<p>그렇게되면 Dependency Scope는 별 의미 없는 것일까....?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트와 타입스크립트]]></title>
            <link>https://velog.io/@404-nut-pound/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%99%80-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</link>
            <guid>https://velog.io/@404-nut-pound/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%99%80-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</guid>
            <pubDate>Wed, 24 Aug 2022 16:11:44 GMT</pubDate>
            <description><![CDATA[<h2 id="타입스크립트는-왜-사용하는가">타입스크립트는 왜 사용하는가</h2>
<blockquote>
</blockquote>
<p>문법이 비교적 자유로운 자바스크립트의 고질적인 문제는 결과를 예측하기 어렵다는 것이다.</p>
<blockquote>
</blockquote>
<p>type-check를 엄격하게 하지 않기 때문에 일반적으로 IDE에서 지원되는 compile 시점 혹은 여러 가지 프로그래밍 언어의 runtime 시점에서 발생하는 오류를 미리 알기 어렵다.</p>
<blockquote>
</blockquote>
<p>그리고 자바스크립트의 &#39;웬만해선 작동해드림&#39; 특성 때문에 오류가 발생했는지 알기 어렵기도 하다.</p>
<blockquote>
</blockquote>
<pre><code class="language-js">10 + &#39;20&#39; = &#39;1020&#39;
(&#39;b&#39; + &#39;a&#39; + + &#39;b&#39; + &#39;a&#39;).toLowerCase() = &#39;banana&#39; //🍌?</code></pre>
<blockquote>
</blockquote>
<p>위와 같은 동작 하면 안 되는 코드가 잘 작동하기도 하고...</p>
<blockquote>
</blockquote>
<p>이런 이유로 Microsoft가 우리에게 타입스크립트를 하사하시었다.</p>
<blockquote>
</blockquote>
<p>타입스크립트로 프로젝트를 구성하면 위와 같은 사용법은 컴파일 단계에서 오류가 발생해서 사고를 방지할 수 있다</p>
<blockquote>
</blockquote>
<p>프로젝트 규모가 커질수록 어디서 오류가 발생할지 알기 어렵기 때문에 더욱 타입스크립트가 필요해지는 것 같다.</p>
<blockquote>
</blockquote>
<p>그런데 최근엔 타입스크립트가 너무 피곤하다고 자바스크립트로 먼저 구현하고 나서 타입스크립트로 리팩토링하기도 한다고 한다.
<del>두 번 일하기?</del></p>
<hr>
<h2 id="기본-문법-비교">기본 문법 비교</h2>
<h3 id="자바스크립트">자바스크립트</h3>
<pre><code class="language-js">/**
 * @param {number} a
 * @param {number} b
 * @returns a + b
 */
function sum(a, b) {
  return a + b;
}

sum(10, &#39;20&#39;); //returns &#39;1020&#39;</code></pre>
<h3 id="타입스크립트">타입스크립트</h3>
<pre><code class="language-ts">/**
 * @param {number} a
 * @param {number} b
 * @returns a + b
 */
function sum(a: number, b: number) {
  return a + b;
}

sum(10, &#39;20&#39;); //error</code></pre>
<hr>
<h2 id="자바스크립트에서-타입스크립트와-유사하게">자바스크립트에서 타입스크립트와 유사하게</h2>
<pre><code class="language-js">// @ts-check 
// 파일 상단에 위 decorator 추가
// 자바스크립트에서 타입스크립트와 유사하게 구현 가능
/**
 * @param {number} a
 * @param {number} b
 * @returns a + b
 */
function sum(a, b) {
  return a + b;
}

sum(10, &#39;20&#39;); //error</code></pre>
<hr>
<h2 id="타입스크립트-구성">타입스크립트 구성</h2>
<pre><code class="language-shell">npm i typescript (-g)</code></pre>
<blockquote>
<p>설치할 때 -g 옵션 생략 시 typescript 빌드는 <code>npx tsc *.ts</code>로 실행</p>
</blockquote>
<pre><code class="language-js">//tsconfig.json
{
  &quot;compilerOptions&quot;: {
    &quot;allowJs&quot;: true, //js 파일 사용
    &quot;checkJs&quot;: true, //js 파일 검사
    &quot;noImplicitAny&quot;: true //any 타입 금지
    ...
  }
}</code></pre>
<blockquote>
<p>기타 옵션에 대해서는 <a href="https://www.typescriptlang.org/tsconfig" target="_blank">타입스크립트 공식 문서</a> 참조</p>
</blockquote>
<hr>
<p>velog는 링크에 <code>{: target=&quot;_blank&quot;}</code> 속성이 안 되는구나</p>
<p>귀찮다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hello, World!]]></title>
            <link>https://velog.io/@404-nut-pound/Hello-World</link>
            <guid>https://velog.io/@404-nut-pound/Hello-World</guid>
            <pubDate>Tue, 23 Aug 2022 16:14:50 GMT</pubDate>
            <description><![CDATA[<h2 id="hello-world-with-java">Hello, World! with Java</h2>
<pre><code class="language-java">class MainClass {

    public static void main(String[] args) {
        System.out.println(&quot;Hello, World!&quot;);
    }
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>