Merhaba Arkadaslar,
Bu yazimda Servlet lifecycle(yasam dongusu) konusundan bahsedecegim ve sonunda da kucuk bir uygulama yapacagiz. Hatirlayacagimiz gibi Servletlerin tum kontrolu Container’in sorumlulugu altindaydi. Dolayisiyla Servletlerin yasam dongusunden de Container sorumludur.
Handle Request
ervlet Container , gelen istegi su adimlari gerceklestirerek karsilar. ( handle request)
Bu vesileyle Servlet Lifecycle’a da goz atmis olacagiz , sonrasinda Servlet Lifecycle’i daha detayli olarak incelemeye calisacagiz.
Ornek bir senaryo uzerinde adim adim incelemelerde bulunalim;
Adim-1
Kullanici Servlet’i cagiran URL linkine tiklar.
Adim-2
Container , istegin Servlet icin oldugunu anlar ve iki tane obje olusturur.
- HttpServletResponse
- HttpServletRequest
Adim-3
Container, bu istek icin yeni bir thread olusturur(create/allocate) ve service() metodunu cagirir. Olusturulan request ve response objelerini bu metoda gecirir.
Adim-4
service() metodu hangi metodun cagrilacagina karar verir ve ilgili metodu cagirir.
Yani ilgili HTTP istegine gore(GET,HEAD,POST) doGet(), doHead(), doPost() cagrilir.
Adim-5
Servlet, response objesini istemciye cikti(cevap) yazmak icin kullanir.
Adim-6
service() metodu tamamlanir. response ve request objeleri garbage collection icin uygun hale gelir. Thread sonlanir ya da thread pool(havuza)’a gonderilir.
Servlet yasam dongusunde hala incelemedigimiz noktalar yer almakta. Simdi de biraz daha detaya inmeye calisalim.
Finding&Loading
Servlet yasam dongusu, Container’in ,Servlet class dosyasini bulmasiyla baslar. Apache Tomcat calistiginda dogal olarak Container modulumuz de calisacaktir. Container deploy edilen projede Servlet siniflarini bulacaktir. Tabi ki bunun icin onceki yazilarda bahsettigim deployment descriptor yani web.xml dosyasindan yararlanacaktir.(finding)
Bulunan bu siniflarin yuklenmesi(loading) ikinci adimdir.
Bu yukleme islemi ya Container baslatildiginda (startup) ya da ilk kullanimda/ilk gerektiginde yapilir.
- Genel olarak Servletler icin yapilandirici tanimlamasi yapilmamalidir. Varsayilan(default) yapilandirici otomatik olarak calisacaktir.
- Servlet’ler icin yapilandirici tanimlamak yerine init metodu kullanilir.
- Yapilandirici , init metodundan once calisir.
- Eger no-arg yapilandirici tanimlayacaksak bu durumda yapilandiricimiz public olmalidir.
- Bununla birlikte sinifimizin da access level’i public olmalidir.
Container, su metodu kullanarak Servlet instance/objesi olusturur. Bu kodun calisabilmesi icin yukarika belirtildigi gibi sinif ve default yapilandirici public olmak zorundadir.
Class.forName(className).newInstance();
Servlet Initialization
Servletin initialized(ilk deger verilmis/atanmis) olmasi yapilandiricinin(constructor) calismasi ile baslar. Ne varki yapilandirici(constructor) obje olusturur, Servlet degil.
Servlet, initialized(ilk deger verilmis/atanmis) ozelligine init() metodu sonlandiginda sahip olur.
Hatirlayacagimiz gibi , ilk ornegimizde HelloServletWorld sinifimiz HttpServlet sinifindan kalitiyordu.
public class HelloServletWorld extends HttpServlet { .... }
HttpServlet sinifina gittigimizde GenericServlet sinifindan kalitildigini gorebiliriz.
public abstract class HttpServlet extends GenericServlet { ... }
GenericServlet sinifimizda su iki overloaded init() metodumuz yer almaktadir.
@Override public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } public void init() throws ServletException { // NOOP by default }
Servlet objesi/instance olustugunda yani yapilandirici calistiktan sonra , init(ServletConfig) metodu cagrilacaktir. Bu metot da overloaded init() metodunu cagiracaktir.
Iki overloaded init() metodunu da override edip kullanabiliriz, fakat parametre almayan init() metodunu override edip kullanmamiz daha uygun(convenience) olacaktir.
Eger init(ServletConfig) metodunu override edersek , bu metodumuz icerisine super.init(config) kodunu ekleyelim. Aksi takdirde ServletConfig objemizi kullanamayiz. Ilerleyen bolumlerde ServletConfig ‘ den bahsedecegim.
init() metodu kullanima ornek olarak veritabani baglantisi icin kullanabilecegimiz username/password database url adresi gibi bilgileri hard-code olarak burada yazabiliriz.
Genellikle, Container calisir calismaz(start up) servlet initialization yapmayacaktir bunun yerine bu servlet icin ilk istek geldiginde ilgili servlet icin initialization islemi yapacaktir. Buna lazy loading denilir.
Deployment descriptor dosyamizda <load-on-startup> etiketi/tag yardimi ile Container basladiginda mumkun olan en kisa surede initialization islemi yapmasi icin tanimlama yapabiliriz. Servlet icin ilk istek gelmeyip oncesinde initialize edilmesine preloading ya da preinitializing denilir.
Dikkat : init() metodu Servlet yasam dongusunde sadece bir kez cagrilir.
Servicing
Servlet objemiz olusturulduktan ve Servlet ozellikleri initialize edildikten sonra artik istekleri karsilamaya(handle request) haziriz.
Container, istek aldiginda service(ServletRequest,ServletResponse) metodunu cagiracaktir.
Servlet.class a gittigimizde Servlet’in interface/arabirim oldugunu ve service() metodunun su sekilde tanimlandigini gorebiliriz.
public interface Servlet { .... public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; .... }
Bu metot GenericServlet sinifinda da yer almaktadir fakat govdeli hale getirilmemistir. Hatirlayacagimiz gibi interface/arabirimlerin metotlari varsayilan olarak public abstract ozellige sahiptir.
public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { .... @Override public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; .... }
HttpServlet sinifimiza gittigimizde ise service() metodumuzun override edilip uygulandigini ve overloaded bir metot eklendigini gorebiliriz.
public abstract class HttpServlet extends GenericServlet { .... @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException("non-HTTP request or response"); } service(request, response); } ..... protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { // Invalid date header - proceed as if none was set ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } } ..... }
Dikkat ederseniz service(request,response) ile overloaded olan diger service metodu cagrilmaktadir ve asil is burada yapilmaktadir, gelen HTTP metodunun turune gore (GET, POST, HEAD,PUT vs) doGet, doPost, doHead gibi metotlari cagirmaktadir.
- service() metodu teknik olarak override edilebilir(final metot degildir) ama kesinlikle (%99.999) override edilmemelidir!
- Bir Servlet’in birden fazla instance/objesi olamaz.(SingleThreadModel haric ilerde bahsedecegim).
- Her istek/request ayri bir thread’te calisir. Container birden fazla/multiple thread uzerinde birden fazla istek/request icin tek bir servlet objesi uzerinde calisir. Yani 100 farkli client/istemci 500 tane istekte de bulunsa HelloServlet in sadece bir instance/objesi olusur.
- Her service() metodu kendi stack alaninda calisir. Cunku her istek/request bir thread’e karsilik gelmektedir. Her thread de yeni bir stack’e karsilik gelir.
Resme dikkat edecek olursak , Client A ve Client B ayni servlet icin istekte/request bulunuyor. Client A nin istegi ile Client B nin istegi ayri thread’lerde calisiyor ve bu istekler icin ayri ayri request , response objeleri olusturuluyor.
Unutmayalim her istek/request icin bir thread ve sadece bir tek obje !
doXXX
service() metodu gelen istegin/request turune gore ilgili doXXX metodunu cagiracaktir.
Ornegin HTTTP GET istegi geldiginde doGet metoduna yonlendirecektir.
Destroying
Container, servlet objesine artik ihtiyac olmadigini dusunuyorsa destroy() metodunu cagiracaktir. Bu metot cagrildiginda servlet objemiz artik servlet ozelligini kaybetmis olacaktir ve service() metodu cagrilamayacaktir.
destroy() metodu GenericServlet abstract sinifinda su sekilde override edilmistir;
@Override public void destroy() { // NOOP by default }
Dolayisiyla bu metot hic bir sey yapmamaktadir , bu nedenle init() metodu gibi kendimiz override etmemiz gerekmektedir.Ornegin,init() metotunda actigimiz veritabani baglantisini destroy() metodunda kapatabiliriz.
Dikkat : destroy() metodu Servlet yasam dongusunde sadece bir kez cagrilir.
Ornek Uygulama
Bir onceki basit bir Servlet ornegi yazmistik yine benzer sekilde bir ornek yapacagiz. Yeni bir paket ve sinif olusturdum.
_02_ServletLifecycle
ServletLifecycle
web.xml dosyamizda gerekli tanimlamalarimizi yapalim.
<servlet> <servlet-name>ServletLifecycle</servlet-name> <servlet-class>_02_ServletLifecycle.ServletLifecycle</servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletLifecycle</servlet-name> <url-pattern>/servletlifecycle</url-pattern> </servlet-mapping>
ServletLifecycle.java
package _02_ServletLifecycle; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ServletLifecycle extends HttpServlet { @Override public void init() throws ServletException { System.out.println("Hello init() method"); System.out.println("init() metodu ServletLifecyle boyunca bir kez calisir."); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter pw = resp.getWriter(); Date today = new Date(); String msg = "ServletLifecycle"; pw.print("<html><body> <h1>" + msg + "</h1>"); pw.print("<p>today:" + today + "<p>"); pw.print("<a href= http://www.injavawetrust.com/>injavawetrust </a>"); pw.print("</body></html>"); } @Override public void destroy() { System.out.println("####destroy"); } }
ServletLifecyle.java dosyamiza sag tiklayip Run As–> Run on Server–>Finish diyelim ve calistiralim. Her sey yolundaysa uygulamamiz sorunsuzca calisacaktir.
Eclipse’te, Console sekmesine dikkat ederseniz Serverimiz calisti ve sonrasinda init() metodumuz calisti.
Tarayiciyi kapatip , ServletLifecycle sinifimizda degisiklik yaptigimizda Tomcat otomatik olarak deploy yapacaktir ve destroy() metodunu cagiracaktir.
init() metodumuz servlet lifecycle(yasam dongusu) boyunca bir kez calisacaktir.
Sayfayi refresh yaptigimizda ya da farkli tarayicidan da calistirsak init() metodumuz tekrar calismayacaktir.
Sinifimizda degisiklik yaptigimizda , Apache Tomcat tarafindan once destroy metodu calisacak sonrasinda tekrar deploy islemi yapildigi icin tekrar init() metodumuz calisacaktir.
Servlet Lifecycle/yasam dongusu metotlarini kisaca ozetleyecek olursak ;
public void init(ServletConfig config) throws ServletException;
Servlet Container init(ServletConfig) metodunu initialize islemi icin kullanir. Bu metot servlet lifecycle/yasam dongusunde sadece bir kez cagrilir.
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
Servlet Container service metodunu her istek geldiginde cagiracaktir. Bu istegin kimden geldigi onemli degildir. Ayni kullanici/client ayni servlet icin istekte bulunsa tekrar tekrar service metodu cagrilacaktir.
public void destroy();
Servlet Container , destroy metodunu Servlet’in unloading islemi icin cagiracaktir. Servlet Lifecycle/yasam dongusunde destroy metodu init metodu gibi sadece bir kez cagrilir.
Kodlar: injavawetrust_v2