Implementando Resultados Personalizados em Struts 2 para Atualizações Parciais de Página

Em aplicações web modernas, é comum a necessidade de atualizar partes específicas de uma página sem recarregar completamente o conteúdo. Um cenário típico é a validação em tempo real de campos de formulário, como a verificação da disponibilidade de um nome de usuário. Este artigo explora como implementar essa funcionalidade em aplicações Struts 2, utilizando um resultado personalizado para comunicação assíncrona entre o servidor e o cliente.

O desafio reside em como o servidor (Action) pode retornar dados ao cliante (navegador) sem acionar um redirecionamento ou uma recarga completa da página. Redirecionamentos e Action Chain resultam em uma atualização completa, o que não atende ao requisito de atualização parcial. A solução proposta é criar um tipo de resultado customizado no Struts 2 que possa enviar dados de volta ao cliente, permitindo a atualização de elementos específicos da interface do usuário via AJAX.

A implementação envolve as seguintes etapas:

  1. Criação de um projeto web (ex: Struts2 AjaxResult).
  2. Inclusão das dependências do Struts 2.
  3. Configuração do web.xml para o filtro do Struts 2.
  4. Definição de uma classe POJO (ex: User.java).
  5. Criação de classes Action (uma base abstrata e uma específica, ex: BaseAction.java, UserAction.java).
  6. Desenvolvimento da classe de resultado customizado (ex: AjaxResult.java).
  7. Configuração do arquivo struts.xml.
  8. Criação da página JSP (ex: add.jsp).
  9. Criação de um arquivo JavaScript (ex: user\_add.js) e inclusão da biblioteca jQuery.

Configurações Principais

web.xml

Configuração essencial para o filtro do Struts 2:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <!-- Filtro central do Struts2 -->
    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

User.java

Classe de modelo para dados do usuário:


import java.io.Serializable;

public class User implements Serializable {
    private Long uid;
    private String username;
    private String password;

    // Getters and setters
    public Long getUid() { return uid; }
    public void setUid(Long uid) { this.uid = uid; }
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
}

BaseAction.java

Classe Action abstrata para encapsular funcionalidades comuns e suporte a ModelDriven:


import java.lang.reflect.ParameterizedType;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;

public abstract class BaseAction<t> extends ActionSupport implements ModelDriven<t> {
    private Class<?> entityClass;
    private T model;

    public static final String ADD_UI = "addUI";
    protected String addUI = ADD_UI;

