Отражение Java - доступ к защищенному полю

Как я могу получить доступ к унаследованному защищенному полю от объекта путем отражения?


person Moro    schedule 09.04.2009    source источник
comment
Вопрос был бы лучше, если бы вы указали, что вы пытались (точно) и что получилось (точно).   -  person Tom Hawtin - tackline    schedule 09.04.2009


Ответы (11)


Две проблемы, с которыми у вас могут возникнуть проблемы: поле может быть недоступно в обычном режиме (частное) и оно не в классе, на который вы смотрите, а где-то вверх по иерархии.

Что-то вроде этого будет работать даже с этими проблемами:

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);
  }
}
person kenj0418    schedule 09.04.2009
comment
Если есть менеджер безопасности, это не удастся. Вам нужно обернуть вызов setAccessible и getDeclaredField в PriviledgedAction и запустить его через java.security.AccessController.doPrivileged(...) - person Varkhan; 09.04.2009
comment
Я люблю Вас всем моим сердцем - person Or Gal; 21.05.2013
comment
Это не работает на Android :( Мое дерево классов выглядит так: A->B->C, и внутри CI не может получить значение защищенного поля, объявленного в A. Обратите внимание, что все три класса находятся в разных пакетах. Любые идеи, как получить вокруг этого? - person Moonwalker; 19.02.2015

Используйте FieldUtils.writeField(object, "fieldname", value, true) или readField(object, "fieldname", true) из Apache Commons язык3.

person mrts    schedule 22.09.2016

Используйте отражение, чтобы получить доступ к членам экземпляра класса, сделать их доступными и установить соответствующие значения. Конечно, вам нужно знать имя каждого участника, которого вы хотите изменить, но я думаю, это не будет проблемой.

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));
    }
}
person jweyrich    schedule 08.04.2010

При использовании Spring ReflectionTestUtils предоставляет несколько удобных инструментов, которые помогают с минимальными усилиями.

Например, чтобы получить значение защищенного поля, известное как int:

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

Или, альтернативно, чтобы установить значение этого поля:

ReflectionTestUtils.setField(theClass, "theProtectedIntField", theIntValue);
person Steve Chambers    schedule 18.01.2017

Я не хотел тащить больше библиотек, поэтому сделал чистую, которая работала на меня. Это расширение одного из методов от 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);
        }
    }

}
person Knubo    schedule 12.12.2012

Если вы просто получаете защищенное поле

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

Если вы используете Eclipse, Ctrl + Пробел вызовет список методов при вводе символа "." после объекта

person Ewebs    schedule 16.09.2016

используйте эту утилиту:

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);
    }
}
person FreeOnGoo    schedule 19.06.2019

Возможно, вы имеете в виду из другого объекта ненадежный контекст с набором SecurityManager? Это нарушит систему типов, так что вы не можете. Из доверенного контекста вы можете вызвать setAccessible, чтобы обойти систему типов. В идеале не используйте отражение.

person Tom Hawtin - tackline    schedule 09.04.2009
comment
В идеале не используйте отражение. Почему? ОП специально пытается использовать отражение ... хотя он не говорит, почему, есть много законных применений для отражения (особенно в тестовом коде). - person David Citron; 09.04.2009
comment
Хотя есть законные способы использования отражения, большинство из них таковыми не являются. В частности, если вы ползете вокруг устаревшей способности соблюдать правила доступа к языку Java (1.0), вам это, вероятно, не нужно. - person Tom Hawtin - tackline; 09.04.2009
comment
@Tom ... если только вы не пытаетесь написать конкретные тестовые примеры JUnit, не ослабляя правила доступа просто для тестового примера - person David Citron; 10.04.2009
comment
Модульное тестирование — это интересно. Вы можете возразить, что это заставляет ваш код быть чище (или излишне общим). Тесты не обязательно следуют обычным правилам красивого кода. - person Tom Hawtin - tackline; 10.04.2009

Вы могли бы сделать что-то вроде...

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

Вам также может потребоваться изменить доступность, как указано в ответе Мориса.

person Community    schedule 09.04.2009
comment
Возвращает массив объектов Field, отражающих все поля, объявленные классом или интерфейсом, представленным этим объектом Class. Он не прошел через суперклассы - person Moro; 09.04.2009
comment
Не могли бы вы уточнить проблему? Вы говорите, что он получил только поля в вашем подклассе? Убедились ли вы, что методу Class.forName() передается имя суперкласса и что доступность была изменена, как предложил Морис? - person Craig Otis; 09.04.2009

Общий служебный метод для запуска любого геттера в этом или любом суперклассе.

Адаптировано из ответа Мариуса.

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

Надеюсь, это кому-то пригодится.

person pds    schedule 18.10.2013

person    schedule
comment
Это может не сработать, если есть менеджер безопасности, который блокирует соответствующее разрешение. - person Miguel Ping; 09.04.2009
comment
getField (имя строки) получает только общедоступные поля. - person Moro; 09.04.2009
comment
Спасибо за попытку, но, как Javadoc (и я пробовал), он возвращает объект Field, который отражает указанное объявленное поле класса или интерфейса, представленного этим объектом класса. он не проходит через суперклассы. - person Moro; 09.04.2009
comment
Извините, не было ясно, что вы не знали, в каком классе было объявлено поле - person Maurice Perry; 09.04.2009