├── .gitignore ├── README.adoc ├── pom.xml └── src └── main ├── java └── demo │ ├── DemoApplication.java │ ├── SecurityConfig.java │ ├── domain │ ├── Speaker.java │ └── SpeakerRepository.java │ └── web │ └── HomeController.java └── resources ├── application.properties ├── import.sql └── static └── error └── 404.html /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | target 4 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | == Spring Boot Introduction (conference format) 2 | 3 | This is the sample app used during an introduction to Spring Boot (conference format, 4 | ~ 50min). 5 | 6 | We start from scratch and generate a secured web application that exposes a domain object 7 | (`Speaker`) as a REST endpoint. 8 | 9 | NOTE: You can bootstrap a simple web-based project at https://start.spring.io 10 | 11 | === Running the app locally 12 | 13 | There are many ways to start your Spring Boot app locally: 14 | 15 | * Run the `main` method in `DemoApplication` right from your IDE 16 | * On the command-line, execute `mvn spring-boot:run` 17 | * Package the project (`mvn package`) and _just_ execute the jar, that is 18 | `java -jar target/spring-boot-intro-conference-0.0.1-SNAPSHOT.jar` 19 | 20 | The H2 database has some default speakers created via `import.sql`. You can use curl to 21 | add more; to create a new `Speaker`, just _POST_ the following document to your local 22 | repository: 23 | 24 | ```json 25 | { 26 | "firstName": "John", 27 | "lastName": "Smith", 28 | "twitter": "yourTwitterAccountHere", 29 | "github": "youtGithubAccountHere", 30 | "bio": "John Smith's bio." 31 | } 32 | ``` 33 | 34 | something like: 35 | 36 | ``` 37 | $ curl http://user:user@localhost:8080/speakers -i -H "Content-type: application/json" -d @yourfile.json` 38 | ``` 39 | 40 | where `yourfile.json` is the name of the file holding the new Speaker representation. 41 | 42 | NOTE: Cross-Site Request Forgery support should be disabled locally for the command above 43 | to work, adding `.and().csrf().disable()` should do the trick. Also, `user/user` is the 44 | user that is created in the `SecurityConfig` class. 45 | 46 | === Running the app in the cloud 47 | 48 | If you want to run that exact same app in the cloud, you need first an account with a 49 | cloud provider. At the time of writing, you can try _Pivotal Web Services_ for free for 50 | 60 days, check `https://run.pivotal.io/` 51 | 52 | Once you've registered, you should be able to use `cf t` to confirm you are connected to 53 | the right organization and space. 54 | 55 | Pushing your app to the cloud should be as easy as: 56 | 57 | * Build your app: `mvn package` 58 | * Go in the `target` directory 59 | * Push the app: `cf push my-spring-boot-demo -p spring-boot-intro-conference-0.0.1-SNAPSHOT.jar` 60 | 61 | NOTE: `my-spring-boot-demo` is the name of your app and will also be used for the default 62 | route of your app (i.e. `my-spring-boot-demo.cfapps.io`). If that name is taken, update 63 | the `cf push` command accordingly. 64 | 65 | That's it, your app is in the cloud. You can now send a sample _json_ representation of a 66 | speaker on the service's URL. You can also use `cf logs my-spring-boot-demo` to look at 67 | the logs of the app. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.2.4.RELEASE 10 | 11 | 12 | com.example 13 | spring-boot-intro-conference 14 | 0.0.1-SNAPSHOT 15 | Spring Boot intro (conference) 16 | Spring Boot introduction sample app 17 | 18 | 19 | 1.8 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-data-jpa 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-data-rest 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-security 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-actuator 42 | 43 | 44 | 45 | com.h2database 46 | h2 47 | runtime 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-devtools 53 | true 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-test 59 | test 60 | 61 | 62 | 63 | 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-maven-plugin 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/main/java/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import java.util.Random; 4 | 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.actuate.health.Health; 7 | import org.springframework.boot.actuate.health.HealthIndicator; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | import org.springframework.context.annotation.Bean; 10 | 11 | @SpringBootApplication 12 | public class DemoApplication { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(DemoApplication.class, args); 16 | } 17 | 18 | @Bean 19 | public HealthIndicator myHealthIndicator() { 20 | return () -> { 21 | if (new Random().nextBoolean()) { 22 | return Health.up().build(); 23 | } 24 | else { 25 | return Health.down().withDetail("booooooo", 42).build(); 26 | } 27 | }; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/demo/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest; 4 | import org.springframework.boot.actuate.health.HealthEndpoint; 5 | import org.springframework.boot.actuate.info.InfoEndpoint; 6 | import org.springframework.boot.autoconfigure.security.servlet.PathRequest; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 11 | import org.springframework.security.core.userdetails.User; 12 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 13 | 14 | @Configuration 15 | class SecurityConfig extends WebSecurityConfigurerAdapter { 16 | 17 | @Override 18 | protected void configure(HttpSecurity http) throws Exception { 19 | http.authorizeRequests() 20 | .requestMatchers(EndpointRequest.to(InfoEndpoint.class, HealthEndpoint.class)).permitAll() 21 | .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("HERO") 22 | .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() 23 | .anyRequest().authenticated() 24 | .and().formLogin() 25 | .and().httpBasic(); 26 | } 27 | 28 | @Bean 29 | @SuppressWarnings("deprecation") 30 | public InMemoryUserDetailsManager inMemoryUserDetailsManager() { 31 | return new InMemoryUserDetailsManager( 32 | User.withDefaultPasswordEncoder().username("hero").password("hero").roles("HERO", "USER").build(), 33 | User.withDefaultPasswordEncoder().username("user").password("user").roles("USER").build() 34 | ); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/demo/domain/Speaker.java: -------------------------------------------------------------------------------- 1 | package demo.domain; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | import javax.persistence.Lob; 9 | 10 | @Entity 11 | public class Speaker implements java.io.Serializable { 12 | 13 | @GeneratedValue(strategy = GenerationType.IDENTITY) 14 | @Id 15 | private Long id; 16 | 17 | private String firstName; 18 | 19 | private String lastName; 20 | 21 | private String twitter; 22 | 23 | private String github; 24 | 25 | @Column(columnDefinition = "TEXT") 26 | @Lob 27 | private String bio; 28 | 29 | public Speaker() { 30 | } 31 | 32 | public Speaker(String firstName, String lastName, String twitter, String github) { 33 | this.firstName = firstName; 34 | this.lastName = lastName; 35 | this.twitter = twitter; 36 | this.github = github; 37 | } 38 | 39 | public Long getId() { 40 | return id; 41 | } 42 | 43 | public String getFirstName() { 44 | return firstName; 45 | } 46 | 47 | public void setFirstName(String firstName) { 48 | this.firstName = firstName; 49 | } 50 | 51 | public String getLastName() { 52 | return lastName; 53 | } 54 | 55 | public void setLastName(String lastName) { 56 | this.lastName = lastName; 57 | } 58 | 59 | public String getTwitter() { 60 | return twitter; 61 | } 62 | 63 | public void setTwitter(String twitter) { 64 | this.twitter = twitter; 65 | } 66 | 67 | public String getGithub() { 68 | return this.github; 69 | } 70 | 71 | public void setGithub(String github) { 72 | this.github = github; 73 | } 74 | 75 | public String getBio() { 76 | return bio; 77 | } 78 | 79 | public void setBio(String bio) { 80 | this.bio = bio; 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return "Speaker{" + "id=" + id + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + '}'; 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /src/main/java/demo/domain/SpeakerRepository.java: -------------------------------------------------------------------------------- 1 | package demo.domain; 2 | 3 | import java.util.Collection; 4 | 5 | import org.springframework.data.repository.CrudRepository; 6 | import org.springframework.data.repository.query.Param; 7 | import org.springframework.data.rest.core.annotation.RestResource; 8 | 9 | public interface SpeakerRepository extends CrudRepository { 10 | 11 | @RestResource(path = "by-twitter") 12 | Speaker findByTwitter(@Param("id") String twitter); 13 | 14 | @RestResource(path = "by-github") 15 | Speaker findByGithub(@Param("id") String github); 16 | 17 | Collection findByLastName(@Param("name") String lastName); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/demo/web/HomeController.java: -------------------------------------------------------------------------------- 1 | package demo.web; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class HomeController { 8 | 9 | @GetMapping("/") 10 | public String home() { 11 | return "Hello World"; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | management.endpoints.web.exposure.include=* 2 | management.endpoint.health.show-details=when_authorized -------------------------------------------------------------------------------- /src/main/resources/import.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Jürgen', 'Höller', 'springjuergen', 'jhoeller', 'Polishing code since 2002. Spring Framework gatekeeper. Taught Chuck Norris how to code.'); 2 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Dave', 'Syer', 'david_syer', 'dsyer', 'Founder and contributor to Spring Batch, lead of Spring Security OAuth, and an active contributor to Spring Integration, Spring Framework, Spring AMQP, Spring Security. Experienced, delivery-focused architect and development manager. Has designed and built successful enterprise software solutions using Spring, and implemented them in major institutions worldwide.'); 3 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Andy', 'Wilkinson', 'ankinson', 'wilkinsona', 'Husband. Dad. Fair weather mountain biker. Developer at Pivotal/SpringSource by day, iOS developer @deftmethods by night.'); 4 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Phil', 'Webb', 'phillip_webb', 'philwebb', 'Phil Webb is a Spring Framework developer and co-creator of the Spring Boot project. Prior to joining Pivotal and relocating to California, Phil worked for a number of UK technology companies.'); 5 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Madhura', 'Bhave', 'madhurabhave23', 'mbhave', 'Spring Boot Developer'); 6 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Brian', 'Clozel', 'bclozel', 'bclozel', 'Spring Framework committer, Pivotal inc.'); 7 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Stéphane', 'Nicoll', 'snicoll', 'snicoll', 'Stéphane has a thing for code quality and robustness. He''s been spreading the word for more than ten years while developing large scale Java enterprise applications in the geospatial, financial, or logistics sectors. An Apache Maven PMC member since 2006, he joined the core Spring Framework development team early 2014, being one of the main contributors to both Spring Framework and Spring Boot since. During his free time, he loves traveling around the world.'); 8 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Sébastien', 'Deleuze', 'sdeleuze', 'sdeleuze', 'Spring framework commiter @ Pivotal and Dart Google Developer Expert'); 9 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Yann', 'Cébron', 'yanncebron', 'YannCebron', 'Yann has been working on Spring support in IntelliJ IDEA for three years. Before joining JetBrains, he spent a decade on (web) applications for a large variety of customers and trades. When he’s not coding, you’ll probably find him in the woods walking his dog or playing the piano.'); 10 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Trisha', 'Gee', 'trisha_gee', 'trishagee', 'Trisha has developed Java applications for a range of industries, including finance, manufacturing and non-profit, for companies of all sizes. She has expertise in Java high performance systems, is passionate about enabling developer productivity, and dabbles with Open Source development. Trisha blogs regularly on subjects that she thinks developers and other humans should care about, she’s a leader of the Sevilla Java User Group, a key member of the London Java Community and a Java Champion - she believes we shouldn''t all have to make the same mistakes again and again.'); 11 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Mark', 'Fisher', 'm_f_', 'markfisher', 'Mark has been a member of the Spring team for over 8 years, contributing to the Spring Framework and several other projects. He founded Spring Integration in 2007 and is one of the authors of Spring Integration in Action, published by Manning in 2012. Currently he co-leads Spring XD where he focuses on the distributed runtime and everything related to the flow of data.'); 12 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Gary', 'Russel', 'gprussel', 'garyrussell', 'Gary has been in software engineering, concentrating on Enterprise Integration, for over 30 years on various platforms, and in the Java space since the late ''90s. He has been developing with the Spring Framework since 2004. He has been a committer on the Spring Integration and Spring AMQP projects for over 4 years, and now leads both projects.'); 13 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Christoph', 'Strobl', 'stroblchristoph', 'christophstrobl', 'father, coder,... with yet so much to learn having so little time'); 14 | INSERT INTO SPEAKER(FIRST_NAME, LAST_NAME, TWITTER, GITHUB, BIO) values ('Mark', 'Paluch', 'mp911de', 'mp911de', 'Mark is Software Craftsman, Spring Data Engineer at Pivotal, a member of the CDI 2.0 expert group, and Lead of the lettuce Redis driver. He has been into developing Java server-, frontend-, and web applications for over 12 years and his focus is now on software architecture, Spring and Redis clients.'); 15 | -------------------------------------------------------------------------------- /src/main/resources/static/error/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ooops, page not found 6 | 16 | 17 | 18 |
19 |
20 |

¯\_(ツ)_/¯

21 |
22 |
23 | 24 | --------------------------------------------------------------------------------