27 Temmuz 2021 Salı

SpringBootLogging Log4J2 Kullanımı

Giriş
SpringBoot loglama için normalde logback kullanır. Açıklaması şöyle
If your spring boot project has been created from the starters project (which almost always is the case), it would have a transitive dependency on spring-boot-starter-logging, and you would not need to include any additional dependency to start with logging in your app. The default choice of framework that spring boot makes is Logback and Slf4j.
SpringBoot logging Ayarları yazısına bakabilirsiniz.

Log4J2 kullanmak için logback veya herhangi bir başka logger'ın path içinde olmaması lazım. Mesela lombok kütüphanesi slf4j kullanıyor.

Örnek
Şöyle yaparız. 
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <exclusions>
    <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-logging</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
Örnek
Şöyle yaparız
</dependency><dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <exclusions>
    <exclusion>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-logging</artifactId>
    </exclusion>
    <exclusion>
     <groupId>ch.qos.logback</groupId>
     <artifactId>logback-classic</artifactId>
    </exclusion>
   </exclusions>
</dependency>
Log4J Konfigürasyon Dosyası
Açıklaması şöyle. Yani XML, JSON veya YAML dosyası kullanılabilir. src/main/resources altına yerleştirilebilir.
Spring Boot automatically configures Log4j if it finds a file named log4j2.xml or log4j2.json or log4j2.yaml in the classpath.

log4j2.xml Dosyası
xml dosyasını yine logback kullanımında olduğu gibi src/main/resources/log4j2.xml şeklinde yaratırız. 

Spring Profile
Açıklaması şöyle
The <SpringProfile> tag lets you include custom configuration based on the active Spring profiles. 
Örnek
Şöyle yaparız
<SpringProfile name=”dev | test”>
<! — configuration to be enabled when the “dev” or “test” profiles are active →
</SpringProfile>

<SpringProfile name=”staging”>
<! — configuration to be enabled when the “staging” profile is active →
</SpringProfile>

<SpringProfile name=”!production”>
<! — configuration to be enabled when the “production” profile is not active →
</SpringProfile>
Örnek
Şöyle yaparız. Burada dev ve prod diye iki profile var
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="logback-spring.xsd">

 <springProfile name="dev">
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
   <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
   </encoder>
  </appender>
  <logger name="com.springboot.postgres" level="debug">
   <appender-ref ref="console" />
  </logger>
 </springProfile>

 <springProfile name="prod">
  <appender name="file" class="ch.qos.logback.core.FileAppender">
   <file>app.log</file>
   <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
   </encoder>
  </appender>
  <logger name="com.springboot.postgres" level="info">
   <appender-ref ref="file" />
  </logger>
 </springProfile>

</configuration>
Environment Properties Kullanımı
Açıklaması şöyle
You can look up properties from Spring Environment within your Log4j2 configuration. For instance, if configure the spring.application.name property in application.properties file, you can use it in your Log4j2 xml configuration with the following configuration. We are using the spring: prefix.
Örnek
Şöyle yaparız
<Properties> 
  <Property name=”applicationName”>${spring:spring.application.name}</property> 
</Properties>
Örnek
Şöyle yaparız. Burada logging.level ortam değişkeni kullanılıyor. Eğer tanımlı değilse varsayılan değer debug
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="logback-spring.xsd">

 <springProfile name="dev">
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
   <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
   </encoder>
  </appender>
  <logger name="com.springboot.postgres" level="${logging.level:-debug}">
   <appender-ref ref="console" />
  </logger>
 </springProfile>
</configuration>
System Properties Kullanımı
Örnek
Şöyle yaparız. Burada sys:logging.level isimli system properties değişkeni kullanılıyor. Eğer tanımlı değilse varsayılan değer debug
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="logback-spring.xsd">

 <springProfile name="dev">
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
   <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
   </encoder>
  </appender>
  <logger name="com.springboot.postgres" level="${sys:logging.level:-debug}">
   <appender-ref ref="console" />
  </logger>
 </springProfile>
</configuration>


SpringMVC @RequestHeader Anotasyonu

Giriş
Şu satırı dahil ederiz
import org.springframework.web.bind.annotation.RequestHeader;
Http Request alanlarını okumak içindir. 

Örnek
Şöyle yaparız
@RequestMapping("/fetchContentType")
public String getContentType(@RequestHeader("Content-Type") String contentType) { return "Content Type is: " + contentType; }
Örnek - Optional Headers
Şöyle yaparız
@RequestMapping("/optionalHeader")
public String getOptionalHeader(@RequestHeader(value = "optional-header", required = false) String headerValue) { return headerValue != null ? "Header value is: " + headerValue : "Header not present."; }
Örnek - Default Values For Optional Headers
Şöyle yaparız
@RequestMapping("/defaultHeaderValue")
public String getDefaultHeader(@RequestHeader(value = "X-Custom-Header", defaultValue = "default-value") String headerValue) { return "Header value is: " + headerValue; }
Örnek - Multiple Headers
Şöyle yaparız
@RequestMapping("/fetchHeaders")
public String getHeaders(@RequestHeader Map<String, String> headers) { return "Headers are: " + headers.toString(); }
Örnek - Type Conversion
Şöyle yaparız
@RequestMapping("/fetchDate")
public String getDate(@RequestHeader("Date") Date date) { return "Date header as a Date object: " + date.toString(); }

