SSH 学习杂记

2022-07-15 18:26:09 浏览数 (1)

Web 开发是一个很有意思的事情。Struts Spring Hibernate 作为一种当今流行的开发模式,我很荣幸地在一周左右的时间里,疯狂地边学边琢磨了一番,甚有感触。

Struts:

Web 应用程序框架统一体:PHP、ASP → Struts → JSF、Shale → Ajax

从左至右与常规桌面应用程序相似度依次提高。

Struts 将用户表单使用 FormBean 打包,根据 struts-config.xml 里面 mapping 的配置送入 ActionServlet,ActionServlet 决定自己处理或者分发给更单一任务的 Action 处理。

事实上 Struts 的贡献主要在表示层上,对于用户输入的数据它可以很方便地进行前期处理,比如包装和验证。控制器继承 Action 父类,重载 ActionForward 方法就可以轻松实现请求转发。

FormBean 通常是最普通的实体类,private 的几个属性,已经它们对应的 public 的 get/set 方法,也就是说,Struts 对 Model 层没什么贡献。

Action 是无状态的,不许用它来保存数据资源。Struts 管理着 Action 子类的创建,并且它们是被 pool 化了的,以便通过重用来提高服务的效率,因此不要使用实例变量。

遇到的问题:

对于 Jsp 中 taglib 声明,书上的意见是应该先在 web.xml 中声明,再使用;当然我遇到了找不到标签的问题,如果这样的方法不行,可以试着在 jsp 中采用类似 http://jakarta.apache.org/Struts/tags-html 这样的格式。

我比较笨,开始都不知道 Struts 那套东西 Eclipse 是可以自动生成的,于是手写,可是有很大好处,起码把整个结构都弄通了。

validate() 表单验证相关:

  1. 先在 struts-config.xmld 的 action-mappings 里面设置 validate=”true” 和 input=”/error.jsp”;
  2. 然后在 validate() 方法里面设定 errors.add(ActionErrors.GLOBAL_MESSAGE, new ActionMessage(“form.err.xxxerror”));
  3. 再配置资源文件,最后在 error.jsp 里面添加标签<html:errors/>。

reset() 方法:在每次填充 FormBean 之前调用,加上所有属性赋空值的语句,以保安全。

Spring:

Spring 是一个从实际项目开发经验中抽取的,可高度重用的应用框架。

由 applicationContext.xml 来决定由哪一个类来实现父类接口。

虽然这个接口可以手动来实现(我就实现了一次),可是引入 spring.jar 这个包以后,就还是用现成的吧:org.springframework.web.struts.DelegatingActionProxy。

Spring 不需要实现任何框架的制定接口,能够轻松将组件从 Spring 中脱离,而且,组件之间依赖关系减少,重用性增加。最后还有一个很大的好处:面向接口编程。

Spring 的两个精髓结构就是控制反转(IoC)和依赖注入(DI)。IoC:由容器控制程序之间的关系,而非代码。DI:组件之间的依赖关系由容器在运行时期决定(由容器动态地将某种依赖关系注入到组件之中),目标是提升组件的重用率。

Spring 在数据持久层也有杰出的贡献,它有两点显著优势:“可以将任意 Java 类纳入事务管理” 和 “事务管理并不依赖特定的事务资源”。当然这一次我更关注的是持久层的封装,即它和 Hibernate 的整合。

遗憾的是,Spring 的 AOP 因为看起来比较头大,我就没有应用。

遇到的问题:无。

Spring和Struts的整合(SS):

Struts 将 Action 管理权完全交给了 Spring,由是,Action 与 Form 都可以等价地视为 Spring 的 Bean。不过,看起来 Spring 基本不对 Struts 中的 Form 有任何处理操作。无论如何,将 Action 视为 Bean,与其他 Bean 放到一起统一管理,它就可以享受 Spring 提供的所有服务(依赖注入、实例管理、事务管理等)。

Jsp 的表单由 Struts 负责打包,根据 struts-config.xml 中的信息打包成 FormBean,传给接口,然后由 Spring 根据 applicationContext.xml 决定采取哪个子类来实现。

具体二者结合的主流方法有两个:

  1. 利用第三方框架的扩展点,实现加载 Spring 的 IoC 容器,如:plugin 扩展(我用的就是这个方法)。
  2. web.xml 中定义 Listener 或者 Servlet,让 web 应用程序一启动就自动加载 Spring 的 IoC 容器。

遇到的问题:

对于接口的实现,找不到 DelegationActionProxy 这个类,引入 spring.jar 就好了。

Hibernate

Hibernate 解决或减轻了很多以往传统 JDBC 遗留的问题,比如代码繁琐、多表连接问题、表间级联问题、层间耦合严重等。

Hibernate 是一个 OR mapping 的工具,是一个轻量级的框架,它使得程序员不必关心数据库的具体内容,而只需专注于持久层之上完全面向对象的业务逻辑。

结构图示:

hibernate.cfg.xml 配置数据库。表 test 和抽象类 AbstractTest 通过 Test.hbm.xml 关联。Test 类是个继承 AbstractTest 的 POJO。TestDAO 为关于 Test 的数据库访问操作提供了一些便捷的方法,它扩展自 BaseHibernateDAO。BaseHibernateDAO 返回从 HibernateSessionFactory 获取的会话,而从会话中可以获得事务。IBaseHibernateDAO 是 BaseHibernateDAO 的接口。HibernateSessionFactory 是会话工厂,负责会话的产生、关闭等。

某一次数据库访问的过程:获得一个 TestDAO 对象,根据这个 TestDAO 获取 session,并且由此 session 获得 transaction。再由 TestDAO 提供的一组方法将操作记入 transaction,提交事务,关闭会话。

遇到的问题:

在 ORM 时,两边的数据类型看起来可能会有些奇怪,但是写错的话也许不容易发现。

对于 HQL 语言开始并没有深刻地理解,它是完全面向对象的,因此将对象名放到语句中,以及大小写敏感也就不足为奇了。

Struts Spring Hibernate和整合(SSH)

表示层:Struts 业务层:Spring 持久层:Hibernate

这个内容我完成得不是很好。SS 已经集成,Hibernate 和 Struts 由于是表示层和持久层的关系,相互并不直接交互,之间的整合没有什么问题。Hibernate 和 Spring 的集成关键就在于配置一个 sessionFactory 的 bean 即可。而 Hibernate 其他类中的大部分,也应该让 Spring 来统一管理。

遇到的问题:

不知道为什么使用 Eclipse 建立 Hibernate 框架时,Eclipse 无法发现已经存在的这个 spring 配置文件,害我手动添加。另一方面,我配置的 SSH 还是用到了 hibernate.cfg.xml,将这个文件和 applicationContext.xml 关联起来,而一些更官方的说法应该是没有 hibernate.cfg.xml 的,因为其中的配置项都放到 applicationContext.xml 中了。

在 Spring 中配置 Hibernate 相关的一些 bean 的时候,都出现了 “Servlet action is not available” 的错误,原因不明。

资源文件:

强烈建议配置好.properties 资源文件,这是避免代码中出现资源字符串,提高移植能力的最好办法。与之类似的一个事情就是 mapping.forward() 方法里面的参数也要在 struts-config.xml 的 global-forwards 标签中配置好。当然,只有在标签库的标签才可以配合使用资源文件。

汉字必须转换成 ascII 码才能存入资源文件,JDK 提供了一个工具,叫 native2ascii,用法如下:

代码语言:javascript复制
native2ascii ChineseInfo.txt target.txt

标签属性值动态设置:

在 jsp 中动态改变标签库中的标签属性值,参考如下:

代码语言:javascript复制
<html:radio value="<%= String.valueOf( ((News)newsList.get(i)).getId() )%>" property="ID">
    <%= ((News)newsList.get(i)).getTitle()%>
</html:radio>

再如:

代码语言:javascript复制
<html:textarea property="text" rows="10" cols="100" value="<%= news.getTitle() %>">
<!-- 要用这样的方法把数据显示在文本区域内 -->

</html:textarea>

要传一些不是很重要但是没必要显示给用户的数据,可以使用<html:hidden property=”xxx” value=”xxxx” /> 的方法,注意它没有标签体。

遇到的问题:

如果使用 bean:message 标签从资源文件里面取字符串,这样的动态方法就不行了,如:

代码语言:javascript复制
<bean:message key="news.importance.< %= String.valueOf(news.getImportance())%>"/>

解决方案:

将属性 key 的值完全构造完毕以后,一次显示,而不要在标签内连结字符串:

代码语言:javascript复制
<% String importance = "news.importance." news.getImportance();%>
<bean:message key="<%= importance %>"/>

另外,jsp 语言可以和 html 语言(包括标签)混合使用,只要使用<% 和%> 区分好界限即可。例如:

代码语言:javascript复制
<%
    if(newsList.size()>=2){
        out.print(newsLeft.getCreateTime().toString());
    else{
%>
        <b><bean:message key="page.leftTime1"/><br></b>
<%
    }
%>

中文问题:

我解决中文问题的方法有四步。

  • 第一, jsp 页面 contentType=”text/html;charset=GBK”。
  • 第二, web.xml 里设置一个 CharacterEncodingFilter,它对应的类实现 javax.servlet.Filter 接口,添加语句:request.setCharacterEncoding(“gbk”) 和 chain.doFilter(request, response),对所有请求(/*)进行过滤操作。
  • 第三, 在连接数据库的语句后绑定传入参数:jdbc:mysql://localhost:3306/test?characterEncoding=gbk
  • 第四, 数据库设置字符集为 gbk。

DAO和 HQL:

在 DAO 里面写 HQL 语句时,可以用后置参数的方法来保证语句的连贯性:

代码语言:javascript复制
String queryString = "from News as model where model."   propertyName   "= ?"; //预留参数用问号代替
Query queryObject = getSession().createQuery(queryString);
queryObject.setParameter(0, value); //这里放置参数

上面的问号也可以用冒号加语句外的变量名代替,这样就相当于后置参数了。

DAO 里写的方法不要放任何和事务相关的操作,让事务的获取、开始和提交都放到 Action 里面去,否则不统一就有可能产生两次提交事务的异常。

遇到的问题:更新一个记录时总是失败,我干脆绕开,使用先删除再添加的方法 “更新”。当然,更好的方法是,将对象取出,处理完毕以后再存入(save 方法),hibernate 会判断当前 id 是否已经存在,不存在就执行 add 操作,存在就执行 update 操作,很方便。

Blob:

Blob 采用单字节存储,适合保存二进制数据;Clob 采用多字节存储,适合保存大型文本数据。

Struts 的文件上传和 Blob 的使用在网上有各种各样的介绍,我总结出这个自认为是最简单易懂的方法:

OR 映射:实体类 News 设属性 private Blob picture,News.hbm.xml 的 hibernate-mapping 标签里面设置<property name=”picture” type=”blob”> <column name=”picture” /> </property>。

保存文件到数据库:

1、jsp 里面:设置<html:form action=”/addNews” enctype=”multipart/form-data”>,这里 enctype=”multipart/form-data” 不可以缺少,form 标签里面设置文件标签:<html:file property=”picture” maxlength=”1000000″></html:file>,遗憾的是,这个标签库并没有提供限定上传文件类型的属性,当然可以使用脚本在客户端限定上传文件的类型:选择文件后,用触发 onchange 事件判断 file 文件的后缀名。

2、对应的 FormBean:import org.apache.struts.upload.FormFile,属性声明:private FormFile picture。

3、Action 里面:先获取 FormFile 对象:FormFile picture = addNewsForm.getPicture(),然后取得输入流:InputStream picIS = picture.getInputStream(),再用 Blob 包装(整个流的读入过程都被透明化了):Blob picBlob = Hibernate.createBlob(picIS),最后保存进对象:news.setPicture(picBlob),并使用 save 方法存入数据库。

从数据库中取出文件:

代码语言:javascript复制
Blob picBlob = news.getPicture();
InputStream is = picBlob.getBinaryStream();
FileOutputStream fos = new FileOutputStream("c:\pic.jpg");
byte[] buf = new byte[10000];
int len;
while( (len=is.read(buf))!=-1 ){
    fos.write(buf,0,len);
}

遇到的问题:

如何将取出来的图片放到客户端供网页显示的指定文件夹(通常在 cookies 内部)内,以便显示?

解决方案:换个思路。将取出的图片放到服务器端一个临时文件夹内,让客户端浏览器自动获取。

客户端总是先朝缓存里面找图片,导致图片过期。尝试:

代码语言:javascript复制
<% 
response.setHeader("Pragma","No-cache"); 
response.setHeader("Cache-Control","no-cache"); 
response.setDateHeader("Expires", 0); 
%>

代码语言:javascript复制
<HEAD> 
    <META HTTP-EQUIV="Pragma" CONTENT="no-cache"> 
    <META HTTP-EQUIV="Cache-Control" CONTENT="no-cache"> 
    <META HTTP-EQUIV="Expires" CONTENT="0"> 
</HEAD>

都不成功。

解决方案:在图片名中包含一个随机数,这样每次的图片文件名都是不同的。

图片获取时,应该为空的 Blob 在获取时发现不是 null,而是一个很小的值,原因不明。

JUnit:

JUnit 是一个集成测试工具,能实现测试的自动化。

如果你要写一段代码:

  1. 先用 JUnit 写测试,然后再写代码。
  2. 写完代码,运行测试,测试失败。
  3. 修改代码,运行测试,直到测试成功。

这就是 JUnit 的测试思想。XP 中推崇的 test first design 就是基于以上技术。

“先写测试,再写代码” 的好处:

  • 1、从技术上强制你先考虑一个类的功能,也就是这个类提供给外部的接口,而不至于太早陷入它的细节。这是面向对象提倡的一种设计原则。
  • 2、好的测试可以产生一个好的文档。特别的,如果你拿到别人的一个程序,对他写测试是了解这个程序功能的很好方法。xp 的原则是 make it simple,不是很推荐另外写文档。
  • 3、需求变动可能会带来其他地方的错误。为此,除了设计好的结构以分割项目外(松耦合),如果有了测试,并已经建立了一个好的测试框架,对于需求的变动,可以即错即改,即地修改,非常方便。

一个 Java 下的 team 开发开发模式:采用 cvs(版本控制) ant(项目管理) junit(集成测试) 的模式:

  1. 每天早上上班,每个开发人员从 cvs server 获取一个整个项目的工作拷贝。
  2. 拿到自己的任务,先用 junit 写今天的任务的测试代码。
  3. 然后写今天任务的代码,运行测试,直到测试通过,任务完成。
  4. 在下班前一两个小时,各个开发人员把任务提交到 cvs server。
  5. 然后由主管对整个项目运行自动测试,哪个测试出错,就找相关人员修改,直到所有测试通过,下班。

简单测试方法:

例如说本来有个待测试的类 SayHello,现在写一个测试类,并且 extends TestCase:

代码语言:javascript复制
assertEquals("Hello,world!", sayHello.sayHello() );

这里还可以有 assertTrue、assertFalse、assertNotNull、assertNull、assertSame 和 assertNotSame 的方法。

接着就可以让 Eclipse 来 run as JUnit Test,当然也可以写个 main 方法,当作普通 Java application 运行。

如果要用 test suite:

代码语言:javascript复制
public static Test suite() {
    TestSuite suite = new TestSuite("Test for com.XY.junit");
    suite.addTestSuite(SayHelloTest.class);
    return suite;
}

其他体会:

SSH 的核心在于 Spring 的 IoC 模式,它可以统一管理各层,而又使得各层松散地耦合在一起,各层之间实现最大的解耦性,这也是 web 统一架构的追求。

在学习这些东西的时候,我认为最重要的是要理解其原理,包括架构、流程,以及一些精巧的构思,而不是钻在某个具体类方法或者某种 IDE 的便捷途径上。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

×Scan to share with WeChat

0 人点赞