August
2009
Reflection API в Java используется для просмотра информации о классах, интерфейсах, методах, полях, конструкторах, аннотациях во время выполнения java программ.
При этом знать названия исследуемых элементов заранее не обязательно.
Все классы для работы с reflection расположены в пакете java.lang.reflect. Это метод (Method), конструктор (Constructor), массив (Array), поле (Field) и многие другие.
Вместо изучения каждого из этих элементов в отдельности, для знакомства с reflection я предлагаю вам небольшой пример. Мы попытаемся в runtime вывести всю возможную информацию о следующем java-классе:
package some;
public abstract class Test implements Serializable, Cloneable {
private int field;
public Test(Object field) { }
@Deprecated
protected static void method(String[] params) { }
}
Итак, приступим.
Способы получения необходимой нам информации в некоторых местах я упростил, чтобы пример не становился слишком большим. Чуть ниже я прокомментирую некоторые фрагменты кода, на которые хотел бы обратить ваше внимание.
package some;
import java.lang.reflect.*;
import java.lang.annotation.Annotation;
import java.io.Serializable;
public abstract class Test implements Serializable, Cloneable {
private int field;
public Test(Object field) { }
@Deprecated
protected static void method(String[] params) { }
}
class Reflect {
public static void main(String[] args) {
Class clazz = Test.class;
// выводим название пакета
Package p = clazz.getPackage();
System.out.println("package " + p.getName() + ";");
// начинаем декларацию класса с модификаторов
int modifiers = clazz.getModifiers();
System.out.print(getModifiers(modifiers));
// выводим название класса
System.out.print("class " + clazz.getSimpleName() + " ");
// выводим название родительского класса
System.out.print("extends " + clazz.getSuperclass().getSimpleName() + " ");
// выводим интерфейсы, которые реализует класс
Class[] interfaces = clazz.getInterfaces();
for (int i = 0, size = interfaces.length; i < size; i++) {
System.out.print(i == 0 ? "implements " : ", ");
System.out.print(interfaces[i].getSimpleName());
}
System.out.println(" {");
// выводим поля класса
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println("\t" + getModifiers(field.getModifiers())
+ getType(field.getType()) + " " + field.getName() + ";");
}
// выводим констукторы класса
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor c : constructors) {
System.out.print("\t" + getModifiers(c.getModifiers()) +
clazz.getSimpleName() + "(");
System.out.print(getParameters(c.getParameterTypes()));
System.out.println(") { }");
}
// выводим методы класса
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
// получаем аннотации
Annotation[] annotations = m.getAnnotations();
System.out.print("\t");
for (Annotation a : annotations)
System.out.print("@" + a.annotationType().getSimpleName() + " ");
System.out.println();
System.out.print("\t" + getModifiers(m.getModifiers()) +
getType(m.getReturnType()) + " " + m.getName() + "(");
System.out.print(getParameters(m.getParameterTypes()));
System.out.println(") { }");
}
System.out.println("}");
}
static String getModifiers(int m) {
String modifiers = "";
if (Modifier.isPublic(m)) modifiers += "public ";
if (Modifier.isProtected(m)) modifiers += "protected ";
if (Modifier.isPrivate(m)) modifiers += "private ";
if (Modifier.isStatic(m)) modifiers += "static ";
if (Modifier.isAbstract(m)) modifiers += "abstract ";
return modifiers;
}
static String getType(Class clazz) {
String type = clazz.isArray()
? clazz.getComponentType().getSimpleName()
: clazz.getSimpleName();
if (clazz.isArray()) type += "[]";
return type;
}
static String getParameters(Class[] params) {
String p = "";
for (int i = 0, size = params.length; i < size; i++) {
if (i > 0) p += ", ";
p += getType(params[i]) + " param" + i;
}
return p;
}
}
В результате выполнения кода мы получим следующее:
package some;
public abstract class Test extends Object implements Serializable, Cloneable {
private int field;
public Test(Object param0) { }
@Deprecated
protected static void method(String[] param0) { }
}
Модификаторы в Java по спецификации представлены в виде целых чисел, например:
public static final int PUBLIC = 0x00000001;
Для декодирования значений модификаторов используется класс Modifiers.
Заметьте, что в классе Class присутствуют пары методов, как например getFields и getDeclaredFields. Метод getFields возвращает только те поля, которые объявлены как public + public поля родительских классов, в то время как getDeclaredFields возвращает все поля текущего класса не зависимо от их видимости. Аналогично для getMethods и getConstructors.
Типы параметров и типы возвращаемых значений могут быть массивами. В этом случае для получения типа элементов, которые можно хранить в массиве, нужно использовать метод getComponentType.
Для получения аннотаций используется метод getAnnotations. Но метод вернет не сами классы аннотаций, а Proxy. Чтобы получить сам класс аннотации используйте метод annotationType().
Если у вас будут вопросы по приведенному выше фрагменту кода, задавайте их в комментариях к статье. Сейчас же я хочу перейти к некоторым практическим аспектам, которые иногда встречаются в реальной жизни.
1. С введением аннотаций в java 1.5 стало легко добавлять метаинформацию к классам, полям, методам и параметрам. По наличию аннотации (или ее отсутствию) можно во время выполнения программы принимать различного рода решения.
Допутим, мы пишем свой модуль для сериализации объектов. Путь некоторая описанная нами аннотация @Transient говорит о том, что конкретное поле не нужно среиализовать. Нам поможет следующий фрагмент кода:
Field field = ...;
Transient transient = field.getAnnotation(Transient.class);
if (transient == null)
... // сериализуем поле
2. Работая с сервлетами часто нужно иметь возможность меппить параметры запроса на объекты.
Вот небольшая часть функционала для такого меппинга из реального проекта, в котором я принимал участие:
static Mapwrappers = new HashMap (); static { wrappers.put(Boolean.class, false); wrappers.put(Double.class, 0.0); wrappers.put(Float.class, 0.0f); wrappers.put(Character.class, (char) 0); wrappers.put(Integer.class, 0); wrappers.put(Long.class, 0L); wrappers.put(Byte.class, (byte) 0); wrappers.put(Short.class, (short) 0); } static Class wrapperFor(Class primitiveType) { for (Class wrapper : wrappers.keySet()) try { // all wrappers have static final field TYPE if (wrapper.getField(”TYPE”).get(null).equals(primitiveType)) return wrapper; } catch (IllegalAccessException impossible) { throw new RuntimeException(impossible); } catch (NoSuchFieldException impossible) { throw new RuntimeException(impossible); } return null; }
В частности, метод wrapperFor используется для определения класса-обертки для примитивных типов. Это удобно для последующего вызова метода valueOf. К сожалению, класс Character не содержит такого метода и его нужно обрабатывать особенным образом.
3. Продлжая тему о сервлетах. В веб-фреймворке, который писали мои друзья, используется следующий контракт: действие представляет собой вызов некоторого метода someMethod у некоторого класса SomeClass. При этом url имеет вид, например, somedomain.com/SomeAction.someMethod.
Вот небольшой фрагмент кода, который показывает, как можно вызвать метод у класса используя reflection. Пример очень упрощен в обучающих целях:
class SomeAction {
public void doSomething() {
System.out.println("done");
}
}
class ActionHandler {
public static void main(String[] args) {
// у нас есть коллекция действий (action)
List<Object> actions = new ArrayList<Object>() {
{ add(new SomeAction()); }
};
// а также путь запроса (request path)
String path = "/SomeAction.doSomething";
// получаем имя класса и метода
String className = path.substring(path.lastIndexOf("/") + 1,
path.indexOf("."));
String methodName = path.substring(path.indexOf(".") + 1);
// находим нужный action
Object action = null;
for (Object a : actions)
if (a.getClass().getSimpleName().equals(className))
action = a;
if (action == null) /* это равносильно HTTP ERROR 404 */;
// находим нужный метод
Method method = null;
try {
method = action.getClass().getMethod(methodName);
} catch (NoSuchMethodException e) { /* HTTP ERROR 404 */ }
// вызываем метод
try {
method.invoke(action);
} catch (IllegalAccessException e) {
// не возникнет если следовать контракту публичных методов в action
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
}
Для вызова, в метод invoke нужно передать инстанс, у которого и будет вызываться наш метод.
Обратите внимание, что InvocationTargetException оборачивает ошибку, которая может произойти в методе, вызываемом через reflection. Для получения самого исключения необходимо взять причину (cause) у инстанса InvocationTargetException.
4. Часто программистам приходится работать с чужими проприетарными библиотеками. Случается, что где-то в библиотеке значения по-умолчанию некоторых полей вас не устраивают, но изменить их через API нет возможности. Или некоторый метод закрыт модификатором доступа private.
Вместо декомпиляции-исправления-компиляции есть способ, который иногда выручает. Это установка флага acccessible. Давайте установим значение 10 для поля field класса Inaccessible:
class Inaccessible {
private int field;
public String toString() {
return field + "";
}
}
class GrantAccess {
public static void main(String[] args) throws Exception {
Inaccessible o = new Inaccessible();
System.out.println(o);
Field field = o.getClass().getDeclaredField("field");
field.setAccessible(true);
field.set(o, 10);
System.out.println(o);
}
}
Программа выведет сначала 0, а потом 10.
5. При написании своих контейнеров (на подобии IoC), вам может пригодиться способ для инстанциирования объектов с помощью default контруктров. Это можно сделать следующим образом:
Constructor constructor = ...; Object object = constructor.newInstnace();
Кстати, мой друг не так давно показал мне интересный способ инстанциирования объектов, который он обнаружил в sun’овских интерналсах. Нам даже посчастливилось воспользоваться данным способом на практике в виде такого вот метода:
static <T> T instance(Class<T> c) {
try {
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
@SuppressWarnings("unchecked")
Constructor<T> constructor = reflectionFactory.newConstructorForSerialization(c, Object.class.getConstructor());
return constructor.newInstance();
} catch (java.lang.Exception e) {
throw new RuntimeException(e);
}
}
А дело было вот в чем. Нам дали API бинарного протокола, который был написан мягко говоря плохо. Каждый transfer объект (а их около 40) содержал большое количество параметров (2-6), каждый из которых необходимо было проставлять через конструктор. Ни один из параметров на нашей стороне вообще не использовался - эти параметры заполнялись и использовались уже на стороне сервера.
Собственно метод instance очень упростил нам способ инстанциирования объектов.
Пока что все. Жду ваших комментариев.
17 Comments »
RSS feed for comments on this post.
Если вы “маппите” объекты, но наверно и процесс называется “маппинг”, а не “меппинг”. Ну да это так, придирашки.
Статья понравилась. Миниатюрная шпаргалка-cookbook по Reflection API. Не хватает только примера вызова метода.
И ещё большое спасибо за хитрый метод инстанцирования. Впервые встречаю. Понравилось ![]()
2 the.malkolm: придирашку поправил ).
2 GrAndSE: да, нужно добавить вызов метода. А то инстанциирование есть, простановка филда есть, а вызова нет.
Хорошая статья, спасибо, но хотелось бы обратить ваше внимание на параметризованные классы. о них норошо написано на хабре
habrahabr.ru/blogs/java/66593/
Статья понравилась. Java только начал постигать, поэтому особенно благодарен.
Спасибо, интересная статья. Только там в первом примере вместо “t”, нужно “\t”.
А также в фрагменте:
if (i > 0) p += “, “;
p += getType(params[i]) + ” param” + i;
нужно заменить “, на “
В смысле косые кавычки на прямые
Исправил. Это гадкий wordpress такие ужасы творит. Вот уж не думал что нужно slash и quote тоже эскейпить.
Автор статьи молодец, тема интересная! Жду новых материалов!
Доброе время суток.
Помогите разобраться в чем мои грабли. Нужно из суперкласса А скопировать в новый экземпляр В все значения полей. Пример:
class A {
private float f1;
public getf1() {return f1;}
public setf1(float f1) {this.f1=f1;}
}
class B extends A {
float fun() {return super.f1;}
public B(A a) {
this.setf1(a.getf1()); // так не желательно (очень много полей - будет некрасиво)
Class cl = A.class;
Class tcl = this.getClass();
Field[] fieldsFrom = cl.getDeclaredFields();
Field[] fieldsTo = cl.getDeclaredFields();
for (int i=0; i
Для того что бы можна было читать анотации в райнтайме нуже перед определением оной прописать
@Retention(RetentionPolicy.RUNTIME)
пример
http://www.java2s.com/Tutorial/Java/0020__Language/ObtainingAnnotationsatRunTimebyUseofReflection.htm
Статья хорошая.
Пожалуйста исправьте в предложении “Путь некоторая описанная нами аннотация @Transient говорит о том, что конкретное поле не нужно среиализовать”: “Путь” на “Пусть” и “среиализовать” на “сериализовать”.
Здраствуйте, извените что задам вопрос не по теме просто очень надо узнать как создавать класс с параметрами.
Короче у меня есть класс User к нему нужны параметры Name и Surname
вот как я написал:
public class User(String[] Name;Surname)
Интересная тема!
как раз недавно начал изучать Java по учебнику, здесь более доступно написано… еще и на примерах… спасибо
обратите внимание на метод
java.lang.reflect.Modifier.toString(int m);
создание сайта бесплатно чтобы заработать от 1305$ создание сайтов web дизайн разработка сайтов веб дизайн создание сайта http://web-miheeff.ru +7 (495) 782-72-56 создание сайта бесплатно чтобы заработать