橫切關注點最突出的例子是伐木 。 日志記錄主要用于通過跟蹤方法調用和總體執行流程來調試和故障排除問題。 由于日志記錄策略必然會影響系統的每個日志記錄部分,因此可以將其視為橫切關注點。 因此,日志記錄會橫切所有已記錄的類和方法。
請注意,AOP和OOP不是唯一的。 相反,它們是相輔相成的,它們的組合使用可以幫助我們生產健壯且可維護的軟件。 使用AOP,我們首先使用OO語言來實現我們的項目,然后通過實現方面來分別處理代碼中的橫切關注點。
在我們最喜歡的Spring框架的幫助下,AOP的使用和采用得到了提高。 Spring使AOP使用非侵入性方法更易于集成到我們的項目中。 Justin在JavaCodeGeeks的題為 “ 使用Spring AspectJ和Maven進行面向方面的編程 ”的文章中曾談到過Spring AOP。 但是,我們最新的JCG合作伙伴 SivaLabs的 Siva也寫了一篇關于Spring AOP的很好的文章 ,我想與您分享,這里是。
(注意:對原始帖子進行了少量編輯以提高可讀性)
在為企業開發軟件應用程序時,我們會從需求收集團隊或我們的業務分析師那里收到項目的需求。 通常,這些需求是代表業務活動的功能需求。 但是,在開發軟件應用程序時,除了功能需求之外,我們還應該考慮其他一些方面,例如性能,事務管理,安全性,日志記錄等。這些被稱為非功能需求。
讓我們考慮一個BookStore應用程序,它提供對書店的網絡訪問。 用戶可以瀏覽各種類別的書籍,將一些書籍添加到購物車中,最后結帳,付款并獲得書籍。 對于此應用程序,我們可能會收到來自業務分析師的要求,如下所示:
- 登錄/注冊屏幕以進入BookStore。
- 用戶應該能夠瀏覽各種類別的書籍
- 用戶應該能夠按名稱,作者姓名,出版商搜索書籍
- 用戶應該能夠在購物車中添加/刪除圖書
- 用戶應該能夠查看其購物車中當前存在哪些物品
- 用戶應該能夠通過某些支付網關進行結帳并支付相應的金額
- 應該向用戶顯示一條成功消息,其中包含購買的所有詳細信息。
- 應向用戶顯示失敗消息,并說明失敗原因。
- 應該授予BookStore管理員/經理訪問添加/刪除/更新圖書詳細信息的權限。
以上所有要求都屬于“功能要求”類別。 在執行上述操作時,即使未明確提及,我們也應注意以下事項:
- 基于角色的用戶界面訪問。 在這里,只有管理員/管理員才有權添加/刪除/更新書籍詳細信息。 [基于角色的授權]
- 采購中的原子性。 假設一個用戶登錄到BookStore并將5本書添加到他的購物車中,簽出并完成了付款。 在后端實施中,我們可能需要在3個表中輸入此購買詳細信息。 如果將數據插入2個表后系統崩潰,則應回滾整個操作。 [交易管理]。
- 沒有人是完美的,沒有系統是完美的。 因此,如果出現問題,并且開發團隊必須找出問題所在,則日志記錄將非常有用。 因此,日志記錄的實現方式應使開發人員應該能夠弄清楚應用程序的確切故障原因并進行修復。 [記錄中]
上面的隱式要求稱為非功能性要求。 除上述之外,對于所有面向公眾的網站,性能顯然應該是至關重要的非功能性要求。
因此,利用上述所有功能需求,我們可以通過將系統分解為各個組件帶來構建系統,同時照顧整個組件的非功能需求。
public class OrderService
{private OrderDAO orderDAO;public boolean placeOrder(Order order){boolean flag = false;logger.info("Entered into OrderService.placeOrder(order) method");try{flag = orderDAO.saveOrder(order);}catch(Exception e){logger.error("Error occured in OrderService.placeOrder(order) method");}logger.info("Exiting from OrderService.placeOrder(order) method");return flag;}
}
public class OrderDAO
{public boolean saveOrder(Order order){boolean flag = false;logger.info("Entered into OrderDAO.saveOrder(order) method");Connection conn = null;try{conn = getConnection();//get database connectionconn.setAutoCommit(false);// insert data into orders_master table which generates an order_id// insert order details into order_details table with the generated order_id// insert shipment details into order_shipment tableconn.commit();conn.setAutoCommit(true);flag = true;}catch(Exception e){logger.error("Error occured in OrderDAO.saveOrder(order) method");conn.rollback();}logger.info("Exiting from OrderDAO.saveOrder(order) method");return flag;}
}
在上面的代碼中,功能需求實現和非功能需求實現混合在同一位置。 記錄跨OrderService和OrderDAO類。 同時,事務管理涉及多個DAO。
使用這種方法,我們有幾個問題:
- 需要更改類以更改功能或非功能需求。 例如:在開發的某個時刻,如果團隊決定將方法進入/退出信息與TimeStamp一起記錄下來,我們幾乎需要更改所有類。
- 事務管理代碼在開始時將自動提交設置為false,在執行數據庫操作,提交/回滾操作邏輯時,將在所有DAO中重復執行。
跨越模塊/組件的這種要求稱為“交叉切割問題”。 為了更好地設計系統,我們應該將這些跨領域的關注點與實際的業務邏輯分開,以便日后更容易更改或增強或維護應用程序。
面向方面的編程是一種使跨領域關注點與實際業務邏輯分離的方法。 因此,讓我們遵循AOP方法并重新設計上述兩類,將交叉關注點分開。
public interface IOrderService
{public boolean placeOrder(Order order);
}
public class OrderService implements IOrderService
{private OrderDAO orderDAO;public boolean placeOrder(Order order){return orderDAO.saveOrder(order);}
}
public class OrderDAO
{public boolean saveOrder(Order order){boolean flag =false;Connectoin conn = null;try{conn = getConnection();//get database connection// insert data into orders_master table which generates an order_id// insert order details into order_details table with the generated order_id// insert shipment details into order_shipment tableflag = true;}catch(Exception e){logger.error(e); } return flag;}
}
現在,讓我們創建一個LoggingInterceptor來實現應如何進行日志記錄,并為OrderService創建一個代理,該Proxy接受來自調用方的調用,使用LoggingInterceptor記錄進入/退出條目,最后委托給實際的OrderService。
通過使用動態代理,我們可以從實際業務邏輯中分離出跨領域關注點的實現(例如日志記錄),如下所示:
public class LoggingInterceptor
{public void logEntry(Method m){logger.info("Entered into "+m.getName()+" method");}public void logExit(Method m){logger.info("Exiting from "+m.getName()+" method");}
}
public class OrderServiceProxy implements IOrderService extends LoggingInterceptor
{private OrderService orderService;public boolean placeOrder(Order order){boolean flag =false;Method m = getThisMethod();//get OrderService.placeOrder() Method objectlogEntry(m);flag = orderService.placeOrder(order);logExit(m);return flag;}
}
現在,OrderService調用方(OrderController)可以獲取OrderServiceProxy并將訂單下達為:
public class OrderController
{public void checkout(){Order order = new Order();//set the order detailsIOrderService orderService = getOrderServiceProxy();orderService.placeOrder(order);}
}
可以使用幾種AOP框架來將實現與交叉關注點分離開。
- SpringAOP
- AspectJ
- JBoss AOP
讓我們看看如何使用Spring AOP將日志記錄與實際業務邏輯分開。 在此之前,首先我們需要了解以下術語:
- JoinPoint:連接點是應用程序執行中可以插入方面的點。此點可以是調用方法,引發異常或甚至修改字段。
- 切入點:切入點定義與應在其中編織建議的一個或多個連接點相匹配。 通常,您使用顯式的類和方法名稱或通過定義匹配的類和方法名稱模式的正則表達式來指定這些切入點。
- 方面:方面是建議和切入點的合并。
- 建議:方面的工作稱為建議。 這是我們應用于現有模型的附加代碼。
SpringAOP支持幾種類型的建議,即:
- 之前:此建議在方法調用之前編織了方面。
- AfterReturning:此建議在方法調用后編織方面。
- AfterThrowing:當方法拋出Exception時,此建議將編織方面。
- 圍繞:此建議在方法調用之前和之后編織方面。
假設我們有以下ArithmeticCalculator接口和實現類。
package com.springapp.aop;public interface ArithmeticCalculator
{public double add(double a, double b);public double sub(double a, double b);public double mul(double a, double b);public double div(double a, double b);
}
package com.springapp.aop;
import org.springframework.stereotype.Component;@Component("arithmeticCalculator")
public class ArithmeticCalculatorImpl implements ArithmeticCalculator
{public double add(double a, double b){double result = a + b;System.out.println(a + " + " + b + " = " + result);return result;}public double sub(double a, double b){double result = a - b;System.out.println(a + " - " + b + " = " + result);return result;}public double mul(double a, double b){double result = a * b;System.out.println(a + " * " + b + " = " + result);return result;}public double div(double a, double b){if(b == 0){throw new IllegalArgumentException("b value must not be zero.");}double result = a / b;System.out.println(a + " / " + b + " = " + result);return result;}
}
以下LoggingAspect類展示了使用Spring AOP應用Logging Advice的各個方面:
package com.springapp.aop;import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;@Aspect
@Component
public class LoggingAspect
{private Log log = LogFactory.getLog(this.getClass());@Pointcut("execution(* *.*(..))")protected void loggingOperation() {}@Before("loggingOperation()")@Order(1)public void logJoinPoint(JoinPoint joinPoint){log.info("Join point kind : " + joinPoint.getKind());log.info("Signature declaring type : "+ joinPoint.getSignature().getDeclaringTypeName());log.info("Signature name : " + joinPoint.getSignature().getName());log.info("Arguments : " + Arrays.toString(joinPoint.getArgs()));log.info("Target class : "+ joinPoint.getTarget().getClass().getName());log.info("This class : " + joinPoint.getThis().getClass().getName());}@AfterReturning(pointcut="loggingOperation()", returning = "result")@Order(2)public void logAfter(JoinPoint joinPoint, Object result){log.info("Exiting from Method :"+joinPoint.getSignature().getName());log.info("Return value :"+result);}@AfterThrowing(pointcut="execution(* *.*(..))", throwing = "e")@Order(3)public void logAfterThrowing(JoinPoint joinPoint, Throwable e){log.error("An exception has been thrown in "+ joinPoint.getSignature().getName() + "()");log.error("Cause :"+e.getCause());}@Around("execution(* *.*(..))")@Order(4)public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable{log.info("The method " + joinPoint.getSignature().getName()+ "() begins with " + Arrays.toString(joinPoint.getArgs()));try{Object result = joinPoint.proceed();log.info("The method " + joinPoint.getSignature().getName()+ "() ends with " + result);return result;} catch (IllegalArgumentException e){log.error("Illegal argument "+ Arrays.toString(joinPoint.getArgs()) + " in "+ joinPoint.getSignature().getName() + "()");throw e;} }}
這是我們的applicationContext.xml應該包括的內容:
這是一個用于測試功能的獨立測試客戶端。
package com.springapp.aop;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringAOPClient
{public static void main(String[] args){ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");ArithmeticCalculator calculator = (ArithmeticCalculator) context.getBean("arithmeticCalculator");double sum = calculator.add(12, 23);System.out.println(sum);double div = calculator.div(1, 10);System.out.println(div);}}
所需的庫如下:
- Spring.jar(2.5.6或以上)
- commons-logging.jar
- aopalliance.jar
- Aspectjrt.jar
- Aspectjweaver.jar
- cglib-nodep-2.1_3.jar
我們可以使用注釋@ Before,@ AfterReturning,@ Around等來定義建議的類型。我們可以以不同的方式定義切入點。 例如:
@Around(“ execution(* *。*(..))”)表示這是一個“環繞”建議,將應用于所有包和所有方法中的所有類。
假設我們只想對com.myproj.services包中的所有服務應用建議。 然后切入點聲明將是:
@Around(“執行(* com.myproj.services。*。*(..))”)
在這種情況下,“(..)”表示帶有任何類型的參數。
如果我們想對許多建議應用相同的切入點,我們可以在方法上定義一個切入點,以后可以參考以下內容。
@Pointcut("execution(* *.*(..))")
protected void loggingOperation() {}@Before("loggingOperation()")
public void logJoinPoint(JoinPoint joinPoint){}
如果必須在同一切入點上應用多個建議,則可以使用@Order批注指定要在其上應用建議的訂單。 在前面的示例中,將首先應用@Before。 然后,在調用add()方法時將應用@Around。
就是這樣。 這是我們JCG合作伙伴之一的Siva提供的非常簡單的說明性教程。
快樂的AOP編碼。 別忘了分享!
相關文章:
- 使用Spring AspectJ和Maven進行面向方面的編程
- GWT 2 Spring 3 JPA 2 Hibernate 3.5教程– Eclipse和Maven 2展示
- GWT Spring和Hibernate進入數據網格世界
- 帶有Spring和Maven教程的JAX–WS
翻譯自: https://www.javacodegeeks.com/2011/01/aspect-oriented-programming-spring-aop.html