Можно ли увидеть, через какой дочерний класс был статический метод родителя, вызываемый в Java? - PullRequest
3 голосов
/ 07 декабря 2009

Сначала немного фона. Я изучаю возможность реализации ActiveRecord Руби в 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();
  }
}

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

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

Это вообще возможно в Java или должно быть сделано что-то вроде groovy / ruby ​​/ python?

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

Ответы [ 7 ]

3 голосов
/ 07 декабря 2009

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

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

1 голос
/ 07 декабря 2009

Как уже отмечали другие, я не думаю, что проблема решаема в 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 будет означать, что возвращаемый тип поиска должен быть ActiveRecord, а не конкретный тип записи, поэтому вам, вероятно, придется привести его к правильный тип по возвращении из звонка. Единственный способ победить это - иметь явное переопределение поиска в каждом объекте записи.

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

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

Вместо:

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

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

0 голосов
/ 04 августа 2010

Полагаю, вы хотите реализовать 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.

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

Наслаждайтесь,

Игорь

0 голосов
/ 07 декабря 2009

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

A example = new B(B.class);

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

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

Thread.currentThread().getStackTrace()

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

0 голосов
/ 07 декабря 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);
   }
}

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

0 голосов
/ 07 декабря 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.

0 голосов
/ 07 декабря 2009

Вы не будете делать это на 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
    }
}
...