performance - ¿Por qué JSF llama a los captadores varias veces?
el getter (8)
Digamos que especifico un componente outputText como este:
<h:outputText value="#{ManagedBean.someProperty}"/>
Si someProperty
un mensaje de registro cuando se llama al captador de someProperty
y se carga la página, es trivial notar que se llama al captador más de una vez por solicitud (dos o tres veces es lo que sucedió en mi caso):
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
Si el valor de someProperty
es costoso de calcular, esto potencialmente puede ser un problema.
Busqué en Google un poco y pensé que este es un problema conocido. Una solución fue incluir un control y ver si ya se había calculado:
private String someProperty;
public String getSomeProperty() {
if (this.someProperty == null) {
this.someProperty = this.calculatePropertyValue();
}
return this.someProperty;
}
El principal problema con esto es que obtienes un montón de código repetitivo, sin mencionar las variables privadas que puede que no necesites.
¿Cuáles son las alternativas a este enfoque? ¿Hay alguna manera de lograr esto sin tanto código innecesario? ¿Hay alguna manera de evitar que JSF se comporte de esta manera?
¡Gracias por tu contribución!
Si el valor de alguna propiedad es costoso de calcular, esto potencialmente puede ser un problema.
Esto es lo que llamamos una optimización prematura. En el raro caso de que un generador de perfiles le diga que el cálculo de una propiedad es tan extraordinariamente costoso que llamarla tres veces en lugar de una vez tiene un impacto significativo en el rendimiento, agrega el almacenamiento en caché como lo describe. Pero a menos que hagas algo realmente estúpido, como los primos de factorización o el acceso a una base de datos en un captador, lo más probable es que tu código tenga una docena de ineficiencias peores en lugares en los que nunca has pensado.
Con JSF 2.0 puede adjuntar un oyente a un evento del sistema
<h:outputText value="#{ManagedBean.someProperty}">
<f:event type="preRenderView" listener="#{ManagedBean.loadSomeProperty}" />
</h:outputText>
Alternativamente, puede incluir la página JSF en una etiqueta f:view
<f:view>
<f:event type="preRenderView" listener="#{ManagedBean.loadSomeProperty}" />
.. jsf page here...
<f:view>
Esto se debe a la naturaleza de las expresiones diferidas #{}
(tenga en cuenta que las expresiones estándar "heredadas" ${}
comportan exactamente igual cuando se usa Facelets en lugar de JSP). La expresión diferida no se evalúa de inmediato , sino que se crea como un objeto ValueExpression
y el método getter detrás de la expresión se ejecuta cada vez que el código llama a ValueExpression#getValue()
.
Normalmente, esto se invocará una o dos veces por ciclo de solicitud-respuesta JSF, dependiendo de si el componente es un componente de entrada o salida ( apréndelo aquí ). Sin embargo, este recuento puede aumentar mucho más cuando se usa para iterar componentes JSF (como <h:dataTable>
y <ui:repeat>
), o aquí y allá en una expresión booleana como el atributo rendered
. JSF (específicamente, EL) no almacenará en la memoria caché el resultado evaluado de la expresión EL, ya que puede devolver valores diferentes en cada llamada (por ejemplo, cuando depende de la fila de datos iterativa actualmente).
Evaluar una expresión EL e invocar un método getter es una operación muy barata, por lo que generalmente no debe preocuparse por esto. Sin embargo, la historia cambia cuando se está ejecutando una lógica de base de datos / negocio costosa en el método de obtención por algún motivo. Esto sería re-ejecutado cada vez!
Los métodos Getter en los beans de respaldo JSF deben diseñarse de tal manera que solo devuelvan la propiedad ya preparada y nada más, exactamente de acuerdo con la especificación de Java . No deberían hacer ninguna lógica DB / business costosa en absoluto. Para ello, deben utilizarse los métodos de escucha @PostConstruct
y / o (acción) del bean. Se ejecutan solo una vez en algún punto del ciclo de vida de JSF basado en solicitud y eso es exactamente lo que desea.
Aquí hay un resumen de todas las diferentes formas correctas de predefinir / cargar una propiedad.
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;
}
}
Tenga en cuenta que no debe usar el constructor del bean ni el bloque de inicialización para el trabajo, ya que puede invocarse varias veces si está utilizando un marco de administración de bean que utiliza proxies, como CDI.
Si realmente no hay otras formas, debido a algunos requisitos de diseño restrictivos, entonces debe introducir una carga perezosa dentro del método getter. Es decir, si la propiedad es null
, luego cárguela y asignela a la propiedad, de lo contrario, devuélvala.
public SomeObject getSomeProperty() {
// If there are really no other ways, introduce lazy loading.
if (someProperty == null) {
someProperty = loadSomeProperty();
}
return someProperty;
}
De esta manera, la costosa lógica de negocio / base de datos no se ejecutará innecesariamente en cada llamada getter.
Ver también:
- ¿Por qué el captador llama tantas veces al captador?
- Invocar la acción del bean administrado JSF en la carga de la página
- Cómo y cuándo debo cargar el modelo desde la base de datos para h: dataTable
- ¿Cómo llenar las opciones de h: selectOneMenu desde la base de datos?
- Muestra la imagen dinámica de la base de datos con p: graphicImage y StreamedContent
- Definición y reutilización de una variable EL en la página JSF
He escrito un article sobre cómo almacenar en caché JSF getter con Spring AOP.
Creo un simple MethodInterceptor
que intercepta todos los métodos anotados con una anotación especial:
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;
}
}
Este interceptor se utiliza en un archivo de configuración de primavera:
<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>
Espero que ayude!
Probablemente podría usar AOP para crear algún tipo de Aspecto que almacene en caché los resultados de nuestros captadores durante un período de tiempo configurable. Esto evitaría la necesidad de copiar y pegar código repetitivo en docenas de accesores.
Publicado originalmente en el foro PrimeFaces en http://forum.primefaces.org/viewtopic.php?f=3&t=29546
Recientemente, me obsesioné en evaluar el rendimiento de mi aplicación, ajustar las consultas de JPA, reemplazar las consultas de SQL dinámico con consultas con nombre, y esta misma mañana, reconocí que un método de obtención era un HOT SPOT en Java Visual VM que el resto de Mi código (o la mayoría de mi código).
Método de getter:
PageNavigationController.getGmapsAutoComplete()
Referenciado por ui: include in en index.xhtml
A continuación, verá que PageNavigationController.getGmapsAutoComplete () es un HOT SPOT (problema de rendimiento) en Java Visual VM. Si miras más abajo, en la captura de pantalla, verás que getLazyModel (), el método de obtención de datos perezoso de PrimeFaces, también es un punto caliente, solo cuando el usuario final está haciendo un montón de cosas / operaciones / tareas de tipo "datos perezosos". en la app :)
Ver código (original) a continuación.
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;
}
Referenciado por lo siguiente en index.xhtml:
<h:head>
<ui:include src="#{pageNavigationController.gmapsAutoComplete ? ''/head_gmapsAutoComplete.xhtml'' : (pageNavigationController.gmaps ? ''/head_gmaps.xhtml'' : ''/head_default.xhtml'')}"/>
</h:head>
Solución: ya que este es un método ''getter'', mueva el código y asigne un valor a gmapsAutoComplete antes de llamar al método; ver codigo abajo
/*
* 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;
}
}
Resultados de la prueba: PageNavigationController.getGmapsAutoComplete () ya no es un HOT SPOT en Java Visual VM (ya ni siquiera aparece)
Compartiendo este tema, ya que muchos de los usuarios expertos han aconsejado a los desarrolladores junior de JSF que NO agreguen código en los métodos de "obtención". :)
Si está utilizando CDI, puede usar los métodos de los productores. Se llamará muchas veces, pero el resultado de la primera llamada se almacena en caché en el alcance del bean y es eficiente para los captadores que están computando o inicializando objetos pesados. Vea here , para más información.
Sigue siendo un gran problema en JSF. Por ejemplo, si tiene un método isPermittedToBlaBla
para verificaciones de seguridad y en su opinión ha rendered="#{bean.isPermittedToBlaBla}
entonces el método se llamará varias veces.
El control de seguridad podría ser complicado por ej. Consulta LDAP, etc. Así que debes evitar eso con
Boolean isAllowed = null ... if(isAllowed==null){...} return isAllowed?
y debe asegurarse dentro de un bean de sesión esto por solicitud.
Creo que JSF debe implementar aquí algunas extensiones para evitar múltiples llamadas (p. Ej., Anotación @Phase(RENDER_RESPONSE)
este método solo se realiza una vez después de la fase RENDER_RESPONSE
...)