Here is my current solution, that is imperfect but I stuck with it as I don't have better ideas yet.
I want to move memory allocation outside of the catch
block, so the memory allocation does not happen in catch
block.
Object[]
array in each repositoryHere is the code:
@Repository
public class UserRepositoryImpl implements UserRepository {
private static final Object[] USER_ENTITY_ARG = new Object[]{User.KIND};
private final Datastore datastore;
private final KeyFactory keyFactory;
private final MessageSource messageSource;
// Constructor and other methods...
@Override
public User save(User user) {
try {
// Existing implementation...
datastore.put(entity);
} catch (DatastoreException datastoreException) {
String errorMessage = messageSource.getMessage(
"persistence.datastore.failed_to_save",
USER_ENTITY_ARG,
LocaleContextHolder.getLocale());
throw new DatastorePersistenceException(errorMessage, datastoreException);
}
return user;
}
// Other methods...
}
catch
block achieved.The code
private static final Object[] USER_ENTITY_ARG = new Object[]{User.KIND};
I do not like it for its' cons.
MessageHelper
class with encapsulated Spring's MessageSource
and pre-allocated memory for Object[]
string interpolation arraysHere is the helper class:
@Service
public class MessageHelperImpl implements MessageHelper { // MessageHelper is just my interface you can see @Override's of its methods
private static final ThreadLocal<Object[]> ARGS1 = ThreadLocal.withInitial(() -> new Object[1]);
private static final ThreadLocal<Object[]> ARGS2 = ThreadLocal.withInitial(() -> new Object[2]);
private final MessageSource messageSource;
@Autowired
public MessageHelperImpl(MessageSource messageSource) {
this.messageSource = messageSource;
}
...
@Override
public String getMessage(String code, Object arg1) {
Object[] args = ARGS1.get();
args[0] = arg1;
return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
}
@Override
public String getMessage(String code, Object arg1, Object arg2) {
Object[] args = ARGS2.get();
args[0] = arg1;
args[1] = arg2;
return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
}
@Override
public String getMessage(String code, Object... args) {
// For varargs, we use the provided array
return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
}
}
then the catch
block looks like this:
} catch (DatastoreException datastoreException) {
// Using the specialized single-argument method
String errorMessage = messageHelper.getMessage(
"persistence.datastore.failed_to_save", User.KIND); // User.KIND is just a String containing "User"
throw new DatastorePersistenceException(errorMessage, datastoreException);
}
messages.properties
and string interpolation logicThreadLocal
ARGS1
and ARGS2
buffers will be allocated before the actual call from the exception. So it does not solve my concern.ThreadLocal
fields itself are created when the MessageHelperImpl
class is loaded - Object[]
arrays created lazily.
ARGS1.get()
that call can be right from the exceptionsupplier () -> new Object[1]
I do not like this solution as it does not guarantee to solve my concern - not to allocate new memory in catch
block.
I see that potentially I can call the ARGS1.get()
for each thread on initialization of the thread but it looks sooo messy like a very poor workaround.
At this moment I do not have working solution that is architecturally nice for the problem of having string interpolation for exception messages.
Please share your thoughts.
P.S. And I am still wondered whether this is normal to create a new
exception in the catch
block - while it is an existing practice of abstracting your app's exception processing from the "vendor-specific" exceptions it still allocates memory in catch
block what is considered a bad practice.