วิธีอ่านค่าของฟิลด์ส่วนตัวจากคลาสอื่นใน Java


482

ฉันมีคลาสที่ออกแบบมาไม่ดีในบุคคลที่สามJARและฉันจำเป็นต้องเข้าถึงหนึ่งในฟิลด์ส่วนตัวของมัน ตัวอย่างเช่นทำไมฉันต้องเลือกเขตข้อมูลส่วนตัวจึงจำเป็น?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

ฉันจะใช้การสะท้อนเพื่อรับค่าได้stuffIWantอย่างไร

คำตอบ:


667

ในการเข้าใช้งานเขตข้อมูลส่วนบุคคลคุณจำเป็นต้องนำออกจากเขตข้อมูลที่ประกาศของชั้นเรียนจากนั้นทำให้เข้าถึงได้:

Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException

แก้ไข : ตามที่ได้รับความเห็นโดยaperkinsทั้งการเข้าถึงฟิลด์การตั้งค่าเป็นเข้าถึงและดึงค่าสามารถโยนExceptions แม้ว่าข้อยกเว้นการตรวจสอบเท่านั้นที่คุณจำเป็นต้องคำนึงถึงมีการแสดงความคิดเห็นข้างต้น

NoSuchFieldExceptionจะถูกโยนถ้าคุณถามหาข้อมูลโดยใช้ชื่อที่ไม่สอดคล้องกับข้อมูลการประกาศให้เป็น

obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException

IllegalAccessExceptionจะถูกโยนถ้าสนามไม่สามารถเข้าถึง (ตัวอย่างเช่นถ้ามันเป็นส่วนตัวและยังไม่ได้รับการทำเข้าถึงได้ผ่านทางหายไปออกf.setAccessible(true)เส้น

RuntimeExceptions ซึ่งอาจจะมีทั้งโยนSecurityExceptions (ถ้า JVM ของSecurityManagerจะไม่ยอมให้มีการเปลี่ยนแปลงการเข้าถึงข้อมูลของ) หรือIllegalArgumentExceptions ถ้าคุณพยายามและการเข้าถึงข้อมูลบนวัตถุไม่ประเภทระดับเขตของงาน:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type

4
คุณช่วยอธิบายความคิดเห็นเกี่ยวกับข้อยกเว้นได้ไหม? รหัสจะทำงาน แต่จะโยนข้อยกเว้น? หรือรหัสอาจโยนข้อยกเว้น?
Nir Levy

1
@Nir - ไม่มี - ในทุกโอกาสรหัสจะทำงานเพียงแค่ปรับ (เป็นค่าเริ่มต้นจะช่วยให้ SecurityManager ยินดีต้อนรับคุณ, เขตจะมีการเปลี่ยนแปลง) - แต่คุณต้องจับตรวจสอบข้อยกเว้น (อย่างใดอย่างหนึ่งหรือจับพวกเขาประกาศว่าพวกเขาจะrethrown ) ฉันแก้ไขคำตอบของฉันเล็กน้อย มันอาจจะดีสำหรับคุณที่จะเขียนกรณีทดสอบขนาดเล็กเพื่อเล่นรอบ ๆ และดูว่าเกิดอะไรขึ้น
oxbow_lakes

3
ขออภัยคำตอบนี้ทำให้ฉันสับสน อาจแสดงตัวอย่างของการจัดการข้อยกเว้นทั่วไป ดูเหมือนว่าจะมีข้อยกเว้นเกิดขึ้นเมื่อชั้นเรียนถูกต่อสายเข้าด้วยกันอย่างไม่ถูกต้อง ตัวอย่างโค้ดทำให้ดูเหมือนว่าข้อยกเว้นจะถูกส่งไปที่ llnes ที่เกี่ยวข้อง
LaFayette

2
getDeclaredField()ไม่พบทุ่งถ้าพวกเขาถูกกำหนดไว้ในคลาสแม่ - คุณต้องยังย้ำผ่านลำดับชั้นผู้ปกครองเรียกgetDeclaredField()ในแต่ละจนกว่าคุณจะพบการแข่งขัน (หลังจากที่คุณสามารถเรียกsetAccessible(true)) Objectหรือคุณเข้าถึง
ลุคฮัทชิสัน

1
@legend คุณสามารถติดตั้งตัวจัดการความปลอดภัยเพื่อห้ามการเข้าถึงดังกล่าว ตั้งแต่ Java 9 การเข้าถึงดังกล่าวไม่ควรทำงานข้ามขอบเขตโมดูล (เว้นแต่โมดูลจะเปิดอย่างชัดเจน) แม้ว่าจะมีขั้นตอนการนำส่งเกี่ยวกับการบังคับใช้กฎใหม่ นอกจากนั้นต้องใช้ความพยายามเพิ่มเติมเช่น Reflection สร้างความแตกต่างให้กับprivateสาขาที่ไม่ใช่เสมอ มันขัดขวางการเข้าถึงโดยไม่ตั้งใจ
Holger

159

ลองFieldUtilsจาก apache commons-lang3:

FieldUtils.readField(object, fieldName, true);

76
ฉันเชื่อว่าคุณสามารถแก้ปัญหาส่วนใหญ่ของโลกได้ด้วยการรวบรวมวิธีการสองสามวิธีจากคอมมอนส์ - lang3
คาเมรอน

@yegor ทำไม java อนุญาตให้ทำสิ่งนี้ หมายความว่าทำไม java อนุญาตให้เข้าถึงสมาชิกส่วนตัว
Asif Mushtaq

1
@ yegor256 ฉันยังสามารถเข้าถึงสมาชิกส่วนตัว C # และ C ++ ได้เช่นกัน .. !! แล้ว? ผู้สร้างภาษาทุกภาษาอ่อนแอหรือไม่
Asif Mushtaq

3
@UnKnown java ไม่ได้ มีสองสิ่งที่แตกต่างกัน - ภาษา java และ Java เป็นเครื่องเสมือน Java หลังทำงานใน bytecode และมีห้องสมุดเพื่อจัดการกับสิ่งนั้น ดังนั้นภาษาจาวาไม่อนุญาตให้คุณใช้เขตข้อมูลส่วนตัวนอกขอบเขตหรือไม่อนุญาตให้กลายพันธุ์การอ้างอิงขั้นสุดท้าย แต่เป็นไปได้ที่จะทำเช่นนั้นโดยใช้เครื่องมือ ซึ่งเพิ่งเกิดขึ้นภายในไลบรารีมาตรฐาน
evgenii

3
@UnKnown หนึ่งในเหตุผลคือ Java ไม่สามารถเข้าถึง "เพื่อน" เพื่อเปิดใช้งานการเข้าถึงฟิลด์โดยกรอบโครงสร้างพื้นฐานเท่านั้นเช่น DI, ORM หรือ XML / JSON serializer เฟรมเวิร์กดังกล่าวจำเป็นต้องเข้าถึงเขตข้อมูลวัตถุเพื่อทำให้เป็นอันดับหรือเริ่มต้นสถานะภายในของวัตถุอย่างถูกต้อง แต่คุณยังอาจต้องการการบังคับใช้การห่อหุ้มเวลาคอมไพล์ที่เหมาะสมสำหรับตรรกะทางธุรกิจของคุณ
Ivan Gammel

26

การสะท้อนกลับไม่ใช่วิธีเดียวในการแก้ไขปัญหาของคุณ (ซึ่งเป็นการเข้าถึงฟังก์ชันการทำงานส่วนตัว / พฤติกรรมของคลาส / ส่วนประกอบ)

โซลูชันทางเลือกคือการแยกคลาสออกจาก. jar ถอดรหัสโดยใช้ (พูด) JodeหรือJadเปลี่ยนฟิลด์ (หรือเพิ่ม accessor) และคอมไพล์ใหม่กับ. jar ดั้งเดิม จากนั้นใส่ .class ข้างหน้าใหม่ของ.jarใน classpath .jarหรือใส่ไว้ใน (ยูทิลิตี jar ช่วยให้คุณสามารถแตกและแทรกเข้าไปยัง. jar ที่มีอยู่เดิมได้)

ดังที่ระบุไว้ด้านล่างสิ่งนี้จะช่วยแก้ไขปัญหาการเข้าถึง / เปลี่ยนสถานะส่วนตัวที่กว้างกว่าแทนที่จะเข้าถึง / เปลี่ยนฟิลด์

แน่นอนว่าสิ่งนี้ต้อง.jarไม่ลงนามแน่นอน


36
วิธีนี้จะค่อนข้างเจ็บปวดสำหรับสนามที่เรียบง่าย
Valentin Rocher

1
ฉันไม่เห็นด้วย. จะช่วยให้คุณไม่เพียง แต่เข้าถึงเขตข้อมูลของคุณ แต่ยังเปลี่ยนชั้นเรียนหากจำเป็นหากเข้าถึงเขตข้อมูลจะไม่เพียงพอ
Brian Agnew

4
จากนั้นคุณต้องทำเช่นนั้นอีกครั้ง จะเกิดอะไรขึ้นถ้า jar ได้รับการอัปเดตและคุณใช้การสะท้อนกลับสำหรับฟิลด์ที่ไม่มีอยู่อีกต่อไป มันเป็นปัญหาเดียวกัน คุณเพียงแค่ต้องจัดการมัน
Brian Agnew

4
ฉันประหลาดใจเป็นเท่าใดนี้ได้รับ downvoted ที่กำหนด) ก็เน้นเป็นทางเลือกที่ขปฏิบัติ) มันเหมาะสำหรับสถานการณ์ที่เปลี่ยนการแสดงผลข้อมูลที่ไม่เพียงพอ
ไบรอัน Agnew

