Entity
类?我想在应用程序启动时注册我的类,而不必按照文档中的建议将它们硬编码到类中。
答案 0 :(得分:2)
这不是runtime
反射成本解决方案,因为它会在compile/build/package
时进行所有反射。
import com.google.appengine.api.ThreadManager;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.annotation.Entity;
import org.reflections.Reflections;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* This class processes the classpath for classes with the @Entity or @Subclass annotations from Objectify
* and registers them with the ObjectifyFactory, it is multi-threaded and works very fast!
*/
public class ObjectifyLoaderContextListener implements ServletContextListener
{
private static final Logger L = LoggerFactory.getLogger(ObjectifyLoaderContextListener.class);
private final Set<Class<?>> entities;
public ObjectifyLoaderContextListener()
{
this.entities = new HashSet<>();
}
@Override
public void contextInitialized(@Nonnull final ServletContextEvent sce)
{
final ExecutorService es = Executors.newCachedThreadPool(ThreadManager.currentRequestThreadFactory());
cb.setExecutorService(es);
final Reflections r = Reflections.collect();
this.entities.addAll(r.getTypesAnnotatedWith(Entity.class));
es.shutdown();
final ObjectifyFactory of = ObjectifyService.factory();
for (final Class<?> cls : this.entities)
{
of.register(cls);
L.debug("Registered {} with Objectify", cls.getName());
}
}
@Override
public void contextDestroyed(@Nonnull final ServletContextEvent sce)
{
/* this is intentionally empty */
}
}
此课程的更新将在this gist中提供。
答案 1 :(得分:-1)
这是基于公认答案的另一种解决方案。
<强>摘要强>
如果要从服务中消除所有生产运行时成本(即使是拾取随后由#collect API使用的XML工件的相对较小的成本),您也可以将成本移至单元测试阶段,同时仍保留100%正在注册的所有实体的安全性。请参阅答案的结尾处。
动机&amp;详情
这有几个好处,如下所述,但它们是否值得,取决于您的用例。就我个人而言,这些问题是否会成为一个问题并非如此 - 考虑到避免在生产过程中做任何不必要的事情(以及从生产路径中删除代码),我不会这样做,我总是会接受它,只要它不会带来额外的复杂性,奇怪的设计成本等等。在这种情况下,没有。这是一种将运行时注册保证从prod转移到测试的教科书方法。
请注意,GAE实例可能会被任意重启多次。其他考虑因素包括您的应用程序对服务启动时间的敏感程度,生成的Reflection XML工件的大小(您的jar依赖项的函数和反映第三方库的详细信息)以及库如何有效地解析它们。虽然您可以通过多线程降低性能风险,但Google仍会向您收取使用的CPU时间费用。
将此转换为单元测试解决方案的相对好处是:
您每次重新启动服务时都可以消除解析XML资源并将其重建为Reflection元数据的成本(可能会多次任意发生)
您可以消除在未来某些时候更改项目JAR依赖关系的风险(对于非平凡的项目,依赖关系往往会随着时间的推移而增长),并且忘记重新评估XML资源的新大小及其解析;这可能会逐渐为您的服务重启时间增加更大的开销,这很难确定,但会影响用户(导致经典死亡人数减少)
您消除了对第三个库的运行时prod依赖性,这总是很好(依赖性将保留给您的单元测试模块)。例如,如果库的未来版本突然降低了XML解析速度,那么您需要在prod中处理它。这是一个可以消除的风险。
这些考虑因素是否仍然纯粹是学术性的,取决于您的使用案例和风险/成本容忍度。
警告:如果由于某种原因您的发布推出过程没有强制要求通过单元测试,这显然不是正确的方法。但我不认为对很多人来说就是这种情况。
<强>代码强>
为了便于阅读,我省略了外部测试类(假设所有代码都存在于Test类中)
@Test
public void testObjectifyRegistration() {
final ObjectifyFactory ofy = Ofy.factory(); // your backend-specific ofy entry point where you've done your registrations
for (final Class<?> cls : allEntities()) {
verifyRegistrationState(ofy, cls, /*expected to be registered?*/!cls.equals(UnregisteredUnittestEntity.class));
}
}
private Iterable<Class<?>> allEntities() {
final ExecutorService es = Executors.newCachedThreadPool();
try {
final ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setUrls(ClasspathHelper.forPackage(""));
cb.setExecutorService(es);
return new HashSet<>(new Reflections(cb).getTypesAnnotatedWith(Entity.class));
} finally {
es.shutdown();
}
}
private void verifyRegistrationState(final ObjectifyFactory ofy, final Class<?> cls, final boolean shouldBeRegistered) {
RuntimeException missingRegistrationExc = null;
try {
ofy.getMetadata(cls); // per today's API contract, this method throws if cls has not been registered with objectify
} catch (RuntimeException exc) {
missingRegistrationExc = exc;
}
if (shouldBeRegistered && missingRegistrationExc != null)
throw missingRegistrationExc;
else if (!shouldBeRegistered && missingRegistrationExc == null)
fail("ObjectifyFactory#getMetadata expected to throw upon unregistered entity, check for new contract of method and update test");
}
@Entity // self-test: used to validate correctness of the unit test itself
private static class UnregisteredUnittestEntity {
@VisibleForTesting
public UnregisteredUnittestEntity() {
}
}
以下是使用@Entity
注释的类尚未在Objectify
注册时的示例失败。
java.lang.IllegalArgumentException:No class&#39; [xxx] .UserImpl&#39;是 已注册 com.googlecode.objectify.impl.Registrar.getMetadataSafe(Registrar.java:120) 在 com.googlecode.objectify.ObjectifyFactory.getMetadata(ObjectifyFactory.java:210) 在 [XXX] .OfyRegistrationTest.verifyRegistrationState(OfyRegistrationTest.java:56) 在 [XXX] .OfyRegistrationTest.testOfyRegistration(OfyRegistrationTest.java:37)