Hi i am trying to study named injection in Dagger2
Here are my Java classes but none of them seems to be working.
What i want is that based on #Named annotation i wish to get different objects.
public interface Server {
public void start();
public void stop();
public String request(String request);
}
public abstract class AbstractServer implements Server {
private boolean started;
#Override
public void start() {
started = true;
}
#Override
public void stop() {
if (!started) {
throw new IllegalStateException("Server was not started");
}
}
}
public class AudioServer extends AbstractServer{
#Override
public String request(String request) {
return "Response from Audio server: " + request;
}
}
public class VideoServer extends AbstractServer {
#Override
public String request(String request) {
return "Response from Video server: " + request;
}
}
#Module
public class ServerModule {
public ServerModule() {
}
#Provides
#Named("audio")
#Singleton
AudioServer provideAudioServer() {
return new AudioServer();
}
#Provides
#Named("video")
#Singleton
VideoServer provideVideoServer() {
return new VideoServer();
}
}
Please not ServerComponent.java is not compiling
#Singleton
#Component(modules = {ServerModule.class})
public interface ServerComponent {
AudioServer provideAudioServer();
VideoServer provideVideoServer();
void inject(TestInject inject);
}
public class TestInject {
private static final Logger logger = Logger.getLogger(TestInject.class.getSimpleName());
#Inject
#Named("audio")
Server audioServer;
public TestInject() {
// ServerComponent component = DaggerServerComponent.builder()
// .build();
// component.inject(this);
}
public void test() {
String serverResponse = null;
if (audioServer != null) {
serverResponse = audioServer.request("game.mp3");
logger.warning(serverResponse);
} else {
serverResponse = "Failure";
logger.info(serverResponse);
}
}
public static void main(String[] args) {
TestInject inject = new TestInject();
inject.test();
}
}
EDITED Please see answer in TestInject.java and ServerComponent.java
public interface Server {
public void start();
public void stop();
public String request(String request);
}
public abstract class AbstractServer implements Server {
private boolean started;
#Override
public void start() {
started = true;
}
#Override
public void stop() {
if (!started) {
throw new IllegalStateException("Server was not started");
}
}
}
public class AudioServer extends AbstractServer{
#Override
public String request(String request) {
return "Response from Audio server: " + request;
}
}
public class VideoServer extends AbstractServer {
#Override
public String request(String request) {
return "Response from Video server: " + request;
}
}
#Module
public class ServerModule {
public ServerModule() {
}
#Provides
#Named("audio")
#Singleton
AudioServer provideAudioServer() {
return new AudioServer();
}
#Provides
#Named("video")
#Singleton
VideoServer provideVideoServer() {
return new VideoServer();
}
}
Please not ServerComponent.java is not compiling
#Singleton
#Component(modules = {ServerModule.class})
public interface ServerComponent {
#Named("audio")
Server provideAudioServer();
#Named("video")
Server provideVideoServer();
void inject(TestInject inject);
}
public class TestInject {
private static final Logger logger = Logger.getLogger(TestInject.class.getSimpleName());
#Inject
#Named("audio")
Server audioServer;
#Inject
#Named("video")
Server videoServer;
public TestInject() {
ServerComponent component = DaggerServerComponent.builder()
.build();
component.inject(this);
}
public void testAudioServer() {
String serverResponse = null;
if (audioServer != null) {
serverResponse = audioServer.request("game.mp3");
logger.warning(serverResponse);
} else {
serverResponse = "audio server Failure";
logger.info(serverResponse);
}
}
public void testVideoServer() {
String serverResponse = null;
if (videoServer != null) {
serverResponse = videoServer.request("movie.mp4");
logger.warning(serverResponse);
} else {
serverResponse = "Video server Failure";
logger.info(serverResponse);
}
}
public static void main(String[] args) {
TestInject inject = new TestInject();
inject.testAudioServer();
inject.testVideoServer();
}
}
Your main problem seems to be related to the fact that you expect in the class TestInject a Server named audio while your provider returns AudioServer so dagger cannot satisfy your dependency.
Indeed don't forget that the annotation #Named is used to distinguish 2 objects of the same type, in other words you can annotate with #Named("audio") different providers as long as they don't return the same type. The object produced will then be identified by its type and its name.
So for example here is one way to fix you problem:
The class TestInject:
public class TestInject {
...
public TestInject() {
// Needed to inject your dependencies
ServerComponent component = DaggerServerComponent.builder()
.build();
component.inject(this);
}
...
}
The class ServerComponent
#Singleton
#Component(modules = ServerModule.class)
public interface ServerComponent {
void inject(TestInject inject);
}
The class ServerModule
#Module
public class ServerModule {
#Provides
#Named("audio")
#Singleton
public Server provideAudioServer() {
return new AudioServer();
}
#Provides
#Named("video")
#Singleton
public Server provideVideoServer() {
return new VideoServer();
}
}
Even with your question update, your module should be what I propose abose otherwise it won't compile for the same reason described previously.
Related
I've a MainHandler class :
#Component
class MainHandler {
//inject this
private Handler handler;
#Autowired
public MainHandler(Handler handler){
this.handler = handler;
}
public void action(String message){
//watch photo
if (message.equals("photo")){
handler.handle();
}
if(message.equals("audio")){
//play music
handler.handle();
}
if(message.equals("video")){
//play video
handler.handle();
}
}
And following other handlers with interface.
Can I inject dependencies with Spring Boot by only interface type handler?
#Component
public interface Handler {
void handle();
}
#Component
class PhotoHandler implements Handler {
public void handle(){
System.out.println("Featuring photo...");
}
}
#Component
class VideoHandler implements Handler {
public void handle(){
System.out.println("Playing video...");
}
}
#Component
class AudioHandler implements Handler {
public void handle(){
System.out.println("Playing music...");
}
}
Or I want to try something like this below. Is it possible ?
class MainHandler {
private VideoHandler videoHandler;
private AudioHandler audioHandler;
private PhotoHandler photoHandler;
#Autowired
public MainHandler(VideoHandler videoHandler,
AudioHandler audioHandler,
PhotoHandler photoHandler) {
this.videoHandler = videoHandler;
this.audioHandler = audioHandler;
this.photoHandler = photoHandler;
}
public void action(String message){
//watch photo
if (message.equals("photo")){
photoHandler.handle();
}
if(message.equals("audio")){
//play music
audioHandler.handle();
}
if(message.equals("video")){
//play video
videoHandler.handle();
}
}
}
So, type of handler depends on user's message. I don't know how Spring can choose which handler gonna be used in this context. Any solution?
There can be multiple solution to this case.
Option #1
You can tweak a design of your handler a bit.
For instance you can introduce a method
boolean canHandle(String message);
so each handler can answer whether passed message can be handled or not.
Then you can inject a list of all handlers into your MainHandler.
private List<Handler> handlers;
Now having that list you can call each handler by message:
public void action(String message) {
handlers.stream()
.filter(h -> h.canHandle(message))
.forEach(handler -> handler.handle());
}
Full example:
#SpringBootApplication
public class SO62370917 {
public static void main(String[] args) {
SpringApplication.run(SO62370917.class, args);
}
#Component
static class MainHandler {
private final List<Handler> handlers;
MainHandler(List<Handler> handlers) {
this.handlers = handlers;
}
public void action(String message) {
handlers.stream()
.filter(h -> h.canHandle(message))
.forEach(Handler::handle);
}
}
#Bean
CommandLineRunner cmd(MainHandler mainHandler) {
return args -> {
mainHandler.action("video");
mainHandler.action("audio");
mainHandler.action("photo");
};
}
interface Handler {
void handle();
boolean canHandle(String message);
}
#Component
class PhotoHandler implements Handler {
public void handle(){
System.out.println("Featuring photo...");
}
#Override
public boolean canHandle(String message) {
return "photo".equals(message);
}
}
#Component
class VideoHandler implements Handler {
public void handle(){
System.out.println("Playing video...");
}
#Override
public boolean canHandle(String message) {
return "video".equals(message);
}
}
#Component
class AudioHandler implements Handler {
public void handle(){
System.out.println("Playing music...");
}
#Override
public boolean canHandle(String message) {
return "audio".equals(message);
}
}
}
Option #2
Use qualifiers.
You can name your handlers however you like and then inject a Map<String, Handler> into your mainHandler. The key would be a bean name and the value - the actual handler. Spring will automatically take care of this.
#SpringBootApplication
public class SO62370917 {
public static void main(String[] args) {
SpringApplication.run(SO62370917.class, args);
}
#Component
static class MainHandler {
private final Map<String, Handler> handlers;
MainHandler(Map<String, Handler> handlers) {
this.handlers = handlers;
}
public void action(String message) {
if (handlers.containsKey(message)) {
handlers.get(message).handle();
}
}
}
#Bean
CommandLineRunner cmd(MainHandler mainHandler) {
return args -> {
mainHandler.action("video");
mainHandler.action("audio");
mainHandler.action("photo");
};
}
interface Handler {
void handle();
}
#Component("photo")
class PhotoHandler implements Handler {
public void handle() {
System.out.println("Featuring photo...");
}
}
#Component("video")
class VideoHandler implements Handler {
public void handle() {
System.out.println("Playing video...");
}
}
#Component("audio")
class AudioHandler implements Handler {
public void handle() {
System.out.println("Playing music...");
}
}
}
The output:
2020-06-14 13:06:47.140 INFO 29447 --- [ main] com.example.demo.SO62370917 : Started SO62370917 in 1.356 seconds (JVM running for 1.795)
Playing video...
Playing music...
Featuring photo...
There are two simple ways in which you can approach :
Recommended : You can use #Qualifier to inject the desired particular bean.
For example
#Component
class MainHandler {
#Autowired
#Qualifier("videoHandler") // example
private Handler handler;
public void action(){
handler.message(); // this will print playing video...
}
}
You can inject the ApplicationContext.
For example :
#Component
class MainHandler {
#Autowired
private ApplicationContext context;
public void action(String message){
//watch photo
if (message.equals("photo")){
((PhotoHandler) context.getBean(PhotoHandler.class)).handle();
}
if(message.equals("audio")){
//play music
((AudioHandler) context.getBean(AudioHandler.class)).handle();
}
if(message.equals("video")){
//play video
((VideoHandler) context.getBean(VideoHandler.class)).handle();
}
}
}
I've created a custom implementation of Call<T>, here is the custom class without the custom code, just the forward code for you to see.
public class CachedCall<T> implements Call<T> {
private final Call<T> delegate;
public CachedCall(Call<T> delegate) {
this.delegate = delegate;
}
#Override
public Response<T> execute() throws IOException {
return delegate.execute();
}
#Override
public void enqueue(Callback<T> callback) {
delegate.enqueue(callback);
}
public void enqueueWithCache(final CachedCallback<T> callback) {
delegate.enqueue(callback);
}
#Override
public boolean isExecuted() {
return delegate.isExecuted();
}
#Override
public void cancel() {
delegate.cancel();
}
#Override
public boolean isCanceled() {
return delegate.isCanceled();
}
#Override
public Call<T> clone() {
return new CachedCall<>(delegate.clone());
}
#Override
public Request request() {
return delegate.request();
}
}
And then in my ApiService, I used this custom implementation on some of my call, and the default one on some other, exemple:
public interface APIService {
#GET("categories")
Call<List<Categorie>> categories(#Query("tag") String tag);
#GET("categories/{categorie}/quotes")
CachedCall<List<Gif>> gifs(#Path("categorie") String categorie);
When methods with the custom one are called, I got a crash:
Caused by: java.lang.IllegalArgumentException: Could not locate call adapter for CustomClass.
Tried:
* retrofit2.adapter.rxjava.RxJavaCallAdapterFactory
* retrofit2.ExecutorCallAdapterFactory
at retrofit2.Retrofit.nextCallAdapter(Retrofit.java:237)
at retrofit2.Retrofit.callAdapter(Retrofit.java:201)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:232)
... 21 more
Do I need to register my custom implementation with Retrofit somewhere?
I've solved my own issue.
You need to create and register your own CallAdapter.Factory:
public class CachedCallAdapterFactory extends CallAdapter.Factory {
final Executor callbackExecutor;
public CachedCallAdapterFactory(Executor callbackExecutor) {
this.callbackExecutor = callbackExecutor;
}
#Override
public CallAdapter<Call<?>> get(final Type returnType, final Annotation[] annotations, final Retrofit retrofit) {
if (getRawType(returnType) != CachedCall.class) {
return null;
}
final Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);
return new CallAdapter<Call<?>>() {
#Override public Type responseType() {
return responseType;
}
#Override public <R> Call<R> adapt(Call<R> call) {
return new CachedCall<>(callbackExecutor, call, responseType, retrofit, annotations);
}
};
}
}
And then register it when you create your Retrofit instance:
retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(URL)
.addCallAdapterFactory(new CachedCallAdapterFactory(new DefaultExecutor()))
.build();
Your DefaultExecutor just need to run its Runnable
private class DefaultExecutor implements Executor {
#Override
public void execute(#NonNull Runnable runnable) {
runnable.run();
}
}
I have a class RabbitQueue which basically acts like a queue and implements my Pollable interface.
I also have a class SaveToDatabaseStrategy which implements my DataProcessingStrategy interface. This is designed following the strategy-pattern.
Now, my class InputHandler which implements my interface InputListener, contains an instance of the Pollable interface and one of the DataProcessingStrategy interface.
However, I don't want to set the Generic type (String) when I declare these two fields, since the Generic type depends on the implementation of this interface which is given later on.
How would you design this?
public interface Pollable<T> {
T poll();
}
public class RabbitQueue implements Pollable<String> {
// code..
}
public interface DataProcessingStrategy<T> {
void processData(T t);
}
public class SaveToDatabaseStrategy<T> implements DataProcessingStrategy<T> {
private Repository<T, ?> repo;
public SaveToDatabaseStrategy(Repository<T, ?> repo) {
this.repo = repo;
}
#Override
public void processData(T data) {
repo.create(data);
System.out.printf("Received data of type %s: %s\n", data.getClass().getSimpleName(), data);
}
}
public interface InputListener<T> {
void inputReceived();
void inputReceived(T t);
}
public class InputHandler implements InputListener<String> {
private Pollable<String> queue;
private DataProcessingStrategy<String> strategy;
public InputHandler(String host, String queueName) throws IOException, TimeoutException {
queue = new RabbitQueue(host, queueName, this);
}
public void setStrategy(DataProcessingStrategy strategy) {
this.strategy = strategy;
}
#Override
public void inputReceived() {
System.out.println("Input received!");
strategy.processData(queue.poll());
}
#Override
public void inputReceived(String s) {
System.out.println("Input received: " + s + "!");
System.out.println("> " + queue.poll());
}
}
You could add a type parameter to the InputHandler class.
public class InputHandler<T> implements InputListener<T> {
private Pollable<T> queue;
private DataProcessingStrategy<T> strategy;
public InputHandler(String host, String queueName) throws IOException, TimeoutException {
queue = new RabbitQueue(host, queueName, this);
}
public void setStrategy(DataProcessingStrategy strategy) {
this.strategy = strategy;
}
#Override
public void inputReceived() {
System.out.println("Input received!");
strategy.processData(queue.poll());
}
#Override
public void inputReceived(String s) {
System.out.println("Input received: " + s + "!");
System.out.println("> " + queue.poll().toString());
}
}
Then create a new object like
new InputHandler<String>(host, queueName)
How should I do the ValueFactoryProvider binding in order to have two custom injection annotations coexist in Jersey 2? Below I have included an example of my current approach and as you can see the Hello annotation injection "hides" the SmallTalk annotation injection.
Hello annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PARAMETER})
public #interface Hello {
}
SmallTalk annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PARAMETER})
public #interface SmallTalk {
}
Hello annotation resolver:
#Singleton
public class HelloResolver {
public static class HelloInjectionResolver extends ParamInjectionResolver<Hello> {
public HelloInjectionResolver() {
super(HelloValueFactoryProvider.class);
}
}
#Singleton
public static class HelloValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
public HelloValueFactoryProvider(final MultivaluedParameterExtractorProvider extractorProvider,
final ServiceLocator injector) {
super(extractorProvider, injector, UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(final Parameter parameter) {
final Class<?> classType = parameter.getRawType();
if (classType == null || (!classType.equals(String.class))) return null;
return new AbstractContainerRequestValueFactory<String>() {
#Override
public String provide() {
return "Hello!";
}
};
}
}
public static class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(HelloValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
bind(HelloInjectionResolver.class).to(
new TypeLiteral<InjectionResolver<Hello>>() {
}
).in(Singleton.class);
}
}
}
SmallTalk annotation resolver:
#Singleton
public class SmallTalkResolver {
public static class SmallTalkInjectionResolver extends ParamInjectionResolver<SmallTalk> {
public SmallTalkInjectionResolver() {
super(SmallTalkValueFactoryProvider.class);
}
}
#Singleton
public static class SmallTalkValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
public SmallTalkValueFactoryProvider(final MultivaluedParameterExtractorProvider extractorProvider,
final ServiceLocator injector) {
super(extractorProvider, injector, UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(final Parameter parameter) {
final Class<?> classType = parameter.getRawType();
if (classType == null || (!classType.equals(String.class))) return null;
return new AbstractContainerRequestValueFactory<String>() {
#Override
public String provide() {
return "Nice weather.";
}
};
}
}
public static class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(SmallTalkValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
bind(SmallTalkInjectionResolver.class).to(
new TypeLiteral<InjectionResolver<SmallTalk>>() {
}
).in(Singleton.class);
}
}
}
Resource configuration:
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(new HelloResolver.Binder());
register(new SmallTalkResolver.Binder());
registerClasses(HelloResource.class);
}
}
Resource using both injection annotations:
#Path("/")
public class HelloResource {
#GET
#Path("hello")
#Produces("application/json")
public String hello(#Hello final String hello, #SmallTalk final String smallTalk) {
return hello + " " + smallTalk;
}
}
Result when requesting the resource - should have been "Hello! Nice weather.":
Found a solution! I added
if (parameter.getAnnotation(Hello.class) == null) return null;
and
if (parameter.getAnnotation(SmallTalk.class) == null) return null;
to the createValueFactory method of the two value factory providers.
I have managed to successfully implement a custom injection annotation with target PARAMETER. I do not understand how I make my the annotation support target METHOD as well though?
Here is my sample code:
Hello annotation:
#Retention(RUNTIME)
#Target({METHOD, PARAMETER})
public #interface Hello {
}
Hello annotation resolver:
#Singleton
public class HelloResolver {
public static class HelloInjectionResolver extends ParamInjectionResolver<Hello> {
public HelloInjectionResolver() {
super(HelloValueFactoryProvider.class);
}
}
#Singleton
public static class HelloValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
public HelloValueFactoryProvider(final MultivaluedParameterExtractorProvider extractorProvider,
final ServiceLocator injector) {
super(extractorProvider, injector, UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(final Parameter parameter) {
if (!String.class.equals(parameter.getRawType())) return null;
if (parameter.getAnnotation(Hello.class) == null) return null;
return new AbstractContainerRequestValueFactory<String>() {
#Override
public String provide() {
final DateTime now = DateTime.now();
if (22 < now.getHourOfDay() || now.getHourOfDay() < 6) {
throw new WebApplicationException(FORBIDDEN);
} else {
return format("Hello, it is %s o'clock so I am awake! :)", forPattern("HH:mm").print(now));
}
}
};
}
}
public static class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(HelloValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
bind(HelloInjectionResolver.class).to(
new TypeLiteral<InjectionResolver<Hello>>() {
}
).in(Singleton.class);
}
}
}
Hello resources:
#Path("hello")
public class HelloResource {
#GET
#Path("method")
#Produces(APPLICATION_JSON)
#Hello
public String method() {
return "Hello!";
}
#GET
#Path("param")
#Produces(APPLICATION_JSON)
public String param(#Hello final String hello) {
return hello;
}
}
When I hit
http://localhost:8080/hello/method
I get a Hello! back no matter if the hour is within the forbidden interval.
I am not sure this will work, but you could try this:
public static class HelloInjectionResolver extends ParamInjectionResolver<Hello> {
public HelloInjectionResolver() {
super(HelloValueFactoryProvider.class);
}
public boolean isMethodParameterIndicator() {
return true;
}
}
Warning: I have not tried this myself but in theory that should allow your resolver to work as a parameter in the method.