Spring容器裝飾者模式應用之實現業務類與服務類自由組合的解決方式

來源:互聯網
上載者:User

標籤:exce   exception   res   ==   tostring   切面   ring   web   實現   

在不論什麼一個項目中都不可或缺的存在兩種bean,一種是實現系統核心功能的bean,我們稱之為業務類,第二種是與系統核心業務無關但同一時候又提供十分重要服務bean,我們稱之為服務類。業務類的bean依據每一個系統自身核心功能的不同能夠有隨意多個,可是服務類的種類在各個系統之間的差異卻並非非常大。在系統中經經常使用到的服務有下面幾種。許可權服務,Log Service。快取服務,事務服務以及預警服務等。在整個系統的不斷進化過程中。服務類與業務類的關係也不斷的發生著變化,由當初垂直模式變為橫切模式,這也是編程思想不斷演化過程。服務類與業務類本來就不應耦合在一起,否則不但會造成大量的代碼冗餘同一時候也難以對服務進行可控性變更。

那麼怎樣解決依據不同業務類自身要求為其提供不同服務的問題呢?spring aop給出了完美解決 方案。Spring解決這個難題的核心思想是將服務類與業務類統統交由spring容器管理。依據不同業務類的不同要求動態佈建服務類。也即動態聯編服務類與業務類實現兩者的自由組合。至於業務類與服務類之間的關係演變能夠用簡單呈現一下:

這張圖是最原始的業務-服務關係圖,看到這連想都不敢想了,相同的服務反覆出如今N多個業務中,想想也是醉了,假設哪天服務內容改變除了跳樓預計也沒有別的選擇了,還好Spring AOP的出現將這個讓人頭疼的問題。使用Spring 註解方式將切面類橫切到各個服務類中就能夠一絕解決代碼冗餘。難於維護的問題


本圖在代碼中的提現例如以下:

