您好,欢迎来到独旅网。
搜索
您的当前位置:首页spring教程

spring教程

来源:独旅网
6.1.1 爱上Spring 2.5的十大理由

\"酒香不怕巷子深\",短短四五年时间,Spring凭借其独特的魅力,神速般地成长为Java EE技术领域的事实标准。它是如此轻巧、便捷地为Java EE企业级应用开发提供了整体技术解决方案,为往昔焦头烂额的Java EE程序员指明了一条充满快乐与享受的春光大道。

如果你还是一名Spring的观望者或初学者,如果你还在为是否投身Spring应用的滚滚大潮犹豫不决的话,我会向你推荐一位让你一见钟情的俊俏姑娘。

面向接口编程,不重复发明轮子:接口定义的是规范,描述的是功能,在软件分层开发模式中尤为重要。一个接口,根据不同的个性化需求可以产生多个不同的实现,将接口与实现进行分离,大大降低了组件之间的耦合度,提高了组件的可测试性与相对性。

基于工厂模式的IoC容器:将Java EE应用中的对象全部交由Spring的Bean工厂进行生成、装配与生命周期的管理,这所Bean工厂肩负JavaBean的实例化、依赖关系的装配及高效的生命周期管理,为Java EE程序员分担了Bean管理的艰巨任务,让Java EE程序员享受到前所未有的\"衣来伸手,饭来张口\"的待遇。

面向切面编程AOP的完美实现:将业务逻辑中到处重复出现的一些诸如日志输出、事务控制及权限控制等代码全面抽取出来,集中放置到某个地方保存起来。使得Java EE程序员只要关注真正的业务逻辑处理即可,大大提高开发效率,最后在具体运行时,再由Spring的AOP模块自动完成重组,使得主业务逻辑代码与共有功能代码完美复合,最终实现我们预期的功能目标。

提供了大量实用的Java EE企业级服务支持:Spring作为一个轻量级的Java EE技术解决方案,如何更好地规范与简化各种Java EE企业级服务的应用是至关重要的。令我们欣慰的是,Spring出色地整合了目前常用的Java EE企业级服务,例如,事务管理、持久化服务、JMS消息服务、RMI远程调用、电子邮件、WebService、任务调度与EJB支持等。

兼容Java应用与Java Web应用:只要有Java身影的地方,就可以有Spring的用武之地。因此,不管是开发Java应用还是Java Web应用,均可受益于Spring。

模块化的架构,满足Java EE程序员的多样化需求:Spring开发团队当时在设计Spring时,就充分考虑到了Java EE程序员的多样化需求,最终以功能模块堆砌的架构展现出来,便于Java EE程序员选择应用,提高了Spring框架的应用灵活性。例如,在基于PDA等移动终端的Java应用开发时,可仅选用Spring的IoC模块;然而,在基于PC的电子商务应用开发中,则可同时选用Spring的IoC与AOP模块,甚至Spring自带的MVC框架,仁者见仁,智者见智。

轻重级:所谓轻量,并非论斤两、量体积。是指其运行过程中消耗资源少、开发过程轻便快捷,一切应用基于普通的JavaBean来实现,不需要专用的容器支持,面向接口编程,耦合度低,便于测试。 全面支持Annotation注解技术,大大简化配置工作:自JDK1.5推出Annotation技术以来,所有框架都争先恐后地加入到Annotation热潮中来,向着\"零配置\"的巅峰冲刺。 使用Spring自带的辅助工具类简化Java EE应用的开发,提高开发效率:例如,使用Spring提供的CharacterEncodingFilter过滤器可解决Java Web应用中的中文编码问题;使用WebApplicationContextUtils类的getRequiredWebApplicationContext方法使得在JSP与Servlet中取得IoC容器的引用;使用HibernateTemplate可快速方便地调用Hibernate Session完成数据库的操作;使用JdbcTemplate简化JDBC的数据库操作等。 与Struts、Hibernate等名流框架无缝集成:尽管Spring致力于提供\"一站式\"服务,在MVC应用领域,大家还是会被Struts的出色表现所折服,谈到ORM,总是忘不了Hibernate娴熟的身影。Spring宽广的胸怀,为Struts与Hibernate敞开大门,提供了极为便利的集成方式,让SSH(Spring+Struts+Hibernate)这个崭新的组合框架从此映入Java EE程序员的眼帘,成为Java EE应用开发史上的新神话。 6.1.2 一睹Spring 2.5芳容 \"千呼万唤始出来,犹抱琵琶半遮面\",现在到了揭开Spring 2.5庐山真面目的时候了。前面我们已经简单提到过Spring的模块化结构,如图6-1所示,正是由于这种模块化的结构,便于Java EE程序员选择应用,提高了Spring框架的应用灵活性。 核心容器模块Core:这是整个Spring的核心模块,为整个Spring框架提供最基础的功能支撑。主要包括控制反转IoC容器、Bean工厂及辅助工具类。大部分中小型Java应用或Java Web应用主要使用的就是这个模块的功能,这也是Spring框架的\"点睛之笔\"。

面向切面编程模块AOP:面向切面编程是\"不重复发明轮子\"的又一体现。从图6-1可看出,Spring的AOP模块为数据访问模块DAO与对象持久化模块ORM提供了良好的切面支持,怪不得在相当一部分的Java EE程序员心目中,整个Spring就等于IoC加AOP,这足以说明AOP模块的重要地位不亚于IoC模块。Spring的大部分AOP是基于Alliance的API开发的,并引入了metadata编程,使得通过代码中的Annotation注解来简化AOP的配置工作。

数据访问模块DAO:DAO(Database Access Object),顾名思义就是数据库访问接口。使用DAO的目的就是将数据控制层和业务逻辑控制层分离开来,既有利于项目开发时的功能划分与分工协作,在项目维护时也更容易定位和解决问题。目前全世界仍然有一大群直接使用JDBC进行数据访问的Java EE程序员,为了规范与简化JDBC的数据访问,Spring的DAO模块对JDBC进行抽象与封装,并提供辅助类JdbcTemplate简化JDBC的数据库操作。同时,充分利用AOP模块提供的面向切面功能,为Spring系统中的对象提供事务管理服务。

对象持久化模块ORM:\"不重复发明轮子\"是Spring一贯的作风,在目前优秀ORM框架云集的时代,Spring不想也没必要实现自己的ORM解决方案。\"不求所有,但求所用\"是智者的行为,Spring为当今流行的各大ORM框架均预留一席之地,并对这些ORM框架提供了良好的封装与集成支持。

企业级服务模块JEE:Spring作为一个轻量级的Java EE技术解决方案,如何更好地规范与简化各种Java EE企业级服务的应用是至关重要的。令我们欣慰的是,Spring出色地整合了目前常用的Java EE企业级服务,例如,事务管理、持久化服务、JMS消息服务、RMI远程调用、电子邮件、WebService、任务调度与EJB支持等。

Spring的Web模块:Spring为Java Web应用提供了自己的MVC框架,其实Spring此举并非\"重复发明轮子\"。因为,在Spring的MVC框架中充分利用了IoC技术将控制逻辑与业务逻辑进行清晰的划分,\"近水楼台先得月\",Spring的MVC框架抢先享用了Spring的企业级服务。当然,对于热衷于其他MVC的Java EE程序员而言,Spring同样为其他MVC框架提供了良好的集成支持,这足以体现Spring的宽广胸怀。

6.1.3 Spring 2.5拿手戏--控制反转与依赖注入

人类社会由数以亿计的拥有不同本领的人组成,通过这些人的不同分工协作完成了许多不可思议的惊天杰作,从而呈现出一幅又一幅美妙的生活全景图。我们习惯将这种组合称为团队,团队讲求的是分工与协作,谁也离不开谁。在Java EE的世界里,充满了身怀绝技的Java类,通过这云云Java类的分工协作,造就了五彩缤纷的Java EE应用王国。

当我们需要别人的帮助才能完成某项工作时,毫不犹豫地就会想到\"请\",这种\"请\"无疑是一种盛情的、主动的\"请求\"。沿用这种处事风格,我们的Java EE应用开发走过了漫长的\"请\"的岁月。例如:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30.

/** 负责处理用户管理底层数据访问 */ public class UserDao{ //保存用户数据

public void saveUser(User user){ //进行数据的保存工作,省略代码 }

...... }

/** 负责处理用户管理业务逻辑 */ public class UserService{ //主动创建数据访问组件

UserDao dao = new UserDao(); //处理新增用户业务逻辑

public void addUser(String userName,String userPwd){ User user = new User(); user.setUserName(userName); user.setUserPwd(userPwd);

//调用UserDao的saveUser方法进行数据保存 dao.saveUser(user); }

...... }

/** 负责处理用户管理业务请求 */ public class UserAction{ //主动创建业务逻辑处理组件

UserService service = new UserService(); //处理新增用户请求

public void addUser(){

31. 32. 33. 34. 35. 36. 37.

String userName = \"liubin\"; String userPwd = \"123456\";

//调用UserService的addUser方法进行业务逻辑处理 service.addUser(userName,userPwd); } ...... }

上面这种风格(见图6-2)的代码是那么眼熟,又是那么亲切,毕竟用了那么多年,不知是习惯了还是麻木了。在一个类(如UserAction类)中需要用到另一个类(如UserService类),也就是一个类依赖另一个类时,顺手就是一个\"new\",当应用变得庞大,依赖关系网也随之复杂,为测试与维护带来的困难呈指数上升。 那能不能做到\"不请自来\"呢?答案是可以的。想象一下,如果有一座工厂专门负责生产Bean实例的话,\"new\"的工作就可以交给它了,倘若还能\"送货上门\"的话,自然也就\"不请自来\"。这就是Spring最初的动机,自Spring呱呱坠地之日起,理想变为现实,从此Java EE应用开发步入了\"不请自来\"的时代。例如:

1. 2. 3. 4. 5. 6. 7. 8. 9.

/** 负责处理用户管理底层数据访问 */ public class UserDao{ //保存用户数据

public void saveUser(User user){ //进行数据的保存工作,省略代码 }

...... }

10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47.

/** 负责处理用户管理业务逻辑 */ public class UserService{ //仅声明数据访问组件的引用 UserDao dao; //处理新增用户业务逻辑

public void addUser(String userName,String userPwd){ User user = new User(); user.setUserName(userName); user.setUserPwd(userPwd);

//调用UserDao的saveUser方法进行数据保存 dao.saveUser(user); }

//提供一条UserDao对象的注入通道 public void setDao(UserDao dao){ this.dao=dao; } ...... }

/** 负责生产与分发Bean的小作坊 */ public class BeanFactory{ //生产一个数据访问组件

UserDao dao = new UserDao(); //再生产一个业务逻辑处理组件

UserService service = new UserService(); //将数据访问组件装配到业务逻辑处理组件中 service.setDao(dao); //分发 业务逻辑处理组件

public UserService getUserService(){ return service } }

/** 负责处理用户管理业务请求 */ public class UserAction{ //实例化Bean小作坊

BeanFactory beanFactory=new BeanFactory(); //从Bean小作坊中取出业务逻辑处理组件实例

48. 49. 50. 51. 52. 53. . 55. 56. 57.

UserService service=beanFactory.getUserService(); //处理新增用户请求

public void addUser(){

String userName = \"liubin\"; String userPwd = \"123456\";

//调用UserService的addUser方法进行业务逻辑处理 service.addUser(userName,userPwd); } ...... }

上述代码中,BeanFactory是我们特意设计的一个Bean小作坊,用于生产、装配与分发Bean实例。大家可以看到,业务控制器UserAction中从Bean小作坊取回的业务逻辑组件是已经完全装配好的成品Bean,在业务逻辑组件UserService中再也看不到往昔\"new\"的身影了,这种基于Bean容器的设计模式,使控制层、逻辑层及数据层实现充分解耦,如图6-3所示,可以提高可测试性与可维护性。 于是很多保守与固执的\"老资历\"Java EE工程师开始呐喊:\"反了!反了!!\"。没错,是反了,是控制权发生了反转,应用本身不再负责依赖对象的创建及维护,依赖对象的创建及维护交由外部容器负责。这样控制权就由应用转移到了外部容器,这种控制权的转移就是所谓的控制反转或反转控制。

所谓依赖注入就是指程序在运行期,由外部容器动态地将依赖对象注入到组件中。这种依赖注入的过程就如同生产车间将零件装配到机器上一样,注入的过程其实就是一种装配的过程。 正是基于这种思想,Spring才推出了功能强大的Bean工厂与控制反转IoC容器,在IoC容器里通过简单地装配完成Bean实例之间的依赖注入。 6.1.4 何为\"面向切面编程AOP\" 在传统的编写业务逻辑处理代码时,我们通常会习惯性地做几件事情:日志记录、事务控制及权限控制等,然后才是编写核心的业务逻辑处理代码。当代码编写完成回头再看时,不禁发现,扬扬洒洒上百行代码中,真正用于核心业务逻辑处理才那么几行,如图6-4所示。方法复方法,类复类,就这样子带着无可奈何遗憾地度过了多少个春秋。这倒也罢,倘若到了项目的尾声,突然决定在权限控制上需要进行大的变动时,成千上万个方法又得一一\"登门拜访\",痛苦\"雪上加霜\"。 如果能把图6-4中众多方法中的所有共有代码全部抽取出来,放置到某个地方集中管理,然后在具体运行时,再由容器动态织入这些共有代码的话,最起码可以解决两个问题:

Java EE程序员在编写具体的业务逻辑处理方法时,只需关心核心的业务逻辑处理,既提高了工作效率,又使代码变更简洁优雅。

在日后的维护中由于业务逻辑代码与共有代码分开存放,而且共有代码是集中存放的,因此使维护工作变得简单轻松。

面向切面编程AOP技术就是为解决这个问题而诞生的,切面就是横切面,如图6-5所示,代表的是一个普遍存在的共有功能,例如,日志切面、权限切面及事务切面等。 下面我们以用户管理业务逻辑组件User Service的AOP实现过程(见图6-6)为例,深度剖析一下AOP技术的实现原理。AOP技术是建立在Java语言的反射机制与动态代理机制之上的。业务逻辑组件在运行过程中,AOP容器会动态创建一个代理对象供使用者调用,该代理对象已经按Java EE程序员的意图将切面成功切入到目标方法的连接点上,从而使切面的功能与业务逻辑的功能同时得以执行。从原理上讲,调用者直接调用的其实是AOP容器动态生成的代理对象,再由代理对象调用目标对象完成原始的业务逻辑处理,而代理对象则已经将切面与业务逻辑方法进行了合成。

现将图6-6中涉及到的一些概念解释如下。

切面(Aspect):其实就是共有功能的实现。如日志切面、权限切面、事务切面等。在实际应用中通常是一个存放共有功能实现的普通Java类,之所以能被AOP容器识别成切面,是在配置中指定的。

通知(Advice):是切面的具体实现。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(After Returning)、异常通知(After Throwing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。

连接点(Join point):就是程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出或字段修改等,但Spring只支持方法级的连接点。

切入点(Point cut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。

目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象。这些对象中已经只剩下干干净净的核心业务逻辑代码了,所有的共有功能代码等待AOP容器的切入。

代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。

织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。 6.1.5 Spring 2.5圣经--面向接口编程 面向接口编程是组件间实现高度解耦的灵丹妙药,是组件化软件开发的基础,同时也是一种良好的编程习惯,在Spring应用开发中尤显重要。Spring的反转控制Icon与面向切面编程AOP的很多实现都强烈要求程序员进行面向接口编程。例如:

6.1.6 开始Spring 2.5旅程--Hello World

在对Spring 2.5有了一个初步的了解之后,下面我们将以Spring 2.5在Java App中的应用SpringHelloWorld项目为例,全面演示一下Spring的基本使用步骤,请大家重点注意一下面向接口编程的实际应用及applicationContext.xml的具体配置。

(1)创建一个\"Java Project\",命名为\"SpringHelloWorld\"。 (2)创建一个文件夹\"lib\"用于存放Spring 2.5框架的Jar包。

(3)将Spring 2.5所需的spring-beans.jar、spring-context.jar、spring-core.jar、commons- attributes-compiler.jar、commons-attributes-api.jar、commons-logging.jar及log4j-1.2.15.jar复制到lib文件夹下,将这些Jar包加入到项目的Classpath中。

(4)创建以下几个包,用于存放项目的类或接口:test.spring.dao包(存放数据访问接口)、test.spring.dao.impl包(存放数据访问接口实现)、test.spring.service包(存放业务逻辑接口)、test.spring.service.impl包(存放业务逻辑接口实现)、test.spring.action包(存放业务控制组件)、test.spring.bean包(存放ORM对象)、test.spring.junit包(存放Junit测试用例)。

(5)编写用户ORM类User,代码如下:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.

package test.spring.bean; /** 用户持久化类 */ public class User { Integer id;//自然ID号 String userName; //用户名 String userPwd;//用户密码 //默认构造方法 public User(){} //自定义构造方法

public User(Integer id, String userName, String userPwd) { this.id = id;

this.userName = userName; this.userPwd = userPwd; }

//省略属性的get/set方法对 }

(6)编写数据访问接口UserDao,代码如下:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13.

package test.spring.dao; import test.spring.bean.User; /** 用户管理底层数据访问接口 */ public interface UserDao { //处理新增用户业务逻辑

public void addUser(User user); //处理装载用户业务逻辑

public User loadUser(Integer id); //处理修改用户业务逻辑

public void modiUser(User user); //处理删除用户业务逻辑

public void delUser(Integer id); }

(7)编写数据访问接口实现类UserDaoImpl,代码如下:

1. 2. 3. 4.

package test.spring.dao.impl; import test.spring.bean.User; import test.spring.dao.UserDao; /** 用户管理底层数据访问接口实现 */

5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26.

public class UserDaoImpl implements UserDao { //处理新增用户业务逻辑

public void addUser(User user) {

//由于该实例未实现数据库访问,省略插入数据库记录的代码

System.out.println(\"用户名为\"+user.getUserName()+\"的用户新增成功!\"); }

//处理删除用户业务逻辑

public void delUser(Integer id) {

//由于该实例未实现数据库访问,省略删除数据库记录的代码 System.out.println(\"ID号为\"+id+\"的用户删除成功!\"); }

//处理装载用户业务逻辑

public User loadUser(Integer id) {

//由于该实例未实现数据库访问,省略读取数据库记录的代码 return new User(1,\"liubin\",\"123456\"); }

//处理修改用户业务逻辑

public void modiUser(User user) {

//由于该实例未实现数据库访问,省略更新数据库记录的代码

System.out.println(\"ID号为\"+user.getId()+\"的用户修改成功!\"); } }

(8)编写业务逻辑接口UserService,代码如下:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13.

package test.spring.service; import test.spring.bean.User; /** 用户管理业务逻辑接口 */

public interface UserService { //处理新增用户业务逻辑

public void addUser(String userName,String userPwd); //处理装载用户业务逻辑

public User loadUser(Integer id); //处理修改用户业务逻辑

public void modiUser(Integer id,String userName,String userPwd); //处理删除用户业务逻辑

public void delUser(Integer id); }

(9)编写业务逻辑接口实现类UserServiceImpl,代码如下:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25.

package test.spring.service.impl; import test.spring.bean.User; import test.spring.dao.UserDao;

import test.spring.service.UserService; /** 用户管理业务逻辑接口实现 */

public class UserServiceImpl implements UserService { //仅声明数据访问组件的引用 UserDao dao; //处理新增用户业务逻辑

public void addUser(String userName, String userPwd) { User user = new User(); user.setUserName(userName); user.setUserPwd(userPwd); dao.addUser(user); }

//处理删除用户业务逻辑

public void delUser(Integer id) { dao.delUser(id); }

//处理装载用户业务逻辑

public User loadUser(Integer id) { return dao.loadUser(id); }

//处理修改用户业务逻辑

public void modiUser(Integer id, String userName, String userPwd) {

26. 27. 28. 29. 30. 31. 32. 33. 34. 35.

User user = dao.loadUser(id); user.setUserName(userName); user.setUserPwd(userPwd); dao.modiUser(user); }

//提供一条UserDao对象的注入通道 public void setDao(UserDao dao){ this.dao=dao; } }

(10)编写业务控制器UserAction,代码如下:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34.

package test.spring.action; import test.spring.bean.User;

import test.spring.service.UserService; /** 用户管理业务控制器 */ public class UserAction { //仅声明业务逻辑组件的引用 UserService service; //处理新增用户请求

public void addUser() { String userName=\"liubin\"; String userPwd=\"123456\";

service.addUser(userName,userPwd); }

//处理删除用户请求

public void delUser() { service.delUser(1); }

//处理装载用户请求

public void loadUser() {

User user = service.loadUser(1);

System.out.println(\"用户名=\"+user.getUserName()); }

//处理修改用户请求

public void modiUser() { Integer id = 1;

String userName=\"liujunyu\"; String userPwd=\"123456\";

service.modiUser(id, userName, userPwd); }

//提供一条UserService对象的注入通道

public void setService(UserService service) { this.service = service; } }

(11)创建Spring 2.5的配置文件applicationContext.xml,并在该配置文件中配置数据访问实现类UserDaoImpl、业务逻辑实现类UserServiceImpl、业务控制器UserAction,代码如下:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10.

xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"

xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd\">

11. 12. 13. 14. 15. 16.

17. 18. 19.

(12)为UserAction创建一个Junit的测试testUserAction,分别对其中的方法进行测试,代码如下:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.

package test.spring.junit; import org.junit.BeforeClass; import org.junit.Test;

import org.springframework.context.ApplicationContext; import org.springframework.context.support. ClassPathXmlApplicationContext; import test.spring.action.UserAction; /** 用户管理业务控制器的测试用例 */ public class testUserAction { static ApplicationContext cxt; static UserAction userAction; //初始化ApplicationContext容器 @BeforeClass

public static void setUpBeforeClass() throws Exception {

15. 16.

//使用ClassPathXmlApplicationContext方式初始化ApplicationContext容器 cxt = new ClassPathXmlApplicationContext(\"applicationContext.xml\");

17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40.

//从Bean工厂容器中获取名为\"userAction\"的UserAction实例 userAction = (UserAction)cxt.getBean(\"userAction\"); }

//测试UserAction的AddUser方法 @Test

public void testAddUser() { userAction.addUser(); }

//测试UserAction的DelUser方法 @Test

public void testDelUser() { userAction.delUser(); }

//测试UserAction的LoadUser方法 @Test

public void testLoadUser() { userAction.loadUser(); }

//测试UserAction的ModiUser方法 @Test

public void testModiUser() { userAction.modiUser(); } }

运行测试用例,控制台打印出预想的提示信息,说明Spring 2.5在SpringHelloWorld项目被成功应用。该实例各组件之间的关系如图6-7所示。

6.2 Spring 2.5核心技术

通过6.1节的学习,大家对Spring 2.5从组成到功能及一些基本概念等应该有了一个初步的了解了。这些功能到底是如何实现的呢?在实际的项目中又该如何应用呢?带着这些疑问,下面我们一起来探究一下Spring 2.5的技术内幕。

6.2.1 Bean工厂之BeanFactory介绍

BeanFactory顾名思义是Bean工厂的意思,采用的是工厂设计模式。Bean工厂的神圣职责就是负责Bean实例的创建、Bean实例之间依赖关系的装配及Bean实例的分发。这是Spring的核心技术之一,作为工厂,\"看图生产\"是必不可少的,同理,我们必须为Bean工厂提供一份\"生产图纸\"用于指导生产,这就是前面提到的Spring的配置文件(如

applicationContext.xml),Spring并未规定配置文件的命名及数量,起什么名字都行,多个配置文件也可以。Spring容器在实例化时就会装载这些配置文件,并进行\"看图生产\"。

基于\"面向接口编程\"的指导思想,BeanFactory被定义为接口,XmlBeanFactory是BeanFactory接口最常用的实现类。XmlBeanFactory通过装载指定的XML配置文件实例化BeanFactory容器。例如:

1.

//通过装载指定的XML配置文件实例化BeanFactory容器

2. Resource res = new FileSystemResource(\"c:/applicationContext.xml\");

3. 4. 5.

BeanFactory factory = new XmlBeanFactory(res); //从Bean工厂容器中获取名为\"userAction\"的UserAction实例 userAction = (UserAction)factory.getBean(\"userAction\");

BeanFactory容器实例化之后,我们就可以使用getBean方法从BeanFactory容器中获取装配好的Bean实例了。

Spring开发团队在设计Spring时,考虑到一些受限资源的应用场合,譬如PDA设备上的Java应用等,故特意将BeanFactory容器尽可能地精简,以减少资源的消耗。但对于那些需要国际化支持、Bean事件发布与监听的应用来讲,BeanFactory显然有点力不从心了。于是通过继承BeanFactory接口,又创建了ApplicationContext与WebApplicationContext等子接口,用以满足Java EE应用的开发。

6.2.2 实用的Bean工厂ApplicationContext

ApplicationContext的中文意思是\"应用上下文\",它继承自BeanFactory接口,除了包含BeanFactory的所有功能之外,在国际化支持、资源访问(如URL和文件)、事件传播等方面进行了良好的支持,被推荐为Java EE应用之首选,可应用在Java APP与Java Web中。

在ApplicationContext接口的众多实现类中,有3个是我们经常用到的(见表6-1),并且使用这3个实现类也基本能满足我们Java EE应用开发中的绝大部分需求。

表6-1 ApplicationContext接口的常用实现类介绍

这些实现类的主要区别就是装载Spring配置文件实例化ApplicationContext容器的方式不同,在ApplicationContext实例化后,同样通过getBean方法从ApplicationContext容器中获取装配好的Bean实例以供使用。

与BeanFactory不同的是,ApplicationContext容器实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。而BeanFactory容器实例化后并不会自动实例化Bean,只有当Bean被使用时BeanFactory容器才会对该Bean进行实例化与依赖关系的装配。

在Java项目中通过ClassPathXmlApplicationContext类手动实例化ApplicationContext容器通常是不二之选。但对于Web项目就不行了,Web项目的启动是由相应的Web服务器负责的,因此,在Web项目中ApplicationContext容器的实例化工作最好交给Web服务器来完成。

Spring为此提供了两种解决方案,一种是基于ContextLoaderListener实现的(此方案只适用于Servlet 2.4及以上规范的Servlet容器)。例如,在web.xml中加入如下代码:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10.

contextConfigLocation

/WEB-INF/applicationContext.xml

org.springframework.web.context. ContextLoaderListener

别一种方案则是基于ContextLoaderServlet实现的。例如,在web.xml中加入如下代码:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12.

contextConfigLocation

/WEB-INF/applicationContext.xml

context

org.springframework.web.context. ContextLoaderServlet 1

从Servlet容器启动时加载组件的顺序来看,Listener组件是优先于Servlet组件的。基于Servlet方式的加载方案主要是为了兼容Servlet 2.3及以下规范的Servlet容器。以Tomcat为例,Tomcat 5.x都已经支持Servlet 2.4规范了,因此,基于Listener方式启动Spring容器是目前的主流选择。

6.2.3 Spring 2.5配置文件详解

Spring配置文件是用于指导Spring工厂进行Bean生产、依赖关系注入(装配)及Bean实例分发的\"图纸\"。Java EE程序员必须学会并灵活应用这份\"图纸\"准确地表达自己的\"生产

意图\"。Spring配置文件是一个或多个标准的XML文档,applicationContext.xml是Spring的默认配置文件,当容器启动时找不到指定的配置文档时,将会尝试加载这个默认的配置文件。 在学会动手\"绘制图纸\"之前,先要学会\"阅读图纸\",熟能生巧讲的就是这个道理,\"熟读唐诗三百首,不会作诗也会吟\"。 下面列举的是一份比较完整的配置文件模板,文档中各XML标签节点的基本用途也给出了详细的解释,这些XML标签节点在后续的知识点中均会用到,熟练掌握了这些XML节点及属性的用途后,为我们动手编写配置文件打下坚实的基础。

下面我们再来看一下本书综合实例项目中用到的完整配置文件applicationContext.xml:

1. 2. 3. 4. 5. 6. 7. 8.

xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"

xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd\">

9. 10. 11. 12. 13. 14. 15.

com.mysql.jdbc.Driver

jdbc:mysql://localhost:3306/eportal?useUnicode=

16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53.

true&characterEncoding=gbk

root

root

20

2

2

20

com/eportal/ORM/News.hbm.xml com/eportal/ORM/Category.hbm.xml com/eportal/ORM/Memberlevel.hbm.xml

. 55. 56. 57. 58. 59. 60. 61. 62. 63. . 65. 66. 67. 68. 69.

com/eportal/ORM/Cart.hbm.xml com/eportal/ORM/Traffic.hbm.xml com/eportal/ORM/Newsrule.hbm.xml com/eportal/ORM/Merchandise.hbm.xml com/eportal/ORM/Admin.hbm.xml com/eportal/ORM/Orders.hbm.xml

com/eportal/ORM/Cartselectedmer.hbm.xml com/eportal/ORM/Newscolumns.hbm.xml com/eportal/ORM/Member.hbm.xml

org.hibernate.dialect.MySQLDialectop> 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80.

true

50 50

class=\"org.springframework.orm.hibernate3.HibernateTransactionManage

r\"> 81. 82. 83. 84. 85. 86.

class=\"org.springframework.transaction.interceptor.TransactionInterc

eptor\"> 87. 88.

. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99.

PROPAGATION_REQUIRED,readOnly PROPAGATION_REQUIRED,readOnly PROPAGATION_REQUIRED,readOnly PROPAGATION_REQUIRED,readOnly PROPAGATION_REQUIRED,readOnly PROPAGATION_REQUIRED

100. 101. 102.

103. 104.

106. 107. 108.

109. adminService 110. columnsService 111. newsService 112. crawlService 113. memberLevelService 114. memberService 115. categoryService 116. merService 117. cartService 118. ordersService 119. trafficService 120. 121.

122. 123. 124. true 125.

126.

127. 128.

129. transactionInterceptor 130. 131. 132.

133.

134. 135. 136.

137.

138. >

139. 140.

141.

142. mpl\">

143. 144.

145.

146.

147. 148.

149.

150. >

151. 152.

153. 1.

156. 157.

158.

159. l\">

160. 161.

162.

163. eImpl\">

1. 165.

166.

167. mpl\">

168. 169.

170.

171.

172. 173.

174.

175. \">

176. 177.

178.

179. mpl\">

180. 181.

182. 183.

185. 186.

187. 188.

190. 191.

192.

193.

195.

196. 197.

198. 199.

201.

202. 203.

204. 205. evelAction\"

206. scope=\"prototype\">

207. 208.

209. 210.

212. 213.

214. 215.

216. 217.

219. 220.

221.

222. ndiseAction\"

223. scope=\"prototype\">

224.

225. 226.

227. 228. 229. action.CartAction\" scope=\"prototype\">

230. 231. 232.

233. 234.

236. 237. 238. 239.

240. 241.

243. 244. 245.

6.2.4 Bean的作用域与生命周期 用时空概念来解释的话,Bean的作用域就是Bean实例的生存空间或叫有效范围,Bean的生命周期就是Bean实例从诞生到消亡的时间周期。Spring 2.5为Bean实例定义了5种作用域用以满足不同情况下的应用需求,如表6-2所示。 表6-2 Spring 2.5中Bean实例的作用域说明 作用域名称 功 能 描 述 单实例作用域,这是Spring容器默认的作用域,使用singleton作用域生成的是单实例,在整个Bean容器中仅保留一个实例对象供所有调用者共享引用。单例模式对于那些无会话状态的Bean(如辅助工具类、DAO组件、业务逻辑组件等)是最理想的选择。例如: singleton 原型模式,这是多实例作用域,针对每次不同的请求,Bean容器均会生成一个全新的Bean实例以供调用者使用。prototype作用域非常适用于那些需要保持会话状态的Bean实例,有一点值得注意的就是,Spring不能对一个prototype Bean的整个生命周期负责,容器在初始化、装配好一个prototype实prototype 例后,将它交给客户端,随后就对该prototype实例不闻不问了。因此,客户端要负责prototype实例的生命周期管理。例如: scope=\"prototype\"> 针对每次HTTP请求,Spring容器会根据Bean的定义创建一个全新的Bean实例, 且该Bean实例仅在当前HTTP request内有效,因此可以根据需要放心地更改所建实例的内部状态, 而其他请求中request 根据Bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。 当处理请求结束,request作用域的Bean实例将被销毁。该作用域仅在基于web的Spring ApplicationContext情形下有效。例如: scope=\"request\"/> 针对某个HTTP Session,Spring容器会根据Bean定义创建一个全新的Bean实例,且该Bean实例仅在当前HTTP Session内有效。 与request作用域一样,我们可以根据需要放心地更改所创建实例的内部状态,而别的HTTP Session中根据Bean定义创建的实例, 将不会看到这些特定于某个HTTP Sessionsession 的状态变化。 当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的Bean实例也会被废弃掉。该作用域仅在基于Web的Spring ApplicationContext情形下有效。例如: scope=\"session\"/> global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的Web应用中才有意义。portlet规范定义了全局Session的概念,它被所有构成某个portlet Web应用的各种不同的portlet所共享。在global session作用域中定义的Bean被限定于全局portlet Session的生命周期范围内。global session 如果我们是在编写一个标准的基于Servlet的Web应用,并且定义了一个或多个具有global session作用域的Bean,系统会使用标准的HTTP Session作用域,并且不会引起任何错误。该作用域仅在基于Web的Spring ApplicationContext情形下有效。例如: scope=\"globalSession\"/> 在Spring的配置文件中配置Bean实例的作用域时是使用bean标签的scope属性来指定的,在省略scope属性的情况下,Spring将启用默认作用域singleton。

从表6-2可看出,Spring 2.5为Bean实例定义的5种作用域中,能用于Java APP的只有singleton与prototype这两种作用域,其中singleton由于使用非常广泛,被Spring 2.5容器当成了默认的作用域。在使用prototype作用域时,如果Bean实例在销毁时有些善后工作(如某些连接的关闭或资源的释放)要处理的话,客户端要肩负起这个责任。request、session与global session这3种作用域必须在基于Web的Spring ApplicationContext情形下才有效。

在Spring容器中,Bean实例从诞生到消亡则更富传奇色彩,如图6-8所示,无处不闪耀着AOP思想的光芒。

在Spring装载配置文件后,Spring工厂实例化完成,紧接着便开始\"看图生产\": (1)使用默认构造方法或指定构造参数进行Bean实例化。

(2)根据property标签的配置调用Bean实例中的相关set方法完成属性的赋值。 (3)如果Bean实现了BeanNameAware接口,则调用setBeanName()方法传入当前Bean的ID。

(4)如果Bean实现了BeanFactoryAware接口,则调用setBeanFactory()方法传入当前工厂实例的引用。

(5)如果Bean实现了ApplicationContextAware接口,则调用setApplicationContext()方法传入当前ApplicationContext实例的引用。

(6)如果有BeanPostProcessor与当前Bean关联,则与之关联的对象的postProcess- BeforeInitialzation()方法将被调用。

(7)如果在配置文件中配置Bean时设置了init-method属性,则调用该属性指定的初始化方法。

(8)如果有BeanPostProcessor与当前Bean关联,则与之关联的对象的postProcess- AfterInitialzation()方法将被调用。

(9)Bean实例化完成,处于待用状态,可以被正常使用了。

(10)当Spring容器关闭时,如果Bean实现了DisposableBean接口,则destroy()方法将被调用。

(11)如果在配置文件中配置Bean时设置了destroy-method属性,则调用该属性指定的方法进行销毁前的一些处理。

(12)Bean实例被正常销毁。

6.2.5 基于XML方式的Bean装配 XML能够应用于各种领域的原因,就是XML具有到目前为止其他方法所不具备的数据描述特点,控制信息不是采用应用软件的独有形式,而是采用谁都可以看得懂的标记形式来表现,所以XML最适合作为数据交换的标准,这也是 Spring极力推荐使用XML文件存放配置信息的原因。 XML使用有意义的标记(tag)来描述数据,XML文件是由一个个称为元素(element)的部件构成的。只要满足XML标记的命名规则,我们可以自由地定义一系列独具匠心的标记。例如,在Spring的配置文件中定义了一套用于表达Bean装配意图的标记,如表6-3所示。 表6-3 Spring 2.5配置文件中常用XML标记说明 标 记 名 称 功 能 描 述 整个配置文件的根节点,包含一个或多个Bean元素。在该标记中可配置命名空间与schema的装beans标记 载路径,还可以通过default-init-method属性为该配置文件中的所有Bean实例统一指定初始化方法,通过default-destroy-method属性为该配置文件中的所有Bean实例统一指定销毁方法,通过default-lazy-init属性为该配置文件中的所有Bean实例统一指定是否进行迟延加载 使用该标记定义一个Bean的实例化信息,用以指导Bean工厂正确地进行Bean的生产与装配。由class属性指定类全名,由id(推荐使用)或name属性指定生成的Bean实例名称。init-method属性bean标记 指定初始化时要调用的方法,destroy-method属性实例销毁时要调用的方法。Bean实例的依赖关系可通过property子标记(set方式注入)或constructor-arg子标记(构造方式注入)来定义。bean标记中的scope属性用以设定Bean实例的生成方式,当scope设置为singleton或默认时以单例模式生成,当scope设置为prototype时则以原型(多例)模式生成。 (续表) 标 记 名 称 功 能 描 述 它是bean标记的子标记,用以传入构造参数进行实例化,这也是注入依赖关系的一种常见方式。constructor-arg标index属性指定构造参数的序号(从0开始),当只有一个构造参数是通常省略不写;type属性指定构记 造参数的类型,当为基本数据类型时亦可省略此属性;参数值可通过ref属性或value属性直接指定,也可以通过ref或value子标记指定 它是bean标记的子标记,用以调用Bean实例中的相关Set方法完成属性值的赋值,从而完成依property标记 赖关系的注入。name属性指定Bean实例中的相应属性名称,属性值可通过ref属性或value属性直接指定,也可以通过ref或value子标记指定 ref标记 该标记通常作为constructor-arg、property、list、set、entry等标记的子标记,由bean属性指定一 个Bean工厂中某个Bean实例的引用 该标记通常作为constructor-arg、property、list、set、entry等标记的子标记,用以直接指定一个常 量值 该标记用以封装List或数组类型属性的依赖注入(即赋值),具体的元素通过ref或value子标list标记 记 指定 set标记 map标记 entry标记 props标记 prop标记 null标记 该标记用以封装Set类型属性的依赖注入(即赋值),具体的元素通过ref或value子标记指定 该标记用以封装Map类型属性的依赖注入(即赋值),具体的元素通过entry子标记指定 该标记通常用做map标记的子标记,用以设置一个“键/值”对。key属性接收字符串类型的“键”名称,“值”则可由ref或value子标记指定 该标记用以封装Properties类型属性的依赖注入(即赋值),具体的元素通过prop子标记指定 该标记通常用做props标记的子标记,用以设置一个“键/值”对。key属性接收字符串类型的“键”名称,“值”则可放置在prop标记体内 该标记用于赋一个null值,与在Java中直接为某个属性赋null值效果等同 value标记 熟练并灵活应用表6-3中列出的这些Spring配置文件标记,基本可以满足从Sping1.x到Spring 2.x中的IoC应用开发。

下面我们以一个简单的Java应用SpringXmlIOC具体演示一下基于XML方式的Bean装配。

首先定义一个用于演示原型模式下Bean实例化的User类,User实例的ID是随机生成的整数:

1. 2. 3. 4. 5. 6. 7. 8.

package test.spring.bean; import java.util.Random; /** 用户持久化类 */ public class User { Integer id;//自然ID号 String userName; //用户名 String userPwd;//用户密码 Random rnd = new Random();

9. 10. 11. 12. 13. 14.

//默认构造方法 public User(){

this.id = rnd.nextInt(1000);//产生一个1000以内的随机ID }

//省略各属性的get/set方法对 }

接下来定义一个包含各种需要演示的数据类型属性、自定义构造方法、初始化方法与销毁方法等的业务类XmlIocBean:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29.

package test.spring.bean;

import java.text.SimpleDateFormat; import java.util.*;

/** 用于演示XML方式装配Bean实例的测试类 */ public class XmlIocBean { //定义两个通过构造方法注入的属性 Date now;

SimpleDateFormat sf; //定义集合类型的属性 int[] intnumber; List list; Set set; Map map;

Properties props; //默认构造方法

public XmlIocBean() {} //自定义带参构造方法

public XmlIocBean(SimpleDateFormat sf,Date now) { this.now = now; this.sf = sf; }

//定义初始化方法

public void myInit(){

System.out.println(\"XmlIocBean类的初始化方法myInit()被调用了!\"); }

//定义一个业务方法输出各属性值 public void execute(){

System.out.println(\"现在的时间是:\"+sf.format(now)); System.out.print(\"intnumber数组元素内容为:\");

30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. . 55. 56. 57. 58. 59. 60. 61. 62.

for (int i:intnumber){ System.out.print(\"\\"+i); }

System.out.print(\"\\r\\nlist实例中的元素内容为:\"); for (Object obj:list){

if (obj instanceof User)System.out.print (\"\用户ID=\"+((User)obj).getId()); else System.out.print(\"\\"+obj); }

System.out.print(\"\\r\\nset实例中的元素内容为:\"); for (Object obj:set){

System.out.print(\"\\"+obj+\"\\"); }

System.out.print(\"\\r\\nmap实例中的元素内容为:\"); for (Object key:map.keySet()){ if (map.get(key) instanceof User ){

System.out.print(\"\用户ID=\"+((User)map.get(key)).getId()); }else{

System.out.print(\"\\"+key+\"=\"+map.get(key)); } }

System.out.print(\"\\r\\nprops实例中的元素内容为:\"); for (Object key:props.keySet()){

System.out.print(\"\\"+key+\"=\"+props.get(key)); }

System.out.println(); }

//定义销毁方法

public void myDestroy(){

System.out.println(\"XmlIocBean类的销毁方法myDestroy()被调用了!\"); }

//省略各属性的get/set方法对 }

下面在Spring的XML配置文件中配置User与XmlIocBean类的装配信息:

1. 2. 3.

< SPAN>

xmlns=\"http://www.springframework.org/schema/beans\"

4. 5. 6.

xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"

xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd\">

7. 8.

SPAN>\"simpleDateFormat\" class=\"java.text.SimpleDateFormat\">

9. 10. 11. 12. 13.

\"yyyy年MM月dd hh时mm分ss秒\" />

SPAN>\"user\" class=\"test.spring.bean.User\" scope=\"prototype\"> 14. 15. 16. 17. 18. 19.

\"userName\" value=\"liubin\" /> \"userPwd\" value=\"123456\" />

SPAN>\"xmlIocBean\" class=\"test.spring.bean.XmlIocBean\" 20. 21. 22.

init-method=\"myInit\" destroy-method=\"myDestroy\">

SPAN>\"0\" type=\"java.text.SimpleDateFormat\" 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36.

ref=\"simpleDateFormat\"/>

\"1\" type=\"java.util.Date\">

class=\"java.util.Date\"/>

\"intnumber\"> 2 15 7

37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. . 55. 56. 57. 58. 59. 60. 61. 62. 63. . 65. 66. 67. 68. 69. 70. 71.

\"list\"> 元素1

\"user\"/> \"user\"/>

\"set\">

set元素1 set元素2

\"map\">

\"key1\"> map元素1

\"key2\"> \"user\"/>

\"props\">

\"prop1\">properties元素1 \"prop2\">properties元素2

最后编写XmlIocBean类的Junit测试用例testXmlIocBean:

1.

package test.spring.junit;

2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.support. AbstractApplicationContext; import org.springframework.context.support. ClassPathXmlApplicationContext; import test.spring.bean.XmlIocBean; /** 业务控制器XmlIocBean的测试用例 */ public class testXmlIocBean { static AbstractApplicationContext cxt; static XmlIocBean xmlIocBean; //初始化ApplicationContext容器 @BeforeClass public static void setUpBeforeClass() throws Exception { //使用ClassPathXmlApplicationContext方式初始化ApplicationContext容器 cxt = new ClassPathXmlApplicationContext(\"applicationContext.xml\"); 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. //从Bean工厂容器中获取名为\"xmlIocBean\"的XmlIocBean实例 xmlIocBean = (XmlIocBean)cxt.getBean(\"xmlIocBean\"); } //测试XmlIocBean的execute方法 @Test public void testExecute() { xmlIocBean.execute(); //手动关闭Spring容器 cxt.close(); } } 测试用例的运行效果如图6-9所示。

6.2.6 基于Annotation方式的Bean装配 从JDK 5开始提供名为Annotation(注释)的功能,它被定义为JSR-175规范。在Java应用中,我们经常会遇到一些需要使用模板代码的情况。例如,为了编写一个JAX-RPC Web Service,我们必须提供一对接口和实现作为模板代码。如果使用Annotation对远程访问的方法代码进行修饰的话,这个模板就能够使用工具自动生成。另外,一些API需要使用与程序代码同时维护的附属文件。例如,JavaBeans需要一个BeanInfo Class与一个Bean同时使用/维护,而EJB则同样需要一个部署描述符,此时在程序中使用Annotation来维护这些附属文件的信息将十分便利而且减少了错误。 注释是以\"@注释名\"或\"@注释名(参数名=参数值)\"的形式在代码中存在的。例如,JDK 5内置的基本注释@Override 、@Deprecated 、@SuppressWarnings(value=\"unchecked\")。注释可以附加在package、 class、method、 field等上面,相当于给它们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问。如果没有外部解析工具等对其加以解析和处理的情况,本身不会对Java的源代码或class文件等产生任何影响,也不会对它们的执行产生任何影响。 在Spring中尽管使用XML配置文件可以完成所有的配置工作,直观表达Java EE程序员的\"装配意图\",但如果应用中Bean数量成千上万的话,最终会导致XML配置文件体积剧增,面对臃肿的XML配置文件,给维护与升级带来一定的困难。 那有没有一种行之有效的XML配置文件减肥方案呢?伟大的Spring开发团队想到了JDK 1.5提供的新特性--Annotation注释技术。从Spring 2.0开始,逐步完善对Annotation注释技术的全面支持,使XML配置文件不再臃肿,向\"零配置\"目标靠拢。 对于初学者而言,也许觉得Annotation注释技术太神秘了,随便放个注释就有如此巨大的威力。在了解了Annotation注释的工作原理之后,方觉得真正伟大的不是Annotation注释本身,而是幕后默默无闻地进行注释处理的Annotation处理器。Annotation注释仅仅是一个标记,用以表达程序员的某种意图,如果不为之量身开发一个Annotation处理器的话,Annotation注释本身将毫无意义,对程序也不会产生任何影响。Annotation处理器是基于JDK的反射机制来实现的,Spring 2.5中定义的一系列Annotation注释,如表6-4所示,幕后均有相应的Annotation处理器与之对应,最终实现Annotation注释的特定语义。 表6-4 Spring 2.5中常用的Annotation注释说明 注 释 名 称 功 能 描 述 通过@Autowired注解对Bean的属性变量、属性的Setter方法及构造函数进行标注,配合对应的注解处理器AutowiredAnnotationBeanProcessor完成Bean的自动配置工作。@Autowired注解默认是按@Autowired Bean类型进行装配的,也就是说注解处理器AutowiredAnnotationBeanProcessor会在Spring容器中寻找与@Autowired注解所在属性同类型的Bean实例进行装配,如果找到多个满足条件的Bean实例时,将会抛出NoSuchBeanDefinitionException异常。@Autowired注解加上@Qualifier注解的话,可以直接指定一个Bean实例名称来进行装配。在实例应用中,不推荐使用@Autowired注解,因为该注解应用不当,会引发一些莫名其妙的问题 @Resource的作用相当于@Autowired,配合对应的注解处理器CommonAnnotationBeanPostProcessor完成Bean的自动配置工作。只不过@Autowired注解默认是按Bean类型进行装配的,而@Resource注解默认是按Bean实例名称进行装配罢了。@Resource有两个属性是比较重要的,分别是name和type。Spring将@Resource注解的name属性解析为Bean实例的名字,而@Resource type属性则解析为Bean实例的类型。所以如果使用name属性,则按Bean实例名称进行装配,而使用type属性时则按Bean类型进行装配。如果既不指定name也不指定type属性,这时则先按Bean实例名称进行装配,如果匹配不到,再按Bean类型进行装配,如果都匹配不到,则抛出NoSuchBeanDefinitionException异常 如果@Autowired注解加上@Qualifier注解的话,可以将默认按Bean类型进行装配变换为按Bean@Qualifier 实例名称进行装配,具体的Bean实例名称由@Qualifier注解的参数指定 在Bean中的某个方法上加上@PostConstruct注解,则该方法将会在Bean初始化之后被Spring容@PostConstruct 器调用,作用等同在Spring配置文件中为bean标签指定init-method属性 在Bean中的某个方法上加上@PreDestroy注解,则该方法将会在Bean实例被销毁之前由Spring@PreDestroy 容器进行调用,作用等同在Spring配置文件中为bean标签指定destroy-method属性 在Java EE中启用Spring的注解装配通常需要经过以下几个步骤。

(1)导入Spring注解装配功能所依赖的JAR包Jcommon-annotations.jar,并将其加入到当前项目的classpath中来,该文件在spring-framework-2.5.6\\spring-framework-2.5.6 \\lib\\j2ee文件夹下可以找到;

(2)在Spring配置文件中定义context命名空间与相应的schemaLocation:

1. 2. 3. 4. 5. 6. 7. 8. 9.

xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"

xmlns:context=\"http://www.springframework.org/schema/context\" xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-2.5.xsd\"> 10. 11.

……

(3)在Spring配置文件中开启相应的注解处理器:

1. 2.

(4)在Bean中使用表6-4中列举的Annotation注释进行Bean属性的自动装配。

下面我们以一个简单的Java应用SpringAnnotationIOC具体演示一下基于Annotation注解方式的Bean装配。

首先定义一个用于演示原型模式下Bean实例化的User类,User类中的随机数生成器rnd是通过@Autowired注解加@Qualifier注解实现按Bean实例名称注入的,User实例的ID是在initUser()方法中赋予的随机整数,initUser()使用@PostConstruct注解被指定为初始化时调用的方法:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24.

package test.spring.bean; import java.util.Random;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; /** 用户持久化类 */ public class User { Integer id;//自然ID号 String userName; //用户名 String userPwd;//用户密码

//使用@Autowired注解加@Qualifier注解实现按Bean实例名称装配 @Autowired @Qualifier(\"random\") Random rnd; //默认构造方法 public User(){}

//使用@PostConstruct注解指定初始化时调用的方法 @PostConstruct

public void initUser() {

this.id = rnd.nextInt(1000);//产生一个1000以内的随机ID this.userName =\"用户\"+this.id;//产生一个相应的用户名 this.userPwd = \"123456\";//指定一个初始化密码 }

//省略属性的get/set方法对 }

接下来定义一个用于演示@Resource、@Autowired、@PostConstruct、@PreDestroy等注解用法的业务类AnnotationIocBean:

1. 2. 3. 4.

package test.spring.bean;

import java.text.SimpleDateFormat; import java.util.*;

import javax.annotation.PostConstruct;

5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42.

import javax.annotation.PreDestroy; import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired; /** 用于演示XML方式装配Bean实例的测试类 */ public class AnnotationIocBean {

//定义两个通过@Resource注解注入的属性,可省略set方法 @Resource Date now;

//为@Resource注解指定name属性,直接引用Bean工厂中的一个Bean实例 @Resource(name=\"simpleDateFormat\") SimpleDateFormat sf;

//定义User类型的属性,通过Annotation注解方式注入 User user1;

//定义两个通过@Autowired注解注入的属性,可省略set方法 @Autowired User user2; @Autowired User user3; //缺省构造方法

public AnnotationIocBean() {}

//使用@PostConstruct注解指定初始化时调用的方法 @PostConstruct

public void init() {

System.out.println(\"AnnotationIocBean被实例化了!\"); }

//定义一个业务方法输出各属性值 public void execute(){

System.out.println(\"现在的时间是:\"+sf.format(now)); System.out.println(\"第一个用户ID=\"+user1.getId()); System.out.println(\"第二个用户ID=\"+user2.getId()); System.out.println(\"第三个用户ID=\"+user3.getId()); }

//使用@PreDestroy注解指定实例销毁时调用的方法 @PreDestroy

public void destroy() {

System.out.println(\"AnnotationIocBean实例被销毁了!\"); }

//通过在set方法上放置@Resource注解实现注解方式注入 @Resource

public void setUser1(User user) { this.user1 = user;

43. 44.

} }

下面在Spring的XML配置文件中配置相关Bean的装配信息,与以前基于XML方式装配Bean相比,配置文件中节省了大量的property标记,从而使配置文件得以瘦身:

1. 2. 3. 4. 5. 6. 7. 8. 9.

xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"

xmlns:context=\"http://www.springframework.org/schema/context\" xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-2.5.xsd\"> 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29.

最后编写AnnotationIocBean类的Junit测试用例testAnnotationIocBean:

1.

package test.spring.junit;

2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17.

import org.junit.BeforeClass; import org.junit.Test;

import org.springframework.context.support. AbstractApplicationContext;

import org.springframework.context.support. ClassPathXmlApplicationContext;

import test.spring.bean.AnnotationIocBean; /** 业务控制器AnnotationIocBean的测试用例 */ public class testAnnotationIocBean { static AbstractApplicationContext cxt; static AnnotationIocBean annotationIocBean; //初始化ApplicationContext容器 @BeforeClass

public static void setUpBeforeClass() throws Exception {

//使用ClassPathXmlApplicationContext方式初始化ApplicationContext容器 cxt = new ClassPathXmlApplicationContext(\"applicationContext.xml\");

18. 19.

//从Bean工厂容器中获取名为\"annotationIocBean\"的AnnotationIocBean实例 annotationIocBean = (AnnotationIocBean)cxt.getBean(\"annotationIocBea

n\"); 20. 21. 22. 23. 24. 25. 26. 27. 28.

}

//测试AnnotationIocBean的execute方法 @Test

public void testExecute() { annotationIocBean.execute(); //手动关闭Sping容器 cxt.close(); } }

测试用例的运行效果如图6-10所示。

6.2.7 基于XML方式的AOP实现

在6.1.4节中,我们对面向切面编程AOP技术的实现原理及基本概念进行了深入剖析,本节将延续6.1.4节的话题,进一步探讨基于XML方式的AOP实现。在面向切面编程AOP技术中,切面的提取、通知的编写、切入点的定义、切面与目标对象的AOP装配是几个重要的环节。

所谓切面的提取就是综观Java EE应用,提取其中的所有共有功能(如日志、权限验证、事务管理等),并将其封装到一个类中,这个类就是切面(如日志切面、权限切面、事务切面等),去除了共有功能的业务逻辑代码将变得更加简洁优雅。

所谓通知的编写就是在切面中对共有功能的具体实现,一个通知往往对应的就是切面中的某个方法,然而根据这个通知欲切入到目标方法的位置不同可分为前置通知(在目标方法被调用之前执行)、后置通知(在目标方法被成功调用之后执行)、异常通知(在捕捉到目标方法抛出异常时执行)、最终通知(不管目标方法调用成功与否,均在调用完毕之后执行)、环绕通知(在目标方法被调用的前后均可进行操作)。在基于XML方式的AOP实现中,编写通知时,并不需要指明通知的类型,通知类型的设定是在Spring的XML配置文件中配置设定的。

所谓切入点的定义就是通过编写一个AOP正则表达式,指明欲拦截与插入通知的连接点。由于Spring的连接点仅支持方法级别,因此,本节中所讲的连接点实指目标对象中的某个方法。在定义AOP切入点时,通常使用execution切入点指示符设置连接点正则表达式,具体语法如下:

1. 2. 3.

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

除了返回值类型模式(ret-type-pattern),名字模式(name-pattern)和参数模式(param-pattern)以外,其他部分都是可选的。访问类型模式

(modifiers-pattern)决定了方法的访问类型,如public表示匹配所有公用方法,此模式通常可省略不写,除非有特定的需求。返回值类型模式(ret-type-pattern)决定了方法的返回值类型,当所有返回值类型均予以匹配的话,可直接设置为*,表示匹配任意的返回值类型。类型名称模式(declaring-type-pattern)表示匹配的包或类的名称规则,\"包路径.*\"表示指定包下的所有类均予以匹配,\"包路径..*\"则表示指定包及其子包下的所有类均予以匹配,\"包路径.类名\"表示指定的类予以匹配。名字模式

name-pattern匹配的是方法名,*表示匹配所有方法,set*表示所有方法名以set开头的方法。参数模式param-pattern稍微有点复杂:()表示匹配不接受任何参数的方法,(..)表示匹配接受任意数量参数的方法,不论参数有无或多少,(*)表示匹配接受一个任

何类型的参数的方法,而(*,String)则表示匹配接受两个参数的方法,第一个参数可以是任意类型,而第二个参数必须是String类型。异常模式(throws-pattern)决定了抛出何种类型的异常,如RuntimeException表示匹配抛出RuntimeException异常的方法,此模式通常可省略不写,除非有特定的需求。 表6-5中列举一些常用的AOP正则表达式以供参考。 表6-5Spring 2.5中常用的AOP正则表达式说明 切入点正则表达式 execution(public * *(..)) execution(* set*(..)) execution(* test.spring.action.UserAction.*(..)) execution(* test.spring.action.*.*(..)) execution(* test.spring.action..*.*(..)) 功 能 描 述 匹配所有公有方法 匹配所有以“set”开头的方法 匹配UserAction类的所有方法 匹配test.spring.action包下所有类的所有方法 匹配test.spring.action包及子包下所有类的所有方法 所谓切面与目标对象的AOP装配就是将编写好的通知和规则好的切入点在Spring配置文件中进行配置,最终让Spring容器按我们的意图实现AOP。

下面我们以一个简单的Java应用SpringXmlAOP具体演示一下基于XML方式的AOP实现,在该实例中省略很多周边代码,大家可参考配套光盘的\"源码部分\"。

首先定义一个日志切面LogAspectJ,并在该切面中编写用于演示的各种通知,值得注意的是,在定义通知方法时传入了一个连接点JoinPoint参数,通过该参数可以取得目标对象的类名、目标方法名称及目标方法的参数等:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.

package test.spring.aop;

import org.apache.log4j.Logger; import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.ProceedingJoinPoint; /** 日志切面 */

public class LogAspectJ { // 取得日志记录器Logger

public Logger logger = Logger.getLogger(LogAspectJ.class); //此方法将用做前置通知

public void myBeforeAdvice(JoinPoint joinpoint) {

String classAndMethod = joinpoint.getTarget().getClass().getName() +\"类的\"+joinpoint.getSignature().getName();

logger.info(\"前置通知:\"+classAndMethod+\"方法开始执行!\"); }

//此方法将用做后置通知

public void myAfterReturningAdvice(JoinPoint joinpoint) {

17. 18. 19. 20. 21. 22.

String classAndMethod = joinpoint.getTarget().getClass().getName() +\"类的\"+joinpoint.getSignature().getName();

logger.info(\"后置通知:\"+classAndMethod+\"方法执行正常结束!\"); }

//此方法将用做异常通知

public void myAfterThrowingAdvice(JoinPoint joinpoint,Exception e)

{ 23.

String classAndMethod = joinpoint.getTarget().getClass().getName()+

24. 25. 26. 27. 28. 29. 30.

\"类的\"+joinpoint.getSignature().getName();

logger.info(\"异常通知:\"+classAndMethod+\"方法抛出异常: \"+e.getMessage()); }

//此方法将用做最终通知

public void myAfterAdvice(JoinPoint joinpoint) {

String classAndMethod = joinpoint.getTarget().getClass().getName()+

31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47.

\"类的\"+joinpoint.getSignature().getName();

logger.info(\"最终通知:\"+classAndMethod+\"方法执行结束!\"); }

//此方法将用做环绕通知

public Object myAroundAdvice(ProceedingJoinPoint pjp) throws Throwable {

long begintime = System.currentTimeMillis();//记下开始时间 //传递给连接点对象进行接力处理 Object result=pjp.proceed();

long endtime = System.currentTimeMillis();//记下结束时间

String classAndMethod = pjp.getTarget().getClass().getName()+ \"类的\"+pjp.getSignature().getName();

logger.info(\"环绕通知:\"+classAndMethod+\"方法执行结束, 耗时\"+(endtime-begintime)+\"毫秒!\"); return result; } }

接下来编写一个业务控制组件UserAction,我们打算将切面应用到该组件上,对该组件的所有方法均予以拦截并切入所有通知,为了演示异常通知的执行效果,特意在addUser方法的尾部人为抛出一个RuntimeException异常:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35.

package test.spring.action; import test.spring.bean.User;

import test.spring.service.UserService; /** 用户管理业务控制器 */ public class UserAction { //仅声明业务逻辑组件的引用 UserService service; //处理新增用户请求

public void addUser() { String userName=\"liubin\"; String userPwd=\"123456\";

service.addUser(userName,userPwd);

throw new RuntimeException(\"这是特意抛出的异常信息!\"); }

//处理删除用户请求

public void delUser() { service.delUser(1); }

//处理装载用户请求

public void loadUser() {

User user = service.loadUser(1);

System.out.println(\"用户名=\"+user.getUserName()); }

//处理修改用户请求

public void modiUser() { Integer id = 1;

String userName=\"liujunyu\"; String userPwd=\"123456\";

service.modiUser(id, userName, userPwd); }

//提供一条UserService对象的注入通道

public void setService(UserService service) { this.service = service; } }

日志切面LogAspectJ与业务控制组件UserAction原本是两个互不相干的类,通过在Spring配置文件中进行AOP装配之后,奇妙的事情就会出现了,LogAspectJ中的通知将会巧妙地切入到UserAction中,以实现预期的日志记录功能:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.

< SPAN>

xmlns=\"http://www.springframework.org/schema/beans\" xmlns:aop=\"http://www.springframework.org/schema/aop\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"

xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-2.5.xsd\">

SPAN>\"dao\" class=\"test.spring.dao.impl.UserDaoImpl\"/> 12. 13.

SPAN>\"service\" class=\"test.spring.service.impl.UserServiceImpl\"> 14. 15. 16. 17. 18.

\"dao\" ref=\"dao\"/>

SPAN>\"userAction\" class=\"test.spring.action.UserAction\"> 19. 20. 21. 22. 23.

\"service\" ref=\"service\" />

SPAN>\"logAspectJ\" class=\"test.spring.aop.LogAspectJ\"/> 24. 25. 26. 27. 28. 29. 30. 31. 32.

\"logaop\" ref=\"logAspectJ\">

\"logpointcut\" expression=\"execution(* test.spring.action.UserAction.*(..))\"/>

SPAN>\"logpointcut\" method=\"myBeforeAdvice\"/> 33.

34. 35. 36. 37. 38. 39. 40. 41.

\"logpointcut\" method=\"myAfterReturningAdvice\"/>

\"logpointcut\" method=\"myAfterThrowingAdvice\" throwing=\"e\"/>

SPAN>\"logpointcut\" method=\"myAfterAdvice\"/> 42. 43.

SPAN>\"logpointcut\" method=\"myAroundAdvice\"/> 44. 45. 46.

显然,Spring的AOP配置标签是放置于aop命名空间之下的,因此,事先应该在beans标签中导入AOP命名空间及其配套的schemaLocation。

最后编写UserAction类的Junit测试用例testUserAction:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.

package test.spring.junit; import org.junit.BeforeClass; import org.junit.Test;

import org.springframework.context.ApplicationContext; import org.springframework.context.support. ClassPathXmlApplicationContext; import test.spring.action.UserAction; /** 用户管理业务控制器的测试用例 */ public class testUserAction { static ApplicationContext cxt; static UserAction userAction; //初始化ApplicationContext容器 @BeforeClass

public static void setUpBeforeClass() throws Exception {

//使用ClassPathXmlApplicationContext方式初始化ApplicationContext容器 cxt = new ClassPathXmlApplicationContext(\"applicationContext.xml\");

17. 18.

//从Bean工厂容器中获取名为\"userAction\"的UserAction实例 userAction = (UserAction)cxt.getBean(\"userAction\");

19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40.

}

//测试UserAction的AddUser方法 @Test

public void testAddUser() { userAction.addUser(); }

//测试UserAction的DelUser方法 @Test

public void testDelUser() { userAction.delUser(); }

//测试UserAction的LoadUser方法 @Test

public void testLoadUser() { userAction.loadUser(); }

//测试UserAction的ModiUser方法 @Test

public void testModiUser() { userAction.modiUser(); } }

测试用例的运行效果如图6-11所示。

6.2.8 基于Annotation方式的AOP实现

通过6.2.6节的学习,我们基本领略到了Annotation注解技术为Spring配置文件瘦身的独特魅力。Annotation注解与XML配置都是为了表达一种意图,两者幕后均存在强大的处

理器后盾支持,不同的是这种意图的放置点不同,Annotation注解是直接放置在Bean组件中的,在运行时由对应的注解处理器捕捉并处理,而XML配置则是集中放置在Spring配置文件中的,正由于太集中,才导致体积成指数增长,最终落个臃肿的下场。 Spring 2.5为AOP装配也提供了一套Annotation注解如表6-6所示,用以取代Spring配置文件中臃肿的AOP配置代码。 表6-6 Spring 2.5中AOP装配的常用Annotation注解说明 注 解 名 称 @Aspect 功 能 描 述 用于定义一个切面 用于定义一个切入点,该注解的使用比较怪异,切入点的名称是由一个方法名称定义的。例如: @Pointcut //定义一个切入点,匹配test.spring.action.UserAction类中的所有方法 @Pointcut(\"execution(* test.spring.action.UserAction.*(..))\") private void anyMethod(){}//定义切入点的名字 @Before @AfterReturning @AfterThrowing @After @Around 用于定义一个前置通知,使用该注解时必须提供一个切入点名称参数。例如:@Before(\"anyMethod()\") 用于定义一个后置通知,使用该注解时必须提供一个切入点名称参数。 例如:@AfterReturning(\"anyMethod()\") 用于定义一个异常通知,使用该注解时必须提供一个切入点名称参数。 例如:@AfterThrowing(\"anyMethod()\") 用于定义一个最终通知,使用该注解时必须提供一个切入点名称参数。例如:@After(\"anyMethod()\") 用于定义一个环绕通知,使用该注解时必须提供一个切入点名称参数。例如:@Around(\"anyMethod()\") 使用表6-6中列举的这些Annotation注解,可将SpringXmlAOP应用中的日志切面LogAspectJ改编如下:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.

package test.spring.aop;

import org.apache.log4j.Logger; import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; /** 日志切面 */ @Aspect

public class LogAspectJ { // 取得日志记录器Logger

public Logger logger = Logger.getLogger(LogAspectJ.class); /**

* 使用@Pointcut注解定义一个切入点,切入点的名字为anyMethod(),

* 切入点正则表达式execution(* test.spring.action.UserAction.*(..)) * 的意思是拦截test.spring.action.UserAction类中的所有方法, * 不论方法参数有无,也不管返回结果为何类型。 * */

17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28.

@Pointcut(\"execution(* test.spring.action.UserAction.*(..))\") private void anyMethod(){}//定义切入点的名字 @Before(\"anyMethod()\")//定义前置通知

public void myBeforeAdvice(JoinPoint joinpoint) {

String classAndMethod = joinpoint.getTarget().getClass(). getName()+

\"类的\"+joinpoint.getSignature().getName();

logger.info(\"前置通知:\"+classAndMethod+\"方法开始执行!\"); }

@AfterReturning(\"anyMethod()\")//定义后置通知

public void myAfterReturningAdvice(JoinPoint joinpoint) {

String classAndMethod = joinpoint.getTarget().getClass().getName()+

29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43.

\"类的\"+joinpoint.getSignature().getName();

logger.info(\"后置通知:\"+classAndMethod+\"方法执行正常结束!\"); }

@AfterThrowing(pointcut=\"anyMethod()\", throwing=\"e\") //定义异常通知

public void myAfterThrowingAdvice (JoinPoint joinpoint,Exception e) {

String classAndMethod = joinpoint.getTarget().getClass().getName() +\"类的\"+joinpoint.getSignature().getName(); logger.info(\"异常通知:\"+classAndMethod+\" 方法抛出异常:\"+e.getMessage()); }

@After(\"anyMethod()\")//定义最终通知

public void myAfterAdvice(JoinPoint joinpoint) {

String classAndMethod = joinpoint.getTarget().getClass().getName()+

44. 45. 46. 47. 48. 49. 50. 51. 52.

\"类的\"+joinpoint.getSignature().getName();

logger.info(\"最终通知:\"+classAndMethod+\"方法执行结束!\"); }

@Around(\"anyMethod()\")//定义环绕通知

public Object myAroundAdvice(ProceedingJoinPoint pjp) throws Throwable {

long begintime = System.currentTimeMillis();//记下开始时间 //传递给连接点对象进行接力处理 Object result=pjp.proceed();

53. . 55. 56. 57. 58. 59. 60.

long endtime = System.currentTimeMillis();//记下结束时间

String classAndMethod = pjp.getTarget().getClass().getName()+ \"类的\"+pjp.getSignature().getName();

logger.info(\"环绕通知:\"+classAndMethod+\"方法执行结束, 耗时\"+(endtime-begintime)+\"毫秒!\"); return result; } }

同时,Spring配置文件中的臃肿的AOP配置代码也可以取消。为了让AOP注解能正常工作,还需要在Spring配置文件中开启AOP注解处理器,改编后的Spring配置文件如下:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15.

xmlns=\"http://www.springframework.org/schema/beans\" xmlns:aop=\"http://www.springframework.org/schema/aop\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"

xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-2.5.xsd\">

16. 17. 18. 19. 20. 21.

22. 23. 24. 25.

26.

6.3 Spring 2.5事务管理机制

事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所做的所有更改都会被撤销。这些操作要么都做,要么都不做,是一个不可分割的工作单位。

例如,将资金从支票账户转到储蓄账户中是一项事务,按如下步骤进行: (1)检查支票账户是否有足够的资金来支付此转账操作。

(2)如果支票账户中有足够的资金,则将该笔资金记入此账户的借方。 (3)将这些资金记入储蓄账户的贷方。 (4)将此次转账记录到支票账户日志中。 (5)将此次转账记录到储蓄账户日志中。

如果这些步骤的任何一个步骤失败,则必须撤销在前面的步骤中所做的所有更改,而且支票账户和储蓄账户的状态必须与它们在事务开始之前的状态相同,这种还原到事务之前状态的过程叫做回滚。倘若每个步骤都正常完成,则对整个事务进行提交,因此,事务最终是以提交或回滚来结束的。

事务具有4个明显的特性。

原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列。这些操作要么完整地被全部执行,要么一步也不做。是一个不可分割的逻辑工作单位。

一致性:一个事务执行的结果将保持一致性,即数据不会因为事务的执行而遭受破坏。

隔离性:一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性:一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其执行结果有任何影响。

在Spring中实现事务管理有两种方式,一种是传统的编程式事务管理,也就是程序员通过编写程序代码实现事务的管理,具体包括定义事务的开始、在程序异常时进行事务的回滚及程序正常执行后的事务提交。

另一种则是基于AOP技术实现的声明式事务管理,事务管理本身是一项共有的系统级服务功能,完全可以将事务管理抽象成一个事务切面,程序员不再关心事务管理的问题,把

主要精力放在核心业务逻辑代码的编写上,然后在需要进行事务管理的方法上切入事务切面,使之具有事务管理的功能,达到事务管理的目的。 显然,从开发效率与易维护的角度来评估的话,Spring声明式事务管理是最受Java EE程序员推崇的。 6.3.1 Spring 2.5的声明式事务管理 Spring的声明式事务管理在底层是建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。 声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或在业务方法上进行@Transactional注解,便可以将事务规则应用到业务逻辑中。因为事务管理本身就是一个典型的切面,正好是 AOP技术的用武之地。Spring 开发团队也意识到了这一点,为声明式事务提供了简单而强大的支持。 声明式事务管理曾经是EJB引以为豪的一个亮点,如今Spring让POJO在事务管理方面也拥有了与EJB一样的待遇,让开发人员在EJB容器之外也用上了强大的声明式事务管理功能,这主要得益于Spring IoC依赖注入容器和Spring AOP面向切面编程的支持。依赖注入容器为声明式事务管理提供了基础设施,使得Bean对于Spring框架而言是可管理的;而Spring AOP则是声明式事务管理的直接实现者。 在通常情况下,大多数Java EE程序员偏爱声明式事务管理,不仅因为其简单易用,更主要是因为这样可使纯业务代码不被污染,极大方便后期的代码维护。 与编程式事务相比,声明式事务美中不足地方是,后者的最细粒度只能作用到方法级别(因为目前Spring切入点仅支持到方法级别),无法做到像编程式事务那样精确控制到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块为方法等。 Spring声明式事务管理既可基于XML配置方式实现,亦可基于Annotation注解方式来实现,主要取决于程序员的使用习惯与个人爱好。不过Spring 开发团队倒是建议大家使用Annotation注解方式,这除了对XML配置文件减肥之外,对于事务传播行为(见表6-7)、事务的回滚等事务管理属性的设置也有更为便捷的实现。 表6-7 Spring 2.5中事务传播行为说明 名 称 REQUIRED REQUIRESNEW 功 能 描 述 REQUIRED表示业务逻辑方法需要在一个事务中运行,如果该方法在运行时,已经处在一个事务中,则直接加入到该事务中,否则为自己创建一个新的事务 REQUIRESNEW表示不管当前是否有事务存在,该业务逻辑方法总会为自己创建一个全新的事务。如果该方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到该方法执行结束后新事务才算结束,原先的事务方恢复执行 NOT_SUPPORTED表示业务逻辑方法不需要事务。如果该方法目前没有关联到某个事务,容器NOT_SUPPORTED 不会为它创建事务。如果该方法在一个事务中被调用,则该事务会被挂起,在方法调用结束后原先的事务才会恢复执行 MANDATORY MANDATORY表示业务逻辑方法只能在一个已经存在的事务中执行,该方法不能创建自己的事务。如果该方法在没有事务的环境下被调用,容器就会抛出事务不存在的异常 SUPPORTS表示业务逻辑方法如果在某个事务范围内被调用,则该方法直接成为当前事务的一部分。如果该方法在事务范围外被调用,则该方法在无事务的环境下执行 NEVER表示业务逻辑方法绝对不能在事务范围内执行。如果该方法在某个事务中执行,容器会抛出异常,只有没有关联到任何事务时该方法才能正常执行 NESTED表示如果一个活动的事务存在,业务逻辑方法则运行在一个嵌套的事务中;如果没有NESTED 活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点,内部事务的回滚不会对外部事务造成影响,它只对DataSourceTransactionManager事务管理器生效 SUPPORTS NEVER 6.3.2 基于XML方式的事务管理配置

对基于方法级别的事务管理而言,方法开始执行时创建事务,方法运行过程中若出现异常则进行事务回滚,方法如果正常执行完成则进行事务的提交。因而,事务管理的主要任务就是事务的创建、事务的回滚与事务的提交,其中是否需要创建事务及如何创建事务是由事务传播行为(见表6-7)控制的,通常数据的读取是不需要事务管理的,或者也可为其指定为只读事务,而对于插入、修改与删除数据的方法来说,就有必要进行事务管理了,在未指定事务传播行为时,Spring 2.5将启用默认的REQUIRED。

Spring 2.5中的事务回滚机制大家也是有必要了解一下的,在默认情况下,Spring 2.5在出现unchecked类型的异常(就是需要调用者来处理的异常)时会自动进行事务回滚,在出现checked类型的异常(就是由被调用者自己来处理的异常)时不会进行事务回滚。当然,我们在配置事务管理时,可通过相关的属性设置来改变Spring的默认事务回滚机制。

下面我们看一个操作数据库的DAO组件UserDaoImpl,该组件调用Spring封装好的JdbcTemplate操作模板类使用JDBC的方式对数据库进行操作:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10.

package test.spring.dao.impl; import javax.annotation.Resource;

import org.springframework.jdbc.core.JdbcTemplate; import java.sql.Types; import test.spring.bean.*; import test.spring.dao.UserDao; /** 用户管理底层数据访问接口实现 */

public class UserDaoImpl implements UserDao { //通过@Resource注解注入Spring提供的JdbcTemplate实例 @Resource JdbcTemplate jdbcTemplate;

11. 12. 13. 14.

//处理新增用户业务逻辑

public void addUser(User user) { //保存用户账号信息

jdbcTemplate.update(\"insert into users(userName,userPwd) values(?,?)

\", 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28.

new Object[]{user.getUserName(),user.getUserPwd()}, new int[]{Types.VARCHAR,Types.VARCHAR}); int userid = jdbcTemplate.queryForInt (\"select id from users where userName=?

and userPwd=?\new Object[]{user.getUserName(),user.getUserPwd()}, new int[]{Types.VARCHAR,Types.VARCHAR}); UserInfo userInfo = user.getUserInfo(); if (userInfo!=null){ //保存用户个人资料

jdbcTemplate.update(\"insert into userinfo (UserID,Email,Phone,Address) values(?,?,?,?)\",

new Object[]{userid,userInfo.getEmail(),userInfo.getPhone(), userInfo.getAddress()},

new int[]{Types.INTEGER,Types.VARCHAR,Types.VARCHAR,Types.VARCHAR});

29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46.

}

//throw new RuntimeException(\"特意抛出的运行时异常!\"); }

//处理删除用户业务逻辑

public void delUser(Integer id) { //删除用户账号信息

jdbcTemplate.update(\"delete from users where id=?\", new Object[]{id},new int[]{Types.INTEGER}); //删除用户个人资料

jdbcTemplate.update(\"delete from userinfo where UserID=?\", new Object[]{id},new int[]{Types.INTEGER}); //特意将表名写成userinfoA,此条语句异常导致事务回滚

//jdbcTemplate.update(\"delete from userinfoA where UserID=?\ new Object[]{id},new int[]{Types.INTEGER}); System.out.println(\"ID号为\"+id+\"的用户删除成功!\"); }

//处理装载用户业务逻辑

public User loadUser(Integer id) {

47. 48. 49. 50. 51. 52. 53. . 55. 56. 57. 58. 59. 60. 61. 62. 63. . 65. 66. 67. 68.

try{

//装载用户账号信息

User user = (User)jdbcTemplate.queryForObject (\"select * from users where id=?\",

new Object[]{id},new int[]{Types.INTEGER},new UserRowMapper()); if (user!=null){ //装载用户个人资料

UserInfo userInfo = (UserInfo)jdbcTemplate.queryForObject (\"select * from userinfo

where UserID=?\new Object[]{id},new int[]{Types.INTEGER}, new UserInfoRowMapper()); user.setUserInfo(userInfo); }

return user;

}catch (Exception e) { return null; } }

//处理修改用户业务逻辑

public void modiUser(User user) { //修改用户账号信息

jdbcTemplate.update(\"update users set userName=?,userPwd=? where id=

?\", 69. 70. 71. 72. 73. 74. 75. 76. 77. 78.

new Object[]{user.getUserName(),user.getUserPwd(),user.getId()}, new int[]{Types.VARCHAR,Types.VARCHAR,Types.INTEGER}); UserInfo userInfo = user.getUserInfo(); if (userInfo!=null){ //修改用户个人资料

jdbcTemplate.update(\"update userinfo set Email=?,Phone=?, Address=? where UserID=?\",

new Object[]{userInfo.getEmail(),userInfo.getPhone(), userInfo.getAddress(),user.getId()},

new int[]{Types.VARCHAR,Types.VARCHAR,Types.VARCHAR,Types.INTEGER});

79. 80. 81. 82.

}

System.out.println(\"ID号为\"+user.getId()+\"的用户修改成功!\"); } }

在UserDaoImpl中,User类主要用于存放用户的账号信息,而用户的个人资料则存放在UserInfo类中,User类与UserInfo类分别映射了数据库的users表与userinfo表,这种映射关系的实现是借助Spring提供的RowMapper接口实现的,RowMapper的功能就是将记录集中的数据字段与持久化类中的属性产生对应关系,将一条记录封装成一个持久化对象并返回。 回想我们以前手动编写的JDBC代码段,既要负责JDBC连接的创建、事务的定义、事务的回滚、事务的提交、Statement或PreparedStatement对象的创建等,又要负责JDBC连接、Statement或PreparedStatement对象的关闭与释放,真正的核心代码就那么一两行。对于这种类似的模板式操作,Spring提供了良好的封装,统一对外提供模板化的操作,大大简化重复代码,提高开发效率。UserDaoImpl中已经再也看不到昔日累赘代码的身影了,JdbcTemplate对JDBC的数据库操作进行了统一封装,使得Java EE程序员直接调用JdbcTemplate的相关方法(见表6-8)高效地完成数据库的操作。 表6-8 JdbcTemplate中常用方法说明 方 法 名 称 protected JDBCTemplate(DataSource datasource) {…} public void execute(String sql){…} public int update(String sql){…} public int update(String sql, Object[] args, int[] types) {…} public int queryForInt(String sql) {…} public int queryForInt(String sql, Object[] args, int[] types) {…} public Object queryObject(String sql, RowMapper rm) {…} public Object queryObject(String sql, Object[] args, int[] types, RowMapper rm) {…} public List query(String sql, RowMapper rm) {…} 功 能 描 述 传入DataSource数据源对象构造JDBCTemplate实例 执行指定的sql且无任何返回 执行指定的sql并返回更新的记录数 应用args指定的参数值数组及types指定的参数类型数组来执行指定的sql并返回更新的记录数 执行指定的sql并返回整型数据结果 应用args指定的参数值数组及types指定的参数类型数组来执行指定的sql并返回整型数据结果 执行指定的sql并返回由RowMapper转换后的对象 应用args指定的参数值数组及types指定的参数类型数组来执行指定的sql并返回由RowMapper转换后的对象 执行指定的sql并返回由RowMapper转换后的对象集合 应用args指定的参数值数组及types指定的参数类型public List queryObject(String sql, Object[] args, int[] types, RowMapper rm) {…} public SqlRowSet queryForRowSet (String sql) {…} public Map queryForMap(String sql) {…} 数组来执行指定的sql并返回由RowMapper转换后的对象集合 执行指定的sql并返回SqlRowSet数据集 执行指定的sql并返回Map实例 由于一条完整的用户信息分散保存在两张不同的表中,为了确保数据的完整性,在对用户信息进行新增、删除与修改时,必须进行严格的事务管理。下面以XML配置的方式在Spring配置文件中对UserDaoImpl中方法进行事务管理:

1. 2.

< SPAN>

3. 4. 5. 6. 7. 8. 9. 10. 11.

xmlns=\"http://www.springframework.org/schema/beans\"

xmlns:context=\"http://www.springframework.org/schema/context\" xmlns:aop=\"http://www.springframework.org/schema/aop\" xmlns:tx=\"http://www.springframework.org/schema/tx\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"

xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-2.5.xsd

12. 13. 14. 15. 16. 17. 18. 19.

http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-2.5.xsd\">

SPAN>\"dataSource\" class=\"com.mchange.v2.c3p0.ComboPooledDataSource\"> 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38.

\"driverClass\"> com.mysql.jdbc.Driver

\"jdbcUrl\">

jdbc:mysql://localhost:3306/db_user?useUnicode= true&characterEncoding=gbk

\"user\"> root

\"password\"> root

39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. . 55. 56. 57. 58.

\"maxPoolSize\"> 20

\"minPoolSize\"> 2

\"initialPoolSize\"> 2

\"maxIdleTime\"> 20

\"txManager\"

class=\"org.springframework.jdbc.datasource.DataSourceTransactionMana

ger\"> 59. 60. 61. 62. 63. . 65. 66. 67.

\"dataSource\" ref=\"dataSource\"/>

\"transactionPointcut\"

expression=\"execution(* test.spring.dao.impl.UserDaoImpl.*(..))\"/>

SPAN>\"txAdvice\" pointcut-ref=\"transactionPointcut\"/> 68. 69. 70. 71. 72. 73.

\"txAdvice\" transaction-manager=\"txManager\">

SPAN>\"load*\" read-only=\"true\" propagation=\"NOT_SUPPORTED\"/>

74. 75. 76. 77. 78. 79.

\"*\"/>

SPAN>\"jdbcTemplate\" class=\"org.springframework.jdbc.core.JdbcTemplate\"> 80. 81. 82. 83.

\"dataSource\"/>

SPAN>\"dao\" class=\"test.spring.dao.impl.UserDaoImpl\"/> 84. 85.

SPAN>\"service\" class=\"test.spring.service.impl.UserServiceImpl\"/> 86. 87.

SPAN>\"userAction\" class=\"test.spring.action.UserAction\"/> 88.

6.3.3 基于Annotation方式的事务管理配置 与基于Annotation方式的IoC或AOP实现类似,基于Annotation方式的事务管理主要是为了顺应技术发展的趋势,以及防止Spring配置文件过于臃肿。 Spring 2.5为事务管理提供了@Transactional注解,通过为@Transactional指定不同的参数,如表6-9所示,以满足不同的事务管理需求。 表6-9 @Transactional注解中常用参数说明 参 数 名 称 readOnly 功 能 描 述 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行rollbackFor 事务回滚。例如: 指定单一异常类:@Transactional(rollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) (续表) 参 数 名 称 功 能 描 述 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如: rollbackForClassName 指定单一异常类名称:@Transactional(rollbackForClassName=\"RuntimeException\") 指定多个异常类名称:@Transactional(rollbackForClassName={\"RuntimeException\ 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进noRollbackFor 行事务回滚。例如: 指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如: noRollbackForClassName 指定单一异常类名称:@Transactional(noRollbackForClassName=\"RuntimeException\") 指定多个异常类名称: @Transactional(noRollbackForClassName={\"RuntimeException\ propagation 该属性用于设置事务的传播行为,具体取值可参考表6-7。 例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 该属性用于设置事务的超时秒数,默认值为-1表示永不超时 isolation timeout 现将UserDaoImpl组件中的方法使用@Transactional注解实现事务管理:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17.

package test.spring.dao.impl; import javax.annotation.Resource;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.sql.Types; import test.spring.bean.*; import test.spring.dao.UserDao;

/** 用户管理底层数据访问接口实现,使用@Transactional注解实现事务管理 */ @Transactional

public class UserDaoImpl implements UserDao { //通过@Resource注解注入Spring提供的JDBCTemplate实例 @Resource JdbcTemplate jdbcTemplate;

//处理新增用户业务逻辑,使用@Transactional注解实现该方法的事务管理 @Transactional(rollbackFor=RuntimeException.class) public void addUser(User user) { //保存用户账号信息

18. jdbcTemplate.update(\"insert into users(userName,userPwd) values(?,?)

\", 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41.

new Object[]{user.getUserName(),user.getUserPwd()}, new int[]{Types.VARCHAR,Types.VARCHAR});

int userid = jdbcTemplate.queryForInt(\"select id from users where userName=? and userPwd=?\",

new Object[]{user.getUserName(),user.getUserPwd()}, new int[]{Types.VARCHAR,Types.VARCHAR}); UserInfo userInfo = user.getUserInfo(); if (userInfo!=null){ //保存用户个人资料

jdbcTemplate.update(\"insert into userinfo

(UserID,Email,Phone,Address) values(?,?,?,?)\",

new Object[]{userid,userInfo.getEmail(),userInfo.getPhone(), userInfo.getAddress()},

new int[]{Types.INTEGER,Types.VARCHAR,Types.VARCHAR, Types.VARCHAR}); }

throw new RuntimeException(\"特意抛出的运行时异常!\"); }

//处理删除用户业务逻辑,使用@Transactional注解实现该方法的事务管理 @Transactional

public void delUser(Integer id) { //删除用户账号信息

jdbcTemplate.update(\"delete from users where id=?\", new Object[]{id}

, 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53.

new int[]{Types.INTEGER}); //删除用户个人资料

jdbcTemplate.update(\"delete from userinfo where UserID=?\", new Object[]{id},

new int[]{Types.INTEGER});

//特意将表名写成userinfoA,此条语句异常导致事务回滚

//jdbcTemplate.update(\"delete from userinfoA where UserID=?\ new Object[]{id},

new int[]{Types.INTEGER});

System.out.println(\"ID号为\"+id+\"的用户删除成功!\"); }

. 55.

//处理装载用户业务逻辑,使用@Transactional注解设置该方法不需要事务

@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)

56. 57. 58. 59. 60. 61. 62. 63. . 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75.

public User loadUser(Integer id) { //装载用户账号信息

User user = (User)jdbcTemplate.queryForObject (\"select * from users where id=?\",

new Object[]{id},new int[]{Types.INTEGER},new UserRowMapper()); if (user!=null){ //装载用户个人资料

UserInfo userInfo = (UserInfo)jdbcTemplate.queryForObject (\"select * from userinfo where

UserID=?\new Object[]{id},new int[]{Types.INTEGER}, new UserInfoRowMapper()); user.setUserInfo(userInfo); }

return user; }

//处理修改用户业务逻辑,使用@Transactional注解实现该方法的事务管理 @Transactional

public void modiUser(User user) { //修改用户账号信息

jdbcTemplate.update(\"update users set userName=?,userPwd=? where id=

?\", 76. 77. 78. 79. 80. 81. 82. 83. 84. 85.

new Object[]{user.getUserName(),user.getUserPwd(),user.getId()}, new int[]{Types.VARCHAR,Types.VARCHAR,Types.INTEGER}); UserInfo userInfo = user.getUserInfo(); if (userInfo!=null){ //修改用户个人资料

jdbcTemplate.update(\"update userinfo set Email=?,Phone=?, Address=? where UserID=?\",

new Object[]{userInfo.getEmail(),userInfo.getPhone(), userInfo.getAddress(),user.getId()},

new int[]{Types.VARCHAR,Types.VARCHAR,Types.VARCHAR,Types.INTEGER});

86. 87. 88.

}

System.out.println(\"ID号为\"+user.getId()+\"的用户修改成功!\"); }

. }

使用了@Transactional注解配置事务管理之后,Spring配置文件中的事务配置代码就可以基本省略:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.

xmlns=\"http://www.springframework.org/schema/beans\"

xmlns:context=\"http://www.springframework.org/schema/context\" xmlns:aop=\"http://www.springframework.org/schema/aop\" xmlns:tx=\"http://www.springframework.org/schema/tx\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"

xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-2.5.xs

d 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33.

http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-2.5.xsd\">

com.mysql.jdbc.Driver

jdbc:mysql://localhost:3306/db_user?useUnicode= true&characterEncoding=gbk

34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. . 55. 56. 57. 58. 59.

root

root

20

2

2

20

class=\"org.springframework.jdbc.datasource.DataSourceTransactionMana

ger\"> 60. 61. 62. 63.

plate\"> . 65. 66. 67. 68. 69.

70. 71.

> 72. 73. 74.

6.4 \"桃园三结义\"--SSH 2组合开发框架始成

尽管Spring致力于提供\"一站式\"服务,在MVC应用领域,大家还是会被Struts的出色表现所折服,谈到ORM,总是忘不了Hibernate娴熟的身影。Spring宽广的胸怀,为Struts与Hibernate敞开大门,提供了极为便利的集成方式,让SSH(Spring+Struts+Hibernate)这个崭新的组合框架从此映入Java EE程序员的眼帘,成为Java EE应用开发史上的新神话。

6.4.1 Spring 2.5集成ORM中间件Hibernate 3.2

在Spring 2.5中集成Hibernate实质上是将Hibernate需要用到的数据源DataSource(如C3P0连接池数据源ComboPooledDataSource)、Hibernate的SessionFactory实例(如LocalSessionFactoryBean)及事务管理器HibernateTransactionManager移交给Spring容器管理,Spring同时也对Hibernate进行了封装,向Java EE程序员提供统一的模板化操作。例如:

1. 2. 3. 4. 5. 6. 7. 8.

xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"

xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd\">

9. 10. 11. 12. 13.

com.mysql.jdbc.Driver

14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51.

jdbc:mysql://localhost:3306/eportal?useUnicode=true& characterEncoding=gbk

root

root

20

2

2

20

52. 53. . 55. 56. 57. 58. 59. 60. 61. 62. 63. . 65. 66. 67. 68. 69. 70. 71. 72. 73.

com/eportal/ORM/News.hbm.xml com/eportal/ORM/Category.hbm.xml com/eportal/ORM/Memberlevel.hbm.xml com/eportal/ORM/Cart.hbm.xml com/eportal/ORM/Traffic.hbm.xml com/eportal/ORM/Newsrule.hbm.xml com/eportal/ORM/Merchandise.hbm.xml com/eportal/ORM/Admin.hbm.xml com/eportal/ORM/Orders.hbm.xml

com/eportal/ORM/Cartselectedmer.hbm.xml com/eportal/ORM/Newscolumns.hbm.xml com/eportal/ORM/Member.hbm.xml

org.hibernate.dialect. MySQLDialect

74. 75. 76. 77. 78. 79. 80. 81. 82.

true

50

class=\"org.springframework.orm.hibernate3.HibernateTransactionManage

r\"> 83. 84. 85. 86. 87.

88. . 90. 91.

class=\"org.springframework.transaction.interceptor. TransactionInterceptor\">

92. 93. 94. 95.

96. 97. 98. 99.

PROPAGATION_REQUIRED,readOnly PROPAGATION_REQUIRED,readOnly PROPAGATION_REQUIRED,readOnly PROPAGATION_REQUIRED,readOnly

100. PROPAGATION_REQUIRED,readOnly 101. 102. PROPAGATION_REQUIRED 103. 104. 105.

106. 107.

109. 110. 111.

112. adminService 113. columnsService 114. newsService 115. crawlService 116. memberLevelService 117. memberService 118. categoryService 119. merService 120. cartService 121. ordersService 122. trafficService 123.

124.

125. 126. 127. true 128.

129. 130. 131.

132. transactionInterceptor 133.

134. 135.

136.

137. 138.

139. 140.

141.

142. >

143. 144. 145. 146. …… 147.

Spring 2.5中事务管理配置的方式有多种,在6.3节中重点讲解的是基于XML配置与Annotation注解方式实现的事务管理,其实使用事务TransactionInterceptor与事务自动代理BeanNameAutoProxyCreator相结合也是一种事务管理配置的常见方式。

在配置好了数据源、SessionFactory实例及事务管理器HibernateTransactionManager之后,就可以使用Spring为我们提供的HibernateTemplate模板操作类进行数据库访问组件的开发了。为了增强组件的可重用性,提高开发效率,可将DAO组件抽象成通用的数据库访问组件。例如:

1. 2. 3. 4. 5. 6. 7.

package com.eportal.DAO; import java.io.Serializable; import java.sql.Connection; import java.util.List; import org.hibernate.*;

import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

8. 9.

/** 统一数据访问接口实现 */

public class BaseDAOImpl extends HibernateDaoSupport implements BaseDAO {

10. 11. 12. 13.

/** 统计指定类的所有持久化对象 */

public int countAll(String clazz) {

final String hql = \"select count(*) from \"+clazz+ \" as a\";

Long count = (Long)getHibernateTemplate().execute(new HibernateCallb

ack(){ 14.

public Object doInHibernate(Session session) throws HibernateExcepti

on{ 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25.

Query query = session.createQuery(hql); query.setMaxResults(1); return query.uniqueResult(); } });

return count.intValue(); }

/** 统计指定类的查询结果 */

public int countQuery(String hql) { final String counthql = hql;

Long count = (Long)getHibernateTemplate().execute(new HibernateCallb

ack(){ 26.

public Object doInHibernate(Session session) throws HibernateExcepti

on{ 27. 28. 29. 30. 31. 32.

Query query = session.createQuery(counthql); query.setMaxResults(1); return query.uniqueResult(); } });

return count.intValue();

33. 34. 35. 36.

}

/** 删除指定ID的持久化对象 */

public void delById(Class clazz,Serializable id) {

getHibernateTemplate().delete(getHibernateTemplate().load(clazz, id)

); 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48.

}

/** 装载指定类的所有持久化对象 */

public List listAll(String clazz) { return getHibernateTemplate().find

(\"from \"+clazz+\" as a order by a.id desc\"); }

/** 分页装载指定类的所有持久化对象 */

public List listAll(String clazz, int pageNo, int pageSize) { final int pNo = pageNo; final int pSize = pageSize;

final String hql = \"from \"+clazz+ \" as a order by a.id desc\"; List list = getHibernateTemplate().executeFind(new HibernateCallback

(){ 49.

public Object doInHibernate(Session session) throws HibernateExcepti

on{ 50. 51. 52. 53. . 55. 56. 57. 58. 59. 60. 61. 62. 63. . 65. 66. 67.

Query query = session.createQuery(hql); query.setMaxResults(pSize);

query.setFirstResult((pNo-1)*pSize); List result = query.list();

if (!Hibernate.isInitialized(result))Hibernate.initialize(result); return result; } });

return list; }

/** 加载指定ID的持久化对象 */

public Object loadById(Class clazz,Serializable id) { return getHibernateTemplate().get(clazz, id); }

/**加载满足条件的持久化对象*/

public Object loadObject(String hql) { final String hql1 = hql; Object obj = null;

68. List list = getHibernateTemplate().executeFind(new HibernateCallback

(){ 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. . 90. 91. 92. 93. 94. 95.

public Object doInHibernate(Session session) throws HibernateException{

Query query = session.createQuery(hql1); return query.list(); } });

if(list.size()>0)obj=list.get(0); return obj; }

/** 查询指定类的满足条件的持久化对象 */ public List query(String hql) { final String hql1 = hql;

return getHibernateTemplate().executeFind(new HibernateCallback(){ public Object doInHibernate(Session session) throws HibernateException{

Query query = session.createQuery(hql1); return query.list(); } }); }

/** 分页查询指定类的满足条件的持久化对象 */

public List query(String hql, int pageNo, int pageSize) { final int pNo = pageNo; final int pSize = pageSize; final String hql1 = hql;

return getHibernateTemplate().executeFind(new HibernateCallback(){ public Object doInHibernate(Session session) throws HibernateExcepti

on{ 96. 97. 98. 99.

Query query = session.createQuery(hql1); query.setMaxResults(pSize);

query.setFirstResult((pNo-1)*pSize); List result = query.list();

100. if (!Hibernate.isInitialized(result))Hibernate.initialize(result); 101. return result; 102. } 103. });

104. }

105. /** 保存或更新指定的持久化对象 */

106. public void saveOrUpdate(Object obj) { 107. getHibernateTemplate().saveOrUpdate(obj); 108. }

109. /** 条件更新数据 */

110. public int update(String hql) { 111. final String hql1 = hql;

112. return ((Integer)getHibernateTemplate().execute(new HibernateCallbac

k(){

113. public Object doInHibernate(Session session) throws HibernateExcepti

on{

114. Query query = session.createQuery(hql1); 115. return query.executeUpdate(); 116. }

117. })).intValue(); 118. }

119. /** 从连接池中取得一个JDBC连接 */ 120. public Connection getConnection() {

121. return getHibernateTemplate().getSessionFactory(). 122. getCurrentSession().connection(); 123. } 124. }

这样一来,所有业务逻辑组件均可调用该DAO组件进行数据库的访问操作了,大大提高了开发效率。从上述代码中可看出,BaseDAOImpl继承了HibernateDaoSupport,HibernateDaoSupport是Spring为简化Hibernate的DAO操作量身定制的工具类,使用getHibernateTemplate()方法即可获得HibernateTemplate实例的引用,HibernateTemplate对Hibernate的原始操作进行了封装,为Java EE程序员提供模板化的操作方法,如表6-10所示。 表6-10 HibernateTemplate中的常用方法说明 方 法 名 称 void delete(Object entity) void deleteAll(Collection entities) 功 能 描 述 删除指定持久化实例 删除集合内全部持久化类实例 通过指定的HibernateCallback实例实现Hibernate的回调操作,相当于重新Object execute(HibernateCallback action) 获得了对Hibernate Session对象的操作权,找回了之前的Hibernate的操作感觉 List find(String queryString) List findByNamedQuery(String queryName) 根据HQL查询字符串来返回实例集合 根据命名查询返回实例集合 void flush() Object get(Class entityClass, Serializable id) Object load(Class entityClass, Serializable id) List loadAll(Class entityClass) Serializable save(Object entity) 刷新一级缓存区的内容,使之与数据库数据保持同步 根据主键加载特定持久化类的实例,如果指定的记录不存在则返回null 根据主键加载特定持久化类的实例,如果指定的记录不存在则抛出异常 返回特定持久化类的所有实例集合 保存新的实例 (续表) 方 法 名 称 void saveOrUpdate(Object entity) void saveOrUpdateAll(Collection entities) void setMaxResults(int maxResults) 功 能 描 述 根据实例的持久化状态,对实例进行保存或者更新操作 根据指定集合中实例的持久化状态,对所有实例进行保存或者更新操作 用于指定一次最多检索出的实例数目,默认为所有实例,在分页查询时设置每页的记录条数 用于将一个处于脱管状态的游离对象加载到Session缓存中,与一个具体的Session实例关联,使游离对象转换成持久对象 返回当前Hibernate Session实例的引用 void update(Object entity) protected org.hibernate.Session getSession() 6.4.2 Spring 2.5集成MVC框架Struts 2 Spring 2.5集成Struts 2(或Struts 2集成Spring 2.5)的主要目的是为了让Struts 2中的Action实例可以访问Spring容器中丰富的业务逻辑组件资源,同时将Action的实例化与依赖注入的工作移交给Spring容器统一管理,充分发挥Spring\"幕后财政\"的优势。 Struts 2凭借其基于插件形式的良好扩展机制,使之与其他框架或组件的结合变得灵活自如,当然也包括大名鼎鼎的Spring在内。将下载的Struts 2压缩包解压后,在lib文件夹下自带有struts2-spring-plugin-2.0.11.1.jar类似的JAR包文件,\"struts2-spring-plugin\"顾名思义就是Struts 2的Spring插件,有了这个插件,让Struts 2与Spring双剑合璧就轻而易举了。 在Struts 2中集成Spring的基本步骤如下。 (1)将Spring框架所依赖的JAR包(如spring.jar、common-annotations.jar、commons- logging.jar)复制到WEB-INF的lib文件夹下。 (2)将Struts 2自带的Spring插件(如:struts2-spring-plugin-2.0.11.1.jar)也复制到WEB-INF的lib文件夹下。 (3)在Struts 2的配置文件struts.xml中将对象工厂ObjectFactory设置为spring: 1. 2.

(4)在Spring配置文件中装配Struts 2的Action实例,由于Struts 2在处理请求时,每个不同的请求均会生成一个相应的Action实例负责处理,因此,在配置时需要使用原型模式,以确保每次生成的是全新的Action实例。例如:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10.

(5)在Struts 2的配置文件struts.xml中配置Action映射时,class属性不再使用类全名,而应该使用Spring配置文件中定义的相应Bean实例名称。例如:

1. 2. 3.

……

4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18.

/admin/editAdmin.jsp

/admin/{1}.jsp

/admin/{1}.jsp /admin/index.jsp /admin/login.jsp admin_browseAdmin /admin ${actionMsg}

19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39.

/admin/editColumns.jsp

/admin/{1}.jsp

/admin/{1}.jsp columns_browseColumns /admin ${actionMsg}

……

(6)在开发Struts 2的Action业务控制器组件时,不需要进行业务逻辑组件的实例化工作,仅需进行依赖声明即可,最终由Spring容器来完成依赖对象的注入工作。例如:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.

……

/** 系统管理员处理控制器 */ @SuppressWarnings(\"serial\")

public class AdminAction extends ActionSupport implements ModelDriven{

/** 通过依赖注入AdminService组件实例 */ AdminService service; ……

/** 处理新增系统用户请求 */ public String addAdmin(){ Admin tempAdmin = new Admin();

tempAdmin.setLoginName(model.getLoginName());

tempAdmin.setLoginPwd(MD5.MD5Encode(model.getLoginPwd())); tempAdmin.setPrivileges(model.getPrivileges());

15. 16. 17. 18. 19. 20. 21. 22. 23. 24.

if (service.saveOrUpdateAdmin(tempAdmin)) {//调用业务逻辑组件保存用户实例

addActionMessage(getText(\"admin_add_succ\")); }else{

addActionMessage(getText(\"admin_add_fail\")); }

return SUCCESS; } …… }

(7)修改web.xml配置文件,让Web应用启动时自动装载Spring容器:

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22.

……

log4jConfigLocation

/WEB-INF/classes/log4j.properties

contextConfigLocation

/WEB-INF/applicationContext.xml

org.springframework.web. context.ContextLoaderListener

org.springframework.web.util.Log4 jConfigListener ……

(8)在JSP页面、Servlet组件、Action业务控制器或自定义标签类中使用Spring提供的辅助类WebApplicationContextUtils获取Spring容器实例的引用,用以访问Spring容器中的Bean实例。例如:

1. 2. 3. 4. 5. 6. 7. 8.

……

/** 相关新闻列表自定义标签类 */

public class AboutNewsTag extends SimpleTagSupport{ BaseDAO dao = null; //数据库DAO接口 ......

/** 标签体处理 */

public void doTag() throws JspException, IOException{

//使用WebApplicationContextUtils工具类获取Spring IoC容器中的dao实例

9. 10.

dao = (BaseDAOImpl)WebApplicationContextUtils

.getRequiredWebApplicationContext(((PageContext)getJspContext())

11. 12. 13. 14. 15.

.getServletContext()).getBean(\"dao\"); ...... } ...... }

6.4.3 SSH 2组合框架的基本开发步骤-ePortal启程

MyEclipse 7.0是目前较流行的Java EE集成开发环境,对目前一些主流的框架均提供了良好的集成,且升级频率比较快,是初学者的理想选择,具体安装与配置可参考2.3.1节。

免费电子商务平台ePortal是本书的一个综合实例,旨在全面演示SSH 2组合框架的详细应用,手把手带领初学者步入SSH 2应用开发的殿堂。

ePortal项目的功能如下:

后台新闻栏目管理模块实现对新闻栏目的新增、删除、修改与分页浏览。

后台新闻资讯管理模块实现对新闻的分栏目管理,包括对新闻资讯的新增、删除、修改、分页浏览与静态页面生成。

后台新闻采集模块实现对新闻采集规则的新增、删除、修改、分页浏览及新闻的采集入库。

后台会员注册管理模块实现对会员级别及注册会员的新增、删除、修改与分页浏览。 后台商品分类管理模块实现对商品分类的新增、删除、修改与分页浏览。

后台商品管理模块实现对商品的分类管理,包括对商品的新增、删除、修改与分页浏览。 后台订单管理模块实现受理已提交的订单、修改订单的状态、查看订单详情、删除历史订单或结单。

后台系统用户管理模块实现对系统用户的新增、删除、修改、分页浏览与权限控制。 后台流量统计模块实现分时段、分区域、按来源及受访页面进行统计与分析,得到不同的分析结果,更好地指导网站内容建设与推广。

前台新闻展示,通过自定义标签实现指定栏目新闻的列表与新闻阅读阅读。 前台商品展示,通过自定义标签实现指定分类商品的列表与商品详情展示。

前台会员购物车与订单的自助管理模块,会员注册并登录后,可以对自己的订单与购物车进行管理。

会员积分的自动体现及会员级别的自动晋升。

结合red5(采用Java开发的开源Flash流媒体服务器)实现商品视频广告的流式播放,实现在线视频购物。

下面我们以ePortal项目为例,讲解一下MyEclipse 7.0中SSH 2组合框架的基本开发步骤。

(1)启动MyEclipse7.0,选择\"File\"→\"New\"→\"Project\"菜单命令,弹出项目向导对话框,如图6-12所示,选择\"MyEclipse\"→\"Java Enterprise Projects\"→\"Web Project\",单击\"Next\"按钮,在弹出的\"New Web Project\"向导对话框(见图6-13)中输入项目名称ePortal,选择Jave EE版本为5.0,最后单击\"Finish\"按钮完成Web项目的创建。

(2)由于目前MyEclipse 7.0尚未提供Struts 2的开发插件,因此,在MyEclipse 7.0中开发Struts 2应用需要手工进行包的导入及配置文件的手动配置等。将Struts 2的几个必需Jar包(commons-logging-1.0.4.jar、freemarker-2.3.8.jar、ognl-2.6.11.jar、struts2-core-2.0.11.1.jar及xwork-2.0.4.jar)及Spring插件struts2-spring-plugin-2.0.11.1.jar复制到ePortal应用的WEB-INF/lib下,为ePortal应用添加Struts 2框架的类库。

(3)在src文件夹下创建国际化资源文件messageResource_zh_CN.properties(内容省略)及Struts 2核心配置文件struts.xml:。

1. 2. 3.

\"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN\" 4. 5. 6. 7. 8. 9. 10. 11.

\"http://struts.apache.org/dtds/struts-2.0.dtd\">

\"/>

12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38.

class=\"com.eportal.struts.interceptor.LoginedCheckInterceptor\"/>

/exception.jsp

/admin/tologin.htm

/admin/error.htm

\"/> 39. 40. 41.

(4)创建ePortal所需的如下包。

com.eportal.struts包:存放与Struts 2相关的类与资源文件。

com.eportal.struts.action包:用于存放Struts的Action类。

com.eportal.struts.model包:用于存放Struts的model类。

com.eportal.ORM包:用于存放Hibernate的ORM映射文件及持久化类。 com.eportal.DAO包:用于存放统一数据库访问接口DAO类。 com.eportal.service包:用于存放所有业务逻辑类。 com.eportal.tld包:用于存放自定义标签类。 com.eportal.servlet包:用于存放servlet类。 com.eportal.bean包:用于存放其他JavaBean。 com.eportal.util包:用于存放Java工具类。 (5)为ePortal应用导入Spring 2.5框架,在ePortal项目上单击鼠标右键,在弹出的快捷菜单中选择\"MyEclipse\"→\"Add Spring Capabilites\"命令,如图6-14所示,此时,打开如图6-15所示的对话框,可不选择任何类库,事后直接通过手动导入Spring的完整JAR包spring.jar即可。单击\"Next\"按钮,打开如图6-16所示的对话框,使用默认值,单击\"Finish\"按钮完成Spring 2.5框架的导入。 (6)将Spring 2.5的完整JAR包spring.jar及Annotation注解处理所依赖的JAR包common-annotations.jar复制到ePortal应用的WEB-INF/lib下。 (7)为ePortal应用导入Hibernate3.2框架,在ePortal项目上单击鼠标右键,在弹出的快捷菜单中选择\"MyEclipse\"→\"Add Hibernate Capabilites\"命令,打开如图6-17所示的对话框,单击\"Next\"按钮,打开如图6-18所示的对话框,此处选择Spring的配置文件,再单击\"Next\"按钮,打开如图6-19所示的对话框,选择第4步创建的Spring配置文件applicationContext.xml,并设置Hibernate的SessionFactory Id为sessionFactory,再单击\"Next\"按钮,打开如图6-20所示的对话框,此处可不进行数据源的配置,事后直接在Spring配置文件applicationContext.xml中进行手动配置比较方便。再单击\"Next\"按钮,打开如图6-21所示的对话框,此处可不进行Hibernate SessionFactory的配置,事后直接在Spring配置文件applicationContext.xml中进行手动配置比较方便。单击\"Finish\"按钮完成Hibernate 3.2框架的导入。 (8)将MySQL的JDBC驱动及连接池组件c3p0的JAR包复制到ePortal应用的WEB-INF/lib下。在Spring配置文件applicationContext.xml中手动配置Hibernate 3.2的数据源、SessionFactory Id及事务管理器(见6.4.1节)。 (9)在配置文件web.xml中配置Struts 2与Spring 2.5:

1. 2. 3. 4.

xmlns=\"http://java.sun.com/xml/ns/javaee\"

xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"

5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42.

xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\">

log4jConfigLocation

/WEB-INF/classes/log4j.properties

contextConfigLocation

/WEB-INF/applicationContext.xml

org.springframework.web.context. ContextLoaderListener

org.springframework.web.util. Log4jConfigListener

struts2

org.apache.struts2.dispatcher. FilterDispatcher

encodingFilter

org.springframework.web.filter.CharacterEncodingFilter

encoding gbk

43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. . 55. 56. 57. 58. 59. 60. 61. 62. 63. . 65. 66. 67. 68.

struts2 /*

encodingFilter *.action

encodingFilter *.jsp

index.jsp

404 /404.htm

500 /500.htm

(10)使用MySQL-Front等工具在MySQL 5中创建数据库eportal,字符集为gbk,然后执行ePortal实例自带的数据库脚本eportal.sql创建数据表。在MyEclipse中创建数据库JDBC连接MySQL_JDBC,如图6-22所示,在MyEclipse Database Explorer视图的数据库连接列表中单击鼠标右键,在弹出的快捷菜单中选择\"New\"命令,如图6-23所示,在弹出的数据库连接属性配置对话框中设置各参数值,如图6-24所示,单击\"Next\"按钮,打开如图6-25所示的对话框,此处使用默认选择,单击\"Finish\"按钮完成数据库外连接的创建。

(11)使用Hibernate的逆向工程工具直接从数据库表生成相应的ORM持久化类与相ORM映射文件。在刚创建的数据库连接MySQL_JDBC上单击鼠标右键,在弹出的快捷菜单中选择\"Open connection\"命令,如图6-26所示,按住\"Shift\"键选择所有需要生成ORM持久化类与ORM映射文件的数据表,如图6-27所示,然后在选中的表上单击鼠标右键,在弹出的快捷菜单中选择\"Hibernate Reverse Engineering\"命令,如图6-28所示,在弹出的

Hibernate逆向工程对话框中进行如图6-29所示的设置,单击\"Next\"按钮,在弹出的对话框中选择ID生成器为identity,如图6-30所示,再单击\"Next\"按钮,打开如图6-31所示的对话框,使用默认值,单击\"Finish\"按钮完成ORM持久化类与相ORM映射文件的创建。 (12)在ePortal应用的WebRoot下创建以下几个文件夹:css、js、admin、common和images,创建404与500错误提示页404.htm、500.htm(内容省略),再创建一个显示Struts 2捕捉到的异常的JSP页面exception.jsp(内容省略),最后部署后便可以初步测试一下了。

在部署之前,先为MyEclipse配置好Tomcat及JDK,选择\"Window\"→\"Preferences\"菜单命令,如图6-32所示,在弹出的对话框中正确配置Tomcat及JDK,如图6-33所示,单击\"OK\"按钮完成Tomcat及JDK的配置。

下面正式部署ePortal应用到Tomcat中,用以测试SSH 2集成是否成功。单击MyEclipse中的部署按钮弹出项目部署对话框,如图6-34所示,单击\"Add\"按钮,在弹出的对话框中选择刚才配置好的Tomcat 6.x,如图6-35所示,单击\"Finish\"按钮完成ePortal应用的部署。

最后单击MyEclipse中的服务器运行/停止按钮启动Tomcat 6.x服务器,如图6-36所示,打开浏览器,在地址栏中输入http://localhost:8080/ePortal/便可进行初步的测试。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- dcrkj.com 版权所有 赣ICP备2024042791号-2

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务