Spring IoC简介
IoC是一个容器,在Spring中,它会认为一切Java资源都是Java Bean,容器的目标就是管理这些Bean和它们之间的关系。所以在Spring IoC里面装载的各种Bean,也可以理解为Java的各种资源,包括Java Bean的创建、事件、行为等,他们由IoC容器管理。除此之外,各个Java Bean之间会存在一定的依赖关系,比如班级是依赖于老师和学生组成的,假设老师、学生都是Java Bean,那么显然二者之间形成了依赖关系,老师和学生有教育和被教育的关系。这些Spring IoC容器都能够对其进行管理。只是Spring IoC管理对象和其依赖关系,采用的不是人为的主动创建,而是由Spring IoC自己通过描述创建的,也就是说Spring是依靠描述来完成对象的创建及其依赖关系的。
比如插座,它依赖国家标准(这个标准可以定义为一个接口,Socket)去定义,现有两种插座(Socket1和Socket2),如图所示:
有两种插座可供选择,具体使用哪种呢?我们可以通过下面的代码来实现使用插座1(Socket1):
Socket socket = new Socket1();
user.setSocket(socket);
user.useSocket();
使用Socket socket = new Socket1();后,国家插座标准接口(Socket)就和插座1(Socket1)捆绑在一起了。这样就会有一个弊端:如果要使用其他的插座,就需要修改代码了。这种情况Socket接口和其实现类Socket1耦合了,如果有一天不再使用Socket1,而是要使用Socket2,那么就要把代码修改为如下代码:
Socket socket = new Socket2();
user.setSocket(socket);
user.useSocket();
如果有其他更好的插座,岂不是还要修改源码?一个大型互联网的对象成千上万,如果要不断修改,那么对系统的可靠性将是一个极大的挑战,Spring IoC可以解决这个问题。
首先,我们不用new的方式创建对象,而是使用配置的方式,然后让Spring IoC容器自己通过配置去找到插座。先用一段XML描述插座和用户的引用插座1:
<bean id="socket" class="Socket1"/>
<bean id="user" class="xxx.User">
<property name="socket" ref="socket"/>
</bean>
注意这些不是Java代码,而是XML配置文件,换句话说只要把配置切换为:
<bean id="socket" class="Socket2"/>
就可以往用户信息中注入插座2,切换插座的实现类十分方便。这个时候Socket接口就可以不依赖任何插座,而是通过配置进行切换,如下图所示:
配置信息是“我要插座2”,相当于XML依赖关系配置,这个时候Spring IoC只会拿到插座2,然后通过国家插座标准接口注入给使用者,提供给使用者使用。换句话说,这是一种被动的行为,而需要的资源(Bean)通过描述信息就可以得到,其中的控制权在Spring IoC容器中,它会根据描述找到使用者需要的资源,这就是控制反转的含义。
这样的好处是Socket接口不在依赖于某个实现类,需要使用某个实现类时我们通过配置信息就可以完成了。这样想修改或者加入其他资源就可以通过配置完成,不需要再用new关键字进行创建对象,依赖关系也可以通过配置完成,从而完全可以即插即拔的管理它们之间的关系。
你不需要去找资源,只要向Spring IoC容器描述所需资源,Spring IoC自己会找到你所需要的资源,这就是Spring IoC的理念。这样就把Bean之间的依赖关系解耦了,更容易写出结构清晰的程序。除此之外,Spring IoC还提供对Java Bean生命周期的管理,可以延迟加载,可以在其他生命周期内定义一些行为等,更加方便有效地使用和管理Java资源,这就是Spring IoC的魅力。
Spring AOP简介
IoC的目标就是为了管理Bean,而Bean是Java面向对象(OOP)的基础设计,比如声明了一个用户类、插座类等都是基于面向对象的概念。
有些情况是面向对象没办法处理的。举个例子,生产部门的订单、生产部门、财务部门三者符合OOP的设计理念。订单发出,生产部门审批通过准备付款,但是财务部门发现订单的价格超支了,需要取消订单。显然超支限定已经不是影响财务部门了,还会影响生产部门之前所做的审批,需要把它们作废。我们把预算超支这个条件称为切面,它影响了订单、生产部门和财务部门3个OOP对象。在现实中,这样的切面条件跨越了3个甚至更多的对象,并且影响了他们的协作。所以只用OOP并不完善,还需要面向切面的编程,通过它去管理在切面上的某些对象之间的协作,如下图所示:
上图中,实线是订单提交的流程,虚线是订单驳回的流程,影响它们的条件是预算超额,这是一个切面条件。
Spring AOP常用于数据库事务的编程,很多情况如同上面的例子,我们在做完第一步数据库数据更新后,不知道下一步是否会成功,如果下一步失败,会使用数据库事务的回滚功能去回滚事务,使得第一步的数据库更新也作废。在Spring AOP实现的数据库事务管理中,是以异常作为消息的。在默认的情况下(可以通过Spring的配置修改),只要Spring接收到了异常信息,它就会将数据库的事务回滚,从而保证数据的一致性。这样我们就知道在Spring的事务管理中只要让它接收到异常信息,它就会回滚事务,而不需要通过代码来实现这个过程。比如上面的例子,可以用一段伪代码来进行一些必要的说明,如下所示:
/**
* Spring AOP 处理订单伪代码
* @param order 订单
**/
private void proceed(Order order) {
// 判断生产部门是否通过订单,数据库记录订单
boolean pflag = productionDept.idPass(order);
if (pflag) { // 如果生产部门通过,进行财务部门审批
if (financialDept.isOverBudget(order)) { // 财务审批是否超限
// 抛出异常回滚事务,之前的订单操作也会被回滚
throw new RuntimeException("预算超限!!");
}
}
}
这里我们完全看不到数据库代码,也没有复杂的try… catch…finally…语句。在现实中Spring AOP的编程也是如此,这些东西都被Spring屏蔽了,不需要关注它,只需关注业务代码,知道知道发生了异常,Spring会回滚事务就足够了。当然这段话还不算准确,因为事务和业务是十分复杂的,但是Spring已经提供了隔离级别和传播行为去控制它们。有了Spring的这些封装,开发人员就可以减少很多的代码和不必要的麻烦。