启明之笔
若隐若现乃相思,雪落无痕心神驰.亦喜亦忧怅寥廓,寒来暑往渐成诗。
主页博客相册|个人档案 |好友
查看文章
Springside之开发bookstore心得
2007-07-17 20:30
bookstore简介:
BookStore是典型的B2C网上商店。因为BookStore主要为了演示各项企业应用技术,所以业务上的实现并不全面。
在书店前台,用户浏览图书,查询图书,用Ajax效果将图书加入购物车,点击结账后进行订单信息的录入,用规则引擎计算价格,点击保存正式生成订单,系统用JMS异步发送Email 通知客户订单的内容。
在书店后台,主要进行图书的增删改管理,与订单的查阅、出货管理,通过Acegi管理权限。并有后台进程定时查询低库存的图书,用Email通知管理员。
另外书店还提供了查询图书的Web Service 与一个演示用的简单JSP。
bookstore所用到的构件或技术有:
书店前台
使用Prorotype.js的传统Ajax模式放入购物车;
便用Compass全文搜索图书;
便用JBoxxRules计算订单价格;
使用DWR反向Ajax向管理员后台推送订单通知;
便用JMS异步发送邮件向管理员通知客户订单的内容;
采用Freemarker模板生成HTML格式的通知信件。
管理后台
Acegi管理权限;
对Product-Book继承关系,Order-OrderItem-Product经典三角关系的Hibernate annotation式配置;
ExtremeTable表格控件的应用;
Scroptaculous的Effectibve效果,显示操作成功与失败的信息;
出版日期的日历控件——Jscalendar;
图书封面的AjaxUpload效果显示上传进度条;
订单发货时业务日志异步批量写入数据库——Log4j;
使用Quartz,在工作时间内每0分钟检查一次低库存图书,如果存在则发信通知管理员;
后台图书管理,订单管理界面的集成测试。
数据库和工具:
数据库方面:我改用MySql来替代bookstore所使用的hsqldb;
工具方面:Eclipse和插件就不多说了,后面我会讲到Hibernate Tools的用法;
重现步骤:
1.通过New Project Wizard建立一个SpringSide的Project,取Project名为“bookstoredemo”,根据上面讲到的各种构件和技术,我们在为bookstoredemo选择构件时,选中Acegi,ExtremeTable,Struts,DWR,ActiveMQ,Mail, Hibernate,Quartz,Compass
2.为Project配置Web属性(主要由MyEclipse配置成1个Web站点,方便以后步署),可以省略。
3.为bookstoredemo配置MySQL数据库连接
3.1JDBC支持:拷贝mysql-connector-java-5.0.6-bin.jar或更高版本到bookstoredemo/webapp/WEB-INF/lib中,这个文件如果没有,可以去找一个。
3.2配置/bookstoredemo/src/resources/config/jdbc.properties,增加或改成下面4行:
## MySQL
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/bookstore?useUnicode=true&characterEncoding=utf8
jdbc.username=spring
jdbc.password=spring
其中的bookstore是我自己建的一个database。
4.更改/boonstoredemo/src/resources/spring/applicationContext-quartz.xml内容为:
<?xml version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE beans PUBLIC “-//SPRING//DTD BEAN 2.0//EN” “http://www.springframework.org/dtd/spring-beans-2.0.dtd”>
<beans>
<!–
<bean name=”quartzScheduler”
class=”org.springframework.scheduling.quartz.SchedulerFactoryBean”>
<property name=”triggers”>
<list>
<ref bean=”cronTrigger”/>
</list>
</property>
<property name=”configLocation” value=”classpath:config/quartz.properties”/>
</bean>
–>
<!– 指定执行的pojo, method 和 cron表达式 –>
<!–
<bean id=”cronTrigger” class=”org.springframework.scheduling.quartz.CronTriggerBean”>
<property name=”jobDetail”>
<bean class=”org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean”>
<property name=”targetObject” ref=”@todo@”/>
<property name=”targetMethod” value=”@todo@”/>
</bean>
</property>
<property name=”cronExpression” value=”@todo@”/>
</bean>
–>
</beans>
因为我们现在还没有开始配置quartz,如果不注释这个文件,将会在Tomcat服务器运行时出现如下错误
[java] org.springframework.beans.factory.BeanCreationException: Error creat
ing bean with name ‘quartzScheduler’ defined in file [D:/springside2/misc/server
s/tomcat-5.5.17/webapps/bookstoredemo/WEB-INF/classes/spring/applicationContext-
quartz.xml]: Cannot resolve reference to bean ‘cronTrigger’ while setting bean p
roperty ‘triggers’ with key [0]; nested exception is org.springframework.beans.f
actory.BeanCreationException: Error creating bean with name ‘cronTrigger’ define
d in file [D:/springside2/misc/servers/tomcat-5.5.17/webapps/bookstoredemo/WEB-I
NF/classes/spring/applicationContext-quartz.xml]: Cannot create inner bean ‘org.
springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean#154864a’ wh
ile setting bean property ‘jobDetail’; nested exception is org.springframework.b
eans.factory.BeanCreationException: Error creating bean with name ‘org.springfra
mework.scheduling.quartz.MethodInvokingJobDetailFactoryBean#154864a’ defined in
file [D:/springside2/misc/servers/tomcat-5.5.17/webapps/bookstoredemo/WEB-INF/cl
asses/spring/applicationContext-quartz.xml]: Cannot resolve reference to bean ‘@
todo@’ while setting bean property ‘targetObject’; nested exception is org.sprin
gframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘@todo@’ i
s defined
这个错误属于严重错误,直接导致程序无法运行!注意要保留<beans>节点,否则也会出错。
5.QuickStart.bat,试看看程序配置是否正确。
6.便用Hibernate Tools生成Hibernate Annotation,顺便在这里讲下Hibernate Tools基本使用方法。
6.1Hibernate Tools的插件安装:从网上下载Hibernate Tools,我下载的是HibernateTools-3.2.0.beta9a.zip,解压缩。选中Eclipse菜单项Help-> Software Updates->Find and Install…;在接下来的界面里选择下面的单选钮(直接从已知站点更新),Next;在接下来的界面里,查看Hibernate Tools是否出现在左边的列表中,如果有且被选中,则说明已经安装过Hibernate Tools了,如果没有,在右连点击“New Local Site”按钮,选择刚才解压的目录,如果目录正确,系统会显示找到插件的信息,,根据提示完成插件安装。
6.2选择项目右键,New->other->Hibernate->Hibernate Configuration File (cfg.xml),在配置页面设定文件为默认的Hibernate.cfg.xml,Next,设定Database Dialect为MySQL;设定Driver class为com.mysql.jdbc.Driver;设定URL为jdbc:mysql://localhost:3306/bookstore设定Username为spring;设定Password为spring。复选Create a console configuration(也可以不选,在下一步新建这个Console文件),Next,Next,Finish。在 Hibernate.cfg.xml编辑页面配置Database Connection Details部分,注意这里右边是已经配置好的,不用改动,但是左边也一定要配置,这部分是为了以后生成Pojo和hbm.xml映射文件准备的。
6.3选择项目右键,New->other->Hibernate->Hibernate Reverse Engineering File(reveng.xml),选择目录为MySQL-Hibernate或其子目录,Next,选择Console configuration为MySQL-Hibernate,Refresh,如果正确,左边的TreeView中会列出bookstore数据库里所有的表。
6.5继续上一步操作,你想为哪几个表建立Pojo,就选中这些表逐个Include到右边。在这里我选择了5张表:Category/Customer/Order_item/Orders/Product,然后Finish。
6.6选择菜单项Run->Hibernate Code Genenration..->Hibernate Code Genenration…..。在Main面版上左边的TreeView中右键->New,在右边Console configuration设置MySQL-Hibernate;Output Directory设为/bookstoredemo/src/java;Package设置为 org.springside.bookstoredemo.model;reveng.xml设置为我们上面建立的/MySQL-Hibernate/ hibernate.reveng.xml;在Exporters面版上复选Use Java 5 Syntax;在下面的ListView复选Domain code(.java)、Hibernate XML Mappings(.hbm.xml)、DAO Code(.java)、Hibernate XML Configuration(.cfg.xml)。完成这些设置之后,我们会发现在bookstoredemo项目下的 bookstoredemo/src/java/下多出来了一些目录和文件,这就是刚才Hibernate Tools产生出来的文件,删掉其中的Home文件。
6.7基本上Hibernate Tools的工作到这里就算是结束了。我所建的Project MySQL-Hibernate可以作为MySQL数据库的模板,有需要可以建一些欺也的工程模板,比如SQL Server、Oracle、DB2等等。
7.把bookstoredemo/src/java/org/springside/bookstoredemo/Hibernate.cfg.xml 移到bookstoredemo/src/resources/config目录下,在bookstoredemo/src/resources/ config目录下建立mapping子目录,把上面生成的hbm.xml移过来。修改Hibernate.cfg.xml文件,保证Mapping文件,Pojo文件一一对应。
注意点:
1注解和映射文件的真正关系,是用注解代替映射文件,要么在实体Bean定义好映射,要么在映射文件里定义好映射。
1.程序里到处都是@…….之类的语句,这是注解,不是注释,在程序中是属于有效语句,千万不要以为是注释就可以偷懒不去写!
1.表Product中的Category_id与Category表是多对一的关系,因此表Category中的Category_id一定要在Product表中存在,否则会导致程序直接出错。
2.Book与Product的继承关系非常简单,主要的语句是
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = “type”, discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue(“product”)
…(其他注解)
public class Product {
.
.
.
}
其中:
@Inheritance(stratery = InheritanceType.SINGLE_TABLE)表示整个继承层次结构内的父类和子类的所有属性都映射到同一张表中,它们的实例通过一个辨别符(Discriminator)列来区分。
@DiscriminatorColumn(name = “type”, discriminatorType = DiscriminatorType.STRING)表示继承关系父实体列type来区分,而type列是字符串型的。 @DiscriminatorValue(“product”)表时用来辨别这个实体的Type列的值是product。
type列不是指的数据表中的Column,而是在实体Bean定义的一个属性。
@Entity
@DiscriminatorValue(“book”)
@…(欺也注解)
public class Book extends Product {
.
.
.
}
其中:
@DiscriminatorValue(“book”)表示用来辨别这个实体的Type列的值是book,也就是说Product表中字段type值为book的所有行够成Book这个实体。
@Undeletable标识对象不能被删除,只能被设为无效的Annoation;(没有关系)
4.实体many-to-one的关系代码如下:
在Product.java文件里:
@ManyToOne
@JoinColumn(name = “CATEGORY_ID”)
public Category getCategory() {
return this.category;
}
public void setCategory(Category category) {
this.category = category;
}
注意,这里定义了Category的实例作为Product的1个属性;
在Category.java里:
@OneToMany(mappedBy=’category”)
public Set getProducts() {
return this.products;
}
public void setProducts(Set products) {
this.products = products;
}
注意,这里定义的products和Product实体是有关系的,注意大小定和多出来的s。具体的数据操作代码可参见程序。
5.增加了图书分类查询,代码可参见/WEB-INF/pages/shop/index.jsp、 org.springside.bookstoredemo.web.ShopAction、 org.springside.bookstoredemo.service.sysmgr.BookManager。
6.HQL语言(Hibernate Query Laguage),类似于SQL的语言,只不过这种语言所用到的“表”、“字段”是实体里面的实体名、属性列,而不是数据库里的字段名,我叫它伪SQL代码。详细可以参考www.hibernate.org/hib_docs/reference/zh-cn/html/queryhql.html。
二 后台管理
1.Acegi认证与授权
1.1在Web.xml中添加
<!–acegi 的filter链代理–>
<filter>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
1.2bookstore对每个Component都有单独的配置。这里也参照它的结构,把Acegi的配置文件放在 bookstoredemo/src/java/org/springside/bookstoredemo/components/acegi目录下,取名applicationContext-acegi-security.xml,并在 bookstoredemo/src/resources/spring目录下新建applicationContext-components.xml 文件,内容如下:
<?xml version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE beans PUBLIC “-//SPRING//DTD BEAN 2.0//EN” “http://www.springframework.org/dtd/spring-beans-2.0.dtd”>
<beans>
<!– Acegi –>
<import resource=”classpath:org/springside/bookstoredemo/components/acegi/applicationContext-acegi-security.xml”/>
</beans>
1.3在applicationContext-acegi-security.xml中添加刚才在Web.xml文件中声明的org.acegisecurity.util.FilterChainPrxy:
<bean id=”filterChainProxy”
class=”org.acegisecurity.util.FilterChainProxy”>
<property name=”filterInvocationDefinitionSource”>
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>
这个bean它主要是装载filterInvocationDefinitionSource指定的filter类,并顺序调用它们的doFilter方法,进行安全服务处理。这个bean总共声明了8个filter。
1.4顺序添加httpSessionContextIntegrationFilter:
<bean id=”httpSessionContextIntegrationFilter”
class=”org.acegisecurity.context.HttpSessionContextIntegrationFilter”/>
这个bean负责每次请求从HttpSession中获取Authentication对象,然后把Authentication存于一个新的 ContextHolder对象(其实质上只是一个ThreadLocal对象)中,则让该次请求过程中的任何Filter都可以通过 ContextHolder来共享Authentication,而不需要从HttpSession中取,减少传HttpRequest参数的麻烦.在请求完后把Authentication对象保存到HttpSession中供下次请求使用,最后把刚才生成的ContextHolder对象销毁.这样就达到了让Authentication对象跨越多个请求的目的.注意此filter须在调用其他Acegi filter前使用
1.5顺序添加logoutFilter:
<!– 注销处理filter –>
<bean id=”logoutFilter”
class=”org.acegisecurity.ui.logout.LogoutFilter”>
<constructor-arg value=”/shop/index.do”/>
<!– URL redirected to after logout –>
<constructor-arg>
<list>
<ref bean=”rememberMeServices”/>
<bean
class=”org.acegisecurity.ui.logout.SecurityContextLogoutHandler”/>
</list>
</constructor-arg>
</bean>
这个bean负责处理退出登录后所需要的清理工作。它会把session销毁,把ContextHolder清空, 把rememberMeServices从cookies中清除掉,然后重定向到指定的退出登陆页面。
1.6顺序添加authenticationProcessingFilter
<!– 表单认证处理filter –>
<bean id=”authenticationProcessingFilter”
class=”org.acegisecurity.ui.webapp.AuthenticationProcessingFilter”>
<property name=”authenticationManager”
ref=”authenticationManager”/>
<property name=”authenticationFailureUrl”
value=”/acegilogin.jsp?login_error=1″/>
<property name=”defaultTargetUrl” value=”/admin/index.jsp”/>
<property name=”filterProcessesUrl”
value=”/j_acegi_security_check”/>
<property name=”rememberMeServices” ref=”rememberMeServices”/>
</bean>
这个bean处理一个认证表单,登陆用的表单必须提交用户名和密码这两个参数给这个filter.由用户名和密码构造一个 UsernamePasswordAuthenticationToken,将传给AuthenticationManager的 authenticate方法进行认证处理。该filter默认处理filterProcessesUrl属性指定的URL,认证失败会转到 authenticationFailureUrl,认证成功会转到defaultTargetUrl页面。
1.7顺序添加securityContextHolderAwareRequestFilter
<bean id=”securityContextHolderAwareRequestFilter”
class=”org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter”/>
这个bean保存当前的请求到SavedRequest,并存入Session,然后转到登录页。
1.8顺序添加rememberMeProcessingFilter
<bean id=”rememberMeProcessingFilter”
class=”org.acegisecurity.ui.rememberme.RememberMeProcessingFilter”>
<property name=”authenticationManager”
ref=”authenticationManager”/>
<property name=”rememberMeServices” ref=”rememberMeServices”/>
</bean>
Springside之开发bookstore心得二
?2008 Baidu