Projections com Spring Data JPA

No primeiro post aqui do blog foi explicado o objetivo e como usar o projeto Spring Data JPA. Desde então já usei ele em diversos projetos e não tenho como falar o tanto de tempo que foi economizado :). Falando especificamente em relação ao post de hoje, a ideia é mostrar uma característica interessante do projeto, que é a possibilidade de fazer queries trazendo apenas uma parte dos seus objetos.

Para tentar deixar mais claro, imagine a seguinte situação: existe um projeto que eu mantenho chamado setupmyproject. Nele eu gero vários projetos dos mais variados tipos e todos eles tem um tipo, data de criação, tecnologias escolhidas, tempo que levaria para fazer na mão etc. Agora eu quero fazer um relatório só mostrando o tipo e a data de criação dos projetos. Normalmente faríamos uma JPQL da seguinte maneira:

  select p from Project p;

O código acima traz todas as informações dos objetos, mas só exibimos uma parte deles. Pensando no melhor cenário, a ideia é trazer apenas as informações necessárias, algo parecido com o que segue.

  select p.type,p.creationDate from Project p;

O problema é que isso não vai voltar em forma de List<Project> e sim em forma de List<Object[]>, já que a implementação da JPA não sabe no que transformar esse pedaço de informação. Basicamente o problema é que a JPA não tem suporte a queries parciais. Uma solução, já presente na especificação é a utilização das constructor expressions. Uma alternativa é a utliza das projections do Spring Data JPA.

A primeira coisa é modificar o método na sua interface que representa o DAO genérico no Spring Data JPA. Vamos fazer ele retornar uma List<TipoDataCriacao>.

   public interface ProjectDao extends Repository<Project,Integer>{
      @Query("select p.type,p.creationDate from Project p")
      public List<TypeCreationDate> all();
   }

Perceba que não modificamos a nossa query! Agora só precisamos criar uma interface com os getters relativos aos atributos que especificamos e o Spring Data JPA se encarrega de já criar uma implementação em execução.

  interface TypeCreationDate {
     public Type getType();
     public DateTime getCreationDate();
  }

A parte legal dessa abordagem é que você só deixa acessível o que realmente precisa e não precisa deixar a query mais complexa adicionando o new na query.

E aí, já utilizou esse recurso com o Spring Data JPA? Fique a vontade para comentar aqui no post :).

Advertisements

Combinando Angular e Spring MVC

O leitor Bruno Leonardo pediu que eu escrevesse um post sobre como lidar com requisições ajax dentro dos controllers do Spring MVC. Talvez a maior dificuldade seja entender que, provavelmente, a única coisa que você vai precisar mudar nos métodos dos controllers é o retorno deles. Em vez de retornar uma página html completa, você tem a possibilidade de retornar os dados em outros formatos, como JSON.  Vou pegar um caso de uso real e mostrar um pouco do fluxo. Para completar, já que estamos falando de JavaScript, vou usar o angular como biblioteca :).

Em um dos projetos que estou programando atualmente, tive que possibilitar o login do usuário via ajax. O html ficou igual ao que segue abaixo.

  <form ng-submit="login()">
    <div>
     <input type="text" ng-model="usuario.email" />
    </div>
    <div>
     <input type="password" ng-model="usuario.senhaLimpa" />
    </div>

    <input type="submit" value="login">
  </form>

