1. 程式人生 > >Spring 面向切面程式設計

Spring 面向切面程式設計

AOP 代理設定模式

概述:

  • 所謂代理,就是一個人或某機構代表一個人或某機構採取行動。在一些情況下,客戶不想或不能直接引用一個物件,而代理物件可以在客戶端和目標物件之間起到中間的作用。
  • 通俗講:
    - 經紀人和藝人的關係,經紀人便是代理,藝人就是目標使用者,藝人不想做的(安裝裝置,舞臺等),由經紀人去聯絡處理,這樣的模式就是代理模式。

JDK: 靜態代理 動態代理 cglib 生成的動態代理

  • 靜態代理
    - 缺點:擴充套件性差、每次生成新的目標物件類,即使一樣,也要重新生成一個代理物件
    - 優點:好理解,程式碼清晰,不復雜

AOP的原始碼中用到了兩種動態代理來實現攔截切入功能

  • jdk動態代理和cglib動態代理。
    - jdk動態代理是由java內部的反射機制來實現的,cglib動態代理底層則是藉助asm來實現的。
    - 總的來說,反射機制在生成類的過程中比較高效,而asm在生成類之後的相關執行過程中比較高效(可以通過將asm生成的類進行快取,這樣解決asm生成類過程低效問題)。

定義一個介面

public interface Singer {
    //唱歌
    public void singing();
    //跳舞
    public void dancing();
}

實現介面的類

public class ZhangJie
implements Singer{ @Override public void singing() { System.out.println("正在唱歌"); } @Override public void dancing() { System.out.println("正在跳舞"); } }

靜態代理

		//首先要例項化的物件是張傑(也就是我們的目標物件)
        Singer singer = new ZhangJie();//多型/向上轉型

        //例項化代理物件來呼叫其方法(在呼叫目標物件前後做相關操作)
ProxyWang proxyWang = new ProxyWang(singer); proxyWang.singing();

動態代理

		//目標物件
        Singer singer = new ZhangJie();
        //jdk提供了這樣一個類,代表代理的意思
        //第一個引數是類載入器
        //第二個引數是目標物件的class
        //第三個引數是代理物件
        //返回值型別為Object
        Singer s = (Singer) Proxy.newProxyInstance(singer.getClass().getClassLoader(),new Class[]{Singer.class},new ProxyAi(singer));
        s.singing();

cglib 生成的動態代理

public class CglibProxy implements MethodInterceptor {

    //例項化我們的目標物件
    private Object obj;

    public Object getObj(Object obj) {
        this.obj = obj;
        //得到建立代理物件的物件
        Enhancer enhancer = new Enhancer();
        //設定類載入器
        enhancer.setClassLoader(this.obj.getClass().getClassLoader());
        //設定其父類
        enhancer.setSuperclass(this.obj.getClass());
        //設定回撥,只要走下面這方法都會走回調
        enhancer.setCallback(this);
        //建立代理物件
        return enhancer.create();

    }

    //這個方法就是在你待用目標物件的方法都會執行這個方法
    //第一個引數是代理物件/第二個引數是目標物件的方法/第三個引數是目標物件的方法引數/第四個引數是代理物件的攔截方法
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object ob = null;
      if (method.getName().equals("singing")){
           System.out.println("裝置已經安裝成功");
           ob = method.invoke(obj, objects);
           System.out.println("唱歌結束");
       }else if (method.getName().equals("dancing")) {
           System.out.println("舞臺已經搭建成功,show time");
           ob = method.invoke(obj, objects);
           System.out.println("舞會結束");
       }
        return ob;
    }
}
 		//例項化一個cglibProxy 代理物件類
        CglibProxy cglibProxy = new CglibProxy();
        Singer singer = new ZhangJie();
        //這個引數代表我們的目標物件
        Singer singer1 = (Singer) cglibProxy.getObj(singer);
        singer1.dancing();

Spring AOP的配置元素

在這裡插入圖片描述

設定的步驟

spring 要配置的bean

<bean id="aopError" class="com.offcn.aop.AopError"></bean>
public class AopError {
    public void afterError(JoinPoint joinPoint,RuntimeException e){
        System.out.println(joinPoint.getSignature().getName()+"這個方法的異常資訊"+e.getMessage());
    }

    public void after(JoinPoint joinPoint){
        System.out.println(joinPoint.getSignature().getName());
    }
    //環繞通知 傳遞的是 JoinPoint 的子類 ProceedingJoinPoint
    public void around(ProceedingJoinPoint proceedingJoinPoint){
        System.out.println(proceedingJoinPoint.getTarget()+ Arrays.toString(proceedingJoinPoint.getArgs()));
        //呼叫此方法執行目標物件相應的方法  這個方法相當於一個分割線,也就是前置通知和後置通知的分割線
        try {
            proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println(proceedingJoinPoint.getSignature().getName());
    }
}

設定切面、切點、異常,前置,後置,環繞通知

	<!-- 設定切面 -->
    <aop:config>
        <!-- 配置切點 -->
        <aop:pointcut id="pointcut" expression="execution(* com.offcn.service.UserService.*(..))"></aop:pointcut>
        <!-- 引入增強bean -->
        <aop:aspect ref="aopError">
            <!-- 配置異常後置增強,這個方法必須引入增強bean的方法, -->
            <aop:after-throwing method="afterError" pointcut-ref="pointcut" throwing="e"></aop:after-throwing>
            <!-- 後置增強 -->
            <aop:after method="after" pointcut-ref="pointcut"></aop:after>
            <!-- 前置增強 -->
            <aop:before method="after" pointcut-ref="pointcut"></aop:before>
            <!-- 環繞增強 -->
            <aop:around method="around" pointcut-ref="pointcut"></aop:around>
        </aop:aspect>	
    </aop:config>

測試 Test

public class Test {
    @org.junit.Test
    public void test(){
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) app.getBean("userService");
        userService.add();
    }
}

註解前置、後置、環繞、異常通知

@Aspect
public class AopError {
    @AfterThrowing(pointcut = "execution(* com.offcn.service.UserService.*(..))",throwing = "e")
    public void afterError(JoinPoint joinPoint, RuntimeException e){
        System.out.println(joinPoint.getSignature().getName()+"這個方法的異常資訊"+e.getMessage());
    }

    public void after(JoinPoint joinPoint){
        System.out.println(joinPoint.getSignature().getName());
    }

    @Around("execution(* com.offcn.service.UserService.*(..))")
    //環繞增強 傳遞的是 JoinPoint 的子類 ProceedingJoinPoint
    public void around(ProceedingJoinPoint proceedingJoinPoint){
        System.out.println(proceedingJoinPoint.getTarget()+ Arrays.toString(proceedingJoinPoint.getArgs()));
        //呼叫此方法執行目標物件相應的方法  這個方法相當於一個分割線,也就是前置增強和後置增強的分割線
        try {
            proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
       System.out.println(proceedingJoinPoint.getSignature().getName());
    }
}