From 6eb1947a6e04a8436a9e6a06f641b55ef51abcac Mon Sep 17 00:00:00 2001 From: Karsten Sperling Date: Fri, 16 Dec 2016 22:41:31 +1300 Subject: [PATCH] Add EbeanAgentAutoConfiguration for Spring Boot --- pom.xml | 57 ++++++++------- .../boot/EbeanAgentAutoConfiguration.java | 69 +++++++++++++++++++ src/main/resources/META-INF/spring.factories | 2 + .../boot/EbeanAgentAutoConfigurationTest.java | 35 ++++++++++ src/test/java/org/unenhanced/Boondoggle.java | 11 +++ src/test/java/org/unenhanced/Wotsit.java | 11 +++ src/test/resources/init-database.xml | 7 +- 7 files changed, 163 insertions(+), 29 deletions(-) create mode 100644 src/main/java/io/ebean/spring/boot/EbeanAgentAutoConfiguration.java create mode 100644 src/main/resources/META-INF/spring.factories create mode 100644 src/test/java/io/ebean/spring/boot/EbeanAgentAutoConfigurationTest.java create mode 100644 src/test/java/org/unenhanced/Boondoggle.java create mode 100644 src/test/java/org/unenhanced/Wotsit.java diff --git a/pom.xml b/pom.xml index 611442188..ee4b98a2a 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ 4.3.4.RELEASE + 1.4.2.RELEASE http://ebean-orm.github.io/ @@ -41,35 +42,23 @@ provided - + io.ebean ebean-agent - 10.1.1 - provided + [10,) + runtime + true - - - - - - - - - - - org.springframework - spring-context - ${spring.framework.version} - provided - - - commons-logging - commons-logging - - + + org.avaje + avaje-agentloader + 2.1.2 + true + + org.springframework spring-jdbc @@ -83,6 +72,13 @@ + + org.springframework.boot + spring-boot-autoconfigure + ${spring.boot.version} + provided + + org.springframework @@ -97,6 +93,13 @@ + + org.springframework.boot + spring-boot-test + ${spring.boot.version} + test + + org.avaje.composite avaje-composite-testing-ebean @@ -143,6 +146,14 @@ + + maven-surefire-plugin + 2.18.1 + + + false + + diff --git a/src/main/java/io/ebean/spring/boot/EbeanAgentAutoConfiguration.java b/src/main/java/io/ebean/spring/boot/EbeanAgentAutoConfiguration.java new file mode 100644 index 000000000..d9970a77a --- /dev/null +++ b/src/main/java/io/ebean/spring/boot/EbeanAgentAutoConfiguration.java @@ -0,0 +1,69 @@ +package io.ebean.spring.boot; + +import org.avaje.agentloader.AgentLoader; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigureOrder; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; + + +/** + * Loads the Ebean enhancement agent early in the Spring Boot startup process, + * if it is present on the classpath. + *

+ * Note that using this mechanism is only recommended for development; + * production applications should ideally be enhanced at build time, or at least + * load the agent via the javaagent JVM option. When the agent is + * loaded at runtime via this class, any entity classes that have already been + * loaded won't be enhanced and will fail to work correctly. + *

+ * For unit tests and similar cases where Spring Boot auto-configuration may not + * be active, loading of the agent can be triggered manually via + * {@link #enable()}. + */ +@Configuration +@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) +@ConditionalOnClass(AgentLoader.class) +public class EbeanAgentAutoConfiguration implements BeanFactoryPostProcessor, PriorityOrdered { + + public EbeanAgentAutoConfiguration() { + load(); // Spring has already evaluated the @ConditionalOnClass + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + // We're not actually doing anything with the BeanFactory, but implementing + // BeanFactoryPostProcessor ensures we get instantiated early, ideally + // before anybody has a chance to load any entity classes we want to + // enhance. + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + private static void load() { + AgentLoader.loadAgentFromClasspath("ebean-agent", "debug=1"); + } + + /** + * Loads the Ebean agent if the agent-loader and the agent itself are present + * on the classpath, or does nothing otherwise. + *

+ * Do not call this method from a static initializer as this can lead to a JVM + * deadlock (the agent attach thread will attempt to acquire the class loader + * lock, which is held during static initialization). + */ + public static void enable() { + try { + load(); + } catch (NoClassDefFoundError e) { + /* ignored */ + } + } +} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..d4fedf3b0 --- /dev/null +++ b/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +io.ebean.spring.boot.EbeanAgentAutoConfiguration diff --git a/src/test/java/io/ebean/spring/boot/EbeanAgentAutoConfigurationTest.java b/src/test/java/io/ebean/spring/boot/EbeanAgentAutoConfigurationTest.java new file mode 100644 index 000000000..bbd7bc81e --- /dev/null +++ b/src/test/java/io/ebean/spring/boot/EbeanAgentAutoConfigurationTest.java @@ -0,0 +1,35 @@ +package io.ebean.spring.boot; + +import io.ebean.bean.EntityBean; +import org.junit.Test; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; +import org.unenhanced.Boondoggle; +import org.unenhanced.Wotsit; +import static org.assertj.core.api.StrictAssertions.assertThat; + +@SpringBootTest(webEnvironment=WebEnvironment.NONE) +public class EbeanAgentAutoConfigurationTest extends AbstractJUnit4SpringContextTests { + + static { + // Validate our test setup. Agent should not be loaded yet, so 'Boondoggle' + // should have been enhanced neither at build time nor at load time. + assertThat(EntityBean.class.isAssignableFrom(Boondoggle.class)).isFalse(); + } + + @Configuration + @EnableAutoConfiguration + public static class Config { + /* no beans needed for this test */ + } + + @Test + public void testAgentIsWorking() { + // Wotsit is outside of the org.example package that's being enhanced + // at build time, so should be picked up by the agent only. + assertThat(EntityBean.class.isAssignableFrom(Wotsit.class)).isTrue(); + } +} diff --git a/src/test/java/org/unenhanced/Boondoggle.java b/src/test/java/org/unenhanced/Boondoggle.java new file mode 100644 index 000000000..0d23f258d --- /dev/null +++ b/src/test/java/org/unenhanced/Boondoggle.java @@ -0,0 +1,11 @@ +package org.unenhanced; + +import javax.persistence.Entity; +import javax.persistence.Id; + +// Not in org.example, i.e. not enhanced at build time +@Entity +public class Boondoggle { + @Id + public String name; +} diff --git a/src/test/java/org/unenhanced/Wotsit.java b/src/test/java/org/unenhanced/Wotsit.java new file mode 100644 index 000000000..e2e723caf --- /dev/null +++ b/src/test/java/org/unenhanced/Wotsit.java @@ -0,0 +1,11 @@ +package org.unenhanced; + +import javax.persistence.Entity; +import javax.persistence.Id; + +// Not in org.example, i.e. not enhanced at build time +@Entity +public class Wotsit { + @Id + public String name; +} diff --git a/src/test/resources/init-database.xml b/src/test/resources/init-database.xml index 2273a21f3..0ccf4cd6b 100644 --- a/src/test/resources/init-database.xml +++ b/src/test/resources/init-database.xml @@ -8,11 +8,6 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> - - - - - @@ -24,7 +19,7 @@ + value="jdbc:h2:mem:ebean-testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false" />