`
xingyx
  • 浏览: 58636 次
  • 性别: Icon_minigender_2
社区版块
存档分类
最新评论
  • 52p9_xu: 感谢分享,本人最近闲来无事也想搭建一个 全注解的SSH框架。不 ...
    SSH注解框架

SSH注解框架

    博客分类:
  • JAVA
阅读更多

Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解实例详解(一)

JavaEE企业级开发中,以SSH2框架为核心的应用非常广,大象根据项目实践经验,通过一个实例,详细的为大家讲解如何实现全注解式的开发。
    开发环境
    JDK1.6.0_18
    Eclipse3.2.1
    MyEclipse5.1.0
    Tomcat6.0.10
    MySQL5.0.27
    Navicat Lite for MySQL 8.1.20
    
每个人的开发环境可能会有差异,但有一点我需要说明的是,JDK的版本不得低于1.5,因为用到了很多1.5版才支持的新特性。TomcatMySQL不要低于我所用的版本,因为我没在其它的版本上进行测试。Navicat则是MySQL数据库的图形化操作工具。我在这里假定各位目前已经设置好了开发环境,下面就开始详细的说明。
    
由于要阐述的内容比较多,大象决定将它们划分成个几章节来讲,这一章就主要来说说jar包的选择。
    
第一部分:选择必须的jar
    
新建一个web项目,然后将必要的jarCOPYlib里面。根据本文实例demo,大象给出下图中的最少jar包配置。
        
    我对这些jar包进行一下说明,方便大家理解。
    
    解压Struts2.1.6lib文件夹,从中选出上面7jar包添加到我们的工程库中。commons-loggingfreemarkerognlstruts2-corexwork5个还是struts2的核心包。但在Struts2.1.6这个版本中,还需要加上commons-fileupload包。如果没有,则启动就会报错,不过不需要像网上传言的那样还得加上commons-iojar包,这些大象都亲自做过测试。在本实例中,我将对struts2也采取注解的方式,所以用到了struts2-convention-plugin-2.1.6.jar这个插件。因为要与spring整合,所以struts2-spring-plugin-2.1.6.jar也必不可少。
    
    大象在这里偷个懒,直接将spring的完整jar包加了进来,如果各位想精简类库的话,就选取它的分类jar包吧。比如本例使用struts2作为MVC框架,所以springwebmvc就不可能用到了。有想改的朋友请自己动手改下。另外有点我想说下,如果采取完整springjar包,还需要Spring2.5.6\lib\concurrent文件夹中的backport-util-concurrent.jar,如果不加这个,spring会报错。但是采取spring分类jar包的形式,这个可以不用加,至于具体使用什么需要依赖这个包,大象还没去测试过,这个有待验证。还有lib\slf4j下的日志包,目前很多都开始采用基于slf4j接口的日志器,它的好处就是日志器是根据slf4j的接口来进行实现,可以在不改变代码的情况下更换日志器。最后Spring的源代码中使用的是commons-logging记录日志,因此这个包不能少,不过因为struts2也用到了,所以这里就省了。
    
    Hibernate3.3版开始,对jar包结构做了一次大的调整,我们只需要加入lib\required文件夹下面的6jar包。请注意这6jar包都是使用Hibernate所必须的。另外再加上hibernate核心包。这里我将slf4j-api-1.5.2.jar换成了1.5.0,这是因为slf4j是一个通用日志接口,不提供任何实现,我在demo里面使用的是log4j,而hibernate包里面没有log4jslf4j实现。而且如果版本不一致,会有异常,因此我就采用Spring2.5.6\lib\slf4j里面提供的配套版本。另外我将commons-collections-3.1.jar换成了Struts2.1.6里面的3.2版。
    
    例子中使用Hibernate JPA来完成实体对象映射,所以上面这些包都必不可少。使用注解的方式,可以不用写繁琐的配置文件,降低了出错机率。而且现在很多人都喜欢这种方式。大家可以去sourceforge下载。
    下载地址http://sourceforge.net/projects/hibernate/files/
    
    本例使用DBCP连接池来管理数据源。
    
    MySQL数据库的连接驱动。
    
    这个包的作用是创建动态代理对象。比如在使用AOP方式管理spring事务时,如果我们的目标对象没有实现接口,而又要使用AOP来处理事务,这时就需要用到这个jar包。可以在Spring2.5.6\lib\cglib里面找到。
    
    JSTL标签库,很经典的东东,如果需要可以将它们加入lib中。
    大象在这里建议大家做开发的时候,不要过多的依赖MyEclipse提供的那些功能,多用手动的方式来做。那样方便是方便了,但不利于学习。比如加入上面这些开发所用的类库,这样可以更清楚的了解每个jar包的作用,增加知识的积累,方便以后调试。Ok,关于这部分的内容到这里就说完了,那么,我们下次继续

 

Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解实例详解(二)

在上一章中详细分析了JAR包的选择,那么这次我将对例子中的一些必须的配置文件进行下说明。虽然这些配置在网上也很容易找到,但是很多都没有讲个因为所以出来,这样根本就得不到提高。在此,大象为各位详细分析一下这些内容。
    实例中涉及的配置文件有这么几个
    applicationContext.xml
    jdbc.properties
    log4j.properties
    struts.xml
    web.xml 
    我准备在本章中只讲applicationContext.xmljdbc.propertiesweb.xmllog4j的配置大同小异而且也不在本文范围。至于struts.xml我准备留到后面与Action代码一起来讲,因为用的是struts2-convention-plugin插件来实现struts2的注解,所以这两个结合起来讲要好一些。
    第二部分:分析配置文件
    1jdbc.properties
    本例采用MySQL数据库,所以我设置了一个属性文件,用来存放一些连接信息和Hibernate相关的设置。

    
因为我们使用的是Hibernate来与数据库进行交互,把这些东西写在单独的文件里,是方便修改,如果你想换成SQL Server或是Oracle,只需要更改driverurl以及dialect,而且还可以自由控制sql语句的显示的开关,非常方便。至于写在这里怎么用呢?请接着看下面的applicationContext.xml说明。
    
2applicationContext.xml
    
这个文件就是spring的主配置文件了,当然,本例也只有这么一个spring的配置文件,内容不多,但做的工作还是很多的,下面我给大家详细分析一下。

    我把这两部分放在一起是因为这两者是相互联系的,而且也比较好说明。可以这样来理解,PropertyPlaceholderConfigurer这个类就是读取jdbc. properties文件,并将它们设置到这个类的属性中。然后再将下面数据源配置中定义的这些${jdbc.driver}${jdbc.url}字符串换成属性文件中相同名称的值。${}这种写法,是类里面方法解析用的,网上都说这是叫占位符,我看了源代码的,其实是把它们当成字符串截取前后的特殊字符,再根据里面定义的名称找属性文件中对应的值。所以这个类只能读取properties格式的文件,你如果还有其它需要加入的属性文件,可以在list之间加入,写在value标签里面。     

    
根据base-package指定的路径,扫描其下所有包含注解的Bean,并自动注入。比如@Repository@Service这些都是注解,前者表示持久层,后者表示业务层。这可是非常非常好的一个功能,是从Spring2.5开始加入的一个非常棒的特性。有了它,我们将不用再去写那繁琐的<bean id="" class="" />。本文的主旨就是全注解,就是为了告诉大家不用写配置文件(当然不是绝对不写)来怎样进行开发工作。关于这部分的具体情况,在后面代码章节中会详细讲解。

    
这就是在Spring中定义Hibernate相关的配置,Spring已经集成了这部分功能。通过class里面定义的类名称我们很容易就能理解,这是使用注解的方式映射实体以及创建Hiberante SessionFactory${hibernate.dialect}${hibernate.show_sql}和上面的数据源配置获取方式一样,当applicationContext.xml定义好之后,就不用再对它进行修改,而是将修改对象变成了jdbc.properties文件。
    
另外在Spring2.5.6版中,加入了一个很有用的小功能,就是packagesToScan属性,它是根据value中定义的路径来扫描其下所有的注解实体类。大象对这个路径做了多种测试,另外又看了源代码,发现它只能匹配某一类型的路径,而不是所有路径。比如上面的value值表示,扫描entity包下面的所有包中的注解类,如果你将类直接放在entity包下,那么服务器启动和程序运行时都不会报错,但是当你的代码需要用到这个类的时候,就会出现异常,提示你找不到实体。     

    
这是事务定义,而且是使用注解方式定义事务@Transactional),proxy-target-class="true"表示采用动态代理类来管理事务,如果是false表示采用接口代理来管理事务(默认值为false)。什么意思呢?就是说对于需要加入事务处理的类,如果是实现接口,那么将采用Spring的默认事务管理(Spring默认方式为接口),如果不采用接口,而直接使用类,那么就需要cglib类库的支持,它通过动态的创建目标类(就是你需要加入事务的类)的子类,然后对这子类中的方法(当然是从目标类中继承来的)进行事务管理。这其实就是AOP切面,而且从中可以看出来,需要加入事务的方法不能为privatestaticfinal 的方法。这样说也不是很严格,说它不能加入事务,是说它不能主动的启动一个事务,如果某个private方法是被某个public方法调用的,而public方法是可以被动态代理加入事务的,所以这个private方法也一样被加入了事务,只是它处在public方法的事务之中。但是staticfinal这两类方法因为不能被子类覆盖,所以无法加入事务。如果这两类型的方法不被其它的事务方法所调用,那么它们就会以无事务的方式运行,因此很容易造成隐患,这一点请大家特别注意。

    
上面这个就是使用配置式来定义事务,两种方式的区别主要是,注解式只用写那么一句话,然后在业务类或方法中加入@Transactional这个注解标记,就完成事务声明,不过对于每个业务类都需要在类或方法中加入这些标记。而配置式声明,就是不用加这些标记,只要你的方法名称命名比较统一,就可以像上面这样定义事务规范,然后在aop标签中定义切入点与执行通知就行了。我感觉如果业务逻辑不是太复杂的情况,配置式会比较简单,而且修改起来也方便,这两种方式我都写出来了,至于用哪一种,由你们自己决定。
    
3web.xml
    
现在使用的Servlet容器还是2.4版,因此web.xml里面还是需要写配置文件的,到了3.0版就可以采取注解的方式来实现了。

    Spring ApplicationContext配置文件的路径,可使用通配符,applicationContext*.xml表示所有以applicationContext开头的xml文件。多个路径用,号分隔。比如可以这样写:

    
不过推荐采用通配符的写法,能够简单点,为什么还要弄那么复杂呢?
    
context-param是在容器启动后最先被执行的,并且被放入到容器上下文中。在这里引入spring的配置文件,是供SpringContextLoaderListener监听器使用。而这个监听器中会有一个ContextLoade类用来获取这个配置文件中的信息。从而进行Spring容器的初始化工作。因为是采用注解的方式来进行开发,所以spring的配置文件其实只有一个,上面那个星号可以去掉。

    这个监听器就是为了读取Spring的配置文件,这在上面已经讲到了。

    
这是Spring提供的一个用来防止内存泄漏的监听器。在我们使用struts2框架,或其它的某些类库时,因为它们自身的设计,会用到Introspector(内省)机制来获取Bean对象的信息。但不幸的是,这些框架或类库在分析完一个类之后却没有将它从内存中清除掉,内存中还保留有大量的静态资源,而这些东西又无法进行垃圾回收,因此产生了很严重的内存泄漏问题。直接表现为服务器的内存使用会随着时间而不断上升,最后的结果当然就是服务器当掉。所以在这里加入此监听器,能够帮助我们更好的处理内存资源回收的问题。

    
这是Spring的编码过滤器,我们可以直接拿来用,相信这段配置应该很好理解,不过请大家注意forceEncoding这个参数,把它设置为true表示不管请求中的编码是什么格式,都将强制采用encoding中设置的编码方式。另外对于响应也将按照encoding指定的编码进行设置。另外不建议将编码设置成gb2312或是gbk格式,请采用基于UnicodeUTF-8编码。

    这个过滤器是个好东西,有了它,我们在使用Hibernate延迟加载的时候,就不会再为因Session被关闭,导致延迟加载数据异常而头痛了。网上有很多人说这个不好,其实在使用中,效果还是不错的。

    
首先我要说这个过滤器的名字很雷,不知道写这类的家伙是不是个变态,或者喜欢恶搞。主要原因就是,这个过滤器的功能是推迟清理值栈中的值,以便在web中进行访问,另外就是为了配合SiteMesh装饰器进行工作(官方中的说明)。如果不加这个,那么Struts2的默认过滤器就会清空值栈中的值,这样就会导致异常。所以说这类的名字和功能完全不搭边,很容易让人产生误解。

    2.1.6版本里面,已经用这个过滤器取代了以前的FilterDispatcher,而且在api文档中已经标注为@deprecated(不赞成),并说明是从Struts 2.1.3版开始就弃用这个过滤器了,改用StrutsPrepareAndExecuteFilter,除此之外,还可以选择StrutsPrepareFilterStrutsExecuteFilter。不过大象建议大家还是选择StrutsPrepareAndExecuteFilter吧,这也是官方推荐的。
    web.xml
里面的几个重要的配置就这些,不过不要忘了,给这些filter加上filter-mapping映射。还有一点,请注意这些过滤器的顺序,这个顺序是很重要的,程序运行时,是根据这些filter-mapping的排列顺序依次执行过滤操作的。如果不想出现莫名其妙的错误,请控制好这些过滤器映射的顺序。
    
我会在最后一章附上源码,大家就这样慢慢看吧。看到最后一章的时候,可能这些相关的知识就比较清楚了。到时再对照源码练习下,应该会有一些收获。恩,这部分就到此结束了,我们下次继续。

Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解实例详解(三)

在前两章我为大家详细分析了JAR包的选择和必须的配置文件,那么这一章,我就对例子的层次结构进行说明,并实现除WEB层的功能代码。
    第三部分:建立框架代码
    工程结构
    
    大家可以看到,本例一共分为:daoentityserviceweb四层。另外在这些层次下,还以业务功能再进行分包,这样做是为了方便在以后的功能扩展中,能更好的管理和维护代码。如果将所有类都直接集中在这4个包下面,随着类的增加,会越来越难以维护,而且查找起来也很费劲。
    
HibernateDao
    
在本例中,我是通过继承Spring提供的HibernateDaoSupport来实现持久层的基类。同时引入泛型参数,封装了一些基本操作方法。
    
    这是HibernateDao的部分代码,引入的这个泛型参数,其实就是实体类(UserRole)。通过传递这个实体类,在构造方法中利用反射特性将它从JVM中取出来。
    
    这里的getClass()方法是获得继承HibernateDao的类(UserDaoRoleDao
    getGenericSuperclass()
方法就是通过这些继承了HibernateDao的类,来得到父类(父类就是HibernateDao)的泛型。注意这个方法的返回值为Type,这是一个类型接口。请参考API
    
因为在继承HibernateDao 的时候,会给它加一个泛型参数。比如,UserRole实体类。因此超类是参数化类型,所以返回的 Type对象包含所使用的实际类型参数。这里返回的Type对象是ParameterizedType接口的实现类ParameterizedTypeImpl,所以要将返回类型转型为ParameterizedType
    getActualTypeArguments()
方法是ParameterizedType接口中的,它的作用就是获得实际类型参数 Type对象的数组,因为我们这里只定义了一个泛型参数,数组里面也只有一个值,所以需要在数组下标处填0。然后最后一步转型千万别忘记了,因为这个方法返回的可是一个Type数组喔。
    
如果对于这部分的说明还有点不理解的话,请到时候有了代码,设个断点跟踪一下,就会全部清楚了。关于java反射,它已经超出本文的范围。大象只对本例中用到的部分进行讲解。
    
使用这种写法,是方便我们进行类型转换与类型检查。另外还可以简化某些方法的写法。比如:createCriteria(Criterion... criterions)这个方法。参数是Criterion类型的可变参数,它是用来设置查询条件。如果要进行对象化查询,那么最简单的写法就可以直接写成createCriteria()。另外还有重载的方法,可以根据传入class类型来创建自定义查询。
    
dao
    
持久层的Dao类是根据实体类定义,一般是一个实体类就会有一个对应的Dao类。当然这要跟业务需求来设计,不是绝对的。另外你也可以为了简便而去掉dao层,将持久化操作与业务逻辑全部写在service层。
    
    这些定义的方法是供service层调用,在业务层,将不会看到一行与持久层有关的代码,降低藕合性是这样做的目的。@Repository注解的作用就是标注这个UserDao是一个持久层组件。还记得前一章讲到的扫描器吗?component-scan 它就是用来将标有@Repository@Service这样的注解类扫描到Spring的容器里,并且同时对标有@Autowired注解的Bean启用了自动注入功能。这就是Spring2.5开始提供的新特性。我们使用注解的方法,就可以告别那繁琐的配置文件定义。
    entity
    关于实体的定义就是使用JPA注解,关于这部分,我以前写过一篇文章专门讲这个,如果有不清楚的朋友可以先参考一下。学习JPA——Hibernate Annotation使用实例
    本例中,我有两点要讲下。
    
第一、管理主键的表generator_table去掉原来单独定义的那个ID主键,把g_key设为主键,整个表将只有两个字段,g_keyg_value
    
第二、在User实体中,我将角色IDrole_id)与角色实体(Role)做了一个多对一关联。这一点是原来文章中没有讲过的。
    
    
请一定注意role_iduser表的字段。我在本例中设定的是一个角色可以对应多个人员,所以这个role_id存的就是roleid的值。fetch = FetchType.LAZY指定采用延迟检索,如果当你取得了User对象,但又不想取Role中的信息,这时,User对象中的role属性是代理状态。Role对象中的值都是空的。只有当你使用role.idrole.name进行取值的时候,hibernate才会去数据库中查找对应的记录,因此在一定程度上降低了资源消耗。不过这里有点要注意,采用延迟检索的时候,需要加上前一篇讲到的OpenSessionInViewFilter过滤器。否则会遇到session关闭的异常。
    
service
    
    
@Service表示这是业务层组件。在业务层需要对UserDao加上@Autowired注解,大象在这里将业务层的方法名与持久层的方法名定义为一样的,是我的一种习惯,大家可以按自己的想法来做。
    
测试
    
既然有了这么多代码,那就来测试一下吧,看看有没有问题。
    
    
好吧,为了照顾那些坚定的JUnit拥护者,再写一个JUnit测试。本例使用junit-4.4.jar
    
    
    
@BeforeClass注解的方法表示在类实例化之前,也就是在类的构造方法执行之前就会执行。而且使用这个注解的方法必须是static void
    
@Test标明这是测试方法,方法名不用像以前那样必须按照规则进行命名,可以自由定义。
    
上图显示大象使用JUnit方式测试也通过了(如果不会通过我写它干嘛?嘿嘿)。
    
假如我将张三改成张四,再来看看测试结果。
    
    
这个截图可以很明显的说明所有东西。
    
这一篇是给大家讲怎么用代码来实现除web层之外的全注解步骤。当然,我主要是讲思路,其实思路比代码重要得多。这一个系列的最后,我会放上所有源码供大家下载。现在这样慢慢分析,是想给大家讲道理。我们应该努力提升自己的境界与层次,而不要只把眼光放在代码上面。下一章将会着重介绍web层,以及struts2的注解插件struts2-convention。那么,我们下次继续。

Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解实例详解(四)

这一章,大象将详细分析web层代码,以及struts2的注解插件——struts2-convention的用法和其它相关知识。
    
第四部分:透析控制层
    
上一章对daoentityservice三层进行了详细的分析,并对代码进行了测试。测试结果表明这部分功能没问题,可以正常使用。本章将对最后一个web层进行详细说明,尽可能的讲明白这些知识要点。
    
数据库
    
本例使用MySQL数据库,只有三张表,一张用于管理表主键的generator_table,另外两张是人员表与角色表。
    
    
    
    
这里我有一点需要说明一下,在学习JPA——Hibernate Annotation使用实例一文中,我将generator_table设了一个id主键字段,其实这个字段是不需要的,直接将g_key设为主键。这样设计更好些,因为表名不可能一样,所以这个存放各个表主键的键名也不会一样。
    userrole这两张表只设了一个主键,没有建立外键关联,而且大象也很反对建立表之间的外键关联。因为这样做之后,约束太多,在实际开发中,很容易出问题,这是我亲身体会过的。所以我建议只对表设置一个流水号主键,其它的都可以根据业务关系来设计字段,这样会更灵活。
    
这里对各个字段都默认将它们设置为null,因为针对不同的表,你都会实现相应的功能,你当然会知道哪些字段是不能为空的,哪些是可以为空的。而且在做数据库设计的时候,你也不可能在短时间内,面面俱到的把所有问题都考虑进去,根据需求的变化,在开发过程中,也是经常会遇到修改数据库的情况。如果之前过于强调字段的非空设置,在编写代码时,为了减少出错,脑袋里可能会不停的想,啊,这个字段是非空的吗?哪个字段不是非空的吧?然后反复对比数据库进行检查,会使人束手束脚很不舒服。因为这些全部都可以人为来控制,所以除了主键外,将其它字段都设为null有利于开发人员更好的进行工作。
    
有人会说了,进行非空设置是一种约束,当程序出错时,很容易发现问题。当然,这话说得没错。大象只是建议,从没说过一定要这样做,我只是说下自己的一点经验总结,仅此而已!想怎么实现都是你的自由。
    
struts2-convention
    
既然说了是全注解开发,而且我们已经实现了HibernateSpring的注解。同样的,Struts2也能够做到用注解来代替配置文件,struts2-convention插件可以帮助我们完成这一功能。它是struts2提供的一个插件,目前网上相关的中文文档主要是一个叫石太洋的人根据官方文档翻译的,很多网站与博客都有转载。我看了原文与译文,感觉讲的不够清楚,例子也很简单。大象根据自己在项目中的实际使用情况,现将个人对这个插件的经验总结写出来与各位分享,希望与大家多交流,共同提高。
    
官方文档 https://cwiki.apache.org/WW/convention-plugin.html
    
请不要把地址中的两个大写W换成小写,否则是打不开页面滴!这个插件的使用其实非常简单,如果光看文档可能会觉得好像很麻烦。那么大象来告诉你怎样快速学习这个插件。
    
首先你要搞清楚,这个插件它会默认扫描所有包名为strutsstruts2actionactions下面的类。然后它会对实现了Action接口以及类名以Action结尾的这些类,作为Action来进行处理。
    
你可以重新定义按哪种包名进行扫描。比如本例设定,只扫描web包下面的所有类,因为我们将Action类都放在这个包下面。
    
那这个插件是怎么实现原来的配置信息的呢?它的映射规则是这样的,对于以Action结尾的的类,去掉Action,取剩下的部分,将所有的字母转换为小写,如果有驼峰式的写法,则用"-"连接符来连接不同的单词,这是此插件的默认方式。最终转换之后的就是请求地址,还是用例子说明。
    
com.bolo.examples.web.base.UserAction    
    
按照上面的规则,请求地址就应该是UserAction去掉Action后缀,将其余部分转换为小写,所以user就是我们的请求地址。不过,这还没有完,因为这里面还有一个命名空间的路径,在通常的配置文件中,一般会将不同的功能进行划分,在package标签里加上namespace属性。使用这个插件,它会为你自动配上命名空间,默认的就是前面说到的以那四种名称为根目录的命名空间,它们之后的都将成为命名空间的名称。
    
com.bolo.examples.struts.UserAction 映射为 /user.action
    com.bolo.examples.struts.base.UserAction
映射为 /base/user.action
    
要是我们不以struts或其它几种默认值为包名,又该怎么办呢?没关系,插件为我们提供了一种自定义根包的配置方式
    
<constant name="struts.convention.package.locators" value="web" />
    
上面这段配置是写在struts.xml里面的,它指定web为根,作用就相当于那四种默认值。
    
com.bolo.examples.web.base.UserAction映射为 /base/user.action
    com.bolo.examples.web.HelloAction
映射为 /hello.action
    com.bolo.examples.web.HelloWorldAction
映射为 /hello-world.action
    
请一定注意驼峰写法的映射方式,假如这里不是HelloWorld,而是Helloworld,那就不会再是hello-world.action,而是helloworld.action了。
    
既然已经知道了它的映射方式,接下来再看看这个插件是如何定义结果页面的。
    convention
默认会到/WEB-INF/content文件夹下面查找对应的结果页面,这个文件夹的名字可以修改,需要在struts.xml中定义
    
<constant name="struts.convention.result.path" value="/WEB-INF/jsp" />
    
文件夹的名字改成了jsp,这样定义后,convention就会在这个文件夹下面查找结果页面。它的查找路径与映射的命名空间有关。默认规则是,在请求的命名空间下面,根据请求名称再结合方法返回的字符串生成最终的结果页面名称,再配以后缀名。convention支持以jspftlvmhtmlhtm等五种后缀格式的文件。这里有个比较特殊的是如果方法返回success,那么可以不用将它与请求名称拼接起来,直接使用请求名称作为返回页面的名称。还是举例子说明。
    
    
比如上面这段代码,HelloAction处于我们定义的根包(web)下面,因此,它的action请求为hello.action。这时,会默认执行execute()方法,由于返回的是success字符串,所以页面的名称可以简写为hello.jsp,但是当执行welcome方法时,由于返回的字符串为welcome,这时的页面名称则为hello-welcome.jspconvention就是遵循这样的规则来进行命名,当然这只是最基本的,我们再来看看稍微复杂点的东东。
    
    
这个RoleAction类的外部,加了两种注解,它们的作用相当于配置文件中的result标签。Results是一个Result类型的数组注解,里面可以包含多个Result配置。使用Result注解来设置返回类型与返回页面,是不准备采取默认的定义方式。比如HelloAction就是我们采取的默认方式。另外对于有些特殊的返回类型,也需要显式的进行定义。
    
因为我对RoleAction中的execute()方法返回结果进行了显式的定义,所以,它将不再返回默认的role.jsp,而是location指定的role-list.jspResult注解中的name值要与返回值对应。
    
当请求路径为role!input.action时,会执行input()方法,对于这个方法来说,由于没有进行显式的定义,所以它会按照默认的命名规则返回role-input.jsp
    
redirectUser方法的返回结果指定了一个typeredirectAction的值,这表示要对Action重定向,在location中也说明了是跳转到哪个Action。请注意这里指定的是user.action,当程序跳转到UserAction时,会默认执行execute方法。
    
假如说,你想执行其它方法该怎么办呢?可以在location里面这样定义,location="user!input.action"。请记住,重定向时,如果是跳转到其它Action或本Action中的其它方法,type要写成redirectAction
    
更进一步,我还想带些参数过去,又该如何呢?请添加params属性,它是一个数组类型。可以这样定义,params={"role_id","${role_id}","role_name","超级管理员"}convention文档中有说明,里面的参数是一个键值对,总是形如key,value,key,value。所以第一个role_id与第三个role_name都叫参数名,二和四则是参数值。另外注意下"${role_id}"的含义,这是使用的OGNL表达式取出存在于值栈中的名叫role_id的值。这是一种动态获取并赋值的方式,在采用配置文件的方式中,也可以这样运用,而role_name参数则是一个固定字符串值。需要特别注意的就是,作为参数名的role_idrole_name,一定要在指向的Action中有这两个同名的属性,并且还有set方法,这是用来给这两个属性赋值。而对于${role_id},则要在当前这个Action中,有它的get方法。用于取值。
    
补充说明一下,在Action类中定义的全局变量,不是非得给它都加上setget方法,这是根据实际情况来设置的。简单的说get()是获得值,set()是设置值。比如,你现在要在页面上显示username,那么就对这个属性设置get方法,如果只是对username设置值,从页面传值到Action,那只需要对它设置set方法就可以了。除此之外,我们也可以不采用struts2提供的值栈方式得到参数值,而是使用非常熟悉的request.getParameter()方法来获取参数。至于实际怎么使用,由各位自己决定,不知道我这样说,大家能不能明白?
    
大象根据实际使用情况,发现动态参数的传递在struts2.1.6存在BUG,如果需要使用这个功能,请将struts2升级到2.1.8.1版。
    
大象根据实际应用,建议大家统一在类名上面定义Results设置,这样做有利于开发与维护;不建议单独对方法使用@Action注解来重新定义它的访问地址与返回结果,因为这样做有些破坏统一性,不过可以根据实际情况进行处理,但不要过多的使用。
    
struts.xml
    
    
整个struts.xml的配置文件就这么多,当然你自己还可以扩展,因为采用了注解,所以以前的那些配置就再也看不到了。在这个文件中,package是继承convention-default,而没有继承struts-default,为什么呢?查看conventionstruts-plugin.xml文件,我们可以发现convention-default继承了struts-default,所以这样写是没错的。另外的几个constant配置就是对convention的常量设置,请看注释。
    
关于paramsPrepareParamsStack拦截器栈,我准备在第五篇,对基础框架进行扩展的时候再详细的说明。大家如果等不急想学习下,可以在网上查找这方面的资料先看看。
    
web
    
大象是这样想的,如果一次讲的太多太复杂不利于理解和吸收,所以对于web层,大家从前面也看到了,代码很简单,因为本篇主要是讲convention插件的知识,然后实现一部分功能用于演示它的效果。下面贴上webWebRoot目录结构、UserAction的代码,以及jsp代码。
    
    
    
    
请注意web包下面的层次结构,这与你的请求路径相关。content文件夹是插件默认指定的名字,你可以修改为别的名字。同样请注意在这个目录下面的文件与子文件夹的定义方式是和web层相同的。如果还没有理解,请再看下我对convention插件的说明。
    
web.xml文件中,设置了一个<welcome-file-list>标签,定义了一个index.jsp,这文件里就一句代码<% response.sendRedirect("hello.action"); %> 它会去执行HelloActionexecute()方法,这方法里面什么逻辑都没有,直接返回结果页面hello.jsp
    
    ${ctx}
是一个EL表达式,设置的是当前项目名称。我在文件开头加了一个静态包含,<%@ include file="/common/taglibs.jsp" %>
    
    
不管是user.action还是role.action,它们默认的执行方法都是execute(),点击这两个链接,返回指定的结果页面。
    
    
    
user.jsp里面,用来循环的list,是根据getList()方法获取的,struts2会自动的分析出属性名。想一下,listget方法是不是就是getList()呢?我之前说过,get()是获得值,set()是设置值。在这里我只是要在列表页面上得到list集合,没有其它的需求,所以不用像这样定义 private List list,再然后给它加上set()get()方法,因为要得到list集合,所以还要在execute()方法里面写上list = userManager.getUsers(),这样做有必要么?我一直都在遵循优雅、高效、简洁的代码风格,并且一直都在朝这方面努力,也提倡大家这样做。编程是门艺术,而不是一种工作,不要把它当工作看,只想着完成任务,拼命的堆代码。这样做很难有提高。应该换一种心态去对待它,用艺术的眼光来重新审视你的代码,你会发现这很有乐趣,也会学到很多。自己的一点浅薄之见,让各位见笑了。
    
这部分的内容就说到这里,下一篇将对paramsPrepareParamsStack拦截器栈进行详细说明,另外再对框架进行一下扩展,封装CRUD功能,只要没有特殊的业务逻辑,在你的实际Action中,再不会看到增删改查这些基本功能。

Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解实例详解(五

这是本系列的最后一章,大象对示例进行适当的扩充并说明。

    其实到第四篇,对于示例的说明就已经全部讲完了,如果按照这样的例子,很难有什么值得学习的地方。大象本着写点有用东西的原则,在这章,对示例进行一下适当的扩充并说明。

    第五部分:扩展框架

    paramsPrepareParamsStack拦截器栈

    paramsPrepareParamsStack这个拦截器栈是在struts2-default.xml中定义的,里面包含了很多个拦截器,最重要的是这三个:paramspreparemodelDriven。我们只要记住这样几点。

    params:它负责将请求参数值设置到Action中与之同名的属性中。

    prepare:当Action实现了Preparable接口时,这个拦截器就会调用prepare()方法。如果你有想在execute()方法之前执行的逻辑处理,它就可以帮你完成这个功能。

    modelDriven:如果Action实现了ModelDriven接口,这个拦截器就会把getModel()方法中的返回结果压入值栈。这就意味着,可以在结果页面上直接使用model对象的属性。

    它的执行顺序是这样的

    首先,params拦截器会给action中的相关参数赋值,如idusernamepassword等等。

    然后,prepare拦截器执行prepare()方法,prepare()会根据参数,如id,去调用相关的方法,设置model对象。当然,实现的这个接口方法由你自己来定义,不局限只设置model之类的功能。

    接着,modelDriven拦截器会将model对象压入值栈,因为它是把getModel()方法中的返回结果放到值栈中,而这个方法的返回类型是个泛型参数,在实现ModelDriven接口的时候,可以给它指定一个具体的对象类型,因此返回类型也将是这个指定的对象类型,如ModelDriven<User>

    最后,params拦截器会将参数再赋值给model对象。

    思考修改与保存这两种动作。当点击人员修改时,请求为:user!input.action?id=1params拦截器会将id参数值设置到Action中的id属性,请一定注意,id属性要有set()方法,然后prepare拦截器开始在prepare()方法中,根据这个id值取得User对象,接着modelDriven会调用getModel()方法,此时,方法中返回的是user对象,所以会把user加入到值栈中,最后再执行一次params拦截器,但这时没有其它的参数值需要赋值给user对象,所以程序会接着往下走,这里假定没有其它的业务逻辑,执行返回,字符串为input,根据前面讲的插件知识,结果页面为user-input.jsp,那么就跳转到修改页面了,而且页面中表单域将显示数据库中的值。如果理解了修改,那么保存也就清楚了。

    prepare()方法虽然不错,但是也有弊端,那就是它会对Action中的每个方法都进行拦截,不管你是执行execute还是input,还是其它的自定义方法,它都会对其拦截,这当然不是我们所希望的。那有没有更好的方式?答案是肯定的,请接着往下看。

    prepareMethedName

    使用prepare拦截器的另一种形式,在prepare名称后面加上需要拦截的方法名。比如,你要拦截input方法,可以写成prepareInput,需要拦截save方法,就写上prepareSave。采取这样的方式后,将会在执行这些方法之前时,才对它们进行拦截。

    例如,请求role!input.action,会执行RoleAction中的input方法,如果我们设置了prepareInput方法,则会先进入此方法执行,执行完后再回到input方法往下执行。

    请注意,在使用这种方式时,Preparable接口定义的prepare()方法体内不要含有任何代码,就是说给这个方法一个空实现。这样,它就什么都不做,所有的拦截处理就全部交由相应的prepareMethedName来完成。

    

    

    它们在每个对应的方法之前执行。prepareEntity就是来初始化实体对象,然后由modelDriven拦截器将getModel()方法中的返回结果放入值栈,当返回页面时,就可以直接取值了。

    StrutsAction

    重新定义一个基类,里面封装大部分的通用操作,主要依靠泛型来实现,将hibernateDao注解进来,通过继承这个基类进行基本的CRUD操作。本文末尾提供示例源码下载,里面有详细的注释,这里我只贴出部分重要代码进行说明,为了行文需要,有些注释去掉了,但源码里面都有,请大家放心。

    

   通过扩展ActionSupport,使用泛型参数,构造函数根据反射得到T的具体类型。

    

    这就是默认的执行方法,基本的操作,在这个超类里面都进行了定义,每个方法里面设置的以do开头的方法,是方便让子类进行覆盖,当基本的业务逻辑无法满足我们的需求时,就可以在子类重写这些方法。

    

    方法有默认实现,主要是列表显示,保存和删除,新增和修改已经有getModel()方法取得实体,在页面上使用s标签就可以直接取值,除非有特殊的业务需求,否则不用覆盖doInputEntity()doViewEntity()方法。请注意,当需要实现自己的逻辑时,只需要覆盖上面定义的这些方法,而不用重写executeinput之类。

    我对HibernateDao又进行了适当的扩展与修改,提供了更多的基本封装方法,不过大家还可以继续添加。里面都有详细的注释,这里就不在赘述了。

    功能扩展

    我对例子做了两个功能,一个是角色表的增加、修改、删除、查看,另一个就是用户表的查询。可以从源码中看到,我在RoleAction中没有写一行关于增删改查的代码,因为它属于基本操作,超类中已经封装好了,所以这部分的代码都省了。对于用户表的查询,我覆盖了doListEntity()方法,在业务层进行条件封装,执行查询,返回结果。

    

    这个list就是在超类中定义的,因为默认实现中也用到了list,另外list有一个get方法,用于在页面中显示。如果不想采取方式取得list集合,就重写doListEntity()方法。这里说明下,我是没有加分页功能的,大家可以按自己的方式添加分页查询。

    在用户查询方法中,我使用的是QBC对象查询,因为这种方式很简洁,不过我在HibernateDao中也写了HQLSQL方式的查询方法,并进行了封装,可以很方便的调用。

    

    

    这个queryResult方法的定义,你可以改为传递用户名与角色ID的参数,大象在这里就是为了方便,直接使用Request请求。这里userDao调用的query方法是在HibernateDao里面封装的,因为继承了HibernateDao,就直接在Service层拿来用了。至于具体的,可以去看源码。

    页面部分没有进行大的调整,主要是将role-list.jsp重命名为role.jsp,因为使用的是超类的默认实现。添加了role-input.jsprole-view.jsp两个文件,并在user.jsp中,加入了查询条件。这些代码都很简单就不再贴了,而且前一篇也贴过一部分。

    对于这个例子的完整讲解说明就到此结束了。大象还想补充说明一下,这个例子只适用于学习,不适合商用,想在实际项目中运用,还需要对框架做大量的改造工作。本系列只是基于SSH2入门学习之用,源码中不含jar包,下图是本例中所需的最少jar文件,大家只要下载了springstrutshibernate三个完整压缩包,那么这些jar基本上都包含了。

    

 

 

 

Struts+Spring+Hibernate/SSH整合开发详细一

更系统地掌握Struts1.x/Struts2.x+Hibernate+Spring框架组合,请学习SpringSideAppfuse

终于,终于将SSH整合成功了,弄了几个小时,可以休息了(有的朋友弄了半个月,甚至重装了系统也没弄好,哎,庆幸啊)。碰到问题千万不要放弃,要相信没有解决不了的问题!

 

 

jsp部分为:index.jspsuccess.jspfail.jsp

UserDAO.javaUser.hbm.xml自动生成,HibernateSessionFactory是多余的。

相关的配置文件、类文件详细信息,请参看Struts+Spring+Hibernate/SSH整合开发详细二

以前是整过这三个框架的,可是工作期间都不曾用过,不知不觉之中,学的东西全忘了。这次又要开始找工作了,看着招聘启事上都写着:要求熟练掌握Struts,HibernateSpring框架……,没得办法,重新学呗。

Spring in Action中文版下载地址(ftp协议)   ftp://222.214.218.61/book5/20080228/cf8b35cc-5bcd-4973-b6b7-37ae3ec78391.rar 如果无法下载,可以在http://www.gougou.com/里重新搜索

首先开始搭建项目环境,步骤如下:

使用平台:MyEclipse 6.0

框架版本:Struts 1.2 Hibernate 3.0 Spring 2.0

1>     添加过程:

         <1> 新建项目。 Package Explorer视图下,右键单击 New -> Web Project
         <2>
添加Struts框架。菜单栏MyEclipse -> Capabilities,或者右键单击新建的项目名,选择快捷菜单中的"MyEclipse"下的二级菜单;设置相关的包名后,请写一个Struts的小例子,用来测试Struts是否可用;
         <3>
添加Spring。步骤同2所述,只不过这次是加入Spring Capabilities
          


          <4>
配置数据源。打开数据库视图,Windows -> Open Perspective -> MyEclipse DataBase Explorer。新建数据源,DB Browser下,右键单击 New ,配置信息类似下图,

           点击Next,选择"Display all schemas""Finish"完工。
          <5>
点击MyEclipse右上角 >>按钮,返回到MyEclipse Java Enterprise视图。按类似于2的步骤,添加Hibernate框架
                 
请选择Hibernate 3.0的版本

  

          <6>通过MyEclipse生成操作数据库的Dao类(UserDao),并自动生成映射表的配置文件(在本示例中包含在applicationContext.xml中),将视图切换到MyEclipse Database Explorer,打开我们在前面配置的数据链接,选择我们操作的表User,右键单击,选择:Hibernate Reverse Engineering。详细步骤如下图所示:

         <7> 至此,项目环境己经搭建完成。

         需要指出的是,为了使用日志管理的功能,你只须拷贝log4j.properties即可,将其放入到src根目录下。

          log4j.properties配置内容如下(对具体配置不太了解的朋友可以在这之后学一下,此处,你只须修改日志文件存储路径即可):

                
                    log4j.rootLogger=info,A1

                    log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender
                    log4j.appender.A1.Append=true

                    # log
文件存储路径,请转换为相应的目录
                    log4j.appender.A1.File=E:/workspace/ssh2/logs/log4j.log

                    log4j.appender.A1.DatePattern = '.'yyyy-MM-dd'.log'

                     log4j.appender.A1.layout=org.apache.log4j.PatternLayout
                    log4j.appender.A1.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss} Method:   %l%n%m%n

                      # 无详细类名、方法名信息
                     #log4j.appender.A1.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss} %m%n

                     # -X:X信息输出时左对齐;
                     # %p:
日志信息级别
                      # %d{}:
日志信息产生时间
                     # %c:
日志信息所在地(类名)
                     # %m:
产生的日志具体信息
                     # %n:
输出日志信息换行

SpringHibernate已经整合在一起了,现在需要将StrutsSpring进行整合。SpringStruts整合实现方式
第一,在struts-config.xml中加入

<!--配置一个插件去集成spring -->
<plug-in
       
className="org.springframework.web.struts.ContextLoaderPlugIn">
       
<set-property property="contextConfigLocation" value="/WEB-INF/applicationContext.xml"/>
</plug-in>

第二,修改struts-config.xmlaction节点的配置信息,将原来定义的
<action path="/validateUser"
    type="com.wuwei.struts.action.ValidateUserAction" name="userForm">
    <forward name="success" path="/success.jsp"></forward>
    <forward name="fail" path="/fail.jsp"></forward>
   </action>
中的type改为: type="org.springframework.web.struts.DelegatingActionProxy">或者你也可以不改,而在<plug-in>标签前加入如下定义

<!-- 加上controller就不用再配置actiontype属性了/或者说type属性不用改为
   type="org.springframework.web.struts.DelegatingActionProxy" -->
<controller
   processorClass="org.springframework.web.struts.DelegatingRequestProcessor">
</controller>

第三,在applicationContext.xml中为ValidateUserAction添加定义,将userDao的实例化加给Spring,这也是Spring中的依赖注入
<!-- Struts -->
<bean name="/validateUser"
   class="com.wuwei.struts.action.ValidateUserAction">
   <property name="userDAO" ref="userDao" />

</bean>

当然,这里ref(引用)Bean userDao也是在applicationContext.xml中己定义好的

<!-- 通过HibernateDaoSupport来操作数据库,需要植入sessionFactory
   UserDao
继承自HibernateDaoSupport
-->
<bean id="userDao" class="com.wuwei.struts.dao.UserDAO">
   <property name="sessionFactory" ref="sessionFactory" />
</bean>

相关的配置文件、类文件详细信息,请参看Struts+Spring+Hibernate/SSH整合开发详细二http://hi.baidu.com/wuweihi/blog/item/1434817fdea0af0d29388a1c.html


如此这般,现在可以运行你的程序了。为了测试三个框架是否整合成功,你可以在action里什么都不做,只是写一条输入语句即可。因为搭建SSH项目时,最容易碰到的错误就是:Servlet action is not available如果连action都访问不了,后面的就不需要谈了(文章B http://blog.csdn.net/shendiaodaxia/archive/2007/12/29/2002805.aspx应该可以解决此类问题)。测试不成功?依然是Servlet action is not available?这个时候log4j.log就起到关键作用了,你可以看下里面出现了什么异常。我在调试时碰到了:

1. java.lang.NoClassDefFoundError: org/objectweb/asm/CodeVisitor  

2. net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
Caused by: java.lang.reflect.InvocationTargetException
Caused by: java.lang.SecurityException: class "com.wuwei.struts.bean.User$$EnhancerByCGLIB$$1016ffbf"'s signer information does not match signer information of other classes in the same package

的错误。

1的解决办法是:

在使用SpringAOP编程时,会用到这几个lib
(版本不一样,文件名也不一样)
asm-2.2.3.jar
asm-commons-2.2.3.jar
asm-util-2.2.3.jar

Hibernate使用如下lib

asm.jar
asm-attrs.jar

其中asm-2.2.3.jarasm.jar存在类上的冲突!!!
使用其中之一或两者都使用,可能会出现如下错误:
java.lang.NoClassDefFoundError: org/objectweb/asm/CodeVisitor
java.lang.NoClassDefFoundError: org/objectweb/asm/commons/EmptyVisitor
java.lang.NoSuchMethodError: org.objectweb.asm.ClassVisitor.visit
。。。。。。
解决办法是:
1.
去掉类路径上的关于Hibernate3lib
asm.jar
asm-attrs.jar
cglib-2.1.3.jar
2.
加入Spring中的以下4lib
asm-2.2.3.jar
asm-commons-2.2.3.jar
asm-util-2.2.3.jar
cglib-nodep-2.1_3.jar

2的解决办法是:

….hbm.xml文件中,将class标签的lazy属性改为false(默认为true)<class name="com.wuwei.struts.bean.User" table="user" lazy="false">。如果有schema="dbo" catalog="test"配置信息,请将其删除

再次运行,打印语句成功 "I'm in a Action" 搭建成功!

 

 

补充:

我用的是SQLServer数据库,测试表是User表,调试时出现了
java.sql.SQLException:
在关键字 'user' 附近有语法错误。这是因为MS SQL中的User表是系统表,不能直接查询select username from user,需要将user用中括号包围[user],同样,为了保证hibernate能正常工作,需将user.hbm.xmlclass元素的table改为     table="[user]"

分享到:
评论
1 楼 52p9_xu 2013-08-09  
感谢分享,本人最近闲来无事也想搭建一个 全注解的SSH框架。不知楼主可否发一份完整的搭建后的框架。毕竟照本宣科有时候也会因为小的问题而功亏一篑。
万分感谢 328926934@qq.com

相关推荐

Global site tag (gtag.js) - Google Analytics