├── .gitignore ├── Dockerfile ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── alarm │ │ ├── AlarmServiceApplication.java │ │ ├── cache │ │ └── CacheManagerCheck.java │ │ ├── domain │ │ └── Notice.java │ │ ├── repository │ │ ├── .NoticeRepository.java.swp │ │ └── NoticeRepository.java │ │ ├── rest │ │ └── NoticeController.java │ │ └── service │ │ ├── NoticeService.java │ │ └── NoticeServiceImpl.java └── resources │ ├── application.yml │ └── bootstrap.yml └── test └── java └── com └── alarm └── AlarmServiceApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jdk-alpine 2 | VOLUME /tmp 3 | #ARG JAR_FILE 4 | #ADD ${JAR_FILE} app.jar 5 | ADD ./target/notice-service-0.0.1.jar app.jar 6 | ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![HitCount](http://hits.dwyl.io/phantasmicmeans/springboot-microservice-with-spring-cloud-netflix.svg)](http://hits.dwyl.io/phantasmicmeans/springboot-microservice-with-spring-cloud-netflix) 2 | 3 | Spring Boot Microservice with Spring Cloud Netflix 4 | ============== 5 | 6 | *by S.M.Lee* 7 | 8 | ![image](https://user-images.githubusercontent.com/20153890/41583901-d1e8aa5c-73e0-11e8-97ff-188fed3cd715.png) 9 | 10 | 11 | > **NOTE** 12 |   13 | 14 | > - 여기서는 MSA에서의 Service중 하나인 Notice Service를 구축하여 본다. 15 | > - Notice Service는 간단한 REST API Server로 구성되고, Spring Cloud Netflix의 여러 component들을 활용한다. 16 | > - Notice Service는 Spring boot Project로 구현된다. 생성된 JAR파일을 Docker container로 띄워 서비스한다. 17 | > - 기존 Spring에서는 Maven, Gradle등의 dependency tool을 이용해 WAR파일을 생성한 후 tomcat같은 WAS에 배포하여 18 | 웹 어플리케이션을 구동하였으나, Spring boot는 JAR파일에 내장 tomcat이 존재하여, 단순히 JAR파일을 빌드하고 실행하는 것 만으로 웹 어플리케이션 구동이 가능하다. 19 | > - JPA repository로 DB(MySQL 5.6)에 접근한다. 20 | 21 |   22 |   23 | 24 | ## Service Description ## 25 | 26 | 27 | 28 | **Project directory tree** 29 | 30 | . 31 | ├── Dockerfile 32 | ├── mvnw 33 | ├── mvnw.cmd 34 | ├── pom.xml 35 | ├── src/main/java/com/example/demo 36 | | | ├── AlarmServiceApplication.java 37 | | | ├── domain 38 | | | | └── Notice.java 39 | | | ├── repository 40 | | | │ └── NoticeRepository.java 41 | | | ├── rest 42 | | | │ └── NoticeController.java 43 | | | ├── service 44 | | | ├── NoticeService.java 45 | | | └── NoticeServiceImpl.java 46 | │      └── resources 47 | │      ├── application.yml 48 | │      └── bootstrap.yml 49 | └── target 50 | ├── classes 51 | ├── notice-service-0.1.0.jar 52 | ├── notice-service.0.1.0.jar.original 53 | ├── generated-sources ... 54 | 55 | 56 |   57 | 58 | **Service는 "Service Register & Discovery" Server인 Eureka Server의 Client이다.** 59 | 60 | 61 | 진행하기에 앞서 Eureka에 대한 이해가 필요하다. Hystrix는 다음 장에서 다룰 예정이지만, Eureka에 대한 이해는 필수적이다. 62 | 하지만 단순히 REST API Server 구축이 목표라면 스킵하고 진행해도 된다. 63 | 64 | > - *Netflix의 Eureka에 대한 이해 => https://github.com/phantasmicmeans/Spring-Cloud-Netflix-Eureka-Tutorial/* 65 | > - *Hystrix에 대한 이해 => https://github.com/phantasmicmeans/Spring-Cloud-Netflix-Hystrix/* 66 | > - *Service Registration and Discovery => https://spring.io/guides/gs/service-registration-and-discovery/* 67 | > - *Service Discovery: Eureka Clients =>https://cloud.spring.io/spring-cloud-netflix/multi/multi__service_discovery_eureka_clients.html* 68 | 69 | 위 reference를 모두 읽고 이 튜토리얼을 진행하면 순탄하게 진행할 수 있을 것이다. 70 | 71 |   72 | 73 | 어쨌든 Eureka Client로 만들어진 Microservice는 Eureka Server(Registry)에 자신의 meta-data(host,port,address 등)를 전송한다. 이로인해 Eureka Client들은 Eureka Registry 정보를 이용해 서로간의 Communication이 가능하다. 74 | 75 | 그리고 Eureka Client는 자신이 살아 있음을 알리는 hearbeat를 Eureka Server에 보낸다. Eureka Server는 일정한 시간안에 hearbeat를 받지 못하면 Registry로 부터 Client의 정보를 제거한다. 76 | 77 | Eureka Client는 Registry에 자신의 hostname을 등록하게 되는데 이는 DNS 역할을 하며, 추후에 Netflix의 API Gateway에서 Ribbon + Hystrix + Eureka 조합을 적절히 활용하여 편하게 Dynamic Routing 시킬 수 있다. 78 | 79 | 큰 개념은 이정도로 이해하고 일단 Server를 구축하고 Eureka Client로 만들어보자. 80 | 81 |   82 |   83 | 84 | ## 1. Dependency ## 85 | 86 | Eureka Client로 service를 만들기 위해 spring-cloud-starter-netflix-eureka-client dependency를 추가한다. 그리고 hystrix 적용을 위해 hystrix dependency를 추가한다. 그리고 dockerfile-maven-plugin 또한 추가한다. 87 | 88 | 89 | **pom.xml** 90 | 91 | 92 | ```xml 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-starter-parent 97 | 2.0.1.RELEASE 98 | 99 | 100 | 101 | 102 | UTF-8 103 | UTF-8 104 | 1.8 105 | 1.8 106 | 1.8 107 | Finchley.M9 108 | phantasmicmeans 109 | 110 | 111 | 112 | 113 | org.springframework.cloud 114 | spring-cloud-starter-netflix-eureka-client 115 | 116 | 117 | org.springframework.cloud 118 | spring-cloud-starter-netflix-hystrix 119 | 120 | 121 | org.springframework.boot 122 | spring-boot-starter-actuator 123 | 124 | 125 | org.springframework.boot 126 | spring-boot-starter-web 127 | 128 | 129 | org.springframework.boot 130 | spring-boot-starter-data-jpa 131 | 132 | 133 | mysql 134 | mysql-connector-java 135 | 5.1.21 136 | 137 | 138 | org.springframework.boot 139 | spring-boot-starter-test 140 | test 141 | 142 | 143 | 144 | 145 | 146 | 147 | org.springframework.cloud 148 | spring-cloud-dependencies 149 | ${spring-cloud.version} 150 | pom 151 | import 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | org.springframework.boot 160 | spring-boot-maven-plugin 161 | 162 | 163 | com.spotify 164 | dockerfile-maven-plugin 165 | 1.3.6 166 | 167 | ${docker.image.prefix}/${project.artifactId} 168 | 169 | target/${project.build.finalName}.jar 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | spring-milestones 180 | Spring Milestones 181 | https://repo.spring.io/milestone 182 | 183 | false 184 | 185 | 186 | 187 | 188 | 189 | ``` 190 | 191 |   192 | 193 | ## 2. Configuration ## 194 | 195 | bootstrap.yml file은 Spring cloud application에서 apllication.yml보다 먼저 실행된다. bootstrap.yml에서 db connection을 진행하고, apllication.yml에서 applicaion의 port와 eureka server instance의 정보를 포함시킨다. 196 | 197 | **1. bootstrap.yml** 198 | 199 | ```yml 200 | spring: 201 | application: 202 | name: notice-service 203 | 204 | jpa: 205 | hibernate: 206 | ddl-auto: update 207 | show_sql: true 208 | use_sql_comments: true 209 | fotmat_sql: true 210 | 211 | datasource: 212 | url: jdbc:mysql://{Your_MYSQL_Server_Address}:3306/notice 213 | username: {MYSQL_ID} 214 | password: {MYSQL_PASSWORD} 215 | driver-class-name: com.mysql.jdbc.Driver 216 | hikari: 217 | maximum-pool-size: 2 218 | 219 | ``` 220 | 221 | 사용중인 MySQL Server Address를 spring.datasource.url 부분에 입력해야한다. 또한 username과 password도 추가한다. 222 | 223 | **2. application.yml** 224 | 225 | ```yml 226 | server: 227 | port: 8763 228 | 229 | eureka: 230 | client: 231 | healthcheck: true 232 | fetch-registry: true 233 | serviceUrl: 234 | defaultZone: ${vcap.services.eureka-service.credentials.uri:http://{Your-Eureka-server-Address}:8761}/eureka/ 235 | instance: 236 | preferIpAddress: true 237 | ``` 238 | 239 | eureka.client.serviceUrl.defaultZone에 다음처럼 Eureka Server Address를 추가한다. 240 | 241 | * eureak.client.fetch-registry - Eureka Registry로 부터 Registry에 속해 있는 Eureka Client들의 정보를 가져올 수 있는 옵션이다. 이는 true로 주자! 242 | * eureka.client.serviceUrl.defaultZone - Spring Cloud Netflix의 공식 Document에서는 "defaultZone" is a magic string fallback value that provides the service URL for any client that does not express a preference (in other words, it is a useful default). 라고 소개한다. 뭐 일단 이대로 진행하면 된다. 243 | * eureka.instance.preferIpAddress - Eureka Client가 Eureka Registry에 자신을 등록할 때 eureka.instance.hostname으로 등록하게 된다. 그러나 어떠한 경우에는 hostname보다 IP Address가 필요한 경우가 있다. 여기서는 IP Address를 이용할 것이다. 244 | * eureka.instance.hostname - JAVA단에서 hostname을 찾지 못하면 IP Address로 Eureka Registry에 전송된다. (이를 방지하려면 eureka.instance.hostname={your_hostname} 으로 원하는 hostname을 입력해도 되고, eureka.instance.hostname=${HOST_NAME} 으로 environment variable을 이용해 run-time때 hostname을 지정해줘도 된다.) 245 | * eureka.instance.instanceId - 위의 예시에서는 instanceId를 등록하지 않는다. default는 ${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}} 이다. Eureka Server가 같은 service(application)이지만 다른 client임을 구별하기 위한 용도로 사용할 수 있다. 246 | 247 | *참고* 248 | * Eureka Client들을 Eureka Registry에 등록하면 다음처럼 등록된다.(아래 사진은 예시일뿐이다.) 249 | ![image](https://user-images.githubusercontent.com/20153890/41632852-e3c59ffa-7476-11e8-8920-0935bafc1c40.png) 250 | 251 | 252 | 사진을 보면 Application, AMIs, Availability Zones, Status를 확인 할 수 있다. 253 | * Application에 보여지는 여러 Service들은 각 Eureka Client의 spring.application.name이다. 254 | * Status는 현재 service가 Up인 상태인지, Down인 상태인지를 나타낸다. 255 | 256 | 또 한가지 알아두어야 할 점은 Status 오른쪽의 list들이다. 이 list에는 각 Eureka Client의 eureka.instance.instanceId값이 등록된다. 257 | 쉽게 이해하기 위해 Notice-Service를 보자. 258 | 259 | Notice-Service는 3개의 Up상태인 client를 가지고 있다. 260 | * notice-service:7c09a271351a998027f0d1e2c72148e5 261 | * notice-service:14d5f9837de754b077a6b58b7e159827 262 | * notice-service:7c6d41264f2f71925591bbc07cfe51ec 263 | 264 | 이 3개의 client는 spring.application.name=notice-service로 같지만, eureka.instance.instanceId가 각기 다르단 얘기이다. 265 | 266 | 즉 Eureka Registry에 같은 spring.application.name을 가진 어떠한 Client가 등록되면, eureka.instance.instanceId값으로 구분 할 수 있다는 얘기다. 우리는 이를 잘 이용해서 추후에 Dynamic Routing을 할 것이므로 알아 두자. 267 | 268 | 269 |   270 |   271 | 272 | ## 3. EurekaClient ## 273 | 274 | ```java 275 | @SpringBootApplication 276 | @EnableEurekaClient 277 | public class AlarmServiceApplication { 278 | 279 | public static void main(String[] args) { 280 | SpringApplication.run(AlarmServiceApplication.class, args); 281 | } 282 | ``` 283 | 284 | dependency는 앞에서 설정 했으므로 main class에 @EnableEurekaClient annotation만 추가하면 된다. 285 | 286 | 그럼 이 Eureka Client를 REST API Server로 만들어보자 287 | 288 |   289 | 290 | ## 4. REST API Server 구축 ## 291 | 292 |   293 | 294 | **REST API** 295 | 296 | METHOD | PATH | DESCRIPTION 297 | ------------|-----|------------ 298 | GET | /notice/ | 전체 알림정보 제공 299 | GET | /notice/{receiver_ID} | 해당 receiver 에 대한 알림 정보 제공 300 | GET | /notice/latest/{receiver_ID} | 해당 receiver 에 대한 최근 10개 알림정보 제공 301 | GET | /notice/previous/{receiver_ID}/{id} | 해당 receiver 에 대한 정보들 중 {id}값을 기준으로 이전 10개 정보 제공 302 | POST | /notice/ | 알림 정보 입력 303 | 304 |   305 |   306 | 307 | 308 | **Table(table name = notice) description** 309 | 310 | | Field | Type | Null | Key | Default | Extra | 311 | --------------|-------------|------|-----|---------|----------------| 312 | | id | int(11) | NO | PRI | NULL | auto_increment | 313 | | receiver_id | varchar(20) | NO | | NULL | | 314 | | sender_id | varchar(20) | NO | | NULL | | 315 | | article_id | int(11) | NO | | NULL | | 316 | 317 | 318 | 우리는 JPA를 이용해 DB에 접근할 것이다. 따라서 JPA란 무엇인지에 대해 간단하게 알아보고 넘어가자. 319 | (DB setting은 개인적으로 하자..) 320 | 321 |   322 | 323 | **JPA란?** 324 | 325 | JPA는 자바 진영의 ORM 기술 표준이다. Java Persistence API(JPA)는 RDB에 접근하기 위한 표준 ORM을 제공하고, 기존 EJB에서 제공하는 Entity Bean을 대체하는 기술이다. Hibernate, OpenJPA 와 같은 구현체들이 있고 이에 따른 표준 인터페이스가 JPA인 것이다. 326 | 327 | **ORM 이란?** 328 | 329 | 객체와 RDB를 매핑한다. 기존에 spring에서 많이 사용하던 mybatis등은 ORM이 아니고, SQL Query를 Mapping하여 실행한다. 330 | 따라서 Spring-Data-JPA를 이용하면, 객체 관점에서 DB에 접근하는 형태로 어플리케이션을 개발할 수 있다. 331 | 332 | **JPA를 사용해야하는 이유는?** 333 | 334 | 1. 생산성 => 반복적인 SQL 작업과 CRUD 작업을 개발자가 직접 하지 않아도 된다. 335 | 2. 성능 => 캐싱을 지원하여 SQL이 여러번 수행되는것을 최적화 한다. 336 | 3. 표준 => 표준을 알아두면 다른 구현기술로 쉽게 변경할 수 있다. 337 | 338 | **JPA Annotation** 339 | 340 | Annotaion | DESCRIPTION 341 | ----------|------------ 342 | @Entity | Entity임을 정의한다. 343 | @Table | (name = "table name") , Mapping할 table 정보를 알려준다. 344 | @id | Entity class의 필드를 table의 PK에 mapping한다. 345 | @Comlumn | field를 column에 매핑한다. 346 | 347 | @Comlumn annotaion은 꼭 필요한것은 아니다. 따로 선언해주지 않아도 기본적으로 멤버 변수명과 일치하는 DB의 Column을 mapping한다. 348 | 349 | @Table annotaion또한 기본적으로 @Entity로 선언된 class의 이름과 실제 DB의 Table 명이 일치하는 것을 mapping한다. 350 | 351 |   352 | 353 | ## 4.1 JPA Entity ## 354 | 355 | SpringBoot에서는 JPA로 데이터를 접근하게끔 유도하고 있다. 이를 활용해서 REST API Server를 구축해보자. 356 | 357 | 아래는 JPA Entity를 담당할 Class이다. 358 | 359 | **Notice.java** 360 | ```java 361 | @Entity 362 | public class Notice { 363 | 364 | @Id 365 | @GeneratedValue(strategy=GenerationType.AUTO) 366 | private int id; 367 | private String receiver_id; 368 | private String sender_id; 369 | private int article_id; 370 | 371 | protected Notice() {} 372 | 373 | public Notice(final int id, final String receiver_id, final String sender_id,final int article_id) 374 | { 375 | this.receiver_id=receiver_id; 376 | this.sender_id=sender_id; 377 | this.article_id=article_id; 378 | } 379 | 380 | public int getId() 381 | { 382 | return id; 383 | } 384 | 385 | public String getReceiver_id() 386 | { 387 | return receiver_id; 388 | } 389 | public String getSender_id() 390 | { 391 | return sender_id; 392 | } 393 | 394 | public void setReceiver_id(String receiver_id) 395 | { 396 | this.receiver_id=receiver_id; 397 | } 398 | 399 | public void setSender_id(String sender_id) 400 | { 401 | this.sender_id=sender_id; 402 | } 403 | 404 | public int getArticle_id() 405 | { 406 | return article_id; 407 | } 408 | public void setArticle_id(int article_id) 409 | { 410 | this.article_id=article_id; 411 | } 412 | @Override 413 | public String toString() 414 | { 415 | return String.format("Notice [id = %d ,receiver_id = '%s', sender_id = '%s', article_id = %d] ", id, receiver_id, sender_id, article_id); 416 | 417 | } 418 | 419 | } 420 | ``` 421 | 422 |   423 | 424 | ## 4.2 Repository ## 425 | 426 | Entity class를 생성했다면, Repository Interface를 생성해야 한다. Spring에서는 Entity의 기본적 insert, delete, update 등이 가능하도록 427 | CrudRepository라는 interface를 제공한다. 428 | 429 | **NoticeRepository.java** 430 | ```java 431 | public interface NoticeRepository extends CrudRepository{ 432 | 433 | 434 | @Query("SELECT n FROM Notice n WHERE receiver_id=:receiver_id ORDER BY id DESC") 435 | List findNoticeByReceiverId(@Param("receiver_id") String receiver_id); 436 | 437 | @Query("SELECT n FROM Notice n WHERE receiver_id=:receiver_id ORDER BY id DESC") 438 | List findLatestNoticeByReceiverId(@Param("receiver_id") String receiver_id, Pageable pageable); 439 | 440 | @Query("SELECT n FROM Notice n WHERE n.receiver_id=:receiver_id AND n.id < :id ORDER BY n.id DESC") 441 | List findPreviousNoticeByReceiverId(@Param("receiver_id")String receiver_id, @Param("id") int id, Pageable pageable); 442 | 443 | 444 | } 445 | 446 | ``` 447 | 448 | 위 코드는 실제 Notice Entity를 이용하기 위한 Repository이다. 기본적인 CRUD외에 필자가 필요한 메소드를 @Query를 이용해 기존의 SQL처럼 사용하도록 지정해 놓은 상태이다. 449 | 450 | 이 외에도 CrudRepositorys는 find(), findAll(), findAllById() 등 여러 method를 제공한다. 이에 대한 세부사항은 다음 레퍼런스를 꼭 참고하자. 451 | * Interface CrudRepository => https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html 452 | 453 |   454 | 455 | ## 5. Service ## 456 | 457 | 이제 실제 필요한 Service interface를 만들어 볼 차례이다. 458 | 459 | ├── rest 460 | │ ├── NoticeController.java 461 | ├── service 462 | ├── NoticeService.java 463 | └── NoticeServiceImpl.java 464 | 465 | 먼저 NoticeService.java 와 NoticeServiceImpl.java파일을 생성한다. NoticeService는 interface로 생성할 것이고, 이에대한 명세는 NoticeServiceImpl.java에서 구현한다. interface에 대한 method 구현시 NoticeRepository의 method를 활용한다. 466 | 467 | **NoticeService.java** 468 | 469 | ```java 470 | public interface NoticeService { 471 | 472 | List findAllNotice(); 473 | List findAllNoticeByReceiverId(String receiver_id); 474 | List findLatestNoticeByReceiverId(String receiver_id); 475 | List findPreviousNoticeByReceiverId(String receiver_id, int id); 476 | Notice saveNotice(Notice notice); 477 | 478 | } 479 | ``` 480 | 481 | **NoticeServiceImpl.java 일부** 482 | 483 | ```java 484 | @Service("noticeService") 485 | public class NoticeServiceImpl implements NoticeService{ 486 | 487 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 488 | 489 | 490 | @Autowired 491 | private NoticeRepository noticeRepository; 492 | 493 | @Override 494 | public List findAllNotice() 495 | { 496 | 497 | Optional> maybeNoticeIter = Optional.ofNullable(noticeRepository.findAll()); 498 | 499 | return Lists.newArrayList(maybeNoticeIter.get()); 500 | 501 | 502 | } 503 | 504 | @Override 505 | public List findAllNoticeByReceiverId(String receiver_id) 506 | { 507 | 508 | Optional> maybeNotice = 509 | Optional.ofNullable(noticeRepository.findNoticeByReceiverId(receiver_id)); 510 | 511 | return maybeNotice.get(); 512 | 513 | } 514 | 515 | 516 | @Override 517 | public List findLatestNoticeByReceiverId(String receiver_id) 518 | { 519 | Optional> maybeLatestNotice= 520 | Optional.ofNullable(noticeRepository.findLatestNoticeByReceiverId(receiver_id, PageRequest.of(0, 10))); 521 | 522 | return maybeLatestNotice.get(); 523 | 524 | 525 | } 526 | 527 | ``` 528 | 529 |   530 | 531 | ## 6. Rest Controller 532 | 533 | 이제 controller를 만들어 보자. rest package를 따로 만들고 그곳에 RestController들을 정의한다. 534 | 535 | 536 | ├── rest 537 | │ ├── NoticeController.java 538 |     539 | @RestControler annotation을 설정하여 RestController를 만든다. 540 | (HystrixMethod적용은 다음 단계에서 진행한다. 여기서는 REST API만 구축한다) 541 | 542 | **NoticeController.java 일부** 543 | 544 | 545 | ```java 546 | @RestController 547 | @CrossOrigin(origins="*") 548 | public class NoticeController { 549 | 550 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 551 | public static List Temp; 552 | 553 | @Autowired 554 | private NoticeService noticeService; 555 | 556 | @Autowired 557 | private DiscoveryClient discoveryClient; 558 | 559 | @RequestMapping(value = "/notice", method=RequestMethod.GET) 560 | public ResponseEntity> getAllNotice(){ 561 | 562 | try{ 563 | Optional> maybeAllStory = Optional.ofNullable(noticeService.findAllNotice()); 564 | 565 | return new ResponseEntity>(maybeAllStory.get(), HttpStatus.OK); 566 | }catch(Exception e) 567 | { 568 | return new ResponseEntity>(HttpStatus.NOT_FOUND); 569 | } 570 | } 571 | 572 | @RequestMapping(value="/notice/{receiver_id}", method = RequestMethod.GET) 573 | public ResponseEntity> getAllNoticeByReceiverId(@PathVariable("receiver_id") final String receiver_id) 574 | { 575 | 576 | try { 577 | Optional> maybeSelectedNotice = 578 | Optional.of(noticeService.findAllNoticeByReceiverId(receiver_id)); 579 | 580 | return new ResponseEntity>(maybeSelectedNotice.get(), HttpStatus.OK); 581 | 582 | }catch(Exception e) 583 | { 584 | return new ResponseEntity>(HttpStatus.NOT_FOUND); 585 | } 586 | } 587 | 588 | ``` 589 |   590 | 591 | ## 7. Maven Packaging 592 | 593 | Host OS에 설치된 maven을 이용해도 되고, spring boot application의 maven wrapper를 사용해도 된다 594 | (maven wrapper는 Linux, OSX, Windows, Solaris 등 서로 다른 OS에서도 동작한다. 따라서 추후에 여러 서비스들을 Jenkins에서 build 할 때 각 서비스들의 Maven version을 맞출 필요가 없다.) 595 | 596 | *A Quick Guide to Maven Wrapper => http://www.baeldung.com/maven-wrapper)* 597 | 598 | **a. Host OS의 maven 이용** 599 | 600 | ```bash 601 | [sangmin@Mint-SM] ~/springcloud-service $ mvn package 602 | ``` 603 |   604 | 605 | **b. maven wrapper 이용** 606 | 607 | ```bash 608 | [sangmin@Mint-SM] ~/springcloud-service $ ./mvnw package 609 | ``` 610 |   611 | 612 | ## 8. Execute Spring Boot Application 613 | 614 | REST API Server가 제대로 구축 되어졌는지 확인해보자. 615 | 616 | ```bash 617 | [sangmin@Mint-SM] ~/springcloud-service $java -jar target/{your_application_name}.jar 618 | ``` 619 | 620 | Eureka Dashboard를 통해 Client가 제대로 등록 되어졌는지 확인해보자 621 | 622 | Check Your Eureka Dashboard 623 | * http://{Your-Eureka-Server-Address}:8761 624 | * http://{Your-Eureka-Server-Address}:8761/eureka/apps 625 | 626 | Client가 Eureka Server에 등록 될 때 약간의 시간이 소요될 수 있다. 627 | 628 |   629 | 630 | ## 9. Dockerizing 631 | 632 | 구축한 Eureka Client를 docker image를 만들어 볼 차례이다. 먼저 Dockerfile을 작성한다. 633 | 634 | > -     $mvn package 635 | 636 | 637 | **Dockerfile** 638 | ``` 639 | FROM openjdk:8-jdk-alpine 640 | VOLUME /tmp 641 | #ARG JAR_FILE 642 | #ADD ${JAR_FILE} app.jar 643 | #dockerfile-maven-plugin으로 docker image를 생성하려면 아래 ADD ~를 주석처리하고, 위 2줄의 주석을 지우면 된다. 644 | ADD ./target/notice-service-0.0.1.jar app.jar 645 | ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] 646 | ``` 647 | 648 | Dockerfile 작성이 끝났다면 image를 build 하자 649 | 650 | **a. dockerfile-maven-plugin 사용시** 651 | 652 | ```bash 653 | [sangmin@Mint-SM] ~/springcloud-service $ ./mvnw dockerfile:build 654 | ``` 655 |   656 | 657 | **b. docker CLI 사용시** 658 | 659 | ```bash 660 | [sangmin@Mint-SM] ~/springcloud-service $ docker build -t {your_docker_id}/notice-service:latest 661 | ``` 662 | 663 | 이후 docker image가 잘 생성 되었음을 확인하자. 664 | 665 | ```bash 666 | [sangmin@Mint-SM] ~/springcloud-service $ docker images 667 | REPOSITORY TAG IMAGE ID CREATED SIZE 668 | phantasmicmeans/notice-service latest 4b79d6a1ed24 2 weeks ago 146MB 669 | openjdk 8-jdk-alpine 224765a6bdbe 5 months ago 102MB 670 | 671 | ``` 672 |   673 | 674 | ## 10. Run Docker Container 675 | 676 | Docker image를 생성하였으므로 이미지를 실행 시켜보자. 677 | 678 | ```bash 679 | [sangmin@Mint-SM] ~ $ docker run -it -p 8763:8763 phantasmicmeans/notice-service:latest 680 | ``` 681 | 682 | 이제 Eureka Dashboard를 통해 Client가 제대로 실행 되었는지 확인하면 된다. 683 | 684 |   685 | 686 | ## Conclusion 687 | 688 | 이상으로 간단한 REST API Server로 구축된 Microservice를 Eureka Client로 구성해 보았다. 다음 장에서는 Eureka Client로 구성된 Microservice에 Hystrix를 적용해 볼 것이다. 689 | 690 | 691 | 692 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.sangmin 7 | notice-service 8 | 0.0.1 9 | jar 10 | alarmservice 11 | 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-parent 16 | 2.0.1.RELEASE 17 | 18 | 19 | 20 | UTF-8 21 | UTF-8 22 | 1.8 23 | 1.8 24 | 1.8 25 | Finchley.M9 26 | phantasmicmeans 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.cloud 35 | spring-cloud-starter-netflix-eureka-client 36 | 37 | 38 | 39 | org.springframework.cloud 40 | spring-cloud-starter-netflix-hystrix 41 | 1.4.4.RELEASE 42 | 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-actuator 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-web 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-data-jpa 58 | 2.0.1.RELEASE 59 | 60 | 61 | 62 | mysql 63 | mysql-connector-java 64 | 5.1.21 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-test 70 | test 71 | 72 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | org.springframework.cloud 110 | spring-cloud-dependencies 111 | ${spring-cloud.version} 112 | pom 113 | import 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | org.springframework.boot 122 | spring-boot-maven-plugin 123 | 124 | 125 | 126 | com.spotify 127 | dockerfile-maven-plugin 128 | 1.3.6 129 | 130 | ${docker.image.prefix}/${project.artifactId} 131 | 132 | target/${project.build.finalName}.jar 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | spring-milestones 143 | Spring Milestones 144 | https://repo.spring.io/milestone 145 | 146 | false 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /src/main/java/com/alarm/AlarmServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.alarm; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cache.annotation.EnableCaching; 7 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 8 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | //import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 12 | //import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 13 | //import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; 14 | //import org.springframework.security.oauth2.provider.token.DefaultTokenServices; 15 | //import org.springframework.security.oauth2.provider.token.TokenStore; 16 | //import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 17 | //import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; 18 | import org.springframework.web.bind.annotation.CrossOrigin; 19 | import org.springframework.context.annotation.Bean; 20 | import org.springframework.context.annotation.Primary; 21 | //import org.springframework.security.config.annotation.web.builders.HttpSecurity; 22 | import org.springframework.web.cors.CorsConfiguration; 23 | import org.springframework.web.cors.CorsConfigurationSource; 24 | import org.springframework.web.cors.CorsUtils; 25 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 26 | 27 | @SpringBootApplication 28 | @EnableEurekaClient 29 | @RestController 30 | @EnableAutoConfiguration 31 | @EnableCircuitBreaker 32 | @EnableCaching 33 | //@EnableResourceServer 34 | @CrossOrigin(origins="*") 35 | public class AlarmServiceApplication { 36 | 37 | public static void main(String[] args) { 38 | SpringApplication.run(AlarmServiceApplication.class, args); 39 | } 40 | 41 | /* 42 | @Bean 43 | public ResourceServerConfigurerAdapter resourceServerConfigurerAdapter() { 44 | return new ResourceServerConfigurerAdapter() { 45 | @Override 46 | public void configure(HttpSecurity http) throws Exception { 47 | http.headers().frameOptions().disable(); 48 | http.authorizeRequests() 49 | .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() 50 | .antMatchers("/notice", "/notice/**").access("#oauth2.hasScope('read')") 51 | .anyRequest().authenticated() 52 | .and().cors().and(); 53 | 54 | } 55 | 56 | @Override 57 | public void configure(final ResourceServerSecurityConfigurer config) { 58 | config.tokenServices(tokenServices()); 59 | } 60 | }; 61 | } 62 | 63 | @Bean 64 | public TokenStore tokenStore() { 65 | return new JwtTokenStore(accessTokenConverter()); 66 | } 67 | 68 | @Bean 69 | public JwtAccessTokenConverter accessTokenConverter() { 70 | JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); 71 | converter.setSigningKey("123"); 72 | return converter; 73 | } 74 | 75 | @Bean 76 | @Primary 77 | public DefaultTokenServices tokenServices() { 78 | DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); 79 | defaultTokenServices.setTokenStore(tokenStore()); 80 | defaultTokenServices.setSupportRefreshToken(true); 81 | return defaultTokenServices; 82 | 83 | } 84 | @Bean 85 | public CorsConfigurationSource corsConfigurationSource() { 86 | 87 | CorsConfiguration configuration = new CorsConfiguration(); 88 | 89 | configuration.addAllowedOrigin("*"); 90 | configuration.addAllowedMethod("*"); 91 | configuration.addAllowedHeader("*"); 92 | configuration.setAllowCredentials(true); 93 | configuration.setMaxAge(3600L); 94 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 95 | source.registerCorsConfiguration("/**", configuration); 96 | return source; 97 | 98 | } 99 | */ 100 | } 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/main/java/com/alarm/cache/CacheManagerCheck.java: -------------------------------------------------------------------------------- 1 | package com.alarm.cache; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.cache.CacheManager; 7 | 8 | public class CacheManagerCheck implements CommandLineRunner { 9 | 10 | private static final Logger logger = LoggerFactory.getLogger(CacheManagerCheck.class); 11 | 12 | private final CacheManager cacheManager ; //singletone cache , 최초 한번 호출될 때에만 CacheMange의 초기화 작업이 수행되므, 이후 동일한 CacheManager인스턴스를 리턴하게 됨. 13 | 14 | public CacheManagerCheck(CacheManager cacheManager) 15 | { 16 | this.cacheManager=cacheManager; 17 | } 18 | 19 | @Override 20 | public void run(String ...strings) throws Exception 21 | { 22 | logger.info("\n\n" + "===========================================\n" 23 | + "Using cacheManager :" + this.cacheManager.getClass().getName() + "\n" 24 | + "=========================================\n\n"); 25 | 26 | } 27 | 28 | public void putCache() 29 | { 30 | this.cacheManager.getCache("BBScache"); 31 | 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/alarm/domain/Notice.java: -------------------------------------------------------------------------------- 1 | package com.alarm.domain; 2 | 3 | import javax.persistence.Entity; 4 | 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | 9 | @Entity 10 | public class Notice { 11 | 12 | @Id 13 | @GeneratedValue(strategy=GenerationType.AUTO) 14 | private int id; 15 | private String receiver_id; 16 | private String sender_id; 17 | private int article_id; 18 | 19 | protected Notice() {} 20 | 21 | public Notice(final int id, final String receiver_id, final String sender_id,final int article_id) 22 | { 23 | this.receiver_id=receiver_id; 24 | this.sender_id=sender_id; 25 | this.article_id=article_id; 26 | } 27 | 28 | public int getId() 29 | { 30 | return id; 31 | } 32 | 33 | public String getReceiver_id() 34 | { 35 | return receiver_id; 36 | } 37 | public String getSender_id() 38 | { 39 | return sender_id; 40 | } 41 | 42 | public void setReceiver_id(String receiver_id) 43 | { 44 | this.receiver_id=receiver_id; 45 | } 46 | 47 | public void setSender_id(String sender_id) 48 | { 49 | this.sender_id=sender_id; 50 | } 51 | 52 | public int getArticle_id() 53 | { 54 | return article_id; 55 | } 56 | public void setArticle_id(int article_id) 57 | { 58 | this.article_id=article_id; 59 | } 60 | @Override 61 | public String toString() 62 | { 63 | return String.format("Notice [id = %d ,receiver_id = '%s', sender_id = '%s', article_id = %d] ", id, receiver_id, sender_id, article_id); 64 | 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/alarm/repository/.NoticeRepository.java.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantasmicmeans/springboot-microservice-with-spring-cloud-netflix/132688bc09776f68b3eaf0c5e366fe462ced0adc/src/main/java/com/alarm/repository/.NoticeRepository.java.swp -------------------------------------------------------------------------------- /src/main/java/com/alarm/repository/NoticeRepository.java: -------------------------------------------------------------------------------- 1 | package com.alarm.repository; 2 | 3 | 4 | import java.util.List; 5 | 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.jpa.repository.Query; 8 | import org.springframework.data.repository.CrudRepository; 9 | import org.springframework.data.repository.query.Param; 10 | 11 | import com.alarm.domain.Notice; 12 | 13 | 14 | public interface NoticeRepository extends CrudRepository{ 15 | 16 | 17 | @Query("SELECT n FROM Notice n WHERE receiver_id=:receiver_id ORDER BY id DESC") 18 | List findNoticeByReceiverId(@Param("receiver_id") String receiver_id); 19 | 20 | @Query("SELECT n FROM Notice n WHERE receiver_id=:receiver_id ORDER BY id DESC") 21 | List findLatestNoticeByReceiverId(@Param("receiver_id") String receiver_id, Pageable pageable); 22 | 23 | @Query("SELECT n FROM Notice n WHERE n.receiver_id=:receiver_id AND n.id < :id ORDER BY n.id DESC") 24 | List findPreviousNoticeByReceiverId(@Param("receiver_id")String receiver_id, @Param("id") int id, Pageable pageable); 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/alarm/rest/NoticeController.java: -------------------------------------------------------------------------------- 1 | package com.alarm.rest; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RequestBody; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestMethod; 15 | import org.springframework.web.bind.annotation.RestController; 16 | import org.springframework.web.util.UriComponentsBuilder; 17 | import org.springframework.web.bind.annotation.CrossOrigin; 18 | import com.alarm.domain.Notice; 19 | import com.alarm.service.NoticeService; 20 | 21 | 22 | @RestController 23 | @CrossOrigin(origins="*") 24 | public class NoticeController { 25 | 26 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 27 | public static List Temp; 28 | 29 | @Autowired 30 | private NoticeService noticeService; 31 | 32 | @RequestMapping(value = "/notice", method=RequestMethod.GET) 33 | public ResponseEntity> getAllNotice(){ 34 | 35 | try{ 36 | 37 | Optional> maybeAllStory = Optional.ofNullable(noticeService.findAllNotice()); 38 | 39 | return new ResponseEntity>(maybeAllStory.get(), HttpStatus.OK); 40 | 41 | }catch(Exception e) 42 | { 43 | return new ResponseEntity>(HttpStatus.NOT_FOUND); 44 | } 45 | 46 | } 47 | 48 | @RequestMapping(value="/notice/{receiver_id}", method = RequestMethod.GET) 49 | public ResponseEntity> getAllNoticeByReceiverId(@PathVariable("receiver_id") final String receiver_id) 50 | { 51 | 52 | try { 53 | 54 | Optional> maybeSelectedNotice = Optional.of(noticeService.findAllNoticeByReceiverId(receiver_id)); 55 | 56 | return new ResponseEntity>(maybeSelectedNotice.get(), HttpStatus.OK); 57 | 58 | }catch(Exception e) 59 | { 60 | return new ResponseEntity>(HttpStatus.NOT_FOUND); 61 | } 62 | 63 | 64 | } 65 | 66 | @RequestMapping(value="/notice/latest/{receiver_id}",method = RequestMethod.GET) 67 | public ResponseEntity> getLastNoticeByReceiverId(@PathVariable("receiver_id") final String receiver_id) 68 | { 69 | 70 | try { 71 | 72 | Optional>maybeLastsNotice = Optional.of(noticeService.findLatestNoticeByReceiverId(receiver_id)); 73 | 74 | return new ResponseEntity>(maybeLastsNotice.get() , HttpStatus.OK); 75 | 76 | }catch(Exception e) 77 | { 78 | return new ResponseEntity>(HttpStatus.NOT_FOUND); 79 | 80 | } 81 | 82 | } 83 | 84 | @RequestMapping(value="/notice/previous/{receiver_id}/{id}",method = RequestMethod.GET) 85 | public ResponseEntity> getPreviousNoticeByReceiverId(@PathVariable("receiver_id") final String receiver_id, @PathVariable("id") final int id) 86 | { 87 | 88 | try { 89 | 90 | Optional> maybePreviousNotice = Optional.of(noticeService.findPreviousNoticeByReceiverId(receiver_id, id)); 91 | 92 | return new ResponseEntity>(maybePreviousNotice.get(), HttpStatus.OK); 93 | 94 | }catch(Exception e) 95 | { 96 | return new ResponseEntity>(HttpStatus.NOT_FOUND); 97 | } 98 | 99 | } 100 | 101 | 102 | @RequestMapping(value="/notice", method = RequestMethod.POST) 103 | public ResponseEntity createNotice(@RequestBody final Notice notice, final UriComponentsBuilder ucBuilder){ 104 | 105 | if(!noticeService.saveNotice(notice)) { return new ResponseEntity(HttpStatus.BAD_REQUEST); } 106 | 107 | return new ResponseEntity(HttpStatus.CREATED); 108 | 109 | } 110 | 111 | } 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /src/main/java/com/alarm/service/NoticeService.java: -------------------------------------------------------------------------------- 1 | package com.alarm.service; 2 | 3 | import java.util.List; 4 | 5 | import com.alarm.domain.Notice; 6 | 7 | public interface NoticeService { 8 | 9 | List findAllNotice(); 10 | List findAllNoticeByReceiverId(String receiver_id); 11 | List findLatestNoticeByReceiverId(String receiver_id); 12 | List findPreviousNoticeByReceiverId(String receiver_id, int id); 13 | Boolean saveNotice(Notice notice); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/alarm/service/NoticeServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.alarm.service; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.domain.PageRequest; 10 | import org.springframework.stereotype.Service; 11 | 12 | import com.alarm.domain.Notice; 13 | import com.alarm.repository.NoticeRepository; 14 | import com.google.common.collect.Lists; 15 | 16 | @Service("noticeService") 17 | public class NoticeServiceImpl implements NoticeService{ 18 | 19 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 20 | 21 | 22 | @Autowired 23 | private NoticeRepository noticeRepository; 24 | 25 | @Override 26 | public List findAllNotice() 27 | { 28 | 29 | Optional> maybeNoticeIter = Optional.ofNullable(noticeRepository.findAll()); 30 | 31 | return Lists.newArrayList(maybeNoticeIter.get()); 32 | 33 | 34 | } 35 | 36 | @Override 37 | public List findAllNoticeByReceiverId(String receiver_id) 38 | { 39 | 40 | Optional> maybeNotice = Optional.ofNullable(noticeRepository.findNoticeByReceiverId(receiver_id)); 41 | 42 | return maybeNotice.get(); 43 | 44 | } 45 | 46 | 47 | @Override 48 | public List findLatestNoticeByReceiverId(String receiver_id) 49 | { 50 | Optional> maybeLatestNotice = Optional.ofNullable(noticeRepository.findLatestNoticeByReceiverId(receiver_id, PageRequest.of(0, 10))); 51 | 52 | return maybeLatestNotice.get(); 53 | 54 | 55 | } 56 | 57 | @Override 58 | public List findPreviousNoticeByReceiverId(String receiver_id, int id) 59 | { 60 | 61 | Optional> maybePreviousNotice = Optional.ofNullable(noticeRepository.findPreviousNoticeByReceiverId(receiver_id, id, PageRequest.of(0, 10))); 62 | 63 | return maybePreviousNotice.get(); 64 | 65 | } 66 | 67 | @Override 68 | public Boolean saveNotice(Notice notice) 69 | { 70 | try { 71 | 72 | Optional maybeNotice = Optional.of(notice); 73 | noticeRepository.save(maybeNotice.get()); 74 | logger.info("Saved"); 75 | return true; 76 | 77 | }catch(Exception e) 78 | { 79 | logger.info("Nothing to save"); 80 | return false; 81 | } 82 | 83 | 84 | } 85 | 86 | 87 | 88 | 89 | } 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8763 3 | 4 | eureka: 5 | client: 6 | healthcheck: true 7 | fetch-registry: true 8 | serviceUrl: 9 | defaultZone: ${vcap.services.eureka-service.credentials.uri:http://127.0.0.1:8761}/eureka/ 10 | instance: 11 | preferIpAddress: true 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: notice-service 4 | 5 | jpa: 6 | hibernate: 7 | ddl-auto: update 8 | show_sql: true 9 | use_sql_comments: true 10 | fotmat_sql: true 11 | 12 | datasource: 13 | url: jdbc:mysql://127.0.0.1:3306/notice 14 | username: sangmin 15 | password: tkdals12 16 | driver-class-name: com.mysql.jdbc.Driver 17 | hikari: 18 | maximum-pool-size: 2 19 | 20 | -------------------------------------------------------------------------------- /src/test/java/com/alarm/AlarmServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.alarm; 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 AlarmServiceApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------