Spring 2.0宝典读书笔记

来源:百度文库 编辑:神马文学网 时间:2024/04/29 01:10:51



1.3  Spring体系介绍

Spring的功能和特点大致上被组织成如下七个部分:

q      核心机制

q      Context容器

q      Web支持

q      MVC框架

q      DAO支持

q      ORM支持

q      面向切面编程支持

核心机制以BeanFactory为基础,管理bean与bean之间的依赖。ApplicationContext容器是BeanFactory功能加强的子接口。Web支持提供Web应用开发的支持。MVC框架则是针对Web应用的MVC思想实现;DAO支持包括一致的异常处理和编程方式;ORM支持则用于与流行的ORM框架的整合;面向切面编程提供与AOP联盟兼容的编程实现。

1.3.1  Spring的核心和Context

Spring以bean的方式组织、管理Java应用中各组件,组件之间的依赖关系松耦合运行良好。这些都得益于Spring的核心机制:依赖注入。Spring使用BeanFactory作为应用中负责生产和管理各组件的工厂,同时也是组件运行的容器。BeanFactory根据配置文件确定容器中bean的实现,管理bean之间的依赖关系。

bean之间的依赖关系由BeanFactory管理,bean的具体实例化过程也由BeanFactory来管理。将bean对bean实现类的依赖解藕,变成对接口的依赖。程序从面向具体类的编程,转向编程面向接口编程。极大地降低应用中组件的耦合。

ApplicationContext是BeanFactory的加强,ApplicationContext接口提供在J2EE应用中的大量增强功能,比如随Web应用启动的自动创建,程序国际化等。

1.3.2 Spring的Web和MVC

Spring的Web框架围绕分发器(DispatcherServlet)设计,DispatcherServlet将请求分发到不同的处理器。Spring的MVC框架提供了清晰的角色划分:控制器,验证器,命令对象,表单对象和模型对象,分发器,处理器映射和视图解析器。Spring支持多种表现层技术:Velocity,XSLT等等,甚至可以直接输出pdf电子文档,或excel文档。

1.3.3 Spring的面向切面编程

面向切面编程(AOP)完善Spring的依赖注入(DI)。面向切面编程在Spring中主要表现为如下几个方面:

q      面向切面编程提供声明式事务管理。

q      Spring支持用户自定义切面。

Spring也能与AspectJ整合。Spring允许通过依赖注入配置AspectJ的切面。

1.3.4 Spring的持久化支持

Spring对各种持久化技术提供一致的编程方式,不管是最直接的JDBC,还是各种流行的ORM框架如:Hibernate,iBATIS,JDO等,Spring都提供一致的异常继承体系。

Spring使用模板封装持久化访问的通用步骤。来自底层数据库的异常都是难以恢复的,因此,Spring将数据库访问的checked异常转换为运行时异常,避免烦琐的try…catch块。 
1.4 Spring的基本设计思想

Spring实现了两种基本设计模式:

q      工厂模式

q      单态模式

Spring容器是实例化和管理全部bean的工厂,工厂模式可将Java对象的调用者从被调用者的实现逻辑中分离出来,调用者只关心被调用者必须满足的某种规则(接口),而不必关心实例的具体实现过程,具体的实现由bean工厂完成。

Spring默认将所有的bean设置成单态模式,即:对所有相同id的bean的请求,都将返回同一个共享实例。单态模式可大大降低Java对象创建和销毁时的系统开销。使用Spring将bean设成单态行为,则无须自己完成单态模式。

1.4.1 单态模式的回顾

单态模式限制了类实例的创建,采用这种模式设计的类,可以保证仅有一个实例,并提供访问该实例的全局访问点。J2EE应用的大量组件,都需要保证一个类只有一个实例。比如数据库引擎访问点只能有一个。更多的时候,为了提高性能,程序应尽量减少Java对象的创建和销毁时开销。使用单态模式可避免Java类的频繁实例化,让相同类的全部实例共享同一内存区。

为了防止单态模式的类被多次实例化,应将类的构造器设成私有的,这样,保证只能通过静态方法获得类实例。而该静态方法则保证每次返回的实例都是同一个,这就需将该类的实例设置成类属性,该属性需要被静态方法访问,因此该属性应设成静态属性。下面给出单态模式的示例代码:

//单态模式测试类

public class SingletonTest

{

    //该类的一个普通属性。

int value ;

//使用静态属性类保存该类的一个实例。

    private static SingletonTest instance;

    //构造器私有化,避免该类被多次实例。

private SingletonTest()

    {

                   System.out.println(“正在执行构造器…”);

    }

    //提供静态方法来返回该类的实例。

public static SingletonTest getInstance()

    {

                   //实例化类实例前,先检查该类的实例是否存在

           if (instance == null)

                   {

                            //如果不存在,则新建一个实例。

                            instance = new SingletonTest();

                   }

                   //返回该类的成员变量:该类的实例。

                   return instance;

    }

         //以下提供对普通属性value的setter和getter方法

    public int getValue()

    {

           return value;

    }

    public void setValue(int values)

    {

           this.value = value;

    }

    public static void main(String[] args)

    {

           SingletonTest  t1 = SingletonTest .getInstance();

           SingletonTest  t2 = SingletonTest .getInstance();

           t2.setValue(9);

                   System.out.println(t1 == t2);

    }

}

根据程序最后的打印结果,可以看出类的两个实例完全相同,这证明:单态模式的类的全部实例是同一共享实例。程序里虽然获得了类的两个实例,但实际上只执行一次构造器,因为对于单态模式的类,不管有多少次的创建实例请求,都只执行一次构造器。

1.4.2 工厂模式的回顾

工厂模式根据调用数据返回某个类的一个实例,此类可能是多个类的某一个类。通常,这些类满足共同的规则(接口)或父类。调用者只关心工厂生产的实例是否满足某种规范,即实现了某个接口;是否可供自己正常调用(调用者仅仅使用)。该模式提供各对象之间清晰的角色划分,降低程序的耦合。

接口产生的全部实例通常实现相同接口,接口里定义全部实例共同拥有的方法,这些方法在不同的实现类中实现方式不同。程序调用者无须关心方法的具体实现,从而降低系统异构的代价。下面是工厂模式的示例代码:

//Person接口定义

public interface Person

{       

         /**

          * @param name 对name打招呼

          * @return 打招呼的字符串

          */

    public String sayHello(String name);

         /**

          * @param name 对name告别

          * @return 告别的字符串

          */

    public String sayGoodBye(String name);

}

该接口定义Person的规范,该接口必须拥有两个方法:能打招呼、能告别。规范要求实现该接口的类必须具有这两个方法:

//American类实现Person接口

public class American implements Person

{

         /**

          * @param name 对name打招呼

          * @return 打招呼的字符串

          */

    public String sayHello(String name)

    {

           return name + ",Hello";

    }

         /**

          * @param name 对name告别

          * @return 告别的字符串

          */

    public String sayGoodBye(String name)

    {

           return name + ",Good Bye";

    }

}

下面是实现Person接口的另一个实现类:Chinese

public class Chinese implements Person

{

         /**

          * @param name 对name打招呼

          * @return 打招呼的字符串

          */

    public String sayHello(String name)

    {

           return name + ",您好";

    }

         /**

          * @param name 对name告别

          * @return 告别的字符串

          */

    public String sayGoodBye(String name)

    {

           return name + ",下次再见";

    }

}

然后看Person工厂的代码:

public class PersonFactory

{

/**

          * 获得Person实例的实例工厂方法

          * @ param ethnic 调用该实例工厂方法传入的参数

          * @ return返回Person实例

          */

public Person getPerson(String ethnic)

    {

                   //根据参数返回Person接口的实例。

           if (ethnic.equalsIgnoreCase("chin"))

           {

                return new Chinese();

           }

            else

           {

                return new American();

           }

    }

}

最简单的工厂模式的框架基本如上所示。

主程序部分仅仅需要与工厂耦合,而无须与具体的实现类耦合在一起。下面是主程序部分:

public class FactroyTest

{

    public static void main(String[] args)

{

           //创建PersonFactory的实例,获得工厂实例

           PersonFactory pf = new PersonFactory();

           //定义接口Person的实例,面向接口编程

           Person p = null;

           //使用工厂获得Person的实例

           p = pf.getPerson("chin");

           //下面调用Person接口的方法

           System.out.println(p.sayHello("wawa"));

           System.out.println(p.sayGoodBye("wawa"));

           //使用工厂获得Person的另一个实例

           p = pf.getPerson("ame");

                   //再次调用Person接口的方法

           System.out.println(p.sayHello("wawa"));

           System.out.println(p.sayGoodBye("wawa"));

    }

}