Como estou usando o angular, utilizei as diretivas do mesmo para indicar que quando o usuário decidir submeter o formulário, a função login() deve ser chamado. É justamente para isso que serve a diretiva ng-submit. Além disso, preciso pegar os valores que estão nos campos dos formulário e atualizar meu objeto JavaScript. Para esta parte, a gente pode usar a diretiva ng-model. A ideia aqui não é explicar em detalhes o angular, mas sim já fornecer um exemplo prático para que você possa estudá-lo depois :). Abaixo segue o código do meu controller do angular, para lidar com essa parte de login.

	angular.module('agendamento-online').controller('LoginController', function($scope,$http,$location,localStorageService,$routeParams) {	
				
		$scope.login = function(){
			var tentandoLogar = $http.post('/api/clientes/salao/login',$scope.usuario);
			
			tentandoLogar.success(function(retorno){
				lidaComRetornoDaApiUsuario(retorno);			
				$location.path("/agendamento-online/servicos.html");H
			});
			
			tentandoLogar.error(function(retorno,status){					
				console.log(retorno);
			});
		};
	}

Vamos dar um zoom aqui nas linhas, para ver o que cada uma está fazendo e também o que precisamos fazer no lado do servidor.

    var tentandoLogar = $http.post('/api/clientes/salao/login',$scope.usuario);

Aqui estamos tentando fazer um post para um endereço específico, passando como argumento o json contendo as informações de login. Agora sim, chegou o momento de suportarmos esse endereço no nosso servidor!

		@RequestMapping(method = RequestMethod.POST, value = "/api/clientes/salao/login", consumes = {
				MediaType.APPLICATION_JSON_VALUE}, produces = MediaType.APPLICATION_JSON_VALUE)
		public ResponseEntity<?> login(@Valid @RequestBody LoginClienteSalaoDTO form,
				BindingResult bindingResult) {
			if (bindingResult.hasErrors()) {
				return ResponseEntity.badRequest().body(
						ValidationErrorBuilder.fromBindingErrors(bindingResult));
			}

			Optional<ClienteSalao> cliente = loginClienteSalaoAction.execute(form);
			if (!cliente.isPresent()) {
				return ResponseEntity.notFound().build();
			}

			return ResponseEntity.ok(new RetornoSimples(cliente.get().getCodigoInterno()));
		}

Perceba que mapeamos ele com a annotation padrão, @RequestMapping. O detalhe que acrescentamos mais algumas informações. Informamos que o parâmetro do método vai vir via JSON, isso é feita através do header da requisição chamado content-type. O próprio angular já define isso para a gente, você pode definir a mesma coisa quando estiver usando jquery ou qualquer outra solução para fazer seu ajax. Também informamos, através do atributo produces que o retorno do método deve ser transformado em JSON e enviado para o cliente.

Só para deixar registrado, por default o seu método  recebe parâmetros com o content-type application/x-www-form-urlencoded, que é o utilizado pelos seus formulários nas páginas pela web. Um detalhe que você deve ficar atento em requisições do tipo post, que esperam, por exemplo, json como formato dos dados nos parâmetros, é que tais informações vem no próprio corpo da requisição. Por conta disso, você precisa anotar o seu parâmetro com @RequestBody. 

Um outro ponto interessante é que o retorno do método é um objeto do tipo ResponseEntity, justamente a abstração do Spring MVC que recebe algum objeto como argumento e transforma ele para o tipo especificado pelo header accept-type, também enviado pela aplicação cliente. Os tipos de retornos suportados são justamente os especificados no atributo producesPerceba que inclusive os erros de validação devem ser retornados como um objeto a ser serializado.

Para fechar, um ponto muito importante é a correta utilização dos status http. Perceba que nessa lógica podem ser gerados três tipos de status:

  1. Bad Request(400) -> Indica que a requisição veio com dados inválidos
  2. Not Found(404) -> Como o próprio nome diz, quer dizer que algo não foi encontrado
  3. ok(200) -> Quer dizer que deu tudo certo

O uso correto dos status influencia diretamente no uso da sua api javascript. Vamos dar uma olhada no trecho de código abaixo.

	var tentandoLogar = $http.post('/api/clientes/salao/login',$scope.usuario);

	tentandoLogar.success(function(retorno){
		lidaComRetornoDaApiUsuario(retorno);
		
		$location.path("/agendamento-online/servicos.html");H
	});

	tentandoLogar.error(function(retorno,status){					
		console.log(retorno);
	});

Perceba que a função post retorna um objeto(promise) que possui alguns outras funções. No exemplo acima, registramos dois callbacks: success e error. A função de erro só é chamada caso um status acima de 400 seja retornado. Isso é super importante! Já vi api’s que sempre retornam 200, mesmo quando um erro de validação acontece :(. Nessa situação, você tem que ficar acrescentando ifs no callback que lida com retornos de sucesso, poluindo o código e afetando na manutenção.

Mais um lembrete aqui. Para seus objetos poderem pegar os objetos e transformar os mesmos para JSON, XML ou qualquer outro formato, você vai precisar de uma biblioteca. Como JSON e XML são os formatos mais comuns, você pode usar o Jackson, que o Spring já possui integração.

Lembre que para ajudar nas configurações você sempre pode usar o Spring Boot, que facilita demais todo o setup do seu projeto. Aproveitando o último parágrafo, caso você tenha interesse no angular, tenta dar uma olhada no curso do Alura ou no curso presencial da Caelum.

Para que servem os qualifiers do Spring?

Atualmente na Caelum, estou participando de um projeto pequeno, bem focado em tirar relatórios de um dos nossos sistemas. Para ficar um pouco mais simples manipular os dados, pelo menos para mim, a gente criou uma aplicação que trata as informações das tabelas do sistema original e cria views ou o que for necessário em outro banco de dados, específico para os relatórios. Para completar a brincadeira, usamos um pouco do R para fazer as consultas nesse novo banco e brincar de gerar saídas para esses relatórios. Apesar do R ser legal, a utilização dele foi mais para preencher aquela vontade de brincar com algo ainda desconhecido para mim.

Como já é de praxe, a aplicação foi escrita usando o Spring Boot, mesmo ela não sendo uma aplicação web. Lembre-se disso, você pode usar o Spring para qualquer tipo de projeto, não só web :). Como ela tem que falar com dois bancos de dados, foi necessário configurar dois datasources.

	@Bean
	public DataSource dataSourceSistemaAntigo() {
		//codigo aqui
	}
	
	@Bean	
	public DataSource dataSourceRelatorios() {
		//codigo aqui
	}

Só que na hora de subir a aplicação eu tomei a seguinte exception:

	Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
	No qualifying bean of type [javax.sql.DataSource] is defined: expected single
	matching bean but found 2: ds_relatorios,ds_cweb

Talvez você já tenha visto a exception NoUniqueBeanDefinitionException. O nome dela não podia ser mais expressivo, o Spring encontrou dois candidatos para injetar em certo ponto do sistema.

	@Autowired
	public GeraDadosAnaliseComentarios(GeracaoRelatorioDao dao,DataSource dataSource) {
		super();
		this.dao = dao;
		this.template = new JdbcTemplate(dataSource);
	}

Como ele vai saber qual DataSource usar aqui? Para resolver essa questão, é necessário que você ensine para ele qual dos dois você quer.

	@Autowired
	public GeraDadosAnaliseComentarios(GeracaoRelatorioDao dao,
			@Qualifier("dataSourceSistemaAntigo") DataSource dataSource) {
		super();
		this.dao = dao;
		this.template = new JdbcTemplate(dataSource);
	}

Esse é o objetivo da annotation @Qualifier, ela permite que você referencie pelo nome o objeto que está injetado. Quando você anota um método com @Bean,  o Spring vai registrar o objeto retornado pelo método no container de IoC usando o mesmo nome do seu método. No caso o nome seria dataSourceSistemaAntigo. Nada também impede de você solicitar um registro com um nome diferente, basta que seja utilizado o atributo name da annotation @Bean. 

	@Bean(name="ds_cweb")	
	public DataSource dataSourceSistemaAntigo(Environment environment) {
		//codigo aqui
	}
	@Autowired
	public GeraDadosAnaliseComentarios(GeracaoRelatorioDao dao,
			@Qualifier("ds_cweb") DataSource dataSource) {
		super();
		this.dao = dao;
		this.template = new JdbcTemplate(dataSource);
	}

Mesmo com essas alterações, nossa aplicação ainda continua dando a mesma exception. O problema aqui é que a classe de auto configuração do DataSource, do Spring Boot, espera que apenas um objeto deste tipo esteja configurado(até onde sei). O trecho de código abaixo, da última versão estável do Spring Boot, mostra exatamente isso.

	if (this.applicationContext.getBeanNamesForType(DataSource.class, false,
			false).length > 0) {
		this.dataSource = this.applicationContext.getBean(DataSource.class);
	}

Precisamos agora ensinar ao Spring que ele sempre deve preferir um dos objetos, quando o lookup for feito sem o uso dos qualifiers.

	@Bean(name="ds_relatorios")	
	@Primary
	public DataSource dataSourceRelatorios(Environment environment) {
		//codigo aqui
	}

É justamente para isso que serve a annotation @Primary. Ela indica qual dos métodos produtores de objetos deve ser priorizado.

Esse post talvez não tenha sido muito avançado, mas é um erro que chega bastante nos fóruns e que muitas vezes as pessoas ficam em dúvida. Espero que ela tenha sido útil para você 🙂

 

Seu método com @Bean não é tão simples

Quase todo projeto que participamos somos obrigados a disparar emails. Para não ficar mandando emails de verdade sempre configuro dois métodos de fábrica, um associado com o profile de dev e outro com o profile de produção.

	@Bean
	@Profile({"production"})
	public MailSender mailSender(AccessEnvironment accessEnvironment) {
	
		JavaMailSenderImpl javaMailSenderImpl = new JavaMailSenderImpl();
		javaMailSenderImpl.setHost("smtp.gmail.com");
		javaMailSenderImpl.setPassword("xxxx");
		javaMailSenderImpl.setPort(587);
		javaMailSenderImpl.setUsername("xxxxx");
		Properties mailProperties = new Properties();
		mailProperties.put("mail.smtp.auth", true);
		mailProperties.put("mail.smtp.starttls.enable", true);
		javaMailSenderImpl.setJavaMailProperties(mailProperties);
		
		return javaMailSenderImpl;
	}
	
	@Bean
	@Profile({"dev","homolog"})
	public MailSender mailSenderDev(AccessEnvironment accessEnvironment) {
	
		return new MailSender() {
			
			@Override
			public void send(SimpleMailMessage[] simpleMessages) throws MailException {
				System.out.println(Arrays.toString(simpleMessages));
			}
			
			@Override
			public void send(SimpleMailMessage simpleMessage) throws MailException {
				System.out.println(simpleMessage);
			}
		};
	}

Só que se você quiser enviar emails em HTML, ao invés de trabalhar com a interface MailSender, vai precisar trabalhar com a interface JavaMailSender. É ela que fornece os métodos para que possamos criar um objeto do MimeMessage, onde podemos definir qual o formato que desejamos enviar o email. Com essa alteração o código fica mais ou menos como segue:

	@Bean
	@Profile({"dev","homolog"})
	public JavaMailSender mailSenderDev() {		
				
		return new JavaMailSender() {
			
			@Override
			public void send(MimeMessagePreparator... mimeMessagePreparators)
					throws MailException {
				throw new UnsupportedOperationException("Nao implementado para dev");
			}
			
			@Override
			public void send(MimeMessagePreparator mimeMessagePreparator)
					throws MailException {
				throw new UnsupportedOperationException("Nao implementado para dev");				
			}
			
			@Override
			public void send(MimeMessage... mimeMessages) throws MailException {
				for (MimeMessage mimeMessage : mimeMessages) {
					send(mimeMessage);
				}
			}
			
			@Override
			public void send(MimeMessage mimeMessage) throws MailException {
				try {
					System.out.println(mimeMessage.getContent());
				} catch (IOException | MessagingException e) {
					log.error("Problema no envio do email para dev, {}",e);	
				}				
			}
			
			@Override
			public MimeMessage createMimeMessage(InputStream contentStream)
					throws MailException {
				//como criar um MimeMessage?
			}
			
			@Override
			public MimeMessage createMimeMessage() {
				//como criar um MimeMessage?
			}
		
			
			@Override
			public void send(SimpleMailMessage... simpleMessages) throws MailException {
				System.out.println(Arrays.toString(simpleMessages));
			}
			
			@Override
			public void send(SimpleMailMessage simpleMessage) throws MailException {
				System.out.println(simpleMessage);
			}
		};
	}	
	
	@Bean
	@Profile({"prod"})
	public JavaMailSender mailSender() {
		JavaMailSenderImpl javaMailSenderImpl = new JavaMailSenderImpl();
		javaMailSenderImpl.setHost("smtp.gmail.com");
		javaMailSenderImpl.setPassword("xxxxx");
		javaMailSenderImpl.setPort(587);
		javaMailSenderImpl.setUsername("xxxxx");
		Properties mailProperties = new Properties();
		mailProperties.put("mail.smtp.auth", true);
		mailProperties.put("mail.smtp.starttls.enable", true);
		javaMailSenderImpl.setJavaMailProperties(mailProperties);
		
		return javaMailSenderImpl;
	}

Só que agora temos um problema. O nosso método que funciona para ambiente de desenvolvimento precisa devolver um objeto do tipo JavaMailSender que saiba construir objetos do tipo MimeMessage. Não que seja impossível, mas é um código um tanto quanto chato. Para tentar resolver isso, podemos recorrer ao principio simples da composição. Fazemos com que a implementação de dev utilize o objeto real e, dessa forma, só precisamos substituir os métodos que enviam o email em si.

	@Bean
	@Profile({"dev","homolog"})
	public JavaMailSender mailSenderDev() {				
		
		return new JavaMailSender() {

			JavaMailSender mailSender = mailSender();
			
			@Override
			public void send(MimeMessagePreparator... mimeMessagePreparators)
					throws MailException {
				throw new UnsupportedOperationException("Nao implementado para dev");
			}
			
			@Override
			public void send(MimeMessagePreparator mimeMessagePreparator)
					throws MailException {
				throw new UnsupportedOperationException("Nao implementado para dev");				
			}
			
			@Override
			public void send(MimeMessage... mimeMessages) throws MailException {
				for (MimeMessage mimeMessage : mimeMessages) {
					send(mimeMessage);
				}
			}
			
			@Override
			public void send(MimeMessage mimeMessage) throws MailException {
				try {
					System.out.println(mimeMessage.getContent());
				} catch (IOException | MessagingException e) {
					log.error("Problema no envio do email para dev, {}",e);	
				}				
			}
			
			@Override
			public MimeMessage createMimeMessage(InputStream contentStream)
					throws MailException {
				return mailSender.createMimeMessage(contentStream);
			}
			
			@Override
			public MimeMessage createMimeMessage() {
				return mailSender.createMimeMessage();
			}
		
			
			@Override
			public void send(SimpleMailMessage... simpleMessages) throws MailException {
				System.out.println(Arrays.toString(simpleMessages));
			}
			
			@Override
			public void send(SimpleMailMessage simpleMessage) throws MailException {
				System.out.println(simpleMessage);
			}
		};
	}	
	
	@Bean
	@Profile({"prod"})
	public JavaMailSender mailSender() {
		JavaMailSenderImpl javaMailSenderImpl = new JavaMailSenderImpl();
		javaMailSenderImpl.setHost("smtp.gmail.com");
		javaMailSenderImpl.setPassword("xxxx");
		javaMailSenderImpl.setPort(587);
		javaMailSenderImpl.setUsername("xxxxx");
		Properties mailProperties = new Properties();
		mailProperties.put("mail.smtp.auth", true);
		mailProperties.put("mail.smtp.starttls.enable", true);
		javaMailSenderImpl.setJavaMailProperties(mailProperties);
		
		return javaMailSenderImpl;
	}

Olhando o código acima, se a gente pudesse fazer uma aposta, o que você acha que aconteceria? Daria algum problema? Funcionaria tudo tranquilamente? O fato é que quando iniciamos a aplicação tomamos a seguinte exception:

	Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate 
		[org.springframework.mail.javamail.JavaMailSender]: Factory method 
		'mailSenderDev' threw exception; nested exception is 
		org.springframework.beans.factory.NoSuchBeanDefinitionException: 
		No bean named 'mailSender' is defined

		at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
		at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
		... 75 common frames omitted
		
	Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'mailSender' is defined
		at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:698) 
			~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
		...

