ทำไม JSF เรียก getters หลายครั้ง


256

สมมติว่าฉันระบุองค์ประกอบ outputText ดังนี้:

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

ถ้าฉันพิมพ์ข้อความบันทึกเมื่อเรียกใช้ getter 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การคำนวณนั้นมีราคาแพงอาจเป็นปัญหาได้

ฉัน googled เล็กน้อยและคิดว่านี่เป็นปัญหาที่ทราบแล้ว วิธีแก้ไขปัญหาหนึ่งคือการรวมการตรวจสอบและดูว่ามีการคำนวณแล้วหรือไม่:

private String someProperty;

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

ปัญหาหลักของการทำเช่นนี้คือคุณได้รับรหัสสำเร็จรูปจำนวนมากไม่ต้องพูดถึงตัวแปรส่วนตัวที่คุณอาจไม่ต้องการ

ทางเลือกอื่นของแนวทางนี้คืออะไร? มีวิธีในการบรรลุนี้โดยไม่ต้องใช้รหัสไม่จำเป็นมาก มีวิธีที่จะหยุด JSF ไม่ให้ทำเช่นนี้หรือไม่

ขอบคุณสำหรับข้อมูลของคุณ!

คำตอบ:


340

สิ่งนี้เกิดจากลักษณะของนิพจน์ที่เลื่อนออกไป#{}(โปรดทราบว่านิพจน์มาตรฐาน "ดั้งเดิม" นั้นมี${}พฤติกรรมเหมือนกันทุกประการเมื่อใช้ Facelets แทน JSP) นิพจน์ที่เลื่อนออกไปจะไม่ถูกประเมินในทันทีแต่สร้างเป็นValueExpressionวัตถุและเมธอด getter ที่อยู่เบื้องหลังนิพจน์นั้นจะถูกดำเนินการทุกครั้งเมื่อมีการเรียกรหัสValueExpression#getValue()วัตถุและวิธีทะเยอทะยานที่อยู่เบื้องหลังการแสดงออกที่จะดำเนินการทุกครั้งเมื่อมีการโทรรหัส

โดยปกติจะเรียกใช้หนึ่งหรือสองครั้งต่อรอบคำขอการตอบกลับ JSF ขึ้นอยู่กับว่าองค์ประกอบนั้นเป็นองค์ประกอบอินพุตหรือเอาต์พุต ( เรียนรู้ที่นี่ ) อย่างไรก็ตามจำนวนนี้สามารถสูงขึ้น (มาก) เมื่อใช้ในการวนซ้ำองค์ประกอบ JSF (เช่น<h:dataTable>และ<ui:repeat>) หรือที่นี่และที่นั่นและมีนิพจน์บูลีนเช่นrenderedแอตทริบิวต์ JSF (โดยเฉพาะอย่างยิ่ง EL) จะไม่แคชผลลัพธ์ที่ประเมินของนิพจน์ EL ทั้งหมดเท่าที่จะทำได้ส่งคืนค่าที่แตกต่างกันในการโทรแต่ละครั้ง (ตัวอย่างเช่นเมื่อขึ้นอยู่กับแถว DataTable ที่ทำซ้ำในปัจจุบัน)

การประเมินค่านิพจน์ EL และการเรียกใช้เมธอด getter เป็นการดำเนินการที่ถูกมากดังนั้นโดยทั่วไปคุณไม่ควรกังวลเกี่ยวกับเรื่องนี้เลย อย่างไรก็ตามเรื่องราวเปลี่ยนไปเมื่อคุณทำการใช้ DB / ตรรกะทางธุรกิจที่มีราคาแพงในวิธีทะเยอทะยานด้วยเหตุผลบางประการ นี่จะถูกดำเนินการซ้ำทุกครั้ง!

วิธีการมุ่งมั่นในการสนับสนุน JSF ถั่วควรจะออกแบบว่าวิธีการที่พวกเขา แต่เพียงผู้เดียวกลับสถานที่ให้บริการอยู่แล้วเตรียมและไม่มีอะไรเพิ่มเติมตรงตามสเปค Javabeans พวกเขาไม่ควรทำ DB / ตรรกะทางธุรกิจที่มีราคาแพงเลย สำหรับ@PostConstructวิธีการฟังของ bean และ / หรือ (action) ควรใช้ พวกเขาจะดำเนินการเพียงครั้งเดียวในช่วงเวลาหนึ่งของวงจรชีวิตตามคำขอ JSF และนั่นคือสิ่งที่คุณต้องการ

นี่คือบทสรุปของวิธีการที่เหมาะสมในการตั้งค่า / โหลดคุณสมบัติ

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;
    }

}

โปรดทราบว่าคุณไม่ควรใช้ตัวสร้างหรือบล็อกการเริ่มต้นของ bean สำหรับงานเนื่องจากอาจถูกเรียกหลายครั้งหากคุณใช้เฟรมเวิร์กการจัดการ bean ซึ่งใช้พร็อกซีเช่น CDI

