Um aplicativo da Web decente consiste em uma mistura de padrões de design. Vou mencionar apenas os mais importantes.
O padrão de design principal (arquitetural) que você deseja usar é o padrão Model-View-Controller . O Controlador deve ser representado por um Servlet que (in) cria / usa diretamente um Modelo e uma Visualização específicos com base na solicitação. O Modelo deve ser representado por classes Javabeanas. Isso geralmente é mais divisível no Modelo de Negócios, que contém as ações (comportamento) e no Modelo de Dados, que contém os dados (informações). A Visualização deve ser representada por arquivos JSP que têm acesso direto ao Modelo ( Dados ) pelo EL (Expression Language).
Depois, há variações baseadas em como as ações e os eventos são tratados. Os populares são:
MVC baseado em solicitação (ação) : este é o mais simples de implementar. O ( Negócios ) Modelo trabalha diretamente com HttpServletRequest
e HttpServletResponse
objetos. Você precisa reunir, converter e validar os parâmetros de solicitação (principalmente) você mesmo. A View pode ser representada por HTML / CSS / JS simples e não mantém o estado entre solicitações. É assim que, entre outros, o Spring MVC , Struts and Stripes funciona.
MVC baseado em componentes : isso é mais difícil de implementar. Mas você acaba com um modelo e uma visualização mais simples, nos quais toda a API Servlet "bruta" é abstraída completamente. Você não precisa reunir, converter e validar os parâmetros de solicitação. O Controller executa esta tarefa e define os parâmetros de solicitação reunidos, convertidos e validados no Modelo . Tudo o que você precisa fazer é definir métodos de ação que funcionem diretamente com as propriedades do modelo. A Visualização é representada por "componentes" no tipo de taglibs JSP ou elementos XML que, por sua vez, geram HTML / CSS / JS. O estado da vistapara os pedidos subsequentes é mantido na sessão. Isso é particularmente útil para eventos de conversão, validação e alteração de valor no servidor. É assim que, entre outros , JSF , Wicket e Play! trabalho.
Como observação lateral, hobbying com uma estrutura MVC doméstica é um exercício de aprendizado muito bom, e eu o recomendo desde que você o mantenha para fins pessoais / privados. Mas quando você se tornar profissional, é altamente recomendável escolher uma estrutura existente em vez de reinventar a sua. Aprender uma estrutura existente e bem desenvolvida leva menos tempo a longo prazo do que desenvolver e manter uma estrutura robusta.
Na explicação detalhada abaixo, restringirei-me a solicitar MVC baseado, pois é mais fácil de implementar.
Primeiro, a parte Controller deve implementar o padrão Front Controller (que é um tipo especializado de padrão Mediador ). Ele deve consistir em apenas um servlet que fornece um ponto de entrada centralizado de todas as solicitações. Ele deve criar o Modelo com base nas informações disponíveis pela solicitação, como pathinfo ou servletpath, o método e / ou parâmetros específicos. O modelo de negócios é chamado Action
no HttpServlet
exemplo abaixo .
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
Action action = ActionFactory.getAction(request);
String view = action.execute(request, response);
if (view.equals(request.getPathInfo().substring(1)) {
request.getRequestDispatcher("/WEB-INF/" + view + ".jsp").forward(request, response);
}
else {
response.sendRedirect(view); // We'd like to fire redirect in case of a view change as result of the action (PRG pattern).
}
}
catch (Exception e) {
throw new ServletException("Executing action failed.", e);
}
}
A execução da ação deve retornar algum identificador para localizar a exibição. O mais simples seria usá-lo como nome do arquivo JSP. Mapeie este servlet em um específico url-pattern
em web.xml
, por exemplo /pages/*
, *.do
ou mesmo apenas *.html
.
Em caso de prefixo-padrões como, por exemplo, /pages/*
você poderia então invocar URL como http://example.com/pages/register , http://example.com/pages/login , etc e fornecer /WEB-INF/register.jsp
, /WEB-INF/login.jsp
com as ações POST GET apropriado e . As peças register
, login
etc, estão disponíveis request.getPathInfo()
como no exemplo acima.
Quando você usa padrões de sufixo, como *.do
, *.html
etc, pode chamar URLs como http://example.com/register.do , http://example.com/login.do , etc, e deve alterar o exemplos de código nesta resposta (também o ActionFactory
) para extrair as partes register
e login
em request.getServletPath()
vez disso.
O Action
deve seguir o padrão de estratégia . Ele precisa ser definido como um tipo de resumo / interface que deve fazer o trabalho com base nos argumentos passados do método abstrato (essa é a diferença com o padrão de comando , em que o tipo de resumo / interface deve fazer o trabalho com base no argumentos passados durante a criação da implementação).
public interface Action {
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
Você pode tornar o Exception
mais específico com uma exceção personalizada como ActionException
. É apenas um exemplo básico de kickoff, o resto é com você.
Aqui está um exemplo de um LoginAction
que (como o próprio nome diz) efetua login no usuário. Por sua User
vez, é um modelo de dados . O View está ciente da presença do User
.
public class LoginAction implements Action {
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = userDAO.find(username, password);
if (user != null) {
request.getSession().setAttribute("user", user); // Login user.
return "home"; // Redirect to home page.
}
else {
request.setAttribute("error", "Unknown username/password. Please retry."); // Store error message in request scope.
return "login"; // Go back to redisplay login form with error.
}
}
}
A ActionFactory
deve seguir o padrão Factory Method . Basicamente, ele deve fornecer um método criacional que retorne uma implementação concreta de um tipo abstrato / interface. Nesse caso, ele deve retornar uma implementação da Action
interface com base nas informações fornecidas pela solicitação. Por exemplo, o método e pathinfo (o pathinfo é a parte após o contexto e o caminho do servlet na URL da solicitação, excluindo a cadeia de consulta).
public static Action getAction(HttpServletRequest request) {
return actions.get(request.getMethod() + request.getPathInfo());
}
Por actions
sua vez, deve haver alguma estática / em todo o aplicativo, Map<String, Action>
que contém todas as ações conhecidas. Cabe a você como preencher este mapa. Codificação rígida:
actions.put("POST/register", new RegisterAction());
actions.put("POST/login", new LoginAction());
actions.put("GET/logout", new LogoutAction());
// ...
Ou configurável com base em um arquivo de propriedades / configuração XML no caminho de classe: (pseudo)
for (Entry entry : configuration) {
actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance());
}
Ou dinamicamente, com base em uma varredura no caminho de classe, para classes que implementam uma certa interface e / ou anotação: (pseudo)
for (ClassFile classFile : classpath) {
if (classFile.isInstanceOf(Action.class)) {
actions.put(classFile.getAnnotation("mapping"), classFile.newInstance());
}
}
Lembre-se de criar um "não fazer nada" Action
para o caso em que não há mapeamento. Deixe, por exemplo, retornar diretamente o request.getPathInfo().substring(1)
então.
Outros padrões
Esses eram os padrões importantes até agora.
Para dar um passo adiante, você pode usar o padrão Facade para criar uma Context
classe que, por sua vez, agrupa os objetos de solicitação e resposta e oferece vários métodos de conveniência para delegar aos objetos de solicitação e resposta e passar isso como argumento para o Action#execute()
método. Isso adiciona uma camada abstrata extra para ocultar a API Servlet bruta. Você deve basicamente terminar com zero import javax.servlet.*
declarações em todas as Action
implementações. Em termos de JSF, é isso que as classes FacesContext
e ExternalContext
estão fazendo. Você pode encontrar um exemplo concreto nesta resposta .
Depois, há o padrão State para o caso em que você deseja adicionar uma camada de abstração extra para dividir as tarefas de reunir os parâmetros de solicitação, convertê-los, validá-los, atualizar os valores do modelo e executar as ações. Em termos de JSF, é isso que LifeCycle
está fazendo.
Depois, há o padrão Composite para o caso em que você deseja criar uma visualização baseada em componente que pode ser anexada ao modelo e cujo comportamento depende do estado do ciclo de vida baseado em solicitação. Em termos JSF, é isso que UIComponent
representa.
Dessa forma, você pode evoluir pouco a pouco em direção a uma estrutura baseada em componentes.
Veja também: