I've encountered strange behavior with a domain class in my project: services that should be injected were null. I've became suspicious as why is that? Services are injected properly in other domain classes so why this one is different?
Constructors experiment
I've created an experiment. I've created empty LibraryService that should be injected and Book domain class like this:
class Book { | |
def libraryService | |
String author | |
String title | |
int pageCount | |
Book() { | |
println("Finished constructor Book()") | |
} | |
Book(String author) { | |
this() | |
this.@author = author | |
println("Finished constructor Book(String author)") | |
} | |
Book(String author, String title) { | |
super() | |
this.@author = author | |
this.@title = title | |
println("Finished constructor Book(String author, String title)") | |
} | |
Book(String author, String title, int pageCount) { | |
this.@author = author | |
this.@title = title | |
this.@pageCount = pageCount | |
println("Finished constructor Book(String author, String title, int pageCount)") | |
} | |
void logInjectedService() { | |
println(" Service libraryService is injected? -> $libraryService") | |
} | |
} |
class LibraryService { | |
def serviceMethod() { | |
} | |
} |
Book
has 4 explicit constructors. I want to check which constructor is injecting dependecies. This is my method that constructs Book
objects and I called it in controller:
class BookController { | |
def index() { | |
constructAndExamineBooks() | |
} | |
static constructAndExamineBooks() { | |
println("Started constructAndExamineBooks") | |
Book book1 = new Book().logInjectedService() | |
Book book2 = new Book("foo").logInjectedService() | |
Book book3 = new Book("foo", 'bar').logInjectedService() | |
Book book4 = new Book("foo", 'bar', 100).logInjectedService() | |
Book book5 = new Book(author: "foo", title: 'bar') | |
println("Finished constructor Book(Map params)") | |
book5.logInjectedService() | |
} | |
} |
Analysis
Output looks like this:
Started constructAndExamineBooks | |
Finished constructor Book() | |
Service libraryService is injected? -> eu.spoonman.refaktor.LibraryService@2affcce2 | |
Finished constructor Book() | |
Finished constructor Book(String author) | |
Service libraryService is injected? -> eu.spoonman.refaktor.LibraryService@2affcce2 | |
Finished constructor Book(String author, String title) | |
Service libraryService is injected? -> null | |
Finished constructor Book(String author, String title, int pageCount) | |
Service libraryService is injected? -> null | |
Finished constructor Book() | |
Finished constructor Book(Map params) | |
Service libraryService is injected? -> eu.spoonman.refaktor.LibraryService@2affcce2 |
What do we see?
✔
Empty constructor injects dependencies.✔
Constructor that invokes empty constructor explicitly injects dependencies.✘
Constructor that invokes parent's constructor explicitly does not inject dependencies.✘
Constructor without any explicit call declared does not call empty constructor thus it does not inject dependencies.✔
Constructor provied by Grails with a map as a parameter invokes empty constructor and injects dependencies.
Conclusion
Always explicitily invoke empty constructor in your Grail domain classes to ensure Dependency Injection! I didn't know until today either!
Hi Tomasz,
ReplyDeletethanks a million for this post! I have the problem that I was unable to inject services into my domain classes and kept fighting with this ever since they started!
Your post got me on the right track! But honestly I am using grails for the first time ... and by now I wish I didn't its a complete mess. I mean how can such a thing be? Is completely unintuitive and unlogical ... what a load of cr...!
Anyway! Thanks again for your post! You really saved my day!
You're welcome. I'm glad you've sorted this out.
DeleteHi Tomasz,
ReplyDeletethank you very much for the post.
It saved lot of my time
--Gangadhar
This is fantastic info & should be part of the Grail docs (is it? did i miss it?). I've been hunting for this info for while, and it explains why I see services injected in a my superclass only *after* load from the db. Thanks so much for posting.
ReplyDeleteI'm glad it hepled!
DeletePlease check the below code.
ReplyDeleteI am still getting NullPointerException. :(
public class PgpEncryptionService implements ResourceLoaderAware {
ResourceLoader resourceLoader
public PgpEncryptionService() {
}
}
Please ignore that public access modifier, it doesn't work without that also.
DeleteThis is a post about Grails 2 three years ago. You probably use a newer Grails version so this post may be irrelevant.
DeleteThanks for your reply..I am using Grails 2.1.0 and its a legacy application.. :(
DeleteI'm learning Grails 3.0.9 now and hit the same problem. After some Googling, your solution is the only one I found that seemed like it might work so I tried it and it does - thanks so much for that. Saved me a ton of time.
ReplyDelete