接上篇beans部分,這部分說下aop的配置。
AOP(Aspect-Oriented Programming,面向切面編程):是一種新的方法論。是對傳統OOP的補充。
AOP的主要編程對象是切面(aspect),而切面模塊化橫切點。
在應用AOP編程時,仍需要定義公共功能,但可以明確的定義這個功能在哪裏,以什麼方式應用,並且不必修改受影響的類,這樣一來橫切關注點就被模塊化到特殊的獨享(切面)裏。
AOP的好處:
每個業務邏輯唯一一個位置,代碼不分散,便於維護和升級。
業務模塊簡潔,值包含核心業務代碼。
AOP術語:
切面(Aspect):橫切關注點(跨越應用程序多個模塊的功能)被模塊化的特殊對象。
通知(Advice):切面必須要完成的工作。
目標(Target):被通知的對象。
代理(Proxy):向目標對象應用通知之後創建的對象。
連接點(JoinPoint):程序執行的某個特定位置。如類某個方法調用前、調用後、方法拋出異常後等。連接點由兩個信息確定:方法表示的程序執行點;相對點表示的方位。例如ArithmeticCalculator#add()方法執行前的連接點:執行點爲ArithmeticCalculator#add()方位爲該方法執行前的位置。
切點(pointcut):每個類都擁有多個連接點。例如:ArithmeticCalculator的所有方法實際上都是連接點,即連接點是程序類中可觀存在的事物。AOP通過切點定位到特點的連接點。類比:連接點相當於數據庫中的記錄,切點相當於查詢條件。起點和連接點不是一對一的關係,一個切點匹配多個連接點,切點通過org.springframeword.Pointcut接口描述,它使用類和方法作爲連接點的查詢條件。
AspectJ:Java社區中最完整最流行的AOP框架。
在Spring中啓用AspectJ註解支持;
要在Spring應用中應用AspectJ註解,必須在classpath下包含AspectJ類庫:aopalliance.jar、aspectj.weaver.jar和Spring-aspects.jar.
將aop scheme添加到<beans>根元素中。
要在SpringIOC容器中啓用AspectJ註解支持,只要在Bean配置文件中定義一個空的XM元素<aop:aspectj-autoproxy>
當SpringIOC容器偵測到Bean配置文件中的<aop:aspectj-autoproxy>元素時,會自動與AspectJ切面匹配的Bean創建代理。
用AspectJ註解聲明切面
要在Spring中聲明AspectJ切面,只需要在IOC容器中將切面生面爲Bean實例,當在SpringIOC容器中初始化AspectJ切面之後,SpingIOC容器就會爲那些與AspectJ切面相匹配的Bean創建代理。
在AspectJ註解中。切面只是一個帶有@Aspect註解的Java類。
通知是標註有某種註解的簡單的java方法。
AspectJ支持5種類型的通知註解:
@Before:前置通知,在方法執行之前執行
@After:後置通知,在方法執行之後執行
@AfterRunning:返回通知,在方法返回結果之後執行
@AfterThrowing:異常通知,在方法拋出異常之後執行
@Around:環繞通知,圍着着方法執行
利用方法簽名編寫AspectJ切入點表達式
最典型的切入點表達式是根據犯法的簽名來匹配各種方法:
execution * com.atguigu.spring.ArithmeticCalculator.*(...):匹配ArithmeticCalculator中聲明的所有方法,第一個*表示任意修飾符機返回值。第二個*代表任意方法。...匹配任意數量的參數,若目標類與接口與接口與該切面在一個包中,可以省略包名。
execution public * com.atguigu.spring.ArithmeticCalculator.*(...):匹配ArithmeticCalculator接口中所有public的方法。
execution public double com.atguigu.spring.ArithmeticCalculator.*(...):匹配ArithmeticCalculator接口中返回double類型的方法
execution public double com.atguigu.spring.ArithmeticCalculator.*(.double, ..):匹配第一個參數爲double類型的方法,..匹配任意數量任意類型的參數。
execution public double com.atguigu.spring.ArithmeticCalculator.*(double, double):匹配參數類型爲double,double類型的方法。
在AspectJ中,切入點表達式可以通過操作符&&,||,!結合起來。
可以在通知方法中聲明一個類型爲JoinPoint的參數。然後就能訪問連接細節。如方法名和參數值。
對於環繞通知來說連接點的參數類型必須是ProceedingJoinPoint。他是JoinPoint的子接口,允許控制何時執行,是否執行連接點。
在環繞通知中,需要明確調用ProceedingJoinPoint的proceed()方法來執行被代理的方法,如果忘記這樣做就會導致通知被執行了,但是目標方法沒有被執行。
注意:環繞通知的方法需要返回目標方法執行之後的結果,即調用jointPoint.proceed()的返回值,否則會出現空指針異常。
指定切面優先級: 可以使用@Order註解來指定切面的優先級,值越小優先級越高
重用切入點定義:通過@Pointcut註解將一個切入點聲明稱簡單的方法,切入點的方法體通常是空的。切入點方法的訪問控制同時也控制着這個切入點的可見性。如果切入點要在多個切面找那個共用,最好將他們幾種在一個公共類中。在引入切入點時,必須將類名也包括在內,如果類沒有與這個切面放在同一個包中,還必須包含包名。
基於註解的aop示例:
public interface ArithmeticCalculator {
int add(int i ,int j);
int sub(int i,int j );
int mul(int i,int j);
int div (int i,int j);
}
@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
@Order(1)
@Component
@Aspect
public class ValidationAspect {
/*@Before("execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))")*/
@Before("LoggingAspect.declareJoinPoint()")
public void validate(JoinPoint joinPoint){
System.out.println("--> validation" + Arrays.asList(joinPoint.getArgs()));
}
}
package com.atguigu.spring.aop.impl;
import java.util.Arrays;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
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;
//把這個類聲明爲一個切面:首先把這個類放到IOC容器中、在聲明爲一個切面
//然後在考慮在哪個類的哪個方法執行前插入通知的方法。
/**
* 可以使用@Order註解來指定切面的優先級,值越小優先級越高
* @author adminitrator
*
*/
@Order(2)
@Component
@Aspect
public class LoggingAspect {
/**
* 定義一個方法,用於聲明切入點表達式,一般地,該方法中不需要添加其他代碼
* 使用@Pointcut註解來聲明切入點表達式
* 後面的其他同志直接使用方法名來引用當前的額切入點表達式。
*/
@Pointcut("execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))")
public void declareJoinPoint(){
}
//聲明該方法是一個前置通知:在目標方法之前執行
//註解中的* 表示ArithmeticCalculator接口下的所有方法
//註解參數中的 public int 也可也用* 來代替,表示任意修飾符,任意返回類型
@Before("declareJoinPoint()")
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(value = "declareJoinPoint()")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+ methodName+" begins with "+ args);
}
//在方法正常返回結束後執行的代碼
//放回通知是可以範文到方法的返回值的
@SuppressWarnings("unused")
@AfterReturning(value = "execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))",
returning="result")
public void afterReturning(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+ methodName+ " result-->" +result);
}
//異常通知
/**
* 在目標方法出現異常時,會執行代碼
* 可以方位到異常對象;可以指定出現特定異常時在執行通知。通諾throwing參數中異常,在方法參數中執行異常類型。
* @param joinPoint
* @param ex
*/
@SuppressWarnings("unused")
@AfterThrowing(value = "execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))",throwing="ex")
public void afterThrowing(JoinPoint joinPoint,Exception ex){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+ methodName +" occurs exception " + ex);
}
//環繞通知 就相當於動態代理
/**
* 環繞通知需要攜帶ProceedingJoinPoint類型參數
* 環繞通知類似於動態代理的全過程:ProceedingJoinPoint 類型的參數可以決定是否執行目標方法
* 且環繞通知必須有返回值,返回值即爲目標方法的返回值。
* @param joinPoint
*/
/* @Around(value = "execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))")
public Object aroundMethod(ProceedingJoinPoint pjd){
//執行目標方法
Object result = null;
try {
System.out.println("The method " + pjd.getSignature().getName()+ " begin " + Arrays.asList(pjd.getArgs()));
result = pjd.proceed();
//後置通知
System.out.println("The method " + pjd.getSignature().getName()+ " ends " +Arrays.asList(pjd.getArgs()));
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
//異常通知
System.out.println("The method " + pjd.getSignature().getName()+ " occurs " + e);
throw new RuntimeException(e);
}
//後置通知
System.out.println("The method " + pjd.getSignature().getName()+ " ends");
return result;
}*/
}
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 配置自動掃描的包 -->
<context:component-scan base-package="com.atguigu.spring.aop.impl" />
<!-- 這句話的作用是使我們在切面聲明的註解(如:before)起作用:在我調用目標方法跟before註解中配置的方法相匹配的時候,aop自動爲那個目標方法所在的類生成代理對象,在調用方法之前執行配置的方法。 -->
<!-- 使AspectJ註解起作用:自動爲匹配的類生成代理對象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
基於XML的AOP配置:
public interface ArithmeticCalculator {
int add(int i ,int j);
int sub(int i,int j );
int mul(int i,int j);
int div (int i,int j);
}
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
public class LoggingAspect {
public void declareJoinPoint() {
}
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) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method " + methodName + " begins with " + args);
}
@SuppressWarnings("unused")
public void afterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method " + methodName + " result-->" + result);
}
@SuppressWarnings("unused")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
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;
try {
System.out.println("The method " + pjd.getSignature().getName() + " begin " + Arrays.asList(pjd.getArgs()));
result = pjd.proceed();
// 後置通知
System.out.println("The method " + pjd.getSignature().getName() + " ends " + Arrays.asList(pjd.getArgs()));
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
// 異常通知
System.out.println("The method " + pjd.getSignature().getName() + " occurs " + e);
throw new RuntimeException(e);
}
// 後置通知
System.out.println("The method " + pjd.getSignature().getName() + " ends");
return result;
}
}
public class ValidationAspect {
public void validate(JoinPoint joinPoint){
System.out.println("--> validation" + Arrays.asList(joinPoint.getArgs()));
}
}
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- arithmeticCalculatorImpl 納入IOC容器管理 -->
<bean id="arithmeticCalculatorImpl" class="com.atguigu.spring.aop.xml.ArithmeticCalculatorImpl"></bean>
<!-- 配置切面的Bean -->
<bean id="loggingAspect" class="com.atguigu.spring.aop.xml.LoggingAspect"></bean>
<bean id="validationAspect" class="com.atguigu.spring.aop.xml.ValidationAspect"></bean>
<!--配置aop -->
<aop:config>
<!-- 配置切點表達式 -->
<aop:pointcut expression="execution(* com.atguigu.spring.aop.xml.ArithmeticCalculator.*(int, int))" id="pointcut"/>
<!-- 配置切面和通知 -->
<aop:aspect ref="loggingAspect" order="2" >
<!-- <aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/> -->
<aop:around method="aroundMethod" pointcut-ref="pointcut" />
</aop:aspect>
<aop:aspect ref="validationAspect" order="1">
<aop:before method="validate" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>