Java REST (JAX-RS)

Paylaş

Java ile REST tabanlı web servis oluşturmak için kullanılan JAX-RS kurulumu, kullanımı ve REST API hazırlarken dikkat edilmesi gereken genel kabul görmüş standartlar yer alıyor.

REST Nedir?

REST hakkında detaylı bilgi almak için REST Nedir? yazıma bakmalısın.

JAX-RS Nedir?

Java programlama dili ile REST tabanlı web servis oluşturmak için kullanılan JCP tarafından belirlenen JSR 339, JSR 370, JSR 311 şartnamesidir.

Java REST API oluşturma

Java ile REST tabanlı web servis oluşturmak için JSR 339, 370 veya 311 şartnamesinde belirtilen kuralları yerine getiren Jersey, RESTEasy, Apache CXF kütüphaneleri kullanılabilir.

REST tabanlı web servislerin belirli bir kuralı olmadığından Java Servlet kullanılarak REST API oluşturulabilir.

Kütüphane kullanmak standart-ortak geliştirme yapılması, WADL üretmesi, Java sınıflarını kolayca JSON-XML veri türlerine göre ifade etme olanağı sağlar.

Kütüphaneler aynı şartnameyi yerine getirdiklerinden benzer kullanıma sahiptir.

Java EE tarafından referans olarak kabul edildiğinden Java ile REST API oluşturma işlemini Jersey kütüphanesi ile hazırlayalım.

Kütüphaneyi kullanmak için maven projesi oluşturma, kütüphane dosyalarını pom.xml dosyasına ekleme, web.xml ayarlarını yapma gibi ayarların yapılması gerekir.

Maven projesi oluşturma

Bunun yerine Maven içerisinde yer alan archetype:generate kullanarak önceden hazırlanmış archetype ile hızlıca ayarları yapabiliriz.

mvn archetype:generate 
-DarchetypeGroupId=org.glassfish.jersey.archetypes 
-DarchetypeArtifactId=jersey-quickstart-webapp 
-DgroupId=com.yusufsezer 
-DartifactId=JavaRestProjem 
-DinteractiveMode=false

Jersey 3 sürümünden sonra paket adlandırması olarak javax yerine jakarta kullanmaktadır.

Jersey 2 sürümü Servlet container (tomcat 9 öncesi) ve Application Server (jakarta desteği olmayan) ile çalışmayacaktır.

Bunun önüne geçmek için maven projesi oluştururken -DarchetypeVersion=2.31 veya 2.4, 2.5, 2.6, 2.7, 2.8, 2.9 parametresini eklemek yeterli olacaktır.

Projeyi derleme

Aşağıdaki komut ile proje derlenir.

mvn package

Derleme işlemi ile target dizinine .war dosyası oluşturulur.

Oluşturulan dosya Servlet Container veya Application Server arayüzü üzerinden yayınlanır.

Servlet Container veya Application Server adresine erişerek proje ile oluşturulan REST adresine (webapi/myresource) erişim sağlanır.

Java sürümünü değiştirme

Oluşturulan maven projesine ait pom.xml dosyası Java 7 sürümüne (1.7) göre oluşturulmaktadır.

Java sürümünü değiştirmek için maven-compiler-plugin ayarını değiştirmek yeterli olacaktır.

<configuration>
  <source>21</source>
  <target>21</target>
</configuration>

Jersey nasıl çalışır

Maven archetype ile oluturulan proje içerisinde yer alan web.xml incelendiğinde aşağıdaki servlet ayarları görünecektir.

