29 Kasım 2020 Pazar

SpringBoot Test @SpringBootTest Anotasyonu properties Alanı

Giriş
Bazı bean'lerin testlerde yüklenmemesi için kullanılabilir

Örnek
@EnabledScheduling anotasyonlu sınıfların testlerde yüklenmemesini isteyebiliriz. Bu durumda @EnabledScheduling kullanan sınıfları yüklenmesini bir tane property değer ile kontrol etmek gerekir.

Kodumuzu şu hale getirelim. Böylece @EnableScheduling sadece io.reflectoring.scheduling.enabled property değeri true iken etkin olur
@Configuration
@EnableScheduling
@ConditionalOnProperty(
        name = "io.reflectoring.scheduling.enabled",
        havingValue = "true",
        matchIfMissing = true)
public class SchedulingConfiguration {
}
Test kodumuzda şöyle yapalım. SchedulingConfiguration sınıfının bean olarak yüklenmediği dolayısıyla da @EnabledScheduling anotasyonunun etkin olmadığı görülebilir.
@SpringBootTest(properties = "io.reflectoring.scheduling.enabled=false")
class SchedulingTest {

  @Autowired(required = false)
  private SchedulingConfiguration schedulingConfiguration;

  @Test
  void test() {
    assertThat(schedulingConfiguration).isNull();
  }
}
Örnek
Teste mahsus bir property değeri atamak için kullanılabilir. Şöyle yaparız
@SpringBootTest(properties = "foo=bar")
class SpringBootPropertiesTest {

  @Value("${foo}")
  String foo;

  @Test
  void test(){
    assertThat(foo).isEqualTo("bar");
  }
}

25 Kasım 2020 Çarşamba

JwtTokenUtil Sınıfı

Giriş
Şu satırı dahil ederiz
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
Bu sınıfta JWT ile çalışmayı kolaylaştıran bazı metodlar var

Extract Metodları
Şöyle yaparız
public String extractUsername(String token) {
  return extractClaim(token, Claims::getSubject);
}

public Date extractExpiration(String token) {
  return extractClaim(token, Claims::getExpiration);
}
Her extract işlemi için şu ortak metodlar kullanılıyor
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
  final Claims claims = extractAllClaims(token);
  return claimsResolver.apply(claims);
}

private Claims extractAllClaims(String token) {
  return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
Burada görüldüğü gibi JWT'ye erişmek için kullanılan bir SECRET_KEY değeri var. Bu değeri bir yerde tanımlamak lazım. Yani sınıf içinde şöyle bir alan var
private String SECRET_KEY = "secret";
Token Kontrolleri
Örnek
Şöyle yaparız
public Claims getAllClaimsFromToken(String token) {
  return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}

private boolean isTokenExpired(String token) {
  return this.getAllClaimsFromToken(token).getExpiration().before(new Date());
}

public boolean isInvalid(String token) {
  return this.isTokenExpired(token);
}
Token Üretmek
Şöyle yaparız. Bu kod aslında biraz eksik ve kötü. Birincisi kullanıcıya ait boş claim yaratıyor. İkincisi eski java Date sınıfını kullanıyor
public String generateToken(String subject) {
   Map<String, Object> claims = new HashMap<>();
   return createToken(claims, subject);
}

private String createToken(Map<String, Object> claims, String subject) {
  return Jwts.builder()
    .setClaims(claims)
    .setSubject(subject)
    .setIssuedAt(new Date(System.currentTimeMillis()))
    .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
    .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
    .compact();
}

SpringSecurity OAuth2 Login application.properties Ayarları

Giriş
Facebook, GitHub, google ile login için kullanılabilir. Kaç tane OAuth2 Login sağlayıcı ayarladıysak giriş sayfasında görünür. Giriş sayfası şöyledir


Google
Örnek 
Şöyle yaparız
spring.security.oauth2.client.registration.google.client-id=<Client-Id>
spring.security.oauth2.client.registration.google.client-secret=<Client-Secrete>
Örnek
Şöyle yaparız
spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: google-client-id
            client-secret: google-client-secret
GitHub
Örnek 
Şöyle yaparız
spring.security.oauth2.client.registration.github.client-id=<Client-Id>
spring.security.oauth2.client.registration.github.client-secret=<Client-Secrete>
Örnek 
Şöyle yaparız. Client nesnesi Authorization Server'dan token alabilir.
spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: your-github-client-id
            client-secret: your-github-client-secret
Facebook
Örnek 
Şöyle yaparız
spring.security.oauth2.client.registration.facebook.client-id=<Client-Id>
spring.security.oauth2.client.registration.facebook.client-secret=<Client-Secrete>
spring.security.oauth2.client.registration.facebook.redirect-uri=http://localhost:8080/api/v1/myapp
Örnek
Şöyle yaparız
spring:
  security:
    oauth2:
      client:
        registration:
           facebook:
              clientId: myID
              clientSecret: mySecret
              accessTokenUri: https://graph.facebook.com/oauth/access_token
              userAuthorizationUri: https://www.facebook.com/dialog/oauth
              tokenName: oauth_token
              authenticationScheme: query
              clientAuthenticationScheme: form
              resource:
                 userInfoUri: https://graph.facebook.com/me

server:
  port: 8080

SpringContext @Scope Anotasyonu - Prototype Scope - Her Kullanımda Yeni Bir Bean Yaratılır

Giriş
Her çağrıda yeni bir bean yaratır.

value Alanı
- ConfigurableBeanFactory.SCOPE_PROTOTYPE sabiti veya
- BeanDefinition.SCOPE_PROTOTYPE sabiti veya
- normal string kullanılabilir.

BeanDefinition.SCOPE_PROTOTYPE sabiti şöyledir
/**
 * Scope identifier for the standard prototype scope: {@value}.
 * <p>Custom scopes can be added via {@code registerScope}.
 * @see #registerScope
 */
String SCOPE_PROTOTYPE = "prototype";
Aynı sonuca varmak için @Scope anotasyonu yerine @Lookup anotasyonu da kullanılabilir.

Örnek - ConfigurableBeanFactory Sabiti
Şöyle yaparız
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,
       proxyMode = ScopedProxyMode.TARGET_CLASS)
