天天看點

Spring(三)spring核心技術——aop

一、AOP簡介

  AOP是Aspect Orient Programming的縮寫,即面向切面程式設計。基于動态代理的,可以使用jdk和cglib兩種代理方式。

  • Aspect: 切面,給你的目标類增加的功能,就是切面。像上面用的日志,事務都是切面。切面的特點:一般都是非業務方法,獨立使用的。
  • Orient:面向,對着。
  • Programming:程式設計

  AOP就是動态代理的規範化,把動态代理的實作步驟,方式都定義好了,讓開發人員用一種統一的方式,使用動态代理。

作用:

  • 在目标類不修改的情況下增加功能
  • 減少代碼的重複
  • 使開發人員專注業務功能的實作
  • 解耦合:業務功能和日志、事務等非業務功能的耦合

二、動态代理的實作方式

  • jdk動态代理

  使用jdk中的Proxy,Method,InvocaitonHanderl建立代理對象。jdk動态代理要求目标類必須實作接口

  • cglib動态代理

  第三方的工具庫,建立代理對象,原理是繼承。 通過繼承目标類,建立子類。子類就是代理對象。要求目标類不能是final的,方法也不能是final的。

三、AOP中名詞概念

  • aspect(切面):表示給業務方法增加的功能,一般為體制輸出、事務、權限檢查等
  • JoinPoint:連接配接點,連接配接業務方法和切面的位置,就某類中的業務方法
  • pointcut(切入點):是一個或多個JoinPoint的集合,表示切面功能執行的位置
  • 目标對象:給哪個類的方法增加功能,這個類就是目标對象
  • advice(通知):也叫增強。表示切面執行的時間,在方法前或後

四、何時使用AOP

  • 某項目功能類不完善,需要增加功能,但是沒有源代碼
  • 給項目的多個類需要增加相同功能
  • 為業務功能增加事務、日志輸出

五、AOP的實作

1、Spring

  Spring在内部實作了aop規範,能做aop的工作。spring主要在事務處理時使用aop,在項目開發中很少使用spring的aop實作,因為spring的aop比較笨重。

2、使用Aspectj架構實作AOP

  一個開源的專門做aop的架構。spring架構中內建了aspectj架構,通過spring就能使用aspectj的功能。

aspectJ架構實作aop有兩種方式:

1)使用xml的配置檔案 : 配置全局事務

2)使用注解,項目中要做aop功能,一般都使用注解

  • 使用相應注解确定切面執行的時間
  • 使用切面表達式确定切面執行的位置

  切面位置的切入點表達式:execution(修飾符 傳回值 包名.類名.方法名(方法參數) 異常)。用來指定切面執行的位置。

aspectj中常用注解如下:

  1. @Aspect:聲明該類是切面類
/*
*   @Aspect:是Aspectj架構中的注解
*       作用:聲明該類是切面類
*       切面類:為目标類增加功能的類,包含切面的功能代碼
*       位置:在類的上面
* */
@Aspect
public class MyAspect {}
           
  1. @Before:前置通知,在目标方法之前執行切面的功能
@Aspect
public class MyAspect {
    /*
    *   定義方法,是實作切面功能的方法
    *     方法定義要求:
    *       1、方法時公共的
    *       2、方法名自定義
    *       3、方法沒有傳回值
    *       4、可以有參數,也可以無參數
    *           如果有參數,參數不是自定義的
    *   @Before:前置通知注解
    *       屬性:value,是切入點表達式,表明切面的功能執行的位置
    *       位置:在方法的上面
    *   特點:
    *       1、在目标方法前執行
    *       2、不會改變和影響目标方法的執行
    * */
    @Before(value = "execution(public void cn.krain.ba01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(){
        System.out.println("前置通知,切面功能,在目标方法執行前輸出執行的時間:"+new Date());
    }
           
  1. @AfterReturning:後置通知,在目标方法之後執行切面的功能,能擷取傳回值
@Aspect
public class MyAspect {
    /*
    *   @AfterReturning定義方法,是實作切面功能的方法
    *     方法定義要求:
    *       1、方法時公共的
    *       2、方法名自定義
    *       3、方法沒有傳回值
    *       4、方法有參數
    *   @AfterReturning:後置通知
    *       屬性:1、value:切入點表達式
    *             2、returning:自定義變量,目标方法的傳回值
    *                           自定義變量名稱和通知方法的形參名一緻
    *       位置:在方法定義的上面
    *   特點:
    *   1、在目标方法之後執行
    *   2、能擷取目标方法的傳回值
    *   3、可修改這個傳回值
    * */
    @AfterReturning(value = "execution(* cn.krain.ba02.SomeServiceImpl.doOther(..))",
                    returning="res")
    public void myAfterReturning(Object res){
        System.out.println("後置通知,切面功能,在目标方法執行前輸出執行的時間:"+res);
        if (res!=null){
            res = "Hello";
        }
    }
}
           
