conhecendo o spring

136
Simplificando o desenvolvimento Java/Java EE - 2006 Maurício Linhares http://techbot.me/

Upload: mauricio-linhares

Post on 06-Jun-2015

719 views

Category:

Technology


27 download

DESCRIPTION

Apresentação desenvolvida originalmente para trabalho de disciplina na graduação de Tecnologia em Desenvolvimento para a Internet em 2006.

TRANSCRIPT

Page 1: Conhecendo o Spring

Simplificando o desenvolvimento Java/Java EE - 2006 Maurício Linhares http://techbot.me/

Page 2: Conhecendo o Spring

No início era apenas o profeta...

Eu trago a linguagem

que vai funcionar em

todas as plataformas! Liberdade!

Page 3: Conhecendo o Spring

Mas a liberdade virou Libertinagem

Eu faço melhor!

Não! Eu faço

melhor!

Só quem tá certo aqui sou eu!

Page 4: Conhecendo o Spring

Acabou a farra! Agora quem não

seguir as especificações vai para os campos de

concentração!

JCP Hitler

Page 5: Conhecendo o Spring

EJB, BMP, JMS, CMP, JAAS, JSP, JSTL, JCA,

JTA, JDBC, AWT, JNDI!

Aprendam ou morram!

Page 6: Conhecendo o Spring

Porque é tão complicado desenvolver

aplicações Java EE?

Page 7: Conhecendo o Spring

Já tá na hora de

simplificar isso.

Page 8: Conhecendo o Spring

Vamos criar um

Framework para

resolver esse

problema!

Page 9: Conhecendo o Spring

Vamos criar um framework!

Mas o que é um framework?

Page 10: Conhecendo o Spring

¡  Um esqueleto semi-pronto para ser estendido;

¡  Contém implementações genéricas;

¡  Deve tornar o difícil fácil e o impossível possível;

O que é?

Page 11: Conhecendo o Spring

E então surge o grande Salvador!

framework

Page 12: Conhecendo o Spring

E qual é a missão dele?

¡  Desenvolver aplicações Java EE tem que ser mais fácil;

¡  Programar para interfaces é melhor do que programar para classes concretas;

¡  Desenvolvimento orientado a objetos é mais importante do que a tecnologia utilizada;

Page 13: Conhecendo o Spring

O que ele oferece pra isso?

¡  Um container de Inversão de Controle;

¡  Abstração para o controle de transações;

¡  Abstração para bancos de dados; ¡  Programação Orientada a Aspectos; ¡  Integração com vários outros

projetos;

Page 14: Conhecendo o Spring

E quem disse que eu sei o que é essa tal de

Inversão de Controle?

Page 15: Conhecendo o Spring

A Inversão de Controle redefine...

...dois princípios da Orientação a Objetos

Page 16: Conhecendo o Spring

Objetos não devem mostrar suas intimidades...

Page 17: Conhecendo o Spring

Objetos devem ser auto contidos...

Page 18: Conhecendo o Spring

E o que é que a

Inversão de controle

diz?

Page 19: Conhecendo o Spring

Objetos devem informar os contratos que eles precisam implementar

Trabalhando com contratos (ou interfaces) as intimidades deles não vão interessar a ninguém

Page 20: Conhecendo o Spring

Os objetos devem dizer de quais objetos eles dependem

Pra que alguém possa fornecer essas dependências

Page 21: Conhecendo o Spring

Inversão de Controle também é...

... uma inversão de responsabilidades

Page 22: Conhecendo o Spring

Inversão de qual responsabilidade?

Page 23: Conhecendo o Spring

Os dois princípios foram realmente redefinidos?

O que? Quando? Porque? Onde está Wally?

Page 24: Conhecendo o Spring

Existem dois tipos de inversão

¡  Injeção de dependências;

¡  Busca por dependências;

Page 25: Conhecendo o Spring

Busca por dependências

¡  Quem precisa, tem que correr atrás;

¡  As dependências ficam em um contexto geral do sistema, ou não;

¡  Costumam ser disponibilizadas em eventos específicos;

Page 26: Conhecendo o Spring

Injeção de dependências

¡  Quem precisa, diz que precisa e recebe na mão;

¡  As dependências ficam onde elas quiserem ficar;

