0%

Netty를 사용하다보면 채널 파이프라인에 여러 이벤트 핸들러를 추가하기 마련이다.
그러다보니 순서가 중요할 때가 있다.

  1. 클라에서 보낸 데이터 중에 헤더를 파싱하고,
  2. 헤더에 따라 바디를 파싱하고,
  3. 바디를 토대로 뭔가를 또 처리해야하고…

이런 식으로 N 개의 이벤트 핸들러를 붙여야하고, 순서가 중요하다보니 어떤 순서대로 실행되는지가 궁금해졌다.

Inbound Event Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ExampleHandler1 : ChannelInboundHandlerAdapter() {
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
println("1")
ctx.fireChannelRead(msg)
}
}

class ExampleHandler2 : ChannelInboundHandlerAdapter() {
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
println("2")
ctx.fireChannelRead(msg)
}
}

class ExampleHandler3 : ChannelInboundHandlerAdapter() {
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
println("3")
ctx.fireChannelRead(msg)
}
}
더 읽어보기 »

Netty는 왜 자바 표준인 NIO의 ByteBuffer를 사용하지 않는 걸까 이유를 몰랐는데 자바 네트워크 소녀 네티를 보고 이유를 알게되어 정리해봄.
ByteBuffer와 ByteBuf의 세부사항 보다는 ByteBuffer는 어떤 문제점을 가지고 있고, ByteBuf는 그 문제점을 어떻게 해결했는지에 초점을 맞추어 정리함.

ByteBuffer의 문제점

Netty의 ByteBuf는 자바의 ByteBuffer가 가진 문제점들을 해결하기 위해 나왔다.

데이터 쓰기/읽기 인덱스가 분리돼있지 않다

1
2
3
4
5
6
7
val byteBuffer = ByteBuffer.allocate(3) // 3바이트를 담을 수 있는 힙버퍼, 전부 0으로 초기화된다.
println(byteBuffer) // java.nio.HeapByteBuffer[pos=0 lim=3 cap=3]

byteBuffer.put(1)
println(byteBuffer) // java.nio.HeapByteBuffer[pos=1 lim=3 cap=3]

println(byteBuffer.get()) // 0
더 읽어보기 »

이벤트 루프의 개념이 명확하지 않아 자바 네트워크 소녀 네티를 보고 정리해봄.

통상적으로 이벤트 기반 어플리케이션이 이벤트를 처리하는 방식은 아래 두 가지가 존재한다고 함.

이벤트 리스너와 이벤트 처리 쓰레드 방식

브라우저에서 DOM에 클릭 이벤트를 어떻게 핸들링하는지 생각해보면 된다.

1
document.querySelector('body').onclick = e => console.dir(e) 
더 읽어보기 »

Netty의 개념이 하도 익숙하지 않아 자바 네트워크 소녀 네티를 보고 용어를 정리해봄.

Netty

Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients.

네티는 비동기 이벤트 기반 네트워크 어플리케이션 프레임워크로써 유지보수를 고려한 고성능 프로토콜 서버와 클라이언트를 빠르게 개발할 수 있다.
즉, TCP 통신을 위해 무조건 Netty를 써야하는 건 아니지만 유지보수하기도 쉽고, 비동기 이벤트 기반이기 때문에 고성능도 보장하게 된다.
Spring Integration 또한 TCP 통신을 지원한다.

Spring Integration provides channel adapters for receiving and sending messages over internet protocols.
Both UDP (User Datagram Protocol) and TCP (Transmission Control Protocol) adapters are provided.

더 읽어보기 »

N줄 요약

SimpleClientHttpRequestFactory(RestTemplate을 기본생성자로 만들었을 때 사용하는)를 사용하더라도 내부에서 KeepAliveCache를 사용하여 커넥션 풀을 관리한다.
기본적으로 KeepAliveKey(protocol, host, port) 당 5개의 풀을 가지며 시스템 프로퍼티 http.maxConnections를 할당해주면 늘릴 수 있다.
커넥션 풀을 초과하면 커넥션은 바로 종료되며, 커넥션 풀 내의 커넥션은 매번 연결을 맺고 끊는 게 아니라 재사용 된다.
당연하게도 서버에서 Keep-Alive를 사용하지 않으면 매번 커넥션이 종료된다.
그럼에도 불구하고 SimpleClientHttpRequestFactory는 다음의 단점이 있기 때문에 토이 프로젝트가 아닌 이상 HttpComponentsClientHttpRequestFactory 같은 다른 구현체를 사용해야할 것 같다.

  • http.maxConnections라는 시스템 프로퍼티를 설정해야하는데 설정을 위해 자주 사용하던 properties(yml)에는 설정할 수 없다보니 다른 방법으로 설정을 해줘야하고, 그러다 보면 설정을 파악하려면 한 군데(properties 또는 yml)만 집중해서는 파악할 수 없는 내용도 있다보니 실수할 여지가 발생할 수 있다.
  • KeepAliveCache가 static 변수이다보니 서로 다른 SimpleClientHttpRequestFactory여도 동일한 커넥션 풀을 참조한다.
  • route(프로토콜, 호스트, 포트) 별 커넥션 풀은 설정할 수 있지만 토탈 커넥션 풀은 제한이 없다.

SimpleClientHttpRequestFactory가 뭐지??

RestTemplate의 기본 생성자를 사용하면 ClientHttpRequestFactory를 별도로 초기화하지 않으므로 기본값인 SimpleClientHttpRequestFactory를 사용한다.

더 읽어보기 »

Spring Boot Reference의 Testing - Detecting Test Configuration 파트를 보면 다음과 같은 내용이 나온다.

