轉自:https://blog.csdn.net/eson_15/article/details/51297698
上一節我們搭建好了Struts2、Hibernate和Spring的開發環境,併成功將它們整合在一起。這節主要完成一些基本的增刪改查以及Service、Dao和Action的抽取。
1. Service層的抽取
上一節中,我們在service層簡單寫了save和update方法,這裡我們開始完善該部分的程式碼,然後對service層的程式碼進行抽取。
1.1 完善CategoryService層
對資料庫的操作無非是增刪改查,首先我們來完善CategoryService層的介面和實現:
1 //CategoryService介面 2 public interface CategoryService extends BaseService<Category> { 3 4 public void save(Category category); //插入 5 6 public void update(Category category);//更新 7 8 public void delete(int id); //刪除 9 10 public Category get(int id); //獲取一個Category 11 12 public List<Category> query(); //獲取全部Category 13 14 }
對CategoryService介面的具體實現:
1 public class CategoryServiceImpl extends BaseServiceImpl<Category> implements CategoryService { 2 3 private SessionFactory sessionFactory; 4 5 //Spring會注進來 6 public void setSessionFactory(SessionFactory sessionFactory) { 7 this.sessionFactory = sessionFactory; 8 } 9 10 protected Session getSession() { 11 //從當前執行緒獲取session,如果沒有則建立一個新的session 12 return sessionFactory.getCurrentSession(); 13 } 14 15 @Override 16 public void save(Category category) { 17 getSession().save(category); 18 } 19 20 @Override 21 public void update(Category category) { 22 getSession().update(category); 23 } 24 25 @Override 26 public void delete(int id) { 27 /*第一種方法有個弊端,就是沒刪除一次得先查詢一次 28 Object obj = getSession().get(Category.class, id); 29 if(obj != null) { 30 getSession().delete(obj); 31 }*/ 32 String hql = "delete Category while id=:id"; 33 getSession().createQuery(hql) // 34 .setInteger("id", id) // 35 .executeUpdate(); 36 } 37 38 @Override 39 public Category get(int id) { 40 return (Category) getSession().get(Category.class, id); 41 } 42 43 @Override 44 public List<Category> query() { 45 String hql = "from Category"; 46 return getSession().createQuery(hql).list(); 47 } 48 }
1.2 Service層抽取實現
完成了CategoryService後,我們來抽取Service層的基礎實現。思路是這樣的:我們抽取一個基礎介面BaseService以及基礎介面的實現BaseServiceImpl,後面開發的時候,如果需要新的Service,只需要做兩步即可:首先定義一個新的介面xxxService繼承BaseService介面,這個介面可以增加新的抽象方法;然後定義一個新的實現類xxxServiceImpl繼承BaseServiceImpl並實現xxxService介面即可。這樣更加便於專案的維護。
我們先根據上面的CategoryService介面來建立BaseService介面:
1 //基礎介面BaseService,使用泛型 2 public interface BaseService<T> { 3 public void save(T t); 4 5 public void update(T t); 6 7 public void delete(int id); 8 9 public T get(int id); 10 11 public List<T> query(); 12 }
然後再根據CategoryServiceImpl實現類建立BaseService介面的實現類BaseServiceImpl:
1 /** 2 * @Description TODO(公共模組的抽取) 3 * @author eson_15 4 * 5 */ 6 @SuppressWarnings("unchecked") 7 public class BaseServiceImpl<T> implements BaseService<T> { 8 9 private Class clazz; //clazz中儲存了當前操作的型別,即泛型T 10 private SessionFactory sessionFactory; 11 12 public BaseServiceImpl() { 13 //下面三個列印資訊可以去掉,這裡是給自己看的 14 System.out.println("this代表的是當前呼叫構造方法的物件" + this); 15 System.out.println("獲取當前this物件的父類資訊" + this.getClass().getSuperclass()); 16 System.out.println("獲取當前this物件的父類資訊(包括泛型資訊)" + this.getClass().getGenericSuperclass()); 17 //拿到泛型的引數型別 18 ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass(); 19 clazz = (Class)type.getActualTypeArguments()[0]; 20 } 21 22 public void setSessionFactory(SessionFactory sessionFactory) { 23 this.sessionFactory = sessionFactory; 24 } 25 26 protected Session getSession() { 27 //從當前執行緒獲取session,如果沒有則建立一個新的session 28 return sessionFactory.getCurrentSession(); 29 } 30 31 @Override 32 public void save(T t) { 33 getSession().save(t); 34 } 35 36 @Override 37 public void update(T t) { 38 getSession().update(t); 39 } 40 41 @Override 42 public void delete(int id) { 43 System.out.println(clazz.getSimpleName()); 44 String hql = "delete " + clazz.getSimpleName() + " as c where c.id=:id"; 45 getSession().createQuery(hql) // 46 .setInteger("id", id) // 47 .executeUpdate(); 48 } 49 50 @Override 51 public T get(int id) { 52 return (T) getSession().get(clazz, id); 53 } 54 55 @Override 56 public List<T> query() { 57 String hql = "from " + clazz.getSimpleName(); 58 return getSession().createQuery(hql).list(); 59 } 60 61 }
抽取完了後,我們就可以改寫CategoryService介面和CategoryServiceImpl實現類了。如下:
1 2 3 //CategoryService介面繼承BaseService介面 4 public interface CategoryService extends BaseService<Category> { 5 /* 6 * 只要新增CategoryService本身需要的新的方法即可,公共方法已經在BaseService中了 7 */ 8 } 9 10 /** 11 * @Description TODO(模組自身的業務邏輯) 12 * @author eson_15 13 * 14 */ 15 public class CategoryServiceImpl extends BaseServiceImpl<Category> implements CategoryService { 16 17 /* 18 * 只需實現CategoryService介面中新增的方法即可,公共方法已經在BaseServiceImpl中實現了 19 */ 20 }
從程式碼中可以看出,新增的Service只需要繼承BaseService介面,然後在介面中新增本Service所需要的業務邏輯即可。新增的ServiceImpl只需要繼承BaseServiceImpl並實現新增的業務邏輯即可。
但是別忘了很重要的一點:就是修改Spring的配置檔案beans.xml中的bean。
1 <!-- 泛型類是不能例項化的,所以要加lazy-init屬性 --> 2 <bean id="baseService" class="cn.it.shop.service.impl.BaseServiceImpl" lazy-init="true"> 3 <property name="sessionFactory" ref="sessionFactory" /> 4 </bean> 5 6 <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:
1 public interface AccountService extends BaseService<Account> { //注意BaseService裡的泛型現在是Account 2 /* 3 * 只要新增AccountService本身需要的新的方法即可,公共方法已經在BaseService中了 4 */ 5 } 6 然後寫一個AccountServiceImpl實現類繼承BaseServiceImpl實現類,並實現AccountService介面即可: 7 8 9 10 public class AccountServiceImpl extends BaseServiceImpl<Account> implements AccountService { 11 12 /* 13 * 只需實現AccountService介面中新增的方法即可,公共方法已經在BaseServiceImpl中實現了 14 */ 15 16 //管理登陸功能,後期再完善 17 }
最後在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介面來注入相應的域物件。我們先來看一下這兩種方法:
1 public class CategoryAction extends ActionSupport implements RequestAware,SessionAware,ApplicationAware{ 2 3 private Category category; 4 5 private CategoryService categoryService; 6 7 public void setCategoryService(CategoryService categoryService) { 8 this.categoryService = categoryService; 9 } 10 11 public String update() { 12 System.out.println("----update----"); 13 categoryService.update(category); 14 return "index"; 15 } 16 17 public String save() { 18 System.out.println("----save----"); 19 return "index"; 20 } 21 22 public String query() { 23 //解決方案一,採用相應的map取代原來的內建物件,這樣與jsp沒有依賴,但是程式碼量比較大 24 // ActionContext.getContext().put("categoryList", categoryService.query()); //放到request域中 25 // ActionContext.getContext().getSession().put("categoryList", categoryService.query()); //放到session域中 26 // ActionContext.getContext().getApplication().put("categoryList", categoryService.query()); //放到application域中 27 28 //解決方案二,實現相應的介面(RequestAware,SessionAware,ApplicationAware),讓相應的map注入 29 request.put("categoryList", categoryService.query()); 30 session.put("categoryList", categoryService.query()); 31 application.put("categoryList", categoryService.query()); 32 return "index"; 33 } 34 35 public Category getCategory() { 36 return category; 37 } 38 39 public void setCategory(Category category) { 40 this.category = category; 41 } 42 43 private Map<String, Object> request; 44 private Map<String, Object> session; 45 private Map<String, Object> application; 46 47 @Override 48 public void setApplication(Map<String, Object> application) { 49 this.application = application; 50 } 51 52 @Override 53 public void setSession(Map<String, Object> session) { 54 this.session = session; 55 } 56 57 @Override 58 public void setRequest(Map<String, Object> request) { 59 this.request = request; 60 } 61 }
還是上一節整合三大框架時的CategoryAction類,我們在裡面加了一個query方法,在該方法中,我們通過向request域、session域和application域中存入查詢的結果。第一種方法是直接使用ActionContext來實現,不需要實現任何介面,但是程式碼量較大;第二種方法通過實現RequestAware、SessionAware和ApplicationAware介面,實現該介面的三個抽象方法把request、session和application注入進來,然後賦給相應的成員變數中,這樣就可以在query方法中向域中存放查詢結果了。這程式碼量貌似比第一種方法更大……但是我們可以抽取,先往下看。
我們在index.jsp中新加一個查詢連線來測試能否將查詢結果顯示出來:
1 2 3 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 4 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 5 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 6 <html> 7 <head> 8 <title>My JSP 'index.jsp' starting page</title> 9 </head> 10 11 <body> 12 <a href="${pageContext.request.contextPath }/category_update.action?category.id=2&category.type=gga&category.hot=false">訪問update</a> 13 <a href="category_save.action">訪問save</a> 14 <a href="category_query.action">查詢所有類別</a><br/> 15 <c:forEach items="${requestScope.categoryList }" var="category"> 16 ${category.id } | ${category.type } | ${category.hot } <br/> 17 </c:forEach> 18 19 <c:forEach items="${sessionScope.categoryList }" var="category"> 20 ${category.id } | ${category.type } | ${category.hot } <br/> 21 </c:forEach> 22 23 <c:forEach items="${applicationScope.categoryList }" var="category"> 24 ${category.id } | ${category.type } | ${category.hot } <br/> 25 </c:forEach> 26 </body> 27 </html>
3.2 抽取BaseAction
剛剛提到了,第二種方法的程式碼量更大,但是我們可以抽取一個BaseAction,專門處理這些域相關的操作。
1 public class BaseAction extends ActionSupport implements RequestAware,SessionAware,ApplicationAware { 2 3 protected Map<String, Object> request; 4 protected Map<String, Object> session; 5 protected Map<String, Object> application; 6 7 @Override 8 public void setApplication(Map<String, Object> application) { 9 this.application = application; 10 } 11 12 @Override 13 public void setSession(Map<String, Object> session) { 14 this.session = session; 15 } 16 17 @Override 18 public void setRequest(Map<String, Object> request) { 19 this.request = request; 20 } 21 }
然後我們自己的Action如果需要用到這些域物件來儲存資料時,直接繼承BaseAction即可,就能直接使用request、session和application物件了。所以修改後的CategoryAction如下:
1 2 3 public class CategoryAction extends BaseAction { 4 5 private Category category; 6 <pre name="code" class="java"> 7 private CategoryService categoryService; 8 9 public void setCategoryService(CategoryService categoryService) { 10 this.categoryService = categoryService; 11 } 12 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; }} 13 14
後面所有要使用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來更方便的解決。
1 public class CategoryAction extends BaseAction implements ModelDriven<Category>{ 2 3 private Category category; 4 5 //使用ModelDriven介面必須要實現getModel()方法,此方法會把返回的項壓到棧頂 6 @Override 7 public Category getModel() { 8 category = new Category(); 9 return category; 10 } 11 <pre name="code" class="java"> private CategoryService categoryService; 12 13 public void setCategoryService(CategoryService categoryService) { 14 this.categoryService = categoryService; 15 } 16 17 public String update() { 18 System.out.println("----update----"); 19 categoryService.update(category); 20 return "index"; 21 } 22 23 public String save() { 24 System.out.println("----save----"); 25 return "index"; 26 } 27 28 public String query() { 29 request.put("categoryList", categoryService.query()); 30 session.put("categoryList", categoryService.query()); 31 application.put("categoryList", categoryService.query()); 32 return "index"; 33 } 34 35 } 36 37 38 39
這樣我們在前臺JSP頁面就不用帶category.id這種繁瑣的引數了,看JSP頁面中的ModelDriven部分:
1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 2 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 4 <html> 5 <head> 6 <title>My JSP 'index.jsp' starting page</title> 7 </head> 8 9 <body> 10 <a href="${pageContext.request.contextPath }/category_update.action?category.id=2&category.type=gga&category.hot=false">訪問update</a> 11 <a href="category_save.action?id=1&type=haha&hot=true">測試ModelDriven</a> 12 <a href="category_query.action">查詢所有類別</a><br/> 13 <c:forEach items="${requestScope.categoryList }" var="category"> 14 ${category.id } | ${category.type } | ${category.hot } <br/> 15 </c:forEach> 16 17 <c:forEach items="${sessionScope.categoryList }" var="category"> 18 ${category.id } | ${category.type } | ${category.hot } <br/> 19 </c:forEach> 20 21 <c:forEach items="${applicationScope.categoryList }" var="category"> 22 ${category.id } | ${category.type } | ${category.hot } <br/> 23 </c:forEach> 24 </body> 25 </html>
測試結果是可以獲得catgory,並且將id,type和hot屬性全部賦值好。我們可以看出,通過實現ModelDriven介面,我們可以很方便的在url中攜帶引數,Action中只需要實現getModel方法,new一個要使用的物件返回即可。到這裡我們很容易想到,struts中肯定會有很多這種model需要獲取,所以這一塊我們也要抽取到BaseAction中去。
3.4 抽取ModelDriven到BaseAction
首先我們在BaseAction中新增ModelDriven部分的程式碼,如下:
1 //因為有很多不同的model都需要使用ModelDriven,所以這裡使用泛型 2 public class BaseAction<T> extends ActionSupport implements RequestAware,SessionAware,ApplicationAware,ModelDriven<T> { 3 4 protected Map<String, Object> request; 5 protected Map<String, Object> session; 6 protected Map<String, Object> application; 7 8 protected T model; 9 10 @Override 11 public void setApplication(Map<String, Object> application) { 12 this.application = application; 13 } 14 15 @Override 16 public void setSession(Map<String, Object> session) { 17 this.session = session; 18 } 19 20 @Override 21 public void setRequest(Map<String, Object> request) { 22 this.request = request; 23 } 24 25 @Override 26 public T getModel() { //這裡通過解析傳進來的T來new一個對應的instance 27 ParameterizedType type = (ParameterizedType)this.getClass().getGenericSuperclass(); 28 Class clazz = (Class)type.getActualTypeArguments()[0]; 29 try { 30 model = (T)clazz.newInstance(); 31 } catch (Exception e) { 32 throw new RuntimeException(e); 33 } 34 return model; 35 } 36 }
抽取完了後,CategoryAction中的程式碼會越來越少:
1 //繼承BaseAction,並且加上泛型 2 public class CategoryAction extends BaseAction<Category> { 3 4 private CategoryService categoryService; 5 6 public void setCategoryService(CategoryService categoryService) { 7 this.categoryService = categoryService; 8 } 9 10 public String update() { 11 System.out.println("----update----"); 12 categoryService.update(model);//直接使用model 13 return "index"; 14 } 15 16 public String save() { 17 System.out.println("----save----"); 18 System.out.println(model); //直接使用model 19 return "index"; 20 } 21 22 public String query() { 23 request.put("categoryList", categoryService.query()); 24 session.put("categoryList", categoryService.query()); 25 application.put("categoryList", categoryService.query()); 26 return "index"; 27 } 28 29 }
到這裡,還有一個看著不爽的地方,就是categoryService這個成員變數,它一直存在在CategoryAction裡,因為CategoryAction中有用到categoryService物件中的方法,所以必須得建立這個物件,並且有set方法才能注入進來。這就導致一個弊端:如果很多Action都需要使用categoryService的話,那就必須在它們的Action裡建立這個物件和set方法,而且,如果一個Action中要使用好幾個不同的service物件,那就得全部建立,這樣就變得很冗雜。
3.5 抽取service到BaseAction
針對上面的問題,我們將工程中所有的service物件都抽取到BaseAction中建立,這樣其他Action繼承BaseAction後,想用什麼service就直接拿來用即可:
1 //我將BaseAction中的內容歸歸類了 2 public class BaseAction<T> extends ActionSupport implements RequestAware,SessionAware,ApplicationAware,ModelDriven<T> { 3 4 //service物件 5 protected CategoryService categoryService; 6 protected AccountService accountService; 7 8 public void setCategoryService(CategoryService categoryService) { 9 this.categoryService = categoryService; 10 } 11 public void setAccountService(AccountService accountService) { 12 this.accountService = accountService; 13 } 14 15 //域物件 16 protected Map<String, Object> request; 17 protected Map<String, Object> session; 18 protected Map<String, Object> application; 19 20 @Override 21 public void setApplication(Map<String, Object> application) { 22 this.application = application; 23 } 24 @Override 25 public void setSession(Map<String, Object> session) { 26 this.session = session; 27 } 28 @Override 29 public void setRequest(Map<String, Object> request) { 30 this.request = request; 31 } 32 33 //ModelDriven 34 protected T model; 35 @Override 36 public T getModel() { 37 ParameterizedType type = (ParameterizedType)this.getClass().getGenericSuperclass(); 38 Class clazz = (Class)type.getActualTypeArguments()[0]; 39 try { 40 model = (T)clazz.newInstance(); 41 } catch (Exception e) { 42 throw new RuntimeException(e); 43 } 44 return model; 45 } 46 }
這樣CategoryAction中就更加清爽了:
1 2 3 public class CategoryAction extends BaseAction<Category> { 4 5 public String update() { 6 System.out.println("----update----"); 7 categoryService.update(model); 8 return "index"; 9 } 10 11 public String save() { 12 System.out.println("----save----"); 13 System.out.println(model); 14 return "index"; 15 } 16 17 public String query() { 18 request.put("categoryList", categoryService.query()); 19 session.put("categoryList", categoryService.query()); 20 application.put("categoryList", categoryService.query()); 21 return "index"; 22 } 23 24 }
有人可能會問,BaseAction中注入了那麼多service物件的話不會冗餘麼?這是不會的,因為就算不寫在BaseAction中,Spring容器也是會建立這個物件的,這點沒有關係,相反,service物件全放在BaseAction中更加便於其他Action的開發,而且BaseAction不需要配到struts.xml檔案中,因為根本就沒有哪個JSP會請求BaseAction,它只是讓其他Action來繼承用的。
還有一點別忘了:那就是修改在beans.xml中的配置:
1 <!-- 如果是prototype型別,預設是使用時建立,不是啟動時自動建立 --> 2 <bean id="baseAction" class="cn.it.shop.action.BaseAction" scope="prototype"> 3 <property name="categoryService" ref="categoryService"></property> 4 <property name="accountService" ref="accountService"></property> 5 </bean> 6 7 <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.."/>