¡  Podem ser disponibilizadas a qualquer momento, depende de quem precisa;

Page 27: Conhecendo o Spring

Voltando ao nosso assunto...

... Vejamos como é possível acessar o Spring

Page 28: Conhecendo o Spring

Acessando o Spring

¡  Configurado com (mais um) arquivo XML;

¡  Os objetos ficam no ApplicationContext;

¡  Os objetos não podem depender de objetos que não estejam no ApplicationContext;

Page 29: Conhecendo o Spring

Um ApplicationContext pode ser criado pelas seguintes classes:

¡  FileSystemXmlApplicationContext ¡  ClassPathXmlApplicationContext ¡  Em uma aplicação web o contexto

normalmente é carregado por um ServletContextListener específico do Spring

Page 30: Conhecendo o Spring

Meu primeiro SpringBean

<bean id="autenticador" class="org.maujr.newsletter.Autenticador">

<property name="usuario"> <value>mauricio.linhares</value> </property> </bean>

Page 31: Conhecendo o Spring

Como ficam os objetos

¡  Todos são singletons por default; ¡  Cada um deve ter o seu próprio “id”;

¡  Podem ser referenciados em um arquivo e utilizados em outro;

¡  As dependências só são inseridas uma única vez;

¡  Não é possível acessar objetos que não tem um contrutor público;

Page 32: Conhecendo o Spring

Mas eu não quero

um Singleton!

Page 33: Conhecendo o Spring

Calma, calma!

<bean id="autenticador" class="org.maujr.newsletter.Autenticador“

singleton=“false”> <property name="usuario"> <value>mauricio.linhares</value> </property> </bean>

Page 34: Conhecendo o Spring

O ApplicationContext transforma automaticamente de String para:

¡  Tipos numéricos; ¡  Class; ¡  URL; ¡  File; ¡  Array de Strings (separando por

vírgulas);

Page 35: Conhecendo o Spring

E os outros tipos?

Page 36: Conhecendo o Spring

São transformados de String

para os seus objetos usando PropertyEditors customizados

Page 37: Conhecendo o Spring

Como eu crio um PropertyEditor?

¡  Extendendo a classe PropertyEditorSupport;

¡  Implementando os métodos setAsText() e getAsText();

¡  E basta registrar ele no ApplicationContext;

¡  99% das vezes isso não é necessário;

Page 38: Conhecendo o Spring

<property/>

Define uma propriedade do bean que vai ser inserida pelo Spring, pode conter diversas outras tags dentro dela

Page 39: Conhecendo o Spring

<constructor-arg/>

Funciona da mesma maneira que a tag <property/>, mas passa as dependências como parâmetros do construtor

Page 40: Conhecendo o Spring

<ref/>

Referencia um outro bean que esteja configurado. Pode ser um bean no mesmo arquivo ou em arquivos diferentes

Page 41: Conhecendo o Spring

Outras tags

¡  <props/> ¡  <list/> ¡  <map/> ¡  <set/> ¡  <value/>

Page 42: Conhecendo o Spring

E se eu não tiver

como chamar

um construtor

?

Page 43: Conhecendo o Spring

FactoryBeans estão na mão!

Page 44: Conhecendo o Spring

Implementando a interface FactoryBean

¡  O método getObject() retorna o objeto que essa fábrica deve produzir;

¡  O método isSingleton() avisa se o objeto produzido é um singleton ou não;

¡  O método getObjectType() deve retonar o tipo (Class) do objeto que esta fábrica produz;

Page 45: Conhecendo o Spring

Como implementar o acesso ao banco de dados no nosso sistema?

Por que não deixar as classes acessarem a classe utilitária diretamente?

Page 46: Conhecendo o Spring

Nosso primeiro FactoryBean

O gerenciador das conexões com o banco de dados

Page 47: Conhecendo o Spring

Qual a responsabilidade dele?

Abrir conexões com o banco de dados para que os repositórios possam trabalhar livremente

Page 48: Conhecendo o Spring

Como funciona o nosso banco?

Banco de dados ObjectServer

Conexões ObjectContainers

Page 49: Conhecendo o Spring

Em código...

ObjectServer bancoDeDados = Db4o.openServer(nomeDoArquivo, porta);

Page 50: Conhecendo o Spring

O que é um Repositório?

