I am attempting to use Spring Boot Cache with a Caffeine cacheManager.
I have injected a service class into a controller like this:
#RestController
#RequestMapping("property")
public class PropertyController {
private final PropertyService propertyService;
#Autowired
public PropertyController(PropertyService propertyService) {
this.propertyService = propertyService;
}
#PostMapping("get")
public Property getPropertyByName(#RequestParam("name") String name) {
return propertyService.get(name);
}
}
and the PropertyService looks like this:
#CacheConfig(cacheNames = "property")
#Service
public class PropertyServiceImpl implements PropertyService {
private final PropertyRepository propertyRepository;
#Autowired
public PropertyServiceImpl(PropertyRepository propertyRepository) {
this.propertyRepository = propertyRepository;
}
#Override
public Property get(#NonNull String name, #Nullable String entity, #Nullable Long entityId) {
System.out.println("inside: " + name);
return propertyRepository.findByNameAndEntityAndEntityId(name, entity, entityId);
}
#Cacheable
#Override
public Property get(#NonNull String name) {
return get(name, null, null);
}
}
Now, when I call the RestController get endpoint and supply a value for the name, every request ends up doing inside the method that should be getting cached.
However, if I call the controller get endpoint but pass a hardcoded String into the service class method, like this:
#PostMapping("get")
public Property getPropertyByName(#RequestParam("name") String name) {
return propertyService.get("hardcoded");
}
Then the method is only invoked the first time, but not on subsequent calls.
What's going on here? Why is it not caching the method call when I supply a value dynamically?
Here is some configuration:
#Configuration
public class CacheConfiguration {
#Bean
public CacheManager cacheManager() {
val caffeineCacheManager = new CaffeineCacheManager("property", "another");
caffeineCacheManager.setCaffeine(caffeineCacheBuilder());
return caffeineCacheManager;
}
public Caffeine<Object, Object> caffeineCacheBuilder() {
return Caffeine.newBuilder()
.initialCapacity(200)
.maximumSize(500)
.weakKeys()
.recordStats();
}
}
2 solutions (they work for me):
remove .weakKeys()
propertyService.get(name.intern()) - wouldn't really do that, possibly a big cost
Sorry, but I don't have enough knowledge to explain this. Probably something to do with internal key representation by Caffeine.
Related
I have a spring component which looks like this. It has other beans auto-wired to it. And these components are returned as a list by the method getConfigurators(String Id)
#Component
public class Configuration{
#Autowired
private Config Config1;
#Autowired
private Config Config2;
#Override
public List<Config> getConfigurators(String Id) {
return List.of(
Config1,
Config2);
}
}
When I run the application the first time, I get everything correct for getConfigurators(String Id). The subsequent runs, I get the values populated by the first run. I want that every time I run the application, the list is not pre populated with the config values from before.
The Config class looks like this
#Component
public class Config1 implements Config {
final List errors = new ArrayList<>();
#Override
public List<> validate() {
boolean isValid // some logic
if (!isValid) {
errors.add("Some error");
}
return errors;
}
}
I have method without parameters and I want to cache return value.
As cache key I want to use current authenticated user from security context
#Cacheable(value = "resultCache", key="#userPrincipal.id")
public result getResult() {}
Is it possible or my idea is wrong.
You have four options to achieve this:
Send the Authentication object as a method parameter:
#Cacheable(value = "resultCache", key="#authentication.name")
public Result getResult(Authentication authentication) {}
Create a custom KeyGenerator and use it in your #Cacheable annotation
public class CustomKeyGenerator implements KeyGenerator {
#Override
public Object generate(Object target, Method method, Object... params) {
return SecurityContextHolder.getContext().getAuthentication().getName();
}
}
#Configuration
#EnableCaching
public class CacheConfiguration {
#Bean("customKeyGenerator")
public KeyGenerator customKeyGenerator() {
return new CustomKeyGenerator();
}
}
#Cacheable(value = "resultCache", keyGenerator="customKeyGenerator")
public Result getResult() {}
Create a bean that provides you the key and references it via SPeL in the key property. I would recommend you going for this approach since it allows you to change the value later more easily.
#Component
public class CacheKeyProvider {
public String getUsernameKey() {
return SecurityContextHolder.getContext().getAuthentication().getName();
}
}
#Cacheable(value = "resultCache", key="#cacheKeyProvider.getUsernameKey()")
public Result getResult() {}
Use the Type SpEL expression
#Cacheable(value = "resultCache", key="T(org.springframework.security.core.context.SecurityContextHolder.getContext()?.authentication?.name)")
public Result getResult() {}
Note that I used the name property from the Principal in the examples. But if you have a custom Principal object you can cast it and return any property you want.
Say we have a FileLoader Interface:
public interface FileLoader {
default String loadFile(String fileId) {
// Default business logic
return "Default implementation for FileLoader. Loading file" + fileId;
}
}
And different implementations for different countries:
public class USAFileLoader implements FileLoader {
#Override
public String loadFile(String fileId) {
// ... Specific business logic for USA
return "USA implementation for FileLoader. Loading file" + fileId;
}
}
public class FRAFileLoader implements FileLoader {
#Override
public String loadFile(String fileId) {
// ... Specific business logic for France
return "France implementation for FileLoader. Loading file" + fileId;
}
}
And we create an endpoint to load files:
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class FileUploadController {
FileLoader fileLoader;
#PostMapping("/load/{fileId}/{countryCode}")
public String loadFile(#PathVariable String fileId, #PathVariable String countryCode) {
fileLoader = ... // Inject the right loader based on countryCode
return fileLoader.loadFile(fileId);
}
}
How can I inject the right FileLoader at runtime for every request, based on countryCode? I've found something in Spring called FactoryBean that apparently may work, but I'm now sure if it's the right tool, or if this is the right way to address this problem. Also, I don't know how injection will behave with requests being proccessed at the same time.
The best thing you can do here using run time polymorphism, add one more abstract method in interface FileLoader for country code
public interface FileLoader {
default String loadFile(String fileId) {
// Default business logic
return "Default implementation for FileLoader. Loading file" + fileId;
}
public abstract String getCountryCode();
}
And then implement it in every implementation class with return the appropriate country code
public class USAFileLoader implements FileLoader {
#Override
public String loadFile(String fileId) {
// ... Specific business logic for USA
return "USA implementation for FileLoader. Loading file" + fileId;
}
public String getCountryCode(){
return "USA";
}
}
And then you can Autowire all beans of type FileLoader into List and call loadFile on appropriate bean
#RestController
public class FileUploadController {
#Autowire
List<FileLoader> fileLoaders;
#PostMapping("/load/{fileId}/{countryCode}")
public String loadFile(#PathVariable String fileId, #PathVariable String countryCode) {
return fileLoaders.stream()
.filter(f->f.getCountryCode().equlas(countryCode))
.findFirst()
.map(loader->loader.loadFile(fileId))
.orElse(()-> FileLoader.super.loadFile(fileId)); //calling interface default method
}
}
You can receive a bean with another way at runtime using ApplicationContext::getBean:
#Autowired
ApplicationContext
#PostMapping("/load/{fileId}/{countryCode}")
public String loadFile(#PathVariable String fileId, #PathVariable String countryCode) {
FileLoader fileloader = (FileLoader) applicationContext.getBean(countryCode);
return fileLoader.loadFile(fileId);
}
However, I'd recommend creating a service layer that aggregates the country-specific implementations and uses a factory pattern. There is nothing bad on such implementation.
Let's say I have this code structure:
public class NotificationService {
public void send(Notification notification) {
// call other services and send the notification
}
}
public class OrderNotification implements Notification {
#Autowired
public TranslationService translationService;
private String orderNumber;
public OrderNotification(String orderNumber) {
this.orderNumber = orderNumber;
}
public String getMessage() {
return translationService.trans('notification.order', new Object[]{orderNumber});
}
}
So, my goal is to use the NotificationService in this way:
notificationService.send(new OrderNotification(orderNumber));
But I know that code above won't work, because of the translationService won't be resolved.
My goal is to pass custom parameters to my Notification classes and being able to use services inside that class. What is the best way to do it in the Spring?
I know that below is not the correct answer to your question. It is however a bad design pattern to combine Entities and Services. An Entity should only contain information about the object and not business logic. A Service contains all the business logic.
You need to separate your Service from your Entity.
OrderNotification looks like a regular entity. The entity should not contain business logic. You need a specific service for the business logic.
public class OrderNotification implements Notification {
private String orderNumber;
public OrderNotification(String orderNumber) {
this.orderNumber = orderNumber;
}
public String getMessage() {
return "Order number: " + orderNumber;
}
//Getter & Setters
...
}
#Service
public class NotificationService {
#Autowired
public TranslationService translationService;
public void send(Notification notification) {
//I do not know what trans accepts, so I assume it can accept Notification
translationService.trans(notification.getMessage());
}
}
If you really need to combine the entity and service - Then I recommend this approach:
#Service
public class Master{
#Autowired
NotificationService notificationService
public void testMethod(){
Notification notification = notificationService.createOrder("order1");
notificationService.send(notification);
}
}
#Service
public class NotificationService {
#Autowired
public TranslationService translationService;
public Notification createOrder(String orderNumber){
return new OrderNotification(orderNumber, translationService);
}
public void send(Notification notification) {
// call other services and send the notification
notification.getMessage();
}
}
public class OrderNotification implements Notification {
private TranslationService translationService;
private String orderNumber;
//I have changed this constructor to accept TranslationService.
public OrderNotification(String orderNumber, TranslationService translationService) {
this.orderNumber = orderNumber;
this.translationService = translationService;
}
public String getMessage() {
return translationService.trans('notification.order', new Object[]{orderNumber});
}
}
You have few options available:
Configure AOP and load time weaving to process Spring annotations on objects created with new keyword. This is explained in the docs 5.8.1. Using AspectJ to dependency inject domain objects with Spring.
Declare OrderNotification as a prototype scoped bean and obtain each instance from the context using BeanFactory.getBean(Class<T> requiredType, Object... args) method.
String orderNumber = "123";
OrderNotificaton = factory.getBean(OrderNotificaton.class, orderNumber);
Drop the #Autowired and use plain constructor injection.
public OrderNotification(TranslationService translationService, String orderNumber) {
this.translationService = Objects.requireNonNull(translationService);
this.orderNumber = Objects.requireNonNull(orderNumber);
}
If you only require simple #Autowired I'd go with option 3. It's the simplest approach and makes writing unit tests easier as you don't have to depend on Spring.
i have the following setup:
#Applicationscoped
#Transactional(txtype.Requires_new)
Public class querybean {
#Inject ExternalSysrltem externalSystemProxy;
Public Handle gethandleByKey(String key) {
return new Handle(/*do external Systems Query, returns an ExternalHandle Object*/)
}
Public static class Handle {
ExternalHandle eh;
/*protected so that User of class cannot Instantiate it otherwise that by getHandleByKey()*/
Protected Handle(ExternalHandle arg) {
This.eh = arg;
}
Public String getHandleInfo() {
Return This.eh.getName() + "/" + this.eh.getState()..;
/*generally wrap the ExternallHandle with businesslogic to hide direct access to the complex ExternalService's Interface*/
}
}
}
Can I get Handle to be a Managed Bean that can be annotated with #Transactional and still create it in the getHandleByKey Method at Runtime by querying the external System?
A static inner class can be a bean according the the spec.
In your example it is not a bean due to its constructor.
As said in comments you could use a producer, but a produced bean can't be intercepted (with #Transaction here)
If you want to keep your pattern, you'll have to create a very complex extension since it should work at low level to ensure interceptor will be activated.
I suggest that you go for something simpler by deporting your ExternalHandle resolution in Handle Bean, allowing you to use a String to construct it.
First create a qualifier with a non binding member to transmit information to your constructor.
#Target({TYPE, METHOD, PARAMETER, FIELD})
#Retention(RUNTIME)
#Documented
#Qualifier
public #interface Keyed {
#Nonbinding
String key();
}
Then create a literal for your annotation to allow creation of an annotation instance with a given key value.
public class KeyedLiteral extends AnnotationLiteral<Keyed> implements Keyed {
private final String key;
public KeyedLiteral(String key) {
this.key = key;
}
#Override
public String key() {
return key;
}
}
Using programmatic lookup and InjectionPoint to transmit your key value. Your code will be like:
#Applicationscoped
#Transactional(txtype.Requires_new)
Public class querybean {
#Inject
#Any
Instance<Handle> handles;
Public Handle gethandleByKey(String key) {
return instances.select(new KeyedLiteral(key)).get()
}
#Dependent
#Transactional
#Keyed("") //enforce the presence of the annotation for the constructor
Public static class Handle {
ExternalHandle eh;
// needed to make the bean proxyable (mandatory for the interceptor bound))
Protected Handle() {}
#Inject
Protected Handle(InjectionPoint ip, ExternalSysrltem externalSystem) {
String key=ip.getAnnotated().getAnnotation(Keyed.class).key();
eh = /*do external Systems Query, returns an ExternalHandle Object from key and externalSystem*/
}
Public String getHandleInfo() {
Return This.eh.getName() + "/" + this.eh.getState()..;
/*generally wrap the ExternallHandle with businesslogic to hide direct access to the complex ExternalService's Interface*/
}
}
}