-
Notifications
You must be signed in to change notification settings - Fork 0
[Config-DI] 싱글톤을 보장하는 싱글톤 컨테이너 개발 #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,127 +11,202 @@ | |
| import http.request.InputStreamHttpRequestConverter; | ||
| import http.response.HttpResponseBufferedStreamConverter; | ||
| import http.response.HttpResponseConverter; | ||
| import web.dispatch.argument.ArgumentResolver; | ||
| import web.dispatch.Dispatcher; | ||
| import web.dispatch.HandlerAdapter; | ||
| import web.dispatch.adapter.DefaultHandlerAdapter; | ||
| import web.dispatch.adapter.SingleArgHandlerAdapter; | ||
| import web.dispatch.argument.ArgumentResolver; | ||
| import web.dispatch.argument.resolver.HttpRequestResolver; | ||
| import web.dispatch.argument.resolver.QueryParamsResolver; | ||
| import web.handler.StaticContentHandler; | ||
| import web.handler.WebHandler; | ||
| import web.renderer.StaticViewRenderer; | ||
| import web.renderer.HttpResponseRenderer; | ||
| import web.renderer.StaticViewRenderer; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class AppConfig { | ||
| //Http | ||
| public HttpRequestConverter httpRequestConverter(){ | ||
| public class AppConfig extends SingletonContainer { | ||
|
|
||
| /** | ||
| * ===== Http ===== | ||
| */ | ||
| public HttpRequestConverter httpRequestConverter() { | ||
| return inputStreamHttpRequestConverter(); | ||
| } | ||
| public HttpResponseConverter httpResponseConverter(){ | ||
|
|
||
| public HttpResponseConverter httpResponseConverter() { | ||
| return httpResponseBufferedStreamConverter(); | ||
| } | ||
|
|
||
| public BufferedReaderHttpRequestConverter httpBufferedReaderRequestConverter(){ | ||
| return new BufferedReaderHttpRequestConverter(); | ||
| public BufferedReaderHttpRequestConverter httpBufferedReaderRequestConverter() { | ||
| return getOrCreate( | ||
| "httpBufferedReaderRequestConverter", | ||
| BufferedReaderHttpRequestConverter::new | ||
| ); | ||
| } | ||
|
|
||
| public HttpResponseBufferedStreamConverter httpResponseBufferedStreamConverter(){ | ||
| return new HttpResponseBufferedStreamConverter(); | ||
| } | ||
| public InputStreamHttpRequestConverter inputStreamHttpRequestConverter(){ | ||
| return new InputStreamHttpRequestConverter(); | ||
| public HttpResponseBufferedStreamConverter httpResponseBufferedStreamConverter() { | ||
| return getOrCreate( | ||
| "httpResponseBufferedStreamConverter", | ||
| HttpResponseBufferedStreamConverter::new | ||
| ); | ||
| } | ||
|
|
||
|
|
||
| //Web | ||
| public Dispatcher dispatcher(){ | ||
| return new Dispatcher( | ||
| webHandlerList(), | ||
| handlerAdapterList(), | ||
| webHandlerResponseHandlerList() | ||
| public InputStreamHttpRequestConverter inputStreamHttpRequestConverter() { | ||
| return getOrCreate( | ||
| "inputStreamHttpRequestConverter", | ||
| InputStreamHttpRequestConverter::new | ||
| ); | ||
| } | ||
|
|
||
| private List<WebHandler> webHandlerList(){ | ||
| return List.of( | ||
| staticContentHandler(), | ||
| registerWithGet(), | ||
| registerWithPost() | ||
| /** | ||
| * ===== Web ===== | ||
| */ | ||
| public Dispatcher dispatcher() { | ||
| return getOrCreate( | ||
| "dispatcher", | ||
| () -> new Dispatcher( | ||
| webHandlerList(), | ||
| handlerAdapterList(), | ||
| webHandlerResponseHandlerList() | ||
|
Comment on lines
61
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 컬렉션 반환 메서드의 싱글톤 캐싱 주의: 만약 런타임에 이 리스트를 수정하면 (예:
|
||
| ) | ||
| ); | ||
| } | ||
| private RegisterWithGet registerWithGet(){ | ||
| return new RegisterWithGet(); | ||
|
|
||
| public List<WebHandler> webHandlerList() { | ||
| return getOrCreate( | ||
| "webHandlerList", | ||
| () -> List.of( | ||
| staticContentHandler(), | ||
| registerWithGet(), | ||
| registerWithPost() | ||
| ) | ||
| ); | ||
| } | ||
| private RegisterWithPost registerWithPost(){ | ||
| return new RegisterWithPost(); | ||
|
|
||
| public RegisterWithGet registerWithGet() { | ||
| return getOrCreate( | ||
| "registerWithGet", | ||
| RegisterWithGet::new | ||
| ); | ||
|
Comment on lines
+78
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 메서드 가시성 변경 (private → public)의 영향: 기존 예를 들어, AppConfig config = new AppConfig();
RegisterWithGet h1 = config.registerWithGet(); // 첫 호출: 캐시 저장
RegisterWithGet h2 = config.registerWithGet(); // 두 번째: 동일 인스턴스 반환이는 싱글톤 보장 측면에서는 긍정적이지만, 캡슐화를 깨뜨립니다. 필요한 진입점만 public으로 유지하고 나머지는 |
||
| } | ||
|
|
||
| private List<HttpResponseRenderer> webHandlerResponseHandlerList(){ | ||
| return List.of( | ||
| staticViewResponseHandler() | ||
| public RegisterWithPost registerWithPost() { | ||
| return getOrCreate( | ||
| "registerWithPost", | ||
| RegisterWithPost::new | ||
| ); | ||
| } | ||
| private StaticContentHandler staticContentHandler(){ | ||
| return new StaticContentHandler(); | ||
|
|
||
| public List<HttpResponseRenderer> webHandlerResponseHandlerList() { | ||
| return getOrCreate( | ||
| "webHandlerResponseHandlerList", | ||
| () -> List.of( | ||
| staticViewResponseHandler() | ||
| ) | ||
| ); | ||
| } | ||
| private StaticViewRenderer staticViewResponseHandler(){ | ||
| return new StaticViewRenderer(); | ||
|
|
||
| public StaticContentHandler staticContentHandler() { | ||
| return getOrCreate( | ||
| "staticContentHandler", | ||
| StaticContentHandler::new | ||
| ); | ||
| } | ||
|
|
||
| public StaticViewRenderer staticViewResponseHandler() { | ||
| return getOrCreate( | ||
| "staticViewResponseHandler", | ||
| StaticViewRenderer::new | ||
| ); | ||
| } | ||
|
|
||
| //Adapter | ||
| public List<HandlerAdapter> handlerAdapterList(){ | ||
| return List.of( | ||
| singleArgHandlerAdapter(), | ||
| defaultHandlerAdapter() | ||
| // ===== Adapter ===== | ||
| public List<HandlerAdapter> handlerAdapterList() { | ||
| return getOrCreate( | ||
| "handlerAdapterList", | ||
| () -> List.of( | ||
| singleArgHandlerAdapter(), | ||
| defaultHandlerAdapter() | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| private SingleArgHandlerAdapter singleArgHandlerAdapter(){ | ||
| return new SingleArgHandlerAdapter( | ||
| argumentResolverList() | ||
| public SingleArgHandlerAdapter singleArgHandlerAdapter() { | ||
| return getOrCreate( | ||
| "singleArgHandlerAdapter", | ||
| () -> new SingleArgHandlerAdapter( | ||
| argumentResolverList() | ||
| ) | ||
| ); | ||
| } | ||
| private DefaultHandlerAdapter defaultHandlerAdapter(){ | ||
| return new DefaultHandlerAdapter(); | ||
|
|
||
| public DefaultHandlerAdapter defaultHandlerAdapter() { | ||
| return getOrCreate( | ||
|
Comment on lines
+128
to
+145
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 싱글톤 의존성 그래프 복잡성: 예를 들어, 현재는 서버 구동 시점에만 로드되므로 문제 없지만, 향후 런타임 재로드나 부분 초기화가 필요하면 의존성 추적이 어려워집니다. 필요시 의존성 명시를 위해 생성자 주입 패턴으로 마이그레이션을 고려하세요. |
||
| "defaultHandlerAdapter", | ||
| DefaultHandlerAdapter::new | ||
| ); | ||
| } | ||
|
|
||
| //Resolver | ||
| public List<ArgumentResolver<?>> argumentResolverList(){ | ||
| return List.of( | ||
| httpRequestResolver(), | ||
| queryParamsResolver() | ||
| // ===== Resolver ===== | ||
| public List<ArgumentResolver<?>> argumentResolverList() { | ||
| return getOrCreate( | ||
| "argumentResolverList", | ||
| () -> List.of( | ||
| httpRequestResolver(), | ||
| queryParamsResolver() | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| private HttpRequestResolver httpRequestResolver(){ | ||
| return new HttpRequestResolver(); | ||
| public HttpRequestResolver httpRequestResolver() { | ||
| return getOrCreate( | ||
| "httpRequestResolver", | ||
| HttpRequestResolver::new | ||
| ); | ||
| } | ||
| private QueryParamsResolver queryParamsResolver(){ | ||
| return new QueryParamsResolver(); | ||
|
|
||
| public QueryParamsResolver queryParamsResolver() { | ||
| return getOrCreate( | ||
| "queryParamsResolver", | ||
| QueryParamsResolver::new | ||
| ); | ||
| } | ||
|
|
||
| //Exception | ||
| public ExceptionHandlerMapping exceptionHandlerMapping(){ | ||
| return new ExceptionHandlerMapping( | ||
| List.of( | ||
| serviceExceptionHandler(), | ||
| errorExceptionHandler(), | ||
| unhandledErrorHandler() | ||
| /** | ||
| * ===== Exception ===== | ||
| */ | ||
| public ExceptionHandlerMapping exceptionHandlerMapping() { | ||
| return getOrCreate( | ||
| "exceptionHandlerMapping", | ||
| () -> new ExceptionHandlerMapping( | ||
| List.of( | ||
| serviceExceptionHandler(), | ||
| errorExceptionHandler(), | ||
| unhandledErrorHandler() | ||
| ) | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| private ServiceExceptionHandler serviceExceptionHandler(){ | ||
| return new ServiceExceptionHandler(); | ||
| public ServiceExceptionHandler serviceExceptionHandler() { | ||
| return getOrCreate( | ||
| "serviceExceptionHandler", | ||
| ServiceExceptionHandler::new | ||
| ); | ||
| } | ||
| private UnhandledErrorHandler unhandledErrorHandler(){ | ||
| return new UnhandledErrorHandler(); | ||
|
|
||
| public UnhandledErrorHandler unhandledErrorHandler() { | ||
| return getOrCreate( | ||
| "unhandledErrorHandler", | ||
| UnhandledErrorHandler::new | ||
| ); | ||
| } | ||
| private ErrorExceptionHandler errorExceptionHandler(){ | ||
| return new ErrorExceptionHandler(); | ||
|
|
||
| public ErrorExceptionHandler errorExceptionHandler() { | ||
| return getOrCreate( | ||
| "errorExceptionHandler", | ||
| ErrorExceptionHandler::new | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package config; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import java.util.function.Supplier; | ||
|
|
||
| public abstract class SingletonContainer { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 정적 맵을 abstract 클래스에서 사용하면 상속 계층 구조 전체에서 공유된다는 점에 주의: 현재 설계에서는 단일 AppConfig만 있으므로 문제가 없지만, 향후 SingletonContainer를 상속받는 다른 설정 클래스가 생기면 모든 싱글톤이 같은 맵에 저장됩니다. 향후 확장성을 위해 다음 중 하나를 고려하세요:
|
||
| private static final Map<String, Object> singletonMap = new HashMap<>(); | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 형변환 경고 억제(@SuppressWarnings)는 타입 안전성을 가릴 수 있습니다: 문자열 기반 key로 인해 런타임 타입 불일치가 발생할 수 있습니다. 예: 개선 방안: |
||
| public <T> T getOrCreate(String name, Supplier<T> factory) { | ||
| Object instance = singletonMap.get(name); | ||
| if (instance != null) { | ||
| return (T) instance; | ||
| } | ||
| T created = factory.get(); | ||
| singletonMap.put(name, created); | ||
| return created; | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기존 동작과 달라진 점:
httpRequestConverter()와httpResponseConverter()는 단순히 다른 메서드를 호출하기만 하므로,getOrCreate로 감싸지 않았습니다. 이로 인해 매번 같은 객체를 반환하지만 리팩토링 의도와 불일치합니다.일관성을 위해 명확히 하세요:
getOrCreate로 감싸기 (싱글톤 명시적 보장)