- 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 Controller) javax.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 { }
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;
} }
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();
} }
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;
} }
- 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
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; . . . }
@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 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); } }
<?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>
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(); } }
- 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; } }
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;
} }
- @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);
} }
@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 { . . . }
No comments:
Post a Comment