Örnek - normal string Sabiti
Şöyle yaparız.
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CompX { ....}
Scope Bean ve Singleton
Singleton bean içine Scope bean inject edilse bile singleton bean hep aynı nesneyi kullanır. 

Çözüm 1
Şöyle yaparız
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ProductPurchasedEvent {
  ...
}

@Service
public class PurchaseService {
  private ProductPurchasedEvent productPurchasedEvent;

  @Autowired
  public PurchaseService(ProductPurchasedEvent productPurchasedEvent) {
    this.productPurchasedEvent = productPurchasedEvent;
  }
  ...
}
Açıklaması şöyle. Burada dikkat edilmesi gereken şey şu. Bu prototype bean'in herhangi bir metod çağrılınca yeni bir nesne yaratılır. Yani iki setter() çağırırsak iki nesne yaratılır. Dolayısıyla prototype bean stateful ise tehlikeli bir yöntem
when a singleton has a prototype dependency (unless we customize it). It eagerly creates and injects all dependent objects at the application startup level
...
Spring supports proxy pattern with injecting prototype beans which means instead of injecting a real bean itself we inject a proxy object that delegates all the work to the real created beans
...
If we enable TARGET_CLASS proxy mode on our prototype class, spring creates a new instance for every method call of prototype
Çözüm 2
Her seferinde yeni bir nesne üretmek için şöyle yaparız
@Service
public class FactoryUtil implements ApplicationContextAware {

  private static ApplicationContext context;

  public FactoryUtil() {
  }

  public static <T> T getBean(Class<T> beanClass) {
    return context.getBean(beanClass);
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) 
    throws BeansException {
    context = applicationContext;
  }
}
Kullanmak için şöyle yaparız
@Component
public class ShapeFactory {
  private Map<ShapesType, Shape> myServiceCache = new HashMap<>();

  private final List<Shape> handlers;

  @Autowired
  private ShapeFactory(List<Shape> handlers) {
    this.handlers = handlers;
  }

  public static Shape getShapeImplementation(ShapesType shapesType) {

    Shape service = myServiceCache.get(shapesType);
    if (null == service) {
      throw new RuntimeException("Unknown shape type: " + shapesType);
    }
    return FactoryUtil.getBean(service.getClass());
  }
}

Scope Bean'ler İçin Pool
Örnek
Elimizde bir bean olsun.
@Component("sheet")
@Scope(value = "prototype")
public class Sheet {
  // Implementation goes here
}
Bu bean için bir havuz oluşturalım.
public class SheetPool {

  private List<Sheet> sheets;

  public List<Sheet> getSheets() {
    return sheets;
  }

  public Sheet getObject() {
    int index = ThreadLocalRandom.current().nextInt(sheets.size());
    return sheets.get(index);
  }

}
Her çağrıda bu bean'den yeni bir tane oluşturmak için şöyle yaparız.
@Configuration
public class ApplicationConfig {

  @Autowired
  ApplicationContext applicationContext;

  @Bean
  public SheetPool sheetPool() {
    SheetPool pool = new SheetPool();

    IntStream.range(0, 10).forEach(e -> {
      pool.getSheets().add((Sheet) applicationContext.getBean("sheet"));
    });

    return pool;
  }
}

24 Kasım 2020 Salı

Swagger (OpenAPI ) Kullanımı

Giriş
Açıklaması şöyle
- Springfox has evolved from a project originally created by Marty Pitt and was named swagger-springmvc. Much kudos goes to Marty.
- springfox-swagger is an implementation of this specification based on the Spring ecosystem.
- springfox-swagger-ui encapsulates swagger-ui, making it possible to use Spring services.

Maven
Swagger 3 için şu satırı dahil ederiz
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-boot-starter</artifactId>
  <version>3.0.0</version>
