I'm wondering how should I initialize fields in Spring Beans? Here is several possible solutions:
1. Initialize fields directly on declaration
import org.springframework.stereotype.Component;
#Component
public class DeclarationInit {
private final int field = Integer.MAX_VALUE;
public int getField() {
return field;
}
}
2. Initialize fields using #Value annotation
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class ValueInit {
#Value("#{T(Integer).MAX_VALUE}")
private int field;
public int getField() {
return field;
}
}
3. Initialize fields using #Autowired annotation
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class AutowiredInit {
private int field;
#Autowired
private void initField() {
field = Integer.MAX_VALUE;
}
public int getField() {
return field;
}
}
4. Initialize fields using #PostConstruct annotation
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
#Component
public class PostConstructInit {
private int field;
#PostConstruct
private void initField() {
field = Integer.MAX_VALUE;
}
public int getField() {
return field;
}
}
All tests succeeds and do not show any difference:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = SomeTestContextConfiguration.class)
public class FieldInitTest {
#Autowired
private DeclarationInit declarationInit;
#Autowired
private ValueInit valueInit;
#Autowired
private AutowiredInit autowiredInit;
#Autowired
private PostConstructInit postConstructInit;
#Test
public void shouldInitializeFieldOnDeclaration() {
assertThat(declarationInit.getField(), equalTo(Integer.MAX_VALUE));
}
#Test
public void shouldInitializeFieldWithValueAnnotation() {
assertThat(valueInit.getField(), equalTo(Integer.MAX_VALUE));
}
#Test
public void shouldInitializeFieldWithAutowiredSetter() {
assertThat(autowiredInit.getField(), equalTo(Integer.MAX_VALUE));
}
#Test
public void shouldInitializeFieldWithPostConstruct() {
assertThat(postConstructInit.getField(), equalTo(Integer.MAX_VALUE));
}
}
Are this declarations equal to each other or should I use only one of them or neither of them?
Assuming the value is a constant, the first option is the simplest to understand and works without Spring, simplifying unit testing.
The second and fourth option are more complex and introduce an unnecessary dependency on the Spring container without any benefit. The third option is outright bizarre, since you're using #Autowired and not performing dependency injection.
I believe spring offers all those options because you might run into different requirements...
If you want MAX_INT and there's no way on earth anyone needs to initialize it differently, then it's enough to declare int field = Integer.MAX_INT regardless of Spring.
If you do want to allow other initial configurations, then you can initialize it using #Autowired, or through a constructor arg, or setter/getter... it's a matter of taste.
#PostConstruct is more suitable for complex situations, e.g. if your field needs to be calculated based on other injected fields.
Related
I am trying to implement a singleton pattern with a caching feature. At first MySingleton was only a POJO and things were simple enough, but then I needed to add a new feature, which also required autowiring a bean. (MyComponent is really an interface to a data repository)
I put the #Component annotation on MySingleton to trigger the autowiring (even though it is always called a static way) and created a private constructor to pass the MyComponent reference to an object created by new. This code seems to work, although I do not fully understand why.
My question: I feel like I'm doing it wrong, but am I?
(would you approve this pull request to your code base?)
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
#SpringBootTest
public class MySingletonTest {
#Test
public void test() {
assertNotNull(MySingleton.getInstance().getMyComponent());
}
}
// ----------------------------------------------------------------------------- //
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class MySingleton {
private static final long CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour
private static final AtomicReference<MySingleton> INSTANCE = new AtomicReference<MySingleton>();
private final Calendar timestamp; // NOTE: this is NOT static!
#Autowired
private static MyComponent myComponent;
private MySingleton(MyComponent myComponent) {
this.timestamp = Calendar.getInstance();
MySingleton.myComponent = myComponent; // I do not understand why this line is needed
}
private boolean isTimeout() {
return Calendar.getInstance().getTimeInMillis() - timestamp.getTimeInMillis() > CACHE_TIMEOUT;
}
public static synchronized MySingleton getInstance() {
if ( INSTANCE.get() == null || INSTANCE.get().isTimeout() ) {
INSTANCE.set(new MySingleton(myComponent));
}
return INSTANCE.get();
}
public MyComponent getMyComponent() {
return myComponent;
}
}
// ----------------------------------------------------------------------------- //
import org.springframework.stereotype.Component;
#Component
public class MyComponent {
}
I am trying to dynamically update the #value annotated fields in my application.
First of all, this application has a custom property source, with source being a Map<Object, String>.
A timer is enabled to update the values after a minute interval.
package com.test.dynamic.config;
import java.util.Date;
import java.util.Map;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.util.StringUtils;
public class CustomPropertySorce extends EnumerablePropertySource<Map<String, Object>> {
public CustomPropertySorce(String name, Map<String, Object> source) {
super(name, source);
new java.util.Timer().schedule(new java.util.TimerTask() {
#Override
public void run() {
source.put("prop1", "yoyo-modified");
source.put("prop2", new Date().getTime());
System.out.println("Updated Source :" + source);
}
}, 60000);
}
#Override
public String[] getPropertyNames() {
// TODO Auto-generated method stub
return StringUtils.toStringArray(this.source.keySet());
}
#Override
public Object getProperty(String name) {
// TODO Auto-generated method stub
return this.source.get(name);
}
}
Initial values of source Map<String, Object> is supplied from the PropertySourceLocator. (This is not the real scenario, but I am trying to recreate the logic used here)
package com.test.dynamic.config;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
public class CustomPropertySourceLocator implements PropertySourceLocator {
#Override
public PropertySource<?> locate(Environment environment) {
Map<String, Object> source=new HashMap<String,Object>(){{put("prop1","yoyo");put("prop2",new Date().getTime());}};
return new CustomPropertySorce("custom_source",source);
}
}
RestController class where I inject these properties using #Value is given below.
environment.getProperty("prop1"); is supplying updated value, but not the #value annotated fields.
I also tried to inject a new property source updatedMap using the addFirst method of environment.propertySources() assuming that it will take precedence over the others. But that effort also went futile. any clue is much appreciated.
package com.test.dynamic.config.controller;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class DataController {
#Value("${prop1}")
private String propertyOne;
#Value("${prop2}")
private Long propertyTwo;
#Autowired
private ConfigurableEnvironment environment;
#GetMapping("/p1")
private String getProp1() {
System.out.println("~~~~>"+environment.getPropertySources());
environment.getPropertySources().forEach(ps -> {
if(ps.containsProperty("prop1") || ps.containsProperty("prop2")) {
System.out.println("*******************************************************");
System.out.println(ps.getName());
System.out.println(ps.getProperty("prop1"));
System.out.println(ps.getProperty("prop2"));
System.out.println("*******************************************************");
}
});
// env.get
return propertyOne;
// return environment.getProperty("prop1");
}
#GetMapping("/p2")
private Long getProp2() {
System.out.println("~~~~>"+environment.getPropertySources());
// env.get
return propertyTwo;
// return environment.getProperty("prop1");
}
#GetMapping("/update")
public String updateProperty() {
Map<String, Object> updatedProperties = new HashMap<>();
updatedProperties.put("prop1", "Property one modified");
MapPropertySource mapPropSource = new MapPropertySource("updatedMap", updatedProperties);
environment.getPropertySources().addFirst(mapPropSource);
return environment.getPropertySources().toString();
}
}
If you think this is not the right way of injecting values to a RestController, please let me know. All possible alternate suggestions/best practices are accepted.
Thank you #flaxel. I used #RefreshScope to resolve this issue.
Posting the solution here if it helps someone with the same query.
In this particular case, I applied #RefreshScope on my Controller to refresh the bean with new values.
You can refer to this link before applying #RefreshScope to your bean.
It is the spring boot actuator that facilitates this refresh mechanism. So in order for this to work, you must have actuator in your classpath.
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: "${springboot_version}"
Then as discussed earlier, add RefreshScope to the bean that needs to be refreshed.
Finally, invoke the actuator/refresh endpoint to trigger the refresh.
If you want to programmatically do it, Autowire an instance of RefreshEndpoint class to your bean and invoke the refresh() method in it.
[Note: You don’t have to strictly follow this approach, but I am giving a clue that it can be Autowired]
#RefreshScope
#RestController
public class DataController {
#Value("${prop1}")
private String prop1;
#Autowired
private RefreshEndpoint refreshEndpoint;
#GetMapping("/p1")
public String getProp1(){
return prop1;
}
#getMappig("/refresh")
public void refresh(){
refreshEndpoint.refresh();
}
}
**************** MORE (if you are developing a library) ********************
What if you are developing a library and you have to get the RefreshEndpoint instance from the current ApplicationContext?
Simply Autowiring RefreshEndpoint may give you a null reference. Instead, you can get hold of the current ApplicationContext by the method given below. And use the ApplicationContext to get the RefreshEndpoint instance to invoke the refresh() method on it.
public class LocalApplicationContextFetcher implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
private static ApplicationContext ctx;
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ctx = applicationContext;
}
public static ApplicationContext getCtx() {
return ctx;
}
public static void refresh(){
ctx.getBean(RefreshEndpoint.class).refresh();
}
}
Finally, add this class to the spring.factories to get invoked by spring.
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.x.y.z.LocalApplicationContextFetcher
I have a random class in a random package that is loaded through reflection after the app launches, is there a way for it to be registered as a component under springboot and have annotations such as #Autowired and #Value etc work for that class.
It works when it is in the same package at launch time, but if introduce it thorough another jar at runtime (same package or not) it doesn't work.
Below are samples that don't work even if it is in the same jar. I can't change the app's configuration - it would defeat the "random package/random class" objective.
Code in Spring boot application package
package sample.app
#SpringBootApplication
public class Application {
public static void main(String[] args) {
// Code that starts app
//
//
try {
Thread.sleep(7000);
Class test = Class.forName("test.Test", true, Application.class.getClassLoader());
System.out.println(test.getMethod("getName").invoke(null)); //NPE
System.out.println(test.getMethod("getProfiles").invoke(null)); //NPE
} catch (Throwable t) {
t.printStackTrace();
}
}
}
Test.java
package test;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.DependsOn;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
#DependsOn("blaaaaaaaah")
#ComponentScan
public class Test {
#DependsOn("blaaaaaaaah")
public static String getName() {
return SpringGetter.instance.getApplicationName();
}
#DependsOn("blaaaaaaaah")
public static String[] getProfiles() {
String[] profiles = SpringGetter.instance.getEnv().getActiveProfiles();
if (profiles == null || profiles.length == 0) {
profiles = SpringGetter.instance.getEnv().getDefaultProfiles();
}
return profiles;
}
}
SpringGetter.java
package test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
#Component("blaaaaaaaah")
public class SpringGetter implements InitializingBean {
public static SpringGetter instance;
#Value("${spring.application.name}")
private String applicationName;
#Autowired
private Environment env;
public SpringGetter() {
System.out.println("consASFJEFWEFJWDNFWJVNJSBVJWNCJWBVJNVJNVJSNJSNCSDJVNSVJtruct");
}
public String getApplicationName() {
return applicationName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public Environment getEnv() {
return env;
}
public void setEnv(Environment env) {
this.env = env;
}
#PostConstruct
public void setInstance() {
instance = this;
}
#Override
public void afterPropertiesSet() throws Exception {
instance = this;
}
}
EDIT:
I managed to dynamically create the SpringGetter class as part of the same package as the Application class(the one with the #SpringBootApplication). I got Test.java to point to that dynamic class and yet no luck.
To simply inject fields into a POJO as if it were a Spring-managed bean, you can use something like the following:
#Component
public class BeanInitializer implements ApplicationContextAware {
private AutowireCapableBeanFactory beanFactory;
#Override
public void setApplicationContext(final ApplicationContext applicationContext) {
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
public void initializeObject(Object pojo) {
beanFactory.autowireBean(pojo);
}
}
Note, however, that this only injects fields marked as #Autowired or #Injected. It does not create proxies that honor method interception strategies based on e.g. #Transactional, #Async, etc.
If you're using Spring 5, have a look at the registerBean() method from GenericApplicationContext. You can find an example here: https://www.baeldung.com/spring-5-functional-beans
The issue in your Test class may also be that you're not loading the Spring Boot context from the main class. You can use the SpringBootTest annotation for this.
I wanted to play around with the different types of bean scopes. So I wrote a test environment which should generate a random number so I could see if a bean had changed. My test setup does not work and I can not explain what I found out.
I'm using Spring Boot 2.13 with the Spring Framework 5.15.
Following setup:
Main class:
package domain.webcreator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class WebcreatorApplication {
public static void main(String[] args) {
SpringApplication.run(WebcreatorApplication.class, args);
}
}
Beans class:
package domain.webcreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Random;
#Configuration
public class Beans {
#Bean
public Random randomGenerator() {
return new Random();
}
}
Scoper class:
package domain.webcreator;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import java.util.Random;
#Service
#Scope("singleton")
public class Scoper {
private Random rand;
public Scoper(Random rand) {
this.rand = rand;
}
public int getNumber(int max) {
return rand.nextInt(max);
}
}
Index Controller
package domain.webcreator.controller;
import domain.webcreator.Scoper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
public class IndexController {
#GetMapping("/")
#ResponseBody
#Autowired
public String indexAction(Scoper scoper) {
return String.valueOf(scoper.getNumber(50));
}
}
My problem is, that I get an NPE while calling scoper.getNumber(50).
This is strange because when debugging, a Random bean is generated and passed to the scoper constructor.
Later on, in the controller, the rand property is null.
What am I doing wrong?
You're trying to apply #Autowired to a random method, which isn't how Spring works. Controller method parameters are for information specific to that HTTP request, not general dependencies, and so Spring is trying to create a new Scoper that is associated with the request--but it doesn't have any incoming values in the request to fill in. (I'm actually surprised you're not getting an error about no default constructor.)
Instead, pass your Scoper in a constructor.
#RestController
public class IndexController {
private final Scoper scoper;
public IndexController(Scoper scoper) {
this.scoper = scoper;
}
#GetMapping("/")
public String indexAction(Scoper scoper) {
return String.valueOf(scoper.getNumber(50));
}
}
A couple of notes:
Singleton scope is the default, and there's no need to specify it.
#RestController is preferable to repeating #ResponseBody unless you have a mixed controller class.
In my Spring Boot test I'm using 2 mock beans with different qualifiers:
#RunWith(SpringRunner.class)
#SpringBootTest
class HohoTest {
#MockBean #Qualifier("haha") IHaha ahaha;
#MockBean #Qualifier("hoho") IHaha ohoho;
}
Since I'm not using these beans explicitly, I would rather move them away from the class body, as the #MockBean annotation is now repeatable:
#RunWith(SpringRunner.class)
#SpringBootTest
#MockBean(IHaha.class)
#MockBean(IHaha.class)
class HohoTest {}
However, I need to pass in a qualifier as well, since they have the same type. Any idea on how I can achieve that?
Because using annotation #Qualifier means choose bean by name, so you can set up a name for a mock with code like this:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {JsonMapperConfig.class})
public class IntegrationFlowTest {
#MockBean(name = "s3MessageRepository")
private S3Repository s3MessageRepository;
// etc
If it is okay to move the mock definition completely out of the test class, you could also create the mocks in a separate #Configuration class:
#Configuration
public class MockConfiguration
{
#Bean #Qualifier("haha")
public IHaha ahaha() {
return Mockito.mock(IHaha.class);
}
#Bean #Qualifier("hoho")
public IHaha ohoho() {
return Mockito.mock(IHaha.class);
}
}
When declaring #MockBean at the class level, there is currently no support for providing a qualifier.
If you would like to have such support, I suggest you request it in the Spring Boot issue tracker.
Otherwise, you will need to continue declaring #MockBean on fields alongside #Qualifier.
I had a similar requirement of injecting mocked service beans with #Order annotation. I also needed to verify the invocation count of service functions. Below is my implementation. It might help someone.
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBootTest
public class ServiceNameTest {
#Autowired private ServiceName serviceName;
// Important: Used to reset interaction count of our static
// bean objects before every test.
#Before
public void reset_mockito_interactions() {
Mockito.clearInvocations(MockServicesConfig.bean1);
Mockito.clearInvocations(MockServicesConfig.bean2);
}
#Configuration
public static class MockServicesConfig {
public static InterfaceName bean1;
public static InterfaceName bean2;
#Bean
#Order(1)
public InterfaceName bean1() {
bean1 = Mockito.mock(InterfaceName.class);
// Common when() stubbing
return bean1;
}
#Bean
#Order(2)
public InterfaceName vmpAdapter() {
bean2 = Mockito.mock(InterfaceName.class);
// Common when() stubbing
return bean2;
}
}
#Test
public void test_functionName_mock_invocation1() {
// Arrange --> Act --> Assert
// nullify other functions custom when() stub.
// updating this functions custom when() stub.
verify(MockServicesConfig.bean1, times(1)).functionName("");
}
#Test
public void test_functionName_mock_invocation2() {
// Arrange --> Act --> Assert
// nullify other functions custom when() stub.
// updating this functions custom when() stub.
verify(MockServicesConfig.bean1, times(1)).functionName("");
}
}
This should now work
#SpringBootTest(
classes = Some.class
)
#MockBean(name = BEAN_NAME, classes = TheBeanClass.class)
#MockBean(name = BEAN_NAME_2, classes = TheBeanClass.class)
class SomeTest {
private final Some some;
#Autowired
SomeTest(Some some) {
this.some = some;
}
}
Please note, if you need to use any of the mocked beans, you will have to put the #Qualifier in the constructor, for example
private final TheBeanClass theBeanclass;
private final Some some;
#Autowired
SomeTest(Some some, #Qualifier(BEAN_NAME) TheBeanClass theBeanClass) {
this.some = some;
this.theBeanClass = theBeanClass;
}