<servlet>
  <servlet-name>Jersey Web Application</servlet-name>
  <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
  <init-param>
    <param-name>jersey.config.server.provider.packages</param-name>
    <param-value>com.yusufsezer</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>Jersey Web Application</servlet-name>
  <url-pattern>/webapi/*</url-pattern>
</servlet-mapping>

ServletContainer sınıfı org.glassfish.jersey.servlet yolunda yer alan sıradan bir Java Servlet sınıfıdır.

Sınıf load-on-startup ve 1 değeri ile ilk olarak çalıştırılacağı belirlenmiştir.

Java Servlet sınıfı jersey.config.server.provider.packages ayarı ile JAX-RS sınıflarının yer aldığı paket belirlenmiştir.

Sınıfı sıradan Java Servlet sınıfı gibi çalışarak Java Reflection yardımı ile Java Annotations araması yaparak işlem yapar.

REST API adresini belirlemek için web.xml dosyasında servlet-mapping yapılarak /webapi/ ile başlayan isteklerin Jersey tarafından ele alınması sağlanmıştır.

JAX-RS kullanımı

Proje ile oluşturulan MyResource sınıfı incelendiğinde sınıf başına @Path ile sınıfın çalışacağı adres belirlenmiştir.

Sınıf içerisinde yer alan getIt metoduna eklenen @GET ile metodun çalışacağı HTTP yöntemi @Produces ile çıktı türü belirlenmiştir.

Çıktı olarak XML veya JSON türünden değer döndürmek için MIME türü değeri (application/xml, application/json) veya MediaType sınıfında yer alan değerler (MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON) kullanılabilir.

Proje tekrar derlenip çalıştırıldığında sayfa header bilgisindeki Content-Type değeri belirlenen çıktı değeri olacaktır.

Sınıf kullanımı

JAX-RS düz metin (String) yerine sınıf kullanmak istendiğinde veri türüne göre ek kütüphanelerin kullanılması gerekecektir.

Örnek bir sınıf oluşturalım.

public class Person {

    private long id;
    private String firstName;
    private String lastName;

   // constructor-get-set metotlar

}

XML

JAX-RS ile XML çıktısı vermeye çalışalım.

@GET
@Produces(MediaType.APPLICATION_XML)
public Person getIt() {
    return new Person(1L, "Yusuf", "Sezer");
}

Projeyi tekrar derleyip REST adresini açtığımızda Internal Server Error hatası Servlet container veya Application Server log ekranında aşağıdaki hata yer alacaktır.

MessageBodyWriter not found for media type=application/xml, 
type=class com.yusufsezer.model.Person, 
genericType=class com.yusufsezer.model.Person.

Bu hatayı gidermek için sınıfın başına @XmlRootElement eklemek yeterli olacaktır.

XML desteği JDK 9 ile birlikte deprecated olmuş JDK 11 ile programlama dilinden çıkarılmıştır.

JDK 11 sonrası destek için Java SOAP yazımda yer alan XML kütüphanesinin pom.xml dosyasın eklenmesi yeterli olacaktır.

JSON

JAX-RS ile JSON çıktısı vermek için @Produces değeri application/json veya MediaType.APPLICATION_JSON kullanılabilir.

Proje tekrar derlenip çalıştırıldığında aşağıdaki hatayı verecektir.

MessageBodyWriter not found for media type=application/json, 
type=class com.yusufsezer.model.Person, 
genericType=class com.yusufsezer.model.Person.

Bu hatayı gidermek için pom.xml dosyasında yer alan aşağıdaki kütüphaneye ait yorum komutlarının kaldırılması yeterli olacaktır.

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-binding</artifactId>
</dependency>

@Path kullanımı

@Path ifadesi sınıflar için kullanılabileceği gibi metotlar içinde kullanılabilir.

@GET
@Path("/yusuf")
@Produces(MediaType.APPLICATION_JSON)
public Person getIt() {
    return new Person(1L, "Yusuf", "Sezer");
}

Adrese /myresource/yusuf ile erişilecektir.

@Path ifadesi kesin değer alabileceği gibi wildcard ifadesi de alabilir.

Wildcard değerine @PathParam ile ulaşılır.

@GET
@Path("{firstName}")
@Produces(MediaType.APPLICATION_JSON)
public Person getIt(@PathParam("firstName") String firstName) {
    System.out.println("Gelen değer: " + firstName);
    return new Person(1L, "Yusuf", "Sezer");
}

NOT: @Path(“{firstName}-{lastName}”) gibi birden fazla değer kullanılabilir.

@Path ifadesi ayrıca düzenli ifade (regex) kullanımına imkan verir.

@GET
@Path("{personId:\\d+}")
@Produces(MediaType.APPLICATION_JSON)
public Person getPerson(@PathParam("personId") long personId) {
    System.out.println("Gelen değer: " + personId);
    return new Person(1L, "Yusuf", "Sezer");
}

@Path içerisine yazılan düzenli ifade ile sadece sayısal değerlerin metodu çalıştırması sağlanmıştır.

Parametre almak

İstemciden gelen parametreleri almak için aşağıdaki annotation ifadeleri kullanılır.

@PathParam

URL parametrelerini almak için kullanılır.

@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("{personId}")
public Person getPerson(@PathParam("personId") long id) {
    System.out.println("Gelen değer: " + id);
    return new Person(1L, "Yusuf", "Sezer");
}

@MatrixParam

Anahtar=değer; ifadelerini almak için kullanılır.

@GET
@Produces(MediaType.APPLICATION_JSON)
public String getProduct(@MatrixParam("secenekler") List<String> secenekler) {
    System.out.println("Gelen değer sayısı: " + secenekler.size());
    return "-";
}

@QueryParam

URL query parametrelerini(?islem=yazdir) almak için kullanılır.

@GET
@Produces(MediaType.APPLICATION_JSON)
public String getProducts(
        @QueryParam("start") int start,
        @QueryParam("size") int size
) {
    System.out.println("Başlangıç : " + start);
    System.out.println("Boyut : " + start);
    return "-";
}

@FormParam

HTML form verilerini almak için kullanılır.

@POST
public String createPerson(
        @FormParam("firstName") String firstName,
        @FormParam("lastName") String lastName
) {
    System.out.println("Adı : " + firstName);
    System.out.println("Soyadı : " + lastName);
    return "-";
}

@HeaderParam

HTTP header bilgilerini almak için kullanılır.

@GET
public String getInfo(@HeaderParam("user-agent") String userAgent) {
    return userAgent;
}

@CookieParam

Cookie bilgilerini almak için kullanılır.

@GET
public String getInfo(@CookieParam("JSESSIONID") String cookie) {
    return cookie;
}

String yerine Cookie sınıfı kullanılarak cookie ile ilgili diğer bilgilere de erişim sağlanabilir.

@BeanParam

Özel parametre sınıfı için kullanılır.

Parametre sayısı arttıkça metot parametresi genişler.

Bu genişlemeyi sıradan bir Java sınıfı oluşturup bu sınıfı @BeanParam ifadesi ile daha sade hale getirebiliriz.

public class PaginateBean {

    @QueryParam("start")
    private int start;
    @QueryParam("size")
    private int size;

    // get-set metotları

}
@GET
public String getInfo(@BeanParam PaginateBean paginate) {
    int start = paginate.getStart();
    int size = paginate.getSize();
    return "-";
}

@Context

Aşağıdaki sınıfların REST ile kullanımını sağlar.

  • SecurityContext
  • Request
  • Application
  • Configuration
  • Providers
  • ResourceContext
  • ServletConfig
  • ServletContext
  • HttpServletRequest
  • HttpServletResponse
  • HttpHeaders
  • UriInfo

Örnek kullanım aşağıdaki gibidir.

@GET
public String getInfo(@Context HttpHeaders headers) {
    StringBuilder sb = new StringBuilder();
    headers
            .getCookies()
            .values()
            .forEach(sb::append);
    return sb.toString();
}

İfade sınıf içerisinde de kullanılabilir.

@Context
HttpHeaders headers;

@GET
public String getInfo() {
    StringBuilder sb = new StringBuilder();
    headers
            .getCookies()
            .values()
            .forEach(sb::append);
    return sb.toString();
}

@Context ifadesini kullanarak bir çok değere erişim sağlanabilir.

HTTP yöntemlerini kullanma

JAX-RS HTTP yöntemlerine göre çalışacak metotları belirlemek için aşağıdaki annotation ifadeleri yer almaktadır.

  • @GET
  • @POST
  • @PUT
  • @DELETE
  • @HEAD
  • @OPTIONS
  • @PATCH

JAX-RS diğer http yöntemlerine ait annotation oluşturmak için ayrıca HttpMethod annotationu kullanılabilir.

NOT: Ön tanımlı JAX-RS annotation ifadeleri HttpMethod ile oluşturulmuştur.

HTTP durum kodlarını kullanma

REST tabanlı web servislerinde işlem sonucu ile ilgili bilgi almak için HTTP durum kodları kullanılır.

JAX-RS ile HTTP durum kodlarını döndürmek için Response sınıfı kullanılır.

@GET
public Response getPerson() {
    return Response
            .ok(new Person(1L, "Yusuf", "Sezer"))
            .build();
}

veya

@GET
public Response getPerson() {
    return Response
            .ok()
            .entity(new Person(1L, "Yusuf", "Sezer"))
            .build();
}

Sınıf içerisinde yer alan created, ok, noContent gibi metotlar kullanılabileceği gibi status metodu da kullanılabilir.

@GET
public Response getPerson() {
    return Response
            .status(Response.Status.CREATED)
            .entity(new Person(1L, "Yusuf", "Sezer"))
            .build();
}
@GET
public Response getPerson() {
    return Response
            .status(Response.Status.NOT_FOUND)
            .entity("bulunamadı")
            .build();
}

Response sınıfı ayrıca ResponseBuilder sınıfı sayesinde tarayıcıya cookie, language, expires, header gibi bilgileri döndürmek için de kullanılır.

@GET
public Response getPerson() {
    return Response
            .status(Response.Status.NOT_FOUND)
            .entity(&quot;bulunamadı&quot;)
            .build();
}

Subresources – Alt REST API

Bir REST API servisinin alt adresleri de olabilir.

Bu adresler Subresources olarak ifade edilir.

Alt sınıfların kullanımı sıradan sınıfların kullanımı gibidir.

@GET
@Path("/{personId}/info")
public PersonInfo getPersonInfo() {
    return new PersonInfo();
}

/myresource/1/info adresine gelen istekler PersonInfo sınıfı tarafından işlenecektir.

İstisna yönetimi

JAX-RS özel hata sınıfı oluşturmak ve oluşan hataları yönetmek için özel istisna yönetimine imkan veren ExceptionMapper arayüzü yer alır.

Özel istisna sınıfının hazırlanması için ilk olarak istisna sınıfı oluşturulur.

public class PersonNotFoundException extends RuntimeException {

    public PersonNotFoundException(String message) {
        super(message);
    }

}

Oluşturulan hata sınıfı ExceptionMapper arayüzünü implement eder.

@Provider
public class PersonNotFoundExceptionMapper implements ExceptionMapper<PersonNotFoundException> {

    @Override
    public Response toResponse(PersonNotFoundException e) {
        return Response
                .status(Response.Status.NOT_FOUND)
                .type(MediaType.APPLICATION_JSON)
                .entity(new ErrorMessage(e.getMessage(), 404))
                .build();
    }
}

NOT: @Provider kullanımı ve web.xml ile belirtilen paket içerisinde yer aldığına dikkat edilmesi gerekir.

Özel istisna sınıfı sıradan istisna sınıfı gibi kullanılır.

@GET
@Path("{personId}")
public Person getPerson(@PathParam("personId") long id) {

    if (id < 1) {
        throw new PersonNotFoundException(id + " not found.");
    }

    return new Person(1L, "Yusuf", "Sezer");
}

Hata sınıfınının çıktı olarak daha anlaşılır olması için ayrıca özel sınıf (ErrorMessage) hazırlanmıştır.

public class ErrorMessage {

    String message;
    int status;

    // constructor-get-set

}

Her istisna için ayrı istisna sınıfı oluşturmak yerine aşağıda yer alan ön tanımlı istisna sınıflarının kullanımı faydalı olacaktır.

  • WebApplicationException
  • BadRequestException
  • NotAuthorizedException
  • ForbiddenException
  • NotFoundException
  • NotAllowedException
  • NotAcceptableException
  • NotSupportedException
  • InternalServerErrorException
  • ServiceUnavailableException

Ön tanımlı veya özel istisna sınıflarını ayrı ayrı ele almak yerine Generic bir ExceptionMapper oluşturmak faydalı olacaktır.

@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {

    @Override
    public Response toResponse(Throwable e) {

        ErrorMessage errorMessage = new ErrorMessage(e.getMessage(), 500);

        if (e instanceof NotFoundException) {
            errorMessage.setStatus(404);
        }

        return Response
                .status(Response.Status.NOT_FOUND)
                .type(MediaType.APPLICATION_JSON)
                .entity(errorMessage)
                .build();
    }

}

HATEOAS

REST tabanlı web servislerin herhangi bir kuralı olmamasının sağladığı avantajlar yanında bazı dezavantajları da vardır.

Oluşturulan web servis ile ilgili herhangi bir bilgi olmadığında REST API kara kutu ve kullanılamaz bir halde olacaktır.

Bunun önüne geçebilmek için dokümantasyon ve REST adreslerine erişimin çıktıya eklenmesi gerekir.

HATEOAS ifadesi bu işlemin adrandırılması için kullanılan bir ifadedir.

Örneğin aşağıdaki gibi bir çıktı elde ettiğimizi varsayalım.

{
  "firstName": "Yusuf",
  "id": 1,
  "lastName": "Sezer"
}

Bu çıktının tekil erişim sayfası, güncelleme ve silme adresinin olmaması belirsizliğe yol açmaktadır.

Çıktının model sınıfına özel link alanı ekleyerek aşağıdaki gibi bir çıktının eklenmesi belirsizliği ortadan kaldıracaktır.

{
  "firstName": "Yusuf",
  "id": 1,
  "lastName": "Sezer",
  "links": [
    {
      "href": "rest-adresi/person/1",
      "rel": "_self",
      "type": "GET"
    },
    {
      "href": "rest-adresi/person/1",
      "rel": "edit",
      "type": "PUT"
    },
    {
      "href": "/person/1",
      "rel": "delete",
      "type": "DELETE"
    }
  ]
}

Content Negotiation

Oluşturulan REST API birden farklı veri formatı döndürebilir.

REST API sınıfındaki bir metot TEXT/PLAIN bir metot XML, diğer metod ise JSON çıktı verebilir.

Tarayıcı ile bu adrese erişim sağlandığında tarayıcını öncelik sırasına göre bir çıktı metot seçimi yapılır.

Bu seçimi POSTMAN, CURL gibi araçlara eklenen Accept parametresi ile değiştirilebilir veya */* ile seçim uygulamaya bırakılabilir.