Page 51: Conhecendo o Spring

Um adaptador entre mecanismos distintos

Page 52: Conhecendo o Spring

Como é o nosso repositório?

public interface Repositorio {

public void adicionar(Persistivel objeto); public void atualizar(Persistivel objeto); public void remover (Persistivel objeto); public Persistivel pegarPeloId(Long id); public List pegarTodos(Class clazz);

}

Page 53: Conhecendo o Spring

A nossa implementação dele...

...é a classe RepositorioDoDb4o

Page 54: Conhecendo o Spring

Ei! Essa classe é abstrata!

Page 55: Conhecendo o Spring

O método getContainer() é abstrato

Então nós temos que encontrar uma maneira de oferecer os ObjectContaners da nossa fábrica

Page 56: Conhecendo o Spring

Mas ainda existe outro problema

O Repositório é um singleton, mas tem que receber novos ObjectContainers a cada chamada do método getContainer()

Page 57: Conhecendo o Spring

Como resolver isso?

¡  Criar uma classe que estenda a RepositorioDoDb4o;

¡  Implementar a interface ApplicationContextAware;

¡  Pegar o ApplicationContext e pegar os ObjectContainers diretamente;

Page 58: Conhecendo o Spring

Em código.... public class RepositorioSpring extends RepositorioDoDb4o

implements ApplicationContextAware { private ApplicationContext context; public ObjectContainer getContainer() { return context.getBean(“objectContainerFactory”); }

public void setApplicationContext(ApplicationContext context) { this.context = context; }

}

Page 59: Conhecendo o Spring

Isso tá muito

complicado!

Page 60: Conhecendo o Spring

Injeção de métodos

Um objeto Singleton pode depender de objetos não-Singleton sem problemas

Page 61: Conhecendo o Spring

Como isso é feito?

O Spring responde pela dependência em vez do objeto dependente

Page 62: Conhecendo o Spring

Em código

<bean id="repositorio" class="org.maujr.persistencia.db4o.RepositorioDoDb4o">

<lookup-method bean="objectContainerFactory" name="getContainer"/> </bean> <bean id="objectContainerFactory" class="org.maujr.persistencia.db4o.ObjectContainerFactory"/>

Page 63: Conhecendo o Spring

O que vai acontecer?

Sempre que o método getContainer() for chamado no Repositório, o FactoryBean que cria os ObjectContainers vai ser chamado

Page 64: Conhecendo o Spring

Complicando um pouco mais...

Ninguém está fechando as transações nem as conexões com o banco. Onde isto está acontecendo?

Page 65: Conhecendo o Spring

No FiltroDoDb4o

Ele faz o “commit” ou “rollback” das transações e no fim fecha a conexão com o banco de dados;

Page 66: Conhecendo o Spring

Em código...

try { chain.doFilter(request, response); ServerUtil.commitTransaction();

} catch (Exception e) {

e.printStackTrace(); ServerUtil.rollbackTransaction();

} finally {

ServerUtil.closeContainer(); }

Page 67: Conhecendo o Spring

E qual é a vantagem

disso?

Page 68: Conhecendo o Spring

Todo o código está livre da interação com o banco

O Spring provê mecanismos mais simples de se utilizar para os frameworks mais conhecidos, como o Hibernate

Page 69: Conhecendo o Spring

Spring MVC

Simplificando o Desenvolvimento web com o Spring

Page 70: Conhecendo o Spring

Peraí!

É necessário adicionar o Servlet que vai responder as requisições do Spring no web.xml com um mapeamento Sem o Servlet do Spring ele não pode responder a requisições feitas ao servidor

Page 71: Conhecendo o Spring

Características

¡  Várias classes de suporte para formulários e requisições normais;

¡  Transformação automática de valores do request para objetos;

¡  Suporte transparente a vários mecanismos de visualização;

¡  Totalmente configurado dentro do próprio Spring

¡  Os controladores não são thread-safe;

Page 72: Conhecendo o Spring

Mapeadores de URL

Existem várias estratégias disponíveis, mas os mais utilizados são BeanNameUrlHandlerMapping e o SimpleUrlHandlerMapping

Page 73: Conhecendo o Spring

BeanNameUrlHandlerMapping

Direciona as requisições para as classes através dos seus identificadores. Uma requisição para “/contados.html” seria enviada para o bean com o “ID” “/contados.html”

