Proxy动态代理应用、源码分析

注:1、Java的动态代理是基于接口的,如果类未实现接口是不能进行代理的,需要使用CGlib方式。

前言

代理是java中非常重要的一种设计模式 ,动态代理则是是一种代理模式的最佳实践,本文将由浅及深的介绍动态代理及其proxy源码分析,文章写的不一定都对,请各位看官带着批判的态度阅读此文章.

代理模式是设计模式中的一种,而且在实际的开发中使用的频率非常高 ,比如spring AOP,mybatis代理都是我们经常使用的.

代理模式的定义:

当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。

静态代理

从上面的介绍中可以看出代理模式其实本意是为了解决访问存在困难或者为了保证透明性的一种工具,有点类似于我们无法访问google,需要一个中间代理商的帮助是一致的.下面通过一个简单的例子来介绍:

public class ProxyDemo {

    static interface BaseUser{
        void info();
        void real();
    }

    static class ProxyUser implements BaseUser{
        BaseUser baseUser;
        public ProxyUser(BaseUser baseUser) {
            this.baseUser = baseUser;
        }
        public void info() {
            System.out.println("I'm Proxy,I can help you");

        }
        public void real() {
            System.out.println("I will help you visit google");
            baseUser.real();
            System.out.println("I had help you visit google");
        }

    }
    static class TargetUser implements BaseUser{
        public void info() {
            System.out.println("I'm google,what you what do?");
        }
        public void real() {
            System.out.println("I.m google,this is searched info");
        }
    }

    public static void main(String[] args) {
        BaseUser targetUser = new TargetUser();
        BaseUser proxyUser = new ProxyUser(targetUser);
        proxyUser.info();
        proxyUser.real();
    }

}

在这里我们也可以认为代理者是两者访问或者交互的载体,需要对双方都非常的熟悉,才能帮你做具体的事,就像如果我现在需要代购,可能就需要找新的代理人!!!

这里也就是我们所说的静态代理

虽然静态代理也能帮我实现一些功能,但是只能说不够强大,此时我们就可以使用动态代理来帮我们更加灵活的去搞事情

动态代理

动态代理的优势:
1. 降低各个功能模块之间的耦合度,提高开发的效率和方便程序的维护度。
2. 减少代码量。
3. 不关注目标的具体实现。

动态代理的实现

JDK动态代理

jdk自带的动态代理主要是通过实现InvocationHandler

InvocationHandler的主要方法

Object invoke(Object proxy, Method method,Object[] args)throws Throwable

在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。即调用真实业务的方法都会进入到此invoke方法,至于为什么,稍后再说明

方法详细介绍

  • 参数:proxy - 调用方法的代理实例对象 method - 代理实例对象调用的接口方法的 Method 实例对象。
  • Method-指代具体被代理的方法。 
    args -包含传入代理实例上方法调用的参数,如果接口方法不使用参数,则为 null。

  • return: 
    从代理实例的方法调用返回的值。

  • throws: Throwable - 从代理实例上的方法调用抛出的异常。

案例

本案例演示的是最常用的拦截方法然后记录日志的功能。

3.1 业务接口

public interface Base {
    public void hello(String name);
}

3.2 业务实现类 LoginImpl

public class LoginImpl implements Base{
    @Override
    public void hello(String name) {
        System.out.println("welcome "+name+", success !!1");
    }
}

3.3 代理类 LoginProxy

class DynamicProxy implements InvocationHandler {

        Object originalObj;

        Object bind(Object originalObj) {
            this.originalObj = originalObj;
            return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this);
        }

    /**
     * 切入点 对所有对象的方法都进行调用
     * method.invoke方法对应代理对象调用login方法
     * @param proxy 代理对象
     * @param method 代理对象的方法
     * @param args  代理对象调用接口方法的参数值
     * @return 代理对象调用方法的返回值
     * @throws Throwable
     */
  @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    before();
    Object invoke = method.invoke(originalObj, args);
    if (invoke != null){
        result(invoke);
    }
    after();

  return invoke;
  }

  private void before() {
  System.out.println("方法执行之前");
  }
  private void after() {
  System.out.println("方法执行之后");
  }
  private void result(Object o) {
  o.toString();
  }


}

