Tại sao JSF gọi getters nhiều lần


256

Giả sử tôi chỉ định một thành phần đầu ra như thế này:

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

Nếu tôi in một thông điệp tường trình khi getter cho someProperty được gọi và tải trang, thì việc thông báo rằng getter sẽ được gọi nhiều hơn một lần cho mỗi yêu cầu (không phải hai hoặc ba lần trong trường hợp của tôi)

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

Nếu giá trị của someProperty là đắt để tính toán, điều này có khả năng có thể là một vấn đề.

Tôi googled một chút và hình dung đây là một vấn đề được biết đến. Một cách giải quyết khác là bao gồm một kiểm tra và xem nếu nó đã được tính toán:

private String someProperty;

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

Vấn đề chính với điều này là bạn nhận được vô số mã soạn sẵn, chưa kể các biến riêng tư mà bạn có thể không cần.

Các lựa chọn thay thế cho phương pháp này là gì? Có cách nào để đạt được điều này mà không cần quá nhiều mã không cần thiết? Có cách nào để ngăn chặn JSF hành xử theo cách này không?

Cảm ơn vì đầu vào của bạn!

Câu trả lời:


340

Điều này được gây ra bởi bản chất của các biểu thức bị trì hoãn #{}(lưu ý rằng các biểu thức tiêu chuẩn "di sản" ${}hoạt động giống hệt nhau khi Facelets được sử dụng thay vì JSP). Biểu thức hoãn lại không được đánh giá ngay lập tức , nhưng được tạo như một ValueExpressionđối tượng và phương thức getter đằng sau biểu thức được thực thi mọi lúc khi mã gọi ValueExpression#getValue().

Điều này thường sẽ được gọi một hoặc hai lần cho mỗi chu kỳ đáp ứng yêu cầu của JSF, tùy thuộc vào việc thành phần đó là thành phần đầu vào hay đầu ra ( tìm hiểu tại đây ). Tuy nhiên, số này có thể tăng (nhiều) cao hơn khi được sử dụng trong các lần lặp các thành phần JSF (như <h:dataTable><ui:repeat>), hoặc ở đây và ở đó trong một biểu thức boolean như renderedthuộc tính. JSF (cụ thể là EL) sẽ không lưu trữ kết quả được đánh giá của biểu thức EL hoàn toàn có thể trả về các giá trị khác nhau trên mỗi cuộc gọi (ví dụ: khi nó phụ thuộc vào hàng có thể truy cập được lặp lại hiện tại).

Đánh giá một biểu thức EL và gọi một phương thức getter là một hoạt động rất rẻ, vì vậy bạn thường không nên lo lắng về điều này. Tuy nhiên, câu chuyện thay đổi khi bạn thực hiện logic DB / nghiệp vụ đắt tiền trong phương thức getter vì một số lý do. Điều này sẽ được thực hiện lại mọi lúc!

Các phương thức Getter trong các bean sao lưu JSF phải được thiết kế theo cách chúng chỉ trả về thuộc tính đã được chuẩn bị và không có gì khác, chính xác theo đặc tả của Javabeans . Họ không nên làm bất kỳ logic DB / kinh doanh đắt tiền nào cả. Cho rằng các @PostConstructphương thức nghe của bean và / hoặc (hành động) nên được sử dụng. Chúng chỉ được thực hiện một lần tại một số thời điểm trong vòng đời của JSF dựa trên yêu cầu và đó chính xác là những gì bạn muốn.

Dưới đây là một bản tóm tắt của tất cả các cách đúng khác nhau để đặt trước / tải một tài sản.

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

}

Lưu ý rằng bạn không nên sử dụng hàm tạo hoặc khối khởi tạo của bean cho công việc vì nó có thể được gọi nhiều lần nếu bạn đang sử dụng khung quản lý bean sử dụng proxy, chẳng hạn như CDI.

Nếu có cho bạn thực sự không có cách nào khác, do một số yêu cầu thiết kế hạn chế, thì bạn nên giới thiệu tải lười biếng bên trong phương thức getter. Tức là nếu tài sản là null, sau đó tải và gán nó cho tài sản, khác trả lại nó.

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

        return someProperty;
    }

Bằng cách này, logic DB / nghiệp vụ đắt tiền sẽ không cần thiết được thực thi trên mỗi lệnh gọi getter duy nhất.

Xem thêm:


