Java: 使一个 class 的静态方法成为另一个 class 的镜像实例方法
Java: make static methods of one class mirror instance methods of another class
我有一个这样的 POJO:
public class Foo {
private String bar1;
private String bar2;
//...
public String getBar1() { return bar1; }
public void setBar1(String bar1) { this.bar1 = bar1; }
public String getBar2() { return bar2; }
public void setBar2(String bar2) { this.bar2 = bar2; }
//...
}
作为 Java 反射的替代方法(通常很慢),我想用这样的静态方法定义一个 class:
public class FooStatic {
public static String getBar1(Foo foo) { return foo.getBar1(); }
public static void setBar1(Foo foo, String bar1) { foo.setBar1(bar1); }
public static String getBar2(Foo foo) { return foo.getBar2(); }
public static void setBar2(Foo foo, String bar2) { foo.setBar2(bar2); }
//...
}
每次更新 Foo class 时都会强制执行静态方法的 creation/deprecation。例如,如果在FooStatic
中删除字段bar2
,那么FooStatic
中的静态方法getBar2()
和setBar2()
应该被编译器识别为被删除.如果使用 getter 和 setter 将新变量 bar3
添加到 Foo
,编译器应强制在 FooStatic
中创建新的静态方法 getBar3()
和 setBar3()
。此外,我有多个 POJO,并且想要一个可扩展的解决方案。这可能吗?
是的……有点。很复杂。
注释处理器是编译器插件,在编译过程中的某些时间 运行。它很快变得复杂 - 例如,IDE 和构建工具 'incremental'(当然,他们不想每次更改单个字符时都重新编译整个代码库)。
注释处理器可以做一些事情:
- 它们可以 运行 作为编译过程的一部分。这可以自动完成 - 他们只需要在 class 路径上,就是全部
- 它们可以由于注释的存在而被触发。
- 他们可以读取现有文件的签名(字段和方法的名称、参数名称、参数类型、return类型和
throws
子句,以及字段的类型,以及 extends
和 implements
子句,以及构造函数的参数名称和类型)。他们无法阅读 body 内容(初始化表达式、方法和构造函数主体)。但我认为你只需要这里的签名。
- 他们可以制作新 文件。他们甚至可以制作新的 java 文件,然后这些文件将与其他文件一起自动编译。
这样,你就有了一条路:做一个注解,然后做一个注解处理器。例如,您可以将其设置为手动写入:
@com.foo.Hossmeister.Singletonify
class Example {
void foo1() {}
String foo2(String arg) throws IOException {}
}
并有一个注释处理器(它也有那个 com.foo.Hossmeister.Singletonify
注释),如果它在 class 路径上,它会自动生成并确保所有其他代码可以自动看到这个文件:
// Generated
class ExampleSingleton {
private ExampleSingleton() {}
private static final Example INSTANCE = new Example();
public void foo1() {
INSTANCE.foo1();
}
public static String foo2(String arg) throws IOException {
return INSTANCE.foo2(arg);
}
}
但是,注释处理器编写起来很棘手,它们可能会拖累编译过程。不过,这是获得您想要的东西的唯一方法。现在您可以在网上搜索/阅读一些东西:)
您首先创建一个单独的项目来定义注释,具有注释处理器(扩展 AbstractProcessor
的 class),将其打包到 jar 中,并确保清单包含SPI 文件告诉 java 扩展 AbstractProcessor 的 class 是注释处理器,然后它会被自动拾取。我给你注释定义:
在名为 Singletonify.java
的文件中:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Singletonify {}
但是……等等!
单例的概念经常有问题。单身人士应该是 'stateless' - 如果他们是无状态的,为什么你的 Foo
class 不完全充满 static
方法,避免需要你的“静态镜像 class”?如果它是有状态的,那么您现在拥有全局状态,这几乎是普遍谴责的 anti-pattern。你不想要全局状态,它使得控制流的推理变得不可能。
第二个问题是可测试性——因为静态的东西不'do'继承,你不能(轻易地)测试静态方法的实现。有了 non-static 东西,这就容易多了。
此问题通常由 so-called 依赖注入框架解决,例如 Dagger、Guice 或 Spring。他们让你编写的代码只是 'gets' 你的 Foo
class 的一个实例,而调用者不必真正弄清楚从哪里得到这个实例:依赖注入框架会处理它。它可以让你做一些事情,比如“拥有这个对象的单身人士...... per web session”。这是非常强大的东西。
我想你可能想要的是一个 DI 系统。在花 2 周时间编写注释处理器之前,您可能需要进行一些调查。
我有一个这样的 POJO:
public class Foo {
private String bar1;
private String bar2;
//...
public String getBar1() { return bar1; }
public void setBar1(String bar1) { this.bar1 = bar1; }
public String getBar2() { return bar2; }
public void setBar2(String bar2) { this.bar2 = bar2; }
//...
}
作为 Java 反射的替代方法(通常很慢),我想用这样的静态方法定义一个 class:
public class FooStatic {
public static String getBar1(Foo foo) { return foo.getBar1(); }
public static void setBar1(Foo foo, String bar1) { foo.setBar1(bar1); }
public static String getBar2(Foo foo) { return foo.getBar2(); }
public static void setBar2(Foo foo, String bar2) { foo.setBar2(bar2); }
//...
}
每次更新 Foo class 时都会强制执行静态方法的 creation/deprecation。例如,如果在FooStatic
中删除字段bar2
,那么FooStatic
中的静态方法getBar2()
和setBar2()
应该被编译器识别为被删除.如果使用 getter 和 setter 将新变量 bar3
添加到 Foo
,编译器应强制在 FooStatic
中创建新的静态方法 getBar3()
和 setBar3()
。此外,我有多个 POJO,并且想要一个可扩展的解决方案。这可能吗?
是的……有点。很复杂。
注释处理器是编译器插件,在编译过程中的某些时间 运行。它很快变得复杂 - 例如,IDE 和构建工具 'incremental'(当然,他们不想每次更改单个字符时都重新编译整个代码库)。
注释处理器可以做一些事情:
- 它们可以 运行 作为编译过程的一部分。这可以自动完成 - 他们只需要在 class 路径上,就是全部
- 它们可以由于注释的存在而被触发。
- 他们可以读取现有文件的签名(字段和方法的名称、参数名称、参数类型、return类型和
throws
子句,以及字段的类型,以及extends
和implements
子句,以及构造函数的参数名称和类型)。他们无法阅读 body 内容(初始化表达式、方法和构造函数主体)。但我认为你只需要这里的签名。 - 他们可以制作新 文件。他们甚至可以制作新的 java 文件,然后这些文件将与其他文件一起自动编译。
这样,你就有了一条路:做一个注解,然后做一个注解处理器。例如,您可以将其设置为手动写入:
@com.foo.Hossmeister.Singletonify
class Example {
void foo1() {}
String foo2(String arg) throws IOException {}
}
并有一个注释处理器(它也有那个 com.foo.Hossmeister.Singletonify
注释),如果它在 class 路径上,它会自动生成并确保所有其他代码可以自动看到这个文件:
// Generated
class ExampleSingleton {
private ExampleSingleton() {}
private static final Example INSTANCE = new Example();
public void foo1() {
INSTANCE.foo1();
}
public static String foo2(String arg) throws IOException {
return INSTANCE.foo2(arg);
}
}
但是,注释处理器编写起来很棘手,它们可能会拖累编译过程。不过,这是获得您想要的东西的唯一方法。现在您可以在网上搜索/阅读一些东西:)
您首先创建一个单独的项目来定义注释,具有注释处理器(扩展 AbstractProcessor
的 class),将其打包到 jar 中,并确保清单包含SPI 文件告诉 java 扩展 AbstractProcessor 的 class 是注释处理器,然后它会被自动拾取。我给你注释定义:
在名为 Singletonify.java
的文件中:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Singletonify {}
但是……等等!
单例的概念经常有问题。单身人士应该是 'stateless' - 如果他们是无状态的,为什么你的 Foo
class 不完全充满 static
方法,避免需要你的“静态镜像 class”?如果它是有状态的,那么您现在拥有全局状态,这几乎是普遍谴责的 anti-pattern。你不想要全局状态,它使得控制流的推理变得不可能。
第二个问题是可测试性——因为静态的东西不'do'继承,你不能(轻易地)测试静态方法的实现。有了 non-static 东西,这就容易多了。
此问题通常由 so-called 依赖注入框架解决,例如 Dagger、Guice 或 Spring。他们让你编写的代码只是 'gets' 你的 Foo
class 的一个实例,而调用者不必真正弄清楚从哪里得到这个实例:依赖注入框架会处理它。它可以让你做一些事情,比如“拥有这个对象的单身人士...... per web session”。这是非常强大的东西。
我想你可能想要的是一个 DI 系统。在花 2 周时间编写注释处理器之前,您可能需要进行一些调查。