Можно ли увидеть, через какой дочерний класс был вызван статический метод родителя в Java?

Сначала немного предыстории. Я изучаю возможность реализовать ActiveRecord Ruby на Java настолько четко и лаконично, насколько это возможно. Для этого мне нужно разрешить следующий тип вызова метода:

Person person = Person.find("name", "Mike");

Что бы решить что-то вроде:

ActiveRecord.find(Person.class, "name", "Mike");

План состоит в том, чтобы Person расширил ActiveRecord, который имел бы статический метод поиска с двумя параметрами (столбец, значение). Этот метод должен знать, что он вызывается через Person.find, а не через другой доменный класс, такой как Car.find, и вызывать метод find(Class, String, Object) для выполнения фактической операции.

Проблема, с которой я сталкиваюсь, заключается в том, чтобы выяснить, через какой дочерний класс ActiveRecord был вызван метод статического поиска (два параметра). Ниже приведен простой тестовый пример:

public class A {
  public static void testMethod() {
    // need to know whether A.testMethod(), B.testMethod(), or C.testMethod() was called
  }
}

public class B extends A { }
public class C extends A { }

public class Runner {
  public static void main(String[] args) {
    A.testMethod();
    B.testMethod();
    C.testMethod();
  }
}

Решения, найденные до сих пор, связаны со временем загрузки или компиляции с использованием аспекта J. Это потребует размещения перехватчика вызовов в методе testMethod() в A и выяснения того, какая сигнатура использовалась для его вызова. Я полностью за плетение во время загрузки, но настройка этой настройки (через аргументы виртуальной машины) немного сложна.

Есть ли более простое решение?

Возможно ли это вообще в java или это нужно сделать в чем-то вроде groovy/ruby/python?

Будет ли в целом подход с использованием чего-то вроде ActiveRecord.find для статических нагрузок и Person.save для экземпляров?


person Gennadiy    schedule 07.12.2009    source источник
comment
Ваш вопрос должен звучать так: Возможно ли.... вызываться в Java?   -  person Marc-André Lafortune    schedule 07.12.2009
comment
Спасибо, Марк, внимание было уделено части Java.   -  person Gennadiy    schedule 07.12.2009
comment
Совершенно не связанное предложение: рассмотрите возможность использования чего-то другого вместо наследования классов. Суперклассы — дефицитный ресурс: их может быть только один. Если вы заставите меня расширить ваш ActiveRecord базовый класс, я больше не смогу расширять свой собственный базовый класс, а это значит, что я больше не смогу иерархически моделировать свой домен или выполнять рефакторинг в целях сохранения. Интерфейсов и аннотаций OTOH много: рассмотрите возможность использования одного из них. (Или миксины, если вы все равно идете по маршруту плетения байт-кода.)   -  person Jörg W Mittag    schedule 07.12.2009
comment
Это довольно раздражающее ограничение в Rails ActiveRecord и одна из основных причин, почему Ruby ORM, появившиеся после ActiveRecord, решили вместо этого использовать примеси, потому что вы можете смешивать бесконечное количество модулей, но только наследовать из одного класса.   -  person Jörg W Mittag    schedule 07.12.2009
comment
@Jorg: Почему бы вам не сделать ваш базовый класс расширением ActiveRecord, используя шаблон Layer Supertype?   -  person MattMcKnight    schedule 07.12.2009
comment
Почему вы хотите, чтобы ваш метод find() был статическим? Помимо упомянутой Робин проблемы наследования, вы резко ограничиваете использование: статические методы применяются ко всему, а не к конкретному экземпляру.   -  person CPerkins    schedule 07.12.2009
comment
@CPerkings - я пытаюсь предоставить аналогичную функциональность и синтаксис для Ruby ActiveRecord и GORM в Grails. Person.findByName(...) читается проще и лучше IMO, чем new Person().findByName(...) или ActiveRecord.findByName(Person.class,...)   -  person Gennadiy    schedule 07.12.2009


Ответы (7)


Вы не можете переопределить статические методы в Java, поэтому любые вызовы статического метода через подкласс будут привязаны к базовому классу во время компиляции. Таким образом, вызов B.testMethod() будет привязан к A.testMethod еще до запуска приложения.

Поскольку вы ищете информацию во время выполнения, она не будет доступна при обычных операциях Java.