Eu não acho que essa seja uma exception simples de ser entendida. O Spring está reclamando que não existe um bean configurado com o nome mailSender. Em primeiro lugar, o Spring assume que o nome do método de fábrica é justamente o nome de registro do objeto gerenciado pelo Spring. A segunda coisa, e mais importante. Onde é que estamos pedindo pela injeção explícita do bean registrado como  “mailSender”? Isso não é feito em nenhum lugar na aplicação! Qualquer pedido de injeção é feito pela própria interface.

	@Service
	public class CentralEmails {

		@Autowired
		private JavaMailSender mailer;
		@Autowired
		private EmailTemplateLoader templateLoader;
		@Autowired
		private HostUrlBuilder urlBuilder;

		...

	}	

Para ficar claro o que acontece, precisamos colocar um simples syso dentro do método anotado com o profile de dev.

	@Bean
	@Profile({"dev","homolog"})
	public JavaMailSender mailSenderDev() {				
		System.out.println(this.getClass());
		return new JavaMailSender() {
			...
		}		
	}	

Quando iniciamos a aplicação é impresso algo assim: class Boot$$EnhancerBySpringCGLIB$$92496cd0. Perceba que a própria classe anotada @SpringBootApplication já é gerenciada pelo Spring. É justamente por isso que você passa ela como argumento para o método run, da classe SpringApplication. O objeto instanciado em função dessa classe na verdade é um proxy. Quando estamos lidando com proxies, você acha que está invocando um método, mas na verdade pode ser que esteja acontecendo algo completamente diferente.

