#Value not set in one specific class - spring

I'm fairly sure I'm being some kind of idiot, but for the life of me I can't see it.
I have a large Spring Boot 2.1 application that extensively uses injection of properties through the #Value annotation. This works great, has done for years. But there's one specific, brand-new object where I can't get the values set. They are always null.
I know the problem isn't with the values themselves, because some of the same values inject just fine into other objects. But I just can't see what's wrong with THIS object, and would be grateful for your eyeballs.
The values in this object (which is in the same directory and builds just fine) are always null:
#Service
public class SSOUtil {
private String domain = "https://login.microsoftonline.com/";
private String tenantId = "[deleted guid]";
public static String localEnvironment = "local";
public static String devEnvironment = "dev";
public static String testEnvironment = "test";
public static String prodEnvironment = "prod";
#Value("${actions.PROD.touchnet_azure_ad_client_secret}")
private String clientSecretTouchnetProd;
#Value("${actions.TEST.touchnet_azure_ad_client_secret}")
private String clientSecretTouchnetTest;
#Value("${actions.DEV.touchnet_azure_ad_client_secret}")
private String clientSecretTouchnetDev;
#Value("${actions.touchnet_azure_ad_client_id_dev}")
private String clientIdDev;
#Value("${actions.touchnet_azure_ad_client_id_test}")
private String clientIdTest;
#Value("${actions.touchnet_azure_ad_client_id_prod}")
private String clientIdProd;
#Value("${touchnet.redirectURLDev}")
private String redirectURLDev;
#Value("${touchnet.redirectURLTest}")
private String redirectURLTest;
#Value("${touchnet.redirectURLProd}")
private String redirectURLProd;
private String clientId;
private String clientSecret;
private String redirectURI;
public SSOUtil() {
this.redirectURI = redirectURLTest;
this.clientSecret = clientSecretTouchnetTest;
}
public String getADLoginURL() {
String returnURL = "";
System.out.println(clientIdTest); // always prints null
}
}
The values in this object work just fine, though, and note that one of them is the same #Value as in the other class:
#Service
public class LibraryHelpServiceBean implements LibraryHelpService {
private CourseServiceBean courseServiceBean;
private final RestTemplate restTemplate;
#Value("${actions.libraryhelp_lti_api_key}")
private String apikey;
#Value("${actions.touchnet_azure_ad_client_id_test}")
String clientIdTest;
public LibraryHelpServiceBean(CourseServiceBean courseServiceBean, RestTemplateBuilder restTemplateBuilder) {
this.courseServiceBean = courseServiceBean;
this.restTemplate = restTemplateBuilder.build();
}
public void doesValueWork() {
this.apikey = this.apikey;
System.out.println(this.clientIdTest); // always prints correct value, a guid
}
}
Both objects are initialized in a similar way: either directly or indirectly through the #Autowired annotation in other objects that I use (and which work fine, and have worked fine for ages). Here's the creation of SSOUtil (my problem class):
#RestController
#RequestMapping(value = "/web")
public class SSOLandingController {
#Autowired
private SSOUtil ssoUtil;
[rest of class omitted]
}
And here's the creation of LibraryHelpServiceBean, which is working fine and has all #Values populate correctly:
#Service
public class LibraryHelpStreamServiceBean implements LibraryHelpStreamService {
private LibraryHelpServiceBean libraryHelpServiceBean;
public LibraryHelpStreamServiceBean(LibraryHelpServiceBean libraryHelpServiceBean){
this.libraryHelpServiceBean = libraryHelpServiceBean;
}
}
I have already tried changing the class annotation for SSOUtil from #Service to #Component (and #Configuration, just for the heck of it).
What could be causing the #Values in SSOUtil to come back null even though some of those same #Values populate just fine in other classes?
I'm convinced that I'm missing something obvious. I'm hoping it's something small like a typo. I'm nervous that it's something big, like I've completely misunderstood how Spring IOC works for the past several years.
Thanks for your help.