3.4 测试类 LoginClient

public class LoginClient {
    public static void main(String[] args) {
  //用于生成代理文件      //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        Base hello = (Base) new DynamicProxy().bind(new LoginImpl());
        hello.hello("zhangsan");
    }
}

3.5 执行结果:

方法执行之前
Hello zhangsan
方法执行之后

从上面的例子我们可以看到动态代理有效的减少了各个模块的耦合度,用于实现日志功能的代码和用于实现登陆功能的代码相互隔离。对两者都没有条件限制,.只有在真正调用业务的时候并需要日志功能时候二者才发生联系。 
任何业务需要日志功能只需要通过代理类创建代理对象即可,不需要重复创建代理类.

究其原理

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");取消掉此方法的注释,我们运行或编译代码后将生成代理的文件,默认是项目根目录下的于包名同名的文件夹下。下面我们看一下生成的代理类反编译后的内容:


import ProxyDemo.Base;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
//$Proxy0是生成代理的格式决定的
final class $Proxy0 extends Proxy implements Base {
  //将基础的tostring,equils,hashcode,还有base接口的方法生成method的对象
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void hello(String var1) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void out() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    //具体的实现
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("ProxyDemo$Base").getMethod("hello", Class.forName("java.lang.String"));
            m3 = Class.forName("ProxyDemo$Base").getMethod("out");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

首先从类的继承关系就很容易理解 
Proxy.newProxyInstance(originalObj.getClass().getClassLoader(),originalObj.getClass().getInterfaces(), this);方法的作用了.

即继承了proxy类也实现了Base接口.这也是Base hello = (Base) new DynamicProxy().bind(new LoginImpl());这也是为什么可以强转为Base对象的原因.同时在代理中将object类中的equils,tostring,hashcode以及所有base接口的方法生成对应的代理方法.

以hello方法为例介绍一下,h表示的是proxy类中的InvocationHandler其实也就是指代我们之前的DynamicProxy对象,然后调用invoke方法就回到DynamicProxy的invoke方法.我们就可以再次做很多中间的操作。

public final void hello(String var1) throws  {
   try {
       super.h.invoke(this, m4, new Object[]{var1});
   } catch (RuntimeException | Error var3) {
       throw var3;
   } catch (Throwable var4) {
       throw new UndeclaredThrowableException(var4);
   }
}

Proxy解读

看完了代理类的内容后,接下来我们就需要去详细的看一下Proxy是如何生成$Proxy0这个代理类的.了解一下其中的工作流程和原理。

首先看newProxyInstance方法

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
    Objects.requireNonNull(h);
    //获取需要代理类的所有实现的接口
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
    //检查是否有生成代理类的权限
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    //查找或者生成代理类
    Class<?> cl = getProxyClass0(loader, intfs);