person Robin    schedule 07.12.2009
comment
Поскольку вызов B.testMethod возможен, даже если A является реализатором, то, что я пытаюсь найти, находится внутри тела testMethod, чтобы узнать, использовался ли A.testMethod или B.testMethod. - person Gennadiy; 07.12.2009
comment
@Gennadiy - я обновил свой ответ, чтобы лучше объяснить, что происходит. - person Robin; 07.12.2009
comment
@Robin - после прочтения подробностей о языке и спецификациях виртуальных машин, боюсь, вы правы. Как я упоминаю в другом комментарии к этому вопросу, аспект J знает разницу в том, что он добавляет аспект при каждом вызове testMethod, а не в самом методе. Это дает вам доступ к сигнатуре вызова (B против A), но будет недопустимым, поскольку вам нужно будет инструментировать каждый фрагмент кода, который может вызывать метод. - person Gennadiy; 07.12.2009
comment
вы не можете переопределить статические методы в исходных кодах Java, но вы можете делать все, что хотите, в байтовом коде, включая перемещение статического метода из суперкласса в подкласс. Это именно тот метод, который я использовал в ActiveJDBC именно для этой цели: code.google.com/p/activejdbc. - person ipolevoy; 19.11.2010

Как отмечали другие, я не думаю, что проблема разрешима в Java, как вы ее ставите. Статический метод на самом деле не наследуется так же, как нестатический метод. (Извините, если я не совсем правильно использую терминологию.)

Тем не менее, мне кажется, есть много способов добиться желаемого результата, если вы готовы немного изменить свой интерфейс.

Наиболее очевидным было бы просто сделать вызов, используя родительский класс. Что плохого в написании

Person person=(Person)ActiveRecord.find(Person.class, "name", "Mike");

?

В качестве альтернативы вы можете сначала создать экземпляр типа записи, а затем выполнить поиск, чтобы заполнить его. Например

Person person=new Person();
person.find("name", "Mike");

В этот момент у вас есть объект Person, и если вам нужно узнать его класс из функции в супертипе, вы просто делаете "this.getClass()".

В качестве альтернативы вы можете создать фиктивный объект Person для выполнения вызовов, просто чтобы позволить вам выполнять getClass() при необходимости. Тогда ваша находка будет выглядеть примерно так:

Person dummyPerson=new Person();
Person realPerson=dummyPerson.find("name", "Mike");

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

Я много раз писал общий код для обработки записей, но я всегда избегал создания объектов Java для каждого типа записей, потому что это неизменно оборачивалось написанием целой кучи кода. Я предпочитаю, чтобы объект Record был полностью универсальным и имел имена полей, индексы, что бы ни были внутренние данные и имена. Если я хочу получить поле «foo» из записи «bar», мой интерфейс будет выглядеть примерно так:

Record bar=Record.get(key);
String foo=bar.get("foo");

Скорее, чем:

BarRecord bar=BarRecord.get(key);
String foo=bar.getFoo();

Не так красиво, и это ограничивает проверку ошибок во время компиляции, но для реализации требуется меньше кода.

person Jay    schedule 07.12.2009
comment
Джей, хотя эта проблема сложна, она решаема, как доказывает моя структура, см. ссылку в моем ответе. - person ipolevoy; 02.03.2011

Вы бы не сделали этого в Java. Вы, вероятно, сделали бы что-то более похожее на:

public interface Finder<T, RT, CT>
{
    T find(RT colName, CT value);
}

public class PersonFinder
    implements Finder<Person, String, String>   
{
    public Person find(String nameCol, String name)
    {
        // code to find a person
    }
}

public class CarFinder
    implements Finder<Car, String, int>   
{
    public Person find(String yearCol, int year)
    {
        // code to find a car
    }
}
person TofuBeer    schedule 07.12.2009
comment
Это лишило бы смысла иметь единую реализацию метода find. Это также заставило бы меня помещать делегаты ActiveRecord.find в каждый доменный класс, делая их намного более громоздкими, чем их аналоги в других языках. - person Gennadiy; 07.12.2009
comment
Как бы то ни было... то, что вы хотите сделать, не так, как в Java. Не все языки поддерживают один и тот же способ ведения дел. Также я бы вообще не использовал статический метод... опять же, это не естественный способ сделать это в Java. - person TofuBeer; 07.12.2009

Это возможно, но это дорого.

Если вы можете найти способ вызвать его только один раз, тогда все готово.

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

Например, с этим ответ можно создать такой регистратор:

 class MyClass {
      private static final SomeLogger logger = SomeLogger.getLogger();
      ....
  }

И пусть этот регистратор создаст другой экземпляр в зависимости от того, кто его вызвал.

