#Component
public class A<K, V> {
#Autowired
// spring unable to inject this, #Qualifier can not use here
private B<K, V> b;
}
public class B<K, V>{}
#Configuration
public class ConifgA {
#Bean
#Qualifier("int_a")
public A<Integer, Integer> getA1() { return new A<>(); }
#Bean
#Qualifier("int_a")
public A<String, String> getA2() { return new A<>(); }
}
#Configuration
public class ConifgB {
#Bean
public B<Integer, Integer> getB1() { return new B<>(); }
#Bean
public B<String, String> getB2() { return new B<>(); }
}
#Component
class C {
#Autowired
#Qualifier("int_a")
A<Integer, Integer> ia;
#Autowired
#Qualifier("str_a")
A<String, String> sa;
}
I can not manually new B() and pass it to A's ctor because B contains data members which was injected by spring
#Qualifier is typically used paired with #Autowired to specify a single bean candidate by name when there are several bean instances with shared interface in the context. If you wish to inject a desired bean name, just setting the default value in #Bean works.
I've written a test class that works for your scenario:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = FooTest.TestConfig.class)
public class FooTest {
#Autowired
private C c;
#Test
public void testCisAvailable(){
Assert.assertNotNull(c);
}
#Configuration
static class TestConfig {
#Bean("int_a")
public A<Integer, Integer> getA1(B<Integer, Integer> b) {
return new A<>(b);
}
#Bean("str_a")
public A<String, String> getA2(B<String, String> b) {
return new A<>(b);
}
#Bean
public B<Integer, Integer> getB1() {
return new B<>();
}
#Bean
public B<String, String> getB2() {
return new B<>();
}
#Bean
public C getC(A<Integer, Integer> ia,
A<String, String> sa) {
return new C(ia, sa);
}
}
static class B<K, V> {
}
static class A<K, V> {
private final B<K, V> b;
#Autowired
public A(B<K, V> b) {
this.b = b;
}
}
static class C {
private final A<Integer, Integer> ia;
private final A<String, String> sa;
#Autowired
public C(#Qualifier("int_a") A<Integer, Integer> ia,
#Qualifier("str_a") A<String, String> sa) {
this.ia = ia;
this.sa = sa;
}
}
}
Related
I would like to save a new entity using HazlecastRepository.
When the id is null, the KeyValueTemplate use SecureRandom and generate id which is like -123123123123123123.
I don't want to save id like that, instead of that i woud like to get it from sequence in db and put it to the map.
I have found 2 solutions:
1) In AdminService get the next value from sequence in database and set it
2) Create atomic counter id in the Hazelcast server and init it with the current value from the sequence. In AdminService get counter, increment value and set id.
but they are not very pretty.
Do you have any other ideas?
The code:
#Configuration
#EnableHazelcastRepositories(basePackages = "com.test")
public class HazelcastConfig {
#Bean
public HazelcastInstance hazelcastInstance(ClientConfig clientConfig) {
return HazelcastClient.newHazelcastClient(clientConfig);
}
#Bean
#Qualifier("client")
public ClientConfig clientConfig() {
ClientConfig clientConfig = new ClientConfig();
clientConfig.setClassLoader(HazelcastConfig.class.getClassLoader());
ClientNetworkConfig networkConfig = clientConfig.getNetworkConfig();
networkConfig.addAddress("127.0.0.1:5701");
networkConfig.setConnectionAttemptLimit(20);
return clientConfig;
}
#Bean
public KeyValueTemplate keyValueTemplate(ClientConfig clientConfig) {
return new KeyValueTemplate(new HazelcastKeyValueAdapter(hazelcastInstance(clientConfig)));
}
}
#Service
#RequiredArgsConstructor
public class AdminService {
private final UserRepository userRepository;
...
#Transactional
public User addOrUpdateUser(UserUpdateDto dto) {
validate(dto);
User user = dto.getId() != null ? userService.getUser(dto.getId()) : new User();
mapUser(user, dto);
return userRepository.save(user);
}
...
}
#Repository
public interface UserRepository extends HazelcastRepository<User, Long> {
}
#KeySpace("users")
#Entity
#Table(name = "users)
#Data
#AllArgsConstructor
#NoArgsConstructor
public class User extends DateAudit implements Serializable {
#javax.persistence.Id
#org.springframework.data.annotation.Id
// #GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_generator")
// #SequenceGenerator(name="user_generator", sequenceName = "user_seq")
private Long id;
...
}
Hazelcast server:
#Component
#Slf4j
public class UserLoader implements ApplicationContextAware, MapStore<Long, User> {
private static UserJpaRepository userJpaRepository;
#Override
public User load(Long key) {
log.info("load({})", key);
return userJpaRepository.findById(key).orElse(null);
}
#Override
public Map<Long, User> loadAll(Collection<Long> keys) {
Map<Long, User> result = new HashMap<>();
for (Long key : keys) {
User User = this.load(key);
if (User != null) {
result.put(key, User);
}
}
return result;
}
#Override
public Iterable<Long> loadAllKeys() {
return userJpaRepository.findAllId();
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
userJpaRepository = applicationContext.getBean(UserJpaRepository.class);
}
#Override
public void store(Long aLong, User user) {
userJpaRepository.save(user);
}
#Override
public void storeAll(Map<Long, User> map) {
for (Map.Entry<Long, User> mapEntry : map.entrySet()) {
store(mapEntry.getKey(), mapEntry.getValue());
}
}
#Override
public void delete(Long aLong) {
userJpaRepository.deleteById(aLong);
}
#Override
public void deleteAll(Collection<Long> collection) {
collection.forEach(this::delete);
}
}
public interface UserJpaRepository extends CrudRepository<User, Long> {
#Query("SELECT u.id FROM User u")
Iterable<Long> findAllId();
}
I think that there is no better way than what you described.
I'd go with the second solution, because then you're at least coupled to Hazelcast server only.
I have this properties in my YAML file:
request-topic:
topics:
IMPORT_CHARGES: topic-name-1
IMPORT_PAYMENTS: topic-name-2
IMPORT_CATALOGS: topic-name-3
And this class:
#Getter
#Setter
#Component
#ConfigurationProperties(prefix = "topic-properties")
public class TopicProperties {
private Map<String, String> topics = new HashMap<>();
public String getTopicNameByType(String type){
return topics.get(type);
}
}
But when I autowire this properies I get empty Map:
#Service
public class TopicRouterImpl implements TopicRouter {
private final TopicProperties topics;
public TopicRouterImpl(TopicProperties topics) {
this.topics = topics;
}
#PostConstruct
public void init(){
topics.getTopicNameByType("IMPORT_CHARGES");
}
#Override
public String getTopicName(MessageType messageType) {
return topics.getTopicNameByType(messageType.name());
}
}
This is due to the name mismatch in your yaml file it should be equals to the specified prefix : topic-properties. Like this :
topic-properties:
topics:
IMPORT_CHARGES: topic-name-1
IMPORT_PAYMENTS: topic-name-2
IMPORT_CATALOGS: topic-name-3
I am new to Junits and Mockito, I am writing a Unit test class to test my service class CourseService.java which is calling findAll() method of CourseRepository.class which implements CrudRepository<Topics,Long>
Service Class
#Service
public class CourseService {
#Autowired
CourseRepository courseRepository;
public void setCourseRepository(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
public Boolean getAllTopics() {
ArrayList<Topics> topicList=(ArrayList<Topics>) courseRepository.findAll();
if(topicList.isEmpty())
{
return false;
}
return true;
}
}
Repository class
public interface CourseRepository extends CrudRepository<Topics,Long>{
}
Domain class
#Entity
#Table(name="Book")
public class Topics {
#Id
#Column(name="Topicid")
private long topicId;
#Column(name="Topictitle",nullable=false)
private String topicTitle;
#Column(name="Topicauthor",nullable=false)
private String topicAuthor;
public long getTopicId() {
return topicId;
}
public void setTopicId(long topicId) {
this.topicId = topicId;
}
public String getTopicTitle() {
return topicTitle;
}
public void setTopicTitle(String topicTitle) {
this.topicTitle = topicTitle;
}
public String getTopicAuthor() {
return topicAuthor;
}
public void setTopicAuthor(String topicAuthor) {
this.topicAuthor = topicAuthor;
}
public Topics(long topicId, String topicTitle, String topicAuthor) {
super();
this.topicId = topicId;
this.topicTitle = topicTitle;
this.topicAuthor = topicAuthor;
}
}
Following is the Junit class I have written but courseRepository is getting initialized to NULL and hence I am getting NullPointerException.
public class CourseServiceTest {
#Mock
private CourseRepository courseRepository;
#InjectMocks
private CourseService courseService;
Topics topics;
#Mock
private Iterable<Topics> topicsList;
#Before
public void setUp() {
MockitoAnnotations.initMocks(CourseServiceTest.class);
}
#Test
public void test_Get_Topic_Details() {
List<Topics> topics = new ArrayList<Topics>();
Mockito.when(courseRepository.findAll()).thenReturn(topics);
boolean result=courseService.getAllTopics();
assertTrue(result);
}
}
Change the setUp() method to:
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
Probably you are dealing with some problem on the framework to make the mocked class be injected by the framework.
I recommend to use Constructor Injection, so you don't need to rely on the reflection and #Inject/#Mock annotations to make this work:
#Service
public class CourseService {
private final CourseRepository courseRepository;
// #Autowired annotation is optional when using constructor injection
CourseService (CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
// .... code
}
The test:
#Test
public void test_Get_Topic_Details() {
List<Topics> topics = new ArrayList<Topics>();
Mockito.when(courseRepository.findAll()).thenReturn(topics);
CourseService courseService = new CourseService(courseRepository);
boolean result = courseService.getAllTopics();
assertTrue(result);
}
I have Spring converter which uses Spring Data REST's component called EnumTranslator
#Component
public class TranslationStringToSpecificationStatusEnumConverter implements Converter<String, Specification.Status> {
private final EnumTranslator enumTranslator;
#Autowired
public TranslationStringToSpecificationStatusEnumConverter(EnumTranslator enumTranslator) {
this.enumTranslator = enumTranslator;
}
#Override
public Specification.Status convert(String source) {
return enumTranslator.fromText(Specification.Status.class, source);
}
}
Recommended way to register such converter is to subclass RepositoryRestConfigurerAdapter as follows:
#Configuration
public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter {
private final TranslationStringToSpecificationStatusEnumConverter converter;
#Autowired
public RepositoryRestConfig(TranslationStringToSpecificationStatusEnumConverter converter) {
this.converter = converter;
}
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(converter);
super.configureConversionService(conversionService);
}
}
When I run the Spring Boot application, it fails on the following:
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| translationStringToSpecificationStatusEnumConverter defined in file ...
↑ ↓
| org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration (field java.util.List org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration.configurers)
↑ ↓
| repositoryRestConfig defined in file ...
└─────┘
So there is circular bean dependency.
How can I register the converter above so that I don't introduce circular bean dependency?
To make it work:
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(String.class, Status.class, new StringToTranslatedEnumConverter<>(Status.class));
super.configureConversionService(conversionService);
}
First I created utility class that help me work with Spring beans in unmanaged objects:
#Component
public final class SpringUtils {
#Autowired private ApplicationContext ctx;
private static SpringUtils instance;
#PostConstruct
private void registerInstance() {
instance = this;
}
public static <T> T getBean(Class<T> clazz) {
return instance.ctx.getBean(clazz);
}
}
Then I created the converter:
public class StringToTranslatedEnumConverter<T extends Enum<T> & TranslatedEnum> implements Converter<String, T> {
private final ConcurrentMapCache cache;
private EnumTranslator enumTranslator;
private Class<T> type;
public StringToTranslatedEnumConverter(Class<T> type) {
this.type = type;
cache = new ConcurrentMapCache(type.getName());
}
#Override
public T convert(String from) {
if (enumTranslator == null) {
enumTranslator = SpringUtils.getBean(EnumTranslator.class);
}
Cache.ValueWrapper wrapper = cache.get(from);
if (wrapper != null) {
//noinspection unchecked
return (T) wrapper.get();
}
T translatedEnum = enumTranslator.fromText(type, from);
cache.put(from, translatedEnum);
return translatedEnum;
}
}
UPDATED
TranslatedEnum - it's interface-marker, used to mark enums which translation is only need.
public interface TranslatedEnum {
}
public enum Status implements TranslatedEnum {
CREATED, DELETED
}
The solution to this problem is Spring Core specific. In order to break circle bean dependency cycle, we have to delay setting converter in RepositoryRestConfig. It can be achieved with setter injection:
#Component
public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter {
private TranslationStringToSpecificationStatusEnumConverter converter;
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(converter);
super.configureConversionService(conversionService);
}
#Autowired
public void setConverter(TranslationStringToSpecificationStatusEnumConverter converter) {
this.converter = converter;
}
}
You can find how to solve it in this commit by Greg Turnquist: https://github.com/pmihalcin/custom-converter-in-spring-data-rest/commit/779a6477d76dc77515b3e923079e5a6543242da2
I used #ComponentScan on the Application class and use the #Configuration on my config class, in my config class, I want to inject beans defined in other config class by using #Autowired annotation, but when I run the application, I got null for these fields.
#Configuration
public class AConfiguration {
#Bean
public A getA(){
return ..;
}
}
#Configuration
public class BConfiguration {
#Autowired
private A a;
#Bean
public B getB() {
**something need a, but a is null**
}
}
#EnableCaching
#Configuration
public class EhcacheConfiguration extends CachingConfigurerSupport {
#Bean
#Override
public CacheManager cacheManager() {
return new EhCacheCacheManager(ehCacheCacheManager().getObject());
}
#Bean
public EhCacheManagerFactoryBean ehCacheCacheManager() {
EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
cmfb.setConfigLocation(new ClassPathResource("ehcache.xml"));
cmfb.setShared(true);
return cmfb;
}
}
#Configuration
#DependsOn("ehcacheConfiguration")
public class ShiroConfiguration {
#Autowired
private org.springframework.cache.CacheManager cacheManager;
}
#SpringBootApplication
#EnableTransactionManagement
public class JarApplication {
public static void main(String[] args) {
SpringApplication.run(JarApplication.class, args);
}
}
You can try this.
#Configuration
public class AConfiguration {
#Bean
public A getA(){
return ..;
}
}
#Configuration
public class BConfiguration {
#Autowired
private A a;
public B getB() {
**something need a, but a is null**
}
}