É exatamente isso que acontece quando você invoca o método público, anotado com @Bean, de uma classe gerenciada pelo Spring. A invocação sugere que o Spring deve recuperar o bean registrado com o mesmo nome do método, o que não é verdade, já que nem subimos o projeto em profile de produção. Essa é a causa da exception.

Para contornar podemos isolar a criação do objeto de envio de email real em um método privado e aproveitá-lo tanto para dev(delegate) quanto para produção.

	@Bean
	@Profile({"dev","homolog"})
	public JavaMailSender mailSenderDev() {				
		System.out.println(this.getClass())
		return new JavaMailSender() {

			JavaMailSender mailSender = gmail();
			
			@Override
			public void send(MimeMessagePreparator... mimeMessagePreparators)
					throws MailException {
				throw new UnsupportedOperationException("Nao implementado para dev");
			}
			
			@Override
			public void send(MimeMessagePreparator mimeMessagePreparator)
					throws MailException {
				throw new UnsupportedOperationException("Nao implementado para dev");				
			}
			
			@Override
			public void send(MimeMessage... mimeMessages) throws MailException {
				for (MimeMessage mimeMessage : mimeMessages) {
					send(mimeMessage);
				}
			}
			
			@Override
			public void send(MimeMessage mimeMessage) throws MailException {
				try {
					System.out.println(mimeMessage.getContent());
				} catch (IOException | MessagingException e) {
					log.error("Problema no envio do email para dev, {}",e);	
				}				
			}
			
			@Override
			public MimeMessage createMimeMessage(InputStream contentStream)
					throws MailException {
				return mailSender.createMimeMessage(contentStream);
			}
			
			@Override
			public MimeMessage createMimeMessage() {
				return mailSender.createMimeMessage();
			}
		
			
			@Override
			public void send(SimpleMailMessage... simpleMessages) throws MailException {
				System.out.println(Arrays.toString(simpleMessages));
			}
			
			@Override
			public void send(SimpleMailMessage simpleMessage) throws MailException {
				System.out.println(simpleMessage);
			}
		};
	}	
	
	@Bean
	@Profile({"prod"})
	public JavaMailSender mailSender() {
		return gmail();
	}

	private JavaMailSender gmail() {
		JavaMailSenderImpl javaMailSenderImpl = new JavaMailSenderImpl();
		javaMailSenderImpl.setHost("smtp.gmail.com");
		javaMailSenderImpl.setPassword("xxxx");
		javaMailSenderImpl.setPort(587);
		javaMailSenderImpl.setUsername("xxxx");
		Properties mailProperties = new Properties();
		mailProperties.put("mail.smtp.auth", true);
		mailProperties.put("mail.smtp.starttls.enable", true);
		javaMailSenderImpl.setJavaMailProperties(mailProperties);
		
		return javaMailSenderImpl;
	}	