I tested your case on my computer, but I'm not able to reproduce your problem. When things like this is happening, try something very simple like this
package no.mycompany.springbootapp;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class SSOUtil2 {
#Value("${actions.touchnet_azure_ad_client_id_test}")
private String clientIdTest;
}
Inject this component into your controller, set a breakpoint inside your controller method and inspect the injected instance.
My experience is that some unexplainable cases I've been involved in here on SO, were solved by cleaning the build or wiping the .m2-folder.

Related

Spring #Value not working in Spring Boot 2.5.5, getting null values

I am trying to inject some property values into variables by means of Spring #Value annotation but I get null values. I tried different configurations and triks but it doesn't work. Think is that before today everythink was working properly. I do not know what I changed in order to get things broken.
Here is my java class:
#Component
#ConditionalOnProperty(prefix = "studioghibli", name = "get")
public class StudioGhibliRestService {
#Value("${studioghibli.basepath}")
private static String BASE_PATH;
#Value("${studioghibli.path}")
private static String PATH;
#Value("${studioghibli.protocol:http}")
private static String PROTOCOL;
#Value("${studioghibli.host}")
private static String HOST;
private static String BASE_URI = PROTOCOL.concat("://").concat(HOST).concat(BASE_PATH).concat(PATH);
#Autowired
StudioGhibliRestConnector connector;
public List<StudioGhibliFilmDTO> findAllFilms() throws SipadContenziosoInternalException {
var response = connector.doGet(BASE_URI, null, null);
if (!response.getStatusCode().is2xxSuccessful() || !response.hasBody()) {
throw new SipadContenziosoInternalException(Errore.INTERNAL_REST_ERROR, "FindAll(), microservizio ".concat(BASE_URI), null);
}
return (List<StudioGhibliFilmDTO>) response.getBody();
}
}
As you can see, the class is annotated with #Component, that because I will need to use it as #Service layer in order to make a rest call in my business logic.
The class is also annotaded with conditional on property...
Here is a screenshot of the debug window at startup:
Since the PROTOCOL value is null, i get a null pointer exception immediately at start up.
Here is part of the application-dev.properties file:
studioghibli.get
studioghibli.protocol=https
studioghibli.host=ghibliapi.herokuapp.com
studioghibli.basepath=/
studioghibli.path=/films
First of all, #Value annotation does not work with static fields.
Secondly, fields with #Value annotation is processed when the instance of the class (a bean) is created by Spring, but static fields exist for a class (for any instance), so when the compiler is trying to define your static BASE_URI field other fields are not defined yet, so you get the NPE on startup.
So you might need a refactoring, try to inject values with the constructor like this:
#Component
#ConditionalOnProperty(prefix = "studioghibli", name = "get")
public class StudioGhibliRestService {
private final StudioGhibliRestConnector connector;
private final String baseUri;
public StudioGhibliRestService(StudioGhibliRestConnector connector,
#Value("${studioghibli.basepath}") String basePath,
#Value("${studioghibli.path}") String path,
#Value("${studioghibli.protocol:http}") String protocol,
#Value("${studioghibli.host}") String host) {
this.connector = connector;
this.baseUri = protocol.concat("://").concat(host).concat(basePath).concat(path);
}
// other code
}
Thanks, It works for me, I have to add some codes to my project. Then I check the spring core document in "#Value" section. Besides
When configuring a PropertySourcesPlaceholderConfigurer using
JavaConfig, the #Bean method must be static.
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}

JUnit test not loading properties file, but runtime code works correctly

