依赖注入和面向切面编程是Spring框架的两个核心特性,理解这两个特性对于我们理解整个Spring框架会有很大的帮助。对于初学者来说,这两个概念往往让人望而生畏,但实际上它们的基本原理并不复杂。
依赖注入
在规模稍大的系统中,往往会出现许多类进行相互调用来完成目标功能。我们以司机类和驾驶类为例,按照传统写法应当是司机类中调用驾驶类的方法。而通过依赖注入的方法将驾驶类注入到司机类中则可以避免司机类中直接创建驾驶类对象。
假设有如下代码来实现司机驾驶汽车前进功能:
public class Pilot implements Person{
private ForwardDrive drive;
public Pilot(){
this.drive = new ForwardDrive(); //司机(P)与驾驶(D)耦合
}
public void drive(){
drive.forward(); //前进
}
}
在代码中可以看出司机类和驾驶类耦合在了一起,因此司机的驾驶能力被极大地限制了:他无法执行停车、倒车等功能。而且,如果驾驶中的前进方法出现了问题,那么司机类也会受到影响。
从上面的代码来看,驾驶汽车前进更像是司机的“主动技能”,需要它自身来调用驾驶类中的前进方法才能完成目标。通过依赖注入的方式,我们可以把驾驶汽车变为司机的“被动技能”,实现司机和驾驶类的解耦合。下面来看一下代码:
public class Pilot implements Person{
```
private Dirve dirve;
public Pilot(Drive drive){
this.drive = drive;
}
public void drive(){
drive.forward();
}
}
Pilot类没有自己创建驾驶任务,而是在构造的时候将驾驶任务作为参数传入(构造器注入)。
public class forwardDrive implements Drive{
```
public void forward(){
System.out.print("前进");
}
}
上面两个类分别是司机类和驾驶类,可以看到在司机类中并没有直接创建驾驶类并调用前进方法。为了将驾驶类注入到司机类中,Spring使用一个xml文件将它们装配(Wiring)起来。
···
<beans>
<bean id="pilot">
<constructor-arg ref="drive">
</bean>
<bean id="drive">
<constructor-arg value="forw">
</bean>
</beans>
···
这里,Pilot和Dirve被声明为Spring中的bean。Spring通过Application context来查询xml文件,根据其中的内容来组装bean。在实际调用中可以直接用pilot.forwardDrive()
来实现司机驾驶汽车前进的功能。这样就完成了Pilot和Dirve的分离。依赖注入可以将两个有依赖关系的类放到容器中实例化,实现类的解耦。
切面
系统中存在业务模块和功能模块(日志模块、安全模块),切面的功能就是把遍布系统各处的功能性代码分离出来。
我们还以上面的类为例。假设为了安全起见,汽车前进后需要自动落锁。按照传统方式实现的伪代码如下:
Class Pilot{
public void dirve() throws exception{
前进();
lock();
}
}
Class Car{
public void lock() {
```
}
}
观察后发现,在Pilot类中的前进方法下调用lock()
方法真的合适吗?自动落锁的功能不是应该由汽车自己实现,而不需要司机参与。将Car抽象为一个切面,Spring框架还是在一个xml文件中进行声明:
···
<beans>
<bean id="pilot">
<constructor-arg ref="drive">
</bean>
<bean id="drive">
<constructor-arg value="forw">
</bean>
首先将car声明为一个bean,再添加aop声明
<bean id="car" ```/>
<aop:config>
<aop:pointcut id="car ```/">
<aop:after opintcut="car" method="lock"/> 后置声明:在前进()调用后执行落锁()
</aop:config>
</beans>
···
这样就不必在Pilot类中调用lock()方法,而是Srping会自动帮你装配好。切面可以将分散的功能性模块集中到一起,使服务模块更加简洁。