当前位置: 代码迷 >> Web前端 >> Struts 2.1.6+Spring 2.5.6+Hibernate 3.3.1 全诠注实例详解
  详细解决方案

Struts 2.1.6+Spring 2.5.6+Hibernate 3.3.1 全诠注实例详解

热度:533   发布时间:2012-09-19 13:43:54.0
Struts 2.1.6+Spring 2.5.6+Hibernate 3.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

第一部分:选择必须的jar包

新建一个web项目,然后将必要的jar包COPY到lib里面。

解压Struts2.1.6的lib文件夹,从中选出上面 7个jar包添加到我们的工程库中。commons-logging、freemarker、ognl、struts2-core、xwork这5个还是 struts2的核心包。但在Struts2.1.6这个版本中,还需要加上commons-fileupload包。如果没有,则启动就会报错,不过不需要像网上传言的那样还得加上commons-io的jar 包,这些大象都亲自做过测试。在本实例中,我将对struts2也采取注解的方式,所以用到了struts2-convention-plugin- 2.1.6.jar这个插件。因为要与spring整合,所以 struts2-spring-plugin-2.1.6.jar也必不可少。

大象在这里偷个懒,直接将spring的完整jar包加了进来,如果各位想精简类库的话,就选取它的分类jar包吧。比如本例使用struts2作为MVC框架,所以spring的webmvc就不可能用到了。有想改的朋友请自己动手改下。另外有点我想说下,如果采取完整spring的jar包,还需要Spring2.5.6\lib \concurrent文件夹中的backport-util-concurrent.jar,如果不加这个,spring会报错。但是采取spring 分类jar包的形式,这个可以不用加,至于具体使用什么需要依赖这个包,大象还没去测试过,这个有待验证。还有lib\slf4j下的日志包,目前很多都开始采用基于slf4j接口的日志器,它的好处就是日志器是根据slf4j的接口来进行实现,可以在不改变代码的情况下更换日志器。最后Spring的源代码中使用的是commons-logging记录日志,因此这个包不能少,不过因为struts2也用到了,所以这里就省了。

Hibernate从3.3版开始,对jar包结构做了一次大的调整,我们只需要加入lib\required文件夹下面的6个jar包。请注意这6个jar包都是使用Hibernate 所必须的。另外再加上hibernate核心包。这里我将slf4j-api-1.5.2.jar换成了1.5.0,这是因为slf4j是一个通用日志接口,不提供任何实现,我在demo里面使用的是log4j,而hibernate包里面没有log4j的slf4j实现。而且如果版本不一致,会有异常,因此我就采用Spring2.5.6\lib\slf4j里面提供的配套版本。另外我将commons-collections-3.1.jar换成了 Struts2.1.6里面的3.2版。

例子中使用Hibernate JPA来完成实体对象映射,所以上面这些包都必不可少。使用注解的方式,可以不用写繁琐的配置文件,降低了出错机率。而且现在很多人都喜欢这种方式。大家可以去sourceforge下载。

本例使用DBCP连接池来管理数据源。


这个包的作用是创建动态代理对象。比如在使用AOP方式管理spring事务时,如果我们的目标对象没有实现接口,而又要使用AOP来处理事务,这时就需要用到这个jar包。可以在Spring2.5.6\lib\cglib里面找到。

JSTL标签库,很经典的东东,如果需要可以将它们加入lib中。

大象在这里建议大家做开发的时候,不要过多的依赖MyEclipse提供的那些功能,多用手动的方式来做。那样方便是方便了,但不利于学习。比如加入上面这些开发所用的类库,这样可以更清楚的了解每个jar包的作用,增加知识的积累,方便以后调试

   
实例中涉及的配置文件有这么几个
    applicationContext.xml
    jdbc.properties
    log4j.properties
    struts.xml
    web.xml
我准备在本章中只讲applicationContext.xml、jdbc.properties和web.xml。log4j的配置大同小异而且也不在本文范围。至于struts.xml我准备留到后面与Action代码一起来讲,因为用的是struts2-convention-plugin插件来实现struts2的注解,所以这两个结合起来讲要好一些。
本例采用MySQL数据库,所以我设置了一个属性文件,用来存放一些连接信息和Hibernate相关的设置。

  因为我们使用的是Hibernate来与数据库进行交互,把这些东西写在单独的文件里,是方便修改,如果你想换成SQL Server或是Oracle,只需要更改driver、url以及dialect,而且还可以自由控制sql语句的显示的开关,非常方便。至于写在这里怎么用呢?请接着看下面的applicationContext.xml说明。
2、applicationContext.xml
    这个文件就是spring的主配置文件了,当然,本例也只有这么一个spring的配置文件,内容不多,但做的工作还是很多的,下面我给大家详细分析一下。
<!-- JDBC参数配置 -->
	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" lazy-init="true">
		<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
		<property name="ignoreResourceNotFound" value="true" />
		<property name="locations">
		<list>
				<value>classpath:/jdbc.properties</value>
			</list>
		</property>
	</bean>

	<!-- 数据源配置 -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<!-- 设置JDBC驱动名称 -->
		<property name="driverClassName" value="${jdbc.driver}" />
		<!-- 设置JDBC连接URL -->
		<property name="url" value="${jdbc.url}" />
		<!-- 设置数据库用户名 -->
		<property name="username" value="${jdbc.username}" />
		<!-- 设置数据库密码 -->
		<property name="password" value="${jdbc.password}" />
		<!-- 设置连接池初始值 -->
		<property name="initialSize" value="5" />
		<!-- 设置连接池最大值 -->
		<property name="maxActive" value="100" />
		<!-- 设置连接池最小空闲值 -->
		<property name="minIdle" value="20" />
		<!-- 设置连接池最大空闲值 -->
		<property name="maxIdle" value="50" />
	</bean>

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

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

	<!-- 定义Hibernate的SessionFactory -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<!-- 依赖注入数据源,注入正是上面定义的dataSource -->
		<property name="dataSource" ref="dataSource"/>
		<!-- mappingResouces属性用来列出全部映射文件 -->
		<property name="mappingResources">
			<list>
				<!-- 以下用来列出Hibernate映射文件 -->
				<value>org/leegang/hrsystem/model/Application.hbm.xml</value>
				<value>org/leegang/hrsystem/model/Attend.hbm.xml</value>
			</list>
		</property>
		<!-- 定义Hibernate的SessionFactory的属性 -->
		<property name="hibernateProperties">
			<props>
				<!-- 指定数据库方言 -->
				<prop key="hibernate.dialect">
					org.hibernate.dialect.MySQLInnoDBDialect</prop>
				<!-- 是否根据需要每次自动创建数据库 -->
				<prop key="hibernate.hbm2ddl.auto">update</prop>
				<!-- 显示Hibernate持久化操作所生成的SQL -->
				<prop key="hibernate.show_sql">true</prop>
				<!-- 将SQL脚本进行格式化后再输出 -->
				<prop key="hibernate.format_sql">true</prop>
			</props>
		</property>
	</bean>

或者注释形式
	<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<!-- 配置Hibernate拦截器,自动填充数据的插入、更新时间 -->
		<property name="entityInterceptor" ref="entityInterceptor" />
		<property name="dataSource" ref="dataSource" />
		<property name="hibernateProperties">
			<value>
				<!-- 设置数据库方言 -->
				hibernate.dialect=${hibernate.dialect}
				<!-- 设置自动创建|更新|验证数据库表结构 -->
				hibernate.hbm2ddl.auto=update
				<!-- 输出SQL语句到控制台 -->
				hibernate.show_sql=${hibernate.show_sql}
				<!-- 格式化输出到控制台的SQL语句 -->
				hibernate.format_sql=${hibernate.format_sql}
				<!-- 是否开启二级缓存 -->
				hibernate.cache.use_second_level_cache=false
				<!-- 配置二级缓存产品 -->
				hibernate.cache.provider_class=org.hibernate.cache.OSCacheProvider
				<!-- 是否开启查询缓存 -->
				hibernate.cache.use_query_cache=false
				<!-- 数据库批量查询数 -->
				hibernate.jdbc.fetch_size=50
				<!-- 数据库批量更新数 -->
				hibernate.jdbc.batch_size=30
			</value>
		</property>

		<property name="annotatedClasses">
			<list>
				<value>net.entity.Area</value>
				<value>net.entity.LogConfig</value>
				<value>net.entity.Log</value>
			</list>
		</property>
	</bean>

