├── README.md ├── book └── 자바웹개발 인쇄_2쇄.pdf ├── demo-diff ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── me │ │ │ └── whiteship │ │ │ └── demodiff │ │ │ └── DemoDiffApplication.java │ └── resources │ │ ├── ExchangeServer.reg │ │ ├── ExchangeServer_BE.reg │ │ ├── ExchangeServer_BE_bak.reg │ │ └── application.properties │ └── test │ └── java │ └── me │ └── whiteship │ └── demodiff │ └── DemoDiffApplicationTests.java ├── demo-oauth2 ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── me │ │ │ └── whiteship │ │ │ └── demooauth2 │ │ │ ├── DemoOauth2Application.java │ │ │ ├── config │ │ │ ├── AuthorizationServerConfig.java │ │ │ ├── ResourceServerConfig.java │ │ │ └── SecurityConfig.java │ │ │ └── users │ │ │ ├── User.java │ │ │ ├── UserController.java │ │ │ ├── UserRepository.java │ │ │ └── UserService.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── me │ └── whiteship │ └── demooauth2 │ └── DemoOauth2ApplicationTests.java ├── demo-spring-docker ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── Dockerfile ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── me │ │ │ └── whiteship │ │ │ └── demospringdocker │ │ │ └── DemoSpringDockerApplication.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── me │ └── whiteship │ └── demospringdocker │ └── DemoSpringDockerApplicationTests.java ├── demo-thymeleaf-web ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── demothymeleafweb │ │ │ ├── DemoThymeleafWebApplication.java │ │ │ ├── Hello.java │ │ │ ├── Member.java │ │ │ ├── MemberRepository.java │ │ │ ├── Person.java │ │ │ ├── SampleController.java │ │ │ └── SecurityConfig.java │ └── resources │ │ ├── application.properties │ │ ├── keystore.jks │ │ ├── messages.properties │ │ └── templates │ │ ├── hello.html │ │ └── index.html │ └── test │ └── java │ └── com │ └── example │ └── demothymeleafweb │ ├── HelloTest.java │ ├── MemberRepositoryTest.java │ └── SampleControllerTest.java ├── demospringbootsecurity ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── me │ │ │ └── whiteship │ │ │ └── demo │ │ │ ├── DemoApplication.java │ │ │ ├── MvcConfig.java │ │ │ ├── WebSecurityConfig.java │ │ │ └── account │ │ │ ├── Account.java │ │ │ ├── AccountController.java │ │ │ ├── AccountRepository.java │ │ │ └── AccountService.java │ └── resources │ │ ├── application.properties │ │ └── templates │ │ ├── hello.html │ │ ├── home.html │ │ └── login.html │ └── test │ └── java │ └── me │ └── whiteship │ └── demo │ └── DemoApplicationTests.java ├── demospringssl ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── keystore.p12 ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── me │ │ │ └── whiteship │ │ │ └── demo │ │ │ └── DemoApplication.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── me │ └── whiteship │ └── demo │ └── DemoApplicationTests.java ├── docker-cmds.md ├── doker-getting-started.md ├── effective-java-3rd ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── me │ │ │ └── whiteship │ │ │ └── effectivejava3rd │ │ │ ├── EffectiveJava3rdApplication.java │ │ │ ├── item01 │ │ │ ├── Foo.java │ │ │ ├── FooInterface.java │ │ │ └── MyFoo.java │ │ │ ├── item02 │ │ │ ├── Calzone.java │ │ │ ├── NutritionFacts.java │ │ │ ├── NyPizza.java │ │ │ ├── Pizza.java │ │ │ └── PizzaClient.java │ │ │ ├── item03 │ │ │ ├── Config.java │ │ │ ├── SingleTest.java │ │ │ ├── Singleton1.java │ │ │ ├── Singleton2.java │ │ │ ├── Singleton3.java │ │ │ ├── UserRepository.java │ │ │ └── UserService.java │ │ │ ├── item04 │ │ │ └── UtilClass.java │ │ │ ├── item05 │ │ │ ├── usecase1 │ │ │ │ └── SpellChecker.java │ │ │ ├── usecase2 │ │ │ │ └── SpellChecker.java │ │ │ ├── usecase3 │ │ │ │ └── SpellChecker.java │ │ │ └── usercase4 │ │ │ │ ├── Config.java │ │ │ │ ├── KoreanDictionary.java │ │ │ │ ├── Lexicon.java │ │ │ │ ├── SpellChecker.java │ │ │ │ └── SpellCheckerClient.java │ │ │ ├── item06 │ │ │ ├── autoboxing │ │ │ │ └── AutoBoxingExample.java │ │ │ ├── expensiveobject01 │ │ │ │ └── RomanNumber.java │ │ │ ├── expensiveobject02 │ │ │ │ └── RomanNumber.java │ │ │ ├── map │ │ │ │ └── UsingKeySet.java │ │ │ └── strings │ │ │ │ └── StringTest.java │ │ │ ├── item07 │ │ │ ├── cache │ │ │ │ └── CacheSample.java │ │ │ ├── memoryleak │ │ │ │ └── Stack.java │ │ │ └── package-info.java │ │ │ ├── item08 │ │ │ ├── FinalizerExample.java │ │ │ ├── SampleResource.java │ │ │ └── SampleRunner.java │ │ │ └── item09 │ │ │ ├── AppRunner.java │ │ │ ├── FirstError.java │ │ │ ├── MyResource.java │ │ │ └── SecondError.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── me │ └── whiteship │ └── effectivejava3rd │ ├── EffectiveJava3rdApplicationTests.java │ └── item06 │ ├── expensiveobject01 │ └── RomanNumberTest.java │ └── expensiveobject02 │ └── RomanNumberTest.java ├── effective-java ├── Readme.md ├── item1.md ├── item2.md ├── item3.md ├── item4.md ├── item5.md ├── item6.md ├── item7.md ├── item8.md └── item9.md ├── hibernate-orm-reference-coding.md ├── infoq.md ├── jpa-getting-started ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── docker-compose.yml ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── me │ │ │ └── whiteship │ │ │ └── jpasudy │ │ │ ├── Application.java │ │ │ ├── Event.java │ │ │ ├── EventDetail.java │ │ │ ├── EventDetailRepository.java │ │ │ └── EventRepository.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── me │ └── whiteship │ └── jpasudy │ ├── EventRepositoryTest.java │ └── JpaSudyApplicationTests.java ├── ksug201811restapi ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── asciidoc │ │ └── index.adoc │ ├── java │ │ └── me │ │ │ └── whiteship │ │ │ └── ksug201811restapi │ │ │ ├── Application.java │ │ │ ├── accounts │ │ │ ├── Account.java │ │ │ ├── AccountAdapter.java │ │ │ ├── AccountRepository.java │ │ │ ├── AccountRoles.java │ │ │ ├── AccountSerializer.java │ │ │ ├── AccountService.java │ │ │ └── MyAppProperties.java │ │ │ ├── common │ │ │ ├── ErrorResource.java │ │ │ └── ErrorsSerializer.java │ │ │ ├── config │ │ │ ├── AuthServerConfig.java │ │ │ ├── ResourceServerConfig.java │ │ │ └── SecurityConfig.java │ │ │ ├── events │ │ │ ├── CurrentUser.java │ │ │ ├── Event.java │ │ │ ├── EventController.java │ │ │ ├── EventDto.java │ │ │ ├── EventDtoValidator.java │ │ │ ├── EventRepository.java │ │ │ ├── EventResource.java │ │ │ └── EventStatus.java │ │ │ └── index │ │ │ └── IndexController.java │ └── resources │ │ ├── application.properties │ │ └── static │ │ └── hello.html │ └── test │ ├── java │ └── me │ │ └── whiteship │ │ └── ksug201811restapi │ │ ├── Ksug201811restapiApplicationTests.java │ │ ├── common │ │ ├── ControllerTests.java │ │ └── TestDocConfig.java │ │ ├── events │ │ ├── EventControllerTest.java │ │ ├── EventRepositoryTest.java │ │ └── EventTest.java │ │ └── security │ │ └── OAuthTokenTest.java │ └── resources │ └── application-test.properties ├── kubernetes-getting-started.md ├── rest-api-with-spring ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml ├── readme.md ├── scripts.md └── src │ ├── main │ ├── asciidoc │ │ └── index.adoc │ ├── java │ │ └── me │ │ │ └── whiteship │ │ │ └── demoinfleanrestapi │ │ │ ├── DemoApplication.java │ │ │ ├── accounts │ │ │ ├── Account.java │ │ │ ├── AccountAdapter.java │ │ │ ├── AccountRepository.java │ │ │ ├── AccountRole.java │ │ │ ├── AccountSerializer.java │ │ │ ├── AccountService.java │ │ │ └── CurrentUser.java │ │ │ ├── common │ │ │ ├── AppProperties.java │ │ │ ├── ErrorsResource.java │ │ │ ├── ErrorsSerializer.java │ │ │ └── TestDescription.java │ │ │ ├── configs │ │ │ ├── AppConfig.java │ │ │ ├── AuthServerConfig.java │ │ │ ├── ResourceServerConfig.java │ │ │ └── SecurityConfig.java │ │ │ ├── events │ │ │ ├── Event.java │ │ │ ├── EventController.java │ │ │ ├── EventDto.java │ │ │ ├── EventRepository.java │ │ │ ├── EventResource.java │ │ │ ├── EventStatus.java │ │ │ └── EventValidator.java │ │ │ └── index │ │ │ └── IndexController.java │ └── resources │ │ └── application.properties │ └── test │ ├── java │ └── me │ │ └── whiteship │ │ └── demoinfleanrestapi │ │ ├── accounts │ │ └── AccountServiceTest.java │ │ ├── common │ │ ├── BaseTest.java │ │ └── RestDocsConfiguration.java │ │ ├── configs │ │ └── AuthServerConfigTest.java │ │ ├── events │ │ ├── EventControllerTests.java │ │ └── EventTest.java │ │ └── index │ │ └── IndexControllerTest.java │ └── resources │ └── application-test.properties ├── spring-boot-reference-coding.md ├── spring-data-jpa-reference-coding.md └── springdockerdemo ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── Dockerfile ├── descrypted.txt ├── file.ssl ├── file.txt ├── keystore.p12 ├── mvnw ├── mvnw.cmd ├── pom.xml ├── private.pem ├── public.pem └── src ├── main ├── java │ └── me │ │ └── whiteship │ │ └── springdockerdemo │ │ └── SpringdockerdemoApplication.java └── resources │ └── application.properties └── test └── java └── me └── whiteship └── springdockerdemo └── SpringdockerdemoApplicationTests.java /README.md: -------------------------------------------------------------------------------- 1 | # Welcome 2 | Hi, This is Keesun (AKA, Whiteship) and this repository would contain the things that I am interested in. 3 | **ALL THE CONTENTS ARE OWNED AND MADE BY Keesun.** 4 | 5 | # Online Courses 6 | ## [:computer: Spring Framework Introduction](https://www.inflearn.com/course/spring/) 7 | ## [:computer: Spring Framework Core Technology](https://www.inflearn.com/course/spring-framework_core/) 8 | ## [:computer: Spring Boot](https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/) 9 | ## [:computer: Spring Data JPA](https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-jpa/) 10 | ## [:computer: REST API Development with Spring](https://www.inflearn.com/course/spring_rest-api/) 11 | 12 | # Youtube 13 | ## [:tv: InfoQ articles](infoq.md) 14 | ## [:tv: Spring Data JPA](spring-data-jpa-reference-coding.md) 15 | ## [:tv: Hibernate ORM Reference Coding](hibernate-orm-reference-coding.md) 16 | ## [:tv: :book: Effective Java](https://github.com/keesun/study/tree/master/effective-java) 17 | ## [:tv: Spring Boot Reference Coding 1-39 (完)](spring-boot-reference-coding.md) 18 | ## [:tv: Getting Started Docker 1-3 (完)](doker-getting-started.md) 19 | ## [:tv: Getting Started Kubernetes 1-3(完)](kubernetes-getting-started.md) 20 | 21 | # Repository 22 | ## [REST API Development WIth Spring](https://github.com/keesun/study/tree/master/rest-api-with-spring) 23 | 24 | # Cheet Sheet 25 | ## [Docker cheetsheet](docker-cmds.md) 26 | 27 | -------------------------------------------------------------------------------- /book/자바웹개발 인쇄_2쇄.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/book/자바웹개발 인쇄_2쇄.pdf -------------------------------------------------------------------------------- /demo-diff/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | -------------------------------------------------------------------------------- /demo-diff/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/demo-diff/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /demo-diff/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /demo-diff/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.3.RELEASE 9 | 10 | 11 | me.whiteship 12 | demo-diff 13 | 0.0.1-SNAPSHOT 14 | demo-diff 15 | Demo project for Spring Boot 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-test 30 | test 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-maven-plugin 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /demo-diff/src/main/java/me/whiteship/demodiff/DemoDiffApplication.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demodiff; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.core.io.ClassPathResource; 6 | import org.springframework.core.io.Resource; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Scanner; 16 | 17 | public class DemoDiffApplication { 18 | 19 | public static void main(String[] args) throws IOException { 20 | Path exchangePath = Paths.get("C:\\Users\\kebaik\\workspace\\study\\demo-diff\\src\\main\\resources\\ExchangeServer.reg"); 21 | File exchange = exchangePath.toFile(); 22 | Path exchangeBEPath = Paths.get("C:\\Users\\kebaik\\workspace\\study\\demo-diff\\src\\main\\resources\\ExchangeServer_BE.reg"); 23 | File exchangeBE = exchangeBEPath.toFile(); 24 | 25 | System.out.println(exchange.exists()); 26 | System.out.println(exchangeBE.exists()); 27 | 28 | if (!exchange.exists() || !exchangeBE.exists()) { 29 | return; 30 | } 31 | 32 | List keys = new ArrayList<>(); 33 | try(Scanner scanner = new Scanner(exchange)) { 34 | while(scanner.hasNextLine()) { 35 | String line = scanner.nextLine(); 36 | if (line.startsWith("[")) { 37 | keys.add(line); 38 | } 39 | } 40 | } 41 | 42 | System.out.println("found " + keys.size() + " keys"); 43 | 44 | try(Scanner scanner = new Scanner(exchangeBE)) { 45 | while(scanner.hasNextLine()) { 46 | String line = scanner.nextLine(); 47 | if (line.startsWith("[")) { 48 | keys.remove(line); 49 | } 50 | } 51 | } 52 | 53 | System.out.println("Remaining... " + keys.size() + " keys"); 54 | keys.forEach(System.out::println); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /demo-diff/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /demo-diff/src/test/java/me/whiteship/demodiff/DemoDiffApplicationTests.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demodiff; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class DemoDiffApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /demo-oauth2/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /demo-oauth2/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/demo-oauth2/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /demo-oauth2/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip 2 | -------------------------------------------------------------------------------- /demo-oauth2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | me.whiteship 7 | demo-oauth2 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | demo-oauth2 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.1.1.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 11 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-data-jpa 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-security 39 | 40 | 41 | org.springframework.security.oauth 42 | spring-security-oauth2 43 | 2.3.4.RELEASE 44 | 45 | 46 | com.h2database 47 | h2 48 | 49 | 50 | javax.xml.bind 51 | jaxb-api 52 | 53 | 54 | org.glassfish.jaxb 55 | jaxb-runtime 56 | 57 | 58 | org.springframework 59 | spring-oxm 60 | ${spring-framework.version} 61 | 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-starter-test 66 | test 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.springframework.boot 74 | spring-boot-maven-plugin 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /demo-oauth2/src/main/java/me/whiteship/demooauth2/DemoOauth2Application.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demooauth2; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoOauth2Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoOauth2Application.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /demo-oauth2/src/main/java/me/whiteship/demooauth2/config/AuthorizationServerConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demooauth2.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.authentication.AuthenticationManager; 6 | import org.springframework.security.core.userdetails.UserDetailsService; 7 | import org.springframework.security.crypto.password.PasswordEncoder; 8 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; 9 | import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; 10 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; 11 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; 12 | import org.springframework.security.oauth2.provider.token.TokenStore; 13 | 14 | @Configuration 15 | @EnableAuthorizationServer 16 | public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { 17 | 18 | @Autowired 19 | private TokenStore tokenStore; 20 | 21 | @Autowired 22 | private AuthenticationManager authenticationManager; 23 | 24 | @Autowired 25 | private PasswordEncoder passwordEncoder; 26 | 27 | @Autowired 28 | private UserDetailsService userDetailsService; 29 | 30 | @Override 31 | public void configure(ClientDetailsServiceConfigurer configurer) throws Exception { 32 | configurer 33 | .inMemory() 34 | .withClient("keesun-client") 35 | .secret(passwordEncoder.encode("keesun-password")) 36 | .authorizedGrantTypes("password", 37 | "authorization_code", 38 | "refresh_token", 39 | "implicit") 40 | .scopes("read", "write", "trust") 41 | .accessTokenValiditySeconds(1*60*60) 42 | .refreshTokenValiditySeconds(6*60*60); 43 | } 44 | 45 | @Override 46 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { 47 | endpoints.tokenStore(tokenStore) 48 | .authenticationManager(authenticationManager) 49 | .userDetailsService(userDetailsService); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /demo-oauth2/src/main/java/me/whiteship/demooauth2/config/ResourceServerConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demooauth2.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 5 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 6 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 7 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; 8 | import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; 9 | 10 | @Configuration 11 | @EnableResourceServer 12 | public class ResourceServerConfig extends ResourceServerConfigurerAdapter { 13 | 14 | @Override 15 | public void configure(ResourceServerSecurityConfigurer resources) throws Exception { 16 | resources.resourceId("resource_id").stateless(false); 17 | } 18 | 19 | @Override 20 | public void configure(HttpSecurity http) throws Exception { 21 | http 22 | .anonymous().disable() 23 | .authorizeRequests() 24 | .antMatchers("/users/**").authenticated() 25 | .and() 26 | .exceptionHandling() 27 | .accessDeniedHandler(new OAuth2AccessDeniedHandler()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /demo-oauth2/src/main/java/me/whiteship/demooauth2/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demooauth2.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.authentication.AuthenticationManager; 6 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 9 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 10 | import org.springframework.security.core.userdetails.UserDetailsService; 11 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 12 | import org.springframework.security.crypto.password.PasswordEncoder; 13 | import org.springframework.security.oauth2.provider.token.TokenStore; 14 | import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; 15 | import org.springframework.web.cors.CorsConfiguration; 16 | import org.springframework.web.cors.CorsConfigurationSource; 17 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 18 | 19 | import javax.annotation.Resource; 20 | import java.util.Arrays; 21 | 22 | @Configuration 23 | @EnableWebSecurity 24 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 25 | 26 | @Resource(name = "userService") 27 | private UserDetailsService userDetailsService; 28 | 29 | @Bean 30 | public PasswordEncoder encoder() { 31 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 32 | } 33 | 34 | @Bean 35 | public TokenStore tokenStore() { 36 | return new InMemoryTokenStore(); 37 | } 38 | 39 | @Bean 40 | @Override 41 | protected AuthenticationManager authenticationManager() throws Exception { 42 | return super.authenticationManager(); 43 | } 44 | 45 | @Override 46 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 47 | auth.userDetailsService(userDetailsService) 48 | .passwordEncoder(encoder()); 49 | } 50 | 51 | @Override 52 | protected void configure(HttpSecurity http) throws Exception { 53 | http 54 | .cors() 55 | .and() 56 | .csrf() 57 | .disable() 58 | .anonymous() 59 | .disable() 60 | .authorizeRequests() 61 | .antMatchers("/api-docs/**").permitAll(); 62 | } 63 | 64 | @Bean 65 | public CorsConfigurationSource corsConfigurationSource() 66 | { 67 | CorsConfiguration configuration = new CorsConfiguration(); 68 | configuration.setAllowedOrigins(Arrays.asList("*")); 69 | configuration.setAllowedMethods(Arrays.asList("*")); 70 | configuration.setAllowedHeaders(Arrays.asList("*")); 71 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 72 | source.registerCorsConfiguration("/**", configuration); 73 | return source; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /demo-oauth2/src/main/java/me/whiteship/demooauth2/users/User.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demooauth2.users; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.Id; 6 | 7 | @Entity 8 | public class User { 9 | 10 | @Id @GeneratedValue 11 | private Long id; 12 | 13 | private String username; 14 | 15 | private String password; 16 | 17 | public Long getId() { 18 | return id; 19 | } 20 | 21 | public void setId(Long id) { 22 | this.id = id; 23 | } 24 | 25 | public String getUsername() { 26 | return username; 27 | } 28 | 29 | public void setUsername(String username) { 30 | this.username = username; 31 | } 32 | 33 | public String getPassword() { 34 | return password; 35 | } 36 | 37 | public void setPassword(String password) { 38 | this.password = password; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "User{" + 44 | "id=" + id + 45 | ", username='" + username + '\'' + 46 | ", password='" + password + '\'' + 47 | '}'; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /demo-oauth2/src/main/java/me/whiteship/demooauth2/users/UserController.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demooauth2.users; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.*; 5 | 6 | import java.util.List; 7 | 8 | @RestController 9 | @RequestMapping("/users") 10 | public class UserController { 11 | 12 | @Autowired 13 | private UserService userService; 14 | 15 | @GetMapping("/user") 16 | public List listUser() { 17 | return userService.findAll(); 18 | } 19 | 20 | @PostMapping("/user") 21 | public User create(@RequestBody User user) { 22 | return userService.save(user); 23 | } 24 | 25 | @DeleteMapping("/user/{id}") 26 | public String delete(@PathVariable Long id) { 27 | userService.delete(id); 28 | return "success"; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /demo-oauth2/src/main/java/me/whiteship/demooauth2/users/UserRepository.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demooauth2.users; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface UserRepository extends JpaRepository { 6 | User findByUsername(String username); 7 | } 8 | -------------------------------------------------------------------------------- /demo-oauth2/src/main/java/me/whiteship/demooauth2/users/UserService.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demooauth2.users; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 9 | import org.springframework.security.crypto.password.PasswordEncoder; 10 | import org.springframework.stereotype.Service; 11 | 12 | import javax.annotation.PostConstruct; 13 | import java.util.Arrays; 14 | import java.util.Collection; 15 | import java.util.List; 16 | 17 | @Service 18 | public class UserService implements UserDetailsService { 19 | 20 | @Autowired 21 | UserRepository userRepository; 22 | 23 | @Autowired 24 | PasswordEncoder passwordEncoder; 25 | 26 | 27 | public List findAll() { 28 | return userRepository.findAll(); 29 | } 30 | 31 | public User save(User user) { 32 | user.setPassword(passwordEncoder.encode(user.getPassword())); 33 | return userRepository.save(user); 34 | } 35 | 36 | public void delete(Long id) { 37 | userRepository.deleteById(id); 38 | } 39 | 40 | @PostConstruct 41 | public void init() { 42 | User keesun = userRepository.findByUsername("keesun"); 43 | if (keesun == null) { 44 | User user = new User(); 45 | user.setUsername("keesun"); 46 | user.setPassword("pass"); 47 | User newUser = this.save(user); 48 | System.out.println(newUser); 49 | } 50 | } 51 | 52 | @Override 53 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 54 | User user = userRepository.findByUsername(username); 55 | return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthorities()); 56 | } 57 | 58 | private Collection getAuthorities() { 59 | return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /demo-oauth2/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/demo-oauth2/src/main/resources/application.properties -------------------------------------------------------------------------------- /demo-oauth2/src/test/java/me/whiteship/demooauth2/DemoOauth2ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demooauth2; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class DemoOauth2ApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /demo-spring-docker/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /demo-spring-docker/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/demo-spring-docker/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /demo-spring-docker/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip 2 | -------------------------------------------------------------------------------- /demo-spring-docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jre 2 | COPY target/demo-spring-docker*.jar app.jar 3 | ENTRYPOINT ["java", "-jar", "/app.jar"] 4 | -------------------------------------------------------------------------------- /demo-spring-docker/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | me.whiteship 7 | demo-spring-docker 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | demo-spring-docker 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.3.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-test 36 | test 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-maven-plugin 45 | 46 | 47 | io.fabric8 48 | docker-maven-plugin 49 | 0.26.0 50 | 51 | 52 | 53 | whiteship/demospringdocker 54 | 55 | ${basedir} 56 | 57 | 58 | 59 | 60 | 61 | 62 | docker-build 63 | package 64 | 65 | build 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /demo-spring-docker/src/main/java/me/whiteship/demospringdocker/DemoSpringDockerApplication.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demospringdocker; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @SpringBootApplication 9 | @RestController 10 | public class DemoSpringDockerApplication { 11 | 12 | @GetMapping("/") 13 | public String hello() { 14 | return "Hello Spring Boot"; 15 | } 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(DemoSpringDockerApplication.class, args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demo-spring-docker/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/demo-spring-docker/src/main/resources/application.properties -------------------------------------------------------------------------------- /demo-spring-docker/src/test/java/me/whiteship/demospringdocker/DemoSpringDockerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demospringdocker; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class DemoSpringDockerApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /demo-thymeleaf-web/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /demo-thymeleaf-web/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/demo-thymeleaf-web/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /demo-thymeleaf-web/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /demo-thymeleaf-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.2.RELEASE 9 | 10 | 11 | com.example 12 | demo-thymeleaf-web 13 | 0.0.1-SNAPSHOT 14 | demo-thymeleaf-web 15 | Demo project for Spring Boot 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-thymeleaf 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-devtools 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-security 37 | 38 | 39 | org.projectlombok 40 | lombok 41 | provided 42 | 43 | 44 | com.h2database 45 | h2 46 | test 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-data-jpa 51 | 52 | 53 | 54 | 55 | org.webjars.bower 56 | jquery 57 | 3.3.1 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-starter-test 63 | test 64 | 65 | 66 | 67 | 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-maven-plugin 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/main/java/com/example/demothymeleafweb/DemoThymeleafWebApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.demothymeleafweb; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoThymeleafWebApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoThymeleafWebApplication.class, args); 11 | } 12 | 13 | } 14 | 15 | -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/main/java/com/example/demothymeleafweb/Hello.java: -------------------------------------------------------------------------------- 1 | package com.example.demothymeleafweb; 2 | 3 | import lombok.NonNull; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | public class Hello { 8 | 9 | @NonNull 10 | private String name; 11 | 12 | @NonNull 13 | private Integer age; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/main/java/com/example/demothymeleafweb/Member.java: -------------------------------------------------------------------------------- 1 | package com.example.demothymeleafweb; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import javax.persistence.Entity; 7 | import javax.persistence.GeneratedValue; 8 | import javax.persistence.Id; 9 | 10 | @Entity 11 | @Getter @Setter 12 | public class Member { 13 | 14 | @Id @GeneratedValue 15 | private Long id; 16 | 17 | private String name; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/main/java/com/example/demothymeleafweb/MemberRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.demothymeleafweb; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface MemberRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/main/java/com/example/demothymeleafweb/Person.java: -------------------------------------------------------------------------------- 1 | package com.example.demothymeleafweb; 2 | 3 | import javax.validation.constraints.Min; 4 | import javax.validation.constraints.NotEmpty; 5 | 6 | public class Person { 7 | 8 | @NotEmpty 9 | private String name; 10 | 11 | @Min(0) 12 | private Integer age; 13 | 14 | public String getName() { 15 | return name; 16 | } 17 | 18 | public void setName(String name) { 19 | this.name = name; 20 | } 21 | 22 | public Integer getAge() { 23 | return age; 24 | } 25 | 26 | public void setAge(Integer age) { 27 | this.age = age; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/main/java/com/example/demothymeleafweb/SampleController.java: -------------------------------------------------------------------------------- 1 | package com.example.demothymeleafweb; 2 | 3 | import lombok.NonNull; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.MessageSource; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.validation.BindingResult; 11 | import org.springframework.web.bind.annotation.*; 12 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 13 | 14 | import javax.servlet.http.HttpSession; 15 | import javax.servlet.http.PushBuilder; 16 | import javax.validation.Valid; 17 | import java.time.LocalDateTime; 18 | 19 | @Controller 20 | @RequiredArgsConstructor 21 | public class SampleController { 22 | 23 | @NonNull MessageSource messageSource; 24 | 25 | @GetMapping("/foo") 26 | @ResponseBody 27 | public String foo() { 28 | return "foo"; 29 | } 30 | 31 | @GetMapping("/bar") 32 | @ResponseBody 33 | public String bar() { 34 | return "bar"; 35 | } 36 | 37 | @GetMapping("/redirect") 38 | public String redirect(RedirectAttributes redirectAttributes) { 39 | redirectAttributes.addFlashAttribute("name", "keesun"); 40 | return "redirect:/receive"; 41 | } 42 | 43 | @GetMapping("/receive") 44 | @ResponseBody 45 | public String receive(Model model, HttpSession httpSession) { 46 | Object name = httpSession.getAttribute("name"); 47 | System.out.println(name); 48 | return name.toString(); 49 | } 50 | 51 | @RequestMapping("/") 52 | public String index(Model model) { 53 | model.addAttribute("title", "Page Title"); 54 | model.addAttribute("username", "Whiteship"); 55 | model.addAttribute("now", LocalDateTime.now()); 56 | return "index"; 57 | } 58 | 59 | @RequestMapping("/push") 60 | public String push(Model model, PushBuilder pushBuilder) { 61 | model.addAttribute("title", "Page Title"); 62 | model.addAttribute("username", "Whiteship"); 63 | model.addAttribute("now", LocalDateTime.now()); 64 | if (pushBuilder != null) { 65 | pushBuilder.path("/webjars/jquery/3.3.1/dist/jquery.min.js").push(); 66 | } 67 | return "index"; 68 | } 69 | 70 | @RequestMapping("/no-push") 71 | public String push(Model model) { 72 | model.addAttribute("title", "Page Title"); 73 | model.addAttribute("username", "Whiteship"); 74 | model.addAttribute("now", LocalDateTime.now()); 75 | return "index"; 76 | } 77 | 78 | @RequestMapping("/hello") 79 | public String hello(Model model) { 80 | model.addAttribute("message", "My message is hm.."); 81 | return "hello"; 82 | } 83 | 84 | @RequestMapping(method = RequestMethod.GET, value = "/s") 85 | @ResponseBody 86 | public String person(@RequestParam String q) { 87 | return q; 88 | } 89 | 90 | @PostMapping("/json") 91 | @ResponseBody 92 | public Person json(@Valid @RequestBody Person person, BindingResult bindingResult) { 93 | System.out.println(bindingResult); 94 | return person; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/main/java/com/example/demothymeleafweb/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.demothymeleafweb; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 5 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 6 | 7 | //@Configuration 8 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 9 | 10 | @Override 11 | public void configure(WebSecurity web) throws Exception { 12 | web.ignoring().mvcMatchers("/foo"); 13 | web.ignoring().mvcMatchers("/bar"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/demo-thymeleaf-web/src/main/resources/application.properties -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/main/resources/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/demo-thymeleaf-web/src/main/resources/keystore.jks -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | home.welcome={0} Welcome -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/main/resources/templates/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Title 5 | 6 | 7 |