Os métodos privados não entram no proxy e, por conta disso, não nos causam nenhum problema :).

Sempre tento variar entre posts mais próximos de códigos que vocês efetivamente usem no cotidiano e alguns que sejam mais sobre detalhes internos do framework. Espero que este tenha te ajudado a entender um pouco do que acontece com seus métodos anotados com @Bean.

[Post Rápido] Classes utilitárias do Spring

Erros acontecem em qualquer tipo de software que seja desenvolvido. Por mais que o sistema possua testes, e eu espero que os desenvolvidos por você possuam, ainda acabamos recebendo várias exceptions durante a execução da aplicação. Nesse cenário, um detalhe que me incomoda é que acabamos várias vezes tomando exceptions do tipo NullPointerException ou até um ArrayIndexOutOfBoundsException, que geralmente vem associadas a valores que não foram definidos nas variáveis ou a coleções que estão vazias.

Claro que se o erro ocorrer, você pode adicionar um teste para cobrir o cenário e tudo vai estar resolvido. Dentro do SetupMyProject temos várias situações dessas, segue um exemplo.

    private ViewPartProcessor findHtmlPartProcessor(ViewElement viewElement) {
         List<ViewPartProcessor> filteredPartsProcessor = //findPartProcessors(viewElement)

         if (filteredPartsProcessor.isEmpty()) {
            throw new IllegalArgumentException(
              "Não foi encontrado nenhum processador de html para "
              + viewElement.getClass());
         }

         if (filteredPartsProcessor.size() > 1) {
             throw new IllegalStateException(
              "Tem mais de um processador de html para "
               + viewElement.getClass());
          }

       ViewPartProcessor choosedHtmlProcessor = filteredPartsProcessor.get(0);
       return choosedHtmlProcessor;
    }

