1. 程式人生 > >D42-Spring(AOP)

D42-Spring(AOP)

1.回顧AOP底層

jdk動態代理
cglib的動態代理

2.AOP的概述

AOP術語

3.AOP的配置

全xml的配置
半xml,半註解
純註解的配配置

4.使用spring的AOP技術,做轉賬的事務增強。—宣告式事務的底層

一、動態代理:在不改變原始碼的情況下對方法增強

  1. jdk動態代理(基於介面)
    • 條件: 要有介面
    • 生成的動態代理物件和被代理物件是兄弟關係
    • 工具類-Proxy
public class
Demo_JDK { public static void main(String[] args) { final AccountService accountService = new AccountServiceImpl(); //String value = accountService.save("aaaaa"); //System.out.println(value); // 在不修改方法原始碼的基礎上,對方法進行增強 ---save() // 動態代理:JDK提供的動態代理 CGLIB提供的動態代理 // JDK提供的動態代理
// 引數1: 和目標物件一樣的類載入器 // 引數2:和目標物件一樣的介面 AccountService proxy =(AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), new Class[]{AccountService.class}, // 匿名內部類 new InvocationHandler() { // 增強業務 代理物件呼叫方法的時候就執行 呼叫一次執行一次
// 引數1 :代理物件的引用 (謹慎用) // 引數2 : 目標方法(save) // 引數3: 目標方法執行過程中需要的引數(aaaa) public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("save".equals(method.getName())){ try { // 增強 System.out.println("之前增強。。。"); // 呼叫原方法 // 引數1:本身應該執行原方法的物件 // 引數2:原方法需要的引數 Object value = method.invoke( accountService, args); //原來方法 System.out.println(value); System.out.println("之後增強。。。"); }catch (Exception e){ System.out.println("異常增強..."); }finally { System.out.println("最終增強...."); } // invoke是誰呼叫的就返回給誰內容 return "我知道是你代理物件呼叫的,我把資料返回給你"; }else { // 呼叫你原來的方法 我不給你增強 method.invoke(accountService, args); return null; } } }); // 代理物件呼叫方法 String value=proxy.save("aaaaa"); //只要代理物件呼叫方法 invoke就會 //執行 invoke執行的是對呼叫方法(save)的增強 System.out.println(value); //proxy.delete(); } }
  1. cglib的動態代理(基於類)
  • 代理物件與物件的 關係-------父子關係
  • 條件:
    • 目標物件的類不需要介面但也不能被final修飾
    • 匯入cglib的jar包—通過工具類-----Enhancer生成的動態代理物件是被代理物件的子類。
public class Demo_CGLIB {
    public static void main(final String[] args) {
        final AccountServiceImpl2 accountServiceImpl2 = new 
        							AccountServiceImpl2();
        //String value = accountServiceImpl2.save("bbbbb");
        //System.out.println(value);
        // 不改變方法原始碼的前提下,對 save 進行增強 --動態代理
        // 第三方提供的動態代理--cglib動態代理
        //cglib動態代理:目標物件不需要介面,也可以做增強
        // 工具類--Enhancer

        //引數1:目標物件的位元組碼檔案
        //引數2:增強業務類  InvocationHandler===MethodInterceptor
        AccountServiceImpl2 proxy=(AccountServiceImpl2)Enhancer.create(
        							accountServiceImpl2.getClass(),
                // 匿名內部類
                new MethodInterceptor() {
                    // 引數1 :代理物件的引用 (謹慎用)
                    // 引數2 : 目標方法(save)
                    // 引數3:  目標方法執行過程中需要的引數(aaaa)
                    // 引數4:方法物件的代理物件
                    public Object intercept(Object o, Method method, 
                    		Object[] objects, MethodProxy methodProxy) 
                    									throws Throwable {

                        if("save".equals(method.getName())){

                            try {
                                // 增強
                                System.out.println("之前增強。。。");
                                // 呼叫原方法
                                // 引數1:本身應該執行原方法的物件
                                // 引數2:原方法需要的引數
                                Object value = method.invoke
                                		(accountServiceImpl2, objects); //原來方法
                                System.out.println(value);

                                System.out.println("之後增強。。。");

                            }catch (Exception e){

                                System.out.println("異常增強...");

                            }finally {
                                System.out.println("最終增強....");
                            }

                            // invoke是誰呼叫的就返回給誰內容
                            return "我知道是你代理物件呼叫的,我把資料返回給你";

                        }else
                        {
                            // 呼叫你原來的方法 我不給你增強
                            method.invoke(accountServiceImpl2, objects);
                            return null;
                        }

                    }
                });

        String value = proxy.save("bbb");
        System.out.println(value);
    }
}

  1. AOP的底層會自動選擇(有介面,選jdk)

二、 AOP的概述

2.1 AOP:面向切面程式設計

  1. 作用: 在不修改原始碼的基礎上,對方法進行增強
  • 底層:jdk動態代理,cglib動態代理(自動抉擇)
  1. 關注點1:自己建立的方法---- AOP做好了事務的方法
  2. 關注點2:告訴spring需要在哪些方法上使用增強方法-用動態代理
  3. 一句話:自己寫增強方法,然後在配置檔案中告訴spring,自己在哪些方法上進行了增強
    • spring:自動使用動態代理技術實現方法的增強

2.2 AOP的相關術語

  1. Target(目標物件): 要增強的物件
  2. Proxy(代理物件):對目標物件的增強封裝
  3. JoinPoint(連線點):目標物件的所有方法
  4. Advice(通知/增強):增強的那段程式碼方法
    4.1. 一個通知就是一個增強方法
  • 前置通知:在切入點(要被增強的方法)之前的增強方法
  • 後置通知:在切入點(要被增強的方法)之後的增強方法
  • 議程通知:在切入點(要被增強的方法)發生異常執行的增強方法
  • 最終通知:在切入點(要被增強的方法)執行完畢的增強方法
  • 環繞通知:代替上面四個
  1. (Aspect)切面 :切入點+通知 = 切面
    目標方法和增強方法合成在一起
  2. Weaving(織入): 將切入點整合到切面的這個過程
    底層是用到動態代理

三、AOP的配置

3.1 AOP的xml方式:

  • 匯入AOP的座標
  • 定義一個類,自己在類中編寫增強方法----ioc管理這個切面類。
  • 通過配置檔案告訴spring使用自己的增強方法在哪些方法上做前置,後置,異常,最終增強。

3.2 基於xml的配置

1. 宣告AOP配置
< aop:config>…</aop:config>

  • 配置切入點:
  • id:切入點的唯一標識
    • expression:切入點表示式
    • 完整寫法:execution(方法的修飾符 方法的返回值 類的全限定名.方法名(引數))
<aop:pointcut id="pt" expression="execution(* com.itheima..AccountServiceImpl.save(..))">
</aop:pointcut>
<!-- 支援萬用字元的寫法:
     *   : 標識任意字串
      ..  : 任意重複次數
      1. 方法的修飾符可以省略:void cn.itcast.service.impl.AccountServiceImpl.saveAccount()
      2. 返回值可以使用*號代替:標識任意返回值型別
               * cn.itcast.service.impl.AccountServiceImpl.saveAccount()
     3. 包名可以使用*號代替,代表任意包(一個包使用一個*)
     4. 使用..配置包名,標識此包以及此包下的所有子包
     5. 類名可以使用*號代替,標識任意類
     6. 方法名可以使用*號代替,表示任意方法
     7. 可以使用..配置引數,任意引數
-->

2. 配置切面

  • ref:切面類的唯一標識
<aop:aspect ref="logger">
  1. 配置通知型別:前置,後置,異常,最終,環繞通知
  • method:切面類中的方法
  • pointcut-ref:切入點唯一標識
<aop:before method="before" pointcut-ref="pt"></aop:before>
//此為前置通知,後置,異常,最終通知類似
  • 環繞通知
<aop:around method="around" pointcut-ref="pt"></aop:around>

4. 測試類

@RunWith(value = SpringJUnit4ClassRunner.class)  
//宣告spring提供的單元測試環境
@ContextConfiguration(locations = "classpath:bean.xml") 
//宣告spring的配置資訊+
public class SpringJunit {

    @Autowired  //預設按照 型別(介面)從容器中查詢物件並注入
                 //如果該容器中有多個該介面的物件
    private AccountService accountService;

    @Test
    public void t1(){
        accountService.save();
    }
}

3.3 基於註解結合XML的方式

  • 半註解: 自己的資源
  • 半XML: 第三方的資源
  1. 註解:
  • IOC的註解
  • AOP的註解
  1. 開啟註解們的支援
  • IOC包掃描
  • 開啟對AOP註解的支援
      • 找切面類
      • 在切面類的通知上配置切入點表示式

3. 註解:

  • @Aspect: 宣告切面類
  • @PointCut: 定義工作的切入點。
      • 配置到空方法上,value:切入點表示式
      • 引用:方法名()
    • 配置通知型別:
      • @Before:前置通知
      • @AfterReturnint : 後置通知
      • @AfterThrowing :異常通知
      • @After :最終通知
      • @Around :環繞通知
  1. 步驟(在bean.xml中):
  • 開啟ioc掃描器
<!--開啟ioc掃描類-->
<context:component-scan base-package="com.itheima">
</context:component-scan>
  • 到Service層
@Service(value = "accountService")
public class AccountServiceImpl implements AccountService {
    //切入點
    public void save() {
        System.out.println("save...");
    }
 }
  • 開啟AOP註解掃描器
<!--開啟AOP掃描類-->
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
    • 找切面類
    • 用註解的方式配置前置,後置異常等.
//切面類---裡面都是增強方法(通知)
@Component(value = "myaspect")
@Aspect//聲明當前類為切面類
public class MyAspect {
    /**
     * 使用註解的方式把表示式抽取出來
     *
     * 要求:
     *  1.需要有一個無參無返回值無內容的方法
     *  2.在該方法上添加註解@Pointcut
     *  3.使用:誰用誰呼叫方法名
     */
/*
* Pointcut:定義為公共的切入點,配置到空方法上,value:切入點表示式
* 引用:方法名
* */
    @Pointcut(value = "execution(* com.itheima..AccountServiceImpl.save(..))")
    public void aaa(){
    }
    //註解的方式前置通知
    //@Before(value = "execution(* com.itheima..AccountServiceImpl.save(..))")
   // @Before(value = "aaa()")
    public void before() {
        System.out.println("前置通知。。");
    }
     //環繞通知.
  //  @Around(value = "aaa()")
    @Around(value = "execution(* com.itheima..AccountServiceImpl.save(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) {

        try {
            //前置通知
            System.out.println("前置通知。。");
            //原方法執行
            proceedingJoinPoint.proceed();//invoke
            //後置通知
            System.out.println("後置通知...");
        } catch (Throwable throwable) {
            //異常通知
            System.out.println("異常通知....");
        } finally {
            //最終通知
            System.out.println("最終通知");
        }
    }
}
  1. 執行順序:
  • 測試層
@RunWith(value = SpringJUnit4ClassRunner.class)  
//宣告spring提供的單元測試環境
@ContextConfiguration(locations = "classpath:bean.xml") 
//宣告spring的配置資訊+
public class SpringJunit {
    @Autowired  //預設按照 型別(介面)從容器中查詢物件並注入
                 //如果該容器中有多個該介面的物件
    private AccountService accountService;
    @Test
    public void t1(){
        accountService.save();
    }
}

  • 載入 配置檔案,然後按照第4步執行。

3.4 純註解的方式

  • @EnableAspectJAutoProxy : 開啟對AOP註解的支援
  1. 不同點:
  • 在測試類中,載入配置類資訊。
@RunWith(value = SpringJUnit4ClassRunner.class) 
 //宣告spring提供的單元測試環境
@ContextConfiguration(classes = SpringConfig.class)
 //宣告spring的配置資訊+
public class SpringJunit {
    @Autowired  //預設按照 型別(介面)從容器中查詢物件並注入
                 //如果該容器中有多個該介面的物件
    private AccountService accountService;
    @Test
    public void t1(){
        accountService.save();
    }
}
  • 用配置類代替配置檔案bean.xml
    • 在配置類中開啟ioc,aop掃描
@ComponentScan(basePackages = "com.itheima")//開啟ioc掃描類
@EnableAspectJAutoProxy//開啟aop掃描類
public class SpringConfig {
}

其他地方與半xml半註解方式一致。