Dank einer Frage von Ulrich Krause im XPages Developer Forum wurde ein Thema „wiederbelebt“, das mir vor einiger Zeit in einem Projekt aufgefallen ist und sich als wahre Bremse bei XPages-Applikationen herausstellt:
Sämtliche Datenquellen (DataContext-Variablen, Repeat Controls, usw.) werden bei jedem Partial Refresh neu berechnet, auch wenn sie nicht das Ziel (bzw. ein Kind-Element des Ziels) des Refreshs sind.
Ich nutze als Beispiel hierfür die von Ulrich Krause gepostete Beispiel-XPage, um das Problem zu verdeutlichen. Den fett markierten Code ist von mir zur Verdeutlichung auf der Serverkonsole eingebaut worden, dass es sich nicht um einen fehlerhaftes print-Statement handelt, sondern der Code wirklich neu berechnet wird.
<?xml version="1.0" encoding="UTF-8"?> <xp:view xmlns:xp="http://www.ibm.com/xsp/core"> <xp:button value="Label" id="button1"> <xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="col1" /> </xp:button> <xp:table> <xp:tr> <xp:td id="col1"> <xp:text escape="true" id="computedField2" value="#{javascript:@Now()}"> <xp:this.converter> <xp:convertDateTime type="both" /> </xp:this.converter> </xp:text> </xp:td> <xp:td id="col2"> <xp:text escape="true" id="computedField1" value="#{javascript:@Now()}"> <xp:this.converter> <xp:convertDateTime type="both" /> </xp:this.converter> </xp:text> <xp:dataTable id="dataTable1" rows="30"> <xp:this.value><![CDATA[#{javascript: print(java.lang.System.currentTimeMillis() + ' You shall not refresh'); }]]></xp:this.value> <xp:column id="column1" /> </xp:dataTable> </xp:td> </xp:tr> </xp:table> </xp:view>
Wird der Button geklickt, wird ein Refresh auf das Element mit der Id „col1“ ausgelöst, und eigentlich dürfte die Data Table nicht neu berechnet werden, doch genau das ist aber der Fall!
[Die Ausgabe auf der Serverkonsole habe ich mit einem Debug PhaseListener kombiniert, um die Phasen im Lifecycle hervorzuheben]
Button
<xp:button value="Label" id="button1"> <xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="col1" /> </xp:button>
1. Öffnen der XPage
19.02.2012 18:02:17 HTTP JVM: Before phase: RENDER_RESPONSE 6 19.02.2012 18:02:17 HTTP JVM: 1329670937472 You shall not refresh 19.02.2012 18:02:17 HTTP JVM: After phase: RENDER_RESPONSE 6
2. Partial Refresh
19.02.2012 18:05:37 HTTP JVM: Before phase: RESTORE_VIEW 1 19.02.2012 18:05:37 HTTP JVM: After phase: RESTORE_VIEW 1 19.02.2012 18:05:37 HTTP JVM: Before phase: APPLY_REQUEST_VALUES 2 19.02.2012 18:05:37 HTTP JVM: 1329671137733 You shall not refresh 19.02.2012 18:05:37 HTTP JVM: After phase: APPLY_REQUEST_VALUES 2 19.02.2012 18:05:37 HTTP JVM: Before phase: PROCESS_VALIDATIONS 3 19.02.2012 18:05:37 HTTP JVM: After phase: PROCESS_VALIDATIONS 3 19.02.2012 18:05:37 HTTP JVM: Before phase: UPDATE_MODEL_VALUES 4 19.02.2012 18:05:37 HTTP JVM: After phase: UPDATE_MODEL_VALUES 4 19.02.2012 18:05:37 HTTP JVM: Before phase: INVOKE_APPLICATION 5 19.02.2012 18:05:37 HTTP JVM: After phase: INVOKE_APPLICATION 5 19.02.2012 18:05:37 HTTP JVM: Before phase: RENDER_RESPONSE 6 19.02.2012 18:05:37 HTTP JVM: 1329671137769 You shall not refresh 19.02.2012 18:05:37 HTTP JVM: After phase: RENDER_RESPONSE 6
Die Berechung der DataTable erfolgt zwei Mal währen des Partial Refreshs.
Ändert man die Ausführung des Buttons auf Partial Excecution, erhält man folgendes Ergebnis:
Button
<xp:button value="Label" id="button1" execMode="partial"> <xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="col1" /> </xp:button>
1. Öffnen der XPage
19.02.2012 18:13:54 HTTP JVM: Before phase: RENDER_RESPONSE 6 19.02.2012 18:13:54 HTTP JVM: 1329671634490 You shall not refresh 19.02.2012 18:13:54 HTTP JVM: After phase: RENDER_RESPONSE 6
2. Partial Refresh
19.02.2012 18:14:02 HTTP JVM: Before phase: RESTORE_VIEW 1 19.02.2012 18:14:02 HTTP JVM: After phase: RESTORE_VIEW 1 19.02.2012 18:14:02 HTTP JVM: Before phase: APPLY_REQUEST_VALUES 2 19.02.2012 18:14:02 HTTP JVM: 1329671642321 You shall not refresh 19.02.2012 18:14:02 HTTP JVM: After phase: APPLY_REQUEST_VALUES 2 19.02.2012 18:14:02 HTTP JVM: Before phase: PROCESS_VALIDATIONS 3 19.02.2012 18:14:02 HTTP JVM: After phase: PROCESS_VALIDATIONS 3 19.02.2012 18:14:02 HTTP JVM: Before phase: UPDATE_MODEL_VALUES 4 19.02.2012 18:14:02 HTTP JVM: After phase: UPDATE_MODEL_VALUES 4 19.02.2012 18:14:02 HTTP JVM: Before phase: INVOKE_APPLICATION 5 19.02.2012 18:14:02 HTTP JVM: After phase: INVOKE_APPLICATION 5 19.02.2012 18:14:02 HTTP JVM: Before phase: RENDER_RESPONSE 6 19.02.2012 18:14:02 HTTP JVM: 1329671642360 You shall not refresh 19.02.2012 18:14:02 HTTP JVM: After phase: RENDER_RESPONSE 6
Wieder erfolgt die Berechung der DataTable zwei Mal währen des Partial Refreshs.
Eine Änderung des Execution-Modes des Events verringert die Berechnung zumindest auf eine Neuberechnung pro Partial Refresh:
Button
<xp:button value="Label" id="button1" execMode="partial"> <xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="col1" execMode="partial"/> </xp:button>
1. Öffnen der XPage
19.02.2012 18:23:17 HTTP JVM: Before phase: RENDER_RESPONSE 6 19.02.2012 18:23:17 HTTP JVM: 1329672197161 You shall not refresh 19.02.2012 18:23:17 HTTP JVM: After phase: RENDER_RESPONSE 6
2. Partial Refresh
19.02.2012 18:23:18 HTTP JVM: Before phase: RESTORE_VIEW 1 19.02.2012 18:23:18 HTTP JVM: After phase: RESTORE_VIEW 1 19.02.2012 18:23:18 HTTP JVM: Before phase: APPLY_REQUEST_VALUES 2 19.02.2012 18:23:18 HTTP JVM: After phase: APPLY_REQUEST_VALUES 2 19.02.2012 18:23:18 HTTP JVM: Before phase: PROCESS_VALIDATIONS 3 19.02.2012 18:23:18 HTTP JVM: After phase: PROCESS_VALIDATIONS 3 19.02.2012 18:23:18 HTTP JVM: Before phase: UPDATE_MODEL_VALUES 4 19.02.2012 18:23:18 HTTP JVM: After phase: UPDATE_MODEL_VALUES 4 19.02.2012 18:23:18 HTTP JVM: Before phase: INVOKE_APPLICATION 5 19.02.2012 18:23:18 HTTP JVM: After phase: INVOKE_APPLICATION 5 19.02.2012 18:23:18 HTTP JVM: Before phase: RENDER_RESPONSE 6 19.02.2012 18:23:18 HTTP JVM: 1329672198875 You shall not refresh 19.02.2012 18:23:18 HTTP JVM: After phase: RENDER_RESPONSE 6
Selbst das Setzen der execId auf col1 ändert nichts – der Refresh führt immer zu einer Neuberechnung der DataTable.
Da diese Neuberechnung bei jedem Partial Refresh ausgelöst wird, werden Applikationen extrem ausgebremst. Daher sollte die Berechnung der Datenquelle wenn möglich auf „Computed On Page Load“ gesetzt werden.
Wenn dies nicht möglich ist, habe ich hier ein kleines Snippet, das die Berechnung nur dann durchführt, wenn die Datenquelle auch wirklich refresht wird:
<xp:repeat id="repeat1" rows="30" var="rCol"> <xp:this.value><![CDATA[#{javascript: function calculate(){ var data:java.util.Vector = new java.util.Vector(); data.add(java.lang.System.currentTimeMillis()); return data; } if( viewScope.containsKey("data") == false){ viewScope.data = calculate(); return viewScope.data; } if( com.ibm.xsp.ajax.AjaxUtil.isAjaxPartialRefresh(facesContext) === true ){ var pId = com.ibm.xsp.ajax.AjaxUtil.getAjaxComponentId( facesContext ); if( pId !== getClientId( 'repeat1' ) ) return viewScope.data; } viewScope.data = calculate(); viewScope.data}]]> </xp:this.value> <xp:label value="#{javascript:rCol }" id="label1"></xp:label> </xp:repeat>
[Hier ein Beispiel mit einem Repeat Control]
Die Version ist recht simpel aufgebaut, eine verbesserte Version werde ich in den nächsten Tagen als XSnippet veröffentlichen.
Wer jetzt nicht weiß, was ein Debug-PhaseListener ist, der findet entsprechenden Code in den XPages Snippets auf OpenNTF
http://openntf.org/XSnippets.nsf/snippet.xsp?id=a-simple-lifecyclelistener-
Hier noch der response von IBM im DPP :
PHAN8RPL3D has been logged to track this
Pingback: XPages: High Performance Applications | blog@hasselba.ch