Làm thế nào để đọc giá trị của một trường riêng từ một lớp khác trong Java?


482

Tôi có một lớp được thiết kế kém trong bên thứ 3 JARvà tôi cần truy cập vào một trong các lĩnh vực riêng tư của nó . Ví dụ, tại sao tôi cần chọn trường riêng là cần thiết?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

Làm thế nào tôi có thể sử dụng sự phản chiếu để có được giá trị của stuffIWant?

Câu trả lời:


668

Để truy cập các trường riêng tư, bạn cần lấy chúng từ các trường được khai báo của lớp và sau đó làm cho chúng có thể truy cập được:

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

EDIT : như đã được nhận xét bởi aperkins , cả hai truy cập vào trường, đặt nó là có thể truy cập và truy xuất giá trị có thể ném Exceptions, mặc dù các trường hợp ngoại lệ được kiểm tra duy nhất bạn cần lưu ý được nhận xét ở trên.

Các NoSuchFieldExceptionsẽ được ném nếu bạn yêu cầu một lĩnh vực bởi một tên mà không tương ứng với một lĩnh vực khai báo.

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

Các IllegalAccessExceptionsẽ được ném nếu lĩnh vực này là không thể truy cập (ví dụ, nếu nó là tư nhân và đã không được thực hiện truy cập thông qua bỏ lỡ các f.setAccessible(true)dòng.

Các RuntimeExceptions có thể bị ném là SecurityExceptions (nếu JVM SecurityManagersẽ không cho phép bạn thay đổi khả năng truy cập của một trường) hoặc IllegalArgumentExceptions, nếu bạn thử và truy cập vào trường trên một đối tượng không thuộc loại của trường:

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

4
bạn có thể vui lòng giải thích các ý kiến ​​ngoại lệ? mã sẽ chạy nhưng sẽ ném ngoại lệ? hoặc mã có thể ném ngoại lệ?
Nir Levy

1
@Nir - Không - trong tất cả khả năng mã sẽ chỉ chạy tốt (như là mặc định SecurityManager sẽ cho phép sự tiếp cận lĩnh vực phải được thay đổi) - nhưng bạn phải xử lý kiểm tra trường hợp ngoại lệ (hoặc bắt chúng hoặc tuyên bố họ được rethrown ). Tôi đã sửa đổi câu trả lời của tôi một chút. Có thể tốt cho bạn khi viết một trường hợp thử nghiệm nhỏ để chơi xung quanh và xem điều gì sẽ xảy ra
oxbow_lakes

3
Xin lỗi, đây là câu trả lời khó hiểu với tôi. Có lẽ cho thấy một ví dụ về xử lý ngoại lệ điển hình. Có vẻ như các trường hợp ngoại lệ sẽ xảy ra khi các lớp được nối với nhau không chính xác. Mẫu mã làm cho có vẻ như các ngoại lệ sẽ được ném vào llnes tương ứng.
LaFọ

2
getDeclaredField()không tìm thấy các trường nếu chúng được xác định trong các lớp cha - bạn cũng phải lặp lại thông qua hệ thống phân cấp của lớp cha, gọi getDeclaredField()cho từng lớp, cho đến khi bạn tìm thấy một kết quả khớp (sau đó bạn có thể gọi setAccessible(true)) hoặc bạn tiếp cận Object.
Luke Hutchison

1
@legend bạn có thể cài đặt trình quản lý bảo mật để cấm truy cập đó. Kể từ Java 9, truy cập như vậy không được phép hoạt động trên các biên của mô-đun (trừ khi một mô-đun được mở rõ ràng), mặc dù có một giai đoạn chuyển tiếp liên quan đến việc thực thi các quy tắc mới. Bên cạnh đó, đòi hỏi nỗ lực bổ sung như Reflection luôn tạo ra sự khác biệt cho các privatelĩnh vực không . Nó ngăn cản truy cập tình cờ.
Holger

159

Thử FieldUtilstừ apache commons-lang3:

FieldUtils.readField(object, fieldName, true);

76
Tôi tin rằng bạn có thể giải quyết hầu hết các vấn đề của thế giới bằng cách kết hợp một vài phương thức từ commons-lang3.
Cameron

@yegor tại sao java cho phép điều này? có nghĩa là tại sao java cho phép truy cập các thành viên tư nhân?
Asif Mushtaq

1
@ yegor256 Tôi vẫn có thể truy cập các thành viên riêng của C # và C ++ .. !! Sau đó? tất cả những người sáng tạo ngôn ngữ là yếu?
Asif Mushtaq

3
@UnKnown java thì không. Có hai thứ khác nhau - ngôn ngữ java và java là máy ảo java. Cái sau hoạt động trên mã byte. Và có những thư viện để thao túng điều đó. Vì vậy, ngôn ngữ java không cho phép bạn sử dụng các trường riêng ngoài phạm vi hoặc không cho phép thay đổi các tham chiếu cuối cùng. Nhưng nó có thể làm điều đó bằng cách sử dụng công cụ. Mà chỉ xảy ra trong thư viện tiêu chuẩn.
evgenii

3
@UnKnown một trong những lý do là Java không có quyền truy cập "bạn bè" để cho phép truy cập vào trường chỉ bằng một khung cơ sở hạ tầng như trình tuần tự hóa DI, ORM hoặc XML / JSON. Các khung như vậy cần truy cập vào các trường đối tượng để tuần tự hóa hoặc khởi tạo chính xác trạng thái bên trong của đối tượng, nhưng bạn vẫn có thể cần thực thi đóng gói thời gian biên dịch thích hợp cho logic nghiệp vụ của mình.
Ivan Gammel

26

Phản ánh không phải là cách duy nhất để giải quyết vấn đề của bạn (đó là truy cập chức năng / hành vi riêng tư của một lớp / thành phần)

Một giải pháp thay thế là trích xuất lớp từ .jar, dịch ngược nó bằng cách sử dụng (nói) Jode hoặc Jad , thay đổi trường (hoặc thêm một trình truy cập) và biên dịch lại nó với .jar ban đầu. Sau đó đặt. Class mới lên trước .jartrong classpath hoặc đặt lại nó trong .jar. (tiện ích jar cho phép bạn trích xuất và gắn lại vào .jar hiện có)

Như đã lưu ý dưới đây, điều này giải quyết vấn đề rộng hơn về việc truy cập / thay đổi trạng thái riêng tư thay vì chỉ đơn giản là truy cập / thay đổi một trường.

Điều này đòi hỏi .jarkhông được ký, tất nhiên.


36
Cách này sẽ khá đau đối với chỉ một lĩnh vực đơn giản.
Valentin Rocher

1
Tôi không đồng ý. Nó cho phép bạn không chỉ truy cập vào trường của mình mà còn thay đổi lớp nếu cần thiết nếu việc truy cập vào trường không đủ.
Brian Agnew

4
Sau đó, bạn phải làm điều đó một lần nữa. Điều gì xảy ra nếu jar nhận được bản cập nhật và bạn sử dụng sự phản chiếu cho một trường không còn tồn tại? Đó chính xác là cùng một vấn đề. Bạn chỉ cần quản lý nó.
Brian Agnew

4
Tôi ngạc nhiên khi thấy điều này bị hạ thấp bao nhiêu khi được đưa ra a) nó được đánh dấu là một giải pháp thay thế thực tế b) nó phục vụ cho các kịch bản trong đó thay đổi khả năng hiển thị của một lĩnh vực là không đủ
Brian Agnew

2
@BrianAgnew có thể đó chỉ là ngữ nghĩa nhưng nếu chúng ta dính vào câu hỏi (sử dụng sự phản chiếu để đọc một trường riêng) không sử dụng sự phản chiếu thì ngay lập tức là một mâu thuẫn. Nhưng tôi đồng ý rằng bạn cung cấp quyền truy cập vào trường ... nhưng trường vẫn không còn riêng tư nữa nên chúng tôi không dính vào "đọc trường riêng" của câu hỏi. Từ một góc độ khác, sửa đổi .jar có thể không hoạt động trong một số trường hợp (jar đã ký), cần được thực hiện mỗi khi jar được cập nhật, yêu cầu thao tác cẩn thận của đường dẫn lớp (mà bạn có thể không kiểm soát hoàn toàn nếu bạn được thực thi trong một thùng chứa ứng dụng) vv
Remi Morin

18

Một tùy chọn khác chưa được đề cập: sử dụng Groovy . Groovy cho phép bạn truy cập các biến cá thể riêng tư như một tác dụng phụ của thiết kế ngôn ngữ. Cho dù bạn có một getter cho lĩnh vực này hay không, bạn chỉ có thể sử dụng

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

4
OP đặc biệt yêu cầu Java
Jochen

3
Nhiều dự án Java ngày nay kết hợp Groovy. Như vậy là đủ để dự án sử dụng DSL Groovy của Spring và họ sẽ có rãnh trên đường dẫn lớp chẳng hạn. Trong trường hợp câu trả lời này là hữu ích và trong khi không trả lời trực tiếp cho OP, nó sẽ có ích cho nhiều khách truy cập.
Amir Abiri

10

Sử dụng Reflection trong Java, bạn có thể truy cập tất cả các private/publictrường và phương thức của lớp này sang lớp khác. Nhưng theo tài liệu của Oracle trong phần nhược điểm mà họ khuyến nghị rằng:

"Vì sự phản chiếu cho phép mã thực hiện các hoạt động bất hợp pháp trong mã không phản chiếu, chẳng hạn như truy cập các trường và phương thức riêng tư, việc sử dụng phản xạ có thể dẫn đến các tác dụng phụ không mong muốn, có thể khiến mã bị rối loạn và có thể phá hủy tính di động. phá vỡ sự trừu tượng và do đó có thể thay đổi hành vi với việc nâng cấp nền tảng "

Dưới đây là các đoạn mã sau để chứng minh các khái niệm cơ bản của Reflection

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()));
        }
    }

}

Hy vọng nó sẽ giúp.


5

Như oxbow_lakes đề cập, bạn có thể sử dụng sự phản chiếu để khắc phục các hạn chế truy cập (giả sử SecurityManager sẽ cho phép bạn).

