控制反转IoC
Spring的核心概念是IoC,抽象概念是依赖关系的转移,控制反转意思就是说,当我们调用一个方法或者类时,不再由我们主动去创建这个类的对象,控制权交给别人(spring)。从对象的角度来说,可以避免对象之间的耦合,从容器的角度来说,可以避免应用程序因依赖于容器的功能,而从容器脱离。简单地说,在进行模块设计时,高层的抽象模块通常是与业务逻辑相关的模块,它应该具有重用性,而不依赖于低层的实现模块,如果高层模块直接调用低层模块的函数,就对低层模块产生了依赖关系。IoC模式基本上是一个高层的模式概念,实现IoC有两种方式:Dependency injection与Service Locator,Spring采用的是Dependency injection来实现,翻译即依赖注入。
依赖注入DI
依赖注入的意义是:保留抽象接口,让组件依赖于抽象接口,当组件要与其他实际的对象发生依赖关系时,由抽象接口来注入依赖的实际对象。若以对象导向的方式来设计,依赖注入解释为“程序不应依赖于实现,而是依赖于抽象接口”。依赖注入有三种基本实现方式:Interface injection、Setter injection与Construction injection,分别称为接口注入、setter方法注入与构造方法注入。
IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin?Fowler又给出了一个新的名字:“依赖注入”,相对IoC?而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
1.接口注入
使用接口注入会要求实现接口,对象所在的容器也会使用这个接口,容器知道接口上所规定的方法,所以可以调用实现接口的对象来完成依赖关系的注入,例如
public class ClassA {
private InterfaceB clzB;
public void doSomething() {
Ojbect obj = Class.forName(Config.ImplementationB).newInstance();
clzB = (InterfaceB)obj;
clzB.doIt();
}
……
}
ClassA依赖于InterfaceB的实现,我们如何获得InterfaceB的实现实例呢?传统的方法是在代码中创建 InterfaceB实现类的实例,并将赋予clzB.这样一来,ClassA在编译期即依赖于InterfaceB的实现。为了将调用者与实现者在编译期分离,于是有了上面的代码。我们根据预先在配置文件中设定的实现类的类名(Config.ImplementationB)动态加载实现类,并通过InterfaceB强制转型后为ClassA所用,这就是接口注入的一个最原始的雏形。
2.setter方法注入
即当前对象只需要为其依赖对象添加setter方法,IOC容器通过此setter方法将相应的依赖对象设置到被注入对象的方式即setter方法注入。
public class Business {
private IDeviceWriter writer;
public void setWriter(IDeviceWriter writer) {
this.writer = writer;
}
public void save() {
writer.saveToDevice();
}
}
3.构造方法注入
即被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IOC容器)知道它需要哪些依赖对象,然后IOC容器会检查被注入对象的构造方法, 取得其所需要的依赖对象列表,进而为其注入相应对象。使用构造方法注入的好处是可以在构造对象的同时建立依赖关系,然而如果建立的对象关系很多,这时候使用Setter来注入依赖关系更合理。
public class Business {
private IDeviceWriter writer;
public Business(IDeviceWriter writer) {
this.writer = writer;
}
public void save() {
writer.saveToDevice();
}
}
接口注入 && setter注入 && 构造器注入
接口注入:接口注入模式因为具备侵入性,它要求组件必须与特定的接口相关联,因此并不被看好,实际使用有限。
Setter 注入:对于习惯了传统 javabean 开发的程序员,通过 setter 方法设定依赖关系更加直观。如果依赖关系较为复杂,那么构造子注入模式的构造函数也会相当庞大,而此时设值注入模式则更为简洁。如果用到了第三方类库,可能要求我们的组件提供一个默认的构造函数,此时构造子注入模式也不适用。
构造器注入:在构造期间完成一个完整的、合法的对象。所有依赖关系在构造函数中集中呈现。依赖关系在构造时由容器一次性设定,组件被创建之后一直处于相对“不变”的稳定状态。只有组件的创建者关心其内部依赖关系,对调用者而言,该依赖关系处于“黑盒”之中。
AOP面向切面的编程过程
我们把一个方法看做是一个切面,在这个切面的前后或者周围,都可以设置其他的处理方法,进行一些特殊的处理,过程包括:
1.首先我们需要一个bean,用某种方法(设置注入需要有setter函数,构造注入需要有构造函数,相应的bean.xml配置文件也会不同)设定注入方式。2.bean.xml,设置bean与类的关系,并关联默认的注入值。
3.获取bean.xml文件,创建实例对象,直接调用方法。
可以看到,我们的程序只有第三步的“创建实例对象,直接调用方法”,并没有为他进行初始化等工作,就可以直接调用它的方法,获取它的值。其实spring在程序初始化的时候,就会为我们把bean对应的对象注入进来,帮助我们完成初始化,因此我们只要通过一个引用对象,就可以直接调用了。
首先是一个必要的POJO类,用于注入属性的值。
public class HelloWorld {
private String message;
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}
接下来需要创建一个bean 配置文件,该文件是一个XML文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="helloWorld" class="com.aop.HelloWorld">
<property name="message" value="Hello World!"/>
</bean>
</beans>
配置文件以<beans>作为根节点,使用<bean>来为每一个Bean进行设置,id是Bean实例的标识,<property>标签的"helloWorld"设置了setMessage方法的参数名称,并在<value>标签上设置了将要传入的值。
下面是第二个类MainApp.java 的内容:
public class MainApp {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
HelloWorld helloWorld = (HelloWorld) context.getBean("helloWorld");
helloWorld.getMessage();
}
}
运行结果:
参考: