Wednesday, July 3, 2013

Java 7'de Sınıf Yükleyici Yazmak

Java 7, programlama dili seviyesinde, geliştiricinin daha verimli kod yazabilmesini sağlayan  basit bir kaç yenilik getirdi: 
  • Otomatik Kaynak Yönetimi 
  • catch içinde | sembolü ile ayırarak birden fazla hata sınıfı tanımlayabiliyoruz
  • switch parametresi olarak tam sayı tipler (byte, short, int, long), char, enum tipler dışında [nihayet] String sınıfını kullanabiliyoruz
Java sanal makinası (JSM) seviyesinde uzun bir süreden sonra yeni bir bytecode komutu geldi: invokedynamic. Henüz Java 7'de derlendiğinde invokedynamic kodu ürettirecek herhangi bir Java ifadesi bulunmuyor. Java 8'de ise fonksiyonel programlama ve arayüz metot eklentileri (=Method Extensions) içeren kodlar derlendiklerinde invokedynamic kodu içerebilecek. JSM seviyesinde, Java 7 ile gelen yeniliklerin bir kısmı, çok çekirdekli sistemlerde başarımı iyileştirmeyi amaçlıyor. Örneğin, sınıf yükleyicileri artık paralel çalışabiliyorlar. Java'da kendi sınıf yükleyicimizi yazabiliriz. Bunun için ClassLoader sınıfından yeni bir sınıf türetmemiz gerekiyor. Java 7'de yükleyicinin paralel çalışmaya uygun olduğunu belirtmek üzere registerAsParallelCapable() metodunu çağırıyoruz. Aşağıdaki örnekte class dosyalarını veritabanında arayan bir sınıf yükleyicisi yer alıyor:    
1:  public class JdbcClassLoader extends ClassLoader {  
2:    
3:    public JdbcClassLoader() {  
4:      boolean isOk = registerAsParallelCapable();  
5:      if (isOk) {  
6:        System.err.println("JdbcClassLoader is registered as parallel capable!");  
7:      } else {  
8:        System.err.println("JdbcClassLoader can not be registered as parallel capable!");  
9:      }  
10:    }  
11:    
12:    @Override  
13:    protected Class<?> findClass(String name) throws ClassNotFoundException {  
14:      Class cls;  
15:      EntityManagerFactory emf= Persistence.createEntityManagerFactory("classdbPU");  
16:      EntityManager em= emf.createEntityManager();  
17:      ClassEntity ce= em.createNamedQuery("ClassEntity.findByClassname", ClassEntity.class)  
18:          .setParameter("classname", name)  
19:          .getSingleResult();  
20:      byte[] bytes= ce.getClassBytes();  
21:      cls= defineClass(name, bytes, 0, bytes.length);  
22:      em.close();  
23:      emf.close();  
24:      return cls;  
25:    }  
26:  }  
27:    
Bunun için MySQL veritabanında önce bir veritabanı ve tablo yaratıyoruz:
mysql> create database classdb;
Query OK, 1 row affected (0.05 sec)
mysql> create table classes (
    -> id int(11) not null auto_increment,
    -> classname varchar(256) not null unique,
    -> class longblob not null,
    -> primary key(id)) engine=innodb;
Query OK, 0 rows affected (0.11 sec)
Aşağıdaki Circle sınıfını derleyip, class dosyasını yukarıdaki classes tablosuna ekliyoruz. Bu veriye test kodunu yazarken ihtiyacımız olacak.
1:  public class Circle {  
2:    private double x,y;  
3:    private double radius;  
4:    
5:    public Circle() {  
6:      radius= 1.0;  
7:    }  
8:    
9:    public Circle(double x, double y, double radius) {  
10:      this.x = x;  
11:      this.y = y;  
12:      this.radius = radius;  
13:    }  
14:    
15:    public double getX() {  
16:      return x;  
17:    }  
18:    
19:    public double getY() {  
20:      return y;  
21:    }  
22:    
23:    public double getRadius() {  
24:      return radius;  
25:    }  
26:    public double area(){  
27:      return Math.PI*radius*radius;  
28:    }    
29:  }  
Circle.class dosyasını ise aşağıdaki şekilde classes tablosuna atıyoruz: 
mysql> insert into classes values(NULL,'com.example.shapes.Circle','');
Query OK, 1 row affected (0.08 sec)

mysql> update classes set class=load_file('c:/temp/Circle.class');
Query OK, 1 row affected (0.11 sec)
Rows matched: 1  Changed: 1  Warnings: 0
Veritabanına erişim için JPA 2 kullanacağız. Bunun için projeye önce persistence unit ve daha sonra ise Entity sınıfını ekliyoruz:
1:  package com.example.entity;  
2:    
3:  import java.io.Serializable;  
4:  import javax.persistence.Basic;  
5:  import javax.persistence.Column;  
6:  import javax.persistence.Entity;  
7:  import javax.persistence.GeneratedValue;  
8:  import javax.persistence.GenerationType;  
9:  import javax.persistence.Id;  
10:  import javax.persistence.Lob;  
11:  import javax.persistence.NamedQueries;  
12:  import javax.persistence.NamedQuery;  
13:  import javax.persistence.Table;  
14:    
15:  /**  
16:   *  
17:   * @author Binnur Kurt  
18:   */  
19:  @Entity  
20:  @Table(name = "classes", catalog = "classdb", schema = "")  
21:  @NamedQueries({  
22:    @NamedQuery(name = "ClassEntity.findAll", query = "SELECT c FROM ClassEntity c"),  
23:    @NamedQuery(name = "ClassEntity.findById", query = "SELECT c FROM ClassEntity c WHERE c.id = :id"),  
24:    @NamedQuery(name = "ClassEntity.findByClassname", query = "SELECT c FROM ClassEntity c WHERE c.classname = :classname")})  
25:  public class ClassEntity implements Serializable {  
26:    private static final long serialVersionUID = 1L;  
27:    @Id  
28:    @GeneratedValue(strategy = GenerationType.IDENTITY)  
29:    @Basic(optional = false)  
30:    @Column(name = "id")  
31:    private Integer id;  
32:    @Basic(optional = false)  
33:    @Column(name = "classname")  
34:    private String classname;  
35:    @Basic(optional = false)  
36:    @Lob  
37:    @Column(name = "class")  
38:    private byte[] classBytes;  
39:    
40:    public ClassEntity() {  
41:    }  
42:    
43:    public ClassEntity(Integer id) {  
44:      this.id = id;  
45:    }  
46:    
47:    public ClassEntity(Integer id, String classname, byte[] classBytes) {  
48:      this.id = id;  
49:      this.classname = classname;  
50:      this.classBytes = classBytes;  
51:    }  
52:    
53:    public Integer getId() {  
54:      return id;  
55:    }  
56:    
57:    public void setId(Integer id) {  
58:      this.id = id;  
59:    }  
60:    
61:    public String getClassname() {  
62:      return classname;  
63:    }  
64:    
65:    public void setClassname(String classname) {  
66:      this.classname = classname;  
67:    }  
68:    
69:    public byte[] getClassBytes() {  
70:      return classBytes;  
71:    }  
72:    
73:    public void setClassBytes(byte[] classBytes) {  
74:      this.classBytes = classBytes;  
75:    }  
76:    
77:    @Override  
78:    public int hashCode() {  
79:      int hash = 0;  
80:      hash += (id != null ? id.hashCode() : 0);  
81:      return hash;  
82:    }  
83:    
84:    @Override  
85:    public boolean equals(Object object) {  
86:      // TODO: Warning - this method won't work in the case the id fields are not set  
87:      if (!(object instanceof ClassEntity)) {  
88:        return false;  
89:      }  
90:      ClassEntity other = (ClassEntity) object;  
91:      if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {  
92:        return false;  
93:      }  
94:      return true;  
95:    }  
96:    
97:    @Override  
98:    public String toString() {  
99:      return "com.example.entity.ClassEntity[ id=" + id + " ]";  
100:    }    
101:  }  
Test kodunda ise Java 7 ile gelen Reflection API'ye göre daha yüksek başarımlı olarak çalışan MethodHandle sınıfını kullanacağız:
1:  package org.godel.test;  
2:    
3:  import java.lang.invoke.MethodHandle;  
4:  import java.lang.invoke.MethodHandles;  
5:  import java.lang.invoke.MethodType;  
6:  import org.godel.loader.JdbcClassLoader;  
7:    
8:  /**  
9:   *  
10:   * @author Binnur Kurt  
11:   */  
12:  public class TestJdbcLoader {  
13:    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, Throwable {  
14:      JdbcClassLoader jcl= new JdbcClassLoader();  
15:      Class cls= jcl.loadClass("com.example.shapes.Circle");  
16:      MethodHandles.Lookup lk= MethodHandles.lookup();  
17:      MethodType mt= MethodType.methodType(double.class);   
18:      MethodHandle mh= lk.findVirtual(cls, "area", mt);  
19:      MethodType mtCons= MethodType.methodType(void.class,double.class,double.class,double.class);  
20:      MethodHandle mhCons= lk.findConstructor(cls, mtCons);  
21:      Object circle= mhCons.invoke(1.0,1.0,100.0);  
22:      double area= (double) mh.invoke(circle);  
23:      System.err.println("Area: "+area);  
24:      System.err.println(cls.getName());  
25:    }  
26:  }  
27:    
Uygulama çalıştırıldığında aşağıdaki gibi bir ekran görüntüsü oluşmaktadır: 
JdbcClassLoader is registered as parallel capable!
Area: 31415.926535897932
com.example.shapes.Circle

No comments:

Post a Comment