SpringCloud Gateway GatewayFilter Arayüzü - Belirli Bir Route İçin Kullanılır

Giriş
Şu satırı dahil ederiz
import org.springframework.cloud.gateway.filter.GatewayFilter;
GlobalFilter ve GatewayFilter benziyorlar. Açıklaması şöyle
Route filters allow the modification of the incoming HTTP request or outgoing HTTP response in some manner. Route filters are scoped to a particular route. Spring Cloud Gateway includes many built-in GatewayFilter Factories.
Örnek
Şöyle yaparız
//Apply filter for a specific route using spring inbuilt filter //"AddRequestHeader"
spring.cloud.gateway.routes[0].filters[0]=AddRequestHeader=first-request-header, 
  first-request-header-value
Örnek
Elimizde şöyle bir kod olsun
@RefreshScope
@Component
public class AuthenticationFilter implements GatewayFilter {

  @Autowired
  private RouterValidator routerValidator;//custom route validator
  @Autowired
  private JwtUtil jwtUtil;

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest request = exchange.getRequest();

    if (routerValidator.isSecured.test(request)) {
      if (this.isAuthMissing(request))
        return this.onError(exchange, "Authorization header is missing in request",
HttpStatus.UNAUTHORIZED);

      final String token = this.getAuthHeader(request);

      if (jwtUtil.isInvalid(token))
        return this.onError(exchange, "Authorization header is invalid",
HttpStatus.UNAUTHORIZED);

      this.populateRequestWithHeaders(exchange, token);
    }
        return chain.filter(exchange);
    }

  ...
}
Yardımcı metodlar için şöyle yaparız
private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) {
  ServerHttpResponse response = exchange.getResponse();
  response.setStatusCode(httpStatus);
  return response.setComplete();
}

private String getAuthHeader(ServerHttpRequest request) {
  return request.getHeaders().getOrEmpty("Authorization").get(0);
}

private boolean isAuthMissing(ServerHttpRequest request) {
  return !request.getHeaders().containsKey("Authorization");
}

private void populateRequestWithHeaders(ServerWebExchange exchange, String token) {
  Claims claims = jwtUtil.getAllClaimsFromToken(token);
  exchange.getRequest().mutate()
    .header("id", String.valueOf(claims.get("id")))
    .header("role", String.valueOf(claims.get("role")))
    .build();
}
RouterValidator kodu şöyledir
@Component
public class RouterValidator {

  public static final List<String> openApiEndpoints = List.of(
    "/auth/register",
    "/auth/login"
  );

  public Predicate<ServerHttpRequest> isSecured =
    request -> openApiEndpoints
      .stream()
      .noneMatch(uri -> request.getURI().getPath().contains(uri));
}
Filtreyi kullanmak için şöyle yaparız
@Configuration
@EnableHystrix
public class GatewayConfig {

  @Autowired
  AuthenticationFilter filter;

  @Bean
  public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
      .route("user-service", r -> r.path("/users/**")
        .filters(f -> f.filter(filter))
        .uri("lb://user-service"))