package com.test.util;@Aspectpublic class AuthorityService {@Autowiredprivate LogManager logManager;@Before("execution(* com.test.web.*.*(..))")public void logAll(JoinPoint point) throws Throwable {System.out.println("======authority-before======");}@After("execution(* com.test.web.*.*(..))")public void after() {System.out.println("=========authority-after=========");}// 方法啟動並執行前後調用@Around("execution(* com.test.web.*.*(..))")public Object around(ProceedingJoinPoint point) throws Throwable {System.out.println("======authority-around開始之前before======");HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();// 獲得具體時間SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// Calendar ca = Calendar.getInstance();// String operDate = df.format(ca.getTime());Log sysLog = new Log();// 開始時間sysLog.setStartTime(df.format(new Date()));// 擷取ip地址String ip = TCPIPUtil.getIpAddr(request);String loginName;String name;String methodRemark = getMthodRemark(point);String methodName = point.getSignature().getName();String packages = point.getThis().getClass().getName();if (packages.indexOf("$$EnhancerByCGLIB$$") > -1) { // 假設是CGLIB動態產生的類try {packages = packages.substring(0, packages.indexOf("$$"));} catch (Exception ex) {ex.printStackTrace();}}Object object;try {// method_param = point.getArgs(); //擷取方法參數// String param=(String) point.proceed(point.getArgs());object = point.proceed();} catch (Exception e) {// 異常處理記錄日誌..log.error(e);throw e;}sysLog.setIp(ip);sysLog.setClazz(packages);sysLog.setMethod(methodName);sysLog.setMessage(methodRemark);// 結束時間sysLog.setEndTime(df.format(new Date()));// 返回結果if (object == null) {sysLog.setResult("無傳回值");} else {sysLog.setResult(object.toString());}logManager.addLog(sysLog);System.out.println("======authority-around開始之前after======");return object;}@AfterReturning("execution(* com.test.web.*.*(..))")public void x(){System.out.println("-------authority-afterreturning-------");}// 擷取方法的中文備忘____用於記錄使用者的動作記錄描寫敘述public static String getMthodRemark(ProceedingJoinPoint joinPoint)throws Exception {String targetName = joinPoint.getTarget().getClass().getName();String methodName = joinPoint.getSignature().getName();Object[] arguments = joinPoint.getArgs();Class targetClass = Class.forName(targetName);Method[] method = targetClass.getMethods();String methode = "";for (Method m : method) {if (m.getName().equals(methodName)) {Class[] tmpCs = m.getParameterTypes();if (tmpCs.length == arguments.length) {Logger methodCache = m.getAnnotation(Logger.class);// 獲得標記,為空白時沒有標記if (methodCache != null) {methode = methodCache.remark();}break;}}}return methode;}}

從上面代碼能夠看出該許可權服務類切入到了com.test.web包下的全部類的全部方法上。同理我們能夠再建立快取服務類。Log Service類等分別切入到其它業務類中,如今看來這個令人頭疼的問題似乎被spring基於@Aspect風格的aop解決掉了,可是我們細緻想一想問題真的攻克了嗎?大家都知道這種切入方式粒度實在是太粗了,並非同樣包下的全部類的全部方法都須要這種服務,舉個非常easy的範例。就拿快取服務和Log Service來講,Log Service是須要具體記錄的它能夠切入不論什麼類的不論什麼方法上。可是快取服務卻並非這樣。快取服務僅僅可能存在於類的擷取資料的方法上,此時將快取服務切入到類的改動。刪除等方法上就顯得非常奇葩了,再通俗的將就是不論什麼一個服務都可能僅僅存在於特定類的特定方法上,這個不確定性決定了僅僅使用Spring @Aspect註解方式是難以解決問題的

此時我們須要如何做呢?還好Spring同一時候提供了基於xml配置方式的aop,這種方式在相當大的程度上彌補了@Aspect不足,他能夠依據不同的配置方式將不同的服務切入到不同類的不同方法上。這樣細的粒度足以讓我們實現各種服務對業務的動態組合。說道這可能會有人問既然spring提供了基於xml配置的aop。那我們僅僅須要將不同的服務均配置在xml檔案裡不是相同能夠實現業務與服務的動態組合嗎?聽上去似乎非常有道理,果真這種話似乎基於註解的aop似乎就能夠被替代了。可是程式效率的問題又讓我們不得不正確xml配置方式和註解方式進行再次衡量。

因為xml是在執行期動態聯編確定切面的,解析xml的過程毫無疑問的會佔用系統資源,假設將大量的aop配置配置在xml檔案裡將會得不償失,而@Aspect方式支援編譯期織入且不須要組建代理程式,這樣就使得效率上會有優勢。如此來看僅僅要將xml方式與@Aspect方式混合使用。將粗粒度的服務(如日誌和許可權服務)使用@Aspect方式進行切入,對於細粒度的服務(如快取服務)使用xml方式配置在spring中就能夠完美解決以上問題了。

以下是兩種方式結合使用的執行個體。先來看設定檔:

<?

xml version="1.0" encoding="UTF-8"?

><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"><!-- 註解掃描包 --><context:component-scan base-package="com.test" /><!-- 開啟註解 --><mvc:annotation-driven /><!-- 靜態資源(js/image)的訪問 --><mvc:resources location="/js/" mapping="/js/**" /><!-- 定義視圖解析器 --><bean id="viewResolver"class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/"></property><property name="suffix" value=".jsp"></property></bean><!-- 啟用Spring對基於@AspectJ aspects的配置支援 --><aop:aspectj-autoproxy></aop:aspectj-autoproxy><bean id="logService" class="com.test.util.LogService"></bean><bean id="cacheService" class="com.test.util.CacheService"></bean> <aop:config proxy-target-class="true"> <aop:aspect ref="cacheService"> <aop:pointcut id="log" expression="execution(* com.test.web.UserController.getAllUser(..))"/> <aop:before pointcut-ref="log" method="logAll"/> <aop:after pointcut-ref="log" method="after"/> <aop:after-returning pointcut-ref="log" method="x"/> <aop:around pointcut-ref="log" method="around"/> </aop:aspect> </aop:config></beans>


接下來是Log Service類:

package com.test.util;@Aspectpublic class LogService {@Autowiredprivate LogManager logManager;@Before("execution(* com.test.web.*.*(..))")public void logAll(JoinPoint point) throws Throwable {System.out.println("======log-before======");}@After("execution(* com.test.web.*.*(..))")public void after() {System.out.println("=========cache-after=========");}// 方法啟動並執行前後調用@Around("execution(* com.test.web.*.*(..))")public Object around(ProceedingJoinPoint point) throws Throwable {System.out.println("======log-around開始之前before======");HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();// 獲得具體時間SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Log sysLog = new Log();// 開始時間sysLog.setStartTime(df.format(new Date()));// 擷取ip地址String ip = TCPIPUtil.getIpAddr(request);String loginName;String name;String methodRemark = getMthodRemark(point);String methodName = point.getSignature().getName();String packages = point.getThis().getClass().getName();if (packages.indexOf("$$EnhancerByCGLIB$$") > -1) { // 假設是CGLIB動態產生的類try {packages = packages.substring(0, packages.indexOf("$$"));} catch (Exception ex) {ex.printStackTrace();}}// String operatingcontent = "";// Object[] method_param = null;Object object;try {// method_param = point.getArgs(); //擷取方法參數// String param=(String) point.proceed(point.getArgs());object = point.proceed();} catch (Exception e) {// 異常處理記錄日誌..log.error(e);throw e;}sysLog.setIp(ip);sysLog.setClazz(packages);// 包名.+方法名// packages + "." + methodNamesysLog.setMethod(methodName);sysLog.setMessage(methodRemark);// 結束時間sysLog.setEndTime(df.format(new Date()));// 返回結果if (object == null) {sysLog.setResult("無傳回值");} else {sysLog.setResult(object.toString());}logManager.addLog(sysLog);System.out.println("======log-around開始之前after======");return object;}@AfterReturning("execution(* com.test.web.*.*(..))")public void x(){System.out.println("-------log-afterreturning-------");}// 擷取方法的中文備忘____用於記錄使用者的動作記錄描寫敘述public static String getMthodRemark(ProceedingJoinPoint joinPoint)throws Exception {String targetName = joinPoint.getTarget().getClass().getName();String methodName = joinPoint.getSignature().getName();Object[] arguments = joinPoint.getArgs();Class targetClass = Class.forName(targetName);Method[] method = targetClass.getMethods();String methode = "";for (Method m : method) {if (m.getName().equals(methodName)) {Class[] tmpCs = m.getParameterTypes();if (tmpCs.length == arguments.length) {Logger methodCache = m.getAnnotation(Logger.class);// 獲得標記,為空白時沒有標記if (methodCache != null) {methode = methodCache.remark();}break;}}}return methode;}}

再接下來是快取服務類;

package com.test.util;public class LogService {@Autowiredprivate CacheManager logManager;public void pointcut() {}public void logAll(JoinPoint point) throws Throwable {System.out.println("======cache-before======");}public void after() {System.out.println("=========cache-after=========");}// 方法啟動並執行前後調用public Object around(ProceedingJoinPoint point) throws Throwable {System.out.println("======cache-around開始之前before======");HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();// 獲得具體時間SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// Calendar ca = Calendar.getInstance();// String operDate = df.format(ca.getTime());Log sysLog = new Log();// 開始時間sysLog.setStartTime(df.format(new Date()));// 擷取ip地址String ip = TCPIPUtil.getIpAddr(request);String loginName;String name;String methodRemark = getMthodRemark(point);String methodName = point.getSignature().getName();String packages = point.getThis().getClass().getName();if (packages.indexOf("$$EnhancerByCGLIB$$") > -1) { // 假設是CGLIB動態產生的類try {packages = packages.substring(0, packages.indexOf("$$"));} catch (Exception ex) {ex.printStackTrace();}}Object object;try {// method_param = point.getArgs(); //擷取方法參數// String param=(String) point.proceed(point.getArgs());object = point.proceed();} catch (Exception e) {// 異常處理記錄日誌..log.error(e);throw e;}sysLog.setIp(ip);sysLog.setClazz(packages);// 包名.+方法名// packages + "." + methodNamesysLog.setMethod(methodName);sysLog.setMessage(methodRemark);// 結束時間sysLog.setEndTime(df.format(new Date()));// 返回結果if (object == null) {sysLog.setResult("無傳回值");} else {sysLog.setResult(object.toString());}logManager.addLog(sysLog);System.out.println("======cache-around開始之前after======");return object;}public void x(){System.out.println("-------cache-afterreturning-------");}}

Log Service類和快取服務類僅僅在詳細處理過程中不一樣,本文在Log Service的基礎上稍加修改簡化處理。大家能夠在使用時依據自身情況進行緩衝處理和Tlog。在詳細運行過程中細心的朋友可能會發如今設定檔裡不同服務的出現順序決定了兩個服務的運行順序。也就是說在spring aop運行切入動作是通過裝飾者模式來完畢,採用類似棧的先進後出方式來詳細運行服務這一點大家一定要注意啊。

Spring容器裝飾者模式應用之實現業務類與服務類自由組合的解決方式

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.