Dagger 2 runtime parameter in a chain of dependencies - java

Decided to give Dagger 2 a try while reorganizing a certain project, however there are still some things not entirely clear to me. What would be the best approach to supporting a chain of dependencies that require a runtime parameter at it's root?
A quick simplified example: let's say there's a base class called "SpecificDatabaseService", which requires a runtime parameter like "login". Then there's a "SampleDao" class, that requires a DatabaseService [amongst some other dependencies]. Finally 2 classes named "FirstProcessor" and "SecondProcessor" would both require a SampleDao, like so:
public class SpecificDatabaseService implements DatabaseService {
public SpecificDatabaseService(String login) { // setup }
}
public class SampleDao {
public SampleDao(DatabaseService service) { // setup }
}
public class FirstProcessor {
public FirstProcessor(SampleDao dao) { // setup }
}
public class SecondProcessor {
public SecondProcessor(SampleDao dao) { // setup }
}
Next thing I'm creating SpecificDatabaseServiceModule and a DatabaseServiceComponent in order to do some additional stuff to DatabaseService later.
#Module
interface SpecificDatabaseServiceModule {
#Binds
DatabaseService bindDatabaseService(SpecificDatabaseService databaseService);
}
#Singleton
#Component(modules = { SpecificDatabaseServiceModule.class })
interface DatabaseServiceComponent {
DatabaseService getDatabaseService();
#Component.Factory
interface Factory {
DatabaseServiceComponent create(#BindsInstance #Named("login") String login);
}
}
Now when I need to create a FirstProcessor, someone has to provide a "login" for FirstProcessorComponent once again (same as with SecondProcessor). And if there's a class, which uses a FirstProcessor as a dependency, then again it needs a "login" to be provided. As the chain grows, it becomes more and more difficult to use Dagger.
Is there an easier way or approach of passing the "login" value once and just sharing module/component. For instance FirstProcessorComponent depends on DatabaseServiceComponent and if Dagger know how to construct DatabaseService, it's able to construct FirstProcessor as well. Hasn't been unable to get it working up till now.

Related

Dagger 2 module "interfaces"?

I'm quite new to Dagger 2 and I'm looking for a way to have a "configurable component".
Essentially this is what I want to achieve:
public interface ErrorReporter{
...
}
public class ConsoleErrorReporter implements ErrorReporter{
... // Print to System.err
}
public class DialogErrorReporter implements ErrorReporter{
... // Show modal dialog to user
}
#Module
public interface UIModule{
#Provides
ErrorReporter provideErrorReporter();
}
#Module
public class ConsoleUIModule{
#Override
#Provides
ErrorReporter provideErrorReporter(ConsoleErrorReporter cer){
return cer;
}
}
#Module
public class GraphicalUIModule{
#Override
#Provides
ErrorReporter provideErrorReporter(DialogErrorReporter der){
return der;
}
}
#Component(modules = {UIModule.class, OtherUniversalModule.class})
public interface ApplicationComponent{
ErrorReporter errorReporter();
}
void main(String[] args){
final UIModule uiModule;
if(args.length == 1 && args[0].equals("gui")){
uiModule = new GraphicalUIModule();
}else{
uiModule = new ConsoleUIModule();
}
DaggerApplicationComponentdac = DaggerApplicationComponent.builder()
.uiModule(uiModule).build();
dac.errorReporter().showError("Hello world!");
}
The above fails with #Provides methods cannot be abstract unfortunately both for interfaces and abstract classes. I have also tried non-abstract base class with concrete implementations that return null and then overriding these in sub classes. However this also fails with #Provides methods may not override another method.
In short I want to define a contract for a module and choose different modules during runtime. I know that Dagger 2 compile time validates the object graph, but if I have a well defined contract that should still be possible right? Or am I forced to create two different components with duplicate code for both user interfaces? Are there other solutions that I'm missing?
I don't think using a module this way is possible, because...
Suppose you have the following two constructors for your classes
#Inject ConsoleErrorReporter(Console console);
#Inject DialogErrorReporter(Graphics graphics);
This would mean that ConsoleUIModule would require a Console and DialogErrorReporter would require a Graphics object to create their respecitve implementation of ErrorReporter.
But if dagger only knows about UIModule because you use the interface there...well...it could not provide the dependencies for either, because it doesn't know about any of them.
And if you don't know the dependencies building a dependency graph at compile time won't work. Also this won't compile even without dagger because provideErrorReporter(ConsoleErrorReporter cer) does not override provideErrorReporter().
What you can and should do is use different components. Because a component is the thing that actually knows how to provide things. And a component already is an interface—and that's what you wanted, right?
You can have component dependencies, where one component depends on another. E.g. have a DependentComponent that provides a NeedsErrorReporter that needs an implementation of ErrorReporter. We also depend on an interface, rather than the actual component (and that's what you wanted after all, right?)
You then implement the interface by actual components, and each component has its respective modules (and maybe even further dependencies). In the end you have a component that you can switch and will provide different versions of an object, properly encapsulated!
#Component(dependencies = UIComponent.class) /* <- an interface! */
interface DependentComponent {
NeedsErrorReporter needsErrorReporter();
}
class NeedsErrorReporter {
#Inject public NeedsErrorReporter(ErrorReporter reporter) { }
}
/* this is _not_ a component, but a simple interface! */
interface UIComponent {
ErrorReporter errorReporter();
}
/* Console */
#Component(modules = ConsoleUIModule.class)
interface ConsoleUIComponent extends UIComponent { }
#Module interface ConsoleUIModule {
#Binds ErrorReporter provideErrorReporter(ConsoleErrorReporter cer);
}
/* Graphic */
#Component(modules = GraphicalUIModule.class)
interface GraphicUIComponent extends UIComponent { }
#Module interface GraphicalUIModule {
#Binds ErrorReporter provideErrorReporter(DialogErrorReporter der);
}
/* The error reporter variants */
interface ErrorReporter {
}
class ConsoleErrorReporter implements ErrorReporter {
#Inject public ConsoleErrorReporter() { }
}
class DialogErrorReporter implements ErrorReporter {
#Inject public DialogErrorReporter() { }
}
Now all you have to do is pick the right component ;)
DaggerDependentComponent.builder().uIComponent(DaggerConsoleUIComponent.create()).build();
// or
DaggerDependentComponent.builder().uIComponent(DaggerGraphicUIComponent.create()).build();

Mocking an injected field in unit tests

I have a Presenter class which uses a field injected through Dagger, it looks something like this:
public class RssListPresenter {
#Inject
RssService rssService; // <-- injected field
public RssListPresenter() {
setupDI();
}
private void setupDI() {
DaggerNetworkComponent.builder()
.networkModule(new NetworkModule())
.build()
.inject(this);
}
public void loadItems() {
Rss rss = rssService.getRssFeed()
// ....
}
}
Everything works fine. Now, I would like to unit test the RssListPresenter class. The question is how do I provide a mock RssService to the presenter?
Ofcourse I can add a new method setRssService(RssService rssService) to the presenter and use it to provide the mock from unit tests, but adding this method just for unit tests does not feel right. What would be the correct way to handle this?
For completeness here are the module and component declarations:
#Singleton
#Component(modules = NetworkModule.class)
public interface NetworkComponent {
void inject(RssListPresenter presenter);
}
#Module
public class NetworkModule {
#Provides
Retrofit provideRetrofit() {
// ...
}
#Provides
#Singleton
RssService providePcWorldRssService(Retrofit retrofit) {
return retrofit.create(RssService.class);
}
}
Property injection is like that is not so easy to test. In this case, Constructor injection is much better. Refactor your constructor to look like this:
private final RssService rssService;
#Inject
public RssListPresenter(RssService rssService) {
this.rssService = rssService;
}
Now you can test it easily:
//mocks
RssService mockRssService;
//system under test
RssListPresenter rssListPresenter;
#Before
public void setup() {
mockRssService = Mockito.mock(RssService.class);
rssListPresenter = new RssListPresenter(mockRssService);
}
You probably shouldn't be using DaggerNetworkComponent.inject(this) inside RssListPresenter. Instead you should be configuring dagger so that when it injects members into your top-level classes (Activity, Fragment, Service) it can access the object graph and create an instance of your RssPresenter.
Why only put injectors in Activity and Service and not in something like RssListPresenter? These are classes that are instantiated by the Android system and so you have no choice but to use injectors.
To clarify, Activity, Fragment etc. are ideal injection targets. RssListPresenter etc. are injected dependencies. You need to configure the dependency injection framework, dagger, so that it can provide the correct dependencies to inject into the injection targets.
So you will also need to write a #Provides method for RssListPresenter
#Provides provideRssListPresenter(RssService rssService) {
return new RssListPresenteR(rssService);
}
Your class violates some of the S.O.L.I.D principles, and this makes it very difficult to unit test it. Your class for sure violats the SRP(Single Responsability Principle) as it has more than one reason to change. There is also a possible Dependency Inversion violation.
I advice to rethink the model of the classes, so that each of them performs a specific function, and has one reason to change.

Spring AOP doesn`t work with class comprising #Transactional method

I develop web-app, with a need to store heavy-weight files and use Apache FTP Server for this purpose. When a new user register his account, the folder named as his username must be created on remote server. To establish connection, before UserCreatingServiceImpl.createUser() method will be performed, I use Spring AOP:
#Component
#Aspect
public class RemoteServerConnectionEstablisher {
private static boolean connectionEstablished = false;
#Autowired
private RemoteServerConnector serverConnector;
#Pointcut("execution(* com.storehouse.business.services.impl.UserCreatingServiceImpl.createUser(..)) ||"
+ " execution (* com.storehouse.business.services.impl.ItemCreatingServiceImpl.createItem(..)) ||"
+ "execution (* com.storehouse.business.services.impl.FileDownloadingServiceImpl.downloadFile(..))")
public void pointcut() {
}
#Before("pointcut()")
public void establishConnection(JoinPoint jp) {
if (!connectionEstablished) {
if (serverConnector.connectToRemoteServer()) {
connectionEstablished = true;
}
}
}
#After("pointcut()")
public void disconnect(JoinPoint jp) {
if (connectionEstablished) {
if (serverConnector.disconnect()) {
connectionEstablished = false;
}
}
}
}
Here is the service class with createUser() method:
#Service
public class UserCreatingServiceImpl implements UserCreatingService {
#Autowired
private UserService userService;
#Autowired
private FTPClient ftpClient;
#Override
public boolean createUser(UserDto userDto) {
try {
ftpClient.makeDirectory(userDto.getUsername());
UserMapper userMapper = new UserMapper();
userService.persistUser(userMapper.dtoToEntity(userDto));
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
#Transactional
public void checkIfUsernameExist(String username) {
}
}
Everything had worked fine, until I added #Transactional method to service -class:
#Transactional
public void checkIfUsernameExist(String username) {
}
Now methods of Aspect-class don`t invoke. Could you explain the reason. Thanks in advance for help.
The issue lies in your pointcut expression.
execution(* com.storehouse.business.services.impl.UserCreatingServiceImpl.createUser(..))
You are intercepting the execution of the createUser method on the UserCreatingServiceImpl. This works when you don't add something that creates a proxy for your implementation. As you will be directly calling this method.
However as you have added #Transactional a proxy is created and the method call is now done on the UserCreatingService as that is the interface that is left due do the created proxy. By default spring uses JDK Dynamic proxies, which are interface based.
To solve do one of these things
Rewrite your pointcut to operate on the interface instead of implementing class
Use class based instead of interface based proxies
Use compile or load time weaving
Rewrite pointcut
Use execution(* com.storehouse.business.services.UserCreatingService+.createUser(..)) instead of what you have now. This will use the interface instead of the concrete class.
Use class based proxies
Assuming you use #EnableAspectJAutoProxy add proxyTargetClass=true to it, leading to #EnableAspectJAutoProxy(proxyTargetClass=true). This will create class based proxies and should make the original pointcut work.
Use compile or load time weaving
Instead of using proxies you also could change the way your code is build/loaded. For compile time weaving you would have to modify your build to use the AspectJ compiler to apply the aspects at compilation time, then you don't need the proxies any more.
Or instead of #EnableAspectJAutoProxy you could add #EnableLoadTimeWeaving which, if you use a recent servlet container, would weave the aspects as soon as a class is being loaded.
Both would eliminate the need for proxies (at least for this part) and would make the original pointcuts work.

Dagger 2 on Android #Singleton annotated class not being injected

I am currently trying to integrate Dagger 2 into an Android application. My project setup is as follows:
library
app (depends on library)
In my library project I defined a class that I'll later inject into other classes that need it (Activities and regular classes) in the library as well as the app project.
#Singleton
public class MyManager{
#Inject
public MyManager(){
//Do some initializing
}
}
Now - for instance in my Fragments or Activities or regular classes I'd inject the above Singleton as follows:
public class SomeClass{
#Inject
MyManager myManager;
}
Or so I thought, because in practice myManager is always null. And apparently it's constructor is never called either, so I guess I must be missing something configuration-wise? Or maybe I misunderstood the documentation and it's not meant to work this way at all? The purpose of MyManager class is to be an application-wide accessible component-accumulating entity - that's why I went for the #Singleton.
UPDATE
To avoid confusion: I mentioned my having components somewhere in a comment I think - this refers to components in the sense of "component based design" and has nothing to do with dagger. The dagger-based code I have is all listed above - there is nothing else related to dagger in my code.
When I started adding #Component I had some compiler issues, because my dagger2 was not setup properly - check out this really helpful thread on how to setup dagger2 correctly: https://stackoverflow.com/a/29943394/1041533
UPDATE 2
Here is my updated code, based on G. Lombard's suggestions - I changed the code as follows - the original Singleton is in the library project:
#Singleton
public class MyManager{
#Inject
public MyManager(){
//Do some initializing
}
}
Also in the library project is the bootstrap class:
#Singleton
#Component
public interface Bootstrap {
void initialize(Activity activity);
}
Then I use the above Bootstrap class in my activity (in my concrete app, NOT in the library project! I do however also have Classes/Activities in the library that'll access Bootstrap to inject MyManager):
public class MyActivity extends Activity{
#Inject
MyManager manager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//DONT DO THIS !!! AS EXPLAINED BY EpicPandaForce
DaggerBootstrap.create().initialize(this);
}
}
But even after this line:
DaggerBootstrap.create().initialize(this);
the manager instance is still null, i.e. not injected.
I just found this: https://stackoverflow.com/a/29326023/1041533
Which if I don't misread, implies I need to specify every single class in the Bootstrap class that will use #Inject to have stuff injected. Sadly - this is not an option, as I have more than 40 classes and activities for which I'd have to do that.
Meaning my Bootstrap interface apparently would have to look something like this:
#Singleton
#Component
public interface Bootstrap {
void initialize(ActivityA activity);
void initialize(ActivityB activity);
void initialize(ActivityC activity);
void initialize(ActivityD activity);
void initialize(ActivityE activity);
void initialize(ActivityF activity);
//and so on and so forth...
}
If the above is true, that would not be worth it for my use case. Plus: Seems there is no compile-time check, if I forgot to specify one of my 40+ classes here? It just wont work - i.e. crash the app at runtime.
You're making a mistake in that you are using
DaggerBootstrap.create().initialize(this);
in your Activity, as scopes are not shared across multiple component instances. What I recommend is using a custom application class
public class CustomApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Bootstrap.INSTANCE.setup();
}
}
#Component
#Singleton
public interface _Bootstrap {
void initialize(ActivityA activityA);
//void initiali...
}
public enum Bootstrap {
INSTANCE;
private _Bootstrap bootstrap;
void setup() {
bootstrap = Dagger_Bootstrap.create();
}
public _Bootstrap getBootstrap() {
return bootstrap;
}
}
Then you could call it as
Bootstrap.INSTANCE.getBootstrap().initialize(this);
This way, you share the component across your classes. I personally named Bootstrap as injector, and _Bootstrap as ApplicationComponent, so it looks like this:
Injector.INSTANCE.getApplicationComponent().inject(this);
But that's just my typical setup. Names don't really matter.
EDIT: To your last question, you can solve this by subscoping and component dependencies.
Your library project should be able to see only the library classes, correct? In that case, all you do is
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface LibraryScope {
}
#Component(modules={LibraryModule.class})
#LibraryScope
public interface LibraryComponent {
LibraryClass libraryClass(); //provision method for `MyManager`
}
#Module
public class LibraryModule {
#LibraryScope
#Provides
public LibraryClass libraryClass() { //in your example, LibraryClass is `MyManager`
return new LibraryClass(); //this is instantiation of `MyManager`
}
}
public enum LibraryBootstrap {
INSTANCE;
private LibraryComponent libraryComponent;
static {
INSTANCE.libraryComponent = DaggerLibraryComponent.create();
}
public LibraryComponent getLibraryComponent() {
return libraryComponent;
}
}
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface ApplicationScope {
}
#Component(dependencies={LibraryComponent.class}, modules={AdditionalAppModule.class})
#ApplicationScope
public interface ApplicationComponent extends LibraryComponent {
AdditionalAppClass additionalAppClass();
void inject(InjectableAppClass1 injectableAppClass1);
void inject(InjectableAppClass2 injectableAppClass2);
void inject(InjectableAppClass3 injectableAppClass3);
}
#Module
public class AdditionalAppModule {
#ApplicationScope
#Provides
public AdditionalAppClass additionalAppClass() { //something your app shares as a dependency, and not the library
return new AdditionalAppClass();
}
}
public enum ApplicationBootstrap {
INSTANCE;
private ApplicationComponent applicationComponent;
void setup() {
this.applicationComponent = DaggerApplicationComponent.builder()
.libraryComponent(LibraryBootstrap.INSTANCE.getLibraryComponent())
.build();
}
public ApplicationComponent getApplicationComponent() {
return applicationComponent;
}
}
Then
#Inject
LibraryClass libraryClass; //MyManager myManager;
...
ApplicationBootstrap.INSTANCE.getApplicationComponent().inject(this);
It's hard to say what your problem was, since you didn't show what your Component looks like and whether you have multiple components etc.
Assuming this logical structure:
/app
MainComponent
SomeClass // where MyManager is to be injected
MainActivity // where SomeClass is to be injected
/library
LibraryComponent
MyManager // Singleton
Then your classes as listed would inject correctly with the following configuration:
#Singleton
#Component
public interface LibraryComponent {
MyManager getMyManager();
}
and the app-level component to inject dependencies into the activity:
#ActivityScope
#Component(dependencies = LibraryComponent.class)
public interface MainComponent {
void inject(MainActivity mainActivity);
}
Note that MainComponent depends on LibraryComponent, but because the latter has singleton scope, you need to define a scope for the other one too, which I was I used the "activity scope" here. (Or you could also just make the MainComponent a singleton and get rid of the LibraryComponent completely if that suits your needs.)
Finally it's all injected into the activity like this:
#Inject
SomeClass someClass;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent.builder()
.libraryComponent(DaggerLibraryComponent.create())
.build()
.inject(this);
someClass.doSomething();
}
I've put a working sample here on GitHub
Update 1:
If I understand your setup correctly, you've so far only used the #Singleton and #Inject annotations on the two classes listed (MyManager and SomeClass), and there is no other Dagger-related code in your project.
In that case, the reason your MyManager isn't getting injected, is because Dagger doesn't know how to provide/instantiate the dependencies. This is where the "components" come in that I mentioned above. Without any Dagger 2 components (interface or abstract class annotated with #Component), your dependencies won't get injected automatically.
I don't know if you have experience with Dependency Injection concepts, but assuming you don't, I'll step through the minimum basics you'll need to understand to get your MyManager injected into SomeClass:
First: when you use DI, you need to understand the difference between "newables" and "injectables". This blogpost by Misko Hevery explains the details.
This means, you can't new up your SomeClass. This won't work:
mSomeClass = new SomeClass();
Because if you did that (say in an activity or fragment), Dagger will have no idea that you expected a dependency to get injected into SomeClass and it has no opportunity to inject anything.
In order for its dependencies to get injected, you have to instantiate (or inject) SomeClass itself through Dagger too.
In other words, say in your Activity where SomeClass is used, you'll need:
#Inject
SomeClass mSomeClass;
Next, you need a Dagger component to perform the actual injection. To create a component, you create an interface with a method that takes your root object (say MainActivity) as argument, e.g.:
#Singleton
#Component
public interface Bootstrap {
void initialize(MainActivity activity);
}
Now when you build your project, Dagger 2 generates a class called DaggerBootstrap that implements this interface. You use this generated class to perform the injection, say in your activity's onCreate:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerBootstrap.create().initialize(this);
mSomeClass.doSomething();
}
I believe this generated component is the key part you're missing. Full code for above here.
Some useful Dagger 2 resources:
the official Dagger 2 guide
reddit page with lots of links
Update 2:
Seems like the final missing piece of the puzzle was that your component provided an inject method for the Activity base class, but not for your actual concrete activity.
Unfortunately Dagger 2 requires an inject method for each activity or other class you want to inject into.
As you mentioned, this will be annoying when you have many different activities in your app. There some possible workarounds for this, search for "dagger 2 inject base class", for example this suggestion by #EpicPandaForce: Dagger 2 base class injections
Also note, as pointed out by #EpicPandaForce in the comments, that in my simplistic example I called DaggerLibraryComponent.create() every time which is probably not what you want, since that component is supposed to provide your singletons, so you're probably better off getting the existing instance from somewhere else such as from your Application instance.

How to provide base classes to autowire in Spring?

I'd like to write some base classes that should be picked by default to autowire with Spring. Only if these classes are extended, thus a custom implementation is provided for them, I want the custom implementation to be picked up, instead of the default one.
How can I achieve it when I don't want to make my default classes abstract (as the application should be able to run without custom implementations, just by the default ones)?
The the following example: I want to provide a basic handler for any errors. But this setup would only work if I make the BaseHandler abstract, which I don't want (as in this case I would force anyone using my library to implement the class).
#MessageEndpoint
public class BaseHandler {
#ServiceActivator(inputChannel = "errorChannel")
public A_Stadisdatensatz handle(Message<MessageHandlingException> message) {
process(message.getPayload());
}
/**
* Override to supply specific processing
*/
void process(Payload payload) {
//do some default processing
}
}
#MessageEndpoint
public class CustomHandler extends BaseHandler {
#Override
void process(Payload payload) {
//custom processing
}
}
If I run it this way, CustomHandler will never be picked up.
So, is it impossible?
I supose that you are using #Autowired to inject the dependencies. In that case you could use #Primary annotation to override defaults ones, ie
#Primary
#MessageEndpoint
public class CustomHandler extends BaseHandler {
#Override
void process(Payload payload) {
//custom processing
}
}

Categories

Resources