A verificação foi feita com um if  e, associado a ele, lançamos uma exception do tipo IllegalArgumentException com uma mensagem descritiva, para ajudar o desenvolvedor do time que receba tal exception. Nesse caso específico ainda temos um estado inválido sendo verificado.

O legal é que como o SetupMyProject usa o Spring como framework base, podemos ir um pouco além e tirar proveito de outras classes que não estão nos holofotes do framework. Ele possui um pacote chamado org.springframework.util que possui um monte de classe que pode ser útil para o seu projeto. Por exemplo, para fazermos a checagem de parâmetros e estado acima, podemos usar a classe Assert, presente no pacote.

       private ViewPartProcessor findHtmlPartProcessor(ViewElement viewElement) {
		List<ViewPartProcessor> filteredPartsProcessor =    //findPartProcessors(viewElement)

		Assert.notEmpty(filteredPartsProcessor,
				"Não foi encontrado nenhum processador de html para "
						+ viewElement.getClass());

		Assert.state(filteredPartsProcessor.size() == 1,
				"Tem mais de um processador de html para "
						+ viewElement.getClass());

		ViewPartProcessor choosedHtmlProcessor = filteredPartsProcessor.get(0);
		return choosedHtmlProcessor;
	}

Essa classe possui vários métodos de checagem, seguem alguns outros exemplos:

     Assert.hasText(text);
     Assert.notNull(object);
     Assert.isTrue(expression);

