Почему JSF вызывает геттеры несколько раз
допустим, я указываю компонент outputText следующим образом:
<h:outputText value="#{ManagedBean.someProperty}"/>
если я печатаю сообщение журнала, когда геттер для someProperty вызывается и загружается страница, тривиально заметить, что геттер вызывается более одного раза на запрос (дважды или три раза-это то, что произошло в моем случае):
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 от такого поведения?
Спасибо за Ваш вклад!
8 ответов:
это вызвано природой отложенных выражений
#{}(обратите внимание, что" устаревшие " стандартные выражения${}вести себя точно так же, когда Facelets используется вместо JSP). Отложенное выражение не тут оценивается, но создается какValueExpressionобъект и метод getter за выражением выполняется каждый раз, когда код вызываетValueExpression#getValue().это обычно вызывается один или два раза в JSF цикл запрос-ответ, в зависимости от того, является ли компонент входным или выходным компонентом (узнать здесь). Однако этот счетчик может подняться (намного) выше при использовании в итерации компонентов JSF (например,
<h:dataTable>и<ui:repeat>), или здесь и там в логическом выражении, как . JSF (в частности, EL) не будет кэшировать вычисленный результат выражения EL вообще, как это мая возвращать разные значения при каждом вызове (например, когда он зависим от в настоящее время повторяемого объекта DataTable строки).вычисление выражения EL и вызов метода getter-это очень дешевая операция, поэтому вы вообще не должны беспокоиться об этом. Однако история изменяется, когда вы по какой-то причине выполняете дорогостоящую БД/бизнес-логику в методе getter. Это будет повторно выполняться каждый раз!
методы Геттера в JSF backing beans должны быть разработаны таким образом, что они исключительно возвращение уже подготовлен собственность и ничего больше, точно так же, как спецификация Javabeans. Они не должны делать никаких дорогих БД/бизнес-логики вообще. Для этого боба
@PostConstructи / или (действие)методы слушателя должны быть использованы. Они исполняются только один раз в какой-то момент жизненного цикла 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; }таким образом, дорогостоящая БД / бизнес-логика не будет излишне выполняться при каждом вызове getter.
Читайте также:
- почему геттер вызывается так много раз с помощью атрибута rendered?
- вызовите JSF управляемое действие компонента при загрузке страницы
- как и когда я должен загрузить модель из базы данных для h: dataTable
- Как заполнить параметры h: selectOneMenu из база данных?
- отображение динамического изображения из базы данных с помощью p: graphicImage и StreamedContent
- определение и повторное использование переменной EL на странице JSF
С помощью JSF 2.0 вы можете подключить прослушиватель к системному событию
<h:outputText value="#{ManagedBean.someProperty}"> <f:event type="preRenderView" listener="#{ManagedBean.loadSomeProperty}" /> </h:outputText>В качестве альтернативы вы можете заключить страницу JSF в
f:viewtag<f:view> <f:event type="preRenderView" listener="#{ManagedBean.loadSomeProperty}" /> .. jsf page here... <f:view>
Я написал статьи о том, как кэшировать 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; } }этот перехватчик используется в файле конфигурации spring:
<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 forum @ http://forum.primefaces.org/viewtopic.php?f=3&t=29546
недавно я был одержим оценкой производительности моего приложения, настройкой запросов JPA, заменой динамических SQL-запросов именованными запросами, и только сегодня утром я понял, что метод getter был более горячей точкой в Java Visual VM, чем остальная часть моего кода (или большинство моего кода).
Getter метод:
PageNavigationController.getGmapsAutoComplete()ссылка ui: включить в индекс.xhtml
ниже, вы увидите, что PageNavigationController.getGmapsAutoComplete () является горячей точкой (проблема производительности) в Java Visual VM. Если вы посмотрите дальше вниз, на захват экрана, вы увидите, что getLazyModel (), PrimeFaces lazy datatable getter метод, является горячей точкой тоже, только когда конечный пользователь делает много "ленивый datatable" типа материала/операций/задач в приложении. :)
Посмотреть (оригинал) код под.
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; }ссылается на следующее в индексе.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 (даже не появляется больше)
общий доступ к этой теме, так как многие из опытных пользователей посоветовали junior JSF разработчики не добавляют код в методы "getter". :)
вероятно, вы можете использовать AOP для создания какого-то аспекта, который кэширует результаты наших геттеров в течение настраиваемого периода времени. Это избавит вас от необходимости копировать и вставлять шаблонный код в десятки методов доступа.
Если вы используете CDI, вы можете использовать методы производителей. Он будет вызываться много раз, но результат первого вызова кэшируется в области Боба и эффективен для геттеров, которые вычисляют или инициализируют тяжелые объекты! Смотрите здесь, для получения дополнительной информации.
Это все еще большая проблема в JSF. Например, если у вас есть метод
isPermittedToBlaBlaдля проверки безопасности, и на ваш взгляд у вас естьrendered="#{bean.isPermittedToBlaBla}тогда метод будет вызываться несколько раз.проверка безопасности может быть сложной, например . LDAP-запрос и т. д. Поэтому вы должны избегать этого с
Boolean isAllowed = null ... if(isAllowed==null){...} return isAllowed?и вы должны обеспечить в сеансовом компоненте это за запрос.
Ich думаю, что JSF должен реализовать здесь некоторые расширения, чтобы избежать нескольких вызовов (например, аннотации
@Phase(RENDER_RESPONSE)calle этот метод только один раз послеRENDER_RESPONSEэтап...)
Если значение someProperty равно дорого рассчитать, это может потенциально это проблема.
Это то, что мы называем преждевременная оптимизация. В редких случаях, когда профилировщик сообщает вам, что вычисление свойства настолько чрезвычайно дорого, что вызов его три раза, а не один раз оказывает значительное влияние на производительность, вы добавляете кэширование, как вы описываете. Но если вы не сделаете что-то действительно глупое, как факторинг простыми или доступа к база данных в геттере, ваш код, скорее всего, имеет дюжину худших неэффективностей в местах, о которых вы никогда не думали.

Comments