JavaRush /จาวาบล็อก /Random-TH /API การสะท้อน การสะท้อน. ด้านมืดของชวา
Oleksandr Klymenko
ระดับ
Харків

API การสะท้อน การสะท้อน. ด้านมืดของชวา

เผยแพร่ในกลุ่ม
สวัสดีคุณหนุ่มปาดาวัน ในบทความนี้ ฉันจะบอกคุณเกี่ยวกับ Force ซึ่งเป็นพลังที่โปรแกรมเมอร์ Java ใช้เฉพาะในสถานการณ์ที่สิ้นหวังเท่านั้น ด้านมืดของ Java ก็คือ -Reflection API
API การสะท้อน  การสะท้อน.  ด้านมืดของชวา - 1
การสะท้อนกลับใน Java ทำได้โดยใช้ Java Reflection API ภาพสะท้อนนี้คืออะไร? มีคำจำกัดความที่สั้นและแม่นยำซึ่งเป็นที่นิยมในอินเทอร์เน็ตเช่นกัน การสะท้อนกลับ (จากภาษาละตินตอนปลาย - การย้อนกลับ)เป็นกลไกในการศึกษาข้อมูลเกี่ยวกับโปรแกรมระหว่างการดำเนินการ การสะท้อนช่วยให้คุณสามารถตรวจสอบข้อมูลเกี่ยวกับฟิลด์ วิธีการ และตัวสร้างคลาสได้ กลไกการสะท้อนกลับช่วยให้คุณสามารถประมวลผลประเภทที่ขาดหายไประหว่างการคอมไพล์ แต่ปรากฏขึ้นระหว่างการทำงานของโปรแกรม การสะท้อนและการมีอยู่ของแบบจำลองที่สอดคล้องกันเชิงตรรกะสำหรับการรายงานข้อผิดพลาดทำให้สามารถสร้างโค้ดไดนามิกที่ถูกต้องได้ กล่าวอีกนัยหนึ่ง การทำความเข้าใจวิธีการทำงานของการสะท้อนกลับใน java จะเปิดโอกาสอันน่าอัศจรรย์มากมายให้กับคุณ คุณสามารถสลับคลาสและส่วนประกอบต่างๆ ได้อย่างแท้จริง
API การสะท้อน  การสะท้อน.  ด้านมืดของชวา - 2
ต่อไปนี้คือรายการพื้นฐานของสิ่งที่การสะท้อนทำได้:
  • ค้นหา/กำหนดคลาสของวัตถุ
  • รับข้อมูลเกี่ยวกับตัวดัดแปลงคลาส ฟิลด์ เมธอด ค่าคงที่ ตัวสร้าง และคลาสพิเศษ
  • ค้นหาวิธีการที่เป็นของอินเทอร์เฟซ/อินเทอร์เฟซที่นำไปใช้
  • สร้างอินสแตนซ์ของคลาสและไม่ทราบชื่อของคลาสจนกว่าโปรแกรมจะถูกดำเนินการ
  • รับและตั้งค่าของฟิลด์วัตถุตามชื่อ
  • เรียกวิธีการของวัตถุตามชื่อ
