JSP 엔진

본 장에서는 JSP 엔진의 개념과 기능, 설정 방법에 대해 설명한다.

1. 개요

JSP 엔진은 웹 엔진에 내부적으로 포함되어 있는 형태이며 웹 컨텍스트마다 하나씩 존재한다. 본 장에서는 JSP 표준에 대한 설명은 포함하지 않는다. JSP 작성 등에 대한 자세한 정보는 JSP 표준을 참고한다.

JSP 엔진은 웹 클라이언트가 JSP 페이지를 요청했을 때 해당 페이지를 찾아서 서블릿으로 전환하는 역할을 한다. 서블릿으로 전환하는 과정에서 Java 파일과 SMAP 파일을 생성하고, Java 파일을 컴파일해서 서비스에 이용할 클래스 파일을 생성한다.

JSP 프리컴파일 기능을 사용하지 않았다면 JSP 컴파일은 최초 요청 시점에 수행한다. 따라서 최초 요청의 경우에는 OS 파일 시스템에 접근하는 일이 빈번하게 발생하기 때문에 응답시간이 늦을 수 있다.

NAS(Network Attached Storage)를 사용하는 환경에서 태그와 JSP include 관계때문에 컴파일해야 할 파일이 많은 경우에는 NAS로 많은 요청이 집중되서 응답시간이 지체되는 현상이 발생할 수 있다. 또한 NAS 드라이버 동작에 따라 java.io.IOException이 발생하지 않고 사이즈가 0인 JSP 파일을 읽는 경우도 발생한다. Jasper에서는 JVM File I/O API를 통해서 파일을 읽기 때문에 이런 경우 JSP 파일에 대한 파싱이 실패할 수 밖에 없다.

웹 컨텍스트 내에서의 JSP

JSP 파일은 웹 컨텍스트의 루트 아래에 존재한다. 별도로 디렉터리를 생성해서 패키징할 수도 있고 Servlet 3.0부터 정의한 META-INF/resources/ 디렉터리 아래에 패키징할 수도 있다.

WEB-INF/lib/ 아래의 *.jar 라이브러리 파일들 안에 META-INF/resources/ 디렉터리가 있는 경우에도 JSP 파일을 찾을 수 있다. 단, WEB-INF/ 디렉터리 아래에 있는 JSP 파일은 서비스되지 않는다. 웹 클라이언트가 WEB-INF/ 아래의 파일들에 보안상 접근할 수 없다. 웹 컨텍스트 내부 구조에 대한 자세한 내용은 웹 컨텍스트 내부 구조(WAR 파일 구조)를 참고한다.

META-INF/resources/에 대한 자세한 내용은 Jakarta ServletContext API 문서의 리소스 관련 API 설명을 참고한다.

[주의]

내부 테스트 결과 Oracle JDK 6에서 제공하는 javac 라이브러리가 thread-safe하지 않다. 이에 따라 jeus.servlet.jsp.compile-java-source-concurrently 프로퍼티를 제공한다(기본값: false). 요청 스레드가 .java 파일을 컴파일할 때는 JVM Scope의 Lock을 잡고 수행한다. 만약 JDK에서 제공하는 javac 라이브러리가 thread-safe한 경우에는 다음과 같이 jeus-web-dd.xml에 true로 설정해서 사용해도 된다. 단, 반드시 단일 스레드가 아닌 멀티 스레드 상황에서 JSP 컴파일이 정상적으로 되는지 테스트해야 한다.

<properties>
    <property>
        <key>jeus.servlet.jsp.compile-java-source-concurrently</key>
        <value>true</value>
    </property>
</properties>

2. Apache Tomcat Jasper

JEUS는 기존부터 Tomcat의 JSP Parser인 Japser를 도입해서 사용했으나 패키지 이름을 변경하고 이를 jeus.jar에 포함하여 제공하였다. JEUS 9은 Tomcat 8 기반의 Jasper를 사용하며 org.apache.jasper 패키지의 이름를 그대로 유지하고 별도의 jasper.jar 라이브러리로 패키징하여 제공한다. 이 라이브러리는 다음 경로에 위치한다.

$JEUS_HOME/lib/system/jasper.jar

Jasper를 사용할 때는 다음 사항에 주의한다.

  • jasper.jar는 일부 JEUS에 맞춰서 수정한 것이므로 Tomcat의 것으로 덮어쓰면 안 된다. 이 경우 서버 기동이 실패한다.

  • 웹 애플리케이션에서 WEB-INF/lib 내에 Tomcat의 jasper.jar를 사용하는 경우 jeus-web-dd.xml의 <webinf-first> 옵션을 true로 설정해야 한다. 그렇지 않으면 JEUS에서 사용하는 jasper.jar의 클래스를 사용하게 되어 기대하는 동작과 다를 수 있다.

Tomcat Jasper와 JEUS Jasper의 차이점

위에서 언급한 바와 같이 JEUS는 Tomcat에서 제공하는 Japser를 일부 수정해서 제공한다. 그러므로 사용할 때 다음의 사항을 고려해야 한다.

  • JEUS는 In-memory JSP Compiliation 기능과 같이 Tomcat Jasper에서 제공하지 않는 기능을 제공한다.

  • JEUS에서는 Tag Handler Pool, PageContext Pool을 사용하지 않는다.

Java 소스 컴파일할 때 64KB 메소드 크기 제한 문제

JSP를 작성할 때 하나의 .jsp 파일 내에 내용이 너무 많은 경우 이를 .java 파일로 생성하면 내부 메소드 크기가 64KB를 초과할 수 있다. 이 경우 해당 .java 파일은 Java Languague 표준 규약을 어긴 것이므로 Java 컴파일러로 컴파일되지 않는다. 그러나 .jsp의 내용 중 SQL 문이나 HTML 태그와 같은 문자열이 대부분을 차지하는 경우에는 64KB 제한을 피해갈 수 있다.

애플리케이션이 가지고 있는 web.xml에 다음과 같이 설정한다.

<servlet>
    <servlet-name>jeus.servlet.servlets.JspServlet</servlet-name>
    <servlet-class>jeus.servlet.servlets.JspServlet</servlet-class>
    <init-param>
      <param-name>genStringAsCharArray</param-name>
      <param-value>true</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>jeus.servlet.servlets.JspServlet</servlet-name>
    <url-pattern>*.jsp</url-pattern>
</servlet-mapping>

문자열이 아니라 실제로 Java 코드량이 많은 경우에는 컴파일이 실패할 수 밖에 없다. 이때는 <jsp:include>를 사용하여 응답 내용을 분리해서 구현한다. 다음은 <jsp:include>를 사용한 예이다.

<body>
    Template Page<br/>
    <jsp:include page="module_one.jsp" />
    <jsp:include page="module_two.jsp" />
</body>

3. JSP 엔진 기능

JSP 엔진은 Graceful Reloading, 프리컴파일, 메모리에서의 JSP 컴파일 및 실행 기능 등을 제공한다.

3.1. JSP Graceful Reloading

JSP 파일은 사용 목적이 비즈니스 로직보다는 사용자 뷰에 가깝기 때문에 실제 서비스 운영 중에도 수정할 수 있기를 원하는 경우가 대부분이다. 이런 이유로 JSP를 많이 사용하는 웹 애플리케션의 경우 WAR 형태보다는 디렉터리 형태로 deploy한다.

  • 디렉터리 형태로 deploy한 경우

    디렉터리로 deploy한 상태에서 해당 디렉터리 아래의 JSP 파일을 수정하면 JSP 엔진에서 기존에 컴파일된 클래스 파일의 마지막 수정 시각과 원본 JSP 파일의 수정 시각을 비교해서 컴파일을 수행한다.

    이때 <check-included-jspfile> 설정이 true인 경우에는 요청한 JSP 파일이 변경되지 않았더라도, 해당 JSP 파일이 include한 JSP 파일들이나 TAG 파일들(.tld)을 체크해서 JSP 파일을 재컴파일한다.

    JSP 엔진은 Graceful Reloading을 지원하기 때문에 위와 같이 JSP 파일 수정으로 인해 재컴파일이 발생하더라도 기존의 컴파일된 것으로 지속적으로 서비스가 가능하다.

  • WAR 형태로 deploy한 경우

    WAR 형태로 deploy할 경우 WAR 파일 전체를 리패키징해서 redeploy를 해야 하기 때문에 서비스 운영 측면에서 리스크가 큰 편이다. 하지만 변경하는 JSP 파일이 많은 경우에는 JEUS에서 제공하는 Graceful Redeploy를 고려할 수 있다. Graceful Redeploy에 관한 자세한 사항은 웹 컨텍스트 Redeploy(Graceful Redeploy)를 참고한다.

    JSP 파일 내에서 System.LoadLibrary()를 사용해서 Native Library를 사용할 경우 JSP 리로딩이 실패할 수 있다. JSP 리로딩을 하기 위해서는 JVM 구조상 각 JSP별로 클래스 로더를 생성해야 하며, 리로딩할 때 클래스 로더를 교체해야 한다. Native Library에 관한 레퍼런스는 JVM이 클래스 로더에서 관리하며 클래스 로더 인스턴스가 GC(Garbage Collection)될 때 해당 레퍼런스를 정리한다. 그렇기 때문에 JSP의 클래스 로더를 교체하는 시점에 맞춰서 JVM GC가 발생하지 않으면 JSP 리로딩이 실패하게 된다. JEUS에서는 JVM GC를 컨트롤할 수 없고, JVM 내부적인 구조에 따라 발생하는 문제이므로, 이를 해결할 방법이 없다. 따라서 Native Library 로딩 작업은 부팅할 때에 한 번만 하도록 구현하는 것이 바람직하다. 그리고 Native Library를 변경하는 경우에는 JVM 내부적으로 라이브러리 리로딩이 된다는 보장을 JEUS에서 할 수 없다.

