How can I let spring evaluate varargs and run beans conditionally?
#SpringBootConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
#Service
#Order(1) //run if no vararg is given
public class Job1 implements ApplicationRunner {
}
#Service
#Order(2) //TODO run only if a "job2" occurs as vararg
public class Job2 implements ApplicationRunner {
}
Question: I want to start those jobs conditionally by varargs given from command line on application start. Is that possible?
How about you use #ConditionalOnProperty on those beans and you pass the property to include/exclude them from the command line?
A full example could be:
#Service
#Order(1) //run if no vararg is given
#ConditionalOnProperty(name = "my.property", havingValue = "job1")
public class Job1 implements ApplicationRunner {
}
#Service
#Order(2) //TODO run only if a "job2" occurs as vararg
#ConditionalOnProperty(name = "my.property", havingValue = "job2")
public class Job2 implements ApplicationRunner {
}
and then you pass the property with:
mvn spring-boot:run -Dspring-boot.run.arguments="--my.property=job1/job2"
Another option could be removing #Component/#Service from the Job1/Job2 classes and in a configuration class creating a bean like:
#Bean
public ApplicationRunner applicatinRunner(ApplicationArguments arguments) throws IOException {
String commandLineArgument = arguments.getSourceArgs()[0];
//your logic here to decide which one you want to instantiate
return new Job1()/Job2();
}
It's possible by simply implementing a custom Condition and evaluating ApplicationArguments. The arguments I was looking for are called non-optional args by Spring:
class Job2Condition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getBeanFactory()
.getBean(ApplicationArguments.class)
.getNonOptionArgs()
.contains("job2");
}
}
#Component
#Conditional(Job2Condition.class)
public class Job2 implements ApplicationRunner {
}
Related
I have a service that uses some object as a generic
#Component
#RequiredArgsConstructor
public class SomeGenericService<T extends Base> {
private final T base;
public void someWork(String info) {
base.someAction(info);
}
}
I also have 3 Base implementations marked with #Component(Base1, Base2, Base3)
I want spring itself to create a service with the generic it needs, for the following example
#Component
#RequiredArgsConstructor
public class Runner implements CommandLineRunner {
private final SomeGenericService<Base1> s1;
private final SomeGenericService<Base2> s2;
private final SomeGenericService<Base3> s3;
#Override
public void run(String... args) throws Exception {
String someString = "text";
s1.someWork(someString);
s2.someWork(someString);
s3.someWork(someString);
}
}
But after the launch, the spring does not understand what I want from it.
Parameter 0 of constructor in SomeGenericService required a single bean, but 3 were found:
- base1: defined in file [Base1.class]
- base2: defined in file [Base2.class]
- base3: defined in file [Base3.class]
Is it possible to set this to automatic, without manually configuring it via the #Bean annotation for each service?
You need to define how those beans should be injected. It's a good practice to have some #Configurations for this purpose. Something like:
#Configuration
#Import({
Base1.class,
Base2.class,
Base3.class
})
public class SomeConfig {
#Bean
SomeGenericService<Base1> someGenericService1() {
return new SomeGenericService(new Base1());
}
#Bean
SomeGenericService<Base2> someGenericService2() {
return new SomeGenericService(new Base2());
}
#Bean
SomeGenericService<Base3> someGenericService3() {
return new SomeGenericService(new Base3());
}
}
I have multiple bean be annotated with #ConditionalOnMissingBean, which will be used? How can i control the priority?
If you want to control #Bean creation ordering, you can use the annotation #Order
#Component
#Order(1)
public class First {
public int first() {
return 1;
}
}
#Component
#Order(2)
public class Second {
public int second() {
return 2;
}
}
Or you can also use #DependsOn
#Configuration
public class ActionCfg {
#Bean
#DependsOn({"actionA","actionB"})
public ActionC actionC(){
return new ActionC();
}
#Bean("ActionA")
public ActionA actionA() {
return new ActionA();
}
#Bean("ActionB")
public ActionB actionB() {
return new ActionB();
}
}
ActionA and ActionB will initialized before ActionC.
The bean whose auto-configuration class is run first will be taken.
There is #AutoConfigureBefore, #AutoConfigureAfter and #AutoConfigureOrder to control the order of auto-configuration classes.
I have a test utility for with I need to have a fresh instance per test method (to prevent that state leaks between tests). So far, I was using the scope "prototype", but now I want to be able to wire the utility into another test utility, and the wired instances shall be the same per test.
This appears to be a standard problem, so I was wondering if there is a "test method" scope or something similar?
This is the structure of the test class and test utilities:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTest {
#Autowired
private TestDriver driver;
#Autowired
private TestStateProvider state;
// ... state
// ... methods
}
#Component
#Scope("prototype") // not right because MyTest and TestStateProvider get separate instances
public class TestDriver {
// ...
}
#Component
public class TestStateProvider {
#Autowired
private TestDriver driver;
// ...
}
I'm aware that I could use #Scope("singleton") and #DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) but this refreshes more than I need – a new TestDriver instance for each test would be enough. Also, this approach is error-prone because all tests using the TestDriver would need to know that they also need the #DirtiesContext annotation. So I'm looking for a better solution.
It is actually pretty easy to implement a testMethod scope:
public class TestMethodScope implements Scope {
public static final String NAME = "testMethod";
private Map<String, Object> scopedObjects = new HashMap<>();
private Map<String, Runnable> destructionCallbacks = new HashMap<>();
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if (!scopedObjects.containsKey(name)) {
scopedObjects.put(name, objectFactory.getObject());
}
return scopedObjects.get(name);
}
#Override
public void registerDestructionCallback(String name, Runnable callback) {
destructionCallbacks.put(name, callback);
}
#Override
public Object remove(String name) {
throw new UnsupportedOperationException();
}
#Override
public String getConversationId() {
return null;
}
#Override
public Object resolveContextualObject(String key) {
return null;
}
public static class TestExecutionListener implements org.springframework.test.context.TestExecutionListener {
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) testContext
.getApplicationContext();
TestMethodScope scope = (TestMethodScope) applicationContext.getBeanFactory().getRegisteredScope(NAME);
scope.destructionCallbacks.values().forEach(callback -> callback.run());
scope.destructionCallbacks.clear();
scope.scopedObjects.clear();
}
}
#Component
public static class ScopeRegistration implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
factory.registerScope(NAME, new TestMethodScope());
}
}
}
Just register the test execution listener, and there will be one instance per test of all #Scope("testMethod") annotated types:
#RunWith(SpringRunner.class)
#SpringBootTest
#TestExecutionListeners(listeners = TestMethodScope.TestExecutionListener.class,
mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
public class MyTest {
#Autowired
// ... types annotated with #Scope("testMethod")
}
I ran into the same problem some time ago and came to this solution:
Use Mocks
I wrote some methods to create specific mockito settings to add behavior to each mock.
So create a TestConfiguration class with following methods and bean definition.
private MockSettings createResetAfterMockSettings() {
return MockReset.withSettings(MockReset.AFTER);
}
private <T> T mockClass(Class<T> classToMock) {
return mock(classToMock, createResetAfterMockSettings());
}
and your bean definition will look like:
#Bean
public TestDriver testDriver() {
return mockClass(TestDriver .class);
}
MockReset.AFTER is used to reset the mock after the test method is run.
And finally add a TestExecutionListeners to your Test class:
#TestExecutionListeners({ResetMocksTestExecutionListener.class})
I want to execute a very simple example which explains the IoC-concept in Spring-Boot.
For that I have create a Bean which gets #Autowired to a main-class, which has a method which does something with the bean.
The bean:
The main:
#Component
public class MyMain {
#Autowired
private MyBean bean1;
public void usingTheBean()
{
bean1.setName("Thats my first bean!");
bean1.setAttribute("And thats just an attribute");
System.out.println(bean1);
}
public static void main(String[] args) {
//MyMain main = new MyMain();
//main.usingTheBean();
}
}
My SpringBootApplication:
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
MyMain main = new MyMain();
main.usingTheBean();
}
}
How can I start the Main-class? with out getting the
java.lang.NullPointerException
for the #Autowired Bean "MyBean" in the main?
I know that the reason for the NullPointer-Exception is that I created the Main-class with the "new" keyword.
But the question focuses more on the question "How can I start a main-class with spring-boot"
Usually, you do not want to use the context directly to create a bean yourself. You should just let the context initialize and then just use the autowired beans. Most likely, the way you approach this problem is very different from the Spring-way of achieving it.
You should have a look at the following examples:
using the CommandLineRunner interface (see here) or
using the InitializingBean interface (see here)
Alternatively, you can solve this via configuration:
#Configuration
public class MyConfig {
#Bean
public MyBean myBean() {
MyBean bean = new MyBean();
bean.setName("...");
bean.setAttribute("...");
return bean;
}
}
You can then simply use
#Autowired
MyBean myBean;
to autowire it.
Yet another alternative would be to inject the values from a config file (e.g. application.properties) if this is possible in your case:
#Component
public class MyBean {
#Value("${my.config.value}")
private String name;
#Value("${my.config.attribute}")
private String attribute;
public MyBean(){
}
...
Having the following entries in your application.properties:
my.config.value = Some value content
my.config.attribute = Some attribute content
Given a class:
class MyConfiguration {
#Bean
String bean() {
return new String();
}
}
as you may notice it does not have #Configuration annotation.
How can I make it behave like it has #Configuration annotation, but not adding it?
#Bean annotation should not work in Lite Mode, https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html
Something like:
#Configuration
class MainConfiguration {
#Bean
MyConfiguration myConfiguration() {
MyConfiguration myConfiguration = do_some_spring_magic();
// myConfiguration behaving like it's having #Configuration here
return myConfiguration;
}
}
I dont understand what is the use case you are trying to achieve here.. But if you are looking for programatically registering beans into the Spring Context then you can do it as below.
#Configuration
public class MyBeanRegisterFactory implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanRegistry) throws BeansException {
//depending on some condition you can do the below line
beanRegistry.registerBeanDefinition("myBeanClass", new RootBeanDefinition("com.mybean.MyBeanClass"));
}
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
In Your case, the bean definitions inside the class MyConfiguration can be programatically registered as below into to the spring context.
http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/support/BeanDefinitionRegistryPostProcessor.html