Grails services are abstractions used for implementing business logic (as well as connecting to backing services/DBs, etc.) outside of a controller. So in a typical controller you might have:
class DashboardController {
StatisticsService statsService
def index() {
// Fetches all the stats that need to be displayed to the
// admin on the dashboard.
AdminDashboardMetrics adm = statsService.getAdminStats()
render(view: "/dashboard", model: [ adm: adm ])
}
}
Here, Grails automatically injects the DashboardController with a bean instance of StatisticsService (provided of course that the service was properly created with grails create-service ...).
But what happens when I need to access StatisticsService outside of a controller, and particularly, under src/groovy?
// src/groovy/com/example/me/myapp/FizzBuzzer.groovy
class FizzBuzzer {
StatisticsService statsService
FizzBuzzer(StatisticsService statsService) {
super()
this.statsService = statsService
}
def doSomething(MyData input) {
MoreData result = statsService.calculate(input)
// use 'result' somehow, etc....
}
}
How do I properly inject FizzBuzzer with the same StatisticsService instance ad what is passed into DashboardController?
You can inject grails service in spring bean by defining injection login in resources.groovy under conf > spring
As I made an ExampleService and Example class in src/groovy
ExampleService
class ExampleService {
def serviceMethod() {
println "do something"
}
}
Example Class under src/groovy
class Example {
ExampleService exampleService
def doSomething() {
def result = exampleService.serviceMethod()
}
}
resources.groovy under conf>spring
beans = {
ex(Example){ bean ->
exampleService = ref('exampleService')
}
}
so I can define Example ex as spring bean in grails-app and it will have ExampleService injected by itself.
Hope this helps. Thanks
You can also get a service using grails.util.Holders
Example:
For injecting MyService service class, use Holders.applicationContext.getBean("myService")
Where "myService" is the name of your service class in lower camel case.
One way of achieving this is by using ServletContext-
ApplicationContext ctx = (ApplicationContext)ServletContextHolder.
getServletContext().getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT)
statisticsService = (StatisticsService ) ctx.getBean("statisticsService ")
see this blog - http://www.grailsbrains.com/availing-grails-goodies-in-srcjava-or-srcgroovy/
Related
Grails 2.4.x here. If I created a Grails service using grails create-service com.example.Widget, then how can I inject a reference of that service (a "bean") into a class under src/groovy?
This is actually not explained anywhere in the official Grails docs, and extensive searching turned back nadda.
1) You can use Spring Beans to inject a service into a non-artefact groovy file, using resources.groovy:
MyClass.groovy
class MyClass {
def widgetService
...
}
resources.groovy
beans = {
myclass(com.example.MyClass) {
widgetService = ref('widgetService')
}
}
2) There is also an additional #Autowired annotation that can do the same thing:
MyClass.groovy
import org.springframework.beans.factory.annotation.Autowired
class MyClass {
#Autowired
def widget
...
}
resources.groovy
beans = {
myclass(com.example.MyClass) {}
}
Notice - this time the myclass bean doesn't need the reference to the widget.
3) There is an alternative to injecting the WidgetService - using the Holders class to get the grailsApplication which will have a reference to the existing bean.
import grails.util.Holders
class MyClass {
def widgetService = Holders.grailsApplication.mainContext.getBean('widgetService')
...
}
**Update**
4) There is another option that is a hybrid of 1) and 2) -
Having the bean(s) injected by autowire=true within resources.groovy:
MyClass.groovy
class MyClass {
def widgetService
...
}
resources.groovy
beans = {
myclass(com.example.MyClass) { bean ->
bean.autowire = true
}
}
This is the approach I've been using locally as I feel it's the cleanest, but it does take more advantage of Grail's 'magic' (for better or worse).
I have a class that I moved under the grails-app/services directory in order to inject the springSecurityService. This class is the implementation (Is that the proper terminology?) of the spring userDetailsService class. Here is my declaration in resources.groovy:
userDetailsService(com.company.product.PracticeUserDetailsService) {
grailsApplication = ref('grailsApplication')
}
This class is extending GormUserDetailsService.
My attempted dependencyInjection results in a null object.
class PracticeUserDetailsService extends GormUserDetailsService{
def springSecurityService
UserDetails loadUserByUsername(String username, boolean loadRoles) throws UsernameNotFoundException {
// impl omitted
}
}
If I create some test controller or some service in grails and inject the springSecurityService, it works fine. So, there is perhaps something about this particular class that is not placing it in the Grails ecosystem. I checked out this to try to manually inject it as follows:
beans = {
springSecurityTEST(grails.plugins.springsecurity.SpringSecurityService) {}
}
Move PracticeUserDetailsService from grails-app/services to src/groovy and change your Spring bean definition in resources.groovy to:
userDetailsService(com.company.product.PracticeUserDetailsService) {
grailsApplication = ref('grailsApplication')
springSecurityService = ref('springSecurityService')
}
For me it happened when I used GrailsApp.run(Application, args) to run the app...
I'd like to use data generator at init in my application. It works fine when create objects using .save() method, but it doesn't work when I want to use dedicated services, because of null pointers instead of injected services. That's my code:
I have defined DataGenerator bean inside conf/spring
beans = {
dataGenerator(DataGenerator)
}
My Bootstrap.groovy looks like:
class BootStrap {
def dataGenerator
def init = { servletContext ->
dataGenerator.generateData()
}
}
In `DataGenerator' I have:
class DataGenerator{
BookService bookService
def generateData() {
log.info("Generating books")
createBooks()
}
def createBooks(){
(1..40).each() {
CreateBookCommand command = new CreateBookCommand()
/* some command populate code*/
bookService.create(command);
}
}
}
The problem is, that I cannot invoke create() method, because bookService is always null
BookService is simple grails service with some dependencies, of course placed in grails-app/services
class BookService {
UserService userService
SpringSecurityService springSecurityService
def create(CreateBookCommand command){
Book book = new Book()
command.bindTo(book)
book.save(flush:true, failOnError:true)
}
/*some other methods*/
}
Could you tell me how to fix it?
Try this in resources.groovy
beans = {
dataGenerator(DataGenerator) { bean ->
bean.autowire = 'byName'
}
}
I assume DataGenrator being a class outside the grails artifact (that is: placed in src/groovy), you can refer the already available service class in the context as:
beans = {
dataGenerator(DataGenerator){
bookService = ref('bookService')
}
}
or try autowiring byName as mentioned by #sudhir.
I have a service to get and set the user in the session. I would like to pass in some user information to each view if there is a logged in user and thought a filter would be the best way so I don't have to duplicate that in every controller/action. When I run the app, it get this error:
Error creating bean with name 'userService': Scope 'session' is not active for the current thread
My filter looks like this:
class SecurityFilters {
def userService
def filters = {
setUser(controller:'*', action:'*') {
before = {
if (userService.isLoggedIn()) {
request.user = userService.getUser()
} else {
request.user = null
}
}
}
}
}
I know I can just ultimately access the user through session.user, but I want to be able to call userService.isLoggedIn() which I can't easily through the view. So is there a way to inject the service into the filter, or should I just create a taglib to wrap userService.isLoggedIn()?
It looks like the problem is your userService is scoped to the session, and there is not necessarily a session at the time the attempt to inject the service into the filter happens.
If your userService is necessarily session-scoped then you need to use a scoped proxy in your spring config. E.g. in grails-app/conf/spring/resources.groovy:
import com.your.service.UserService
...
userServiceSession(UserService)
{ bean ->
bean.scope = 'session'
}
userServiceSessionProxy(org.springframework.aop.scope.ScopedProxyFactoryBean)
{
targetBeanName = 'userServiceSession'
proxyTargetClass = true
}
Then rename your injected variable in your SecurityFilter:
def userServiceSessionProxy
(and obviously rename where you use it elsewhere in the class).
What this should do is inject the proxy at injection time, but only go to the actual service at the time the filter is executed (when there is a session).
Note: not sure whether doing this still lets other places where there will be a session (e.g. controllers) still reference the service as 'userService', if not you might be able to rename userServiceSession to userService in resources.groovy (and update targetBeanName accordingly).
It looks like there are some problems with services in filters. This bug might point you in the right direction: https://jira.grails.org/browse/GRAILS-5982
You can also try to get a reference to your service through the ApplicationContext. Here's an example: How do I get an instance of a Grails service programmatically?
import org.codehaus.groovy.grails.web.context.ServletContextHolder
import org.springframework.context.ApplicationContext
import org.springframework.web.context.support.WebApplicationContextUtils
class SecurityFilters {
def userService
def filters = {
setUser(controller:'*', action:'*') {
before = {
def servletCtx = ServletContextHolder.getServletContext()
ApplicationContext applicationContext = WebApplicationContextUtils.
getRequiredWebApplicationContext(servletCtx)
userService =applicationContext.getBean('userService')
if (userService.isLoggedIn()) {
request.user = userService.getUser()
} else {
request.user = null
}
}
}
}
}
I need to call the Static Resources Plugin (http://www.grails.org/Static+Resources+Plugin) from my domain class.
This works perfectly in a controller:
def tstLink = resourceLinkTo(dir:"docs/${identifier}",file:originalFileName)
but in a domain class I get
Exception Message: No signature of method: static org.maflt.ibidem.Item.resourceLinkTo() is applicable for argument types: (java.util.LinkedHashMap) values: [[dir:docs/19e9ea9d-5fae-4a35-80a2-daedfbc7c2c2, file:2009-11-12_1552.png]]
I assume this is a general problem.
So how do you call a taglib as a function in a domain class?
I encountered this problem a while ago for an app I was working on. What I ended up doing was putting a call to the tag in a service method:
class MyService {
def grailsApplication //autowired by spring
def methodThatUsesATag(identifier, originalFileName) {
//This is the default grails tag library
def g = grailsApplication.mainContext.getBean('org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib')
g.resourceLinkTo(dir:"docs/${identifier}",file:originalFileName)
}
}
Then in my domain class, I could get to the service via spring autowiring as well:
class MyDomain {
String originalFileName
def myService //autowired
static transients = ['myService'] //Necessary so that GORM doesn't try to persist the service instance.
//You can create a method at this point that uses your
//service to return what you need from the domain instance.
def myMethod() {
myService.methodThatUsesATag(id, originalFileName)
}
}
Most taglibs rely on data from the controller so it's often not possible to reuse them while others concern view logic so often it's not something you would want to put in a domain class.
That said, I'm sure you have your reasons so maybe the source of the taglib will help:
class ResourceTagLib {
def externalResourceServerService
def resourceLinkTo = { attrs ->
out << externalResourceServerService.uri
out << '/'
if(attrs['dir']) {
out << "${attrs['dir']}/"
}
if(attrs['file']) {
out << "${attrs['file']}"
}
}
}
ie inject the externalResourceServerService into your domain class and the rest should be simple.