MVC
MVC é um design pattern implementado por vários frameworks, inclusive o NEO, que é uma sigla para Model, View, Controller (Modelo, Visão, Controle).
Cada parte do MVC tem uma responsabilidade na aplicação.
-
Model: É o modelo de dados da aplicação. Classes que guardam
informações.
-
View: É a camada de visão. Onde é montada a tela para o usuário.
Em J2EE a camada de visão é geralmente representada por JSPs.
-
Controller: É o que faz a integração da camada View com a
camada Model. Responde as requisições do usuário e
busca os dados necessários para montar a visão.
Controla o fluxo da requisição.
O NEO fornece um controller padrão e várias tags para ser utilizadas na visão.
A camada Model é escrita pelo desenvolvedor da aplicação.
Controller
O controller base do NEO é o MultiActionController,
responsável por responder as requisições que vieram do
usuário. Um controller responde a determinada URL que deve ser configurada
através de annotations. É necessário também configurar um servlet do NEO
no web.xml que repassará as requisições para os controllers corretos.
Registrando o servlet no web.xml
Para utilizar o controller do NEO é necessário registrar um servlet específico
no web.xml. Esse servlet receberá a requisição do cliente e rapassará ao
controller correto. Esse servlet é o DispatcherServlet. Cada DispatcherServlet
registrado é reponsável por um módulo.
Importante: Utilize o DispatcherServlet do NEO. br.com.linkcom.neo.controller.DispatcherServlet.
O Spring também possui um DispatcherServlet.
Veja um exemplo de web.xml com um módulo configurado:
[JSP]
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<listener>
<listener-class>br.com.linkcom.neo.core.web.init.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>x</servlet-name>
<servlet-class>br.com.linkcom.neo.controller.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>x</servlet-name>
<url-pattern>/x/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>resourceServlet</servlet-name>
<servlet-class>br.com.linkcom.neo.view.ResourceServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>resourceServlet</servlet-name>
<url-pattern>/resource/*</url-pattern>
</servlet-mapping>
</web-app>
-
<serlvet>...<servlet>
(linhas 12 - 15)
Declara um br.com.linkcom.neo.controller.DispatcherServlet para o módulo x.
O servlet-name deve ser o módulo que esse servlet registra.
-
<serlvet-mapping>...<servlet-mapping>
(linhas 16 - 19)
Registra a url /x/* para o servlet x. A URL deve ser sempre /[modulo]/*
Onde [modulo] é o módulo que esse servlet registra
A configuração é feita uma vez para cada módulo. Após a configuração do web.xml
a configuração dos controllers é feita através de annotations.
Criando um MultiActionController
Após configurar o DispatcherServlet no web.xml o Controller já pode ser criado.
É recomendável que o controller seja uma subclasse de
br.com.linkcom.neo.controller.MultiActionController. Fique atento para
importar a classe do NEO e não a do Spring.
Além de extender o MultiActionController o controller deve informar em qual
URL ele responderá através da anotação Controller. Veja o exemplo:
package teste;
import br.com.linkcom.neo.controller.Controller;
import br.com.linkcom.neo.controller.MultiActionController;
@Controller(path="/x/primeiro")
public class PrimeiroController extends MultiActionController {
}
A URL pode ser dividida em duas partes, a primeira é o módulo e a segunda pode
ser um nome qualquer que desejamos para o controller.
A URL onde o controller deve ser registrado sempre deve começar com o nome de algum módulo.
No caso, o módulo escolhido foi o 'x'. Um controller só será capaz de responder a uma requisição
se existir um DispatcherServlet cadastrado para o módulo.
A classe está correta mas ainda não possui nenhum código. O MultiActionController possui
o conceito de Actions. Actions são funcionalidades que desejamos executar no sistema.
Um MultiActionController pode ter quantas Actions forem necessárias, cada Action é representada
por um método. Um método para ser uma action deve possuir uma determinada assinatura.
Deve ser enviado na requisição um parâmetro especial indicando qual é a Action
desejada. Se o parâmetro não for informado, a Action executada será a Action padrão.
Veja como ficaria o controller com apenas uma Action padrão:
package teste;
import org.springframework.web.servlet.ModelAndView;
import br.com.linkcom.neo.controller.Controller;
import br.com.linkcom.neo.controller.DefaultAction;
import br.com.linkcom.neo.controller.MultiActionController;
import br.com.linkcom.neo.core.web.WebRequestContext;
@Controller(path="/x/primeiro")
public class PrimeiroController extends MultiActionController {
@DefaultAction
public ModelAndView actionPadrao(WebRequestContext request){
...
}
}
Para definirmos um método como Action padrão basta anotar esse método com @DefaultAction.
Um método para ser Action deve ter a seguinte assinatura:
public ModelAndView [nome](WebRequestContext request)
O método deve ser public retornar um ModelAndView e receber um parâmetro do tipo WebRequestContext.
O nome do método pode ser qualquer um. O método pode, opcionalmente, lançar qualquer exceção.
O ModelAndView é uma classe do Spring, ela representa o modelo e a visão que devem ser utilizados.
O modelo é simplismente um atributo colocado na requisição equivalente a (request.setAttribute) e
a visão é o JSP que desejamos mostrar.
Supondo que desejamos mostrar o seguinte jsp:
[JSP]
<html>
<body>
Olá
</body>
</html>
Devemos retornar um ModelAndView no controller com um nome lógico para esse JSP.
Veja o exemplo:
package teste;
import org.springframework.web.servlet.ModelAndView;
import br.com.linkcom.neo.controller.Controller;
import br.com.linkcom.neo.controller.DefaultAction;
import br.com.linkcom.neo.controller.MultiActionController;
import br.com.linkcom.neo.core.web.WebRequestContext;
@Controller(path="/x/primeiro")
public class PrimeiroController extends MultiActionController {
@DefaultAction
public ModelAndView actionPadrao(WebRequestContext request){
return new ModelAndView("jspOla");
}
}
O nome lógico do nosso jsp como informado pelo controller é jspOla. Esse nome deve ser transformado
para o nome físico do JSP. Essa transformação é apenas a concatenação de algumas informações
/WEB-INF/jsp/[modulo]/[nome informado pelo controller].jsp
Onde [modulo] é o nome do módulo onde o controller foi registrado. E [nome informado pelo controller] é
a string utilizada no construtor do ModelAndView.
No exemplo utilizado o nome do JSP seria:
/WEB-INF/jsp/x/jspOla.jsp
Então, se desejarmos que aquele jsp seja mostrado na tela do usuário devemos salvá-lo com o nome jspOla.jsp
no diretório /WEB-INF/jsp/x
Quando pedirmos no browser http://[host]:[porta]/[app]/x/primeiro a requisição cairá no
DispatcherServlet configurado no web.xml que por sua vez repassará a requisição para o PrimeiroController.
Como nenhuma Action foi informada, o método que for a Action padrão será executado, nesse caso actionPadrao.
Esse método retorna um ModelAndView com o model jspOla. O NEO receberá esse ModelAndView e fará a tradução
para /WEB-INF/jsp/x/jspOla.jsp e executará um forward da requisição para esse JSP.
Várias Actions em um MultiActionController
É possível que um controller possua várias Actions. Isso é útil para fazer com que ações
relacionadas fiquem dentro da mesma classe. Poderiamos querer uma action para listar os usuários
e outra para salvar um usuário no banco de dados, com o MultiActionController é possível
criar dois métodos (um para listar e outro para salvar) e chamá-los de acordo com a ação desejada.
Para escolher qual método deve ser chamado no MultiActionController um parâmetro ACAO deve ser enviada
na requisição. Por exemplo, suponhamos o seguinte controller:
package teste;
import org.springframework.web.servlet.ModelAndView;
import br.com.linkcom.neo.controller.Controller;
import br.com.linkcom.neo.controller.DefaultAction;
import br.com.linkcom.neo.controller.MultiActionController;
import br.com.linkcom.neo.core.web.WebRequestContext;
@Controller(path="/modulo/exemplo")
public class ExemploController extends MultiActionController {
@DefaultAction
public ModelAndView padrao(WebRequestContext request){
return new ModelAndView("pagina1");
}
public ModelAndView acao1(WebRequestContext request){
return new ModelAndView("pagina1");
}
public ModelAndView acao2(WebRequestContext request){
return new ModelAndView("pagina1");
}
}
Se pedirmos no browser: .../modulo/exemplo?ACAO=acao1 a requisição cairá no método acao1.
Se pedirmos no browser: .../modulo/exemplo?ACAO=acao2 a requisição cairá no método acao2.
Se pedirmos no browser: .../modulo/exemplo?ACAO=padrao a requisição cairá no método padrao.
Se pedirmos no browser: .../modulo/exemplo a requisição cairá no método padrao.
Quando nenhuma ação é enviada o método escolhido será o anotado com @DefaultAction
É possível também indicar outro nome de ação para o método através da annotation @Action. Veja o exemplo:
package teste;
import org.springframework.web.servlet.ModelAndView;
import br.com.linkcom.neo.controller.Action;
import br.com.linkcom.neo.controller.Controller;
import br.com.linkcom.neo.controller.DefaultAction;
import br.com.linkcom.neo.controller.MultiActionController;
import br.com.linkcom.neo.core.web.WebRequestContext;
@Controller(path="/modulo/exemplo")
public class ExemploController extends MultiActionController {
@DefaultAction
public ModelAndView padrao(WebRequestContext request){
return new ModelAndView("pagina1");
}
@Action("executar")
public ModelAndView acao1(WebRequestContext request){
return new ModelAndView("pagina1");
}
public ModelAndView acao2(WebRequestContext request){
return new ModelAndView("pagina1");
}
}
Se pedirmos no browser: .../modulo/exemplo?ACAO=executar a requisição cairá no método acao1.
Se pedirmos no browser: .../modulo/exemplo?ACAO=acao1 a requisição cairá no método acao1.
Se pedirmos no browser: .../modulo/exemplo?ACAO=acao2 a requisição cairá no método acao2.
Se pedirmos no browser: .../modulo/exemplo?ACAO=padrao a requisição cairá no método padrao.
Se pedirmos no browser: .../modulo/exemplo a requisição cairá no método padrao.
Commands
As actions do MultiActionController também podem receber um segundo parâmetro chamado command.
O command é um POJO com propriedades que são configuradas através dos parâmetros da requisição.
Se o command for de uma classe que possua o atributo nome, e for enviado um parâmetro nome na
requisição, o valor desse parâmetro será mapeado no atributo nome do POJO. Exemplo:
package teste;
public class MeuBean {
String nome;
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
}
package teste;
import org.springframework.web.servlet.ModelAndView;
import br.com.linkcom.neo.controller.Controller;
import br.com.linkcom.neo.controller.DefaultAction;
import br.com.linkcom.neo.controller.MultiActionController;
import br.com.linkcom.neo.core.web.WebRequestContext;
@Controller(path="/modulo/verificaNome")
public class VerificaNomeController extends MultiActionController {
@DefaultAction
public ModelAndView executar(WebRequestContext request, MeuBean meuBean){
String nome = meuBean.getNome();
request.setAttribute("nome", nome);
return new ModelAndView("mostraNome");
}
}
Se pedirmos no browser: .../modulo/exemplo?ACAO=executar&nome=joao a requisição cairá no método executar
e irá configurar o atributo nome da classe MeuBean
com o valor joao. A chamada meuBean.getNome() irá retornar joao.
Se não tivesse sido informado o parâmetro ACAO a requisição seria enviada para o método
executar do mesmo jeito, já que ele está anotado com
@DefaultAction.
Os atributos da classe não precisam ser do tipo String. O NEO irá converter os
parâmetros para os tipos necessários. Para converter os parâmetros o NEO
utiliza um DataBinder que extende o DataBinder do Spring. Esse dataBinder
utiliza PropertyEditors para converter de String para o tipo necessário.
O NEO já possui conversores para a maioria dos tipos de dados, mas se desejar
informar outro conversor é necessário sobrescrever o método
initBinder do MultiActionController.
Exemplo:
package teste;
import java.beans.PropertyEditor;
import java.util.Date;
import javax.servlet.ServletRequest;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.servlet.ModelAndView;
import br.com.linkcom.neo.controller.Controller;
import br.com.linkcom.neo.controller.DefaultAction;
import br.com.linkcom.neo.controller.MultiActionController;
import br.com.linkcom.neo.core.web.WebRequestContext;
@Controller(path="/modulo/verificaNome")
public class VerificaNomeController extends MultiActionController {
@DefaultAction
public ModelAndView executar(WebRequestContext request, MeuBean meuBean){
String nome = meuBean.getNome();
request.setAttribute("nome", nome);
return new ModelAndView("mostraNome");
}
@Override
protected void initBinder(ServletRequest request, ServletRequestDataBinder binder) throws Exception {
binder.registerCustomEditor(Date.class, new MyDatePropertyEditor());
}
}
Sempre que for necessário mapear os parâmetro da requisição para um controller um binder é criado.
O método initBinder é chamado passando-se uma referência desse
binder para uma possível inicialização.
Nesse caso estamos registrando um tradutor para o tipo Date.
View
A camada View no neo é na verdade um JSP. Após o controller realizar as tarefas necessárias
ele indica para qual JSP deve ser continuada a requisição. Essa indicação é feita através
de um objeto da classe ModelAndView.
A classe ModelAndView possui um construtor que recebe
uma argumento do tipo String, esse argumento é o nome do JSP que deve ser utilizado.
O nome informado é um nome lógico e deve ser transformado pelo NEO para se obter o nome
real do JSP. O nome real do JSP é formado da seguinte forma:
/WEB-INF/jsp/ + o módulo onde está registrado o controller + nome informado do jsp + .jsp
Um controller registrado na path /gestao/controllers/AdicionarRecurso
que retornasse
new ModelAndView("adicionarRecurso"); iria redirecionar
para
/WEB-INF/jsp/gestao/adicionarRecurso.jsp.
JSP Base
Para evitar que o cabeçalho e o rodapé de todos os JSPs sejam copiados em cada página
é possível utilizar o JSP base. O JSP base é uma página JSP que incluirá a página requisitada.
Ao invés da requisição ir direto para a página indicada pelo
ModelAndView ela irá para o base que incluirá no local
desejado a página pedida. O JSP base deve ter o nome base.jsp
e deve ficar dentro do diretório /WEB-INF/jsp/[módulo] onde [módulo] é o módulo
onde o controller está registrado. Exemplo (suponha que o controller está registrado no
módulo modulo1 e o retorno do ModelAndView foi
hello):
[JSP]
<HTML>
<BODY>
<jsp:include page="${bodyPage}" />
</BODY>
</HTML>
/WEB-INF/jsp/modulo1/base.jsp
[JSP]
Hello World!!!
/WEB-INF/jsp/modulo1/hello.jsp
Quando a requisição for redirecionada para o JSP ela será desviada para o base.jsp
e o ele incluirá através do código
<jsp:include page="${bodyPage}" />
o JSP requisitado. É como se tivéssemos apenas um JSP com o seguinte código:
[JSP]
<HTML>
<BODY>
Hello World!!!
</BODY>
</HTML>
O NEO irá detectar a presença do base.jsp automaticamente. Não é necessária
nenhuma configuração.
Resumo
No NEO, utilizamos o padrão MVC para responder às requisições. O controller no NEO
é representado pelo MultiActionController e deve ser anotado com @Controller para
informar em qual URL o controller responderá. A primeira parte da URL deve ser um
módulo que esteja registrado no web.xml através do DispatcherServlet. O MultiActionController
permite que várias Actions sejam escritas na mesma classe. Uma Action é um método
reponsável por atender determinada requisição. Para escolher qual o método (Action) adequado,
o MultiActionController verifica o parâmetro ACAO que veio na requisição. A Action pode
ter um command, se tiver o MultiActionController mapeia os parâmetros da requisição
aos atributos da classe command. A Action retorna um ModelAndView que representa qual
JSP deve ser acessado. O NEO traduz o nome do JSP para o nome físico do JSP e faz o
redirecionamento. Se existir um arquivo base.jsp para o módulo em questão será
feito o redirecionamento para esse arquivo, e esse arquivo inclui o JSP desejado.