I'm stuck trying to get the second test of the code below to work.
Both have the same payload structure, the one that works use Map<> only, the second one uses classes.
Why does the first test work but the second doesn't?
It seems a bug with usingRecursiveFieldByFieldElementComparator.
package com.example.hi;
import com.example.hi.ExampleTest.Foo.Bar;
import lombok.AllArgsConstructor;
import lombok.Value;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
public class ExampleTest {
Comparator<LocalDateTime> truncateSeconds = (a, exp) ->
a.isAfter(exp.truncatedTo(ChronoUnit.SECONDS)) ? 0 : 1;
#Test
void works() {
var a = Map.of("values", Map.of("one", singletonList(Map.of("date", LocalDateTime.now()))));
var b = Map.of("values", Map.of("one", singletonList(Map.of("date", LocalDateTime.now()))));
assertThat(singletonList(a))
.usingRecursiveFieldByFieldElementComparator()
.usingComparatorForElementFieldsWithType(truncateSeconds, LocalDateTime.class)
.containsExactly(b);
}
#Test
void works_not() {
var a = new Foo(Map.of("one", singletonList(new Bar(LocalDateTime.now()))));
var b = new Foo(Map.of("one", singletonList(new Bar(LocalDateTime.now()))));
assertThat(singletonList(a))
.usingRecursiveFieldByFieldElementComparator()
.usingComparatorForElementFieldsWithType(truncateSeconds, LocalDateTime.class)
.containsExactly(b);
}
#Value
#AllArgsConstructor
public static class Foo {
Map<String, List<Bar>> values;
#Value
#AllArgsConstructor
public static class Bar {
LocalDateTime date;
}
}
}
The issue has been fixed in AssertJ Core 3.17.0 and the following should work:
// static import RecursiveComparisonConfiguration.builder
RecursiveComparisonConfiguration configuration = builder().withIgnoreAllOverriddenEquals(true)
.withComparatorForType(truncateSeconds, LocalDateTime.class)
.build();
assertThat(list).usingRecursiveFieldByFieldElementComparator(configuration)
.containsExactly(b);
Related
I have just started with Java Spring and am getting familiar with the framework.
Let's say I have a controller with two endpoints
"/remove_old"
"/remove_new"
They do the same job: controller layer -> service layer -> DAO except for databases which should be used in dao methods - those are different. As I understand, this can be nicely handled by Spring with no change in the service layer. How should I organize my beans to make it the most appropriate way? The only solution I can think of so far is to autowire everything and then expose Dao::setDatabase method which would be called at the controller layer.
Here is a spring-boot3, ARD-solution:
(starter-used)
Simple entity:
package com.example.routingds.demo;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.Data;
#Data
#Entity
class SomeEntity {
#Id
#GeneratedValue
Long id;
}
Plus repo:
package com.example.routingds.demo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SomeRepository extends JpaRepository<SomeEntity, Long> {}
An enum (for all our data sources/tenants):
package com.example.routingds.demo;
public enum MyTenant {
OLD, NEW;
}
A thingy like (ref1, ref2, ref3...):
package com.example.routingds.demo;
public class MyTenantThreadLocalContextHolder {
private static ThreadLocal<MyTenant> threadLocal = new ThreadLocal<>();
public static void set(MyTenant tenant) {
threadLocal.set(tenant);
}
public static MyTenant get() {
return threadLocal.get();
}
}
...(we have plenty options here, but this is thread safe & easy to test/static!)
Then a (very) simple controller like:
package com.example.routingds.demo;
import static com.example.routingds.demo.MyTenant.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
#Controller
public class DemoController {
#Autowired
private SomeRepository someRepository;
#DeleteMapping("/remove_new/{id}")
public void removeNew(#PathVariable Long id) {
removeInternal(NEW, id);
}
#DeleteMapping("/remove_old/{id}")
public void removeOld(#PathVariable Long id) {
removeInternal(OLD, id);
}
private void removeInternal(MyTenant current, Long id) {
// set context ...
MyTenantThreadLocalContextHolder.set(current);
// and "just delete" (ard+context will choose correct DS):
someRepository.deleteById(id);
}
}
Lets go wire it:
application.properties:
fallback.datasource.url=jdbc:h2:./data/fallback
fallback.datasource.username=sa
fallback.datasource.password=
#fallback.datasource. ... more if you like/need
old.datasource.url=jdbc:h2:./data/old
old.datasource.username=sa
old.datasource.password=
# ...
new.datasource.url=jdbc:h2:./data/new
new.datasource.username=sa
new.datasource.password=
# ...
# assuming all dbs initialized , otherwise set these (+ un-comment main class):
#spring.sql.init.mode=always
#spring.sql.init.continue-on-error=false
# debug:
spring.jpa.show-sql=true
spring.h2.console.enabled=true
# https://github.com/spring-projects/spring-data-jpa/issues/2717:
spring.jpa.properties.jakarta.persistence.sharedCache.mode=UNSPECIFIED
App/Main/Config:
package com.example.routingds.demo;
import static com.example.routingds.demo.MyTenant.*;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
//import org.springframework.boot.autoconfigure.sql.init.SqlDataSourceScriptDatabaseInitializer;
//import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
//import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
#SpringBootApplication
public class RoutingDsDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RoutingDsDemoApplication.class, args);
}
// load the props:
#Bean
#Primary // one should be primary ...
#ConfigurationProperties("fallback.datasource")
public DataSourceProperties fallbackDSProps() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("old.datasource")
public DataSourceProperties oldDSProps() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("new.datasource")
public DataSourceProperties newDSProps() {
return new DataSourceProperties();
}
// the main (abstract routing) data source:
#Bean
#Primary
public DataSource dataSource(
#Qualifier("masterDS") DataSource masterDS,
#Qualifier("newDS") DataSource newDS,
#Qualifier("oldDS") DataSource oldDS) {
return new AbstractRoutingDataSource() {
{ // inline instance:
setTargetDataSources( // ! we operationally use only OLD, NEW:
Map.of(
// lookup key, data source:
OLD, oldDS,
NEW, newDS
)
);
//... but as a default/fallback/no-context:
setDefaultTargetDataSource(masterDS);
afterPropertiesSet();
}
// inline override:
#Override
protected Object determineCurrentLookupKey() {
return MyTenantThreadLocalContextHolder.get();
}
};
}
// the "slaves" / underlying / your DS's:
#Bean // default/master/backup/unused:
DataSource masterDS(DataSourceProperties props) {
return props.initializeDataSourceBuilder().build();
}
#Bean
DataSource oldDS(#Qualifier("oldDSProps") DataSourceProperties props) {
return props.initializeDataSourceBuilder().build();
}
#Bean
DataSource newDS(#Qualifier("newDSProps") DataSourceProperties props) {
return props.initializeDataSourceBuilder().build();
}
// for (script) db initialization, we might need this (+'sql.init.mode=always'):
// #Bean
// DataSourceScriptDatabaseInitializer initOld(#Qualifier("oldDS") DataSource oldDS, SqlInitializationProperties settings) {
// return new SqlDataSourceScriptDatabaseInitializer(oldDS, settings);
// }
//
// #Bean
// DataSourceScriptDatabaseInitializer initNew(#Qualifier("newDS") DataSource newDS, SqlInitializationProperties settings) {
// return new SqlDataSourceScriptDatabaseInitializer(newDS, settings);
// }
}
Init schema (src/main/resources/schema.sql):
create table some_entity (id bigint not null, primary key (id));
create sequence some_entity_seq start with 1 increment by 50;
TESTING TIME!! :))
package com.example.routingds.demo;
import static com.example.routingds.demo.MyTenant.*;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
#SpringBootTest
#AutoConfigureMockMvc // we will test the controller!
class RoutingDsDemoApplicationTests {
// tenant/DS dependent test IDs:
static final EnumMap<MyTenant, Set<Long>> TEST_IDS = new EnumMap<>(
Map.of(
OLD, new HashSet<>(),
NEW, new HashSet<>()
)
);
#TestConfiguration // some "test setup":
static class TestDataConfig {
#Bean
InitializingBean testData(SomeRepository repo) { // <- normal/autowired repo
return () -> {
// for OLD and NEW (tenant):
Arrays.stream(MyTenant.values())
.forEach((t) -> {
// set context/db:
MyTenantThreadLocalContextHolder.set(t);
// clean up (we shouldn't need this/DANGER):
// repo.deleteAll();
// save 100 SomeEntity's, and store ids to TEST_IDS:
IntStream.range(0, 100).forEach((i) -> {
TEST_IDS.get(t).add(
repo.save(new SomeEntity()).getId()
);
});
});
};
}
}
#Autowired
MockMvc mockMvc;
#Autowired
SomeRepository helper;
#Test
void testRemoveOld() {
// for each (known) OLD id:
TEST_IDS.get(OLD).stream().forEach((id) -> {
try {
mockMvc
.perform(delete("/remove_old/" + id))
.andExpect(status().isOk());
} catch (Exception ex) {
fail(ex);
}
});
// verify deleted:
MyTenantThreadLocalContextHolder.set(OLD);
TEST_IDS.get(OLD).stream().forEach((id) -> {
assertFalse(helper.existsById(id));
});
}
#Test
void testRemoveNew() {
// for each (known) NEW id:
TEST_IDS.get(NEW).stream().forEach((id) -> {
try {
mockMvc
.perform(delete("/remove_new/" + id))
.andExpect(status().isOk());
} catch (Exception ex) {
fail(ex);
}
});
// verify deleted:
MyTenantThreadLocalContextHolder.set(NEW);
TEST_IDS.get(NEW).stream().forEach((id) -> {
assertFalse(helper.existsById(id));
});
}
}
passes:
Results:
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
See also:
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-access.configure-two-datasources
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-initialization
I am practising restful api endpoints using https://api.predic8.de/shop/docs
Here is my repo
I am getting a NPE failure when I try to use #InjectMocks during my TDD approach
However, I can make my test pass when I make a direct call in the setup()
vendorService = new VendorServiceImpl(VendorMapper.INSTANCE, vendorRepository);
I wanted to extend my learning by trying to create an endpoint for getting all vendors.
When I employ TDD along the way, but, my test getAllVendors() fails on a NPE when I try to use #InjectMocks but passes when I substitute it for a direct call in the setup() method.
The NPE is linked to the mapper class I think.
Here are the classes that I believe are useful. VendorServiceTest, VendorServiceImpl, VendorMapper.
I have commented out the direct call in the setup as I want to get the test passing using #InjectMocks
package guru.springfamework.services;
import guru.springfamework.api.v1.mapper.VendorMapper; import
guru.springfamework.api.v1.model.VendorDTO; import
guru.springfamework.domain.Vendor; import
guru.springfamework.repositories.VendorRepository; import
org.junit.Before; import org.junit.Test; import
org.mockito.InjectMocks; import org.mockito.Mock; import
org.mockito.MockitoAnnotations; import
org.springframework.test.web.servlet.MockMvc;
import java.util.Arrays; import java.util.List;
import static org.junit.Assert.*; import static
org.mockito.Mockito.when;
public class VendorServiceTest {
public static final String NAME = "Tasty";
public static final Long ID = 1L;
#Mock
VendorMapper vendorMapper;
#Mock
VendorRepository vendorRepository;
#InjectMocks
VendorServiceImpl vendorService;
//VendorService vendorService;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
//vendorService = new VendorServiceImpl(VendorMapper.INSTANCE, vendorRepository);
}
#Test
public void getAllVendors() {
//given
List<Vendor> vendors = Arrays.asList(new Vendor(), new Vendor(), new Vendor());
when(vendorRepository.findAll()).thenReturn(vendors);
//when
List<VendorDTO> vendorDTOList = vendorService.getAllVendors();
//then
assertEquals(3, vendorDTOList.size());
}
#Test
public void findByName() {
}
}
package guru.springfamework.services;
import guru.springfamework.api.v1.mapper.VendorMapper; import
guru.springfamework.api.v1.model.VendorDTO; import
guru.springfamework.repositories.VendorRepository; import
org.springframework.stereotype.Service;
import java.util.List; import java.util.stream.Collectors;
#Service public class VendorServiceImpl implements VendorService {
private final VendorMapper vendorMapper;
private final VendorRepository vendorRepository;
public VendorServiceImpl(VendorMapper vendorMapper, VendorRepository vendorRepository) {
this.vendorMapper = vendorMapper;
this.vendorRepository = vendorRepository;
}
#Override
public List<VendorDTO> getAllVendors() {
return vendorRepository
.findAll()
.stream()
.map(vendor -> {
VendorDTO vendorDTO = vendorMapper.vendorToVendorDTO(vendor);
vendorDTO.setVendorUrl("/api/v1/vendors/" + vendor.getId());
return vendorDTO;
})
.collect(Collectors.toList());
}
#Override
public VendorDTO findByName(String name) {
return vendorMapper.vendorToVendorDTO(vendorRepository.findByName(name));
}
#Override
public VendorDTO getVendorById(Long id) {
return vendorMapper.vendorToVendorDTO(vendorRepository.findById(id).orElseThrow(RuntimeException::new));
}
}
package guru.springfamework.api.v1.mapper;
import guru.springfamework.api.v1.model.VendorDTO; import
guru.springfamework.domain.Vendor; import org.mapstruct.Mapper; import
org.mapstruct.factory.Mappers;
#Mapper public interface VendorMapper {
VendorMapper INSTANCE = Mappers.getMapper(VendorMapper.class);
VendorDTO vendorToVendorDTO(Vendor vendor);
}
Does anyone know where and why I am going wrong?
The problem is that you created mock object for the mapper, but you didn't say what should happen when the method vendorToVendorDTO is called.
Therefore, when that method is called in the next line of code:
VendorDTO vendorDTO = vendorMapper.vendorToVendorDTO(vendor);
It will return null, and then in this line of code:
vendorDTO.setVendorUrl("/api/v1/vendors/" + vendor.getId());
You will get NullPointerException.
To make this work, change your getAllVendors() method as follows:
#Test
public void getAllVendors() {
//given
List<Vendor> vendors = Arrays.asList(new Vendor(), new Vendor(), new Vendor());
VendorDTO mockDto = mock(VendorDTO.class);
when(vendorRepository.findAll()).thenReturn(vendors);
when(vendorMapper.vendorToVendorDTO(any(Vendor.class))).thenReturn(mockDto);
//when
List<VendorDTO> vendorDTOList = vendorService.getAllVendors();
//then
assertEquals(3, vendorDTOList.size());
}
And the test should pass.
Have you tried to put #RunWith(MockitoJUnitRunner.class)/#ExtendsWith(MockitoExtension.class) over your test class?
I'm working on reading data from Kafka and have encountered this code to return the Kafka connection details but I don't understand how the context is shared. Here is the class to setup the KafkaConnection :
import akka.actor.typed.ActorSystem;
import akka.kafka.ConsumerMessage;
import akka.kafka.ConsumerSettings;
import akka.kafka.Subscriptions;
import akka.kafka.javadsl.Consumer;
import akka.stream.javadsl.SourceWithContext;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.common.serialization.Deserializer;
import java.time.Duration;
#Getter
#Slf4j
public final class KafkaSource<K, V> {
private final SourceWithContext<ConsumerRecord<K, V>, ConsumerMessage.Committable, ?> commitableSource;
#Builder
private KafkaSource(final Deserializer<K> keyd, final Deserializer<V> valueDeserializer, final ActorSystem actorSystem) {
final String kafkaBootstrapServers = "localhost:9092";
final ConsumerSettings<K, V> kafkaConsumerSettings =
ConsumerSettings.create(actorSystem, keyd, valueDeserializer)
.withBootstrapServers(kafkaBootstrapServers)
.withGroupId("testGroup12")
.withProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest")
.withStopTimeout(Duration.ofSeconds(5));
final String topics = "request-topic";
this.commitableSource = Consumer.sourceWithOffsetContext(kafkaConsumerSettings,
Subscriptions.topics(topics)).mapContext(ctx -> ctx);
}
}
Here is the Akka Stream to process the data from Kafka I've written :
import akka.actor.typed.ActorSystem;
import akka.actor.typed.javadsl.Behaviors;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.common.serialization.StringDeserializer;
public class ReadFromKafka {
private static ObjectMapper objectMapper = new ObjectMapper();
public static void main(String args[]) {
final ActorSystem actorSystem = ActorSystem.create(Behaviors.empty(), "as");
var ksource = KafkaSource.<String, String>builder()
.actorSystem(actorSystem)
.keyd(new StringDeserializer()).valueDeserializer(new StringDeserializer())
.build();
ksource.getCommitableSource()
.map(ConsumerRecord::value)
.map(x -> {
var mappedObject = objectMapper.readValue(x, RequestDto.class);
System.out.println("mappedObject is :" + mappedObject);
return mappedObject;
}
)
.log("error")
.asSource()
.map(pair -> pair.second().commitInternal())
.run(actorSystem);
}
}
The class being mapped, RequestDto :
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import lombok.extern.jackson.Jacksonized;
import java.util.Date;
#Jacksonized
#AllArgsConstructor
#Getter
#Builder
#ToString
public class RequestDto {
#JsonFormat(pattern = "yyyy-MM-dd HH:mm:sss")
private final Date datePurchased;
private String someOtherField;
}
Although ReadFromKafka works as expected why is ConsumerMessage.Committable from SourceWithContext<ConsumerRecord<K, V>, ConsumerMessage.Committable, ?> not dropped when executing :
.map(ConsumerRecord::value)
.map(x -> {
var mappedObject = objectMapper.readValue(x, RequestDto.class);
System.out.println("mappedObject is :" + mappedObject);
return mappedObject;
}
)
.asSource() allows accessing the context within the Tuple at second position to then commit the offset using :
.map(pair -> pair.second().commitInternal())
I'm confused as to how this works, it appears something implicit is happening in the background that allows the context to be propagated throughout the stream?
A SourceWithContext<A, B, M> defines the stream operations which it supports to only work on the A part of the value.
So if f is a function taking an A and returning a C, .map(f) results in a SourceWithContext<C, B, M>.
Under the hood, it's a Source<Pair<A, B>, M>. map could be defined as something like (as always apologies for atrocious Java):
private Source<Pair<A, B>, M> underlying;
public <C> SourceWithContext<C, B, M> map(Function<A, C> f) {
Source<Pair<C, B>, M> src =
underlying.map(pair -> {
A a = pair.first();
C c = f(a);
return Pair.of<C, B>(c, pair.second()); // no idea if this is the correct Java syntax, but you get the idea
})
return SourceWithContext.fromPairs<C, B, M>(src);
}
Note that the f never gets to see the second part of the Pair. So long as every operation does the right thing with respect to the context, it just works.
There are operations where there's no unambiguous "right thing" to do. An example of this is an operation which could reorder elements.
I have encountered an interesting yet a terrifying situation. My application has two features namely, OtpListener and CdmMonitor running on two separate threads. For now, only the OtpListener feature was required so I commented out CdmMonitor in the main class.
However, I still see the logs for the commented out class CdmMonitorService. This service was running (when it shouldn't as it was commented)
Do threads implemented from the Runnable class operate this way? I did not find anything of the sort in its documentation.
Main Class: OtpTrackerApp.java
package dev.xfoil.otpTracker;
import dev.xfoil.otpTracker.service.CdmMonitorService;
import dev.xfoil.otpTracker.service.OTPListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
#ComponentScan({"dev.xfoil.otpTracker.*","dev.xfoil.shared.*", "dev.xfoil.shared.dal.repositories.springdata.*", "dev.xfoil.shared.dal.entities.*","dev.xfoil.shared.dal.cache.*"})
public class OtpTrackerApp implements CommandLineRunner {
#Autowired
public OTPListener otpListener;
// #Autowired
// public CdmMonitorService cdmMonitorService;
public static void main(String[] args){
SpringApplication.run(OtpTrackerApp.class, args);
}
#Override
public void run(String... args) throws Exception {
// Thread monitoringThread = new Thread(cdmMonitorService);
// monitoringThread.start();
otpListener.startListening();
}
}
CdmMonitorService.java
package dev.xfoil.otpTracker.service;
import com.google.common.flogger.FluentLogger;
import dev.xfoil.otpTracker.firebase.FirestoreManager;
import dev.xfoil.shared.dal.repositories.springdata.MachineMonitorRepo;
import dev.xfoil.shared.dal.repositories.springdata.MachineOperationsHistoryRepo;
import dev.xfoil.shared.dal.repositories.springdata.MachinesRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
#Service
public class CdmMonitorService implements Runnable {
FluentLogger logger = FluentLogger.forEnclosingClass();
#Autowired
MachineMonitorRepo machineMonitorRepo;
#Autowired
MachineOperationsHistoryRepo machineOperationsHistoryRepo;
#Autowired
MachinesRepo machinesRepo;
#Autowired
FirestoreManager firestoreManager;
public CdmMonitorService() {
}
#Override
#Scheduled(fixedDelay = 120000l)
public void run() {
try {
// code removed for brevity
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
logger.atWarning().log("Exception in CDM Monitor Thread " + e);
}
}
}
OtpListener.java
package dev.xfoil.otpTracker.service;
import com.google.common.flogger.FluentLogger;
import dev.xfoil.shared.dal.entities.AccountLedger;
import dev.xfoil.shared.dal.models.OTP;
import dev.xfoil.shared.dal.repositories.springdata.LedgerRepo;
import dev.xfoil.shared.dal.repositories.springdata.NoteCountRepo;
import dev.xfoil.shared.dal.repositories.springdata.UtilRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
#Service
public class OTPListener {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
#Autowired
UtilRepo utilRepo;
#Autowired
ThirdPartyApiCalls discordServer;
#Autowired
NoteCountRepo noteCountRepo;
private static LocalDateTime latestOTPTime;
#Value("#{${webhooks}}")
HashMap<String, String> discordLink = new HashMap<String, String>();
#PostConstruct
public LocalDateTime getLatestOTPTimestamp() {
latestOTPTime = utilRepo.getTimeOfLatestOTP();
return latestOTPTime;
}
public void startListening() throws InterruptedException {
logger.atInfo().log("Current in-mem links:");
try {
// code commented for brevity
Thread.sleep(5 * 60 * 1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
I am coding in spring-boot. I tried to get the value of properties.properties in other packages without success. For example in the classe ClassUtils.java, the value of testValue is always null
This is my project
This is my code:
package com.plugins.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.plugins.domain.ClassUtils;
#RestController
public class SearchContactController {
#Value("${environnement.test}")
String testValue;
#Value("${environnement.url}")
String urlValue;
#RequestMapping(value = "/test")
public String pingRequest() {
System.out.println("value ===> " + testValue + " /// " + urlValue);
return "test !" + ClassUtils.getTestValue();
}
}
This is my second class, where I can't get the value of testValue variable:
package com.plugins.domain;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class ClassUtils {
#Value("${environnement.test}")
static String testValue;
public static String getTestValue(){
return "The return "+testValue;
}
}
This is my springApp.java
package com.plugins;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class SpringBootVideApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootVideApplication.class, args);
}
}
Enable #ComponentScan({"com.plugins"}) , in Application
To access the properties defined in application.properties
myapp.url="xxxxxx"
in your class
#Value("${myapp.url}")
private String testValue;
but this cannot be a static variable, if it is a static variable you do some hack like this, by defining setter method
private static String testValue;
#Value("${myapp.url}")
public void testValue(String value) {
testValue = value;
}
I resolve this issue by addin #Autowired in the class which use the method of the other class this is a snippet
// Class: SearchContactController.java
#Autowired
ClassUtils cd;
#RequestMapping(value = "/ping")
public String pingRequest() {
return "Ping OK !" + cd.getTestValue();
}