Skip to content

Stop using Spring Boot for pure Spring applications #2172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 24 additions & 43 deletions utbot-spring-analyzer/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer
import com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer

Expand All @@ -19,62 +18,34 @@ java {
targetCompatibility = JavaVersion.VERSION_1_8
}

val withoutSpringConfiguration by configurations.creating {}
val withSpringConfiguration by configurations.creating {
extendsFrom(withoutSpringConfiguration)
}
configurations.implementation.get().extendsFrom(withSpringConfiguration)
val shadowJarConfiguration: Configuration by configurations.creating {}
configurations.implementation.get().extendsFrom(shadowJarConfiguration)

dependencies {
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot
withSpringConfiguration("org.springframework.boot:spring-boot:$springBootVersion")

implementation("org.springframework.boot:spring-boot:$springBootVersion")
implementation("io.github.microutils:kotlin-logging:$kotlinLoggingVersion")

fun ModuleDependency.excludeSlf4jApi() = exclude(group = "org.slf4j", module = "slf4j-api")

withoutSpringConfiguration(project(":utbot-rd")) { excludeSlf4jApi() }
withoutSpringConfiguration(project(":utbot-core")) { excludeSlf4jApi() }
withoutSpringConfiguration(project(":utbot-framework-api")) { excludeSlf4jApi() }
withoutSpringConfiguration("com.jetbrains.rd:rd-framework:$rdVersion") { excludeSlf4jApi() }
withoutSpringConfiguration("com.jetbrains.rd:rd-core:$rdVersion") { excludeSlf4jApi() }
withoutSpringConfiguration("commons-logging:commons-logging:$commonsLoggingVersion") { excludeSlf4jApi() }
withoutSpringConfiguration("commons-io:commons-io:$commonsIOVersion") { excludeSlf4jApi() }
// TODO stop putting dependencies that are only used in SpringAnalyzerProcess into shadow jar
shadowJarConfiguration(project(":utbot-rd")) { excludeSlf4jApi() }
shadowJarConfiguration(project(":utbot-core")) { excludeSlf4jApi() }
shadowJarConfiguration(project(":utbot-framework-api")) { excludeSlf4jApi() }
shadowJarConfiguration("com.jetbrains.rd:rd-framework:$rdVersion") { excludeSlf4jApi() }
shadowJarConfiguration("com.jetbrains.rd:rd-core:$rdVersion") { excludeSlf4jApi() }
shadowJarConfiguration("commons-logging:commons-logging:$commonsLoggingVersion") { excludeSlf4jApi() }
shadowJarConfiguration("commons-io:commons-io:$commonsIOVersion") { excludeSlf4jApi() }
}

application {
mainClass.set("org.utbot.spring.process.SpringAnalyzerProcessMainKt")
}

val shadowWithoutSpring by tasks.register<ShadowJar>("shadowJarWithoutSpring") {
configureShadowJar(withoutSpringConfiguration)
archiveFileName.set("utbot-spring-analyzer-shadow.jar")
}
// see more details -- https://github.com/spring-projects/spring-boot/issues/1828
tasks.shadowJar {
configurations = listOf(shadowJarConfiguration)

val shadowWithSpring by tasks.register<ShadowJar>("shadowJarWithSpring") {
configureShadowJar(withSpringConfiguration)
archiveFileName.set("utbot-spring-analyzer-with-spring-shadow.jar")
}

val springAnalyzerJar: Configuration by configurations.creating {
isCanBeResolved = false
isCanBeConsumed = true
}

artifacts {
add(springAnalyzerJar.name, shadowWithoutSpring)
add(springAnalyzerJar.name, shadowWithSpring)
}

fun ShadowJar.configureShadowJar(configuration: Configuration) {
// see more details -- https://github.com/johnrengelman/shadow/blob/master/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.groovy
group = "shadow"
from(sourceSets.main.get().output)
exclude("META-INF/INDEX.LIST", "META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "module-info.class")

configurations = listOf(configuration)

// see more details -- https://github.com/spring-projects/spring-boot/issues/1828
isZip64 = true
// Required for Spring
mergeServiceFiles()
Expand All @@ -87,4 +58,14 @@ fun ShadowJar.configureShadowJar(configuration: Configuration) {
})

transform(Log4j2PluginsCacheFileTransformer::class.java)
archiveFileName.set("utbot-spring-analyzer-shadow.jar")
}

val springAnalyzerJar: Configuration by configurations.creating {
isCanBeResolved = false
isCanBeConsumed = true
}

artifacts {
add(springAnalyzerJar.name, tasks.shadowJar)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.utbot.spring.analyzers

import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.core.env.ConfigurableEnvironment
import org.utbot.spring.exception.UtBotSpringShutdownException

class PureSpringApplicationAnalyzer : SpringApplicationAnalyzer {
override fun analyze(sources: Array<Class<*>>, environment: ConfigurableEnvironment): List<String> {
val applicationContext = AnnotationConfigApplicationContext()
applicationContext.register(*sources)
applicationContext.environment = environment
return UtBotSpringShutdownException.catch { applicationContext.refresh() }.beanQualifiedNames
}

override fun canAnalyze() = true
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,8 @@
package org.utbot.spring.analyzers

import com.jetbrains.rd.util.getLogger
import com.jetbrains.rd.util.info
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.context.ApplicationContextException
import org.utbot.spring.configurators.ApplicationConfigurator
import org.utbot.spring.api.ApplicationData
import org.utbot.spring.postProcessors.UtBotBeanFactoryPostProcessor
import org.springframework.core.env.ConfigurableEnvironment

val logger = getLogger<SpringApplicationAnalyzer>()

class SpringApplicationAnalyzer(private val applicationData: ApplicationData) {

fun analyze(): List<String> {
logger.info { "Current Java version is: " + System.getProperty("java.version") }

val applicationBuilder = SpringApplicationBuilder(SpringApplicationAnalyzer::class.java)
val applicationConfigurator = ApplicationConfigurator(applicationBuilder, applicationData)

applicationConfigurator.configureApplication()

try {
applicationBuilder.build()
applicationBuilder.run()
} catch (e: ApplicationContextException) {
// UtBotBeanFactoryPostProcessor destroys bean definitions
// to prevent Spring application from actually starting and
// that causes it to throw ApplicationContextException.
logger.info { "Bean analysis finished successfully" }
}
return UtBotBeanFactoryPostProcessor.beanQualifiedNames
}
}
interface SpringApplicationAnalyzer {
fun analyze(sources: Array<Class<*>>, environment: ConfigurableEnvironment): List<String>
fun canAnalyze(): Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.utbot.spring.analyzers

import com.jetbrains.rd.util.error
import com.jetbrains.rd.util.getLogger
import com.jetbrains.rd.util.info
import org.springframework.boot.SpringBootVersion
import org.springframework.core.SpringVersion
import org.utbot.spring.api.ApplicationData
import org.utbot.spring.utils.EnvironmentFactory
import org.utbot.spring.utils.SourceFinder

private val logger = getLogger<SpringApplicationAnalyzerFacade>()

class SpringApplicationAnalyzerFacade(private val applicationData: ApplicationData) {

fun analyze(): List<String> {
logger.info { "Current Java version is: " + System.getProperty("java.version") }
logger.info { "Current Spring version is: " + runCatching { SpringVersion.getVersion() }.getOrNull() }
logger.info { "Current Spring Boot version is: " + runCatching { SpringBootVersion.getVersion() }.getOrNull() }

val sources = SourceFinder(applicationData).findSources()
val environmentFactory = EnvironmentFactory(applicationData)

for (analyzer in listOf(SpringBootApplicationAnalyzer(), PureSpringApplicationAnalyzer())) {
if (analyzer.canAnalyze()) {
logger.info { "Analyzing with $analyzer" }
try {
return analyzer.analyze(sources, environmentFactory.createEnvironment())
} catch (e: Throwable) {
logger.error("Analyzer $analyzer failed", e)
}
}
}
logger.error { "All Spring analyzers failed, using empty bean list" }
return emptyList()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.utbot.spring.analyzers

import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.core.env.ConfigurableEnvironment
import org.utbot.spring.exception.UtBotSpringShutdownException

class SpringBootApplicationAnalyzer : SpringApplicationAnalyzer {
override fun analyze(sources: Array<Class<*>>, environment: ConfigurableEnvironment): List<String> {
val app = SpringApplicationBuilder(*sources)
.environment(environment)
.build()
return UtBotSpringShutdownException.catch { app.run() }.beanQualifiedNames
}

override fun canAnalyze(): Boolean = try {
this::class.java.classLoader.loadClass("org.springframework.boot.SpringApplication")
true
} catch (e: ClassNotFoundException) {
false
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.utbot.spring.exception

import com.jetbrains.rd.util.getLogger
import com.jetbrains.rd.util.info

private val logger = getLogger<UtBotSpringShutdownException>()

/**
* Use this exception to shutdown the application
* when all required analysis actions are completed.
*/
class UtBotSpringShutdownException(
message: String,
val beanQualifiedNames: List<String>
): RuntimeException(message) {
companion object {
fun catch(block: () -> Unit): UtBotSpringShutdownException {
try {
block()
throw IllegalStateException("UtBotSpringShutdownException has not been thrown")
} catch (e: Throwable) {
// Spring sometimes wraps exceptions in other exceptions, so we go over
// all the causes to determine if UtBotSpringShutdownException was thrown
for(cause in generateSequence(e) { it.cause })
if (cause is UtBotSpringShutdownException) {
logger.info { "UtBotSpringShutdownException has been successfully caught" }
return cause
}
throw e
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,45 @@ package org.utbot.spring.loggers

import com.jetbrains.rd.util.LogLevel
import com.jetbrains.rd.util.getLogger
import com.jetbrains.rd.util.info
import org.apache.commons.logging.Log
import org.apache.commons.logging.impl.LogFactoryImpl
import org.utbot.spring.exception.UtBotSpringShutdownException

@Suppress("unused") // used via -Dorg.apache.commons.logging.LogFactory=org.utbot.spring.loggers.RDApacheCommonsLogFactory
class RDApacheCommonsLogFactory : LogFactoryImpl() {
override fun getInstance(name: String): Log {
val logger = getLogger(category = name)
return object : Log {
override fun trace(message: Any?) = logger.log(LogLevel.Trace, message, throwable = null)
override fun trace(message: Any?, t: Throwable?) = logger.log(LogLevel.Trace, message, throwable = t)
override fun debug(message: Any?) = logger.log(LogLevel.Debug, message, throwable = null)
override fun debug(message: Any?, t: Throwable?) = logger.log(LogLevel.Debug, message, throwable = t)
override fun info(message: Any?) = logger.log(LogLevel.Info, message, throwable = null)
override fun info(message: Any?, t: Throwable?) = logger.log(LogLevel.Info, message, throwable = t)
override fun warn(message: Any?) = logger.log(LogLevel.Warn, message, throwable = null)
override fun warn(message: Any?, t: Throwable?) = logger.log(LogLevel.Warn, message, throwable = t)
override fun error(message: Any?) = logger.log(LogLevel.Error, message, throwable = null)
override fun error(message: Any?, t: Throwable?) = logger.log(LogLevel.Error, message, throwable = t)
override fun fatal(message: Any?) = logger.log(LogLevel.Fatal, message, throwable = null)
override fun fatal(message: Any?, t: Throwable?) = logger.log(LogLevel.Fatal, message, throwable = t)
private fun log(level: LogLevel, message: Any?, throwable: Throwable?) {
if (throwable is UtBotSpringShutdownException) {
// avoid polluting logs with stack trace of expected exception
logger.info { message }
logger.info { "${throwable::class.java.name}: ${throwable.message}" }
} else
logger.log(level, message, throwable)
}
private fun isEnabled(level: LogLevel) = logger.isEnabled(level)

override fun isTraceEnabled() = logger.isEnabled(LogLevel.Trace)
override fun isDebugEnabled() = logger.isEnabled(LogLevel.Debug)
override fun isInfoEnabled() = logger.isEnabled(LogLevel.Info)
override fun isErrorEnabled() = logger.isEnabled(LogLevel.Error)
override fun isFatalEnabled() = logger.isEnabled(LogLevel.Fatal)
override fun isWarnEnabled() = logger.isEnabled(LogLevel.Warn)
override fun trace(message: Any?) = log(LogLevel.Trace, message, throwable = null)
override fun trace(message: Any?, t: Throwable?) = log(LogLevel.Trace, message, throwable = t)
override fun debug(message: Any?) = log(LogLevel.Debug, message, throwable = null)
override fun debug(message: Any?, t: Throwable?) = log(LogLevel.Debug, message, throwable = t)
override fun info(message: Any?) = log(LogLevel.Info, message, throwable = null)
override fun info(message: Any?, t: Throwable?) = log(LogLevel.Info, message, throwable = t)
override fun warn(message: Any?) = log(LogLevel.Warn, message, throwable = null)
override fun warn(message: Any?, t: Throwable?) = log(LogLevel.Warn, message, throwable = t)
override fun error(message: Any?) = log(LogLevel.Error, message, throwable = null)
override fun error(message: Any?, t: Throwable?) = log(LogLevel.Error, message, throwable = t)
override fun fatal(message: Any?) = log(LogLevel.Fatal, message, throwable = null)
override fun fatal(message: Any?, t: Throwable?) = log(LogLevel.Fatal, message, throwable = t)

override fun isTraceEnabled() = isEnabled(LogLevel.Trace)
override fun isDebugEnabled() = isEnabled(LogLevel.Debug)
override fun isInfoEnabled() = isEnabled(LogLevel.Info)
override fun isErrorEnabled() = isEnabled(LogLevel.Error)
override fun isFatalEnabled() = isEnabled(LogLevel.Fatal)
override fun isWarnEnabled() = isEnabled(LogLevel.Warn)
}
}
}
Loading