Showing posts with label Java EE 7. Show all posts
Showing posts with label Java EE 7. Show all posts

Sunday, March 13, 2016

WebLogic 12.2.1'de Kümeleme (1. Bölüm)


Oracle orta katman uygulamalarının hemen hemen hepsi Java ve Weblogic uygulama sunucusu üzerinde koşuyor. Bu nedenle Oracle'ın Java SE ve Java EE platformlarına yatırım yapmak ve platformun gelişmesine öncülük etmek zorunda. Java SE platformunun en son sürümü olan Java SE 8, 2014 yılı Mart ayında çıktı. Java SE 8 getirdiği önemli yenilikler ile Java geliştiricilerini heyecanlandırmışa benziyor. Kurumsal uygulama geliştirmek için tasarlanan Java EE platformunun en son sürümü Java EE 7 ise 2013 yılı Haziran ayında çıktı. Oracle Java EE 7 uyumlu uygulama sunucusunu nihayet 2015 yılı sonuna doğru duyurdu: Oracle WebLogic 12.2.1. Weblogic 12.2.1 sürümü hem Java EE 7 uyumlu hem de Java SE 8 üzerinde çalışıyor olması açısından önemli bir sürüm. Java EE 7Java EE 5 ile gelen yeni programlama modelinin devam ettirildiği en olgun sürümdür. Ayrıca Java EE 7HTML 5 ile gelen yeni nesil web mimarilerini destekleyen API'ler içeriyor: Java API for WebSocket, Java API for JSON Processing, JAX-RS 2.0
Bu yazıda WebLogic 12cR2 sürümünün kurulumunu ve kümeleme çözümünü inceleyeceğiz. Kümleme çözümünün kurulumu için Weblogic 12cR2'nin ve yazılımsal yük dengeleyici olarak kullanacağımız Oracle HTTP Server (OHS)'nin kurulum dosyalarına ihtiyacımız bulunuyor. Bu kurulum dosyalarını Oracle'ın edelivery sitesinden indirebiliriz: edelivery.oracle.com.
http://www.edelivery.com giriş ekranında "Sign In" butonu ile giriş yapmamız gerekiyor
Ücretsiz olarak oluşturulabilecek Oracle hesabı ile giriş yapmalısınız.
Oracle'ın tüm ürünlerine Oracle Software Delivery Cloud üzerinden sorgulayıp, ulaşmak mümkündür. Arama alanına weblogic yazdığımızda listede gözüken Oracle Weblogic Suite başlığını seçiyoruz.
Ürünün adını seçtikten sonra kurulum yapmayı planladığımız platformu seçiyoruz: Linux x86-64.
Yazılım ve platform seçildiğinde seçilen ürün listede gözükecektir: Oracle Weblogic Suite ve Linux x86-64. Artık "Continue" butonuna basarak ilerleyebiliriz.
Toplam yaklaşık 11GB boyutunda kurulum paketlerine ulaşmak için "Continue" butonuna basmalısınız. 
Kurulum dosyalarını indireceğimiz sayfaya gelmeden önce "mutlaka baştan sona" kadar okumamız gereken  bir sayfa bulunuyor. Aldığım hızlı okuma eğitimi sayesinde bir çırpıda okuyup "Continue" butonuna basıyorum!
Burada kırmızı çerçeve içine aldığım iki dosyayı indirmemiz gerekiyor: i) OHS 12.2.1 için V78157-01.zip ii) WebLogic 12.2.1 için V78155-01.zip

WebLogic 12cR2'nin kurulumu

1. Weblogic 12cR2'nin kurulumuna geçmeden önce Java 8 kurmamız gerekiyor. Bunun için jdk 8 update 60'ı kuracağım:
[oracle@host01]tar xvf jdk-8u60-linux-x64.tar.gz -C /u01/app
[oracle@host01]mv /u01/app/jdk1.8.0_60 /u01/app/jdk8
[oracle@host01]export JAVA_HOME=/u01/app/jdk8
[oracle@host01]export PATH=$JAVA_HOME/bin:$PATH
Java kurulumunu test edelim:
[oracle@host01]$ java -version
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
Evet, artık Weblogic 12cR2'nin kurulumuna geçebiliriz!
2. edelivery sitesinden indirdiğimiz V78155-01.zip dosyasını açıyoruz. İçinden bir java arşiv dosyası (jar) dosyası çıkacaktır: fmw_12.2.1.0.0_wls.jar. Kurulum için bu jar dosyasını çalıştırıyoruz:
[oracle@host01]$ java -d64 -jar fmw_12.2.1.0.0_wls.jar
1. Adım: Karşılama ekranını "Next" butonuna basarak geçiyoruz.
2. Adım: Kurulumdan hemen sonra güncelleme almak isteyebilirsiniz. Bunun için Oracle destek hesap bilgilerini girmeniz gerekir. Ya da elle indirdiğiniz güncelleme dosylarının bulunduğu dizini verebilirsiniz. Biz güncellemeleri atlayacağız.
3. Adım: Kurulumu yapacağımız dizini tanımlıyoruz.Bu dizin "Oracle Home" olarak adlandırılır.