Điều đó nói rằng, nếu lớp này được thiết kế tồi đến mức nó khiến bạn phải dùng đến những tin tặc như vậy, có lẽ bạn nên tìm kiếm một giải pháp thay thế. Chắc chắn rằng vụ hack nhỏ này có thể giúp bạn tiết kiệm được vài giờ, nhưng nó sẽ khiến bạn tốn bao nhiêu tiền?


3
Tôi thực sự may mắn hơn thế, tôi chỉ sử dụng mã này để trích xuất một số dữ liệu, sau đó tôi có thể ném nó trở lại vào Thùng rác.
Frank Krueger

2
Vâng, trong trường hợp đó, hack đi. :-)
Laurence Gonsalves

3
Điều này không cung cấp một câu trả lời cho câu hỏi. Để phê bình hoặc yêu cầu làm rõ từ một tác giả, hãy để lại nhận xét bên dưới bài đăng của họ.
Mureinik

@Mureinik - Nó trả lời câu hỏi, với từ "bạn có thể sử dụng sự phản chiếu". Nó thiếu một ví dụ hoặc bất kỳ lời giải thích lớn hơn hoặc làm thế nào, nhưng nó là một câu trả lời. Downvote nó nếu bạn không thích nó.
ArtOfWarfare


3

Bạn cần làm như sau:

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

Nếu sử dụng Spring, ReflectionTestUtils cung cấp một số công cụ hữu ích giúp đỡ ở đây với nỗ lực tối thiểu. Nó được mô tả là "để sử dụng trong các kịch bản thử nghiệm đơn vị và tích hợp" . Ngoài ra còn có một lớp tương tự có tên ReflectionUtils nhưng điều này được mô tả là "Chỉ dành cho sử dụng nội bộ" - xem câu trả lời này để được giải thích ý nghĩa của điều này.

Để giải quyết ví dụ được đăng:

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

1
Nếu bạn quyết định sử dụng lớp Utils vào mùa xuân, bạn thực sự nên sử dụng lớp không thử nghiệm ( docs.spring.io/spring-framework/docs/cản/javadoc-api/org/ế ) thay vào đó, trừ khi, tất nhiên, bạn 'Đang thực sự sử dụng nó cho các bài kiểm tra đơn vị.
Đồng bộ hóa

Có thể, mặc dù lớp đó được mô tả là "Chỉ dành cho sử dụng nội bộ". Tôi đã thêm một số thông tin để trả lời về điều này. (Đã giữ ví dụ sử dụng ReflectionTestUtilsnhư trong kinh nghiệm của tôi, tôi chỉ cần làm điều này trong tình huống thử nghiệm.)
Steve Chambers

1

Chỉ là một lưu ý bổ sung về sự phản chiếu: Tôi đã quan sát thấy trong một số trường hợp đặc biệt, khi một số lớp có cùng tên tồn tại trong các gói khác nhau, sự phản chiếu như được sử dụng trong câu trả lời trên cùng có thể không chọn đúng lớp từ đối tượng. Vì vậy, nếu bạn biết gói. Class của đối tượng là gì, thì tốt hơn là truy cập các giá trị trường riêng của nó như sau:

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);

(Đây là lớp ví dụ không hoạt động với tôi)


1

Bạn có thể sử dụng @JailBreak của Manifold để phản ánh Java trực tiếp, an toàn kiểu:

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

public class Foo {
    private String stuffIWant;
}

@JailBreakmở khóa foobiến cục bộ trong trình biên dịch để truy cập trực tiếp vào tất cả các thành viên trong Foohệ thống phân cấp.

Tương tự, bạn có thể sử dụng phương thức mở rộng jailbreak () để sử dụng một lần:

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

Thông qua jailbreak()phương pháp bạn có thể truy cập bất kỳ thành viên nào trong Foohệ thống phân cấp.

Trong cả hai trường hợp, trình biên dịch sẽ giải quyết quyền truy cập trường cho bạn - một cách an toàn, như thể một trường công khai, trong khi Manifold tạo mã phản chiếu hiệu quả cho bạn dưới mui xe.

Khám phá thêm về Manifold .


1

Nó khá dễ dàng với công cụ XrayInterface . Chỉ cần xác định các getters / setters bị thiếu, ví dụ

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

và xray dự án thiết kế nghèo nàn của bạn:

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

Trong nội bộ này dựa vào sự phản ánh.


0

Hãy thử giải quyết vấn đề cho trường hợp, tập hợp mà bạn muốn đặt / nhận dữ liệu là một trong các lớp của riêng bạn.

Chỉ cần tạo một public setter(Field f, Object value)public Object getter(Field f)cho điều đó. Bạn thậm chí có thể thực hiện một số kiểm tra mô hình riêng của bạn bên trong các chức năng thành viên này. Ví dụ: cho setter:

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);
    }
}

Tất nhiên, bây giờ bạn phải tìm ra java.lang.reflect.Fieldcho sStringtrước khi thiết lập các giá trị trường.

Tôi sử dụng kỹ thuật này trong một Trình lập bản đồ Kết quả chung và mô hình.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.