如何使用切换 Java 注释?
简单的功能切换:- if(toggle enabled) do x
Spring 允许使用“配置文件”来切换 bean。
我使用这些,它们很好,但我想在字段或类上切换注释.. 我该怎么做?
用例,我有一个具有 jpa 注释的类。我希望能够在某些环境中通过配置标记某些字段是@transient。
如何使用切换 Java 注释?
简单的功能切换:- if(toggle enabled) do x
Spring 允许使用“配置文件”来切换 bean。
我使用这些,它们很好,但我想在字段或类上切换注释.. 我该怎么做?
用例,我有一个具有 jpa 注释的类。我希望能够在某些环境中通过配置标记某些字段是@transient。
如前所述,尝试“禁用”注释虽然可能是可能的,但并不是解决问题的最佳方法。
就像 Adrian Shum 所说,您应该更改框架处理注释的方式。在您的情况下,您的 JPA 实现下面应该有一些 ORM 提供程序(例如 Hibernate)。
大多数 ORM 都有一些方法来提供自定义功能,例如在 Hibernate 的情况下,您可以创建一个拦截器并通过将 hibernate.ejb.interceptor 添加到 JPA 配置中的持久性单元来注册它,详见此处。
这个拦截器应该做什么取决于你,但我建议使用不同的注释(例如@ConditionalTransiet),一种方法是通过反射检查字段,检查它们是否有注释,如果它在错误的环境中然后使用onLoad 和 onSave 从对象中擦除相关字段。
一种可能的选择是使用aspectj及其声明注释的能力和 spring 的加载时方面编织的能力。
我想注解不能有条件地声明,但你总是可以在一个单独的 jar 中编译它们,可以根据特定环境将其放入类路径中,以便加载时编织器能够找到它。
更新
虽然这里有很多有用的答案,但我发现使用 aspectj 禁用/启用注释非常有趣,因此示例如下。
最新版本的 aspectj 支持删除注释,但目前此功能仅适用于字段注释,因此非常有用的方法是根本不声明注释并且如果必须启用它们 - 将带有预编译方面的 jar如前所述,将启用类路径中的注释。
样本
第一个罐子
主要班
package org.foo.bar;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
MyClass myObj = context.getBean("myObj", MyClass.class);
System.out.println(myObj);
System.out.println(myObj.getValue1());
System.out.println(myObj.getValue2());
}
}
我们将在其中声明注释的类
package org.foo.bar;
public class MyClass {
@MyAnn("annotated-field-1")
private String field1;
private String field2;
@MyAnn("annotated-method-1")
public String getValue1() {
String value = null;
try {
MyAnn ann = getClass().getDeclaredMethod("getValue1").getAnnotation(MyAnn.class);
if(ann != null) {
value = ann.value();
}
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
return value;
}
public String getValue2() {
String value = null;
try {
MyAnn ann = getClass().getDeclaredMethod("getValue2").getAnnotation(MyAnn.class);
if(ann != null) {
value = ann.value();
}
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
return value;
}
@Override
public String toString() {
String field1 = null;
try {
MyAnn ann = getClass().getDeclaredField("field1").getAnnotation(MyAnn.class);
if(ann != null) {
field1 = ann.value();
}
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
String field2 = null;
try {
MyAnn ann = getClass().getDeclaredField("field2").getAnnotation(MyAnn.class);
if(ann != null) {
field2 = ann.value();
}
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
StringBuilder sb = new StringBuilder();
sb.append("MyClass");
sb.append("{field1='").append(field1).append('\'');
sb.append(", field2='").append(field2).append('\'');
sb.append('}');
return sb.toString();
}
}
注释本身
package org.foo.bar;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface MyAnn {
String value();
}
应用程序上下文
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<context:load-time-weaver />
<bean id="myObj" class="org.foo.bar.MyClass" />
</beans>
第二个罐子
方面
package org.foo.bar;
public aspect ToggleAnnotationAspect {
declare @field : private String org.foo.bar.MyClass.field1 : -@MyAnn;
declare @field : private String org.foo.bar.MyClass.field2 : @MyAnn("annotated-field-2");
declare @method : public String org.foo.bar.MyClass.getValue2() : @MyAnn("annotated-method-2");
}
元信息/aop.xml
<?xml version="1.0"?>
<aspectj>
<aspects>
<aspect name="org.foo.bar.ToggleAnnotationAspect"/>
</aspects>
</aspectj>
在类路径中没有第二个 jar 的情况下运行应用程序
java -javaagent:spring-instrument-3.1.3.RELEASE.jar \
-classpath app1.jar;<rest_of_cp> org.foo.bar.Main
将打印
MyClass{field1='annotated-field-1', field2='null'}
annotated-method-1
null
使用类路径中的第二个 jar 运行应用程序
java -javaagent:spring-instrument-3.1.3.RELEASE.jar \
-classpath app1.jar;app1-aspects.jar;<rest_of_cp> org.foo.bar.Main
将打印
MyClass{field1='null', field2='annotated-field-2'}
annotated-method-1
annotated-method-2
因此根本没有对应用程序源进行任何修改。
我认为这在现场级别是不可能的。您可能做的是将整个班级排除在 JPA 考虑的范围之外(通过持久性单元配置)。我相信这应该可以按照个人资料来完成。
不,你不能这样做。
注释只是一段元数据。编译源代码后,它会附加到字节码(嗯,取决于保留)。因此它总是在那里。您不能使用正常方式使其在运行时消失。
然而,注释只是元数据。它自己什么都不做。应该有其他人检查注释,并相应地做他们的工作。因此,您应该研究的是,您应该找到某种方法来告诉检查注释的“某人”,并告诉它解释注释的正确方法是什么(例如忽略某些注释等)
没有执行此操作的通用方法,因为这完全取决于检查注释的人。
如果您坚持采取麻烦的方式,我相信您可能会在运行时更改类。这将是一项乏味的工作。我记得,像 Javassist 这样的工具允许您“重写”由类加载器加载的类并将其保存回来。但是您将面临很多问题,例如,您的类替换过程应该在任何其他代码运行之前发生,如果没有,例如,Hibernate 将已经检查了未修改的类并完成了它的设置,甚至您删除了之后来自类的注释,它不会做任何事情。
你可以试试这个(使用方面):
@Profile("active")
privileged aspect AddField {
private String MyClass.name;
}
@Profile("inactive")
privileged aspect AddFieldTransient {
@Transient
private String MyClass.name;
}
不确定配置文件注释是否适用于方面类。此外,这种方式需要您为要应用此行为的每个字段添加这些方面。很难使它比这更通用。
在 Hibernate 开始时,他们将其设计为将配置与实际类分开,使用单独的映射 xml 文件。注释只是后来作为方便的妥协而添加的。
在标准化的 JPA 中仍然可以使用 orm.xml 配置来覆盖注释。请参阅http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/xml-overriding.html以获取参考。
在您的情况下,如果您使用metadata-complete="true"
,则所有元数据都来自 orm.xml 文件而不是注释。然后你可以使用两个不同的 orm.xml。
<entity class="Administration" access="PROPERTY" metadata-complete="true">
<attributes>
<basic name="status"/>
<basic name="optional">
</attributes>
<entity class="Administration" access="PROPERTY" metadata-complete="true">
<attributes>
<basic name="status"/>
<!-- omitted optional property -->
</attributes>