JDK动态代理源码分析

环境支持

JDK代理是不需要第三方库支持的,只需要 JDK 环境就可以进行代理

使用条件:

  • 必须实现 InvocationHandler 接口;
  • 使用 Proxy.newProxyInstance 产生代理对象;
  • 被代理的对象必须要实现接口;

使用 JDK 动态代理 5 大步骤

  • 通过实现 InvocationHandler 接口来自定义自己的 InvocationHandler;
  • 通过 Proxy.getProxyClass 获得动态代理类;
  • 通过反射机制获得代理类的构造方法,方法签名为 getConstructor(InvocationHandler.class);
  • 通过构造函数获得代理对象并将自定义的 InvocationHandler 实例对象传为参数传入;
  • 通过代理对象调用目标方法;

案例

IHello 接口

public interface IHello {
    void sayHello();
}

IHello 接口实现类

public class HelloImpl implements IHello {

    @Override
    public void sayHello() {
        System.out.println("hello world !!! ");
    }
}

通过实现 InvocationHandler 接口来自定义 InvocationHandler;

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

public class MyInvocationHandler implements InvocationHandler {

    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在覆写的 invoke 方法内反射调用被代理对象的前后实现代码增强逻辑
        System.out.println("----- before invoke ------");
        Object result = method.invoke(target, args);
        System.out.println("----- after invoke ------");
        return result;
    }
}

测试

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;

public class MyProxyTest {

    public static void main(String[] args) {
        // 第一种方法
        demoOne();
        // 第二种方法
        demoTwo();
    }

    private static void demoTwo() {
        // 1、生成$Proxy0的class文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 2、Proxy.getProxyClass 获取动态代理类
        Class proxyClass = Proxy.getProxyClass(IHello.class.getClassLoader(), IHello.class);
        try {
            // 3、通过反射机制获得代理类的构造方法
            Constructor constructor = proxyClass.getConstructor(InvocationHandler.class);
            // 4、通过代理类构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入
            IHello hello = (IHello) constructor.newInstance(new MyInvocationHandler(new HelloImpl()));
            // 5、通过代理对象调用目标方法
            hello.sayHello();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private static void demoOne() {
        IHello target = new HelloImpl();
        // 将2~4步骤封装好的简便方法来创建动态代理对象 $Proxy0
        // 注意:只能强转为接口类,不能是具体的实现类型
        IHello hello = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), 
            new Class[]{IHello.class}, 
            new MyInvocationHandler(target));
        hello.sayHello();
    }
}

源码分析

Proxy.newProxyInstance

    public static Object newProxyInstance(ClassLoader loader,  Class<?>[] interfaces,  InvocationHandler h)
        throws IllegalArgumentException {
        // 检查是否为null
        Objects.requireNonNull(h);
        // 拷贝所有接口类
        final Class<?>[] intfs = interfaces.clone();
        // 获取系统安全管理器
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
           // Reflection.getCallerClass返回调用该方法的方法的调用类;loader:接口的类加载器
           // 进行包访问权限、类加载器权限等检查
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         * 查找所有接口类的代理类
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            // 获取代理构造对象
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            // 检查代理构造对象构造方法权限,如果过不是public,设置为可访问
            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);
        }
    }

newProxyInstance()方法主要执行逻辑

  • 生成代理类
    getProxyClass0(loader, intfs);
  • 获取构造器
    final Constructor<?> cons = cl.getConstructor(constructorParams);
  • 生成代理对象
    cons.newInstance(new Object[]{h});

获取代理对象类

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

    // If the proxy class defined by the given loader implementing
    // the given interfaces exists, this will simply return the cached copy;
    // otherwise, it will create the proxy class via the ProxyClassFactory
    // 提升性能,如果加载过从缓存中获取,没有通过 ProxyClassFactory 创建被代理类
    return proxyClassCache.get(loader, interfaces);
}

会在项目的根目录下的/com/sun/proxy/下生成名为$Proxy0.class 文件

package com.sun.proxy;

import com.gerry.pang.lettucedemo.service.IHello;
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 IHello {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    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 void sayHello() throws  {
        try {
            // InvocationHandler h;
            // 当前代理类对象,被类中方法, 方法入参
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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 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"));
            // 获取到被代理类中的hello方法对象
            m3 = Class.forName("com.gerry.pang.lettucedemo.service.IHello").getMethod("sayHello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

总结

动态代理执行流程

JDK动态代理基于拦截器和反射来实现。通过Proxy.newProxyInstance 自动生成基于接口的被代理对象的类 $Proxy0,在这个类继承了Proxy类,同时实现了IHello接口,即代理类接口,覆写了sayHello方法,其内部是通过反射调用被代理类的sayHello方法,也就是说这里就是个壳子。

有意思的开放性面试题

平时收集一些有意思的开放性面试题,有跳槽打算的小伙伴们可以思考以下一些问题,或许说不定下次面试官就问到你头上了呢。

考察过往工作类

  • 当前负责的系统你觉得还有那些方面可以继续优化

  • 介绍以往项目中你觉得技术上或者设计上比较有难度的工作

  • 介绍以往项目踩过的坑,当时怎么发现的问题,又是解决的

  • 请列举出工作中用到的设计模式?说说为什么用,解决了什么问题

架构设计类

  • 让你设计xxx(秒杀、监控、日志采集、网管)系统,你会怎么设计,需要考虑的点有哪些?

  • 需要你来设计一个xxx中间件,该如何设计?从你知道的点尽可能的多说出些?

  • 给你3天时间,你所负责的系统预计将会有突发50倍的流量。对此你怎么能够最大化的转化这些流量,说说你的思路。条件:不允许加机器加人,以现有的服务和设备。

  • 项目数据库表是你设计的吗?一般要注意什么?如何考虑扩展性?

知识面考察类

  • 描述下一个接口 Http 请求,从浏览器到后端的整个请求过程

  • 对某个服务如何从性能 /稳定性方面进行提升

问题排查类

  • 线上环境遇到 JVM / 内存 / CPU 问题,你会使用哪些工具来分析?找到问题后又该如何去解决呢?

个人考察类

  • 说说你最大的优/缺点?
  • 说说你未来n年内的职业规划?
  • 你说你想实现xx目标,聊聊你想怎么做,有什么计划?
  • 你和别人比,你觉得他们强的地方在哪里?
  • 谈谈你为什么跳槽?

Java8 函数式编程有意思的写法

函数式编程有意思的写法

1、collect相关

字符串拼接

List<Sring> menu = new ArrayList<Sring>();
String shortMenu = menu.stream().collect(Collectors.joining(","));

// 对象list转换为字符串ist
List<String> shopIdList = resultList.parallelStream().map(n -> 
n.getShopId()).collect(Collectors.toList());

// 排序
Collections.sort(tableData, Comparator
.comparing(ShopActivityRateDTO::getActivityRate, Comparator.nullsLast(Comparator.reverseOrder()))
.thenComparing(ShopActivityRateDTO::getNodeCode, Comparator.naturalOrder()));

// list转map
Map<String, TbShopTagRelationDTO> activeTagShopMap = activeTagShopList.parallelStream().collect(Collectors.toMap(TbShopTagRelationDTO::getShopId, Function.identity(), (oldValue, newValue) -> newValue));

Map<String, String> branchCodeAreaNameMap = branchCompanyOfSuperNodeList.parallelStream().collect(HashMap::new, (m, v) -> m.put(v.getBranchCompanyCode(), v.getRegionName()), 
HashMap::putAll);

2、‘尾-递’阶乘

static long factorialTailRecurive(long n) {
    return factorialHelper(1, n);
}

static long factorialHelper(long acc, long n) {
    return n == 1 ? acc : factorialHelper(acc * n, n-1);
}

3、reduce 相关操作

查看列表中的最大值

Option<Integer> max = numbers.stream().reduce('Integer::max);
Option<Integer> max = numbers.stream().reduce('Integer::min);

元素求和/机

int product = numbers.stream().reduce(0, (a , b) -> a + b);
int product = numbers.stream().reduce(1, (a , b) -> a * b);

阶乘

static long factorialTailRecurive(long n) {
    return LongStream.rangeClosed(1, n).reduce(1, (long a, long b) -> a * b);
}

4、Optional相关

串联获取属性

person.flatMap(Person::getCar)
         .flatMap(Car::gerInsurance)
         .map(Insurance::getName)
         .orElse("Unkonwn");

HashMap 根据 value 排序

// creating HashMap
Map<String, Integer> namesAges = new HashMap<>();
 
 // storing the values
namesAges.put("Hari", 35);
namesAges.put("Jhon", 30);
namesAges.put("Jakey", 50);
namesAges.put("kane", 45);
 
Map<String, Integer> sortByValueMap = namesAges.entrySet().stream().sorted(Entry.comparingByValue())
         .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue(),
                        (entry1, entry2) -> entry2, LinkedHashMap::new));

Map<String, Integer> sortedMapInDescending = namesAges.entrySet()
         .stream()
         sorted(Collections.reverseOrder(Entry.comparingByValue()))
         .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue(),
                    (entry1, entry2) -> entry2, LinkedHashMap::new));    

极客时间课程推荐(第一波)

最近在极客时间学习了一些课程,总上感觉老师写的专栏水准都是挺高的,确实学到了不少内容,重点是每个专栏的价格也不贵,所以推荐给大家,顺便也薅下商家的羊毛。
有想学习的小伙伴可以扫描支持一下。:stuck_out_tongue:

推荐一:朱赟de技术管理课

这个是我购买的第一个课程,硅谷女博士的技术管理科,其实里面不光有技术管理,还有一些工作方法,心路成长总结等,女博士啊,看了之后确实收获不少,我觉得你值得拥有。
[![女博士的技术管理课](https://i.loli.net/2019/09/30/c61CyVhAfKgla8E.jpg "女博士的技术管理课")](https://i.loli.net/2019/09/30/c61CyVhAfKgla8E.jpg "女博士的技术管理课")

推荐二:MySQL实战45讲

这个课程身为码农的我们还真的好好学习一下,这个课程我看了1遍,现在正在看第2遍,说实话第一遍看下来确实挺不容易的,里面对我来说70%以上的章节都是新知识点,而且里面确实干活满满,还得继续学,我的预期是在年底前看完看三遍。
[![大牛丁奇的MySQL课程](https://i.loli.net/2019/09/30/8ClFIShy3c7Ekef.jpg "大牛丁奇的MySQL课程")](https://i.loli.net/2019/09/30/8ClFIShy3c7Ekef.jpg "大牛丁奇的MySQL课程")

推荐三:玩转Git三剑客

这个是个视频课程,60集,当时是因为项目第一次用Git所以买了之后,对一些章节进行了突击学习,这种基础课程60节真的是超值,老师讲的也很细。还犹豫啥不会Git的小伙伴,赶紧来一套。
[![超值Git学习视频教程](https://i.loli.net/2019/09/30/kOZqxnWMuyetzrA.jpg "超值Git学习视频教程")](https://i.loli.net/2019/09/30/kOZqxnWMuyetzrA.jpg "超值Git学习视频教程")

Docker常用命令总结

查看docker服务端和客户端版本
# docker version

查看docker容器信息,显示 Docker 系统信息,包括镜像和容器数。
# docker info

帮助命令
# docker --help

查看docker进程
docker ps
[参数列表]
-l 返回最后的容器的状态
-a 看所有容器包括已经停止的容器

从服务器拉取镜像
# docker pull  

列出本地所有镜像
# docker images
# docker image prune // 删除所有悬虚镜像

存储镜像
# docker save 

载入镜像
# docker load 

删除容器
# docker rmi 

将已终止容器启动
# docker start 

停止正在运行的容器
# docker stop

命令来停止容器或然后再启动容器。
# docker restart

提交本地修改容器
# docker commit 

查看docker容器信息
# docker inspect <容器名>

查看容器
# docker container ls  //查看所有运行的容器
# docker container prune  //删除所有的停止的运行的容器

查看数据卷
# docker volume
# docker volume prune //删除所有停止使用的数据卷

查看docker的宿主机端口映射容器端口
# docker port <容器名>

查看docker 系统镜像、容器、数据卷使用情况
# docker sytem df  

从宿主机进入docker容器
docker exec -it <容器id> bash
docker exec -it <容器id> sh
docker exec -it <容器id> bash
docker exec -it <容器id> sh

物理机和容器之间复制命令如下:
容器复制文件到物理机:docker cp <容器名称>:<容器目录> <物理机目录>
物理机复制文件到容器:docker cp <物理机目录> <容器名称>:<容器目录>
eg:docker cp channelWeb:/opt/tomcat/apache-tomcat-7.0.75/webapps/channel-web.war /home/app/

查看docke容器内日志
# docker logs -f --tail=100  <容器名>

查看docker各容器CPU占用情况
# ctop

运行docker虚拟机
# docker run --name <虚拟机别名> --rm -p <宿主机端口:虚拟机端口>  --network  <自定义网络>  <镜像名称>
[参考资料]
[参数列表]
-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上,
-i 则让容器的标准输入保持打开。
-d 让 Docker 容器在后台以守护态(Daemonized)形式运行
-v 标记来创建一个数据卷并挂载到容器里。在一次 run 中多次使用可以挂载多个数据卷。用户也可以通过 :ro 指定为只读。
-p 标示来指定端口。

进入容器 
# docker exec -it <容器名称> bash

导出容器快照
# docker export 

导入容器快照
# docker import