3.2. JSP 프리컴파일

JSP 프리컴파일은 웹 컨텍스트의 JSP 페이지들을 미리 컴파일할 수 있는 기능이다. 이는 appcompiler 스크립트나 콘솔 툴의 precompile-jsp(약어 jspc) 명령어를 통해서 가능하다. appcompiler는 오프라인 상태에서도 JSP 프리컴파일이 가능하고, precompile-jsp는 JEUS에 deploy된 모듈에 대해서 프리컴파일을 수행할 수 있다. 이 기능을 사용하면 JSP가 처음 요청되었을 때의 성능을 향상시킬 수 있다. appcompiler나 콘솔 툴의 precompile-jsp 명령어에 대한 자세한 내용은 각각 JEUS Reference 안내서의 appcompilerprecompile-jsp를 참고한다.

3.3. 메모리에서의 JSP 컴파일 및 실행 기능

NAS를 사용해서 여러 개의 웹 엔진이 이를 공유해서 하나의 소스로 동일한 서비스를 수행하는 경우 JSP 컴파일 시점에 필요한 OS 파일 시스템 접근 작업으로 인해 서비스 지연 현상 또는 에러가 발생할 수 있다. 이러한 문제점을 해결하기 위해서 JSP 프리컴파일, JSP Graceful Reloading 기능을 제공하지만 이를 좀 더 근본적으로 해결하기 위해서 메모리에서의 JSP 컴파일 및 실행 기능을 제공한다.

JSP 엔진은 요청 처리 스레드에서 JSP 컴파일의 결과물인 .java 파일 및 .class 파일을 저장하지 않고 모두 메모리에 두고 사용한다. 따라서 .jsp 파일의 메타 데이터와 .jsp 파일의 콘텐츠를 읽는 작업 이외에는 파일 시스템에 접근하지 않는다. 이렇게 파일 시스템 접근을 최소화하여 JSP 컴파일 타임에 발생할 수 있는 서비스 지연을 최소화하였다.

그러나 .java 파일 및 .class 파일은 추후 다른 용도를 위해서 필요하다. 이 파일들은 요청 처리 스레드가 아닌 JSP 엔진마다 하나씩 가지고 있는 Background Thread를 이용해서 파일 시스템에 사용한다. 순차적으로 처리하기 때문에 파일 I/O가 파일 시스템에 한 번에 몰릴 확률이 낮다. 하지만 .smap 파일은 사용하지 않는다. 만약 .smap 파일이 필요한 경우에는 기존의 JSP 컴파일 방식을 사용해야 한다.

JSP 엔진은 이 기능을 기본적으로 사용한다. 만약 기존과 같은 방식을 원하는 경우에는 jeus-web-dd.xml에 설정할 수 있다. 자세한 설정 사항은 jeus-web-dd.xml 설정을 참고한다.

4. JSP 엔진 설정

JSP 엔진은 domain.xml을 사용하거나 각 웹 애플리케이션의 jeus-web-dd.xml에 설정할 수 있다.

4.1. 웹 엔진 레벨에서 설정

다음은 domain.xml에서 웹 엔진의 JSP 엔진 설정하는 과정에 대한 설명이다.

