Obecně se dá říci, že se v aplikaci při ajaxových requestech setkáme se třemi různými typy výjímek:

  1. Neočekávaná výjímka při ajaxovém načítání nějakého obsahu, např. pomocí JQuery metody load()
  2. Neočekávaná výjímka při ajaxovém requestu, který nenačítá žádný obsah, ale pouze volá nějaké vzdálené metody na serveru, typicky ajaxový submit formuláře nebo obecně ajaxové volání nějaké metody, která má za úkol provést update databáze
  3. Očekávaná výjímka, pomocí které uživatele informujeme, že z nějakého důvodu nemůže provést operaci – např. submit formuláře v případě, že nejsou vyplněné všechny povinné položky (za předpokladu, že není nebo nefunguje validace na straně klienta)

Zásadní rozdíl mezi očekávanými a neočekávanými výjímkami je ten, že v případě neočekávaných chceme vypsat kompletní výjímku včetně stacktrace (abychom rychle viděli, o jakou jde výjímku a kde v kódu nastala), kdežto u očekávaných výjímek budeme chtít pouze zobrazit chybovou hlášku uživateli (např. „Nevyplnili jste všechna povinná pole“). Určitě není žádoucí, aby se u očekávaných výjímek vypisoval celý stacktrace.

Řešení:

Ve Struts2 namapujeme obecnou exception na nějakou naši metodu, ve které chybu vypíšeme do konzole a uložíme ji např. do session včetně kompletního stacktrace, abychom k ní později mohli přistoupit (bude ukázáno dále).

Struts2 – struts.xml

<global-exception-mappings>
        <exception-mapping result="error" exception="java.lang.Exception"></exception-mapping>
</global-exception-mappings>

<global-results>
        <result name="error" type="chain">
                <param name="actionName">exceptionHandle</param>
        </result>
</global-results>

<action name="exceptionHandle" method="exceptionHandle" class="cz.nullpointer.actions.MyExceptionHandleAction">
        <result name="error">/myErrorPage.jsp</result>
        <result name="ajaxError" type="httpheader">500</result>
</action>

<action name="exceptionContent" method="exceptionContent" class=" cz.nullpointer.actions.MyExceptionHandleAction ">
        <result name="success">/myErrorPage.jsp</result>
</action>

V příkladu se budou všechny výjímky mapovat na akci exceptionHandle, kterou spravuje třída cz.nullpointer­.actions.MyEx­ceptionHandle­Action, konkrétně metoda exceptionHandle. Akci exceptionContent a resulty akce exceptionHandle „error“ a „ajaxError“ si vysvětlíme dále.

V metodě exceptionHandle budeme muset rozlišovat, **zda chyba vznikla během ajaxového požadavku, který nevrací žádný obsah (např. submit formuláře) nebo během ajaxového či „obyčejného“ requestu, který obsah (stránku) vrací. Z tohoto důvodu budeme do našich ajaxových requestů přidávat nějaký parametr, např „&noContentAjax­Call=true“. ** V prvním případě totiž potřebujeme vrátit chybový http status (např. 500), aby ajaxová funkce poznala, že je něco v nepořádku, a že se bude vypisovat výjímka (viz dále).

V případě requestu vracejícího obsah budeme vracet chybovou stránku a výjímka se tak zobrazí místo obsahu, který jsme chtěli původně načíst (a http status bude v tomto případě 200). Dále zde budeme rozlišovat, o jaký typ výjímky se jedná, jestli o neočekávanou (tedy všechny výjímky kromě očekávaných) nebo očekávánou. V příkladu všechny očekávané výjímky sloučíme do jedné runtime výjímky OcekavanaVyjim­kaException.

Java – OcekavanaVyjim­kaException

public class OcekavanaVyjimkaException extends RuntimeException {
  private static final long serialVersionUID = 1L;

        public OcekavanaVyjimkaException (String message) {
        super(message);
  }
}

Budeme předpokládat, že všechny očekávané výjímky v aplikaci budou vytvářet instanci exception OcekavanaVyjim­kaException, např. takto:

