Srping guide --xiaxin【读书笔记】

Spring Guide -xiaxin

Spring 初探

准备工作

构建Spring基础代码

1. Action接口:

Action 接口定义了一个execute 方法,在我们示例中,不同的Action 实现提供了各自的
execute方法,以完成目标逻辑。

public interface Action {
    public String execute(String str);
}  

2. Action接口的两个实现UpperAction、LowerAction

public class UpperAction implements Action {
    private String message;
    public String getMessage() {
    return message;
}
public void setMessage(String string) {
    message = string;
}
public String execute(String str) {
    return (getMessage() + str).toUpperCase();
}
}  

UpperAction将其message属性与输入字符串相连接,并返回其大写形式。

public class LowerAction implements Action {
    private String message;
    public String getMessage() {
    return message;
}
public void setMessage(String string) {
    message = string;
}
public String execute(String str) {
    return (getMessage()+str).toLowerCase();
}
}

请确保配置bean.xml位于工作路径之下,注意工作路径并不等同于CLASSPATH ,eclipse
的默认工作路径为项目根路径,也就是.project文件所在的目录,而默认输出目录/bin是项目
CLASSPATH的一部分,并非工作路径。
LowerAction将其message属性与输入字符串相连接,并返回其小写形式。

3. Spring配置文件(bean.xml)

<beans>
<description>Spring Quick Start</description>
<bean id="TheAction"
class="net.xiaxin.spring.qs.UpperAction">
<property name="message">
<value>HeLLo</value>
</property>
</bean>
</beans>  

4. 测试代码

public void testQuickStart() {
    ApplicationContext ctx=new
    FileSystemXmlApplicationContext("bean.xml");
    Action action = (Action) ctx.getBean("TheAction");
    System.out.println(action.execute("Rod Johnson"));
}  

可以看到,上面的测试代码中,我们根据"bean.xml"创建了一个ApplicationContext实
例,并从此实例中获取我们所需的Action实现。

Spring 基础语义

Dependency Injection

IoC,用白话来讲,就是由容器控制程序之间的关系,而非传统实现中,由程序代码直接操控。这也
就是所谓“控制反转”的概念所在:控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转。
篇经典文章《Inversion
of Control Containers and the Dependency Injection pattern》为IoC正名 ,IoC又获得了
一个新的名字:“依赖注入(Dependency Injection)”。
笔记本电脑与外围存储设备通过预先指定的一个接口(USB)相连,对于笔记本而言, <br />只是将用户指定的数据发送到USB接口,而这些数据何去何从,则由当前接入的USB设备决定。在USB <br />设备加载之前,笔记本不可能预料用户将在USB接口上接入何种设备,只有USB设备接入之后,这种设 <br />备之间的依赖关系才开始形成。 <br />对应上面关于依赖注入机制的描述,在运行时(系统开机,USB 设备加载)由容器(运行在笔记本 <br />中的Windows操作系统)将依赖关系(笔记本依赖USB设备进行数据存取)注入到组件中(Windows <br />文件访问组件)。 <br />这就是依赖注入模式在现实世界中的一个版本。
回顾Quick Start中的示例,UpperAction/LowerAction在运行前,其Message节点为空。运行
后由容器将字符串“HeLLo”注入。此时UpperAction/LowerAction即与内存中的“HeLLo”字符串对
象建立了依赖关系。也许区区一个字符串我们无法感受出依赖关系的存在。如果把这里的Message 属性
换成一个数据源(DataSource),可能更有感觉:

<beans>
<bean id="dataSource"
    class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:comp/env/jdbc/sample</value>
</property>
</bean>
<bean id="SampleDAO" class="net.xiaxin.spring.dao.SampleDAO">
<property name="dataSource">
<ref local="dataSource"/>
</property>
</bean>
</beans>  

其中SampleDAO中的dataSource将由容器在运行期动态注入,而DataSource的具体配置和初始化工作
也将由容器在运行期完成。
上面的实例中,我们假设SampleDAO是一个运行在J2EE容器中的组件(如Weblogic)。在运行期,通
过JNDI从容器中获取DataSource实例。
现在假设我们的部署环境发生了变化,系统需要脱离应用服务器独立运行,这样,由于失去了容器的支持,
原本通过JNDI获取DataSource的方式不再有效。我们需要如何修改以适应新的系统环境?很简单,我们
只需要修改dataSource的配置:

