이 기사에서는 JSP 2.0 EL(Expression Language)을 사용하여 JSP 페이지에서 각 요청 및 사용자에 대해 캐시된 컨텐츠를 사용자 정의할 수 있도록 함으로써 위에 설명된 기술을 개선합니다. 캐시 페이지 조각에는 JSP 컨테이너에 의해 평가되지 않는 JSP 표현식이 포함될 수 있습니다. 이러한 표현식의 값은 페이지가 실행될 때마다 사용자 정의 태그에 의해 결정됩니다. 따라서 동적 콘텐츠 생성이 최적화되지만 캐시된 조각에는 기본 JSP 표현 언어를 사용하여 각 요청에 의해 생성된 콘텐츠의 일부가 포함될 수 있습니다. JSP 2.0 EL API의 도움으로 Java 개발자는 표현 언어를 사용하여 이를 가능하게 할 수 있습니다.
콘텐츠 캐싱 VS 데이터 캐싱
콘텐츠 캐싱이 유일한 옵션은 아닙니다. 예를 들어, 데이터베이스에서 추출된 데이터도 캐시될 수 있습니다. 실제로 저장된 정보에는 HTML 마크업이 포함되지 않고 메모리도 덜 필요하므로 데이터 캐싱이 더 효율적일 수 있습니다. 그러나 대부분의 경우 메모리 내 캐싱을 구현하는 것이 더 쉽습니다. 특정 경우에 애플리케이션이 다수의 트랜잭션 객체로 구성되고, 중요한 CPU 리소스를 점유하고, 복잡한 데이터를 생성하고, JSP 페이지를 사용하여 데이터를 표시한다고 가정합니다. 모든 것이 잘 작동하던 어느 날 갑자기 서버 부하가 증가하여 긴급 솔루션이 필요해졌습니다. 이때 트랜잭션 개체와 프레젠테이션 계층 사이에 캐시 계층을 설정하는 것은 매우 훌륭하고 효과적인 솔루션입니다. 그러나 동적 컨텐츠를 캐시하는 JSP 페이지는 매우 빠르고 원활하게 수정되어야 합니다. 단순한 JSP 페이지 편집에 비해 애플리케이션의 비즈니스 로직을 변경하려면 일반적으로 더 많은 작업과 테스트가 필요합니다. 또한 페이지가 여러 복합 소스에서 정보를 집계하는 경우 웹 계층에는 약간의 변경만 필요합니다. 문제는 캐시된 정보가 오래되면 캐시 공간을 비워야 하며 트랜잭션 개체는 이러한 일이 발생하는 시점을 알아야 한다는 것입니다. 그러나 콘텐츠 캐싱이나 데이터 캐싱 또는 기타 최적화 기술을 구현하도록 선택할 때 고려해야 할 요소가 많으며 때로는 개발 중인 프로그램에 대한 특별한 요구 사항이 있습니다. 데이터 캐싱과 콘텐츠 캐싱은 반드시 상호 배타적일 필요는 없으며 함께 사용할 수 있습니다. 예를 들어, 데이터베이스 기반 애플리케이션에서는 데이터베이스에서 추출된 데이터와 해당 데이터를 표시하는 HTML이 별도로 캐시됩니다. 이는 JSP를 사용하여 실시간으로 템플릿을 생성하는 것과 다소 유사합니다. 이 기사에서 논의된 EL 기반 API 기술은 JSP EL을 사용하여 데이터를 렌더링 템플릿에 로드하는 방법을 보여줍니다.
JSP 변수를 사용하여 동적 콘텐츠 캐싱
캐싱 메커니즘을 구현할 때마다 캐시 객체를 저장하는 방법이 필요합니다. 이 기사에서는 String 유형의 객체가 관련됩니다. 한 가지 옵션은 객체 캐싱 프레임워크를 사용하거나 Java 맵을 사용하여 사용자 정의 캐싱 체계를 구현하는 것입니다. JSP에는 캐싱 메커니즘에 필요한 ID-객체 매핑을 제공하기 위해 "범위 속성" 또는 "JSP 변수"라는 것이 이미 있습니다. 페이지 또는 요청 범위를 사용하는 경우 이는 의미가 없지만 애플리케이션 범위에서는 모든 사용자와 페이지가 공유하므로 캐시된 콘텐츠를 저장하기에 좋은 장소입니다. 사용자별로 별도의 캐싱이 필요한 경우에도 세션 범위를 사용할 수 있지만 이는 그리 효율적이지 않습니다. 다음 예제와 같이 JSP 변수를 사용하여 JSTL 태그 라이브러리를 사용하여 콘텐츠를 캐시할 수 있습니다.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:if test="${empty cashedFragment}"> <c:set var="cachedFragment" 범위="응용프로그램"> ... </c:설정></c:if> |
캐시된 페이지 조각은 다음 명령문을 사용하여 결과를 출력합니다.
${applicationScope.cachedFragment} |
각 요청에 대해 캐시 조각을 맞춤설정해야 하면 어떻게 되나요?
예를 들어 카운터를 포함하려면 다음 두 조각을 캐시해야 합니다.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:if test="${sessionScope.counter == null}"> <c:set var="counter"scope="session" value="0"/> </c:if><c:set var="counter" value="${counter+1}" range="session"/> <c:if test="${empty cashedFragment1}"> <c:set var="cachedFragment1" 범위="응용 프로그램"> ... </c:set></c:if><c:if test="${empty cashedFragment2}"> <c:set var="cachedFragment2" 범위="응용 프로그램"> ... </c:설정></c:if> |
다음 문을 사용하여 캐시 콘텐츠를 출력할 수 있습니다.
${cachedFragment1} ${counter} ${cachedFragment2} |
전용 태그 라이브러리의 도움으로 사용자 정의가 필요한 페이지 조각의 캐싱이 매우 쉬워집니다. 위에서 언급한 것처럼 캐시 콘텐츠는 시작 태그(<jc:cache>)와 종료 태그(</jc:cache>)로 캡슐화될 수 있습니다. 각 사용자 정의는 JSP 표현식(${...})을 출력하기 위해 다른 태그(<jc:dynamic expr="..."/>)를 사용하여 표현될 수 있습니다. 동적 콘텐츠는 JSP 표현식을 사용하여 캐시되며 캐시된 콘텐츠가 생성될 때마다 할당됩니다. 아래 섹션에서 이것이 어떻게 달성되는지 확인할 수 있습니다. Counter.jsp는 카운터가 포함된 페이지 조각을 캐시합니다. 카운터는 사용자가 페이지를 새로 고칠 때마다 자동으로 1씩 증가합니다.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="jc" uri="http://devsphere.com/articles/jspcache" %> <c:if test="${sessionScope.counter == null}"> <c:set var="counter"scope="session" value="0"/> </c:if><c:set var="counter" value="${counter+1}" range="session"/> <jc:cache id="cachedFragmentWithCounter"> ... <jc:dynamic expr="sessionScope.counter"/> ... </jc:cache> |
JSP 변수는 사용하기 쉽고 간단한 웹 앱을 위한 좋은 콘텐츠 캐싱 솔루션입니다. 그러나 애플리케이션이 많은 동적 콘텐츠를 생성하는 경우 캐시 크기를 제어할 수 없다는 것은 확실히 문제입니다. 전용 캐시 프레임워크는 캐시 모니터링, 캐시 크기 제한, 캐시 정책 제어 등을 허용하는 보다 강력한 솔루션을 제공할 수 있습니다.
JSP 2.0 표현 언어 API
JSP 컨테이너(예: Tomcat)를 사용하여 JSP에 EL API 표현식을 적용합니다. 페이지에는 값이 할당되어 있으며 Java 코드에서 사용할 수 있습니다. 이를 통해 XML 파일, 텍스트 기반 리소스 및 사용자 정의 스크립트 등에서 JSP EL을 사용하여 웹 페이지 외부에서 개발할 수 있습니다. EL API는 웹 페이지의 표현식이 할당되거나 이와 관련된 표현식이 작성되는 시기를 제어해야 할 때에도 유용합니다. 예를 들어, 캐시된 페이지 조각에는 사용자 정의 JSP 표현식이 포함될 수 있으며 EL API는 캐시된 콘텐츠가 내보내질 때마다 이러한 표현식을 할당하거나 재평가하는 데 사용됩니다.
기사에서는 예제 프로그램을 제공합니다(기사 끝 부분의 리소스 섹션 참조). 이 애플리케이션에는 Java 클래스(JspUtils)와 클래스의 eval() 메소드가 포함되어 있습니다. 이 메소드에는 예상 유형인 JSP 표현식이 있습니다. 표현식과 JSP 컨텍스트 객체. Eval() 메소드는 JSP 컨텍스트에서 ExpressionEvaluator를 가져오고 평가() 메소드를 호출하여 표현식, 표현식의 예상 유형 및 JSP congtext에서 얻은 변수를 전달합니다. JspUtils.eval() 메서드는 표현식의 값을 반환합니다.
패키지 com.devsphere.articles.jspcache; import javax.servlet.jsp.JspContext; import javax.servlet.jsp.JspException; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.el.ELException; import javax.servlet.jsp.el.ExpressionEvaluator; import java.io.IOException;공개 클래스 JspUtils { 공개 정적 객체 평가(문자열 expr, 클래스 유형, JspContext jspContext) throwsJspException { 노력하다 { if (expr.indexOf("${") == -1) return expr; ExpressionEvaluator 평가자= jspContext.getExpressionEvaluator(); return evaluator.evaluate(expr, 유형, jspContext.getVariableResolver(), null); } 잡기(ELException e) { 새로운 JspException(e)을 던져라; } } ... } |
참고: JspUtils.eval()은 주로 표준 ExpressionEvaluator를 캡슐화합니다. expr에 ${가 없으면 JSP 표현식이 없으므로 JSP EL API가 호출되지 않습니다.
TLD(태그 라이브러리 설명자) 파일 생성
JSP 태그 라이브러리에는 태그 이름 지정, 해당 속성 및 태그에서 작동하는 Java 클래스를 사용자 정의하기 위한 TLD(태그 라이브러리 설명자) 파일이 필요합니다. jspcache.tld는 두 개의 사용자 정의 태그를 설명합니다. <jc:cache>에는 캐시된 페이지 조각의 ID와 JSP 범위(항상 저장해야 하는 JSP 페이지의 콘텐츠 범위)라는 두 가지 속성이 있습니다. <jc:dynamic>에는 단 하나의 속성만 있습니다. 즉, 캐시 조각이 출력될 때마다 JSP 표현식을 할당해야 합니다. TLD 파일은 이러한 두 개의 사용자 정의 태그를 다음과 같이 CacheTag 및 DynamicTag 클래스에 매핑합니다.
<?xml 버전="1.0" 인코딩="UTF-8" ?> <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-인스턴스" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jstalibrary_2_0.xsd" version="2.0"> <tlib-버전>1.0</tlib-버전> <짧은 이름>jc<//짧은 이름> <uri>http://devsphere.com/articles/jspcache</uri> <태그> <이름>캐시</이름> <태그-클래스>com.devsphere.articles.jspcache.CacheTag</tag-class> <body-content>스크립트 없음</body-content> <속성> <이름>id</name> <필수>true<//필수> <rtexprvalue>true</rtexprvalue> <//attribute> <attribute> <이름>범위<//이름> <필수>false</필수> <rtexprvalue>false</rtexprvalue> </attribute> <//tag> <tag> <name>dynamic</name> <tag-class> com.devsphere.articles.jspcache.DynamicTag</tag-class> <body-content>empty<//body-content> <속성> <name>expr<//name> <필수>true<//필수> <rtexprvalue>false</rtexprvalue> </attribute> </tag></taglib> |
TLD 파일은 웹 애플리케이션 설명자 파일(web.xml)에 포함되어 있습니다. 이 5개 파일에는 캐시 사용 가능 여부를 나타내는 초기 매개변수도 포함되어 있습니다.
<?xml 버전="1.0" 인코딩="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-인스턴스" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-app_2_4.xsd" version="2.4"> <컨텍스트 매개변수> <매개변수 이름> com.devsphere.articles.jspcache.enabled</param-name> <param-value>true<//param-value> </context-param> <jsp-config> <taglib> <taglib-uri>http://devsphere.com/articles/jspcache</taglib-uri> <taglib 위치>/WEB-INF/jspcache.tld</taglib 위치> </taglib> </jsp-config></web-app> |
<jc:cache>의 작동 메커니즘을 이해합니다.
JSP 컨테이너는 이를 처리하기 위해 JSP 페이지의 각 <jc:cache> 태그에 대해 CacheTag 인스턴스를 생성합니다. JSP 컨테이너는 CacheTag 클래스가 SimpleTagSupport에서 상속하는 setJsp(), setParent() 및 setJspBody() 메소드 호출을 담당합니다. JSP 컨테이너는 또한 조작된 태그의 각 속성에 대해 setter 메소드를 호출합니다. SetId() 및 setScope() 메소드는 속성 값을 전용 필드에 저장합니다. 이 값은 CacheTag() 생성자를 사용하여 기본값으로 초기화되었습니다.
패키지 com.devsphere.articles.jspcache; import javax.servlet.ServletContext; import javax.servlet.jsp.JspContext; import javax.servlet.jsp.JspException; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.SimpleTagSupport; import java.io.IOException; import java.io.StringWriter; 공개 클래스 CacheTag는 SimpleTagSupport를 확장합니다. { 공개 정적 최종 문자열 CACHE_ENABLED = "com.devsphere.articles.jspcache.enabled"; 개인 문자열 ID 개인 int 범위; 개인 부울 캐시 활성화; 공개 CacheTag() { ID = null; 범위 = PageContext.APPLICATION_SCOPE; }공개 무효 setId(문자열 ID) { this.id = 아이디; } 공공 무효 setScope(문자열 범위) { this.scope = JspUtils.checkScope(scope); } ... } setScope() 메소드는 JspUtils.checkScope()를 호출하여 String에서 int 유형으로 변환된 범위의 속성 값을 확인합니다. ...공개 클래스 JspUtils { ... public static int checkScope(문자열 범위) { if ("페이지".equalsIgnoreCase(범위)) PageContext.PAGE_SCOPE를 반환합니다. else if ("요청".equalsIgnoreCase(범위)) PageContext.REQUEST_SCOPE를 반환합니다. else if ("세션".equalsIgnoreCase(범위)) PageContext.SESSION_SCOPE를 반환합니다. else if ("application".equalsIgnoreCase(scope)) PageContext.APPLICATION_SCOPE를 반환합니다. 그렇지 않으면 새로운 IllegalArgumentException이 발생합니다. ( "잘못된 범위: " + 범위) }} |
CacheTag 인스턴스가 태그에 대해 작동할 준비가 되면 JSP 컨테이너는 doTag() 메소드를 호출하고 getJspContext()를 사용하여 JSP 컨텍스트를 얻습니다. 이 객체는 PageContext로 캐스팅되므로 getServletContext() 메서드를 호출할 수 있습니다. 서블릿 컨텍스트는 캐싱 메커니즘이 활성화되었는지 여부를 나타내는 초기화 매개변수의 값을 얻는 데 사용됩니다. 캐싱이 활성화된 경우 doTag()는 id 및 범위 속성 값을 사용하여 캐시된 페이지 조각을 얻으려고 시도합니다. 페이지 조각이 캐시되지 않은 경우 doTag()는 getJspBody().invoke()를 사용하여 <jc:cache> 및 </jc:cache>로 캡슐화된 JSP 코드를 실행합니다. JSP 본문에 의해 생성된 출력은 StringWriter에 버퍼링되고 toStirng() 메서드에 의해 검색됩니다. 이러한 방식으로 doTag()는 JSP 컨텍스트의 setAttribute() 메소드를 호출하여 새 JSP 변수를 생성합니다. 이 변수는 JSP 표현식(${…})을 포함할 수 있는 캐시 콘텐츠를 제어합니다. 이러한 표현식은 jspContext.getOut().print()를 사용하여 콘텐츠를 출력하기 전에 JspUtils.eval()에 의해 할당됩니다. 이 동작은 캐싱이 활성화된 경우에만 발생합니다. 그렇지 않으면 doTag()는 getJspBody().invoke(null)을 통해 JSP 본문을 실행하고 출력 결과는 캐시되지 않습니다.
... 공개 클래스 CacheTag는 SimpleTagSupport를 확장합니다. { ... public void doTag()에서 JspException, IOException이 발생합니다. { JspContext jspContext = getJspContext(); ServletContext 애플리케이션 = ((PageContext) jspContext).getServletContext(); 문자열 캐시EnabledParam= application.getInitParameter(CACHE_ENABLED); 캐시Enabled = 캐시EnabledParam != null && 캐시EnabledParam.equals("true"); if(캐시 활성화) { String cashedOutput= (String) jspContext.getAttribute(id, 범위); if (cachedOutput == null) { StringWriter 버퍼 = 새로운 StringWriter(); getJspBody().invoke(buffer); 캐시된 출력 = buffer.toString(); jspContext.setAttribute(id, 캐시된 출력, 범위); } 문자열 평가 출력 = (문자열) JspUtils.eval(cachedOutput, String.class, jspContext); jspContext.getOut().print(evaluatedOutput); } 그렇지 않으면 getJspBody().invoke(null); } ... } |
단일 JspUtils.eval() 호출은 모든 ${...} 표현식을 평가합니다. 왜냐하면 많은 수의 ${...} 구조를 포함하는 텍스트도 표현식이기 때문입니다. 각 캐시 조각은 복잡한 JSP 표현식으로 처리될 수 있습니다. IsCacheEnabled() 메서드는 doTag()에 의해 초기화된 캐시Enabled 값을 반환합니다.
IsCacheEnabled() 메서드는 doTag()에 의해 초기화된 캐시Enabled 값을 반환합니다. ...공용 클래스 CacheTag는 SimpleTagSupport를 확장합니다. { ... 공개 부울 isCacheEnabled() { return 캐시Enabled; } } |
<jc:cache> 태그를 사용하면 페이지 개발자가 캐시된 페이지 조각의 ID를 선택할 수 있습니다. 이를 통해 캐시된 페이지 조각을 여러 JSP 페이지에서 공유할 수 있으며 이는 JSP 코드를 재사용할 때 유용합니다. 그러나 충돌 가능성을 피하기 위해서는 일부 명명 프로토콜이 여전히 필요합니다. 자동 ID 내부에 URL을 포함하도록 CacheTag 클래스를 수정하면 이러한 부작용을 피할 수 있습니다.
<jc:dynamic>이 수행하는 작업 이해
각 <jc:dynamic>은 DynamicTag 클래스의 인스턴스에 의해 처리되며 setExpr() 메서드는 expr 속성 값을 비공개 필드에 저장합니다. DoTag() 메소드는 JSP 표현식을 생성하고 ${ 접두사 및 } 접미사를 expr 속성 값에 추가합니다. 그런 다음 doTag()는 findAncestorWithClass()를 사용하여 <jc:dynamic> 태그 요소가 포함된 <jc:cache>의 CacheTag 핸들러를 찾습니다. 찾을 수 없거나 캐싱이 비활성화된 경우 JSP 표현식은 JspUtils.eval()을 사용하여 평가되고 값이 출력됩니다. 그렇지 않으면 doTag()는 값이 없는 표현식을 출력합니다.
패키지 com.devsphere.articles.jspcache; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.SimpleTagSupport; import java.io.IOException; 공개 클래스 DynamicTag는 SimpleTagSupport를 확장합니다. { 개인 문자열 expr; 공개 무효 setExpr(문자열 expr) { this.expr = expr; } public void doTag()에서 JspException, IOException이 발생합니다. { 문자열 출력 = "${" + expr + "}"; CacheTag 조상 = (CacheTag) findAncestorWithClass (이것은 CacheTag.class); if (조상 == null || !ancestor.isCacheEnabled()) 출력 = (문자열)JspUtils.eval (출력, String.class, getJspContext()); getJspContext().getOut().print(output); } } |
위 코드를 분석하면 <jc:cache>와 <jc:dynamic>이 협력하여 최대한 효율적인 솔루션을 달성한다는 것을 알 수 있습니다. 캐시를 사용할 수 있는 경우 페이지 조각은 <jc:dynamic>에 의해 생성된 JSP 표현식과 함께 버퍼에 저장되고 CacheTag 값이 할당됩니다. 캐싱이 비활성화되면 캐싱은 의미가 없게 되며 <jc:cache>는 단순히 JSP 본문 부분을 실행하고 DynamicTag는 JSP 표현식에 값을 할당하게 됩니다. 특히 콘텐츠가 변경되고 JSP 페이지가 다시 컴파일되는 개발 프로세스 중에 캐싱을 비활성화해야 하는 경우가 있습니다. 물론 개발 후에는 프로덕션 환경에서 캐싱을 활성화해야 합니다.
요약
콘텐츠 캐싱은 웹 애플리케이션 성능을 향상시키기 위한 매우 사용하기 쉬운 방법입니다. 이 기사에서는 JSP 표현 언어를 사용하여 각 사용자 또는 요청에 대한 캐시 콘텐츠를 사용자 정의하는 데 중점을 둡니다. 이 기사 전반에 걸쳐 간략하게 소개된 태그 라이브러리는 소규모 웹 앱에 적합하며 중간 규모 애플리케이션의 성능을 향상시킬 수 있습니다. 대규모 엔터프라이즈 수준 애플리케이션을 개발하려면 단순히 JSP 변수를 사용하는 대신 더 나은 캐싱 메커니즘을 지원하는 프레임워크 사용을 고려해야 합니다.