    //生成构造函数
    try {
        if (sm != null) {
            //检查是否有权限
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        //public $Proxy0(InvocationHandler var1)
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        //访问修饰符设置
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        //返回代理类的对象
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException | InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

具体的实现逻辑在getProxyClass0方法中,最外面的方法只是描述了生成代理后然后创建对应的代理对象。首先看一下checkProxyAccess方法的具体内容

//主要作用检查权限是否可以操作
private static void checkProxyAccess(Class<?> caller, ClassLoader loader, Class<?>... interfaces) {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        ClassLoader ccl = caller.getClassLoader();
        //classloader验证
        if (VM.isSystemDomainLoader(loader) && !VM.isSystemDomainLoader(ccl)) {
            sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
        }
        //
        ReflectUtil.checkProxyPackageAccess(ccl, interfaces);
    }
}

//检查接口的包权限
public static void checkProxyPackageAccess(ClassLoader var0, Class... var1) {
    SecurityManager var2 = System.getSecurityManager();
    if (var2 != null) {
        Class[] var3 = var1;
        int var4 = var1.length;

        for (int var5 = 0; var5 < var4; ++var5) {
            Class var6 = var3[var5];
            ClassLoader var7 = var6.getClassLoader();
            if (needsPackageAccessCheck(var0, var7)) {
                checkPackageAccess(var6);
            }
        }
    }

}

当验证完权限之后,查看如何获取代理类的getProxyClass0方法

private static Class<?> getProxyClass0(ClassLoader loader,
                                          Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    //从缓存中获取,如果不存在就创建
    return proxyClassCache.get(loader, interfaces);
}

使用proxyClassCache做缓存,其目的是为了复用,同时防止多线程重复创建。在weekCache类中使用了多个map进行记录,稍后我们再做详细介绍.

//获取或生成代理类 此处因为不是线程安全的做了多次判断
public V get(K key, P parameter) {
    Objects.requireNonNull(parameter);
    //删除过期条目
    expungeStaleEntries();
    //创建cacheKey
    Object cacheKey = CacheKey.valueOf(key, refQueue);

    //查看key是否已经存在valuemaps中
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    if (valuesMap == null) {
        //不存在的话通过,再次尝试尝试获取,如果没有就插入
        ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                valuesMap = new ConcurrentHashMap<>());
        if (oldValuesMap != null) {
            valuesMap = oldValuesMap;
        }
    }
    //生成代理对象的key 为弱引用类型
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    //尝试从valuemap中获取
    Supplier<V> supplier = valuesMap.get(subKey);
    Factory factory = null;

    while (true) {
        //如果确实已经有线程创建了
        if (supplier != null) {
            //直接获取 supplier might be a Factory or a CacheValue<V> instance
            V value = supplier.get();
            if (value != null) {
                //最终返回value
                return value;
            }
        }
        // 不存在创建一个supplier factory实现了supplier
        if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }


        if (supplier == null) {
            //如果不存在则保存到valuemap中
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) {
                // 添加成功
                supplier = factory;
            }
            // 创建的时候发现已经有了,尝试替换
        } else {
            if (valuesMap.replace(subKey, supplier, factory)) {
                //替换成功
                supplier = factory;
            } else {
                // retry with current supplier
                supplier = valuesMap.get(subKey);
            }
        }
    }
}

get方法首先去查看是否存在缓存过期的情况,存在则清除掉.如果不存在,尝试的生成key和value的相关元数据,

下面介绍key的生成方法KeyFactory.apply方法

//根据接口个数的不同选择生成不同的key对象
public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
    switch (interfaces.length) {
        case 1: return new Key1(interfaces[0]); // the most frequent
        case 2: return new Key2(interfaces[0], interfaces[1]);
        case 0: return key0;
        default: return new KeyX(interfaces);
    }
}

然后在判断是否存在同时其他线程生成,然后就是尝试着保存添加信息,如果已经有了就尝试替换.最终通过supplier.get()方法获取, 
最终实际的逻辑在supplier.get()方法中,下面看一下具体的过程

public synchronized V get() { // serialize access
     // 再次检查是否匹配
     Supplier<V> supplier = valuesMap.get(subKey);
     if (supplier != this) {
         //因为此方法调用之前有可能发生valuesMap.replace(subKey, supplier, factory)
         return null;
     }
     // 创建
     V value = null;
     try {
         //真正的逻辑,重点方法
         value = Objects.requireNonNull(valueFactory.apply(key, parameter));
     } finally {
         if (value == null) { 
             // 如果最终没能生成代理对象,从valuemap移除
             valuesMap.remove(subKey, this);
         }
     }
     // the only path to reach here is with non-null value
     assert value != null;

     //包装value为acacheValue
     CacheValue<V> cacheValue = new CacheValue<>(value);

     // 保存到reverseMap
     reverseMap.put(cacheValue, Boolean.TRUE);

     // 尝试这替换valuemap中的cacheValue
     if (!valuesMap.replace(subKey, this, cacheValue)) {
         throw new AssertionError("Should not reach here");
     }
     return value;
 }

下面详细介绍value的ProxyClassFactory.apply方法.

 //apply方法详解
   public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

       Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
       for (Class<?> intf : interfaces) {
           Class<?> interfaceClass = null;
           try {
               //使用给定的类加载器加载接口
               interfaceClass = Class.forName(intf.getName(), false, loader);
           } catch (ClassNotFoundException e) {
           }
           if (interfaceClass != intf) {
               throw new IllegalArgumentException(
                       intf + " is not visible from class loader");
           }
           //验证是否为接口
           if (!interfaceClass.isInterface()) {
               throw new IllegalArgumentException(
                       interfaceClass.getName() + " is not an interface");
           }
           //验证接口不是重复的
           if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
               throw new IllegalArgumentException(
                       "repeated interface: " + interfaceClass.getName());
           }
       }

       String proxyPkg = null;
       //修饰符
       int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

       /*
        * 验证接口的可见性
        * 如果不是public类型的接口又不在同一个包下抛出异常
        */
       for (Class<?> intf : interfaces) {
           int flags = intf.getModifiers();
           if (!Modifier.isPublic(flags)) {
               accessFlags = Modifier.FINAL;
               String name = intf.getName();
               int n = name.lastIndexOf('.');
               String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
               if (proxyPkg == null) {
                   proxyPkg = pkg;
               }
               //如果不是public类型的接口又不在同一个包下抛出异常
               else if (!pkg.equals(proxyPkg)) {
                   throw new IllegalArgumentException(
                           "non-public interfaces from different packages");
               }
           }
       }

       if (proxyPkg == null) {
           // 没有包使用默认的包 com.sun.proxy
           proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
       }

       /*
        * 代理类的名称 按顺序递增 => $proxy0
        */
       long num = nextUniqueNumber.getAndIncrement();
       String proxyName = proxyPkg + proxyClassNamePrefix + num;

       /*
        * 生成代理类的字节数组
        */
       byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
               proxyName, interfaces, accessFlags);
       try {
           //调用native方法生成Class
           return defineClass0(loader, proxyName,
                   proxyClassFile, 0, proxyClassFile.length);
       } catch (ClassFormatError e) {
           throw new IllegalArgumentException(e.toString());
       }
   }
}

主要的步骤如下

1.尝试着用现有的类加载器加载接口,如果成功

2.验证是否为接口,接口是否重复 ,如果成功

3.验证接口访问权限,如果成功

4.获取包的信息,和类名设置,

5.生成代理的字节数组

6.通过native方法defineClass0获取字节数字的具体的Class

这里着重讲解一下如何生成字节数组的

 //生成代理类
 public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
     ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
     /**
      * 生成具体文件字节数组
      * 1.找到所有接口的方法
      * 2.添加object类的三个方法 tostring hashcode equils
      * 3.遍历生成具体的代理方法,代理方法的逻辑都想似,回调我们的代理类
      */
     final byte[] var4 = var3.generateClassFile();
     // private static final boolean saveGeneratedFiles = GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();
     //这就是我们为什么设置sun.misc.ProxyGenerator.saveGeneratedFiles = true的原因,设置后就会生成代理类的文件
     if (saveGeneratedFiles) {
         AccessController.doPrivileged(new PrivilegedAction<Void>() {
             public Void run() {
                 try {
                     int var1 = var0.lastIndexOf(46);
                     Path var2;
                     if (var1 > 0) {
                         //生成path 将.替换成系统文件分隔符
                         Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                         //创建文件夹
                         Files.createDirectories(var3);
                         //具体文件
                         var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                     } else {
                         //没包就放在项目根目录下
                         var2 = Paths.get(var0 + ".class");
                     }
                     //写入到文件中
                     Files.write(var2, var4, new OpenOption[0]);
                     return null;
                 } catch (IOException var4x) {
                     throw new InternalError("I/O exception saving generated file: " + var4x);
                 }
             }
         });
     }

     return var4;
 }

