Configurações automáticas do Spring Boot

Uma das mágicas do Spring Boot é que colocamos uma nova dependência no nosso projeto e magicamente tudo ou quase tudo já está configurado. A pergunta que fica é: como essa configuração é feita? Aí entra as classes denominadas de AutoConfigurations do Spring Boot.

Vamos dar uma olhada na classe abaixo:

	@Configuration
	@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class,
			EnableTransactionManagement.class, EntityManager.class })
	@Conditional(HibernateEntityManagerCondition.class)
	@AutoConfigureAfter({ DataSourceAutoConfiguration.class })
	public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration {

		private static final Log logger = LogFactory
				.getLog(HibernateJpaAutoConfiguration.class);

		private static final String JTA_PLATFORM = "hibernate.transaction.jta.platform";

		/**
		 * {@code NoJtaPlatform} implementations for various Hibernate versions.
		 */
		private static final String NO_JTA_PLATFORM_CLASSES[] = {
				"org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform",
				"org.hibernate.service.jta.platform.internal.NoJtaPlatform" };

		/**
		 * {@code WebSphereExtendedJtaPlatform} implementations for various Hibernate
		 * versions.
		 */
		private static final String WEBSHERE_JTA_PLATFORM_CLASSES[] = {
				"org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform",
				"org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform", };

		@Autowired
		private JpaProperties properties;

		@Autowired
		private DataSource dataSource;

		@Override
		protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
			return new HibernateJpaVendorAdapter();
		}
              
                ...
     }

Perceba que várias annotations foram usadas em cima da classe HibernateJpaAutoConfiguration, vamos analisar cada uma delas para que tudo fique mais claro. A mais comum é @Configuration, que é utilizada quando queremos indicar que uma classe contém métodos que produzem beans. Caso fique mais curioso, vá na classe mãe e veja os métodos anotados com @Bean. 

Uma outra interessante é a @ConditionalOnClass, que como o próprio nome diz, é utilizada para verificar se determinada classe está no classpath do projeto. Faz até bastante sentido, já que se não tiver tais classes não faz sentido configurar nada da JPA. Além dessa annotation uma outra importante é a @Conditional. Ela recebe como argumento uma classe que implementa a interface Condition, do próprio Spring, e que é utilizada para executar algum código baseada em alguma condição. Vamos dar uma olhada na classe HibernateEntityManagerCondition.

	//classe interna
	static class HibernateEntityManagerCondition extends SpringBootCondition {

		private static String[] CLASS_NAMES = {
				"org.hibernate.ejb.HibernateEntityManager",
				"org.hibernate.jpa.HibernateEntityManager" };

		@Override
		public ConditionOutcome getMatchOutcome(ConditionContext context,
				AnnotatedTypeMetadata metadata) {
			for (String className : CLASS_NAMES) {
				if (ClassUtils.isPresent(className, context.getClassLoader())) {
					return ConditionOutcome.match("found HibernateEntityManager class");
				}
			}
			return ConditionOutcome.noMatch("did not find HibernateEntityManager class");
		}
	}

Simplesmente ela verifica se a implementação do EntityManager, provida pelo Hibernate, está no classpath. Ao invés de implementar a interface Condition diretamente, ela herda da interface SpringBootCondition. A ideia é que caso a condição não seja atendida um log mais claro seja escrito, para que o programador possa ter uma visão mais clara de como resolver o problema. Apenas para matar a curiosidade, vamos dar uma olhada dentro da annotation @ConditionalOnClass. 

	@Conditional(OnClassCondition.class)
	public @interface ConditionalOnClass {

		/**
		 * The classes that must be present. Since this annotation parsed by loading class
		 * bytecode it is safe to specify classes here that may ultimately not be on the
		 * classpath.
		 * @return the classes that must be present
		 */
		Class<?>[] value() default {};

		/**
		 * The classes names that must be present.
		 * @return the class names that must be present.
		 */
		String[] name() default {};

	}

Ela é só um atalho para uma @Condition que usa uma implementação pronta para checar se uma classe existe ou não no classpath. Isso é muito usado no Spring, annotations representam a composição de várias outras para que a configuração fique mais fácil para os programadores.

Para fechar a análise das annotations, faltou apenas darmos uma olhada na @AutoConfigureAfter. Como o próprio nome informa, ele diz para o Spring Boot que a classe HibernateJpaAutoConfiguration deve ser usada para criar as configurações apenas depois que a classe DataSourceAutoConfiguration for executada.

Para não passar desapercebido, dê também uma olhada no fonte da classe para você ver que realmente a maioria dos métodos que a gente se acostumou a configurar para ter o Hibernate/JPA funcionando, já está escrito dentro da AutoConfiguration provida pelo Spring Boot.

Esse foi um post que passeou um pouco mais por dentro do Spring Boot, algo que não afeta diretamente no seu dia a dia de desenvolvimento. De todo jeito, quando mais você souber do que acontece por dentro das tecnologias, mais preparado você fica para lidar com algum problema que possa aparecer.

