Restore support for empty persistence unit with fat jars
Due to the layout format change in 1.4, Spring Framework is no longer able to compute a default persistence unit root URL. If a Spring Boot 1.4 application has JPA but does not have any entity, the application started from a fat jar now fails with a quite cryptic exception. This commit introduces `ApplicationInfo` as a general replacement for the `ApplicationArguments` and `Banner` singleton beans that `SpringApplication` registers on startup. `ApplicationInfo` also defines the detected "main" `Class` that can be used to compute a last resort URL that makes sense. If such bean is available, `EntityManagerFactoryBuilder` now sets the default persistence unit root location, preventing Spring Framework to attempt to resolve an unknown location. Note that in our case the persistence unit root location is actually useless: given the way the persistence unit is created, nothing actually uses it but Hibernate, as a compliant JPA provider, has to make sure this setting is set to a valid URL nevertheless. Closes gh-6635
This commit is contained in:
@@ -27,6 +27,7 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.boot.ApplicationInfo;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
@@ -105,6 +106,9 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
|
||||
jpaVendorAdapter, this.properties.getProperties(),
|
||||
persistenceUnitManagerProvider.getIfAvailable());
|
||||
builder.setCallback(getVendorCallback());
|
||||
if (this.beanFactory.containsBean("springApplicationInfo")) {
|
||||
builder.setApplicationInfo(this.beanFactory.getBean(ApplicationInfo.class));
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,8 +94,8 @@ You can also use the `spring.main.banner-mode` property to determine if the bann
|
||||
to be printed on `System.out` (`console`), using the configured logger (`log`) or not
|
||||
at all (`off`).
|
||||
|
||||
The printed banner will be registered as a singleton bean under the name
|
||||
`springBootBanner`.
|
||||
The printed banner will be available via an `ApplicationInfo` registered as a singleton
|
||||
bean under the name `springApplicationInfo`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
||||
@@ -11,9 +11,9 @@ welcome = { ->
|
||||
// Try to print using the banner interface
|
||||
if (beanFactory != null) {
|
||||
try {
|
||||
def banner = beanFactory.getBean("springBootBanner")
|
||||
def appInfo = beanFactory.getBean("springApplicationInfo")
|
||||
def out = new java.io.ByteArrayOutputStream()
|
||||
banner.printBanner(environment, null, new java.io.PrintStream(out))
|
||||
appInfo.banner.printBanner(environment, null, new java.io.PrintStream(out))
|
||||
return out.toString()
|
||||
} catch (Exception ex) {
|
||||
// Ignore
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot;
|
||||
|
||||
/**
|
||||
* Provide application-related information such as the {@link ApplicationArguments} and
|
||||
* the {@link Banner}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.4.1
|
||||
*/
|
||||
public final class ApplicationInfo {
|
||||
|
||||
private final Class<?> mainApplicationClass;
|
||||
|
||||
private final ApplicationArguments applicationArguments;
|
||||
|
||||
private final Banner banner;
|
||||
|
||||
protected ApplicationInfo(SpringApplication application, ApplicationArguments applicationArguments, Banner banner) {
|
||||
this.mainApplicationClass = application.getMainApplicationClass();
|
||||
this.applicationArguments = applicationArguments;
|
||||
this.banner = banner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main application class that has been deduced or explicitly configured.
|
||||
* @return the main application class or {@code null}
|
||||
*/
|
||||
public Class<?> getMainApplicationClass() {
|
||||
return this.mainApplicationClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ApplicationArguments} used to start this instance.
|
||||
* @return the application arguments
|
||||
*/
|
||||
public ApplicationArguments getApplicationArguments() {
|
||||
return this.applicationArguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Banner} used by this instance.
|
||||
* @return the banner or {@code null}
|
||||
*/
|
||||
public Banner getBanner() {
|
||||
return this.banner;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -359,6 +359,10 @@ public class SpringApplication {
|
||||
if (printedBanner != null) {
|
||||
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
|
||||
}
|
||||
ApplicationInfo applicationInfo = new ApplicationInfo(this, applicationArguments,
|
||||
printedBanner);
|
||||
context.getBeanFactory().registerSingleton("springApplicationInfo",
|
||||
applicationInfo);
|
||||
|
||||
// Load the sources
|
||||
Set<Object> sources = getSources();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot.orm.jpa;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -24,10 +25,15 @@ import java.util.Set;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.ApplicationInfo;
|
||||
import org.springframework.orm.jpa.JpaVendorAdapter;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
/**
|
||||
* Convenient builder for JPA EntityManagerFactory instances. Collects common
|
||||
@@ -39,10 +45,13 @@ import org.springframework.util.ClassUtils;
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class EntityManagerFactoryBuilder {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(EntityManagerFactoryBuilder.class);
|
||||
|
||||
private JpaVendorAdapter jpaVendorAdapter;
|
||||
|
||||
private PersistenceUnitManager persistenceUnitManager;
|
||||
@@ -51,6 +60,8 @@ public class EntityManagerFactoryBuilder {
|
||||
|
||||
private EntityManagerFactoryBeanCallback callback;
|
||||
|
||||
private Class<?> applicationClass;
|
||||
|
||||
/**
|
||||
* Create a new instance passing in the common pieces that will be shared if multiple
|
||||
* EntityManagerFactory instances are created.
|
||||
@@ -78,6 +89,34 @@ public class EntityManagerFactoryBuilder {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* An optional {@link ApplicationInfo} used to further tune the entity manager.
|
||||
* @param applicationInfo the application info
|
||||
*/
|
||||
public void setApplicationInfo(ApplicationInfo applicationInfo) {
|
||||
this.applicationClass = applicationInfo.getMainApplicationClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine a persistence unit root location to use if no {@code persistence.xml} or
|
||||
* {@code orm.xml} are present in the project.
|
||||
* @return the persistence unit root location or {@code null}
|
||||
*/
|
||||
protected String determinePersistenceUnitRootLocation() {
|
||||
if (this.applicationClass != null) {
|
||||
try {
|
||||
URL mainLocation = this.applicationClass.getProtectionDomain().
|
||||
getCodeSource().getLocation();
|
||||
return ResourceUtils.extractJarFileURL(mainLocation).toString();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.info("Could not determine persistence unit root location: " + ex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A fluent builder for a LocalContainerEntityManagerFactoryBean.
|
||||
*/
|
||||
@@ -181,6 +220,10 @@ public class EntityManagerFactoryBuilder {
|
||||
entityManagerFactoryBean.getJpaPropertyMap()
|
||||
.putAll(EntityManagerFactoryBuilder.this.jpaProperties);
|
||||
entityManagerFactoryBean.getJpaPropertyMap().putAll(this.properties);
|
||||
String rootLocation = determinePersistenceUnitRootLocation();
|
||||
if (rootLocation != null) {
|
||||
entityManagerFactoryBean.setPersistenceUnitRootLocation(rootLocation);
|
||||
}
|
||||
if (EntityManagerFactoryBuilder.this.callback != null) {
|
||||
EntityManagerFactoryBuilder.this.callback
|
||||
.execute(entityManagerFactoryBean);
|
||||
|
||||
@@ -100,6 +100,9 @@ public class BannerTests {
|
||||
application.setWebEnvironment(false);
|
||||
this.context = application.run();
|
||||
assertThat(this.context.containsBean("springBootBanner")).isTrue();
|
||||
assertThat(this.context.containsBean("springApplicationInfo")).isTrue();
|
||||
assertThat(this.context.getBean(
|
||||
"springApplicationInfo", ApplicationInfo.class).getBanner()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -109,7 +112,9 @@ public class BannerTests {
|
||||
Banner banner = mock(Banner.class);
|
||||
application.setBanner(banner);
|
||||
this.context = application.run();
|
||||
Banner printedBanner = (Banner) this.context.getBean("springBootBanner");
|
||||
ApplicationInfo applicationInfo = this.context.getBean("springApplicationInfo",
|
||||
ApplicationInfo.class);
|
||||
Banner printedBanner = applicationInfo.getBanner();
|
||||
assertThat(ReflectionTestUtils.getField(printedBanner, "banner"))
|
||||
.isEqualTo(banner);
|
||||
verify(banner).printBanner(any(Environment.class),
|
||||
@@ -127,6 +132,8 @@ public class BannerTests {
|
||||
application.setWebEnvironment(false);
|
||||
this.context = application.run();
|
||||
assertThat(this.context.containsBean("springBootBanner")).isFalse();
|
||||
assertThat(this.context.getBean("springApplicationInfo", ApplicationInfo.class).
|
||||
getBanner()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -143,6 +150,8 @@ public class BannerTests {
|
||||
this.context = application.run();
|
||||
assertThat(this.out.toString()).contains("I printed a deprecated banner");
|
||||
assertThat(this.context.containsBean("springBootBanner")).isFalse();
|
||||
assertThat(this.context.getBean("springApplicationInfo", ApplicationInfo.class).
|
||||
getBanner()).isNull();
|
||||
}
|
||||
|
||||
static class DummyBanner implements Banner {
|
||||
|
||||
@@ -778,6 +778,21 @@ public class SpringApplicationTests {
|
||||
assertThat(System.getProperty("java.awt.headless")).isEqualTo("false");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getApplicationInfo() {
|
||||
TestSpringApplication application = new TestSpringApplication(
|
||||
ExampleConfig.class);
|
||||
application.setWebEnvironment(false);
|
||||
this.context = application.run("foo");
|
||||
ApplicationInfo applicationInfo = this.context.getBean(ApplicationInfo.class);
|
||||
assertThat(application.getMainApplicationClass()).isEqualTo(application
|
||||
.getMainApplicationClass());
|
||||
assertThat(applicationInfo.getApplicationArguments()).isNotNull();
|
||||
assertThat(applicationInfo.getApplicationArguments().getNonOptionArgs())
|
||||
.containsExactly("foo");
|
||||
assertThat(applicationInfo.getBanner()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getApplicationArgumentsBean() throws Exception {
|
||||
TestSpringApplication application = new TestSpringApplication(
|
||||
|
||||
Reference in New Issue
Block a user