English | ํ๊ตญ์ด | ๐ Documentation
Spring์ด ๋ด๋ถ์ ์ผ๋ก ์ด๋ป๊ฒ ์๋ํ๋์ง ์ดํดํ๊ธฐ ์ํด ์ฒ์๋ถํฐ ๋ง๋ ๊ฒฝ๋ Java ์น ํ๋ ์์ํฌ. ์ด์ ์์ ํ ์๋ํ๋ NIO & ํ์ด๋ธ๋ฆฌ๋ ์๋ฒ์ ๋น๋๊ธฐ WebSocket ์คํ์ ์ ๊ณตํฉ๋๋ค. ์ฌ์ ํ ๋ช ํ์ฑ ยท ํดํน ๊ฐ๋ฅ์ฑ ยท ํ์ฅ์ฑ์ ์ค์ํฉ๋๋ค.
๋ฒ์: ์ปจํ ์ด๋/AOP/์น/์๋ฒ ๋ด๋ถ ๊ตฌ์กฐ์ ์ง์คํฉ๋๋ค. ์ ์ฒด ORM์ v1.0์์ ์๋์ ์ผ๋ก ์ ์ธํ์ฌ ๋ฒ์๋ฅผ ์๊ฒ ์ ์งํ๊ณ ์ฝ๋ ๊ฐ์ฌ๋ฅผ ์ฝ๊ฒ ๋ง๋ค์์ต๋๋ค.
| ์์ญ | ์ํ & ์ฃผ์ ํน์ง |
|---|---|
| IoC / DI ์ปจํ ์ด๋ | โข @Component, @Service, @Controller, @Repository, @Configuration, @Aspect ์ค์บ.โข ์์ฑ์ ๊ธฐ๋ฐ ์ฃผ์ . โข @Order ์ง์๊ณผ ํจ๊ป ์๋ List<T> ์ฑ์ฐ๊ธฐ.โข ์ํ ์์กด์ฑ ๊ฐ์ง (์์ ์ ๋ ฌ). โข BeanDefinitionRegistrar๋ฅผ ํตํ ์๋ ๊ตฌ์ฑ (ํฉ๋ฆฌ์ ์ธ ๊ธฐ๋ณธ๊ฐ ์ ๊ณต). |
| ๋น ์ ์ | โข ConstructorBeanDefinition & MethodBeanDefinition.โข ํฉํ ๋ฆฌ ๋ฉ์๋ ๋๋ ์์ฑ์ ์ ๋ต. โข ์์ฑ์ ๋ฉํ ์บ์ โ ํ์ ์ธ์ ์์ฑ์๋ฅผ ์ํ ์์ ํ ํ๋ก์ ์์ฑ. |
| AOP | โข ์ด๋
ธํ
์ด์
๊ธฐ๋ฐ (@Before, @After, @Around).โข AspectJ ์คํ์ผ ํฌ์ธํธ์ปท ( *, .., ?).โข Spring์์ ์๊ฐ์ ๋ฐ์ Advisor/Advice/Pointcut ๊ณ์ธต ๊ตฌ์กฐ. โข CGLIB ์๋ธํด๋์ฑ + Objenesis ํด๋ฐฑ (๊ธฐ๋ณธ ์์ฑ์ ๋ถํ์). โข ์์๊ฐ ์ง์ ๋ advisor ์ฒด์ธ, ํ๊ฒ๋ณ ํ๋ก์. |
| ๊ตฌ์ฑ ํ๋ก์ | @Configuration(proxyBeanMethods = true)๋ฅผ ์ํ CGLIB ํ๋ก์ โ ๋ฐ๋ณต๋๋ @Bean ํธ์ถ ์บ์ฑ. |
| ์น ๋ ์ด์ด (HTTP) | โข ์ ์ธ์ ๋ผ์ฐํ
(@GetMapping, @PostMapping, โฆ + {var} ํจํด).โข @PathVariable, @RequestParam, @RequestBody ๋ฑ์ ์ํ ArgumentResolver.โข RequestDispatcher๊ฐ ๋ฐ์ธ๋ฉ โ ํธ์ถ โ ํด๊ฒฐ (ResponseEntity, DTO, void). |
| ์๋ฒ | ์ ๊ท: java.nio.channels ๊ธฐ๋ฐ NIO ์๋ฒ.โข ํ์ด๋ธ๋ฆฌ๋ ๋ชจ๋: ๊ฐ์ ์ค๋ ๋ ๋๋ ํด๋์ ํ์ ํตํ HTTP, NIO๋ฅผ ํตํ WS; ์ค์ ์ ๋ฐ๋ผ ์ ํ ๊ฐ๋ฅ. โข ํ์ต/๋จ์์ฑ์ ์ํ ๋ธ๋กํน ํด๋ฐฑ ์ ์ง. |
| ํํฐ & ์ธํฐ์ ํฐ | โข ์๋ธ๋ฆฟ ์คํ์ผ Filter ์ฒด์ธ.โข ๊ธ๋ก๋ฒ ํํฐ (์ธ์ฆ, CORS, ๋ก๊น โฆ). โข ๋ฏธ๋ค์จ์ด ์คํ์ผ Interceptor ์ฒด์ธ.โข RequestDispatcher์ List<Filter> / List<Interceptor> ์๋ ์ฃผ์
. |
| ๋ณด์ | โข ๋ชจ๋์ ์ธ์ฆ (AuthenticationManager, AuthenticationProvider, UserDetailsService).โข AuthenticationFilter๋ฅผ ํตํ ์ฌ์ฉ์๋ช
/๋น๋ฐ๋ฒํธ ๋ก๊ทธ์ธ.โข @PreAuthorize๋ฅผ ํตํ ๋ฉ์๋ ๋ณด์ (AOP ๊ธฐ๋ฐ).โข AuthorizationFilter๋ฅผ ํตํ URL ๊ถํ ๋ถ์ฌ.โข ์์ฒญ๋ณ ThreadLocal์ ์ฌ์ฉํ๋ SecurityContextHolder.โข ์๋ ๊ตฌ์ฑ ( @EnableSproutSecurity). |
| ์์ธ ์ฒ๋ฆฌ | โข HTTP ์์ธ (BadRequest, MethodNotAllowed, โฆ).โข @ControllerAdvice + @ExceptionHandler.โข ํ์ฅ ๊ฐ๋ฅํ ExceptionResolver ์ฒด์ธ. |
| ๋ฐ์ดํฐ ์ก์ธ์ค | โข ๊ฒฝ๋ JdbcTemplate ์ถ์ํ.โข HikariCP ํตํฉ. โข AOP ๊ธฐ๋ฐ @Transactional ์ง์.โข TransactionManager ์ถ์ํ (์๋ ์ปค๋ฐ/๋กค๋ฐฑ). |
| WebSocket (๋น๋๊ธฐ/NIO) | โข RFC6455 ํธ๋์
ฐ์ดํฌ + ํ๋ ์ ํ์/์ธ์ฝ๋ (๋ง์คํน, ping/pong, close). โข ๋ ผ๋ธ๋กํน ์ฐ๊ธฐ ํ, OP_WRITE ํ ๊ธ, ๋๋ ์ธ ํ ์ฐ์ํ ์ข ๋ฃ. โข ๋จํธํ ์ฒ๋ฆฌ (ํ ์คํธ/๋ฐ์ด๋๋ฆฌ ์ฐ์ ํ๋ ์). โข WebSocketSession ์ถ์ํ + ์๋ช
์ฃผ๊ธฐ ํ
(@OnOpen, @OnMessage, @OnClose, @OnError).โข ํ๋ฌ๊ทธํ WebSocketMessageDispatcher & WebSocketArgumentResolver.โข HTTP NIO์ ๋์ผํ ์ ๋ ํฐ ๋ฃจํ์์ ์คํ๋๊ฑฐ๋ ๋ณ๋๋ก ์คํ ๊ฐ๋ฅโ๊ตฌ์ฑ ๊ฐ๋ฅ. |
| ๋ถํธ์คํธ๋ฉ | ํ ์ค๋ก ์คํ: SproutApplication.run()์ด ์ปจํ
์ด๋๋ฅผ ๋ถํ
ํ๊ณ ์๋ฒ๋ฅผ ์์ํฉ๋๋ค. |
- ํด๋ก & ๋น๋
$ git clone https://github.com/yyubin/sprout.git
$ cd sprout && ./gradlew build- ์คํ
Java 21 + CGLIB ํ๋ก์๋
--add-opensํ๋๊ทธ๊ฐ ํ์ํฉ๋๋ค (๊น์ ๋ฆฌํ๋ ์ ์ ์ ๊ฑฐํ ๋๊น์ง):
$ java \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens=java.base/java.io=ALL-UNNAMED \
--add-opens=java.base/java.util=ALL-UNNAMED \
-jar build/libs/sprout.jar์๋ฒ ๋ชจ๋/์ค๋ ๋ ๋ชจ๋ธ์ ์ด์ CLI ์ธ์๊ฐ ์๋ application.yml์์ ์ฝ์ด์ต๋๋ค.
- ์ต์ ์์ (๋ณ๊ฒฝ ์์)
@ComponentScan("app")
public class DemoApplication {
public static void main(String[] args) throws Exception {
SproutApplication.run(DemoApplication.class);
}
}@Aspect
public class LoggingAspect {
@Around(pointcut = "app..*Service.*")
public Object logExecTime(ProceedingJoinPoint pjp) throws Throwable {
long t0 = System.nanoTime();
try {
return pjp.proceed();
} finally {
System.out.printf("%s took %d ยตs%n", pjp.getSignature().toLongString(),
(System.nanoTime()-t0)/1_000);
}
}
}@Controller
@RequestMapping("/api")
public class TestController {
private final GreetingService svc;
public TestController(GreetingService svc) { this.svc = svc; }
@GetMapping("/hello/{id}")
public MessageDto hello(@PathVariable Long id) {
return new MessageDto(svc.greet(id));
}
}Sprout๋ ์์ ์ AppConfig๋ฅผ ํตํด application.yml์ ๋ก๋ํฉ๋๋ค. ์ค์ฒฉ๋ ํค๋ ์ ํ๊ธฐ๋ฒ์ผ๋ก ํด๊ฒฐ๋ฉ๋๋ค (์: server.execution-mode).
author: you
server:
execution-mode: hybrid # nio | hybrid (๊ธฐ๋ณธ๊ฐ: hybrid)
thread-type: virtual # virtual | platform (ํ์ด๋ธ๋ฆฌ๋/๋ธ๋กํน HTTP ์์ปค์๋ง ์ ์ฉ)
thread-pool-size: 150 # thread-type = platform์ผ ๋ ์ฌ์ฉ
sprout:
database:
url: jdbc:mysql://localhost:3306/sprout
username: root
password: change-me| ๋ชจ๋ | HTTP | WebSocket | ์ฌ์ฉ ์ฌ๋ก |
|---|---|---|---|
| blocking | ํ๋ซํผ ์ค๋ ๋ | ํด๋น ์์ | ๋๋ฒ๊น /ํ์ต |
| nio | NIO ์ ๋ ํฐ | NIO | ๋์ ๋์์ฑ/๋ฎ์ ๋ฉ๋ชจ๋ฆฌ |
| hybrid | ๊ฐ์/ํ (HTTP) | NIO (WS) | ํ๋ก๋์ ํ์ต ๊ฒฝํ |
| ์ ํ | ์ฌ์ฉ ์๊ธฐ |
|---|---|
| virtual | ์์ฒญ ํธ๋ค๋ฌ์ ๊ธฐ๋ณธ ์ ํ (๊ถ์ฅ) |
| platform pool | ๋์์ฑ์ ์ ํํด์ผ ํ ๋๋ง (JDBC ๋๋ผ์ด๋ฒ, ๋ธ๋กํน I/O) |
-
AppConfig๊ฐ YAML์ ํ ๋ฒ ์ฝ๊ณ ํฌํผ๋ฅผ ์ ๊ณต:getStringProperty,getIntProperty. -
ServerAutoConfigurationRegistrar๊ฐserver.*ํค๋ฅผ ๊ฒ์ฌํ๊ณ ๋ฑ๋ก:- HTTP ํธ๋ค๋ฌ:
NioHttpProtocolHandler๋๋BlockingHttpProtocolHandler(ํ์ด๋ธ๋ฆฌ๋/๋ธ๋กํน์ฉ) - RequestExecutorService:
VirtualRequestExecutorService(๊ฐ์ ์ค๋ ๋) ๋๋RequestExecutorPoolService(๊ณ ์ ํ)
- HTTP ํธ๋ค๋ฌ:
ํ: ์์ฒญ ํธ๋ค๋ฌ์๋ ๊ฐ์ ์ค๋ ๋๋ฅผ ์ ํธํ์ธ์; ๋์์ฑ์ ์ ํํด์ผ ํ ๋๋ง ๊ณ ์ ํ๋ก ์ ํํ์ธ์ (์: JDBC ๋๋ผ์ด๋ฒ ๋๋ ๋ธ๋กํน I/O).
์ด ๋น๋์ค๋ Sprout์ ์์ ํ ๋ ผ๋ธ๋กํน NIO WebSocket ์คํ์ด ์๋ํ๋ ๋ชจ์ต์ ๋ณด์ฌ์ค๋๋ค.
๋ฐ๋ชจ์์ ๋ณด์ฌ์ง๋ ๊ฒ:
- NIO ๊ธฐ๋ฐ WebSocket ํธ๋์ ฐ์ดํฌ & ํ๋ ์ ์ฒ๋ฆฌ
- ๋์ ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ
- Echo, ๋ธ๋ก๋์บ์คํธ, ์ฑํ ์คํ์ผ ๋ฉ์์ง
- Ping/Pong ํ๋ ์
- ์ฐ์ํ ์ข ๋ฃ ์ฒ๋ฆฌ
- OP_WRITE ํ ๊ธ์ ์ฌ์ฉํ ๋ ผ๋ธ๋กํน ์ฐ๊ธฐ ํ
๋น๋์ค์์ ์ฌ์ฉ๋ ํธ๋ค๋ฌ๋ Sprout์ API๋ก ๊ตฌ์ถ๋ ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ๋ ๋ฒจ WebSocket ํธ๋ค๋ฌ์ ๋๋ค (์๋ ์ฐธ์กฐ).
@Component
@WebSocketHandler("/ws/benchmark")
public class WebSocketBenchmarkHandler {
private static final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(WebSocketSession session) {
sessions.put(session.getId(), session);
}
@OnClose
public void onClose(WebSocketSession session, CloseCode code) {
sessions.remove(session.getId());
}
@MessageMapping("/echo")
public void echo(WebSocketSession session, @Payload String msg) throws IOException {
session.sendText(msg);
}
@MessageMapping("/broadcast")
public void broadcast(WebSocketSession session, @Payload String msg) throws IOException {
for (WebSocketSession s : sessions.values()) {
if (s.isOpen()) s.sendText(msg);
}
}
@MessageMapping("/ping")
public void ping(WebSocketSession session, @Payload String ignored) throws IOException {
session.sendPing("ping".getBytes());
}
}๋ ผ๋ธ๋กํน ์ฐ๊ธฐ ๋ฐ ํ๋ ์ ๋ ๋ฒจ ์ ์ด์ ํจ๊ป Sprout์ NIO ์ ๋ ํฐ ๋ฃจํ์์ ์คํ๋ฉ๋๋ค.
687๊ฐ ํ ์คํธ, 0๊ฐ ์คํจ (100% ํต๊ณผ, Gradle 8.10.1 ยท 2025โ10-27)
ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง (Jacoco):
- ๋ผ์ธ ์ปค๋ฒ๋ฆฌ์ง: 85%
- ๋ธ๋์น ์ปค๋ฒ๋ฆฌ์ง: 75%
์ปค๋ฒ๋ฆฌ์ง ํ์ด๋ผ์ดํธ:
- ํต์ฌ ์ปจํ
์ด๋: ์ค์บ๋, ๋น ๊ทธ๋ํ/์์ ์ ๋ ฌ, ์์ฑ์ ์ฃผ์
,
@Order๋ฆฌ์คํธ ์ฃผ์ - AOP: advice ๋น๋/์ธํฐ์ ํฐ, advisor ๋ ์ง์คํธ๋ฆฌ, ํฌ์ธํธ์ปท ํ์ฑ
- MVC ๋ ์ด์ด: ์์ฒญ ํ์ฑ (๋ผ์ธ/ํค๋/์ฟผ๋ฆฌ), ํธ๋ค๋ฌ ๋งคํ & ํธ์ถ, argument resolver, ์์ธ advice
- ๋ณด์: ์ธ์ฆ provider, ๋น๋ฐ๋ฒํธ ์ธ์ฝ๋ฉ, ์ปจํ ์คํธ ์ ํ, ํํฐ & ๊ถํ ๋ถ์ฌ aspect
- ์๋ฒ ์คํ: HTTP Blocking/NIO/Hybrid ์ ๋ต, executor service (๊ฐ์ vs ํ), ํ๋กํ ์ฝ detector/handler
- WebSocket: ํธ๋์ ฐ์ดํฌ, ํ๋ ์ ์ธ์ฝ๋/ํ์, ping/pong, ๋จํธํ, ๋น๋๊ธฐ ์ฐ๊ธฐ & ์ฐ์ํ ์ข ๋ฃ, dispatcher/resolver
- ์ ํธ๋ฆฌํฐ:
HttpUtils(Content-Length & chunked), ์๋ต ๋ฒํผ ์์ฑ, ๊ธฐํ ํฌํผ
๋๊ตฌ & ์คํ์ผ:
- JUnit 5 + Mockito (JDK-final ํด๋์ค๋ฅผ ์ํ ์ธ๋ผ์ธ/lenient)
- ๋ฌด๊ฑฐ์ด ๋ชจํน๋ณด๋ค ๊ฒฐ์ ๋ก ์ ๋์์ ์ ํธํ๋ ๊ฐ์ง ๊ตฌํ (์: ํ๋ ์ ์ธ์ฝ๋/ํ์)
- ์ค์ ์์ผ ์์ด NIO ๋์์ ๊ฒ์ฆํ๊ธฐ ์ํ Selector/
interestOps์ํ ๊ฐ์ง - ๋น๋ ๋ฆฌํฌํธ:
build/reports/tests/test/index.html
| ๋ฆด๋ฆฌ์ค | ๊ณํ / ์๋ฃ | ๋น๊ณ |
|---|---|---|
| v0.2 | โ AOP ํต์ฌ ์ ๊ณต | @Before/@After/@Around, AspectJ ํฌ์ธํธ์ปท |
| v0.3 | โ ๋ฏธ๋ค์จ์ด & ๊ธ๋ก๋ฒ ์ธํฐ์ ํฐ | ํํฐ + ์ธํฐ์ ํฐ ์ฒด์ธ |
| v0.4 | โ ๋ฐ์ดํฐ ์ก์ธ์ค & ๋ณด์ ํต์ฌ | JdbcTemplate, @Transactional, ์ธ์ฆ/๊ถํ๋ถ์ฌ |
| v0.5 | โ NIO & ํ์ด๋ธ๋ฆฌ๋ ์๋ฒ, ๋น๋๊ธฐ WebSocket | ์ ๋ ํฐ ๋ฃจํ, OP_WRITE ๊ด๋ฆฌ, ์ฐ์ํ ์ข ๋ฃ |
| v1.0 | ๐ฏ ์์ ์ ์ธ API & ํ๋ ์์ํฌ ์ฑ์๋ | ํ๋ก๋์ ์ฌ์ฉ ์ค๋น, ํฌ๊ด์ ์ธ ๋ฌธ์ |
v1.0 ์ดํ ๋ก๋๋งต:
| ๊ธฐ๋ฅ | ์ํ | ์ค๋ช |
|---|---|---|
| ๊ฒฝ๋ ORM | ๐ ๊ณํ๋จ | ์ํฐํฐ ๋งคํ, ์ด๋ ธํ ์ด์ ๊ธฐ๋ฐ ORM, ๊ฐ๋จํ ์ฟผ๋ฆฌ DSL |
| ํ๋ก๋์ ๋๊ตฌ | ๐ ๊ณํ๋จ | ๋ฉํธ๋ฆญ, ๋ชจ๋ํฐ๋ง, ํฅ์๋ ์ฑ๋ฅ ํ๋กํ์ผ๋ง |
| ๊ณ ๊ธ ๊ธฐ๋ฅ | ๐ ๊ณํ๋จ | ํฅ์๋ ๋ณด์, ์บ์ฑ ๋ ์ด์ด, ๊ฒ์ฆ ํ๋ ์์ํฌ |
๋ก๋๋งต์ ์งํฅ์ ์ ๋๋ค. ํ์ต ๊ณํ์ ๋ฐ๋ผ ๋ณ๋ ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค.
- Spring Framework โ ๊ฐ์ฌํฉ๋๋ค..
- Reflections, CGLIB, Objenesis, Jackson โ ๋ฐํ์ ๋ฉํ ํ๋ก๊ทธ๋๋ฐ & ์ง๋ ฌํ ๋ฐฑ๋ณธ.
PR & ์ด์๋ฅผ ํ์ํฉ๋๋ค. ๋ก๋๋งต ํญ๋ชฉ์ ์ ํํ๊ฑฐ๋ ๊ธฐ๋ฅ์ ์ ์ํด์ฃผ์ธ์.
MIT License. LICENSE ์ฐธ์กฐ.
