Saturday, March 5, 2011

Làm thế nào để tự tạo một ClassLoader

Lập trình khía cạnh là gì?
Sự xuất hiện của ngôn ngữ hướng đối tượng là một cuộc cách mạng trong lĩnh vực phần mềm. Nhiều người đánh giá nếu không có phương pháp phân tích và thiết kế hướng đối tượng, ngành công nghiệp phần mềm sẽ rơi vào khủng hoảng bởi sự phức tạp ngày càng gia tăng trong các hệ thống phần mềm. May mắn thay điều đó đã không xảy ra. Sau hơn 30 năm thống trị kĩ nghệ phần mềm, hướng đối tượng có vẻ đã hụt hơi với những yêu cầu cao hơn của các ứng dụng thực tế. Trong bối cảnh đó, lập trình hướng khía cạnh (Aspect Oriented Programming gọi tắt là AOP) dường như là một cứu cánh, và là một sự bổ sung để duy trì sức mạnh của hướng đối tượng.

Vậy hướng khía cạnh là gì? Một ví dụ rất đơn giản sau đây sẽ giúp độc giả hình dung sơ nét về lập trình hướng khía cạnh. Giả sử ta có lớp Dog với phương thức bark() – sủa – cho in ra dòng chữ gâu gâu gâu hoặc phức tạp hơn vừa in ra  vừa trả về câu này.

public class Dog implements Animal {

      public String bark(){
            System.out.println("gau gau gau");
            return "gau gau gau";
      }
}

Giả sử nếu ta muốn kiểm tra (authorize) xem người dùng hiện hành có được phép thực thi phương thức này không ta cần phải:

public String bark(){

            if(currentUser.isAuthorized())
{
            System.out.println("gau gau gau");
            return "gau gau gau";
}
else
      throws “Invalid Access Exception”  
      }

Rõ ràng, việc kiểm tra quyền không nằm chung logic với bark(). Việc nhúng đoạn code này vào trong bark khiến cho chương trình rối rắm và khó bảo trì sau này. Cách tốt nhất để làm chuyện này là cho phép một cơ chế Đánh chặn (Interception): trước khi phương thức này được gọi việc kiểm quyền của người dùng sẽ được thực thi . Đoạn mã kiểm sẽ được đặt ở đâu đó trong chương trình, tách riêng (decouple) ra khỏi bark().

Java 1.3 cho phép làm được điều này thông qua DynamicProxy. Trên thực tế Spring AOP sử dụng phương pháp này cònAspectJ và JBOSS AOP sử dụng việc pha trộn lớp (class-weaving).

Tự hiện thực 1 AOP framework
Sau đây ta sẽ tự viết code để làm AOP mà không nhờ vào bất cứ một thư viện thứ ba nào (third-party libraries).

Trước tiên ta có 1 lớp tên là MyProxyFactory, một nhà máy chuyên sản xuất các proxy. Lớp này có 1 phương thức duy nhất là createProxy(). Để tạo được một proxy ta cần có các nguyên liệu đầu vào:
1) tên lớp cần làm proxy,
2) tên lớp sẽ dùng để thêm vào các tính năng bổ sung (decorate)
3) targetObject: là đối tượng khi được tạo bằng proxy sẽ đứng ra gọi các phương thức.

public class MyProxyFactory {

      public T createProxy(Class clazz, final T targetObject,
                  final MyDecorateClass decorate) {
            InvocationHandler invocationHandler = new InvocationHandler() {

                  public Object invoke(Object proxy, Method method, Object[] args)
                              throws Throwable {

                        decorate.before();
                        Object object = method.invoke(targetObject, args);
                        decorate.after();
                        return object;
                  }
            };

            Proxy proxy = (Proxy) Proxy.newProxyInstance(Thread.currentThread()
                        .getContextClassLoader(), new Class[] { clazz },
                        invocationHandler);

            return (T) proxy;
      }


Để tạo một proxy rất đơn giản ta chỉ cần gọi Proxy.newProxyInstance() với các thông số như sau:
-         ClassLoader hiện tại
-         Một mảng các lớp cần làm proxy
-         Và cuối cùng là invocationHandler. Bộ xử lý gọi hàm (invocation handler) làm nhiệm vụ ráp nối phương thức chính (của lớp cần làm proxy) với các phương thức đánh chặn (từ lớp trang trí)

Lớp trang trí chỉ đơn giản là in ra dòng chữ trước và sau khi phương thức chính được thực thi :

public class MyDecorateClass {

