# JavaSec101 - P1: Java Reflection
Trước khi bắt đầu bài viết thì đây là chuỗi bài mình tìm hiểu về Java do có sự tò mò nhiều chút và bài viết chỉ là mình đi đọc, đọc đến đâu dịch lại theo ý hiểu của bản thân đến đó
>Bài viết Không mang tính chất flexin trên circle k nên nếu có sai sót về kiến thức mong mọi người góp ý để mình sửa đổi
## 1. Java Reflection là gì?
Java Reflection là một tính năng trong Java, Java Reflection cho phép truy cập các thông tin của đối tượng (tên class, các field, các method) và chỉnh sửa các field của đối tượng **(kể cả các field private)** trong quá trình runtime.
## 2. Cơ chế hoạt động của Reflection
Trong một chương trình Java, sau khi load class, một đối tượng của class sẽ được tạo ra trong bộ nhớ heap, và một class sẽ chỉ có duy nhất một đối tượng trong heap. Đối tượng này sẽ chứa toàn bộ cấu trúc của class. Và chúng ta có thể có được toàn bộ thông tin của class thông qua nó.
Đối tượng được khởi tạo cho class giống như một tấm gương phản chiếu của class, thông qua nó chúng ta có thể thấy toàn bộ cấu trúc của class. Vậy nên cơ chế này mới có tên là Reflection (phản chiếu)

Đối tượng nhắc tới ở đây là một đối tượng đặc biệt được tạo ra bởi một class đặc biệt là **java.lang.Class** mà mình sẽ viết riêng ở dưới.
## 3. Ưu và nhược điểm của Java Reflection
**Ưu điểm:** Các đối tượng có thể được tạo ra và sử dụng một cách dynamic (Mình cũng chưa nghĩ ra cách dịch từ này một cách dễ hiểu), và các phương thức, thuộc tính của cơ chế Reflection rất dễ sử dụng và linh hoạt.
**Nhược điểm:** Theo mình đọc thì nó sẽ làm quá trình thực thi chậm đi và cũng gây ảnh hưởng mất an toàn đến luồng của chương trình.
## 4. Tổng quan về class loading
Trước khi đi sâu vào cơ chế hoạt động của reflection, hãy cùng mình tìm hiêu cách mà một class được load.
Có 2 kiểu load là:
* **Static loading:** Class sẽ được load trong quá trình biên dịch (compile time) và nếu class không tồn tại trong chương trình thì compliler sẽ báo lỗi.
* **Dynamic loading:** Class sẽ được load trong runtime kể cả khi nó không tồn tại trong chương trình, và class có được sử dụng ở runtime hay không thì cũng không có lỗi
Ví dụ:
```java
public class Classload{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int key = sc.nextInt();
switch(key){
case 0:
Cat cat = new Cat();
break;
case 1:
// Giả sử tạo ra một đối tượng Dog bằng cơ chế reflection
break;
}
}
}
```
Từ đoạn code trên, cat object có được khởi tạo hay không phụ thuộc vào **key**. Chúng ta sẽ hiểu như thế đúng không?
Nhưng khi complile, complier sẽ tìm xem trong project, chương trình đã viết Class chưa bất kể key có giá trị bao nhiêu, nếu chưa có thì nó sẽ báo lỗi
Compiler sẽ không kiểm tra xem class Dog đã tồn tại trước đó chưa nếu tạo ra đối tượng Dog bằng Reflection
> **Khi nào thì class được load?**
> **static loading:**
> * Khi mà một đối tượng mới được khởi tạo class sẽ được load
> * Khi một thành phần static thuộc class được gọi.
> * Khi class con kế thừa class cha được load thì class cha cũng sẽ được load
>
> **Dynamic loading**
> * Đối với reflection, class nào dùng trong runtime thì sẽ được load
#### 4.1 Sơ đồ quá trình load class

