Upgrade to Pro — share decks privately, control downloads, hide ads and more …

De zéro à héros avec Spring Boot

De zéro à héros avec Spring Boot

Session université à Devoxx France 2015 avec Brian Clozel (@brianclozel)

Examples: https://github.com/snicoll-demos/spring-boot-uni-devoxxfr

Stéphane Nicoll

April 08, 2015
Tweet

More Decks by Stéphane Nicoll

Other Decks in Programming

Transcript

  1. @snicoll @brianclozel #springboot De zéro à héros avec Spring Boot

    Stéphane Nicoll & Brian Clozel Spring team Pivotal
  2. @snicoll @brianclozel #springboot Agenda • Part #1: Booting sequence •

    [slides] a short intro to Spring Boot • [live coding] building an app from scratch • — ☕️ break — • Part #2: More on Spring Boot • Boot autoconfig and starters • Externalized configuration
  3. @snicoll @brianclozel #springboot Introduction to Spring Boot • Single point

    of focus • Getting started quickly with Spring • Common non-functional requirements for a "real" application • Exposes a lot of useful features by default • Gets out of the way quickly if you want to change defaults • An opportunity for Spring to be opinionated
  4. @snicoll @brianclozel #springboot ಠ_ಠ Spring Boot is NOT • A

    prototyping tool • Only for embedded container apps • Sub-par Spring experience • For Spring beginners only
  5. @snicoll @brianclozel #springboot Installation • Requirements: • JDK6+ • Maven

    3.2+ / Gradle 1.12+ • Tools: • Spring Tool Suite • IntelliJ IDEA • spring CLI (from https://start.spring.io or gvm)
  6. @snicoll @brianclozel #springboot Getting started quickly @RestController class HomeController {

    @RequestMapping("/") String home() { 'Hello Devoxx France' } } $ spring run app.groovy
  7. @snicoll @brianclozel #springboot What just happened? import ... @Grab(“org.springframework.boot:spring-boot-starter-web:1.2.3.RELEASE") @EnableAutoConfiguration

    @RestController class HomeController { @RequestMapping("/") String home() { "Hello Devoxx France"; } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
  8. @snicoll @brianclozel #springboot Now with Java @RestController public class HomeController

    { @Value("${conference.name:devoxx}") private String conference; @RequestMapping("/") public String home() { return "Hello " + conference; } } curl http://localhost:8080/
  9. @snicoll @brianclozel #springboot First integration test @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = DemoApplication.class)

    @WebIntegrationTest(randomPort = true) public class HomeControllerIntegrationTest { @Value("${local.server.port}") private int port; @Test public void runAndInvokeHome() { String url = "http://localhost:" + port + "/"; String body = new RestTemplate() .getForObject(url, String.class); assertThat(body, is("Hello devoxx")); } }
  10. @snicoll @brianclozel #springboot Add a Speaker Entity @Entity public class

    Speaker { @GeneratedValue @Id private Long id; private String firstName; private String lastName; private String twitter; @Column(columnDefinition = "TEXT") private String bio; public Speaker(String firstName, String lastName, String twitter) { // initialize attributes } // getters and setters }
  11. @snicoll @brianclozel #springboot Add a Speaker Repository public interface SpeakerRepository

    extends CrudRepository<Speaker, Long> { Speaker findByTwitter(String twitter); Collection<Speaker> findByLastName(String lastName); }
  12. @snicoll @brianclozel #springboot Test that repository @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = DemoApplication.class)

    public class SpeakerRepositoryTest { @Autowired private SpeakerRepository speakerRepository; @Test public void testFindByTwitter() throws Exception { Speaker stephane = speakerRepository.save( new Speaker("Stephane", "Nicoll", "snicoll")); assertThat(speakerRepository.findByTwitter("snicoll").getId(), is(stephane.getId())); } }
  13. @snicoll @brianclozel #springboot RESTful repository public interface SpeakerRepository extends CrudRepository<Speaker,

    Long> { @RestResource(path = "by-twitter") Speaker findByTwitter(@Param("id") String twitter); Collection<Speaker> findByLastName(@Param("name") String lastName); }
  14. @snicoll @brianclozel #springboot Spring Security config @Configuration public class SecurityConfig

    extends GlobalAuthenticationConfigurerAdapter { @Override public void init(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("hero").password("hero").roles("HERO", "USER").and() .withUser("user").password("user").roles("USER"); } }
  15. @snicoll @brianclozel #springboot Fix our web test @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes =

    DemoApplication.class) @WebIntegrationTest(randomPort = true) public class HomeControllerIntegrationTest { @Value("${local.server.port}") private int port; @Test public void runAndInvokeHome() { String url = "http://localhost:" + port + "/"; String body = new TestRestTemplate("user", "user") .getForObject(url, String.class); assertThat(body, is("Hello devoxx")); } }
  16. @snicoll @brianclozel #springboot Enabling @Secured @Configuration @EnableGlobalMethodSecurity(securedEnabled = true) public

    class SecurityConfig extends GlobalAuthenticationConfigurerAdapter { @Override public void init(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("hero").password("hero").roles("HERO", "USER").and() .withUser("user").password("user").roles("USER"); } }
  17. @snicoll @brianclozel #springboot Securing an endpoint @RestController public class HomeController

    { @Value("${conference.name:devoxx}") private String conference; @RequestMapping("/") @Secured("ROLE_HERO") public String home() { return "Hello " + conference; } }
  18. @snicoll @brianclozel #springboot Adding a Health Indicator @Bean public HealthIndicator

    devoxxHealthIndicator() { return () -> { if (new Random().nextBoolean()) { return Health.up().build(); } else { return Health.down().withDetail("Boooo",42).build(); } }; }
  19. @snicoll @brianclozel #springboot Best experience with PaaS • Spring Boot

    features get a lot done for 12factor.net • PaaS friendly: fast startup, devops oriented $ mvn package $ cf push devoxx -p target/devoxx-0.0.1-SNAPSHOT.jar $ cf scale devoxx -i 4
  20. Part #2: More on Boot Explaining the key concepts behind

    Boot and how to use them in your application
  21. @snicoll @brianclozel #springboot All types of apps • web •

    command line • batch • redis/gemfire/jpa/elasticsearch • integration • JMS, AMQP • etc
  22. @snicoll @brianclozel #springboot Command Line apps @Component public class Startup

    implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("Hello World!"); } }
  23. @snicoll @brianclozel #springboot Starter POMs <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> •

    standard POM / gradle files: define dependencies • many available: batch, integration, web, ampq… • starter data-jpa = spring-data-jpa + JSR-303 + hibernate compile("org.springframework.boot:spring-boot-starter-web")
  24. @snicoll @brianclozel #springboot @EnableAutoConfiguration • attempts to autoconfigure your app

    • backs off as you define your own beans • Regular @Configuration classes, @Conditional* annotations @Configuration @ComponentScan @EnableAutoConfiguration public class MyApplication { } @SpringBootApplication public class MyApplication { }
  25. @snicoll @brianclozel #springboot Spring 4 @Conditional @Configuration @Conditional(CustomCondition.class) public class

    AppConfiguration { // @Bean methods } // defined in Boot @ConditionalOnClass(ObjectMapper.class) @ConditionalOnMissingBean("aBeanName")
  26. @snicoll @brianclozel #springboot Writing your own starter • Add support

    for X in Boot with a PR! • Distribute a client lib in your company • Standardize usage of component X in a platform • [your use case here]
  27. @snicoll @brianclozel #springboot New autoconfig project <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency>

    • Create a new hello-service-auto-configuration project • Only one mandatory dependency • Should contain specific dependencies and autoconfiguration classes
  28. @snicoll @brianclozel #springboot Add custom service interface public interface HelloService

    { String sayHello(); } • This is the part that we’re trying to auto-configure • In a typical use case, this interface comes from a 3rd party lib
  29. @snicoll @brianclozel #springboot Create a default impl. public class ConsoleHelloService

    implements HelloService { public String sayHello() { System.out.println("Hello Console"); } } • This default implementation will be shipped with the autoconfig project but should not be used if the application provides one.
  30. @snicoll @brianclozel #springboot AutoConfig class example @Configuration @ConditionalOnClass(HelloService.class) public class

    HelloServiceAutoConfiguration { @ConditionalOnMissingBean @Bean public HelloService helloService() { return new ConsoleHelloService(); } }
  31. @snicoll @brianclozel #springboot Back to main project • Add our

    autoconfig project as a dependency in our main project <dependency> <groupId>org.test.devoxx</groupId> <artifactId>hello-service-auto-configuration</artifactId> <version>...</version> </dependency>
  32. @snicoll @brianclozel #springboot Replace the Startup class @Component public class

    Startup implements CommandLineRunner { @Autowired private HelloService helloService; @Override public void run(String... args) throws Exception { helloService.sayHello(); } } • Our command line app now uses a Hello Service • running this will use the default implementation
  33. @snicoll @brianclozel #springboot Override default impl. @Bean
 public HelloService helloService()

    {
 return () -> 
 LoggerFactory.getLogger(DemoApplication.class)
 .info("Hello from logs");
 } • Add a @Bean definition in our DemoApplication class • We provide our own impl, so the default one won’t be created
  34. @snicoll @brianclozel #springboot Externalized Configuration • Goal: same code, different

    environments • properties and YAML formats • Dozens of out-of-the-box configuration keys • Supports inheritance/overriding • Type safe configuration with @ConfigurationProperties • IDE support with configuration metadata
  35. @snicoll @brianclozel #springboot Adding properties @RestController public class HomeController {

    @Value("${conference.name:devoxx}") private String conference; //... } # application.yml example conference: name: devoxx france
  36. @snicoll @brianclozel #springboot Per-Profile config values # application.yml - default

    profile server: address: 192.168.1.100 --- spring: profiles: development server: address: 127.0.0.1 --- spring: profiles: production server: address: 192.168.1.120
  37. @snicoll @brianclozel #springboot Per-Profile config values # application.properties server.address=192.168.1.100 #

    application-development.properties server.address=127.0.0.1 # application-production.properties server.address=192.168.1.120
  38. @snicoll @brianclozel #springboot Configuration priority 1.Command line arguments. 2.JNDI attributes

    from java:comp/env. 3.Java System properties (System.getProperties()). 4.OS environment variables. 5.application-{profile}.properties or application-{profile}.yml 6.application.properties or application.yml 7.@PropertySource annotations on your @Configuration classes. 8.Default properties (specified using SpringApplication.setDefaultProperties).
  39. @snicoll @brianclozel #springboot Logging • Logback, Log4J, Log4J2, java.util.Logging •

    Logback with colored output • Default is INFO (use « -- debug » on the command line) • CONSOLE by default (can configure a file output) logging: file: logs/application_log level: ROOT: WARN org.example: INFO org.example.acme: DEBUG
  40. @snicoll @brianclozel #springboot Update our starter public class ConsoleHelloService implements

    HelloService { @Value("${hello.target:console}") private String target; public String sayHello() { System.out.println("Hello " + this.target); } } • Delete the @Bean HelloService in the application • Update the default one with a property attribute
  41. @snicoll @brianclozel #springboot Override that prop value # application.yml example

    hello: name: devoxx france java -jar devoxx-0.0.1-SNAPSHOT.jar \ --hello.name=Foo • set that property value with a application.yml key • or a command line argument!
  42. @snicoll @brianclozel #springboot Type safe properties @ConfigurationProperties("hello") public class HelloProperties

    { private String prefix = "Hello "; @NotNull private String target; // getters and setters }
  43. @snicoll @brianclozel #springboot Update Starter impl public class ConsoleHelloService implements

    HelloService { @Autowired private HelloProperties properties; @Override public void run(String... strings) throws Exception { System.out.println(properties.getPrefix() + properties.getTarget()); }
  44. @snicoll @brianclozel #springboot Generate config metadata <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional>

    </dependency> • annotation processor that generates meta-data • uses Javadoc to build keys descriptions • default values detected • manual declaration allowed
  45. @snicoll @brianclozel #springboot Document properties @ConfigurationProperties("hello") public class HelloProperties {

    /** * Prefix of welcome message. */ private String prefix = "Hello "; /** * Target of welcome message. */ @NotNull private String target; // getters and setters }
  46. @snicoll @brianclozel #springboot Serving static resources # in classpath classpath:/static/**

    classpath:/public/** classpath:/resources/** classpath:/META-INF/resources/** # in WAR files src/main/webapp/ static/ public/
  47. @snicoll @brianclozel #springboot Example: Thymeleaf • Templates picked up from

    classpath:/templates • Useful configuration keys: • spring.thymeleaf.prefix • spring.thymeleaf.cache • Backs off when creating custom Beans
  48. @snicoll @brianclozel #springboot Error Handling • /error handles errors in

    a sensible way (HTML, JSON, etc) • Customize or extend with ErrorAttributes or an « error » view • Create dedicated error pages with
 EmbeddedServletContainerCustomizer
  49. @snicoll @brianclozel #springboot Create a SpeakerController @Controller public class SpeakerController

    { private final SpeakerRepository speakerRepository; @Autowired public SpeakerController(SpeakerRepository speakerRepository) { this.speakerRepository = speakerRepository; } @RequestMapping("/ui/speakers/{id}") public String show(@PathVariable Long id, ModelMap model) { Speaker speaker = speakerRepository.findOne(id); if (speaker == null) { throw new SpeakerNotFoundException(); } model.put("speaker", speaker); return "speakers/show"; } @ResponseStatus(HttpStatus.NOT_FOUND) public static class SpeakerNotFoundException extends RuntimeException { } }
  50. @snicoll @brianclozel #springboot Create a template <html xmlns:th="http://www.thymeleaf.org"> <head> <title

    th:text="${speaker.firstName} + ' ' + ${speaker.lastName}">View speaker</title> </head> <body> <div class="profile"> <h1 class="name" th:text="${speaker.firstName} + ' ' + ${speaker.lastName}">Stephane Nicoll</h1> <div> <p th:text="${speaker.bio}">Sample Biography.</p> </div> <div> <a th:href="'http://twitter.com/' + ${speaker.twitter}" class="twitter" th:text="'@' + $ {speaker.twitter}">@snicoll</a> </div> </div> </body> </html>
  51. @snicoll @brianclozel #springboot Creating a WAR file @SpringBootApplication public class

    Application extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(Application.class); } public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); } } <packaging>war</packaging>
  52. @snicoll @brianclozel #springboot Embedded server config • Registering Filters with

    @Bean methods • using application properties, configure: • ports • SSL • compression • and more • Configure Connectors with Java API
  53. @snicoll @brianclozel #springboot Use Undertow instead <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions>

    <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency>
  54. @snicoll @brianclozel #springboot Microservices? SPRING Toolbox for building Microservices/Cloud native

    apps • Distributed configuration • service registration and discovery • Load-balancing, circuit breakers and more!
  55. @snicoll @brianclozel #springboot Spring at Devoxx France • Hands-on Lab

    « Microservices Bootcamp »
 13:30-16:30 - Paris 221M
 with Josh Long and Sébastien Deleuze
 • Modern Enterprise Java architectures with Spring 4.1
 Thursday 13:00-13:50 - Amphi bleu
 with Juergen Hoeller and Stéphane Nicoll
 • The Spring BOF
 Thursday 19:30-20:30 - Neuilly 251