29 Ekim 2019 Salı

SpringMVC HandlerInterceptor Arayüzü

Giriş
Şu satırı dahil ederiz. Bu arayüzü gerçekleştiren sınıflardan birisi HandlerInterceptorAdapter 
import org.springframework.web.servlet.HandlerInterceptor;
Bu arayüz aslında bir advice gerçekleştirimi için. Tek farkı @ControllerAdvice yerine bu arayüzden kalıtıyoruz. Açıklaması şöyle. Request ve Response nesnelerini değiştirmek için uygun değil dense de bence çok uygun.
Basically, Interceptor is similar to a Servlet Filter, but in contrast to the latter, It is located after DispatcherServlet and as a result, related HandlerInterceptor class configured inside the application context. Filters are known to be more powerful, they are allowed to exchange the request and response objects that are handed down the chain whereas, Interceptors are just allowed to add some custom pre-processing, option of prohibiting the execution, and also custom post-processing.
Şeklen şöyle



Genel Kullanım
Interceptor, InterceptorRegistry nesnesine eklenir.

Örnek
Şöyle yaparız
@Component
public class CustomWebConfigurer implements WebMvcConfigurer {

  @Autowired
  private InterceptLog logInterceptor;

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(logInterceptor);
  }
}

@Component
public class InterceptLog implements HandlerInterceptor {

   
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
    Object handler) throws Exception {
    if(request.getMethod().equals(HttpMethod.GET.name())
      || request.getMethod().equals(HttpMethod.DELETE.name())
      || request.getMethod().equals(HttpMethod.PUT.name()))    {
        ...
    }
    return true;
  }
}
Örnek - URL
Eğer sadece belli bir URL için eklemek istersek şöyle yaparız
@Configuration
public class WebConfig implements WebMvcConfigurer {

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LogInterceptor() )
            .addPathPatterns("/student/*");;
  }
}
Örnek - URL
Eğer Interceptor'lar arasında sıra belirtmek istersek şöyle yaparız
@Override
public void addInterceptors(InterceptorRegistry registry) {
  registry.addInterceptor(new LogInterceptor()).order(1);
  registry.addInterceptor(new AuthenticationInterceptor()).order(2);
}

preHandle - HttpServletRequest + HttpServletResponse + handle
Açıklaması şöyle. Yani isteği kesmek için uygun bir nokta
This method will be called before sending the request to the controller. Returning false in this method will stop the process from passing the request to the controller
Örnek
Request nesnesine yeni bir özellik ekleyen HandlerInterceptor  için şöyle yaparız.
@Slf4j
public class LogInterceptor implements HandlerInterceptor {

  @Override
  public boolean preHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler) throws Exception {
    UUID uuid = UUID.randomUUID();
    request.setAttribute("start" , System.currentTimeMillis());
    request.setAttribute("request-id", uuid );
    log.info( "{} - calling {}" , uuid , request.getRequestURI() );
    return true;
  }

  @Override
  public void postHandle( HttpServletRequest request,
                          HttpServletResponse response,
                          Object handler,
                         ModelAndView modelAndView) throws Exception {
    log.info( "{} - response in {}ms", 
      request.getAttribute("request-id"),  
      System.currentTimeMillis() - (long) request.getAttribute("start") );
   }

  @Override
  public void afterCompletion(HttpServletRequest request,
                              HttpServletResponse response,
                              Object handler,
                              Exception exception) throws Exception {
    log.info( "{} - completed in {}ms", 
      request.getAttribute("request-id"),  
      System.currentTimeMillis() - (long) request.getAttribute("start") );
  }
}
postHandle metodu
Açıklaması şöyle
This method will be used to perform operations before sending the response to the client.
afterCompletion metodu
Açıklaması şöyle
This method will be used for operations after the whole cycle was completed and the view is generated


25 Ekim 2019 Cuma

SpringMVC @RestController Anotasyonu

Giriş
Şu satırı dahil ederiz.
import org.springframework.web.bind.annotation.RestController;
Bu sınıfın kardeşi @Controller anotasyonu.

Bu anotasyon ilgili sınıftaki bütün metodların birer REST servis noktası olmasını sağlar. Açıklaması şöyle. Bu anotasyon aslında @Controller ve @ResponseBody anotasyonlarının bileşimi.

 @RestController İki Anotasyonun Bileşimidir
Bu iki anotasyon @Controller ve @ResponseBody anotasyonudur. Yani şu iki kod aynıdır.
@Controller
@ResponseBody
public class MyController { }