<beans>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>org.gjt.mm.mysql.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost/sample</value></property>
<property name="username">
<value>user</value>
</property>
<property name="password">
<value>mypass</value>
</property>
</bean>
<bean id="SampleDAO" class="net.xiaxin.spring.dao.SampleDAO">
<property name="dataSource">
<ref local="dataSource"/>
</property>
</bean>
</beans>

这里我们的DataSource改为由Apache DBCP组件提供。没有编写任何代码我们即实现了DataSource的
切换。回想传统编码模式中,如果要进行同样的修改,我们需要付出多大的努力。
依赖注入机制减轻了组件之间的依赖关系,同时也大大提高了组件的可移植性,这意味着,组件得到重用
的机会将会更多.

依赖注入的几种实现类型

Type1 接口注入

我们常常借助接口来将调用者与实现者分离。如:

public class ClassA {
    private InterfaceB clzB;
    public doSomething() {
        Ojbect obj =
            Class.forName(Config.BImplementation).newInstance();
        clzB = (InterfaceB)obj;
        clzB.doIt()
    }
    ……
}  

上面的代码中,ClassA依赖于InterfaceB的实现,如何获得InterfaceB实现类的实例?传统的方法是在
代码中创建InterfaceB实现类的实例,并将起赋予clzB。
而这样一来,ClassA在编译期即依赖于InterfaceB的实现。为了将调用者与实现者在编译期分离,于是有
了上面的代码,我们根据预先在配置文件中设定的实现类的类名(Config.BImplementation),动态
加载实现类,并通过InterfaceB强制转型后为ClassA所用。这就是接口注入的一个最原始的雏形。
而对于一个Type1型IOC容器而言,加载接口实现并创建其实例的工作由容器完成。

如下面这个类:

public class ClassA {
    private InterfaceB clzB;
    public Object doSomething(InterfaceB b) {
        clzB = b;
        return clzB.doIt();
    }
    ……
}

在运行期,InterfaceB实例将由容器提供。
Type1型IOC发展较早(有意或无意),在实际中得到了普遍应用,即使在IOC的概念尚未确立时,这样的
方法也已经频繁出现在我们的代码中。
下面的代码大家应该非常熟悉:

public class MyServlet extends HttpServlet {
public void doGet(
    HttpServletRequest request,
    HttpServletResponse response)
    throws ServletException, IOException {
    ……
    }
}

这也是一个Type1 型注入,HttpServletRequest和HttpServletResponse实例由Servlet Container
在运行期动态注入。
另,Apache Avalon是一个较为典型的Type1型IOC容器。

Type2 设值注入

Quick Start中的示例,就是典
型的设置注入,即通过类的setter方法完成依赖关系的设置。

Type3 构造子注入

构造子注入,即通过构造函数完成依赖关系的设定,如:

public class DIByConstructor {
    private final DataSource dataSource;
    private final String message;
    public DIByConstructor(DataSource ds, String msg) {
        this.dataSource = ds;
        this.message = msg;
    }
    ……
}

可以看到,在Type3类型的依赖注入机制中,依赖关系是通过类构造函数建立,容器通过调用类的构
造方法,将其所需的依赖关系注入其中。

几种依赖注入模式的对比总结

