This is the first time for me working with Quarkus and CDI and I think I'm getting it wrong.
In my application users can download some files. I'd like to control how many files are being downloaded simultaneously. I thought I could do this by creating a bean with #ApplicationScoped annotation, forcing instantiation at startup, and then injecting it wherever I need to know how many files are currently downloading.
This is what I managed to do:
#ApplicationScoped
public class DownloadState {
private int downloadingFiles;
void startup(#Observes StartupEvent event) {
setDownloadingFiles(0);
System.out.println("downloading files: " + downloadingFiles);
}
public int getDownloadingFiles() {
return downloadingFiles;
}
public void setDownloadingFiles(int downloadingFiles) {
this.downloadingFiles = downloadingFiles;
}
public void incrementDownloadingFiles() {
downloadingFiles++;
}
public void decrementDownloadingFiles() {
downloadingFiles--;
}
}
Doing this I can see the log at startup saying "downloading files: 0", so I know the class has been instantiated.
I try to access the number of downloading files here:
public class Downloader {
private static final Logger LOG = Logger.getLogger(Downloader.class);
#Inject
DownloadState downloadState;
private Dotenv dotenv = Dotenv.configure()
.directory("../")
.filename(".env.local")
.load();
private String fileName = dotenv.get("DOWNLOAD_PATH");
private String url;
public Downloader(String url, String fileName) {
this.url = url;
this.fileName += fileName;
}
public void downloadProduct() {
LOG.info("Downloading Files: " + downloadState.getDownloadingFiles());
//...
}
}
Whenever downloadProduct is called a NullPointerException is thrown on the line LOG.info("Downloading Files: " + downloadState.getDownloadingFiles());
Am I getting CDI totally wrong? Any help is really appreciated, thank you in advance.
I assume you're calling the Downloader constructor directly -- in which case, indeed no injection will happen and your downloadState will be null. Dependency injection is "contagious" -- you have to use it everywhere.
In your case, I'd probably just make Downloader also #ApplicationScoped, inject it everywhere you use it, and probably move the url and fileName parameters from constructor to downloadProduct. Actually at that point, the number of downloadingFiles could also be in Downloader. (Also note that it can be accessed from multiple threads -- so I'd probably use an AtomicInteger for downloadingFiles.)
All in all, something like this:
#ApplicationScoped
public class Downloader {
private static final Logger LOG = Logger.getLogger(Downloader.class);
private final AtomicInteger downloadingFiles = new AtomicInteger(0);
private final String downloadPath = Dotenv.configure()
.directory("../")
.filename(".env.local")
.load()
.get("DOWNLOAD_PATH");
public void downloadProduct(String url, String fileName) {
String path = downloadPath + fileName;
int current = downloadingFiles.incrementAndGet();
LOG.info("Downloading Files: " + current);
//...
}
}
Related
I am working on a Micronaut project, where I would like to see if the environment variables from the application.yml are being correctly assigned using the #Value annotation, when the app starts locally.
But every time the app is starting it shows me that the variables are not being assigned to the environment variables from the application.yml file.
That is my code:
public class Application {
private static String localTestString = "I am the local String";
#Value("${aws.secretkeyid}")
public static String applicationYmlTestString;
#Value("${aws.keyid}")
private static int keyId;
public static void main(String[] args) {
Micronaut.run(Application.class);
}
static{
log.warn("Local Test String is: " + localTestString);
log.warn("Application Yml Test String is: " + applicationYmlTestString);
log.warn("Key ID: " + keyId);
}
}
This is my application.yml
aws:
keyid: 123
secretkeyid: "abcdesdasdsddddd"
region: "europe-1"
Output:
Local Test String is: I am the local String
Application Yml Test String is: null
Key ID: 0
As we see the two variables applicationYmlTestString and keyId are not being assigned to the environment variables. Is there a way to solve this problem and to get:
Application Yml Test String is: abcdesdasdsddddd
Key ID: 123
Thank you in advance!
There are two issues with the example you have shown. Firstly, Micronaut does not inject values to static fields annotated with #Value annotation. (It's not weird, Spring does not support it as well.) Secondly, after injecting values to non-static fields, you won't be able to read their values using the class' static constructor. The whole application context must be ready to read such values, so you need to use an event listener that reacts to the application startup event.
Here is the simplest way to achieve it based on your example:
package micronaut.hello.world;
import io.micronaut.context.annotation.Value;
import io.micronaut.context.event.StartupEvent;
import io.micronaut.runtime.Micronaut;
import io.micronaut.runtime.event.annotation.EventListener;
import jakarta.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Singleton
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
#Value("${aws.secretkeyid}")
private String applicationYmlTestString;
#Value("${aws.keyid}")
private int keyId;
public static void main(String[] args) {
Micronaut.run(Application.class, args);
}
#EventListener
void onStartup(StartupEvent event) {
log.warn("Application Yml Test String is: " + applicationYmlTestString);
log.warn("Key ID: " + keyId);
}
public String getApplicationYmlTestString() {
return applicationYmlTestString;
}
public void setApplicationYmlTestString(String applicationYmlTestString) {
this.applicationYmlTestString = applicationYmlTestString;
}
public int getKeyId() {
return keyId;
}
public void setKeyId(int keyId) {
this.keyId = keyId;
}
}
There are three things worth mentioning:
The above example uses #EventListener annotation that makes the given method "event-aware", and this method will be triggered when the specific event is published by the application (or framework.)
We react to io.micronaut.context.event.StartupEvent - an event fired once startup is complete.
Keep in mind that to make this #EventListener annotation work, we need to annotate the application class with #Singleton to make this class a proper Micronaut bean.
Alternatively, if making an application class a singleton bean does not look good to you, you can implement the ApplicationEventListener interface and create a dedicated bean that will react to the same startup event. In this example, I use a static inner class, but that's just to make this example simple:
package micronaut.hello.world;
import io.micronaut.context.annotation.Value;
import io.micronaut.context.event.ApplicationEventListener;
import io.micronaut.context.event.StartupEvent;
import io.micronaut.runtime.Micronaut;
import jakarta.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
Micronaut.run(Application.class, args);
}
#Singleton
static class OnStartupEventListener implements ApplicationEventListener<StartupEvent> {
#Value("${aws.secretkeyid}")
private String applicationYmlTestString;
#Value("${aws.keyid}")
private int keyId;
#Override
public void onApplicationEvent(StartupEvent event) {
log.warn("Application Yml Test String is: " + applicationYmlTestString);
log.warn("Key ID: " + keyId);
}
public String getApplicationYmlTestString() {
return applicationYmlTestString;
}
public void setApplicationYmlTestString(String applicationYmlTestString) {
this.applicationYmlTestString = applicationYmlTestString;
}
public int getKeyId() {
return keyId;
}
public void setKeyId(int keyId) {
this.keyId = keyId;
}
}
}
But eventually, you should consider implementing a configuration class and use it instead of injecting values with the #Value annotation. However, whatever option you choose, the same thing applies - the configuration class can be injected to a non-static field and can be checked using an event listener mechanism.
And as Tim mentioned in the comment below, "Be careful logging environment variables though... They have a habit of being secrets, and logging them out tends to end up with them being in plain text in loads of different systems 😉". If you really need to log such information to double-check if the expected configuration is injected, try doing it in the controlled dev environment only. Assuming that you use the dev profile for the local env, you could use #Requires annotation to limit specific event listener to only that dev environment:
#Singleton
#Requires(env = "dev")
class OnStartupEventListener implements ApplicationEventListener<StartupEvent> {
#Value("${aws.secretkeyid}")
private String applicationYmlTestString;
#Value("${aws.keyid}")
private int keyId;
#Override
public void onApplicationEvent(StartupEvent event) {
log.warn("Application Yml Test String is: " + applicationYmlTestString);
log.warn("Key ID: " + keyId);
}
public String getApplicationYmlTestString() {
return applicationYmlTestString;
}
public void setApplicationYmlTestString(String applicationYmlTestString) {
this.applicationYmlTestString = applicationYmlTestString;
}
public int getKeyId() {
return keyId;
}
public void setKeyId(int keyId) {
this.keyId = keyId;
}
}
I have this two files:
application.properties
// ... stuffs ...
spring.profiles.active=some_id
application-some_id.properties
// ... stuffs ...
some.string=value
I want to get the value of the property "some.string" from my java code like this:
Utility.java
public final class Utility {
public final static String SOME_STRING_VALUE = <Something>.getProperty("some.string");
private Utility() {
}
}
What should I write instead of <Something>? I know that working with spring I should use spring stuffs like #Value, #Component, etc ... and 99% of the project is like that. I'd like to have just this exception.
Try using the Environment API :
#Autowired
private Environment env;
.
.
env.getProperty("some.string");
Update :
To be able to use in a static context you can try using lazy instantiation like follow (but in this case you should remove final accessor) :
final class Utility {
#Autowired
private Environment env;
private static String SOME_STRING_VALUE;
public static String getStringValue() {
if (SOME_STRING_VALUE == null) {
SOME_STRING_VALUE = env.getProperty("some.string");
}
return SOME_STRING_VALUE;
}
private Utility() {}
}
Hope this helps
I've written a class which reads the entire file and returns the content.
class ClassToTest {
public methodToTest(String input) {
return privateMethod(input);
}
private privateMethod(input) {
ClassPathResource classPathResource = new ClassPathResource(input);
IOUtils.toString(classPathResource.getFile());
}
}
Now, inside my test class, I don't want my test to actually read the file from so I'm trying to mock the method classPathResource.getFile() but somehow I'm not able to do so without writing PrepareForTests() and if I do that those test are not counted in JaCoCo.
I've written test case as
#Test
public void test_methodToTest() {
mockStatic(IOUtils.class);
when(IOUtils.toString(any()).thenReturn("DUMMY_STRING");
methodToTesT("file1.txt");
...
}
The problem is IOUtils.toString gets mocked properly but the call classPathResource.getFile() tries to access the file on the disk. For this, I can do this
PowerMockito.whenNew(ClassPathResource.class)
.withAnyArguments().thenReturn(mockedClassPath);
And add annotation to my test class as
#PrepareForTest(ClassToTest.class)
class MyTestClass {
...
}
But now the problem is this test class is skipped from the JACOCO test coverage . How can I write tests for this class?
You can pass a mocked reference into the constructor doing this:
class ClassToTest {
private ClassPathResource classPathResource;
public ClassToTest(ClassPathResource classPathResource) {
this.classPathResource = classPathResource;
}
public methodToTest(String input) {
IOUtils.toString(classPathResource.getFile(input));
}
}
Or you can pass the mocked reference into the method doing this:
class ClassToTest {
public methodToTest(ClassPathResource classPathResource) {
IOUtils.toString(classPathResource.getFile());
}
}
Having to mock a private member should be seen as a code smell and an indication that something is wrong with the current design. Because ClassPathResource is being initialized internal to the subject class it is now tightly coupled to that class. While not entirely impossible to mock it does make testing the class cleanly more difficult. Consider inverting the creation of the class to a delegate as a dependency.
public interface PathResource {
String getFile(String input);
}
This will allow the injection of the dependency
class ClassToTest {
private classPathResource;
public ClassToTest (PathResource resource) {
this.classPathResource = resource;
}
public String methodToTest(String input) {
return privateMethod(input);
}
private String privateMethod(String input) {
return IOUtils.toString(classPathResource.getFile(input));
}
}
and the dependency can be mocked/faked/stubbed when testing.
public void Test() {
//Arrange
//mock creation
PathResource resource = mock(PathResource.class);
String input = "path";
String expected = "expected_output";
//stubbing
when(resource.getFile(input)).thenReturn(expected);
ClassToTest subject = new ClassToTest(resource);
//Act
String actual = subject.methodToTest(input);
//Assert
verify(resource).getFile(input);
assertEquals(expected, actual);
}
in production code the ClassPathResource would be derived from the abstraction
public class ClassPathResource implements PathResource {
//...code removed for brevity
}
and it would be associated with the abstraction at the composition root.
Following the above suggestions would now allow ClassToTest to be tested in isolation without any knock on effects of implementation concerns.
I'm aware of that this topic might be considered as offtopic or convention/opinion based, but I have not found any other place that I could find solution for my problem.
I'm writing and Spring application, fully configured with annotations in Java. I'm loading the properties file with #PropertySource annotation:
#Configuration
#ComponentScan("myapp.app")
#PropertySource("app.properties")
public class ApplicationConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer getPropertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Let's assume, that I have app.properties file of following content:
property1=someProperty1Value
property2=someProperty2Value
I'm loading this value with following code:
#Service
public class MyServiceImpl implements MyService {
#Value("${property1}")
private String property1Value;
#Value("${property2}")
private String property2Value;
#Override
public void doStuff() {
System.out.println(property1Value);
System.out.println(property2Value);
}
}
This is working perfectly fine. On the other hand, I find it hard to maintain - if some will think that "property1" is not the best name for a property and would like to rename it, then it will be needed to find all strings "${property1}" and rename it. I tought that I could extract it to the constant class:
public final class Properties {
public static final String PROPERTY_1 = "${property1}";
public static final String PROPERTY_2 = "${property2}";
private Properties() {
}
}
This requires refactoring of the existing bindings to new constant values:
#Value(Properties.PROPERTY_1)
private String property1Value;
Looks nice, but I do not like the mess in the Properties class, I think it will be better to the constant values without bracelets:
public static final String PROPERTY_1 = "property1";
Which leads to another refactoring in the MyServiceImpl class:
#Value("${" + Properties.PROPERTY_1 + "}")
private String property1Value;
But boy, that's really ugly. I thought about extracting constant values to the Enum:
public enum Properties {
PROPERTY_1("property1"),
PROPERTY_2("property2");
private final String key;
private Properties(String key) {
this.key = key;
}
public String getKey() {
return key;
}
public String getSpringKey() {
return "${" + getKey() + "}";
}
}
and use it like
#Value(Properties.PROPERTY_1.getSpringKey())
private String property1Value;
but then IDE reminded me, that annotation value has to be a constant.
After creating this enum, I thought that I might be over-thinking it, and it should be kept as simple as possible. Currently I came back to the solution with constants in format of
public static final String PROPERTY_1 = "${property1}";
Finally, I would like to ask you to provide another, nice-looking solution, or some reference links where I could read about some common solution.
I actually have a programm with a servlet :
#WebServlet("/Controler")
public class Controler extends HttpServlet {
}
I need to use a property file : file.properties in my program. To load it, I have a class :
public class PropLoader {
private final static String m_propertyFileName = "file.properties";
public static String getProperty(String a_key){
String l_value = "";
Properties l_properties = new Properties();
FileInputStream l_input;
try {
l_input = new FileInputStream(m_propertyFileName); // File not found exception
l_properties.load(l_input);
l_value = l_properties.getProperty(a_key);
l_input.close();
} catch (Exception e) {
e.printStackTrace();
}
return l_value;
}
}
My property file is in the WebContent folder, and I can access it with :
String path = getServletContext().getRealPath("/file.properties");
But I can't call theses methods in another class than the servlet...
How can I access to my property file in the PropLoader class ?
If you want to read the file from within the webapp structure, then you should use ServletContext.getResourceAsStream(). And of course, since you load it from the webapp, you need a reference to the object representing the webapp: ServletContext. You can get such a reference by overriding init() in your servlet, calling getServletConfig().getServletContext(), and pass the servlet context to the method loading the file:
#WebServlet("/Controler")
public class Controler extends HttpServlet {
private Properties properties;
#Override
public void init() {
properties = PropLoader.load(getServletConfig().getServletContext());
}
}
public class PropLoader {
private final static String FILE_PATH = "/file.properties";
public static Properties load(ServletContext context) {
Properties properties = new Properties();
properties.load(context.getResourceAsStream(FILE_PATH));
return properties;
}
}
Note that some exceptions must be handled.
Another solution would be to put the file under WEB-INF/classes in the deployed webapp, and use the ClassLoader to load the file: getClass().getResourceAsStream("/file.properties"). This way, you don't need a reference to ServletContext.
I would recommend to use the getResourceAsStream method (example below). It would need that the properties file be at the WAR classpath.
InputStream in = YourServlet.class.getClassLoader().getResourceAsStream(path_and_name);
Regards
Luan