Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Zero-cost, compile-time, type-safe dependency injection library.

License

NotificationsYou must be signed in to change notification settings

softwaremill/macwire

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MacWire

Ideas, suggestions, problems, questionsMaven Central

Zero-cost, compile-time, type-safe dependency injection library.

The core of MacWire is a set of macros, that is code which runs at compile-time. The macros generate thenew instancecreation code automatically, allowing you to avoid a ton of boilerplate code, while maintaining type-safety.

The core library has no dependencies, and incurs no run-time overhead. The main mechanism of defining dependencies of aclass are plain constructors orapply methods in companion objects. MacWire doesn't impact the code of your classesin any way, there are no annotations that you have to use or conventions to follow.

There's a couple of wiring variants that you can choose from:

  • autowire create an instance of the given type, using the provided dependencies. Any missing dependencies are createdusing constructors/apply methods.
  • wire create an instance of the given type, using dependencies from the context, within which it is called.Dependencies are looked up in the enclosing trait/class/object and parents (via inheritance).
  • wireRec is a variant ofwire, which creates missing dependencies using constructors.

In other words,autowire is context-free, while thewire family of macros is context-dependent.

MacWire is available for Scala 2.12, 2.13 and 3 on the JVM, JS and Native platforms. Not all functionalities areavailable for each Scala version.

To use, add the following dependency:

// sbt"com.softwaremill.macwire" %% "macros" % "2.6.6" % "provided"// scala-cli//> using dep com.softwaremill.macwire::macros:2.6.6

Table of Contents

autowire

Scala 3direct-style

Autowire generates the code needed to instantiate the given type. To create the instance, the public primaryconstructor is used, or if one is absent - theapply method from the companion object. Any dependencies arecreated recursively. For example, given the following classes:

classDatabaseAccess()classSecurityFilter()classUserFinder(databaseAccess:DatabaseAccess,securityFilter:SecurityFilter)classUserStatusReader(userFinder:UserFinder)

invokingautowire as below:

importcom.softwaremill.macwire.*valuserStatusReader= autowire[UserStatusReader]()

generates following code:

valuserStatusReader=valwiredDatabaseAccess=newDatabaseAccess()valwiredSecurityFilter=newSecurityFilter()valwiredUserFinder=newUserFinder(wiredDatabaseAccess, wiredSecurityFilter)valwiredUserStatusReader=newUserStatusReader(wiredUserFinder)  wiredUserStatusReader

autowire accepts an arbitrary number of parameters, which specify how to lookup or create dependencies, insteadof the default construct/apply mechanism. These are described in detail below. Each such parameter might be:

  • an instance to use
  • a function to create an instance
  • a class to instantiate to provide a dependency for the types it implements (provided as:classOf[SomeType])
  • aautowireMembersOf(instance) call, to use the members of the given instance as dependencies

autowire is context-free: its result does not depend on the environment, within which it is called (except forimplicit parameters, which are looked up using the usual mechanism). It only depends on the type that is specifiedfor wiring, and any parameters that are passed.

Providing instantiated dependencies

In some cases it's necessary to instantiate a dependency by hand, e.g. to initialise it with some configuration,or to manage its lifecycle. In such cases, dependencies can be provided as parameters to theautowire invocation.They will be used whenever a value of the given type is needed.

As an example, consider aDataSource, which needs to be configured with a JDBC connection string, and has amanaged life-cycle:

importjava.io.CloseableclassDataSource(jdbcConn:String)extendsCloseable {defclose()= () }classDatabaseAccess(ds:DataSource)classSecurityFilter()classUserFinder(databaseAccess:DatabaseAccess,securityFilter:SecurityFilter)classUserStatusReader(userFinder:UserFinder)

We can provide aDataSource instance to be used byautowire:

importcom.softwaremill.macwire.*importscala.util.UsingUsing.resource(DataSource("jdbc:h2:~/test")): ds=>  autowire[UserStatusReader](ds)

Using factories

In addition to instances, which should be used byautowire, it's also possible to provide factory methods. They willbe used to create instances of types which is the result of the provided function, using the dependencies which are thefunction's parameters. Any dependencies are recursively created.

For example, we can provide a custom way to create aUserFinder:

classDatabaseAccess()classSecurityFilter()classUserFinder(databaseAccess:DatabaseAccess,securityFilter:SecurityFilter,adminOnly:Boolean)classUserStatusReader(userFinder:UserFinder)autowire[UserStatusReader](UserFinder(_, _, adminOnly=true))

Specifying implementations to use

You can specify which classes to use to create instances. This is useful when the dependencies are expressed forexample usingtraits, not the concrete types. Such a dependency can be expressed by providing aclassOf[]parameter toautowire:

traitDatabaseAccessclassDatabaseAccessImpl()extendsDatabaseAccessclassSecurityFilter()classUserFinder(databaseAccess:DatabaseAccess,securityFilter:SecurityFilter)classUserStatusReader(userFinder:UserFinder)autowire[UserStatusReader](classOf[DatabaseAccessImpl])

Without theclassOf[], MacWire wouldn't know how to create an instance implementingDatabaseAccess.

Using dependencies contained in objects

Finally, it's possible to use the members of a given instance as dependencies. Simply pass aautowireMembersOf(someInstance)as a parameter toautowire.

Errors

autowire reports an error when:

  • a dependency can't be created (e.g. there are no public constructors /apply methods)
  • there are circular dependencies
  • the provided dependencies contain a duplicate
  • a provided dependency is not used
  • a primitive orString type is used as a dependency (instead, use e.g. an opaque type)

Each error contains the wiring path. For example, below there's no public constructor forDatabaseAccess, whichresults in a compile-time error:

classDatabaseAccessprivate ()classSecurityFilter()classUserFinder(databaseAccess:DatabaseAccess,securityFilter:SecurityFilter)classUserStatusReader(userFinder:UserFinder)autowire[UserStatusReader]()// compile-time error:// cannot find a provided dependency, constructor or apply method for: DatabaseAccess;// wiring path: UserStatusReader -> UserFinder -> DatabaseAccess

Limitiations of autowire

autowire doesn't handle propagating generic type parameters, when creating missing dependencies using constructors orapply methods. E.g. if you have aclass B[X](a: A[X]), and aB[Int] needs to be created, the specific value forthe type parameterX won't be propagated when resolving the dependencies.

As a work-around, you need to explicitly provide the generic dependencies with concrete type parameters. A genericdependency might appear multiple times, with different type parameters. To wire the classes from the example, you'dneed:autowire[...](B[Int](_), someInstance: A[Int]) or similar.

wire

Scala 2Scala 3direct-style

MacWire generatesnew instance creation code of given classes, using values in the enclosing type for constructorparameters, with the help of Scala Macros.

For a general introduction to DI in Scala, take a look at theGuide to DI in Scala,which also features MacWire.

MacWire helps to implement the Dependency Injection (DI) pattern, by removing the need to write theclass-wiring code by hand. Instead, it is enough to declare which classes should be wired, and how the instancesshould be accessed (see Scopes).

Classes to be wired should be organized in "modules", which can be Scalatraits,classes orobjects.Multiple modules can be combined using inheritance or composition; values from the inherited/nested modules are alsoused for wiring.

MacWire can be in many cases a replacement for DI containers, offering greater control on when and how classes areinstantiated, typesafety and using only language (Scala) mechanisms.

Example usage:

classDatabaseAccess()classSecurityFilter()classUserFinder(databaseAccess:DatabaseAccess,securityFilter:SecurityFilter)classUserStatusReader(userFinder:UserFinder)traitUserModule {importcom.softwaremill.macwire._lazyvaltheDatabaseAccess= wire[DatabaseAccess]lazyvaltheSecurityFilter= wire[SecurityFilter]lazyvaltheUserFinder= wire[UserFinder]lazyvaltheUserStatusReader= wire[UserStatusReader]}

will generate:

traitUserModule {lazyvaltheDatabaseAccess=newDatabaseAccess()lazyvaltheSecurityFilter=newSecurityFilter()lazyvaltheUserFinder=newUserFinder(theDatabaseAccess, theSecurityFilter)lazyvaltheUserStatusReader=newUserStatusReader(theUserFinder)}

For testing, just extend the base module and override any dependencies with mocks/stubs etc, e.g.:

traitUserModuleForTestsextendsUserModule {overridelazyvaltheDatabaseAccess= mockDatabaseAccessoverridelazyvaltheSecurityFilter= mockSecurityFilter}

The core library has no dependencies.

For more motivation behind the project see also these blogs:

How wiring works

For each constructor parameter of the given class, MacWire tries to find a valueconforming to the parameter'stype in the enclosing method and trait/class/object:

  • first it tries to find a unique value declared as a value in the current block, argument of enclosing methodsand anonymous functions.
  • then it tries to find a unique value declared or imported in the enclosing type
  • then it tries to find a unique value in parent types (traits/classes)
  • if the parameter is marked as implicit, it is ignored by MacWire and handled by the normal implicit resolution mechanism

Here value means either aval or a no-parameterdef, as long as the return type matches.

A compile-time error occurs if:

  • there are multiple values of a given type declared in the enclosing block/method/function's arguments list, enclosing type or its parents.
  • parameter is marked as implicit and implicit lookup fails to find a value
  • there is no value of a given type

The generated code is then once again type-checked by the Scala compiler.

Factories

A factory is simply a method. The constructor of the wired class can contain parameters both fromthe factory (method) parameters, and from the enclosing/super type(s).

For example:

classDatabaseAccess()classTaxDeductionLibrary(databaseAccess:DatabaseAccess)classTaxCalculator(taxBase:Double,taxDeductionLibrary:TaxDeductionLibrary)traitTaxModule {importcom.softwaremill.macwire._lazyvaltheDatabaseAccess= wire[DatabaseAccess]lazyvaltheTaxDeductionLibrary= wire[TaxDeductionLibrary]deftaxCalculator(taxBase:Double)= wire[TaxCalculator]// or: lazy val taxCalculator = (taxBase: Double) => wire[TaxCalculator]}

will generate:

traitTaxModule {lazyvaltheDatabaseAccess=newDatabaseAccess()lazyvaltheTaxDeductionLibrary=newTaxDeductionLibrary(theDatabaseAccess)deftaxCalculator(taxBase:Double)=newTaxCalculator(taxBase, theTaxDeductionLibrary)}

Factory methods

You can also wire an object using a factory method, instead of a constructor. For that, usewireWith instead ofwire. For example:

classA()classC(a:A,specialValue:Int)objectC {defcreate(a:A)=newC(a,42)}traitMyModule {lazyvala= wire[A]lazyvalc= wireWith(C.create _)}

lazy val vs.val

It is safer to uselazy vals, as when usingval, if a value is forward-referenced, it's value during initializationwill benull. Withlazy val the correct order of initialization is resolved by Scala.

Recursive wiring

When usingwire and a value for a parameter can't be found, an error is reported.wireRec takes a differentapproach - it tries to recursively create an instance, using normal wiring rules. This allows to explicitly wireonly those objects, which are referenced from the code, skipping helper or internal ones.

The previous example becomes:

classDatabaseAccess()classSecurityFilter()classUserFinder(databaseAccess:DatabaseAccess,securityFilter:SecurityFilter)classUserStatusReader(userFinder:UserFinder)traitUserModule {importcom.softwaremill.macwire._lazyvaltheUserStatusReader= wireRec[UserStatusReader]}

and will generate:

traitUserModule {lazyvaltheUserStatusReader=newUserStatusReader(newUserFinder(newDatabaseAccess(),newSecurityFilter()))}

This feature is inspired by @yakivy's work onjam.

Composing modules

Modules (traits or classes containing parts of the object graph) can be combined using inheritance or composition.The inheritance case is straightforward, aswire simply looks for values in parent traits/classes. With composition,you need to tell MacWire that it should look inside the nested modules.

To do that, you can use imports:

classFacebookAccess(userFind:UserFinder)classUserModule {lazyvaluserFinder= ...// as before}classSocialModule(userModule:UserModule) {importuserModule._lazyvalfacebookAccess= wire[FacebookAccess]}

Avoiding imports

Dependency:

"com.softwaremill.macwire"%%"util"%"2.6.6"

If you are using that pattern a lot, you can annotate your modules using@Module, and they will be used whensearching for values automatically:

classFacebookAccess(userFind:UserFinder)@ModuleclassUserModule { ... }// as beforeclassSocialModule(userModule:UserModule) {lazyvalfacebookAccess= wire[FacebookAccess]}

Scopes

Dependency:

"com.softwaremill.macwire" %% "proxy" % "2.6.6"

There are two "built-in" scopes, depending on how the dependency is defined:

  • singleton:lazy val /val
  • dependent - separate instance for each dependency usage:def

MacWire also supports user-defined scopes, which can be used to implement request or session scopes in web applications.Theproxy subproject defines aScope trait, which has two methods:

  • apply, to create a scoped value
  • get, to get or create the current value from the scope

To define a dependency as scoped, we need a scope instance, e.g.:

traitWebModule {lazyvalloggedInUser= session(newLoggedInUser)defsession:Scope}

With abstract scopes as above, it is possible to use no-op scopes for testing (NoOpScope).

There's an implementation ofScope targeted at classical synchronous frameworks,ThreadLocalScope. The apply methodof this scope creates a proxy (usingjavassist); the get methodstores the value in a thread local. The proxy should be defined as aval orlazy val.

In a web application, the scopes have to be associated and disassociated with storages.This can be done for example in a servlet filter.To implement a:

  • request scope, we need a new empty storage for every request. TheassociateWithEmptyStorage is useful here
  • session scope, the storage (aMap) should be stored in theHttpSession. Theassociate(Map) method is useful here

For example usage see theMacWire+Scalatra examplesources.

You can run the example withsbt examples-scalatra/run and going tohttp://localhost:8080.

Note that theproxy subproject does not depend on MacWire core, and can be used stand-alone with manual wiring or any otherframeworks.

Accessing wired instances dynamically

Dependency:

"com.softwaremill.macwire"%%"util"%"2.6.6"

To integrate with some frameworks (e.g.Play 2) or to create instances of classeswhich names are only known at run-time (e.g. plugins) it is necessary to access the wired instances dynamically.MacWire contains a utility class in theutil subproject,Wired, to support such functionality.

An instance ofWired can be obtained using thewiredInModule macro, given an instance of a module containing thewired object graph. Anyvals,lazy vals and parameter-lessdefs (factories) from the module which are referenceswill be available in theWired instance.

The object graph in the module can be hand-wired, wired usingwire, or a result of any computation.

Wired has two basic functionalities: looking up an instance by its class (or trait it implements), and instantiatingnew objects using the available dependencies. You can also extendWired with new instances/instance factories.

For example:

// 1. Defining the object graph and the moduletraitDatabaseConnectorclassMysqlDatabaseConnectorextendsDatabaseConnectorclassMyApp {defsecurityFilter=newSecurityFilter()valdatabaseConnector=newMysqlDatabaseConnector()}// 2. Creating a Wired instanceimportcom.softwaremill.macwire._valwired= wiredInModule(newMyApp)// 3. Dynamic lookup of instanceswired.lookup(classOf[SecurityFilter])// Returns the mysql database connector, even though its type is MysqlDatabaseConnector,// which is assignable to DatabaseConnector.wired.lookup(classOf[DatabaseConnector])// 4. Instantiation using the available dependencies{packagecom.softwaremillclassAuthenticationPlugin(databaseConnector:DatabaseConnector)}// Creates a new instance of the given class using the dependencies available in MyAppwired.wireClassInstanceByName("com.softwaremill.AuthenticationPlugin")

Limitations

When:

  • referencing wired values within the trait/class/object
  • using multiple modules in the same compilation unit
  • using multiple modules with scopes

due to limitations of the current macros implementation in Scala (for more details seethis discussion)to avoid compilation errors it is recommended to add type ascriptions to the dependencies. This is a way of helpingthe type-checker that is invoked by the macro to figure out the types of the values whichcan be wired.

For example:

classA()classB(a:A)// note the explicit type. Without it wiring would fail with recursive type compile errorslazyvaltheA:A= wire[A]// reference to theA; if for some reason we need explicitly write the constructor calllazyvaltheB=newB(theA)

This is an inconvenience, but hopefully will get resolved once post-typer macros are introduced to the language.

Also, wiring will probably not work properly for traits and classes defined inside the containing trait/class, or insuper traits/classes.

Note that the type ascription may be a subtype of the wired type. This can be useful if you want to expose e.g. a traitthat the wired class extends, instead of the full implementation.

Akka integration

Dependency:

"com.softwaremill.macwire"%%"macrosakka"%"2.6.6"%"provided"

Macwire provides wiring suport forakka through themacrosAkka module.Hereyou can find example code. The module adds three macroswireAnonymousActor[A],wireActor[A] andwireProps[A]which help create instances ofakka.actor.ActorRef andakka.actor.Props.

These macros require anActoRefFactory (ActorSystem orActor.context) to be in scope as a dependency.If actor's primary constructor has dependencies - they are required to be in scope as well.

Example usage:

importakka.actor.{Actor,ActorRef,ActorSystem}classDatabaseAccess()classSecurityFilter()classUserFinderActor(databaseAccess:DatabaseAccess,securityFilter:SecurityFilter)extendsActor {overridedefreceive:Receive= {case m=>// ...  }}importcom.softwaremill.macwire._importcom.softwaremill.macwire.akkasupport._valtheDatabaseAccess= wire[DatabaseAccess]//1st dependency for UserFinderActor//it compiles to: val theDatabaseAccess = new DatabaseAccessvaltheSecurityFilter= wire[SecurityFilter]//2nd dependency for UserFinderActor//it compiles to: val theSecurityFilter = new SecurityFiltervalsystem=ActorSystem("actor-system")//there must be instance of ActoRefFactory in scopevaltheUserFinder= wireActor[UserFinderActor]("userFinder")//this compiles to://lazy val theUserFinder = system.actorOf(Props(classOf[UserFinderActor], theDatabaseAccess, theSecurityFilter), "userFinder")

In order to make it working all dependencies createdActor's (UserFinderActor's) primary constructor andinstance of theakka.actor.ActorRefFactory must be in scope. In above example this is all true. Dependenciesof theUserFinderActor areDatabaseAccess andSecurityFilter and they are in scope.TheActorRefFactory is in scope as well becauseActorSystem which is subtype of it is there.

Creating actor within another actor is even simpler than in first example because we don't need to haveActorSystem in scope.TheActorRefFactory is here becauseActor.context is subtype of it. Let's see this in action:

classUserStatusReaderActor(theDatabaseAccess:DatabaseAccess)extendsActor {valtheSecurityFilter= wire[SecurityFilter]valuserFinder= wireActor[UserFinderActor]("userFinder")//this compiles to://val userFinder = context.actorOf(Props(classOf[UserFinderActor], theDatabaseAccess, theSecurityFilter), "userFinder")overridedefreceive= ...}

The difference is that previously macro expanded intosystem.actorOf(...)and when inside another actor it expanded intocontext.actorOf(...).

It's possible to create anonymous actors.wireAnonymousActor is for it:

valuserFinder= wireAnonymousActor[UserFinderActor]//this compiles to://val userFinder = context.actorOf(Props(classOf[UserFinderActor], theDatabaseAccess, theSecurityFilter))

How about creatingakka.actor.Props? It's there and can be achieved by callingwireProps[A].Wiring onlyProps can be handy when it's required to setup theProps before passing them to theactorOf(...) method.

Let's say we want to create some actor with router. It can be done as below:

valuserFinderProps= wireProps[UserFinderActor]//create Props//This compiles to: Props(classOf[UserFinderActor], theDatabaseAccess, theSecurityFilter)  .withRouter(RoundRobinPool(4))//change it according requirementsvaluserFinderActor= system.actorOf(userFinderProps,"userFinder")//create the actor

How about creating actors which depends onActorRefs? The simplest way is topass them as arguments to the constructor. But how to distinguish twoactorRefs representing two different actors?They have the same type though.

classDatabaseAccessActorextendsActor { ... }classSecurityFilterActorextendsActor { ... }valdb:ActorRef= wireActor[DatabaseAccessActor]("db")valfilter:ActorRef= wireActor[SecurityFilterActor]("filter")classUserFinderActor(databaseAccess:ActorRef,securityFilter:ActorRef)extendsActor {...}//val userFinder = wireActor[UserFinderActor] wont work here

We can't just callwireActor[UserFinderActor] because it's not obvious which instance of ActorRefis fordatabaseAccess and which are forsecurityFilter. They are both of the same type -ActorRef.

The solution for it is to use earlier describedqualifiers.In above example solution for wiring may look like this:

sealedtraitDatabaseAccess//marker typeclassDatabaseAccessActorextendsActor { ... }sealedtraitSecurityFilter//marker typeclassSecurityFilterActorextendsActor { ... }valdb:ActorRef@@DatabaseAccess= wireActor[DatabaseAccessActor]("db").taggedWith[DatabaseAccess]valfilter:ActorRef@@SecurityFilter= wireActor[SecurityFilterActor]("filter").taggedWith[SecurityFilter]classUserFinderActor(databaseAccess:ActorRef@@DatabaseAccess,securityFilter:ActorRef@@SecurityFilter)extendsActor {...}valuserFinder= wireActor[UserFinderActor]

It is also possible to wire an actor using a factory function.For that, the module provides three additional macroswireAnonymousActorWith,wireActorWith andwirePropsWith.Their usage is similar towireWith (seeFactory methods).For example:

classUserFinderActor(databaseAccess:DatabaseAccess,securityFilter:SecurityFilter)extendsActor { ... }objectUserFinderActor {defget(databaseAccess:DatabaseAccess)=newUserFinderActor(databaseAccess,newSimpleSecurityFilter())}valtheUserFinder= wireActorWith(UserFinderActor.get _)("userFinder")

Multi Wiring (wireSet)

UsingwireSet you can obtain a set of multiple instances of the same type. This is done without constructing the set explicitly. All instances of the same type which are found by MacWire are used to construct the set.

Consider the below example. Let's suppose that you want to create aRockBand(musicians: Set[Musician]) object. It's easy to do so using thewireSet functionality:

traitMusicianclassRockBand(musicians:Set[Musician])traitRockBandModule {lazyvalsinger=newMusician {}lazyvalguitarist=newMusician {}lazyvaldrummer=newMusician {}lazyvalbassist=newMusician {}lazyvalmusicians= wireSet[Musician]// all above musicians will be wired together// musicians has type Set[Musician]lazyvalrockBand= wire[RockBand]}

Autowire for cats-effect

Scala 2cats-effect

Warning:autowire is an experimental feature, if you have any feedback regarding its usage, let us know! Future releases might break source/binary compatibility. It is available for Scala 2 only for now.

Dependency:"com.softwaremill.macwire" %% "macrosautocats" % "2.6.6"

In case you need to build an instance from some particular instances and factory methods you can leverageautowire. This feature is intended to integrate with effect-management libraries (currently we supportcats-effect).

autowire takes as an argument a list of arguments which may contain:

  • values (e.g.new A())
  • factory methods (e.g.C.create _)
  • factory methods that returncats.effect.Resource orcats.effect.IO (e.g.C.createIO _)
  • cats.effect.Resource (e.g.cats.effect.Resource[IO].pure(new A()))
  • cats.effect.IO (e.g.cats.effect.IO.pure(new A()))

Using the dependencies from the given arguments it creates an instance of the given type. Any missing instances are created using their primary constructor, provided that the dependencies are met. If this is not possible, a compile-time error is reported. In other words, awireRec is performed, bypassing the instances search phase.

The result of the wiring is always wrapped incats.effect.Resource. For example:

importcats.effect._classDatabaseAccess()classSecurityFilterprivate (databaseAccess:DatabaseAccess)objectSecurityFilter {defapply(databaseAccess:DatabaseAccess):SecurityFilter=newSecurityFilter(databaseAccess)}classUserFinder(databaseAccess:DatabaseAccess,securityFilter:SecurityFilter)classUserStatusReader(databaseAccess:DatabaseAccess,userFinder:UserFinder)objectUserModule {importcom.softwaremill.macwire.autocats._valtheDatabaseAccess:Resource[IO,DatabaseAccess]=Resource.pure(newDatabaseAccess())valtheUserStatusReader:Resource[IO,UserStatusReader]= autowire[UserStatusReader](theDatabaseAccess)}

will generate:

[...]objectUserModule {importcom.softwaremill.macwire.autocats._valtheDatabaseAccess:Resource[IO,DatabaseAccess]=Resource.pure(newDatabaseAccess())valtheUserStatusReader:Resource[IO,UserStatusReader]=UserModule.this.theDatabaseAccess.flatMap(    da=>Resource.pure[IO,UserStatusReader](newUserStatusReader(da,newUserFinder(da,SecurityFilter.apply(da))))  )}

Interceptors

Dependency:

"com.softwaremill.macwire" %% "proxy" % "2.6.6"

MacWire contains an implementation of interceptors, which can be applied to class instances in the modules.Similarly to scopes, theproxy subproject defines anInterceptor trait, which has only one method:apply.When applied to an instance, it should return an instance of the same class, but with the interceptor applied.

There are two implementations of theInterceptor trait provided:

  • NoOpInterceptor: returns the given instance without changes
  • ProxyingInterceptor: proxies the instance, and returns the proxy. A provided function is calledwith information on the invocation

Interceptors can be abstract in modules. E.g.:

traitBusinessLogicModule {lazyvalmoneyTransferer= transactional(wire[MoneyTransferer])deftransactional:Interceptor}

During tests, you can then use theNoOpInterceptor. In production code or integration tests, you can specify a realinterceptor, either by extending theProxyingInterceptor trait, or by passing a function to theProxyingInterceptor object:

objectMyApplicationextendsBusinessLogicModule {lazyvaltm= wire[TransactionManager]lazyvaltransactional=ProxyingInterceptor { ctx=>try {      tm.begin()valresult= ctx.proceed()      tm.commit()      result    }catch {casee:Exception=> tm.rollback()    }  }}

Thectx is an instance of anInvocationContext, and contains information on the parameters passed to the method,the method itself, and the target object. It also allows to proceed with the invocation with the same or changedparameters.

For more general AOP, e.g. if you want to apply an interceptor to all methods matching a given pointcut expression,you should useAspectJ or an equivalent library. The interceptors that are implementedin MacWire correspond to annotation-based interceptors in Java.

Qualifiers

Note

While the below works both in Scala 2 & Scala 3, in Scala 3, you can use the built-inopaque typesinstead.

Sometimes you have multiple objects of the same type that you want to use during wiring. Macwire needs to have someway of telling the instances apart. As with other things, the answer is: types! Even when not usingwire, it maybe useful to give the instances distinct types, to get compile-time checking.

For that purpose Macwire includes support for tagging viascala-common,which lets you attach tags to instances to qualify them. Thisis a compile-time only operation, and doesn't affect the runtime. The tags are derived fromMiles Sabin's gist.

To bring the tagging into scope, importcom.softwaremill.tagging._.

Using tagging has two sides. In the constructor, when declaring a dependency, you need to declare what tag it needsto have. You can do this with the_ @@ _ type constructor, or if you prefer another syntaxTagged[_, _]. The firsttype parameter is the type of the dependency, the second is a tag.

The tag can be any type, but usually it is just an empty marker trait.

When defining the available instances, you need to specify which instance has which tag. This can be done with thetaggedWith[_] method, which returns a tagged instance (A.taggedWith[T]: A @@ T). Tagged instances can be usedas regular ones, without any constraints.

Thewire macro does not contain any special support for tagging, everything is handled by subtyping. For example:

classBerry()traitBlacktraitBluecaseclassBasket(blueberry:Berry@@Blue,blackberry:Berry@@Black)lazyvalblueberry= wire[Berry].taggedWith[Blue]lazyvalblackberry= wire[Berry].taggedWith[Black]lazyvalbasket= wire[Basket]

Multiple tags can be combined using theandTaggedWith method. E.g. if we had a berry that is both blue and black:

lazyvalblackblueberry= wire[Berry].taggedWith[Black].andTaggedWith[Blue]

The resulting value has typeBerry @ (Black with Blue) and can be used both as a blackberry and as a blueberry.

Development

Debugging

To print debugging information on what MacWire does when looking for values, and what code is generated, set themacwire.debug system property. E.g. with SBT, start usingsbt -Dmacwire.debug.

Future development - vote!

Take a look at theavailable issues. If you'd like to see one developedplease vote on it. Or maybe you'll attempt to create a pull request?

Platform and version-specifics

Scala.js

Macwire also works withScala.js. For an example, see here:Macwire+Scala.js example.

Scala3 support

The Scala 3 version is written to be compatible with Scala 2 where possible. Currently there are a few missing features:

For full list of incompatibilities take a look attests/src/test/resources/test-cases andutil-tests/src/test/resources/test-cases .

Other

Commercial Support

We offer commercial support for MacWire and related technologies, as well as development services.Contact us to learn more about our offer!

Copyright

Copyright (C) 2013-2025 SoftwareMillhttps://softwaremill.com.


[8]ページ先頭

©2009-2025 Movatter.jp