หากคุณไม่มีวิธีอื่นเนื่องจากความต้องการด้านการออกแบบที่ จำกัด คุณควรแนะนำวิธีการโหลดแบบขี้เกียจภายในเมธอด getter นั่นคือถ้าคุณสมบัติเป็นnullแล้วโหลดและกำหนดให้กับทรัพย์สินมิฉะนั้นส่งคืน

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

        return someProperty;
    }

วิธีนี้ตรรกะฐานข้อมูล / ธุรกิจที่มีราคาแพงจะไม่ถูกดำเนินการโดยไม่จำเป็นในการเรียกทะลุผ่านทุกครั้ง

ดูสิ่งนี้ด้วย:


5
อย่าใช้ getters เพื่อทำตรรกะทางธุรกิจ นั่นคือทั้งหมดที่ ปรับตรรกะโค้ดของคุณใหม่ เดิมพันของฉันว่ามันได้รับการแก้ไขแล้วเพียงแค่ใช้ constructor, postconstruct หรือวิธีการกระทำอย่างชาญฉลาด
BalusC

3
-1 ไม่เห็นด้วยอย่างยิ่ง จุดทั้งหมดของข้อมูลจำเพาะ javaBeans คือการอนุญาตให้คุณสมบัติเป็นมากกว่าค่าฟิลด์และ "คุณสมบัติที่ได้รับ" ซึ่งคำนวณจากการบินเป็นปกติอย่างสมบูรณ์แบบ กังวลเกี่ยวกับการโทร getter ซ้ำซ้อนคืออะไร แต่การเพิ่มประสิทธิภาพก่อนวัยอันควร
Michael Borgwardt

3
คาดหวังว่าพวกเขาจะทำมากกว่าการส่งคืนข้อมูลตามที่คุณระบุไว้อย่างชัดเจน :)
BalusC

4
คุณสามารถเพิ่มที่เริ่มต้นขี้เกียจใน getters ยังคงถูกต้องใน JSF :)
Bozho

2
@Harry: มันจะไม่เปลี่ยนพฤติกรรม แต่คุณสามารถจัดการกับตรรกะทางธุรกิจใด ๆ ใน getter เงื่อนไขโดยโหลดขี้เกียจและ / FacesContext#getCurrentPhaseId()หรือโดยการตรวจสอบหมายเลขระยะปัจจุบันโดย
BalusC

17

ด้วย JSF 2.0 คุณสามารถแนบ listener กับเหตุการณ์ของระบบ

<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

ฉันได้เขียนบทความเกี่ยวกับวิธีการแคช JSF beans getter กับ Spring AOP

ฉันสร้างง่าย ๆ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 แบบไดนามิกด้วยการสืบค้นที่ตั้งชื่อและเมื่อเช้านี้ฉันรู้ว่าวิธีการทะเลาะกันเป็น HOT SPOT ใน Java Visual VM มากกว่าส่วนที่เหลือของ รหัสของฉัน (หรือส่วนใหญ่ของรหัสของฉัน)

วิธีการทะเยอทะยาน:

PageNavigationController.getGmapsAutoComplete()

อ้างอิงโดย ui: รวมไว้ใน index.xhtml

ด้านล่างคุณจะเห็นว่า PageNavigationController.getGmapsAutoComplete () เป็น HOT SPOT (ปัญหาประสิทธิภาพ) ใน Java Visual VM หากคุณมองลงไปอีกในการจับภาพหน้าจอคุณจะเห็นว่า getLazyModel (), PrimeFaces วิธีสันตะปาปา datatable getatable เป็นจุดร้อนเช่นกันเมื่อ enduser ทำสิ่งที่ประเภท / การดำเนินงาน / งานจำนวนมากขี้เกียจ ในแอป :)

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 () ไม่ได้เป็น HOT SPOT ใน Java Visual VM (ไม่แสดงอีกต่อไป)

การแชร์หัวข้อนี้เนื่องจากผู้ใช้ผู้เชี่ยวชาญหลายคนแนะนำให้นักพัฒนารุ่นเยาว์ JSF ไม่ต้องเพิ่มโค้ดในเมธอด 'getter' :)


4

หากคุณกำลังใช้ CDI คุณสามารถใช้วิธีผู้ผลิต มันจะถูกเรียกหลายครั้ง แต่ผลลัพธ์ของการโทรครั้งแรกจะถูกเก็บไว้ในขอบเขตของ bean และมีประสิทธิภาพสำหรับผู้ที่กำลังประมวลผลหรือเริ่มต้นของหนัก! ดูที่นี่สำหรับข้อมูลเพิ่มเติม


3

คุณอาจใช้ AOP เพื่อสร้างมุมมองบางส่วนที่เก็บผลลัพธ์ของตัวรับของเราตามระยะเวลาที่กำหนด สิ่งนี้จะป้องกันไม่ให้คุณต้องคัดลอกและวางรหัส boilerplate ใน accessors หลายสิบ


