我的JAVA日记(十二)关于proxy

我的JAVA日记(十二)关于proxy

最近好像没什么人写了,换地了还是冬眠了……

代理。四月下旬我拎着箱子由魔都的张江来到帝都的中关村,换了一个程序猿的聚集地,对我而言好像没什么变化,换了张床睡而已。来到北京自然第一件事是:住哪?当初裸辞,还没来得及回味个中滋味,接下来就是投简历、找房子住。定了工作地方才能定住在哪,所以我需要疯狂面试!!!通过各种渠道散简历,有猎头可能通过我发布的信息找到我,给我安排面试:去哪儿,58…… 加上自己的安排,三天面了十多家公司(不含二面,真的是极限酸爽)。最后工作定了。然后就是租房子了,找到中介,看了不到一个小时也定下来了。前后不过三天…… 其中帮我找工作的猎头,帮我订房子的中介起了很大的作用,猎头算是代理我:给我安排面试事宜;中介算是代理房东(物主)跟我谈租房细节。可见代理充斥着我们的生活,方便我们的生活,作为中间人为幕后的我、房东处理事情,但真是在幕后还是我们。

那Java怎么跟代理勾搭上?代理能帮我们做什么?有哪些实践?

代理其实在程序语言中来源于设计模式:代理模式------使用代理对象完成用户请求,屏蔽了用户对真实对象的访问。就像租房子:房东拥有房屋,对房子有控制权,可以将使用权转让。我作为租客需要验证房东是不是有房权证啊 具不具备出租资格啊。验证完了之后要定租金 签订合同啊。房东只是想把房子出租出去,不想关注怎么验证,怎么拟合同,太麻烦了,也不专业。这时候就需要代理出面了。

房东:我要出租房子!

中介:我要验证信息 签订合同 定期收房租!

我:租房子!

各司其职,单一职责 整个世界清晰了。 让我们来实现吧……

接口:可以出租房子的人

public interface Owner {
/**
     * 将房屋出租给租客
     * @param tenantName
     * @return
     */
    Boolean rentHouse(String tenantName);

    /**
     * 返回房东姓名
     * @return
     */
    String findHouseOwnerName();

}

房东实现了Owner接口:

public class HouseOwner implements Owner {

private String ownerName;

    public HouseOwner(String ownerName) {
this.ownerName = ownerName;
    }

public Boolean rentHouse(String tenantName) {
//房东只愿意把房子租给叫vincent的人 就是我拉
        if (StringUtils.equalsIgnoreCase("vincent", tenantName))         {
            System.out.println(StringUtils.join(ownerName + "愿意将房子出租给" + tenantName));
            return Boolean.TRUE;
        } else {
            System.out.println(StringUtils.join(ownerName + "拒绝将房子出租给" + tenantName));
        }
return Boolean.FALSE;
    }

public String findHouseOwnerName() {
return ownerName;
    }
}

中介:中介也实现了Owner接口 但他只是代理

public class HouseOwnerProxy implements Owner {

private Owner houseOwner;

    public HouseOwnerProxy(Owner houseOwner) {
this.houseOwner = houseOwner;
    }

public Boolean rentHouse(String tenantName) {
        System.out.println("第一步:检查房东房屋的合法性");
        Boolean isSuccessRent = houseOwner.rentHouse(tenantName);
        if (isSuccessRent) {
            System.out.println(StringUtils.join("第二步:与", tenantName, "签订租房合同"));
            System.out.println("最后一步:将信息同步给房东" + houseOwner.findHouseOwnerName() + ",完成分成!!!");
        } else {
            System.out.println("房东拒绝出租,看下一家……");
        }
return isSuccessRent;
    }

public String findHouseOwnerName() {
return houseOwner.findHouseOwnerName();
    }
}

租房场景:

public class ProxyTestCenter {
public static void main(String[] args) {
//房东
        HouseOwner houseOwner = new HouseOwner("包租婆");
        //中介
        HouseOwnerProxy proxy = new HouseOwnerProxy(houseOwner);
        //中介出租房屋给vincent
        proxy.rentHouse("vincent");
    }
}

运行结果:

整个流程就是这样呢 。虽说是中介出租房屋给我 其实真正的幕后是房东在处理。

上面就是【静态代理】。

