Spring 的基本设计思想

以下所说的struts-config.XMLApplicationResources.properties等文件名是缺省时使用的,假如你使用了多模块,或指定了不同的资源文件名称,这些名字要做相应的修改。

1
“No bean found under attribute key XXX”
struts-config.xml里定义了一个ActionForm,但type属性指定的类不存在,type属性的值应该是Form类的全名。或者是,在Action的定义中,nameattribute属性指定的ActionForm不存在。



2
“Cannot find bean XXX in any scope”
Action里一般会request.setAttribute()一些对象,然后在转向的jsp文件里(用tagrequest.getAttribute()方法)得到这些对象并显示出来。这个异常是说jsp要得到一个对象,但前面的Action里并没有将对象设置到request(也可以是sessionservletContext)里。

可能是名字错了,请检查jsp里的tag的一般是name属性,或getAttribute()方法的参数值;或者是Action逻辑有问题没有执行setAttribute()方法就先转向了。
还有另外一个可能,纯粹是jsp文件的问题,例如<logic:iterate>会指定一个id值,然后在循环里<bean: write>使用这个值作为name的值,假如这两个值不同,也会出现此异常。(都是一个道理,request里没有对应的对象。)


3
“Missing message for key "XXX"”
缺少所需的资源,检查ApplicationResources.properties文件里是否有jsp文件里需要的资源,例如:



<bean:message key="msg.name.prompt"/>

这行代码会找msg.name.prompt资源,假如AppliationResources.properties里没有这个资源就会出现本异常。在使用多模块时,要注重在模块的struts-config-xxx.xml里指定要使用的资源文件名称,否则当然什么资源也找不到,这也是一个很轻易犯的错误。

4
“No getter method for property XXX of bean teacher”
这条异常信息说得很明白,jsp里要取一个bean的属性出来,但这个bean并没有这个属性。你应该检查jsp中某个标签的property属性的值。例如下面代码中的cade应该改为code才对:


<bean:write name="teacher" property="cade" filter="true"/>


5
“Cannot find ActionMappings or ActionFormBeans collection”
待解决。


6
“Cannot retrieve mapping for action XXX”
.jsp<form>标签里指定action='/XXX',但这个Action并未在struts-config.xml里设置过。


7
HTTP Status 404 - /xxx/xxx.jsp
Forward
path属性指向的jsp页面不存在,请检查路径和模块,对于同一模块中的Action转向,path中不应包含模块名;模块间转向,记住使用contextRelative="true"


8
、没有任何异常信息,显示空白页面
可能是Action里使用的forwardstruts-config.xml里定义的forward名称不匹配。


9
“The element type "XXX" must be terminated by the matching end-tag "XXX".”
这个是struts-config.xml文件的格式错误,仔细检查它是否是良构的xml文件,关于xml文件的格式这里就不赘述了。


10
“Servlet.init() for servlet action threw exception”
一般出现这种异常在后面会显示一个关于ActionServlet的异常堆栈信息,其中指出了异常具体出现在代码的哪一行。我曾经碰到的一次提示如下:


Java.lang.NullPointerException
    at org.apache.struts.action.ActionServlet.parseModuleConfigFile(ActionServlet.java:1003)
    at org.apache.struts.action.ActionServlet.initModuleConfig(ActionServlet.java:955)


为解决问题,先下载struts的源码包,然后在ActionServlet.java的第1003行插入断点,并对各变量进行监视。很丢人,我竟然把struts-config.xml文件弄丢了,因此出现了上面的异常,应该是和CVS同步时不小心删除的。

11
“Resources not defined for Validator”
这个是利用Validator插件做验证时可能出现的异常,这时你要检查validation.xml文件,看里面使用的资源是否确实有定义,form的名称是否正确,等等。










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实例组织在一起。配置文件如下所示:


<!-- 
下面是标准的XML文件头 -->

<?xml version="1.0" encoding="gb2312"?>

<!-- 
下面一行定义SpringXML配置文件的
dtd -->

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

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

<!-- 
以上三行对所有的Spring配置文件都是相同的
-->

<!--  Spring
配置文件的根元素
-->

<beans>

         <!—
定义第一bean,该beanidchinese, class指定该bean实例的实现类
-->

         <bean id="chinese" class="lee.Chinese">

                   <!--  property
元素用来指定需要容器注入的属性,axe属性需要容器注入


此处是设值注入,因此Chinese类必须拥有setAxe方法 -->

                   <property name="axe">

                            <!-- 
此处将另一个bean的引用注入给
chinese bean -->

                            <ref local=”stoneAxe”/>

                   </property>

</bean>

<!-- 
定义
stoneAxe bean -->

         <bean id="stoneAxe" class="lee.StoneAxe"/>

</beans>

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


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

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

下面看主程序部分:

public class BeanTest

{

    //
主方法,程序的入口

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

{

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

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

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

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

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

                   //
直接执行PersonuserAxe()方法。

                   p.useAxe();

    }

}

程序的执行结果如下:

石斧砍柴好慢

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

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

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

<?xml version="1.0"?>

<!-- 
定义编译该项目的基本信息-->

<project name="spring" basedir="." default=".">

    <!-- 
定义编译和运行该项目时所需的库文件
-->

<path id="classpath">

         <!-- 
该路径下存放spring.jar和其他第三方类库
-->

           <fileset dir="../../lib">

                <include name="*.jar"/>

           </fileset>

                   <!-- 
同时还需要引用已经编译过的class文件
-->

           <pathelement path="."/>

    </path>

         <!-- 
编译全部的java文件
-->

         <target name="compile" description="Compile all source code">

                   <!-- 
指定编译后的class文件的存放位置
-->

           <javac destdir="." debug="true"

                deprecation="false" optimize="false" failοnerrοr="true">

                            <!-- 
指定需要编译的源文件的存放位置
-->

                <src path="."/>

                            <!-- 
指定编译这些java文件需要的类库位置
-->

                <classpath refid="classpath"/>

           </javac>

    </target>

         <!-- 
运行特定的主程序
-->

         <target name="run" description="run the main class" depends="compile">

                   <!-- 
指定运行的主程序:lee.BeanTest
-->

           <java classname="lee.BeanTest" fork="yes" failοnerrοr="true">

                            <!-- 
指定运行这些java文件需要的类库位置
-->

                <classpath refid="classpath"/>

           </java>

    </target>

</project>

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


Axe
的另一个实现如下:

//Axe
的另一个实现类 SteelAxe

public class SteelAxe implements Axe

{

         //
默认构造器


public SteelAxe()

         {

         }

         //
实现Axe接口的chop方法

         public String chop()

         {

                   return "
钢斧砍柴真快";

         }

}

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


<!-- 
定义一个steelAxe bean-->

<bean id="steelAxe" class="lee.SteelAxe"/>

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


<ref local=”stoneAxe”/>

改成

<ref local=”steelAxe”/>

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

钢斧砍柴真快

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

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





1.4 Spring
的基本设计思想

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

q     
工厂模式

q      
单态模式

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

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

    }

         //
以下提供对普通属性valuesettergetter方法

    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工厂能产生的全部实例。下面是关于两个实例的配置文件:

<!-- 
下面是XML文件的文件头-->

<?xml version="1.0" encoding="gb2312"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

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

<!--  beans
Spring配置文件的根元素
-->

<beans>

         <!-- 
定义第一个bean,beanid
chinese->

         <bean id="chinese" class="lee.Chinese"/>

         <!-- 
定义第二个bean,beanid
american-->

    <bean id="american" class="lee.American"/>

</beans>

主程序部分如下:


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生命周期的管理。最重要的是:还可管理beanbean之间的依赖关系,以及bean的属性值。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1598583

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值