Converter

JAX-RS gelen özel sınıf parametrelerini kabul etmeyecektir.

Sınıfları ayrıştırabilmek için bir çevrim işlemine ihtiyaç vardır.

JAX-RS içerisinde yer alan Converter özelliği ile özel sınıfların parametre olarak metoda geçirilmesi sağlanır.

Bunun için JAX-RS sınıflara özel converter hazırlamak için ParamConverterProvider arayüzünün implement edilmesi gerekiyor.

Böylece aşağıdaki gibi MyDate sınıfı parametre olarak alınabiliyor.

public String getIt(@PathParam("dateString") MyDate myDate) {
	return "My date: " + myDate.toString();
}

Message Body Writer

JAX-RS sınıfları çıktı olarak yazdırmak için MessageBodyWriter arayüzünün implement edilmesi gerekiyor.

İmplement edildikten sonra artık nesne çıktı olarak sunulabiliyor.

@GET
@Produces(MediaType.TEXT_PLAIN)
public Date getDate() {
	return Calendar.getInstance().getTime();
}

NOT: JSON ve diğer çıktıları almak için MessageBodyWriter arayüzünü uygulayan sınıflar kullanılır.

Custom Media Types

JAX-RS sadece MediaType veya ön tanımlı MIME türlerine ek özel çıktı vermeyi sağlar.

