Giriş: Jersey ve RESTful Web Servisleri
Modern web uygulamalarının bel kemiğini oluşturan RESTful mimari, kaynak tabanlı, durum bilgisi olmayan ve HTTP protokolünü tam anlamıyla kullanan bir yaklaşımdır. Java dünyasında bu mimariyi uygulamak için çeşitli çerçeveler (framework) bulunmaktadır. Bunlardan biri de, Java EE standardı olan JAX-RS (Java API for RESTful Web Services) referans uygulaması olan Jersey’dir. Bu rehberde, Jersey kullanarak nasıl RESTful web servisleri geliştireceğinizi adım adım öğreneceksiniz.
Jersey Nedir?
Jersey, JAX-RS API’sinin açık kaynaklı bir referans implementasyonudur. JAX-RS, Java geliştiricilerinin RESTful servisleri kolayca oluşturabilmeleri için standart bir API sağlar. Jersey, bu API’yi kullanarak, POJO’lar (Plain Old Java Objects) aracılığıyla HTTP isteklerini ve yanıtlarını yönetmenizi, URI’ları kaynaklara eşlemenizi ve çeşitli medya tiplerini (JSON, XML, metin vb.) işlemenizi sağlar.
RESTful Mimari ve Önemi
REST (Representational State Transfer), web servisleri için bir yazılım mimarisi stilidir. Temel prensipleri şunlardır:
- Kaynaklar (Resources): Her şey bir kaynaktır ve tekil bir URI (Uniform Resource Identifier) ile tanımlanır. Örn:
/urunler,/urunler/123. - Durum Bilgisi Olmaması (Stateless): Her istek, sunucunun isteği işlemek için ihtiyaç duyduğu tüm bilgiyi içermelidir. Sunucu, önceki isteklerden herhangi bir istemci durumunu saklamaz.
- Standart Metotlar (Standard Methods): HTTP metotları (GET, POST, PUT, DELETE) kaynaklar üzerinde belirli işlemler yapmak için kullanılır.
- Temsil (Representation): Kaynaklar, JSON, XML veya HTML gibi farklı formatlarda temsil edilebilir.
Jersey, bu prensipleri Java koduna dönüştürmek için güçlü ve esnek bir yapı sunar.
Jersey Projesi Kurulumu ve Yapılandırması
Jersey ile bir RESTful servis geliştirmeye başlamadan önce, projemizi doğru bir şekilde kurmamız ve gerekli bağımlılıkları eklememiz gerekir. Bu bölümde, Maven kullanarak bir Jersey projesi oluşturma adımlarını inceleyeceğiz.
Ön Gereksinimler
- JDK (Java Development Kit) 8 veya üzeri
- Maven (Proje Yönetim Aracı)
- Tercih edilen bir IDE (IntelliJ IDEA, Eclipse, VS Code)
- Bir Servlet Konteyneri (Apache Tomcat, Jetty vb.)
Maven Projesi Oluşturma
Yeni bir Maven projesi oluşturarak başlayabiliriz. Komut satırından veya IDE’niz üzerinden “maven-archetype-webapp” şablonunu kullanarak bir web uygulaması projesi oluşturabilirsiniz:
mvn archetype:generate -DgroupId=com.example -DartifactId=jersey-tutorial -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false
Bu komut, jersey-tutorial adında yeni bir Maven web projesi oluşturacaktır.
pom.xml Bağımlılıkları
Oluşturulan pom.xml dosyasına Jersey ve ilgili bağımlılıkları eklememiz gerekiyor. Özellikle jersey-server (JAX-RS API ve çekirdek implementasyon) ve jersey-container-servlet (Jersey’nin bir Servlet konteynerinde çalışmasını sağlayan modül) bağımlılıklarını eklemeliyiz. JSON desteği için jersey-media-json-jackson bağımlılığını da eklemek pratik olacaktır.
4.0.0
com.example
jersey-tutorial
war
1.0-SNAPSHOT
jersey-tutorial Maven Webapp
http://maven.apache.org
2.34
UTF-8
org.glassfish.jersey.core
jersey-server
${jersey.version}
org.glassfish.jersey.containers
jersey-container-servlet-core
${jersey.version}
org.glassfish.jersey.media
jersey-media-json-jackson
${jersey.version}
javax.servlet
javax.servlet-api
3.1.0
provided
junit
junit
4.13.2
test
jersey-tutorial
org.apache.maven.plugins
maven-compiler-plugin
3.8.1
1.8
1.8
org.apache.tomcat.maven
tomcat7-maven-plugin
2.2
web.xml Yapılandırması
Web uygulamanızın src/main/webapp/WEB-INF dizini altında bulunan web.xml dosyası, Jersey’nin Servlet konteyneri tarafından nasıl yükleneceğini ve hangi URL desenlerini dinleyeceğini belirtir. Jersey’nin ServletContainer sınıfını bir Servlet olarak tanımlamamız ve kaynak sınıflarımızın bulunduğu paketi belirtmemiz gerekir.
Jersey RESTful Web Application
jersey-servlet
org.glassfish.jersey.servlet.ServletContainer
jersey.config.server.provider.packages
com.example.resources
1
jersey-servlet
/api/*
Yukarıdaki yapılandırmada, jersey.config.server.provider.packages parametresi, Jersey’nin hangi paketlerdeki JAX-RS anotasyonlu sınıfları tarayacağını belirtir. url-pattern ise, /api/* ile başlayan tüm isteklerin Jersey tarafından işleneceğini gösterir.
Temel RESTful Servis Oluşturma
Artık projemiz yapılandırıldığına göre, ilk RESTful servislerimizi oluşturmaya başlayabiliriz. Jersey, JAX-RS anotasyonlarını kullanarak Java sınıflarını ve metotlarını REST kaynaklarına dönüştürmeyi son derece kolaylaştırır.
Kaynak Sınıfı Oluşturma
com.example.resources paketi altında MerhabaServis adında bir sınıf oluşturalım. Bu sınıf, @Path anotasyonu ile bir URI yoluna eşlenecektir.
package com.example.resources;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/merhaba") // Bu sınıfın tüm metotları /api/merhaba altında erişilebilir olacak
public class MerhabaServis {
@GET // HTTP GET isteklerini karşılar
@Produces(MediaType.TEXT_PLAIN) // Yanıtın düz metin formatında olacağını belirtir
public String getMerhabaMesaji() {
return "Merhaba Dünya, Jersey'e Hoş Geldiniz!";
}
}
Bu örnekte:
@Path("/merhaba"): Bu sınıfın/api/merhabaURI’si altında erişilebilir olduğunu belirtir.@GET:getMerhabaMesajimetodunun HTTP GET isteklerine yanıt vereceğini belirtir.@Produces(MediaType.TEXT_PLAIN): Metodun döndürdüğü yanıtıntext/plainmedya tipinde olacağını belirtir.
HTTP Metotları ve Parametre İşleme
RESTful servisler, farklı HTTP metotlarını (GET, POST, PUT, DELETE) kullanarak kaynaklar üzerinde CRUD (Create, Read, Update, Delete) işlemleri yapar. Ayrıca, URI’den veya sorgu parametrelerinden veri alabilirler.
GET İstekleri ve Parametreler
GET istekleri genellikle veri okumak için kullanılır. @PathParam ve @QueryParam ile parametreleri yakalayabiliriz.
package com.example.resources;
import javax.ws.rs.*; // Tüm JAX-RS anotasyonlarını import edelim
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; // HTTP yanıtını özelleştirmek için
@Path("/kullanicilar")
public class KullaniciServis {
// GET /api/kullanicilar
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getAllKullanicilar() {
return "Tüm kullanıcılar listeleniyor...";
}
// GET /api/kullanicilar/{id}
@GET
@Path("/{id}") // URI yolunda bir parametre tanımlar
@Produces(MediaType.TEXT_PLAIN)
public String getKullaniciById(@PathParam("id") int kullaniciId) {
return "ID'si " + kullaniciId + " olan kullanıcı getiriliyor.";
}
// GET /api/kullanicilar/ara?ad=Ahmet&soyad=Yilmaz
@GET
@Path("/ara")
@Produces(MediaType.TEXT_PLAIN)
public String searchKullanicilar(@QueryParam("ad") String ad,
@QueryParam("soyad") String soyad) {
return "Adı: " + ad + ", Soyadı: " + soyad + " olan kullanıcılar aranıyor.";
}
}
@PathParam("id"): URI yolundaki{id}kısmınıkullaniciIdint değişkenine eşler.@QueryParam("ad"): URI sorgu parametrelerindekiaddeğeriniadString değişkenine eşler.
POST İstekleri ve Veri Gönderme
POST istekleri genellikle yeni bir kaynak oluşturmak için kullanılır. @Consumes anotasyonu, metodun hangi medya tipindeki veriyi kabul ettiğini belirtir.
package com.example.resources;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path("/urunler")
public class UrunServis {
// POST /api/urunler
@POST
@Consumes(MediaType.TEXT_PLAIN) // İstek gövdesinde düz metin bekler
@Produces(MediaType.TEXT_PLAIN) // Yanıtın düz metin olacağını belirtir
public Response createUrun(String urunAdi) {
// Veritabanına ürün ekleme işlemi simüle ediliyor
System.out.println("Yeni ürün oluşturuldu: " + urunAdi);
return Response.status(Response.Status.CREATED) // 201 Created HTTP durumu döner
.entity("Ürün başarıyla oluşturuldu: " + urunAdi)
.build();
}
}
@POST: Metodun HTTP POST isteklerine yanıt vereceğini belirtir.@Consumes(MediaType.TEXT_PLAIN): İstek gövdesinintext/plainformatında olması gerektiğini belirtir.Response.status(Response.Status.CREATED).entity(...).build(): Özel bir HTTP yanıtı oluşturmak için kullanılır. Bu, 201 Created durum koduyla bir yanıt döndürür.
Veri Modelleri ve JSON/XML Entegrasyonu
Gerçek dünya uygulamalarında, genellikle basit String veya int değerler yerine daha karmaşık veri yapıları (POJO’lar) ile çalışırız. Jersey, bu POJO’ları otomatik olarak JSON veya XML gibi formatlara dönüştürmek (serileştirme) ve bu formatlardan POJO’lara geri dönüştürmek (deserileştirme) için güçlü bir destek sunar.
POJO (Plain Old Java Object) Oluşturma
Öncelikle, temsil etmek istediğimiz veri için bir POJO oluşturalım.
package com.example.model;
import javax.xml.bind.annotation.XmlRootElement; // XML desteği için
@XmlRootElement // Bu anotasyon, JAXB'nin bu sınıfı XML'e dönüştürmesine yardımcı olur
public class Urun {
private int id;
private String ad;
private double fiyat;
public Urun() {
// Varsayılan kurucu metot, JSON/XML serileştirme için gereklidir
}
public Urun(int id, String ad, double fiyat) {
this.id = id;
this.ad = ad;
this.fiyat = fiyat;
}
// Getter ve Setter metotları
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getAd() { return ad; }
public void setAd(String ad) { this.ad = ad; }
public double getFiyat() { return fiyat; }
public void setFiyat(double fiyat) { this.fiyat = fiyat; }
@Override
public String toString() {
return "Urun{" +
"id=" + id +
", ad='" + ad + '\'' +
", fiyat=" + fiyat +
'}';
}
}
@XmlRootElement anotasyonu, bu POJO’nun XML formatına dönüştürülürken kök element olarak kullanılabileceğini belirtir. JSON için genellikle varsayılan kurucu metot ve getter/setter’lar yeterlidir (Jackson kütüphanesi tarafından kullanılır).
JSON Desteği ile POJO Kullanımı
pom.xml dosyamıza jersey-media-json-jackson bağımlılığını eklediğimiz için, Jersey otomatik olarak Jackson kütüphanesini kullanarak POJO’ları JSON’a serileştirecek ve JSON’dan POJO’lara deseralize edecektir.
package com.example.resources;
import com.example.model.Urun;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Path("/urunler")
public class UrunKaynak {
// Basit bir bellek içi depolama
private static List urunler = new ArrayList<>();
private static AtomicInteger idCounter = new AtomicInteger();
static {
urunler.add(new Urun(idCounter.incrementAndGet(), "Laptop", 1200.00));
urunler.add(new Urun(idCounter.incrementAndGet(), "Mouse", 25.00));
}
// GET /api/urunler
@GET
@Produces(MediaType.APPLICATION_JSON) // JSON formatında yanıt döndürür
public List getAllUrunler() {
return urunler;
}
// GET /api/urunler/{id}
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response getUrunById(@PathParam("id") int id) {
for (Urun urun : urunler) {
if (urun.getId() == id) {
return Response.ok(urun).build(); // 200 OK ile ürünü döndür
}
}
return Response.status(Response.Status.NOT_FOUND).entity("Ürün bulunamadı.").build(); // 404 Not Found
}
// POST /api/urunler
@POST
@Consumes(MediaType.APPLICATION_JSON) // İstek gövdesinde JSON bekler
@Produces(MediaType.APPLICATION_JSON)
public Response createUrun(Urun yeniUrun) {
yeniUrun.setId(idCounter.incrementAndGet());
urunler.add(yeniUrun);
System.out.println("Yeni ürün eklendi: " + yeniUrun);
return Response.status(Response.Status.CREATED).entity(yeniUrun).build(); // 201 Created ile oluşturulan ürünü döndür
}
// PUT /api/urunler/{id}
@PUT
@Path("/{id}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response updateUrun(@PathParam("id") int id, Urun guncelUrun) {
for (int i = 0; i < urunler.size(); i++) {
if (urunler.get(i).getId() == id) {
guncelUrun.setId(id); // ID'nin değişmediğinden emin ol
urunler.set(i, guncelUrun);
System.out.println("Ürün güncellendi: " + guncelUrun);
return Response.ok(guncelUrun).build(); // 200 OK ile güncellenen ürünü döndür
}
}
return Response.status(Response.Status.NOT_FOUND).entity("Güncellenecek ürün bulunamadı.").build();
}
// DELETE /api/urunler/{id}
@DELETE
@Path("/{id}")
@Produces(MediaType.TEXT_PLAIN)
public Response deleteUrun(@PathParam("id") int id) {
boolean removed = urunler.removeIf(urun -> urun.getId() == id);
if (removed) {
System.out.println("Ürün silindi: ID " + id);
return Response.ok("Ürün başarıyla silindi.").build(); // 200 OK
}
return Response.status(Response.Status.NOT_FOUND).entity("Silinecek ürün bulunamadı.").build();
}
}
Bu UrunKaynak sınıfı, Urun POJO’larını kullanarak tam bir CRUD (Create, Read, Update, Delete) operasyonlarını JSON formatında nasıl gerçekleştirebileceğimizi gösterir.
Jersey Uygulamasını Çalıştırma ve Test Etme
Servislerimizi kodladığımıza göre, şimdi bunları bir Servlet konteynerinde çalıştırma ve test etme zamanı.
Uygulama Sunucusu (Servlet Konteyneri) Kullanımı
En yaygın yöntem, uygulamayı bir WAR (Web Application Archive) dosyası olarak paketleyip Apache Tomcat veya Jetty gibi bir uygulama sunucusuna dağıtmaktır.
1. WAR Dosyası Oluşturma: Projenizin kök dizininde aşağıdaki Maven komutunu çalıştırın:
mvn clean package
Bu komut, target klasöründe jersey-tutorial.war (veya pom.xml‘deki finalName neyse o) adında bir WAR dosyası oluşturacaktır.
2. WAR Dosyasını Dağıtma: Oluşturulan WAR dosyasını Tomcat’in webapps dizinine kopyalayın ve Tomcat’i başlatın. Tomcat otomatik olarak uygulamayı dağıtacaktır.
3. Maven Tomcat Plugin ile Çalıştırma (Geliştirme Ortamı İçin): pom.xml‘e eklediğimiz tomcat7-maven-plugin‘i kullanarak doğrudan Maven üzerinden uygulamayı çalıştırabilirsiniz:
mvn tomcat7:run
Bu, Tomcat’i gömülü olarak başlatacak ve uygulamanızı dağıtacaktır. Varsayılan olarak, uygulamanız http://localhost:8080/jersey-tutorial adresinde erişilebilir olacaktır.
Servisi Test Etme
Servislerinizi test etmek için Postman, Insomnia gibi GUI tabanlı araçları veya cURL gibi komut satırı araçlarını kullanabilirsiniz.
Varsayılan olarak, uygulamanız http://localhost:8080/jersey-tutorial altında çalışıyorsa ve web.xml‘deki url-pattern /api/* ise, servisleriniz http://localhost:8080/jersey-tutorial/api/... adresinde olacaktır.
GET İstekleri
Tüm ürünleri getir
curl http://localhost:8080/jersey-tutorial/api/urunler
ID'si 1 olan ürünü getir
curl http://localhost:8080/jersey-tutorial/api/urunler/1
POST İstekleri (JSON ile Yeni Ürün Oluşturma)
curl -X POST \
http://localhost:8080/jersey-tutorial/api/urunler \
-H 'Content-Type: application/json' \
-d '{
"ad": "Klavye",
"fiyat": 75.50
}'
PUT İstekleri (JSON ile Ürün Güncelleme)
curl -X PUT \
http://localhost:8080/jersey-tutorial/api/urunler/1 \
-H 'Content-Type: application/json' \
-d '{
"id": 1,
"ad": "Kablosuz Klavye",
"fiyat": 99.99
}'
DELETE İstekleri
curl -X DELETE \
http://localhost:8080/jersey-tutorial/api/urunler/2
Bu komutları çalıştırdığınızda, konsolunuzda veya HTTP istemcinizde beklenen JSON yanıtlarını veya düz metin mesajlarını görmelisiniz.
Sonuç ve Sıkça Sorulan Sorular (SSS)
Bu rehberde, Jersey kullanarak temel bir RESTful web servisinin nasıl oluşturulacağını, yapılandırılacağını ve test edileceğini öğrendiniz. Jersey, JAX-RS standartlarını takip ederek Java’da RESTful uygulamalar geliştirmek için güçlü, esnek ve standartlara uygun bir yol sunar. POJO’ları JSON/XML’e dönüştürme yeteneği ve basit anotasyon tabanlı yaklaşımı sayesinde, veri odaklı servisleri hızlıca oluşturabilirsiniz.
Sıkça Sorulan Sorular (SSS)
S1: Jersey’i Spring REST ile karşılaştırırsak, hangisini seçmeliyim?
C1: Jersey, JAX-RS’in referans implementasyonudur ve saf bir JAX-RS deneyimi sunar. Spring REST (Spring WebFlux veya Spring MVC ile), Spring ekosisteminin bir parçasıdır ve Spring’in diğer modülleri (Dependency Injection, Data JPA vb.) ile sorunsuz entegrasyon sağlar. Eğer projeniz zaten Spring tabanlıysa veya geniş bir ekosistem arıyorsanız Spring REST daha uygun olabilir. Sadece JAX-RS standartlarına bağlı kalmak istiyorsanız Jersey mükemmel bir seçimdir.
S2: JAX-RS tam olarak nedir ve Jersey’nin rolü ne?
C2: JAX-RS (Java API for RESTful Web Services), Java EE’nin bir parçası olan, RESTful web servisleri oluşturmak için bir standart API’dir. Geliştiricilerin RESTful servisleri yazmak için kullanacakları anotasyonları ve arayüzleri tanımlar. Jersey ise bu JAX-RS API’sinin bir implementasyonudur. Yani JAX-RS “ne yapılması gerektiğini” söyler, Jersey ise “nasıl yapılacağını” gösterir ve sağlar.
S3: Jersey uygulamalarında güvenlik nasıl sağlanır?