Page 74: Conhecendo o Spring

SimpleUrlHandlerMapping

Mais complexo, direciona as requisições através de um mapa de chaves e valores. É possível usar “curingas” “como “*” nas URLs.

Page 75: Conhecendo o Spring

Em código... <bean id="urlMapper"

class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<property name="mappings"> <props> <prop key="/noticia.html"> inserirNoticiaAction </prop> <prop key="/noticias.html"> listarNoticiasAction </prop> <prop key="/principal.html"> principalAction </prop> </props>

</property> </bean>

Page 76: Conhecendo o Spring

ViewResolvers

¡  São as classes responsáveis pela descoberta das views;

¡  Transformam nomes lógicos em uma requisição para a view apropriada;

Page 77: Conhecendo o Spring

Em código....

<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">

<property name="basenames"> <value>views</value> </property> <property name="defaultParentView"> <value>mostrar-principal</value> </property> </bean>

Page 78: Conhecendo o Spring

Configurando...

mostrar-principal.class= org.springframework.web.servlet.view.velocity.VelocityView

mostrar-principal.url= org/maujr/velocity/principal.html

mostrar-principal.exposeSpringMacroHelpers= true

noticia.url=org/maujr/velocity/noticia.html noticia.exposeSpringMacroHelpers=true noticias.url=org/maujr/velocity/noticias.htm

Page 79: Conhecendo o Spring

A interface Controller

¡  É a interface base para a parte web do Spring;

¡  Define um único método, “handleRequest()” que deve retornar um objeto ModelAndView;

¡  Esse é o método chamado quando uma dessas classes recebe uma requisição HTTP;

Page 80: Conhecendo o Spring

Os objetos ModelAndView

¡  Servem como abstração para colocação de objetos que devem aparecer na visualização;

¡  Também recebe o nome lógico pelo qual a view responsável vai ser encontrada;

¡  Não é necessário indicar qual o tipo da view que vai gerar a resposta;

Page 81: Conhecendo o Spring

Controlador AbstractController

¡  Classe simples para a resposta a requisições diretas;

¡  Não é indicada para o uso de formulários;

¡  Pode responder a qualquer método HTTP;

¡  Não tem suporte a validação; ¡  O método que deve ser sobrescrito

é o handleRequestInternal()

Page 82: Conhecendo o Spring

Criando o ListarAction

¡  Um objeto que lista todos os objetos de uma certa classe;

¡  Faz uma busca utilizando o

repositório;

Page 83: Conhecendo o Spring

Propriedades do nosso objeto

private Class classe; private String view; private Repositorio repositorio;

Page 84: Conhecendo o Spring

Em código...

protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {

ModelAndView mv = new ModelAndView( this.getView() ); mv.getModel().put("objetos", this.getRepositorio().pegarTodos( this.getClasse()) ); return mv; }

Page 85: Conhecendo o Spring

O Controlador SimpleFormController

¡  É utilizado para lidar com formulários;

¡  Faz a conversão automática de tipos primitivos e também pode registrar seus próprios PropertyEditors;

¡  Tem suporte a validação; ¡  Os objetos podem ficar na sessão

ou podem ser recriados a cada requisição

Page 86: Conhecendo o Spring

Configurando <bean id="inserirNoticiaAction"

class="org.maujr.action.FormAction"> <property name="commandClass"> <value>org.maujr.noticia.Noticia</value> </property> <property name="formView"> <value>noticia</value> </property> <property name="successView"> <value>noticias-redirect</value> </property> <property name="validator"> <ref bean="noticiasValidator"/> </property> <property name="repositorio"> <ref bean="repositorio"/> </property>

</bean>

Page 87: Conhecendo o Spring

É o mesmo objeto para o GET e o POST

¡  No GET ele carrega o objeto e coloca ele para ser visualizado no método formBackingObject();

¡  No POST ele faz a validação do

formulário e se não tiver nenhum erro chama o método onSubmit();

Page 88: Conhecendo o Spring

Criando o FormAction

¡  É responsável por inserir e editar os objetos no banco de dados;

¡  É capaz de tranformar todas as propriedades simples;

¡  Pode ser extendido para lidar com propriedades complexas;

Page 89: Conhecendo o Spring