การสะท้อนกลับใช้ในเทคโนโลยี Java สมัยใหม่เกือบทั้งหมด เป็นเรื่องยากที่จะจินตนาการว่า Java ในฐานะแพลตฟอร์มจะประสบความสำเร็จในการนำไปใช้มหาศาลเช่นนี้โดยปราศจากการไตร่ตรองหรือไม่ เป็นไปได้มากว่าฉันทำไม่ได้ คุณคุ้นเคยกับแนวคิดทางทฤษฎีทั่วไปของการสะท้อนแล้ว ตอนนี้เรามาดูการประยุกต์ใช้ในทางปฏิบัติกันดีกว่า! เราจะไม่ศึกษาวิธีการทั้งหมดของ Reflection API แต่จะศึกษาเฉพาะวิธีการที่พบในการปฏิบัติจริงเท่านั้น เนื่องจากกลไกการสะท้อนเกี่ยวข้องกับการทำงานกับคลาส เราจะมีคลาสง่ายๆ - MyClass:
public class MyClass {
   private int number;
   private String name = "default";
//    public MyClass(int number, String name) {
//        this.number = number;
//        this.name = name;
//    }
   public int getNumber() {
       return number;
   }
   public void setNumber(int number) {
       this.number = number;
   }
   public void setName(String name) {
       this.name = name;
   }
   private void printData(){
       System.out.println(number + name);
   }
}
อย่างที่เราเห็น นี่คือคลาสที่พบบ่อยที่สุด ตัวสร้างที่มีพารามิเตอร์ถูกใส่เครื่องหมายความคิดเห็นด้วยเหตุผล เราจะกลับมาที่สิ่งนี้ในภายหลัง หากคุณดูเนื้อหาของชั้นเรียนอย่างใกล้ชิด คุณอาจเห็นว่าไม่มีgetter'a สำหรับname. ฟิลด์นั้นnameถูกทำเครื่องหมายด้วย access modifier privateเราจะไม่สามารถเข้าถึงได้ภายนอกคลาส=>เราไม่สามารถรับค่าของมันได้ “แล้วมีปัญหาอะไรล่ะ? - คุณพูด. “เพิ่มgetterหรือเปลี่ยนตัวแก้ไขการเข้าถึง” และคุณจะพูดถูก แต่จะเกิดอะไรขึ้นถ้าMyClassมันอยู่ในไลบรารี aar ที่คอมไพล์แล้วหรือในโมดูลปิดอื่นที่ไม่มีสิทธิ์ในการแก้ไข และในทางปฏิบัติสิ่งนี้เกิดขึ้นบ่อยมาก getterและโปรแกรมเมอร์ ที่ไม่ตั้งใจบางคนก็ลืมเขียน ถึงเวลาจำเรื่องไตร่ตรอง! ลองไปที่privateสนามnameชั้นเรียนMyClass:
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //no getter =(
   System.out.println(number + name);//output 0null
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(number + name);//output 0default
}
เรามาดูกันว่าเกิดอะไรขึ้นที่นี่ตอนนี้ มีคลาสที่ยอดเยี่ยมในClassjava มันแสดงถึงคลาสและอินเทอร์เฟซในแอปพลิเคชัน Java ที่ปฏิบัติการได้ เราจะไม่แตะต้อง ความเชื่อมโยงระหว่างClassและClassLoader. นี่ไม่ใช่หัวข้อของบทความ ถัดไปเพื่อรับฟิลด์ของคลาสนี้คุณต้องเรียกเมธอด เมธอดgetFields()นี้จะส่งคืนฟิลด์ที่มีอยู่ทั้งหมดของคลาสให้เรา สิ่งนี้ไม่เหมาะกับเราเนื่องจาก field ของเราคือprivateดังนั้นเราจึงใช้ method นี้getDeclaredFields()method นี้จะคืนค่า array ของ class fields ด้วยเช่นกัน แต่ตอนนี้เป็นทั้งprivateและ protectedในสถานการณ์ของเรา เรารู้ชื่อของฟิลด์ที่เราสนใจ และเราสามารถใช้เมธอดgetDeclaredField(String)โดยที่Stringคือชื่อของฟิลด์ที่ต้องการ บันทึก: getFields()และgetDeclaredFields()อย่าส่งคืนฟิลด์ของคลาสพาเรนต์! เยี่ยมมาก เราได้รับวัตถุField ที่มีลิงก์ไปยังไฟล์name. เพราะ ฟิลด์นี้ไม่ใช่публичным(สาธารณะ) ควรให้สิทธิ์การเข้าถึงเพื่อทำงานกับฟิลด์นั้น วิธีนี้setAccessible(true)ช่วยให้เราทำงานต่อไปได้ ตอนนี้สนามnameอยู่ภายใต้การควบคุมของเราแล้ว! คุณสามารถรับค่าของมันได้โดยการเรียกget(Object)อ็อบเจ็กต์Fieldโดยที่ ซึ่งObjectเป็นอินสแตนซ์ของคลาสของMyClassเรา เราโยนมันและStringกำหนดให้กับตัวแปรของเรา nameในกรณีที่จู่ๆ เราไม่มีsetter'a เราสามารถใช้วิธีตั้งค่าใหม่สำหรับฟิลด์ name ได้set:
field.set(myClass, (String) "new value");
ยินดีด้วย! คุณเพิ่งเข้าใจกลไกพื้นฐานของการไตร่ตรองและสามารถเข้าถึงprivateสนามได้! ให้ความสนใจกับบล็อกtry/catchและประเภทของข้อยกเว้นที่ได้รับการจัดการ IDE เองจะระบุการมีอยู่ที่จำเป็นของพวกเขา แต่ชื่อของพวกเขาทำให้ชัดเจนว่าเหตุใดพวกเขาจึงมาที่นี่ ไปข้างหน้า! ดังที่คุณอาจสังเกตเห็น เราMyClassมีวิธีการแสดงข้อมูลเกี่ยวกับข้อมูลชั้นเรียนอยู่แล้ว:
private void printData(){
       System.out.println(number + name);
   }
