mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-09-03 04:26:12 +08:00
Support for liquibase in executable jars
Create LiquibaseServiceLocatorInitializer to replace the standard liquibase classpath scanning logic with SpringPackageScanClassResolver which will work correctly in Spring Boot packaged executable JARs. Issue: #55580628
This commit is contained in:
parent
5e6260ec5a
commit
0fa0082b2a
@ -166,6 +166,11 @@
|
||||
<artifactId>hsqldb</artifactId>
|
||||
<version>2.2.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectreactor</groupId>
|
||||
<artifactId>reactor-spring</artifactId>
|
||||
|
@ -64,6 +64,11 @@
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
|
@ -0,0 +1,50 @@
|
||||
package org.springframework.boot.liquibase;
|
||||
|
||||
import liquibase.servicelocator.CustomResolverServiceLocator;
|
||||
import liquibase.servicelocator.ServiceLocator;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.SpringApplicationInitializer;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link SpringApplicationInitializer} that replaces the liquibase {@link ServiceLocator}
|
||||
* with a version that works with Spring Boot executable archives.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class LiquibaseServiceLocatorInitializer implements
|
||||
ApplicationContextInitializer<ConfigurableApplicationContext>,
|
||||
SpringApplicationInitializer {
|
||||
|
||||
static final Log logger = LogFactory
|
||||
.getLog(LiquibaseServiceLocatorInitializer.class);
|
||||
|
||||
@Override
|
||||
public void initialize(SpringApplication springApplication, String[] args) {
|
||||
if (ClassUtils.isPresent("liquibase.servicelocator.ServiceLocator", null)) {
|
||||
new LiquibasePresent().replaceServiceLocator();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner class to prevent class not found issues
|
||||
*/
|
||||
private static class LiquibasePresent {
|
||||
|
||||
public void replaceServiceLocator() {
|
||||
ServiceLocator.setInstance(new CustomResolverServiceLocator(
|
||||
new SpringPackageScanClassResolver()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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.liquibase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import liquibase.servicelocator.DefaultPackageScanClassResolver;
|
||||
import liquibase.servicelocator.PackageScanClassResolver;
|
||||
import liquibase.servicelocator.PackageScanFilter;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Liquibase {@link PackageScanClassResolver} implementation that uses Spring's resource
|
||||
* scanning to locate classes. This variant is safe to use with Spring Boot packaged
|
||||
* executable JARs.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class SpringPackageScanClassResolver extends DefaultPackageScanClassResolver {
|
||||
|
||||
@Override
|
||||
protected void find(PackageScanFilter test, String packageName, ClassLoader loader,
|
||||
Set<Class<?>> classes) {
|
||||
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(
|
||||
loader);
|
||||
try {
|
||||
Resource[] resources = scan(loader, packageName);
|
||||
for (Resource resource : resources) {
|
||||
Class<?> candidate = loadClass(loader, metadataReaderFactory, resource);
|
||||
if (candidate != null && test.matches(candidate)) {
|
||||
classes.add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Resource[] scan(ClassLoader loader, String packageName) throws IOException {
|
||||
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(loader);
|
||||
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
|
||||
+ ClassUtils.convertClassNameToResourcePath(packageName) + "/**/*.class";
|
||||
Resource[] resources = resolver.getResources(pattern);
|
||||
return resources;
|
||||
}
|
||||
|
||||
private Class<?> loadClass(ClassLoader loader, MetadataReaderFactory readerFactory,
|
||||
Resource resource) {
|
||||
try {
|
||||
MetadataReader reader = readerFactory.getMetadataReader(resource);
|
||||
return ClassUtils.forName(reader.getClassMetadata().getClassName(), loader);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (LiquibaseServiceLocatorInitializer.logger.isWarnEnabled()) {
|
||||
LiquibaseServiceLocatorInitializer.logger.warn(
|
||||
"Ignoring cadidate class resource " + resource, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -4,4 +4,5 @@ org.springframework.boot.context.initializer.ConfigFileApplicationContextInitial
|
||||
org.springframework.boot.context.initializer.ContextIdApplicationContextInitializer,\
|
||||
org.springframework.boot.context.initializer.EnvironmentDelegateApplicationContextInitializer,\
|
||||
org.springframework.boot.context.initializer.LoggingApplicationContextInitializer,\
|
||||
org.springframework.boot.context.initializer.VcapApplicationContextInitializer
|
||||
org.springframework.boot.context.initializer.VcapApplicationContextInitializer,\
|
||||
org.springframework.boot.liquibase.LiquibaseServiceLocatorInitializer
|
||||
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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.liquibase;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import liquibase.servicelocator.ServiceLocator;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link LiquibaseServiceLocatorInitializer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class LiquibaseServiceLocatorInitializerTests {
|
||||
|
||||
@Test
|
||||
public void replacesServiceLocator() throws Exception {
|
||||
SpringApplication application = new SpringApplication(Conf.class);
|
||||
application.setWebEnvironment(false);
|
||||
application.run();
|
||||
ServiceLocator instance = ServiceLocator.getInstance();
|
||||
Field field = ReflectionUtils.findField(ServiceLocator.class, "classResolver");
|
||||
field.setAccessible(true);
|
||||
Object resolver = field.get(instance);
|
||||
assertThat(resolver, instanceOf(SpringPackageScanClassResolver.class));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class Conf {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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.liquibase;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import liquibase.logging.Logger;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for SpringPackageScanClassResolver.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class SpringPackageScanClassResolverTests {
|
||||
|
||||
@Test
|
||||
public void testScan() {
|
||||
SpringPackageScanClassResolver resolver = new SpringPackageScanClassResolver();
|
||||
resolver.addClassLoader(getClass().getClassLoader());
|
||||
Set<Class<?>> implementations = resolver.findImplementations(Logger.class,
|
||||
"liquibase.logging.core");
|
||||
assertThat(implementations.size(), greaterThan(0));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user