Java Servlet Filter Nedir ve Neden Kullanılır?
Java Servlet Filter, web uygulamalarınızın gelen isteklerini (requests) ve giden cevaplarını (responses) bir servlet’e ulaşmadan önce veya ulaştıktan sonra işleyebilen güçlü bir bileşendir. Bir Servlet Filter, bir veya daha fazla servlet’e uygulanabilir ve bir “filtre zinciri” oluşturmak için diğer filtrelerle birleştirilebilir. Bu zincirdeki her filtre, isteği veya cevabı değiştirebilir, günlüğe kaydedebilir veya tamamen engelleyebilir.
Filtreler, web uygulamanızın çekirdek mantığına dokunmadan, ortak işlevsellikleri merkezi bir noktadan yönetmek için mükemmel bir yol sunar. İşte filtrelerin yaygın kullanım alanlarından bazıları:
- Güvenlik: Kullanıcı kimlik doğrulama (authentication) ve yetkilendirme (authorization) kontrolleri yapmak. Belirli sayfalara erişimi kısıtlamak.
- Loglama ve Denetim: Gelen isteklerin (URL, IP adresi, kullanıcı bilgileri) veya giden cevapların durum kodlarının loglanması.
- Veri Sıkıştırma: Sunucudan istemciye gönderilen verileri sıkıştırarak bant genişliği kullanımını azaltmak.
- Karakter Seti Ayarları: Gelen isteklerin ve giden cevapların karakter setini (örneğin, UTF-8) tek bir noktadan ayarlamak.
- Performans İzleme: İsteklerin işlenmesinin ne kadar sürdüğünü ölçmek ve performans darboğazlarını tespit etmek.
- İstek/Cevap Manipülasyonu: Gelen isteğin parametrelerini değiştirmek veya giden cevabın içeriğini manipüle etmek.
- Önbellekleme (Caching): Belirli kaynaklar için önbellekleme mekanizmaları uygulamak.
Filtreler, web uygulamanızın modülerliğini artırır, kod tekrarını azaltır ve uygulamayı daha kolay yönetilebilir hale getirir.
Servlet Filter’ın Temelleri: Arayüz ve Yaşam Döngüsü
Bir Java Servlet Filter oluşturmak için javax.servlet.Filter arayüzünü implement etmeniz gerekir. Bu arayüz üç ana metot tanımlar: init(), doFilter() ve destroy(). Bu metotlar, filtrenin yaşam döngüsünü yönetir.
Filter Arayüzünün Metotları
init(FilterConfig filterConfig):Bu metot, filtre web konteyneri (örneğin, Tomcat, Jetty) tarafından ilk kez yüklendiğinde ve başlatıldığında çağrılır. Filtre yaşam döngüsünde yalnızca bir kez çalışır. Genellikle, filtrenin başlatılması için gerekli tek seferlik kurulum işlemlerini (kaynak yükleme, yapılandırma parametrelerini okuma vb.) yapmak için kullanılır.
FilterConfignesnesi, filtrenin yapılandırma bilgilerini (web.xml‘den veya@WebFilteranotasyonundan alınan init parametreleri gibi) veServletContextnesnesini sağlar.doFilter(ServletRequest request, ServletResponse response, FilterChain chain):Bu metot, filtrenin ana iş mantığını içerir ve filtrenin eşleştiği her web isteği için çağrılır. Bu metodun içinde, isteği veya cevabı manipüle edebilir, güvenlik kontrolleri yapabilir veya loglama işlemleri gerçekleştirebilirsiniz.
ServletRequest: Gelen isteği temsil eder.ServletResponse: Giden cevabı temsil eder.FilterChain: Bu nesne, filtre zincirindeki bir sonraki filtreyi veya zincirin sonuysa hedef servlet’i çağırmak için kullanılır.chain.doFilter(request, response)çağrısı çok önemlidir. Bu çağrıyı yapmazsanız, istek hedef servlet’e ulaşmaz ve istemciye herhangi bir cevap gönderilmez (veya filtre kendi cevabını gönderir).
chain.doFilter()çağrısından önce yapılan işlemler, isteğin hedefe ulaşmasından önce gerçekleşir. Çağrıdan sonra yapılan işlemler ise, hedef servlet işini bitirip cevabı oluşturduktan sonra gerçekleşir.destroy():Bu metot, web konteyneri filtreyi bellekten kaldırmadan önce çağrılır. Filtre yaşam döngüsünde yalnızca bir kez çalışır. Genellikle, filtre tarafından açılan kaynakları (veritabanı bağlantıları, dosya akışları vb.) serbest bırakmak için kullanılır.
Basit Bir Servlet Filter Örneği: İstek Loglama
Şimdi, gelen her isteğin URL’sini ve işlenme süresini konsola yazdıran basit bir loglama filtresi oluşturalım.
1. Filter Sınıfını Oluşturma
Öncelikle, javax.servlet.Filter arayüzünü implement eden bir Java sınıfı oluşturalım.
package com.example.filters;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
// @WebFilter anotasyonu ile filtreyi yapılandırma.
// urlPatterns = "/*" tüm gelen isteklere uygulanacağı anlamına gelir.
@WebFilter(urlPatterns = "/*", description = "Tüm istekleri loglayan filtre")
public class LoglamaFiltresi implements Filter {
private String filterName; // Filtre adını tutmak için
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Filtre başlatıldığında çağrılır
this.filterName = filterConfig.getFilterName();
System.out.println("--- " + filterName + " başlatıldı. ---");
System.out.println("Init parametresi (örnek): " + filterConfig.getInitParameter("logLevel"));
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
long startTime = System.currentTimeMillis();
// İstek hedefe ulaşmadan önce yapılacak işlemler
System.out.println(filterName + " - İstek geliyor: " + httpRequest.getRequestURL() +
" | Metot: " + httpRequest.getMethod() +
" | Başlangıç Zamanı: " + startTime);
// Filtre zincirindeki bir sonraki filtreyi veya hedef servlet'i çağır
chain.doFilter(request, response);
// İstek hedeften döndükten sonra yapılacak işlemler
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
System.out.println(filterName + " - İstek işlendi: " + httpRequest.getRequestURL() +
" | Bitiş Zamanı: " + endTime +
" | İşlem Süresi: " + executionTime + "ms");
}
@Override
public void destroy() {
// Filtre sonlandırıldığında çağrılır
System.out.println("--- " + filterName + " sonlandırıldı. ---");
}
}
2. Filter’ı Yapılandırma (@WebFilter veya web.xml)
Yukarıdaki örnekte @WebFilter anotasyonunu kullanarak filtreyi yapılandırdık. Bu, Servlet API 3.0 ve sonrası için önerilen modern yaklaşımdır.
@WebFilter anotasyonunun temel özellikleri:
urlPatterns: Filtrenin hangi URL desenlerine uygulanacağını belirtir. Örneğin,"/"tüm isteklere,"/admin/"sadece/adminaltındaki isteklere,"*.jsp"tüm JSP dosyalarına uygulanır.description: Filtrenin kısa bir açıklamasını sağlar.filterName: Filtrenin adını belirtir (varsayılan olarak sınıf adı kullanılır).
Eski Servlet API sürümleri veya bazı durumlarda daha fazla kontrol için web.xml dosyasını da kullanabilirsiniz. İşte web.xml ile aynı filtreyi nasıl yapılandıracağınız:
loglamaFiltresi com.example.filters.LoglamaFiltresi logLevel INFO loglamaFiltresi /*
Bu yapılandırma ile, LoglamaFiltresi adında bir filtre tanımlanır ve bu filtre tüm gelen isteklere (/*) uygulanır. init-param etiketi ile filtreye başlatma parametreleri de geçilebilir, bu parametreler FilterConfig nesnesi aracılığıyla init() metodunda erişilebilir.
Örnek Çalışma Akışı
Bir kullanıcı http://localhost:8080/mywebapp/index.jsp adresine bir istek gönderdiğinde:
- Web konteyneri isteği alır.
LoglamaFiltresi‘nindoFilter()metodu çağrılır.doFilter()metodu, isteğin URL’sini ve başlangıç zamanını loglar.chain.doFilter(request, response)çağrısı yapılır. İstek, hedefindex.jspdosyasına (veya varsa zincirdeki bir sonraki filtreye) ulaşır.index.jspişlenir ve cevabı oluşturulur.- Cevap,
LoglamaFiltresi‘ninchain.doFilter()çağrısından sonraki kısmına geri döner. doFilter()metodu, isteğin bitiş zamanını ve toplam işleme süresini loglar.- Cevap istemciye geri gönderilir.
Gelişmiş Filter Kullanım Senaryoları ve Pratik İpuçları
Filtreler sadece loglama için değil, daha karmaşık işlevler için de kullanılabilir. İşte birkaç gelişmiş senaryo ve pratik ipucu:
1. Kimlik Doğrulama (Authentication) Filtresi
Belirli sayfaların veya kaynakların sadece oturum açmış kullanıcılar tarafından erişilebilir olmasını sağlamak için bir kimlik doğrulama filtresi yazabilirsiniz.
package com.example.filters;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter(urlPatterns = "/secure/*", description = "Güvenli alan için kimlik doğrulama filtresi")
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false); // false: yeni session oluşturma
boolean isLoggedIn = (session != null && session.getAttribute("username") != null);
String requestURI = httpRequest.getRequestURI();
// Eğer kullanıcı oturum açmamışsa ve güvenli bir kaynağa erişmeye çalışıyorsa
if (!isLoggedIn && requestURI.contains("/secure/")) {
System.out.println("Yetkisiz erişim denemesi: " + requestURI);
// Kullanıcıyı giriş sayfasına yönlendir
httpResponse.sendRedirect(httpRequest.getContextPath() + "/login.jsp");
} else {
// Kullanıcı oturum açmış veya güvenli olmayan bir kaynağa erişiyor, devam et
chain.doFilter(request, response);
}
}
// init() ve destroy() metotları bu örnekte boş bırakılabilir
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
Bu filtre, /secure/ ile başlayan tüm URL’ler için kullanıcının oturum açıp açmadığını kontrol eder. Eğer oturum açmamışsa, kullanıcıyı /login.jsp sayfasına yönlendirir.
2. Karakter Seti Ayarlama Filtresi
Tüm gelen isteklerin ve giden cevapların karakter setini UTF-8 olarak ayarlamak, özellikle uluslararası karakterlerle çalışırken yaygın bir gereksinimdir.
package com.example.filters;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(urlPatterns = "/*", description = "Karakter seti ayarlama filtresi")
public class CharacterEncodingFilter implements Filter {
private String encoding;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// init parametresinden karakter setini oku, yoksa varsayılan UTF-8 kullan
this.encoding = filterConfig.getInitParameter("encoding");
if (this.encoding == null) {
this.encoding = "UTF-8";
}
System.out.println("Karakter Seti Filtresi başlatıldı. Encoding: " + this.encoding);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setCharacterEncoding(this.encoding);
response.setCharacterEncoding(this.encoding);
response.setContentType("text/html; charset=" + this.encoding); // HTML için Content-Type ayarla
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("Karakter Seti Filtresi sonlandırıldı.");
}
}
Bu filtre, gelen isteğin ve giden cevabın karakter setini UTF-8 olarak ayarlar, böylece Türkçe karakterler gibi özel karakterlerin doğru şekilde işlenmesini sağlar.
Pratik İpuçları
- Filtreleri Tek Sorumluluk Prensibiyle Tasarlayın: Her filtre tek bir spesifik görevi yerine getirmelidir (örn. sadece loglama, sadece kimlik doğrulama). Birden fazla işi tek bir filtrede birleştirmek yerine, her iş için ayrı bir filtre oluşturup bunları zincirleyin. Bu, kodunuzu daha okunabilir, sürdürülebilir ve test edilebilir hale getirir.
chain.doFilter()Çağrısını Unutmayın: Eğer bu çağrıyı yapmazsanız, istek hedef servlet’e veya zincirdeki bir sonraki filtreye asla ulaşmayacaktır. Bu, uygulamanızın kilitlenmesine veya yanlış davranışa yol açabilir. İstisna durumlarında (örn. yetkisiz erişim) isteği yönlendirmek veya hata cevabı döndürmek için bu çağrıyı atlayabilirsiniz.- Filtre Zinciri Sırası: Birden fazla filtre kullandığınızda, çalıştırma sırası önemlidir.
web.xmlkullanıyorsanız,filter-mappingetiketlerinin sırası çalıştırma sırasını belirler. İlk tanımlanan filtre ilk çalışır.@WebFilteranotasyonunu kullanıyorsanız, filtrelerin çalışma sırası genellikle alfabetik olarak (filterName’e göre) belirlenir, ancak bu tam garanti değildir ve konteynerden konteynere değişebilir. Daha kontrollü bir sıralama için@WebFilteranotasyonundadispatcherTypesveyaurlPatternsile daha spesifik eşleşmeler sağlayabilir veyaweb.xmlkullanmaya devam edebilirsiniz. Servlet 3.1 ilejavax.servlet.annotation.HandlesTypesveyajavax.servlet.annotation.Ordergibi anotasyonlar daha kontrollü sıralama sağlayabilir.
- Hata Yönetimi: Filtrelerinizde oluşabilecek hataları yönetmek için
try-catchblokları kullanın. Hatalar, uygulamanızın çökmesine veya beklendiği gibi çalışmamasına neden olabilir. - Performans Etkisi: Her istek için çalışan filtreler, performans üzerinde bir miktar ek yük oluşturabilir. Gereksiz filtrelerden kaçının ve filtrelerinizin iş mantığını optimize edin.
Sonuç ve Sıkça Sorulan Sorular (SSS)
Java Servlet Filtreleri, web uygulamalarınıza çapraz kesen işlevsellikleri eklemek için inanılmaz derecede esnek ve güçlü bir mekanizma sunar. Güvenlikten performansa, loglamadan veri manipülasyonuna kadar geniş bir yelpazede görevleri merkezi ve modüler bir şekilde yönetmenizi sağlarlar. Doğru kullanıldığında, filtreler web uygulamanızın bakımını kolaylaştırır ve kod tekrarını azaltır.
Sıkça Sorulan Sorular (SSS)
S1: Filtreler ve Interceptor’lar arasındaki fark nedir?
C1: “Interceptor” terimi genellikle Spring Framework gibi daha üst seviye çerçevelerle ilişkilendirilir. Servlet Filtreleri, Servlet API’nin bir parçasıdır ve HTTP istek/cevap döngüsünün en alt seviyesinde çalışır. Interceptor’lar ise genellikle bir çerçeve (örneğin Spring MVC) içinde daha spesifik olaylara (örneğin bir controller metodunun çağrılması) yanıt verir ve daha zengin bir bağlam (örneğin Spring bean’leri) sunabilir. Temel fark, Servlet Filtrelerinin konteyner seviyesinde çalışması, Interceptor’ların ise çerçeve seviyesinde çalışmasıdır.
S2: Bir filtrede birden fazla iş yapılabilir mi?
C2: Teknik olarak yapılabilir, ancak bu, “Tek Sorumluluk Prensibi”ne aykırıdır ve kodun karmaşıklığını artırır. Her filtreyi tek bir görev için tasarlamak (örneğin, bir filtre sadece loglama, diğeri sadece kimlik doğrulama yapar), kodun daha modüler, okunabilir, test edilebilir ve sürdürülebilir olmasını sağlar. Birden fazla işi olan filtreler yerine, birden fazla filtreyi zincirlemek daha iyi bir yaklaşımdır.
S3: chain.doFilter() çağrılmazsa ne olur?
C3: Eğer bir filtrede chain.doFilter(request, response) metodunu çağırmazsanız, istek filtre zincirindeki bir sonraki filtreye veya hedef servlet’e ulaşmaz. Bu durumda, filtre kendi başına bir cevap döndürmeli (örneğin, bir hata sayfası yönlendirmesi veya doğrudan içerik yazma) veya istek işlenmeden kalır ve istemciye herhangi bir cevap gönderilmez (bu da zaman aşımına veya boş bir cevaba yol açabilir).
S4: Filtreler hangi sırayla çalışır?
C4: web.xml kullanıldığında, filtreler filter-mapping etiketlerinin web.xml içindeki sırasına göre çalışır. İlk tanımlanan mapping ilk çalışır. @WebFilter anotasyonları kullanılırken, Servlet konteynerleri genellikle filtre adlarına göre alfabetik bir sıralama uygular, ancak bu davranış konteynerden konteynere değişebilir ve tam olarak garanti edilmez. Kesin sıralama kontrolü için web.xml veya Servlet 3.1+’daki özel mekanizmalar tercih edilebilir.
S5: Filtreler sadece Servlet’ler için mi geçerlidir?
C5: Evet, Servlet Filtreleri, Java Servlet API’nin bir parçasıdır ve Java EE/Jakarta EE web uygulamaları bağlamında Servlet’ler, JSP’ler ve diğer web kaynakları için gelen istekleri ve giden cevapları kesmek ve işlemek için kullanılırlar.