여덟 번째 수업 이모저모

  • 수업은 2017-04-27 19:00:00 +0900에 시작.
  • 오늘은 Reflection 연습을 하고, annotation 기반의 MVC Framework를 만들어 본다.
    • Reflection에 익숙한 사람은 Framework 만들기로 바로 들어가기로 한다.
    • 나는 Reflection에 익숙한 편이라고 스스로 판단하고 Framework 만들기로 바로 들어갔다.

단상 : 삶과 프로그래밍

  • 몰입
    • 몰입은 단순히 작업 능률에만 영향을 주는 것이 아니라, 삶과 행복과도 관련이 있다.
    • 그렇다면 어떻게 몰입할 것인가?

Reflection

나는 Java로 코드를 작성할 때 Reflection을 즐겨 쓰는 편이지만 Reflection을 썩 좋아하지는 않는다. Reflection을 써서 코딩하고 있다 보면 Java의 아이덴티티가 희석되는 느낌이 들기 때문이다.

나는 Java 문법상의 한계가 Reflection에서 드러난다고 생각한다. 그리고 Reflection이 Java의 장점에 해를 끼친다고 본다.

나는 딱히 자신 있는 언어가 없고, Java에 대해서도 아직 초심자에 속한다고 생각한다. 정적 타입 언어보다는 동적 타입 언어가 더 성격에 맞는 편이다. 그럼에도 불구하고 뭔가 새로운 것을 만들 때, 나는 Java를 항상 중요한 선택지 중의 하나로 고려한다. 왜냐하면 Java는 컴파일 타임에서 꽤 많은 실수를 걸러낼 수 있기 때문이다. 그게 가능하기 때문에 Java IDE의 신뢰할 수 있는 대규모 리팩토링이나 스마트한 자동완성도 가능하다.

그런데 Reflection을 적극적으로 사용하게 되면 컴파일 타임에서 얻을 수 있는 이득을 상당부분 포기해야 한다. 내 기준으로 Java를 선택할 중요한 이유가 사라지는 셈이다.

하지만 그럼에도 불구하고 Java는 현대 산업에 있어 중요한 언어이고 그러한 Java의 한계를 극복하게 해 주는 Reflection은 주요 프레임워크들에 의해 널리 사용되고 있다. 물론 Reflection이 제한 없이 사용되는 건 아닌 모양이다. Reflection은 프로그램 가동시에만 사용하고, 가능한 한 검증된 라이브러리에 수록된 것만 사용해야 한다는 암묵적인 원칙이 공유되고 있는 느낌이다.

폴 그레이엄이 Java를 두고 실수 예방에 초점을 맞춘 언어라고 했던 게 생각난다. 폴 그레이엄은 Java를 까면서 그런 말을 했지만, 사실 Java는 실수 예방에 굉장히 뛰어난 언어이기 때문에 주류 언어가 되었고 앞으로도 꽤 오랫동안 산업계에서 사랑받을 거라고 생각한다.

그러고보니 래리 월은 Java가 좋은 의미의 현대판 Cobol이라 했었던 것 같다. (어느 문서에서 말했는지는 잘 기억이 안 나는데 링크를 찾아다 붙여놓을 예정이다)

MVC Framework 구현

  • 구현 내용은 GitHub Repository에 올려두었다.
  • Reflection을 사용해서 @Controller 어노테이션과 @RequestMapping 어노테이션을 수집하고, 그에 따라 요청을 처리하도록 수정하였다. 아래는 UserController.java의 내용을 축약한 것이다.
@Controller("/users")
public class UserController extends AbstractController {

    private static final Logger log = LoggerFactory.getLogger(UserController.class);
    private UserDao userDao = UserDao.getInstance();

    @RequestMapping("")
    public ModelAndView list(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 생략
    }

    @RequestMapping("/form")
    public ModelAndView form(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 생략
    }

    @RequestMapping(value = "/create", method = RequestMethod.POST)
    public ModelAndView create(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 생략
    }

    @RequestMapping(value = "/update", method = RequestMethod.POST)
    public ModelAndView update(HttpServletRequest req, HttpServletResponse response) throws Exception {
        // 생략
    }

    @RequestMapping("/updateForm")
    public ModelAndView updateForm(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 생략
    }

    @RequestMapping("/profile")
    public ModelAndView profile(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 생략
    }
}
  • url에 중복이 많아서 Controller에 prefix를 지정할 수 있도록 작업해 봤다.
  • 하나하나의 메서드가 각각의 Request를 담당하게 되니 여러 Controller 클래스를 하나로 합칠 수 있었다.
  • 이정도면 일단 만족. Spring이랑 비슷한 모양이 나오네.
  • 아, DispatcherServlet은 다음과 같은 모양을 갖추게 되었다.
@WebServlet(name = "dispatcher", urlPatterns = "/", loadOnStartup = 1)
public class DispatcherServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);

    private ControllerMapping controllerMap;

    @Override
    public void init() throws ServletException {
        controllerMap = new ControllerMapping(core.annotation.Controller.class, "next.controller");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String requestUri = req.getRequestURI();
        logger.debug("Method : {}, Request URI : {}", req.getMethod(), requestUri);

        final ControllerData controllerData = controllerMap.get(req.getMethod(), req.getRequestURI());
        final ModelAndView mav = controllerData.execute(req, resp);
        final View view = mav.getView();

        try {
            view.render(mav.getModel(), req, resp);
        } catch (Exception e) {
            logger.error("Exception : {}", e);
            throw new ServletException(e.getMessage());
        }
        return;
    }
}

Links

링크를 찾아 일일이 달아놓는 건 꽤 귀찮은 일이지만, 한편으로는 꽤 재미있는 일이기도 하다.