Spring Boot

Objectifs ... en 30min

  • Qu'est-ce que Spring ?
  • De Spring à Spring Boot
  • Comment ça marche ?

Spring, en bref

Sur le papier...

  • "Concurrent" de Java EE
  • Inversion de contrôle (injection de dépendance), AOP, abstraction
  • Conteneur léger
  • Inspire Java EE

En pratique...

@Component
class MyDao {

	void save(String value) {
		// write to file
	}

}
@Component
class MyService {

	@Autowired
	MyDao dao;

	void doSomething() {
		dao.save("foo");
	}

}

L'histoire de Spring

[2003] Spring 1.0

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>

	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="url" value="..." />
	</bean>

	<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="hibernateProperties">
			<props>
				<prop key="...">...</prop>
			</props>
		</property>
	</bean>

	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="persistenceUnitName" value="default" />
	</bean>

	<tx:annotation-driven />
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>

</beans>

[2007] Spring 2.5

@Configuration
public class JpaDatabaseConfig {

	@Bean
	public DataSource dataSource() {
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setUrl(...);
		return dataSource;
	}

	@Bean
	public SessionFactory sessionFactory() {
		LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
		sessionFactory.setDataSource(dataSource());
		Properties properties = new Properties();
		properties.put("...", ...);
		sessionFactory.setHibernateProperties(properties);
		return sessionFactory.getObject();
	}

	@Bean
	public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
		LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
		entityManagerFactoryBean.setDataSource(dataSource());
		entityManagerFactoryBean.setPersistenceUnitName("default");
		return entityManagerFactoryBean;
	}

	@Bean
	public PlatformTransactionManager transactionManager() {
		JpaTransactionManager transactionManager = new JpaTransactionManager();
		transactionManager.setEntityManagerFactory(entityManagerFactory);
		return transactionManager;
	}

}

[2014] - Spring Boot

spring.datasource.url=...

Démonstration de puissance

Webservice

Maven

<?xml version="1.0" encoding="UTF-8"?>
<project>
	<modelVersion>4.0.0</modelVersion>

	<groupId>fr.pinguet62</groupId>
	<artifactId>spring-boot</artifactId>
	<version>1.0-SNAPSHOT</version>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<version>1.5.4.RELEASE</version>
		</dependency>
	</dependencies>
</project> 

Webservice

Java

@SpringBootApplication
@RestController
public class MyController {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(MyController.class, args);
    }

    @GetMapping("/")
    public String home() {
        return "Hello Davidson!";
    }
}

Webservice

$ curl localhost:8080/
Hello Davidson!

Mais encore ?

  • Mavent : POM parent
  • AOP, Cache, Mail (SMTP), i18n, Cloud, Batch, Security, LDAP, OAuth
  • ActiveMQ (JMS), RabbitMQ (AMQP)
  • Base de données
    • SQL : JDBC, JPA (Hibernate)
    • NoSQL : H2, Cassandra, Couchbase, Mongodb, Neo4j, Redis, Hazelcast, Elasticsearch, Solr, ...
  • Webservice : REST & SOAP
  • Social : Facebook, Twitter, LinkedIn, ...
  • Liquibase, Flyway
  • ...

Comment ça marche ? (1/2)

@Conditional

@Conditional

Exemple

Code condionné par l'environnement d'exécution de l'application.

@Conditional

Solution 1 : le bon vieux if/else...

@Service
class MyService {

	void doSomething() {
		String currentEnv = System.getProperty("environment");
		if (currentEnv.equals("DEV"))
			System.out.println("I'm in development");
		else if (currentEnv.equals("PROD"))
			System.out.println("I'm in production");
	}

}

Moche !!!

@Conditional

Solution 2 : le polymorphisme

interface Script {
	void execute();
}

class DevScript implements Script {
	@Override
	public void execute() {
		System.out.println("In development");
	}
}

class ProdScript implements Script {
	@Override
	public void execute() {
		System.out.println("In production");
	}
}

@Conditional

Solution 2 : le polymorphisme

@Service
class MyService {
	Script script;

	// Factory
	MyService() {
		String currentEnv = System.getProperty("environment");
		if (currentEnv.equals("DEV"))
			script = new DevScript();
		else if (currentEnv.equals("PROD"))
			script = new ProdScript();
	}

	void doSomething() {
		script.execute();
	}
}

Context Spring...

@Conditional

Solution : des @Bean

@Component
class DevScript implements Script { /*...*/ }

@Component
class ProdScript implements Script { /*...*/ }
@Service
class MyService {
	@Autowired
	Script script;

	void doSomething() {
		script.execute();
	}
}

Collision...

@Conditional

@Conditional(MyCondition.class)
@Component
class MyComponent {}
class MyCondition implements Condition {
	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		return ...;
	}
}
  • Meta-annotation
  • Paramètres

@Conditional

class EnvCondition extends SpringBootCondition {
	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnEnv.class.getName());
		String expectedEnv = (String) attributes.get("value");
		String currentEnv = System.getProperty("environment");
		boolean matches = currentEnv.equals(expectedEnv);
		return new ConditionOutcome(matches, (String) null);
	}
}
@Conditional(EnvCondition.class)
@interface ConditionalOnEnv {
	String value();
}

@Conditional

@ConditionalOnEnv("DEV")
@Component
class DevScript implements Script { /*...*/ }

@ConditionalOnEnv("PROD")
@Component
class ProdScript implements Script { /*...*/ }

@Conditional

    Spring Boot...
  • @ConditionalOnMissingBean
  • @ConditionalOnClass
  • @ConditionalOnProperty

Comment ça marche ? (2/2)

SpringFactoriesLoader

SpringFactoriesLoader

Situation

Classe MyConfig qui fournie une (bon vieux) Properties

Properties properties = new MyConfig().getProperties();
String value = properties.getProperty("key");

Problème/Conséquence

Configuration manuelle : absence du PropertySource de Spring
@Value auto-configuration

SpringFactoriesLoader

Solution : EnvironmentPostProcessor

  • Implémentation
    public class MyConfigEnvironmentLoader implements EnvironmentPostProcessor {
        @Override
        public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
            Properties properties = new MyConfig().getProperties();
            PropertySource<?> propertySource = new PropertiesPropertySource("MyConfig", properties);
            environment.getPropertySources().addLast(propertySource);
        }
    }
  • Référencement : /META-INF/spring.factories
    org.springframework.boot.env.EnvironmentPostProcessor = \
                    fr.pinguet62.MyConfigPropertiesEnvironmentLoader

SpringFactoriesLoader

  • Cycle de vie :
    • PropertySourceLoader
    • SpringApplicationRunListener
    • ApplicationContextInitializer
    • ApplicationListener
    • ...
  • Référencement : classpath :
    /META-INF/spring.factories

Spring Boot

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
	org.springframework.boot.env.PropertiesPropertySourceLoader,\
	org.springframework.boot.env.YamlPropertySourceLoader

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
	org.springframework.boot.context.event.EventPublishingRunListener

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
	org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
	org.springframework.boot.context.ContextIdApplicationContextInitializer,\
	org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
	org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
	org.springframework.boot.context.FileEncodingApplicationListener,\
	org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
	org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
	org.springframework.boot.logging.LoggingApplicationListener

Attention !

Spring Boot n'est pas...
  • (que) un serveur web embarqué (Tomcat)
  • un framework

Conclusion

Arrêtez de configurez !!!
  • Ma configuration est-elle stable/évolutive ?
  • Spring Boot : configure "tout" pour vous
  • Existe-il déjà une configuration toute prête ?

Question(s) ?

Config