首先我們應該想想爲什麼要使用aop面向切面編程?面向切面的底層實現是什麼?小編在這裏舉個例子吧
小編首先給出Spring全家桶,方便大家下載使用---->Spring全家桶
1.自定義代理對象代理類以及實現類
1.1 定義接口(ArithmeticCacluetator)
public interface ArithmeticCacluetator {
/*
定義加減乘除四個方法
*/
public void add(int i , int j);
public void sub(int i , int j);
public void mul(int i , int j);
public void div(int i , int j);
}
1.2 創建實現類(ArithmeticCacluetatorImpl),並且這裏在每一個方法都輸出了操作日誌
public class ArithmeticCacluetatorImpl implements ArithmeticCacluetator {
@Override
public void add(int i, int j) {
System.out.println(" i : "+ i + " + " + " j : " + j + " = " + (i + j));
}
@Override
public void sub(int i, int j) {
System.out.println(" i : "+ i + " - " + " j : " + j + " = " + (i - j));
}
@Override
public void mul(int i, int j) {
System.out.println(" i : "+ i + " * " + " j : " + j + " = " + (i * j));
}
@Override
public void div(int i, int j) {
System.out.println(" i : " + i + " / " + " j : " + j + " = " + (i / j));
}
}
1.3 創建測試類:
ArithmeticCacluetator cacluetator = new ArithmeticCacluetatorImpl();
cacluetator.add(1,3);
cacluetator.sub(1,3);
cacluetator.mul(1,3);
cacluetator.div(1,3);
輸出結果:
i : 1 + j : 3 = 4
i : 1 - j : 3 = -2
i : 1 * j : 3 = 3
i : 1 / j : 3 = 0
不難發現,這樣一一輸出會十分的麻煩,每個方法裏面都要去寫一個輸出的日誌,這樣還會導致代碼實現類的可讀性很差,所以我們應當棄用這種寫法,而使用代理類來幫助我們實現
1.4 創建代理類(這裏默認大家有動態代理基礎哈)
public class ArithmeticCacluetatirProxy {
public static void main(String[] args) {
//真實對象
ArithmeticCacluetatorImpl arithmeticCacluetator = new ArithmeticCacluetatorImpl();
//代理對象
ArithmeticCacluetator arithmeticCacluetator1_proxy = (ArithmeticCacluetator) Proxy.newProxyInstance(arithmeticCacluetator.getClass().getClassLoader(), arithmeticCacluetator.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//這裏判斷只是用來模擬其他的不同的方法名,以及要執行的對應的日誌
if(method.getName().equals("add")){
System.out.println("Welcome to "+method.getName()+" Arithmeticacluetator !!!");
Object obj = method.invoke(arithmeticCacluetator, args);
return obj;
}else{
System.out.println("Welcome to AutoJugeMethod "+method.getName()+" Arithmeticacluetator !!!");
Object obj = method.invoke(arithmeticCacluetator, args);
return obj;
}
}
});
//這裏是通過代理類去調用方法
arithmeticCacluetator1_proxy.add(12,13);
arithmeticCacluetator1_proxy.sub(12,13);
arithmeticCacluetator1_proxy.mul(12,13);
arithmeticCacluetator1_proxy.div(12,13);
}
輸出結果:
method : add
Welcome to add Arithmeticacluetator !!!
i : 12 + j : 13 = 25
method : sub
Welcome to AutoJugeMethod sub Arithmeticacluetator !!!
i : 12 - j : 13 = -1
method : mul
Welcome to AutoJugeMethod mul Arithmeticacluetator !!!
i : 12 * j : 13 = 156
method : div
Welcome to AutoJugeMethod div Arithmeticacluetator !!!
i : 12 / j : 13 = 0
於是我們通過動態代理的形式,簡化了日誌的輸出,這種編程思想,就稱之爲面向切面編程(AOP)
2.AOP註解:
@Before | 前置通知,在方法執行之前執行 |
---|---|
@After | 後置通知,在方法執行之後執行 |
@AfterRunning; | 返回通知,在方法返回結果之後執行 |
@AfterThrowing | 異常通知,在方法拋出異常之後 |
@Around | 環繞通知,圍繞着方法執行 |
注意:要在Spring中聲明AspectJ切面,只需要在I0C容器中將切面聲明爲Bean實例.當在Spring I0C容器中初始化AspectJ切面之後,Spring I0C容器就會爲那些與AspectJ切面相匹配的Bean創建代理.
2.1 搭建基本環境(要聲明爲IOC容器再聲明切面纔有效)
2.1.1 創建LogginAspect 類,首先聲明爲IOC容器(@Componet),再次聲明爲切面(@Aspect)創建beforeInfo方法。
1.添加註解@Before(在方法執行之前執行 ),所以我們應該是最先看到這個方法裏面的內容輸出。
2.表達式:execution([方法可見性] xx(具體類全路徑).xx(方法) ) 標識在xx包下的xx方法(是否包含參數 如果是包含參數就寫出對應的參數類型【int , int …】 , 如果是任意參數就傳入 ‘*’[ . . ])
@Component
@Aspect
public class LogginAspect {
//參數:
//JoinPoint 切入點
@Before("execution(* cn.itcast.spring.aop.Impl.Service.ArithmeticCacluetatorImpl.*(..))")
public void beforeInfo(JoinPoint joinpoint) {
String name = joinpoint.getSignature().getName();//獲取方法名稱
Object[] args = joinpoint.getArgs();//獲取傳入的參數
System.out.println("before... " + " name : " + name);
System.out.println("args : " + Arrays.asList(args));
}
2.1.2 創建spring(bean-aop.xml)配置文件中配置掃描,並且開啓aop切面代理模式
<context:component-scan base-package="cn.itcast.spring.aop.Impl.services"/>
<aop:aspectj-autoproxy/>
2.1.3 編寫測試類AspectTestDemo
// 創建IOC
ApplicationContext context = new ClassPathXmlApplicationContext("bean-aop.xml");
ArithmeticCacluetator bean = context.getBean(ArithmeticCacluetator.class);
System.out.println(bean.getClass().getName()); //這裏獲取bean的類型
bean.add(121,131);
爲了清楚測試結果,小編將實現類(ArithmeticCacluetatorImpl)中日誌輸出全部都刪除了,現在測試的是add方法,所以小編在add方法中打印一句話
@Override
public void add(int i, int j) {
System.out.println("我是add方法");
}
@Override
public void sub(int i, int j) {
}
@Override
public void mul(int i, int j) {
}
@Override
public void div(int i, int j) {
}
輸出結果:
com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
不難發現,我們在方法中輸出的這句話是最後面才輸出的,所以@Before註解是在方法執行前調用的
2.2 @After後置通知註解配置使用
@After("execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetator.*(..))")
public void afterMethod(JoinPoint joinpoint) {
String name = joinpoint.getSignature().getName();
System.out.println("after... " + name);
}
然後再次跑一次測試類:輸出結果如下
com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
after… add
2.3 @AfterReturning返回值通知註解配置使用。
注意:
1.只有當沒有發生異常時,纔會執行此方法。2.添加returning = value 參數 用來接受返回值。3.在方法中聲明Object value 返回值
/**
只有當成功了纔會
@param joinpoint
@param result
*/
@AfterReturning(value = "execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetatorImpl.*(..))",returning = "result")
public void returnMethod(JoinPoint joinpoint,Object result) {
String name = joinpoint.getSignature().getName();
System.out.println("return... " + name + " : " + result);
}
調用測試方法:
輸出結果:
com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
after… add
return… add : null
因爲小編的add方法是void類型,所以這裏沒有返回值,返回的是null。這裏小編臨時性的將add方法的返回值改爲int類型,再次查看輸出結果如下:
@Override
public int add(int i, int j) {
System.out.println("我是add方法");
return i+j;
}
com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
after… add
return… add : 252
2.4 @AfterThrowing異常通知註解配置使用。
注意:1.只會在指定的異常出現時纔會觸發。2.定義throwing = value 設置異常參數名稱。3.在方法中聲明 (異常類型 value)這裏小編聲明的是空指針異常
/**
* 指定什麼異常 就在什麼異常發生時執行
* @param joinPoint
* @param ex
*/
@AfterThrowing(value = "execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetatorImpl.*(..))",
throwing = "ex")
public void throwException(JoinPoint joinPoint , NullPointerException ex){
System.out.println("ERROR ... " + ex);
}
2.4.1 修改實現類中的除法方法,自定義一個數學異常,來測試異常聲明
@Override
public void div(int i, int j) {
int c = i / j;
}
2.4.2 調用div方法,傳入參數【100,0】
輸出結果:
com.sun.proxy.$Proxy19
before… name : div
args : [100, 0]
after… div
Exception in thread “main” java.lang.ArithmeticException: / by zero
2.4.3 修改throwException方法中的異常聲明爲Exception
@AfterThrowing(value = "execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetatorImpl.*(..))",
throwing = "ex")
public void throwException(JoinPoint joinPoint , Exception ex){
System.out.println("ERROR ... " + ex);
}
輸出結果:
com.sun.proxy.$Proxy19
before… name : div
args : [100, 0]
after… div
ERROR … java.lang.ArithmeticException: / by zero
2.4.4 @Pointcut註解的使用。1.同類目錄下可直接引用註解。2.異類目錄下需要寫全路徑。3.好處:因爲小編剛纔演示的時候每一次都要寫一次表達式,並且表達式都是一模一樣的,這樣就寫了很多重複的代碼,做着同樣的事,避免麻煩,我們可以使用@Pointcut註解來聲明表達式,可以在需要的地方直接引用即可
@Pointcut("execution(* cn.itcast.spring.aop.Impl.Order.Service.ArithmeticCacluetator.*(..))")
public void declareExecutionExpression(){};
@Before("declareExecutionExpression()")
public void sayMehtod(){
System.out.println(" VildationAspect aop ... ");
}
2.4.5 異類下的的引用:
@Before("cn.itcast.spring.aop.Impl.Order.VildationAspect.declareExecutionExpression()")
public void beforeMethod(){
System.out.println("before Method ... ");
}
2.5 @Around環繞通知註解使用(基於動態代理 , 小編這裏單獨將環繞通知提取出來了,不會受到之前配置過的四個通知的影響)。注意:1.切入點應該聲明爲ProceedingJoinPoint。2.聲明返回值爲Object類型。
/**
可以獲取 String method;
@param jdp
@return
*/
@Around("execution(* cn.itcast.spring.aop.Impl.Around.Services.ArithmeticCacluetatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint jdp){
Object result = null;
String name = jdp.getSignature().getName();
try {
//前置通知
System.out.println("Around Before ... getArgs ... " + Arrays.asList(jdp.getArgs()));
result = jdp.proceed(); //method.invoke(); 代理對象調用
//後置通知
System.out.println("Around After .... " + name);
//返回通知
System.out.println("Around AfterReturing ... " + result);
}catch (Throwable e){
//出現異常,這裏不選擇手動拋出,用控制檯打印
System.out.println("The Method : " + name +"Error Appentend ... " + e);
}
return result;
}
2.5.1 調用實現類中的div方法
bean.div(10,0);
輸出結果:
Around Before … getArgs … [10, 0]
The Method : divError Appentend … java.lang.ArithmeticException: / by zero
這裏出現了異常,僅僅只執行了前置通知,後面的通知都沒有被執行,就直接走進catch中去了
2.5.2 在接口中添加public String show(String name)方法,實現類實現
@Override
public String show(String name) {
System.out.println(name+" comming...");
return name;
}
2.5.3 執行show(“Audi”)方法
測試結果如下:
Around Before … getArgs … [Audi]
Audi comming…
Around After … show
Around AfterReturing … Audi
3.AOP 使用XML形式實現:
3.1 因爲是在XML中聲明使用,所以我們便不需要再添加註解。創建spring配置文件使用(bean-application-xml.xml)這裏需要用到aop,context標籤,將這段複製到XML就可以使用了
<?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:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
</beans>
3.2 聲明所有使用到的Bean
<!-- 配置使用到的bean -->
<bean id="logginAspect" class="cn.itcast.spring.aop_xml.LogginAspect"/>
<bean id="vildationAspect" class="cn.itcast.spring.aop_xml.VildationAspect"/>
<bean id="arithmeticCacluetator" class="cn.itcast.spring.aop_xml.Service.ArithmeticCacluetatorImpl"></bean>
<bean id="logginAround" class="cn.itcast.spring.aop.Impl.Around.LogginAround"></bean>
3.3 切面類LogginAspect
public class LogginAspect {
public void beforeInfo(JoinPoint joinpoint) {
String name = joinpoint.getSignature().getName();
Object[] args = joinpoint.getArgs();
System.out.println("before... " + " name : " + name);
System.out.println("args : " + Arrays.asList(args));
}
public void afterMethod(JoinPoint joinpoint) {
String name = joinpoint.getSignature().getName();
System.out.println("after... " + name);
}
public void returnMethod(JoinPoint joinpoint,Object result) {
String name = joinpoint.getSignature().getName();
System.out.println("return... " + name + " : " + result);
}
public void throwException(JoinPoint joinPoint , NullPointerException ex){
System.out.println("ERROR ... " + ex);
}
}
3.4 VildationAspect類(設置了Order屬性,方法是最先被執行的)
public void sayMehtod(){
System.out.println(" VildationAspect aop ... ");
}
3.5 配置程序一致:
<aop:config>
<!--
配置的基本邏輯:
1.配置pointcut 公共指向
2.配置Aspect 註解 ref - 涉及到的類
3.配置 [ 前置 , 後置 , 返回值 , 拋出異常 , 環繞通知]
-->
<aop:pointcut id="declearExpression" expression="execution(* cn.itcast.spring.aop_xml.Service.ArithmeticCacluetator.*(..))"></aop:pointcut>
<!-- 切面作用在哪個類上 相當於註解@Aspect -->
<aop:aspect ref="logginAspect">
<!-- 基本配置 -->
<aop:before method="beforeInfo" pointcut-ref="declearExpression"/>
<aop:after method="afterMethod" pointcut-ref="declearExpression"/>
<!-- 這裏注意參數一定要對應 -->
<aop:after-returning method="returnMethod" pointcut-ref="declearExpression" returning="result"/>
<aop:after-throwing method="throwException" pointcut-ref="declearExpression" throwing="ex"/>
</aop:aspect>
<!-- 配置優先級別 -->
<aop:aspect ref="vildationAspect" order="1">
<aop:before method="sayMehtod" pointcut-ref="declearExpression"/>
</aop:aspect>
<aop:aspect ref="logginAround">
<aop:around method="aroundMethod" pointcut-ref="declearExpression"></aop:around>
</aop:aspect>
</aop:config>
3.6 測試類:
ArithmeticCacluetator bean = (ArithmeticCacluetator) context.getBean("arithmeticCacluetator");
System.out.println(bean);
int result = bean.add(12, 3);//返回值爲int類型
System.out.println("result : " + result);
輸出結果:
VildationAspect aop … 設置了Order屬性,所以sayMethod方法最先被執行
Around Before … getArgs … [12, 3] //前置通知
Around After … add // 後置通知
Around AfterReturing … 15 //返回值通知
result : 15 //自己打印的返回值
好了,這裏就是AOP面向切面編程的內容了,雖然有點抽象,但是相信通過不斷地學習,可以不斷地去了解它