@RestController
public class MyRestController { }
@ResponseBody ise cevabın JSON olarak gönderimesini sağlar.
@RestController is a stereotype annotation that combines @ResponseBody and @Controller. More than that, it gives more meaning to your Controller and also may carry additional semantics in future releases of the framework.
Örnek
Açıklaması şöyle.
By default Spring will use Jackson to serialize objects returned from endpoints (in case of RestController at least) to JSON. To overcome this just return a String from your endpoint...
Eğer cevabın JSON olarak değil String olarak gitmesini istersek şöyle yaparız.
@PostMapping("/reverse")
public String reverseList(@RequestBody String string) {
  ...
}
RestController Sınıfın Metodlarında Kullanılan Anotasyonlar Nelerdir?
1. Get isteği için  @GetMapping veya onun uzun hali olan @RequestMapping ile birlikte kullanılır.
2. Post isteği için @PostMapping veya onun uzun hali olan @RequestMapping ile birlikte kullanılır.
3. Delete isteği için @DeleteMapping veya onun uzun hali olan @RequestMapping ile birlikte kullanılır.
4. Metodlara geçilen parametreler için @PathVariable kullanılır.

RestController'da Kullanılan Metod Parametreleri
Metod parametresi olan nesneler
- controller altındaki dto/request ve dto/response dizinlerinde saklayabiliriz.
- bir projede incoming ve outgoing şeklinde kullanmıştık ancak bence güzel olmamıştı

RestController İçin Path Nasıl Verilir
RestController'a bir path vermek gerekir. Eğer vermezsek controller'ın path'i boş string kabul edilir.

Dolayısıyla base path "api" ise  ve sınııfımızda get için "status" metodu varsa şöyle erişebiliriz.
http://localhost:8080/status

Örnek
RestController'a bir path vermek için şöyle yaparız
@RestController
@RequestMapping("/api")
public class HelloWorldController {

  //URI: http://localhost:8080/api/hello
  @RequestMapping(value = "/hello", method = RequestMethod.GET)
  public ResponseEntity<String> get() {
    return new ResponseEntity<>("Hello World", HttpStatus.OK);
  }

}
value Alanı
@RestController anotasyonu sınıfı Spring Bean haline getirir. Bu alana verilen değer bean'in ismi olur.

21 Ekim 2019 Pazartesi

SpringMVC AbstractHttpMessageConverter Sınıfı

Giriş
Açıklaması şöyle
10. How to Create a Custom Implementation of the HttpMessageConverter to Support a New Type of Request/Response?
You just need to create an implementation of the AbstractHttpMessageConverter and register it using the WebMvcConfigurerAdapter#extendMessageConverters() methods with the classes that generate a new type of request/response.
writeInternal metodu
Örnek
Açıklaması şöyle.
You have to keep in mind that in our example, the defined HTTP message converter will always be applied when the handler method returns the value of type Team (see the supports method), and HTTP Accept header matches "application/vnd.ms-excel".
Şöyle yaparız
@Service
public class TeamToXlsConverter extends AbstractHttpMessageConverter<Team> {
  private static final MediaType EXCEL_TYPE =
    MediaType.valueOf("application/vnd.ms-excel");

  TeamToXlsConverter() {
    super(EXCEL_TYPE);
  }

  @Override
  protected Team readInternal(Class<? extends Team> clazz,
    HttpInputMessage inputMessage)
    throws IOException, HttpMessageNotReadableException {
    return null;
  }

  @Override
  protected boolean supports(Class<?> clazz) {
    return (Team.class == clazz);
  }

  @Override
  protected void writeInternal(Team team, HttpOutputMessage outputMessage)
    throws IOException, HttpMessageNotWritableException {

    try (Workbook workbook = new HSSFWorkbook()) {
      ...
      workbook.write(outputMessage.getBody());
    }
  }
}

20 Ekim 2019 Pazar

SpringMVC CommonsMultipartFile Sınıfı

Giriş
Şu satırı dahil ederiz. MultipartFile arayüzünü gerçekleştirir.
import org.springframework.web.multipart.commons.CommonsMultipartFile;
Açıklaması şöyle.
Spring also makes it easy to handle file upload within a handler method, by automatically binding upload data to an array of CommonsMultipartFile objects. Spring uses Apache Commons FileUpload as the underlying multipart resolver.
Örnek
Şöyle yaparız.
@RequestMapping(value = "/uploadFiles", method = RequestMethod.POST)
public String handleFileUpload(@RequestParam CommonsMultipartFile[] fileUpload)
  throws Exception {

  for (CommonsMultipartFile aFile : fileUpload){
    // stores the uploaded file
    aFile.transferTo(new File(aFile.getOriginalFilename()));
  }
  return "Success";
}