message

8 | Click this 9 | 10 | -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 |

Welcome to home page

10 |

Now

11 |

Locale

12 | 13 | -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/test/java/com/example/demothymeleafweb/HelloTest.java: -------------------------------------------------------------------------------- 1 | package com.example.demothymeleafweb; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | public class HelloTest { 8 | 9 | @Test 10 | public void testHello() { 11 | Hello hello = new Hello("keesun", 20); 12 | 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/test/java/com/example/demothymeleafweb/MemberRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.example.demothymeleafweb; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | @RunWith(SpringRunner.class) 12 | @DataJpaTest 13 | public class MemberRepositoryTest { 14 | 15 | @Autowired MemberRepository memberRepository; 16 | 17 | @Test 18 | public void test1() { 19 | System.out.println("test1"); 20 | } 21 | 22 | @Test 23 | public void test2() { 24 | System.out.printf("test2"); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /demo-thymeleaf-web/src/test/java/com/example/demothymeleafweb/SampleControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.example.demothymeleafweb; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | import org.springframework.test.web.servlet.MockMvc; 11 | 12 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 13 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 16 | 17 | @RunWith(SpringRunner.class) 18 | @WebMvcTest 19 | public class SampleControllerTest { 20 | 21 | @Autowired 22 | private MockMvc mockMvc; 23 | 24 | @Autowired 25 | private ObjectMapper objectMapper; 26 | 27 | @Test 28 | public void testHello() throws Exception { 29 | 30 | 31 | this.mockMvc.perform(get("/s") 32 | .param("q","aaa")) 33 | .andDo(print()) 34 | .andExpect(status().isOk()) 35 | ; 36 | } 37 | 38 | @Test 39 | public void json() throws Exception { 40 | Person person = new Person(); 41 | person.setName("keesun"); 42 | person.setAge(-100); 43 | 44 | this.mockMvc.perform(post("/json") 45 | .contentType(MediaType.APPLICATION_JSON_UTF8) 46 | .content(objectMapper.writeValueAsString(person))) 47 | .andDo(print()) 48 | .andExpect(jsonPath("name").value("keesun")); 49 | } 50 | 51 | @Test 52 | public void redirect() throws Exception { 53 | this.mockMvc.perform(get("/redirect")) 54 | .andDo(print()); 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /demospringbootsecurity/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /demospringbootsecurity/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/demospringbootsecurity/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /demospringbootsecurity/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip 2 | -------------------------------------------------------------------------------- /demospringbootsecurity/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | me.whiteship 7 | demo 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | demo 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.3.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-thymeleaf 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-security 39 | 40 | 41 | 42 | org.springframework.security 43 | spring-security-test 44 | test 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-test 49 | test 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-maven-plugin 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /demospringbootsecurity/src/main/java/me/whiteship/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /demospringbootsecurity/src/main/java/me/whiteship/demo/MvcConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demo; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | @Configuration 8 | public class MvcConfig implements WebMvcConfigurer { 9 | 10 | @Override 11 | public void addViewControllers(ViewControllerRegistry registry) { 12 | registry.addViewController("/home").setViewName("home"); 13 | registry.addViewController("/").setViewName("home"); 14 | registry.addViewController("/hello").setViewName("hello"); 15 | registry.addViewController("/login").setViewName("login"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /demospringbootsecurity/src/main/java/me/whiteship/demo/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demo; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 7 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 8 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 9 | import org.springframework.security.crypto.password.PasswordEncoder; 10 | 11 | @Configuration 12 | @EnableWebSecurity 13 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 14 | 15 | @Override 16 | protected void configure(HttpSecurity http) throws Exception { 17 | http.authorizeRequests() 18 | .antMatchers("/", "/home", "/create").permitAll() 19 | .anyRequest().authenticated() 20 | .and() 21 | .formLogin() 22 | .loginPage("/login") 23 | .permitAll() 24 | .and() 25 | .logout() 26 | .logoutSuccessUrl("/home") 27 | .permitAll(); 28 | } 29 | 30 | @Bean 31 | public PasswordEncoder passwordEncoder() { 32 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /demospringbootsecurity/src/main/java/me/whiteship/demo/account/Account.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demo.account; 2 | 3 | public class Account { 4 | 5 | private Integer id; 6 | 7 | private String email; 8 | 9 | private String password; 10 | 11 | public Integer getId() { 12 | return id; 13 | } 14 | 15 | public void setId(Integer id) { 16 | this.id = id; 17 | } 18 | 19 | public String getEmail() { 20 | return email; 21 | } 22 | 23 | public void setEmail(String email) { 24 | this.email = email; 25 | } 26 | 27 | public String getPassword() { 28 | return password; 29 | } 30 | 31 | public void setPassword(String password) { 32 | this.password = password; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /demospringbootsecurity/src/main/java/me/whiteship/demo/account/AccountController.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demo.account; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | public class AccountController { 9 | 10 | @Autowired 11 | AccountService accountService; 12 | 13 | @GetMapping("/create") 14 | public Account create() { 15 | Account account = new Account(); 16 | account.setEmail("keesun@mail.com"); 17 | account.setPassword("password"); 18 | 19 | return accountService.save(account); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /demospringbootsecurity/src/main/java/me/whiteship/demo/account/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demo.account; 2 | 3 | import org.springframework.stereotype.Repository; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Random; 8 | 9 | @Repository 10 | public class AccountRepository { 11 | 12 | private Map accounts = new HashMap<>(); 13 | private Random random = new Random(); 14 | 15 | public Account save(Account account) { 16 | account.setId(random.nextInt()); 17 | accounts.put(account.getEmail(), account); 18 | return account; 19 | } 20 | 21 | public Account findByEmail(String username) { 22 | return accounts.get(username); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demospringbootsecurity/src/main/java/me/whiteship/demo/account/AccountService.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demo.account; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 | import org.springframework.security.crypto.password.PasswordEncoder; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Collection; 15 | import java.util.List; 16 | 17 | @Service 18 | public class AccountService implements UserDetailsService { 19 | 20 | @Autowired 21 | private AccountRepository accounts; 22 | 23 | @Autowired 24 | private PasswordEncoder passwordEncoder; 25 | 26 | @Override 27 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 28 | Account account = accounts.findByEmail(username); 29 | 30 | List authorities = new ArrayList<>(); 31 | authorities.add(new SimpleGrantedAuthority("ROLE_USER")); 32 | 33 | return new User(account.getEmail(), account.getPassword(), authorities); 34 | } 35 | 36 | public Account save(Account account) { 37 | account.setPassword(passwordEncoder.encode(account.getPassword())); 38 | return accounts.save(account); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /demospringbootsecurity/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/demospringbootsecurity/src/main/resources/application.properties -------------------------------------------------------------------------------- /demospringbootsecurity/src/main/resources/templates/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | Hello World! 6 | 7 | 8 |

Hello [[${#httpServletRequest.remoteUser}]]!

9 |
10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /demospringbootsecurity/src/main/resources/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Security Example 5 | 6 | 7 |

Welcome!

8 | 9 |

Click here to see a greeting.

10 | 11 | -------------------------------------------------------------------------------- /demospringbootsecurity/src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | Spring Security Example 6 | 7 | 8 |
9 | Invalid username and password. 10 |
11 |
12 | You have been logged out. 13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | -------------------------------------------------------------------------------- /demospringbootsecurity/src/test/java/me/whiteship/demo/DemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demo; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class DemoApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /demospringssl/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /demospringssl/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/demospringssl/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /demospringssl/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip 2 | -------------------------------------------------------------------------------- /demospringssl/keystore.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/demospringssl/keystore.p12 -------------------------------------------------------------------------------- /demospringssl/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | me.whiteship 7 | demo 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | demo 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.3.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-test 36 | test 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-maven-plugin 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /demospringssl/src/main/java/me/whiteship/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PostMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @SpringBootApplication 10 | @RestController 11 | public class DemoApplication { 12 | 13 | @GetMapping("/") 14 | public String hello() { 15 | return "Hello Spring"; 16 | } 17 | 18 | @PostMapping("/post") 19 | public String post() { 20 | return "post"; 21 | } 22 | 23 | public static void main(String[] args) { 24 | SpringApplication.run(DemoApplication.class, args); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /demospringssl/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.ssl.key-store: keystore.p12 2 | server.ssl.key-store-password: 123456 3 | server.ssl.keyStoreType: PKCS12 4 | server.ssl.keyAlias: tomcat 5 | -------------------------------------------------------------------------------- /demospringssl/src/test/java/me/whiteship/demo/DemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demo; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class DemoApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /docker-cmds.md: -------------------------------------------------------------------------------- 1 | 2 | ### 컨테이너 상세 정보 보기 3 | 4 | ``` 5 | docker inspect sharp_bartik 6 | ``` 7 | 8 | ### 컨테이너 아이디 가져오기 9 | 10 | ``` 11 | CID=$(docker run -d monolith:1.0.0) 12 | ``` 13 | 14 | ### 컨테이너 아이피 가져오기 15 | 16 | ``` 17 | CID=$(docker run -d monolith:1.0.0) 18 | CIP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${CID}) 19 | ``` 20 | 21 | ### 컨테이너 전부 멈추기 22 | 23 | ``` 24 | stop $(sudo docker ps -aq) 25 | ``` 26 | 27 | ### 컨테이너 전부 삭제 28 | 29 | ``` 30 | docker rm $(sudo docker ps -aq) 31 | ``` 32 | 33 | ### MySQL 34 | 35 | ``` 36 | docker run -p 3306:3306 --name mysql_boot -e MYSQL_ROOT_PASSWORD=1 -e MYSQL_DATABASE=springboot -e MYSQL_USER=keesun -e MYSQL_PASSWORD=pass -d mysql 37 | 38 | docker exec -i -t mysql_boot bash 39 | 40 | mysql -u root -p 41 | ``` 42 | -------------------------------------------------------------------------------- /doker-getting-started.md: -------------------------------------------------------------------------------- 1 | # 도커 시작하기 2 | 3 | ## 참고 4 | 5 | [도커 시작하기](https://docs.docker.com/get-started/) 6 | [유툽/백기선/도커](https://www.youtube.com/playlist?list=PLfI752FpVCS84hxOeCyI4SBPUwt4Itd0T) 7 | 8 | ## 유툽 인덱스 9 | 10 | [![도커 시작하기 1](https://img.youtube.com/vi/9tW0QSsrhwc/0.jpg)](https://youtu.be/9tW0QSsrhwc) 11 | 12 | 파트 1에서는 도커에 대한 기본적인 설명과 특징을 살펴봤습니다. 13 | 14 | 파트 2에서는 Dockerfile을 사용해서 간단한 파이썬 이미지를 만들었고, docker run을 사용해서 컨테이너를 실행하고, 이미지를 태깅하고, 도커 레지스트리에 등록하는 것까지 따라해 보았습니다. 15 | 16 | [![도커 시작하기 2](https://img.youtube.com/vi/p58k2_HMWRM/0.jpg)](https://youtu.be/p58k2_HMWRM) 17 | 18 | 오늘은 시작하기 파트3 과 파트4를 봤습니다. 도커의 서비스라는 개념을 살펴봤으며 docker-compose.yml 파일을 사용해서 간단히 서비스를 정의하고 실행해 봤습니다. 19 | 20 | 파트3에서 실행할 때는 로컬 머신에서 도커 스왐을 만들고 docker stack deploy를 사용했는데, 파트 4에서는 VM을 두개 만들어서 본격적으로 스왐을 구성합니다. 21 | 22 | myvm1은 스왐 매니저로 만들고 myvm2는 워커로 만들었습니다. 스왐 매니저만 도커 명령을 실행할 수 있기 때문에 매번 docker-machine ssh myvm1 "docker ..." 이런식으로 도커를 실행해야 하는 번거로움이 있는데, docker-machine env 를 사용해서 그런 수고를 덜 수 있는 방법을 살펴봤습니다. (그런데 간혹 로컬에 있는 도커랑 햇갈릴듯..) 23 | 24 | 혹시나 맥에서 VirtualBox 설치할 때 문제 있으신 분들은 아래 링크 참고하셔서 해결하시기 바랍니다. 혹은 제 영상 보시면 간략히 설명해 드렸으니까 참고하세요. 25 | 26 | https://stackoverflow.com/questions/46546192/virtualbox-not-installing-on-high-sierra 27 | 28 | [![도커 시작하기 3](https://img.youtube.com/vi/X--WPFfSbFc/0.jpg)](https://youtu.be/X--WPFfSbFc) 29 | 30 | 마지막은 이전까지 만들었던 docker-compose.yml에 서비스를 두개 더 추가하고 AWS나 Azure에 배포하는 방법을 소개하고 있습니다. 31 | 32 | 기존 서비스와는 독립적인 서비스 (컨테이너 배포 상황을 보여주는 이미지)와 기존 서비스가 사용하는 서비스 (레디스)를 각각 추가해서 동작하는걸 데모로 해봤습니다. 중간에 맥 쓰시는 분들은 아마도 저처럼 문제(비주얼라이저 컨테이너가 잘 안뜨는)가 생길 수 있을텐데요. 영상에서 해결했사오니 궁금하신 분들은 확인해 보세요. 33 | 34 | 아쉽게도 6부 AWS나 Azure에 배포하는건 따라해보지 못했습니다. 차마 그쪽까지 세팅할만큼 부지런하질 못해서... 대신 개념적으로만 살펴봤는데요. 결국엔 AWS든 Azure든 도커 스왐으로 묶어서 docker-compose.yml 파일을 배포하는건 똑같더라구요. 대신 손만 조금 더 많이 가게 생겼더군요. 35 | 36 | 각 서비스 계정 열어야지, 도커 인스턴스 만들어야지, 스왐으로 연결 해야지, 컨테이너 관련된 포트를 열어줘야지.. @_@.. 37 | 38 | -------------------------------------------------------------------------------- /effective-java-3rd/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /effective-java-3rd/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/effective-java-3rd/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /effective-java-3rd/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip 2 | -------------------------------------------------------------------------------- /effective-java-3rd/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | me.whiteship 7 | effective-java-3rd 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | effective-java-3rd 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.1.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 10 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-test 36 | test 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-maven-plugin 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-compiler-plugin 54 | 55 | ${java.version} 56 | ${java.version} 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/EffectiveJava3rdApplication.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class EffectiveJava3rdApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(EffectiveJava3rdApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item01/Foo.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item01; 2 | 3 | import java.util.EnumSet; 4 | 5 | import static me.whiteship.effectivejava3rd.item01.Foo.Color.BLUE; 6 | import static me.whiteship.effectivejava3rd.item01.Foo.Color.RED; 7 | import static me.whiteship.effectivejava3rd.item01.Foo.Color.WHITE; 8 | 9 | public class Foo { 10 | 11 | String name; 12 | 13 | String address; 14 | 15 | public Foo() { 16 | } 17 | 18 | private static final Foo GOOD_NIGHT = new Foo(); 19 | 20 | public Foo(String name) { 21 | this.name = name; 22 | } 23 | 24 | public static Foo withName(String name) { 25 | return new Foo(name); 26 | } 27 | 28 | public static Foo withAddress(String address) { 29 | Foo foo = new Foo(); 30 | foo.address = address; 31 | return foo; 32 | } 33 | 34 | public static Foo getFoo() { 35 | return GOOD_NIGHT; 36 | } 37 | 38 | public static Foo getFoo(boolean flag) { 39 | Foo foo = new Foo(); 40 | 41 | // TODO 어떤 특정 약속 되어 있는 텍스트 파일에서 Foo의 구현체의 FQCN 을 읽어온다. 42 | // TODO FQCN 에 해당하는 인스턴스를 생성한다. 43 | // TODO foo 변수를 해당 인스턴스를 가리키도록 수정한다. 44 | 45 | return foo; 46 | } 47 | 48 | public static void main(String[] args) { 49 | Foo foo = new Foo("keesun"); 50 | 51 | Foo foo1 = Foo.withName("keesun"); 52 | 53 | Foo foo2 = Foo.getFoo(); 54 | 55 | Foo foo3 = Foo.getFoo(false); 56 | 57 | EnumSet colors = EnumSet.allOf(Color.class); 58 | 59 | EnumSet blueAndWhite = EnumSet.of(BLUE, WHITE); 60 | } 61 | 62 | enum Color { 63 | RED, BLUE, WHITE 64 | } 65 | 66 | // private static method가 필요한 이유 67 | public static void doSomething() { 68 | // TODO 청소를 한다. 69 | // TODO 애들이랑 놀아준다. 70 | // TODO 저녁 약속에 간다. 71 | 게임을하고잔다(); 72 | } 73 | 74 | public static void doSomethingTomorrow() { 75 | // TODO 애들 데리고 수영장에 간다. 76 | // TODO 밥을 먹는다. 77 | 게임을하고잔다(); 78 | } 79 | 80 | private static void 게임을하고잔다() { 81 | // TODO 게임을 한다. 82 | // TODO 잔다. 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item01/FooInterface.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item01; 2 | 3 | public interface FooInterface { 4 | 5 | public static Foo getFoo() { 6 | return new Foo(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item01/MyFoo.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item01; 2 | 3 | public class MyFoo extends Foo { 4 | } 5 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item02/Calzone.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item02; 2 | 3 | public class Calzone extends Pizza { 4 | 5 | private final boolean sauceInside; 6 | 7 | public static class Builder extends Pizza.Builder { 8 | private boolean sauceInside = false; 9 | 10 | public Builder sauceInde() { 11 | sauceInside = true; 12 | return this; 13 | } 14 | 15 | @Override 16 | public Calzone build() { 17 | return new Calzone(this); 18 | } 19 | 20 | @Override 21 | protected Builder self() { 22 | return this; 23 | } 24 | } 25 | 26 | private Calzone(Builder builder) { 27 | super(builder); 28 | sauceInside = builder.sauceInside; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item02/NutritionFacts.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item02; 2 | 3 | //import lombok.Builder; 4 | //import lombok.Singular; 5 | // 6 | //import java.util.List; 7 | 8 | //@Builder 9 | public class NutritionFacts { 10 | 11 | // @Builder.Default private int servingSize = 10; 12 | private int sodium; 13 | private int carbohydrate; 14 | private int servings; 15 | // @Singular private List names; 16 | 17 | public static void main(String[] args) { 18 | // NutritionFacts nutritionFacts = NutritionFacts.builder() 19 | // .servings(10) 20 | // .carbohydrate(100) 21 | // .name("keesun") 22 | // .clearNames() 23 | // .build(); 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item02/NyPizza.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item02; 2 | 3 | import java.util.Objects; 4 | 5 | public class NyPizza extends Pizza { 6 | 7 | public enum Size { 8 | SMALL, MEDIUM, LARGE 9 | } 10 | 11 | private final Size size; 12 | 13 | public static class Builder extends Pizza.Builder { 14 | private final Size size; 15 | 16 | public Builder(Size size) { 17 | this.size = Objects.requireNonNull(size); 18 | } 19 | 20 | @Override 21 | public NyPizza build() { 22 | return new NyPizza(this); 23 | } 24 | 25 | @Override 26 | protected Builder self() { 27 | return this; 28 | } 29 | } 30 | 31 | private NyPizza(Builder builder) { 32 | super(builder); 33 | size = builder.size; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item02/Pizza.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item02; 2 | 3 | import java.util.EnumSet; 4 | import java.util.Objects; 5 | 6 | public abstract class Pizza { 7 | 8 | public enum Topping { 9 | HAM, MUSHROOM, ONION 10 | } 11 | 12 | final EnumSet toppings; 13 | 14 | abstract static class Builder> { 15 | EnumSet toppings = EnumSet.noneOf(Topping.class); 16 | 17 | public T addTopping(Topping topping) { 18 | toppings.add(Objects.requireNonNull(topping)); 19 | return self(); 20 | } 21 | 22 | abstract Pizza build(); 23 | 24 | protected abstract T self(); 25 | } 26 | 27 | Pizza(Builder builder) { 28 | toppings = builder.toppings; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item02/PizzaClient.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item02; 2 | 3 | public class PizzaClient { 4 | 5 | public static void main(String[] args) { 6 | NyPizza nyPizza = new NyPizza.Builder(NyPizza.Size.MEDIUM) 7 | .addTopping(Pizza.Topping.HAM) 8 | .addTopping(Pizza.Topping.ONION) 9 | .build(); 10 | 11 | Calzone calzone = new Calzone.Builder() 12 | .addTopping(Pizza.Topping.ONION) 13 | .sauceInde() 14 | .build(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item03/Config.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item03; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ComponentScan(basePackageClasses = Config.class) 8 | public class Config { 9 | } 10 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item03/SingleTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item03; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 5 | 6 | public class SingleTest { 7 | 8 | public static void main(String[] args) throws NoSuchMethodException { 9 | ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class); 10 | UserService userService1 = applicationContext.getBean(UserService.class); 11 | UserService userService2 = applicationContext.getBean(UserService.class); 12 | 13 | System.out.println(userService1 == userService2); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item03/Singleton1.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item03; 2 | 3 | public class Singleton1 { 4 | 5 | public static final Singleton1 instance = new Singleton1(); 6 | 7 | int count; 8 | 9 | private Singleton1() { 10 | count++; 11 | if (count != 1) { 12 | throw new IllegalStateException("this object should be singleton"); 13 | } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item03/Singleton2.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item03; 2 | 3 | import java.io.Serializable; 4 | 5 | public class Singleton2 implements Serializable { 6 | 7 | private static final Singleton2 instance = new Singleton2(); 8 | 9 | private Singleton2() { 10 | 11 | } 12 | 13 | public static Singleton2 getInstance() { 14 | return instance; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item03/Singleton3.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item03; 2 | 3 | import java.io.Serializable; 4 | 5 | public enum Singleton3 { 6 | 7 | INSTANCE; 8 | 9 | public String getName() { 10 | return "keesun"; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item03/UserRepository.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item03; 2 | 3 | import org.springframework.stereotype.Repository; 4 | 5 | @Repository 6 | public class UserRepository { 7 | } 8 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item03/UserService.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item03; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Scope; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.io.Serializable; 8 | 9 | @Service 10 | public class UserService implements Serializable { 11 | 12 | @Autowired 13 | UserRepository userRepository; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item04/UtilClass.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item04; 2 | 3 | public abstract class UtilClass { 4 | 5 | public static String getName() { 6 | return "keesun"; 7 | } 8 | 9 | public static void main(String[] args) { 10 | 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item05/usecase1/SpellChecker.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item05.usecase1; 2 | 3 | import java.util.List; 4 | 5 | public class SpellChecker { 6 | 7 | private static final Lexicon dictionary = new KoreanDicationry(); 8 | 9 | private SpellChecker() { 10 | // Noninstantiable 11 | } 12 | 13 | public static boolean isValid(String word) { 14 | throw new UnsupportedOperationException(); 15 | } 16 | 17 | public static List suggestions(String typo) { 18 | throw new UnsupportedOperationException(); 19 | } 20 | 21 | public static void main(String[] args) { 22 | SpellChecker.isValid("hello"); 23 | } 24 | } 25 | 26 | 27 | interface Lexicon {} 28 | 29 | class KoreanDicationry implements Lexicon {} 30 | 31 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item05/usecase2/SpellChecker.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item05.usecase2; 2 | 3 | import java.util.List; 4 | 5 | public class SpellChecker { 6 | 7 | private final Lexicon dictionary = new KoreanDicationry(); 8 | 9 | private SpellChecker() { 10 | } 11 | 12 | public static final SpellChecker INSTANCE = new SpellChecker() { 13 | }; 14 | 15 | public boolean isValid(String word) { 16 | throw new UnsupportedOperationException(); 17 | } 18 | 19 | 20 | public List suggestions(String typo) { 21 | throw new UnsupportedOperationException(); 22 | } 23 | 24 | public static void main(String[] args) { 25 | SpellChecker.INSTANCE.isValid("hello"); 26 | } 27 | 28 | } 29 | 30 | 31 | interface Lexicon {} 32 | 33 | class KoreanDicationry implements Lexicon {} 34 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item05/usecase3/SpellChecker.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item05.usecase3; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | import java.util.function.Supplier; 6 | 7 | public class SpellChecker { 8 | 9 | private final Lexicon dictionary; 10 | 11 | public SpellChecker(Supplier dictionary) { 12 | this.dictionary = Objects.requireNonNull(dictionary.get()); 13 | } 14 | 15 | public boolean isValid(String word) { 16 | throw new UnsupportedOperationException(); 17 | } 18 | 19 | public List suggestions(String typo) { 20 | throw new UnsupportedOperationException(); 21 | } 22 | 23 | public static void main(String[] args) { 24 | Lexicon lexicon = new TestDictionary(); 25 | SpellChecker spellChecker = new SpellChecker(() -> lexicon); 26 | spellChecker.isValid("hello"); 27 | } 28 | 29 | } 30 | 31 | interface Lexicon {} 32 | 33 | class KoreanDictionary implements Lexicon {} 34 | 35 | class TestDictionary implements Lexicon {} -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item05/usercase4/Config.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item05.usercase4; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ComponentScan(basePackageClasses = Config.class) 8 | public class Config { 9 | } 10 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item05/usercase4/KoreanDictionary.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item05.usercase4; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class KoreanDictionary implements Lexicon { 7 | 8 | @Override 9 | public void print() { 10 | System.out.println("Korean"); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item05/usercase4/Lexicon.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item05.usercase4; 2 | 3 | public interface Lexicon { 4 | void print(); 5 | } 6 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item05/usercase4/SpellChecker.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item05.usercase4; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.List; 6 | 7 | @Component 8 | public class SpellChecker { 9 | 10 | private Lexicon lexicon; 11 | 12 | public SpellChecker(Lexicon lexicon) { 13 | this.lexicon = lexicon; 14 | } 15 | 16 | public boolean isValid(String word) { 17 | lexicon.print(); 18 | return true; 19 | } 20 | 21 | public List suggestions(String typo) { 22 | throw new UnsupportedOperationException(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item05/usercase4/SpellCheckerClient.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item05.usercase4; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 5 | 6 | public class SpellCheckerClient { 7 | 8 | public static void main(String[] args) { 9 | ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class); 10 | SpellChecker spellChecker = applicationContext.getBean(SpellChecker.class); 11 | spellChecker.isValid("hello"); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item06/autoboxing/AutoBoxingExample.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item06.autoboxing; 2 | 3 | public class AutoBoxingExample { 4 | 5 | public static void main(String[] args) { 6 | long start = System.currentTimeMillis(); 7 | long sum = 0l; 8 | for (long i = 0 ; i <= Integer.MAX_VALUE ; i++) { 9 | sum += i; 10 | } 11 | System.out.println(sum); 12 | System.out.println(System.currentTimeMillis() - start); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item06/expensiveobject01/RomanNumber.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item06.expensiveobject01; 2 | 3 | public class RomanNumber { 4 | 5 | static boolean isRomanNumeral(String s) { 6 | return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item06/expensiveobject02/RomanNumber.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item06.expensiveobject02; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class RomanNumber { 6 | 7 | private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 8 | 9 | static boolean isRomanNumeral(String s) { 10 | return ROMAN.matcher(s).matches(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item06/map/UsingKeySet.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item06.map; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | public class UsingKeySet { 8 | 9 | public static void main(String[] args) { 10 | Map menu = new HashMap<>(); 11 | menu.put("Burger", 8); 12 | menu.put("Pizza", 9); 13 | 14 | Set names1 = menu.keySet(); 15 | Set names2 = menu.keySet(); 16 | 17 | names1.remove("Burger"); 18 | System.out.println(names2.size()); // 1 19 | System.out.println(menu.size()); // 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item06/strings/StringTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item06.strings; 2 | 3 | public class StringTest { 4 | 5 | public static void main(String[] args) { 6 | Boolean true1 = Boolean.valueOf("true"); 7 | Boolean true2 = Boolean.valueOf("true"); 8 | 9 | System.out.println(true1 == true2); 10 | System.out.println(true1 == Boolean.TRUE); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item07/cache/CacheSample.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item07.cache; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.WeakHashMap; 7 | 8 | public class CacheSample { 9 | 10 | public static void main(String[] args) { 11 | Object key1 = new Object(); 12 | Object value1 = new Object(); 13 | 14 | Map cache = new WeakHashMap<>(); 15 | cache.put(key1, value1); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item07/memoryleak/Stack.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item07.memoryleak; 2 | 3 | import java.util.Arrays; 4 | import java.util.EmptyStackException; 5 | 6 | // Can you spot the "memory leak"? 7 | public class Stack { 8 | 9 | private Object[] elements; 10 | 11 | private int size = 0; 12 | 13 | private static final int DEFAULT_INITIAL_CAPACITY = 16; 14 | 15 | public Stack() { 16 | this.elements = new Object[DEFAULT_INITIAL_CAPACITY]; 17 | } 18 | 19 | public void push(Object e) { 20 | this.ensureCapacity(); 21 | this.elements[size++] = e; 22 | } 23 | 24 | public Object pop() { 25 | if (size == 0) { 26 | throw new EmptyStackException(); 27 | } 28 | 29 | Object result = this.elements[--size]; 30 | this.elements[size] = null; 31 | return result; 32 | } 33 | 34 | /** 35 | * Ensure space for at least one more element, 36 | * roughly doubling the capacity each time the array needs to grow. 37 | */ 38 | private void ensureCapacity() { 39 | if (this.elements.length == size) { 40 | this.elements = Arrays.copyOf(elements, 2 * size + 1); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item07/package-info.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item07; -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item08/FinalizerExample.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item08; 2 | 3 | public class FinalizerExample { 4 | 5 | @Override 6 | protected final void finalize() throws Throwable { 7 | System.out.println("Clean up"); 8 | } 9 | 10 | public void hello() { 11 | System.out.println("hi"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item08/SampleResource.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item08; 2 | 3 | import java.lang.ref.Cleaner; 4 | 5 | public class SampleResource implements AutoCloseable { 6 | 7 | private boolean closed; 8 | 9 | private static final Cleaner CLEANER = Cleaner.create(); 10 | 11 | private final Cleaner.Cleanable cleanable; 12 | 13 | private final ResourceCleaner resourceCleaner; 14 | 15 | public SampleResource() { 16 | this.resourceCleaner = new ResourceCleaner(); 17 | this.cleanable = CLEANER.register(this, resourceCleaner); 18 | } 19 | 20 | private static class ResourceCleaner implements Runnable { 21 | 22 | @Override 23 | public void run() { 24 | System.out.println("Clean"); 25 | } 26 | } 27 | 28 | @Override 29 | public void close() throws RuntimeException { 30 | if (this.closed) { 31 | throw new IllegalStateException(); 32 | } 33 | 34 | closed = true; 35 | cleanable.clean(); 36 | } 37 | 38 | public void hello() { 39 | System.out.println("hello"); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item08/SampleRunner.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item08; 2 | 3 | public class SampleRunner { 4 | 5 | public static void main(String[] args) { 6 | try(var sampleResource = new SampleResource()) { 7 | sampleResource.hello(); 8 | } 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item09/AppRunner.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item09; 2 | 3 | public class AppRunner { 4 | 5 | public static void main(String[] args) { 6 | MyResource myResource = new MyResource(); 7 | try { 8 | myResource.doSomething(); 9 | } catch (FirstError error) { 10 | System.out.println("first error"); 11 | throw error; 12 | } finally { 13 | myResource.close(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item09/FirstError.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item09; 2 | 3 | public class FirstError extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item09/MyResource.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item09; 2 | 3 | public class MyResource implements AutoCloseable { 4 | 5 | public void doSomething() { 6 | System.out.println("Do something"); 7 | throw new FirstError(); 8 | } 9 | 10 | @Override 11 | public void close() { 12 | System.out.println("Close My Resource"); 13 | throw new SecondError(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/java/me/whiteship/effectivejava3rd/item09/SecondError.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item09; 2 | 3 | public class SecondError extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /effective-java-3rd/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/effective-java-3rd/src/main/resources/application.properties -------------------------------------------------------------------------------- /effective-java-3rd/src/test/java/me/whiteship/effectivejava3rd/EffectiveJava3rdApplicationTests.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class EffectiveJava3rdApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /effective-java-3rd/src/test/java/me/whiteship/effectivejava3rd/item06/expensiveobject01/RomanNumberTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item06.expensiveobject01; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Java6Assertions.assertThat; 6 | 7 | public class RomanNumberTest { 8 | 9 | @Test 10 | public void isRomanNumeral() { 11 | assertThat(RomanNumber.isRomanNumeral("IV")).isTrue(); 12 | assertThat(RomanNumber.isRomanNumeral("XX")).isTrue(); 13 | assertThat(RomanNumber.isRomanNumeral("IIIIV")).isFalse(); 14 | } 15 | 16 | 17 | } -------------------------------------------------------------------------------- /effective-java-3rd/src/test/java/me/whiteship/effectivejava3rd/item06/expensiveobject02/RomanNumberTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.effectivejava3rd.item06.expensiveobject02; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Java6Assertions.assertThat; 6 | 7 | public class RomanNumberTest { 8 | 9 | @Test 10 | public void isRomanNumeral() { 11 | assertThat(RomanNumber.isRomanNumeral("IV")).isTrue(); 12 | assertThat(RomanNumber.isRomanNumeral("XX")).isTrue(); 13 | assertThat(RomanNumber.isRomanNumeral("IIIIV")).isFalse(); 14 | } 15 | 16 | 17 | } -------------------------------------------------------------------------------- /effective-java/Readme.md: -------------------------------------------------------------------------------- 1 | # 이팩티브 자바 3판 요약 2 | 3 | http://www.informit.com/store/effective-java-9780134685991 4 | 5 | *다음의 내용은 이팩티브 자바 3판을 읽고 본인이 주관적으로 정리한 내용입니다. 많은 내용을 생략하거나 혹은 잘못 이해한 부분도 있을 수 있사오니 제대로 학습하고 싶으신 분들이라면 반드시 위의 책을 읽을 것을 권장합니다.* 6 | 7 | 본문에 고치고 싶은 부분이 있다면 이슈를 올려주시거나, 풀리퀘를 보내주세요. 다만.. 새로운 내용은 추가는 저만 할 겁니다. 8 | 9 | 감사합니다. 10 | 11 | --- 12 | 13 | ## 1부 객체 만들고 없애기 14 | * [아이템 1: 생성자 대신 static 팩토리 메소드를 고려해 볼 것](item1.md) 15 | * [아이템 2: 생성자 매개변수가 많은 경우에 빌더 사용을 고려해 볼 것](item2.md) 16 | * [아이템 3: private 생성자 또는 enum 타입을 사용해서 싱글톤으로 만들 것](item3.md) 17 | * [아이템 4: private 생성자로 noninstantiability를 강제할 것](item4.md) 18 | * [아이템 5: 리소스를 엮을 때는 의존성 주입을 선호하라](item5.md) 19 | * [아이템 6: 불필요한 객체를 만들지 말자](item6.md) 20 | * [아이템 7: 더이상 쓰지 않는 객체 레퍼런스는 없애자](item7.md) 21 | * [아이템 8: Finalizer와 Cleaner는 사용하지 말 것](item8.md) 22 | * [아이템 9: Try-Finally 대신 Try-with-Resource 사용하라](item9.md) 23 | -------------------------------------------------------------------------------- /effective-java/item1.md: -------------------------------------------------------------------------------- 1 | # 아이템 1. 생성자 대신 static 팩토리 메소드를 고려해 볼 것 2 | 3 | [![생성자 대신 static 팩토리 메소드를 고려해 볼 것](https://img.youtube.com/vi/X7RXP6EI-5E/0.jpg)](https://youtu.be/X7RXP6EI-5E) 4 | 5 | ```java 6 | public static Boolean valueOf(boolean b) { 7 | return b ? Boolean.TRUE : Boolean.FALSE; 8 | } 9 | ``` 10 | 11 | public 생성자를 사용해서 객체를 생성하는 전통적인 방법 말고, 이렇게 public static 팩토리 메소드를 사용해서 해당 클래스의 인스턴스를 만드는 방법도 있다. 12 | 13 | 이런 방법에는 각각 장단점이 있는데 다음과 같다. 14 | 15 | ## 장점 1: 이름을 가질 수 있다. 16 | 17 | 생성자에 제공하는 파라메터가 거기서 반환하는 객체를 잘 설명하지 못할 경우에, 잘 만든 이름을 가진 static 팩토리를 사용하는 것이 사용하기 보다 더 쉽고 (해당 팩토리 메소드의 클라이언트 코드를) 읽기 편한다. 그 예로 `BigInteger.probblePrime`을 들고 있다. 18 | 19 | 또, 생성자는 시그니처에 제약이 있다. 똑같은 타입을 파라미터로 받는 생성자 두개를 만들 수 없으니까 그런 경우에도 public static 팩토리 메소드를 사용하는것이 유용하다. 20 | 21 | ## 장점 2: 반드시 새로운 객체를 만들 필요가 없다. 22 | 23 | 불변(immutable) 클래스([아이템 17](item17.md))인 경우나 매번 새로운 객체를 만들 필요가 없는 경우에 미리 만들어둔 인스턴스 또는 캐시해둔 인스턴스를 반환할 수 있다. `Boolean.valueOf(boolean)` 메소드도 그 경우에 해당한다. 24 | 25 | ## 장점 3: 리턴 타입의 하위 타입 인스턴스를 만들 수도 있다. 26 | 27 | 클래스에서 만들어 줄 객체의 클래스를 선택하는 유연함이 있다. 리턴 타입의 하위 타입의 인스턴스를 만들어줘도 되니까, 리턴 타입은 인터페이스로 지정하고 그 인터페이스의 구현체는 API로 노출 시키지 않지만 그 구현체의 인스턴스를 만들어 줄 수 있다는 말이다. `java.util.Collections`가 그 예에 해당한다. 28 | 29 | `java.util.Collections`는 45개에 달하는 인터페이스의 구현체의 인스턴스를 제공하지만 그 구현체들은 전부 non-public이다. 즉 인터페이스 뒤에 감쳐줘 있고 그럼으로서 public으로 제공해야 할 API를 줄였을 뿐 아니라 개념적인 무게(conceptual weight)까지 줄일 수 있었다. 30 | 31 | 여기서 개념적인 무게란, 프로그래머가 어떤 인터페이스가 제공하는 API를 사용할 때 알아야 할 개념의 개수와 난이도를 말한다. 32 | 33 | 그러한 팩토리를 사용하는 코드가 구현체가 아닌 인터페이스 타입으로 코딩하게 되는건 덤으로 좋은 일이다. 34 | 35 | 자바 8부터 인터페이스에 public static 메소드를 추가할 수 있게 되었지만 private static 메소드는 자바 9부터 추가할 수 있다. 따라서 자바 8부터 인터페이스에 public static 메소드를 사용해서 그 인터페이스의 구현체를 메소드를 제공할 수도 있지만 private static 메소드가 필요한 경우, 자바 9가 아니면 기존처럼 별도의 (인스턴스를 만들 수 없는, `java.util.Collections` 같은) 클래스를 사용해야 할 수도 있다. 36 | 37 | ## 장점 4: 리턴하는 객체의 클래스가 입력 매개변수에 따라 매번 다를 수 있다. 38 | 39 | 장점 3과 같은 이유로 객체의 타입은 다를 수 있다. `EnumSet` 클래스 ([아이템 36](item36.md))는 생성자 없이 public static 메소드, `allOf()`, `of()` 등을 제공한다. 그 안에서 리턴하는 객체의 타입은 enum 타입의 개수에 따라 `RegularEnumSet` 또는 `JumboEnumSet`으로 달라진다. 40 | 41 | 그런 객체 타입은 노출하지 않고 감춰져 있기 때문에 추후에 JDK의 변화에 따라 새로운 타입을 만들거나 기존 타입을 없애도 문제가 되지 않는다. 42 | 43 | ## 장점 5: 리턴하는 객체의 클래스가 public static 팩토리 메소드를 작성할 시점에 반드시 존재하지 않아도 된다. 44 | 45 | 장점 3, 4와 비슷한 개념이다. 이러한 유연성을 제공하는 static 팩토리 메소드는 `서비스 프로바이더` 프레임워크의 근본이다. `JDBC`를 예로 들고 있다. 46 | 47 | `서비스 프로바이더` 프레임워크는 서비스의 구현체를 대표하는 `서비스 인터페이스`와 구현체를 등록하는데 사용하는 `프로바이더 등록 API` 그리고 클라이언트가 해당 서비스의 인스턴스를 가져갈 때 사용하는 `서비스 엑세스 API`가 필수로 필요하다. 부가적으로, 서비스 인터페이스의 인스턴스를 제공하는 `서비스 프로바이더 인터페이스`를 만들 수도 있는데, 그게 없는 경우에는 리플랙션을 사용해서 구현체를 만들어 준다. 48 | 49 | `JDBC`의 경우, `DriverManager.registerDriver()`가 `프로바이더 등록 API`. `DriverManager.getConnection()`이 `서비스 엑세스 API`. 그리고 `Driver`가 `서비스 프로바이더 인터페이스` 역할을 한다. 50 | 51 | 자바 6부터는 `java.util.ServiceLoader`라는 일반적인 용도의 서비스 프로바이더를 제공하지만, `JDBC`가 그 보다 이전에 만들어졌기 때문에 `JDBC`는 `ServiceLoader`를 사용하진 않는다. 52 | 53 | ## 단점 1: public 또는 protected 생성자 없이 static public 메소드만 제공하는 클래스는 상속할 수 없다. 54 | 55 | 따라서, `Collections 프레임워크`에서 제공하는 편의성 구현체(`java.util.Collections`)는 상속할 수 없다. 오히려 불변 타입([아이템 17](item17.md))인 경우나 상속 대신 컴포지션을 권장([아이템 18](item18.md))하기 때문에 장점이라 말할지도 모르겠다. 56 | 57 | ## 단점 2: 프로그래머가 static 팩토리 메소드를 찾는게 어렵다. 58 | 59 | 생성자는 Javadoc 상단에 모아서 보여주지만 static 팩토리 메소드는 API 문서에서 특별히 다뤄주지 않는다. 따라서 클래스나 인터페이스 문서 상단에 팩토리 메소드에 대한 문서를 제공하는 것이 좋겠다. 60 | 61 | ## 참고 62 | 63 | * [ServiceLoader](https://docs.oracle.com/javase/9/docs/api/java/util/ServiceLoader.html) -------------------------------------------------------------------------------- /effective-java/item3.md: -------------------------------------------------------------------------------- 1 | # 아이템 3: private 생성자 또는 enum 타입을 사용해서 싱글톤으로 만들 것 2 | 3 | [![private 생성자 또는 enum 타입을 사용해서 싱글톤으로 만들 것](https://img.youtube.com/vi/xBVPChbtUhM/0.jpg)](https://youtu.be/xBVPChbtUhM) 4 | 5 | 오직 한 인스턴스만 만드는 클래스를 *싱글톤*이라 부른다. 보통 함수 같은 Stateless 객체([아이템 24](item24.md)) 또는 본질적으로 유일한 시스템 컴포넌트를 그렇게 만든다. 6 | 7 | **싱글톤을 사용하는 클라이언트 코드를 테스트 하는게 어렵다.** 싱글톤이 인터페이스를 구현한게 아니라면 mock으로 교체하는게 어렵기 때문이다. 8 | 9 | 싱글톤으로 만드는 두가지 방법이 있는데, 두 방법 모두 생성자를 private 으로 만들고 publis static 멤버를 사용해서 유일한 인스턴스를 제공한다. 10 | 11 | ## final 필드 12 | 13 | 첫 번째 방법은 final 필드로 제공한다. 14 | 15 | ```java 16 | public class Elvis { 17 | 18 | public static final Elvis INSTANCE = new Elvis(); 19 | 20 | private Elvis() { 21 | } 22 | 23 | } 24 | ``` 25 | 26 | 리플렉션을 사용해서 private 생성자를 호출하는 방법을 제외하면 (그 방법을 막고자 생성자 안에서 카운팅하거나 flag를 이용해서 예외를 던지게 할 수도 있지만) 생성자는 오직 최초 한번만 호출되고 Elvis는 싱글톤이 된다. 27 | 28 | ### 장점 29 | 30 | 이런 API 사용이 static 팩토리 메소드를 사용하는 방법에 비해 더 명확하고 더 간단하다. 31 | 32 | ## static 팩토리 메소드 33 | 34 | ```java 35 | public class Elvis { 36 | 37 | private static final Elvis INSTANCE = new Elvis(); 38 | 39 | private Elvis() { 40 | } 41 | 42 | public static Elvis getInstance() { 43 | return INSTANCE; 44 | } 45 | 46 | } 47 | ``` 48 | 49 | ### 장점 50 | 51 | API를 변경하지 않고로 싱글톤으로 쓸지 안쓸지 변경할 수 있다. 처음엔 싱글톤으로 쓰다가 나중엔 쓰레드당 새 인스턴스를 만든다는 등 클라이언트 코드를 고치지 않고도 변경할 수 있다. 52 | 53 | 필요하다면 `Generic 싱글톤 팩토리`([아이템 30](item30.md))를 만들 수도 있다. 54 | 55 | static 팩토리 메소드를 `Supplier`에 대한 `메소드 레퍼런스`로 사용할 수도 있다. 56 | 57 | ## 직렬화 (Serialization) 58 | 59 | 위에서 살펴본 두 방법 모두, 직렬화에 사용한다면 역직렬화 할 때 같은 타입의 인스턴스가 여러개 생길 수 있다. 그 문제를 해결하려면 모든 인스턴스 필드에 `transient`를 추가 (직렬화 하지 않겠다는 뜻) 하고 `readResolve` 메소드를 다음과 같이 구현하면 된다. (객체 직렬화 API의 비밀 참고) 60 | 61 | ```java 62 | private Object readResolve() { 63 | return INSTANCE; 64 | } 65 | 66 | ``` 67 | 68 | ## Enum 69 | 70 | 직렬화/역직렬화 할 때 코딩으로 문제를 해결할 필요도 없고, 리플렉션으로 호출되는 문제도 고민할 필요없는 방법이 있다. 71 | 72 | ```java 73 | public enum Elvis { 74 | INSTANCE; 75 | } 76 | ``` 77 | 78 | 코드는 좀 불편하게 느껴지지만 싱글톤을 구현하는 최선의 방법이다. 하지만 이 방법은 Enum 말고 다른 상위 클래스를 상속해야 한다면 사용할 수 없다. (하지만 인터페스는 구현할 수 있다.) 79 | 80 | ## 참고 81 | 82 | * [객체 직렬화 API의 비밀](http://www.oracle.com/technetwork/articles/java/javaserial-1536170.html) 83 | -------------------------------------------------------------------------------- /effective-java/item4.md: -------------------------------------------------------------------------------- 1 | # 아이템 4: private 생성자로 noninstantiability를 강제할 것 2 | 3 | static 메서드와 static 필드를 모아둔 클래스를 만든 경우 해당 클래스를 abstract로 만들어도 인스턴스를 만드는 걸 막을 순 없다. 상속 받아서 인스턴스를 만들 수 있기 때문에. 4 | 5 | 그리고 아무런 생성자를 만들지 않은 경우 컴파일러가 기본적으로 아무 인자가 없는 pubilc 생성자를 만들어 주기 때문에 그런 경우에도 인스턴스를 만들 수 있다. 6 | 7 | 명시적으로 private 생성자를 추가해야 한다. 8 | 9 | ```java 10 | // Noninstantiable utility class 11 | public class UtilityClass { 12 | // Suppress default constructor for noninstantiability 13 | private UtilityClass() { 14 | throw new AssertionError(); 15 | } 16 | } 17 | ``` 18 | 19 | AssetionError는 꼭 필요하진 않지만, 그렇게 하면 의도치 않게 생성자를 호출한 경우에 에러를 발생시킬 수 있고, private 생성자기 때문에 상속도 막을 수 있다. 20 | 21 | 생성자를 제공하지만 쓸 수 없기 때문에 직관에 어긋나는 점이 있는데, 그 때문에 위에 코드처럼 주석을 추가하는 것이 좋다. 22 | 23 | 부가적으로 상속도 막을 수 있다. 상속한 경우에 명시적이든 암묵적이든 상위 클래스의 생성자를 호출해야 하는데, 이 클래스의 생성자가 private이라 호출이 막혔기 떄문에 상속을 할 수 없다. 24 | -------------------------------------------------------------------------------- /effective-java/item5.md: -------------------------------------------------------------------------------- 1 | # 아이템 5: 리소스를 엮을 때는 의존성 주입을 선호하라 2 | 3 | 대부분의 클래스는 여러 리소스에 의존한다. 이 책에서는 `SpellChecker`와 `Dictionary`를 예로 들고 있다. 즉, `SpellCheck`가 `Ditionary`를 사용하고, 이를 의존하는 리소스 또는 의존성이라고 부른다. 이때 `SpellChecker`를 다음과 같이 구현하는 경우가 있다. 4 | 5 | ## 부적절한 구현 6 | 7 | ### static 유틸 클래스 ([아이템4](item4.md)) 8 | 9 | ```java 10 | // 부적절한 static 유틸리티 사용 예 - 유연하지 않고 테스트 할 수 없다. 11 | public class SpellChecker { 12 | 13 | private static final Lexicon dictionary = new KoreanDicationry(); 14 | 15 | private SpellChecker() { 16 | // Noninstantiable 17 | } 18 | 19 | public static boolean isValid(String word) { 20 | throw new UnsupportedOperationException(); 21 | } 22 | 23 | 24 | public static List suggestions(String typo) { 25 | throw new UnsupportedOperationException(); 26 | } 27 | } 28 | 29 | interface Lexicon {} 30 | 31 | class KoreanDicationry implements Lexicon {} 32 | ``` 33 | 34 | ### 싱글톤으로 구현하기 ([아이템3](item3.md)) 35 | 36 | ```java 37 | // 부적절한 싱글톤 사용 예 - 유연하지 않고 테스트 할 수 없다. 38 | public class SpellChecker { 39 | 40 | private final Lexicon dictionary = new KoreanDicationry(); 41 | 42 | private SpellChecker() { 43 | } 44 | 45 | public static final SpellChecker INSTANCE = new SpellChecker() { 46 | }; 47 | 48 | public boolean isValid(String word) { 49 | throw new UnsupportedOperationException(); 50 | } 51 | 52 | 53 | public List suggestions(String typo) { 54 | throw new UnsupportedOperationException(); 55 | } 56 | 57 | } 58 | ``` 59 | 60 | 사전을 하나만 사용할꺼라면 위와 같은 구현도 만족스러울 수 있겠지만, 실제로는 각 언어의 맞춤법 검사기는 사용하는 사전이 각기 다르다. 또한 테스트 코드에서는 테스트용 사전을 사용하고 싶을 수도 있다. 61 | 62 | **어떤 클래스가 사용하는 리소스에 따라 행동을 달리 해야 하는 경우에는 스태틱 유틸리티 클래스와 싱글톤을 사용하는 것은 부적절하다.** 63 | 64 | 그런 요구 사항을 만족할 수 있는 간단한 패턴으로 생성자를 사용해서 새 인스턴스를 생성할 때 사용할 리소스를 넘겨주는 방법이 있다. 65 | 66 | ## 적절한 구현 67 | 68 | ```java 69 | public class SpellChecker { 70 | 71 | private final Lexicon dictionary; 72 | 73 | public SpellChecker(Lexicon dictionary) { 74 | this.dictionary = Objects.requireNonNull(dictionary); 75 | } 76 | 77 | public boolean isValid(String word) { 78 | throw new UnsupportedOperationException(); 79 | } 80 | 81 | public List suggestions(String typo) { 82 | throw new UnsupportedOperationException(); 83 | } 84 | 85 | } 86 | 87 | class Lexicon {} 88 | ``` 89 | 90 | 위와 같은 의존성 주입은 생성자, 스태틱 팩토리([아이템1](item1.md)) 그리고 빌더([아이템2](item2.md))에도 적용할 수 있다. 91 | 92 | 이 패턴의 변종으로 리소스의 팩토리를 생성자에 전달하는 방법도 있다. 이 방법은 자바 8에 들어온 `Supplier` 인터페이스가 그런 팩토리로 쓰기에 완벽하다. `Supplier`를 인자로 받는 메서드는 보통 `bounded wildcard type` ([아이템31](item31.md))으로 입력을 제한해야 한다. 93 | 94 | ```java 95 | Mosaic create(Supplier tileFactory) { ... } 96 | ``` 97 | 98 | 의존성 주입이 유연함과 테스트 용이함을 크게 향상 시켜주지만, 의존성이 많은 큰 프로젝트인 경웅에는 코드가 장황해 질 수 있다. 그점은 대거, 쥬스, 스프링 같은 프레임웍을 사용해서 해결할 수 있다. 99 | 100 | 요약하자면 의존하는 리소스에 따라 행동을 달리하는 클래스를 만들 때는 싱글톤이나 스태틱 유틸 클래스를 사용하지 말자. 그런 경우에는 리소스를 생성자나 팩토리로 전달하는 의존성 주입을 사용하여 유연함, 재사용성, 테스트 용이성을 향상 시키자. 101 | 102 | ## 참고 103 | 104 | * [팩토리 메소드 패턴](https://en.wikipedia.org/wiki/Factory_method_pattern) 105 | * [Supplier 인터페이스](https://docs.oracle.com/javase/8/docs/api/java/util/function/Supplier.html) 106 | -------------------------------------------------------------------------------- /effective-java/item9.md: -------------------------------------------------------------------------------- 1 | # Try-Finally 대신 Try-with-Resource 사용하라 2 | 3 | 자바 라이브러리에는 `InputStream`, `OutputStream` 그리고 `java.sql.Connection`과 같이 정리(close)가 가능한 리소스가 많은데, 그런 리소스를 사용하는 클라이언트 코드가 보통 리소스 정리를 잘 안하거나 잘못하는 경우가 있다. 4 | 5 | ```java 6 | public class FirstError extends RuntimeException { 7 | } 8 | ``` 9 | 10 | ```java 11 | public class SecondException extends RuntimeException { 12 | } 13 | ``` 14 | 15 | ```java 16 | public class MyResource implements AutoCloseable { 17 | 18 | public void doSomething() throws FirstError { 19 | System.out.println("doing something"); 20 | throw new FirstError(); 21 | } 22 | 23 | @Override 24 | public void close() throws SecondException { 25 | System.out.println("clean my resource"); 26 | throw new SecondException(); 27 | } 28 | } 29 | ``` 30 | 31 | ```java 32 | MyResource myResource = null; 33 | try { 34 | myResource = new MyResource(); 35 | myResource.doSomething(); 36 | } finally { 37 | if (myResource != null) { 38 | myResource.close(); 39 | } 40 | } 41 | ``` 42 | 43 | 이 코드에서 예외가 발생하면 `SecondException`이 출력되고 `FirstException`은 덮힌다. 즉 안 보인다. 그러면 문제를 디버깅하기 힘들어 진다. 또한 중복으로 try-catch를 만들어여 하는 경우에도 실수를 할 가능성이 높다. (자바 퍼즐러 88쪽 참고) 44 | 45 | 자바7에 추가된 Try-with-Resource를 사용하면 코드 가독성도 좋고, 문제를 분석할 때도 훨씬 좋다. 왜냐면 Try-Finally를 사용할 때 처럼 처음에 발생한 예외가 뒤에 발생한 에러에 덮히지 않으니까. 46 | 47 | 뒤에 발생한 에러는 첫번째 발생한 에러 뒤에다 쌓아두고(suppressed) 처음 발생한 에러를 중요시 여긴다. 그리고 `Throwable`의 `getSuppressed` 메소드를 사용해서 뒤에 쌓여있는 에러를 코딩으로 사용할 수도 있다. 48 | 49 | `catch` 블록은 Try-Fianlly와 동일하게 사용할 수 있다. 50 | 51 | # 참고 52 | * [자바 퍼즐러 88쪽](https://stackoverflow.com/questions/48449093/what-is-wrong-with-this-java-puzzlers-piece-of-code) 53 | 54 | -------------------------------------------------------------------------------- /hibernate-orm-reference-coding.md: -------------------------------------------------------------------------------- 1 | # 하이버네이트 ORM 레퍼런스 코딩 2 | 3 | ## 참고 4 | 5 | [하이버네이트 ORM 레퍼런스](http://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html) 6 | [유툽/백기선/하이버네이트ORM](https://www.youtube.com/playlist?list=PLfI752FpVCS8BIvtsPyKGY50a5vH-3xw-) 7 | 8 | ## 유툽 인덱스 9 | 10 | ### 하이버네이트 ORM Day 1. Entity 타입과 Value 타입 그리고 @Basic 11 | 12 | [![하이버네이트 ORM Day 1. Entity 타입과 Value 타입 그리고 @Basic](https://img.youtube.com/vi/Y0tUaidXRqo/0.jpg)](https://youtu.be/Y0tUaidXRqo) 13 | 14 | * Spring Boot와 Spring Data JPA 기반의 간략한 테스트용 프로젝트 (플레이 그라운드 처럼 쓰실 수 있습니다.) 15 | * http://github.com/keesun/study 16 | * 엔티티 타입과 벨류 타입이 뭐냐 뭔 차이냐.. 17 | * 베이직 타입이라는건 뭐냐.. 18 | * @Basic 애노테이션으로 설정할 수 있는 값들은 뭔지.. 19 | * Spring Data Repository를 사용해서 간단한 CRUD 테스트 작성해보기 20 | 21 | -------------------------------------------------------------------------------- /infoq.md: -------------------------------------------------------------------------------- 1 | # InfoQ 2 | 3 | ## 참고 4 | 5 | [InfoQ](https://www.infoq.com/) 6 | [유툽/백기선/InfoQ](https://www.youtube.com/playlist?list=PLfI752FpVCS8BIvtsPyKGY50a5vH-3xw-) 7 | 8 | ## 유툽 인덱스 9 | 10 | ### 오라클과 구글의 9조짜리 소송에 대해 11 | 12 | [![오라클과 구글의 9조짜리 소송에 대해](https://img.youtube.com/vi/ZFj_Mvpt5DY/0.jpg)](https://youtu.be/ZFj_Mvpt5DY) 13 | 14 | 인포큐에 올라온 기사 "Oracle Seeks $8.8 Billion in Damages from Google after Appeal"를 읽으며 개인적인 생각과 실제 코드를 보여드렸습니다. 15 | 16 | 원문은 아래에서 확인하실 수 있습니다. 17 | 18 | https://www.infoq.com/news/2018/04/google-owe-oracle?utm_source=infoq&utm_medium=popular_widget&utm_campaign=popular_content_list&utm_content=homepage 19 | 20 | 간략히 요약하자면, 1심에서 구글 승. 오라클이 항소심 걸고, 항소심에서 오라클 승. 그런데 2심에서 다시 구글 승. 오라클이 항소 걸고, 또 오라클이 항소심에서 승. 여기까지가 현재이고. 대법원에 갈 가능성이 있습니다. 21 | 22 | 대법원에 가면 지금까지의 1심과 2심처럼 다시 구글이 이길 가능성이 높지 않을까 예측해 봅니다만.. 그거야 뭐 한 2년뒤면 알게 될지도 모르겠네요. 23 | 24 | 제 개인적인 소견도 마지막에 살짝 말씀 드렸구요. 25 | 26 | 그럼 아무쪼록 오늘도 좋은 하루 되시고. 27 | 시청해 주셔서 감사합니다. 28 | 29 | 좋아요과 구독하기! 감사합니다. 30 | 31 | ### 자바 10 지역 변수 타입 추론 32 | 33 | [![자바 10 지역 변수 타입 추론](https://img.youtube.com/vi/iL-hr64hts4/0.jpg)](https://youtu.be/iL-hr64hts4) 34 | 35 | 이번에는 InfoQ에 올라온 자바 10 배포 소식 https://www.infoq.com/news/2018/03/Java10GAReleased 을 보며 코딩해 보았습니다. 36 | 37 | 현재까지 제가 알아본 바로는 자바 10으로 코딩할 수 있는 IDE는 인텔리J 2018.1 버전 뿐입니다. 현재 얼리 엑세스가 가능하오니, 다운 받아 사용해 보세요. 38 | 39 | https://www.jetbrains.com/idea/nextversion/ 40 | 41 | 자바 설치 방법은 소개 안해드렸는데 워낙 간단하니까 알아서 하실 수 있으시죠? 42 | 43 | http://www.oracle.com/technetwork/java/javase/downloads/jdk10-downloads-4416644.html 44 | 45 | ### 스프링 5에서 맛보는 신박한 빈 등록 방법 46 | 47 | [![스프링 5에서 맛보는 신박한 빈 등록 방법](https://img.youtube.com/vi/f3SbCh2G5tA/0.jpg)](https://youtu.be/f3SbCh2G5tA) 48 | 49 | InfoQ에 올라온 Josh Long의 발표, Programmatic Bean Registration with Spring Framework 5.0을 보며 코딩해 보았습니다. 50 | 51 | https://www.infoq.com/presentations/bean-registration-spring-5# 52 | 53 | 코틀린과 자바 코드로 애노테이션을 사용하지 않고 빈을 등록하는 방법을 살펴봤습니다. SpringApplicationBuilder가 가지고 있는 initializers()라는 메서드가 핵심입니다. 54 | 55 | 그 메서드에 ApplicationContextInitializer 구현체를 넘겨줘야 하는데 그걸 람다식으로 구현하면서 빈들도 역시 람다식으로 등록할 수 있네요. 56 | 57 | 이번에 처음으로 코틀린을 써봤는데, 코틀린에 대한 제 첫인상은 영상에서 확인하시죠. ㅎㅎ 58 | 59 | -------------------------------------------------------------------------------- /jpa-getting-started/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /jpa-getting-started/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/jpa-getting-started/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /jpa-getting-started/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip 2 | -------------------------------------------------------------------------------- /jpa-getting-started/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | 3 | volumes: 4 | pgsql: 5 | 6 | services: 7 | pgsql: 8 | image: postgres:10-alpine 9 | environment: 10 | POSTGRES_USER: jpa 11 | POSTGRES_PASSWORD: jpa 12 | POSTGRES_DB: jpa 13 | PGDATA: /data/postgres 14 | volumes: 15 | - pgsql:/data/postgres 16 | ports: 17 | - "5432:5432" 18 | restart: always 19 | -------------------------------------------------------------------------------- /jpa-getting-started/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | me.whiteship 7 | jpa-sudy 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | jpa-study 12 | JPA Getting Started 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.1.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-jpa 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-jooq 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-web 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-devtools 44 | runtime 45 | 46 | 47 | com.h2database 48 | h2 49 | test 50 | 51 | 52 | org.postgresql 53 | postgresql 54 | runtime 55 | 56 | 57 | org.projectlombok 58 | lombok 59 | true 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-test 64 | test 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-maven-plugin 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /jpa-getting-started/src/main/java/me/whiteship/jpasudy/Application.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.jpasudy; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /jpa-getting-started/src/main/java/me/whiteship/jpasudy/Event.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.jpasudy; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import javax.persistence.*; 8 | 9 | @Entity 10 | @Getter @Setter @EqualsAndHashCode(of = "id") 11 | public class Event { 12 | 13 | @Id @GeneratedValue 14 | private Long id; 15 | 16 | private String title; 17 | 18 | @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) 19 | private EventDetail eventDetail; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /jpa-getting-started/src/main/java/me/whiteship/jpasudy/EventDetail.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.jpasudy; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import javax.persistence.*; 8 | 9 | @Entity 10 | @Getter @Setter @EqualsAndHashCode(of = "id") 11 | public class EventDetail { 12 | 13 | @Id 14 | @GeneratedValue 15 | private Long id; 16 | 17 | @Lob @Basic(fetch = FetchType.LAZY) 18 | private String content; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /jpa-getting-started/src/main/java/me/whiteship/jpasudy/EventDetailRepository.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.jpasudy; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface EventDetailRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /jpa-getting-started/src/main/java/me/whiteship/jpasudy/EventRepository.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.jpasudy; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface EventRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /jpa-getting-started/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Database 2 | spring.datasource.url=jdbc:postgresql://localhost:5432/jpa 3 | spring.datasource.username=jpa 4 | spring.datasource.password=jpa 5 | 6 | # JPA 7 | spring.jpa.hibernate.ddl-auto=create-drop 8 | spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false 9 | spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect 10 | spring.jpa.properties.hibernate.format_sql=true 11 | spring.jpa.show-sql=true -------------------------------------------------------------------------------- /jpa-getting-started/src/test/java/me/whiteship/jpasudy/EventRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.jpasudy; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | import javax.persistence.EntityManager; 10 | import java.util.Optional; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | @RunWith(SpringRunner.class) 15 | @DataJpaTest 16 | public class EventRepositoryTest { 17 | 18 | @Autowired 19 | EventRepository eventRepository; 20 | 21 | @Autowired 22 | EntityManager entityManager; 23 | 24 | @Test 25 | public void addEvent() { 26 | Event event1 = new Event(); 27 | event1.setTitle("new title1"); 28 | 29 | EventDetail eventDetail1 = new EventDetail(); 30 | eventDetail1.setContent("new content1"); 31 | event1.setEventDetail(eventDetail1); 32 | 33 | Event event2 = new Event(); 34 | event2.setTitle("new title"); 35 | 36 | EventDetail eventDetail2 = new EventDetail(); 37 | eventDetail2.setContent("new content"); 38 | event2.setEventDetail(eventDetail2); 39 | 40 | // 2 Inserts 41 | Event savedEvent1 = eventRepository.saveAndFlush(event1); 42 | assertThat(savedEvent1.getId()).isNotNull(); 43 | 44 | Event savedEvent2 = eventRepository.saveAndFlush(event2); 45 | assertThat(savedEvent2.getId()).isNotNull(); 46 | 47 | System.out.println("find by id"); 48 | entityManager.clear(); 49 | 50 | Event eventOnly = eventRepository.findById(savedEvent1.getId()).orElseThrow(() -> new RuntimeException());// Select 51 | 52 | System.out.println("get detail content"); 53 | assertThat(eventOnly.getEventDetail().getContent()).isEqualTo("new content1"); // Select detail 54 | 55 | System.out.println("find all"); 56 | entityManager.clear(); 57 | eventRepository.findAll(); // Select 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /jpa-getting-started/src/test/java/me/whiteship/jpasudy/JpaSudyApplicationTests.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.jpasudy; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class JpaSudyApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /ksug201811restapi/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /ksug201811restapi/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/ksug201811restapi/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /ksug201811restapi/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | = Natural REST API Guide 2 | 백기선; 3 | :doctype: book 4 | :icons: font 5 | :source-highlighter: highlightjs 6 | :toc: left 7 | :toclevels: 4 8 | :sectlinks: 9 | :operation-curl-request-title: Example request 10 | :operation-http-response-title: Example response 11 | 12 | [[overview]] 13 | = 개요 14 | 15 | [[overview-http-verbs]] 16 | == HTTP 동사 17 | 18 | 본 REST API에서 사용하는 HTTP 동사(verbs)는 가능한한 표준 HTTP와 REST 규약을 따릅니다. 19 | 20 | |=== 21 | | 동사 | 용례 22 | 23 | | `GET` 24 | | 리소스를 가져올 때 사용 25 | 26 | | `POST` 27 | | 새 리소스를 만들 때 사용 28 | 29 | | `PUT` 30 | | 기존 리소스를 수정할 때 사용 31 | 32 | | `PATCH` 33 | | 기존 리소스의 일부를 수정할 때 사용 34 | 35 | | `DELETE` 36 | | 기존 리소스를 삭제할 떄 사용 37 | |=== 38 | 39 | [[overview-http-status-codes]] 40 | == HTTP 상태 코드 41 | 42 | 본 REST API에서 사용하는 HTTP 상태 코드는 가능한한 표준 HTTP와 REST 규약을 따릅니다. 43 | 44 | |=== 45 | | 상태 코드 | 용례 46 | 47 | | `200 OK` 48 | | 요청을 성공적으로 처리함 49 | 50 | | `201 Created` 51 | | 새 리소스를 성공적으로 생성함. 응답의 `Location` 헤더에 해당 리소스의 URI가 담겨있다. 52 | 53 | | `204 No Content` 54 | | 기존 리소스를 성공적으로 수정함. 55 | 56 | | `400 Bad Request` 57 | | 잘못된 요청을 보낸 경우. 응답 본문에 더 오류에 대한 정보가 담겨있다. 58 | 59 | | `404 Not Found` 60 | | 요청한 리소스가 없음. 61 | |=== 62 | 63 | [[overview-errors]] 64 | == 오류 65 | 66 | 에러 응답이 발생했을 때 (상태 코드 >= 400), 본문에 해당 문제를 기술한 JSON 객체가 담겨있다. 에러 객체는 다음의 구조를 따른다. 67 | 68 | include::{snippets}/errors/response-fields.adoc[] 69 | 70 | 예를 들어, 잘못된 요청으로 이벤트를 만들려고 했을 때 다음과 같은 `400 Bad Request` 응답을 받는다. 71 | 72 | include::{snippets}/errors/http-response.adoc[] 73 | 74 | [[overview-hypermedia]] 75 | == 하이퍼미디어 76 | 77 | 본 REST API는 하이퍼미디어와 사용하며 응답에 담겨있는 리소스는 다른 리소스에 대한 링크를 가지고 있다. 78 | 응답은 http://stateless.co/hal_specification.html[Hypertext Application from resource to resource. Language (HAL)] 형식을 따른다. 79 | 링크는 `_links`라는 키로 제공한다. 본 API의 사용자(클라이언트)는 URI를 직접 생성하지 않아야 하며, 리소스에서 제공하는 링크를 사용해야 한다. 80 | 81 | [[resources]] 82 | = 리소스 83 | 84 | [[resources-index]] 85 | == 인덱스 86 | 87 | 인덱스는 서비스 진입점을 제공한다. 88 | 89 | 90 | [[resources-index-access]] 91 | === 인덱스 조회 92 | 93 | `GET` 요청을 사용하여 인덱스에 접근할 수 있다. 94 | 95 | operation::index[snippets='response-body,http-response,links'] 96 | 97 | [[resources-events]] 98 | == 이벤트 99 | 100 | 이벤트 리소스는 이벤트를 만들거나 조회할 때 사용한다. 101 | 102 | [[resources-events-list]] 103 | === 이벤트 목록 조회 104 | 105 | `GET` 요청을 사용하여 서비스의 모든 이벤트를 조회할 수 있다. 106 | 107 | operation::get-events[snippets='response-fields,curl-request,http-response,links'] 108 | 109 | [[resources-events-create]] 110 | === 이벤트 생성 111 | 112 | `POST` 요청을 사용해서 새 이벤트를 만들 수 있다. 113 | 114 | operation::create-event[snippets='request-fields,curl-request,http-response,links'] 115 | 116 | [[resources-events-get]] 117 | === 이벤트 조회 118 | 119 | `Get` 요청을 사용해서 기존 이벤트 하나를 조회할 수 있다. 120 | 121 | operation::get-event[snippets='request-fields,curl-request,http-response,links'] 122 | 123 | [[resources-events-update]] 124 | === 이벤트 수정 125 | 126 | `PUT` 요청을 사용해서 기존 이벤트를 수정할 수 있다. 127 | 128 | operation::update-event[snippets='request-fields,curl-request,http-response,links'] 129 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/Application.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi; 2 | 3 | import org.modelmapper.ModelMapper; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.ui.ModelMap; 8 | 9 | @SpringBootApplication 10 | public class Application { 11 | 12 | @Bean 13 | public ModelMapper modelMapper() { 14 | return new ModelMapper(); 15 | } 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(Application.class, args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/accounts/Account.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.accounts; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import java.util.Set; 7 | 8 | @Entity 9 | @Getter @Setter @EqualsAndHashCode(of = "id") 10 | @Builder @AllArgsConstructor @NoArgsConstructor 11 | public class Account { 12 | 13 | @Id @GeneratedValue 14 | private Integer id; 15 | 16 | private String username; 17 | 18 | private String password; 19 | 20 | @ElementCollection(fetch = FetchType.EAGER) 21 | @Enumerated(EnumType.STRING) 22 | private Set roles; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/accounts/AccountAdapter.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.accounts; 2 | 3 | import org.springframework.security.core.GrantedAuthority; 4 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 5 | import org.springframework.security.core.userdetails.User; 6 | 7 | import java.util.Collection; 8 | import java.util.Set; 9 | import java.util.stream.Collectors; 10 | 11 | 12 | public class AccountAdapter extends User { 13 | 14 | private Account account; 15 | 16 | public AccountAdapter(Account account) { 17 | super(account.getUsername(), account.getPassword(), authorities(account.getRoles())); 18 | this.account = account; 19 | } 20 | 21 | private static Collection authorities(Set roles) { 22 | return roles.stream().map(r -> new SimpleGrantedAuthority("ROLE_" + r.name())) 23 | .collect(Collectors.toSet()); 24 | } 25 | 26 | public Account getAccount() { 27 | return account; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/accounts/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.accounts; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import java.util.Optional; 6 | 7 | public interface AccountRepository extends JpaRepository { 8 | Optional findByUsername(String username); 9 | } 10 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/accounts/AccountRoles.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.accounts; 2 | 3 | public enum AccountRoles { 4 | 5 | ADMIN, USER 6 | } 7 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/accounts/AccountSerializer.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.accounts; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | 7 | import java.io.IOException; 8 | 9 | public class AccountSerializer extends JsonSerializer { 10 | @Override 11 | public void serialize(Account account, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { 12 | jsonGenerator.writeStartObject(); 13 | jsonGenerator.writeNumberField("id", account.getId()); 14 | jsonGenerator.writeEndObject(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/accounts/AccountService.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.accounts; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 | import org.springframework.security.crypto.password.PasswordEncoder; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.Collection; 14 | import java.util.Optional; 15 | import java.util.Set; 16 | import java.util.stream.Collectors; 17 | 18 | @Service 19 | public class AccountService implements UserDetailsService { 20 | 21 | @Autowired 22 | private AccountRepository accountRepository; 23 | 24 | @Autowired 25 | private PasswordEncoder passwordEncoder; 26 | 27 | @Override 28 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 29 | Optional accountOptional = this.accountRepository.findByUsername(username); 30 | Account account = accountOptional.orElseThrow(() -> new UsernameNotFoundException(username)); 31 | return new AccountAdapter(account); 32 | } 33 | 34 | 35 | 36 | public void createAccount(Account account) { 37 | account.setPassword(passwordEncoder.encode(account.getPassword())); 38 | this.accountRepository.save(account); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/accounts/MyAppProperties.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.accounts; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | @ConfigurationProperties("my-app") 10 | @Getter @Setter 11 | public class MyAppProperties { 12 | 13 | private String clientId; 14 | 15 | private String clientSecret; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/common/ErrorResource.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.common; 2 | 3 | import me.whiteship.ksug201811restapi.index.IndexController; 4 | import org.springframework.hateoas.Link; 5 | import org.springframework.hateoas.Resource; 6 | import org.springframework.validation.Errors; 7 | 8 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 9 | 10 | public class ErrorResource extends Resource { 11 | public ErrorResource(Errors content, Link... links) { 12 | super(content, links); 13 | add(linkTo(IndexController.class).withRel("index")); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/common/ErrorsSerializer.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.common; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | import org.springframework.boot.jackson.JsonComponent; 7 | import org.springframework.validation.Errors; 8 | 9 | import java.io.IOException; 10 | 11 | @JsonComponent 12 | public class ErrorsSerializer extends JsonSerializer { 13 | @Override 14 | public void serialize(Errors errors, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { 15 | jsonGenerator.writeStartArray(); 16 | errors.getFieldErrors().forEach(e -> { 17 | try { 18 | jsonGenerator.writeStartObject(); 19 | jsonGenerator.writeStringField("field", e.getField()); 20 | jsonGenerator.writeStringField("objectName", e.getObjectName()); 21 | jsonGenerator.writeStringField("defaultMessage", e.getDefaultMessage()); 22 | Object rejectedValue = e.getRejectedValue(); 23 | if (rejectedValue != null) { 24 | jsonGenerator.writeStringField("rejectedValue", rejectedValue.toString()); 25 | } else { 26 | jsonGenerator.writeStringField("rejectedValue", ""); 27 | } 28 | jsonGenerator.writeEndObject(); 29 | } catch (IOException e1) { 30 | e1.printStackTrace(); 31 | } 32 | }); 33 | 34 | jsonGenerator.writeEndArray(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/config/AuthServerConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.config; 2 | 3 | import me.whiteship.ksug201811restapi.accounts.AccountService; 4 | import me.whiteship.ksug201811restapi.accounts.MyAppProperties; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.authentication.AuthenticationManager; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; 10 | import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; 11 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; 12 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; 13 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; 14 | import org.springframework.security.oauth2.provider.token.TokenStore; 15 | 16 | @Configuration 17 | @EnableAuthorizationServer 18 | public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { 19 | 20 | @Autowired 21 | AccountService accountService; 22 | 23 | @Autowired 24 | PasswordEncoder passwordEncoder; 25 | 26 | @Autowired 27 | AuthenticationManager authenticationManager; 28 | 29 | @Autowired 30 | TokenStore tokenStore; 31 | 32 | @Autowired 33 | MyAppProperties myAppProperties; 34 | 35 | @Override 36 | public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { 37 | security.passwordEncoder(passwordEncoder); 38 | } 39 | 40 | @Override 41 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception { 42 | clients.inMemory() 43 | .withClient(myAppProperties.getClientId()) 44 | .secret(passwordEncoder.encode(myAppProperties.getClientSecret())) 45 | .scopes("write", "read") 46 | .authorizedGrantTypes("password", "refresh_token") 47 | .accessTokenValiditySeconds(10 * 60) 48 | .refreshTokenValiditySeconds(60 * 60); 49 | } 50 | 51 | @Override 52 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { 53 | endpoints.tokenStore(tokenStore) 54 | .authenticationManager(authenticationManager) 55 | .userDetailsService(accountService); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/config/ResourceServerConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.http.HttpMethod; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 7 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 8 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; 9 | import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; 10 | 11 | @Configuration 12 | @EnableResourceServer 13 | public class ResourceServerConfig extends ResourceServerConfigurerAdapter { 14 | 15 | @Override 16 | public void configure(ResourceServerSecurityConfigurer resources) throws Exception { 17 | resources.resourceId("events"); 18 | } 19 | 20 | @Override 21 | public void configure(HttpSecurity http) throws Exception { 22 | http 23 | .anonymous() 24 | .and() 25 | .authorizeRequests() 26 | .mvcMatchers(HttpMethod.GET, "/api/**").permitAll() 27 | .anyRequest().authenticated() 28 | .and() 29 | .exceptionHandling() 30 | .accessDeniedHandler(new OAuth2AccessDeniedHandler()) 31 | ; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.config; 2 | 3 | import me.whiteship.ksug201811restapi.accounts.AccountService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.authentication.AuthenticationManager; 8 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 9 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 10 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 13 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 14 | import org.springframework.security.crypto.password.PasswordEncoder; 15 | import org.springframework.security.oauth2.provider.token.TokenStore; 16 | import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; 17 | 18 | @Configuration 19 | @EnableWebSecurity 20 | @EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true) 21 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 22 | 23 | @Autowired 24 | AccountService accountService; 25 | 26 | @Bean 27 | public PasswordEncoder passwordEncoder() { 28 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 29 | } 30 | 31 | @Bean 32 | public TokenStore tokenStore() { 33 | return new InMemoryTokenStore(); 34 | } 35 | 36 | @Bean 37 | @Override 38 | public AuthenticationManager authenticationManagerBean() throws Exception { 39 | return super.authenticationManagerBean(); 40 | } 41 | 42 | @Override 43 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 44 | auth.userDetailsService(accountService) 45 | .passwordEncoder(passwordEncoder()); 46 | } 47 | 48 | @Override 49 | public void configure(WebSecurity web) throws Exception { 50 | web.ignoring().mvcMatchers("/docs/**"); 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/events/CurrentUser.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.events; 2 | 3 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target(ElementType.PARAMETER) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account") 13 | public @interface CurrentUser { 14 | } 15 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/events/Event.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.events; 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 4 | import lombok.*; 5 | import me.whiteship.ksug201811restapi.accounts.Account; 6 | import me.whiteship.ksug201811restapi.accounts.AccountSerializer; 7 | 8 | import javax.persistence.*; 9 | import java.time.LocalDateTime; 10 | 11 | @Entity 12 | @Getter @Setter @EqualsAndHashCode(of = "id") 13 | @Builder @AllArgsConstructor @NoArgsConstructor 14 | public class Event { 15 | 16 | @Id @GeneratedValue 17 | private Integer id; 18 | private String name; 19 | private String description; 20 | private LocalDateTime beginEnrollmentDateTime; 21 | private LocalDateTime closeEnrollmentDateTime; 22 | private LocalDateTime beginEventDateTime; 23 | private LocalDateTime endEventDateTime; 24 | private String location; // (optional) 이게 없으면 온라인 모임 25 | private int basePrice; // (optional) 26 | private int maxPrice; // (optional) 27 | private int limitOfEnrollment; 28 | private boolean offline; 29 | private boolean free; 30 | @Enumerated(EnumType.STRING) 31 | private EventStatus eventStatus = EventStatus.DRAFT; 32 | 33 | @ManyToOne 34 | @JsonSerialize(using = AccountSerializer.class) 35 | private Account owner; 36 | 37 | public void update() { 38 | if (basePrice == 0 && maxPrice == 0) { 39 | this.free = true; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/events/EventDto.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.events; 2 | 3 | import lombok.*; 4 | 5 | import javax.validation.constraints.Min; 6 | import javax.validation.constraints.NotEmpty; 7 | import javax.validation.constraints.NotNull; 8 | import java.time.LocalDateTime; 9 | 10 | @Getter @Setter 11 | @Builder @AllArgsConstructor @NoArgsConstructor 12 | public class EventDto { 13 | 14 | @NotEmpty 15 | private String name; 16 | @NotEmpty 17 | private String description; 18 | @NotNull 19 | private LocalDateTime beginEnrollmentDateTime; 20 | @NotNull 21 | private LocalDateTime closeEnrollmentDateTime; 22 | @NotNull 23 | private LocalDateTime beginEventDateTime; 24 | @NotNull 25 | private LocalDateTime endEventDateTime; 26 | private String location; // (optional) 이게 없으면 온라인 모임 27 | @Min(0) 28 | private int basePrice; // (optional) 29 | @Min(0) 30 | private int maxPrice; // (optional) 31 | @Min(0) 32 | private int limitOfEnrollment; 33 | } 34 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/events/EventDtoValidator.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.events; 2 | 3 | import org.springframework.stereotype.Component; 4 | import org.springframework.validation.Errors; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | @Component 9 | public class EventDtoValidator { 10 | 11 | public void validate(EventDto eventDto, Errors errors) { 12 | int maxPrice = eventDto.getMaxPrice(); 13 | if (maxPrice < eventDto.getBasePrice()) { 14 | errors.rejectValue("maxPrice", "wrong.value", "Max price가 base 보다 낮으면 안되요."); 15 | } 16 | 17 | LocalDateTime closeEnrollmentDateTime = eventDto.getCloseEnrollmentDateTime(); 18 | if (closeEnrollmentDateTime.isBefore(eventDto.getBeginEnrollmentDateTime())) { 19 | errors.rejectValue("closeEnrollmentDateTime", "wrong.value", "closeEnrollmentDateTime is wrong"); 20 | } 21 | 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/events/EventRepository.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.events; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface EventRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/events/EventResource.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.events; 2 | 3 | import org.springframework.hateoas.Link; 4 | import org.springframework.hateoas.Resource; 5 | 6 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 7 | 8 | public class EventResource extends Resource { 9 | public EventResource(Event content, Link... links) { 10 | super(content, links); 11 | add(linkTo(EventController.class).slash(content.getId()).withSelfRel()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/events/EventStatus.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.events; 2 | 3 | public enum EventStatus { 4 | 5 | DRAFT, PUBLISHED, ENROLLMENT_STARTED 6 | } 7 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/java/me/whiteship/ksug201811restapi/index/IndexController.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.index; 2 | 3 | import me.whiteship.ksug201811restapi.events.EventController; 4 | import org.springframework.hateoas.ResourceSupport; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 9 | 10 | @RestController 11 | public class IndexController { 12 | 13 | @GetMapping("/api") 14 | public ResourceSupport root() { 15 | ResourceSupport index = new ResourceSupport(); 16 | index.add(linkTo(EventController.class).withRel("events")); 17 | return index; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /ksug201811restapi/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.username=postgres 2 | spring.datasource.password=pass 3 | spring.datasource.url=jdbc:postgresql://localhost:5432/postgres 4 | spring.datasource.driver-class-name=org.postgresql.Driver 5 | 6 | spring.jpa.hibernate.ddl-auto=create-drop 7 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true 8 | spring.jpa.properties.hibernate.format_sql=true 9 | 10 | logging.level.org.hibernate.SQL=DEBUG 11 | logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE 12 | 13 | my-app.client-id=myApp 14 | my-app.client-secret=secret -------------------------------------------------------------------------------- /ksug201811restapi/src/main/resources/static/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 |

스태틱 페이지

9 | 10 | -------------------------------------------------------------------------------- /ksug201811restapi/src/test/java/me/whiteship/ksug201811restapi/Ksug201811restapiApplicationTests.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class Ksug201811restapiApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /ksug201811restapi/src/test/java/me/whiteship/ksug201811restapi/common/ControllerTests.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.common; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.junit.Ignore; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; 8 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.context.annotation.Import; 11 | import org.springframework.test.context.ActiveProfiles; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | 15 | @RunWith(SpringRunner.class) 16 | @SpringBootTest 17 | @AutoConfigureMockMvc 18 | @AutoConfigureRestDocs 19 | @Import(TestDocConfig.class) 20 | @ActiveProfiles("test") 21 | @Ignore 22 | public class ControllerTests { 23 | 24 | @Autowired 25 | protected MockMvc mockMvc; 26 | 27 | @Autowired 28 | protected ObjectMapper objectMapper; 29 | } 30 | -------------------------------------------------------------------------------- /ksug201811restapi/src/test/java/me/whiteship/ksug201811restapi/common/TestDocConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.common; 2 | 3 | import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer; 7 | 8 | import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; 9 | 10 | @Configuration 11 | public class TestDocConfig { 12 | 13 | @Bean 14 | public RestDocsMockMvcConfigurationCustomizer customizer() { 15 | return new RestDocsMockMvcConfigurationCustomizer() { 16 | @Override 17 | public void customize(MockMvcRestDocumentationConfigurer configurer) { 18 | configurer.operationPreprocessors() 19 | .withRequestDefaults(prettyPrint()) 20 | .withResponseDefaults(prettyPrint()); 21 | } 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ksug201811restapi/src/test/java/me/whiteship/ksug201811restapi/events/EventRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.events; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | import java.util.List; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | @RunWith(SpringRunner.class) 14 | @DataJpaTest 15 | public class EventRepositoryTest { 16 | 17 | @Autowired 18 | EventRepository eventRepository; 19 | 20 | @Test 21 | public void crud() { 22 | Event event = Event.builder() 23 | .name("test event") 24 | .build(); 25 | 26 | eventRepository.save(event); 27 | 28 | List all = eventRepository.findAll(); 29 | assertThat(all.size()).isEqualTo(1); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /ksug201811restapi/src/test/java/me/whiteship/ksug201811restapi/events/EventTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.events; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | 8 | public class EventTest { 9 | 10 | @Test 11 | public void builder() { 12 | String name = "test event"; 13 | Event event = Event.builder() 14 | .name(name) 15 | .description("ksug") 16 | .build(); 17 | 18 | assertThat(event.getName()).isEqualTo(name); 19 | } 20 | 21 | @Test 22 | public void javaBean() { 23 | String name = "keesun"; 24 | Event event = new Event(); 25 | event.setName(name); 26 | assertThat(event.getName()).isEqualTo(name); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /ksug201811restapi/src/test/java/me/whiteship/ksug201811restapi/security/OAuthTokenTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.ksug201811restapi.security; 2 | 3 | import me.whiteship.ksug201811restapi.accounts.Account; 4 | import me.whiteship.ksug201811restapi.accounts.AccountRoles; 5 | import me.whiteship.ksug201811restapi.accounts.AccountService; 6 | import me.whiteship.ksug201811restapi.accounts.MyAppProperties; 7 | import me.whiteship.ksug201811restapi.common.ControllerTests; 8 | import org.junit.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.http.MediaType; 11 | 12 | import java.net.http.HttpHeaders; 13 | import java.util.Set; 14 | 15 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; 16 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 17 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 20 | 21 | public class OAuthTokenTest extends ControllerTests { 22 | 23 | @Autowired 24 | AccountService accountService; 25 | 26 | @Autowired 27 | MyAppProperties myAppProperties; 28 | 29 | @Test 30 | public void getAccessToken() throws Exception { 31 | String username = "user@email.com"; 32 | String password = "user"; 33 | 34 | Account account = Account.builder() 35 | .username(username) 36 | .password(password) 37 | .roles(Set.of(AccountRoles.ADMIN)) 38 | .build(); 39 | 40 | this.accountService.createAccount(account); 41 | 42 | this.mockMvc.perform(post("/oauth/token") 43 | .with(httpBasic(myAppProperties.getClientId(), myAppProperties.getClientSecret())) 44 | .accept(MediaType.APPLICATION_JSON_UTF8) 45 | .param("grant_type", "password") 46 | .param("username", username) 47 | .param("password", password)) 48 | .andDo(print()) 49 | .andExpect(status().isOk()) 50 | .andExpect(jsonPath("access_token").hasJsonPath()) 51 | ; 52 | 53 | 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ksug201811restapi/src/test/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.username=sa 2 | spring.datasource.password= 3 | spring.datasource.url=jdbc:h2:mem:testdb 4 | spring.datasource.driver-class-name=org.h2.Driver 5 | 6 | spring.datasource.hikari.jdbc-url=jdbc:h2:mem:testdb 7 | 8 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect 9 | -------------------------------------------------------------------------------- /kubernetes-getting-started.md: -------------------------------------------------------------------------------- 1 | # 쿠버네티스 시작하기 2 | 3 | ## 참고 4 | 5 | [쿠버네티스 시작하기](https://docs.docker.com/get-started/) 6 | [유툽/백기선/도커](https://www.youtube.com/playlist?list=PLfI752FpVCS84hxOeCyI4SBPUwt4Itd0T) 7 | 8 | ## 유툽 인덱스 9 | 10 | ### 쿠버네티스 시작하기 1부. minikube와 kubectl 사용해서 앱 배포하기 11 | 12 | [![쿠버네티스 시작하기 1부. minikube와 kubectl 사용해서 앱 배포하기](https://img.youtube.com/vi/IFc1mG48j0s/0.jpg)](https://youtu.be/IFc1mG48j0s) 13 | 14 | 쿠버네티스란 무엇인가. 쿠버네티스의 매니저와 노드에 대해 알아봤습니다. 그리고 웹에서 인터렉티브 하게 미니큐브를 사용해 간단한 애플리케이션을 배포하는 것 까지 살펴봤습니다. 15 | 16 | https://kubernetes.io/docs/tutorials/kubernetes-basics/explore-intro/ 17 | 18 | 여기서 1과 2에 해당하는 내용보고 2 마무리 하는 도중에 갑자기 스트리밍이 끊겼어요. 19 | 20 | Deployment라는 걸 만들어서 배포하면 Pod라는게 생기고 그게 애플리케이션 단위인것 같은데 Pod에 대해서는 3부에서 더 자세히 다룬다고 합니다. 원래 3부까지 보려고 했는데 스트리밍이 갑자기 끊어지는 바람에.. 다음에 이어서 보겠습니다. 21 | 22 | ### 쿠버네티스 시작하기 2부. Pod, Node 그리고 Service 23 | 24 | [![쿠버네티스 시작하기 2부. Pod, Node 그리고 Service](https://img.youtube.com/vi/lVG9LU90ZQw/0.jpg)](https://youtu.be/lVG9LU90ZQw) 25 | 26 | 쿠버네티스의 Pod, Node 그리고 서비스에 대해 살펴봤습니다. (3, 4번 모듈) 27 | 28 | https://kubernetes.io/docs/tutorials/kubernetes-basics/expose-intro/ 29 | 30 | 쿠버네티스의 포드? 파드? 는 컨테이너들과 그 컨테이너들끼리 공유할 수 있는 리소스들의 묶음이고 파드당 아이피가 하나 할당이 됩니다. 즉 그 안에 있는 컨테이너들끼리는 같은 IP 를 공유해서 쓰는거겠죠. 31 | 32 | 그리고 Node는 그 Pod 실행하는 실제 워커 머신인데, 하나의 노드 안에 여러개의 파드를 실행할 수도 있다고 합니다. 그리고 노드는 그 안에 컨테이너들과 큐비클이라는게 있고, 큐비클이 실제 쿠버네티스 매니저랑 의사소통 하면서 컨테이너들을 어떻게 뛰울지 상태를 바꿀지 결정합니다. 33 | 34 | 그렇게 어떤 노드 안에 돌고 있는 파드안에 들어있는 컨테이너에 접근을 하려면 지금까지는 kubectl proxy를 띄워서 접속했었는데요. 서비스라는 걸 쓰면 그렇게 private하고 isolated된 네트워크에서 돌고 있는 애플리케이션을 클러스터 밖으로 노출시킬 수 있습니다. 35 | 36 | 서비스는 여러 파드들의 묶음인데 아마도.. 레이블로 사용해서 묶는거 같고요. (이부분은 저에게 명확하지 않습니다.) 라우팅과 디스커버리도 담당합니다. 서비스에서 중요해 보이는게 타입인데, 이 타입에 따라 클러스터 밖으로 노출하는 방법이 달라집니다. 37 | 38 | 데모에서는 NODE_PORT 라는 타입을 사용해서 특정 애프리케이션이 사용하는 포트를 지정해주면, 그 포트에 접근할 수 있는 공개된 포트를 랜덤하게 만들어 주더군요. 그럼 그 포트 번호화 미니큐브가 돌고 있는 IP를 조합해서 클러스터 밖에서도 애플리케이션에 접속할 수 있게 되었습니다. 39 | 40 | ### 쿠버네티스 시작하기 3부 (완결): 애플리케이션 스케일링과 롤링 업데이트 41 | 42 | [![쿠버네티스 시작하기 3부 (완결): 애플리케이션 스케일링과 롤링 업데이트](https://img.youtube.com/vi/6q1MRXNUzPU/0.jpg)](https://youtu.be/6q1MRXNUzPU) 43 | 44 | 오늘로 쿠버네티스 시작하기 3부가 완료됐습니다. 45 | 46 | https://kubernetes.io/docs/tutorials/kubernetes-basics/ 47 | 48 | 쿠버네티스 기본 5부과 6부가 생각보다 짧아서 금방 봤네요. 오토스켈링 기능도 제공한다고 하는데 튜토리얼에서 다루진 않았습니다. 비교적 간편하게 큐브컨트롤(kubectl)을 사용해서 스케일 아웃, 스케일 인 할 수 있었습니다. 49 | 50 | 여러 인스턴스를 띄워둔 경우에 무중단 배포도 쿠버네티스로 할 수 있는데, 롤링 업데이트라고 하더군요. 그런데 기본값으로는 모든 파드를 다 내리고 한번에 새 파드를 다 만들어서 중단이 생길 수도 있을거 같은데, 뭔가 비율을 조정할 수 있을 것 같은데 이것도 역시 튜토리얼에서 다루진 않네요. 51 | 52 | 개인적으로 도커 스왐보다 재밌고 다양한 기능을 제공하는것 같아 마음에 드네요. 다음 기회에 더 자세히 살펴보겠습니다. 53 | -------------------------------------------------------------------------------- /rest-api-with-spring/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /rest-api-with-spring/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/rest-api-with-spring/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /rest-api-with-spring/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /rest-api-with-spring/readme.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | This project is demonstrating building REST API that meets Self-Describtive Message and HATEOAS(Hypermedia As The Engine Of Application Status) by using multiple Spring techs: 4 | - Spring Boot 5 | - Spring Data JPA 6 | - Spring Security OAuath2 7 | - Spring HATEOAS 8 | - Spring REST Docs 9 | -------------------------------------------------------------------------------- /rest-api-with-spring/scripts.md: -------------------------------------------------------------------------------- 1 | # Scripts 2 | 3 | Here, I memo scripts that I have used during development. 4 | 5 | ## Postgres 6 | 7 | ### Run Postgres Container 8 | 9 | ``` 10 | docker run --name ndb -p 5432:5432 -e POSTGRES_PASSWORD=pass -d postgres 11 | 12 | ``` 13 | 14 | This cmdlet will create Postgres instance so that you can connect to a database with: 15 | * database: postgres 16 | * username: postgres 17 | * password: pass 18 | * post: 5432 19 | 20 | ### Getting into the Postgres container 21 | 22 | ``` 23 | docker exec -i -t ndb bash 24 | ``` 25 | 26 | Then you will see the containers bash as a root user. 27 | 28 | ### Connect to a database 29 | 30 | ``` 31 | psql -d postgres -U postgres 32 | ``` 33 | 34 | ### Query Databases 35 | 36 | ``` 37 | \l 38 | ``` 39 | 40 | ### Query Tables 41 | 42 | ``` 43 | \dt 44 | ``` 45 | 46 | ### Quit 47 | 48 | ``` 49 | \q 50 | ``` 51 | 52 | ## application.properties 53 | 54 | ### Datasource 55 | 56 | ``` 57 | spring.datasource.username=postgres 58 | spring.datasource.password=pass 59 | spring.datasource.url=jdbc:postgresql://localhost:5432/postgres 60 | spring.datasource.driver-class-name=org.postgresql.Driver 61 | ``` 62 | 63 | ### Hibernate 64 | 65 | ``` 66 | spring.jpa.hibernate.ddl-auto=create-drop 67 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true 68 | spring.jpa.properties.hibernate.format_sql=true 69 | 70 | logging.level.org.hibernate.SQL=DEBUG 71 | logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE 72 | ``` 73 | 74 | ### Test Database 75 | 76 | ``` 77 | spring.datasource.username=sa 78 | spring.datasource.password= 79 | spring.datasource.url=jdbc:h2:mem:testdb 80 | spring.datasource.driver-class-name=org.h2.Driver 81 | ``` 82 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | = REST API Guide 2 | 백기선; 3 | :doctype: book 4 | :icons: font 5 | :source-highlighter: highlightjs 6 | :toc: left 7 | :toclevels: 4 8 | :sectlinks: 9 | :operation-curl-request-title: Example request 10 | :operation-http-response-title: Example response 11 | 12 | [[overview]] 13 | = 개요 14 | 15 | [[overview-http-verbs]] 16 | == HTTP 동사 17 | 18 | 본 REST API에서 사용하는 HTTP 동사(verbs)는 가능한한 표준 HTTP와 REST 규약을 따릅니다. 19 | 20 | |=== 21 | | 동사 | 용례 22 | 23 | | `GET` 24 | | 리소스를 가져올 때 사용 25 | 26 | | `POST` 27 | | 새 리소스를 만들 때 사용 28 | 29 | | `PUT` 30 | | 기존 리소스를 수정할 때 사용 31 | 32 | | `PATCH` 33 | | 기존 리소스의 일부를 수정할 때 사용 34 | 35 | | `DELETE` 36 | | 기존 리소스를 삭제할 떄 사용 37 | |=== 38 | 39 | [[overview-http-status-codes]] 40 | == HTTP 상태 코드 41 | 42 | 본 REST API에서 사용하는 HTTP 상태 코드는 가능한한 표준 HTTP와 REST 규약을 따릅니다. 43 | 44 | |=== 45 | | 상태 코드 | 용례 46 | 47 | | `200 OK` 48 | | 요청을 성공적으로 처리함 49 | 50 | | `201 Created` 51 | | 새 리소스를 성공적으로 생성함. 응답의 `Location` 헤더에 해당 리소스의 URI가 담겨있다. 52 | 53 | | `204 No Content` 54 | | 기존 리소스를 성공적으로 수정함. 55 | 56 | | `400 Bad Request` 57 | | 잘못된 요청을 보낸 경우. 응답 본문에 더 오류에 대한 정보가 담겨있다. 58 | 59 | | `404 Not Found` 60 | | 요청한 리소스가 없음. 61 | |=== 62 | 63 | [[overview-errors]] 64 | == 오류 65 | 66 | 에러 응답이 발생했을 때 (상태 코드 >= 400), 본문에 해당 문제를 기술한 JSON 객체가 담겨있다. 에러 객체는 다음의 구조를 따른다. 67 | 68 | include::{snippets}/errors/response-fields.adoc[] 69 | 70 | 예를 들어, 잘못된 요청으로 이벤트를 만들려고 했을 때 다음과 같은 `400 Bad Request` 응답을 받는다. 71 | 72 | include::{snippets}/errors/http-response.adoc[] 73 | 74 | [[overview-hypermedia]] 75 | == 하이퍼미디어 76 | 77 | 본 REST API는 하이퍼미디어와 사용하며 응답에 담겨있는 리소스는 다른 리소스에 대한 링크를 가지고 있다. 78 | 응답은 http://stateless.co/hal_specification.html[Hypertext Application from resource to resource. Language (HAL)] 형식을 따른다. 79 | 링크는 `_links`라는 키로 제공한다. 본 API의 사용자(클라이언트)는 URI를 직접 생성하지 않아야 하며, 리소스에서 제공하는 링크를 사용해야 한다. 80 | 81 | [[resources]] 82 | = 리소스 83 | 84 | [[resources-index]] 85 | == 인덱스 86 | 87 | 인덱스는 서비스 진입점을 제공한다. 88 | 89 | 90 | [[resources-index-access]] 91 | === 인덱스 조회 92 | 93 | `GET` 요청을 사용하여 인덱스에 접근할 수 있다. 94 | 95 | operation::index[snippets='response-body,http-response,links'] 96 | 97 | [[resources-events]] 98 | == 이벤트 99 | 100 | 이벤트 리소스는 이벤트를 만들거나 조회할 때 사용한다. 101 | 102 | [[resources-events-list]] 103 | === 이벤트 목록 조회 104 | 105 | `GET` 요청을 사용하여 서비스의 모든 이벤트를 조회할 수 있다. 106 | 107 | operation::get-events[snippets='response-fields,curl-request,http-response,links'] 108 | 109 | [[resources-events-create]] 110 | === 이벤트 생성 111 | 112 | `POST` 요청을 사용해서 새 이벤트를 만들 수 있다. 113 | 114 | operation::create-event[snippets='request-fields,curl-request,http-request,request-headers,http-response,response-headers,response-fields,links'] 115 | 116 | [[resources-events-get]] 117 | === 이벤트 조회 118 | 119 | `Get` 요청을 사용해서 기존 이벤트 하나를 조회할 수 있다. 120 | 121 | operation::get-event[snippets='request-fields,curl-request,http-response,links'] 122 | 123 | [[resources-events-update]] 124 | === 이벤트 수정 125 | 126 | `PUT` 요청을 사용해서 기존 이벤트를 수정할 수 있다. 127 | 128 | operation::update-event[snippets='request-fields,curl-request,http-response,links'] -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/accounts/Account.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.accounts; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import java.util.Set; 7 | 8 | @Entity 9 | @Getter @Setter @EqualsAndHashCode(of = "id") 10 | @Builder @NoArgsConstructor @AllArgsConstructor 11 | public class Account { 12 | 13 | @Id @GeneratedValue 14 | private Integer id; 15 | 16 | @Column(unique = true) 17 | private String email; 18 | 19 | private String password; 20 | 21 | @ElementCollection(fetch = FetchType.EAGER) 22 | @Enumerated(EnumType.STRING) 23 | private Set roles; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/accounts/AccountAdapter.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.accounts; 2 | 3 | import lombok.Getter; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 6 | import org.springframework.security.core.userdetails.User; 7 | 8 | import java.util.Collection; 9 | import java.util.Set; 10 | import java.util.stream.Collectors; 11 | 12 | public class AccountAdapter extends User { 13 | 14 | private Account account; 15 | 16 | public AccountAdapter(Account account) { 17 | super(account.getEmail(), account.getPassword(), authorities(account.getRoles())); 18 | this.account = account; 19 | } 20 | 21 | private static Collection authorities(Set roles) { 22 | return roles.stream() 23 | .map(r -> new SimpleGrantedAuthority("ROLE_" + r.name())) 24 | .collect(Collectors.toSet()); 25 | } 26 | 27 | public Account getAccount() { 28 | return account; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/accounts/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.accounts; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import java.util.Optional; 6 | 7 | public interface AccountRepository extends JpaRepository { 8 | Optional findByEmail(String username); 9 | } 10 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/accounts/AccountRole.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.accounts; 2 | 3 | public enum AccountRole { 4 | 5 | ADMIN, USER 6 | } 7 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/accounts/AccountSerializer.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.accounts; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | 7 | import java.io.IOException; 8 | 9 | public class AccountSerializer extends JsonSerializer { 10 | 11 | @Override 12 | public void serialize(Account account, JsonGenerator gen, SerializerProvider serializers) throws IOException { 13 | gen.writeStartObject(); 14 | gen.writeNumberField("id", account.getId()); 15 | gen.writeEndObject(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/accounts/AccountService.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.accounts; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.core.userdetails.UserDetails; 5 | import org.springframework.security.core.userdetails.UserDetailsService; 6 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 7 | import org.springframework.security.crypto.password.PasswordEncoder; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class AccountService implements UserDetailsService { 12 | 13 | @Autowired 14 | AccountRepository accountRepository; 15 | 16 | @Autowired 17 | PasswordEncoder passwordEncoder; 18 | 19 | public Account saveAccount(Account account) { 20 | account.setPassword(this.passwordEncoder.encode(account.getPassword())); 21 | return this.accountRepository.save(account); 22 | } 23 | 24 | @Override 25 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 26 | Account account = accountRepository.findByEmail(username) 27 | .orElseThrow(() -> new UsernameNotFoundException(username)); 28 | return new AccountAdapter(account); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/accounts/CurrentUser.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.accounts; 2 | 3 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target(ElementType.PARAMETER) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account") 13 | public @interface CurrentUser { 14 | } 15 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/common/AppProperties.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.common; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.validation.constraints.NotEmpty; 9 | 10 | @Component 11 | @ConfigurationProperties(prefix = "my-app") 12 | @Getter @Setter 13 | public class AppProperties { 14 | 15 | @NotEmpty 16 | private String adminUsername; 17 | 18 | @NotEmpty 19 | private String adminPassword; 20 | 21 | @NotEmpty 22 | private String userUsername; 23 | 24 | @NotEmpty 25 | private String userPassword; 26 | 27 | @NotEmpty 28 | private String clientId; 29 | 30 | @NotEmpty 31 | private String clientSecret; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/common/ErrorsResource.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.common; 2 | 3 | import me.whiteship.demoinfleanrestapi.index.IndexController; 4 | import org.springframework.hateoas.EntityModel; 5 | import org.springframework.hateoas.Link; 6 | import org.springframework.validation.Errors; 7 | 8 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; 9 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; 10 | 11 | public class ErrorsResource extends EntityModel { 12 | public ErrorsResource(Errors content, Link... links) { 13 | super(content, links); 14 | add(linkTo(methodOn(IndexController.class).index()).withRel("index")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/common/ErrorsSerializer.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.common; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | import org.springframework.boot.jackson.JsonComponent; 7 | import org.springframework.validation.Errors; 8 | 9 | import java.io.IOException; 10 | 11 | @JsonComponent 12 | public class ErrorsSerializer extends JsonSerializer { 13 | @Override 14 | public void serialize(Errors errors, JsonGenerator gen, SerializerProvider serializers) throws IOException { 15 | gen.writeStartArray(); 16 | errors.getFieldErrors().forEach(e -> { 17 | try { 18 | gen.writeStartObject(); 19 | gen.writeStringField("field", e.getField()); 20 | gen.writeStringField("objectName", e.getObjectName()); 21 | gen.writeStringField("code", e.getCode()); 22 | gen.writeStringField("defaultMessage", e.getDefaultMessage()); 23 | Object rejectedValue = e.getRejectedValue(); 24 | if (rejectedValue != null) { 25 | gen.writeStringField("rejectedValue", rejectedValue.toString()); 26 | } 27 | gen.writeEndObject(); 28 | } catch (IOException e1) { 29 | e1.printStackTrace(); 30 | } 31 | }); 32 | 33 | errors.getGlobalErrors().forEach(e -> { 34 | try { 35 | gen.writeStartObject(); 36 | gen.writeStringField("objectName", e.getObjectName()); 37 | gen.writeStringField("code", e.getCode()); 38 | gen.writeStringField("defaultMessage", e.getDefaultMessage()); 39 | gen.writeEndObject(); 40 | } catch (IOException e1) { 41 | e1.printStackTrace(); 42 | } 43 | }); 44 | gen.writeEndArray(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/common/TestDescription.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.common; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.SOURCE) 10 | public @interface TestDescription { 11 | 12 | String value(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/configs/AppConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.configs; 2 | 3 | import me.whiteship.demoinfleanrestapi.accounts.Account; 4 | import me.whiteship.demoinfleanrestapi.accounts.AccountRepository; 5 | import me.whiteship.demoinfleanrestapi.accounts.AccountRole; 6 | import me.whiteship.demoinfleanrestapi.accounts.AccountService; 7 | import me.whiteship.demoinfleanrestapi.common.AppProperties; 8 | import org.modelmapper.ModelMapper; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.ApplicationArguments; 11 | import org.springframework.boot.ApplicationRunner; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 15 | import org.springframework.security.crypto.password.PasswordEncoder; 16 | 17 | import java.util.Set; 18 | 19 | @Configuration 20 | public class AppConfig { 21 | 22 | @Bean 23 | public ModelMapper modelMapper() { 24 | return new ModelMapper(); 25 | } 26 | 27 | @Bean 28 | public PasswordEncoder passwordEncoder() { 29 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 30 | } 31 | 32 | @Bean 33 | public ApplicationRunner applicationRunner() { 34 | return new ApplicationRunner() { 35 | 36 | @Autowired 37 | AccountService accountService; 38 | 39 | @Autowired 40 | AppProperties appProperties; 41 | 42 | @Override 43 | public void run(ApplicationArguments args) throws Exception { 44 | Account admin = Account.builder() 45 | .email(appProperties.getAdminUsername()) 46 | .password(appProperties.getAdminPassword()) 47 | .roles(Set.of(AccountRole.ADMIN, AccountRole.USER)) 48 | .build(); 49 | accountService.saveAccount(admin); 50 | 51 | Account user = Account.builder() 52 | .email(appProperties.getUserUsername()) 53 | .password(appProperties.getUserPassword()) 54 | .roles(Set.of(AccountRole.USER)) 55 | .build(); 56 | accountService.saveAccount(user); 57 | } 58 | }; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/configs/AuthServerConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.configs; 2 | 3 | import me.whiteship.demoinfleanrestapi.accounts.AccountService; 4 | import me.whiteship.demoinfleanrestapi.common.AppProperties; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.authentication.AuthenticationManager; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; 10 | import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; 11 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; 12 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; 13 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; 14 | import org.springframework.security.oauth2.provider.token.TokenStore; 15 | 16 | @Configuration 17 | @EnableAuthorizationServer 18 | public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { 19 | 20 | @Autowired 21 | PasswordEncoder passwordEncoder; 22 | 23 | @Autowired 24 | AuthenticationManager authenticationManager; 25 | 26 | @Autowired 27 | AccountService accountService; 28 | 29 | @Autowired 30 | TokenStore tokenStore; 31 | 32 | @Autowired 33 | AppProperties appProperties; 34 | 35 | @Override 36 | public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { 37 | security.passwordEncoder(passwordEncoder); 38 | } 39 | 40 | @Override 41 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception { 42 | clients.inMemory() 43 | .withClient(appProperties.getClientId()) 44 | .authorizedGrantTypes("password", "refresh_token") 45 | .scopes("read", "write") 46 | .secret(this.passwordEncoder.encode(appProperties.getClientSecret())) 47 | .accessTokenValiditySeconds(10 * 60) 48 | .refreshTokenValiditySeconds(6 * 10 * 60); 49 | } 50 | 51 | @Override 52 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { 53 | endpoints.authenticationManager(authenticationManager) 54 | .userDetailsService(accountService) 55 | .tokenStore(tokenStore); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/configs/ResourceServerConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.configs; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.http.HttpMethod; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 7 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 8 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; 9 | import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; 10 | 11 | @Configuration 12 | @EnableResourceServer 13 | public class ResourceServerConfig extends ResourceServerConfigurerAdapter { 14 | 15 | @Override 16 | public void configure(ResourceServerSecurityConfigurer resources) throws Exception { 17 | resources.resourceId("event"); 18 | } 19 | 20 | @Override 21 | public void configure(HttpSecurity http) throws Exception { 22 | http 23 | .anonymous() 24 | .and() 25 | .authorizeRequests() 26 | .mvcMatchers(HttpMethod.GET, "/api/**") 27 | .permitAll() 28 | .anyRequest() 29 | .authenticated() 30 | .and() 31 | .exceptionHandling() 32 | .accessDeniedHandler(new OAuth2AccessDeniedHandler()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/configs/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.configs; 2 | 3 | import me.whiteship.demoinfleanrestapi.accounts.AccountService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.autoconfigure.security.servlet.PathRequest; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 10 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.security.oauth2.provider.token.TokenStore; 15 | import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; 16 | 17 | @Configuration 18 | @EnableWebSecurity 19 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 20 | 21 | @Autowired 22 | AccountService accountService; 23 | 24 | @Autowired 25 | PasswordEncoder passwordEncoder; 26 | 27 | @Bean 28 | public TokenStore tokenStore() { 29 | return new InMemoryTokenStore(); 30 | } 31 | 32 | @Bean 33 | @Override 34 | public AuthenticationManager authenticationManagerBean() throws Exception { 35 | return super.authenticationManagerBean(); 36 | } 37 | 38 | @Override 39 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 40 | auth.userDetailsService(accountService) 41 | .passwordEncoder(passwordEncoder); 42 | } 43 | 44 | @Override 45 | public void configure(WebSecurity web) throws Exception { 46 | web.ignoring().mvcMatchers("/docs/index.html"); 47 | web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations()); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/events/Event.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.events; 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 4 | import lombok.*; 5 | import me.whiteship.demoinfleanrestapi.accounts.Account; 6 | import me.whiteship.demoinfleanrestapi.accounts.AccountSerializer; 7 | 8 | import javax.persistence.*; 9 | import java.time.LocalDateTime; 10 | 11 | @Builder @AllArgsConstructor @NoArgsConstructor 12 | @Getter @Setter @EqualsAndHashCode(of = "id") 13 | @Entity 14 | public class Event { 15 | 16 | @Id @GeneratedValue 17 | private Integer id; 18 | private String name; 19 | private String description; 20 | private LocalDateTime beginEnrollmentDateTime; 21 | private LocalDateTime closeEnrollmentDateTime; 22 | private LocalDateTime beginEventDateTime; 23 | private LocalDateTime endEventDateTime; 24 | private String location; // (optional) 이게 없으면 온라인 모임 25 | private int basePrice; // (optional) 26 | private int maxPrice; // (optional) 27 | private int limitOfEnrollment; 28 | private boolean offline; 29 | private boolean free; 30 | @Enumerated(EnumType.STRING) 31 | private EventStatus eventStatus = EventStatus.DRAFT; 32 | @ManyToOne 33 | @JsonSerialize(using = AccountSerializer.class) 34 | private Account manager; 35 | 36 | public void update() { 37 | // Update free 38 | if (this.basePrice == 0 && this.maxPrice == 0) { 39 | this.free = true; 40 | } else { 41 | this.free = false; 42 | } 43 | // Update offline 44 | if (this.location == null || this.location.isBlank()) { 45 | this.offline = false; 46 | } else { 47 | this.offline = true; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/events/EventDto.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.events; 2 | 3 | import lombok.*; 4 | 5 | import javax.validation.constraints.Min; 6 | import javax.validation.constraints.NotEmpty; 7 | import javax.validation.constraints.NotNull; 8 | import java.time.LocalDateTime; 9 | 10 | @Data @Builder @NoArgsConstructor @AllArgsConstructor 11 | public class EventDto { 12 | 13 | @NotEmpty 14 | private String name; 15 | @NotEmpty 16 | private String description; 17 | @NotNull 18 | private LocalDateTime beginEnrollmentDateTime; 19 | @NotNull 20 | private LocalDateTime closeEnrollmentDateTime; 21 | @NotNull 22 | private LocalDateTime beginEventDateTime; 23 | @NotNull 24 | private LocalDateTime endEventDateTime; 25 | private String location; // (optional) 이게 없으면 온라인 모임 26 | @Min(0) 27 | private int basePrice; // (optional) 28 | @Min(0) 29 | private int maxPrice; // (optional) 30 | @Min(0) 31 | private int limitOfEnrollment; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/events/EventRepository.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.events; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface EventRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/events/EventResource.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.events; 2 | 3 | import org.springframework.hateoas.EntityModel; 4 | import org.springframework.hateoas.Link; 5 | 6 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; 7 | 8 | public class EventResource extends EntityModel { 9 | 10 | public EventResource(Event event, Link... links) { 11 | super(event, links); 12 | add(linkTo(EventController.class).slash(event.getId()).withSelfRel()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/events/EventStatus.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.events; 2 | 3 | public enum EventStatus { 4 | 5 | DRAFT, PUBLISHED, BEGAN_ENROLLMENT; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/events/EventValidator.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.events; 2 | 3 | import org.springframework.stereotype.Component; 4 | import org.springframework.validation.Errors; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | @Component 9 | public class EventValidator { 10 | 11 | public void validate(EventDto eventDto, Errors errors) { 12 | if (eventDto.getBasePrice() > eventDto.getMaxPrice() && eventDto.getMaxPrice() > 0) { 13 | errors.reject("wrongPrices", "Values fo prices are wrong"); 14 | } 15 | 16 | LocalDateTime endEventDateTime = eventDto.getEndEventDateTime(); 17 | if (endEventDateTime.isBefore(eventDto.getBeginEventDateTime()) || 18 | endEventDateTime.isBefore(eventDto.getCloseEnrollmentDateTime()) || 19 | endEventDateTime.isBefore(eventDto.getBeginEnrollmentDateTime())) { 20 | errors.rejectValue("endEventDateTime", "wrongValue", "endEventDateTime is wrong"); 21 | } 22 | 23 | // TODO BeginEventDateTime 24 | // TODO CloseEnrollmentDateTime 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/java/me/whiteship/demoinfleanrestapi/index/IndexController.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.index; 2 | 3 | import me.whiteship.demoinfleanrestapi.events.EventController; 4 | import org.springframework.hateoas.RepresentationModel; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; 9 | 10 | @RestController 11 | public class IndexController { 12 | 13 | @GetMapping("/api") 14 | public RepresentationModel index() { 15 | var index = new RepresentationModel(); 16 | index.add(linkTo(EventController.class).withRel("events")); 17 | return index; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.jackson.deserialization.fail-on-unknown-properties=true 2 | 3 | spring.datasource.username=postgres 4 | spring.datasource.password=pass 5 | spring.datasource.url=jdbc:postgresql://localhost:5432/postgres 6 | spring.datasource.driver-class-name=org.postgresql.Driver 7 | 8 | spring.jpa.hibernate.ddl-auto=create-drop 9 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true 10 | spring.jpa.properties.hibernate.format_sql=true 11 | 12 | logging.level.org.hibernate.SQL=DEBUG 13 | logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE 14 | 15 | logging.level.org.springframework.security=DEBUG 16 | 17 | my-app.admin-username=admin@email.com 18 | my-app.admin-password=admin 19 | my-app.user-username=user@email.com 20 | my-app.user-password=user 21 | my-app.client-id=myApp 22 | my-app.client-secret=pass 23 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/test/java/me/whiteship/demoinfleanrestapi/accounts/AccountServiceTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.accounts; 2 | 3 | import me.whiteship.demoinfleanrestapi.common.BaseTest; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 9 | import org.springframework.security.crypto.password.PasswordEncoder; 10 | 11 | import java.util.Set; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.junit.jupiter.api.Assertions.assertThrows; 15 | 16 | public class AccountServiceTest extends BaseTest { 17 | 18 | @Autowired 19 | AccountService accountService; 20 | 21 | @Autowired 22 | PasswordEncoder passwordEncoder; 23 | 24 | @Test 25 | public void findByUsername() { 26 | // Given 27 | String password = "keesun"; 28 | String username = "keesun@email.com"; 29 | Account account = Account.builder() 30 | .email(username) 31 | .password(password) 32 | .roles(Set.of(AccountRole.ADMIN, AccountRole.USER)) 33 | .build(); 34 | this.accountService.saveAccount(account); 35 | 36 | // When 37 | UserDetailsService userDetailsService = accountService; 38 | UserDetails userDetails = userDetailsService.loadUserByUsername(username); 39 | 40 | // Then 41 | assertThat(this.passwordEncoder.matches(password, userDetails.getPassword())).isTrue(); 42 | } 43 | 44 | @Test 45 | public void findByUsernameFail() { 46 | assertThrows(UsernameNotFoundException.class, () -> accountService.loadUserByUsername("random@email.com")); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /rest-api-with-spring/src/test/java/me/whiteship/demoinfleanrestapi/common/BaseTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.common; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.junit.jupiter.api.Disabled; 5 | import org.modelmapper.ModelMapper; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; 8 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.context.annotation.Import; 11 | import org.springframework.test.context.ActiveProfiles; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | 14 | @SpringBootTest 15 | @AutoConfigureMockMvc 16 | @AutoConfigureRestDocs 17 | @Import(RestDocsConfiguration.class) 18 | @ActiveProfiles("test") 19 | @Disabled 20 | public class BaseTest { 21 | 22 | @Autowired 23 | protected MockMvc mockMvc; 24 | 25 | @Autowired 26 | protected ObjectMapper objectMapper; 27 | 28 | @Autowired 29 | protected ModelMapper modelMapper; 30 | } 31 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/test/java/me/whiteship/demoinfleanrestapi/common/RestDocsConfiguration.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.common; 2 | 3 | import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer; 4 | import org.springframework.boot.test.context.TestConfiguration; 5 | import org.springframework.context.annotation.Bean; 6 | 7 | import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; 8 | 9 | @TestConfiguration 10 | public class RestDocsConfiguration { 11 | 12 | @Bean 13 | public RestDocsMockMvcConfigurationCustomizer restDocsMockMvcConfigurationCustomizer() { 14 | return configurer -> configurer.operationPreprocessors() 15 | .withRequestDefaults(prettyPrint()) 16 | .withResponseDefaults(prettyPrint()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/test/java/me/whiteship/demoinfleanrestapi/configs/AuthServerConfigTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.configs; 2 | 3 | import me.whiteship.demoinfleanrestapi.accounts.AccountService; 4 | import me.whiteship.demoinfleanrestapi.common.AppProperties; 5 | import me.whiteship.demoinfleanrestapi.common.BaseTest; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | 10 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; 11 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 12 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 13 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 15 | 16 | public class AuthServerConfigTest extends BaseTest { 17 | 18 | @Autowired 19 | AccountService accountService; 20 | 21 | @Autowired 22 | AppProperties appProperties; 23 | 24 | @Test 25 | @DisplayName("인증 토큰을 발급 받는 테스트") 26 | public void getAuthToken() throws Exception { 27 | this.mockMvc.perform(post("/oauth/token") 28 | .with(httpBasic(appProperties.getClientId(), appProperties.getClientSecret())) 29 | .param("username", appProperties.getUserUsername()) 30 | .param("password", appProperties.getUserPassword()) 31 | .param("grant_type", "password")) 32 | .andDo(print()) 33 | .andExpect(status().isOk()) 34 | .andExpect(jsonPath("access_token").exists()); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /rest-api-with-spring/src/test/java/me/whiteship/demoinfleanrestapi/events/EventTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.events; 2 | 3 | import junitparams.JUnitParamsRunner; 4 | import junitparams.Parameters; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | @RunWith(JUnitParamsRunner.class) 11 | public class EventTest { 12 | 13 | @Test 14 | public void builder() { 15 | Event event = Event.builder() 16 | .name("Inflearn Spring REST API") 17 | .description("REST API development with Spring") 18 | .build(); 19 | assertThat(event).isNotNull(); 20 | } 21 | 22 | @Test 23 | public void javaBean() { 24 | // Given 25 | String name = "Event"; 26 | String description = "Spring"; 27 | 28 | // When 29 | Event event = new Event(); 30 | event.setName(name); 31 | event.setDescription(description); 32 | 33 | // Then 34 | assertThat(event.getName()).isEqualTo(name); 35 | assertThat(event.getDescription()).isEqualTo(description); 36 | } 37 | 38 | @Test 39 | @Parameters 40 | public void testFree(int basePrice, int maxPrice, boolean isFree) { 41 | // Given 42 | Event event = Event.builder() 43 | .basePrice(basePrice) 44 | .maxPrice(maxPrice) 45 | .build(); 46 | 47 | // When 48 | event.update(); 49 | 50 | // Then 51 | assertThat(event.isFree()).isEqualTo(isFree); 52 | } 53 | 54 | private Object[] parametersForTestFree() { 55 | return new Object[] { 56 | new Object[] {0, 0, true}, 57 | new Object[] {100, 0, false}, 58 | new Object[] {0, 100, false}, 59 | new Object[] {100, 200, false} 60 | }; 61 | } 62 | 63 | @Test 64 | @Parameters 65 | public void testOffline(String location, boolean isOffline) { 66 | // Given 67 | Event event = Event.builder() 68 | .location(location) 69 | .build(); 70 | 71 | // When 72 | event.update(); 73 | 74 | // Then 75 | assertThat(event.isOffline()).isEqualTo(isOffline); 76 | } 77 | 78 | private Object[] parametersForTestOffline() { 79 | return new Object[] { 80 | new Object[] {"강남", true}, 81 | new Object[] {null, false}, 82 | new Object[] {" ", false} 83 | }; 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /rest-api-with-spring/src/test/java/me/whiteship/demoinfleanrestapi/index/IndexControllerTest.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.demoinfleanrestapi.index; 2 | 3 | import me.whiteship.demoinfleanrestapi.common.BaseTest; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 7 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 8 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 9 | 10 | public class IndexControllerTest extends BaseTest { 11 | 12 | @Test 13 | public void index() throws Exception { 14 | this.mockMvc.perform(get("/api/")) 15 | .andExpect(status().isOk()) 16 | .andExpect(jsonPath("_links.events").exists()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /rest-api-with-spring/src/test/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.username=sa 2 | spring.datasource.password= 3 | spring.datasource.url=jdbc:h2:mem:testdb 4 | spring.datasource.driver-class-name=org.h2.Driver 5 | 6 | spring.datasource.hikari.jdbc-url=jdbc:h2:mem:testdb 7 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect 8 | 9 | -------------------------------------------------------------------------------- /spring-data-jpa-reference-coding.md: -------------------------------------------------------------------------------- 1 | # 스프링 데이터 JPA 레퍼런스 코딩 2 | 3 | ## 참고 4 | 5 | [스프링 데이터 JPA 레퍼런스](https://docs.spring.io/spring-data/jpa/docs/2.0.7.RELEASE/reference/html/) 6 | 7 | ## 유툽 인덱스 8 | 9 | ### 스프링 데이터 JPA Day 1. 주요 인터페이스 자세히 살펴보기 10 | 11 | [![스프링 데이터 JPA Day 1. 주요 인터페이스 자세히 살펴보기](https://img.youtube.com/vi/ZDi5_DZjc88/0.jpg)](https://youtu.be/ZDi5_DZjc88) 12 | 13 | 스프링 데이터 JPA 레피런스를 보기 시작했습니다. 이번에는 처음부터 3.2장까지 살펴봤습니다. 14 | 15 | https://docs.spring.io/spring-data/jpa/docs/2.0.7.RELEASE/reference/html/ 16 | 17 | 의존성 관리는 스프링 부트를 쓰는 방법과 스프링 데이터의 릴리즈 트레인을 사용하는 방법이 있는데, 사실상 스프링 부트도 스프링 데이터의 릴리즈 트레인을 사용합니다. 데모로 스프링 부트에서 데이터 버전 커스터마이징 하는 방법을 보여드렸습니다. 18 | 19 | 중요한 내용은 JpaRepository - PagingAndSortingRepository - CrudRepository - Repository 이러한 상속 구조와 각 인터페이스의 역할을 이해하는 것인데요. 20 | 21 | 젤 밑단에 있는 영속화 기술에 특화된 JpaRepository를 빼면 나머진 다른 영속화 기술용 리파지토리에서도 재사용하는 공용 인터페이스들 입니다. 그 인터페이스들을 자세히 살펴보면서 설명해 드렸습니다. 22 | 23 | 간단한 예제로 Book이라는 엔티티 추가하고 findByIsbn이라는 쿼리 메서드 쿼리 만드는 것을 보여드렸습니다. 24 | 25 | ### 스프링 데이터 JPA Day 2. 상속 없이도 리포지토리 만들 수 있다 26 | 27 | [![스프링 데이터 JPA Day 2. 상속 없이도 리포지토리 만들 수 있다](https://img.youtube.com/vi/1K_SWhn-Yps/0.jpg)](https://youtu.be/1K_SWhn-Yps) 28 | 29 | 스프링 데이터 JPA 레퍼런스 2.3을 살펴봤습니다. 30 | 31 | Repository 인터페이스를 상속해서 만드는 방법 말고, 애노테이션을 사용해서 만드는 방법도 있는데.. 가능은 하지만 사실상 낚시성 제목에나 쓰라고 만들어 둔 기능 같다는 생각이 드네요. 32 | 33 | 그보다 사실 훨씬 중요한 내용은 스프링 프레임워크 5에 도입된 널 처리 관련 애노테이션들을 사용할 수 있다는 겁니다. 34 | 35 | https://docs.spring.io/spring/docs/5.0.5.RELEASE/spring-framework-reference/core.html#null-safety 36 | 37 | 레퍼런스에 설명이 약간 오묘하면서 이상한 부분이 있어서 코딩으로 확인해 봤는데 아니나 다를까... 예상 했던 대로 동작했구요. 38 | 39 | 마지막으로 여러가지 영속화 기술을 사용할 때, 즉, JPA도 쓰고 몽고 DB도 쓰는 경우에 해당 기술에 특화된 인터페이스의 리포지토리를 상속 받으시는게 좋습니다. 아니면 맨 마지막에 나온 코드 예제처럼 해당 기술의 베이스 패키지를 각기 다른 패키지로 지정해 주거나요.. (이건 다음 시간에 코딩으로 확인해 보겠습니다.) 40 | 41 | ### 스프링 데이터 JPA Day 3. 쿼리 메서드 만들기 42 | 43 | [![스프링 데이터 JPA Day 3. 쿼리 메서드 만들기](https://img.youtube.com/vi/nwDeGsXn01I/0.jpg)](https://youtu.be/nwDeGsXn01I) 44 | 45 | 스프링 데이터 JPA 레퍼런스 2.4를 보기 시작했습니다. 46 | 47 | 메서드를 보고 쿼리를 만드는 방법이 크게 두가지 있습니다. 메서드 이름을 보고 쿼리를 만드는 방법과, 메서드에 있는 어떠한 정보를 (보통 애노테이션이겠죠?) 바탕으로 개발자가 직접 정의한 쿼리를 가져오는 방법. 48 | 49 | 그럼 그 두가지 방법 중에 어떤 방법으로 가져와야 하는지.. 그게 이제 스토리지 마다 달라 질 수 있는데, 세가지 전략과 그 중에 기본으로 설정되어있는 전략은 무엇인지 또 어떻게 그 전략을 바꿀 수 있는지 살펴봤습니다. -------------------------------------------------------------------------------- /springdockerdemo/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /springdockerdemo/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/springdockerdemo/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /springdockerdemo/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip 2 | -------------------------------------------------------------------------------- /springdockerdemo/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jre 2 | COPY target/springdockerdemo-*.jar app.jar 3 | ENTRYPOINT ["java", "-jar", "app.jar"] 4 | -------------------------------------------------------------------------------- /springdockerdemo/descrypted.txt: -------------------------------------------------------------------------------- 1 | spring boot 2 | -------------------------------------------------------------------------------- /springdockerdemo/file.ssl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/springdockerdemo/file.ssl -------------------------------------------------------------------------------- /springdockerdemo/file.txt: -------------------------------------------------------------------------------- 1 | spring boot 2 | -------------------------------------------------------------------------------- /springdockerdemo/keystore.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keesun/study/7f47b9ae5c1b25be31a7e1b0fe7602cc70017031/springdockerdemo/keystore.p12 -------------------------------------------------------------------------------- /springdockerdemo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | me.whiteship 7 | springdockerdemo 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | springdockerdemo 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.3.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-test 36 | test 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-maven-plugin 45 | 46 | 47 | io.fabric8 48 | docker-maven-plugin 49 | 0.26.0 50 | 51 | 52 | 53 | whiteship/springdockerdemo 54 | 55 | ${basedir} 56 | 57 | 58 | 59 | 60 | 61 | 62 | docker-build 63 | package 64 | 65 | build 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /springdockerdemo/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQC8qt+olXVoq5E/x0DXG8N+PeVls82Y4VNtigC9Oc0CWTf00hI7 3 | nIl3N1yL+Z9fzRVDgGgbeUzQMv5c4G7D8mpW+6iDOxoXQq4Kq3cSEedICCZJqHbq 4 | mLAfT1j9KhG6eDLF7HZORdWz9DuVdtzSFdLjOkImHeEDePON7vAE+uxjswIDAQAB 5 | AoGADC7ckn5UPpYVoxCy1zErxpMopRCfTif+wywOMCnzWxt3yY9nLgJFvjUM/Nz5 6 | ta4AmYNJNbz3gNpKIqU7gdu9khP6tMjqEbALUwP73OXuU7I5ctOLLI6pIUDvblig 7 | qdKS/yvTdegPwLYdvvTxsL12l+VYxmp/6DSDRLSZD1Q6dYECQQDf9QPQlynZvwPp 8 | zxXZnQsTkJxA76oRZ0995uY0nMMqIJvrckzXdlNSq9L2HobCHeRq3aIhWohTDakJ 9 | bpHpQtn5AkEA16lJ9uvIgNO9uX9IFrQe6xQpeRIiaeeL8VgANeE39LxnDPRqOzel 10 | 9lfM/Jc8YFReY+tOEmdYhYGlgm9OdEa2CwJAUJ+a/Pe+SDY8yWoUmp+vgh6YMcRV 11 | vCgt9Mwv6ZbZp4vPtcYTJaniOyvCKXo51x39wf8Bw/Ici5GqXcvhWVEIuQJAK4Ts 12 | +RgBgQW5jnpB6Nr2Nkf+SoE/UpG4Kr6wZC8LpKZ0QSf9W+/R3NFA+2PleibsWUt9 13 | mLckJOTRqm2drlcqmwJBAJf6aIjoP7WsQZ+76PvRfe0jXxxE5KHlj37fUzfVE9vb 14 | 6Y7O0277j3HojECdT5aYfcRlTt0mhaUBSqxWFTYjeWE= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /springdockerdemo/public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8qt+olXVoq5E/x0DXG8N+PeVl 3 | s82Y4VNtigC9Oc0CWTf00hI7nIl3N1yL+Z9fzRVDgGgbeUzQMv5c4G7D8mpW+6iD 4 | OxoXQq4Kq3cSEedICCZJqHbqmLAfT1j9KhG6eDLF7HZORdWz9DuVdtzSFdLjOkIm 5 | HeEDePON7vAE+uxjswIDAQAB 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /springdockerdemo/src/main/java/me/whiteship/springdockerdemo/SpringdockerdemoApplication.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.springdockerdemo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @SpringBootApplication 9 | @RestController 10 | public class SpringdockerdemoApplication { 11 | 12 | @GetMapping("/") 13 | public String hello() { 14 | return "Hello Spring Boot"; 15 | } 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(SpringdockerdemoApplication.class, args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /springdockerdemo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.ssl.key-store: keystore.p12 2 | server.ssl.key-store-password: 123456 3 | server.ssl.keyStoreType: PKCS12 4 | server.ssl.keyAlias: tomcat -------------------------------------------------------------------------------- /springdockerdemo/src/test/java/me/whiteship/springdockerdemo/SpringdockerdemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package me.whiteship.springdockerdemo; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringdockerdemoApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------