This question already has answers here:
How to autowire #ConfigurationProperties into #Configuration?
(2 answers)
Closed 3 years ago.
Java 8 and Spring Boot 1.5.8 here. I have the following application.properties file:
logging:
config: 'logback.groovy'
myapp:
hystrixTimeoutMillis: 500
jwt:
expiry: 86400000
secret: 12345
machineId: 12345
spring:
cache:
type: none
Which maps to the following #ConfigurationProperties POJO:
#ConfigurationProperties(prefix = "myapp")
public class MyAppConfig {
private Jwt jwt;
private Long hystrixTimeoutMillis;
private String machineId;
public Jwt getJwt() {
return jwt;
}
public void setJwt(Jwt jwt) {
this.jwt = jwt;
}
public Long getHystrixTimeoutMillis() {
return hystrixTimeoutMillis;
}
public void setHystrixTimeoutMillis(Long hystrixTimeoutMillis) {
this.hystrixTimeoutMillis = hystrixTimeoutMillis;
}
public String getMachineId() {
return machineId;
}
public void setMachineId(String machineId) {
this.machineId = machineId;
}
public static class Jwt {
private Long expiry;
private String secret;
public Long getExpiry() {
return expiry;
}
public void setExpiry(Long expiry) {
this.expiry = expiry;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
}
}
And I have the following #Configuration (injector) class:
#Configuration
public class MyAppInjector implements ApplicationContextAware {
private Logger log = LoggerFactory.getLogger(this.getClass());
private ApplicationContext applicationContext;
#Autowired
private MyAppConfig myAppConfig;
#Bean
public AuthService authService(MyAppConfig myAppConfig) {
return new JwtAuthService(myAppConfig);
}
}
And the following JwtAuthService class:
public class JwtAuthService implements AuthService {
private static final String BEARER_TOKEN_NAME = "Bearer";
private Logger log = LoggerFactory.getLogger(this.getClass());
private MyAppConfig myAppConfig;
#Autowired
public JwtAuthService(MyAppConfig myAppConfig) {
this.myAppConfig = myAppConfig;
}
#Override
public boolean isValidAuthToken(String authToken) {
return true;
}
}
At startup I get the following error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field myAppConfig in com.example.myapp.spring.MyAppInjector required a bean of type 'com.example.myapp.spring.MyAppConfig' that could not be found.
Action:
Consider defining a bean of type 'com.example.myapp.spring.MyAppConfig' in your configuration.
Why am I getting this error? Where am I injecting/configuring things incorrectly?
You are not declaring MyAppConfig as a bean anywhere in your example, #ConfigurationProperties doesn't make annotated class a bean. You can do it as part of MyAppInjector configuration:
#Configuration
public class MyAppInjector {
#Bean
public AuthService authService() {
return new JwtAuthService(myAppConfig());
}
#Bean
public MyAppConfig myAppConfig() {
return new MyAppConfig();
}
}
Class with #ConfigurationProperties should also be bean. You need to annotate it as #Component or manually register in #Configuration class with #Bean annotation (instead of trying to autowire it there)
Related
I've registered a Spring Post Processor to register some beans based on application properties.
Also I would like to add support for encrypted properties.
I use spring and jasypt spring boot
Here are my configurations:
application.yml:
rest:
jwt-client:
location:
base-url: loc_URL
invoke-timeout:
connection: 5000
read: 500
jwt-config:
token: ENC(gRbmO8Gv2Yb3vUf27nykDPd5/wjVdj+r5REr5HcIh4k=)
Post Processor class:
#RequiredArgsConstructor
public class DynamicClientBeanConfiguration implements BeanDefinitionRegistryPostProcessor {
private static final String BEAN_POSTFIX = "-api-client-config";
private final Environment environment;
#Override
public void postProcessBeanDefinitionRegistry(#NonNull BeanDefinitionRegistry registry) {
Binder.get(environment)
.bind("rest.jwt-client", Bindable.mapOf(String.class, JwtClientConfig.class))
.orElse(new LinkedHashMap<>())
.forEach((key, value) -> {
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(JwtClientConfig.class);
bd.setInstanceSupplier(() -> value);
bd.addQualifier(new AutowireCandidateQualifier(key + BEAN_POSTFIX));
registry.registerBeanDefinition(key + BEAN_POSTFIX, bd);
log.debug("Registered JwtClientConfig bean with #Qualifier(\"{}\")", key + BEAN_POSTFIX);
});
}
#Override
public void postProcessBeanFactory(#NonNull ConfigurableListableBeanFactory beanFactory) {
// Nothing to override
}
}
Custom Configuration Properties class:
#Getter
#Setter
#ConfigurationProperties
public class JwtClientConfig {
private String baseUrl;
private InvokeTimeout invokeTimeout;
private JwtConfig jwtConfig;
#Getter
#Setter
public static class InvokeTimeout {
private int connection;
private int read;
}
#Getter
#Setter
public static class JwtConfig {
private String token;
}
}
However when the following part of code in the post processor executed it binds raw data from properties like ENC(...) but not decrypted as I expect:
Binder.get(environment)
.bind("rest.jwt-client", Bindable.mapOf(String.class, JwtClientConfig.class))
.orElse(new LinkedHashMap<>())
It it possible to bind already decrypted properties in Post Processor?
Note: I already look at and tried some approaches on SO e.g. How to test Spring's declarative caching support on Spring Data repositories?, but as most of them old, I cannot make them work properly and I need a solution with the latest library versions. So, I would be appreciated if you have a look at the question and help.
#Service
#EnableCaching
#RequiredArgsConstructor
public class DemoServiceImpl implements DemoService {
private static final String CACHE_NAME = "demoCache";
private final LabelRepository labelRepository;
private final LabelTranslatableRepository translatableRepository;
private final LanguageService languageService;
#Override
public LabelDTO findByUuid(UUID uuid) {
final Label label = labelRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException("Not found."));
final List<LabelTranslatable> translatableList = translatableRepository.findAllByEntityUuid(uuid);
return new LabelDTO(Pair.of(label.getUuid(), label.getKey()), translatableList);
}
}
I created the following Unit Test to test caching for the nethod above:
#EnableCaching
#ImportAutoConfiguration(classes = {
CacheAutoConfiguration.class,
RedisAutoConfiguration.class
})
#ExtendWith(MockitoExtension.class)
class TextLabelServiceImpl_deneme_Test {
#Autowired
private CacheManager cacheManager;
#InjectMocks
private LabelService labelService;
#Mock
private LabelRepository labelRepository;
#Mock
private LabelTranslatableRepository translatableRepository;
#Test
void test_Cache() {
UUID uuid = UUID.randomUUID();
final TextLabel textLabel = new TextLabel();
textLabel.setId(1);
textLabel.setKey("key1");
TextLabelTranslatable textLabelTranslatable = new TextLabelTranslatable();
textLabelTranslatable.setEntityUuid(uuid);
textLabelTranslatable.setLanguage(SupportedLanguage.fr);
textLabelTranslatable.setValue("value1");
final List<TextLabelTranslatable> translatableList = new ArrayList<>();
translatableList.add(textLabelTranslatable);
when(labelRepository.findByUuid(uuid)).thenReturn(Optional.of(textLabel));
when(translatableRepository.findAllByEntityUuid(uuid)).thenReturn(translatableList);
TextLabelDTO result1 = labelService.findByUuid(uuid);
TextLabelDTO result2 = labelService.findByUuid(uuid);
assertEquals(result1, result2);
Mockito.verify(translatableRepository, Mockito.times(1)).findAllByEntityUuid(uuid);
}
I am not sure if there is a missing part in my test, but at the last line (Mockito.verify()), it returns 2 instead of 1 that means caching not works. But it is working properly and there is a problem in my test I think. How should I complete the unit test to check the caching properly?
You need to annotate the service class method with #Cacheable. Try to follow the code in this tutorial. The following test code works as expected
#Import({CacheConfig.class, DemoServiceImpl.class})
#ExtendWith(SpringExtension.class)
#EnableCaching
#ImportAutoConfiguration(classes = {
CacheAutoConfiguration.class,
RedisAutoConfiguration.class
})
class DemoServiceImplTest {
#MockBean
private LabelRepository labelRepository;
#Autowired
private DemoServiceImpl demoService;
#Autowired
private CacheManager cacheManager;
#TestConfiguration
static class EmbeddedRedisConfiguration {
private final RedisServer redisServer;
public EmbeddedRedisConfiguration() {
this.redisServer = new RedisServer();
}
#PostConstruct
public void startRedis() {
redisServer.start();
}
#PreDestroy
public void stopRedis() {
this.redisServer.stop();
}
}
#Test
void givenRedisCaching_whenFindItemById_thenItemReturnedFromCache() {
UUID id = UUID.randomUUID();
Label aLabel = new Label(id, "label");
Mockito.when(labelRepository.findById(id)).thenReturn(Optional.of(aLabel));
Label labelCacheMiss = demoService.findByUuid(id);
Label labelCacheHit = demoService.findByUuid(id);
Mockito.verify(labelRepository, Mockito.times(1)).findById(id);
}
}
With this service class code:
#Service
#RequiredArgsConstructor
#EnableCaching
public class DemoServiceImpl {
public static final String CACHE_NAME = "demoCache";
private final LabelRepository labelRepository;
#Cacheable(value = CACHE_NAME)
public Label findByUuid(UUID uuid) {
return labelRepository.findById(uuid)
.orElseThrow(() -> new EntityNotFoundException("Not found."));
}
}
And this CacheConfig:
#Configuration
public class CacheConfig {
#Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration(DemoServiceImpl.CACHE_NAME,
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)));
}
#Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60))
.disableCachingNullValues()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
}
}
Tried to create a configuration class which depends on another bean class using #DependsOn annotation and later found that #DependsOn only work along with #Bean and #Component and not with #Configuration.
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
private static final String URL_MAPPINGS = "/*";
private static final Class<?>[] classes = new Class[]{Application.class, Config1.class};
public static void main(String[] args) {
SpringApplication.run(classes, args);
}
...
}
#Configuration
public class MongoConfig {
#Value("${db.name}")
private String dbName;
#Value("${db.url}")
private String url;
#Override
public MongoClient mongo() {
ConnectionString connectionString = new ConnectionString("mongodb:"+url);
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.build();
return MongoClients.create(mongoClientSettings);
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongo(), dbName);
}
}
#Import({ MongoConfig.class, Config2.class})
#Configuration
public class Config1 {
public BeanA beanA(MongoTemplate mongoTemplate, Environment environment) {
return new BeanA(mongoTemplate);
}
}
#Configuration
#ComponentScan("com.test")
//this should be processed after BeanA is created, similar to #DependsOn
public class Config2 {
...
}
public class BeanA {
private MongoTemplate mongoTemplate;
public BeanA(MongoTemplate mongoTemplate, Environment environment) {
this.mongoTemplate = mongoTemplate;
this.applicationPropertySource = new ApplicationPropertySource(SOURCE_NAME);
MutablePropertySources mutablePropertySources = ((StandardEnvironment) environment).getPropertySources();
mutablePropertySources.addFirst(applicationPropertySource);
getExistingProperties().forEach((key, val) -> applicationPropertySource.set(key, val));
}
private Map<String, String> getExistingProperties() {
//assume values are obtained from DB
}
public static class ApplicationPropertySource extends PropertiesPropertySource {
public ApplicationPropertySource(String name) {
super(name, new HashMap());
}
public Object set(String key, String value) {
return getSource().put(key, value);
}
}
I like to achieve this to avoid adding #DependsOn annotation on all the beans in the project, is this even possible?
Spring version is v1.5.12.RELEASE
I have more than one yml files in Spring Boot in resource classpath location like following structure of Spring Boot. Initially I have written only for application-abc.yml and at the time all the values of this file was loading in their corresponding class but when I have added on another file application-xyz.yml then also it loads into their corresponding configuration classes but at this time only loading the values of application-xyz.yml in both the configuration classes. So, want help to configure values of both the files in their corresponding configuration files in a single build :
-src
-main
-java
-packages
-config
-ApplicationAbcConfig.java
-ApplicationConfig.java
-ApplicationFactory.java
-ApplicationXyzConfig.java
-Authentication.java
-Operations.java
-Payload.java
-RequestPayload.java
-ResponsePayload.java
-services
-YmlConfigurationSelection.java
-resources
-application.yml
-application-abc.yml
-application-xyz.yml
-MultipleYmlDemoProject.java
Content of application-abc.yml
authentication:
name: name
type: type
payload:
request:
- sequence: 1
attributes:
- attributes1
- attributes2
response:
- sequence: 1
attributes:
- attributes3
- attributes4
operations:
name: name
type: type
payload:
request:
- sequence: 1
attributes:
- attributes5
- attributes6
response:
- sequence: 1
attributes:
- attributes7
- attributes8
Content of application-xyz.yml
authentication:
name: name
type: type
payload:
request:
- sequence: 1
attributes:
- attributes9
- attributes10
response:
- sequence: 1
attributes:
- attributes11
- attributes12
operations:
name: name
type: type
payload:
request:
- sequence: 1
attributes:
- attributes13
- attributes14
response:
- sequence: 1
attributes:
- attributes15
- attributes16
Content of ApplicationConfig.java
public interface ApplicationConfig {
public Authentication getAuthentication();
public void setAuthentication(Authentication authentication);
public Operations getOperations();
public void setOperations(Operations operations);
}
Content of Authentication.java
public class Authentication {
private String name;
private String type;
private Payload payload;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Payload getPayload() {
return payload;
}
public void setPayload(Payload payload) {
this.payload = payload;
}
}
Content of Operations.java
public class Operations {
private String name;
private String type;
private Payload payload;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Payload getPayload() {
return payload;
}
public void setPayload(Payload payload) {
this.payload = payload;
}
}
Content of Payload.java
public class Payload {
private List<RequestPayload> request;
private List<ResponsePayload> response;
public List<RequestPayload> getRequest() {
return request;
}
public void setRequest(List<RequestPayload> request) {
this.request = request;
}
public List<ResponsePayload> getResponse() {
return response;
}
public void setResponse(List<ResponsePayload> response) {
this.response = response;
}
}
Content of RequestPayload.java
public class RequestPayload {
private String sequece;
private List<String> attributes;
public String getSequece() {
return sequece;
}
public void setSequece(String sequece) {
this.sequece = sequece;
}
public List<String> getAttributes() {
return attributes;
}
public void setAttributes(List<String> attributes) {
this.attributes = attributes;
}
}
Content of ResponsePayload.java
public class ResponsePayload {
private String sequece;
private List<String> attributes;
public String getSequece() {
return sequece;
}
public void setSequece(String sequece) {
this.sequece = sequece;
}
public List<String> getAttributes() {
return attributes;
}
public void setAttributes(List<String> attributes) {
this.attributes = attributes;
}
}
Content of ApplicationAbcConfig.java
#Configuration
#SpringBootConfiguration
#EnableConfigurationProperties
#org.springframework.context.annotation.PropertySource("classpath:application-abc.yml")
public class ApplicationAbcConfig implements ApplicationConfig, PropertySourceFactory {
private Authentication authentication;
private Operations operations;
#Override
public Authentication getAuthentication() {
return authentication;
}
#Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
#Override
public Operations getOperations() {
return operations;
}
#Override
public void setOperations(Operations operations) {
this.operations = operations;
}
#Override
public PropertySource<?> createPropertySource(#Nullable String name, EncodedResource resource) throws IOException {
Properties propertiesFromYaml = loadYamlIntoProperties(resource);
String sourceName = name != null ? name : resource.getResource().getFilename();
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
}
private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
try {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
} catch (IllegalStateException e) {
// for ignoreResourceNotFound
Throwable cause = e.getCause();
if (cause instanceof FileNotFoundException)
throw (FileNotFoundException) e.getCause();
throw e;
}
}
}
Content of ApplicationXyzConfig.java
#Configuration
#SpringBootConfiguration
#EnableConfigurationProperties
#org.springframework.context.annotation.PropertySource("classpath:application-xyz.yml")
public class ApplicationXyzConfig implements ApplicationConfig, PropertySourceFactory {
private Authentication authentication;
private Operations operations;
#Override
public Authentication getAuthentication() {
return authentication;
}
#Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
#Override
public Operations getOperations() {
return operations;
}
#Override
public void setOperations(Operations operations) {
this.operations = operations;
}
#Override
public PropertySource<?> createPropertySource(#Nullable String name, EncodedResource resource) throws IOException {
Properties propertiesFromYaml = loadYamlIntoProperties(resource);
String sourceName = name != null ? name : resource.getResource().getFilename();
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
}
private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
try {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
} catch (IllegalStateException e) {
// for ignoreResourceNotFound
Throwable cause = e.getCause();
if (cause instanceof FileNotFoundException)
throw (FileNotFoundException) e.getCause();
throw e;
}
}
}
Content of ApplicationFactory.java
#Component
public class ApplicationFactory {
#Autowired
private ApplicationAbcConfig applicationAbcConfig;
#Autowired
private ApplicationXyzConfig applicationXyzConfig;
public ApplicationConfig getApplicationPropertiesConfig(String application) {
if (application.equalsIgnoreCase("abc")) {
return applicationAbcConfig;
} else if (application.equalsIgnoreCase("xyz")) {
return applicationXyzConfig;
} else {
return null;
}
}
}
Content of YmlConfigurationSelection.java
public class YmlConfigurationSelection {
#Autowired
private ApplicationFactory applicationFactory;
private ApplicationConfig applicationConfig;
public Object accessingProperties(String application) {
applicationConfig = applicationFactory.getApplicationPropertiesConfig(application);
return null;
}
}
Content of MultipleYmlDemoProject.java
#SpringBootApplication
#SpringBootConfiguration
#PropertySource(factory = ApplicationAbcConfig.class, value = "classpath:application-abc.yml")
#PropertySource(factory = ApplicationXyzConfig.class, value = "classpath:application-xyz.yml")
public class MultipleYmlDemoProject {
public class MultipleYmlDemo {
public static void main(String[] args) {
ConfigurableApplicationContext ctx =
SpringApplication.run(YamlPropertysourceApplication.class, args);
ConfigurableEnvironment env = ctx.getEnvironment();
}
}
}
It looks like you have an old spring application that was attempted to be migrated to spring boot.
Spring boot works natively with yaml files, so if you do the integration in a spring boot way, it will be possible to delete a lot of boilerplate code that you have to maintain. Also the naming of configurations is problematic: the names application-<something>.yml are reserved to be used with spring boot profiles, maybe if you'll rename to myprops-abc/xyz.yaml it will behave in a different way, I can't say for sure.
All-in-all I suggest you the following way, which is much better IMO:
Both configuration sets must be loaded into one configuration, so create a configuration properties file that denotes this one configuration:
#ConfigurationProperties(prexix="security")
public class SecurityConfigProperties {
private SecurityRealm abc;
private SecurityRealm xyz;
// getters, setters
}
public class SecurityRealm {
private Authentication autentication;
private Operations operations;
// getters setters
}
public class Authentication {...}
private class Operations {...}
Now place all the content from abc and xyz yaml into one file application.yaml and give a 'security' prefix:
security:
abc: // note, this 'abc' matches the field name of the configuration
authentication:
...
operations:
....
xyz:
authentication:
...
operations:
....
OK, everything is mapped, create the configuration like this:
#Configuration
#EnableConfigurationProperties(SecurityConfigProperties.class)
public class SecurityConfiguration {
#Bean
public SecurityBeanForABC(SecurityConfigProperties config) {
return new SecurityBeanForABC(config.getAbc().getAuthentication(), config.getAbc().getOperations());
}
... the same for XYZ
}
Note that with this approach you only maintain a configuration mapping in java objects and there is no code for loading / resolving properties) - everything is done by spring boot automatically. If you configure a special annotation processor and have a descent IDE you can get even auto-completion facilities for those yaml properties but its out of scope for this question. The point is that doing things in a way directly supported by spring boot has many advantages :)
I'm creating a Spring REST app using Spring Boot 2, Spring Data REST, Spring HATEOAS.
I created this controller:
#Api(tags = "City Entity")
#RepositoryRestController
#RequestMapping(path = "/api/v1")
#PreAuthorize("isAuthenticated()")
public class CityController {
#Autowired
private LocalValidatorFactoryBean validator;
#Autowired
private PagedBeanResourceAssembler<City> pagedBeanResourceAssembler;
#Autowired
private CityService cityService;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(validator);
}
#GetMapping(path = "/cities/search/autocomplete")
public ResponseEntity<?> autocomplete(#RequestParam(name = "city") String city, #RequestParam(name = "country", required = false) String country, Pageable pageable, Locale locale) {
return new ResponseEntity<>(pagedBeanResourceAssembler.toResource(cityService.autocomplete(city, country, pageable)), HttpStatus.OK);
}
}
The service method is:
#Transactional(readOnly = true)
public Page<City> autocomplete(String text, String country, Pageable pageable) {
//my logic
return elasticSearchManager.search(ElasticSearchUtil.getIndexName(City.class), null, City.class, filters, null, pageable);
}
As you can see City bean is not stored in the DB. In fact the bean is:
public class City implements Persistable<Long> {
private Long id;
#NotBlank
private String name;
private String district;
private String region;
private String zipCode;
#NotNull
#Size(min = 2, max = 2)
private String country;
}
and finally this is my PagedBeanResourceAssembler:
#Component
public class PagedBeanResourceAssembler<T> implements ResourceAssembler<Page<T>, PagedResources<T>> {
#Autowired
private EntityLinks entityLinks;
#Override
public PagedResources<T> toResource(Page<T> page) {
PagedResources<T> pagedResources = new PagedResources<T>(page.getContent(), asPageMetadata(page));
return pagedResources;
}
private PagedResources.PageMetadata asPageMetadata(Page<?> page) {
Assert.notNull(page, "Page must not be null!");
return new PagedResources.PageMetadata(page.getSize(), page.getNumber(), page.getTotalElements(), page.getTotalPages());
}
}
When I make a http call I see a WARNING message in the console:
08/02/2019 11:09:35,526 WARN http-nio-8082-exec-1 RepositoryRestMvcConfiguration$ResourceSupportHttpMessageConverter:205 - Failed to evaluate Jackson serialization for type [class org.springframework.hateoas.PagedResources]: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer.<init>()
08/02/2019 11:09:35,527 WARN http-nio-8082-exec-1 MappingJackson2HttpMessageConverter:205 - Failed to evaluate Jackson serialization for type [class org.springframework.hateoas.PagedResources]: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer.<init>()
Not using a PagedResources the error goes away. I don't understand where I'm doing something wrong. I got that HalResourcesSerializer has not a default constructor, but I don't use it directly and I don't understand either why with Entity persisted in the db a controller such this works fine.
How can I fix this problem continuing to use a PagedResource?
======== UPDATE ==========
I add my configuration to give a more detailed view:
CustomConfiguration:
#Configuration
#EnableRetry
#EnableTransactionManagement
#EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
public class CustomConfiguration {
public static CustomConfiguration INSTANCE;
#PostConstruct
public void init() {
INSTANCE = this;
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public static SpringSecurityAuditorAware springSecurityAuditorAware() {
return new SpringSecurityAuditorAware();
}
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:/i18n/messages");
// messageSource.setDefaultEncoding("UTF-8");
// set to true only for debugging
messageSource.setUseCodeAsDefaultMessage(false);
messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
messageSource.setFallbackToSystemLocale(false);
return messageSource;
}
#Bean
public MessageSourceAccessor messageSourceAccessor() {
return new MessageSourceAccessor(messageSource());
}
/**
* Enable Spring bean validation https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation
*
* #return
*/
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setValidationMessageSource(messageSource());
return factoryBean;
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
/**
* Utility class from Google to work with phone numbers {#link https://github.com/googlei18n/libphonenumber}
*
* #return
*/
#Bean
public PhoneNumberUtil phoneNumberUtil() {
return PhoneNumberUtil.getInstance();
}
/**
* To enable SpEL expressions
*
* #return
*/
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
/**
* Define the specific storage manager to use (disk, S3, etc)
*
* #return
*/
#Bean
public StorageManager storageManager() {
return new S3StorageManager();
}
/**
* GRACEFUL SHUTDOWN
*/
#Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
#Bean
public ConfigurableServletWebServerFactory webServerFactory(final GracefulShutdown gracefulShutdown) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(gracefulShutdown);
return factory;
}
}
GlobalRepositoryRestConfigurer:
#Configuration
public class GlobalRepositoryRestConfigurer implements RepositoryRestConfigurer {
private Logger log = LogManager.getLogger();
#Autowired(required = false)
private Jackson2ObjectMapperBuilder objectMapperBuilder;
#Autowired
private Validator validator;
#Value("${cors.mapping}")
private String corsMapping;
#Value("#{'${cors.allowed.headers}'.split(',')}")
private String[] corsAllowedHeaders;
#Value("#{'${cors.exposed.headers}'.split(',')}")
private String[] corsExposedHeaders;
#Value("#{'${cors.allowed.methods}'.split(',')}")
private String[] corsAllowedMethod;
#Value("#{'${cors.allowed.origins}'.split(',')}")
private String[] corsAllowedOrigins;
#Value("${cors.max.age}")
private int corsMaxAge;
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.getCorsRegistry().addMapping(corsMapping).exposedHeaders(corsExposedHeaders).allowedOrigins(corsAllowedOrigins)
.allowedHeaders(corsAllowedHeaders).allowedMethods(corsAllowedMethod).maxAge(corsMaxAge);
}
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
}
/**
* ValidationException serialiazer
*
* #return
*/
#Bean
public ValidationExceptionSerializer validationExceptionSerializer() {
return new ValidationExceptionSerializer();
}
#Bean
public CustomValidationExceptionSerializer customValidationExceptionSerializer() {
return new CustomValidationExceptionSerializer();
}
#Bean
public ConstraintViolationExceptionSerializer constraintViolationExceptionSerializer() {
return new ConstraintViolationExceptionSerializer();
}
/**
* Customize Object Mapper
*/
#Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
if (this.objectMapperBuilder != null) {
/**
* Custom serializer for ConstraintViolationException
* (https://jira.spring.io/browse/DATAREST-593)
*/
try {
SimpleModule constraintExceptionModule = new SimpleModule();
constraintExceptionModule.addSerializer(ConstraintViolationException.class, constraintViolationExceptionSerializer());
constraintExceptionModule.addSerializer(ValidationException.class, validationExceptionSerializer());
constraintExceptionModule.addSerializer(cloud.optix.server.exceptions.ValidationException.class, customValidationExceptionSerializer());
objectMapper.registerModule(constraintExceptionModule);
this.objectMapperBuilder.configure(objectMapper);
} catch (Exception e) {
log.error("", e);
}
}
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
#Override
public void configureExceptionHandlerExceptionResolver(ExceptionHandlerExceptionResolver exceptionResolver) {
}
/**
* Adding converter to donwload files in{#link org.springframework.web.bind.annotation.RestController}
*
* #param messageConverters
*/
#Override
public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
// super.configureHttpMessageConverters(messageConverters);
messageConverters.add(new ResourceHttpMessageConverter());
}
}
WebMvcConfiguration:
#Configuration
// Enable entity links for Spring HATEOAS
#EnableHypermediaSupport(type = {HypermediaType.HAL})
public class WebMvcConfiguration implements WebMvcConfigurer {
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private TenantRestClient tenantRestClient;
#Value("${cors.mapping}")
private String corsMapping;
#Value("#{'${cors.allowed.headers}'.split(',')}")
private String[] corsAllowedHeaders;
#Value("#{'${cors.exposed.headers}'.split(',')}")
private String[] corsExposedHeaders;
#Value("#{'${cors.allowed.methods}'.split(',')}")
private String[] corsAllowedMethod;
#Value("#{'${cors.allowed.origins}'.split(',')}")
private String[] corsAllowedOrigins;
#Value("${cors.max.age}")
private int corsMaxAge;
#Autowired
public WebMvcConfiguration() {
}
#Bean
public LocaleResolver localeResolver() {
return new SmartLocaleResolver();
}
public class SmartLocaleResolver extends CookieLocaleResolver {
#Override
public Locale resolveLocale(HttpServletRequest request) {
String acceptLanguage = request.getHeader("Accept-Language");
if (acceptLanguage == null || acceptLanguage.trim().isEmpty()) {
return super.determineDefaultLocale(request);
}
return request.getLocale();
}
}
/**
* Custom exception in WEB MVC
*
* #return
*/
#Bean
public CustomErrorAttributes myCustomErrorAttributes() {
return new CustomErrorAttributes();
}
/**
* Global CORS security configuration
*
* #param registry
*/
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping(corsMapping).exposedHeaders(corsExposedHeaders).allowedOrigins(corsAllowedOrigins).allowedHeaders(corsAllowedHeaders)
.allowedMethods(corsAllowedMethod).maxAge(corsMaxAge);
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TenantInterceptor());
}
}
Try commenting out this line in your configuration:
this.objectMapperBuilder.configure(objectMapper);
RepositoryRestConfigurer configures the objectMapper for itself quite well I assume.
If you need it for automatically adding more modules from your classpath, then add/configure those modules manually.
You will get same error when you use RepresentationModelAssembler<Object, PersistentEntityResource> resourceAssembler as parameter to your controller method. When you use PersistentEntityResourceAssembler resourceAssembler, spring will create right instance for you.
I suggest you to try to examine class hierarchy of PagedBeanResourceAssembler class and find some more specific class/implementation.