SSH框架网上商城项目第2战之基本增删查改、Service和Action的抽取
上一节《ssh框架网上商城项目第1战之整合struts2、hibernate4.3和spring4.2》我们搭建好了struts2、hibernate和spring的开发环境,并成功将它们整合在一起。这节主要完成一些基本的增删改查以及service、dao和action的抽取。
1. service层的抽取
上一节中,我们在service层简单写了save和update方法,这里我们开始完善该部分的代码,然后对service层的代码进行抽取。
1.1 完善categoryservice层
对数据库的操作无非是增删改查,首先我们来完善categoryservice层的接口和实现:
//categoryservice接口 public interface categoryservice extends baseservice<category> { public void save(category category); //插入 public void update(category category);//更新 public void delete(int id); //删除 public category get(int id); //获取一个category public list<category> query(); //获取全部category }
对categoryservice接口的具体实现:
public class categoryserviceimpl extends baseserviceimpl<category> implements categoryservice { private sessionfactory sessionfactory; //spring会注进来 public void setsessionfactory(sessionfactory sessionfactory) { this.sessionfactory = sessionfactory; } protected session getsession() { //从当前线程获取session,如果没有则创建一个新的session return sessionfactory.getcurrentsession(); } @override public void save(category category) { getsession().save(category); } @override public void update(category category) { getsession().update(category); } @override public void delete(int id) { /*第一种方法有个弊端,就是没删除一次得先查询一次 object obj = getsession().get(category.class, id); if(obj != null) { getsession().delete(obj); }*/ string hql = "delete category while id=:id"; getsession().createquery(hql) // .setinteger("id", id) // .executeupdate(); } @override public category get(int id) { return (category) getsession().get(category.class, id); } @override public list<category> query() { string hql = "from category"; return getsession().createquery(hql).list(); } }
1.2 service层抽取实现
完成了categoryservice后,我们来抽取service层的基础实现。思路是这样的:我们抽取一个基础接口baseservice以及基础接口的实现baseserviceimpl,后面开发的时候,如果需要新的service,只需要做两步即可:首先定义一个新的接口xxxservice继承baseservice接口,这个接口可以增加新的抽象方法;然后定义一个新的实现类xxxserviceimpl继承baseserviceimpl并实现xxxservice接口即可。这样更加便于项目的维护。
我们先根据上面的categoryservice接口来创建baseservice接口:
//基础接口baseservice,使用泛型 public interface baseservice<t> { public void save(t t); public void update(t t); public void delete(int id); public t get(int id); public list<t> query(); }
然后再根据categoryserviceimpl实现类创建baseservice接口的实现类baseserviceimpl:
/** * @description todo(公共模块的抽取) * @author eson_15 * */ @suppresswarnings("unchecked") public class baseserviceimpl<t> implements baseservice<t> { private class clazz; //clazz中存储了当前操作的类型,即泛型t private sessionfactory sessionfactory; public baseserviceimpl() { //下面三个打印信息可以去掉,这里是给自己看的 system.out.println("this代表的是当前调用构造方法的对象" + this); system.out.println("获取当前this对象的父类信息" + this.getclass().getsuperclass()); system.out.println("获取当前this对象的父类信息(包括泛型信息)" + this.getclass().getgenericsuperclass()); //拿到泛型的参数类型 parameterizedtype type = (parameterizedtype) this.getclass().getgenericsuperclass(); clazz = (class)type.getactualtypearguments()[0]; } public void setsessionfactory(sessionfactory sessionfactory) { this.sessionfactory = sessionfactory; } protected session getsession() { //从当前线程获取session,如果没有则创建一个新的session return sessionfactory.getcurrentsession(); } @override public void save(t t) { getsession().save(t); } @override public void update(t t) { getsession().update(t); } @override public void delete(int id) { system.out.println(clazz.getsimplename()); string hql = "delete " + clazz.getsimplename() + " as c where c.id=:id"; getsession().createquery(hql) // .setinteger("id", id) // .executeupdate(); } @override public t get(int id) { return (t) getsession().get(clazz, id); } @override public list<t> query() { string hql = "from " + clazz.getsimplename(); return getsession().createquery(hql).list(); } }
抽取完了后,我们就可以改写categoryservice接口和categoryserviceimpl实现类了。如下:
//categoryservice接口继承baseservice接口 public interface categoryservice extends baseservice<category> { /* * 只要添加categoryservice本身需要的新的方法即可,公共方法已经在baseservice中了 */ } /** * @description todo(模块自身的业务逻辑) * @author eson_15 * */ public class categoryserviceimpl extends baseserviceimpl<category> implements categoryservice { /* * 只需实现categoryservice接口中新增的方法即可,公共方法已经在baseserviceimpl中实现了 */ }
从代码中可以看出,新增的service只需要继承baseservice接口,然后在接口中新增本service所需要的业务逻辑即可。新增的serviceimpl只需要继承baseserviceimpl并实现新增的业务逻辑即可。
但是别忘了很重要的一点:就是修改spring的配置文件beans.xml中的bean。
<!-- 泛型类是不能实例化的,所以要加lazy-init属性 --> <bean id="baseservice" class="cn.it.shop.service.impl.baseserviceimpl" lazy-init="true"> <property name="sessionfactory" ref="sessionfactory" /> </bean> <bean id="categoryservice" class="cn.it.shop.service.impl.categoryserviceimpl" parent="baseservice"/>
将原来categoryservice中的property干掉,然后增加parent属性,指明继承baseservice;然后配置一下baseservice,将sessionfactory配到baseservice中去,另外要注意一点:设置lazy-init属性为true,因为baseservice是泛型类,泛型类是不能实例化的。至此,service层的抽取就搞定了。
2. service层添加一个account
刚刚抽取好了service层,那么现在我们想写一个account(管理员)的service就很简单了:
首先写一个accountservice接口继承baseservice:
public interface accountservice extends baseservice<account> { //注意baseservice里的泛型现在是account /* * 只要添加accountservice本身需要的新的方法即可,公共方法已经在baseservice中了 */ }
然后写一个accountserviceimpl实现类继承baseserviceimpl实现类,并实现accountservice接口即可:
public class accountserviceimpl extends baseserviceimpl<account> implements accountservice { /* * 只需实现accountservice接口中新增的方法即可,公共方法已经在baseserviceimpl中实现了 */ //管理登陆功能,后期再完善 }
最后在beans.xml文件里加上如下配置:
<bean id="accountservice" class="cn.it.shop.service.impl.accountserviceimpl" parent="baseservice" />
这样就写好了一个新的service了,以后需要添加service就遵循这个流程,非常方便。
3. action的抽取
3.1 action中往域(request,session,application等)中存数据
我们知道,在action中可以直接通过actioncontext.getcontext()去获取一个actioncontext对象,然后通过该对象再去获得相应的域对象;也可以通过实现xxxaware接口来注入相应的域对象。我们先来看一下这两种方法:
public class categoryaction extends actionsupport implements requestaware,sessionaware,applicationaware{ private category category; private categoryservice categoryservice; public void setcategoryservice(categoryservice categoryservice) { this.categoryservice = categoryservice; } public string update() { system.out.println("----update----"); categoryservice.update(category); return "index"; } public string save() { system.out.println("----save----"); return "index"; } public string query() { //解决方案一,采用相应的map取代原来的内置对象,这样与jsp没有依赖,但是代码量比较大 // actioncontext.getcontext().put("categorylist", categoryservice.query()); //放到request域中 // actioncontext.getcontext().getsession().put("categorylist", categoryservice.query()); //放到session域中 // actioncontext.getcontext().getapplication().put("categorylist", categoryservice.query()); //放到application域中 //解决方案二,实现相应的接口(requestaware,sessionaware,applicationaware),让相应的map注入 request.put("categorylist", categoryservice.query()); session.put("categorylist", categoryservice.query()); application.put("categorylist", categoryservice.query()); return "index"; } public category getcategory() { return category; } public void setcategory(category category) { this.category = category; } private map<string, object> request; private map<string, object> session; private map<string, object> application; @override public void setapplication(map<string, object> application) { this.application = application; } @override public void setsession(map<string, object> session) { this.session = session; } @override public void setrequest(map<string, object> request) { this.request = request; } }
还是上一节整合三大框架时的categoryaction类,我们在里面加了一个query方法,在该方法中,我们通过向request域、session域和application域中存入查询的结果。第一种方法是直接使用actioncontext来实现,不需要实现任何接口,但是代码量较大;第二种方法通过实现requestaware、sessionaware和applicationaware接口,实现该接口的三个抽象方法把request、session和application注入进来,然后赋给相应的成员变量中,这样就可以在query方法中向域中存放查询结果了。这代码量貌似比第一种方法更大……但是我们可以抽取,先往下看。
我们在index.jsp中新加一个查询连接来测试能否将查询结果显示出来:
<%@ page language="java" import="java.util.*" pageencoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!doctype html public "-//w3c//dtd html 4.01 transitional//en"> <html> <head> <title>my jsp 'index.jsp' starting page</title> </head> <body> <a href="${pagecontext.request.contextpath }/category_update.action?category.id=2&category.type=gga&category.hot=false">访问update</a> <a href="category_save.action">访问save</a> <a href="category_query.action">查询所有类别</a><br/> <c:foreach items="${requestscope.categorylist }" var="category"> ${category.id } | ${category.type } | ${category.hot } <br/> </c:foreach> <c:foreach items="${sessionscope.categorylist }" var="category"> ${category.id } | ${category.type } | ${category.hot } <br/> </c:foreach> <c:foreach items="${applicationscope.categorylist }" var="category"> ${category.id } | ${category.type } | ${category.hot } <br/> </c:foreach> </body> </html>
3.2 抽取baseaction
刚刚提到了,第二种方法的代码量更大,但是我们可以抽取一个baseaction,专门处理这些域相关的操作。
public class baseaction extends actionsupport implements requestaware,sessionaware,applicationaware { protected map<string, object> request; protected map<string, object> session; protected map<string, object> application; @override public void setapplication(map<string, object> application) { this.application = application; } @override public void setsession(map<string, object> session) { this.session = session; } @override public void setrequest(map<string, object> request) { this.request = request; } }
然后我们自己的action如果需要用到这些域对象来存储数据时,直接继承baseaction即可,就能直接使用request、session和application对象了。所以修改后的categoryaction如下:
public class categoryaction extends baseaction { private category category; <pre name="code" class="java"> private categoryservice categoryservice; public void setcategoryservice(categoryservice categoryservice) { this.categoryservice = categoryservice; } public string update() {system.out.println("----update----");categoryservice.update(category); return "index"; }public string save() {system.out.println("----save----");return "index"; } public string query() {request.put("categorylist", categoryservice.query()); session.put("categorylist", categoryservice.query()); application.put("categorylist", categoryservice.query()); return "index"; } public category getcategory() { return category; } public void setcategory(category category) {this.category = category; }}
后面所有要使用request、session和application域的action,只要直接继承baseaction即可,非常方便。
3.3 获取参数(modeldriven)
我们继续看上面的categoryaction类,里面有个成员变量category,这是个pojo,定义这个变量并写好set和get方法是为了jsp页面可以通过url后面附带参数传进来,参数是category对象中的属性,比如id,type等,但是url中的参数必须写成category.id、category.type等。这样struts会自动将这写参数注入到category对象中,然后我们就可以直接使用这个category对象了,但是这样有点繁琐。我们可以使用modeldriven来更方便的解决。
public class categoryaction extends baseaction implements modeldriven<category>{ private category category; //使用modeldriven接口必须要实现getmodel()方法,此方法会把返回的项压到栈顶 @override public category getmodel() { category = new category(); return category; } <pre name="code" class="java"> private categoryservice categoryservice; public void setcategoryservice(categoryservice categoryservice) { this.categoryservice = categoryservice; } public string update() { system.out.println("----update----"); categoryservice.update(category); return "index"; } public string save() { system.out.println("----save----"); return "index"; } public string query() { request.put("categorylist", categoryservice.query()); session.put("categorylist", categoryservice.query()); application.put("categorylist", categoryservice.query()); return "index"; } }
这样我们在前台jsp页面就不用带category.id这种繁琐的参数了,看jsp页面中的modeldriven部分:
<%@ page language="java" import="java.util.*" pageencoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!doctype html public "-//w3c//dtd html 4.01 transitional//en"> <html> <head> <title>my jsp 'index.jsp' starting page</title> </head> <body> <a href="${pagecontext.request.contextpath }/category_update.action?category.id=2&category.type=gga&category.hot=false">访问update</a> <a href="category_save.action?id=1&type=haha&hot=true">测试modeldriven</a> <a href="category_query.action">查询所有类别</a><br/> <c:foreach items="${requestscope.categorylist }" var="category"> ${category.id } | ${category.type } | ${category.hot } <br/> </c:foreach> <c:foreach items="${sessionscope.categorylist }" var="category"> ${category.id } | ${category.type } | ${category.hot } <br/> </c:foreach> <c:foreach items="${applicationscope.categorylist }" var="category"> ${category.id } | ${category.type } | ${category.hot } <br/> </c:foreach> </body> </html>
测试结果是可以获得catgory,并且将id,type和hot属性全部赋值好。我们可以看出,通过实现modeldriven接口,我们可以很方便的在url中携带参数,action中只需要实现getmodel方法,new一个要使用的对象返回即可。到这里我们很容易想到,struts中肯定会有很多这种model需要获取,所以这一块我们也要抽取到baseaction中去。
3.4 抽取modeldriven到baseaction
首先我们在baseaction中添加modeldriven部分的代码,如下:
//因为有很多不同的model都需要使用modeldriven,所以这里使用泛型 public class baseaction<t> extends actionsupport implements requestaware,sessionaware,applicationaware,modeldriven<t> { protected map<string, object> request; protected map<string, object> session; protected map<string, object> application; protected t model; @override public void setapplication(map<string, object> application) { this.application = application; } @override public void setsession(map<string, object> session) { this.session = session; } @override public void setrequest(map<string, object> request) { this.request = request; } @override public t getmodel() { //这里通过解析传进来的t来new一个对应的instance parameterizedtype type = (parameterizedtype)this.getclass().getgenericsuperclass(); class clazz = (class)type.getactualtypearguments()[0]; try { model = (t)clazz.newinstance(); } catch (exception e) { throw new runtimeexception(e); } return model; } }
抽取完了后,categoryaction中的代码会越来越少:
//继承baseaction,并且加上泛型 public class categoryaction extends baseaction<category> { private categoryservice categoryservice; public void setcategoryservice(categoryservice categoryservice) { this.categoryservice = categoryservice; } public string update() { system.out.println("----update----"); categoryservice.update(model);//直接使用model return "index"; } public string save() { system.out.println("----save----"); system.out.println(model); //直接使用model return "index"; } public string query() { request.put("categorylist", categoryservice.query()); session.put("categorylist", categoryservice.query()); application.put("categorylist", categoryservice.query()); return "index"; } }
到这里,还有一个看着不爽的地方,就是categoryservice这个成员变量,它一直存在在categoryaction里,因为categoryaction中有用到categoryservice对象中的方法,所以必须得创建这个对象,并且有set方法才能注入进来。这就导致一个弊端:如果很多action都需要使用categoryservice的话,那就必须在它们的action里创建这个对象和set方法,而且,如果一个action中要使用好几个不同的service对象,那就得全部创建,这样就变得很冗杂。
3.5 抽取service到baseaction
针对上面的问题,我们将工程中所有的service对象都抽取到baseaction中创建,这样其他action继承baseaction后,想用什么service就直接拿来用即可:
//我将baseaction中的内容归归类了 public class baseaction<t> extends actionsupport implements requestaware,sessionaware,applicationaware,modeldriven<t> { //service对象 protected categoryservice categoryservice; protected accountservice accountservice; public void setcategoryservice(categoryservice categoryservice) { this.categoryservice = categoryservice; } public void setaccountservice(accountservice accountservice) { this.accountservice = accountservice; } //域对象 protected map<string, object> request; protected map<string, object> session; protected map<string, object> application; @override public void setapplication(map<string, object> application) { this.application = application; } @override public void setsession(map<string, object> session) { this.session = session; } @override public void setrequest(map<string, object> request) { this.request = request; } //modeldriven protected t model; @override public t getmodel() { parameterizedtype type = (parameterizedtype)this.getclass().getgenericsuperclass(); class clazz = (class)type.getactualtypearguments()[0]; try { model = (t)clazz.newinstance(); } catch (exception e) { throw new runtimeexception(e); } return model; } } 这样categoryaction中就更加清爽了: public class categoryaction extends baseaction<category> { public string update() { system.out.println("----update----"); categoryservice.update(model); return "index"; } public string save() { system.out.println("----save----"); system.out.println(model); return "index"; } public string query() { request.put("categorylist", categoryservice.query()); session.put("categorylist", categoryservice.query()); application.put("categorylist", categoryservice.query()); return "index"; } }
有人可能会问,baseaction中注入了那么多service对象的话不会冗余么?这是不会的,因为就算不写在baseaction中,spring容器也是会创建这个对象的,这点没有关系,相反,service对象全放在baseaction中更加便于其他action的开发,而且baseaction不需要配到struts.xml文件中,因为根本就没有哪个jsp会请求baseaction,它只是让其他action来继承用的。
还有一点别忘了:那就是修改在beans.xml中的配置:
<!-- 如果是prototype类型,默认是使用时创建,不是启动时自动创建 --> <bean id="baseaction" class="cn.it.shop.action.baseaction" scope="prototype"> <property name="categoryservice" ref="categoryservice"></property> <property name="accountservice" ref="accountservice"></property> </bean> <bean id="categoryaction" class="cn.it.shop.action.categoryaction" scope="prototype" parent="baseaction"/>
新加一个baseaction的bean,将工程中所有service对象作为property配好,将原来的categoryaction中的property干掉。
以后我们如果要写新的xxxaction,直接继承baseaction即可,如果xxxaction中有用到某个service,直接拿来用即可,只需要在beans.xml文件中加一个xxxaction对应的bean,在struts.xml文件中配置好跳转即可。
4. 将xml改成注解
我们可以看到,随着项目越写越大,beans.xml中的配置会越来越多,而且很多配置有冗余,为了更加便于开发,我们现在将xml的配置改成注解的形式,我们先看一下beans.xml中的配置:
这些是我们之前搭建环境以及抽取的时候写的bean,这些都需要转换成注解的形式,下面我们一块一块的换掉:首先替换service部分,这部分有三个:baseservice、categoryservice和accountservice。替换如下:
然后将beans.xml中的相应部分干掉即可。接下来修改action部分,主要有baseaction、categoryaction和accountaction三个,替换如下:
然后再干掉beans.xml中的action部分的配置即可,最后在beans.xml文件中添加一个如下配置,就可以使用注解了。
<context:component-scan base-package="cn.it.shop.."/>
有人可能会问,为什么service和action两个使用注解的时候不一样呢?service中使用的是@service而action中使用的是@controller呢?其实是一样的,只是为了区分它们是不同层的bean而已,便于阅读。
整个项目的源码下载地址:
原文地址:
以上就是ssh框架网上商城项目第2战的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。