[performance] JSF가 게터를 여러 번 호출하는 이유

다음과 같이 outputText 구성 요소를 지정한다고 가정 해 보겠습니다.

<h:outputText value="#{ManagedBean.someProperty}"/>

getter for someProperty가 호출 될 때 로그 메시지를 인쇄 하고 페이지를로드하는 경우 getter가 요청 당 두 번 이상 호출되고 있음을 알면 사소합니다.

DEBUG 2010-01-18 23:31:40,104 (ManagedBean.java:13) - Getting some property
DEBUG 2010-01-18 23:31:40,104 (ManagedBean.java:13) - Getting some property

의 값 someProperty이 계산 비용이 비싸면 문제가 될 수 있습니다.

나는 조금 구글 검색하고 이것이 알려진 문제라고 생각했다. 한 가지 해결 방법은 검사를 포함하고 이미 계산되었는지 확인하는 것입니다.

private String someProperty;

public String getSomeProperty() {
    if (this.someProperty == null) {
        this.someProperty = this.calculatePropertyValue();
    }
    return this.someProperty;
}

이것의 주요 문제는 필요없는 개인 변수는 말할 것도없고 많은 상용구 코드를 얻는다는 것입니다.

이 방법의 대안은 무엇입니까? 너무 많은 불필요한 코드없이 이것을 달성 할 수있는 방법이 있습니까? JSF가 이런 식으로 동작하지 못하게하는 방법이 있습니까?

입력 해 주셔서 감사합니다!



답변

이는 지연된 표현식의 특성으로 인해 발생합니다 #{}( ${}JSP 대신 Facelets를 사용하는 경우 “레거시”표준 표현식이 정확히 동일하게 작동 함). 지연된 표현식은 즉시 평가 되지 않지만 ValueExpression객체 로 작성되며 표현식 뒤의 getter 메소드는 코드가 호출 할 때마다 실행됩니다 ValueExpression#getValue().

이것은 일반적으로 컴포넌트가 입력 컴포넌트인지 출력 컴포넌트인지에 따라 JSF 요청-응답주기 당 1 ~ 2 회 호출됩니다 ( 여기 학습 ). 그러나 JSF 구성 요소 (예 : <h:dataTable><ui:repeat>) 를 반복 하거나 rendered속성 과 같은 부울 표현식으로 사용하면이 수가 더 많아 질 수 있습니다 . JSF (특히 EL)는 각 호출에서 다른 값을 반환 할 수 있으므로 (예를 들어 현재 반복되는 데이터 테이블 행에 종속 된 경우) EL 표현식의 평가 결과를 전혀 캐시하지 않습니다 .

EL 표현식을 평가하고 getter 메소드를 호출하는 것은 매우 저렴한 조작이므로 일반적으로 전혀 걱정하지 않아도됩니다. 그러나 어떤 이유로 getter 메소드에서 비싼 DB / 비즈니스 로직을 수행하면 스토리가 변경됩니다. 이것은 매번 다시 실행됩니다!

JSF 지원 Bean의 Getter 메소드 는 Javabeans 스펙에 따라 이미 준비된 특성 만 리턴 하고 더 이상 아무것도 리턴하지 않도록 설계해야합니다 . 고가의 DB / 비즈니스 로직을 전혀 사용하지 않아야합니다. 이를 위해 콩 및 / 또는 (액션) 리스너 메소드를 사용해야합니다. 요청 기반 JSF 라이프 사이클의 특정 시점에서 한 번만 실행 되며 정확히 원하는 것입니다.@PostConstruct

다음은 속성을 사전 설정 /로드하는 다른 모든 올바른 방법에 대한 요약입니다 .

public class Bean {

    private SomeObject someProperty;

    @PostConstruct
    public void init() {
        // In @PostConstruct (will be invoked immediately after construction and dependency/property injection).
        someProperty = loadSomeProperty();
    }

    public void onload() {
        // Or in GET action method (e.g. <f:viewAction action>).
        someProperty = loadSomeProperty();
    }

    public void preRender(ComponentSystemEvent event) {
        // Or in some SystemEvent method (e.g. <f:event type="preRenderView">).
        someProperty = loadSomeProperty();
    }