Apenas para ilustrar um pouco mais. Estamos no meio do desenvolvimento de uma nova feature do SetupMyProject e foi necessário gerar um pouco de HTML a partir de código JAVA. Em partes do código era necessário colocar uns textos entre aspas simples e, independente do tipo concatenação, ela sempre é chata e muito propensa a erros.

     "<form:textarea path='"+textAreaHtmlElement.getName()+"'>"

No pacote org.springframework.util do Spring também existe a classe StringUtilsque já possui um método chamado quote responsável por fazer justamente o que precisamos.

     "<form:textarea path="+StringUtils.quote(textAreaHtmlElement.getName())+">";

Esse é um ponto que eu defendo bastante. Para mim faz bastante sentido se acoplar a solução que você escolheu para servir de alicerce para o seu projeto. O problema de acoplamento gerado por essa decisão só vai ser sentido se você realmente precisar trocar esse alicerce, o que é não é nada comum. E você, vê algum problema em se acoplar ao framework?

 

Novo blog sobre o universo Spring

Oi pessoal! O objetivo deste blog é disseminar um conteúdo interessante sobre o Spring em português! Será escrito um post por semana, durante várias semanas. Espero que vocês gostem e que peçam sempre por novos conteúdos. Usarei como base de discussão um projeto baseado no livro que eu escrevi, mas só que adaptado as necessidades impostas pelos textos do blog. Ele pode ser encontrado no github.