Inject
是一个 Swift 依赖注入容器,它采用了 Spring
的基本想法(尽可能实现),并且利用 Swift 语言特性(如闭包)来提供一个简单直观的 API。
除了核心功能,还实现了一些其他概念
但让我们再次回到依赖容器吧::)
基本思想是有一个中心对象,它了解所有不同类型的对象和对象依赖,并且其任务是通过对字段(通过属性设置器、方法或适当的构造函数调用)进行填充来适当地实例化和组装它们。类不必了解当前基础设施的任何信息——例如协议的实现细节或特定的配置值——因为这些知识完全由容器负责并注入到类中。
如果您考虑单元测试,其中服务实现需要通过某种本地变体(例如模拟)进行交换,那么您就能体会到它的好处。
另一个主要好处是,对象的生存周期也由一个中心实例管理。一方面,这避免了在代码中到处使用单例模式——这简直是混乱——另一方面,它还允许其他功能,例如会话作用域的对象,或者调用关闭整个容器——释放资源——的功能。
以下是支持的功能摘要
@Inject
自动修复机制的注入singleton
和 protoype
作为内置口味postConstruct
)BeanPostProcessor
的FactoryBean
的${property=<default>}
)有关详细信息,请访问
让我们看看一些简单的示例。
let environment = try! Environment(name: "my first environment")
try! environment
// a bar created by the default constructor
.define(environment.bean(Bar.self, factory: Bar.init))
// a foo that depends on bar
.define(environment.bean(Foo.self, factory: {
return Foo(bar: try! environment.getBean(Bar.self))
}).requires(class: Bar.self))
// get goin'
.startup()
在环境配置后,可以通过getBean()
函数简单地检索Bean。
let foo = try environment.getBean(Foo.self)
在幕后,所有Bean定义都将进行验证,例如查找循环依赖或无法解决的依赖性,并且所有单例Bean都将被立即构建。
其他注入—这里是指属性注入—可以通过流畅接口表达
environment.define(environment.bean(Foo.self, id: "foo-1")
.property("name", value: "foo")
.property("number", value: 7))
注入
Java @Inject
注解的类似概念已经提供,允许您在类级别上定义注入。
public class AbstractConfigurationSource : NSObject, Bean, BeanDescriptorInitializer, ... {
// MARK: instance data
...
var configurationManager : ConfigurationManager? = nil // injected
...
// MARK: implement BeanDescriptorInitializer
public func initializeBeanDescriptor(beanDescriptor : BeanDescriptor) {
beanDescriptor["configurationManager"].inject(InjectBean())
}
// MARK: implement Bean
// we know, that all injections have been executed....
public func postConstruct() throws -> Void {
try configurationManager!.addSource(self)
}
作用域
作用域确定Bean实例何时以及创建多少次。
示例:
environment.define(environment.bean(Foo.self)
.scope("prototype")
.property("name", value: "foo")
.property("number", value: 7))
可以通过在当前环境中定义实现类(例如会话作用域)简单地添加其他作用域。
懒加载Bean
标记为懒加载的Bean将在第一次请求后构建。
工厂Bean
工厂Bean是实现了特定协议的Bean,它依次创建其他Bean。
environment
.define(environment.bean(FooFactory.self)
.property("someProperty", value: "...") // configure the factory....
.target(Foo.self) // i will create foo's
)
let foo = environment.getBean(Foo.self) // is created by the factory!
抽象Bean
可以定义一个Bean骨架(可能隐藏丑陋的技术参数),然后通过添加缺失的部分由程序员完成配置。
environment
// a template
.define(environment.bean(Foo.self, id: "foo-template", abstract: true)
.property("url", value: "...")
.property("port", value: 8080))
// the concrete bean
.define(environment.bean(Foo.self, parent: "foo-template")
.property("missing", value: "foo") // add missing properties
)
通常,模板是父环境的一部分,用于分离技术项。
Bean后处理器
Bean后处理器是实现了特定协议的类,用于在容器中调用以修改将被构建的实例。
生命周期回调
实现不同协议的类可以实现,当容器创建实例时将由容器调用。最重要的是在创建实例并在所有后处理器执行完毕后执行的postConstruct
。
配置值
每个容器定义了一个中央注册表,用于维护来自不同来源的配置值,可以通过统一的API检索。
let environment = ...
environment.addConfigurationSource(ProcessInfoConfigurationSource()) // process info
environment.addConfigurationSource(PlistConfigurationSource(name: "Info")) // will read Info.plist in the current bundle
// retrieve some values
environment.getConfigurationValue(Int.self, key: "SIMULATOR_MAINSCREEN_HEIGHT", defaultValue: 100) // implicit conversion!
XML配置
对于所有XML爱好者(:-)),提供了一个用于原始(至少是其子集)Spring Schema的XML解析器。
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns:configuration="http://www.springframework.org/schema/configuration"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/configuration http://www.springframework.org/schema/util/spring-util.xsd">
<!-- configuration values are collected from different sources and can be referenced in xml and the api -->
<!-- in addition to static values dynamic sources are supported that will trigger listeners or simply change injected values on the fly-->
<!-- here are some examples for sources -->
<!-- builtin namespace & corresponding handler to set configuration values -->
<configuration:configuration namespace="com.foo">
<configuration:define key="bar" type="Int" value="1"/>
</configuration:configuration>
<!-- other supported sources are -->
<!-- the current process info values, e.g. "PATH" -->
<bean class="Inject.ProcessInfoConfigurationSource"/>
<!-- a plist -->
<bean class="Inject.PlistConfigurationSource">
<property name="name" value="Info"/>
</bean>
<!-- a post processor will be called by the container after construction giving it the chance -->
<!-- to modify it or completely exchange it with another object ( proxy... ) -->
<!-- here we simple print every bean on stdout...:-) -->
<bean class="SamplePostProcessor"/>
<!-- create some foo's -->
<!-- depends-on will switch the instantiation order -->
<bean id="foo-1" class="Foo" depends-on="foo-2">
<property name="id" value="foo-1"/>
<!-- references an unknown configuration key, which will set the default value instead... -->
<property name="number" value="${dunno=1}"/>
</bean>
<bean id="foo-2" class="Foo">
<property name="id" value="foo-2"/>
<!-- same thing recursively -->
<property name="number" value="${dunno=${wtf=1}}"/>
</bean>
<!-- scope prototype means that whenever the bean is requestd a new instance will be created ( default scope is singleton ) -->
<!-- other scopes yould be easily added, e.g. a session scope... -->
<bean id="foo-prototype" class="Foo" scope="prototype">
<property name="id" value="foo-prototype"/>
<!-- this should work... the : separates the namespace from the key! -->
<property name="number" value="${com.foo:bar}"/>
</bean>
<!-- bar will be injected by all foo's. Obviously the bar needs to be constructed first -->
<bean id="bar-parent" class="Bar" abstract="true">
<property name="magic" value="4711"/>
</bean>
<!-- will inherit the magic number -->
<!-- lazy means that it will be constructed when requested for the first time -->
<bean id="bar" class="Bar" parent="bar-parent" lazy="true">
<property name="id" value="bar"/>
</bean>
<!-- both foo's will inject the bar instance -->
<!-- baz factory will create Baz instances... -->
<bean class="BazFactory" target="Baz">
<property name="name" value="factory"/>
<!-- will be set as the baz id... -->
<property name="id" value="id"/>
</bean>
<!-- bazongs -->
<bean id="bazong-1" class="Bazong">
<property name="id" value="id"/>
<!-- by reference -->
<property name="foo" ref="foo-1"/>
</bean>
<bean id="bazong-2" class="Bazong">
<property name="id" value="id"/>
<!-- in-place -->
<property name="foo">
<bean class="Foo">
<property name="id" value="foo-3"/>
<property name="number" value="1"/>
</bean>
</property>
</bean>
</beans>
var environment = Environment(name: "environment")
var data : NSData = ...
environment
.loadXML(data)
.startup()
除了注入容器之外,还实现并集成了日志框架。
一旦配置了单例
// a composition of the different possible log entry constituents
let formatter = LogFormatter.timestamp("dd/M/yyyy, H:mm:s") + " [" + LogFormatter.logger() + "] " + LogFormatter.level() + " - " + LogFormatter.message()
let consoleLogger = ConsoleLog(name: "console", formatter: formatter, synchronize: false)
LogManager()
.registerLogger("", level : .OFF, logs: [QueuedLog(name: "console", delegate: consoleLogger)]) // root logger
.registerLogger("Inject", level : .WARN) // will inherit all log destinations
.registerLogger("Inject.Environment", level : .ALL)
提供了常用的方法
// this is usually a static var in a class!
var logger = LogManager.getLogger(forClass: MyClass.self) // will lookup with the fully qualified name
logger.warn("ouch!") // this is a autoclosure!
logger.fatal(SomeError(), message: "ouch")
使用ErrorType
参数调用error
和fatal
函数。这两个函数将发出包含原始消息、错误表示法和当前堆栈跟踪的消息。
提供的日志目标包含
队列日志目标使用分发队列。默认情况下,将创建一个序列队列,其目的是简单地序列化条目。在这种情况下,'synchronize: false'可以防止控制台操作与互斥锁同步
根据具体的bean定义,可能需要相应的类继承自 NSObject
。这一限制是由于Swift缺乏反射支持。一旦语言发展,我将改变这一点...
本项目采用MIT许可证发布。有关详情请参阅LICENSE。