Chia sẻ kỹ thuật reflection

1. Mở đầu :
Hiện tại như mình thấy thì có rất ít sách cũng như ebook đề cập đến kỹ thuật reflection trong programming, và cũng rất ít bạn biết về khái niệm này, trong khi hầu hết các framework lớn đều sử dụng. Không biết tới kỹ thuật này thì quả là một thiệt thòi không hề nhỏ :smile:. Hôm nay mình sẽ chia sẻ một ít kiến thức về reflection trong programming, hy vọng có ích cho các bạn. ( lưu ý với các ngôn ngữ script, context có thể khác đi ).

2. Khái niệm
Đã bao giờ bạn tự thắc mắc làm sao các framework MVC có thể mapping url request vào chính xác từng controller, hay làm sao spring lại có thể khởi tạo bean từ xml configuration or annotation, injection các giá trị vào bean? Tất cả là nhờ đến kỹ thuật reflection.Vậy reflection là gì?

Reflection là một quy tắc cho phép bạn có thể quan sát (observing) và thay đổi chương trình lúc runtime (wiki) . Hiểu nôm na có nghĩa là reflection cho phép bạn gán/modify mã code trong thời gian chạy, hay nói cách khác, nó cho phép chúng ta làm việc với “meta data” của một object.

Dựa vào reflection, chúng ta có thể kiểm tra xem một class A đã được định nghĩa chưa, Một method B thì có những annotation nào, call method của một object chưa biết tên lúc runtime mà không cần phải biết tên method đó lúc compile…

3. Example
Để hiểu rõ hơn về reflection thì chúng ta sẽ thử làm một ví dụ về reflection. À quên mất ngôn ngữ là java nhé các bạn :smile: ( .NET cũng tương tự như vậy nhé)

Bài toán : Giả sử bạn có 1 chương trình để thực hiện update dữ liệu vào CSDL. Vì mỗi CSDL khác nhau nên chúng có cơ chế làm việc khác nhau. Giả sử chúng ta có class MysqlJdbcDriver để thao tác với CSDL Mysql, MssqlJdbcDriver để thao tác với CSDL MsSql. 2 class này đều implement interface JdbcDriver, có 1 method là executeSql. Tùy vào config, chương trình sẽ tự động instance driver tương ứng để kết nối CSDL.

Interface JdbcDriver

public interface JdbcDriver {
     void executeSql(String sql);
}

Class MysqlJdbcDriver

public class MysqlJdbcDriver implements JdbcDriver {

    public void executeSql(String sql) {
        System.out.println("Run on Mysql");
        System.out.println("Execute sql : " + sql);
    }
} 

Class MssqlJdbcDriver

public class MssqlJdbcDriver implements JdbcDriver {

    public void executeSql(String sql) {
        System.out.println("Run on Mssql");
        System.out.println("Execute sql : " + sql);
    }
 } 

Class Program

public class Program {
    /** Chú ý : chuỗi chứa class driver trong ví dụ này mình đang để hard code.
        Thực tế thì nó có thể được load từ xml configuration, Annotation value,environment variable database or anywhere
    */
    private String driverConfig = "MssqlJdbcDriver";

    JdbcDriver jdbc;

    public Program() throws Exception {
        Class<?> driverClass = Class.forName(driverConfig);
         // create object by name
        this.jdbc = (JdbcDriver) driverClass.getConstructor().newInstance();
    }

    public void doUpdateData() {
        this.jdbc.executeSql("update a set b = 'c'");
    }

    public static void main(String[] args) throws Exception {

        Program p = new Program();
        p.doUpdateData();
    }
}

Chạy chương trình lên bạn sẽ thấy kết quả :smile:

Run on Mysql
Execute sql : update a set b = ‘c’

Thay đổi giá trị của driverConfig thành MssqlJdbcDriver, kết quả

Run on Mssql
Execute sql : update a set b = ‘c’

Bạn thấy đấy, nếu có thay đổi CSDL, thì class Program cũng không phải thay đổi gì. Chúng ta đã injection giá trị cho variable jdbc :smile:

Extra : Trong MVC, thì các url thường được mapping với các controller cụ thể. Các bạn cũng có thể dùng reflection để gửi request đến đúng Controller. Kiến trúc cũng rất đơn giản :

  1. Bạn có 1 Servlet sẽ handle toàn bộ request : url-pattern = * (web.xml)
  2. Trong Servlet này, dựa vào url request, bạn có thể xác định được url request tương ứng với class controller nào? tương ứng với method nào trong controller đó. giả sử request là /abc/def thì class controller sẽ là acb, method là def ( or bạn có thể tự đặt ra rule cho riêng mình :smile: )
  3. Khởi tạo đối tượng controller và call method tương ứng bằng reflection.
  4. Nhận kết quả trả về từ step 3, response về cho người dùng.

4. Kết luận :
Để hiểu rõ hơn thì các bạn cần phải thực hành nhiều hơn và gặp những bài toán cụ thể. Vì phần này đa số nằm ở các framework nên cũng ít va chạm. Hy vọng topic có ích cho các bạn. Mình chỉ chia sẻ những kiến thức mình biết. Nếu có gì sai mong các bạn góp ý. Thank all.

12 Likes

một kĩ thuật quan trọng và đáng nhớ, nhưng chủ yếu để đọc hiểu code, tự làm cũng mệt, cái gì dính chữ meta vào là thấy ngán :slight_smile:

mình có xem qua mấy bài trên mạng nói về reflection nó nói là : Reflection là kĩ thuật rất cần thiết để lấy các thông tin của một kiểu dữ liệu. Dựa vào đó ta có thể kích hoạt (như các phương thức) hoặc tạo thể hiện của kiểu dữ liệu đó
Vậy là nó dựa vào method forName() để lấy thông tin của 1 class nào đó thông qua tên class đó phải không

như trong cái vd này:
nếu đã biết được tên chính xác của đối tượng này rồi sao còn phải dùng cái Field đó để set giá trị cho field nữa sao không dùng foo.set() cho nhanh ???

public class Foo {
 
    public int myField=0;
 
    public static void main(String args[])
    {
        try {
            Foo foo=new Foo();
            System.out.println("Default value: "+foo.myField);
            Class c = Class.forName("Foo");
 
            Field f=c.getField("myField");
            f.set(foo, 100);
            System.out.println("After changing: " +foo.myField);
        }
        catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

Đối với đoạn code trên, sửa thế này thì nó có ý nghĩa này:

import java.reflect.Method;
public class Foo {
 
    public static int myField=0;
    public static void Say() {
        System.out.println("Say foo with myField: " + myField);
    }
 
    public static void main(String args[])
    {
        try {
            Class c = Class.forName("Foo");

            // Thay doi gia tri bien static
            Field f=c.getField("myField");
            f.set(c.newInstance(), 100); // c.newInstance() se tra ve mot Object dai dien cho class Foo
            
            // Goi phuong thuc static
           Method func = c.getMethod("Say",null);
           func.invoke(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    } 
}

Như vậy trong code hàm main không cần biết đến sự hiện diện của class Foo mà vẫn có thể thao tác các thành phần của nó, áp dụng được với cả thành phần không phải là static hoặc có đối số truyền vào

Đây là một ví dụ không thực sự làm cho việc sử dụng reflection có ý nghĩa.
Bạn hãy hình dung 1 bài toán như sau :
Trong mô hình web MVC, thường thì khi request lên 1 url, request này sẽ được forward (chính xác là forward) tới 1 controller cụ thể. Thường quy tắc sẽ như sau, VD
/Product/Edit/1
=> Khi đó Product nghĩa là Product controller, Edit là action và 1 là parameter.
Khi request tới URL này, thì cụ thể là method edit của class ProductController được call.
Vậy câu hỏi là làm sao các engine của các web MVC có thể gọi được method này dựa vào url??? Bạn không thể biết trước người dùng request tới url nào để khởi tạo các controller này cả. (Cũng không thể dùng switch case để làm vì làm sao mà biết được người ta sẽ define những controller nào? )
Câu trả lời là họ dùng reflection. Đầu tiên các engine sẽ parse url để xem url đó request đến/ mapping đến controller nào (qua 1 số tập quy luật như RouterConfig của ASP.NET or antonation của JAVA).
Như ví dụ ở trên thì mình sẽ lấy được TÊN của controller là “Product”. Từ tên controller này, dùng refecltion thì có thể khởi tạo được class ProductController, tương tự như vậy để gọi method Edit.
Trước khi parse url, bản thân engine hoàn toàn không biết controller được request tới là j cả, nên không thể if else, switch case được…
Đây chỉ là 1 ví dụ rất đơn giản ( thực tế nó phức tạp hơn nhiều ). Hoặc bạn cũng có thể tham khảo các library ORM (database), nó cũng sử dụng reflection để mapping column, table với các properties, class.
Nói tóm lại, trước mắt bạn cứ hiểu reflection là làm việc với META Data của 1 object. Dữ liệu để mô tả 1 dữ liệu khác thì người ta gọi là meta data.

1 Like

Em muốn sử dụng Reflection để đọc các chức năng hiển thị của mỗi lớp con mở rộng lớp Demo.Em muốn clazz chỉ khởi tạo một lần kiểu singletons,nhưng em không biết làm thế nào để thực hiện, xin vui lòng giúp em với: D

   public class Demo{
    public abstract void display();
    }  

    public static void getDisplay(Class<? extends Demo> clazz) throws 
                       InstantiationException, IllegalAccessException {
             //Ý em là kiểu if(clazz==null)?class.newInstance:return clazz
            return clazz.newInstance().display();
    }
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?