"리플렉션 없음"의 범위

이 원칙은 프레임워크 core와, core가 그 위에 지어진 JSON 라이브러리인 silk-json에 적용된다: 라우팅, 파라미터 추출, JSON 처리는 어디에서도 리플렉션을 쓰지 않는다.

기본 템플릿 엔진은 바로 그 경계에 놓여 있으므로, 그 리플렉션이 어디까지 닿는지 정확히 밝혀 둘 가치가 있다. jte는 모든 템플릿을 Java 클래스로 컴파일하므로, ${deck.title}은 컴파일된 메서드 호출로 실행된다. 모델은 결코 리플렉션 대상이 되지 않으며, 그 표현식의 오타는 조용히 빈 값이 되는 대신 컴파일 에러가 된다. 엔진이 리플렉션을 쓰는 곳은 템플릿 로딩뿐이다: 엔진은 생성된 클래스를 이름으로 찾아 그 render 메서드를 리플렉션으로 호출한다. 그 리플렉션은 빌드가 생성한 클래스에 한정되며, 직접 작성한 클래스에는 결코 닿지 않는다. 이렇게 한정되어 있으므로 네이티브 이미지 설정도 기계적으로 끝난다: jte 자체의 빌드 확장이 바로 그 생성된 클래스들에 대한 리플렉션 설정을 쓰며, 모델 클래스도 프레임워크 클래스도 항목이 필요 없다.

별도 모듈에 있는 템플릿 엔진은 각자 자기 리플렉션을 가져온다: Handlebars는 레코드나 빈에 대해 리플렉션으로 물러나고, FreeMarker는 객체 래퍼를 통해 읽으며, Thymeleaf는 모든 표현식을 OGNL로 평가한다. 이것도 각 엔진을 core 밖에 두는 이유 중 하나다. 모듈을 가져오면 그 리플렉션도 함께 가져온 것이며, 기본 jte에 머무는 애플리케이션은 모델 접근을 리플렉션 없이 유지한다.

spider-silk-jetty-websocket도 같은 방식으로 자기 리플렉션을 가져오며, 그 범위를 좁게 유지한다. Jetty는 엔드포인트의 콜백 각각에 대한 MethodHandle을 잡아 엔드포인트 클래스별로 캐시하는 방식으로 바인딩하고, 이 모듈이 여는 모든 연결은 모듈 자신이 선언한 한 클래스가 맡는다. Jetty가 찾아보는 것은 그 하나의 클래스다. 애플리케이션이 작성한 WebSocketHandler는 인터페이스 호출로 불리며 결코 리플렉션 대상이 되지 않는다.

silk-json-processor가 생성하는 코덱은 이 규칙에서 core 쪽에 머문다. 프로세서는 javac 안에서 실행되고, 써내는 코드는 타입 자신의 접근자와 생성자를 부르며, 런타임에 이름으로 찾는 것은 없는데, 이는 jte가 템플릿에서 택한 것과 같은 절충이다. 애플리케이션이 추가하는 JSON 바인딩 라이브러리는 템플릿 모듈과 같은 조건에 놓인다: 가져오면 그 리플렉션도 함께 가져온 것이다. Jackson과 Gson은 필드를 리플렉션으로 읽고 쓰므로, 바인딩되는 모든 타입은 네이티브 이미지 설정에 항목이 필요하다. spider-silk-jackson은 Jackson을 코어의 JsonWriter와 JsonReader 뒤에 두며, Jackson의 리플렉션은 코어가 아니라 그 모듈에 머문다. avaje-jsonb는 컴파일 시점에 타입별 어댑터를 생성하므로 silk-json의 코덱처럼 항목이 필요 없다. 라이브러리에는 어느 쪽이든 core가 관여하지 않는다: 이음새(seam)는 WebResponse.rawJson(String)과 req.bodyStream()이며, 손으로 쓴 라이터나 생성된 코덱에 머무는 애플리케이션은 와이어 포맷을 리플렉션 없이 유지한다.

예제 프로젝트의 리포지토리는 spring-jdbc의 DataClassRowMapper를 택하며, 이는 도메인 레코드를 리플렉션으로 생성한다. 이는 프레임워크가 아니라 예제의 선택이며, 경계가 어디를 지나는지 보여주기 위해 의도적으로 유지한 것이다. 네이티브 이미지에서는 이 선택의 대가로 리플렉션 설정이 필요하며, 이 설정은 도메인 패키지 하나에 한정되고 빌드가 생성한다. core는 거기에 단 하나의 항목도 보태지 않는다.