接口注入模式因为历史较为悠久,在很多容器中都已经得到应用。但由于其在灵活性、易用性上不如
其他两种注入模式,因而在IOC的专题世界内并不被看好。
Type2和Type3型的依赖注入实现则是目前主流的IOC实现模式。

  • Type2 设值注入的优势
    • 1. 对于习惯了传统JavaBean开发的程序员而言,通过setter方法设定依赖关系显得更加直
      观,更加自然。
    • 2. 如果依赖关系(或继承关系)较为复杂,那么Type3模式的构造函数也会相当庞大(我们需
      要在构造函数中设定所有依赖关系),此时Type2模式往往更为简洁。
    • 3. 对于某些第三方类库而言,可能要求我们的组件必须提供一个默认的构造函数(如Struts
      中的Action),此时Type3类型的依赖注入机制就体现出其局限性,难以完成我们期望的功
      能。
  • Type3 构造子注入的优势:
    • 1. “在构造期即创建一个完整、合法的对象”,对于这条Java设计原则,Type3无疑是最好的
      响应者。
    • 2. 避免了繁琐的setter方法的编写,所有依赖关系均在构造函数中设定,依赖关系集中呈现,
      更加易读。
    • 3. 由于没有setter方法,依赖关系在构造时由容器一次性设定,因此组件在被创建之后即处于
      相对“不变”的稳定状态,无需担心上层代码在调用过程中执行setter方法对组件依赖关系
      产生破坏,特别是对于Singleton模式的组件而言,这可能对整个系统产生重大的影响。
    • 4. 同样,由于关联关系仅在构造函数中表达,只有组件创建者需要关心组件内部的依赖关系。
      对调用者而言,组件中的依赖关系处于黑盒之中。对上层屏蔽不必要的信息,也为系统的
      层次清晰性提供了保证。
    • 5. 通过构造子注入,意味着我们可以在构造函数中决定依赖关系的注入顺序,对于一个大量
      依赖外部服务的组件而言,依赖关系的获得顺序可能非常重要,比如某个依赖关系注入的
      先决条件是组件的DataSource及相关资源已经被设定。

可见,Type3和Type2模式各有千秋,理论上,以Type3类型为主,辅之以Type2
类型机制作为补充,可以达到最好的依赖注入效果,不过对于基于Spring Framework开发的应用而言,
Type2使用更加广泛。

Spring Bean封装机制

Spring 从核心而言,是一个DI 容器,其设计哲学是提供一种无侵入式的高扩展性框架。即无需代
码中涉及Spring专有类,即可将其纳入Spring容器进行管理。
作为对比,EJB则是一种高度侵入性的框架规范,它制定了众多的接口和编码规范,要求实现者必须
遵从。侵入性的后果就是,一旦系统基于侵入性框架设计开发,那么之后任何脱离这个框架的企图都将付
出极大的代价。
为了避免这种情况,实现无侵入性的目标。Spring 大量引入了Java 的Reflection机制,通过动态
调用的方式避免硬编码方式的约束,并在此基础上建立了其核心组件BeanFactory,以此作为其依赖注入
机制的实现基础。
org.springframework.beans包中包括了这些核心组件的实现类,核心中的核心为BeanWrapper
和BeanFactory类。这两个类从技术角度而言并不复杂,但对于Spring 框架而言,却是关键所在,如果
有时间,建议对其源码进行研读,必有所获。

Bean Wrapper

从Quick Start的例子中可以看到,所谓依赖注入,即在运行期由容器将依赖关系注入到组件之中。
讲的通俗点,就是在运行期,由Spring根据配置文件,将其他对象的引用通过组件的提供的setter方法进
行设定。
我们知道,如果动态设置一个对象属性,可以借助Java的Reflection机制完成:

Class cls = Class.forName("net.xiaxin.beans.User");
Method mtd = cls.getMethod("setName",new Class[]{String.class});
Object obj = (Object)cls.newInstance();
mtd.invoke(obj,new Object[]{"Erica"});
return obj;

上面我们通过动态加载了User类,并通过Reflection调用了User.setName方法设置其name属性。
对于这里的例子而言,出于简洁,我们将类名和方法名都以常量的方式硬编码。假设这些常量都是通过配
置文件读入,那我们就实现了一个最简单的BeanWrapper。这个BeanWrapper的功能很简单,提供一个
设置JavaBean属性的通用方法(Apache BeanUtils 类库中提供了大量针对Bean的辅助工具,如果有兴
趣可以下载一份源码加以研读)。
Spring BeanWrapper基于同样的原理,提供了一个更加完善的实现。
看看如何通过Spring BeanWrapper操作一个JavaBean:

Object obj = Class.forName("net.xiaxin.beans.User").newInstance();
BeanWrapper bw = new BeanWrapperImpl(obj);
bw.setPropertyValue("name", "Erica");
System.out.println("User name=>"+bw.getPropertyValue("name"));

对比之前的代码,相信大家已经知道BeanWrapper的实现原理。
诚然,通过这样的方式设定Java Bean属性实在繁琐,但它却提供了一个通用的属性设定机制,而这
样的机制,也正是Spring依赖注入机制所依赖的基础。
通过BeanWrapper,我们可以无需在编码时就指定JavaBean的实现类和属性值,通过在配置文件
加以设定,就可以在运行期动态创建对象并设定其属性(依赖关系)。
上面的代码中,我们仅仅指定了需要设置的属性名“name”,运行期,BeanWrapper将根据Java
Bean规范,动态调用对象的“setName”方法进行属性设定。属性名可包含层次,如对于属性名
“address.zipcode”,BeanWrapper会调用“getAddress().setZipcode”方法。

Bean Factory

Bean Factory,顾名思义,负责创建并维护Bean实例。
Bean Factory负责根据配置文件创建Bean实例,可以配置的项目有:
1. Bean属性值及依赖关系(对其他Bean的引用)
2. Bean创建模式(是否Singleton模式,即是否只针对指定类维持全局唯一的实例)
3. Bean初始化和销毁方法
4. Bean的依赖关系
下面是一个较为完整的Bean配置示例:

<beans>
<description>Spring Bean Configuration Sample</description>
<bean
id="TheAction" ⑴
class="net.xiaxin.spring.qs.UpperAction" ⑵
singleton="true" ⑶
init-method="init" ⑷
destroy-method="cleanup" ⑸
depends-on="ActionManager" ⑹
>
<property name="message">
<value>HeLLo</value> ⑺
</property>
<property name="desc">
<null/>
</property>
<property name="dataSource">
<ref local="dataSource"/> ⑻
</property>
</bean>
<bean id="dataSource"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:comp/env/jdbc/sample</value>
</property>
</bean>
</beans>

⑴ id
Java Bean在BeanFactory中的唯一标识,代码中通过BeanFactory获取
JavaBean实例时需以此作为索引名称。
⑵ class
Java Bean 类名
⑶ singleton
指定此Java Bean是否采用单例(Singleton)模式,如果设为“true”,则在
BeanFactory作用范围内,只维护此Java Bean的一个实例,代码通过BeanFactory
获得此Java Bean实例的引用。反之,如果设为“false”,则通过BeanFactory获取
此Java Bean实例时,BeanFactory每次都将创建一个新的实例返回。
⑷ init-method
初始化方法,此方法将在BeanFactory创建JavaBean实例之后,在向应用层返回引
用之前执行。一般用于一些资源的初始化工作。
⑸ destroy-method
销毁方法。此方法将在BeanFactory销毁的时候执行,一般用于资源释放。
⑹ depends-on
Bean依赖关系。一般情况下无需设定。Spring会根据情况组织各个依赖关系的构建工作(这里
示例中的depends-on属性非必须)。
只有某些特殊情况下,如JavaBean中的某些静态变量需要进行初始化(这是一种Bad
Smell,应该在设计上应该避免)。通过depends-on指定其依赖关系可保证在此Bean加
载之前,首先对depends-on所指定的资源进行加载。

通过 节点可指定属性值。BeanFactory将自动根据Java Bean对应的属性
类型加以匹配。
下面的”desc”属性提供了一个null值的设定示例。注意 代表一
个空字符串,如果需要将属性值设定为null,必须使用 节点。

指定了属性对BeanFactory中其他Bean的引用关系。示例中,TheAction的dataSource属
性引用了id为dataSource的Bean。BeanFactory将在运行期创建dataSource bean实例,并将其
引用传入TheAction Bean的dataSource属性。
下面的代码演示了如何通过BeanFactory获取Bean实例:

InputStream is = new FileInputStream("bean.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);
Action action = (Action) factory.getBean("TheAction");

此时我们获得的Action实例,由BeanFactory进行加载,并根据配置文件进行了初始化和属性设定。
联合上面关于BeanWrapper的内容,我们可以看到,BeanWrapper实现了针对单个Bean的属性设
定操作。而BeanFactory则是针对多个Bean的管理容器,根据给定的配置文件,BeanFactory从中读取
类名、属性名/值,然后通过Reflection机制进行Bean加载和属性设定。

你可能感兴趣的:(Srping guide --xiaxin【读书笔记】)