以下所说的struts-config.XML和ApplicationResources.properties等文件名是缺省时使用的,假如你使用了多模块,或指定了不同的资源文件名称,这些名字要做相应的修改。
1、“No bean found under attribute key XXX”
在struts-config.xml里定义了一个ActionForm,但type属性指定的类不存在,type属性的值应该是Form类的全名。或者是,在Action的定义中,name或attribute属性指定的ActionForm不存在。
2、“Cannot find bean XXX in any scope”
在Action里一般会request.setAttribute()一些对象,然后在转向的jsp文件里(用tag或request.getAttribute()方法)得到这些对象并显示出来。这个异常是说jsp要得到一个对象,但前面的Action里并没有将对象设置到request(也可以是session、servletContext)里。
可能是名字错了,请检查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里使用的forward与struts-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"?>
<!-- 下面一行定义Spring的XML配置文件的dtd -->
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<!-- 以上三行对所有的Spring配置文件都是相同的 -->
<!-- Spring配置文件的根元素 -->
<beans>
<!—定义第一bean,该bean的id是chinese, 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的灵巧性。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编译和运行该应用的简单脚本:
<?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之间没有任何代码耦合关系,bean与bean之间的依赖关系由Spring管理。采用setter方法为目标bean注入属性的方式,称为设值注入。
业务对象的更换变得相当简单,对象与对象之间的依赖关系从代码里分离出来,通过配置文件动态管理。
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工厂能产生的全部实例。下面是关于两个实例的配置文件:
<!-- 下面是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,该bean的id为chinese->
<bean id="chinese" class="lee.Chinese"/>
<!-- 定义第二个bean,该bean的id为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生命周期的管理。最重要的是:还可管理bean与bean之间的依赖关系,以及bean的属性值。
Spring 的基本设计思想
最新推荐文章于 2023-08-03 18:16:34 发布