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)

This commit is contained in:
Roland Praml
2016-10-05 21:48:11 +02:00
parent 169eba4296
commit 3ee0b0224e
10 changed files with 307 additions and 4 deletions
@@ -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<BeanFindController> findControllers = new ArrayList<BeanFindController>();
private List<BeanPersistController> persistControllers = new ArrayList<BeanPersistController>();
private List<BeanPostLoad> postLoaders = new ArrayList<BeanPostLoad>();
private List<BeanPostConstructListener> postConstructListeners = new ArrayList<BeanPostConstructListener>();
private List<BeanPersistListener> persistListeners = new ArrayList<BeanPersistListener>();
private List<BeanQueryAdapter> queryAdapters = new ArrayList<BeanQueryAdapter>();
private List<BulkTableEventListener> bulkTableEventListeners = new ArrayList<BulkTableEventListener>();
@@ -2055,6 +2057,16 @@ public class ServerConfig {
postLoaders.add(postLoad);
}
/**
* Register a BeanPostConstructListener instance.
* <p>
* Note alternatively you can use {@link #setPostConstructListeners(List)} to set
* all the BeanPostConstructListener instances.
* </p>
*/
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<BeanPostConstructListener> getPostConstructListeners() {
return postConstructListeners;
}
/**
* Set the list of BeanPostLoader instances.
*/
public void setPostConstructListeners(List<BeanPostConstructListener> listeners) {
this.postConstructListeners = listeners;
}
/**
* Return the BeanPersistController instances.
*/
@@ -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.
* <p>
* 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.
* </p>
*/
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 &#64;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);
}
@@ -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());
@@ -523,7 +523,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@SuppressWarnings("unchecked")
public <T> T createEntityBean(Class<T> type) {
BeanDescriptor<T> desc = getBeanDescriptor(type);
return (T) desc.createEntityBean();
return (T) desc.createEntityBean(true);
}
/**
@@ -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<T> implements MetaBeanInfo, BeanType<T> {
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<T> implements MetaBeanInfo, BeanType<T> {
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<T> implements MetaBeanInfo, BeanType<T> {
@Override
@SuppressWarnings("unchecked")
public T createBean() {
return (T) createEntityBean();
return (T) createEntityBean(true);
}
/**
* Creates a new EntityBean.
* The parameter <code>isNew</code> 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<T> implements MetaBeanInfo, BeanType<T> {
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.
@@ -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);
@@ -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<Method> preDeletes = new ArrayList<Method>();
private final List<Method> postDeletes = new ArrayList<Method>();
private final List<Method> postLoads = new ArrayList<Method>();
private final List<Method> postConstructs = new ArrayList<Method>();
/**
* 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<Method> 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
}
}
}
@@ -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<BeanPostConstructListener> list;
private final BeanPostConstructListener[] chain;
/**
* Construct given the list of BeanPostCreate's.
*/
public ChainedBeanPostConstructListener(List<BeanPostConstructListener> 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<BeanPostConstructListener> newList = new ArrayList<BeanPostConstructListener>();
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<BeanPostConstructListener> newList = new ArrayList<BeanPostConstructListener>();
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);
}
}
}
@@ -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<BeanPostConstructListener> 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);
}
}
}
}
@@ -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<T> {
private final List<BeanPersistListener> persistListeners = new ArrayList<BeanPersistListener>();
private final List<BeanQueryAdapter> queryAdapters = new ArrayList<BeanQueryAdapter>();
private final List<BeanPostLoad> postLoaders = new ArrayList<BeanPostLoad>();
private final List<BeanPostConstructListener> postConstructListeners = new ArrayList<BeanPostConstructListener>();
private CacheOptions cacheOptions = CacheOptions.NO_CACHING;
@@ -533,6 +536,19 @@ public class DeployBeanDescriptor<T> {
}
}
/**
* 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<T> {
postLoaders.add(postLoad);
}
public void addPostConstructListener(BeanPostConstructListener postConstructListener) {
postConstructListeners.add(postConstructListener);
}
public String getDraftTable() {
return draftTable;
}