Advertisements

Deployando sua aplicação com o Spring Boot

Já escrevi aqui no blog, duas vezes, o quanto o Spring Boot pode nos ajudar no desenvolvimento das aplicações. Levou ao extremo o termo convenção sobre configuração e realmente deixou tudo muito mais simples. Um ponto que eu ainda não comentei foi sobre o processo de deploy, se você tiver escolhido o JSP como tecnologia de view. Caso tenha escolhido outras, como o ascendente Thymeleaf, o processo de deploy é o mais simples possível, já que é só gerar o jar e rodar. Em outro post eu comento porque eu escolhi não trocar de tecnologia de view.

O processo até é bem documentado no site, mas acho que alguns pontos merecem um reforço. O primeiro é justamente o fato de termos escolhido JSP como view. O Spring Boot configura o Tomcat para sempre procurar as páginas a partir do caminho src/main/webapp, relativo ao ponto de execução do seu jar. Só que esses arquivos não são copiados quando você executa um mvn clean install, ou seja, você precisa colocar no seu script de deploy um passo para copiar a estrutura e jogar no mesmo local do seu jar executável. Caso você seja curioso, dê uma olhada nesse commit e veja os valores fixos na classe.  A própria documentação sugere que você não deve usar o jar executável quando estiver com jsps.

Para contornar esse problema e deixar, pelo menos para mim, tudo um pouco mais simples, podemos recorrer a um outro plugin do maven que também roda um tomcat embedded, só que referenciando um war.  O webapp-runner gera para você um jar que sobe o tomcat e permite que você passe como argumento o caminho para um arquivo war.

java -jar dependency/webapp-runner.jar tudosobreesporte-1.0.0-SNAPSHOT.war

Para usá-lo, basta que você adicione a seguinte entrada no seu pom.xml. 

	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-dependency-plugin</artifactId>
		<executions>
			<execution>
				<phase>package</phase>
				<goals>
					<goal>copy</goal>
				</goals>
				<configuration>
					<artifactItems>
						<artifactItem>
							<groupId>com.github.jsimone</groupId>
							<artifactId>webapp-runner</artifactId>
							<version>7.0.57.2</version>
							<destFileName>webapp-   runner.jar</destFileName>
						</artifactItem>
					</artifactItems>
				</configuration>
			</execution>
		</executions>
	</plugin>

Além disso você precisa fazer só mais algumas alterações no seu pom.xml. Quando o SetupMyProject gera o seu projeto para o Spring Boot, o tipo de projeto é jar, justamente porque é o que precisa para você subir uma aplicação com o Spring Boot em dev. Você precisa trocar o tipo para war.

  <packaging>war</packaging>

Outro detalhe importante é que precisamos apagar o plugin do Spring Boot que gera um fat jar quando executamos um mvn install. 

        <!-- apague esse trecho -->
	<plugin>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-maven-plugin</artifactId>
	</plugin>

Só que agora surgiu um possível problema. Quando gerávamos o jar com tudo dentro, era também incorporado uma classe específica do Spring Boot que usava a classe do nosso projeto que possuía o método main, geralmente aquela que chamamos de Boot,  para conseguir dar o start no nosso projeto. Isso é tão verdade que se você tiver mais de uma classe no projeto com o método main, dá erro de execução quando vamos rodar o mvn install com o plugin do bootAgora que temos um war, que vai ser carregado normalmente pelo Servlet Container, precisamos voltar a estratégia padrão do Spring MVC, ou seja, é necessário ter uma classe que implemente a interface WebApplicationInitializer. 

Para a nossa sorte o Spring Boot já possui uma implementação dessa interface justamente pensando nesse estilo de deploy :). Basta que a sua classe que tem o main e que, geralmente, também tem a annotation SpringBootApplication, herde de SpringBootServletInitializer.  Não teria problema alguma se você optasse por criar uma classe separada e herdasse também, fiz direto na classe Boot apenas pela facilidade.

  public class Boot extends SpringBootServletInitializer {
    ...
  }

Para fechar, precisamos indicar para a classe SpringBootServletInitializer quais as nossas classes responsáveis pela configuração do nosso projeto.

	@Override
	protected SpringApplicationBuilder configure(
			SpringApplicationBuilder application) {
		return application.sources(Boot.class);
	}

Repare que eu passei como argumento a própria classe anotada com SpringBootApplication, já que essa annotation já define que todas as classes, a partir do pacote onde ela está declarada, devem ser scaneadas.

Agora que já ajustamos o código precisamos só prestar atenção em último detalhe. O tempo inteiro numa aplicação com o Spring Boot usamos o arquivo application.properties. Só que quando vamos para produção, em geral vamos ter outras configurações. Alguns exemplos:

  1. Nome e dados de acesso do banco de dados
  2. Caminhos para escrita de arquivos

