AOP基礎(chǔ)之動態(tài)代理

AOP即面向切面編程,實現(xiàn)的方式有很多炒瘸,這篇文章主要介紹一下動態(tài)代理實現(xiàn)AOP的方式膘滨。主要從動態(tài)代理的原理進(jìn)行分析。

1. jdk自帶動態(tài)代理

代理分為靜態(tài)代理和動態(tài)代理捶枢,靜態(tài)代理這里就不多說了握截,直接看動態(tài)代理,下面看個小例子烂叔。
假設(shè)有個Login接口和LoginImpl實現(xiàn)類

public interface Login {
   boolean login(String userName,String passwd);
}
public class LoginImpl implements Login{
    @Override
    public boolean login(String userName, String passwd) {
        System.out.println("userName:"+userName+"passwd:"+passwd);
        return true;
    }
}

現(xiàn)在我們對login方法進(jìn)行動態(tài)代理谨胞。動態(tài)代理實現(xiàn)方式有兩種,jdk自帶的動態(tài)代理和cglib方式长已。jdk自帶的動態(tài)代理方式要求被代理對象必須實現(xiàn)至少一個接口畜眨,cglib則沒有這個限制。但是cglib也有其自身的限制术瓮,就是被代理對象不能是final修飾的康聂,同時final修飾的方法也是不能被代理的“模看到這里恬汁,可能有些讀者已經(jīng)明白了其中的道理,其實試想一下辜伟,我們?nèi)绻獙崿F(xiàn)對某個對象的代理氓侧,就要能拿到被代理對象的方法脊另,大致有以下兩種思路:

  • 通過接口,被代理類和代理類均實現(xiàn)相同的接口约巷,代理類通過接口可以很輕松的拿到被代理類的方法偎痛。
  • 繼承的方式,如果代理類繼承了被代理類独郎,那么很明顯踩麦,通過子類進(jìn)行方法增強(qiáng),可以達(dá)到aop的目的氓癌,但是final類不能被繼承谓谦。

猜測jdk自帶的動態(tài)代理應(yīng)該采用的是思路一,cglib應(yīng)該采用的是思路二贪婉,是不是這樣呢反粥,我們?nèi)ヌ剿饕幌麓鸢福葋砜磈dk自帶的動態(tài)代理模式疲迂。實現(xiàn)起來很簡單才顿,首先需要實現(xiàn)InvocationHandler接口

public class ProxyHandler implements InvocationHandler{
    private Object target;
    public ProxyHandler(Object obj){
        this.target=obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ret=null;
        System.out.println("before->"+method.getName());
        ret=method.invoke(target, args);
        System.out.println("after->"+method.getName());
        return ret;
    }
}

接著通過Proxy的newProxyInstance方法創(chuàng)建Proxy對象

public class Main {
     public static void main(String[] args)throws Exception 
        {  
            LoginImpl imp=new LoginImpl();
            Login login=(Login)Proxy.newProxyInstance(imp.getClass().getClassLoader(), imp.getClass().getInterfaces(), new ProxyHandler(imp));
            boolean ret=login.login("lanjunjian", "1234");
            System.out.println(ret);
      }
}

結(jié)果如下:可見我們成功的實現(xiàn)了動態(tài)代理。

before->login
userName:lanjunjian    passwd:1234
after->login
true

下面我們看一下動態(tài)代理是怎么實現(xiàn)的尤蒿。動態(tài)代理其實就涉及到兩個類娜膘,InvocationHandler和Proxy。InvocationHandler是個接口优质,只包含invoke方法竣贪,這里沒什么可看的,直接查看下Proxy的newProxyInstance方法巩螃。

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        if (h == null) {
            throw new NullPointerException();
        }
        Class cl = getProxyClass(loader, interfaces);
        try {
            Constructor cons = cl.getConstructor(constructorParams);
            return (Object) cons.newInstance(new Object[] { h });
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        } catch (IllegalAccessException e) {
            throw new InternalError(e.toString());
        } catch (InstantiationException e) {
            throw new InternalError(e.toString());
        } catch (InvocationTargetException e) {
            throw new InternalError(e.toString());
        }
    }

看起來很清晰演怎,通過getProxyClass方法生成了代理對象的Class,然后調(diào)用代理對象的只含InvocationHandler的構(gòu)造函數(shù)生成實例避乏。接著看一下getProxyClass方法爷耀。

private final static String proxyClassNamePrefix = "$Proxy";
String proxyName = proxyPkg + proxyClassNamePrefix + num;
public static Class<?> getProxyClass(ClassLoader loader,
                                       Class<?>... interfaces)
      throws IllegalArgumentException
  {
      if (interfaces.length > 65535) {
          throw new IllegalArgumentException("interface limit exceeded");
      }
      Class proxyClass = null;
      Set interfaceSet = new HashSet();       // for detecting duplicates
      for (int i = 0; i < interfaces.length; i++) {
          String interfaceName = interfaces[i].getName();
          Class interfaceClass = null;
          try {
              interfaceClass = Class.forName(interfaceName, false, loader);
          } catch (ClassNotFoundException e) {
          }
          if (interfaceClass != interfaces[i]) {
              throw new IllegalArgumentException(
                  interfaces[i] + " is not visible from class loader");
          }
          interfaceSet.add(interfaceClass);
          interfaceNames[i] = interfaceName;
      }
            ...
            ...
          byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                  proxyName, interfaces);
           proxyClass = defineClass0(loader, proxyName,
                      proxyClassFile, 0, proxyClassFile.length);
          proxyClasses.put(proxyClass, null);
          ...
          ...
      return proxyClass;
  }

篇幅原因,省略了部分代碼拍皮,代碼很清晰歹叮,沒有太多可以解釋的,主要是進(jìn)行收集接口铆帽,然后轉(zhuǎn)交給ProxyGenerator的generateProxyClass生成字節(jié)碼的byte數(shù)組咆耿。ProxyGenerator類并不屬于J2SE規(guī)范,代碼位于sun.misc包下爹橱。我們大致看一下ProxyGenerator源碼,字節(jié)碼生成的過程是在generateClassFile中完成的萨螺。

  private final static boolean saveGeneratedFiles =
        java.security.AccessController.doPrivileged(
            new GetBooleanAction(
                "sun.misc.ProxyGenerator.saveGeneratedFiles")).booleanValue();

    /**
     * Generate a proxy class given a name and a list of proxy interfaces.
     */
    public static byte[] generateProxyClass(final String name,
                                            Class[] interfaces)
    {
        ProxyGenerator gen = new ProxyGenerator(name, interfaces);
        final byte[] classFile = gen.generateClassFile();

        if (saveGeneratedFiles) {
            java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction() {
                public Object run() {
                    try {
                        FileOutputStream file =
                            new FileOutputStream(dotToSlash(name) + ".class");
                        file.write(classFile);
                        file.close();
                        return null;
                    } catch (IOException e) {
                        throw new InternalError(
                            "I/O exception saving generated file: " + e);
                    }
                }
            });
        }
        return classFile;
    }
 private byte[] generateClassFile() {
        addProxyMethod(hashCodeMethod, Object.class);
        addProxyMethod(equalsMethod, Object.class);
        addProxyMethod(toStringMethod, Object.class);
        for (int i = 0; i < interfaces.length; i++) {
            Method[] methods = interfaces[i].getMethods();
            for (int j = 0; j < methods.length; j++) {
                addProxyMethod(methods[j], interfaces[i]);
            }
        }
        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            checkReturnTypes(sigmethods);
        }
        try {
            methods.add(generateConstructor());
            for (List<ProxyMethod> sigmethods : proxyMethods.values()) { 
                for (ProxyMethod pm : sigmethods) {
                    // add static field for method's Method object
                    fields.add(new FieldInfo(pm.methodFieldName,
                        "Ljava/lang/reflect/Method;",
                         ACC_PRIVATE | ACC_STATIC));
                    methods.add(pm.generateMethod());
                }
            }
            methods.add(generateStaticInitializer());
        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception");
        }

        if (methods.size() > 65535) {
            throw new IllegalArgumentException("method limit exceeded");
        }
        if (fields.size() > 65535) {
            throw new IllegalArgumentException("field limit exceeded");
        }
        cp.getClass(dotToSlash(className));
        cp.getClass(superclassName);
        for (int i = 0; i < interfaces.length; i++) {
            cp.getClass(dotToSlash(interfaces[i].getName()));
        }
        cp.setReadOnly();
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        DataOutputStream dout = new DataOutputStream(bout);
        try {
            dout.writeInt(0xCAFEBABE);
            dout.writeShort(CLASSFILE_MINOR_VERSION);
            dout.writeShort(CLASSFILE_MAJOR_VERSION);
            cp.write(dout);  
            dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
            dout.writeShort(cp.getClass(dotToSlash(className)));
            dout.writeShort(cp.getClass(superclassName));
            dout.writeShort(interfaces.length);
                                        // u2 interfaces[interfaces_count];
            for (int i = 0; i < interfaces.length; i++) {
                dout.writeShort(cp.getClass(
                    dotToSlash(interfaces[i].getName())));
            }
            dout.writeShort(fields.size());
            for (FieldInfo f : fields) {
                f.write(dout);
            }
            dout.writeShort(methods.size());
            for (MethodInfo m : methods) {
                m.write(dout);
            }
            dout.writeShort(0); // (no ClassFile attributes for proxy classes)
        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception");
        }
        return bout.toByteArray();
    }

generateClassFile展示了Proxy類生成的全過程,包括了接口聲明的所有方法,此外還包括Object類中的三個方法hashCode慰技,equals椭盏,toString方法。具體的addProxyMethod這里就不分析了吻商,我們可以想辦法拿到動態(tài)代理生成的字節(jié)碼掏颊。動態(tài)代理是在運行期進(jìn)行的,默認(rèn)是不保存字節(jié)碼文件的艾帐,怎么保存字節(jié)碼呢蚯舱?我們看到上文中g(shù)enerateProxyClass方法會判斷saveGeneratedFiles是否為true。ok掩蛤,很明顯我們在代理前將這個變量設(shè)置為true即可。我們運行前加入如下代碼陈肛,可在工程根目錄下得到包名為com.sun.proxy的Proxy[num].class文件揍鸟,num一般情況下為0。

public static void saveGeneratedJdkProxyFiles() throws Exception {
            Field field = System.class.getDeclaredField("props");
            field.setAccessible(true);
            Properties props = (Properties) field.get(null);
        props.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
}

下面我們反編譯一下$Proxy0.class句旱。

public final class $Proxy0 extends Proxy implements Login
{
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    public $Proxy0(final InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
    public final boolean equals(final Object o) {
        try {
            return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[] { o });
        }
        catch (Error | RuntimeException t) {
            throw t;
        }
        catch (Throwable t2) {
            throw new UndeclaredThrowableException(t2);
        }
    }
    public final String toString() {
        try {
            return (String)super.h.invoke(this, $Proxy0.m2, null);
        }
        catch (Error | RuntimeException t) {
            throw t;
        }
        catch (Throwable t2) {
            throw new UndeclaredThrowableException(t2);
        }
    }
    public final boolean login(final String s, final String s2) {
        try {
            return (boolean)super.h.invoke(this, $Proxy0.m3, new Object[] { s, s2 });
        }
        catch (Error | RuntimeException t) {
            throw t;
        }
        catch (Throwable t2) {
            throw new UndeclaredThrowableException(t2);
        }
    }
    public final int hashCode() {
        try {
            return (int)super.h.invoke(this, $Proxy0.m0, null);
        }
        catch (Error | RuntimeException t) {
            throw t;
        }
        catch (Throwable t2) {
            throw new UndeclaredThrowableException(t2);
        }
    }
    static {
        try {
            $Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            $Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);
            $Proxy0.m3 = Class.forName("com.ljj.Login").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
            $Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]);
        }
        catch (NoSuchMethodException ex) {
            throw new NoSuchMethodError(ex.getMessage());
        }
        catch (ClassNotFoundException ex2) {
            throw new NoClassDefFoundError(ex2.getMessage());
        }
    }
}

可以看到$Proxy繼承了Proxy類并實現(xiàn)了Login接口阳藻,在靜態(tài)代碼塊中加載了所有需要代理的方法,方法的調(diào)用都是通過InvocationHandler的invoke方法轉(zhuǎn)發(fā)的谈撒。這里也看出來了由于java的單繼承的限制腥泥,jdk自帶的動態(tài)代理是無法代理普通類的,換句話說即使某個類實現(xiàn)了某個接口啃匿,但主要不是接口內(nèi)定義的方法都是無法進(jìn)行代理的蛔外。此外,動態(tài)代理后方法的調(diào)用只能通過反射來進(jìn)行溯乒,性能上會有一些開銷夹厌。

2. cglib動態(tài)代理

為了彌補jdk自帶動態(tài)代理的限制,出現(xiàn)了cglib裆悄,可以實現(xiàn)類的動態(tài)代理矛纹,像spring框架就是jdk動態(tài)代理和cglib結(jié)合進(jìn)行的,jdk動態(tài)代理搞不定的利用cglib進(jìn)行光稼,cglib引入了ASM庫來進(jìn)行底層字節(jié)碼生成或南。這里簡單的介紹一下。
同樣定義一個Login類艾君,不實現(xiàn)任何接口

public class Login {
    public boolean login(String name,String passwd){
        System.out.println("name->:"+name+"passwd->:"+passwd);
        return false;
    }
}

第一步實現(xiàn)一個MethodInterceptor類似于InvocationHandler

public class Hacker implements MethodInterceptor{
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("xxxxxxbefore");
        Object ret=proxy.invokeSuper(obj, args);
        System.out.println("xxxxxxafter");
        return ret;
    }
}

第二步直接調(diào)用

 public static void main(String[] args)throws Exception 
        {    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/ljj/Documents/workspace/HookTest"); 
         Test1 test=new Test1();
         Hacker hacker=new Hacker();
         Enhancer enhancer=new Enhancer();
         enhancer.setSuperclass(test.getClass());
         enhancer.setCallback(hacker);
         Test1 proxy=(Test1)enhancer.create();
         proxy.login("lanjunjian", "12346");
        }

由于android中加載的是dex文件采够,不是class文件,cglib不支持android系統(tǒng)冰垄,所以cglib的具體實現(xiàn)過程就不詳細(xì)說了吁恍。具體原理可以參考這篇文章說說cglib動態(tài)代理。簡單理解cglib是采用繼承的方式進(jìn)行代理。生成的代理類是繼承自被代理類的冀瓦。

public class Test1$$EnhancerByCGLIB$$d42d7d8c extends Test1 implements Factory

此外伴奥,cglib采用了FastClass機(jī)制,F(xiàn)astClass就是根據(jù)方法簽名保存了代理類和被代理類的索引信息翼闽,然后為每個方法生成一個MethodProxy拾徙,proxy中有Invoke和invokeSuper兩個方法,當(dāng)我們調(diào)用invokeSuper時感局,根據(jù)方法簽名去FastClass可以找到被代理類的方法尼啡,然后直接進(jìn)行調(diào)用。所以cglib和動態(tài)代理很大的區(qū)別是 cglib使用的是直接調(diào)用询微,jdk是利用的反射崖瞭。也有人利用dexmaker實現(xiàn)了android上的cglib,項目地址:MethodInterceptProxy

3.動態(tài)代理在android上的簡單應(yīng)用

代理可以理解為是hook的一種手段撑毛,例如插件框架中替換Instrumention书聚,實際上采用的是靜態(tài)代理的方式,但是很多情況下藻雌,接口或類可能是hide的雌续,我們無法通過繼承或者接口實現(xiàn)等方式構(gòu)造代理類,這種情況下我們就沒法使用靜態(tài)代理胯杭,可以酌情考慮動態(tài)代理驯杜。
我認(rèn)為進(jìn)行動態(tài)代理最大的難點在于hook的點很難找,主要能找到hook點做个,一切也好辦鸽心。一般情況下,hook點最好是靜態(tài)或者是單例居暖,有些時候很難找到實例對象再悼,而且往往我們都需要借助反射來獲取被代理對象。下面就以發(fā)送通知為例膝但,假設(shè)我要攔截每次發(fā)送通知的內(nèi)容該怎么做呢冲九?

 Intent intent=new Intent();
 Notification build = new Notification.Builder(this)
     .setContentTitle("hook")
     .setContentText("攔截通知")
     .setAutoCancel(true)
     .setSmallIcon(R.mipmap.ic_launcher)
     .setWhen(System.currentTimeMillis())
     .setContentIntent(PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
     .build();
 NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
 manager.notify((int) (System.currentTimeMillis()/1000L), build);

上一段是我們典型的發(fā)送通知的代碼。整個發(fā)送的過程是由NotificationManager來控制的跟束,我們知道通知的發(fā)送是一個跨進(jìn)程的操作莺奸,這里由于篇幅原因,不去詳細(xì)談Binder相關(guān)的內(nèi)容冀宴,只是為了從主觀上感受下動態(tài)代理在android方面怎么使用灭贷。

 public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
    {
        int[] idOut = new int[1];
        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        // Fix the notification as best we can.
        Notification.addFieldsFromContext(mContext, notification);
        if (notification.sound != null) {
            notification.sound = notification.sound.getCanonicalUri();
            if (StrictMode.vmFileUriExposureEnabled()) {
                notification.sound.checkFileUriExposed("Notification.sound");
            }
        }
        fixLegacySmallIcon(notification, pkg);
        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
            if (notification.getSmallIcon() == null) {
                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
                        + notification);
            }
        }
        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
        final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
        try {
            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                    copy, idOut, user.getIdentifier());
            if (localLOGV && id != idOut[0]) {
                Log.v(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

當(dāng)調(diào)用manager.notify時,會調(diào)用到NotificationManager的notifyAsUser方法略贮,可以看到整個發(fā)送流程都是通過INotificationManager接口進(jìn)行的甚疟。一看到接口感覺應(yīng)該可以做點什么仗岖,我們進(jìn)一步看一下getService方法。

private static INotificationManager sService;
    /** @hide */
    static public INotificationManager getService()
    {
        if (sService != null) {
            return sService;
        }
        IBinder b = ServiceManager.getService("notification");
        sService = INotificationManager.Stub.asInterface(b);
        return sService;
    }

首先在ServiceManager通過getService方法獲取到了一個原聲的IBinder對象览妖,然后通過AIDL機(jī)制由asInterface方法轉(zhuǎn)換成了本地的代理對象,INotificationManager是一個由AIDL接口生成的本地代理對象轧拄,正好sService是一個static變量,我們通過反射獲取到該對象然后替換成我們的Proxy是不是就能實現(xiàn)通知的攔截了呢讽膏?想一下我們動態(tài)代理的實現(xiàn)過程檩电,操作一下。我們需要三個要素府树,接口的Class對象俐末,獲取被代理對象和一個InvocationHandler的子類。奄侠。

  • 獲取接口class對象很簡單卓箫,反射即可。
Class<?> INotificationManagerClazz = Class.forName("android.app.INotificationManager");
  • 獲取被代理對象垄潮,也就是要拿到sService烹卒,可以通過反射拿到,我們可以通過發(fā)射sService變量拿到,也可以通過反射調(diào)用getService方法獲取魂挂,如果直接發(fā)射sService變量,此時有可能獲取到的為null馁筐,所以采用反射getService方法獲取涂召。
 NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
      Method method = notificationManager.getClass().getDeclaredMethod("getService");
      method.setAccessible(true);
      final Object sService = method.invoke(notificationManager);
  • InvocationHandler的子類直接實現(xiàn)一個即可。
    準(zhǔn)備工作做完了敏沉,我們直接進(jìn)行代理的生成果正,同時要記得用proxy替換原來的sService,所有的工作就完成了盟迟。
 Object proxy = Proxy.newProxyInstance(INotificationManagerClazz.getClassLoader(),
          new Class[]{INotificationManagerClazz},new ProxyHandler(sService));
      Field target = notificationManager.getClass().getDeclaredField("sService");
      target.setAccessible(true);
      target.set(notificationManager, proxy);

我們怎么攔截內(nèi)容呢秋泳,觀察notifyAsUser方法中會調(diào)用enqueueNotificationWithTag方法,我們只需要攔截這個方法即可攒菠。

class ProxyHandler implements InvocationHandler {
    private Object mObject;
    public ProxyHandler(Object mObject) {
      this.mObject = mObject;
    }
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if (method.getName().equals("enqueueNotificationWithTag")) {
        for (int i = 0; i < args.length; i++) {
          if(args[i] instanceof Notification){
            Notification notification=(Notification)(args[i]);
            String content=notification.extras.getString(Notification.EXTRA_TEXT);
            Log.i("ljj", "invoke: "+content);
          }
        }
        return method.invoke(mObject, args);
      }
      return null;
    }
  }

這里說明一下迫皱,通知的內(nèi)容在不同版本里獲取方式不太一樣,這里只是為了直觀的體現(xiàn)動態(tài)代理的作用辖众,沒有進(jìn)行適配卓起,下面給出完整的hook代碼。

  public  void hookNotificationContent(Context context) {
    try {
      NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
      Method method = notificationManager.getClass().getDeclaredMethod("getService");
      method.setAccessible(true);
      final Object sService = method.invoke(notificationManager);//獲取到Nofificiaton原來的sService對象
      Class<?> INotificationManagerClazz = Class.forName("android.app.INotificationManager");
      Object proxy = Proxy.newProxyInstance(INotificationManagerClazz.getClassLoader(),
          new Class[]{INotificationManagerClazz},new ProxyHandler(sService));
      Field target = notificationManager.getClass().getDeclaredField("sService");
      target.setAccessible(true);
    target.set(notificationManager, proxy);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

最后總結(jié)一下凹炸,這篇文章比較基礎(chǔ)戏阅,主要想搞明白以下三個知識點,希望對大家有所幫助。

  • jdk動態(tài)代理的原理以及為什么只能對接口做代理
  • cglib與jdk動態(tài)代理的區(qū)別
  • 在android的應(yīng)用場景

參考文獻(xiàn)

  1. java動態(tài)代理機(jī)制詳解
  2. Android插件化開發(fā)-hook 系統(tǒng)服務(wù)(通過binder修改粘貼板服務(wù)行為)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啤它,一起剝皮案震驚了整個濱河市奕筐,隨后出現(xiàn)的幾起案子舱痘,更是在濱河造成了極大的恐慌,老刑警劉巖离赫,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芭逝,死亡現(xiàn)場離奇詭異,居然都是意外死亡笆怠,警方通過查閱死者的電腦和手機(jī)铝耻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蹬刷,“玉大人瓢捉,你說我怎么就攤上這事“斐桑” “怎么了泡态?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長迂卢。 經(jīng)常有香客問我某弦,道長,這世上最難降的妖魔是什么而克? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任靶壮,我火速辦了婚禮,結(jié)果婚禮上员萍,老公的妹妹穿的比我還像新娘腾降。我一直安慰自己,他們只是感情好碎绎,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布螃壤。 她就那樣靜靜地躺著,像睡著了一般筋帖。 火紅的嫁衣襯著肌膚如雪奸晴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天日麸,我揣著相機(jī)與錄音寄啼,去河邊找鬼。 笑死代箭,一個胖子當(dāng)著我的面吹牛辕录,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播梢卸,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼走诞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蛤高?” 一聲冷哼從身側(cè)響起蚣旱,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤碑幅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后塞绿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沟涨,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年异吻,在試婚紗的時候發(fā)現(xiàn)自己被綠了裹赴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡诀浪,死狀恐怖棋返,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情雷猪,我是刑警寧澤睛竣,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站求摇,受9級特大地震影響射沟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜与境,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一验夯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧摔刁,春花似錦挥转、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽准潭。三九已至趁俊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刑然,已是汗流浹背寺擂。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留泼掠,地道東北人怔软。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像择镇,于是被迫代替她去往敵國和親挡逼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容