스프링 부트 2.2 릴리즈노트

JAVA/Spring|2019. 11. 1. 18:20

한 달 동안 잠시 해외를 다녀온 사이 Spring Boot 2.2가 릴리즈 되었습니다.
지금까지 버전업 되면서 변경 사항들을 모두 파악하지는 못했지만
계속 사용하게될 SpringBoot이기 때문에 2.1x에서 2.2x로 올라가며 변경돼 사항을 파악하고자
릴리즈 노트를 번역 및 개인적으로 궁금한 사항을 파악해보았습니다.

릴리즈 정보 출처

https://spring.io/blog/2019/10/16/spring-boot-2-2-0
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.2-Release-Notes


스프링 관련 프로젝트 업데이트

  • Spring AMQP 2.2
  • Spring Batch 4.2
  • Spring Data Moore
  • Spring Framework 5.2
  • Spring HATEOAS 1.0
  • Spring Integration 5.2
  • Spring Kafka 2.3
  • Spring Security 5.2
  • Spring Session Corn

서드 파티 라이브러리 업데이트

  • Elasticsearch 6.7
  • Flyway 6.0
  • Jackson 2.10
  • JUnit 5.5
  • Micrometer 1.3
  • Reactor Dysprosium
  • Solr 8.0

성능 개선

SpringBoot2.1보다 2.2에서는 더 빠른 시작과 더 적은 메모리 사용량을 보여줄 것이며 특히 타이트한 메모리 환경에서 유용할 것입니다.

초기화 지연(Lazy initialization)

spring.main.lazy-initialization 속성을 통해 전역으로 lazy init을 활성화시킬 수 있습니다.
초기화를 늦춤에 따라 아래의 추가적인 비용들이 발생합니다.

Java13 지원

Spring 5.2가 Java 13을 지원함에 따라 Spring Boot2.2도 Java 13을 지원하며, Java11과 Java8 또한 지원합니다.

@ConfigurationProperties 생성자 바인딩 지원

설정 properties는 이제 생성자 바인딩을 지원하기 때문에 클래스의 불변을 지원합니다.
@ConfigurationProperties를 붙이거나, @ConstructorBinding를 붙이면 생성자 바인딩이 활성화되며, @DefaultValue와 @DateTimeFormat 이 생성자 바인딩으로 동작합니다.

RSocket 지원

RSocket에 대한 자동 설정을 지원합니다. spring-boot-starter-rsocket

 

수정 중이며 잘못된 내용이 있으면 의견 부탁드립니다.

감사합니다.

댓글()

[Spring] zuul 사용시 Eureka client로 분배가 안되는경우

JAVA/Spring|2019. 8. 16. 19:56

Toy 프로젝트에 zuul 을 적용해서 사용해보고있는데

여러 appSvr를 EurekaServer에 붙이고 zuul로 호출해봤으나 이상하게도

하나의 appSvr로 호출되는 현상이 발생했습니다.

 

이게 원래 그런가?(active-standby로..?) 라고 처음에는 생각해봤지만 그건 너무 아닌것 같아서

이런저런 시도를 하다 eureakServer에 /eureka/apps 로 등록된 app을 확인해보니

zuul(API-GATEWAY)이랑 appSvr(MEMBER-API 라는 샘플..)이 정상적으로 등록되어있지만

appSvr가 3개가 나타나야 하는데 하나만 나타나는것을 확인했습니다.

 

등록된 정보들을 살펴보다가 떠오른것이..

eureka.instanceId 설정을.. 안했...네요..

 

설정을 안하다보니, ip:applicationName:port 으로 instanceId가 다 지정되어

(port는 일부러 random으로 띄우기 위해 0으로 설정했었습니다.)

동일한 instanceId 로 등록을 시도하고 있었던것 이였습니다.

 

그래서 'eureka.instance.instance-id' 설정을 추가해주었고

설정값은 'hostname:applicationname:springinstanceId:random값'

으로 하여 중복이 발생하지 않고 기존처럼 인식할수 있도록 변경하였습니다.

 

eureka.instance.instance-id=${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${random.value}}

 

간단한 설정 실수였지만.. 역시 reference 를 잘 읽어야.. 하겠습니다.

 

요약