这就是在Spring中定义Hibernate相关的配置,Spring已经集成了这部分功能。通过class里面定义的类名称我们很容易就能理解,这是使用注解的方式映射实体以及创建Hiberante SessionFactory。${hibernate.dialect}、${hibernate.show_sql}和上面的数据源配置获取方式一样,当applicationContext.xml定义好之后,就不用再对它进行修改,而是将修改对象变成了jdbc.properties文件。

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

上面这个就是使用配置式来定义事务,两种方式的区别主要是,注解式只用写那么一句话,然后在业务类或方法中加入@Transactional这个注解标记,就完成事务声明,不过对于每个业务类都需要在类或方法中加入这些标记。而配置式声明,就是不用加这些标记,只要你的方法名称命名比较统一,就可以像上面这样定义事务规范,然后在aop标签中定义切入点与执行通知就行了。我感觉如果业务逻辑不是太复杂的情况,配置式会比较简单,而且修改起来也方便

   关于数据库
两张表只设了一个主键,没有建立外键关联,而且大象也很反对建立表之间的外键关联。因为这样做之后,约束太多,在实际开发中,很容易出问题,这是我亲身体会过的。所以我建议只对表设置一个流水号主键,其它的都可以根据业务关系来设计字段,这样会更灵活。

    这里对各个字段都默认将它们设置为null,因为针对不同的表,你都会实现相应的功能,你当然会知道哪些字段是不能为空的,哪些是可以为空的。而且在做数据库设计的时候,你也不可能在短时间内,面面俱到的把所有问题都考虑进去,根据需求的变化,在开发过程中,也是经常会遇到修改数据库的情况。如果之前过于强调字段的非空设置,在编写代码时,为了减少出错,脑袋里可能会不停的想,啊,这个字段是非空的吗?哪个字段不是非空的吧?然后反复对比数据库进行检查,会使人束手束脚很不舒服。因为这些全部都可以人为来控制,所以除了主键外,将其它字段都设为null有利于开发人员更好的进行工作。

    struts2-convention 在shop++中使用的就是这种方式

    既然说了是全注解开发,而且我们已经实现了Hibernate与Spring的注解。同样的,Struts2也能够做到用注解来代替配置文件,struts2-convention插件可以帮助我们完成这一功能。它是struts2提供的一个插件,目前网上相关的中文文档主要是一个叫石太洋的人根据官方文档翻译的,很多网站与博客都有转载。我看了原文与译文,感觉讲的不够清楚,例子也很简单。大象根据自己在项目中的实际使用情况,现将个人对这个插件的经验总结写出来与各位分享,希望与大家多交流,共同提高。

    官方文档 https://cwiki.apache.org/WW/convention-plugin.html

    请不要把地址中的两个大写W换成小写,否则是打不开页面滴!这个插件的使用其实非常简单,如果光看文档可能会觉得好像很麻烦。那么大象来告诉你怎样快速学习这个插件。

    首先你要搞清楚,这个插件它会默认扫描所有包名为struts、struts2、action、actions下面的类。然后它会对实现了Action接口以及类名以Action结尾的这些类,作为 Action来进行处理。
    你可以重新定义按哪种包名进行扫描。比如本例设定,只扫描web包下面的所有类,因为我们将Action类都放在这个包下面。

    那这个插件是怎么实现原来的配置信息的呢?它的映射规则是这样的,对于以Action结尾的的类,去掉Action,取剩下的部分,将所有的字母转换为小写,如果有驼峰式的写法,则用"-"连接符来连接不同的单词,这是此插件的默认方式。最终转换之后的就是请求地址,还是用例子说明。通过
<constant name="struts.convention.action.name.separator" value="_" />设置
驼峰式设计

    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支持以jsp、ftl、vm、 html、htm等五种后缀格式的文件。这里有个比较特殊的是如果方法返回success,那么可以不用将它与请求名称拼接起来,直接使用请求名称作为返回页面的名称。


方法拦截器 methodinterceptor对注解的方式也有效,在package中 admin,namespace下  定义拦截器则会对该包下的零配置生效。并且默认的
<action name="index" >
<result type="redirectAction">admin!firstLookup.action</result>
</action>
index也被拦截。

<!-- 定义默认访问页 -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.action</welcome-file>  虚拟action
</welcome-file-list>
  相关解决方案