引例
若要求對方法進行日志記錄,而我們采取每個方法單獨寫日志代碼,将會導緻
原有業務急劇膨脹,每個方法處理必須兼顧多個關注點;并且導緻代碼分散,若
日志條件發生改變則必須修改所有子產品
為了解決這個問題,使用AOP程式設計(面向切面程式設計)
AOP術語:
切面(Aspect): 橫切關注點(跨越應用程式多個子產品的功能)被子產品化的特殊對象
通知(Advice): 切面必須要完成的工作
目标(Target): 被通知的對象
代理(Proxy): 向目标對象應用通知之後建立的對象
連接配接點(Joinpoint):程式執行的某個特定位置:如類某個方法調用前、調用後、
方法抛出異常後等。連接配接點由兩個資訊确定:方法表示的程式執行點;相對點表示
的方位。例如 ArithmethicCalculator#add() 方法執行前的連接配接點,執行點為
ArithmethicCalculator#add(); 方位為該方法執行前的位置
切點(pointcut):每個類都擁有多個連接配接點:例如 ArithmethicCalculator
的所有方法實際上都是連接配接點,即連接配接點是程式類中客觀存在的事務。AOP 通過切
點定位到特定的連接配接點。類比:連接配接點相當于資料庫中的記錄,切點相當于查詢條
件。切點和連接配接點不是一對一的關系,一個切點比對多個連接配接點,切點通過
org.springframework.aop.Pointcut 接口進行描述,它使用類和方法作為連接配接點的查詢條件。
實作方法
一.使用動态代理
好處:-每個事物邏輯位于一個位置,代碼不分散,便于維護和更新
-業務子產品更簡潔,隻包含核心業務代碼
方法執行需要進行日志記錄的類:算數類,實作的自定義接口中包含加減乘除四個方法
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
return i+j;
}
@Override
public int sub(int i, int j) {
return i-j;
}
@Override
public int mul(int i, int j) {
return i*j;
}
@Override
public int div(int i, int j) {
return i/j;
}
}
動态代理類:
public class ArithmeticCalculatorLoggingProxy {
private ArithmeticCalculator target;
public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
this.target = target;
}
public ArithmeticCalculator getLoggingProxy() {
ArithmeticCalculator proxy=null;
//代理對象由哪一個類加載器負責加載
ClassLoader loader=target.getClass().getClassLoader();
//代理對象的類型,即其中有哪些方法
Class [] interfaces = new Class[]{ArithmeticCalculator.class};
//當調用代理對象其中的方法時,該執行的代碼
InvocationHandler h=new InvocationHandler() {
/**
* proxy:正在傳回的那個代理對象,一般情況下,在invoke方法中都不使用該對象
使用該對象的方法會再次調用invoke,導緻死循環。
* method:正在被調用的方法
* args:調用方法時,傳入的參數
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName=method.getName();
//記錄日志
//執行方法
try {
//前置通知
System.out.println("The method"+methodName+" begins with "+ Arrays.asList(args));
Object result = method.invoke(target, args);
//傳回通知,此時可以通路傳回值
System.out.println("The method"+methodName+" ends with "+ result);
return result;
}catch (Exception e){
e.printStackTrace();
//異常通知,可以通路到方法出現的異常
}
System.out.println("The method"+methodName+" ends with ");
//後置通知,因為方法可能會出現異常,是以通路不到方法的傳回值
//記錄日志
return null;
}
};
proxy= (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);
return proxy;
}
}
spring配置檔案
<!--配置自動掃描的包-->
<context:component-scan base-package="com.itlc.spring.aop.impl"></context:component-scan>
<!--使Aspject注解起作用:自動為比對的類生成代理對象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
一.spring AOP
步驟:
1.加入jar包,在idea中還需要導入aopalliance-1.0.jar和aspectjweaver.jar
這兩個jar包下載下傳,密碼:iqc5
2.在配置檔案中加入aop的命名空間
3.基于注解的方式
(1).使Aspject注解起作用:自動為比對的類生成代理對象
(2).把橫切關注點的代碼抽象到切面的類中
1).切面首先是一個IOC中的bean,即加入@Component注解
2).切面還需要加入@Aspect注解
(3)在類中加入各種通知
1).申請一個方法
2).在方法前加入各種通知注解
使用方法為書寫完整方法,包括路徑與參數類型,用*表示上一個路徑中的所有檔案,也可用*代替
表示任意申請符,方法中參數的申請符,可以用..來代替所有
@Before: 前置通知, 在方法執行之前執行
@After: 後置通知, 在方法執行之後執行
@AfterRunning: 傳回通知, 在方法傳回結果之後執行
@AfterThrowing: 異常通知, 在方法抛出異常之後
@Around: 環繞通知, 圍繞着方法執行
通知内部綁定對應方法
例如:@Before("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(int ,int ))")
@Before("execution(public int com.itlc.spring.aop.impl.*.*(int ,int ))")
@Before("execution(* int com.itlc.spring.aop.impl.*.*(..))")
(4)可以在通知方法中聲明一個類型為JoinPoint的參數,然後就能通路連結細節,如方法名稱和參數值
(5)當有多個切面類綁定一個方法的時候,課用@Order(n),n為阿拉伯數字,表示切面執行的優先級,值越小優先級越高
(6) 在通知标簽裡中存在大量重複資訊
1)此時定義一個方法,用于作為切入點表達式,一般地,該方法再不需要添加其他的代碼
2)使用@Pointcut來聲明切入點表達式
3)後面的其他通知直接使用方法名來引用目前的切入點表達式
例:@Pointcut("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(.. ))")
public void declareJountPointExpression(){}
則通知的注解可改為:@Before("declareJountPointExpression()")
方法執行需要進行日志記錄的類:算數類,實作的自定義接口中包含加減乘除四個方法
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
return i+j;
}
@Override
public int sub(int i, int j) {
return i-j;
}
@Override
public int mul(int i, int j) {
return i*j;
}
@Override
public int div(int i, int j) {
return i/j;
}
}
切面類:
//把這個類聲明為一個切面:需要把該類放入IOC容器中,再聲明為一個切面
@Aspect
@Component
public class LoggingAspect {
/*
* 定義一個方法,用于作為切入點表達式,一般地,該方法再不需要添加其他的代碼
* 使用@Pointcut來聲明切入點表達式
* 後面的其他通知直接使用方法名來引用目前的切入點表達式
* */
@Pointcut("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(.. ))")
public void declareJountPointExpression(){}
/*聲明該方法是一個前置通知:在目标方法開始之前執行
使用方法為書寫完整方法,包括路徑與參數類型,用*表示上一個路徑中的所有檔案,也可用*代替
表示任意申請符*/
@Before("declareJountPointExpression()")
public void beforeMethod(JoinPoint joinPoint){
//傳入的參數,包括調用的方法名都在joinPoint這個對象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" begins with" + args);
}
//聲明該方法是一個後置通知:無論是否發生異常,在目标方法開始之後執行
//注意:不能在後置通知中通路目标方法執行的結果,因為方法可能出現異常
@After("declareJountPointExpression()")
public void afterMethod(JoinPoint joinPoint){
//傳入的參數,包括調用的方法名都在joinPoint這個對象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" ends");
}
/*
* 傳回通知
* 在方法正常結束受執行的代碼
* 傳回通知是可以通路到方法的傳回值的
* */
@AfterReturning(value="declareJountPointExpression()",returning = "result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
//傳入的參數,包括調用的方法名都在joinPoint這個對象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" ends with" + result);
}
/*
* 在目标方法方法出現異常時會執行的代碼
* 可以通路到異常對象;
* 且可以指定在出現特定異常時再執行通知代碼:将形參清單中的Exception換成特定的異常類名即可
* */
@AfterThrowing(value="declareJountPointExpression()",throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
//傳入的參數,包括調用的方法名都在joinPoint這個對象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" occurs exception" + ex);
}
/*
* 環繞通知需要攜帶ProceedingJoinPint
* 環繞通知類似于動态代理的全過程:ProceedingJoinPint 類型的參數可以決定是否執行目标方法
* 且環繞通知必須有傳回值,傳回值即為目标方法的傳回值
* 環繞通知功能最強,但并不是最常用的
* */
@Around("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjd){
Object result=null;
String methodName=pjd.getSignature().getName();
try {
//前置通知
System.out.println("The method "+methodName+" begins with" + Arrays.asList(pjd.getArgs()));
result=pjd.proceed();
//傳回通知
System.out.println("The method "+methodName+" ends with "+result);
} catch (Throwable throwable) {
//異常通知
System.out.println("The method "+methodName+" occur exception:"+throwable);
}
//後置通知
System.out.println("The method "+methodName+" ends");
return result;
}
}
spring配置:
<!--配置自動掃描的包-->
<context:component-scan base-package="com.itlc.spring.aop.impl"></context:component-scan>
<!--使Aspject注解起作用:自動為比對的類生成代理對象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
Main:
public class Main {
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
ArithmeticCalculator arithmeticCalculator=ctx.getBean(ArithmeticCalculator.class);
int result=arithmeticCalculator.add(,);
System.out.println(result);
result=arithmeticCalculator.div(,);
System.out.println(result);
}
}
三.通過配置檔案來實作AOP
原理類似切面類來實作,不過不用注解标注
方法執行需要進行日志記錄的類:算數類,實作的自定義接口中包含加減乘除四個方法
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
return i+j;
}
@Override
public int sub(int i, int j) {
return i-j;
}
@Override
public int mul(int i, int j) {
return i*j;
}
@Override
public int div(int i, int j) {
return i/j;
}
}
切面類1:
public class LoggingAspect {
public void beforeMethod(JoinPoint joinPoint){
//傳入的參數,包括調用的方法名都在joinPoint這個對象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" begins with" + args);
}
public void afterMethod(JoinPoint joinPoint){
//傳入的參數,包括調用的方法名都在joinPoint這個對象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" ends");
}
public void afterReturningMethod(JoinPoint joinPoint,Object result){
//傳入的參數,包括調用的方法名都在joinPoint這個對象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" ends with" + result);
}
public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
//傳入的參數,包括調用的方法名都在joinPoint這個對象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" occurs exception" + ex);
}
public Object aroundMethod(ProceedingJoinPoint pjd){
Object result=null;
String methodName=pjd.getSignature().getName();
try {
//前置通知
System.out.println("The method "+methodName+" begins with" + Arrays.asList(pjd.getArgs()));
result=pjd.proceed();
//傳回通知
System.out.println("The method "+methodName+" ends with "+result);
} catch (Throwable throwable) {
//異常通知
System.out.println("The method "+methodName+" occur exception:"+throwable);
}
//後置通知
System.out.println("The method "+methodName+" ends");
return result;
}
}
切面類2:
public class VlidationAspect {
public void beforeMethod(JoinPoint joinPoint){
//傳入的參數,包括調用的方法名都在joinPoint這個對象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("VlidationAspect--> The method "+methodName+" begins with" + args);
}
public void afterMethod(JoinPoint joinPoint){
//傳入的參數,包括調用的方法名都在joinPoint這個對象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("VlidationAspect--> The method "+methodName+" ends");
}
public void afterReturningMethod(JoinPoint joinPoint,Object result){
//傳入的參數,包括調用的方法名都在joinPoint這個對象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("VlidationAspect--> The method "+methodName+" ends with" + result);
}
public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
//傳入的參數,包括調用的方法名都在joinPoint這個對象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("VlidationAspect--> The method "+methodName+" occurs exception" + ex);
}
}
spring配置檔案
<!--配置bean-->
<bean id="arithmeticCalculator"
class="com.itlc.spring.aop.impl.ArithmeticCalculatorImpl"></bean>
<!--配置切面的bean-->
<bean id="LoggingAspect" class="com.itlc.spring.aop.impl.LoggingAspect"></bean>
<bean id="vlidationAspect" class="com.itlc.spring.aop.aopByBean.VlidationAspect"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切點表達式-->
<aop:pointcut id="pointcut" expression="execution(* com.itlc.spring.aop.impl.ArithmeticCalculator.*(..))"/>
<!--配置切面及通知-->
<aop:aspect ref="LoggingAspect" order="2">
<aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
<aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
<aop:after-returning method="afterReturningMethod" pointcut-ref="pointcut" returning="result"/>
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing>
<!--<aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>-->
</aop:aspect>
<aop:aspect ref="vlidationAspect" order="1">
<aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
</aop:aspect>
</aop:config>
Main:
public class Main {
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("aopByBean.xml");
ArithmeticCalculator arithmeticCalculator= (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
int result=arithmeticCalculator.add(,);
System.out.println(result);
result=arithmeticCalculator.div(,);
System.out.println(result);
}
}