主程序从Person接口的具体类中解耦出来,而且,程序调用者无须关心Person的实例化过程,角色划分清晰。主程序仅仅与工厂服务定位结合在一起:获得工厂的引用,程序将可获得所有工厂能产生的实例。具体类的变化,重要接口不发生任何改变,调用者程序代码部分几乎无须发生任何改动。

下面看Spring对这两种模式的实现。

1.4.3 Spring对单态与工厂模式的实现

无须修改程序的接口和实现类。Spring提供工厂模式的实现,因此,对于PersonFactory工厂类,此处不再需要。Spring使用配置文件管理所有的bean,该bean就是Spring工厂能产生的全部实例。下面是关于两个实例的配置文件:

         "http://www.springframework.org/dtd/spring-beans.dtd">

        

   

主程序部分如下:

public class SpringTest

{

    public static void main(String[] args)

    {

                   //实例化Spring容器

           ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");

                   //定义Person接口的实例

           Person p = null;

                   //通过Spring上下文获得chinese实例

           p = (Person)ctx.getBean("chinese");

                   //执行chinese实例的方法

           System.out.println(p.sayHello("wawa"));

           System.out.println(p.sayGoodBye("wawa"));

                   //通过Spring上下文获得american实例

           p = (Person)ctx.getBean("american");

                   //执行american实例的方法

           System.out.println(p.sayHello("wawa"));

           System.out.println(p.sayGoodBye("wawa"));

    }

}

使用Spring至少有一个好处:即使没有工厂类PersonFactory,程序一样可以使用工厂模式。所有工厂模式的功能,Spring完全可以提供。下面对主程序部分做出简单的修改:

public class SpringTest

{

    public static void main(String[] args)

    {

                   //实例化Spring容器

           ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");

                   //定义Person接口的实例p1

           Person p1 = null;

                   //通过Spring上下文获得chinese实例

           p1 = (Person)ctx.getBean("chinese");

                   //定义Person接口的实例p1

                   Person p2 = null;

           p2 = (Person)ctx.getBean("chinese");

                   System.out.println(p1 == p2);

    }

}

程序执行结果是:

true

表明:Spring对接受容器管理的全部bean,默认采用单态模式管理。除非必要,笔者建议不要随便更改bean的行为方式:性能上,单态的bean比非单态的bean更优秀。

仔细检查上面的代码,发现如下特点:

q      除测试用主程序部分,代码并未出现Spring特定的类和接口。

q      调用者代码,也就是测试用主程序部分,仅仅面向Person接口编程。而无须知道实现类的具体名称。同时,可以通过修改配置文件来切换底层的具体实现类。

q      工厂通常无须多个实例,因此,工厂应该采用单态模式设计。Spring的上下文,也就是产生bean实例的工厂,已被设计成单态的。

Spring实现的工厂模式,不仅提供了创建bean的功能,还提供对bean生命周期的管理。最重要的是:还可管理bean与bean之间的依赖关系,以及bean的属性值。

1.5  Spring的核心机制

Spring能有效地组织J2EE应用各层的对象。不管是控制层的Action对象,还是业务层的Service对象,还是持久层的DAO对象,都可在Spring的管理下有机地协调、运行。Spring将各层的对象以松耦合的方式组织在一起,Action对象无须关心Service对象的具体实现,Service对象无须关心持久层对象的具体实现,各层对象的调用完全面向接口。当系统需要重构时,代码的改写量将大大减少。

上面所说的一切都得宜于Spring的核心机制,依赖注入。依赖注入让bean与bean之间以配置文件组织在一起,而不是以硬编码的方式耦合在一起。

1.5.1 理解依赖注入

依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。具体含义是:当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入。

不管是依赖注入,还是控制反转,都说明Spring采用动态、灵活的方式来管理各种对象。对象与对象之间的具体实现互相透明。在理解依赖注入之前,看如下这个问题在各种社会形态里如何解决:一个人(Java实例,调用者)需要一把斧子(Java实例,被调用者)。

(1)原始社会里,几乎没有社会分工。需要斧子的人(调用者)只能自己去磨一把斧子(被调用者)。对应的情形为:Java程序里的调用者自己创建被调用者。

(2)进入工业社会,工厂出现。斧子不再由普通人完成,而在工厂里被生产出来,此时需要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。对应Java程序的简单工厂的设计模式。