throw new OcekavanaVyjimkaException(“Nebyla vyplnna vechny povinnpole formule!”);

Java třída MyExceptionHan­dleAction pak může vypadat následovně:

Java – MyExceptionHan­dleAction

public class MyExceptionHandleAction extends ActionSupport {

boolean noContentAjaxCall;

public String exceptionHandle() {
        ActionContext ac = ActionContext.getContext();
        String exceptionStack = ac.getValueStack().findValue("exceptionStack").toString();
        String exception = ac.getValueStack().findValue("exception").toString();
        System.out.println(exceptionStack);     //Vypis do konzole
        setExceptionCustom(exception);  //Ulozeni do session, setter viz nize
        //Zde může být další log vyjímek např. do DB nebo do textového souboru
                if (!exception.contains(OcekavanaVyjimkaException.class.getName())) {
        setExceptionStackCustom(exceptionStack);        //Ulozeni do session, setter viz nize
        }
if (isNoContentAjaxCall ()) {
        return "ajaxError";     // httpheader=500
        } else {
                return ERROR;   // myErrorPage.jsp a httpheader=200
        }
}

public String exceptionContent() {
        return SUCCESS;
}

public boolean isNoContentAjaxCall () {
        return noContentAjaxCall;
}

public void setNoContentAjaxCall (boolean noContentAjaxCall) {
        this. noContentAjaxCall = noContentAjaxCall;
}
public Map getSession() {
        return ActionContext.getContext().getSession();
}
public void setExceptionStackCustom(String exceptionStackCustom) {
        getSession().put("exceptionStackCustom", exceptionStackCustom);
}

public String getExceptionStackCustom() {
        String result = (String) getSession().get("exceptionStackCustom");
        getSession().put("exceptionStackCustom", null);
        return result ;
}

public void setExceptionCustom(String exceptionCustom) {
        getSession().put("exceptionCustom", exceptionCustom);
}

public String getExceptionCustom() {
        String result = (String) getSession().get("exceptionCustom");
        getSession().put("exceptionCustom", null);
        return result ;
}
public String getExceptionCustomOnlyMessage(String exceptionText) {
return exceptionText.replace(OcekavanaVyjimkaException.class.getCanonicalName(), "").substring(2);
                getSession().put("exceptionCustom", null);      //oseknutí OcekavanaVyjimkaException
        }

Všimněte si, že v metodě exceptionHandle() uložíme stacktrace chyby do session pouze v případě, že se nejedná o instanci exception OcekavanaVyjim­kaException.

Čtení ze session je destruktivní, tzn. po první přečtení výjímky uložené v session dojde ke smazání, aby to v session zbytečně „neviselo“, když jsme již text chyby přečetli a zobrazili

Dále si všimňete, že na základě booleanovského parametru noContentAjaxCall se vrací result „ajaxError“ pro requesty nevracející obsah nebo „error“ pro requesty vracející obsah – mapování viz. výše uvedený struts.xml. Poslední komentář zaslouží metoda getExceptionCus­tomOnlyMessage(String exceptionText), která vrátí ze standardního exception pouze zprávu, tedy např. po

throw new OcekavanaVyjimkaException(“Nebyla vyplnna vechny povinnpole formule!”);

nevrátí metoda „OcekavanaVyjim­kaException: Nebyla vyplněna všechny povinná pole formuláře!„, ale pouze „Nebyla vyplněna všechny povinná pole formuláře!„ Tato metoda se volá s JSP, jak bude ukázáno v následujícím odstavci.

A nyní JSP myErrorPage.jsp (viz struts.xml), které se hned vrátí při ajax requestu vracejícímu obsah (noContentAjaxCall = false) nebo se teprve bude volat pro ajaxové requesty nevracející obsah (noContentAjaxCall = true) – viz dále.

JSP – myErrorPage.jsp

<s:if test=" noContentAjaxCall ">
        <s:set var="exception" value="%{exceptionCustom}"></s:set>
        <s:set var="exceptionStack" value="%{exceptionStackCustom}"></s:set>
</s:if>
<s:else>
        <s:set var="exception" value="%{exception}"></s:set>
        <s:set var="exceptionStack" value="%{exceptionStack}"></s:set>
</s:else>
<s:if test="#exceptionStack == null || #exceptionStack == ''">
        <s:property value="getExceptionCustomOnlyMessage(#exception)"/>
</s:if>
<s:else>
        <div class="error">
                <s:property value="#exception" />
         </div>
         <div class="errorStack">
                <s:property value="#exceptionStack" />
         </div>
</s:else>

V JSP vidíme, že podle prázdné či neprázdné proměnné exceptionStack se vypíše buď jen text exception nebo kompletní exception včetně stacktrace.

Ukázky použití. V případě metody load() načítající obsah např. do DIVu „#ajaxObsahDiv“ se nemusíme o nic starat:

$("#ajaxObsahDiv").load("/actionWhichThrowsException") ;

V případě chyby se vrátila rovnou chybová stránka (viz výše), která se zobrazí v DIVu „#ajaxObsahDiv“
V případě ajaxového requestu, který má za úkol např. pouze updatnout data v DB se již trošku starat musíme a chybovou stránku musíme zavolat ajaxově v error callbacku.

$.get(“/actionWhichThrowsExceptionfunction(result) {
        }).error(function(e) {
                showErrorAjaxDialog();
        });

Nyní zbývá definovat JQuery metodu použitou showErrorAjax­Dialog().

Pro zobrazení chyby za neobsahového ajax requestu využijeme komponentu z JQuery UI – dialog. Současné řešení předpokládá, že máme někde na stránce prázdný div ve tvaru:

<div id=”ajaxExceptionDialog”></div>

do kterého budeme načítat JQuery dialog. Pak můžeme v JQuery definovat metodu showErrorAjax­Dialog(), kterou budou volat ajaxové metody v případě chyby.

JQuery:

function showErrorAjaxDialog () {
        $("#ajaxExceptionDialog").load("exceptionContent.action?ajaxCall=true").dialog({
                "width":"500px"
        });
}

Tento ajax volá struts2 akci exceptionContent defnovanou výše“. Výsledkem je samozřejmě opět chybová stránka, která se tentokrát zobrazí v dialogu.

Závěr:

Toto řešení ukazuje především způsob práce s vyjímkami ve Struts2 a jejich distrubici do výsledku ajaxových requestů volaných pomocí JQuery. Řešení je samozřejmě využitelné i pro ostatní JQuery metody využívající ajax, včetně nejrůznějších pluginů, které často získávají svůj obsah pomocí ajaxového volání vracející JSON.

Komentáře

Smitha527 před 3 měsíci

This design is incredible! You obviously know how to keep a reader amused. ackgdfefaadkkfcc

reagovat

Smithe557 před 3 měsíci

I'm glad that it turned out so effectively and I hope it will continue in the future because it is so worthwhile and meaningful to the community. cgebkkcaeeegafga

reagovat

Smithd581 před měsícem

Very neat article post.Much thanks again. Much obliged. ecaffckcaeckbfgf

reagovat

Smithb871 před měsícem

This web site really has all the information and facts I wanted about this subject and didn't know who to ask. dddkfdfeeeddeekb

reagovat

Smithg905 před 12 dny

Heya im for the first time here. I discovered this board and I to uncover It truly helpful &amp it helped me out a whole lot. I hope to supply something back and aid other people such as you helped me. eckbacfgcgeeeagk

reagovat

Přidat komentář

  • Můžete použít Texy syntaxi, HTML není povoleno
  • Například: *kurzíva*, **tučně**, "text odkazu":adresa
 

Autor článku Lukáš Knápek

Od roku 2008 věnuji víceméně dobrovolně třetinu života vyvíjení webových aplikací, především prostřednictvím těchto jazyků a nástrojů: Java, Struts2, Hibernate, Spring, Maven, Dojo, JQuery, Tomcat a Eclipse. A to vše s vírou, že to bude užitečné nejen k vydělávání peněz pro potřeby Lukáše Knápka :-)