แต่โปรแกรมเมอร์คนนี้ก็ทิ้งมรดกไว้ที่นี่เช่นกัน วิธีการนี้อยู่ภายใต้ตัวแก้ไขการเข้าถึงprivateและเราต้องเขียนโค้ดเอาต์พุตด้วยตัวเองในแต่ละครั้ง มันไม่เรียงลำดับ การสะท้อนของเราอยู่ที่ไหน... มาเขียนฟังก์ชันต่อไปนี้:
public static void printData(Object myClass){
   try {
       Method method = myClass.getClass().getDeclaredMethod("printData");
       method.setAccessible(true);
       method.invoke(myClass);
   } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
       e.printStackTrace();
   }
}
ขั้นตอนที่นี่เหมือนกับการรับฟิลด์โดยประมาณ - เราได้วิธีการที่ต้องการตามชื่อและให้สิทธิ์การเข้าถึง และการเรียกอ็อบเจ็กต์Methodที่เราใช้invoke(Оbject, Args)โดยที่Оbjectยังเป็นอินสแตนซ์ของคลาสMyClassด้วย Args- อาร์กิวเมนต์ของวิธีการ - เราไม่มีเลย ตอนนี้เราใช้ฟังก์ชันเพื่อแสดงข้อมูลprintData:
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // outout 0default
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       field.set(myClass, (String) "new value");
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   printData(myClass);// output 0new value
}
ไชโย ตอนนี้เราสามารถเข้าถึงวิธีการส่วนตัวของชั้นเรียนได้แล้ว แต่จะเกิดอะไรขึ้นถ้าเมธอดยังคงมีข้อโต้แย้ง และเหตุใดจึงมีคอนสตรัคเตอร์ที่มีการใส่ความคิดเห็นไว้? ทุกสิ่งมีเวลาของมัน จากคำจำกัดความในตอนต้นเป็นที่ชัดเจนว่าการสะท้อนกลับช่วยให้คุณสร้างอินสแตนซ์ของคลาสในโหมดruntime(ในขณะที่โปรแกรมกำลังทำงาน)! เราสามารถสร้างอ็อบเจ็กต์ของคลาสด้วยชื่อแบบเต็มของคลาสนั้นได้ ชื่อคลาสแบบเต็มคือชื่อของคลาส โดยมีพาธไปยังคลาสนั้นในรูปpackageแบบ
API การสะท้อน  การสะท้อน.  ด้านมืดของชวา - 3
ในลำดับชั้นของฉันpackageชื่อเต็มMyClassจะเป็น " reflection.MyClass" คุณยังสามารถค้นหาชื่อคลาสด้วยวิธีง่ายๆ (มันจะส่งคืนชื่อคลาสเป็นสตริง):
MyClass.class.getName()
มาสร้างตัวอย่างของคลาสโดยใช้การสะท้อน:
public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       myClass = (MyClass) clazz.newInstance();
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}
ในขณะที่แอปพลิเคชัน Java เริ่มทำงาน จะไม่มีการโหลดคลาสทั้งหมดลงใน JVM หากโค้ดของคุณไม่ได้อ้างถึง class MyClassใครก็ตามที่รับผิดชอบในการโหลดคลาสลงใน JVM และนั่นคือClassLoaderจะไม่โหลดคลาสนั้นที่นั่น ดังนั้นเราจึงจำเป็นต้องบังคับClassLoaderให้มันโหลดและรับคำอธิบายคลาสของเราในรูปแบบของตัวแปรClassประเภท สำหรับงานนี้ มีเมธอดforName(String)โดยที่Stringชื่อของคลาสที่เราต้องการคำอธิบาย เมื่อได้รับแล้วСlassการเรียกเมธอดnewInstance()จะกลับมาObjectซึ่งจะถูกสร้างขึ้นตามคำอธิบายเดียวกัน ยังคงต้องนำวัตถุนี้มาสู่ชั้นเรียนของMyClassเรา เย็น! มันยาก แต่ฉันหวังว่ามันจะเข้าใจได้ ตอนนี้เราสามารถสร้างอินสแตนซ์ของคลาสได้จากบรรทัดเดียว! น่าเสียดายที่วิธีที่อธิบายไว้ใช้ได้กับตัวสร้างเริ่มต้นเท่านั้น (ไม่มีพารามิเตอร์) จะเรียกเมธอดด้วยอาร์กิวเมนต์และคอนสตรัคเตอร์ด้วยพารามิเตอร์ได้อย่างไร? ถึงเวลาที่จะยกเลิกการแสดงความคิดเห็นคอนสตรัคเตอร์ของเรา ตามที่คาดไว้newInstance()ไม่พบตัวสร้างเริ่มต้นและใช้งานไม่ได้อีกต่อไป มาเขียนการสร้างอินสแตนซ์คลาสใหม่:
public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       Class[] params = {int.class, String.class};
       myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}