</dependency>

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>3.0.0</version>
</dependency>
Adresler
JSON çıktı için şu adrese gideriz
http://localhost:8000/api-doc
YAML çıktı için şu adrese gideriz
http://localhost:8000/api-doc/swagger.yaml
Swagger 3 UI için şu adrese gideriz
http://localhost:8080/swagger-ui/index.html
Swagger UI
GET, POST işlemleri farklı renklerde. Şeklen şöyle
Her işlemin detayı şöyle


Bir örnek burada.

Select a Definition
Açıklaması burada. Eğer hiç bir ayar yapmazsak "default" olarak gelir. Docket.groupName() ile yeni bir definition eklenir.

OAS3
Specification v3 olduğunu gösterir Şeklen şöyle

Şeklen şöyle



Docket Sınıfı
Şu satırı dahil ederiz
import springfox.documentation.spring.web.plugins.Docket;
apis metodu
Örnek
Şöyle yaparız
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
public class SpringFoxConfig {
  @Bean
  public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
        .select()
        .apis(RequestHandlerSelectors.any())
        .paths(PathSelectors.any())
        .build();
  }
}
globalRequestParameters metodu
Örnek
Şöyle yaparız. Böylece GET isteğine limit ve pages parametreleri geçilmeli diye belirtiyoruz.
import springfox.documentation.service.ParameterType;
import springfox.documentation.service.RequestParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

@Bean
public Docket getDocketInstanceEx1() {
  final Set<String> produces = new HashSet<String>();
  produces.add(MediaType.APPLICATION_JSON_VALUE);
  produces.add(MediaType.APPLICATION_XML_VALUE);
		
  List<RequestParameter> listRequestParamters = new ArrayList<>();
  RequestParameter limitParameter = new RequestParameterBuilder()
    .name("limit") 
    .required(false)
    .query(q -> q.model(modelSpecificationBuilder -> modelSpecificationBuilder.scalarModel(ScalarType.STRING)))
    .in(ParameterType.QUERY)
    .build();
  RequestParameter pagesParameter = new RequestParameterBuilder()
    .name("pages")
    .required(false)
    .query(q -> q.model(modelSpecificationBuilder -> modelSpecificationBuilder.scalarModel(ScalarType.STRING)))
    .in(ParameterType.QUERY)
    .build();
  listRequestParamters.add(limitParameter);
  listRequestParamters.add(pagesParameter);
		
  return new Docket(DocumentationType.SWAGGER_2)
    .produces(produces)
    .consumes(produces)
    .globalRequestParameters(listRequestParamters)
    .select()
    .apis(RequestHandlerSelectors.basePackage("ro.stefan.controller"))
    .paths(PathSelectors.any())
    .build();
}	

produces metodu
Bu alana girilen değer API Documentation sayfasında Response Content Type alanında görülebiliyor. Açıklaması şöyle
Because as mentioned already an API can accept and return data in different formats (json, xml, etc.) so we can use consumes and produces to specify the MIME types understood by your API. To set the consumes and produces type is easy and just represents an array of MIME type.
This modification as presented in the examples are done globally so that means that all controllers from that spring boot project will render this but you can override this value at API level (method from the controller) if you have exceptions.
Örnek - global olarak kullanım
Şöyle yaparız
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
public class SpringFoxConfig {
 
 @Bean
 public Docket getDocketInstance() {
  final Set<String> produces = new HashSet<String>();
  produces.add(MediaType.APPLICATION_JSON_VALUE);
  produces.add(MediaType.APPLICATION_XML_VALUE);
  
  return new Docket(DocumentationType.SWAGGER_2)
    .produces(produces)
    .select()
    .apis(RequestHandlerSelectors.basePackage("ro.stefan.controller"))
    .paths(PathSelectors.any())
    .build();
 }
}
select metodu
Örnek
Şu bean'i yaratmak gerekir.
@Configuration
public class SwaggerConfig {
  @Bean
  public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
      .select()
      // .apis(RequestHandlerSelectors.any())
      .apis(RequestHandlerSelectors.basePackage("com.tahaburak.atem.controller"))
      .paths(PathSelectors.any())
      .build();
  }
}
ApiInfo  Sınıfı
Şu satırı dahil ederiz
import io.swagger.model.ApiInfo;
Örnek
Şöyle yaparız
@Configuration
public class SwaggerConfiguration {

  @Bean
  public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
      .select()
      .apis(RequestHandlerSelectors.any())
      .paths(PathSelectors.any())
      .build()
      .apiInfo(apiInfo());
  }

  private ApiInfo apiInfo() {
    return new ApiInfo(
      "Spring Boot Simple REST API",
      "Spring Boot Simple REST API Swagger Documentation",
      "Version 1",
      "urn:tos",
      new Contact("Admin", "www.datamify.com", "info@datamify.com"),
        "Apache 2.0",
        "https://www.apache.org/licenses/LICENSE-2.0",
        new ArrayList<>());
  }
}