  1. @Around:環繞通知,能在目标方法前後增強功能,能夠控制目标方法的執行,修改傳回值
@Aspect
public class MyAspect {
    /*
    *   環繞通知的定義格式
    *     方法定義要求:
    *       1、方法時公共的
    *       2、方法名自定義
    *       3、必須有傳回值
    *       4、方法有固定的參數:proceedingJoinPoint
    *   @Around:環繞通知
    *       屬性:value 切入點表達式
    *       位置:在方法的定義上面
    *   特點:
    *       1、是功能最強的通知
    *       2、能夠在目标方法前後增強功能
    *       3、控制目标方法是否被執行
    *       4、修改原來目标方法的執行結果,影響最後的調用結果
    * */
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))" )
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {

        //擷取第一個參數的值
        String name = null;
        Object args [] = joinPoint.getArgs();
        if (args!=null && args.length>=1){
            Object arg = args[0];
            name = (String) arg;
        }

        Object result = null;

        //實作環繞通知
        System.out.println("環繞通知,在目标方法之前,輸出時間:"+new Date());

        //1、目标方法調用
        //控制目标方法的執行
        if (name.equals("張三")){
            result = joinPoint.proceed();       //等同于執行目标方法
        }

        System.out.println("環繞通知,在目标方法之後,送出事務");

        //修改目标函數傳回結果
        if (result!=null){
            result = "Hello Aop";
        }
        return result;
    }
}
           
  1. @AfterThrowing:異常通知,在目标方法抛出異常後執行
@Aspect
public class MyAspect {
    /*  異常通知的定義格式
    *     方法定義要求:
    *       1、方法時公共的
    *       2、方法名自定義
    *       3、方法沒有傳回值
    *       4、固定參數Exception,如果還有參數則是:JoinPoint
    *       5、throwing的值要與Exception的參數名相同
    *   @AfterThrowing:異常通知
    *       屬性:1、value 切入點表達式
    *            2、throwing 自定義變量,表示目标方法抛出的異常對象
    *                       如果有異常,通過郵件、短信通知
    *   特點:
    *       1、在目标方法出現異常時執行
    *       2、監控目标方法是否存在異常
    *
    *   執行過程:
    *       try{
    *           SomeServiceImpl.doSecond();
    *       }catch(Exception e){
    *           myAfterThrowing(e);
    *       }
    * */
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond())",
                    throwing = "ex")
    public void myAfterThrowing(Exception ex){
        System.out.println("異常通知,方法執行異常,執行:"+ex.getMessage());
        //發送短信
    }
}
           
  1. @After:最終通知,總是會執行
@Aspect
public class MyAspect {
    /*  最終通知的定義格式
    *   方法定義要求:
    *       1、方法時公共的
    *       2、方法名自定義
    *       3、方法沒有傳回值
    *       4、沒有參數,如果有參數則是:JoinPoint
    * */

    /*
    *   @After  最終通知
    *       屬性:value  切入表達式
    *       位置:在方法的上面
    *
    *   特點:
    *       1、總是會執行
    *       2、在目标方法之後執行
    *
    *   執行過程:
    *       try{
    *           //目标方法
    *       }catch(){
    *
    *       }finally{
    *           myAfter();
    *       }
    * */
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter(){
        System.out.println("執行最終通知,總是會被執行的代碼");
    }
}
           
  1. @Pointcut:定義和管理切入點的輔助注解
@Aspect
public class MyAspect {
    @After(value = "myPT()")
    public void myAfter(){
        System.out.println("執行最終通知,總是會被執行的代碼");
    }

    @Before(value = "myPT()")
    public void myBefore(){
        System.out.println("執行前置通知");
    }
    /*
    *   @Pointcut:定義和管理切入點表達式,如果項目中的切入點表達式是重複的,可使用Pointcut
    *       屬性:value  切入點表達式
    *       位置:在自定義方法上面
    *   為切入點表達式定義别名,使用"方法名稱()"代替切入點表達式
    * */
    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
    private void myPT(){
        //無功能代碼
    }
}
           
  • 編寫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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--把對象交給spring,有spring統一建立和管理-->
    <!--建立SomeService目标對象-->
    <bean name="someService" class="cn.krain.ba.SomeServiceImpl" />

    <!--建立aspect切面對象-->
    <bean name="myAspect" class="cn.krain.ba.MyAspect" />

    <!--
        聲明自動代理生成器:使用aspectj的内部功能,建立目标對象的代理對象。
        建立代理對象是在記憶體中實作的,修改目标對象在記憶體中的結構,建立成代理對象
        是以,目标對象就是被修改後的代理對象
    -->
    <aop:aspectj-autoproxy />

    <!--
        如果項目中有接口時,仍希望使用CGHLIB動态代理
        proxy-target-class="true":告訴架構使用cglib動态代理
    -->
    <aop:aspectj-autoproxy proxy-target-class="true" />
</beans>
           
  • 測試類MyTest.java
public class MyTest {
    public void Test(){
        String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //根據對象名擷取代理對象
        SomeService proxy = (SomeService) ac.getBean("someService");
        //proxy."目标方法名"();
    }
}