From 3ee0b0224edf11137eca0086d67a0f4b5a0bc1d8 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Wed, 5 Oct 2016 21:48:11 +0200 Subject: [PATCH] ENH: Support for javax.annotation.PostConstruct. BeanPostConstructListener will be informed when an entity-bean is constructed/created to perform dependency-injection (e.g. Spring-Autowire) --- .../com/avaje/ebean/config/ServerConfig.java | 25 +++++ .../event/BeanPostConstructListener.java | 41 +++++++++ .../server/core/DefaultContainer.java | 1 + .../server/core/DefaultServer.java | 2 +- .../server/deploy/BeanDescriptor.java | 27 +++++- .../server/deploy/BeanDescriptorManager.java | 7 +- .../deploy/BeanLifecycleAdapterFactory.java | 55 +++++++++++ .../ChainedBeanPostConstructListener.java | 91 +++++++++++++++++++ .../server/deploy/PostConstructManager.java | 42 +++++++++ .../deploy/meta/DeployBeanDescriptor.java | 20 ++++ 10 files changed, 307 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/avaje/ebean/event/BeanPostConstructListener.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPostConstructListener.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/PostConstructManager.java diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java index 70cf6dbcf..f66c56111 100644 --- a/src/main/java/com/avaje/ebean/config/ServerConfig.java +++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java @@ -11,6 +11,7 @@ import com.avaje.ebean.config.dbplatform.DbType; import com.avaje.ebean.event.BeanFindController; import com.avaje.ebean.event.BeanPersistController; import com.avaje.ebean.event.BeanPersistListener; +import com.avaje.ebean.event.BeanPostConstructListener; import com.avaje.ebean.event.BeanPostLoad; import com.avaje.ebean.event.BeanQueryAdapter; import com.avaje.ebean.event.BulkTableEventListener; @@ -329,6 +330,7 @@ public class ServerConfig { private List findControllers = new ArrayList(); private List persistControllers = new ArrayList(); private List postLoaders = new ArrayList(); + private List postConstructListeners = new ArrayList(); private List persistListeners = new ArrayList(); private List queryAdapters = new ArrayList(); private List bulkTableEventListeners = new ArrayList(); @@ -2055,6 +2057,16 @@ public class ServerConfig { postLoaders.add(postLoad); } + /** + * Register a BeanPostConstructListener instance. + *

+ * Note alternatively you can use {@link #setPostConstructListeners(List)} to set + * all the BeanPostConstructListener instances. + *

+ */ + public void add(BeanPostConstructListener listener) { + postConstructListeners.add(listener); + } /** * Return the list of BeanFindController instances. */ @@ -2083,6 +2095,19 @@ public class ServerConfig { this.postLoaders = postLoaders; } + /** + * Return the list of BeanPostLoader instances. + */ + public List getPostConstructListeners() { + return postConstructListeners; + } + + /** + * Set the list of BeanPostLoader instances. + */ + public void setPostConstructListeners(List listeners) { + this.postConstructListeners = listeners; + } /** * Return the BeanPersistController instances. */ diff --git a/src/main/java/com/avaje/ebean/event/BeanPostConstructListener.java b/src/main/java/com/avaje/ebean/event/BeanPostConstructListener.java new file mode 100644 index 000000000..1893b480e --- /dev/null +++ b/src/main/java/com/avaje/ebean/event/BeanPostConstructListener.java @@ -0,0 +1,41 @@ +package com.avaje.ebean.event; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Fired after a bean is constructed, but not yet loaded from database. + *

+ * Note: You MUST NOT set any default values, as in a following step, + * properties will get unload. Use {@link BeanPostLoad} instead. + * + * it's intended to do some dependency-injection here. + * If you plan to use this feature you should use {@link EbeanServer#createEntityBean(Class)} + * to create new beans. + *

+ */ +public interface BeanPostConstructListener { + + /** + * Return true if this BeanPostConstructListener instance should be registered + * for post construct on this entity type. + */ + boolean isRegisterFor(Class cls); + + /** + * Called immediately after construction. Perform DI here. + */ + void autowire(Object bean); + + /** + * Called after every @PostConstruct annotated method of the bean is executed + */ + void postConstruct(Object bean); + + /** + * Called after {@link EbeanServer#createEntityBean(Class)}. Only for new beans. + * intended to set default values here. + */ + void postCreate(Object bean); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java index 0f25e5cc7..009513583 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java @@ -194,6 +194,7 @@ public class DefaultContainer implements SpiContainer { bootup.addIdGenerators(serverConfig.getIdGenerators()); bootup.addPersistControllers(serverConfig.getPersistControllers()); bootup.addPostLoaders(serverConfig.getPostLoaders()); + bootup.addPostConstructListeners(serverConfig.getPostConstructListeners()); bootup.addFindControllers(serverConfig.getFindControllers()); bootup.addPersistListeners(serverConfig.getPersistListeners()); bootup.addQueryAdapters(serverConfig.getQueryAdapters()); diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index a87e07960..99aab043f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -523,7 +523,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @SuppressWarnings("unchecked") public T createEntityBean(Class type) { BeanDescriptor desc = getBeanDescriptor(type); - return (T) desc.createEntityBean(); + return (T) desc.createEntityBean(true); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index fca0972de..b04cd4218 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -20,6 +20,7 @@ import com.avaje.ebean.config.dbplatform.PlatformIdGenerator; import com.avaje.ebean.event.BeanFindController; import com.avaje.ebean.event.BeanPersistController; import com.avaje.ebean.event.BeanPersistListener; +import com.avaje.ebean.event.BeanPostConstructListener; import com.avaje.ebean.event.BeanPostLoad; import com.avaje.ebean.event.BeanQueryAdapter; import com.avaje.ebean.event.changelog.BeanChange; @@ -242,6 +243,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { private volatile BeanPersistController persistController; private final BeanPostLoad beanPostLoad; + private final BeanPostConstructListener beanPostConstructListener; /** * Listens for post commit insert update and delete events. @@ -424,6 +426,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { this.beanFinder = deploy.getBeanFinder(); this.persistController = deploy.getPersistController(); this.persistListener = deploy.getPersistListener(); + this.beanPostConstructListener = deploy.getPostConstructListener(); this.beanPostLoad = deploy.getPostLoad(); this.queryAdapter = deploy.getQueryAdapter(); this.changeLogFilter = deploy.getChangeLogFilter(); @@ -1558,17 +1561,26 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { @Override @SuppressWarnings("unchecked") public T createBean() { - return (T) createEntityBean(); + return (T) createEntityBean(true); } /** * Creates a new EntityBean. + * The parameter isNew controls either this is a new bean (then + * {@link BeanPostConstructListener#postCreate(Object)} will be invoked) or + * a reference (then {@link BeanPostLoad#postLoad(Object)} will be invoked + * on first access (lazy load) or immediately (eager load) */ @SuppressWarnings("unchecked") - public EntityBean createEntityBean() { + public EntityBean createEntityBean(boolean isNew) { try { EntityBean bean = (EntityBean) prototypeEntityBean._ebean_newInstance(); + if (beanPostConstructListener != null) { + beanPostConstructListener.autowire(bean); // calls all registered listeners + beanPostConstructListener.postConstruct(bean); // calls first the @PostConstruct method and then the listeners + } + if (unloadProperties.length > 0) { // 'unload' any properties initialised in the default constructor EntityBeanIntercept ebi = bean._ebean_getIntercept(); @@ -1576,12 +1588,23 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { ebi.setPropertyUnloaded(unloadProperties[i]); } } + if (beanPostConstructListener != null && isNew) { + beanPostConstructListener.postCreate(bean); + // if bean is not new, postLoad will be executed later in the bean's lifecycle + } return bean; } catch (Exception ex) { throw new PersistenceException(ex); } } + + /** + * Creates a new entitybean without invoking {@link BeanPostConstructListener#postCreate(Object)} + */ + public EntityBean createEntityBean() { + return createEntityBean(false); + } /** * Create a reference bean based on the id. diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java index 864c34586..37556b202 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -107,6 +107,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap { private final PostLoadManager postLoadManager; + private final PostConstructManager postConstructManager; + private final BeanFinderManager beanFinderManager; private final PersistListenerManager persistListenerManager; @@ -219,6 +221,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { this.beanLifecycleAdapterFactory = new BeanLifecycleAdapterFactory(); this.persistControllerManager = new PersistControllerManager(bootupClasses); this.postLoadManager = new PostLoadManager(bootupClasses); + this.postConstructManager = new PostConstructManager(bootupClasses); this.persistListenerManager = new PersistListenerManager(bootupClasses); this.beanQueryAdapterManager = new BeanQueryAdapterManager(bootupClasses); this.beanFinderManager = new BeanFinderManager(bootupClasses); @@ -624,10 +627,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap { int qa = beanQueryAdapterManager.getRegisterCount(); int cc = persistControllerManager.getRegisterCount(); int pl = postLoadManager.getRegisterCount(); + int pc = postConstructManager.getRegisterCount(); int lc = persistListenerManager.getRegisterCount(); int fc = beanFinderManager.getRegisterCount(); - logger.debug("BeanPersistControllers[" + cc + "] BeanFinders[" + fc + "] BeanPersistListeners[" + lc + "] BeanQueryAdapters[" + qa + "] BeanPostLoaders[" + pl + "]"); + logger.debug("BeanPersistControllers[" + cc + "] BeanFinders[" + fc + "] BeanPersistListeners[" + lc + "] BeanQueryAdapters[" + qa + "] BeanPostLoaders[" + pl + "] BeanPostConstructors[" + pc + "]"); } private void logStatus() { @@ -1143,6 +1147,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { persistControllerManager.addPersistControllers(descriptor); postLoadManager.addPostLoad(descriptor); + postConstructManager.addPostConstructListeners(descriptor); persistListenerManager.addPersistListeners(descriptor); beanQueryAdapterManager.addQueryAdapter(descriptor); beanFinderManager.addFindControllers(descriptor); diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanLifecycleAdapterFactory.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanLifecycleAdapterFactory.java index 70add7faf..19e782aab 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanLifecycleAdapterFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanLifecycleAdapterFactory.java @@ -5,6 +5,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import javax.annotation.PostConstruct; import javax.persistence.PersistenceException; import javax.persistence.PostLoad; import javax.persistence.PostPersist; @@ -16,6 +17,7 @@ import javax.persistence.PreUpdate; import com.avaje.ebean.event.BeanPersistAdapter; import com.avaje.ebean.event.BeanPersistRequest; +import com.avaje.ebean.event.BeanPostConstructListener; import com.avaje.ebean.event.BeanPostLoad; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; @@ -50,6 +52,10 @@ public class BeanLifecycleAdapterFactory { // has postLoad methods deployDesc.addPostLoad(new PostLoadAdapter(methodHolder.postLoads)); } + if (!methodHolder.postConstructs.isEmpty()) { + // has postConstruct methods + deployDesc.addPostConstructListener(new PostConstructAdapter(methodHolder.postConstructs)); + } } /** @@ -65,6 +71,7 @@ public class BeanLifecycleAdapterFactory { private final List preDeletes = new ArrayList(); private final List postDeletes = new ArrayList(); private final List postLoads = new ArrayList(); + private final List postConstructs = new ArrayList(); /** * Has one of the pre or post insert update delete annotated methods. @@ -107,6 +114,9 @@ public class BeanLifecycleAdapterFactory { if (method.isAnnotationPresent(PostLoad.class)) { postLoads.add(method); } + if (method.isAnnotationPresent(PostConstruct.class)) { + postConstructs.add(method); + } } } @@ -241,4 +251,49 @@ public class BeanLifecycleAdapterFactory { } } } + + /** + * PostConstructAdapter using reflection to invoke lifecycle methods. + */ + private static class PostConstructAdapter implements BeanPostConstructListener { + + private final Method[] postConstructMethods; + + private PostConstructAdapter(List postConstructMethods) { + this.postConstructMethods = toArray(postConstructMethods); + } + + @Override + public boolean isRegisterFor(Class cls) { + // Not used + return false; + } + + private void invoke(Method method, Object bean) { + try { + method.invoke(bean); + } catch (InvocationTargetException e) { + throw new PersistenceException("Error invoking lifecycle method", e); + } catch (IllegalAccessException e) { + throw new PersistenceException("Error invoking lifecycle method", e); + } + } + + @Override + public void postConstruct(Object bean) { + for (int i = 0; i < postConstructMethods.length; i++) { + invoke(postConstructMethods[i], bean); + } + } + + @Override + public void autowire(Object bean) { + // autowire is done by global PostConstructListener only + } + + @Override + public void postCreate(Object bean) { + // postCreate is done by global PostConstructListener only + } + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPostConstructListener.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPostConstructListener.java new file mode 100644 index 000000000..a4f754300 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPostConstructListener.java @@ -0,0 +1,91 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.event.BeanPostConstructListener; + +import java.util.ArrayList; +import java.util.List; + +/** + * Handles multiple BeanPostLoad's for a given entity type. + */ +public class ChainedBeanPostConstructListener implements BeanPostConstructListener { + + private final List list; + + private final BeanPostConstructListener[] chain; + + /** + * Construct given the list of BeanPostCreate's. + */ + public ChainedBeanPostConstructListener(List list) { + this.list = list; + this.chain = list.toArray(new BeanPostConstructListener[list.size()]); + } + + /** + * Register a new BeanPostCreate and return the resulting chain. + */ + public ChainedBeanPostConstructListener register(BeanPostConstructListener c) { + if (list.contains(c)){ + return this; + } else { + List newList = new ArrayList(); + newList.addAll(list); + newList.add(c); + + return new ChainedBeanPostConstructListener(newList); + } + } + + /** + * De-register a BeanPostCreate and return the resulting chain. + */ + public BeanPostConstructListener deregister(BeanPostConstructListener c) { + if (!list.contains(c)){ + return this; + } else { + ArrayList newList = new ArrayList(); + newList.addAll(list); + newList.remove(c); + + return new ChainedBeanPostConstructListener(newList); + } + } + + /** + * Return the size of the chain. + */ + protected int size() { + return chain.length; + } + + @Override + public boolean isRegisterFor(Class cls) { + // never called + return false; + } + + /** + * Fire postLoad on all registered BeanPostCreate implementations. + */ + @Override + public void postConstruct(Object bean) { + for (int i = 0; i < chain.length; i++) { + chain[i].postConstruct(bean); + } + } + + @Override + public void autowire(Object bean) { + for (int i = 0; i < chain.length; i++) { + chain[i].autowire(bean); + } + } + + @Override + public void postCreate(Object bean) { + for (int i = 0; i < chain.length; i++) { + chain[i].postCreate(bean); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/PostConstructManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/PostConstructManager.java new file mode 100644 index 000000000..8e7a84041 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/PostConstructManager.java @@ -0,0 +1,42 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.event.BeanPostConstructListener; +import com.avaje.ebeaninternal.server.core.bootup.BootupClasses; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * Default implementation for creating BeanControllers. + */ +public class PostConstructManager { + + private static final Logger logger = LoggerFactory.getLogger(PostConstructManager.class); + + private final List list; + + public PostConstructManager(BootupClasses bootupClasses) { + this.list = bootupClasses.getBeanPostConstructoListeners(); + } + + public int getRegisterCount() { + return list.size(); + } + + /** + * Register BeanPostLoad listeners for a given entity type. + */ + public void addPostConstructListeners(DeployBeanDescriptor deployDesc) { + + for (int i = 0; i < list.size(); i++) { + BeanPostConstructListener c = list.get(i); + if (c.isRegisterFor(deployDesc.getBeanType())) { + logger.debug("BeanPostLoad on[" + deployDesc.getFullName() + "] " + c.getClass().getName()); + deployDesc.addPostConstructListener(c); + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index c04891764..c29a7b6e3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -11,6 +11,7 @@ import com.avaje.ebean.config.dbplatform.PlatformIdGenerator; import com.avaje.ebean.event.BeanFindController; import com.avaje.ebean.event.BeanPersistController; import com.avaje.ebean.event.BeanPersistListener; +import com.avaje.ebean.event.BeanPostConstructListener; import com.avaje.ebean.event.BeanPostLoad; import com.avaje.ebean.event.BeanQueryAdapter; import com.avaje.ebean.event.changelog.ChangeLogFilter; @@ -22,6 +23,7 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType; import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; import com.avaje.ebeaninternal.server.deploy.ChainedBeanPersistController; import com.avaje.ebeaninternal.server.deploy.ChainedBeanPersistListener; +import com.avaje.ebeaninternal.server.deploy.ChainedBeanPostConstructListener; import com.avaje.ebeaninternal.server.deploy.ChainedBeanPostLoad; import com.avaje.ebeaninternal.server.deploy.ChainedBeanQueryAdapter; import com.avaje.ebeaninternal.server.deploy.IndexDefinition; @@ -155,6 +157,7 @@ public class DeployBeanDescriptor { private final List persistListeners = new ArrayList(); private final List queryAdapters = new ArrayList(); private final List postLoaders = new ArrayList(); + private final List postConstructListeners = new ArrayList(); private CacheOptions cacheOptions = CacheOptions.NO_CACHING; @@ -533,6 +536,19 @@ public class DeployBeanDescriptor { } } + /** + * Return the BeanPostCreate(could be a chain of them, 1 or null). + */ + public BeanPostConstructListener getPostConstructListener() { + if (postConstructListeners.isEmpty()) { + return null; + } else if (postConstructListeners.size() == 1) { + return postConstructListeners.get(0); + } else { + return new ChainedBeanPostConstructListener(postConstructListeners); + } + } + public void addPersistController(BeanPersistController controller) { persistControllers.add(controller); } @@ -549,6 +565,10 @@ public class DeployBeanDescriptor { postLoaders.add(postLoad); } + public void addPostConstructListener(BeanPostConstructListener postConstructListener) { + postConstructListeners.add(postConstructListener); + } + public String getDraftTable() { return draftTable; }