    @SuppressWarnings("unchecked")
    public BaseAction() {
        try {
            ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
            this.entityClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
            this.model = (T) this.entityClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public T getModel() {
        return this.model;
    }

    public String getAddUI() {
        return addUI;
    }
}
</t></t>

UserAction.java

Action para gerenciar operações de usuário:


import com.opensymphony.xwork2.ActionContext;
// Assuming User and BaseAction are in appropriate packages
// import com.yourpackage.domain.User;
// import com.yourpackage.struts2.action.base.BaseAction;

public class UserAction extends BaseAction<User> {

    /**
     * Redireciona para a página de adição de usuário.
     * @return A string de resultado "addUI".
     */
    public String addUI() {
        // Lógica para carregar dados iniciais, se necessário
        return getAddUI();
    }

    /**
     * Verifica a existência de um nome de usuário.
     * Retorna uma mensagem indicando se o nome é utilizável ou já existe.
     * @return A string de resultado "success".
     */
    public String checkUserName() {
        User user = getModel();
        String message;

        // Simula a verificação no banco de dados
        if ("admin".equals(user.getUsername())) {
            message = "Este nome de usuário já está em uso.";
            // Coloca a mensagem no topo da ValueStack
            ActionContext.getContext().getValueStack().push(message);
        } else {
            message = "Este nome de usuário está disponível.";
            ActionContext.getContext().getValueStack().push(message);
        }
        return SUCCESS;
    }
}

AjaxResult.java

Resultado customizado para retornar dados via AJAX:


import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.Result;

public class AjaxResult implements Result {

    @Override
    public void execute(ActionInvocation invocation) throws Exception {
        HttpServletResponse response = ServletActionContext.getResponse();
        response.setCharacterEncoding("UTF-8"); // Codificação para evitar problemas com caracteres especiais

        // Pega o objeto do topo da ValueStack
        Object topOfStack = ActionContext.getContext().getValueStack().peek();
        String responseData = (topOfStack != null) ? topOfStack.toString() : "";

        // Escreve os dados na resposta HTTP
        response.getWriter().print(responseData);
    }
}

struts.xml

Configuração principle do Struts 2, incluindo a definição do resultado customizado:


<?xml version="1.0" encoding="UTF-8"?>
<struts>
    <!-- Habilita o modo de desenvolvimento (útil durante o desenvolvimento) -->
    <constant name="struts.devMode" value="true"/>
    <constant name="struts.ui.theme" value="simple"/>

    <!-- Inclui configurações de outros pacotes -->
    <include file="struts/struts-user.xml"/>

    <!-- Definição do pacote para resultados AJAX -->
    <package name="ajax-default" extends="struts-default">
        <result-types>
            <result-type name="ajax" class="com.yourpackage.result.AjaxResult" />
        </result-types>
    </package>
</struts>

struts-user.xml

Configuração específica para as Actions de usuário, incluindo o uso do resultado AJAX:


<?xml version="1.0" encoding="UTF-8"?>
<struts>
    <!-- Pacote para navegação padrão de usuário -->
    <package name="user" namespace="/" extends="struts-default">
        <action name="userAction_*" method="{1}" class="com.yourpackage.action.UserAction">
            <result name="addUI">/WEB-INF/jsp/user/add.jsp</result>
        </action>
    </package>

    <!-- Pacote para chamadas AJAX de usuário, utilizando o resultado 'ajax' -->
    <package name="userjson" namespace="/" extends="ajax-default">
        <action name="userJSONAction_*" method="{1}" class="com.yourpackage.action.UserAction">
            <result type="ajax" /> <!-- Associa qualquer resultado 'success' ao tipo 'ajax' -->
        </action>
    </package>
</struts>

add.jsp

Página de adição de usuário com campos e um local para feedback:


<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<html>
<head>
    <title>Adicionar Usuário</title>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <script src="${pageContext.request.contextPath}/js/jquery-1.4.2.js"></script>
    <script src="${pageContext.request.contextPath}/js/user_add.js"></script>
</head>
<body>
<form action="userAction_add.action" method="post"> <!-- Ação de submit -->
    <table>
        <tr>
            <td>Departamento:</td>
            <td>
                <select name="departmentId">
                    <option value="0">Selecione um departamento</option>
                    <option value="7">Diretoria</option>
                    <option value="1">Marketing</option>
                    <option value="2">Vendas</option>
                    <!-- ... outros departamentos ... -->
                </select>
            </td>
        </tr>
        <tr>
            <td>Nome de Usuário:</td>
            <td><input type="text" name="username" id="usernameField" /><label id="message"></label></td>
        </tr>
        <tr>
            <td>Senha:</td>
            <td><input type="password" name="password" /></td>
        </tr>
        <tr>
            <td colspan="2"><input type="submit" value="Registrar" /></td>
        </tr>
    </table>
</form>
</body>
</html>

(Nota: Os dados de departamento na lista suspensa seriam tipicamente carregados do banco de dados.)

user_add.js

Script JavaScript para lidar com a lógica AJAX:


var userModule = {
    // Inicializa os eventos da página
    initEvents: function() {
        // Remove bindings anteriores para evitar duplicação
        $("#usernameField").unbind("blur");
        // Adiciona um listener para o evento 'blur' (perda de foco) no campo de username
        $("#usernameField").bind("blur", function() {
            userModule.checkUsernameAvailability($(this).val());
        });
    },

    /**
     * Verifica a disponibilidade do nome de usuário via AJAX.
     * @param username O nome de usuário a ser verificado.
     */
    checkUsernameAvailability: function(username) {
        if (!username) {
            $("#message").text("Por favor, insira um nome de usuário.").css("color", "gray");
            return;
        }

        var requestParams = {
            "user.username": username // Mapeia para o modelo User na Action
        };

        $.post("userJSONAction_checkUserName.action", requestParams, function(data) {
            var messageLabel = $("#message");
            messageLabel.text(data); // Exibe a resposta do servidor

            if (data.includes("disponível")) {
                messageLabel.css("color", "blue");
            } else {
                messageLabel.css("color", "red");
            }
        });
    }
};

// Executa ao carregar o DOM
$(document).ready(function() {
    userModule.initEvents();
});

Execução

Ao acessar a URL http://localhost:8080/YourProjectName/userAction\_addUI.action, a página de adição de usuário será exibida. Ao digitar um nome de usuário no campo correspondente e perder o foco (evento blur), uma requisição AJAX será enviada para o servidor. Se o nome de usuário for "admin", a resposta do servidor, retornada via AjaxResult, será "Este nome de usuário já está em uso.". Caso contrário, a resposta será "Este nome de usuário está disponível.". O feedback visual é apresentado em tempo real.

Considerações

A abordagem de resultado customizado é eficaz para cenários simples de AJAX. No entanto, ela apresenta limitações:

  • Rigidez: O AjaxResult e a Action associada (ex: UserAction) estão fortemente acoplados. A Action empilha dados específicos na ValueStack, e o AjaxResult os recupera. Se a necessidade de dados retornar mudar, ambos, Action e Resultado, precisam ser modificados.
  • Reutilização Limitada: Se outros módulos (ex: gerenciamento de departamentos) também precisarem de respostas AJAX, seria necessário criar resultados customizados adicionais ou refatorar significativamente o AjaxResult para torná-lo genérico e reutilizável. Isso pode levar à duplicação de código ou a uma lógica de resultado complexa e difícil de manter.

Para cenários mais complexos ou para aumentar a flexibilidade e a reutilização, frameworks JavaScript modernos com bibliotecas de requisição HTTP ou o uso de bibliotecas como Jackson (para serialiazção JSON) diretamente na Action podem ser alternativas mais adequadas.

Tags: struts 2 ajax resultado customizado javascript jQuery

Publicado em 7-20 16:30