4. Adım: Kurulumun türünü seçiyoruz. Üç seçeneğimiz bulunuyor: a) WebLogic kurulumu b) Coherence kurulumu c) Örnek uygulamalarla birlikte WebLogic kurulumu. WebLogic kurulumu Coherence kurulumunu da içerir. Coherence Oracle'ın "Distributed Cache Server" çözümüdür.
5. Adım: Kurulum öncesi işletim sistemi ve Java versiyonu denetleniyor.
6. Adım: Oracle Support hesabı bilgileri isteniyor. Bu kurulumda Oracle Support hesabı bilgilerini girmek istemiyoruz. Bu nedenle "wish to receive security updates via My Oracle Support" seçimini kaldırıyoruz.
6. Adım: Evet, Oracle Support almak istemiyoruz. "Rahat ol Oracle! Ne yaptığımızın farkındayız!" anlamında Yes butonuna basıyoruz. 
7. Adım: Seçimlerimizi, daha sonra sessiz kurulum yapmak için "Response File" olarak kaydetmek isteriz. Bu amaçla Save Response File butonuna basıyoruz.
Response File" dosyasının adını veriyoruz: fmw.rsp. 
8. Adım: Kurulum nihayet başladı
8. Adım: Dosyalar kopyalanıyor.
8. Adım: Kütüphane dosyaları oluşturuluyor.
8. Adım: Kurulum yapılıyor.
8. Adım: Kurulum tamamlandı!
9. Adım: Kurulumun başarı ile tamamlandığını gösterir ekran. Bundan sonra domain oluşturacağız. Bunu için gerekli komutu elle çalıştırmak istediğimiz için "Automatically Launch the Quickstart Configuration Wizard" seçeneğini kaldırıyoruz. Finish butonuna basarak kurulumu tamamlayalım.

Alan (=Domain) Kurulumu

WebLogic sunucularını yönetebilmek için "alan" (=domain) oluşturuyoruz. Her alan mutlaka ve bir tane yönetim sunucusundan (Admin Server) oluşur. Bunun dışında herhangi bir sayıda "yönetilen" weblogic sunucuları yer alır. Yönetim sunucusu sunucuların yapılandırma bilgilerini saklar ve alandaki diğer yönetilen sunuculara iletir. Yönetilen sunuculara uzaktan ulaşmak ve yönetmek için makinalardan (=machine) yararlanıyoruz. Her yönetilen weblogic sunucuları bir makinada tanımlanır. Bir fiziksel makinada birden fazla makina ve bir makinada birden fazla yönetilen weblogic sunucusu bulunabilir. Ancak her alanda ancak bir tane yönetim sunucusu yer alır. Bu yazıda iki düğümlü bir küme (=cluster) kuracağız. Bunun için iki makina ve iki yönetilen weblogic sunucusu tanımlayacağız. Kuracağımız sistemin şemasını aşağıdaki şekilden inceleyebilirsiniz:
Kurulum şeması
Alan aslında bir şablondan yaratılır. Kurulumla gelen şablonlar /u01/app/fmw/wlserver/common/templates/wls
 dizininde yer alır. Weblogic kurulumunda wls.jar şablonunu kullanacağız. Alan yaratmak için aşağıdaki komutu çalıştırıyoruz:
