JavaWeb入门_模仿天猫整站Tmall_SSH实践项目
Tmall_SSH
技术栈 Struts2 + Hibernate + Spring + Jsp + Tomcat , 是 Java Web 入门非常好的练手项目
效果展示:
项目简介
关联项目
github - 天猫 JavaEE 项目
github - 天猫 SSH 项目
github - 天猫 SSM 项目
之前使用 JavaEE 整套技术来作为解决方案,实现模仿天猫网站的各种业务场景,现在开始使用框架技术,毕竟工作中还是要用框架。
本项目技术相对老旧,现在很少用 Struts2 了,但如果接手老项目的话还是要懂的,学习过程我们也可以认识到它们当时优秀的设计理念,
当时解决了哪些痛点,后面又是因为什么被新技术替代,这样才能加深对 Java Web 整个平台的理解,不亏。
项目用到的技术如下:
Java:Java SE基础
前端:HTML
,CSS
,JavaScript
,AJAX
,JQuery
,Bootstrap
J2EE:Tomcat
,Servlet
,JSP
,Filter
框架:Hibernate
,Struts
,Spring
,SSH整合
数据库:MySQL
表结构
建表sql 已经放在 Github 项目的 /sql 文件夹下
表名 | 中文含义 | 介绍 |
---|---|---|
Category | 分类表 | 存放分类信息,如女装,平板电视,沙发等 |
Property | 属性表 | 存放属性信息,如颜色,重量,品牌,厂商,型号等 |
Product | 产品表 | 存放产品信息,如LED40EC平板电视机,海尔EC6005热水器 |
PropertyValue | 属性值表 | 存放属性值信息,如重量是900g,颜色是粉红色 |
ProductImage | 产品图片表 | 存放产品图片信息,如产品页显示的5个图片 |
Review | 评论表 | 存放评论信息,如买回来的蜡烛很好用,么么哒 |
User | 用户表 | 存放用户信息,如斩手狗,千手小粉红 |
Order | 订单表 | 存放订单信息,包括邮寄地址,电话号码等信息 |
OrderItem | 订单项表 | 存放订单项信息,包括购买产品种类,数量等 |
一 | 多 |
---|---|
Category-分类 | Product-产品 |
Category-分类 | Property-属性 |
Property-属性 | PropertyValue-属性值 |
Product-产品 | PropertyValue-属性值 |
Product-产品 | ProductImage-产品图片 |
Product-产品 | Review-评价 |
User-用户 | Order-订单 |
Product-产品 | OrderItem-订单项 |
User-用户 | OrderItem-订单项 |
Order-订单 | OrderItem-订单项 |
User-用户 | User-评价 |
以上直接看可能暂时无法完全理解,结合后面具体到项目的业务流程就明白了。
开发流程
首先使用经典的 SSH 模式进行由浅入深地开发出第一个分类管理模块 ,
然后分析这种方式的弊端,对其进行项目重构,重构这一块可以学习到不少 Java 里的中高级处理手法,
使得框架更加紧凑,后续开发更加便利和高效率。
实体类设计
准备 Category 实体类,并用 Hibernate 注解标示其对应的表,字段等信息。
举个例子,对于 分类 / category
的 实体类 和 表结构 设计如下:
DAOImpl 类设计
DAO 是 Data Access Object 的缩写,专门用于进行数据库访问的操作。
DAOImpl 继承了 HibernateTemplate,这是一个 Hibernate 框架提供的模板类,提供了各种各样的 CRUD方法,满足各种数据库操作的需要。
重写 HibernateTemplate 的 setSessionFactory() 方法, 以用于注入 SessionFactory ,
SessionFactory 是在 spring 的配置文件里面定义的 bean ,可以看到其配置了连接数据库的数据源等信息,这样 dao 操作的时候,就不必获取对应的数据库连接进行操作,
Spring 将数据源 ds 对象注入 SessionFactory ,sf 又被注入到 HibernateTemplate ,dao 继承 HibernateTemplate 就可以直接操作数据库了,非常简便。
对比不使用 Spring+Hibernate 的情况,我们需要利用数据管理类 DBUtil 获取 Connectoion ,
并在 dao 里面获取对应的 Statement 分别实现 CURD 等方法,利用 JDBC 从数据库取出数据,再构造成 bean 对象返回。
Service 类
设计 CategoryService 接口,用于提供业务方法 list() ,即查询所有的分类。
public interface CategoryService{
public List list();
}
CategoryServiceImpl 实现了 CategoryService 接口,提供list()方法的具体实现,同时自动装配(注入) 了 DAOImpl 的实例 dao ,
在 list() 方法中,通过 dao 获取所有的分类对象。
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
DAOImpl dao;
@Override
public List list() {
DetachedCriteria dc = DetachedCriteria.forClass(Category.class); // 获取DetachedCriteria对象
dc.addOrder(Order.desc("id"));
return dao.findByCriteria(dc); //这是 HibernateTemplate 提供的方法
}
}
Action 类
CategoryAction 类作为 MVC 设计模式中的控制层起作用。
- 使用 basicstruts ,对应配置文件 struts.xml 中定义的 basicstruts 保持一致
- 在 Result 注解中,定义了返回的页面为 /admin/listCategory.jsp
- 自动装配(注入)categoryService 对象,用于从数据库获取所有分类对象的集合。
- 把对 admin_category_list 路径的访问映射到 list 方法上
- list() 方法通过 categoryService 获取到所有的分类对象,放在 categories 属性中。
- 同时提供了 getCategories() 方法,用于向listCategory.jsp页面传递数据
配置文件
web.xml
这个web.xml做了3件事情
1.让所有请求都进入 Struts2 的过滤器 StrutsPrepareAndExecuteFilter
2.对所有请求进行 UTF-8 编码
3.指定Spring配置文件 applicationContext.xml 的位置
<web-app>
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
struts.xml
<struts>
<constant name="struts.i18n.encoding" value="UTF-8"></constant>
<constant name="struts.objectFactory" value="spring"/>
<package name="basicstruts" extends="struts-default">
</package>
</struts>
applicationContext.xml
applicationContext 除了配置上述的 SessionFactory ,还要配置事务管理器
<!-- 配置事务管理器(声明式的事务) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sf"></property>
</bean>
访问 jsp 显示数据
Action 携带数据跳转到 jsp ,作为视图,担当的角色是显示数据,借助 JSTL 的 c:forEach 标签遍历从CategoryAction 的 list() 的传递过来的集合。
完整版的 listCategory.jsp 还包含4个公共文件,分别是 头部,导航,行业,页脚。
分类管理还有增加,编辑,修改,删除,分页,另外后台其他管理页面,前台页面。具体的需要浏览代码,篇幅原因就不展开了。
思路流程图
重构(这里非常精彩)
分类管理的 CURD 功能全部做好之后,代码层面的问题开始逐渐浮现出来了,问题主要表现在 Service 层,和 Action 层。
Service层的问题
Service 层代码如下:
首先看接口:CategoryService。 其声明的方法基本上就是 CURD 和分页。可以预见的是,在后续做产品管理,用户管理,订单管理等等,
也会有这么一个非常近似的 CURD 的接口,换句话说,这里是有做抽象和代码重构的机会和价值的。
然后看实现类:CategoryServiceImpl。 CategoryServiceImpl本身其实就是个架子,真正起作用的是为其注入的DAO对象,
所以这个地方也是可以引入委派模式,使得代码调用更加顺畅。
Service 层的重构
Service层 的重构行为主要包括两种角度
- 对CURD进行抽象
- 委派模式的重构
由于可以预见的在后续做产品管理,用户管理,订单管理等等,也会有这么一个非常近似的CURD的接口,
那么我们就做一个BaseService,里面就提供这些CRUD和分页查询的方法。
接着设计 BaseServiceImpl 类,其 CURD 关键方法都是调用的被注入的 dao 完成的,这样就十分适合使用委派模式来重构这块代码。
不使用委派模式访问数据库都需要通过dao.XXX()来进行。 而委派重构之后,数据库相关方法,不再需要通过dao,直接调用即可,代码看上去更简洁。
委派类 ServiceDelegateDAO
设计一个新的类,叫做 ServiceDelegateDAO ,在其中注入 dao ,然后让对 dao 的每一个方法进行委派。
那么到底什么是委派呢? 如 ServiceDelegateDAO 类所示:
public void delete(Object entity) throws DataAccessException {
dao.delete(entity);
}
当调用 ServiceDelegateDAO 对象的 delete(Object entity) 的时候,其实就是委派给的 dao 的 delete(Object entity) 方法。
但是从调用者的角度来看,调用者只知道 ServiceDelegateDAO 这个类的 delete(Object entity) 方法,而意识不到 dao 的存在。
而 dao 继承了 HibernateTemplate ,一共有一百多个方法,哈哈这么麻烦肯定有工具可以一键生成,
果然利用 idea 立马就生成了所有委派方法,即快速又不会出错,突然感觉好开心。
BaseServiceImpl
BaseServiceImpl 的构造器非常骚气,BaseServiceImpl 的 clazz 对象需要引用实体类对象,这样 CURD 方法中的 DetachedCriteria dc = DetachedCriteria.forClass(clazz);
才能获取到对应的查询对象。
所以这里利用反射,在构造方法中,借助异常处理和反射得到 Category.class 或者 Product.class 。 即要做到哪个类继承了 BaseServiceImpl ,clazz 就对应哪个类对应的实体类对象。
首先要获取是哪个类继承了 BaseServiceImpl ,因为实例化子类,父类的构造方法一定会被调用,
所以在父类 BaseServiceImpl 里故意抛出一个异常,然后手动捕捉住它,
在其对应的 StackTrace 里的第二个(下标是1) 栈跟踪元素 StackTraceElement ,即对应子类。
这样我们就拿到了子类名称 CategoryServiceImpl 或者 ProductServiceImpl,具体的在代码注释里了,写的非常清楚。
这样 CategoryService 就不需要自己声明方法了,只需要继承接口 BaseService 即可
CategoryServiceImpl 也不需要自己提供实现了,继承 BaseServiceImpl 并实现接口 CategoryService 即可
这么做的好处主要在于:后续新功能开发的过程中,当需要新增加新的Service类的话,比如 PropertyService,无需从头开发,
只需要继承 BaseServiceImpl 并实现 PropertyService,那么其所需要CRUD一套方法都有了。
- 开发成本显著降低
- 更加不容易出错(因为方法都被抽象在父类中了,并且被前面的业务验证过了,要出错早就被纠正过了)
Action的问题
先看代码
这样的CategoryAction代码完成功能是没有问题的,但是问题恰恰在于,这样一个本来是用于充当控制层(Controller)的类,需要集中应付太多的需求:
- 返回页面的定义
- 单个对象的getter setter
- 集合对象的getter setter
- 分页对象的getter setter
- 上传文件对象的getter setter
- Service层对象的注入
- 作为控制层进行的访问路径映射
把所有的这些代码,都放在一个类里面,这个类就会显得繁杂,不易阅读,不易维护。 所以这个地方也是很有代码重构价值的。
Action 层的重构
目前 CategoryAction 存在的问题是一个类需要做太多的事情,显的繁杂,影响阅读和维护。 那么重构思路就是把不同的事情,放在专门的类进行处理,各司其职。
- 上传专用
Action4Upload
这个类就专门用于处理图片上传,其他的事情一概不管 - 分页专用
Action4Pagination
专门用于处理分页,并且继承上传专用 Action4Upload - 对象和集合
Action4Pojo
用于提供实体对象以及实体对象集合的setter和getter.
setter用于接收注入
getter用于提供数据到JSP(VIEW)上 - 注入服务专用
Action4Service
Action4Service提供服务的注入
Action4Service 另外提供了一个方法 t2p() ,专门用于把对象指向对应的持久对象。
方法调用的最后结果就导致父类 Action4Pojo 中声明的 pojo 本身是指向瞬时对象的,现在指向了持久对象(从数据库中取出的对象)。
再定义返回页面的 Action4Result
继承 Action4Service ,专门进行返回页面的定义
这样
CategoryAction 继承Action4Result, 于是就间接地继承了 Action4Service,Action4Pojo,Action4Pagination,Action4Upload,
于是就通过继承提供了各种相关的功能,CategoryAction 本身只需要专注于扮演控制器(Controller)本身就行了。这些工作对后面其他 Action 同样适用,大大简化了后续开发。
此外,后续还有针对 Service 的关系查询重构,和Service 多条件查询重构,具体的由于篇幅原因,请移步github 项目的地址
页面展示
本篇博客所讲不足整个项目的 1/10 ,有兴趣的朋友请移步 github 项目的地址 。
参考
天猫SSH整站学习教程 里面除了本项目,还有 Java 基础,前端,Tomcat 及其他中间件等教程, 可以注册一个账户,能保存学习记录。
JavaWeb入门_模仿天猫整站Tmall_SSH实践项目的更多相关文章
- JavaWeb入门_模仿天猫整站Tmall_JavaEE实践项目
Tmall_JavaEE 技术栈 Servlet + Jsp + Tomcat , 是Java Web入门非常好的练手项目 效果展示: 模仿天猫前台 模仿天猫后台 项目简介 关联项目 github - ...
- JavaWeb入门_模仿天猫整站Tmall_SSM实践项目
Tmall_SSM 技术栈 Spring MVC+ Mybatis + Spring + Jsp + Tomcat , 是 Java Web 入门非常好的练手项目 效果展示: 模仿天猫前台 模仿天猫后 ...
- scrapy进阶(CrawlSpider爬虫__爬取整站小说)
# -*- coding: utf-8 -*- import scrapy,re from scrapy.linkextractors import LinkExtractor from scrapy ...
- 前端到后台ThinkPHP开发整站--php开发案例
前端到后台ThinkPHP开发整站--php开发案例 总结 还是需要做几个案例,一天一个为佳,那样才能做得快. 从需求分析着手,任务体系要构建好,这样才能非常高效. 转自: 前端到后台ThinkPHP ...
- 网站seo整站优化有什么优势
http://www.wocaoseo.com/thread-314-1-1.html 现在很多企业找网络公司做网站优化,已经不再像以前那样做目标关键词,而是通过整站优化来达到企业营销目的 ...
- DEDE整站动态化或整站静态化设置方法,织梦栏目批量静态/动态方法
跟版网建站接到一个朋友提问,100多各栏目全部要从动态变成静态,里面的文章也要静态化,如何更快捷的设置dede的静态化或者动态化呢? 直接用DEDE后台的SQL命令行工具, SQL语句: DEDE整站 ...
- [参考]wget下载整站
wget -m -e robots=off -U "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.6) Gecko/200 ...
- java开发_模仿百度文库_OpenOffice2PDF_注意事项
在模仿百度文库的操作过程中,有很多朋友反映出来的一些问题,是我想起了写这篇blog. 主要是让大家在做的过程中注意一些东西,否则达不到想要的效果. 第一步:我们先从 java开发_模仿百度文库_Ope ...
- 09_android入门_采用android-async-http开源项目的GET方式或POST方式实现登陆案例
根据08_android入门_android-async-http开源项目介绍及使用方法的介绍,我们通过最常见的登陆案例进行介绍android-async-http开源项目中有关类的使用.希望对你学习 ...
随机推荐
- android module 模块共用远程包
在项目有多模块,需要使用到同一个第三方包时,引入报错,个人解决方法如下 1. 在模块build.gradle 文件中配置maven远程地址 可从app下的build.gradle文件里复制 allpr ...
- WPF下字体模糊的问题
原文:WPF下字体模糊的问题 一直以来,发现WPF中的小字体下的文字变得比较模糊,比如: WPF与Winform字体显示比较: 为了看到更清楚,我们放大点显示: 放得更大些: 中文.日文等亚洲文字的 ...
- 得知OpenCV研究报告指出系列(一)VS2010+OpenCV2.4.9环境配置
学习OpenCV,首先,当然,要知道如何配置的环境. 余系统的软件和硬件环境,如以下: 以本人的配置环境为例,配置过程例如以下. 第一步 下载及解压OpenCV源代码 尽管非常多第三方站点及一些学习论 ...
- Linux性能测试 vmstat命令
vmstat命令是最常见的Linux/Unix监控工具,可以展现给定时间间隔的服务器的状态值,包括服务器的CPU使用率,内存使用,虚拟内存交换情况,IO读写情况.这个命令是我查看Linux/Unix最 ...
- Matlab随笔之判别分析
原文:Matlab随笔之判别分析 从概率论角度,判别分析是根据所给样本数据,对所给的未分类数据进行分类. 如下表,已知有t个样本数据,每个数据关于n个量化特征有一个值,又已知该样本数据的分类,据此,求 ...
- SQL_DML简单的操作
***********************************************声明*************************************************** ...
- Oracle 已有则更新,没有则插入
使用merge merge into 表名 t1 using (select '数据数据' 字段1,'数据数据' 字段2 from dual) t2 on (t1.字段1 = t2.字段1) when ...
- C# ToString() 数据格式
double[] numbers= {1054.32179, -195489100.8377, 1.0437E21, -1.0573e-05}; string[] specifiers = { &qu ...
- Android开发四大件
四大组件 Activity Activity是Android应用程序的界面,比如查看联系人.打电话.玩游戏的界面等一个应用程序通常包含多个Activity,即多个界面Activity通过布局管理各种V ...
- python 运行出现flask运行时提示出错了或者报服务器出错,ValueError: View function did not return a response
python manage.py runserver -d