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:
- Criação de um projeto web (ex: Struts2 AjaxResult).
- Inclusão das dependências do Struts 2.
- Configuração do
web.xmlpara o filtro do Struts 2. - Definição de uma classe POJO (ex:
User.java). - Criação de classes Action (uma base abstrata e uma específica, ex:
BaseAction.java,UserAction.java). - Desenvolvimento da classe de resultado customizado (ex:
AjaxResult.java). - Configuração do arquivo
struts.xml. - Criação da página JSP (ex:
add.jsp). - 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
AjaxResulte a Action associada (ex:UserAction) estão fortemente acoplados. A Action empilha dados específicos naValueStack, e oAjaxResultos 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
AjaxResultpara 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.