      .route("auth-service", r -> r.path("/auth/**")
        .filters(f -> f.filter(filter))
        .uri("lb://auth-service"))
      .build();
    }
}
Açıklaması şöyle 
- all requests that starts with /users/** should be routed to user service, and our custom JWT filter should be applied to each such request 
- all requests that starts with /auth/** should be routed to auth service, and our custom JWT filter should be applied to each such request too.
Örnek
Şöyle yaparız
public class CustomerRateLimitFilter extends
AbstractGatewayFilterFactory<CustomerRateLimitFilter.Config> {
  private final RateLimiter<?> rateLimiter = ...;
  private final KeyResolver keyResolver = ...;
       
  @Override
  public GatewayFilter apply(Config config) {
    return new OrderedGatewayFilter((exchange, chain) -> {

      Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
      return keyResolver.resolve(exchange).flatMap(key -> {
        if (StringUtil.isNullOrEmpty(key)) {
          return handleErrorResponse(exchange, HttpStatus.UNPROCESSABLE_ENTITY);
        }
        Mono<RateLimiter.Response> result = rateLimiter.isAllowed(route.getId(), key);
        return result.flatMap(response -> {

        response.getHeaders().forEach((k, v) -> exchange.getResponse().getHeaders()
.add(k, v));

          if (response.isAllowed()) {
            return chain.filter(exchange);
          }
          return handleErrorResponse(exchange, HttpStatus.TOO_MANY_REQUESTS);
        });
      });
    }, RATELIMIT_ORDER);
  }
 
}
Kullanmak için şöyle yaparız
spring:
  cloud:
    gateway:
      routes:
        - id: face-match-api-route
          uri: http://localhost:8800
          predicates:
            - Path=/face/**
          filters:
            - StripPrefix=1
            - name: CustomerRateLimitFilter
            - name: CustomerQuotaFilter

26 Temmuz 2021 Pazartesi

SpringRetry FixedBackOffPolicy Sınıfı

Giriş
Açıklaması şöyle.
BackOffPolicy is used to control back off between retry attempts. A FixedBackOffPolicy pauses for a fixed period of time before continuing.
Örnek
Şöyle yaparız
@Bean
public RetryTemplate retryTemplate() {

  RetryTemplate retryTemplate = new RetryTemplate();

  //BackOff Policy
  FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
  fixedBackOffPolicy.setBackOffPeriod(2000l);
  retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

  //Retry Policy
  SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
  retryPolicy.setMaxAttempts(2);
  retryTemplate.setRetryPolicy(retryPolicy);

  return retryTemplate;

}

SpringRetry TimeoutRetryPolicy Sınıfı

Örnek - timeout
Şöyle yaparız
RetryTemplate template = new RetryTemplate();

TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(30000L);

template.setRetryPolicy(policy);

Foo result = template.execute(new RetryCallback<Foo>() {
@Override
    public Foo doWithRetry(RetryContext context) {
        // Do stuff that might fail, e.g. webservice operation
        return result;
    }

});

SpringRetry SimpleRetryPolicy Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.retry.policy.SimpleRetryPolicy;
Açıklaması şöyle.
RetryPolicy determines when an operation should be retried. A SimpleRetryPolicy is used to retry a fixed number of times.
constructor
Örnek
Şöyle yaparız
public class PayApiRetryTemplate extends RetryTemplate implements InitializingBean {

  private final PayApiConnProp prop;
  private final Class<? extends ApiException>[] exceptions;

    @Override
    public void afterPropertiesSet() throws Exception {
        this.setBackOffPolicy(...);
        this.setRetryPolicy(retryPolicy());
    }

   
  private Map<Class<? extends Throwable>, Boolean> includedExceptions() {
    Map<Class<? extends Throwable>, Boolean> includedExceptions = new HashMap<>();
    for (Class<? extends ApiException> exception : this.exceptions) {
      includedExceptions.put(exception, true);
    }
    return includedExceptions;
  }

  private RetryPolicy retryPolicy() {
    return new SimpleRetryPolicy(this.prop.getRetry().getMaxAttempts(), 
      includedExceptions());
  }
}
setMaxAttempts metodu
Örnek
Şöyle yaparız
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;

@SpringBootApplication
@EnableRetry
@EnableJpaRepositories(basePackages = "com.betterjavacode.retrydemo.daos")
public class RetrydemoApplication {

  @Bean
  public RetryTemplate retryTemplate(){
    RetryTemplate retryTemplate = new RetryTemplate();

    FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
    backOffPolicy.setBackOffPeriod(100);

    SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
    simpleRetryPolicy.setMaxAttempts(2);

    retryTemplate.setRetryPolicy(simpleRetryPolicy);
    retryTemplate.setBackOffPolicy(backOffPolicy);
    return retryTemplate;
  }
}

25 Temmuz 2021 Pazar

SpringWebFlux Mono.switchIfEmpty metodu

Giriş
Flux.switchIfEmpty metodu ile kardeştir.

Açıklaması şöyle. Mono boş ise switchIfEmpty() ile belirtilen sonucu döner
we can emit an error signal when our mono has no values to emit.
Örnek
Şöyle yaparız
Mono<String> publisher = Mono.just("My first publisher");
publisher
  .filter(value -> value.equals("This is not my first publisher"))
  .switchIfEmpty(Mono.error(new RuntimeException("Something went wrong")))
  .subscribe(new MySubscriber());
Örnek
Şöyle yaparız.
Mono.just(new Employee().setName("Kill"))
    .switchIfEmpty(Mono.defer(() -> Mono.just(new Employee("Bill"))))
    .block()
    .getName();
Örnek
Şöyle yaparız. 
@RestController
@RequestMapping(value = "/posts")
class PostController {
  private final PostRepository posts;

  @GetMapping("/{id}")
  public Mono<Post> get(@PathVariable("id") Long id) {
    return Mono.just(id)
      .flatMap(posts::findById)
      .switchIfEmpty(Mono.error(new PostNotFoundException(id)));
  }
}