2
@BrianAgnew อาจเป็นเพียงความหมาย แต่ถ้าเรายึดติดกับคำถาม (โดยใช้การสะท้อนเพื่ออ่านข้อมูลส่วนตัว) การไม่ใช้การสะท้อนนั้นเป็นความขัดแย้งในตัวเองทันที แต่ฉันยอมรับว่าคุณให้สิทธิ์เข้าถึงฟิลด์ ... แต่ยังคงเป็นฟิลด์ที่ไม่เป็นส่วนตัวอีกต่อไปดังนั้นเราจึงไม่ยึดติดกับ "อ่านฟิลด์ส่วนตัว" ของคำถาม จากอีกมุมหนึ่งการแก้ไข. jar อาจไม่ทำงานในบางกรณี (jar ที่เซ็นชื่อ) ต้องทำทุกครั้งที่มีการอัปเดต jar ต้องมีการจัดการอย่างระมัดระวังของ classpath (ซึ่งคุณอาจไม่สามารถควบคุมได้อย่างสมบูรณ์หากคุณดำเนินการใน แอปพลิเคชั่นคอนเทนเนอร์) ฯลฯ
Remi Morin

18

หนึ่งในตัวเลือกอื่น ๆ ที่ยังไม่ได้รับการกล่าวถึงยังใช้งาน: ใช้Groovy Groovy อนุญาตให้คุณเข้าถึงตัวแปรอินสแตนซ์ส่วนตัวเป็นผลข้างเคียงของการออกแบบภาษา ไม่ว่าคุณจะมีทะเยอทะยานสำหรับสนามหรือไม่คุณก็สามารถใช้

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()

4
OP ขอเฉพาะ Java
Jochen

3
วันนี้หลายโครงการ Java รวม groovy ก็เพียงพอแล้วที่โครงการจะใช้ Groovy DSL ของ Spring และพวกเขาจะมี groovy บน classpath เช่น ในกรณีนี้คำตอบนี้มีประโยชน์และในขณะที่ไม่ตอบรับ OP โดยตรงจะเป็นประโยชน์สำหรับผู้เข้าชมจำนวนมาก
Amir Abiri

10

การใช้Reflection ใน Javaคุณสามารถเข้าถึงprivate/publicฟิลด์และเมธอดทั้งหมดของคลาสหนึ่งไปยังคลาสอื่น แต่ตามเอกสารคู่มือของOracle ในส่วนข้อบกพร่องที่พวกเขาแนะนำว่า:

"เนื่องจากการสะท้อนกลับทำให้โค้ดสามารถดำเนินการที่ผิดกฎหมายในโค้ดที่ไม่สะท้อนแสงเช่นการเข้าถึงฟิลด์และวิธีการส่วนตัวการใช้การสะท้อนอาจส่งผลให้เกิดผลข้างเคียงที่ไม่คาดคิดซึ่งอาจทำให้โค้ดไม่สมบูรณ์และอาจทำลายรหัสพกพาได้ แบ่ง abstractions และดังนั้นจึงอาจเปลี่ยนพฤติกรรมด้วยการอัพเกรดของแพลตฟอร์ม "

นี่คือรหัสต่อไปนี้เพื่อแสดงให้เห็นถึงแนวคิดพื้นฐานของการสะท้อน

Reflection1.java

public class Reflection1{

    private int i = 10;

