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


256

다음과 같이 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가 이런 식으로 동작하지 못하게하는 방법이 있습니까?

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

답변:


340

이는 지연된 표현식의 특성으로 인해 발생합니다 #{}( ${}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 / 비즈니스 로직이 불필요하게 실행되는 것은 아닙니다.

또한보십시오:


5
getter를 사용하여 비즈니스 로직을 수행하지 마십시오. 그게 다야. 코드 로직을 재정렬하십시오. 그것은 생성자, postconstruct 또는 action 메소드를 현명한 방법으로 사용하여 이미 수정되었다는 내기.
BalusC

3
-1, 강력하게 동의하지 않습니다. javaBeans 스펙의 전체 요점은 특성이 단순한 필드 값 이상이 될 수 있도록 하는 것이며 즉석에서 계산되는 "유도 특성"은 완벽하게 정상입니다. 중복 게터 호출에 대한 걱정은 조기 최적화에 지나지 않습니다.
Michael Borgwardt

3
자신이 명시한대로 데이터를 반환하는 것 이상을 수행 할 것으로 예상됩니다. :)
BalusC

4
getters의 게으른 초기화가 JSF에서도 여전히 유효하다는 것을 추가 할 수 있습니다 :)
Bozho

2
@Harry : 그것은 행동을 바꾸지 않을 것입니다. 그러나 지연 로딩 및 / 또는로 현재 단계 ID를 확인하여 getter의 모든 비즈니스 로직을 조건부로 처리 할 수 ​​있습니다 FacesContext#getCurrentPhaseId().
BalusC

17

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>

9

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>

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


6

원래 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'메소드에 코드를 추가하지 말라고 권고했기 때문에이 주제를 공유했습니다. :)


4

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


3

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


당신이 말하는 Spring AOP입니까? Aspects를 다루는 코드 스 니펫을 어디에서 찾을 수 있습니까? Spring을 사용하지 않기 때문에 Spring 문서의 6 장 전체를 읽는 것은 지나친 것처럼 보인다;)
Sevas

-1

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

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


따라서 질문-someProperty가 계산하는 데 비싼 물건 (또는 데이터베이스 또는 팩터링 프라임에 액세스 할 때)에 해당하는 경우 요청 당 여러 번 계산을 피하는 가장 좋은 방법은 무엇입니까? 최고. 질문에 대답하지 않으면 의견을 게시하기에 좋은 곳입니다. 또한 귀하의 게시물은 BalusC의 게시물에 대한 귀하의 의견과 모순되는 것 같습니다. 귀하는 귀하의 게시물에서 즉시 계산을 수행하는 것이 좋다고 말하고 귀하의 게시물에서 멍청하다고 말합니다. 어디에 선을 그릴 지 물어봐도 될까요?
Sevas

흑백 문제가 아니라 슬라이딩 스케일입니다. 일부 값은 백만 분의 1 초도 채 걸리지 않기 때문에 ( 실제로는 훨씬 적음) 일부 문제는 분명히 문제가되지 않습니다 . DB 또는 파일 액세스와 같은 일부 문제는 10ms 이상이 걸릴 수 있기 때문에 분명히 문제가됩니다. 게터뿐만 아니라 가능한 경우 피할 수 있도록 반드시 알아야합니다. 그러나 다른 모든 것의 선은 프로파일 러가 말하는 곳입니다.
Michael Borgwardt

-1

또한 JSF 재고 대신 Primefaces와 같은 프레임 워크를 사용하는 것이 좋습니다. JSF 팀 e보다 먼저 이러한 문제를 해결합니다. g 글 머리 기호로 부분 제출을 설정할 수 있습니다. 그렇지 않으면 BalusC가 잘 설명했습니다.


-2

JSF에서는 여전히 큰 문제입니다. 예를 들어 isPermittedToBlaBla보안 검사 rendered="#{bean.isPermittedToBlaBla}방법이 있고 보기에 있으면 메서드가 여러 번 호출됩니다.

예를 들어 보안 점검이 복잡 할 수 있습니다. LDAP 쿼리 등을 사용하면

Boolean isAllowed = null ... if(isAllowed==null){...} return isAllowed?

세션 Bean 내에서 요청마다이를 확인해야합니다.

Ich는 JSF가 여러 호출을 피하기 위해 여기에 몇 가지 확장을 구현해야한다고 생각합니다 (예 : 주석은 @Phase(RENDER_RESPONSE)이 메소드를 RENDER_RESPONSE단계 후에 한 번만 호출합니다 ...)


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.