    public void change(ValueChangeEvent event) {
        // Or in some FacesEvent method (e.g. <h:inputXxx valueChangeListener>).
        someProperty = loadSomeProperty();
    }

    public void ajaxListener(AjaxBehaviorEvent event) {
        // Or in some BehaviorEvent method (e.g. <f:ajax listener>).
        someProperty = loadSomeProperty();
    }

    public void actionListener(ActionEvent event) {
        // Or in some ActionEvent method (e.g. <h:commandXxx actionListener>).
        someProperty = loadSomeProperty();
    }

    public String submit() {
        // Or in POST action method (e.g. <h:commandXxx action>).
        someProperty = loadSomeProperty();
        return "outcome";
    }

    public SomeObject getSomeProperty() {
        // Just keep getter untouched. It isn't intented to do business logic!
        return someProperty;
    }

}

당신이해야합니다 하지 당신이 그런 CDI와 같은 프록시를 사용하는 콩 관리 프레임 워크를 사용하는 경우가 여러 번 호출 할 수 있기 때문에 작업에 빈의 생성자 또는 초기화 블록을 사용합니다.

제한적인 디자인 요구 사항으로 인해 실제로 다른 방법이 없다면 getter 메소드 내부에 게으른 로딩을 도입해야합니다. 즉, 속성이 null인 경우 속성 을로드하고 속성에 할당하고 그렇지 않으면 반환합니다.

    public SomeObject getSomeProperty() {
        // If there are really no other ways, introduce lazy loading.
        if (someProperty == null) {
            someProperty = loadSomeProperty();
        }

        return someProperty;
    }

이런 식으로 모든 단일 getter 호출에서 고가의 DB / 비즈니스 로직이 불필요하게 실행되는 것은 아닙니다.

또한보십시오:


답변

JSF 2.0을 사용하면 시스템 이벤트에 리스너를 연결할 수 있습니다

<h:outputText value="#{ManagedBean.someProperty}">
   <f:event type="preRenderView" listener="#{ManagedBean.loadSomeProperty}" />
</h:outputText>

또는 JSF 페이지를 f:view태그로 묶을 수 있습니다

<f:view>
   <f:event type="preRenderView" listener="#{ManagedBean.loadSomeProperty}" />

      .. jsf page here...

<f:view>


답변

Spring AOP로 JSF bean getter를 캐시하는 방법에 대한 기사 를 작성했습니다 .

MethodInterceptor특수 주석으로 주석이 달린 모든 메소드를 가로채는 간단한 것을 만듭니다 .

public class CacheAdvice implements MethodInterceptor {

private static Logger logger = LoggerFactory.getLogger(CacheAdvice.class);

@Autowired
private CacheService cacheService;

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {

    String key = methodInvocation.getThis() + methodInvocation.getMethod().getName();

    String thread = Thread.currentThread().getName();

    Object cachedValue = cacheService.getData(thread , key);

    if (cachedValue == null){
        cachedValue = methodInvocation.proceed();
        cacheService.cacheData(thread , key , cachedValue);
        logger.debug("Cache miss " + thread + " " + key);
    }
    else{
        logger.debug("Cached hit " + thread + " " + key);
    }
    return cachedValue;
}


public CacheService getCacheService() {
    return cacheService;
}
public void setCacheService(CacheService cacheService) {
    this.cacheService = cacheService;
}

}

이 인터셉터는 스프링 구성 파일에서 사용됩니다.

    <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    <property name="pointcut">
        <bean class="org.springframework.aop.support.annotation.AnnotationMatchingPointcut">
            <constructor-arg index="0"  name="classAnnotationType" type="java.lang.Class">
                <null/>
            </constructor-arg>
            <constructor-arg index="1" value="com._4dconcept.docAdvance.jsfCache.annotation.Cacheable" name="methodAnnotationType" type="java.lang.Class"/>
        </bean>
    </property>
    <property name="advice">
        <bean class="com._4dconcept.docAdvance.jsfCache.CacheAdvice"/>
    </property>
</bean>

그것이 도움이되기를 바랍니다!


답변

원래 PrimeFaces 포럼 @ http://forum.primefaces.org/viewtopic.php?f=3&t=29546에 게시 됨