[oracle@host01 ~]$ cd /u01/app/fmw/oracle_common/common/bin/
[oracle@host01 bin]$ ./config.sh 
Başlangıç ekranı ile alan yaratmaya başlıyoruz!
1. Adım: Alanı hangi dizine yaratacağımızı söylüyoruz.  
2. Adım: Alanı hangi dizine yaratacağımızı söylüyoruz.
3. Adım: Weblogic sunucuları için kullanıcı adı ve parolayı belirliyoruz.
4. Adım: Alanı üretim ortamı için düşünüyoruz. Java versiyonumuz ise HotSpot 1.8.0_60 olacak.
5. Adım: Yönetim sunucusu ve yönetilen sunucu ve kümeleme ayarı yapacağımızı bildirdik.
6. Adım: Yönetim sunucusu host01.example.com ağına 7001 numaralı port üzerinden hizmet verecek.
7. Adım: İki adet yönetilen sunucumuz var: server1 ve server2.
8. Adım: Kümeleme adını cluster1 olarak verdik. Yük dengeleyiciyi daha sonra tanımlayacağız.
9. Adım: cluster1 kümesinin sunucularını tanımladık.
10. Adım: Makinaların tanımları veriliyor.
11. Adım: Hangi sunucunun hangi makinada çalışacağının tanımı veriliyor.
12. Adım: Alanı yaratmadan önce seçimlerimizi gözden geçirebiliriz. Sonra Create butonuna basarak alanı yaratıyoruz.
13. Adım: Alan yaratıldı.
14. Adım: Alan başarı ile yaratıldı!
Alanı host01.example.com'da yarattık. Şimdi bu alanı paketleyip host02.com.example'da da oluşturalım. Bunun için pack.sh ve unpack.sh betiklerinden yararlanıyoruz. Bu betikleri /u01/app/fmw/oracle_common/common/bin dizininde bulabilirsiniz.
oracle@host01:~ >export JAVA_HOME=/u01/app/jdk
oracle@host01:~ >export PATH=$JAVA_HOME/bin:$PATH
oracle@host01:~ >java -version
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
oracle@host01:~ >export ORACLE_HOME=/u01/app/fmw
oracle@host01:~ >export PATH=$ORACLE_HOME/oracle_common/common/bin:$PATH
oracle@host01:~ >pack.sh -domain /u01/domains/part1/wlsadmin -template=/home/oracle/managedserver.jar -template_name=wlsadmin_managed -managed=true
<< read domain from "/u01/domains/part1/wlsadmin"
>>  succeed: read domain from "/u01/domains/part1/wlsadmin"
<< set config option Managed to "true"
>>  succeed: set config option Managed to "true"
<< write template to "/home/oracle/managedserver.jar"
..........................................................................................
>>  succeed: write template to "/home/oracle/managedserver.jar"
<< close template
>>  succeed: close template
oracle@host01:~ >scp /home/oracle/managedserver.jar oracle@host02.example.com:/home/oracle 
managedserver.jar                                                           100%   62KB  62.5KB/s   00:00
Alanı paketleyip host02.example.com makinasına taşıdıktan sonra, bu makinada aşağıdaki komutu çalıştırıyoruz:
[root@host02 ~]# export JAVA_HOME=/u01/app/jdk
[root@host02 ~]# export PATH=$JAVA_HOME/bin:$PATH
[root@host02 ~]# export ORACLE_HOME=/u01/app/fmw
[root@host02 ~]# export PATH=$ORACLE_HOME/oracle_common/common/bin:$PATH
[root@host02 ~]# unpack.sh -domain=/u01/domains/part1/wlsadmin -template=/home/oracle/managedserver.jar 
<< read template from "/home/oracle/managedserver.jar"
>>  succeed: read template from "/home/oracle/managedserver.jar"
<< set config option DomainName to "wlsadmin"
>>  succeed: set config option DomainName to "wlsadmin"
<< write Domain to "/u01/domains/part1/wlsadmin"
..........................................................................................
>>  succeed: write Domain to "/u01/domains/part1/wlsadmin"
<< close template
>>  succeed: close template

Sunucuların Başlatılması