<web-engine>
    <jsp-engine>
        <check-included-jspfile>true</check-included-jspfile>
        <keep-generated>true</keep-generated>
        <use-in-memory-compilation>true</use-in-memory-compilation>
        <graceful-jsp-reloading>false</graceful-jsp-reloading>
        <graceful-jsp-reloading-period>30000</graceful-jsp-reloading-period>
        <jsp-work-dir>...</jsp-work-dir>
        <java-compiler>java6</java-compiler>
        <compile-output-dir>...</compile-output-dir>
        <compile-option>...</compile-option>
    </jsp-engine>
</web-engine>

다음은 설정 항목에 대한 설명이다.

항목 설명

Jsp Work Dir

JSP에서 생성된 Java 소스 파일들이 저장되는 루트 디렉터리를 지정한다. 설정했다고 루트 디렉터리에 바로 파일을 생성하는 것은 아니다. JSP 엔진이 속한 도메인, 서버 이름, 그리고 웹 애플리케이션 이름으로 디렉터리를 생성한 후 그 아래에 파일을 생성한다. 즉, 서로 다른 웹 엔진 간에 클래스 파일들을 공유하지 않는다.

'Compile Output Dir' 항목을 설정하지 않은 경우 클래스 파일도 동일한 위치에 생성된다. 기본적으로 다음과 같은 위치에 생성된다.

  • EAR 애플리케이션

    INTERNALGENERATED_HOME/ear1/web1/__jsp_work
  • Standalone 웹 모듈

    INTERNALGENERATED_HOME/web1/__jsp_work

Java Compiler

Java Compiler 실행 명령어이다. JSP의 생성된 Java 소스를 서블릿 클래스로 컴파일할 컴파일러를 지정한다.

기본 설정이 가장 효율적이기 때문에 사용하지 않는 것을 권장한다.

Compile Output Dir

JSP Parser가 생성한 Java 파일을 컴파일한 클래스 파일의 위치이다. 이 클래스 파일을 실제로 서비스에 사용한다.

설정하지 않는 경우 Java 파일과 동일한 위치에 생성된다.

Compile Option

Java 컴파일러 실행 옵션이다.

Check Included Jspfile

JSP 엔진은 기본적으로 요청한 JSP 페이지의 변경 여부뿐만 아니라 <%@ include file=”xxx.jsp” %> directive로 include된 모든 JSP 파일들 및 태그 파일들에 대해 변경되었는지 확인해서 컴파일한다.

만약 false로 설정하면 요청한 JSP 페이지의 변경 여부만 확인하여 컴파일한다.

Keep Generated

JSP Parser가 생성한 Java 파일 및 SMAP 파일을 유지하는 옵션이다.

디버깅할 때 유용하고, false로 설정하면 파일을 생성한 후 삭제하는 것이기 때문에 특별한 이유가 없다면 성능을 위해 별도의 설정을 하지 않는 것을 권장한다.

Graceful Jsp Reloading

JSP 파일이 변경된 경우 지정된 주기마다 이를 감지하여 JSP 페이지 인스턴스를 새로 생성한다.

Graceful Jsp Reloading Period

Graceful Jsp Reloading이 동작되는 주기를 설정한다. (단위: ms)

Use In Memory Compilation

서비스 중인 JSP 파일을 새로 컴파일해야 할 때 .java 및 .class 파일을 메모리에 생성해서 컴파일하고 이를 실행하는 기능이다. 자세한 사항은 메모리에서의 JSP 컴파일 및 실행 기능을 참고한다.

4.2. jeus-web-dd.xml 설정

JSP 엔진 설정은 jeus-web-dd.xml에서도 가능하다.

웹 컨텍스트 설정 파일 : <jeus-web-dd.xml>
<jeus-web-dd xmlns="http://www.tmaxsoft.com/xml/ns/jeus" version="9">
    <enable-jsp>true</enable-jsp>
    <jsp-engine>
       <jsp-work-dir>/home/user1/myjspwork/</jsp-work-dir>
       <java-compiler>java6</java-compiler>
       <compile-option>-g:none –verbose</compile-option>
       <compile-encoding>UTF-8</compile-encoding>
       <check-included-jspfile>true</check-included-jspfile>
       <keep-generated>true</keep-generated>
       <use-in-memory-compilation>true</use-in-memory-compilation>
    </jsp-engine>
</jeus-web-dd>

다음은 설정 태그에 대한 설명이다.

태그 설명

<enable-jsp>

false로 설정하면 JSP 기능을 사용하지 않으며, JEUS default servlet에 의해 jsp 파일 내용으로 서비스한다.

<jsp-engine>