I have some production code that works correctly. But the JUnit test I wrote for it does not load the testing properties file and fails.
Googled for examples of how to load test properties. None of the answers helped.
The testing properties file, stored in src/test/resources, is:
#Generated by Git-Commit-Id-Plugin
#Sat Aug 31 08:34:13 EDT 2019
build-details.branch=feature/issue-189
build-details.build.time=20190831083412
build-details.build.version=0.4.9
build-details.commit.id=3cbdbaee42d2b6bde342fce32ecd61905d8d12d4
build-details.commit.time=20190831083101
The JUnit test is:
#RunWith(MockitoJUnitRunner.class)
#TestPropertySource(locations = {"classpath:/build-details.properties"})
public class DetailsServiceTest {
private static final String TEST_BRANCH = "feature/issue-532";
private static final String TEST_BUILD_TIME = "20190831083412";
private static final String TEST_BUILD_VERSION = "0.4.9";
private static final String TEST_COMMIT_ID = "3cbdbaee42d2b6bde342fce32ecd61905d8d12d4";
private static final String TEST_COMMIT_TIME = "20190831083101";
#InjectMocks private DetailsService detailsService;
#Test
public void testGetDetails() {
final BuildDetails result = detailsService.getBuildDetails();
assertNotNull(result);
assertEquals(TEST_BRANCH,
result.getBranch());
assertEquals(TEST_BUILD_TIME,
result.getBuildTime());
assertEquals(TEST_BUILD_VERSION,
result.getBuildVersion());
assertEquals(TEST_COMMIT_ID,
result.getCommitId());
assertEquals(TEST_COMMIT_TIME,
result.getCommitTime());
}
}
And the service being tested is:
#Service
#PropertySource({"classpath:/build-details.properties"})
public class DetailsService {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
#Value("${build-details.branch") private String branch;
#Value("${build-details.build.time}") private String buildTime;
#Value("${build-details.build.version}") private String buildVersion;
#Value("${build-details.commit.id}") private String commitId;
#Value("${build-details.commit.time}") private String commitTime;
public BuildDetails getBuildDetails() {
final BuildDetails result = new BuildDetails();
result.setBranch(branch);
result.setBuildTime(buildTime);
result.setBuildVersion(buildTime);
result.setCommitId(commitId);
result.setCommitTime(commitTime);
return result;
}
}
As I mentioned before, the service works as expected. When I run the service and query it, I'm getting back:
{"branch":"${build-details.branch","buildTime":"20190831112809","buildVersion":"20190831112809","commitId":"6e709c0401627149505c91348f960e585fc5f24b","commitTime":"20190831103506"}
(values reflecting the live build).
But when I run the unit test, all of the field values are consistently null.
Using the MockitoJUnitRunner doesn't load the Spring context in the test, so those value annotations aren't getting the values injected. You could try switching to a different runner that loads the spring context to have spring inject those values from your test properties file.
Try using the Spring runner to see if that helps. Instead of using #InjectMocks you can use #Autowired to get the bean from spring which should have the values from the test properties file.
#RunWith(SpringRunner.class)
#TestPropertySource(locations = {"classpath:/build-details.properties"})
public class DetailsServiceTest {
#Autowired
private DetailsService detailsService;
//...
}
Also, it looks like you're missing a closing curly brace on one of the #Value annotations and a setter is using the build time. Guessing it should be:
#Value("${build-details.branch}") private String branch;
// ...
result.setBuildVersion(buildVersion); // instead of buildTime

Groovy + Spring - DI with no boilerplate (constructor) code