Итак, таким же образом у вас может быть что-то вроде:

 class A  {
      public static void myStatic() {
          // find out who call it
          String calledFrom = new RuntimeException()
                              .getStackTrace()[1].getClassName();
      }
 }

Это нормально для одноразовой инициализации. Но не на 1000 звонков. Хотя я не знаю, может ли хорошая виртуальная машина встроить это для вас.

Я бы пошел по пути AspectJ.

person Community    schedule 07.12.2009
comment
Я попробовал этот маршрут, используя тест JUnit. Я вижу, кто вызывал testMethod, но не видел, был ли он вызван с использованием подписи A.testMethod() против B.testMethod(). Я думаю, это может быть связано с тем, что компилятор на самом деле избавился от этой информации, но опять же, аспект J каким-то образом знает разницу. - person Gennadiy; 07.12.2009
comment
это неправильно, так как myStatic() всегда будет связан с классом A (просто попробуйте это в классе Runner, определенном в вопросе) - person dfa; 07.12.2009
comment
@дфа. Я узнал, что делает аспект J. Перехват вызова, в отличие от вызова, придает аспект каждому вызову testMethod(), а не самой реализации testMethod(). Это позволяет вам получить доступ к сигнатуре вызова, таким образом, вы можете определить разницу между B.testMethod() и A.testMethod(). - person Gennadiy; 07.12.2009

Моя теория по этому поводу, создав нечто подобное, заключается в использовании стратегии генерации кода для создания делегата для каждого класса, содержащего метод. У вас не может быть столько скрытого кода в Java, это, вероятно, не стоит усилий, пока вы генерируете что-то разумное. Если вы действительно хотите скрыть это, вы можете сделать что-то вроде...

public class Person extends PersonActiveRecord
{

}

//generated class, do not touch
public class PersonActiveRecord extends ActiveRecord
{
   public Person find(Map params)
   {
      ActiveRecord.find(Person.class, params);
   }
}

Но это имеет тенденцию слишком сильно портить вашу иерархию наследования. Я говорю, просто сгенерируйте классы и покончим с этим. Не стоит скрывать метод find.

person MattMcKnight    schedule 07.12.2009

Вы можете сделать это очень вручную, создав хакерский конструктор.

A example = new B(B.class);

И пусть конструктор суперкласса хранит переданный ему класс.

Я не думаю, что созданное выше исключение сработает, но если вы когда-нибудь захотите сделать что-то подобное, не создавая исключения...

Thread.currentThread().getStackTrace()

Возможно, вам удастся сделать это намного проще с помощью метапрограммирования и javassist.

person Dean J    schedule 07.12.2009

Я полагаю, вы хотите реализовать ActiveRecord на Java. Когда я решил сделать то же самое, я столкнулся с той же проблемой. Это сложно для Java, но я смог его преодолеть. Недавно я выпустил всю структуру под названием ActiveJDBC здесь: http://code.google.com/p/activejdbc/

Если интересно, вы можете просмотреть источники, чтобы увидеть, как это было реализовано. Посмотрите на метод Model.getClassName().

Вот как я решил получить имя класса из статического метода. Вторая проблема заключалась в том, чтобы фактически переместить все статические методы из суперкласса в подклассы (в конце концов, это неуклюжая форма наследования!). Я использовал Javassist для этого. Эти два решения позволили мне полностью реализовать ActiveRecord на Java. Первоначально манипулирование байт-кодом выполнялось динамически при загрузке классов, но я столкнулся с некоторыми проблемами загрузки классов в Glassfish и Weblogic и решил реализовать статическое манипулирование байт-кодом. Это делается с помощью http: activejdbc.googlecode.com/svn/trunk/activejdbc-instrumentation/ подключаемого модуля Maven.

Я надеюсь, что это дает исчерпывающий ответ на ваш вопрос.

Наслаждаться,

Игорь

person ipolevoy    schedule 03.08.2010
comment
Спасибо за комментарий Игорь. Мы должны поговорить, так как у меня установлена ​​версия 0.3.1 code.google.com/p/ar4j В конце концов я решил, что записи реализуют, а не расширяют активную запись прототипа, и большая часть управления осуществляется через Spring. Думаю, в этом и будет главное отличие наших проектов. - person Gennadiy; 04.08.2010
comment
Геннадий, конечно, если проекты разные, то и аудитория для них будет разная. У меня отвращение к весне, я стараюсь избегать ее, как чумы. ActiveJDBC является автономным проектом и не имеет зависимостей - person ipolevoy; 19.11.2010