HouseOwnerProxy也像HouseOwner一样实现了Owner接口,但实际调用的是HouseOwner的实现。它在调用的前后加上了其他的功能 :检查房屋的合法性 签订合同 房租等…… 比较装逼的叫法是:无入侵的类代理!!

刚才是代理类为我们处理了额外的事情:检查合法性,制定合同等;房东只关心他自己的部分。我们可以联想代理可以为我们做什么?sample:假如我们需要监控每个业务service方法的处理时间以实现对慢处理的优化【类似DB慢查询log】,该怎么做?

---我们在方法开始记录一个时间 在方法处理完后记录时间 两个时间相减即可。

这样我们需要对我们2000多个service差不多60000个方法都加上这样的代码,这样监控的代码和业务实现的代码耦合在一起,像一坨屎一样!我们要分开,所以我们写了2000个代理类60000个代理方法,这样分开了!找了个实习生终于写完了,,第二天code review的时候 实习生背了锅 被开了。架构师对我说 :“vincent 你负责把他的静态代理改成动态的。” 我答应了,尽管我并不知道什么叫“动态代理”。

为了能够逆袭追 . 上女神,一代码农vincent开始熬夜查资料,终于研究明白了。他打算在下一个分享日给大家分享----JDK动态代理。

“动态代理” ------- 动态地生成代理类! 让我们忘了那个一个一个方法手写代理类的实习生吧,动态生成吧。vincent改了代码,他了解到JDK内置的动态代理核心就是两个类(接口)。InvocationHandler 和 Proxy! InvocationHandler翻译过来叫调用处理器,是一个接口,有一个需要实现的方法

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

proxy就是动态代理类,我们用动态代理来实现业务吧:

public interface Owner {

    Boolean rentHouse(String tenantName) throws InterruptedException;

    void cleanHouse() throws InterruptedException;

    void decorateHouse() throws InterruptedException;

    void sleepWithGirls() throws InterruptedException;

}
public class HouseOwner implements Owner {

private String ownerName;

    public HouseOwner(String ownerName) {
this.ownerName = ownerName;
    }

public Boolean rentHouse(String tenantName) throws InterruptedException {
        TimeUnit.SECONDS.sleep(RandomUtils.nextInt(5));
        return Boolean.TRUE;
    }

public void cleanHouse() throws InterruptedException {
        TimeUnit.SECONDS.sleep(RandomUtils.nextInt(5));
    }

public void decorateHouse() throws InterruptedException {
        TimeUnit.SECONDS.sleep(RandomUtils.nextInt(5));
    }

public void sleepWithGirls() throws InterruptedException {
        TimeUnit.SECONDS.sleep(RandomUtils.nextInt(5));
    }

public String findHouseOwnerName() {
return ownerName;
    }
}

注意下面的代理类,实现了InvocationHandler接口,不再一一实现每一个方法了!!!

public class HouseOwnerProxy implements InvocationHandler {

private Owner houseOwner;

    public HouseOwnerProxy(Owner houseOwner) {
this.houseOwner = houseOwner;
    }

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
        Object result = method.invoke(houseOwner, args);
        long endTime = System.currentTimeMillis();
        System.out.println(StringUtils.join( method.getName() + "方法一共消耗了:", endTime - startTime) + "  毫秒");
        return result;
    }

public Owner  getOwnerProxy(){
return (Owner)     Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
       houseOwner.getClass().getInterfaces(),this);
    }
}

下面是具体场景:

public class ProxyTestCenter {
public static void main(String[] args) throws InterruptedException {
//房东
        HouseOwner houseOwner = new HouseOwner("包租婆");
        //中介
        HouseOwnerProxy proxy = new HouseOwnerProxy(houseOwner);
        //生产代理类
        Owner owner = proxy.getOwnerProxy();

        owner.cleanHouse();
        owner.decorateHouse();
        owner.rentHouse("vincent");
        owner.sleepWithGirls();
    }
}

中介通过InvocationHandler代理了【每一个方法】,运行结果:

这样我们就动态地实现代理,之所以叫“动态”,是getOwnerProxy这里:

(Owner) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
houseOwner.getClass().getInterfaces(),
this)

创建了具体的实现,让我们分析下这个方法的参数,第一个是ClassLoader,第二个是被代理类实现的接口们 ,第三个是代理类。我们猜想,jvm在运行的时候是【动态创建了具有那些接口属性,并加上代理实现】的类,然后调用代理类的 cleanHouse, decorateHouse的方法。