Yönetim sunucusunu startWebLogic.sh betiğini kullanarak başlatıyoruz:
gnome-terminal --title "Admin Server"-e "/u01/domains/part1/wlsadmin/startWebLogic.sh" 
Üretim ortamı için kurulum yaptığımızdan, weblogic sunucusu bizden kullanıcı adı ve parola isteyecektir:
Sunucuyu her başlatırken bunun olmasını istemeyiz. Çözüm için aşağıdaki adımları bir kez yapmanız ve kullanıcı adı ve parolayı içeren boot.properties dosyasını oluşturmalısınız:
[oracle@host01]$ cd /u01/domains/part1/wlsadmin/servers/AdminServer/
[oracle@host01] mkdir security
[oracle@host01] cd security/
[oracle@host01] touch boot.properties
[oracle@host01] echo username=weblogic > boot.properties 
[oracle@host01] echo password=Welcome1 >> boot.properties 
Bundan sonraki ilk açılışta dosyanın içeriğinin AES ile şifrelenmiş olduğunu göreceksiniz:
[oracle@host01 security]$ cat boot.properties 
#Tue Mar 08 11:51:03 EET 2016
password={AES}e9YzTK33FBYiEMG0P5iZ3oyBCE4+XLVlKiSv5bnylG4\=
username={AES}MxPQruwueQAdLQBd2rWd44yanML0BW1md3bznAUiaik\=
host01.example.com makinasında server1'i başlatmak için startManagedWebLogic.sh betiğini kullanıyoruz:
[oracle@host01]$ gnome-terminal --title "server1" "/u01/domains/part1/wlsadmin/bin/startManagedWebLogic.sh server1 host01.example.com:7001" &
host02.example.com makinasında server2'yi başlatmak için startManagedWebLogic.sh betiğini kullanıyoruz:
[oracle@host02]$ gnome-terminal --title "server2" "/u01/domains/part1/wlsadmin/bin/startManagedWebLogic.sh server2 host01.example.com:7001" &
Şimdi, weblogic yönetim ekranına (http://host01.example.com:7001/console) bağlanıp sunucuların çalıştığını doğrulayalım:


Düğüm Yöneticisi (=Nodemanager) Kullanımı

Sunucuları her seferinde çalıştıkları makinaya uzaktan bağlanarak çalıştırmak, durdurmak, yeniden başlatmak, istenmeyen bir şekilde sonlandığında yeniden ayağa kaldırmak işlemlerini elle yapmak yerine otomatikleştirmek isteriz. Bu işi bizim için düğüm yöneticisi (Node manager) gerçekleştirecektir. Her bir makinada bir düğüm yöneticisi çalışır. Şimdi host01.example.com'da düğüm yöneticilerini başlatalım:
[oracle@host01]$ gnome-terminal --title "Nodemanager @host01" "/u01/domains/part1/wlsadmin/bin/startNodeManager.sh" &
host02.example.com'da düğüm yöneticisini başlatmak için yine aynı betiği çalıştırıyoruz.

[oracle@host02]$ gnome-terminal --title "Nodemanager @host02" "/u01/domains/part1/wlsadmin/bin/startNodeManager.sh" &
Aynı makinada her alan için bir düğüm yöneticisi olabileceği gibi tek bir düğüm yöneticisi birden fazla alan için de hizmet verebilir. Düğüm yöneticisinin çalıştığını yönetim ekranından doğrulayabiliriz:
machine1'deki düğüm yöneticisi çalışır durumda.
machine2'deki düğüm yöneticisi çalışır durumda.
Yazının ikinci bölümünde kümeleme stratejileri, yapılandırılması ve çalışması ile ilgili detaylar ve yük dengeleme konuları yer alacaktır.

Sunday, November 8, 2015

Java EE 7 Platformunda İlgiye Dayalı Programlama

Java EE üzerinde ilgiye dayalı programlama yapmak için farklı katmanlarda farklı çözümler yer alır:
  • Web Filter
         HTTP isteği, sunum katmanına girmeden önce araya girip, güvenlik (kimlik doğrulama, yetkilendirme, kullanıcının tek oturum açabilmesi gibi), kayıt tutmak, başarımı izlenmek gibi farklı ilgileri kodlayabiliriz. Aşağıdaki örnekte, hangi saatte hangi oturumdan hangi kaynağa (xhtml, jsp gibi) erişildiğinin kaydını tutmak için yazılmış bir Web Filtresi yer alıyor. Kodu basit tutmak amacıyla, herhangi bir loglama çatısından (log4j, logback gibi) yararlanılmamıştır. 
package com.example.web.filters;

import java.io.IOException;
import java.util.Date;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

@WebFilter(urlPatterns = "/*")
public class SessionAuditFilter implements Filter {

   @Override
   public void destroy() {

   }

   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    HttpServletRequest _request = (HttpServletRequest) request;
    String pathInfo = _request.getPathInfo();
    if (pathInfo.endsWith(".xhtml") || pathInfo.endsWith(".jsp")) {
       Date now = new Date();
       String sessionId = _request.getSession().getId();
       System.err.println("Access to " + pathInfo 
                              + " for the session " + sessionId 
                              + " @ " + now);
    }
    chain.doFilter(request, response);
   }

   @Override
   public void init(FilterConfig filterConfig) throws ServletException {

   }

}
  • WebListener
WebListener kullanarak, 
  • oturum açıldığında ya da sona erdiğinde (HttpSessionListener), 
  • oturumda saklanmak üzere bir nesne eklendiğinde, güncellendiğinde ya da silindiğinde (HttpSessionAttributeListener), 
  • uygulama başlatıldığında ya da durdurulduğunda (ServletContextListener)
araya girip işler yapmak mümkündür. Aşağıdaki örnekte HttpSessionAttributeListener kullanarak oturuma eklenen nesnelerin boyutu ve bellek yerleşimi ekrana listelenmektedir:
package com.example.web.listeners;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

import org.openjdk.tools.objectlayout.ObjectLayout;

@WebListener
public class SessionAttributeMemoryLayoutListener implements HttpSessionAttributeListener {

   @Override
   public void attributeAdded(HttpSessionBindingEvent event) {
    Object value = event.getValue();
    System.err.println("Attribute is added: " + value);
    printMemoryLayout(value);
   }

   @Override
   public void attributeRemoved(HttpSessionBindingEvent event) {
     Object value = event.getValue();
     System.err.println("Attribute is removed: " + value);
     printMemoryLayout(value);
   }

   @Override
   public void attributeReplaced(HttpSessionBindingEvent event) {
     Object value = event.getValue();
     System.err.println("Attribute is replaced: " + value);
     printMemoryLayout(value);
   }