หากต้องการรับตัวสร้างคลาส ให้เรียกเมธอดจากคำอธิบายคลาสgetConstructors()และหากต้องการรับพารามิเตอร์ตัวสร้าง ให้เรียกgetParameterTypes():
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
ด้วยวิธีนี้เราจะได้ Constructor ทั้งหมดและพารามิเตอร์ทั้งหมดมาให้พวกเขา ในตัวอย่างของฉัน มีการเรียกตัวสร้างเฉพาะที่มีพารามิเตอร์เฉพาะที่ทราบอยู่แล้ว และเพื่อเรียกคอนสตรัคเตอร์นี้ เราใช้เมธอดnewInstanceซึ่งเราระบุค่าของพารามิเตอร์เหล่านี้ สิ่งเดียวกันนี้จะเกิดขึ้นinvokeกับวิธีการโทร คำถามเกิดขึ้น: การเรียกตัวสร้างแบบสะท้อนกลับจะมีประโยชน์ที่ไหน? เทคโนโลยีจาวาสมัยใหม่ดังที่ได้กล่าวไว้ในตอนต้นไม่สามารถทำได้หากไม่มี Reflection API ตัวอย่างเช่น DI (การพึ่งพาการฉีด) ซึ่งคำอธิบายประกอบรวมกับการสะท้อนของวิธีการและตัวสร้างทำให้เกิดไลบรารี Dagger ซึ่งเป็นที่นิยมในการพัฒนา Android หลังจากอ่านบทความนี้แล้ว คุณจะถือว่าตนเองมีความรู้แจ้งในกลไกของ Reflection API ได้อย่างมั่นใจ ไม่ใช่เพื่อสิ่งใดที่การสะท้อนกลับถูกเรียกว่าด้านมืดของจาวา มันทำลายกระบวนทัศน์ OOP อย่างสิ้นเชิง ในจาวา การห่อหุ้มทำหน้าที่ซ่อนและจำกัดการเข้าถึงส่วนประกอบบางส่วนของโปรแกรมให้ผู้อื่นเข้าถึงได้ ด้วยการใช้ตัวแก้ไขส่วนตัว เราหมายความว่าการเข้าถึงฟิลด์นี้จะอยู่ภายในคลาสที่มีฟิลด์นี้อยู่เท่านั้น โดยอิงจากสิ่งนี้ เราจึงสร้างสถาปัตยกรรมเพิ่มเติมของโปรแกรม ในบทความนี้ เราได้เห็นแล้วว่าคุณสามารถใช้การสะท้อนเพื่อไปไหนมาไหนได้อย่างไร ตัวอย่างที่ดีในรูปแบบของโซลูชันทางสถาปัตยกรรมคือรูปแบบการออกแบบเชิงสร้างสรรค์Singleton- แนวคิดหลักคือตลอดการดำเนินการทั้งหมดของโปรแกรม คลาสที่ใช้เทมเพลตนี้ควรมีสำเนาเดียวเท่านั้น ซึ่งทำได้โดยการตั้งค่าตัวแก้ไขการเข้าถึงเริ่มต้นให้เป็นส่วนตัวสำหรับตัวสร้าง และมันจะแย่มากถ้าโปรแกรมเมอร์บางคนที่มีการไตร่ตรองของตัวเองสร้างคลาสดังกล่าว ยังไงก็ตาม มีคำถามที่น่าสนใจมากที่ฉันเพิ่งได้ยินจากพนักงานของฉัน: ชั้นเรียนที่ใช้เทมเพลตจะมีSingletonทายาทได้หรือไม่ เป็นไปได้ไหมที่แม้แต่การสะท้อนกลับก็ไร้พลังในกรณีนี้ เขียนความคิดเห็นของคุณเกี่ยวกับบทความและคำตอบในความคิดเห็นและถามคำถามของคุณ! พลังที่แท้จริงของ Reflection API มาพร้อมกับ Runtime Annotations ซึ่งเราอาจจะพูดถึงในบทความเกี่ยวกับด้านมืดของ Java ในอนาคต ขอขอบคุณสำหรับความสนใจของคุณ!
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION