I'm working on an app and I only want to use Spring's DI features. My problem is that I can't manage to disable Spring Boot's autoconfiguration features. I keep getting this exception:
2020-06-26 14:00:03.240 [main] TRACE o.s.b.diagnostics.FailureAnalyzers - Failed to load org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer
java.lang.NoClassDefFoundError: org/springframework/jdbc/CannotGetJdbcConnectionException
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
at java.lang.Class.getConstructor0(Class.java:3075)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at org.springframework.boot.diagnostics.FailureAnalyzers.loadFailureAnalyzers(FailureAnalyzers.java:75)
at org.springframework.boot.diagnostics.FailureAnalyzers.<init>(FailureAnalyzers.java:66)
at org.springframework.boot.diagnostics.FailureAnalyzers.<init>(FailureAnalyzers.java:60)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:204)
at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:441)
at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:427)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:312)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at MyApplication.main(MyApplication.java:19)
I tried not using #SpringBootApplication only #ComponentScan, but it didn't work. I also tried excluding the relevant autoconfiguration classes:
#SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
XADataSourceAutoConfiguration.class})
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication .class, args);
}
}
but this is not working either. How can I get rid of autoconfiguration completely? I didn't find a #DisableAutoConfiguration annotation, yet there is a #EnableAutoConfiguration.
Edit:
My beans are configured in a separate file in the same package as my application class:
#Configuration
public class BeanConfig {
#Bean
public Database database() {
return DatabaseConfig.configureDatabase();
}
#Bean
public UserRepository userRepository() {
return new InMemoryUserRepository(
Collections.singletonList(new UserEntity(
1,
"user",
Arrays.asList(Permission.ADOPT.name(), Permission.EDIT_PROFILE.name()))));
}
#DependsOn("database")
#Bean
public DogRepository dogRepository() {
return new H2DogRepository();
}
#Bean
public AdoptDog adoptDog() {
return new AdoptDog(dogRepository());
}
#Bean
public FindDogs findDogs() {
return new FindDogs(dogRepository());
}
#Bean
public CreateDog createDog() {
return new CreateDog(dogRepository());
}
#Bean
public DeleteDogs deleteDogs() {
return new DeleteDogs(dogRepository());
}
#Bean
public FindUser findUser() {
return new FindUser(userRepository());
}
#PostConstruct
public void initialize() {
ApplicationEngine engine = ServerConfig.configureServer(
userRepository(), adoptDog(), findDogs(), createDog(), findUser(), deleteDogs());
DogUtils.loadDogs().forEach(dogRepository()::create);
CheckerUtils.runChecks(engine, userRepository());
}
}
I have a different server technology, and I'm using this project to demonstrate Kotlin-Java interoperability (all these files are Kotlin files which are referenced here).
The problem was that Spring was looking for classes which I didn't have (I'm not using any of them) but was necessary for it to work.
Once I added "org.springframework.boot:spring-boot-starter-data-jdbc" and "javax.validation:validation-api:2.0.1.Final" as dependencies it started to work.
Add only spring-boot to dependencies (don't use *-starter-*)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
, and use #ComponentScan instead of #SpringBootApplication.
Actually this may be down to spring.factories in some of your jars and authors violating it. (i.e not listing them under the key of EnableAutoConfiguration as per https://docs.spring.io/autorepo/docs/spring-boot/2.0.0.M3/reference/html/boot-features-developing-auto-configuration.html
In this example, an Application Listener will be registered regardless of auto config enabled or not. May be some of the jars in your class path have beans defined in spring factories
https://www.logicbig.com/tutorials/spring-framework/spring-boot/application-listener-via-spring-factories.html
Related
I'm new to spring boot and I'm trying to wrap my head around how to make dependency injection work for deployment and testing.
I have a #RestController and a supporting #Service. The service injects another class that is an interface for talking to Kafka. For the Kafka interface I have two implementations: one real and one fake. The real one I want to use in production and the fake in test.
My approach is to use two different configuration for each environment (prod and test).
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
Then in my main application I would like to somehow load AppConfiguration.
#SpringBootApplication
public class DeployerServiceApiApplication {
public static void main(String[] args) {
SpringApplication.run(DeployerServiceApiApplication.class, args);
}
// TODO: somehow load here...
}
And in my test load the fake configuration somehow
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest {
#Autowired private MockMvc mockMvc;
// TODO: somehow load AppTestConfiguration here
#Test
public void testDeployAction() throws Exception {
...
ResultActions resultActions = mockMvc.perform(...);
...
}
}
I've spent the better part of a day trying to figure this out. What I'm trying to accomplish here is fundamental and should be straight forward yet I keep running into issues which makes me wonder if the way I'm thinking about this is all wrong.
Am not sure if i understand your question completely but from description i guess you wish to initialize bean based on environment. Please see below.
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
and then you can pass the "-Dspring.profiles.active=prod" argument while starting you application using java command or you can also specify the profile in your test case like below.
#SpringBootTest
#ActiveProfile("test")
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest
Use spring profiles, you can annotate your test class with #ActiveProfiles("test-kafka") and your test configuration with #Profile("test-kafka").
This is pretty easy task in spring boot world
Rewrite your classes as follows:
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
This will instruct spring boot to load the relevant configuration when the "prod"/"test" specified.
Then you can start your application in production with --spring.profiles.active=prod and in the Test you can write something like this:
#SpringBootTest
#ActiveProfiles("test")
public class DeployerServiceApiApplicationTest {
...
}
If you want to run all the tests with this profile and do not want to write this ActiveProfiles annotation you can create src/test/resources/application.properties and put into it: spring.active.profiles=test
I am trying to enable loadtimeweaving without javaagent jar files of aspectweaver and spring-instrument. This what I have implemented to achieve the same But it's not working.
#ComponentScan("com.myapplication")
#EnableAspectJAutoProxy
#EnableSpringConfigured
#EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.AUTODETECT)
public class AopConfig implements LoadTimeWeavingConfigurer {
#Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
/**
* Makes the aspect a Spring bean, eligible for receiving autowired components.
*/
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() throws Throwable {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
}
A workaround I found was to hot-attach InstrumentationSavingAgent from spring-instrument instead of starting the agent via -javaagent command line parameter. But for that you need an Instrumentation instance. I just used the tiny helper library byte-buddy-helper (works independently of ByteBuddy, don't worry) which can do just that. Make sure that in Java 9+ JVMs the Attach API is activated if for some reason this is not working.
So get rid of implements LoadTimeWeavingConfigurer and the two factory methods in your configuration class and just do it like this:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.10.14</version>
</dependency>
#SpringBootApplication
public class Application {
public static void main(String[] args) {
Instrumentation instrumentation = ByteBuddyAgent.install();
InstrumentationSavingAgent.premain("", instrumentation);
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// ...
}
}
Feel free to ask follow-up questions if there is anything you do not understand.
Update: One more thing I noticed is that this only works for me with aspectjWeaving = ENABLED, not with AUTODETECT. And for one sample Spring bean I noticed that #Component did not work, probably because of some bootstrapping issue between Spring vs AspectJ. Hence, I replaced it by an explicit #Bean configuration, then it worked. Something like this:
#Configuration
#ComponentScan("com.spring.aspect.dynamicflow")
#EnableLoadTimeWeaving(aspectjWeaving = ENABLED)
public class ApplicationConfig {
#Bean
public JobProcess jobProcess() {
return new JobProcessImpl();
}
}
I'm implementing micrometer to spring web project. While trying to add #Timed annotation. Prior to add #Timed i'm supposed to create TimedSpect bean. But it says could not autowire no bean of MeterRegistry type found
#Configuration
public class MetricsCofiguration {
#Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
Not sure if you still need this answer but this is what i tried and its working fine.
#Configuration
#EnableAspectJAutoProxy
public class TimedConfiguration {
#Autowired
MeterRegistry registry;
#Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
Make sure you have below starter dependency in your pom.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
I'm trying to use #EnableMongoRepositories for using two separate mongo repositories like:
#Configuration
#EnableMongoRepositories(mongoTemplateRef = "mongoBOTemplate", basePackages = "sandbox.dao.bo")
public class BOMongoConfig {
#Value("#{mongo.hostBO}")
private String hostBO;
#Value("#{mongo.databaseBO}")
private String databaseBO;
#Bean
public MongoDbFactory mongoBODbFactory() throws Exception {
return new SimpleMongoDbFactory(new MongoClient(hostBO), databaseBO);
}
#Bean
public MongoTemplate mongoBOTemplate() throws Exception {
return new MongoTemplate(mongoBODbFactory());
}
}
and
#Configuration
#EnableMongoRepositories(mongoTemplateRef = "mongoTemplate", basePackages = "sandbox.dao.sandbox")
public class SandboxMongoConfig {
#Value("#{mongo.host}")
private String host;
#Value("#{mongo.database}")
private String database;
#Bean
public MongoDbFactory mongoDbFactory() throws Exception {
return new SimpleMongoDbFactory(new MongoClient(host), database);
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongoDbFactory());
}
}
but I'm confused because of this error:
710 [RMI TCP Connection(2)-127.0.0.1] ERROR org.springframework.web.servlet.DispatcherServlet - Context initialization failed
java.lang.IllegalArgumentException: Environment must not be null!
at org.springframework.util.Assert.notNull(Assert.java:112)
at org.springframework.data.repository.config.RepositoryConfigurationSourceSupport.<init>(RepositoryConfigurationSourceSupport.java:50)
at org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource.<init>(AnnotationRepositoryConfigurationSource.java:74)
at org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport.registerBeanDefinitions(RepositoryBeanDefinitionRegistrarSupport.java:74)
at org.springframework.context.annotation.ConfigurationClassParser.processImport(ConfigurationClassParser.java:340)
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:233)
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:154)
at org.springframework.context.annotation.ConfigurationClassParser.processImport(ConfigurationClassParser.java:349)
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:233)
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:154)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:140)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:282)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:223)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:630)
As I understood there is only one option to fix it is using #Profile. I'm using maven to profile management and not sure why I need hardcore profiles in code...
Could anyone help me with misunderstanding?
Thanks.
Well, you have to somehow show spring which of those configurations to use for particular case. Otherwise how would it be possible decide which MongoDbFactory instance to create? So yes, use #Profile above both #Configuration classes.
Also please note that maven profiles are not spring profiles. Might be that you dont have to mix maven into that ( if maven profile is only use to set spring one ). I such case you can add -Dspring.profiles.active=profile while running your app.
I'm using JavaConfig to manage and wire Spring beans into my Java app. The Java application is a main method - and basically runs as a batch job, invoked via a bash file. Is there a way that I can use a different (test) config in my main method?
public static void main(String[] args) {
final ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
// do Stuff
}
I have used the following annotations successfully before in my test classes:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { TestConfig.class })
, but this does not work for "main" applications. Short of passing in the Spring context to use as an argument, not sure what I can do here. Thanks
You should be able to use profiles in your actual config class to do what you want as well.
By setting the desired Profile you can "inject" the different beans you want.
Your ApplicationConfig might look like:
#Configuration
#Import({
JndiDataConfig.class,
TestDataConfig.class,
)
public class ApplicationConfig {
...
where TestDataConfig looks (in part) like:
#Configuration
#Profile("test")
public class TestDataConfig {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
and where JndiDataConfig looks like:
#Configuration
#Profile("production")
public class JndiDataConfig {
#Bean
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}