1. eureka.instance.instance-id 를설정하지 않거나 중복 된다면 제대로 동작 안할수 있다.

 

 

 

댓글()

[Spring] JDBC Template 어떻게 Thread-Safe 할까?

JAVA/Spring|2019. 8. 6. 18:27

Spring 스터디중 나온 질문 입니다.

 

DAO 클래스를 만들때 "JDBC Template"는 new 해서 사용하기도 하지만, "Thread-Safe 하기 때문에 빈으로 등록해서 사용할 수 있다"

 

Toy 프로젝트 때는 MyBatis를 쓰거나, JPA를 사용하기 때문에 JDBC Template를 사용할 일이 많지 않았지만,

대체 내부 구조가 어떻게 되어있길래 Thread-Safe한거지? 라는 의문이 들었습니다.

(JDBC에 Connection, Statement, ResultSet 은 Thread-Safe 하지 않기 때문에..)

 

예상 할 수 있는건

락(sync)를 잡아 처리 하나? 아니면 ThreadLocal로 처리하나?

정도로 예상해볼수 있는데 락잡아 처리한다면.. 쫌 아닐꺼라 생각되지만 코드를 살펴봤습니다.

JdbcTemplate의 query, update, execute 등 대부분의 메소드들은 살펴보면 메소드 내에서

DataSourceUtils.getConnection(ds) 를 통해 Connection을 얻어오고 있으며

메소드에서 생성된 ps나 rs 그리고 connection(물론 connection은 무조건 정리하지는 않습니다.) 정리 하고 있습니다.

//즉, JdbcTemplate 객체에 상태 변화 없이 메소드 내에서 모든 리소스가 정리 됨

 

JdbcTemplate 객체내에서 리소스(예를 들어 Connection)를 정리하지 않고 보존해야 하는경우도 있는데

(예를 들어 @Transaction)

그경우를 위해 DataSourceUtils.getConnection(ds) 코드를 따라가보면

TransactionSynchronizationManager.getResource(ds)를 통해 ConnectionHolder를 가져오는데

TransactionSynchronizationManager.getResource(ds)를 더 들어가보면

ThreadLocal<Map<Object,Object>>로 되어있는 resources를 가져와 ds를 키로 ConnectionHolder를 가져오는것을 볼수 있었습니다.

 

 

 

결론

1. JdbcTemplate 인스턴스는 상태를 갖지 않고 메소드 내에서 생성된 리소스(rs, ps, connection) 들을 정리하기 때문에 Thread-Safe하다

2. Transaction이 필요한경우 정리하면 안되는 Connection의 경우 JdbcTemplate이 상태를 가지지 않고TransactionSynchronizationManager에 ThreadLocal로 보관 하기 때문에 JdbcTemplate를 Thread-Safe하며,

Connection 객체 전달 및 전파 필요 없이 트랜잭션 제어를 하기 용이 하다.

 

 

댓글()

[Azure] SpringBoot, AppService로 배포

JAVA/Spring|2019. 8. 2. 18:51

개인 Toy 프로젝트를 라즈베리파이를 통해 개발하다 요즘 MS Azure를 활용하여 개발중입니다.

AWS를 쓰다 무료기간이 지난후 과금으로 인해 Cloud를 사용하지 않고 있다 살펴보니 좋더군요..

 

구글도 아마존도 MS 도 다 비슷하게 1년동안 무료로 제공하고 있으니 

꼭 Azure를 사용할 필요는 없습니다.

 

ms azure는 한달 22만원에 크레딧을 제공하고 있습니다

https://azure.microsoft.com/ko-kr/free/

처음에는 1년에 22만원을 제공하나? 해서 조금씩 써야겠다고 생각했지만.. 매달 지원해주고 있습니다.

 

'Virtual machines'로 우분투 서버를 에 수동으로 App을 배포해서 사용해도 되겠지만

'App Services' 서비스에 배포 하는 방법을 알아보았습니다.

 

이런저런 방법들을 시도해도 잘 되지 않아 괜히 고생했지만 결국 현재 사용하고 있는 방법을 담고 있는 링크는

https://docs.microsoft.com/ko-kr/java/azure/spring-framework/deploy-spring-boot-java-app-with-maven-plugin?view=azure-java-stable

 