> 1. Giai đoạn load: Class file của class sẽ được load vào memory mà một đối tượng java.lang.Class sẽ được tạo cho nó
> 2. Giai đoạn join: Được chia thành 3 giai đoạn nhỏ: validation, preparation, và parsing. Ở giai đoạn này binary data của class sẽ được hợp nhất vào JRE.
> 3. Giai đoạn khởi tạo: JVM sẽ khởi tạo những **static members** của class
##### 4.2 Giai đoạn load:
Ở giai đoạn này , JVM sẽ convert bytecode từ nhiều nguồn khác nhau (có thể là class, jar package,...) vào một **stream of binary bytes** (dòng dữ liệu của các byte nhị phân) rồi sau đó load vào trong memory và tạo ra một **object java.lang.Class** đại diện cho class được load.
##### 4.3 Giai đoạn kết nối:
###### 4.3.1 Verification
* Mục đính chính là đảm bảo thông tin chứa trong byte stream của class đáp ứng đủ yêu cầu của JVM và không gây nguy hiểm về bảo mật theo đánh giá của JVM
* Nó bao gồm 3 bước:
* File format verification (file phải bắt đầu bằng các byte **0xcafebabe**)
* Metadata verification
* Bytecode verification
* Symbol reference verification
* Có thể tắt bước verify bằng cách chạy với tham số **-Xverify:none**
###### 4.3.2 Preparation
Ở đoạn này thì JVM sẽ phân bổ bộ nhớ cho các biến static và khởi tạo chúng với giá trị khởi tạo(Các kiểu dữ liệu khác nhau thì có các giá trị khởi tạo mặc định khác nhau, ví dụ như int - 0, boolean - false). Không gian bộ nhớ của các biến này nằm ở **method area**
Ví dụ đi cho dễ hiểu:
```java
public class Main {
public static int n2 = 20;
public int n1 = 0;
public static final int n3 = 10;
public static void main(String[] args) {
System.out.println(n2);
}
}
```
> n1 là thuộc tính instance, không phải static nên nó sẽ không được cấp phát bộ nhớ trong giai đoạn preparation
> n2 là một biến static nên JVM sẽ cấp phát bộ nhớ và khởi tạo nó với giá trị mặc định là = 0 chứ không phải = 20
> n3 là một biến static final nên nó sẽ không bao giờ thay đổi sau khi khởi tạo nên JVM sẽ cấp phát bộ nhớ là khởi tạo nó với giá tri là 10 luôn.

###### 4.3.3 Resolution
Trong giai đoạn này, các tham chiếu tới các class, phương thức, và biến tĩnh được giải quyết để trỏ đến các đối tượng thực sự trong bộ nhớ
##### 4.2 Giai đoạn khởi tạo:
> 1. JVM không thực thi code trong giai đoạn này. Mà nó sẽ thực thi phương thức <cinit>()
> 2. <cinit>() là phương thức mà compiler đi tìm những phép gán của tất cả các **biến static** trong class và trong **code blocks** theo thứ tự từ trên xuống và gán giá trị cho chúng.
> 3. JVM đảm bảo rằng phương thức <cinit>() của từng class là thuộc tính khóa và đồng bộ hóa trong môi trường đa luồng.
Nếu có nhiều class cùng lúc khởi tạo một class thì chỉ có một thread chạy phương thức <cinit>() của class đó còn tất cả thread còn lại sẽ phải chờ cho đến lượt.
Ví dụ:
```java=
public class Main {
public static void main(String[] args) {
System.out.println(B.num);
}
}
class B{
static {
System.out.println("Khối static của B được load");
num = 300;
}
static int num =100;
public B(){
System.out.println("B() Constructer cua B duoc goi");
}
}
```
Giải thích:
1. Giai đoạn loading: Load class B và tạo một đối tượng class của B
2. Giai đoạn connection: khởi tạo giá trị của biến num = 0 theo mặc định của kiểu int
3. Giai đoạn khởi tạo: Chạy phương thức **<cinit>()**
```java=
cinit(){
System.out.println("Khối static của B được load");
num=300;
num=100;
}
```

