天天看点

Spring之IOC和AOPIOCAOP触类旁通

Spring是一个开源框架,主要实现两件事,IOC(控制反转)和AOP(面向切面编程)。

IOC

所谓控制反转,就是控制权的转移,举例说明:一个人要开车,正常情况下,人应该自己去找车,而实现控制反转后,人就不需要考虑车从哪里来了,直接开就行了,人就把找车的控制权转移给了别的对象。体会一下下面的代码

先定义一个接口Car

public interface Car {
    void go();
}
           

定义两种车

public class Benz implements Car {

    public void go() {
        System.out.println("benz go......");
    }
}

public class BMW implements Car{

    public void go() {
        System.out.println("bmw go......");
    }
}
           

下面是人开车

public class Person {

    Car car=new Benz();

    void DriveCar(){
        System.out.println("begin drive");
        car.go();
    }
}
           

这是正常的代码控制流程,人想要开车,就要自己去实例化一辆车。但是,这样子的话,这个人就只能开一种车。怎么样才能让这个人能开各种车呢,就是要实现控制反转,也就是说,人不再去自己实例化车了,那人怎样得到车的对象呢?我们可以通过依赖注入(Dependency Injection,简称DI)的方式来让人得到车的对象,从而实现控制反转。所以,我们要修改Person类

public class Person {

    Car car=null;
    public Person(Car car){
        this.car=car;
    }

    void driveCar(){
        System.out.println("begin drive");
        car.go();
    }
}
           

现在的Person类已经不自己实例化车的对象了,而是通过构造函数来获得车的对象,所以,这个类就可以开各种车了,只要这个车实现了Car接口就可以。看一下如何使用Person类

public static void main(String[] args) {
    Person p=new Person(new Benz());
    p.driveCar();
}
           

现在的Person类可以开不止一种车,只要你通过构造函数传递进来。在这个例子中,Car对象就是Person类的依赖,当我们实例化Person类时,将一个Car的实例传递给Person类,就是依赖注入,我们的Person类从而实现了控制反转。

控制反转到底反转了什么?有种说法是这样的:所谓控制反转,反转的是获取对象依赖的过程。控制权反转后,获取依赖对象的过程由自身管理变为由IOC容器注入。

Spring实现依赖注入的方式

在上面的这行代码中

Person p=new Person(new Benz());

,我们通过手动的方式new了一个Benz()的对象,然后将其注入到Person类中。而Spring不这么干,因为Spring觉得,你这行代码实例化了一个具体的Benz类,如果你以后想要在这里实例化一个BMW类的话,岂不是要修改代码?那我干脆写到配置文件里好了,即便你将来要改主意,至少不需要修改代码,于是就有了下面的配置

<beans>
    <bean id="car" class="com.XXX.Benz" />
    <bean id="person" class="com.XXX.Person" >
        <property name="car" ref="car" />
    <bean/>
</beans>
           

然后,Spring再提供一些机制,从配置文件中获取Person类的对象时,它所依赖的car对象会被装配进来,而person对象不需要关心到底是哪个具体的类被传递进来了。所以,Spring作为一个IOC框架主要做了两步:创建对象和组装对象之间的关系。

AOP

AOP(Aspect Oriented Programming)是面向切面编程,下面我来举例说明什么是切面。在一个完整的网站项目中,很多模块都需要做日志记录,很多地方都需要做登录判断,很多地方都需要做异常处理。日志记录,登录判断,异常处理等这些逻辑,就是所谓的切面。假设我将这些切面的逻辑写得到处都是,那么代码的可维护性就可想而知了。AOP就是为了实现关注点分离,将这些切面的逻辑抽出来写到单独的类中,然后再想办法将他们与一般的模块组装到一块来执行,普通模块甚至都不知道他们已经和切面组装到一块了。

看下面的代码,看不懂没关系

<bean id="audience" class="com.springinaction.springidol.Audience" />
<aop:config>
  <aop:aspect ref="audience">
    <aop:pointcut id="performance" expression=
        "execution(* com.springinaction.springidol.Performer.perform(..))" 
        />

    <aop:before 
        pointcut-ref="performance"
        method="takeSeats" /> <!--<co id="co_refPointcut"/>-->
    <aop:before 
        pointcut-ref="performance"
        method="turnOffCellPhones" /> <!--<co id="co_refPointcut"/>-->
    <aop:after-returning
        pointcut-ref="performance" 
        method="applaud" /> <!--<co id="co_refPointcut"/>-->
    <aop:after-throwing 
        pointcut-ref="performance" 
        method="demandRefund" /> <!--<co id="co_refPointcut"/>-->
  </aop:aspect>
</aop:config>
           

上面配置的大概意思是,当Performer.perform方法将要发生时,Spring框架中的代理会将目标方法(Performer.perform())拦截下来,执行目标方法前先执行Audience.takeSeats()和Audienceturn.OffCellPhones()方法,然后运行目标方法,当目标方法执行完毕返回时,再运行Audienceturn.applaud()方法。如果目标方法不幸抛出了异常,代理会运行Audienceturn.demandRefund()方法。总之,Spring的代理类全方位地监控了目标方法的执行,而目标方法只专注于自己的事情,甚至都不知道代理类的存在。

触类旁通

编程的思想总是惊人的相似。我的博客中有和IOC以及AOP相关的博客,可以参考一下。关于IOC的可以参考策略模式,关于AOP,可以参考ASP.NET MVC Filters。