17 Ekim 2019 Perşembe

SpringAOP @Around Anotasyonu

Giriş
Şu satırı dahil ederiz.
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Around hangi metodları keseceğini kendisine geçilen @Pointcut veya anotasyon ile bulur.

Örnek - execution
Elimizde şöyle bir anotasyon olsun
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
public @interface PerformanceLogger {

  TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}
Şöyle yaparız
@Aspect
@Component
public class PerformanceLoggerAspect {

  @Around("execution(public * com.emyasa..*(..)) && @target(PerformanceLogger)")
  public void logPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
    long startTime = System.currentTimeMillis();
    joinPoint.proceed();
    long timeTakenInMilliseconds = System.currentTimeMillis() - startTime;

    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    Class<?> declaringClass =  methodSignature.getMethod().getDeclaringClass();
    PerformanceLogger logger = declaringClass.getAnnotation(PerformanceLogger.class);

    final String timeUnit;
    final long timeTaken;
    switch (logger.timeUnit()) {
      case MILLISECONDS:
        timeUnit = "ms";
        timeTaken = timeTakenInMilliseconds;
        break;
      case SECONDS:
        timeUnit = "s";
        timeTaken = TimeUnit.MILLISECONDS.toSeconds(timeTakenInMilliseconds);
        break;
      default:
        throw new UnsupportedOperationException("timeUnit unsupported");
      }

     final String logMessage = String.format("%s method took %d %s",
       methodSignature.getMethod().getName(), timeTaken, timeUnit);

      LOGGER.info(logMessage);
  }
}
Örnek - @annotation
Şöyle yaparız. Metodumuzda sadece ProceedingJoinPoint parametresi var.
@Aspect
@Configuration
@RequiredArgsConstructor
@Slf4j
public class TimerCounterAspect {


  @Around("@annotation(br.com.myproject.TimerCount)")
  public Object around(ProceedingJoinPoint joinPoint) {
        
    Object oReturn;
    try {
      oReturn = joinPoint.proceed();
    } catch (Throwable throwable) {
      log.error("...", throwable);
      throw new RuntimeException(throwable);
    } finally {
      ...
    }
    return oReturn;
  }
}
Örnek - @annotation + parametre
Elimizde şöyle bir anotasyon olsun.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Benchmark {

    boolean isEnabled() default true;
}
Şöyle yaparız. Metodumuzda sadece ProceedingJoinPoint ve kendi anotasyonumuz olan Benchmark parametresi var.
@Aspect
@Component
public class BenchmarkingAspect {

  @Around("@annotation(benchmark)")
  public Object benchmarkMethodRuntimeAspect(ProceedingJoinPoint proceedingJoinPoint,
    Benchmark benchmark) throws Throwable {
    if (benchmark.isEnabled()) {
      StopWatch stopWatch = new StopWatch();
      stopWatch.start();
      Object returnedValue = proceedingJoinPoint.proceed();
      stopWatch.stop();
      log.info("Benchmarked: {} from class {}",
        proceedingJoinPoint.getSignature().getName(),
        proceedingJoinPoint.getTarget().getClass().getCanonicalName());
        log.info(stopWatch.prettyPrint());
        return returnedValue;
    } else {
      return proceedingJoinPoint.proceed();
    }
  }
}
Örnek - @annotation
Elimizde şöyle bir anotasyon olsun
@Target(ElementType.METHOD) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface LogExecutionTime {

}
Şöyle yaparız. Metodumuzda sadece ProceedingJoinPoint parametresi var.
@Aspect //specifies that this is an aspect
@Component //turn into a bean
public class ExampleAspect {

  @Around("@annotation(LogExecutionTime)")
  public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {

    long start = System.currentTimeMillis(); //executed before the method annotated

    Object proceed = joinPoint.proceed();

    //everything below gets executed after the method.
    long executionTime = System.currentTimeMillis() - start;
    System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");

   return proceed;
  }
}
Örnek
@Pointcut ile şöyle yaparız.
@Component
@Aspect
public class BucketReadPermissionInterceptor {
  @Pointcut("@annotation(com.test.interceptor.ReadPermission)")
  private void readPointcut() {
  }

  @Around("readPointcut()")
  public Response<Object> readCheck(ProceedingJoinPoint joinPoint) {
    Object[] args = joinPoint.getArgs();
    Response<Object> ret = new Response<Object>();
    for (Object object : args) {
      System.out.println("<debug info> " + object);
    }
    try {
      ret = (Response<Object>)joinPoint.proceed();
    } catch (Throwable e) {
      e.printStackTrace();
    }
    return ret;
  }
}