I have created spring-boot project which bases on Groovy instead of Java.
Now I have following #RestController:
#RestController
class HelloRest {
private final HelloService helloService
#GetMapping("hello")
String hello(#RequestParam("name") String name) {
helloService.createHelloMessage(name)
}
}
Question is how to inject
#Service
class HelloService {...}
in most simple way avoiding boilerplate (in this case the constructor) code?
In Java I would use: #lombok.RequiredArgsConstructor and in fact it works also if I use it in my groovy project.
On the other hand for example the #Immutable annotation from groovy.transform doesn't work as it creates in fact more than single constructor. Whereas Spring expects single constructor to be able automatically #Autowired the dependencies.
As far I see 2 solutions:
Generate the constructor
Use lombok with its annotations
Is there any solution build into Groovy which could be used here instead?
At this moment there is no Groovy mechanism that does same thing as #lombok.RequiredArgsConstructor. The main problem in your case is that Groovy always generates no-args default constructor for all currently known features like #Immutable annotation. The closest (but not accurate) way is to use #TupleConstructor like:
#RestController
#TupleConstructor(includes = ['helloService'], includeFields = true, includeProperties = false, force = true)
class HelloRest {
private final HelloService helloService
#GetMapping("hello")
String hello(#RequestParam("name") String name) {
return helloService.createHelloMessage(name)
}
}
This Groovy code will produce bytecode similar to this Java code:
#RestController
#TupleConstructor(
includeFields = true,
force = true,
includeProperties = false,
includes = {"helloService"}
)
public class HelloRest implements GroovyObject {
private final HelloService helloService;
public HelloRest(HelloService helloService) {
CallSite[] var2 = $getCallSiteArray();
MetaClass var3 = this.$getStaticMetaClass();
this.metaClass = var3;
this.helloService = (HelloService)ScriptBytecodeAdapter.castToType(helloService, HelloService.class);
}
public HelloRest() {
CallSite[] var1 = $getCallSiteArray();
this((HelloService)null);
}
#GetMapping({"hello"})
public String hello(#RequestParam("name") String name) {
CallSite[] var2 = $getCallSiteArray();
return (String)ShortTypeHandling.castToString(var2[0].call(this.helloService, name));
}
}
It is almost what you need, except this default constructor that was generated as well.
Things are getting even more complicated when using #Immutable annotation, because this version would generate 3 constructors:
public HelloRest(HelloService helloService)
public HelloRest()
public HelloRest(HashMap args)
Of course in this case you would have to remove private final in front of HelloService field definition, because this AST transformation works only with fields that are not yet final.
In this case two options you have found (creating construct manually or using Lombok) are probably the best solutions to your problem.
Alternative solution
There is also one "dirty" solution that allows you to write less amount of code, but promotes injection by reflection. Consider following code:
#RestController
class HelloRest {
#Autowired
private final HelloService helloService
#GetMapping("hello")
String hello(#RequestParam("name") String name) {
return helloService.createHelloMessage(name)
}
}
It will generate bytecode similar to following Java code:
#RestController
public class HelloRest implements GroovyObject {
#Autowired
private final HelloService helloService;
public HelloRest() {
CallSite[] var1 = $getCallSiteArray();
MetaClass var2 = this.$getStaticMetaClass();
this.metaClass = var2;
}
#GetMapping({"hello"})
public String hello(#RequestParam("name") String name) {
CallSite[] var2 = $getCallSiteArray();
return (String)ShortTypeHandling.castToString(var2[0].call(this.helloService, name));
}
}
Even though there is only single default constructor that does not even touch our helloService field, Spring bean will get injected by reflection. I share this option only to show all alternatives, although your initial instinct to use constructor injection is the best possible way to use dependency injection in practice.
You can use #Cannonical or #Immutable on the class. That way the constructor will be created for you

Jackson #JsonFilter is not getting applied when used at field or method level

