팀 프로젝트로 구현했던 '레이저 장기'를 리팩토링 하는 작업을 시작해보려 합니다. 프로젝트를 진행했을 때 시간이 촉박하여 좀 더 고민해보고 싶었던 부분을 생각의 흐름대로 개발한 부분이 있습니다. 또 마지막에 마스터로 부터 받은 피드백도 반영해보고 싶어 빠른 시간 내에 리팩토링을 진행하고 싶었습니다.
오늘의 목표
게임 프로그래밍에서 요청 패스가 들어오면 해당 요청 패스의 Controller로 Request가 들어오게 합니다.
스프링에서 요청 Path에 대해 HandlerMapping이 해당 Controller를 연결해줍니다. 하지만 게임 중에 이용하고 있는 소켓 프로그래밍에선 또 다른 HTTP 요청시 연결이 끊기게 됩니다. 그래서 소켓 프로그래밍 메시지로 요청 패스를 전해받으면 제가 만든 HandlerMapping을 통해 해당 컨트롤러로 연결시켜보려 합니다.
이를 위해선 해당 어노테이션이 있는 Controller를 찾고 이 안에 있는 Method들에 대해서 Map으로 관리할 필요가 있는데요. 이때 필요한 것이 Reflection API입니다. Reflection API는 특정 ClassPath내 클래스 스캔은 물론 그 안의 메서드, 필드까지 다 찾아줄 수 있기 때문에 거의 치트키인데요. 이를 이용해서 MappingHandler를 만들어보겠습니다. 우선 Reflection API를 쉽게 사용하기 위해 사용하는 라이브러리가 있습니다. 이를 추가시켜주면 다양한 Reflection API를 사용할 수 있습니다.
compile('org.reflections:reflections:0.9.10')
@Component
public class GameControllerMappingHandler {
private static final Logger log = getLogger(GameControllerMappingHandler.class);
public static final String ROOT_PACKAGE = "lasermaze";
private static final Map<MessageType, Method> mapper = new HashMap<>();
static {
init();
}
private static void init() {
Reflections reflections = new Reflections(ROOT_PACKAGE);
Set<Class<?>> controllers = reflections.getTypesAnnotatedWith(SocketController.class);
for (Class<?> controller : controllers) {
Method[] methods = controller.getMethods();
registerMethods(methods);
}
}
private static void registerMethods(Method[] methods) {
Arrays.stream(methods)
.filter(method -> method.isAnnotationPresent(RequestMapping.class))
.forEach(method -> mapper.put(_toMessageType(method), method));
}
private static MessageType _toMessageType(Method method) {
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
return new MessageType(requestMapping.value()[0], requestMapping.method()[0]);
}
public void invoke(MessageDto messageDto) throws Exception {
Method method = mapper.get(messageDto.getMessageType());
Object clazz = method.getDeclaringClass().newInstance();
method.invoke(clazz);
}
}
위 코드에서 init()
이 처음에 클래스 로드되고 나서 static 블록 내에서 특정 컨트롤러의 메서드들을 찾아 등록하는 역할을 합니다. 상세하게는 ROOT_PACKAGE
아래 클래스 중 SocketController.class
라는 어노테이션을 가진 컨트롤러를 찾구요. 그 안에서 RequestMapping.class
어노테이션을 가진 메서드들을 찾아 그것의 value와 method를 가지고 MessageType
이라는 객체를 만들어 method와 함께 mapper에 등록합니다.
이후에 키 값을 가지고 mapper 안에 있는 Method를 실행할 때 주의할 점이 있습니다. method.invoke() 할 때 매개변수로 해당 메서드가 들어있는 클래스의 객체를 첫번째 매개변수로 넣어야하는데요. 이때 그냥 method.getClass().newInstance()
를 하면 오류가 발생합니다.
딱 그 매서드가 들어있는 클래스를 찾기 위해선 method.getDeclaringClass().newInstance()
로 써야하는데요. 나중에 매개변수나 생성자 등 찾을 때도 declaring 된 것을 찾아야 한다는 점!! 잊지 말아야 하겠습니다.
'TIL' 카테고리의 다른 글
Todays' Dev Notes(2018-03-11) (0) | 2019.03.12 |
---|---|
Today's Dev Notes(2019-03-10) (0) | 2019.03.11 |
Intellij에서 디버깅 사용하기 (0) | 2019.02.24 |
Today's Dev Notes(2019-02-07) (0) | 2019.02.07 |
Today's Dev Notes(2019-01-23) (0) | 2019.01.23 |