Java 反射 - 访问受保护的字段

Posted

技术标签:

【中文标题】Java 反射 - 访问受保护的字段【英文标题】:Java reflection - access protected field 【发布时间】:2010-10-18 15:01:21 【问题描述】:

如何通过反射从对象访问继承的受保护字段?

【问题讨论】:

如果你说出你尝试了什么(准确地)和发生了什么(准确地),这个问题会更好。 【参考方案1】:

使用此实用程序:

import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Stream;

import static java.lang.String.format;

public final class ReflectionUtils 

    private ReflectionUtils()  

    private static final String GETTER_PREFIX = "get";
    private static final String SETTER_PREFIX = "set";

    /**
     * Get name of getter
     *
     * @param fieldName fieldName
     * @return getter name
     */
    public static String getterByFieldName(String fieldName) 
        if (isStringNullOrEmpty(fieldName))
            return null;

        return convertFieldByAddingPrefix(fieldName, GETTER_PREFIX);
    

    /**
     * Get name of setter
     *
     * @param fieldName fieldName
     * @return setter name
     */
    public static String setterByFieldName(String fieldName) 
        if (isStringNullOrEmpty(fieldName))
            return null;

        return convertFieldByAddingPrefix(fieldName, SETTER_PREFIX);
    

    /**
     * Get the contents of the field with any access modifier
     *
     * @param obj obj
     * @param fieldName fieldName
     * @return content of field
     */
    public static Object getFieldContent(Object obj, String fieldName) 
        if (!isValidParams(obj, fieldName))
            return null;

        try 
            Field declaredField = getFieldAccessible(obj, fieldName);
            return declaredField.get(obj);
         catch (IllegalAccessException e) 
            throw new IllegalArgumentException("Cannot get field content for field name: " + fieldName, e);
        
    

    /**
     * @param clazz clazz
     * @param fieldName fieldName
     * @return content static field
     */
    public static Object getStaticFieldContent(final Class<?> clazz, final String fieldName) 
        try 
            Field field = getFieldWithCheck(clazz, fieldName);
            field.setAccessible(true);
            return field.get(clazz);
         catch (Exception e) 
            String exceptionMsg = format("Cannot find or get static field: '%s' from class: '%s'", fieldName, clazz);
            throw new RuntimeException(exceptionMsg, e);
        
    

    /**
     * Set the contents to the field with any access modifier
     *
     * @param obj obj
     * @param fieldName fieldName
     * @param value value
     */
    public static void setFieldContent(Object obj, String fieldName, Object value) 
        if (!isValidParams(obj, fieldName))
            return;

        try 
            Field declaredField = getFieldAccessible(obj, fieldName);
            declaredField.set(obj, value);
         catch (IllegalAccessException e) 
            throw new IllegalArgumentException("Cannot set field content for field name: " + fieldName, e);
        
    

    /**
     * Call a method with any access modifier
     *
     * @param obj obj
     * @param methodName methodName
     * @return result of method
     */
    public static Object callMethod(Object obj, String methodName) 
        if (!isValidParams(obj, methodName))
            return null;

        try 
            Method method = obj.getClass().getMethod(methodName);
            method.setAccessible(true);
            return method.invoke(obj);
         catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) 
            throw new IllegalArgumentException("Cannot invoke method name: " + methodName, e);
        
    

    /**
     * Get all fields even from parent
     *
     * @param clazz clazz
     * @return array of fields
     */
    public static Field[] getAllFields(Class<?> clazz) 
        if (clazz == null) return null;

        List<Field> fields = new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()));
        if (clazz.getSuperclass() != null) 
            // danger! Recursion
            fields.addAll(Arrays.asList(getAllFields(clazz.getSuperclass())));
        
        return fields.toArray(new Field[] );
    

    /**
     * Get the Field from Object even from parent
     *
     * @param obj obj
     * @param fieldName fieldName
     * @return @code Optional
     */
    public static Optional<Field> getField(Object obj, String fieldName) 
        if (!isValidParams(obj, fieldName))
            return Optional.empty();

        Class<?> clazz = obj.getClass();
        return getField(clazz, fieldName);
    

    /**
     * Get the Field from Class even from parent
     *
     * @param clazz clazz
     * @param fieldName fieldName
     * @return @code Optional
     */
    public static Optional<Field> getField(Class<?> clazz, String fieldName) 
        if (!isValidParams(clazz, fieldName))
            return Optional.empty();

        Field[] fields = getAllFields(clazz);
        return Stream.of(fields)
                .filter(x -> x.getName().equals(fieldName))
                .findFirst();
    

    /**
     * @param clazz clazz
     * @param fieldName fieldName
     * @return Class
     */
    public static Class<?> getFieldType(Class<?> clazz, String fieldName) 
        return getFieldWithCheck(clazz, fieldName).getType();
    

    /**
     * @param clazz clazz
     * @param fieldName fieldName
     * @return Field
     */
    public static Field getFieldWithCheck(Class<?> clazz, String fieldName) 
        return ReflectionUtils.getField(clazz, fieldName)
                .orElseThrow(() -> 
                    String msg = String.format("Cannot find field name: '%s' from class: '%s'", fieldName, clazz);
                    return new IllegalArgumentException(msg);
                );
    

    /**
     * Get the field values with the types already listed according to the field type
     *
     * @param clazz clazz
     * @param fieldName fieldName
     * @param fieldValue fieldValue
     * @return value cast to specific field type
     */
    public static Object castFieldValueByClass(Class<?> clazz, String fieldName, Object fieldValue) 
        Field field = getField(clazz, fieldName)
                .orElseThrow(() -> new IllegalArgumentException(String.format("Cannot find field by name: '%s'", fieldName)));

        Class<?> fieldType = field.getType();

        return castFieldValueByType(fieldType, fieldValue);
    

    /**
     * @param fieldType fieldType
     * @param fieldValue fieldValue
     * @return casted value
     */
    public static Object castFieldValueByType(Class<?> fieldType, Object fieldValue) 
        if (fieldType.isAssignableFrom(Boolean.class)) 
            if (fieldValue instanceof String) 
                return convertStringToBoolean((String) fieldValue);
            
            if (fieldValue instanceof Number) 
                return !(fieldValue).equals(0);
            
            return fieldValue;
        

        else if (fieldType.isAssignableFrom(Double.class)) 
            if (fieldValue instanceof String) 
                return Double.valueOf((String)fieldValue);
            
            return ((Number) fieldValue).doubleValue();
        

        else if (fieldType.isAssignableFrom(Long.class)) 
            if (fieldValue instanceof String) 
                return Long.valueOf((String)fieldValue);
            
            return ((Number) fieldValue).longValue();
        

        else if (fieldType.isAssignableFrom(Float.class)) 
            if (fieldValue instanceof String) 
                return Float.valueOf((String)fieldValue);
            
            return ((Number) fieldValue).floatValue();
        

        else if (fieldType.isAssignableFrom(Integer.class)) 
            if (fieldValue instanceof String) 
                return Integer.valueOf((String)fieldValue);
            
            return ((Number) fieldValue).intValue();
        

        else if (fieldType.isAssignableFrom(Short.class)) 
            if (fieldValue instanceof String) 
                return Short.valueOf((String)fieldValue);
            
            return ((Number) fieldValue).shortValue();
        

        return fieldValue;
    

    private static boolean convertStringToBoolean(String s) 
        String trim = s.trim();
        return !trim.equals("") && !trim.equals("0") && !trim.toLowerCase().equals("false");
    

    private static boolean isValidParams(Object obj, String param) 
        return (obj != null && !isStringNullOrEmpty(param));
    

    private static boolean isStringNullOrEmpty(String fieldName) 
        return fieldName == null || fieldName.trim().length() == 0;
    

    private static Field getFieldAccessible(Object obj, String fieldName) 
        Optional<Field> optionalField = getField(obj, fieldName);
        return optionalField
                .map(el -> 
                    el.setAccessible(true);
                    return el;
                )
                .orElseThrow(() -> new IllegalArgumentException("Cannot find field name: " + fieldName));
    

    private static String convertFieldByAddingPrefix(String fieldName, String prefix) 
        return prefix + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    