I am using Spring version 4.3.3 and Jackson version 2.8.3. I am trying to filter out specific fields from an entity bean based on some custom logic that is determined at runtime. The #JsonFilter seems ideal for this type of functionality. The problem is that when I put it at the field or method level, my custom filter never gets invoked. If I put it at the class level, it gets invoked just fine. I don't want to use it at the class level though since then I would need to separately maintain the list of hardcoded field names that I want to apply the logic to. As of Jackson 2.3, the ability to put this annotation at the field level is supposed to exist.
Here is the most basic custom filter without any custom logic yet:
public class MyFilter extends SimpleBeanPropertyFilter {
#Override
protected boolean include(BeanPropertyWriter beanPropertyWriter) {
return true;
}
#Override
protected boolean include(PropertyWriter propertyWriter) {
return true;
}
}
Then I have the Jackson ObjectMapper configuration:
public class MyObjectMapper extends ObjectMapper {
public MyObjectMapper () {
SimpleFilterProvider filterProvider = new SimpleFilterProvider();
filterProvider.addFilter("myFilter", new MyFilter());
setFilterProvider(filterProvider);
}
}
Then finally I have my entity bean:
#Entity
public class Project implements Serializable {
private Long id;
private Long version;
#JsonFilter("myFilter") private String name;
#JsonFilter("myFilter") private String description;
// getters and setters
}
If I move the #JsonFilter annotation to the class level where #Entity is, the filter at least gets invoked, but when it is at the field level like in the example here, it never gets invoked.
I have the same need but after examining the unit tests I discovered that this is not the use-case covered by annotating a field.
Annotating a field invokes a filter on the value of the field not the instance containing the field. For example, imagine you have to classes, A and B, where A contains a field of type B.
class A {
#JsonFilter("myFilter") B foo;
}
Jackson applies "myFilter" to the fields in B not in A. Since your example contains fields of type String, which has no fields, Jackson never invokes your filter.
I have a need to exclude certain fields based on the caller's permissions. For example, an employee's profile may contain his taxpayer id, which is considered sensitive information and should only be serialized if the caller is a member of the Payrole department. Since I'm using Spring Security, I wish to integrate Jackson with the current security context.
public class EmployeeProfile {
private String givenName;
private String surname;
private String emailAddress;
#VisibleWhen("hasRole('PayroleSpecialist')")
private String taxpayerId;
}
The most obvious way to do this is to Jackson's filter mechanism but it has a few limitations:
Jackson does not support nested filters so adding an access filter prohibits using filters for any other purpose.
One cannot add Jackson annotations to existing, third-party classes.
Jackson filters are not designed to be generic. The intent is to write a custom filter for each class you wish to apply filtering. For example, I you need to filter classes A and B, then you have to write an AFilter and a BFilter.
For my use-case, the solution is to use a custom annotation introspector in conjunction with a chaining filter.
public class VisibilityAnnotationIntrospector extends JacksonAnnotationIntrospector {
private static final long serialVersionUID = 1L;
#Override
public Object findFilterId(Annotated a) {
Object result = super.findFilterId(a);
if (null != result) return result;
// By always returning a value, we cause Jackson to query the filter provider.
// A more sophisticated solution will introspect the annotated class and only
// return a value if the class contains annotated properties.
return a instanceof AnnotatedClass ? VisibilityFilterProvider.FILTER_ID : null;
}
}
This is basically a copy SimpleBeanProvider that replaces calls to include with calls to isVisible. I'll probably update this to use a Java 8 BiPredicate to make the solution more general but works for now.
This class also takes another filter as an argument and will delegate to it the final decision on whether to serialize the field if the field is visible.
public class AuthorizationFilter extends SimpleBeanPropertyFilter {
private final PropertyFilter antecedent;
public AuthorizationFilter() {
this(null);
}
public AuthorizationFilter(final PropertyFilter filter) {
this.antecedent = null != filter ? filter : serializeAll();
}
#Deprecated
#Override
public void serializeAsField(Object bean, JsonGenerator jgen, SerializerProvider provider, BeanPropertyWriter writer) throws Exception {
if (isVisible(bean, writer)) {
this.antecedent.serializeAsField(bean, jgen, provider, writer);
} else if (!jgen.canOmitFields()) { // since 2.3
writer.serializeAsOmittedField(bean, jgen, provider);
}
}
#Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception {
if (isVisible(pojo, writer)) {
this.antecedent.serializeAsField(pojo, jgen, provider, writer);
} else if (!jgen.canOmitFields()) { // since 2.3
writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
#Override
public void serializeAsElement(Object elementValue, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception {
if (isVisible(elementValue, writer)) {
this.antecedent.serializeAsElement(elementValue, jgen, provider, writer);
}
}
private static boolean isVisible(Object pojo, PropertyWriter writer) {
// Code to determine if the field should be serialized.
}
}
I then add a custom filter provider to each instance of ObjectMapper.
#SuppressWarnings("deprecation")
public class VisibilityFilterProvider extends SimpleFilterProvider {
private static final long serialVersionUID = 1L;
static final String FILTER_ID = "dummy-filter-id";
#Override
public BeanPropertyFilter findFilter(Object filterId) {
return super.findFilter(filterId);
}
#Override
public PropertyFilter findPropertyFilter(Object filterId, Object valueToFilter) {
if (FILTER_ID.equals(filterId)) {
// This implies that the class did not have an explict filter annotation.
return new AuthorizationFilter(null);
}
// The class has an explicit filter annotation so delegate to it.
final PropertyFilter antecedent = super.findPropertyFilter(filterId, valueToFilter);
return new VisibilityPropertyFilter(antecedent);
}
}
Finally, I have a Jackson module that automatically registers the custom annotaion introspector so I don't have to add it to each ObjectMapper instance manually.
public class FieldVisibilityModule extends SimpleModule {
private static final long serialVersionUID = 1L;
public FieldVisibilityModule() {
super(PackageVersion.VERSION);
}
#Override
public void setupModule(Module.SetupContext context) {
super.setupModule(context);
// Append after other introspectors (instead of before) since
// explicit annotations should have precedence
context.appendAnnotationIntrospector(new VisibilityAnnotationIntrospector());
}
}
There are more improvements that can be made and I still have more unit tests to write (e.g., handling arrays and collections) but this is the basic strategy I used.
You can try this approach for the same purpose:
#Entity
#Inheritance(
strategy = InheritanceType.SINGLE_TABLE
)
#DiscriminatorColumn(
discriminatorType = DiscriminatorType.STRING,
length = 2
)
#Table(
name = "project"
)
#JsonTypeInfo(
use = Id.CLASS,
include = As.PROPERTY,
property = "#class"
)
#JsonSubTypes({
#Type(
value = BasicProject.class,
name = "basicProject"
),
#Type(
value = AdvanceProject.class,
name = "advanceProject"
)})
public abstract class Project {
private Long id;
private Long version;
}
#Entity
#DiscriminatorValue("AD")
public class AdvanceProject extends Project {
private String name;
private String description;
}
#Entity
#DiscriminatorValue("BS")
public class BasicProject extends Project {
private String name;
}
I don't think you will make it work. I was trying and these are results of my investigation, maybe it will be helpful.
First of all, as #Faron noticed, the #JsonFilterannotation is applied for the class being annotated not a field.
Secondly, I see things this way. Let's imagine, somewhere in Jackson internals you are able to get the actual field. You can figure out if there is the annotation using Java Reflection API. You can even get the filter name. Then you get to the filter and pass the field value there. But it happens at runtime, how will you get the corresponding JsonSerializer of the field type if you decide to serialize the field? It is impossible because of type erasure.
The only alternative I see is to forget about dynamic logic. Then you can do the following things:
1) extend JacksonAnnotationIntrospector (almost the same as implement AnnotationIntrospector but no useless default code) overriding hasIgnoreMarker method. Take a look at this answer
2) criminal starts here. Kinda weird way taking into account your initial goal but still: extend BeanSerializerModifier and filter out fields there. An example can be found here. This way you can define serializer that actually doesn't serialize anything (again, I understand how strange it is but maybe one will find it helpful)
3) similar to the approach above: define useless serializer based on BeanDescription implementing ContextualSerializer's createContextual method. The example of this magic is here
Thanks to this really good blog, I was able to use #JsonView to filter out specific fields from an entity bean based on some custom logic that is determined at runtime.
Since the #JsonFilter does not apply for the fields within a class, I found this to be a cleaner workaround.
Here is the sample code:
#Data
#AllArgsConstructor
public class TestEntity {
private String a;
#JsonView(CustomViews.SecureAccess.class)
private Date b;
#JsonView(CustomViews.SecureAccess.class)
private Integer c;
private List<String> d;
}
public class CustomViews {
public static interface GeneralAccess {}
public static interface SecureAccess {}
public static class GeneralAccessClass implements GeneralAccess {}
public static class SecureAccessClass implements SecureAccess, GeneralAccess {}
public static Class getWriterView(final boolean hasSecureAccess) {
return hasSecureAccess
? SecureAccessClass.class
: GeneralAccessClass.class;
}
}
#Test
public void test() throws JsonProcessingException {
final boolean hasSecureAccess = false; // Custom logic resolved to a boolean value at runtime.
final TestEntity testEntity = new TestEntity("1", new Date(), 2, ImmutableList.of("3", "4", "5"));
final ObjectMapper objectMapper = new ObjectMapper().enable(MapperFeature.DEFAULT_VIEW_INCLUSION);
final String serializedValue = objectMapper
.writerWithView(CustomViews.getWriterView(hasSecureAccess))
.writeValueAsString(testEntity);
Assert.assertTrue(serializedValue.contains("a"));
Assert.assertFalse(serializedValue.contains("b"));
Assert.assertFalse(serializedValue.contains("c"));
Assert.assertTrue(serializedValue.contains("d"));
}

Spring: How to inject a value to static field?

With this class
#Component
public class Sample {
#Value("${my.name}")
public static String name;
}
If I try Sample.name, it is always 'null'. So I tried this.
public class Sample {
public static String name;
#PostConstruct
public void init(){
name = privateName;
}
#Value("${my.name}")
private String privateName;
public String getPrivateName() {
return privateName;
}
public void setPrivateName(String privateName) {
this.privateName = privateName;
}
}
This code works. Sample.name is set properly. Is this good way or not? If not, is there something more good way? And how to do it?
First of all, public static non-final fields are evil. Spring does not allow injecting to such fields for a reason.
Your workaround is valid, you don't even need getter/setter, private field is enough. On the other hand try this:
#Value("${my.name}")
public void setPrivateName(String privateName) {
Sample.name = privateName;
}
(works with #Autowired/#Resource). But to give you some constructive advice: Create a second class with private field and getter instead of public static field.
Soruce of this info is this: https://www.baeldung.com/spring-inject-static-field
Spring uses dependency injection to populate the specific value when it finds the #Value annotation. However, instead of handing the value to the instance variable, it's handed to the implicit setter instead. This setter then handles the population of our NAME_STATIC value.
#RestController
//or if you want to declare some specific use of the properties file then use
//#Configuration
//#PropertySource({"classpath:application-${youeEnvironment}.properties"})
public class PropertyController {
#Value("${name}")//not necessary
private String name;//not necessary
private static String NAME_STATIC;
#Value("${name}")
public void setNameStatic(String name){
PropertyController.NAME_STATIC = name;
}
}
This is my sample code for load static variable
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class OnelinkConfig {
public static int MODULE_CODE;
public static int DEFAULT_PAGE;
public static int DEFAULT_SIZE;
#Autowired
public void loadOnelinkConfig(#Value("${onelink.config.exception.module.code}") int code,
#Value("${onelink.config.default.page}") int page, #Value("${onelink.config.default.size}") int size) {
MODULE_CODE = code;
DEFAULT_PAGE = page;
DEFAULT_SIZE = size;
}
}
For those who want to use ApplicationContext in the main class of a Spring Boot application, you can just use the return value of SpringApplication.run.
Although workarounds may need to be implemented, one should try to avoid them in most scenarios if possible. Spring is great at handling dependency injection and treats most objects as Singletons. This means that Spring can handle the creation of objects for you, and the injection of these objects at runtime. When combining this with the fact that your Spring managed bean is likely a Singleton, the use of static methods and variables is largely unnecessary. You can simply autowire in an instance of the object you are looking for at the constructor level or variable level and reference the non-static version of the method or variable. This is ideal and behaves similarly to a static reference. Non static variables are basically static because you are only ever using one instance of the object in every part of the code and because of dependency injection you are never handling the instantiation of the object, just like with a static reference! Great! Now I'm sure there are instances where you need the work around (i.e. you aren't using dependency injection or class is not a singleton), but try to not use workarounds if possible. Also this is just my 2 cents. Someone may be able to offer 3. (:
public class InjectableClass{
#Value("${my.value}")
private String myString;
public String nonStaticMethod(){
return myString;
}
}
public class LogicClass{
private InjectableClass injectableClass;
#Autowire
public LogicClass(InjectableClass injectableClass){
this.injectableClass = injectableClass;
}
public void logicClassMethod(){
System.out.println("Hey! Here is the value I set on myString: " +
injectableClass.nonStaticMethod() + ". That was
basically like using a static method!");
}
}

Resources