Tatanka.com.br: Sérgio Lopes
Dite as regras na sua aplicação:
Web MVC com IoC e Reflection
Essa palestra foi apresentada como Tutorial no Conexão Java 2005.
O conteúdo abaixo contém mais explicações que
os slides exibidos, assim todos podem acompanhar melhor. Os códigos também estão disponíveis em links.
(no final da página estão todos os códigos juntos)
Sérgio L. Lopes Jr. (sergio arroba caelum.com.br)
A Palestra
- Construir nosso próprio framework Web do zero!
- Recursos:
- MVC
- IoC
- Reflection
- Refactoring
- Vejamos uma aplicação Web completa! (código aqui)
Problemas
- É grande
- É difícil de manter
- Desemprega o WebDesigner ou o Programador
- É feio, nojento, cheira mal...
MVC, uma solução
Dividir tudo em camadas
- Tudo pequeno
- Bem mais fácil de manter
- Garante o ganha-pão de todo mundo
- É bonito, agradável e cheiroso...
MVC, uma solução
As camadas:
- Modelo (Model)
- Visualização (View)
- Controlador (Controller)
Qual o papel de um controlador?
- Recebe a requisição
- Decide o que executar
- Executa!
- Redireciona pra View
- Vamos renomear nossa servlet para Controller
Onde ficam as ações?
Ações são o que está no "switch"... Mas:
- Deveriam estar fora de quem as controla
- Classes separadas para cada ação
- E como padronizar?
Command Pattern
- É um padrão de projeto comportamental
- Usado para executar comandos independentemente de suas implementações
public interface Action {
void execute(
HttpServletRequest request, HttpServletResponse response
);
}
Separando as ações
- Uma Action pra cada ação
- Controlador decide qual Action executar
- Criar Actions Somadora e Inversor copiando e colocando o código do switch no execute()
- Colocar
throws Exception
no execute() de Action
- Instanciar as Actions no switch e chamar execute depois do switch
- Refatorar: criar método criaAction(pagina) no Controller
HTML no código Java?
- Alguém ainda está perdendo o emprego!
- JSP para visualização; sem HTML nas Actions
- Questão: como passar atributos da Action para o JSP? setAttributte e getAttribute no request!
- Retirar HTML das Actions e criar arquivos: inversor.jsp, somadora.jsp, topo.jsp, rodape.jsp
controller.properties
this.properties = new Properties();
this.properties.load(getClass().getClassLoader().
getResourceAsStream("controller.properties"));
- Redirecionamos para o JSP assim:
request.getRequestDispatcher(properties.getProperty(pagina + ".view")).forward(request,response);
O que é Reflection?
- API para manipular classes, métodos, construtores, atributos etc em tempo de execução
- É a abstração da abstração
Independência
- Próximo passo: Actions independentes da Web
- Somar um número não depende da Web
- Inverter uma String não depende da Web
- Estamos atrelados ao request em dois pontos:
- setAttribute
- getParameter
Passando Atributos
- Action coloca o atributo no request com setAttribute(); JSP pega o resultado com getAttribute()
- E se a View pegasse os atributos direto na Action?
- Action teria um método:
public String getInvertida()
E o JSP exibe com: ${invertida}
Simples! Só isso! O resto é mágica...
- Isso se chama MVC Pull
- Conectar a View e a Action de forma transparente
- A View pega na Actions os objetos para serem mostrados
- A Action não precisa mais colocar os objetos em nenhum lugar
- E vamos usar mais Reflection!
Mudanças no Controller
- Instanciar o MagicRequest:
MagicRequest magicRequest = new MagicRequest(request, action);
e usamos no forward:
request.getRequestDispatcher(view).forward(magicRequest, response);
- JSP agora invoca o getAttribute() da MagicRequest
- Criamos os métodos getInvertida() e getNome() no Inversor; e getNumero1(), getNumero2(), getResultado() na Somadora
Mais Independência
- Estamos atrelados ao request em dois pontos:
setAttribute
- getParameter
Pegar parâmetros
- Usamos o request.getParameter()
- E se não pegássemos os parâmetros?
- E se eles fossem magicamente colocados na nossa Action?
- Hein??
Inversão de Controle
- Sem IoC: a classe é responsável por pegar de fora o que ela precisa
- Com IoC: o "ambiente" entrega pra classe tudo que ela precisa.
- O controle da aplicação é invertido! A classe não se serve mais, é servida...
- Traduzindo: nossa Action não precisaria mais pegar os parâmetros
- o ambiente (o Controlador) se encarrega de colocar os parâmetros dentro da Action
- Dependency Injection (Injeção de Dependências): nossa Action depende de certas coisas que são injetadas dentro dela
IoC: Setter Injection
- Mas como as coisas chegam magicamente para a Action?
- 1° tipo de IoC: Setter Injection
- Action tem setters pra tudo que ela precisar
- Controlador chama esse setter passando o parâmetro esperado!
- Exemplo para o Inversor:
- Método
setNome(String nome)
na classe Inversor
- O controlador se encarrega de fazer:
inversor.setNome(request.getParameter("nome"));
- Tiramos os request.getParameter() da Action
- Criamos, nas Actions, campos e seus setters para cada parâmetro
- Agora falta fazer a mágica acontecer no controlador... Mais Reflection!
Setter Injection
paraCada (metodo nos metodosDaAction) {
se (é um setter) {
procura um parametro com esse nome;
chama o setter passando o valor do parametro;
}
}
for (Method method : action.getClass().getMethods()) {
String m = method.getName();
if (m.startsWith("set")) {
String p = request.getParameter(m.substring(3,4).toLowerCase() + m.substring(4));
if (p != null) {
method.invoke(action, p);
}
}
}
Independentes!
- Nossas Actions não dependem mais do Request ou do Response!
- Podemos tirar o request e o response do método execute()
- Refatorar: No Eclipse: Refactor -> Change Method Signature
- Código da Action aqui
Muita independência?
- Mas, e se precisamos...
- ...fazer aquela mega-super-ultra feature que precisa muito do Request?
- ...da Sessão do usuário para gravar uma informação?
- Solução: mais mágica! Mais IoC! Mais Reflection!
IoC: Constructor Injection
- 2° tipo de IoC: Contructor Injection
- A Action tem um construtor que recebe o tipo de argumento necessário
- O controlador chama esse construtor passando exatamente o que a Action precisa
- Exemplo para o Inversor:
- Construtor
Inversor(HttpServletRequest request){ ... }
na classe Inversor
- O controlador magicamente faz:
Action a = new Inversor(request);
Diferentes injeções
- Diferença entre setter injection (SI) e constructor injection (CI):
- SI: setter chamado é resolvido em função do nome do campo
- CI: parâmetros passados ao construtor são resolvidos em função do tipo
- Constructor Injection é mais recomendado porque é atômico. Todas as dependências são injetadas
de uma vez e somente na instanciação. Com Setter Injection podemos esquecer alguma dependência e não é atômico.
Constructor Injection: Exemplo
public class Login implements Action {
private HttpServletRequest request;
private String nome;
public Login(HttpServletRequest r) {
request = r;
}
public void execute() {
this.request.getSession().
setAttribute("usuario", this.nome);
}
//setter para o campo nome
}
- Criamos essa classe e colocamos no controller.properties
- Criamos login.jsp
Constructor Injection: Implementando
construtor := o construtor da Action;
paraCada (parametro nos parametrosDoConstrutor) {
acha um objeto que satisfaça o tipo do parametro;
}
chama o construtor passando os objetos encontrados;
- Principal questão: onde achar um objeto que satisfaça aquele tipo?
- Resposta: criamos um contexto onde o controlador procura objetos (um simples mapa)
Constructor Injection: O contexto
- Mapa onde salvamos objetos que podem ser usados em constructor injection
- Colocar um objeto nesse contexto significa que eles estão disponíveis para
as Actions que os solicitarem
Map context = new HashMap();
context.put(HttpServletRequest.class, request);
context.put(HttpServletResponse.class, response);
- Novo criaAction() usando IoC:
private Action criaAction(String pagina, Map context) throws Exception {
Class action = Class.forName(properties.getProperty(pagina + ".action"));
Constructor[] constructors = action.getConstructors();
if (constructors.length == 0)
return (Action) action.newInstance();
Constructor constructor = constructors[0];
Class[] types = constructor.getParameterTypes();
Object[] parameters = new Object[types.length];
int i = 0;
for (Class type: types) {
parameters[i++] = context.get(type);
}
return (Action) constructor.newInstance(parameters);
}
Como está o framework?
- Separamos tudo em camadas
- A visualização está nos JSPs
- O controlador é totalmente reaproveitável
- As ações estão em classes separadas
- São totalmente independentes da Web (ou qualquer outro contexto)
- Mas, se precisamos de algo específico, conseguimos também
- Tudo configurado num .properties muito simples
- Está muito bem encaminhado; agora é só melhorar...
Sugestões: Algumas melhorias no nosso framework
- Tirar o parâmetro "pagina"
- Não usar a interface Action!
- Podemos configurar qual método chamar
- Ou marcar o método com uma Anotação
- Setter Injection que converte automaticamente para outros tipos
- Constructor Injection mais inteligente (instanciando objetos também)
- PicoContainer (www.picocontainer.org)
- Há mais 2 "tipos de Reflection": nos campos (Fields) e anotações (Annotations)
Sugestões: Alguns links legais
Achou algum erro? Tem alguma sugestão ou dúvida? Escreva pra mim em sergio arroba caelum.com.br
Tatanka.com.br: Sérgio Lopes