5
Chỉ không sử dụng getters để làm logic kinh doanh. Đó là tất cả. Sắp xếp lại logic mã của bạn. Tôi cá rằng nó đã được sửa bằng cách chỉ sử dụng phương thức khởi tạo, hậu cấu trúc hoặc phương thức hành động một cách thông minh.
BalusC

3
-1, không đồng ý mạnh mẽ. Toàn bộ điểm của đặc tả javaBeans là cho phép các thuộc tính không chỉ là một giá trị trường và "các thuộc tính dẫn xuất" được tính toán khi đang bay là hoàn toàn bình thường. Lo lắng về các cuộc gọi getter dư thừa không có gì ngoài tối ưu hóa sớm.
Michael Borgwardt

3
Mong đợi nếu họ làm nhiều hơn là trả lại dữ liệu như bạn đã tuyên bố rõ ràng như vậy :)
BalusC

4
bạn có thể thêm rằng khởi tạo lười biếng trong getters vẫn còn hiệu lực trong JSF :)
Bozho

2
@Harry: Nó sẽ không thay đổi hành vi. Tuy nhiên, bạn có thể xử lý bất kỳ logic nghiệp vụ nào trong getter một cách có điều kiện bằng cách lười tải và / hoặc bằng cách kiểm tra ID pha hiện tại bằng cách FacesContext#getCurrentPhaseId().
BalusC

17

Với JSF 2.0, bạn có thể đính kèm một trình lắng nghe vào một sự kiện hệ thống

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

Ngoài ra, bạn có thể gửi trang JSF trong f:viewthẻ

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

      .. jsf page here...

<f:view>

9

Tôi đã viết một bài viết về cách lưu trữ bộ đệm đậu JSF với Spring AOP.

Tôi tạo một đơn giản MethodInterceptorchặn tất cả các phương thức được chú thích bằng một chú thích đặc biệt:

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

}

Thiết bị chặn này được sử dụng trong tệp cấu hình lò xo:

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

Hy vọng nó sẽ giúp!


6

Ban đầu được đăng trong diễn đàn PrimeFaces @ http://forum.primefaces.org/viewtopic.php?f=3&t=29546

Gần đây, tôi bị ám ảnh khi đánh giá hiệu năng của ứng dụng, điều chỉnh các truy vấn JPA, thay thế các truy vấn SQL động bằng các truy vấn được đặt tên và chỉ sáng nay, tôi đã nhận ra rằng một phương thức getter là một SPOT HOT trong Java Visual VM so với phần còn lại của mã của tôi (hoặc phần lớn mã của tôi).

Phương pháp Getter:

PageNavigationController.getGmapsAutoComplete()

Được tham chiếu bởi ui: bao gồm trong index.xhtml

Dưới đây, bạn sẽ thấy PageNavlationControll.getGmapsAutoComplete () là một SPOT NÓNG (vấn đề về hiệu năng) trong Java Visual VM. Nếu bạn nhìn sâu hơn, trên màn hình chụp, bạn sẽ thấy phương thức getter có thể truy cập dữ liệu lười biếng getLazyModel (), cũng là một điểm nóng, chỉ khi enduser thực hiện nhiều loại công cụ / thao tác / thao tác lười biếng trong ứng dụng. :)

Java Visual VM: hiển thị SPOT NÓNG

Xem mã (bản gốc) bên dưới.

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

Được tham chiếu bởi các mục sau trong index.xhtml:

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

Giải pháp: vì đây là phương thức 'getter', di chuyển mã và gán giá trị cho gmapsAutoComplete trước khi phương thức được gọi; xem mã dưới đây.

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

Kết quả kiểm tra: PageNavlationControll.getGmapsAutoComplete () không còn là SPOT HẤP DẪN trong Java Visual VM (thậm chí không hiển thị nữa)

Chia sẻ chủ đề này, vì nhiều người dùng chuyên gia đã khuyên các nhà phát triển JSF cơ sở KHÔNG thêm mã trong các phương thức 'getter'. :)


4

Nếu bạn đang sử dụng CDI, bạn có thể sử dụng các phương thức của Nhà sản xuất. Nó sẽ được gọi nhiều lần, nhưng kết quả của cuộc gọi đầu tiên được lưu trữ trong phạm vi của bean và có hiệu quả đối với các getters đang tính toán hoặc khởi tạo các đối tượng nặng! Xem ở đây , để biết thêm.


3

Bạn có thể có thể sử dụng AOP để tạo một số loại Aspect lưu trữ kết quả của các getters của chúng tôi trong một khoảng thời gian có thể định cấu hình. Điều này sẽ ngăn bạn khỏi cần sao chép và dán mã soạn sẵn trong hàng tá người truy cập.


Đây có phải là mùa xuân AOP mà bạn đang nói đến? Bạn có biết nơi tôi có thể tìm thấy một đoạn mã hoặc hai giao dịch với các khía cạnh không? Đọc toàn bộ chương 6 của tài liệu Mùa xuân có vẻ như quá mức cần thiết vì tôi không sử dụng Mùa xuân;)
Sevas

-1

Nếu giá trị của someProperty đắt đỏ để tính toán, điều này có khả năng có thể là một vấn đề.

Đây là những gì chúng ta gọi là tối ưu hóa sớm. Trong trường hợp hiếm hoi mà một trình hồ sơ cho bạn biết rằng việc tính toán một tài sản rất tốn kém đến mức gọi nó ba lần thay vì một lần có tác động hiệu suất đáng kể, bạn thêm bộ nhớ đệm như bạn mô tả. Nhưng trừ khi bạn làm điều gì đó thực sự ngu ngốc như bao thanh toán các số nguyên tố hoặc truy cập cơ sở dữ liệu trong một getter, mã của bạn rất có thể có hàng tá sự thiếu hiệu quả tồi tệ hơn ở những nơi bạn chưa từng nghĩ tới.


Do đó, câu hỏi - nếu someProperty tương ứng với thứ gì đó đắt tiền để tính toán (hoặc khi bạn đặt nó truy cập vào cơ sở dữ liệu hoặc các số nguyên tố bao thanh toán), cách tốt nhất để tránh thực hiện phép tính nhiều lần cho mỗi yêu cầu và là giải pháp tôi liệt kê trong câu hỏi tốt nhất Nếu bạn không trả lời câu hỏi, bình luận là một nơi tốt để đăng, phải không? Ngoài ra, bài đăng của bạn dường như mâu thuẫn với nhận xét của bạn về bài đăng của BalusC - trong các bình luận bạn nói rằng thật tốt khi tính toán, và trong bài đăng của bạn, bạn nói rằng điều đó thật ngu ngốc. Cho tôi hỏi bạn vẽ đường này ở đâu?
Sevas

Đó là một thang trượt, không phải là vấn đề đen trắng. Một số thứ rõ ràng không phải là một vấn đề, ví dụ như thêm một vài giá trị, bởi vì chúng mất ít hơn một phần triệu giây ( thực tế ít hơn nhiều ). Một số rõ ràng là một vấn đề, như DB hoặc truy cập tệp, bởi vì chúng có thể mất 10ms hoặc lâu hơn - và bạn chắc chắn cần phải biết những điều này để bạn có thể tránh chúng nếu có thể, không chỉ trong getters. Nhưng đối với tất cả mọi thứ khác, dòng là nơi trình hồ sơ cho bạn biết.
Michael Borgwardt

-1

Tôi cũng sẽ tư vấn sử dụng Framework như Primefaces thay vì stock JSF, họ giải quyết các vấn đề như vậy trước nhóm JSF e. g trong các khu vực nguyên thủy bạn có thể thiết lập trình một phần. Nếu không, BalusC đã giải thích nó tốt.


-2

Nó vẫn là vấn đề lớn trong JSF. Ví dụ nếu bạn có một phương thức isPermittedToBlaBlakiểm tra bảo mật và theo quan điểm của bạn thì bạn có rendered="#{bean.isPermittedToBlaBla}phương thức đó sẽ được gọi nhiều lần.

Kiểm tra an ninh có thể phức tạp, ví dụ. Truy vấn LDAP, v.v. Vì vậy, bạn phải tránh điều đó với

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

và bạn phải đảm bảo trong một phiên bean này theo yêu cầu.

Ich nghĩ rằng JSF phải triển khai ở đây một số tiện ích mở rộng để tránh nhiều cuộc gọi (ví dụ: chú thích làm @Phase(RENDER_RESPONSE)dịu phương thức này chỉ một lần sau RENDER_RESPONSEgiai đoạn ...)


2
Bạn có thể lưu trữ kết quả trong RequestParameterMap
Barshe Roussy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.