Özel bir format çıktısı vermek için @Produces(“text/ozel”) gibi bir kullanım yeterli olacaktır.

NOT: Özel çıktı istemci tarafından tanınmadığı durumda beklenmedik durumlar oluşabilir.

Filter

JAX-RS, Java Servlet yapısında yer alan Filter özelliğine benzer bir filter özelliğine sahiptir.

Bu özelliği sayesinde bir istek geldiğinde veya cevap dönüşü yapıldığında ekleme yapılabilir.

Örneğin aşağıdaki filtre özel header eklemek için kullanılabilir.

@Provider
public class PoweredByResponseFilter implements ContainerResponseFilter {

	@Override
	public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
			throws IOException {
		responseContext.getHeaders().add("X-Powered-By", "Yusuf Sezer");
	}

}

Filter özelliği (ContainerRequestFilter, ContainerResponseFilter) ile REST API için yetkilendirme, GZIP sıkıştırma vb. işlemler yapılabilir.

Interceptor

Filtreleme özelliği amacının dışına çıkabilir.

Bu durumlar için benzer işlemi yapan ve sıralama önceliği farklı olan Interceptor özelliği kullanılabilir.

  • ReaderInterceptor – ContainerRequestFilter
  • WriterInterceptor – ContainerResponseFilter

Interceptor özelliği genellikle GZIP sıkıştırma gibi filtreme içerisinde kullanılan işlemler için kullanılmaktadır.

Sonuç

JAX-RS, Jakarta/Java EE tarafından standartları belirtilen Java ile REST tabanlı API oluşturmak için kullanılan bir şartnamedir.

Bu şartnameye ait özelliklerin öğrenilmesi benzer çalışma yapısına sahip MicroProfile, Jakarta/Java EE, Spring, Spring Boot gibi yapıların öğrenilmesini kolaylaştıracaktır.

JAX-RS ile bir REST API oluştururken HATEOAS ve dokümantasyon (swagger, openapi) gibi yapıları kullanmak istemcilerin REST API’yı kullanımını kolaylaştıracaktır.

JAX-RS örneğine buradan ulaşabilirsiniz.

Java Derslerine buradan ulaşabilirsiniz.

Hayırlı günler dilerim.


Bunlarda ilgini çekebilir


LinkedIn grubuna buradan katılın.