[HTML]
불필요한 JSP 재컴파일을 피하는 방법
by Nagesh Susarla
2005/01/05
개요
JSP(JavaServer Pages) 뉴스 그룹에 관한 가장 일반적인 질문 중 하나는 JSP 재컴파일과 관련된 질문입니다. 원치 않는데도 JSP를 재컴파일해야 하는 작업은 많은 개발자들이 겪는 골칫거리입니다. 이 문서에서는 WebLogic JSP 컨테이너의 내부적인 작동에 대해 먼저 설명한 다음 보기에도 명백한 "원치 않는" 시나리오에 컨테이너의 stale checking 알고리즘을 적용하면서 재컴파일을 유발시키는 시나리오에 대해 설명합니다. 또한 이 문서에서는 JSP 및 서블릿 클래스의 재로딩 작업을 제어하는 파라미터에 대해서도 설명합니다. 이러한 파라미터는 운영 중인 모드에서 실행되는 서버의 경우 강력히 권장됩니다.
JSP 컨테이너의 stale checking 메커니즘
WebLogic에서 JSP는 .class 파일로 컴파일됩니다. 여기서 말하는 stale checking 메커니즘이란 특정 JSP .class 파일이 현재의 JSP 파일 보다 이전 것인지("stale") 여부를 결정하는 데 사용하는 로직을 말합니다. WebLogic JSP 컨테이너는 JSP 및 그 종속 파일을 수정하는 경우에만 재컴파일됩니다. 생성된 Java 코드를 살펴보면 JSP 컨테이너의 내부적인 작동을 가장 잘 이해할 수 있습니다. 그 예로 명령줄 JSP 컴파일러를 사용하여 JSP를 컴파일한 다음 생성된 소스 코드를 살펴보겠습니다. JSP 컴파일러(weblogic.jspc)는 기본 WebLogic Server 설치 키트와 함께 제공됩니다.
foo.jsp라는 간단한 JSP 페이지를 생각해 봅시다.
A simple JSP page
이제 명령줄 JSP 컴파일러를 사용하여 JSP를 컴파일하고 keepgenerated라는 옵션을 지정합니다. 이름에서 알 수 있듯이 이 옵션은JSP 페이지에 해당 Java 코드를 생성하고 그것을 디스크에 보관합니다.
java weblogic.jspc -keepgenerated -d .\WEB-INF\classes foo.jsp
[jspc]warning: expected file /WEB-INF/web.xml not found,
tag libraries cannot be resolved.
<Jul 11, 2004 7:29:26 PM PDT> <Warning> <HTTP>
<BEA-101181> <Could not find web.
xml under WEB-INF in the doc root: ..>
컴파일러는 위 옵션에서 지정한 대로 출력 디렉터리(-d)에 .java 파일 및 해당 .class 파일을 생성합니다. 컴파일러가 생성된 클래스 파일을 jsp_servlet이라는 패키지에 넣어두면 weblogic.xml에서 무시되지만 않는다면 기본 JSP 패키지 접두어가 됩니다. 그러므로 생성된 Java 파일은 .\WEB-INF\classes\jsp_servlet에서 찾을 수 있고 __foo.java라고 부르게 됩니다.
실제로 태그 라이브러리를 사용하여 작업하고 있는 것이 아니기 때문에 컴파일러에서 보낸 web.xml 파일을 찾을 수 없다는 경고 메시지는 무시할 수 있습니다.
생성된 코드(__foo.java)에서 설명해야 할 내용과 가장 관련되는 부분은 아래와 같이 나타나는 staticIsStale() 메서드입니다.
목록1. staticIsStale() 메서드
public static boolean _staticIsStale(weblogic.servlet.jsp.StaleChecker sci) {
if (sci.isResourceStale("/foo.jsp", 1089594167518L, "8.1.2.0",
"America/Los_Angeles"))
return true;
return false;
}
위 코드에서 JSP가 수정되었는지 확인하기 위해 weblogic.servlet.jsp.StaleChecker 인터페이스에서 isResourceStale() 메서드를 호출했습니다. isResourceStale() 메서드에 대한 파라미터는 다음과 같으며 아래에 나타난 순서대로 적용됩니다.
- 확인할 리소스(예: /foo.jsp)
- JSP 페이지의 타임스탬프
- WebLogic 릴리스 빌드 버전
- 사용 중인 시스템의 기본 표준 시간대
JSP 컨테이너는 StaleChecker 인터페이스를 구현하여 _staticIsStale() 메서드를 호출합니다. 이 구현은 목록 1에 나타난 파라미터를 가진 콜백(isResourceStale())을 수신합니다. 이 파라미터를 사용한 구현은 주어진 리소스의 stale 여부를 추론하기 위해 필요한 모든 정보를 수집합니다. JSP 컨테이너는 리소스(파라미터 1) /foo.jsp의 타임스탬프(파라미터 2)가 컴파일된 클래스 파일 안에 저장된 타임스탬프 보다 새 것(크면)이거나 릴리스 빌드 버전이 서로 다르면 JSP .class 파일이 "stale"하다고 판단합니다.
여기서 중요한 결과를 살펴보겠습니다.
JSP 페이지의 타임스탬프는 클래스 파일 내부에 저장되고 컴파일 시 계산되기 때문에 클래스 파일의 타임스탬프를 수정해도 stale checking 프로세스에 아무런 영향을 주지 못합니다. (이것은 가장 잘못 알려진 통념이며 위 예제가 이 통념이 잘못된 것임을 명백하게 반증하기를 바랍니다.)
네 번째 파라미터 즉, 표준 시간대는 배포가 보관된 포맷(.war)인 경우에만 사용됩니다.
WebLogic 릴리스 빌드 버전은 각 서비스 팩에 따라 달라지므로 각 서비스 팩의 모든 JSP를 미리 컴파일해야 합니다. 이러한 요구 사항은 JSP 클래스가 최신 서비스 팩 또는 릴리스에서 컴파일러 버그 수정 또는 런타임 변경의 이점을 이용할 수 있도록 하기 위해 설정되었습니다.
Static Includes의 경우
사람들이 가지는 또 다른 논리적 질문은 JSP 페이지의 static includes 중 하나를 수정했을 경우에도 JSP 컨테이너가 주어진 JSP 페이지를 재컴파일할 것인가라는 문제입니다. 대답은 예입니다. static include 같은 종속 파일을 수정한 경우에도 전체 페이지("컴파일 단위"라고 함)를 재컴파일합니다. 컨테이너가 이러한 종속성을 처리하는 방법을 살펴보기 위해 다음과 같이 baz.inc라는 static include를 가진 JSP를 생각해 봅시다.
목록 2. foo.jsp
A simple jsp page.
<%@ include file="baz.inc"%>
목록 3. baz.inc
--
Simple Static Include
--
foo.jsp에 JSP 컴파일러의 이전 명령줄을 다시 실행하면 아래와 같은 코드를 가진 Java 파일이 만들어집니다. 보시다시피, 종속성(이 경우 baz.inc)을 수정하더라도 전체 JSP 또는 "컴파일 단위"가 재컴파일되어 각 종속성이 _staticIsStale() 메서드에 일괄 처리됩니다. JSP 컨테이너는 루트 JSP 페이지(foo.jsp)가 stale 여부를 나타내는 boolean을 반환할 것으로 예상합니다. 그러므로 생성된 각 JSP 클래스 파일은 모든 종속성 파일도 확인하는 코드를 생성합니다.
public static boolean _staticIsStale(weblogic.servlet.jsp.StaleChecker sci) {
if (sci.isResourceStale("/foo.jsp", 1089616972487L, "8.1.2.0", "America/Los_Angeles"))
return true;
if (sci.isResourceStale("/baz.inc", 1089616984268L, "8.1.2.0", "America/Los_Angeles"))
return true;
return false;
}
요약하면, WebLogic JSP 컨테이너는 각 JSP .class가 자체의 종속성 목록을 유지하도록 하고 원래 각 JSP .class에 따라 JSP(및 그 종속 파일)의 상태(타임스탬프)를 저장합니다. 컨테이너가 JSP .class에 _staticIsStale() 메서드를 호출하면 차례로 단일 리소스의 stale 여부를 결정하는 데 필요한 모든 정보를 전달하는 weblogic.servlet.jsp.StaleChecker.isResourceStale()을 사용하여 JSP 컨테이너로 다시 호출합니다. 이렇게 하면 stale checking 작업이 대폭 간단해지고 각 JSP의 타임스탬프를 별도의 장소에 유지할 필요가 없어집니다.
JSP 재컴파일을 발생시키는 시나리오
JSP 컨테이너가 stale checking을 수행하는 경우 고려해야 할 요소를 살펴보았습니다. 이제 JSP가 재컴파일하게 되는 일반적인 시나리오를 몇 가지 살펴보겠습니다.
빌드 스크립트를 사용하여 파일을 복사하면 JSP의 타임스탬프를 수정할 수 있습니다. 그러면 모든 JSP를 재컴파일하게 됩니다.
모든 JSP가 src라는 디렉터리에 있는 시나리오를 생각해 봅시다. 흔히 빌드 스크립트는 모든 JSP를 복사하고 서블릿 Java 파일을 빌드 디렉터리에 컴파일합니다. 그런 다음 이 스크립트는 src 디렉터리에서 weblogic.jspc를 실행하고 컴파일된 모든 JSP를 빌드 디렉터리에 둡니다. 여기서JSP를 빌드 디렉터리로 복사하면 빌드 스크립트에서 cp –p/-m을 사용하여 파일의 타임스탬프를 유지하지 않는 한 JSP의 타임스탬프를 아주 잘 변경할 수 있습니다. 그리고 이러한 웹 애플리케이션을 빌드 디렉터리에서 서버로 배포하면 JSP 클래스가 배포되기 이전의 JSP 즉, 이 경우 빌드 디렉터리로 복사한 JSP를 사용하여 컴파일되었기 때문에 모든 JSP가 재컴파일됩니다. 이것이 바로 가장 일반적인 재컴파일 사례로, 복사하여 파일 타임스탬프를 유지하도록 하면 이러한 재컴파일을 피할 수 있습니다.
weblogic.xml의 packagePrefix 파라미터를 수정하면 재컴파일하게 됩니다. stale checking 메커니즘은 특정 웹 애플리케이션의 weblogic.xml 파일에서 packagePrefix를 찾아 /foo.jsp의 <packagePrefix>.__foo.class 클래스를 검색합니다. weblogic.jspc를 사용하여 모든 JSP를 사전에 구축하고 웹 애플리케이션의 WEB-INF/classes 디렉터리에 넣어둔 다음 이 웹 애플리케이션 아카이브(WAR)를 서버에 배포했다고 가정해 봅시다. 이 웹 애플리케이션에 foo.jsp라는 JSP 가 있다고 가정해 봅시다. weblogic.xml에 "packagePrefix"가 없을 경우 stale checking 메커니즘은 jsp_servlet.__foo.class 클래스를 찾게 될 것입니다. 이제 weblogic.xml을 수정하고 일명 com.bar라는 패키지 접두어를 추가한 다음 동일한 WAR를 서버에 다시 배포했다고 가정해 봅시다. 이 때 foo.jsp에 액세스하면 stale checking 메커니즘이 "com.foo.__foo.class"라는 클래스를 찾게 되므로 JSP가 재컴파일됩니다. -package 파라미터를 사용하여 weblogic.jspc 명령을 호출하고 동일한 패키지 이름을 사용한다면 이러한 재컴파일을 피할 수 있습니다.
또한 weblogic.xml의 workingDir 파라미터를 수정해도 재컴파일됩니다. 이 경우 JSP 컨테이너는 일반적인 웹 애플리케이션 클래스 경로 이외에 새 "workingDir"에서 JSP 클래스를 찾게 됩니다. 새 버전을 배포하기 이전의 작업 디렉터리에 JSP 클래스가 있었기 때문에 JSP 컨테이너는 이 클래스를 찾을 수 없으므로 요청된 JSP를 재컴파일합니다.
참고: 시나리오 2와 3은 weblogic.xml을 수정할 경우에도 웹 애플리케이션을 다시 빌드하거나 미리 컴파일해야 할 필요성을 명백하게 보여 줍니다. 수정한 후 미리 컴파일된 WAR을 배포하면 JSP는 재컴파일되지 않습니다.
미리 구축된 WAR을 WebLogic Server의 최신 버전에 배포하면 모든 JSP가 재컴파일됩니다. stale checking 메커니즘에 관한 설명에서 이미 설명한 대로, JSP 컨테이너를 버전이 다른 서버에 배포할 경우 모든 JSP를 재컴파일합니다. 이것은 모든 JSP 컴파일러/런타임 향상 또는 주어진 버전 또는 서비스 팩의 버그 수정을 생성된 코드에서 이용할 수 있다는 것을 확인하기 위해 실행됩니다. (이러한 제한이 없다면 JSP 런타임에 메서드 없음 이라는 클래스로 끝나고 말 것입니다.) JSP를 미리 컴파일하는 빌드 스크립트는 배포하려는 서버에서 사용하는 동일한 버전의 weblogic.jar를 사용하는 것이 이상적입니다. 서버와 함께 사용될 패치 또는 롤링 픽스는 빌드 스크립트에서 사용하는 클래스 패스에 추가하는 것이 좋습니다. 한마디로 요약하면 빌드 및 배포 환경이 정확히 같아야 합니다. 그러면 배포 후 불필요한 재컴파일 문제를 방지할 수 있습니다.
stale checking에 대한 추가 제어
컨테이너가 stale checking을 수행할 때 제어하면 컨테이너가 더 잘 수행될 수 있도록 조정할 수 있어서 서블릿 뿐만 아니라 JSP의 응답 시간이 향상됩니다. stale checking을 할 때마다 JSP 컨테이너는 디스크로 이동하여 해당되는 특정 JSP에 대한 마지막 수정 시간을 다시 읽어야 합니다. 이러한 프로세스를 너무 자주 호출할 경우 JSP의 응답 시간에 영향을 미치므로 성능이 저하될 수 있습니다. 그러므로 일반적으로 애플리케이션을 많이 변경하는 개발 시 stale checking을 상당히 활성화시켜 볼 필요가 있습니다. 브라우저에서 새로 고침/다시 로드를 클릭하고 새 페이지를 다시 로드함은 물론 JSP 컨테이너를 재컴파일하도록 하여 특정 JSP에 변경한 사항을 테스트하는 것이 좋습니다. 그러나 운영 중인 모드일 경우 이와 같이 하면 성능이 저하될 수 있습니다.
다음과 같은 파라미터의 기본값이 개발 모드에 가장 적합합니다. 운영 중인 시나리오에 배포할 경우에는 상황에 따라 이를 변경하는 것이 좋습니다.
PageCheckSeconds
이미 컴파일된 JSP에 대한 새로운 요청이 발생할 때마다 컨테이너는 설정 파일(이 경우 weblogic.xml)에서 pageCheckSeconds의 값을 확인하고 최종 stale checking 시간과 현재 시간과의 간격이 pageCheckSeconds 보다 클 경우 stale checking 작업을 수행합니다. 예를 들어pageCheckSeconds 값이 10초로 설정되어 있다고 합시다. foo.jsp에 대한 요청에서 컨테이너는 현재 시간과 최종 stale 검사 시간과의 간격이 ageCheckSeconds 보다 큰지 확인합니다. 이 경우 페이지가 재컴파일되었고 10 초가 지난 후 액세스했다고 가정하면 컨테이너는 클래스가 stale한지 확인합니다. 이 간격 이내에 발생한 foo.jsp에 대한 요청은 모두 stale checking을 하지 않습니다.
개발 모드를 사용하지 않고 JSP 페이지를 매 초(기본값)마다 확인할 필요가 없다면 이 값을 -1(stale checking 절대 수행 안 함) 또는 60초처럼 아주 높은 값으로 변경하는 것이 좋습니다. 그러면 File.lastModified()를 호출하여 JSP에 액세스할 때마다 JSP 클래스를 다시 로드하고 타임스탬프를 확인할 필요가 없습니다.
목록 4. 이 파라미터의 설정 방법을 보여주는 weblogic.xml의 코드
<!DOCTYPE weblogic-web-app PUBLIC "-//BEA Systems, Inc.//DTD Web Application 8.1//EN"
"http://www.bea.com/servers/wls810/dtd/weblogic810-web-jar.dtd">
<weblogic-web-app>
<jsp-descriptor>
<jsp-param>
<param-name>pageCheckSeconds</param-name>
<param-value>10</param-value>
</jsp-param>
</jsp-descriptor>
</weblogic-web-app>
웹 애플리케이션에서 개별적으로 JSP를 절대로 변경하지 않는 운영 중인 환경에서는 서블릿 및 JSP 모두에 대해 stale checking을 절대 수행하지 않도록 컨테이너를 구성하는 것이 가장 좋습니다.
servlet-reload-check-secs
개발 모드에서는 서블릿을 수정하고 WAR의 WEB-INF/classes 디렉터리로 재컴파일한 경우 브라우저에서 요청하면 컨테이너가 서블릿의 최종 버전을 호출할 것으로 예상합니다. 이것을 처리하기 위해 WebLogic 웹 컨테이너는 WEB-INF/classes의 파일이 servlet-reload-check-secs 간격마다 수정되었는지 확인합니다. 이 파라미터의 기본값은 1초입니다. 이 값은 애플리케이션을 다시 배포하지 않고 서블릿 클래스에 대한 최종 변경 사항을 확인하려는 개발 모드에 알맞은 기본값입니다. 그러나 운영 중인 모드 이전에는 이 값을 -1(서블릿 파일 절대 다시 로드 안 함)로 변경해야 합니다. 개별 클래스를 변경하지 않는 운영 중인 모드에서는 항상 servlet-reload-check-secs 값을 -1로 설정하는 것이 가장 좋습니다.
목록 5. servlet-reload-check-secs
값을 -1로 설정한 weblogic.xml
샘플
<!DOCTYPE weblogic-web-app PUBLIC "-//BEA Systems, Inc.//DTD Web Application 8.1//EN"
"http://www.bea.com/servers/wls810/dtd/weblogic810-web-jar.dtd">
<weblogic-web-app>
<container-descriptor>
<servlet-reload-check-secs>-1</servlet-reload-check-secs>
</container-descriptor>
</weblogic-web-app>
JSP 클래스 로더
WebLogic Server가 JSP 클래스를 로드하는 방법을 살펴보는 것으로 토론을 마치겠습니다. 각 JSP는 자체의 클래스 로더(일반적으로 일회용 클래스 로더라고 함)에 로드됩니다. 이 클래스 로더는 웹 애플리케이션 클래스 로더의 자식이며 관련된 JSP 클래스 및 내부 클래스(있을 경우)를 로드합니다. 호기심 많은 독자라면 왜 WebLogic이 모든 JSP를 자체의 클래스 로더에 로드하는 지 궁금할 것입니다. 이런 복잡함이 정말로 필요할까? WebLogic이 웹 애플리케이션 클래스 로더를 사용하면 더 수월하지 않을까? 이런 질문은 합당하며 WebLogic 클래스 로더의 편리함을 추구하는 추종자라면 당연히 질문해 보아야 합니다. 이 문제를 다루려면 웹 애플리케이션에 몇 개의 JSP, 몇 개의 서블릿, 하나의 필터, 태그 핸들러 클래스도 포함된 수 백 개의 유틸리티 클래스가 있다고 가정하고, 이제 이러한 클래스를 모두 단일 클래스 로더에 로드했다고 합시다. 만일 단일 JSP를 수정한 다음 브라우저에서 다시 로드를 클릭하면 다음과 같은 상황이 발생됩니다.
- 이 페이지는 JSP 컨테이너에 의해 재컴파일됩니다.
- 클래스의 이전 버전을 로드하는 데 사용된 전체 웹 애플리케이션 클래스 로더는 버려집니다.
- 새로운 웹 애플리케이션 클래스 로더가 생성되고 모든 서블릿 및 JSP(방금 변경한 JSP도 포함)가 다시 로드되고 다시 초기화됩니다.
Java에서는 새로운 버전의 클래스를 다시 로드하기 위해 클래스 로더를 재사용할 수 없습니다. 오히려 클래스 로더를 버리고 새로운 것을 만들어야 합니다. 이런 이유로 위 시나리오는 상당히 바람직하지 않습니다. 단일 클래스만 변경하더라도 애플리케이션 서버가 상당히 많은 클래스를 다시 로드해야 합니다.
이제 WebLogic이 자신의 클래스 로더 스키마를 구현하는 방법을 살펴보겠습니다. 위에서 언급한 동일한 시나리오에서 JSP를 수정하고 브라우저에서 다시 로드를 클릭하면 서버는 다음 작업을 수행합니다.
- JSP 컨테이너가 이 페이지를 재컴파일합니다.
- 이 JSP 클래스의 이전 버전을 로드하는 데 사용했던 단일 JSP 클래스 로더를 버립니다.
- 웹 애플리케이션 클래스 로더를 부모로 사용하는 새로운 JSP 클래스 로더를 만들어 페이지를 처리합니다.
보시다시피 단일 JSP 클래스만 다시 로드하고, JSP만 약간 수정된 채 전체 웹 애플리케이션 클래스 로더는 수정되지 않고 영향을 받지도 않았습니다. 그러므로 단일 JSP를 수정하는 경우 컨테이너는 이전 클래스 로더를 버리고, 재컴파일하고, 이 JSP에 대해 생성된 클래스만 다시 로드합니다. 이렇게 하면 전체 웹 애플리케이션 클래스 로더를 다시 로드할 필요가 없습니다. 특정 웹 애플리케이션에서 일부 JSP만 자주 변경해야 하는 경우 이러한 방법은 커다란 도움이 됩니다.
결론
JSP 컨테이너의 내부에 관한 이러한 지식이 있으면 JSP를 불필요하게 재컴파일하는 상황을 피할 수 있을 뿐 아니라 pageCheckSeconds 및 servlet-reload-checks-secs 파라미터를 사용하여 페이지 응답 시간을 향상시킬 수 있습니다.
Nagesh Susarla는 BEA Systems의 WebLogic Server 개발 팀의 선임 소프트웨어 엔지니어입니다.
Return to dev2dev.
[/HTML]'Know > Java' 카테고리의 다른 글
Jakarta Commons HttpClient (0) | 2006.01.10 |
---|---|
캐릭터셋 관련 (0) | 2006.01.05 |
유니코드(Unicode)와 유니코드 인코딩 (0) | 2005.08.08 |
올바른 프레임워크의 선택과 사용법 (0) | 2005.07.15 |
리팩토링 냄새표 (0) | 2005.06.26 |