Spring AOP นี้คุณกำลังพูดถึงหรือไม่? คุณจะรู้หรือไม่ว่าฉันจะหาข้อมูลโค้ดได้หรือไม่ อ่านบทที่ 6 ทั้งเอกสารฤดูใบไม้ผลิดูเหมือนว่า overkill เป็นฉันไม่ได้ใช้ฤดูใบไม้ผลิ;)
Sevas

-1

หากค่าของ someProperty นั้นมีราคาแพงในการคำนวณสิ่งนี้อาจเป็นปัญหาได้

นี่คือสิ่งที่เราเรียกว่าการเพิ่มประสิทธิภาพก่อนวัยอันควร ในกรณีที่หายากที่ผู้สร้างโปรไฟล์บอกคุณว่าการคำนวณคุณสมบัติมีราคาแพงมากจนเรียกว่าสามครั้งแทนที่จะเป็นหนึ่งครั้งมีผลกระทบต่อประสิทธิภาพการทำงานอย่างมีนัยสำคัญคุณเพิ่มการแคชตามที่คุณอธิบาย แต่ถ้าคุณไม่ทำอะไรโง่ ๆ เช่นการแยกตัวประกอบหรือการเข้าถึงดาต้าเบสในทะเยอทะยานรหัสของคุณก็น่าจะมีประสิทธิภาพที่แย่กว่านั้นอีกนับสิบที่คุณไม่เคยคิดมาก่อน


ดังนั้นคำถาม - หากทรัพย์สินบางอย่างสอดคล้องกับสิ่งที่มีราคาแพงในการคำนวณ (หรือเมื่อคุณวางไว้ในการเข้าถึงฐานข้อมูลหรือช่วงเวลาที่เหมาะสม) วิธีที่ดีที่สุดในการหลีกเลี่ยงการคำนวณหลายครั้งต่อคำขอคืออะไร ดีที่สุด. หากคุณไม่ตอบคำถามความคิดเห็นเป็นที่ที่เหมาะสำหรับการโพสต์ไม่ใช่หรือ? นอกจากนี้โพสต์ของคุณดูเหมือนจะขัดแย้งกับความคิดเห็นของคุณในโพสต์ของ BalusC - ในความคิดเห็นที่คุณบอกว่าเป็นการดีที่จะทำการคำนวณได้ทันทีและในโพสต์ของคุณคุณบอกว่ามันโง่ ฉันขอถามได้ไหมว่าคุณจะลากเส้นที่ไหน?
Sevas

มันเป็นสเกลเลื่อนไม่ใช่ปัญหาขาวดำ บางสิ่งบางอย่างเห็นได้ชัดไม่ได้เป็นปัญหาที่เกิดขึ้นเช่นการเพิ่มค่าไม่กี่เพราะพวกเขาใช้เวลาน้อยกว่าล้านของวินาที ( มากน้อยจริง) บางคนเห็นได้ชัดว่ามีปัญหาเช่นการเข้าถึงฐานข้อมูลหรือไฟล์เพราะพวกเขาอาจใช้เวลา 10ms หรือนานกว่านั้นและคุณจำเป็นต้องรู้สิ่งเหล่านี้อย่างแน่นอนเพื่อให้คุณสามารถหลีกเลี่ยงพวกเขาได้ถ้าเป็นไปได้ แต่สำหรับทุกสิ่งทุกอย่างบรรทัดนั้นคือตำแหน่งที่ profiler บอกคุณ
Michael Borgwardt

-1

ฉันจะแนะนำด้วยการใช้ Framework เช่น Primefaces แทนที่จะเป็นหุ้น JSF พวกเขาจัดการกับปัญหาดังกล่าวก่อนทีม JSF e g ในหน้าแรกคุณสามารถตั้งค่าส่งบางส่วน มิฉะนั้น BalusC ก็อธิบายได้ดี


-2

มันยังคงเป็นปัญหาใหญ่ใน JSF ตัวอย่างเช่นหากคุณมีวิธีการisPermittedToBlaBlaตรวจสอบความปลอดภัยและในมุมมองของคุณคุณมีrendered="#{bean.isPermittedToBlaBla}วิธีการที่จะเรียกว่าหลายครั้ง

การตรวจสอบความปลอดภัยอาจมีความซับซ้อนเช่น แบบสอบถาม LDAP เป็นต้นดังนั้นคุณต้องหลีกเลี่ยงด้วย

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

และคุณต้องให้แน่ใจว่าภายในถั่วนี้สิ่งนี้ต่อคำขอ

ฉันคิดว่า JSF ต้องใช้ส่วนขยายที่นี่เพื่อหลีกเลี่ยงการโทรหลายครั้ง (เช่นคำอธิบายประกอบเรียก@Phase(RENDER_RESPONSE)วิธีนี้เพียงครั้งเดียวหลังจากRENDER_RESPONSEเฟส ... )


2
คุณสามารถแคชผลลัพธ์ใน RequestParameterMap
Christophe Roussy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.