를 참고하였습니다.

 

SpringBoot App을 개발후 'Azure App Service'에 배포하기 위해 필요한 내용은 사실

"Azure App Service용 Maven 플러그인 구성" 부터 보면 됩니다.

 

0. azure cli 설치 및 로그인(https://docs.microsoft.com/ko-kr/cli/azure/?view=azure-cli-latest)

0.1 배포시 az 인증이 필요하기 때문에 az login 커맨드를 통해 로그인

 

az cli 설치 하지 않고 배포하는방법은 추후 발견하게되면 첨부 하겠습니다..

 

1. pom.xml 에 azure 플러그인을 추가

<plugin>
 <groupId>com.microsoft.azure</groupId>
 <artifactId>azure-webapp-maven-plugin</artifactId>
 <version>1.6.0</version>
</plugin>

2. mvn azure-webapp:config 수행

2.1 배포할 환경 설정하게 됩니다.(OS, javaVersion 등)

2.2 위 설정 이후 pom.xml에 <configuration> 항목에 설정한 내용들이 추가 되는데, 여기서 <region>이 미국으로 설정되던데 한국으로 바꾸고 싶다면 <region>koreacentral</region> 으로 바꾸시면 됩니다.

2.3 pom.xml에 appName과 resourceGroup 모두 적힌 그대로 배포되기 때문에 필요하면 배포전 수정하시기 바랍니다.

 

3. port 설정을 위해 <appSettings>에 JAVA_OPTS ,-Dserver.port=80 설정

3.1 저는 추가로 profile 설정이 필요해서 SPRING_OPTS 로 설정을 시도해보았지만 안되어 JAVA_OPTS에 -Dserver.port=80 -Dspring.profiles.active=run 설정하여 정상적으로 동작했습니다.

 

 

4. 설정파일은 다 됬고 이제 배포

4.1 mvn clean package

4.2 mvn azure-webapp:deploy

4.3 4.2을 하다가 인증 에러 발생시 0.1 에 로그인 다시 진행

 

5. 배포 완료 

5.1 이제 포탈에서 적혀있는 url로 접속하여 정상적으로 접속 되는지 확인하면 됩니다.

 

Azure 에 'App Services'는 'Virtual machines' 과 달리 

Development Tools 이나 Scale out 과  같은 기능들도 지원되고 있습니다.

 

 

아직은 기본적으로 각 AppService만 띄워 사용중인데 추후 기회가 된다면

제공하고 있는 기능들을 활용 후 정리해두도록 하겠습니다.

 

댓글()

Spring Boot Toy 프로젝트 세션 클러스터링-1

JAVA/Spring|2019. 7. 4. 12:30

현재 라즈베리 파이에 올려놓은 Toy Project는 

앞에는 Nginx(web서버) 뒤에는 SpringBoot(WAS라 해야하나 WebApp이라고 해야하나..) 그리고 mysql로 구성되어있습니다.

 

nginx를 둔 이유는 정적 리소스 처리도 있지만, 버전 업을 위해 Boot를 배포해야하는 경우

Boot를 바로 내리기보다 먼저 신규 Boot를 배포하고 nginx의 설정을 기존 boot에서 신규 boot로 reload하여

DownTime을 제거하는 것이 가장 큰 이유였습니다.

 

현재 Toy프로젝트는 로그인 방식이 아니고, 쿠키를 활용하여 동작하기 때문에 위와같이 사용하더라도

사용자에게 문제가 생길 부분은 없지만, 추후 로그인을 위해 그리고 세션에 데이터를 활용하게 된다면

신규버전 배포시에 기존 boot에서 로그인 한 사용자들은 로그아웃이 되는 예상치 상황을 만날수 있음을 인지했습니다.

 

바로 적용을 할지는 의문이지만, 먼저 찾아보았을때 boot(톰캣)도 세션도 클러스터링이 가능하다는 내용들을 발견하여

추후 적용을 할때 빠르게 접근하기 위해 조금씩 내용을 정리할 예정입니다.

(필요할때는 왜 세션서버와 클러스터링이 생각이 떠오르지 않았는지.. 참..--;;)

 

일단 잠시 찾아봤을때

세션 클러스터링 방법

  1. 세션을 여러 서버들이 접근할 수 있는 곳에 저장한다(File, SQL Server, no SQL Server)
  2. 인스턴스 끼리 클러스터링 한다.
  3. 다른 인스턴스에 공유해둔다.

mysql을 사용중이니 mysql을 활용해도 좋을것 같고,

다른 분들을 봤을때 redis에도 저장해서 활용하시는것 같습니다(성능상 redis가 좋긴 하겠지요).

 

다음 글은 해당 내용 적용기를 작성하는걸로!

댓글()

Spring Cloud Config Server

JAVA/Spring|2019. 6. 25. 19:00

요즘 Spring Boot로 배포함에 따라 jar 하나만 배포하는 경우가 많습니다.

때문에 Config 파일은 어떻게 배포해야할지 고려해야할 부분들이 생겼고

 

간단하면서 단순한 두가지 방법이 있습니다.

 

1. Jar에 설정 파일을 포함한다.

   jar내에 config 파일을 포함시켜 배포할 수 있지만 설정을 변경을 해야하는 경우 재배포 하거나

   jar내에 config 파일을 수정할수도 있습니다.

   하지만 간단히 설정만 바꾸고자 하는데 너무 과분하다고 생각이 듭니다.

 

2. 외부에 config 파일을 두고 boot를 띄울때 커맨드를 주어 config를 잡는다.

   java -jar example.jar --spring.config.location:....

  설정파일이 외부에 있기 때문에 설정하기에는 편합니다.

  하지만 배포해야할 서버, 설정을 변경해야 할 서버가 많아진다면 

  노가다이기도 하고, 변경하다가 놓치거나 잘못 변경하는 실수가 발생할 수 있습니다.

  물론 변경 내역 관리가 안되는건 덤입니다.

 

위에 문제점들을 개선할수 있는 방법으로 Spring Cloud Config Server를 활용할수 있습니다.

 

'Spring Cloud Config Server' 란

 

각 서버가 Config 파일을 직접 관리하거나 가지고 있는것이 아니라 'Spring Cloud Config Server'를 통해 설정을 가져오며 'Spring Cloud Config Server'는 설정 파일을 git이나 파일 스토리지에 관리하여 Client 요청에 따라 config 내용을 전달해줍니다.

 

'Spring Cloud Config Server'를 활용하게 되면 App(그림에서는 Microservice) 배포시에

각종 설정들(DB나 기타 설정들)은 고려하지 않아도 되며 'Spring Cloud Config Server' 과 profile을 통해 각 설정들을 얻어올수 있습니다.

 

아직  코드를 깊게 보지 않았으나

조금 사용 해봤을때 일단 설정은 굉장히 간단하며

 

추후 깊게 볼 필요(기본적으로 지원하는 git이나 file이 아니라 커스텀 작업을 해야한다면)가 생기면 추가로 살펴보려고 합니다.

잠깐 살펴보니 JDBC Backend랑 있네요

https://cloud.spring.io/spring-cloud-config/2.1.x/single/spring-cloud-config.html#_jdbc_backend

 

 

참고로 'Spring Cloud Config Server' 를 WebFlux로 띄워볼까 하여 시도해봤지만

@EnableWebFlux 옵션을 추가하면 기동시 에러가 발생합니다.

에러와 Spring starter에서 'Spring Cloud Config Server'만 선택시 tomcat이 함께 포함되는것을 봐서는 내부에 @EnableWebMvc가 포함되어있는것으로 예상됩니다.

 

 

설정

'Spring Cloud Config Server'를 사용하기 위해서는

 

1. @EnableConfigServer 설정

2. git url 및 접속정보 설정(private일 경우 userusername, password 설정 필요)

 

3. git에 설정파일 push(설정파일은 프로젝트명-profile.yml)  , yml아니라 properties도 가능

4. url로 테스트(url:port/프로젝트명/profile)

 

refresh 주기라던가 즉시 refresh 등 실제 사용에 필요한 부분들은 

차차 필요시 알아볼 예정입니다.

 

'Spring Cloud Config Client' 는 건너뛸수 없으니 조만간 블로그로 정리 예정입니다.

댓글()

[SpringBoot] Embedded Tomcat, NioEndpoint

JAVA/Spring|2019. 6. 11. 19:22

SpringBoot Async 처리를 다시 학습 중에

어떻게 Embedded Tomcat에서는 요청을 처리하고 있는지 파악하기 위해 조금 코드를 살펴봤다.

 

 

일단 크게 별다른 설정을 하지 않고 디버그 모드로 붙었을 때 아래와 같이 스레드가 떠있다.

대략 오늘 살펴보려는 스레드는 4가지 정도다

 

  • http-nio-8080-Acceptor-0
    • Http Port accept 하는 스레드(ServerSocket.accept())
    • 관련 클래스는 org.apache.tomcat.util.net.Acceptor
    • accept 되면 getpoller() 후 채널을 regist 한다
      • getpoller는 poller가 여러 개의 경우 rr로 동작한다.
  • http-nio-8080-ClientPoller-0~1
    • Accept 된 Socket을 Selector에 OP_READ를 등록하고 Selector를 epoll 한다.
    • 관련 클래스는 org.apache.tomcat.util.net.NioEndpoint$Poller
    • 요청이 들어오면(read가 불리면), ThreadPoolExecutor.execute를 부르게 되고, 결국에 TaskQueue에 offer()를 부르게 된다.
    • poller가 하나가 아니라서 어떤 기준으로 동작 하나 살펴보니 아래와 같이 값이 설정되어있으며
      • polle는 많아도 2개
      • 기본 동작은 selector는 select()로 blocking이 아닌 select(1000), 1초마다 깨어 난다.

  • http-nio-8080-exec-1~10
    • 실제 ServletThread
    • TaskQueue(BlockingQueue)를 take 하며 대기, poller 혹은 다른 곳에서 TaskQueue에 이벤트를 넣어주면 동작
    • 선후 처리 및 Controller 코드가 보통 이 스레드를 통해 불리게 되며 Async 처리 이후 다시 돌아와(받은 스레드가 대기하여 다시 돌아온다는 이야기는 아니고, ServletThread->Async->ServletThread) 처리
    •  

  • MvcAsync
    • Controller에서 Async 처리(Callable이라던지 기타 등등..) 이후 응답을 직접 하려나? 해서 살펴보니
    • 마지막에 다시 TaskQueue에 offer를 하고 있었다.

 

 

전체 처리를 대략적으로 그리면 아래와 같이 될 것 같다.

 

 

댓글()

[Spring][Thymeleaf] if else그리고 조건문

JAVA/Spring|2019. 5. 22. 19:30

Spring Boot에서 View를 주로 Thymeleaf를 사용하고 있다.

 

생각 안나는 것들은 필요할때마다 검색해서 사용하고 있기 때문에

 

정리해본다.

 

예제는 dutch 값이 모델에 있을경우 dutch.name을 확인 후 selected 세팅을 하도록

 

단순 if

<option  th:if="${dutch!=null}" 
th:each="coffee : ${T(com.meteor.coffee.CoffeeEnum).values()}" 
th:value="${coffee.name()}" th:text="#{${coffee.name()}+'.name'}"
th:selected="${coffee.name() == dutch.coffeeKind.name()}">
</option>

 

 

if-else

<option  th:if="${dutch!=null}" 
th:each="coffee : ${T(com.meteor.coffee.CoffeeEnum).values()}" 
th:value="${coffee.name()}" th:text="#{${coffee.name()}+'.name'}"
th:selected="${coffee.name() == dutch.coffeeKind.name()}">
</option>

<option  th:unless="${dutch!=null}" 
th:each="coffee : ${T(com.meteor.coffee.CoffeeEnum).values()}" 
th:value="${coffee.name()}" th:text="#{${coffee.name()}+'.name'}">
</option>

 

여기서 기억에 남는건 unless에 들어가는 조건이 위의 if 조건에 들어간것과 같아야 한다는것

 

 

추가적으로, 변수 내에서도 분기가 가능하다

<form method="post"> 에서 method를 분기 처리한다면

 

<form method="${dutch==null ? 'post':'put'}"> 처럼 표현이 가능하다.

물론 put은 

<input type="hidden" name="_method" value="put">

으로 표현되겠지만..

 

 

댓글()