## 5. Class class
1. **Class** cũng là một class
2. Chúng ta không thể tự khởi tạo đối tượng của class Class (với từ khóa new) nó chỉ được khởi tạo bởi JVM mỗi khi có class được load
3. Chỉ có một đối tượng của Class được tạo cho mỗi class được load như đã nói ở trên.
4. Các instance object của các class ánh xạ được Class object tương ứng
5. Class object được tạo ra cho class load vào có chứa toàn bộ thông tin của class đó, chúng ta có thể lấy ra thông tin bằng các method, Object của **Class**
6. Bytecode của class được lưu trong **method area** cũng có thể được gọi (bao gồm method code, tên biến, tên phương thức, quyền truy cập các phương thức, thuộc tính như là private hay public,.. )
> Chú ý điều 5 và 6 nhé moại người
Ngoài kiểu **int** ra thì tất cả các kiểu khác trong java đều là các **class** (bao gồm cả các interface)
### Làm sao để lấy được instance của class? Có 5 cách
1. Sử dụng các biến static **class** trong class
```java
Class cls = String.class; // class là một biến static của class String
```
2. Nếu có object của một class thì có thể sử dụng method **getClass()**
```java
String s = "Hello";
Class cls = s.getClass();
```
3. Nếu biết toàn bộ tên của class thì có thể sử dụng **Class.forName()**
```java
Class cls = Class.forName("java.lang.String");
```
4. Đối với các kiểu dữ liệu cơ bản (int, double,char,...) thêm **.class** ở sau
```java
Class intergerClass = int.class;
```
5. Đối với lớp đóng gói tương ứng của các kiểu dữ liệu thì dùng **.TYPE**
```java
Class intClass = Interger.TYPE;
```
Mỗi class chỉ có một Class object trong heap nên khi lấy được Class object gốc thì sẽ hoàn toàn kiểm soát class, đối tượng
```java
Class cls1 = String.class;
String s = "Hello";
Class cls2 = s.getClass();
boolean sameClass = cls2 == cls1; // true
```
Sự khác biệt giữa so sánh class với **==** và **instanceof**
Ví dụ
```java
Interger n = new Integer(123);
boolean b1 = n instanceof Interger; // True vì n có kiểu Interger;
boolean b2 = n instanceof Number; // True vì n có kiểu Interger là lớp con của Number;
boolean b3 = n.getClass() == Interger.class; // True vì n là class Interger
boolearn b3 = n.getClass() == Number.class; // False vì n không phải class number
```
Bởi vì mục địch chính của reflection là làm sao để có nhiều thông tin về class nhất. Hmm có đoạn này mình đi tham khảo được:
```java=
static void printInfoClass(Class cls){
System.out.println("Class name: " + cls.getName());
System.out.println("Class Simple name: " +cls.getSimpleName());
if(cls.getPackage() != null){
System.out.println("Package name: " + cls.getPackage().getName());
}
System.out.println("is interface: " + cls.isInterface());
System.out.println("is enum: " + cls.isEnum());
System.out.println("is array: " + cls.isArray());
System.out.println("is primitive: " + cls.isPrimitve());
}
```