Quando ele recebe um GET

String id = request.getParameter("chave");

if ( id != null ) { Persistivel objeto =repositorio.pegarPeloId( new Long(id) ); return objeto;

}

return super.formBackingObject(request);

Page 90: Conhecendo o Spring

Quando ele recebe um POST

this.getRepositorio().adicionar( (Persistivel) command);

return new ModelAndView( this.getSuccessView() );

Page 91: Conhecendo o Spring

Sequência de execução do Spring MVC

Page 92: Conhecendo o Spring

Não estou vendo a validação!

Page 93: Conhecendo o Spring

A validação é feita pela interface Validator

Basta implementar os métodos supports() e validate(). Quando ocorrer um erro, chama-se o método “reject()” no objeto Errors, o Spring retorna a requisição e mostra o erro automaticamente.

Page 94: Conhecendo o Spring

Em código...

public void validate(Object obj, Errors errors) {

Pessoa pessoa = (Pessoa) obj; if (pessoa.getNome() == null) {

errors.rejectValue(“nome”, “obrigatorio”, “Este campo é obrigatório”); }

}

Page 95: Conhecendo o Spring

Revisando...

Page 96: Conhecendo o Spring

O que é...

Inversão de controle?

Page 97: Conhecendo o Spring

Quais são os...

Tipos de inversão de controle?

Page 98: Conhecendo o Spring

É possível...

Utilizar objetos que não tenham construtores públicos?

Page 99: Conhecendo o Spring

O que é...

Um FactoryBean?

Page 100: Conhecendo o Spring

Todos os objetos...

Tem que ser singletons?

Page 101: Conhecendo o Spring

E então?

Acabou?

Page 102: Conhecendo o Spring

Agora eu vou

dominar a Fenda do Biquini!

Page 103: Conhecendo o Spring

Não tão rápido! Ainda falta o Velocity!

Page 104: Conhecendo o Spring

O que é o Velocity?

É uma ferramenta de geração texto dinamicamente, para aplicações desktop ou web

Page 105: Conhecendo o Spring

Como ele funciona?

¡  Você escreve um arquivo de texto qualquer com as diretivas da Velocity Template Language (VTL);

¡  Manda o Velocity carregar o arquivo;

¡  Cria um conjunto de objetos que vai ser utilizado para gerar o resultado;

¡  E recebe um String com o que for criado;

Page 106: Conhecendo o Spring

VelocityEngine

¡  É a classe que é utilizada para se acessar o Velocity;

¡  Faz o carregamento dos Templates; ¡  Uma mesma VelocityEngine pode

ser reutilizada por toda a aplicação;

Page 107: Conhecendo o Spring

Configurando uma VelocityEngine

engine = new VelocityEngine();

Properties props = new Properties();

props.put("resource.loader", "class"); props.put("class.resource.loader.class",

ClasspathResourceLoader.class.getName()); props.put("class.resource.loader.cache",

"false");

engine.init(props);

Page 108: Conhecendo o Spring

Criando e executando um template

Template template = engine.getTemplate("org/maujr/velocity/noticias.html");

Context context = new VelocityContext();

List<Noticia> noticias = new ArrayList<Noticia>();

//criando as notícias context.put("objetos", noticias);

Writer writer = new StringWriter(); template.merge(context, writer); System.out.println(writer.toString());

Page 109: Conhecendo o Spring

O que é a VTL?

É uma linguagem simples para o tratamento dos templates

Page 110: Conhecendo o Spring

Utilizando uma variável do Context

¡  $nomeDaVariável – Declaração simples

¡  ${nomeDaVariavel} – Declaração completa

¡  $!{nomeDaVariavel} – Declaração silenciosa

Page 111: Conhecendo o Spring

Chamando métodos

Métodos que não recebem parâmetros podem ser chamados normalmente, como em: ${pessoa.calcularPeso()}

Page 112: Conhecendo o Spring

Acessando propriedades JavaBeans

Propriedades JavaBeans podem ser acessadas da mesma maneira que em Expression Language: ${noticia.titulo} ${aluno.curso.nome}

Page 113: Conhecendo o Spring

Tem alguma diferença?

<a href=“/verArtigo/$command.id.do”> Ver artigo

</a>

<a href=“/verArtigo/${command.id}.do”> Ver artigo

