'boot'에 해당되는 글 13건

  1. 2020.04.24 [Thymeleaf] Springboot + Thymeleaf 사용 시 viewResolver 이슈.
  2. 2020.04.09 [Spring] WebClient 사용 예제
  3. 2020.04.09 [Spring] Scheduler + WebClient Hello ~

[Thymeleaf] Springboot + Thymeleaf 사용 시 viewResolver 이슈.

ITWeb/개발일반 2020. 4. 24. 08:17

이전 글 참고)

https://jjeong.tistory.com/1386

 

controller 단에서 viewName 작성 시 "/" 를 제거 하고 작성을 하셔야 합니다.

 

Spring framework 의 UrlBasedViewResolver.java 내 코드를 보면,

protected AbstractUrlBasedView buildView(String viewName) throws Exception {
   Class<?> viewClass = getViewClass();
   Assert.state(viewClass != null, "No view class");
   AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
   view.setUrl(getPrefix() + viewName + getSuffix());
   
   ...중략...
}

Thymeleaf 기본 설정을 보면,

spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

...중략...

 

로컬에서는 되는 viewName 그러나 서버에서 안되는 ...)

    @GetMapping("/list")
    public String list(Model model) {
        return "/list";
    }

 

서버에서 되는 viewName)

    @GetMapping("/list")
    public String list(Model model) {
        return "list";
    }

둘 다 resources/templates/list.html 을 찾습니다.

만약, templates/thing/list.html 로 설정을 해야 한다면,

 

return "thing/list"; 

로 작성을 하시면 됩니다.

로컬에서만 테스트 하다 보면 이런 내용도 왜 안되지 하고 있을 때가 있어서 기록 합니다.

:

[Spring] WebClient 사용 예제

ITWeb/개발일반 2020. 4. 9. 17:01

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.RequestHeadersSpec.html#retrieve--

- retrieve()
Perform the HTTP request and retrieve the response body.
This method is a shortcut to using exchange() and decoding the response body through ClientResponse.

- exchange()
Perform the HTTP request and return a ClientResponse with the response status and headers. You can then use methods of the response to consume the body

 

https://spring.io/guides/gs/reactive-rest-service/

 

[Non Blocking]

// retrieve() 예제
  @Override
  public void runner() {
    Mono<String> response = WebClient
        .create(watcherEndpointHost)
        .method(HttpMethod.POST)
        .uri(watcherEndpointUri)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
        .acceptCharset(Charset.forName("UTF-8"))
        .body(BodyInserters.fromValue(getCheckQuery()))
        .retrieve()
        .bodyToMono(String.class);

    response.subscribe(result -> {
      logger.debug("{}", result);
    }, e -> {
      logger.debug("{}", e.getMessage());
    });
  }
  
// exchange() 예제  
  @Override
  public void runner() {
    Mono<String> response = WebClient
        .create(watcherEndpointHost)
        .method(HttpMethod.POST)
        .uri(watcherEndpointUri)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
        .acceptCharset(Charset.forName("UTF-8"))
        .body(BodyInserters.fromValue(getCheckQuery()))
        .exchange()
        .flatMap(clientResponse -> clientResponse.bodyToMono(String.class));

    response.subscribe(result -> {
      logger.debug("{}", result);
    }, e -> {
      logger.debug("{}", e.getMessage());
    });
  }

 

[Blocking]

  @Override
  public void runner() {
    String response = WebClient
        .create(watcherEndpointHost)
        .method(HttpMethod.POST)
        .uri(watcherEndpointUri)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
        .acceptCharset(Charset.forName("UTF-8"))
        .body(BodyInserters.fromValue(getCheckQuery()))
        .exchange()
        .block()
        .bodyToMono(String.class)
        .block();

    logger.debug("{}", response);
  }
  
  @Override
  public void runner() {
    Mono<String> response = WebClient
        .create(watcherEndpointHost)
        .method(HttpMethod.POST)
        .uri(watcherEndpointUri)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
        .acceptCharset(Charset.forName("UTF-8"))
        .body(BodyInserters.fromValue(getCheckQuery()))
        .retrieve()
        .bodyToMono(String.class);

    String result = response.block();
    
    logger.debug("{}", result);
  }

 