최근에, 나는 앱의 성능을 평가하고, JPA 쿼리를 튜닝하고, 동적 SQL 쿼리를 명명 된 쿼리로 대체하고, 오늘 아침에 getter 메소드가 Java Visual VM에서 HOT SPOT보다 더 많은 것을 인식했습니다. 내 코드 (또는 대부분의 코드).

게터 방법 :

PageNavigationController.getGmapsAutoComplete()

index.xhtml의 ui : include에 의해 참조 됨

아래에서 PageNavigationController.getGmapsAutoComplete ()가 Java Visual VM의 HOT SPOT (성능 문제)임을 알 수 있습니다. 자세히 살펴보면, 화면 캡처에서 PrimeFaces 게으른 데이터 테이블 게터 메소드 인 getLazyModel ()이 핫스팟임을 알 수 있습니다. 최종 사용자가 많은 ‘게으른 데이터 테이블’유형의 물건 / 작업 / 태스크 앱에서. 🙂

Java Visual VM : HOT SPOT 표시

아래의 (원본) 코드를 참조하십시오.

public Boolean getGmapsAutoComplete() {
    switch (page) {
        case "/orders/pf_Add.xhtml":
        case "/orders/pf_Edit.xhtml":
        case "/orders/pf_EditDriverVehicles.xhtml":
            gmapsAutoComplete = true;
            break;
        default:
            gmapsAutoComplete = false;
            break;
    }
    return gmapsAutoComplete;
}

index.xhtml에서 다음에 의해 참조됩니다.

<h:head>
    <ui:include src="#{pageNavigationController.gmapsAutoComplete ? '/head_gmapsAutoComplete.xhtml' : (pageNavigationController.gmaps ? '/head_gmaps.xhtml' : '/head_default.xhtml')}"/>
</h:head>

해결책 : 이것은 ‘getter’메서드이므로 메소드를 호출하기 전에 코드를 이동하고 gmapsAutoComplete에 값을 할당하십시오. 아래 코드를 참조하십시오.

/*
 * 2013-04-06 moved switch {...} to updateGmapsAutoComplete()
 *            because performance = 115ms (hot spot) while
 *            navigating through web app
 */
public Boolean getGmapsAutoComplete() {
    return gmapsAutoComplete;
}

/*
 * ALWAYS call this method after "page = ..."
 */
private void updateGmapsAutoComplete() {
    switch (page) {
        case "/orders/pf_Add.xhtml":
        case "/orders/pf_Edit.xhtml":
        case "/orders/pf_EditDriverVehicles.xhtml":
            gmapsAutoComplete = true;
            break;
        default:
            gmapsAutoComplete = false;
            break;
    }
}

테스트 결과 : PageNavigationController.getGmapsAutoComplete ()는 더 이상 Java Visual VM에서 HOT SPOT이 아닙니다 (더 이상 표시되지 않음).

많은 전문가 사용자들이 주니어 JSF 개발자들에게 ‘getter’메소드에 코드를 추가하지 말라고 권고했기 때문에이 주제를 공유했습니다. 🙂


답변

CDI를 사용하는 경우 생산자 방법을 사용할 수 있습니다. 여러 번 호출되지만 첫 번째 호출의 결과는 Bean 범위에 캐시되며 무거운 오브젝트를 계산하거나 초기화하는 게터에 효율적입니다! 자세한 내용은 여기를 참조 하십시오 .


답변

아마도 AOP를 사용하여 게터의 결과를 구성 가능한 시간 동안 캐시하는 일종의 Aspect를 만들 수 있습니다. 이렇게하면 수십 명의 접근자가 상용구 코드를 복사하여 붙여 넣을 필요가 없습니다.


답변

someProperty의 값이 계산 비용이 비싸면 문제가 될 수 있습니다.

이것이 바로 조기 최적화라고합니다. 드문 경우이지만 프로파일 러가 속성 계산이 너무 비싸서 한 번이 아니라 세 번 호출하면 성능에 큰 영향을 미친다고 설명하는 경우 설명에 따라 캐싱을 추가합니다. 그러나 프라 터를 팩터링하거나 게터에서 데이터베이스에 액세스하는 것과 같이 실제로 어리석은 짓을하지 않는 한, 코드는 생각지도 못한 장소에서 열악한 비 효율성을 가질 가능성이 높습니다.