Essa ideia de environment já é bem suportada pelo Spring e o Spring Boot tira apenas proveito. Na hora que você passe o argumento -Dspring.profiles.active atribuindo o valor de ambiente que você quer.

  java -Dspring.profiles.active=production -jar webapp-runner.jar tudosobreesporte-1.0.0-SNAPSHOT.war& 

No exemplo acima você está ensinando ao Spring Boot a procurar por um arquivo chamado application-production.properties :).

É importante ressaltar que você pode continuar, em dev, executando a classe Boot. A única diferença é que em ambiente de produção você agora roda um war convencional.

O post de hoje teve mais o objetivo de centralizar algumas informações importantes sobre deploy de uma aplicação usando o Spring Boot. Como já comentei, o mundo do Spring é gigante e, se tiver algum assunto que você esteja mais interessado, não deixe de comentar ;).

 

[OFF-TOPIC] Geração de CRUDs automática no SetupMyProject

Ontem colocamos no ar uma nova versão do SetupMyProject. A maior novidade é que agora você tem a possibilidade de gerar o seu projeto já com um cadastro completo de qualquer entidade que você queira, os famosos CRUDs. Todos nós sabemos fazer um CRUD, isso não é segredo, o problema é o tempo que perdemos para fazer o trabalho. Criar as telas, entidades, daos e validações consomem um certo tempo. Todo o processo é realizado online, como já acontece atualmente, não queremos que você precise instalar nem configurar nada para ter acesso a funcionalidade.

Um decisão que tivemos que tomar foi: como o usuário vai entrar com os dados necessários para que nós possamos gerar o cadastro dele? Com certeza esse não foi um caminho fácil de ser decidido, mas por hora ficamos com a opção da escrita de um xml .

    <models>
	  <model name="Product">
		  <field name='name' type='java.lang.String'/>
		  <field name='description' type='java.lang.String' />
		  <field name='price' type='java.math.BigDecimal' />
       </model>
    </models>

No exemplo acima, solicitamos a geração de um cadastro para uma classe chamada Product e, além disso, especificamos quais são os atributos e tipos que queremos para ela. Para facilitar a escrita do xml, criamos uma documentação no github, além de fazer a validação do xml com um xsd para garantir que a estrutura mínima foi obedecida. Dá para ir um pouco além e gerar cadastros mais completos.

      <models>
         <model name="Product">
		   <field name='name' type='java.lang.String'/>
		   <field name='description' type='java.lang.String' />
		   <field name='price' type='java.math.BigDecimal' />
		   <field name='category' type='Category' javaType='false' formInputType="select" formInputName='category.id' selectLabelField="name" />
         </model>
         <model name="Category">
	        <field name='name' type='java.lang.String' />
	        <field name='description' type='java.lang.String' />
         </model>
      </models>

Na situação acima é informado a necessidade de dois cadastros. Especificamente na tela de produtos, informamos que queremos um select para que o usuário possa escolher a categoria do produto.  Perceba que para as entidades específicas do usuário não é necessário especificar o nome completo da classe, já que ela vai ficar em um pacote chamado models, relativo ao pacote base do projeto. Por exemplo, br.com.empresa.novoprojeto.models.Category. Um próximo passo é suportar que seja gerado uma relação do tipo oneToMany, já que por enquanto a geração automática só suporta relações do tipo ManyToOne. 

Por fim, existem situações onde não queremos a geração de um controller e uma view para cadastro, é o caso de uma entidade do tipo Checkout num sistema de e-commerce. Geralmente esse objeto é gerado a partir de um carrinho de compras. Para esse tipo de situação, basta que seja adicionado o atributo viewController.

      <models>
         <model name="Product">
	       <field name='name' type='java.lang.String'/>
	       <field name='description' type='java.lang.String' />
	       <field name='price' type='java.math.BigDecimal' />
	       <field name='category' type='Category' javaType='false' formInputType="select" formInputName='category.id' selectLabelField="name" />
        </model>
        <model name="Category">
	        <field name='name' type='java.lang.String'/>
	        <field name='description' type='java.lang.String' />
        </model>
        <model name="User">
	        <field name='login' type='java.lang.String'/>
	        <field name='password' type='java.lang.String' />
        </model>
		<model name="Checkout" viewController="false">
	        <field name='value' type='java.math.BigDecimal'/>
	        <field name='user' type='User' javaType="false" />
		</model>
     </models>

Como não poderia deixar de ser, as opções de projetos com SpringMVC direto e Spring Boot já suportam a geração de CRUDs. Ficaremos muito felizes se vocês puderem ir lá e testar :). Aproveitando o espaço, também geramos cadastros para projetos baseados em JSF 2.2 e VRaptor 4, tem para todo gosto. Um último toque que adicionamos foi a geração do readme do projeto, para os passos finais de configuração dos plugins que foram selecionados :).

Para tentar ficar mais perto do desenvolvimento do projeto, e não só do começo, a nossa próxima feature é que você consiga gerar configurações de plugins, ou cadastros adicionais, para projetos que já estão em andamento!