If you are familiar with the Spring Test Framework, you may be used to using @ContextConfiguration(classes=…​) in order to specify which Spring @Configuration to load. Alternatively, you might have often used nested @Configuration classes within your test.
When testing Spring Boot applications, this is often not required. Spring Boot’s @*Test annotations search for your primary configuration automatically whenever you do not explicitly define one.
The search algorithm works up from the package that contains the test until it finds a class annotated with @SpringBootApplication or @SpringBootConfiguration.

Detecting Test Configuration을 위해서 스프링에 친숙하다면 @ContextConfiguration이나 Nested @Configuration이 필요하다고 하고,
Spring Boot를 사용하면 @*Test(@SpringBootTest, @WebMvcTest, @DataJpaTest, etc.)에서 별다른 설정을 하지 않았다면 primary configuration을 찾아나간다고 한다.

N줄 요약

글이 길어지다보니 아무도 안 볼 거 같고, 집중을 하고 소스코드를 따라가면서 읽어야해서 우선 먼저 요약을 적어놓는다.

더 읽어보기 »

1
2
3
dependencies {
compileOnly("org.springframework.boot:spring-boot-starter-web:2.4.0")
}

compileOnly는 compile classpath에만 추가된다.
runtime classpath에는 추가되지 않아서 원래는 java.lang.ClassNotFoundException이 나야 정상이다.
하지만 IntelliJ의 Spring Boot Configuration으로 실행하면 실행이 잘만 된다.


실제로 classpath를 찍어보면 아래와 같이 spring-boot-starter-web을 포함하고 있다.

1
2
3
4
5
fun main(args: Array<String>) {
val property = System.getProperty("java.class.path")
println(property)
runApplication<DemoApplication>(*args)
}
1
2
3
4
5
6
7
/Users/perfectacle/IdeaProjects/demo/build/classes/kotlin/main:
/Users/perfectacle/IdeaProjects/demo/build/resources/main:
/Users/perfectacle/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.4.10/998caa30623f73223194a8b657abd2baec4880ea/kotlin-stdlib-jdk8-1.4.10.jar:
/Users/perfectacle/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-web/2.4.0/4bdd422c370d1d66ffc12ecafdecc70cad406367/spring-boot-starter-web-2.4.0.jar:
/Users/perfectacle/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.4.10/30e46450b0bb3dbf43898d2f461be4a942784780/kotlin-stdlib-jdk7-1.4.10.jar:
/Users/perfectacle/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.10/ea29e063d2bbe695be13e9d044dcfb0c7add398e/kotlin-stdlib-1.4.10.jar:
...
더 읽어보기 »

JUnt 4

Field Injection 밖에 되지 않음.
Spring Boot 2.2.0부터 JUnit 5가 기본으로 탑재되기 시작했고,
Spring Boot 2.4.0부터는 아예 JUnit 4 의존성이 제거됐기 때문에 JUnit 4의 사용은 하지 말아야한다.

1
2
3
4
5
6
7
8
@RunWith(SpringRunner::class)
@SpringBootTest
class SomeTest {
@Autowired
private lateinit var a: SomeComponent
@Test
fun contextLoad() {}
}

JUnit 5

JUnit 5의 @ExtendedWith 어노테이션을 이용하면 테스트 전/후로 다양한 일을 할 수 있다.
@ExtendedWith 어노테이션은 어노테이션에 명시한 Extension들을 실행하는 역할 뿐이 하지 않는다.

더 읽어보기 »

SpringProperties 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// ...
import java.util.Properties;
// ...
public final class SpringProperties {

private static final String PROPERTIES_RESOURCE_LOCATION = "spring.properties";

private static final Properties localProperties = new Properties();


static {
try {
ClassLoader cl = SpringProperties.class.getClassLoader();
URL url = (cl != null ? cl.getResource(PROPERTIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResource(PROPERTIES_RESOURCE_LOCATION));
if (url != null) {
try (InputStream is = url.openStream()) {
localProperties.load(is);
}
}
}
catch (IOException ex) {
System.err.println("Could not load 'spring.properties' file from local classpath: " + ex);
}
}
// ...
@Nullable
public static String getProperty(String key) {
String value = localProperties.getProperty(key);
if (value == null) {
try {
value = System.getProperty(key);
}
catch (Throwable ex) {
System.err.println("Could not retrieve system property '" + key + "': " + ex);
}
}
return value;
}
// ...
}
  1. PROPERTIES_RESOURCE_LOCATION(spring.properties) 파일을 읽어서 InputStream에 넣고
  2. localProperties.load(is)를 통해 Properties에 위에서 읽어들인 InputStream을 load 하고
  3. 나중에 필요할 때 키 값을 통해 프로퍼티를 불러오고 있다.

localProperties는 Properties라는 자바 표준 API를 사용하고 있기 때문에 저 spring.properties에는 어떻게 키와 프로퍼티를 구성하는지 알아보자.

Properties

더 읽어보기 »

TDD By Example 책을 보다가 감명 받은 부분을 정리해봤다.
기본적으로 아래 4가지 원칙을 따라 진행한다.

  1. Red - 실패하는 작은 테스트를 작성(최초에는 컴파일 조차 되지 않음)
  2. Green - 빨리 테스트가 통과하게 끔 수정(이를 위해선 어떠한 죄악도 용서됨)
  3. Refactoring - 모든 중복제거(2번에서 수행한 죄악들을 청산)

해당 포스트는 프랑(CHF, 스위스 통화)을 달러($)로 변환하는 간단한 테스트를 작성하는 것부터 시작한다.

프랑에서 달러로 변환하기

아래와 같은 간단한 코드들을 이번 예제에서 사용해보자.

더 읽어보기 »