WebClient  는 기본 async 방식으로 동작 합니다.

그리고 어디선가 문서에서 봤는데 Connection Pool 관련 고민을 하지 않고 사용해도 된다고 했던 것으로 기억 합니다.

구글링 하다 걸린 코드 걸어 둡니다.

 

[Timeout 설정]

// TcpClient 이용
TcpClient tcpClient = TcpClient.create()
                 .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000) // Connection Timeout
                 .doOnConnected(connection ->
                         connection.addHandlerLast(new ReadTimeoutHandler(10)) // Read Timeout
                                   .addHandlerLast(new WriteTimeoutHandler(10))); // Write Timeout
WebClient webClient = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
    .build();


// ReactorClientHttpConnector 이용
ReactorClientHttpConnector connector =
            new ReactorClientHttpConnector(options ->
                    options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000));
WebClient webClient = WebClient.builder().clientConnector(connector).build();

 

보시면 아시겠지만 방법은 다양하게 많이 있습니다.

직관적이고 이해 하기 쉬운 방법을 찾아서 사용하시면 될 것 같습니다.

 

:

[Spring] Scheduler + WebClient Hello ~

ITWeb/개발일반 2020. 4. 9. 15:09

필요한 Dependency)

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-quartz'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-webflux'
}

 

Main Application 에서 @EnableScheduling 을 작성 해 주시면 아주 쉽게 Annotation 설정 만으로 사용 하실 수 있습니다.

예제는 아래 문서 보시면 너무 쉽게 되어 있습니다.

https://spring.io/guides/gs/scheduling-tasks/

 

추가적인 내용을 조금 더 작성 하자면,

- fixedDelay

실행 시간을 포함해서 완료 된 이후 타이머가 동작 하는 방식 입니다.

 

- fixedRate

실행 시간 상관 없이 그냥 지정된 타이머 주기로 동작 하는 방식 입니다.

결국, 실행 시간이 길 경우 중복 실행이 될 수 있습니다.

 

- PeriodicTrigger

특정 주기에 맞춰서 실행 됩니다.

fixedRate 과 유사한 방식 이라고 보시면 됩니다.

 

- CronTrigger

cron 설정 주기에 맞춰서 실행 됩니다.

 

저는 그냥 Web Service 형태로 해서 구성을 해서 아래와 같이 적용했습니다.

 

Scheduler 에 대한 Abstract 를 만들어서 사용했습니다. (구글링 해보면 예제 코드 많이 나옵니다.)

  @PostConstruct
  public void init() {
    this.startScheduler();
  }

  @Override
  public void runner() {
    String response = WebClient
        .create(watcherEndpointHost)
        .method(HttpMethod.POST)
        .uri(watcherEndpointUri)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
        .acceptCharset(Charset.forName("UTF-8"))
        .body(BodyInserters.fromValue(getCheckQuery()))
        .exchange()
        .block()
        .bodyToMono(String.class)
        .block();

    logger.debug("{}", response);
  }

  @Override
  public Trigger getTrigger() {
    return new PeriodicTrigger(10, TimeUnit.SECONDS);
  }

  @Override
  public String getCheckQuery() {
    ...중략...
    return checkQuery;
  }

GenericScheduler 를 Singleton 으로 만들어서 Abstract 를 하나 구성했습니다.

  public void startScheduler() {
    GenericScheduler.getInstance()
        .schedule(getRunnable(), getTrigger());
  }

  public void stopScheduler() {
    GenericScheduler.getInstance().shutdown();
  }

  private Runnable getRunnable(){
    return new Runnable(){
      @Override
      public void run() {
        runner();
      }
    };
  }

  public abstract void runner();

  public abstract Trigger getTrigger();

  public abstract String getCheckQuery();

대략적인 구성은 아래 처럼 나옵니다.

@SpringBootApplication
@EnableScheduling
public class MegatoiMonitorWatcherApplication { ... }

public class GenericScheduler { ... }

public abstract class AbstractGenericScheduler { ... }

@Component
public class WatcherCpuChecker extends AbstractGenericScheduler { ... }

 

: