Skip to content

四火的唠叨

一个纯正程序员的啰嗦

Menu
  • 所有文章
  • About Me
  • 关于四火
  • 旅行映像
  • 独立游戏
  • 资源链接
Menu

SSH 学习杂记

Posted on 03/28/200806/23/2019 by 四火

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

Struts:

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

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

clip_image002

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 是一个从实际项目开发经验中抽取的,可高度重用的应用框架。

clip_image003

由 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 提供的所有服务(依赖注入、实例管理、事务管理等)。

clip_image004

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

结构图示:

clip_image005

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,用法如下:

native2ascii ChineseInfo.txt target.txt

标签属性值动态设置:

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

<html:radio value="<%= String.valueOf( ((News)newsList.get(i)).getId() )%>" property="ID">
    <%= ((News)newsList.get(i)).getTitle()%>
</html:radio>

再如:

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

</html:textarea>

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

遇到的问题:

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

<bean:message key="news.importance.< %= String.valueOf(news.getImportance())%>"/>

解决方案:

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

<% String importance = "news.importance."+news.getImportance();%>
<bean:message key="<%= importance %>"/>

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

<%
    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 语句时,可以用后置参数的方法来保证语句的连贯性:

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 方法存入数据库。

从数据库中取出文件:

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 内部)内,以便显示?

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

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

<% 
response.setHeader("Pragma","No-cache"); 
response.setHeader("Cache-Control","no-cache"); 
response.setDateHeader("Expires", 0); 
%>

或

<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:

assertEquals("Hello,world!", sayHello.sayHello() );

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

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

如果要用 test suite:

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

你可能也喜欢看:

  1. C++学习杂记
  2. A page widgetization practice
  3. 从错误页面的角度看用户体验
  4. Javascript Memoizer
  5. Memcached 调优

1 thought on “SSH 学习杂记”

  1. resume database Software says:
    10/20/2014 at 4:23 PM

    Great article.

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

订阅·联系

四火,啰嗦的程序员一枚,现居西雅图

Amazon Google Groovy Hadoop Haskell Java JavaScript LeetCode Oracle Spark 互联网 亚马逊 前端 华为 历史 同步 团队 图解笔记 基础设施 工作 工作流 工具 工程师 应用系统 异步 微博 思考 技术 数据库 曼联 测试 生活 眼界 程序员 管理 系统设计 缓存 编程范型 美股 英语 西雅图 设计 问题 面向对象 面试

分类

  • Algorithm and Data Structure (30)
  • Concurrency and Asynchronization (6)
  • System Architecture and Design (43)
  • Distributed System (18)
  • Tools Frameworks and Libs (13)
  • Storage and Data Access (8)
  • Front-end Development (33)
  • Programming Languages and Paradigms (55)
  • Testing and Quality Assurance (4)
  • Network and Communication (6)
  • Authentication and Authorization (6)
  • Automation and Operation Excellence (13)
  • Machine Learning and Artificial Intelligence (6)
  • Product Design (7)
  • Hiring and Interviews (14)
  • Project and Team Management (14)
  • Engineering Culture (17)
  • Critical Thinking (25)
  • Career Growth (57)
  • Life Experience and Thoughts (45)

推荐文章

  • 聊一聊分布式系统中的时间
  • 谈谈分布式锁
  • 常见分布式系统设计图解(汇总)
  • 系统设计中的快速估算技巧
  • 从链表存在环的问题说起
  • 技术面试中,什么样的问题才是好问题?
  • 从物理时钟到逻辑时钟
  • 近期面试观摩的一些思考
  • RSA 背后的算法
  • 谈谈 Ops(汇总 + 最终篇):工具和实践
  • 不要让业务牵着鼻子走
  • 倔强的程序员
  • 谈谈微信的信息流
  • 评审的艺术——谈谈现实中的代码评审
  • Blog 安全问题小记
  • 求第 K 个数的问题
  • 一些前端框架的比较(下)——Ember.js 和 React
  • 一些前端框架的比较(上)——GWT、AngularJS 和 Backbone.js
  • 工作流系统的设计
  • Spark 的性能调优
  • “残酷” 的事实
  • 七年工作,几个故事
  • 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(汇总)
  • 一道随机数题目的求解
  • 层次
  • Dynamo 的实现技术和去中心化
  • 也谈谈全栈工程师
  • 多重继承的演变
  • 编程范型:工具的选择
  • GWT 初体验
  • java.util.concurrent 并发包诸类概览
  • 从 DCL 的对象安全发布谈起
  • 不同团队的困惑
  • 不适合 Hadoop 解决的问题
  • 留心那些潜在的系统设计问题
  • 再谈大楼扔鸡蛋的问题
  • 几种华丽无比的开发方式
  • 我眼中的工程师文化
  • 观点的碰撞
  • 谈谈盗版软件问题
  • 对几个软件开发传统观点的质疑和反驳
  • MVC 框架的映射和解耦
  • 编程的未来
  • DAO 的演进
  • 致那些自嘲码农的苦逼程序员
  • Java 多线程发展简史
  • 珍爱生命,远离微博
  • 网站性能优化的三重境界
  • OSCache 框架源码解析
  • “ 你不适合做程序员”
  • 画圆画方的故事

近期评论

  • Ticket: TRANSACTION 1.922915 BTC. Go to withdrawal >> https://yandex.com/poll/enter/BXidu5Ewa8hnAFoFznqSi9?hs=20bd550f65c6e03103876b28cabc4da6& on 倔强的程序员
  • panshenlian.com on 初涉 ML Workflow 系统:Kubeflow Pipelines、Flyte 和 Metaflow
  • panzhixiang on 关于近期求职的近况和思考
  • Anonymous on 闲聊投资:亲自体验和护城河
  • 四火 on 关于近期求职的近况和思考
  • YC on 关于近期求职的近况和思考
  • mafulong on 常见分布式基础设施系统设计图解(四):分布式工作流系统
  • 四火 on 常见分布式基础设施系统设计图解(八):分布式键值存储系统
  • Anonymous on 我裸辞了
  • https://umlcn.com on 资源链接
© 2025 四火的唠叨 | Powered by Minimalist Blog WordPress Theme