웹 컨텍스트에 포함된 JSP 페이지에 대한 설정이다. 만약 jeus-web-dd.xml 파일에 JSP 엔진이 설정되어 있다면, 여기에 설정된 내용이 domain.xml에 설정된 것보다 우선한다.

domain.xml에서 JSP 엔진 설정에 대한 자세한 내용은 웹 엔진 레벨에서 설정을 참고한다.

4.3. JSP 하위 호환성을 위한 웹 컨텍스트 레벨의 옵션 설정

JEUS 4 및 5에서는 사용자의 편의성과 Servlet 2.3 이전에 개발된 애플리케이션을 위해서 표준이 아닌 기능도 제공하고, JSP 문법 체크도 최신 스펙에 비해서 엄격하지 않게 적용하였다. 하지만 JEUS 6부터는 좀 더 엄격한 문법 체크와 JSP 2.1 기능을 제대로 지원하기 위해 Jasper 기반의 JSP Parser로 교체하였다. 하지만 기존 JSP에 대한 하위 호환성을 위해서 기존의 JSP Parser도 지원한다.

따라서 JEUS 4 및 5에서는 문제가 없던 웹 모듈을 deploy하면 여러 가지 에러가 발생할 수 있다. JSP 2.1 등의 최신 스펙을 사용하려면 JSP 컴파일할 때 발생하는 에러 메시지를 확인 후 수정해서 업그레이드해야 한다.

만약 최신 스펙이 필요하지 않고 기존에 개발된 모듈을 그대로 사용하려면 다음과 같이 jeus-web-dd.xml에 JEUS 4 및 5 호환의 JSP Parser가 해당 웹 컨텍스트에만 적용되도록 설정할 수 있다.

JSP 하위 호환성을 위한 웹 컨텍스트 설정 : <jeus-web-dd.xml>
    <properties>
        <property>
            <key>jeus.servlet.jsp.modern</key>
            <value>false</value>
        </property>
    </properties>

jeus-web-dd.xml에 설정한 옵션은 해당 웹 컨텍스트에만 적용되고, 웹 엔진이나 가상 호스트 단위로도 옵션을 설정할 수 있다. 웹 엔진 레벨에서 옵션을 적용하려면 다음과 같은 VM 옵션을 설정한다. VM 옵션의 설정은 JEUS Server 안내서의 서버 추가를 참고한다.

-Djeus.servlet.jsp.modern=false

VM 옵션은 가상 호스트, 웹 컨텍스트 레벨에서 치환이 가능하며 jeus-web-dd.xml에 설정한 옵션이 최종적으로 적용된다. jeus-web-dd.xml에 옵션 설정이 없다면 상위의 기본 설정이 적용된다.

이 옵션의 사용을 절대로 권장하지 않으며 부득이하게 하위 호환성이 필요한 경우만 적용하고, 신규 개발은 새로운 애플리케이션으로 표준을 준수하여 개발할 것을 권장한다. 이 호환성 옵션은 차기 버전이나 다음 Fix에서 삭제될 수 있다. 나머지 옵션에 대한 자세한 내용은 JEUS Reference 안내서의 웹 엔진 프로퍼티를 참고한다.

4.4. jarscan.properties 파일 설정

애플리케이션이 deploy될 때 classpath의 모든 .jar에서 .tld 파일을 스캔한다. 애플리케이션이 사용하는 .jar 형태의 라이브러리 중에서 .tld 파일이 포함되어 있지 않는 것을 알고 있을 경우 JEUS_HOME/domains/<domain-name>/config에 있는 jarscan.properties 파일에 해당 .jar를 추가하여 스캔하지 않도록 할 수 있다. 이를 통해서 deploy 시간을 단축시킬 수 있다.

애플리케이션이 deploy될 때 JEUS의 시스템 라이브러리가 classpath에 포함되므로 jarscan.properties 파일에는 .tld 파일을 포함하지 않는 시스템 라이브러리가 기본적으로 설정되어 있다.

불필요한 tld 스캔을 막기 위한 jarscan.properties 파일 설정 : <jarscan.properies>
jeus.servlet.jsp.JarScanFilterImpl.skipSet= \
    lib/system/EJML-core-0.29.jar, lib/system/EJML-dense64-0.29.jar, \
    ...
    lib/shared/wsit-2.3/webservices-rt-2.3.1.jar, lib/shared/wsit-2.3/webservices-tools-2.3.1.jar, \
    WEB-INF/lib/noTldLib.jar