Spring Framework. Inject bean at runtime based on request parameter - java

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.

Related

Spring Boot cache not caching method call based on dynamic controller parameter

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.

How to autowire a spring bean based on some session info

We are working on a multilingual Spring based web application (not Spring Boot).
Now we are searching for the "spring way" of doing the following.
user starts a http session with some parameters, e.g. a locale "de" and/or a country-code "DE" (the type of parameter isn't really important)
user works with application
at some point the user triggers an action that somewhere deep inside needs a "localized" functionality
Example (java pseudocode):
// service with functionality common for all users
#Service
class CommonService implements ICommonService
{
// how to autowire a service based on some info in the actual HttpSession, eg. CustomServiceUK or CustomServiceDE
#Autowired
private ICustomService customServiceImpl;
#Override
public void doSomeAction(String param)
{
... do some common stuff
customResult = customServiceImpl.calculate(param);
... do some common stuff with custom result
}
}
// custom service implementations
#Service("CustomServiceUK")
class CustomServiceUK implements ICustomService
{
#Override
public String calculate(String value)
{
... execute logic on value for an "uk" user
}
}
#Service("CustomServiceDE")
class CustomServiceDE implements ICustomService
{
#Override
public String calculate(String value)
{
... execute logic on value for an "de" user
}
}
How to inject a custom service based on some info in the actual HttpSession (e.g. CustomServiceUK or CustomServiceDE) into CommonService?
What are our options to solve this issue? Is there something like a dynamic #Qualifier or some #Autowired Spring-Factory thing?
(the service implementation to use must not necessarily depend on the locale of the user but on some other piece of session/request information)
Thanks for your answers.
Actually we end up with the following solution which works for us.
We created an additional implementation of ICustomService with name CustomServiceProxy.
This service has #Primary annotation to tell Spring that this component should be injected when no explicit qualifier is supplied.
The service gets the sessionData and a Map with all Spring managed ICustomService-Components injected (Map-Key = Qualifier of the Component).
Now when some method on CustomServiceProxy gets called, it generates the Map-Key based on the actual sessionData (e.g. language), lookup the ICustomService in the Map and delegates the call to this specific service.
// service with functionality common for all users
#Service
class CommonService implements ICommonService
{
// because of #Primary an instance of CustomServiceProxy will be injected
#Autowired
private ICustomService customServiceImpl;
#Override
public void doSomeAction(String param)
{
... do some common stuff
customResult = customServiceImpl.calculate(param);
... do some common stuff with custom result
}
}
// custom service implementations
#Service
#Primary
class CustomServiceProxy implements ICustomService
{
private CustomData sessionData;
private Map<String, ICustomService> services;
#Autowired
public CustomServiceProxy(CustomData sessionData, Map<String, ICustomService> services)
{
this.sessionData = sessionData;
this.services = services;
}
#Override
public String calculate(String value)
{
String serviceName = "CustomService" + sessionData.getLanguage().toUpperCase();
ICustomService customService = services.get(serviceName);
// handle missing service: throw exception or maybe switch to a default implementation
Objects.requireNonNull(customService, "missing CustomService with name " + serviceName);
return customService.calculate(value);
}
}
#Service("CustomServiceUK")
class CustomServiceUK implements ICustomService
{
#Override
public String calculate(String value)
{
... execute logic on value for an "uk" user
}
}
#Service("CustomServiceDE")
class CustomServiceDE implements ICustomService
{
#Override
public String calculate(String value)
{
... execute logic on value for an "de" user
}
}

Best way to dynamically resolve dependencies in Java Spring?

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.

Can a CDI Bean's method return a managed Bean it created?

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*/
}
}
}

HK2 IterableProvider named method not finding Implementation

I have a problem trying to inject a contract with two services bound to it.
I'm using Jersey, and extending ResourceConfig to configure my app, where I'm binding two different implementations (classes FooImpl1 and FooImpl2) to a same contract (interface Foo), ranking them differently. Each of these implementations is annotated with #Named and its name.
In one of my controllers I want to have access to both implementations, so I inject an IterableProvider<Foo> fooProvider.
If I do not specify anything, the implementation with the highest rank is injected always, which is what I want.
The problem appears when I want a concrete implementation, one of them. When I call fooProvider.named( nameOfTheClass ).get(), is returning me null, but if I iterate over the fooProvider, I can have access to both implementations, so they are injected.
Anybody has an idea of what could I be missing?
Thanks a lot for your help.
Yeah so I'm not sure why it doesn't work with the #Named annotation value, as that's what's stated int the javadoc, but without the need for any annotations, we can configure the name when we do our bindings. We can do so with the named method.
register(new AbstractBinder(){
#Override
public void configure() {
bind(Foo1Impl.class).named("foo1").to(Foo.class);
bind(Foo2Impl.class).named("foo2").to(Foo.class);
}
});
UPDATE
So the above solution has been tested. If you are having problems still, post a complete runnable example that demonstrates it not working, like below (which is working)
Interface and Implementations
public interface Greeter {
String getGreeting(String name);
}
public class EnglishGreeter implements Greeter {
#Override
public String getGreeting(String name) {
return "Hello " + name + "!";
}
}
public class SpanishGreeter implements Greeter {
#Override
public String getGreeting(String name) {
return "Hola " + name + "!";
}
}
Resource
#Path("greeting")
public class GreetingResource {
#Inject
private IterableProvider<Greeter> greeters;
#GET
public Response getResponse(#QueryParam("lang") String lang,
#QueryParam("name") String name) throws Exception {
Greeter greeter = greeters.named(lang).get();
String message = greeter.getGreeting(name);
return Response.ok(message).build();
}
}
Binding. I did it in a Feature, but in a ResourceConfig, it's all the same.
#Provider
public class GreetingFeature implements Feature {
#Override
public boolean configure(FeatureContext context) {
context.register(new AbstractBinder(){
#Override
public void configure() {
bind(EnglishGreeter.class).named("english")
.to(Greeter.class).in(Singleton.class);
bind(SpanishGreeter.class).named("spanish")
.to(Greeter.class).in(Singleton.class);
}
});
return true;
}
}
Result

Categories