(3)进入“按需分配”社会,需要斧子的人不需要找到工厂,坐在家里发出一个简单指令:需要斧子。斧子就自然出现在他面前。对应Spring的依赖注入。

第一种情况下,Java实例的调用者创建被调用的Java实例,必然要求被调用的Java类出现在调用者的代码里。无法实现二者之间的松耦合。

第二种情况下,调用者无须关心被调用者具体实现过程,只需要找到符合某种标准(接口)的实例,即可使用。此时调用的代码面向接口编程,可以让调用者和被调用者解耦,这也是工厂模式大量使用的原因。但调用者需要自己定位工厂,调用者与特定工厂耦合在一起。

第三种情况下,调用者无须自己定位工厂,程序运行到需要被调用者时,系统自动提供被调用者实例。事实上,调用者和被调用者都处于Spring的管理下,二者之间的依赖关系由Spring提供。

所谓依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理。依赖注入通常有两种:

q      设值注入。

q      构造注入。

 

1.5.2 设值注入

设值注入是指通过setter方法传入被调用者的实例。这种注入方式简单、直观,因而在Spring的依赖注入里大量使用。看下面代码,是Person的接口

//定义Person接口

public interface Person

{

         //Person接口里定义一个使用斧子的方法

public void useAxe();

}

然后是Axe的接口

//定义Axe接口

public interface Axe

{

         //Axe接口里有个砍的方法

public void chop();

}

Person的实现类

//Chinese实现Person接口

public class Chinese implements Person

{

         //面向Axe接口编程,而不是具体的实现类

private Axe axe;

//默认的构造器

         public Chinese()

         {

         }

         //设值注入所需的setter方法

         public void setAxe(Axe axe)

         {

                   this.axe = axe;

         }

 

         //实现Person接口的useAxe方法

public void useAxe()

         {

                   System.out.println(axe.chop());

         }

}

Axe的第一个实现类

//Axe的第一个实现类 StoneAxe

public class StoneAxe implements Axe

{

         //默认构造器

public StoneAxe()

         {

         }

         //实现Axe接口的chop方法

         public String chop()

         {

                   return "石斧砍柴好慢";

         }

}

下面采用Spring的配置文件将Person实例和Axe实例组织在一起。配置文件如下所示:

         "http://www.springframework.org/dtd/spring-beans.dtd">

        

        

                  

                  

                           

                           

                  

        

从配置文件中,可以看到Spring管理bean的灵巧性。bean与bean之间的依赖关系放在配置文件里组织,而不是写在代码里。通过配置文件的指定,Spring能精确地为每个bean注入属性。因此,配置文件里的bean的class元素,不能仅仅是接口,而必须是真正的实现类。

Spring会自动接管每个bean定义里的property元素定义。Spring会在执行无参数的构造器后、创建默认的bean实例后,调用对应的setter方法为程序注入属性值。property定义的属性值将不再由该bean来主动创建、管理,而改为被动接收Spring的注入。

每个bean的id属性是该bean的惟一标识,程序通过id属性访问bean,bean与bean的依赖关系也通过id属性完成。

下面看主程序部分:

public class BeanTest

{

    //主方法,程序的入口

public static void main(String[] args)throws Exception

{

           //因为是独立的应用程序,显式地实例化Spring的上下文。

           ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");

                   //通过Person bean的id来获取bean实例,面向接口编程,因此

                   //此处强制类型转换为接口类型

                   Person p = (Person)ctx.getBean("chinese");

                   //直接执行Person的userAxe()方法。

                   p.useAxe();

    }

}

程序的执行结果如下:

石斧砍柴好慢

主程序调用Person的useAxe()方法时,该方法的方法体内需要使用Axe的实例,但程序里没有任何地方将特定的Person实例和Axe实例耦合在一起。或者说,程序里没有为Person实例传入Axe的实例,Axe实例由Spring在运行期间动态注入。

Person实例不仅不需要了解Axe实例的具体实现,甚至无须了解Axe的创建过程。程序在运行到需要Axe实例的时候,Spring创建了Axe实例,然后注入给需要Axe实例的调用者。Person实例运行到需要Axe实例的地方,自然就产生了Axe实例,用来供Person实例使用。