      void before()
      {
            System.out.println("I invoke you BEFORE actual doing your task");
      }
      void after()
      {
            System.out.println("I invoke you AFTER actual doing your task");
      }
}


Cài đặt một hàm main để test :
      public static void main(String[] args)
      {          
            MyProxyFactory myProxy = new MyProxyFactory();
            MyDecorateClass myDecorate = new MyDecorateClass();
           
            Animal proxiedDog = myProxy.createProxy(Animal.class, new Dog(), myDecorate);
            String words = proxiedDog.bark();  
           
            System.out.println(words);               
      }

Ta được kết quả như sau :

I invoke you BEFORE actual doing your task
gau gau gau
I invoke you AFTER actual doing your task
gau gau gau

Có nghĩa là TRƯỚC và SAU khi hàm bark() được thực thi, hai phương thức before và after của MyDecorateClass đã được gọi.

Qua ví dụ trên ta thấy rõ ràng, những quảng cáo về sức mạnh của Spring AOP hay AspectJ cũng khá đơn giản và không có một chút gì kỳ bí. Nó hoàn toàn có thể tự được thực hiện bởi những lập trình viên bình thường.


1. ClassLoader là gì?

ClassLoader như tên gọi của nó là một thành phần của Máy Ảo Java được dùng để load các class java vào trong máy ảo để thực thi. Với cách hiểu như vậy, ta hoàn toàn có thể dự đoán cách thức mà ClassLoader hoạt động.

Để chạy 1 ứng dụng Java ta cần có các thư viện của Java là những file jar chứa trong jre/lib. Như vậy những Class chứa trong các thư viện này phải được nạp vào trước thông qua Bootstrap ClassLoader. Sau đó AppClassLoader sẽ nạp các Class của bản thân ứng dụng và các lớp chứa trong những gói jar mà ứng dụng cần đến ví dụ như logging.

Việc nạp một Class sẽ do ClassLoader đảm trách thông qua phương thức loadClass. Do vậy để thực hiện một ClassLoader ta chỉ cần mở rộng lớp này.

Trình tự thực thi của phương thức loadClass như sau:
- Tìm đến thư mục chứa file .class của class cần nạp
- Mở và đọc file trả về nội dung file .class dưới dạng byte[]
- Trả về Class đã nạp bằng cách gọi phương thức defineClass.

*Trước khi nạp một class cần kiểm tra xem class đã được nạp chưa? Nếu class đã được nạp thì hàm loadClass chỉ cần trả về class đã nạp
**Nếu tên class bắt đầu bằng java nghĩa là đây là một class hệ thống, chỉ cần dùng phương thức findSystemClass để trả về class.

2. Ví dụ MyClassLoader
Giả sử ta có một class tên là FooClass. Khi được nạp class này sẽ in ra màn hình dòng chữ Loaded, sau khi được khởi tạo, nó sẽ in ra dòng chữ Created.

public class FooClass {
        static {
          System.out.println ("Loaded");
        }
        public FooClass () {
          System.out.println ("Created");
        }
       
      }

Còn đây là lớpCustomClassLoader mở rộng lớp ClassLoader của Java.

public class CustomClassLoader extends ClassLoader {

      private Hashtable classes = new Hashtable();

      public CustomClassLoader() {
            super(CustomClassLoader.class.getClassLoader());
      }

      public Class loadClass(String className) throws ClassNotFoundException {
            return findClass(className);
      }

      public Class findClass(String className) {
            byte classByte[];

            className = className.trim();
            Class result = null;
            result = (Class) classes.get(className);
            if (result != null) {
                  return result;
            }
            if (className.startsWith("java")) {
                  try {
                        return findSystemClass(className);
                  } catch (Exception e) {
                  }
            }

            try {
                  String classPath = "D:\\java extreme\\classloader\\MyClassLoader\\bin\\";
                  String fileName = className.replace('.', File.separatorChar) + ".class";
                  String fullPathToFile = classPath + fileName;
                  classByte = loadClassData(fullPathToFile);
                  result = defineClass(className, classByte, 0, classByte.length,   null);

                  classes.put(className,result);
                 
                  return result;

            } catch (Exception e) {
                  return null;
            }
      }

      private byte[] loadClassData(String className) throws IOException {
            File f;
            f = new File(className);
            int size = (int) f.length();
            byte buff[] = new byte[size];
            FileInputStream fis = new FileInputStream(f);
            DataInputStream dis = new DataInputStream(fis);
            dis.readFully(buff);
            dis.close();
            return buff;
      }
}

Cài đặt một hàm main để test lớp này:

    public static void main(String [] args) throws Exception{
       CustomClassLoader customClassLoader = new CustomClassLoader();
       Class c = customClassLoader.loadClass("_examples.FooClass_");
      
       Object fc = c.newInstance();
      
       FooClass fc2 = new FooClass();
           
       System.out.println(fc.getClass().getClassLoader());
      
       System.out.println(fc2.getClass().getClassLoader());      
             
    }
Ta được kết quả như sau:
Loaded
Created
Loaded
Created
examples.CustomClassLoader@addbf1
sun.misc.Launcher$AppClassLoader@11b86e7

Chứng tỏ fc được nạp bởi CustomClassLoader

Download ví dụ này ở đây MyClassLoader.zip - 5.9 KB

1 comment: