【模式】AOP{更新中}

 

代理模式

代理模式的產生:明星A會唱歌,很多人都想找A商演,A自己不會。經紀人B,作爲明星A的代理,負責接收邀請,篩選邀請,安排演唱前後接送,粉絲公關等事宜。明星A只負責唱歌就可以了。

代理模式:抽象一下,經紀人B 這羣人就是ProxyClass-代理類,而明星A 這羣人就是Class。ProxyClass類向外提供 Class 所有的方法(功能)。外部只需調用ProxyClass的方法,就可以讓Class完成對應功能。ProxyClass 可以對調用前後進行控制。比如當有人調用ProxyClass的m1讓明星A唱歌的時候,其對參數進行篩選,再調用 Class 的 m1 讓明星A去赴演。

代理模式的設計類圖:可以看到ProxyClass擁有Class的所有方法,並調用Class的方法。我想肯定有人會問,爲什麼還需要畫蛇添足讓兩個類實現同一個接口。一是一種對外申明,Class有的接口ProxyClass全能代理。二是Class可以有很多個代理類,代理類都具有共同的代理接口,其次不需要修改現有依賴Class/ProxyClass的代碼。

 

代理模式
代理模式設計類圖

 

 

JDK動態代理

JDK動態代理的產生:

  • 被代理類方法特別多,代理類要做的事情雷同:假設 明星A 十項全能,有很多才藝。經紀人B 在接收邀請後做的事情卻都是篩選。那麼需要完全手寫一個新的類,然後定義所有明星A擁有的方法。
  • 被代理類內容更改:再者隨着時間的推移,A的技能在不斷變化,B也又要隨之修改,非常繁雜。
  • 代理類更換被代理類:假設 經紀人B 不想再給 明星A 代理了,換了另一個 運動明星C,則又得根據 明星C 修改 經紀人B 的代理方法。

本質想說明兩點,普通的靜態代理,也就是自己編寫代理類:一個是代理類編寫時的繁雜的重複,另一個是代理類ProxyClass 受到 被代理類Class 的變更的影響很大。

 

JDK動態代理的設計:

  • 供自定義方法模板:因爲通常代理類的所有方法體都類似,因此不再定義一個代理類,只定義一個如下的方法模板即可。Java提供了一個java.lang.reflect.InvacationHandler接口,實現該接口的invoke方法,在方法體中寫下述自定義模板。
do proxy stuff
call origin class's method like sing
do proxy stuff
  • 生成代理類:Java不是在編譯時生成的類,而是在運行時生成類,Java提供了一個java.lang.reflect.Proxy類,類中有一個newInstance方法:Proxy.newInstance(類加載器ClassLoader,被代理的接口列表Class<?>[],自定義的方法模板InvocationHandler),返回一個爲指定接口代理的代理對象。

 

JDK動態代理的使用:代碼如下,我代理的是java.lang.Runnable接口。

package basics.proxy;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author 100101001
 * @date 12/23/2019 10:18 AM
 */
public class jdk {

    private static int a = 123;    

    public static void main(String[] args) {
        Target t =new Target();
        //運行時生成類 Proxy$0 extends Proxy implements Runnable
        Runnable a = (Runnable) Proxy.newProxyInstance(Runnable.class.getClassLoader(), new Class[]{
                Runnable.class
        }, new InvocationHandler() { //這裏採用了匿名類實現接口
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("jdk proxy before method..");
                method.invoke(t, args);
                System.out.println("jdk proxy after method..");
                //Runnable接口的run方法無返回值
                return null;
            }
        });
        new Thread(a).start();
    }

    //靜態內部類等效於外部類,只不過類名jdk$Target
    //能訪問jdk的所有靜態變量和成員
    static class Target implements Runnable{
        @Override
        public void run() {
            System.out.println("target run"+a);
        }
    }
}

運行結果:

jdk proxy before method..
target run123
jdk proxy after method..

運行局部變量:a就是代理對象$Proxy0,其內部有一個h就是InvocationHnadler,因爲它是jdk的一個匿名內部類,所以取類名 jdk$1,而t也是jdk的內部類Target(定義時)對象,所以取名 jdk$Target(可以和外部Target類區分,外部通過jdk.Target來訪問該類)。

動態生成的代理類:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
 
public final class Proxy0
  extends Proxy
  implements Runnable
{
  // 在靜態初始化代碼塊中使用反射獲取Method
  // 比如 m3=Class.forName(java.lang.Runnable).getMethod("run", new Class[0])
  // h是構造Proxy時,傳入的InvocationHandler
  // 在run方法體中直接 this.h.invoke(this, m3, null);
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;
  
  public ProxySubject(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void run()
  {
    try
    {
      this.h.invoke(this, m3, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String toString()
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final int hashCode()
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("java.lang.Runnable").getMethod("run", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

 

AOP: Aspect-Oriented Programming

面向切面編程的產生:在OOP(Object-Oriented Progamming)中,方法都需要做一些共同的非功能性操作,比如對訪問加鎖,記錄調用日誌,異常處理。通過繼承提供共同的操作,有單繼承限制。因此,AOP產生了。我們把這些公共操作一一提煉成對應的代理模板(方法),組合到一個類中,這個類被稱爲切面(與繼承垂直共享相對的一種橫向共享的概念)。比如寫一個日誌切面類,定義實現兩個代理模板方法,分別是開始計時(調用原方法前)和結束計時(調用原方法前)。這個日誌切面類,記錄了方法執行時間,方便優化分析,這些使用了切面功能的方法就是切入點切面類似之前動態代理中的實現InvocationHandler的類切入點類似之前的動態代理中的被代理的接口中的方法類似這樣的提煉公共操作,編寫切面類,定義切入點的過程就是AOP。

 

面向切面編程的應用:這一部分參考了 簡書作者:糖紙瘋了 的文章 006--【SpringBoot】AOP使用集錦,web應用框架往往會使用AOP實現功能模塊(比如框架提供對Controller類所有方法返回值序列化這一公共操作,開發者無需自行編寫),向外提供AOP工具(比如在SpringBoot中提供AOP註解,允許用戶自定義切面)。

這裏自定義接口日誌切面效果如下。在接口執行前,記錄了接口方法,接口所在類,接口參數,接口URL等信息,執行後,記錄了返回值類型。

日誌效果圖

在SpringBoot中,自定義Aspect的方法如下。

  • Aspect註解切面類
  • 切面類內PointCut註解方法指明代理的方法
  • Before,After,AfterReturning,AfterThrowing,Around等指明切面在方法執行的流程中代做的事情
@Aspect
@Component
public class LogWebAspect {
    //統計請求的處理時間
    ThreadLocal<Long> startTime = new ThreadLocal<>();
    /**
     * 指定切點
     * 匹配 com.enzoism.controller包及其子包下的所有類的所有方法
     */
    @Pointcut("execution(public * com.enzoism.controller.*.*(..))")
    public void webLog(){
    }

    /**
     * 前置通知,方法調用前被調用
     * @param joinPoint
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint){
        System.out.println("我是前置通知!!!");
        startTime.set(System.currentTimeMillis());
        //獲取目標方法的參數信息
        Object[] obj = joinPoint.getArgs();
        System.out.println("請求參數:"+obj);
        // 代理
        Signature signature = joinPoint.getSignature();
        //代理的是哪一個方法
        System.out.println("方法:"+signature.getName());
        //AOP代理類的名字
        System.out.println("方法所在包:"+signature.getDeclaringTypeName());
        //AOP代理類的類(class)信息
        signature.getDeclaringType();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] strings = methodSignature.getParameterNames();
        System.out.println("參數名:"+ Arrays.toString(strings));
        System.out.println("參數值ARGS : " + Arrays.toString(joinPoint.getArgs()));
        // 接收到請求,記錄請求內容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest req = attributes.getRequest();
        // 記錄下請求內容
        System.out.println("請求URL : " + req.getRequestURL().toString());
        System.out.println("HTTP_METHOD : " + req.getMethod());
        System.out.println("IP : " + req.getRemoteAddr());
        System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());

    }

    /**
     * 處理完請求返回內容
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 處理完請求,返回內容
        System.out.println("方法的返回值 : " + ret);
    }

    /**
     * 後置異常通知
     * @param jp
     */
    @AfterThrowing("webLog()")
    public void throwss(JoinPoint jp){
        System.out.println("方法異常時執行.....");
    }

    /**
     * 後置最終通知,final增強,不管是拋出異常或者正常退出都會執行
     * @param jp
     */
    @After("webLog()")
    public void after(JoinPoint jp){
        System.out.println("方法執行時間:"+ (System.currentTimeMillis() - startTime.get()));
        System.out.println();
    }

    /**
     * 環繞通知,環繞增強,相當於MethodInterceptor
     * @param pjp
     * @return
     */
    @Around("webLog()")
    public Object arround(ProceedingJoinPoint pjp) {
        try {
            Object o =  pjp.proceed();
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}

切面除了可以應用到某個包下的所有類中的所有方法,還可以指定應用於某個註解??待更新

@Aspect
@Component
public class LogAnnotationAspect {
    /**
     * 指定切點
     * 匹配 被添加LogAnnotation標註的方法
     */
    @Pointcut("@annotation(logAnnotation)")
    public void pointCut(LogAnnotation logAnnotation) {
    }

    /**
     * 環繞通知,環繞增強,相當於MethodInterceptor
     * @param pjp
     * @return
     */
    @Around("pointCut(logAnnotation)")
    public Object arround(ProceedingJoinPoint pjp, LogAnnotation logAnnotation) {
        try {
            Object o = pjp.proceed();
            System.out.println("---------------直接指定到logAnnotation位置");
            System.out.println("actionType:"+logAnnotation.actionType());
            System.out.println("controllerName:"+logAnnotation.controllerName());
            System.out.println("module:"+logAnnotation.module());
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}

 

發佈了20 篇原創文章 · 獲贊 20 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章