</a>

Page 114: Conhecendo o Spring

Como criar uma variável no template?

Usando #set: #set( $nome = “Maurício”)

Page 115: Conhecendo o Spring

Execução Condicional - #if

¡  Operador para tratar condições ¡  Recebe um boolean ¡  Se o valor retornar “null” ele

interpreta como false ¡  Pode conter uma condição #else ¡  Pode conter várias condições #elseif

para funcionar como um switch ¡  Pode conter os operadores ==, >=,

<= e !=

Page 116: Conhecendo o Spring

Em código....

#if (${command.id}) <input type="hidden"

name="chave" value="$!{command.id}"/>

#end

Page 117: Conhecendo o Spring

Em código com #else e #elseif

#if (${command.id}) <input type="hidden"

name="chave" value="$!{command.id}"/>

#elseif (${command.id} == 10) O valor do id é 10

#else

Caiu no Else

#end

Page 118: Conhecendo o Spring

Passeando pelas coleções com #foreach

¡  Faz a iteração dentro de todas as coleções do Java

¡  Não tem controle nativo do tamanho do loop

¡  O contador pode ser acessado pela variável ${velocityCounter}

Page 119: Conhecendo o Spring

Em código...

#foreach($noticia in ${objetos}) <tr> <td>${noticia.titulo}</td> <td>${noticia.texto}</td> <td> <a href="noticia.html?chave=${noticia.id}">

Editar </a> </td> </tr> #end

Page 120: Conhecendo o Spring

E esse #springBind é

o que?

Page 121: Conhecendo o Spring

É um #macro!

Um macro é uma função que pode ser reutilizada em vários templates diferentes

Page 122: Conhecendo o Spring

Como se define isso?

#macro (nomeDoMacro $variavel $outraVariavel $maisOutra)

escreve qualquer coisa

#end

Page 123: Conhecendo o Spring

Onde colocar isso?

¡  Macros devem ser colocados em arquivos separados

¡  Eles são carregados pelo próprio Velocity quando uma VelocityEngine é criada, através da propriedade “velocimacro.library”

Page 124: Conhecendo o Spring

Em código...

velocimacro.library=spring.vm Ou se forem vários: velocimacro.library=

spring.vm,struts.vm,webwork.vm

Page 125: Conhecendo o Spring

Incluindo conteúdo estático com #include

Inclui um arquivo de texto qualquer, mas não faz avaliações de VTL ou variáveis

Page 126: Conhecendo o Spring

Em código

#include(“noticias.html”) #include(“noticias.html”, “rodape.html”, “topo.html”)

Page 127: Conhecendo o Spring

Incluindo templates dinâmicos com #parse

Funciona do mesmo modo que o #include, mas faz avaliação de conteúdo dinâmico em VTL

Page 128: Conhecendo o Spring

E agora?

Acabou? Mais alguma coisa? Tá na hora de ir pegar o menino na escola?

Page 129: Conhecendo o Spring

Agora eu vou colocar em prática o

meu plano maligno!

Page 130: Conhecendo o Spring

A aplicação de exemplo precisa de novas funcionalidades

¡  Cadastrar, editar e listar usuários

¡  Montar as mensagens de e-mail que vão ser enviadas para cada endereço

Page 131: Conhecendo o Spring

Trabalhem!

Page 132: Conhecendo o Spring

Referências

¡  Harrop, Rob; Machacek, Jan. Pro Spring. Apress, 2005.

¡  Harrop, Rob. Pro Jakarta Velocity. Apress, 2004.

¡  Evans, Eric. Domain Driven Design:Tackling Complexity in the Heart of Software. Addisson-Wesley, 2004

Page 133: Conhecendo o Spring

Referências

¡  Johnson, Rod; Hoeller, Juergen. Expert One-On-One J2EE Development Without EJB. Wrox Press, 2004

¡  Johnson, Rod. Expert One-On-One J2EE Development. Wrox Press, 2002

Page 134: Conhecendo o Spring

Trilha Sonora

Arch Enemy – Doomsday Machine

Black Label Society - Mafia

Page 135: Conhecendo o Spring

Trilha Sonora

Therion – Lemuria & Sirus B

Nickelback – All The Right Reasons

Page 136: Conhecendo o Spring