调用者不仅无须关心被调用者的实现过程,连工厂定位都可以省略(真是按需分配啊!)。下面也给出使用Ant编译和运行该应用的简单脚本:

   

        

          

               

          

                  

          

   

        

        

                  

          

                deprecation="false" optimize="false" failonerror="true">

                           

               

                           

               

          

   

        

        

                  

          

                           

               

          

   

如果需要改写Axe的实现类。或者说,提供另一个实现类给Person实例使用。Person接口、Chinese类都无须改变。只需提供另一个Axe的实现,然后对配置文件进行简单的修改即可。

Axe的另一个实现如下:

//Axe的另一个实现类 SteelAxe

public class SteelAxe implements Axe

{

         //默认构造器

public SteelAxe()

         {

         }

         //实现Axe接口的chop方法

         public String chop()

         {

                   return "钢斧砍柴真快";

         }

}

然后,修改原来的Spring配置文件,在其中增加如下一行:

该行重新定义了一个Axe的实现:SteelAxe。然后修改chinese bean的配置,将原来传入stoneAxe的地方改为传入steelAxe。也就是将

改成

此时再次执行程序,将得到如下结果:

钢斧砍柴真快

Person与Axe之间没有任何代码耦合关系,bean与bean之间的依赖关系由Spring管理。采用setter方法为目标bean注入属性的方式,称为设值注入。

业务对象的更换变得相当简单,对象与对象之间的依赖关系从代码里分离出来,通过配置文件动态管理。 

1.5.3 构造注入

所谓构造注入,指通过构造函数来完成依赖关系的设定,而不是通过setter方法。对前面代码Chinese类做简单的修改,修改后的代码如下:

//Chinese实现Person接口

public class Chinese implements Person

{

         //面向Axe接口编程,而不是具体的实现类

private Axe axe;

//默认的构造器

         public Chinese()

         {

         }

         //构造注入所需的带参数的构造器

         public Chinse(Axe axe)

         {

                   this.axe = axe;

         }

         //实现Person接口的useAxe方法

public void useAxe()

         {

                   System.out.println(axe.chop());

         }

}

此时无须Chinese类里的setAxe方法,构造Person实例时,Spring为Person实例注入所依赖的Axe实例。构造注入的配置文件也需做简单的修改,修改后的配置文件如下:

         "http://www.springframework.org/dtd/spring-beans.dtd">

        

        

                  

        

执行效果与使用steelAxe设值注入时的执行效果完全一样。区别在于:创建Person实例中Axe属性的时机不同——设值注入是现创建一个默认的bean实例,然后调用对应的构造方法注入依赖关系。而构造注入则在创建bean实例时,已经完成了依赖关系的注入。

 

1.5.4 两种注入方式的对比

设值注入和构造注入,都是Spring支持的依赖注入模式。也是目前主流的依赖注入模式。两种注入模式各有优点:

1.5.4.1 设值注入的优点

q      与传统的JavaBean的写法更相似,程序开发人员更容易了解、接受。通过setter方法设定依赖关系显得更加直观、自然。

q      对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读。Spring在创建bean实例时,需要同时实例化其依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。

q      尤其是某些属性可选的情况下,多参数的构造器更加笨重。

1.5.4.2 构造注入的优点

q      可以在构造器中决定依赖关系的注入顺序。优先依赖的优先注入。例如,组件中其他依赖关系的注入,常常需要依赖于Datasource的注入。采用构造注入,可以在代码中清晰地决定注入顺序。

q      对于依赖关系无须变化的bean,构造注入更有用处。因为没有setter方法,所有的依赖关系全部在构造器内设定。因此,无须担心后续的代码对依赖关系产生破坏。

q      依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。

建议采用以设值注入为主,构造注入为辅的注入策略。对于依赖关系无须变化的注入,尽量采用构造注入;而其他的依赖关系的注入,则考虑采用设值注入。 

1.6 小结

本章简短的介绍了Spring的起源和背景。分别讲述了利用Eclipse和Ant开发Spring应用的步骤。同时也对Spring体系作了一个简单预览,以便读者对Spring架构形成总体把握。本章还详细介绍了两种基本设计模式、两种设计模式在J2EE应用中的用法,以及Spring对两种模式的实现。

最后形象地介绍了依赖注入的概念,详细地讲解了Spring对两种依赖注入的支持和用法。同时对两种依赖注入进行简单的对比。请读者务必理解Spring的核心机制:依赖注入。此外,设值注入、构造注入应能理解各自的区别,并可以正确应用。