主要的方法是通过ProxyGenerator对象生成字节数组,具体生成的步骤可以如下几步:

1.找到所有接口的方法
2.添加object类的三个方法 tostring hashcode equils
3.遍历生成具体的代理方法,代理方法的逻辑都想似,回调我们的代理类

我可以通过之前展示的一个代理方法即可才想到其中的大概流程.

 public final void hello(String var1) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

最主要不同的地方就是方法名,方法参数,invoke方法的参数。其他的几乎都相同。本文就不做代码上的具体介绍.

然后通过sun.misc.ProxyGenerator.saveGeneratedFiles 的值,来决定是否生成代理文件到磁盘.

如果生成,则生成包信息,类信息,然后将字节数组写入到文件中.默认情况下和在项目的根据下,创建和包名的文件夹和$proxy+i的代理文件.

缓存

从我们的代码中我们可以看到WeekCache中使用多个map进行记录

//cachekey的引用队列,于JVM关系密切详细介绍请看这篇文章http://blog.csdn.net/u012332679/article/details/57489179
private final ReferenceQueue<K> refQueue
        = new ReferenceQueue<>();
    // 最外层map,key=>cacheKey,value => valueMap
    private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
        = new ConcurrentHashMap<>();
    //记录保存value的Supplier对象map   
    private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
        = new ConcurrentHashMap<>();
     //key生成对象
    private final BiFunction<K, P, ?> subKeyFactory;
    //value生成对象
    private final BiFunction<K, P, V> valueFactory;

map中的key是cacheKey,是一种弱应用类型的对象, 
reverseMap的key是cacheValue,同为一种弱应用类型的对象. 
两者也同时为内存回收的主要对象,当某个map中的key失效的时候,在下一次进行get,containsValue,size三个方法的时候都会触发expungeStaleEntries方法,然后将value从reverseMap中清除,valuemap从map中清除.而refQueue的回收,是由Reference中ReferenceHandler轮询去回收的.如果回收了,refQueue.poll会成功触发,然后就想清除操作.

private void expungeStaleEntries() {
    CacheKey<K> cacheKey;
    while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {
      cacheKey.expungeFrom(map, reverseMap);
    }
}
void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
                         ConcurrentMap<?, Boolean> reverseMap) {
  // removing just by key is always safe here because after a CacheKey
  // is cleared and enqueue-ed it is only equal to itself
  // (see equals method)...
  ConcurrentMap<?, ?> valuesMap = map.remove(this);
  // remove also from reverseMap if needed
  if (valuesMap != null) {
    for (Object cacheValue : valuesMap.values()) {
      //移除弱应用CacheValue
      reverseMap.remove(cacheValue);
    }
  }
}

valueMap是回记录真正的代理类相关信息 
key => subKeyFactory.apply(key, parameter) 通过classLoader和interface[]组成 
value=> supplier=>Factory 或者 CacheValue

valuemap中value的两种形式 
1.刚创建时为factory对象 
2.factory.applay方法执行后会替换为CacheValue,并且将CacheValue保存到reverseMap中

小结

总体的逻辑就是这样子, 通过源代码的阅读,对JDK的动态代理实现清晰很多。也从根本上对动态代理的实现过程有了更深的理解,我们此时就可以自己尝试着思考Spring AOP的具体实现。 
本文没有对缓存和代理生成的细节做详细分析和总结,还需要深入的研究

结语

由于个人的能力有限,文章中存在错误的地方或者有宝贵的意见,欢迎大家留言评论。

之后会写Spring AOP 和 cglib动态代理的相关文章。

与君共勉!!

版权声明:仅限于个人学习作用的转载.转载请注明出处 blog.csdn.net/jsu_9207 https://blog.csdn.net/jsu_9207/article/details/51394697
               handler);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值