   private void printMemoryLayout(Object o) {
     try {
       System.err.println("Size: " + ObjectLayout.sizeOf(o));
       ObjectLayout.analyze(System.err, o.getClass());
     } catch (Exception e) {
       e.printStackTrace();
     }
   }
}
  • PhaseListener
           JSF çatısında HTTP istekleri karşılanırken Uç Denetçinin (=Front Controllerjavax.faces.webapp.FacesServlet yönettiği 6 evreden (=Phase) oluşan bir döngü çalışır. Bu evrelerden her biri çalışmadan önce ve çalıştıktan sonra araya girip farklı problemler ile ilgilenen kodlar yazılabilir. Aşağıdaki örnekte bu evrelerin teker teker ve toplam olarak çalışma sürelerini ölçen bir PhaseListener çözümü verilmiştir. Çözümde PerformanceMonitorPhaseListener sınıfı kodlanırken, Thread-Safe olmasını sağlayacak önlemler alınmıştır:
package com.example.jsf;

import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.http.HttpServletRequest;

public class PerformanceMonitorPhaseListener implements PhaseListener {

   @Override
   public void afterPhase(PhaseEvent event) {
      HttpServletRequest request = (HttpServletRequest) 
              event.getFacesContext().getExternalContext().getRequest();
       long start = (Long) request.getAttribute("start");
       Long total = (Long) request.getAttribute("total");
       if (total == null) total = 0L;
       long duration = System.nanoTime() - start;
       total += duration;
       request.setAttribute("total", total);
       String sessionId = request.getSession().getId();
       System.err.println("Session (" + sessionId + ") [" 
                           + event.getPhaseId() + "]: " + duration);
       if (event.getPhaseId().equals(PhaseId.RENDER_RESPONSE)) {
          System.err.println("Session (" + sessionId + ") [TOTAL]: " + total);
       }
   }

   @Override
   public void beforePhase(PhaseEvent event) {
       HttpServletRequest request = (HttpServletRequest) 
              event.getFacesContext().getExternalContext().getRequest();
       request.setAttribute("start", System.nanoTime());
   }

   @Override
   public PhaseId getPhaseId() {
       return PhaseId.ANY_PHASE;
   }

}
  • Context and Dependency Injection (CDI) ile İlgiye Dayalı Programlama
Java EE 6 ile birlikte yeni bir bileşen modelimiz daha var: CDI (Contexts and Dependency Injection) Bean. CDI temel olarak hizmet alan ve veren bileşenler arasındaki bağımlılıkları yöneten bir çatı sunuyor. Tıpkı Spring, Guice, JBoss Seam gibi. Bunun dışında ilgiye dayalı programlama, olay temelli programlama ve dekoratör tasarım kalıbı gibi çözümlere hızlı bir şekilde ulaşmamızı sağlar. İlgiye dayalı programlama için ise önce ilgiyi tanımlayacak bir not (=annotation) tasarlıyoruz:
package com.example.aspects;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.interceptor.InterceptorBinding;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@InterceptorBinding
public @interface Audit {

}
Bu notun (@Audit) metod ve sınıflara düşülebileceğini @Target({ ElementType.METHODElementType.TYPE }) damgasını kullanarak tanımladık. Yürütme zamanında uygulamanın bu notları okuyabilmesi için hem derleyicinin notu class uzantılı dosyaya meta veri olarak yazması hem de sınıf yükleyicinin (=class loader) belleğe PermGen ya da Meta-Space alanına yüklemesi için @Retention(RetentionPolicy.RUNTIME) notunu düştük. Tanımda kullandığımız üçüncü not (@InterceptorBinding) ile bunun CDI çatısında ilgiye dayalı programlama yapmak için tasarlanmış bir not olduğunu belirtiyoruz. Notlar derlendiklerinde çalışan bir koda dönüşmezler, meta bilgi olarak .class dosyasına yazılırlar. Bu nedenle, bu notun iliştirildiği metodlar çalışmadan önce araya girip işler yapmamızı sağlayacak metodu tanımlamalıyız: 
package com.example.aspects;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Date;

import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

@Interceptor
@Audit
public class AuditInterceptor implements Serializable {
   @AroundInvoke
   public Object audit(InvocationContext ic) throws Exception {
       String methodName = ic.getMethod().getName();
       Date now = new Date();
       System.err.println( methodName + " is called at " + now );
       System.err.println( "Parameters are " + Arrays.toString(ic.getParameters()) );
       Object result = ic.proceed();
       now = new Date();
       System.err.println(methodName + " returns " + result + " @ " + now);
       return result;
   }
}
Burada @Interceptor notu, bu sınıfın CDI için bir ilgiye dönük programlama için tasarlanmış  araya girip işler yapacak bir sınıf olduğunu, @Audit notu ise bunu @Audit notu ile damgalanmış metodlar için yapacağını tanımlıyor. AuditInterceptor sınıfının araya girip işler yapacak metodunun (audit) ise @AroundInvoke notu düşüldüğünü görüyoruz. Hangi nesnenin (ic.getTarget()) hangi metodu (ic.getMethod()) çalışmadan önce araya girdiğimize, parametrelerine (ic.getParameters()) ve diğer bilgilere erişmek için ise audit() metoduna parametre olarak gelen InvocationContext tipindeki parametreden yararlanıyoruz. ic.proceed() çağrısı ile ise asıl işi yapacak nesnenin araya girdiğimiz metodunun çalışmasını sağlıyoruz. @Audit notunu bir sınıfa iliştirirsek o sınıfın tüm metodları çalışmadan önce araya girilmesini sağlamış oluruz:
package com.example.stockmarket.viewmodel;

import java.io.Serializable;
import java.util.Collection;

import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;

import com.example.stockmarket.dao.StockDao;
import com.example.stockmarket.entity.Stock;

@Named("viewModel")
@SessionScoped
@Audit
public class StockViewModel implements Serializable {
   @Inject
   transient private StockDao stockDao;
   private Stock stock= new Stock();
   private Collection<Stock> stocks;

   public Stock getStock() {
       return stock;
   }

   public void setStock(Stock stock) {
       this.stock = stock;
   }

   public Collection<Stock> getStocks() {
       return stocks;
   }

   public void setStocks(Collection<Stock> stocks) {
       this.stocks = stocks;
   }

   public void doFind(){ 
       stock= stockDao.find(this.stock.getSymbol());
   }

   public void doFindAll(){
       this.stocks= stockDao.findAll();
   }

   public void doDelete(){
       this.stock= stockDao.delete(stock.getSymbol());
       this.stock= new Stock();
   }

   public void doAdd(){
       stockDao.add(stock);
       this.stock= new Stock();
   }

   public void doUpdate(){
       stockDao.update(stock);
       this.stock= new Stock();
   }

}
Eğer @Audit notunu sadece bir metoda düşersek, o not düştüğümüz metod çalışmadan önce araya girilir:
package com.example.stockmarket.dao.impl;

import java.util.Collection;
import java.util.List;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import com.example.aspects.Audit;
import com.example.stockmarket.dao.StockDao;
import com.example.stockmarket.entity.Stock;

@Stateless
@LocalBean
public class JpaStockDao implements StockDao {
   @PersistenceContext(unitName="stockdbPU")
   private EntityManager em;

   @Override
   public Stock find(String symbol) {  
       return em.find(Stock.class, symbol);
   }

   @Override
   public Collection<Stock> findAll() {
       List<Stock> stocks = 
          em.createNamedQuery("Stock.findAll",Stock.class)
     .getResultList();
       return stocks;
   }

   @Override
   public Stock add(Stock stock) {
       em.persist(stock);
       return stock;
   }

   @Override
   @Audit
   public Stock update(Stock stock) {
       Stock found= em.find(Stock.class, stock.getSymbol());
       if (found==null) return null;
       found.setPrice(stock.getPrice());
       found.setCompany(stock.getCompany());
       return found;
   }

   @Override
   @Audit
   public Stock delete(String symbol) {
       Stock found= em.find(Stock.class, symbol);
       if (found==null) return null;
       em.remove(found);
       return found;
   }

}
Yukarıdaki örnekte sadece update(Stock stock) ve delete(String symbol) metodlarından önce araya girilecektir.
  • Enterprise Java Beans (EJB) Bileşenlerinde İlgiye Dayalı Programlama
Tarihsel nedenlerden dolayı Java EE'de bir kaç tane bileşen modeli ile karşılaşıyoruz:
  i. EJB kabında çalışan ağır sıklet bileşen modeli: EJB
 ii. Web kabında çalışan hafif sıklet bileşen modeli: EJB Lite
iii. Hafif sıklet bileşen modeli: CDI
EJB, (Session Bean ve Message-Driven Bean) CDI ile karşılaştırıldığında ağır siklet bileşenler sunuyor. Bazı ilgiler EJB bileşenlerine dokunmuş olarak geliyor: Kalıcılık ve Hareket Yönetimi (Transaction Management), Yetkilendirme. CDI bileşeleri için yukarıda yazdığımız Interceptor sınıfını aynen EJB bileşenleri için de kullanabiliriz. CDI öncesinde ise Interceptor'ları hangi sınıf ve metodunda araya girmek için kullanacağımızı, @Interceptors notu ile tanımlıyorduk:
@Stateless
@LocalBean
@Interceptors({AuditInterceptor.class,ProfilingInterceptor.class})
public class JpaStockDao implements StockDao {

   @PersistenceContext(unitName="stockdbPU")
   private EntityManager em;
   
   .
   .
   .
 
}
ya da bunu metodlara da uygulayabiliriz:
@Stateless
@LocalBean
public class JpaStockDao implements StockDao {
   @PersistenceContext(unitName="stockdbPU")
   private EntityManager em;
 
   @Override
   @Interceptors(AuditInterceptor.class) 
   public Stock find(String symbol) {  
       return em.find(Stock.class, symbol);
   }

   @Override
   @Interceptors({AuditInterceptor.class,ProfilingInterceptor.class}) 
   public Collection<Stock> findAll() {
       List<Stock> stocks = 
            em.createNamedQuery("Stock.findAll",Stock.class)
       .getResultList();
       return stocks;
   }

   .
   .
   .

}
  • JAX-WS Web Servislerinde İlgiye Dayalı Programlama
Web servisleri dağıtık bir ortamda çeşitli sayıda uygulamanın ya da bileşenin organizasyon içinde ya da organizasyonlar arasında platformdan ve programlama dilinden bağımsız olarak saydam bir şekilde çalıştırılmasına olanak sağlar. Web servisleri üç teknoloji üzerine kuruludur: 
  • Web Services Description Language (WSDL): Web servisi arayüzünün XML tabanlı olarak tanımlanmasını sağlar 
  • Simple Object Access Protocol (SOAP) : Web servislerine erişimi sağlayan XML tabanlı mesajlaşma standardı
  •  Universal Description, Discovery and Integration (UDDI): Web servis kayıtlarının tutulmasını ve aranabilmesini sağlayar. Şirketler hakkında bir kategorizasyon ve katalog bilgisi sağlar. 
Web servislerinde üç farklı rolde düğüm bulunur (Şekil-1):
  • Tüketici
  • Servis Sağlayıcı
  • Servis Kayıt Sunucusu
Şekil-1 Web Servislerinde yer alan düğümler
Bu üç düğüm arasında haberleşmede SOAP standardı kullanılır. Java EE 7 platformunda SOAP Web Servisi gerçeklemek için JAX-WS 2.2'den yararlanıyoruz. JAX-WS'de SOAP mesajı geldiğinde işlenmeden önce, yanıt gönderilmeden önce ya da hata oluşmuş ise hata mesajı (SOAP Fault) gönderilmeden önce araya girip işler yapmak mümkündür. Bunun için Web Servisinin gerçeklemesini verdiğimiz sınıfa @HandlerChain() notunu düşüyoruz:
@WebService
@Stateless
@HandlerChain(file="handlers.xml")
public class ImdbWebService {

   @Inject
   private MovieService movieService;
 
   public Collection<String> findAllGenres(){  
      return movieService.findAllGenres();
   }
 
   public Collection<Movie> findAllMoviesByYearRange(int from,int to){
      return movieService.findAllMoviesByYearRange(from, to);
   }

}
Bu notta, araya girip işler yapacak sınıfları listelediğimiz XML dokümanının adını veriyoruz (file="handlers.xml"):
<?xml version="1.0" encoding="UTF-8"?>
<javaee:handler-chains xmlns:javaee="http://java.sun.com/xml/ns/javaee"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <javaee:handler-chain>
      <javaee:handler>
 <javaee:handler-name>LoggingHandler</javaee:handler-name>
 <javaee:handler-class>com.example.imdb.aop.LoggingHandler</javaee:handler-class>
      </javaee:handler>
   </javaee:handler-chain>
</javaee:handler-chains>
Bu dokümanın, web servisini sağlayan sınıf ile aynı pakette olması gerekir. XML dokümanında tanıtılan LoggingHandler sınıfının ise SOAPHandler arayüzünü gerçeklemesi gerekir:
package com.example.imdb.aop;

import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.ProtocolException;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

public class LoggingHandler implements SOAPHandler<SOAPMessageContext> {

 @Override
 public boolean handleMessage(SOAPMessageContext mc) {
  // mc.getMessage().getSOAPHeader();
  // mc.getMessage().getSOAPBody();
  return true;
 }

 @Override
 public boolean handleFault(SOAPMessageContext context) {
  return true;
 }

 @Override
 public void close(MessageContext context) {
 }

 @Override
 public Set<QName> getHeaders() {
  return Collections.emptySet();
 }

}
LoggingHandler sınıfında, SOAP mesajı geldiğinde ve yanıt dönüldüğünde handleMessage metodu çalışır. Bu metod parametre olarak SOAPMessageContext tipinden bir parametre alır. Bu parametreyi kullanarak SOAP mesajının başlığına (mc.getMessage().getSOAPHeader()), gövdeye (mc.getMessage().getSOAPBody()) ve diğer alanlarına okumak ya da değiştirmek amacıyla erişilebilir. Bu metod true dönerse, zincirde sıradaki sınıfın handleMessage metodu çalıştırılır ya da en nihayetinde servis çalıştırılır. Aksi halde işlem durdurulur ve giriş mesajı yanıt olarak dönülür.
  • JPA'da İlgiye Dayalı Programlama
Java EE platformunda kalıcılık probleminin çözümü için JDBC ya da JPA kullanıyoruz. JDBC alt düzey bir API olduğu için basit karmaşıklıktaki kalıcılık işlemleri için bile çok fazla tekrar kod yazmayı gerektirir. Üstelik bu kod veritabanı ile ilgili çok fazla detay barındırır, bu da kodun veritabanındaki değişikliklere karşı kırılgan olmasına neden olur. Bu kırılganlık azaltılmak istenirse daha da fazla kod yazmak gerekecektir. JPA ise bu problemi tabloları Java sınıflarına karşı düşürerek çözmeye çalışır. Karşı düşürülen sınıfa Entity adını veriyoruz. Veritabanındaki tablolar Java sınıflarına, bu tabloların sütunları sınıfın özniteliklerine ve tablolar arasındaki çeşitli türden ilişkileri (1-1, 1-N, M-1, M-N) Java sınıfları arasındaki ilişkilere karşı düşürüldüğünde, JPA'nın EntityManager sınıfını kullanarak tüm kalıcılık problemlerini çözebiliriz. Karşı düşürmeyi notları kullanarak Entity sınıfında gerçekliyoruz. İstenirse bu işlem bir XML dokümanı ile de sağlanabilir. Anahtar alana göre arama yapmak için find() metodunu, Entity nesnesini eklemek için persist() metodunu, Entity nesnesini güncellemek için merge() metodunu ve Entity nesnesini silmek için ise remove() metodunu kullanmamız yeterli olur. Tüm bu metodlar (persist, merge ve remove) parametre olarak eklenecek, güncellenecek ve silinecek Entity nesnesini alırlar. Örnek bir dönüşüm için bir alan ve aynı zamanda @Entity sınıfı olan Stock sınıfını kullanacağız:
package com.example.stockmarket.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.Min;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

@Entity
@Table(name = "stocks")
@NamedQueries({ 
   @NamedQuery(name = "Stock.findAll", query = "select s from Stock s") 
})
public class Stock extends BaseEntity implements Serializable {

   @Id
   @Pattern(regexp="^[a-zA-Z]{3,5}$",message="This is not a valid stock symbol!")
   private String symbol;

   @Size(min=8,message="Company name must be larger than or equal to 8!")
   @Column(name = "COMPANY_NAME")
   private String company;

   @Min(value=1,message="Price can not be smaller than 1!")
   private double price;
 
   public Stock() {
   }

   public Stock(String symbol, String company, double price) {
       this.symbol = symbol;
       this.company = company;
       this.price = price;
   }

   public String getSymbol() {
       return symbol;
   }

   public void setSymbol(String symbol) {
       this.symbol = symbol;
   }

   public String getCompany() {
       return company;
   }

   public void setCompany(String company) {
       this.company = company;
   }

   public double getPrice() {
       return price;
   }

   public void setPrice(double price) {
       this.price = price;
   }

}
Tüm Entity sınıflarını @Entity notunu düşüyoruz, ayrıca anahtar alanın hangi öz niteliğe karşı düştüğünü @Id notu ile tanımlamalıyız. Bu iki zorunlu not dışında, dönüşümde kullanılabilecek pek çok sayıda seçimlik not bulunuyor. Her Entity sınıfında hangi tarihte yaratıldığını ve güncellendiğini gösteren alanlar bulunması istenirse, bu alanları her Entity sınıfında tekrar etmek yerine bir alt sınıfta tanımlamayı ve tüm Entity sınıflarını bu alt sınıftan türetmeyi tercih ederiz:
package com.example.stockmarket.entity;

import java.util.Date;

import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@MappedSuperclass
public class BaseEntity {

   @Temporal(TemporalType.TIMESTAMP)
   private Date created;

   @Temporal(TemporalType.TIMESTAMP)
   private Date updated;

   public Date getCreated() {
       return created;
   }

   public void setCreated(Date created) {
       this.created = created;
   }

   public Date getUpdated() {
       return updated;
   }

   public void setUpdated(Date updated) {
       this.updated = updated;
   }

}
Şimdi, bir problemimiz var: Bu tarihleri kim dolduracak? Bu problemin çözümü için EntityListener'dan yararlanıyoruz. EntityListener'lar kayıt eklenmeden ya da kayıt güncellenmeden hemen önce araya girip, bu tür işleri yapacak kodları yazmamızı sağlar. Bu sınıflar sıradan Java sınıflarıdır. Tek özellikleri içinde yaşam döngüsü notları düşülmüş metodlar barındırmalarıdır:
  • @PrePersist
  • @PostPersist
  • @PreUpdate
  • @PostUpdate
  • @PreRemove
  • @PostRemove
Şimdi, bu tarihleri oluşturacak bir EntityListener yazalım:
package com.example.stockmarket.entity.listeners;

import java.util.Date;

import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;

import com.example.stockmarket.entity.BaseEntity;

public class AutoDateListener {

   @PrePersist
   public void initializeDates(BaseEntity entity) {
       Date now = new Date();
       entity.setCreated(now);
       entity.setUpdated(now);
   }

   @PreUpdate
   public void update(BaseEntity entity) {
       Date now = new Date();
       entity.setUpdated(now);
   }

}
Son olarak bu EntityListener'ın kullanmasını sağlayacak tanımlamayı Entity sınıfımıza (Stock.java) bir not (@EntityListeners) olarak ekleyelim:
@Entity
@Table(name = "stocks")
@NamedQueries({ 
   @NamedQuery(name = "Stock.findAll", query = "select s from Stock s") 
})
@EntityListeners(AutoDateListener.class)
public class Stock extends BaseEntity implements Serializable {

   .
   .
   .

}
Nota parametre olarak, içinde Entity yaşam döngüsü notları düşülmüş metodları bulunan sınıfı veriyoruz. EE 7 daha fazla bilgi edinmek isteyenler için Developing Enterprise Applications on Java EE 7 eğitimini tavsiye ederim.