I am the founder and lead developer of Hibernate Envers, a Hibernate core module, which provides entity versioning/auditing capabilities. I am also one of the co-founders of SoftwareMill, a company specializing in delivering customized software solutions (http://softwaremill.com, "Extraordinary software as a standard"), based on Java and JBoss technologies. After work, apart from being involved in development of Envers, I work on several small open source projects, like ElasticMQ (simple message queue written in Scala with an SQS interface), projects around static analysis (using JSR 308 - Typestate Annotations/ Checkers Framework and FindBugs), and some CDI/Weld (not always portable) extensions, like autofactories or stackable security interceptors. I am also interested in new JVM-based languages, especially with functional elements (like Scala, JRuby) and frameworks built using them (like Lift), as well as improving the ways we use Dependency Injection. Adam is a DZone MVB and is not an employee of DZone and has posted 52 posts at DZone. You can read more from them at their website. View Full User Profile

MacWire 0.2: Scopes are Simple!

04.29.2013
| 1610 views |
  • submit to reddit

MacWire generates new instance creation code of given classes, using values in the enclosing type for constructor parameters, with the help of Scala Macros. DI container replacement.

Version 0.2 has just landed!

First things first …

First, some bad news. Due to limitations of the current macros implementation in Scala (for more details see this discussion) in order to avoid occasional compilation errors it is necessary to add type ascriptions to the dependencies. This is a way of helping the type-checker that is invoked by the macro to figure out the types of the values which can be wired.

In practice it is good to always write the type ascription. For example:

trait MyModule {
   ...
   lazy val theUserFinder: UserFinder = wire[UserFinder]
   ...
}

This is a major inconvenience, but hopefully will get resolved some day. See the “Limitations” section in the README for more details.

… to the point

Now, to the good news. Especially when developing web applications, it is very useful to scope some of the dependencies, so that there’s a new instance for each request (but for each dependency usage, it is the same instance in one request), or that there’s a single instance per http session.

So far MacWire had two “built-in” scopes: singleton (declare the dependency as lazy val) and dependent (def). Now it is possible to create custom scopes, by implementing the Scope trait.

Scopes often seem “magical”; in traditional frameworks it is usually necessary to add an annotation to the class and then the dependency is automagically turned into a scoped one by the container.

In fact implementing a scoped bean is quite simple. We need two things. Firstly, instead of using instances directly, we need to use a proxy which delegates method calls to the “current instance”. What “current instance” means is scope-specific.

Secondly, we need a way to read a value from the scope, and store a value in the scope, if there’s no value yet. The two methods in the Scope trait correspond directly to the two cases outlined above.

Using a scope is quite straightforward; the scope of a dependency is declared in the wiring code, in the module. For example:

trait WebModule {
   lazy val loggedInUser: LoggedInUser = session(wire[LoggedInUser])
 
   def session: Scope
}

Note that the scope here is abstract (only the name suggests that it is a session scope). This has a couple of advantages. For testing, we can substitute a NoOpScope. Also, we can move the framework-integration code to the integration layer, or even provide a couple of implementations depending on the framework/container used.

MacWire comes with a Scope implementation targeted at synchronous web frameworks, ThreadLocalScope. The scope needs to be associated and disassociated with a scope storage. To implement a:

  • request scope, we need a new empty storage for every request (associateWithEmptyStorage method)
  • session scope, the storage (a Map) should be stored in the HttpSession (associate(Map) method)

This association can be done, for example, in a servlet filter:

class ScopeFilter(sessionScope: ThreadLocalScope) extends Filter {
  def init(filterConfig: FilterConfig) {}
 
  def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
    val httpRequest = request.asInstanceOf[HttpServletRequest]
 
    val sessionScopeStorage = new ScopeStorage {
      def get(key: String) = Option(httpRequest.getSession.getAttribute(key))
      def set(key: String, value: Any) {
        httpRequest.getSession.setAttribute(key, value)
      }
    }
 
    sessionScope.withStorage(sessionScopeStorage) {
      chain.doFilter(request, response)
    }
  }
 
  def destroy() {}
}

Note also that it is trivial to use a scoped value e.g. in an asynchronous process, where no web request is available: simply associate the scope with any storage, can be a temporary map. In traditional web frameworks this usually requires some amount of hacking.

To sum up, to use a scope in MacWire you need to:

  • declare a Scope in your module, use if for scoped dependencies
  • provide an implementation of the scope, e.g. ThreadLocalScope
  • associate the scope with a storage, e.g. in a servlet filter

You can see how this works in practice by browsing & running a Scalatra+MacWire example application. To run, clone MacWire and use this command: sbt examples-scalatra/run. To browse the code, simply head to GitHub.

2013-04-25_1649

The example contains a request-scoped dependency (SubmittedData), session-scoped dependency (LoggedInUser) and three main services (Service1, Service2, Service3). Each class has some dependencies (declared in the constructor), and is wired using wire[]. The code is organised in two modules: logic and servlet. The services have methods to return a string-status, containing the current request and session-scoped values, so that it is possible to verify that indeed the values are shared properly. The servlet module contains a servlet which shows the service status, plus the scopes implementations.

Note that the scopes subproject is completely independent, and can be used stand-alone with manual wiring or any other library/framework. Its only dependency is javassist.

Have fun!
Adam


 

Published at DZone with permission of Adam Warski, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)