    public void methoda()
    {

        System.out.println("method1");
    }
    public void methodb()
    {

        System.out.println("method2");
    }
    public void methodc()
    {

        System.out.println("method3");
    }

}

Reflection2.java

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class Reflection2{

    public static void main(String ar[]) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        Method[] mthd = Reflection1.class.getMethods(); // for axis the methods 

        Field[] fld = Reflection1.class.getDeclaredFields();  // for axis the fields  

        // Loop for get all the methods in class
        for(Method mthd1:mthd)
        {

            System.out.println("method :"+mthd1.getName());
            System.out.println("parametes :"+mthd1.getReturnType());
        }

        // Loop for get all the Field in class
        for(Field fld1:fld)
        {
            fld1.setAccessible(true);
            System.out.println("field :"+fld1.getName());
            System.out.println("type :"+fld1.getType());
            System.out.println("value :"+fld1.getInt(new Reflaction1()));
        }
    }

}

หวังว่ามันจะช่วย


5

ตามที่ oxbow_lakes กล่าวถึงคุณสามารถใช้การไตร่ตรองเพื่อหลีกเลี่ยงข้อ จำกัด การเข้าถึง (สมมติว่า SecurityManager ของคุณจะให้คุณ)

ที่กล่าวว่าหากคลาสนี้ได้รับการออกแบบมาไม่ดีจนทำให้คุณหันไปใช้แฮ็กเกอร์เช่นนั้นบางทีคุณควรมองหาทางเลือกอื่น แน่ใจว่าแฮ็คตัวเล็ก ๆ นี้อาจช่วยคุณประหยัดเวลาไม่กี่ชั่วโมง แต่ราคาเท่าไหร่ที่จะทำให้คุณเสียเงิน?


3
ฉันโชคดีกว่าที่จริงฉันแค่ใช้รหัสนี้เพื่อดึงข้อมูลบางอย่างหลังจากนั้นฉันสามารถโยนกลับเข้าไปในถังรีไซเคิลได้
แฟรงก์ครูเกอร์

2
ถ้าอย่างนั้นแฮ็ค :-)
Laurence Gonsalves

3
สิ่งนี้ไม่ได้ให้คำตอบสำหรับคำถาม หากต้องการวิจารณ์หรือขอคำชี้แจงจากผู้แต่งโปรดแสดงความคิดเห็นใต้โพสต์ของพวกเขา
Mureinik

@Mureinik - ตอบคำถามด้วยคำว่า "คุณสามารถใช้การสะท้อน" มันไม่มีตัวอย่างหรือคำอธิบายที่มากขึ้นหรืออย่างไร แต่มันเป็นคำตอบ ลงคะแนนถ้าคุณไม่ชอบมัน
ArtOfWarfare

4

ใช้เฟรมเวิร์ก Soot Java Optimization เพื่อปรับเปลี่ยน bytecode โดยตรง http://www.sable.mcgill.ca/soot/

Soot เขียนขึ้นอย่างสมบูรณ์ใน Java และทำงานกับ Java เวอร์ชันใหม่


3

คุณต้องทำสิ่งต่อไปนี้:

private static Field getField(Class<?> cls, String fieldName) {
    for (Class<?> c = cls; c != null; c = c.getSuperclass()) {
        try {
            final Field field = c.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field;
        } catch (final NoSuchFieldException e) {
            // Try parent
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "Cannot access field " + cls.getName() + "." + fieldName, e);
        }
    }
    throw new IllegalArgumentException(
            "Cannot find field " + cls.getName() + "." + fieldName);
}

2

หากใช้ Spring, ReflectionTestUtilsจะมีเครื่องมือที่มีประโยชน์บางอย่างที่ช่วยเหลือได้โดยง่าย มันอธิบายว่า"สำหรับการใช้งานในหน่วยและการทดสอบการรวมสถานการณ์" นอกจากนี้ยังมีคลาสที่คล้ายกันที่ชื่อว่าReflectionUtilsแต่นี่ถูกอธิบายว่าเป็น"มีไว้สำหรับใช้ภายในเท่านั้น" - ดูคำตอบสำหรับการตีความความหมายของสิ่งนี้

ในการจัดการกับตัวอย่างที่โพสต์:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");

1
หากคุณตัดสินใจที่จะใช้คลาส Utils โดย Spring คุณควรใช้คลาสที่ไม่ผ่านการทดสอบ ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/ … ) แทนแน่นอน ใช้งานจริงสำหรับการทดสอบหน่วย
ซิงค์

