Adopting to @Marcin Wisnicki it is possible to define an annotation say 'StringResource' with default value empty string and define utility class say 'StringResouceReader', a modified convertor such that:
- If StringResource is applied on a field as @StringResource @Value(classpath:path) then contents of resource on path is auto injected as string
- If StringResource is applied on a class as @StringResource(qualifier) and apply @Value(classpath:path) on a field and if name of the field or member starts with the qualifier then contents of resource on path is auto injected as string
The advantage of this approach is to make code more cleaner when there are multiple resources to be to be auto injected at different fields in same class. @StringResource(qualifier) can be specified on class and use @Value on as usual.
StringResource Class
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface StringResource {
String value() default "";
}
StringResourceRender
@Service
public class StringResourceReader{
@Cacheable
public String getString(Resource resource){
return readAsString(resource);
}
private static String readAsString(Resource resource){
try {
return StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private static boolean isJavaIdentifier(String name){
if(name.isEmpty()){
return false;
}
char[] chars = name.toCharArray();
for(int i = 0;i<chars.length;i++){
if(i==0&&!Character.isJavaIdentifierStart(chars[i])){
return false;
}else if(!Character.isJavaIdentifierStart(chars[i])){
return false;
}
}
return true;
}
public static void registerConvertor(ConfigurableEnvironment environment, ResourceLoader resourceLoader){
environment.getConversionService().addConverter(new ConditionalGenericConverter() {
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
if(targetType.hasAnnotation(Value.class)){
if(targetType.hasAnnotation(StringResource.class)){
return true;
}else{
Object source = targetType.getResolvableType().getSource();
if(source instanceof Member) {
Member member = (Member) source;
StringResource stringResource = AnnotationUtils.findAnnotation(member.getDeclaringClass(), StringResource.class);
if (stringResource != null) {
String qualifier = stringResource.value().trim();
if(qualifier.length()==0){
throw new IllegalStateException("Annotation StringResource must specify argument qualifier when used on a class");
}else if(!isJavaIdentifier(qualifier)){
throw new IllegalStateException("Qualifier must be java identifier");
}else{
return member.getName().startsWith(qualifier);
}
} else {
return false;
}
}else {
return false;
}
}
}else{
return false;
}
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return new HashSet(Arrays.asList(new GenericConverter.ConvertiblePair(String.class, String.class)));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return readAsString(resourceLoader.getResource(source.toString()));
}
});
}
}
Then register convertor in SpringApplication constructor
@SpringBootApplication
public class SomeApplicationApplication {
public SomeApplicationApplication(ConfigurableEnvironment environment, ResourceLoader resourceLoader){
StringResourceReader.registerConvertor(environment, resourceLoader);
}
public static void main(String[] args) {
SpringApplication.run(SomeApplicationApplication.class, args);
}
}