【讨论】:

【参考方案2】:

如果使用 Spring,ReflectionTestUtils 提供了一些方便的工具,可以帮助您轻松解决问题。

例如,要获取已知为int 的受保护字段值:

int theIntValue = (int)ReflectionTestUtils.getField(theClass, "theProtectedIntField");

或者设置该字段的值:

ReflectionTestUtils.setField(theClass, "theProtectedIntField", theIntValue);

【讨论】:

【参考方案3】:

使用来自Apache Commons lang3 的FieldUtils.writeField(object, "fieldname", value, true)readField(object, "fieldname", true)

【讨论】:

【参考方案4】:

如果您只是获得受保护的字段

Field protectedfield = Myclazz.class.getSuperclass().getDeclaredField("num");

如果您使用的是 Eclipse,Ctrl + Space 将在您键入“.”时显示方法列表。在对象之后

【讨论】:

【参考方案5】:

您可能遇到的两个问题 - 该字段可能无法正常访问(私有),并且它不在您正在查看的类中,而是在层次结构的某个位置。

即使遇到这些问题,这样的方法也可以:

public class SomeExample 

  public static void main(String[] args) throws Exception
    Object myObj = new SomeDerivedClass(1234);
    Class myClass = myObj.getClass();
    Field myField = getField(myClass, "value");
    myField.setAccessible(true); //required if field is not normally accessible
    System.out.println("value: " + myField.get(myObj));
  

  private static Field getField(Class clazz, String fieldName)
        throws NoSuchFieldException 
    try 
      return clazz.getDeclaredField(fieldName);
     catch (NoSuchFieldException e) 
      Class superClass = clazz.getSuperclass();
      if (superClass == null) 
        throw e;
       else 
        return getField(superClass, fieldName);
      
    
  


class SomeBaseClass 
  private Integer value;

  SomeBaseClass(Integer value) 
    this.value = value;
  


class SomeDerivedClass extends SomeBaseClass 
  SomeDerivedClass(Integer value) 
    super(value);
  

【讨论】:

如果有安全管理器,这将失败。您需要将对 setAccessible 和 getDeclaredField 的调用封装在一个 PrivilegedAction 中,并通过 java.security.AccessController.doPrivileged(...) 运行它 我全心全意地爱你 这在 android 上不起作用 :( 我的类树看起来像这样 A->B->C 并且在 CI 内部无法获取在 A 中声明的受保护字段的值。注意所有三个类都在不同的包。任何想法如何解决这个问题?【参考方案6】:

一种通用的实用方法,用于在此或任何超类中运行任何 getter。

改编自Marius's答案。

public static Object RunGetter(String fieldname, Object o)
    Object result = null;
    boolean found = false;
    //Search this and all superclasses:
    for (Class<?> clas = o.getClass(); clas != null; clas = clas.getSuperclass())
        if(found)
           break;
        
        //Find the correct method:
        for (Method method : clas.getDeclaredMethods())
            if(found)
                break;
            
            //Method found:
            if ((method.getName().startsWith("get")) && (method.getName().length() == (fieldname.length() + 3))) 
                if (method.getName().toLowerCase().endsWith(fieldname.toLowerCase()))                            
                    try
                        result = method.invoke(o);  //Invoke Getter:
                        found = true;
                     catch (IllegalAccessException | InvocationTargetException ex)
                        Logger.getLogger("").log(Level.SEVERE, "Could not determine method: " + method.getName(), ex);
                    
                
            
        
    
    return result;

希望这对某人有用。

【讨论】:

【参考方案7】:

我不想拖入更多库,所以我制作了一个适合我的纯库。它是 jweyrich 方法之一的扩展:

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Date;
import java.util.Random;
import java.util.UUID;

public abstract class POJOFiller 

    static final Random random = new Random();

    public static void fillObject(Object ob) 
        Class<? extends Object> clazz = ob.getClass();

        do 
            Field[] fields = clazz.getDeclaredFields();
            fillForFields(ob, fields);

            if (clazz.getSuperclass() == null) 
                return;
            
            clazz = clazz.getSuperclass();

         while (true);

    

    private static void fillForFields(Object ob, Field[] fields) 
        for (Field field : fields) 
            field.setAccessible(true);

            if(Modifier.isFinal(field.getModifiers())) 
                continue;
            

            try 
                field.set(ob, generateRandomValue(field.getType()));
             catch (IllegalArgumentException | IllegalAccessException e) 
                throw new IllegalStateException(e);
            
        
    

    static Object generateRandomValue(Class<?> fieldType) 
        if (fieldType.equals(String.class)) 
            return UUID.randomUUID().toString();
         else if (Date.class.isAssignableFrom(fieldType)) 
            return new Date(System.currentTimeMillis());
         else if (Number.class.isAssignableFrom(fieldType)) 
            return random.nextInt(Byte.MAX_VALUE) + 1;
         else if (fieldType.equals(Integer.TYPE)) 
            return random.nextInt();
         else if (fieldType.equals(Long.TYPE)) 
            return random.nextInt();
         else if (Enum.class.isAssignableFrom(fieldType)) 
            Object[] enumValues = fieldType.getEnumConstants();
            return enumValues[random.nextInt(enumValues.length)];
         else if(fieldType.equals(Integer[].class)) 
            return new Integer[] random.nextInt(), random.nextInt();
        
        else 
            throw new IllegalArgumentException("Cannot generate for " + fieldType);
        
    


【讨论】:

【参考方案8】:

使用反射来访问类实例的成员,使它们可访问并设置它们各自的值。当然,您必须知道要更改的每个成员的名称,但我想这不会有问题。

public class ReflectionUtil 
    public static Field getField(Class clazz, String fieldName) throws NoSuchFieldException 
        try 
            return clazz.getDeclaredField(fieldName);
         catch (NoSuchFieldException e) 
            Class superClass = clazz.getSuperclass();
            if (superClass == null) 
                throw e;
             else 
                return getField(superClass, fieldName);
            
        
    
    public static void makeAccessible(Field field) 
        if (!Modifier.isPublic(field.getModifiers()) ||
            !Modifier.isPublic(field.getDeclaringClass().getModifiers()))
        
            field.setAccessible(true);
        
    


public class Application 
    public static void main(String[] args) throws Exception 
        KalaGameState obj = new KalaGameState();
        Field field = ReflectionUtil.getField(obj.getClass(), 'turn');
        ReflectionUtil.makeAccessible(field);
        field.setInt(obj, 666);
        System.out.println("turn is " + field.get(obj));
    

【讨论】:

【参考方案9】:

你可以这样做......

Class clazz = Class.forName("SuperclassObject");

Field fields[] = clazz.getDeclaredFields();

for (Field field : fields) 
    if (field.getName().equals("fieldImLookingFor")) 
        field.set...() // ... should be the type, eg. setDouble(12.34);
    

您可能还需要更改可访问性,如 Maurice 的回答中所述。

【讨论】:

返回一个 Field 对象数组,反映由此 Class 对象表示的类或接口声明的所有字段。它没有通过超类 您能具体说明一下这个问题吗?你是说它只获得了你的子类中的字段?您是否确保 Class.forName() 方法正在传递超类的名称,并且按照 Maurice 的建议修改了可访问性?【参考方案10】:
field = myclass.getDeclaredField("myname");
field.setAccessible(true);
field.set(myinstance, newvalue);

【讨论】:

如果有安全管理器阻止相关权限,这可能不起作用。 getField(String name) 只获取公共字段。 感谢尝试,但作为 Javadoc(我尝试过),它返回一个 Field 对象,该对象反映了该 Class 对象表示的类或接口的指定声明字段。它不经过超类。 对不起,不清楚你没有 new 在哪个类中声明了该字段【参考方案11】:

您可能是指来自不同对象的带有SecurityManager 集的不受信任的上下文?那会破坏类型系统,所以你不能。在受信任的上下文中,您可以调用 setAccessible 来击败类型系统。理想情况下,不要使用反射。

【讨论】:

“理想情况下,不要使用反射。”为什么? OP 专门尝试使用反射......虽然他没有说明原因,但反射有许多合法用途(尤其是在测试代码中)。 虽然反射有合法的用途,但大多数不是。特别是,如果您正在考虑尊重 Java (1.0) 语言访问规则的遗留能力,您可能不需要它。 @Tom ...除非您尝试编写特定的 JUnit 测试用例而不放松测试用例的访问规则 单元测试很“有趣”。您可能会争辩说它迫使您的代码更清洁(或者不必要地通用)。测试不一定遵循好代码的常规规则。

以上是关于Java 反射 - 访问受保护的字段的主要内容,如果未能解决你的问题,请参考以下文章

使用 Java 反射访问测试用例中的受保护方法

Java:从内部类访问受保护的字段

Java - 子类无法访问受保护的字段? [复制]

从继承的受保护的 Java 字段创建公共访问器

可以通过反射类访问的公共,私有,受保护类有啥用? [复制]

Java:访问修饰符比“受保护”更严格,比“私人”限制更少?