静态代理从代码层面很容易理解,而动态代理总让人觉得有点悬乎,到底发生了什么?为什么按照这样的方式就能代理每一个方法?没办法,看源码喽……

public interface InvocationHandler {

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

InvocationHandler就是一个接口而已,看来就是一颗棋子,没什么料。剩下的就是Proxy类了,由Proxy.newProxyInstance开始吧:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
throws IllegalArgumentException
{
  
if (h == null) {
throw new NullPointerException();
    }
 //获取接口类
final Class<?>[] intfs = interfaces.clone();
//安全检查 跳过
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

/*
     * Look up or generate the designated proxy class.
     */
//重点就在这了  如何获取的Class?????
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
//以下可以看作是通过反射 生成对象 
    try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
// create proxy instance with doPrivilege as the proxy class may
            // implement non-public interfaces that requires a special permission
            return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
return newInstance(cons, ih);
                }
            });
        } else {
return newInstance(cons, ih);
        }
    } catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
    }
}

由上面单拿出来:Class<?> cl = getProxyClass0(loader, intfs);

进去:

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
    }
     //看着应该是自己做的map cache
    return proxyClassCache.get(loader, interfaces);
}

显然proxyClassCache.get 进去:

proxyClassCache.get(loader, interfaces);
public V get(K key, P parameter) {
    Objects.requireNonNull(parameter);

    expungeStaleEntries();

    Object cacheKey = CacheKey.valueOf(key, refQueue);

    // lazily install the 2nd level valuesMap for the particular cacheKey
    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;
        }
    }

// create subKey and retrieve the possible Supplier<V> stored by that
    // subKey from valuesMap
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    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) {
return value;
            }
        }
// else no supplier in cache
        // or a supplier that returned null (could be a cleared CacheValue
        // or a Factory that wasn't successful in installing the CacheValue)

        // lazily construct a Factory
        if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }

if (supplier == null) {
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) {
// successfully installed Factory
                supplier = factory;
            }
// else retry with winning supplier
        } else {
if (valuesMap.replace(subKey, supplier, factory)) {
// successfully replaced
                // cleared CacheEntry / unsuccessful Factory
                // with our Factory
                supplier = factory;
            } else {
// retry with current supplier
                supplier = valuesMap.get(subKey);
            }
        }
    }
}

不再赘述,找到Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

进去(看源码重要的不是看什么 而是不看什么!):

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

     //掉过  直奔主题
    /*
     * Generate the specified proxy class.
     */
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        proxyName, interfaces);

return defineClass0(loader, proxyName,
                            proxyClassFile, 0, proxyClassFile.length);

}

由上面进入:ProxyGenerator.generateProxyClass( proxyName, interfaces)

//看名字是多么的直白啊 
public static byte[] generateProxyClass(final String var0, Class[] var1) {
    ProxyGenerator var2 = new ProxyGenerator(var0, var1);
//通过var2来创建类的字节码 核心实现 有很多的字节操作细节
    final byte[] var3 = var2.generateClassFile();
//  saveGeneratedFiles参数是new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();  如果为true 创建class到文件系统
  
    if(saveGeneratedFiles) {
        AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
                    Path var2;
                    if(var1 > 0) {
                        Path var3x = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar), new String[0]);
                        Files.createDirectories(var3x, new FileAttribute[0]);
                        var2 = var3x.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                    } else {
                        var2 = Paths.get(var0 + ".class", new String[0]);
                    }

                    Files.write(var2, var3, new OpenOption[0]);
                    return null;
                } catch (IOException var4) {
throw new InternalError("I/O exception saving generated file: " + var4);
                }
            }
        });
    }

return var3;
}

generateProxyClass还能更明显么?终于找到你。我们通过sun.misc.ProxyGenerator.saveGeneratedFiles能找到class文件,OK ,我就想找到动态生成的class文件看一眼,通过代码很难分析到底生成到那个路径了,只能debug了,我就debug刚才的main方法,万万没想到:

默认的值是false。也就是生成的class不落盘,那咋办?刚才我们知道:

saveGeneratedFiles = ((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();

哈. 一个参数而已,我们自己重新设置,所以有了:

public class FuckProxyGenerator {

private static final String SAVEGENERATEDFLAG = "sun.misc.ProxyGenerator.saveGeneratedFiles";

    public static void generatorClass() throws IOException {
        Boolean systemParam = Boolean.valueOf(System.getProperty(SAVEGENERATEDFLAG));
        if (Boolean.FALSE == systemParam) {
            System.setProperty(SAVEGENERATEDFLAG, Boolean.TRUE.toString());
        }
        systemParam = Boolean.valueOf(System.getProperty(SAVEGENERATEDFLAG));
        byte[] byteArray = ProxyGenerator.generateProxyClass("ProxyClass", HouseOwner.class.getInterfaces());
        FileUtils.writeByteArrayToFile(new File("/Users/vincent/Documents/tempFiles/TargetHouseOwnerProxy.class"), byteArray);

    }

public static void main(String[] args) throws IOException {
generatorClass();
    }

}

反编译TargetHouseOwnerProxy.class

import com.starbucks.demo.proxy.Owner;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class ProxyClass extends Proxy
implements Owner
{
private static Method m3;
    private static Method m1;
    private static Method m6;
    private static Method m4;
    private static Method m0;
    private static Method m5;
    private static Method m2;

    public ProxyClass(InvocationHandler paramInvocationHandler)
throws
    {
super(paramInvocationHandler);
    }

public final Boolean rentHouse(String paramString)
throws InterruptedException
    {
try
        {
return (Boolean)this.h.invoke(this, m3, new Object[] { paramString });
        }
catch (Error|RuntimeException|InterruptedException localError)
        {
throw localError;
        }
catch (Throwable localThrowable)
        {
throw new UndeclaredThrowableException(localThrowable);
        }
    }

public final boolean equals(Object paramObject)
throws
    {
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 sleepWithGirls()
throws InterruptedException
    {
try
        {
this.h.invoke(this, m6, null);
            return;
        }
catch (Error|RuntimeException|InterruptedException localError)
        {
throw localError;
        }
catch (Throwable localThrowable)
        {
throw new UndeclaredThrowableException(localThrowable);
        }
    }

public final void cleanHouse()
throws InterruptedException
    {
try
        {
this.h.invoke(this, m4, null);
            return;
        }
catch (Error|RuntimeException|InterruptedException localError)
        {
throw localError;
        }
catch (Throwable localThrowable)
        {
throw new UndeclaredThrowableException(localThrowable);
        }
    }

public final int hashCode()
throws
    {
try
        {
return ((Integer)this.h.invoke(this, m0, null)).intValue();
        }
catch (Error|RuntimeException localError)
        {
throw localError;
        }
catch (Throwable localThrowable)
        {
throw new UndeclaredThrowableException(localThrowable);
        }
    }

public final void decorateHouse()
throws InterruptedException
    {
try
        {
this.h.invoke(this, m5, null);
            return;
        }
catch (Error|RuntimeException|InterruptedException localError)
        {
throw localError;
        }
catch (Throwable localThrowable)
        {
throw new UndeclaredThrowableException(localThrowable);
        }
    }

public final String toString()
throws
    {
try
        {
return (String)this.h.invoke(this, m2, null);
        }
catch (Error|RuntimeException localError)
        {
throw localError;
        }
catch (Throwable localThrowable)
        {
throw new UndeclaredThrowableException(localThrowable);
        }
    }

static
    {
try
        {
m3 = Class.forName("com.starbucks.demo.proxy.Owner").getMethod("rentHouse", new Class[] { Class.forName("java.lang.String") });
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
            m6 = Class.forName("com.starbucks.demo.proxy.Owner").getMethod("sleepWithGirls", new Class[0]);
            m4 = Class.forName("com.starbucks.demo.proxy.Owner").getMethod("cleanHouse", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m5 = Class.forName("com.starbucks.demo.proxy.Owner").getMethod("decorateHouse", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            return;
        }
catch (NoSuchMethodException localNoSuchMethodException)
        {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        }
catch (ClassNotFoundException localClassNotFoundException)
        {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
    }
}import com.starbucks.demo.proxy.Owner;
        import java.lang.reflect.InvocationHandler;
        import java.lang.reflect.Method;
        import java.lang.reflect.Proxy;
        import java.lang.reflect.UndeclaredThrowableException;

public final class ProxyClass extends Proxy
implements Owner
{
private static Method m3;
    private static Method m1;
    private static Method m6;
    private static Method m4;
    private static Method m0;
    private static Method m5;
    private static Method m2;

    public ProxyClass(InvocationHandler paramInvocationHandler)
throws
    {
super(paramInvocationHandler);
    }

public final Boolean rentHouse(String paramString)
throws InterruptedException
    {
try
        {
return (Boolean)this.h.invoke(this, m3, new Object[] { paramString });
        }
catch (Error|RuntimeException|InterruptedException localError)
        {
throw localError;
        }
catch (Throwable localThrowable)
        {
throw new UndeclaredThrowableException(localThrowable);
        }
    }

public final boolean equals(Object paramObject)
throws
    {
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 sleepWithGirls()
throws InterruptedException
    {
try
        {
this.h.invoke(this, m6, null);
            return;
        }
catch (Error|RuntimeException|InterruptedException localError)
        {
throw localError;
        }
catch (Throwable localThrowable)
        {
throw new UndeclaredThrowableException(localThrowable);
        }
    }

public final void cleanHouse()
throws InterruptedException
    {
try
        {
this.h.invoke(this, m4, null);
            return;
        }
catch (Error|RuntimeException|InterruptedException localError)
        {
throw localError;
        }
catch (Throwable localThrowable)
        {
throw new UndeclaredThrowableException(localThrowable);
        }
    }

public final int hashCode()
throws
    {
try
        {
return ((Integer)this.h.invoke(this, m0, null)).intValue();
        }
catch (Error|RuntimeException localError)
        {
throw localError;
        }
catch (Throwable localThrowable)
        {
throw new UndeclaredThrowableException(localThrowable);
        }
    }

public final void decorateHouse()
throws InterruptedException
    {
try
        {
this.h.invoke(this, m5, null);
            return;
        }
catch (Error|RuntimeException|InterruptedException localError)
        {
throw localError;
        }
catch (Throwable localThrowable)
        {
throw new UndeclaredThrowableException(localThrowable);
        }
    }

public final String toString()
throws
    {
try
        {
return (String)this.h.invoke(this, m2, null);
        }
catch (Error|RuntimeException localError)
        {
throw localError;
        }
catch (Throwable localThrowable)
        {
throw new UndeclaredThrowableException(localThrowable);
        }
    }

static
    {
try
        {
m3 = Class.forName("com.starbucks.demo.proxy.Owner").getMethod("rentHouse", new Class[] { Class.forName("java.lang.String") });
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
            m6 = Class.forName("com.starbucks.demo.proxy.Owner").getMethod("sleepWithGirls", new Class[0]);
            m4 = Class.forName("com.starbucks.demo.proxy.Owner").getMethod("cleanHouse", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m5 = Class.forName("com.starbucks.demo.proxy.Owner").getMethod("decorateHouse", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            return;
        }
catch (NoSuchMethodException localNoSuchMethodException)
        {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        }
catch (ClassNotFoundException localClassNotFoundException)
        {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
    }
}

其中

public final class ProxyClass extends Proxy implements Owner

就是动态生成的代理类。

抽出一个方法看一下:

Boolean rentHouse(String paramString)

return (Boolean)this.h.invoke(this, m3, new Object[] { paramString });

就是调用了实现InvocationHandler接口的代理类中得invoke方法。

public ProxyClass(InvocationHandler paramInvocationHandler) throws {
super(paramInvocationHandler);
}

也就是说:动态代理是jvm运行时会动态创建一个代理类 实现其中每一个方法。在调用时实际是调用那个代理类的方法而已。是动态的生成静态代理类!

这样貌似明白了动态代理,开始用动态代理替代静态的代理----那2000个service的运行时间监控。又一轮的code review,架构师看了vincent的代码,说“vincent,你可以用CGLib啊!再优化优化,太业余了” vincent卒 享年25岁!

发布于 2015-11-29

文章被以下专栏收录

    用来回顾学习JAVA历程,复习所学JAVA姿势,给大家围观。希望能给准备入坑的程序员提供一些参考。 尽量不写和主题无关内容,不定期更新。 请不要找我问问题,我解决不了╮(╯▽╰)╭。真的。 所有人都能在专栏文章下评论,请多找我问题,让我知道自己的欠缺(๑•ㅂ•)و✧。 如果有暴力装逼行径,请毫不留情指出我的破腚。啊不是,破绽。 真不知道脸会不会被打肿……