อาจเป็นไปได้แม้ว่าคลาสนั้นจะอธิบายว่า "มีไว้สำหรับใช้ภายในเท่านั้น" ฉันได้เพิ่มข้อมูลลงในคำตอบเกี่ยวกับเรื่องนี้แล้ว (เก็บตัวอย่างไว้ใช้ReflectionTestUtilsในประสบการณ์ของฉันฉันแค่ต้องการทำสิ่งนี้ในสถานการณ์การทดสอบเท่านั้น)
Steve Chambers

1

เพียงบันทึกเพิ่มเติมเกี่ยวกับการสะท้อน: ฉันได้สังเกตในบางกรณีพิเศษเมื่อมีหลายคลาสที่มีชื่อเดียวกันอยู่ในแพ็คเกจที่ต่างกันการสะท้อนที่ใช้ในคำตอบสูงสุดอาจล้มเหลวในการเลือกคลาสที่ถูกต้องจากวัตถุ ดังนั้นหากคุณรู้ว่าอะไรคือ package.class ของวัตถุดังนั้นคุณควรเข้าถึงค่าเขตข้อมูลส่วนตัวดังนี้:

org.deeplearning4j.nn.layers.BaseOutputLayer ll = (org.deeplearning4j.nn.layers.BaseOutputLayer) model.getLayer(0);
Field f = Class.forName("org.deeplearning4j.nn.layers.BaseOutputLayer").getDeclaredField("solver");
f.setAccessible(true);
Solver s = (Solver) f.get(ll);

(นี่คือคลาสตัวอย่างที่ไม่ทำงานสำหรับฉัน)


1

คุณสามารถใช้@JailBreak ของ Manifold สำหรับการสะท้อน Java แบบปลอดภัยโดยตรงได้:

@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;

public class Foo {
    private String stuffIWant;
}

@JailBreakปลดล็อกfooตัวแปรโลคัลในคอมไพเลอร์เพื่อเข้าถึงสมาชิกทั้งหมดในFooลำดับชั้นของโดยตรง

ในทำนองเดียวกันคุณสามารถใช้วิธีการขยายการแหกคุก ()สำหรับการใช้ครั้งเดียว:

foo.jailbreak().stuffIWant = "123";

ด้วยjailbreak()วิธีการที่คุณสามารถเข้าถึงสมาชิกใด ๆ ในFooลำดับชั้นของ

ในทั้งสองกรณีคอมไพเลอร์แก้ไขการเข้าถึงฟิลด์ให้คุณพิมพ์อย่างปลอดภัยราวกับว่าเป็นฟิลด์สาธารณะในขณะที่ Manifold สร้างโค้ดการสะท้อนที่มีประสิทธิภาพสำหรับคุณภายใต้ประทุน

ค้นพบเพิ่มเติมเกี่ยวกับManifold


1

มันค่อนข้างง่ายด้วยเครื่องมือXrayInterface เพียงกำหนด getters / setters ที่หายไปเช่น

interface BetterDesigned {
  Hashtable getStuffIWant(); //is mapped by convention to stuffIWant
}

และ xray โครงการที่คุณออกแบบไม่ดี:

IWasDesignedPoorly obj = new IWasDesignedPoorly();
BetterDesigned better = ...;
System.out.println(better.getStuffIWant());

ภายในนี้ขึ้นอยู่กับการสะท้อน


0

พยายามที่จะแก้ไขปัญหาสำหรับกรณีที่คุณต้องการตั้งค่า / รับข้อมูลเป็นหนึ่งในคลาสของคุณเอง

เพียงแค่สร้างpublic setter(Field f, Object value)และpublic Object getter(Field f)เพื่อที่ คุณสามารถตรวจสอบ securoty ด้วยตัวคุณเองภายในฟังก์ชั่นสมาชิกเหล่านี้ เช่นตัวตั้งค่า:

class myClassName {
    private String aString;

    public set(Field field, Object value) {
        // (A) do some checkings here  for security

        // (B) set the value
        field.set(this, value);
    }
}

แน่นอนว่าตอนนี้คุณต้องไปหาออกjava.lang.reflect.FieldสำหรับsStringก่อนที่จะมีการตั้งค่าของค่าของฟิลด์

ฉันจะใช้เทคนิคนี้ใน ResultSet-to-and-from-model-mapper

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.