```
output:
Class name: B
Class Simple name: B
Package name:
is interface: false
is enum: false
is array: false
is primitive: false
Class name: java.lang.String
Class Simple name: String
Package name: java.lang
is interface: false
is enum: false
is array: false
is primitive: false
```
Khi mà có được Class object thì có thể khởi tạo đối tượng bằng cách:
```java
Class cls = String.class;
String s = (String) cls.newInstance();
```
> Nhưng điểm hạn chế của nó là chỉ có thể khởi tạo không tham số, không thể gọi từ khóa **new**
### 5.1 Truy xuất các trường của class
Khi đã có được Class object rồi thì chúng ta sẽ dễ dàng có được thông tin các trường của class với các method được viết sẵn
```java
Field getField(name)
//Lấy một trường public
//(bao gồm cả trường của class cha) dựa vào tên trường đó
Field getDeclaredField(name)
// Lấy một trường public của class hiên tại
//(không bao gồm trường của class cha) dựa vào tên
Field[] getFields()
// Lấy tất cả public fields (bao gồm cả field của class cha)
Field[] getDeclaredFields()
// lấy tất cả field của class (không bao gồm class cha)
```
Ví dụ;
```java
public class Main {
public static void main(String[] args) throws Exception{
Class stdClass = Student.class;
System.out.println(stdClass.getField("name"));
System.out.println(stdClass.getDeclaredField("grade"));
System.out.println(stdClass.getField("score"));
}
}
class Student extends Person{
public int score;
private int grade;
}
class Person{
public String name;
}
```
output:

#### 5.2 Truy xuất và chỉnh sửa thông tin Field
Lấy thông tin field thì từ những hàm mình đã nêu trên thui. Thêm phương thức **get()** để lấy giá trị của field.
```java
import java.lang.Class;
import java.lang.reflect.Field;
public class Main {
public static void main(String[] args) throws Exception{
Person D = new Person("Deku");
Class B = D.getClass();
Field name = B.getDeclaredField("name");
Object value = name.get(D);
System.out.println(value);
}
}
class Person{
private String name;
public Person(String name){
this.name = name;
}
}
```
Nhưng có một điều lưu ý ở đây là nếu như field mà chúng ta muốn lấy thông tin được khai báo **private **thì phải có gọi thêm một hàm để có quyền access chính là **setAccessible**
```java
name.setAccessible(true);
```
Để chỉnh sửa giá trị của field thì sử dụng hàm **set()** và đương nhiên là cũng phải lưu ý xem nó được khai báo với **private** hay **public**, **protected**
```java
import java.lang.Class;
import java.lang.reflect.Field;
public class Main {
public static void main(String[] args) throws Exception{
Person D = new Person("Deku");
Class B = D.getClass();
Field name = B.getDeclaredField("name");
name.setAccessible(true);
System.out.println((name.get(D)));
name.set(D, "Dekuuuuuuuu");
System.out.println((name.get(D)));
}
}
class Person{
private String name;
public Person(String name){
this.name = name;
}
}
```
output:

### 5.4 Truy xuất và gọi các phương thức của class
Để truy xuất và gọi các method của class thì chúng ta lại có các phương thức có sẵn của Class object
**Method getMethod(name, Class object của type param)**: lấy public method với tên của nó được truyền vào của class và class cha
**Method getDeclaredMethod(name, Class object của type param)**: lấy method (cả public và private) với tên của nó được truyền vào của class không bao gồm class cha
**Method[] getMethods()** lấy tất cả public method của class gồm cả của class cha
**Method[] getDeclaredMethods()** lấy tất cả method của class
ví dụ
```java
import java.lang.Class;
import java.lang.reflect.Field;
public class Main {
public static void main(String[] args) throws Exception{
Class stdClass = Student.class;
System.out.println(stdClass.getMethod("getScore", String.class));
System.out.println(stdClass.getMethod("getName"));
System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
}
}
class Student extends Person{
public int getScore(String type){
return 99;
}
private int getGrade(int year){
return 1;
}
}
class Person{
public String getName(){
return "Person";
}
}
```
Output:
```java
public int Student.getScore(java.lang.String)
public java.lang.String Person.getName()
private int Student.getGrade(int)
```
#### 5.4.1 Gọi phương thức
Bình thường chúng ta sẽ gọi method như thế này
```java
String s = "hello world";
String r = s.substring(6);
//output: world
```
Đối với reflection sử dụng hàm **invoke(object, param)**
```java
import java.lang.Class;
import java.lang.reflect.*;
public class Main {
public static void main(String[] args) throws Exception{
String s = "Hello world";
Method m = String.class.getMethod("substring", int.class);
String r = (String) m.invoke(s,6);
System.out.println(r);
}
}
```
Chú ý đoạn này là có thể có nhiều phương thức bị overload như method substring cũng có method substring với 2 param là **substring(int,int)** thì phải
```java
getMethod("substring",int.class,int.class)
```
Đối với các method khai báo với **private** thì phải sử dụng thêm **setAccessible()**
### 5.4.2 Tính đa hình
Câu hỏi đặt ra là nếu class con override một method của class cha thì khi sử dụng reflection để truy xuất method, method nào sẽ được gọi? Method của class cha hay của class con.
```java
import java.lang.Class;
import java.lang.reflect.*;
public class Main {
public static void main(String[] args) throws Exception{
Method h = Person.class.getMethod("hello");
h.invoke(new Student());
}
}
class Person{
public void hello(){
System.out.println("Person:hello");
}
}
class Student extends Person{
public void hello(){
System.out.println("Student:hello");
}
}
```
output:
```
Student:hello
```
Theo mình đọc được thì tùy vào object được truyền vào hàm **invoke()** và vẫn sẽ tuân theo flow của tính đa hình, đó là method của class con sẽ override method của class cha
Đoạn code trên tương đương với:
```java
Person p = new Student();
p.hello();
```
### 5.5. Truy xuất thông tin các hàm khởi tạo
Bình thường chúng ta có thể khởi tạo một đối tượng bằng cách:
```java
Person p = new Person();
```
Nếu muốn khởi tạo một đối tượng với Reflection thì:
```java
Person p = Person.class.newInstance();
```
Nhưng điểm hạn chế của **newInstance()** là chúng ta không thể khởi tạo đối tượng có tham số truyền vào.
Đừng lo vì Java Reflection đã tiếp tục cung cấp cho chúng ta **Constructor** class object với các method có sẵn để lấy được các hàm khởi tạo của class
```java
import java.lang.Class;
import java.lang.reflect.*;
public class Main {
public static void main(String[] args) throws Exception{
Constructor cons1 = Integer.class.getConstructor(int.class);
Integer n = (Integer) cons1.newInstance(123);
System.out.println(n);
}
}
```
```java
getConstructor(Class...);
// lấy một public Constructor
getDeclaredConstructor(Class...)
// lấy một constructor của class (có thể public hoặc private, protected)
getConstructors()
// lấy tất cả public constructor của class
getDeclaredConstructors()
// Lấy tất cả không chừa cái constructor nào
```
### 5.6. Lấy những thông tin về tính kế thừa của class
Để có được class cha của class hiện tại sử dụng hàm **getSupperClass()**
```java
import java.lang.Class;
import java.lang.reflect.*;
public class Main {
public static void main(String[] args) throws Exception{
Class i = Integer.class;
Class n = i.getSuperclass();
System.out.println(n);
}
}
```
output:
```java
class java.lang.Number
```
Để có được list interface của class sử dụng **getInterfaces()**
```java
import java.lang.Class;
import java.lang.reflect.*;
public class Main {
public static void main(String[] args) throws Exception{
Class i = Integer.class;
Class[] interfaces = i.getInterfaces();
for (Class is : interfaces){
System.out.println(is);
}
}
}
```
Output:
```java
interface java.lang.Comparable
interface java.lang.constant.Constable
interface java.lang.constant.ConstantDesc
```
## 6. Nguồn tham khảo
[Blog tung của](https://blog.csdn.net/weixin_45395059/article/details/126765905?ops_request_misc=&request_id=&biz_id=102&utm_term=java%20reflection&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-7-126765905.nonecase&spm=1018.2226.3001.4187)
[Blog của anh Tsu](https://tsublogs.wordpress.com/2023/02/15/javasecurity101-1-java-reflection/)
[Blog của Onsra](https://hackmd.io/@onsra03/r1PvmWpnh)