首先是一点背景。我正在研究尽可能干净简洁地在Java中实现Ruby的ActiveRecord的可能性。为此,我需要允许以下类型的方法调用:
Person person = Person.find("name", "Mike");
哪个会解决为:
ActiveRecord.find(Person.class, "name", "Mike");
计划是让Person扩展ActiveRecord,它将具有带有两个参数(列,值)的静态find方法。这个方法需要知道它是通过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进行加载时或编译时编织。这将涉及在A中的testMethod()上放置一个调用拦截器,并找出用于调用它的签名。我完全是为了加载时间编织,但设置它(通过VM args)的设置有点复杂。
有更简单的解决方案吗?
这在java中是否完全可能,或者需要在groovy / ruby / python中完成?
使用像ActiveRecord.find这样的静态负载和Person.save实例的方法会更好吗?
答案 0 :(得分:3)
您不能在Java中覆盖静态方法,因此通过子类对静态方法的任何调用都将在编译时绑定到基类。因此,在运行应用程序之前,对B.testMethod()的调用将绑定到A.testMethod。
由于您在运行时查找信息,因此无法通过普通的Java操作来使用它。
答案 1 :(得分:1)
正如其他人所指出的那样,我认为这个问题在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而不是特定的记录类型,所以你可能需要将它转换为从通话返回时的正确类型。击败它的唯一方法是在每个记录对象中明确覆盖查找。
我已经有很多次写过一些通用的记录处理代码,但我总是避免为每种记录类型创建Java对象,因为这总是会变成编写一大堆代码。我更喜欢保持Record对象完全通用,并且具有字段名称,索引,无论是内部数据和名称。如果我想从“bar”记录中检索“foo”字段,我的界面将如下所示:
Record bar=Record.get(key);
String foo=bar.get("foo");
而不是:
BarRecord bar=BarRecord.get(key);
String foo=bar.getFoo();
不那么漂亮,它限制了编译时错误检查,但实现的代码却少了。
答案 2 :(得分:0)
你不会在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
}
}
答案 3 :(得分:0)
这是可能的,但它很昂贵。
如果你能找到一种只调用一次的方法,那么你就可以了。
您可以创建一个新的例外并查看第一帧,然后您就会知道是谁调用它。问题是它不是高效的。
例如使用此answer,可以创建一个这样的记录器:
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个电话。虽然我不知道一个好的VM是否可以为你内联。
我会选择AspectJ路径。
答案 4 :(得分:0)
我的相关理论是,使用代码生成策略为包含该方法的每个类创建一个委托。你不能在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);
}
}
但它往往会过多地破坏你的继承层次结构。我说只是生成类并完成它。不值得隐藏查找方法。
答案 5 :(得分:0)
你可以通过创建一个hackish构造函数来非常手动地完成它。
A example = new B(B.class);
让超类构造函数存储传递给它的类。
我不认为上面抛出的异常会起作用,但是如果你想在没有创建异常的情况下做这样的事情......
Thread.currentThread().getStackTrace()
您可以通过元编程和 javassist 更顺畅地完成这项工作。
答案 6 :(得分:0)
我想你想在Java中实现ActiveRecord。当我决定这样做时,我遇到了同样的问题。这对Java来说很难,但我能够克服它。 我最近在这里发布了名为ActiveJDBC的整个框架: http://code.google.com/p/activejdbc/
如果有兴趣,您可以查看来源以了解其实施方式。查看Model.getClassName()方法。
这就是我解决从静态方法获取类名的方法。第二个问题是实际上将所有静态方法从超类移动到子类(毕竟这是一种笨重的继承形式!)。我使用Javassist。这两个解决方案允许我完全用Java实现ActiveRecord。 字节码操作最初是在加载类时动态完成的,但我在Glassfish和Weblogic中遇到了一些类加载问题,并决定实现静态字节码操作。这是通过http:activejdbc.googlecode.com/svn/trunk/activejdbc-instrumentation/ Maven插件完成的。
我希望这能为您的问题提供详尽的答案。
享受,
伊戈尔