Spring Framework 3 100% Livre de XML
Comecei a usar o Spring Framework quando este estava ainda na versão 2.5. Naquela época, muitos programadores reclamavam e criticavam o Spring pelo excesso de configurações baseadas em arquivos XML. Realmente, tudo era configurado via XML, embora eu pessoalmente, não achasse essa configuração tão confusa a ponto de deixar de usar o framework.
Após o lançamento da versão 3, o Spring passou a fornecer a possibilidade do uso de anotações de forma oficial. Assim, o número excessivo de arquivos do tipo XML foi realmente reduzido, restando apenas arquivos de configurações essenciais. Mesmo assim o Spring ainda continuou a receber criticas em relação a necessidade do uso de XML. Porém, o que muitos desconhecem é que existe a chamada “configuração programática”, que evita a necessidade de qualquer arquivo do tipo de XML. Esta possibilidade também apareceu na versão 3 do framework e, neste tutorial, vou demonstrar como configurar uma simples aplicação com acesso a banco de dados sem o uso de qualquer arquivo de configuração do tipo XML.
1. Preparando o projeto
Para o exemplo deste tutorial será necessário usar as bibliotecas do Spring Framework 3 e mais algumas dependências, faça os seguintes downloads:
2. Iniciando o projeto
Crie um projeto na sua IDE de preferência, adicione as bibliotecas citadas no projeto e em seguida crie a classe de entidade Person
.
package com.mballem.tutorial.entity; /** * http://www.mballem.com/ */ public class Person { private long id; private String firstName; private String surname; private String email; private int age; //gere os metodos get/set e toString(). }
Vamos agora criar a interface IDAO
– Listagem 2 – e classe PersonDAO
– Listagem 3 – usando como modo de persistencia a API JdbcTemplate do Spring Framework.
package com.mballem.tutorial.dao; import java.util.List; /** * http://www.mballem.com/ */ public interface IDAO { void createTable(); void save(Object... values); void update(long id, Object... values); void delete(long id); List find(); T findOne(String value); }
package com.mballem.tutorial.dao; import com.mballem.tutorial.entity.Person; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; /** * http://www.mballem.com/ */ public class PersonDAO implements IDAO { private JdbcTemplate jdbcTemplate; public PersonDAO(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; createTable(); } @Override public void createTable() { String sql = "CREATE TABLE IF NOT EXISTS Persons( " + "id bigint " + "GENERATED BY DEFAULT AS IDENTITY (START WITH 1) not null, " + "firstName varchar(20) not null, " + "surname varchar(50) not null, " + "email varchar(50) not null unique, " + "age integer not null, " + "CONSTRAINT PK_Person PRIMARY KEY(id)" + ")"; jdbcTemplate.execute(sql); } @Override public void save(Object... values) { String sql = "insert into Persons (firstName, surname, email, age) " + "values (?,?,?,?)"; jdbcTemplate.update(sql, values); } @Override public void update(long id, Object... values) { String sql = "update Persons " + "set firstName = ?, surname = ?, email = ?, age = ? " + "where id = ?"; jdbcTemplate.update(sql, values[0], values[1], values[2], values[3], id); } @Override public void delete(long id) { String sql = "delete from Persons where id = ?"; jdbcTemplate.update(sql, id); } @Override public List find() { return jdbcTemplate.query("select * from Persons", new PersonMapper()); } @Override public Person findOne(String value) { String sql = "select * from Persons where email like ?"; return jdbcTemplate.queryForObject(sql, new PersonMapper(), value); } private static final class PersonMapper implements RowMapper { public Person mapRow(ResultSet rs, int rowNum) throws SQLException { Person person = new Person(); person.setId((Long) rs.getObject("id")); person.setFirstName(rs.getString("firstName")); person.setSurname(rs.getString("surname")); person.setEmail(rs.getString("email")); person.setAge(rs.getInt("age")); return person; } } }
A classe PersonDAO
implementa a interface IDAO
, assim, devemos codificar os metodos da interface. Temos também uma variável de instancia do tipo JdbcTemplate
, a qual é inicializada (injetada) através do método construtor da classe. Ainda no construtor, invocamos o método createTable()
, o qual será responsável por criar a tabela Persons
no banco de dados (HSQLD em modo Standalone). Através do objeto jdbcTemplate
, temos acesso a uma variedade de metodos que simplificam as instruções de insert, update, delete e também as queries (consultas). No final da classe temos um classe interna chamada PersonMapper
. Esta classe tem como responsabilidade transformar os dados recebidos do banco em um objeto do tipo Person
. E note que nos metodos de consulta, find()
e findOne()
, passamos como par metro um instancia da classe interna. Saiba mais sobre a API JdbcTemplate através da documentação do Spring.
Vamos agora criar a classe PersonService
(Listagem 4). O padrão service é normalmente usado como a parte responsável pelas regras de negocio da aplicação e também quem faz as chamadas ao DAO. Neste exemplo, não teremos nenhuma regra de negócio que fizesse ser necessário ter um service, mas resolvi adicioná-lo ao projeto para exemplificar como podemos fazer a injeção de dependência em diversos beans. Nesta classe teremos uma variável de instancia do tipo PersonDAO
, a qual será injetada através do construtor da classe PersonService
.
package com.mballem.tutorial.service; import com.mballem.tutorial.dao.PersonDAO; import com.mballem.tutorial.entity.Person; import java.util.List; /** * http://www.mballem.com/ */ public class PersonService { private PersonDAO personDAO; public PersonService(PersonDAO personDAO) { this.personDAO = personDAO; } public void save(Object... values) { personDAO.save(values); } public void update(long id, Object... values) { personDAO.update(id, values); } public void delete(long id) { personDAO.delete(id); } public List find() { return personDAO.find(); } public Person findOne(String value) { return personDAO.findOne(value); } }
3. Configurando os Beans sem uso de arquivos XML
A partir de agora vamos construir três classes, onde teremos a classe BeanDataSource
responsável pela conexão com o banco de dados e também por criar o bean jdbcTemplate
. Teremos ainda a classe BeanDaoSource
, responsável pelos beans do tipo DAO
e a classe BeanServiceSource
, responsável pelos beans do tipo Service
.
Veja na Listagem 5, a classe de configuração BeanDataSource
. Essa classe será reconhecida pelo Spring como uma classe de configuração através da anotação @Configuration
. Temos também três variáveis de instancia, url
, user
e pass
, que representam os dados de conexão que estão armazenados em um arquivo de propriedades:
jdbc.url=jdbc:hsqldb:file:./db/tutorial-spring jdbc.username=sa jdbc.password=
Para ler este arquivo, usamos o bean nomeado como propriedades – @Bean(name = “propriedades”)
– e assim, através das anotações @Value
as variáveis são inicializadas com os valores contidos no arquivo. O bean chamado de dataSource
será o responsável por realizar a conexão com o banco de dados e devemos injetá-lo no bean jdbcTemplate
para que o objeto jdbcTemplate
seja inicializado com uma instancia desta conexão.
package com.mballem.tutorial.configuration; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.PropertiesLoaderSupport; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DriverManagerDataSource; import javax.sql.DataSource; /** * http://www.mballem.com/ */ @Configuration public class BeanDataSource { @Value("#{propriedades['jdbc.url']}") private String url; @Value("#{propriedades['jdbc.user']}") private String user; @Value("#{propriedades['jdbc.pass']}") private String pass; @Bean(name = "dataSource") public DataSource dataSource() { return new DriverManagerDataSource(url,url, pass); } @Bean(name = "jdbcTemplate") public JdbcTemplate jdbcTemplate() { JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource()); return jdbcTemplate; } @Bean(name = "propriedades") public PropertiesLoaderSupport jdbcProperties() { PropertiesFactoryBean props = new PropertiesFactoryBean(); props.setLocation(new ClassPathResource("/jdbc.properties")); return props; } }
Vamos agora criar o bean para a classe PersonDAO
, para isso, usamos a classe de configuração BeanDaoSource
, da Listagem 6. Observe que usamos uma nova anotação, a @Import
, que representará uma classe que desejamos importar para nosso bean. Essa anotação é similar a instrução import
usada em arquivos de configuração do Spring do tipo XML. Temos também um variável de instancia do tipo JdbcTemplate
, anotada como @Autowired
. E por fim, criamos o bean personDAO
e injetamos o objeto jdbcTemaplte
através da construtor da classe PersonDAO
.
package com.mballem.tutorial.configuration; import com.mballem.tutorial.dao.PersonDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.jdbc.core.JdbcTemplate; /** * http://www.mballem.com/ */ @Configuration @Import(value = { BeanDataSource.class }) public class BeanDaoSource { @Autowired private JdbcTemplate jdbcTemplate; @Bean(name = "personDAO") public PersonDAO personDAO() { return new PersonDAO(jdbcTemplate); } }
Na Listagem 7 encerramos as configurações com a classe BeanServiceSource
, a qual irá criar um bean para a classe PersonService
. Aqui, fazemos a injeção do bean personDAO
através do construtor da classe PersonService
.
package com.mballem.tutorial.configuration; import com.mballem.tutorial.dao.PersonDAO; import com.mballem.tutorial.service.PersonService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; /** * http://www.mballem.com// */ @Configuration @Import(value = { BeanDaoSource.class }) public class BeanServiceSource { @Autowired private PersonDAO personDAO; @Bean(name = "personService") public PersonService personService() { return new PersonService(personDAO); } }
Basta agora testar a aplicação, para isso, utilize a classe Teste
da Listagem 8. Quando usamos a configuração programática, devemos usar a classe AnnotationConfigApplicationContext
ou AnnotationConfigWebApplicationContext
para aplicações web. Através do método scan()
, informamos o pacote em que o Spring deverá procurar pelas classes anotadas. Segundo a documentação, devemos invocar o método refresh()
.
package com.mballem.tutorial; import com.mballem.tutorial.entity.Person; import com.mballem.tutorial.service.PersonService; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * http://www.mballem.com/ */ public class Teste { private PersonService personService; public Teste(PersonService personService) { this.personService = personService; } public static void main(String[] args) { //inicializa o container do Spring AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); //informa em que pacote se encontram as anotacoes do Spring ctx.scan("com.mballem.tutorial"); ctx.refresh(); //inicializa a variavel personService com o bean //PersonService por meio do construtor de Teste. Teste t = new Teste(ctx.getBean(PersonService.class)); t.save(); t.update(); t.delete(); } private void save() { personService.save("Diogo Luiz", "Da Silva", "diogo@email.com", 25); personService.save("Roberto Carlos", "Ramos", "rbo.carlos@email.com", 30); personService.save("Luiz Carlos","De Souza", "luiz22@email.com", 22); personService.save("Viviane","De Vargas", "vivi.ane@email.com", 27); for (Person person : personService.find()) { System.out.println(person.toString()); } } private void update() { Person person = personService.findOne("vivi.ane@email.com"); personService.update( person.getId(), person.getFirstName(), person.getSurname(), "viviane@email.com", 31 ); for (Person p : personService.find()) { System.out.println(p.toString()); } } private void delete() { Person person = personService.findOne("luiz22@email.com"); personService.delete(person.getId()); for (Person p : personService.find()) { System.out.println(p.toString()); } } }
Veja na Figura 2 a estrutura do projeto:
Saiba mais em: