资讯专栏INFORMATION COLUMN

Java动态代理深度解析

whinc / 972人阅读

摘要:动态代理深度解析引言说起动态代理,很多人可能都没有直接去使用过。因为的动态代理只能代理接口,而不能代理原始的类。接下来是真正压轴的环节,实现自己的动态代理类。

Java动态代理深度解析 引言

说起动态代理,很多人可能都没有直接去使用过。但是只要用过Spring,那动态代理就是一个是个绕不过的坎,因为Spring的核心特性之一AOP就是基于动态代理来实现的,那么什么情况下需要用到动态代理呢?

场景

考虑这样一个教师的接口:

public interface Teacher {
    void teach();
}

假设我们有一个TeacherChan的实现类,陈老师教的是摄影:

public class TeacherChan implements Teacher {

    @Override
    public void teach() {
        System.out.println("大家好,我是陈老师,我教大家摄影!");
    }
    
}

另外还有一个TeacherCang的实现类,苍老师教的是生物:

public class TeacherCang implements Teacher {

    @Override
    public void teach() {
        System.out.println("大家好,我是苍老师,我教大家生物!");
    }
    
}

不管是陈老师还是苍老师,只要实现了Teacher这个接口,给我们传道授业解惑,为了礼貌起见,我们总应该给人家问声好吧。而问好这件事不需要老师主动要求,可以交给代理来做,每次有老师来上课,代理自动做了问好这件事。而代理类又分为静态代理和动态代理,静态代理在编写代码时已经确定了要代理的类,只能代理单一的类型,在此略过,今天重点讲动态代理。

Java动态代理

Java动态代理创建代理类的方法为:

Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

其中ClassLoader是用来定义代理类的class文件的,用系统默认的就好,interfaces是要代理的接口,InvocationHandler是用来实际执行代理方法的接口,常用做法是实现该接口,并将需要代理的类实例对象传进去。
实现自己的方法执行器:

public class JdkDynamicProxy implements InvocationHandler {

    private Object proxied;

    public JdkDynamicProxy(Object object) {
        this.proxied = object;
    }

    /**
     * proxy为创建的代理类实例,method是本次被代理的方法,args是方法的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("-------------------老师好[by jdk动态代理]-------------------");
        // 执行代理方法
        Object obj = method.invoke(proxied, args);
        System.out.println("-------------------老师再见[by jdk动态代理]-------------------");
        // 返回方法执行结果
        return obj;
    }

}

创建代理类对象并执行:

// 代理陈老师
Teacher proxy1 = (Teacher) Proxy.newProxyInstance(Teacher.class.getClassLoader(),
        new Class[]{Teacher.class}, new JdkDynamicProxy(new TeacherChan()));
proxy1.teach();
// 代理苍老师
Teacher proxy2 = (Teacher) Proxy.newProxyInstance(Teacher.class.getClassLoader(),
        new Class[]{Teacher.class}, new JdkDynamicProxy(new TeacherCang()));
proxy2.teach();

输出结果:

-------------------老师好[by jdk动态代理]-------------------
大家好,我是陈老师,我教大家摄影!
-------------------老师再见[by jdk动态代理]-------------------
-------------------老师好[by jdk动态代理]-------------------
大家好,我是苍老师,我教大家生物!
-------------------老师再见[by jdk动态代理]-------------------

实际上,Java会过滤掉接口所有final、native等方法,并为剩下的所有符合条件的方法生成代理方法。而且,熟悉Spring的朋友应该知道,Spring的AOP机制的实现不仅使用了Java的动态代理,而且还引入了CGLib。因为Java的动态代理只能代理接口,而不能代理原始的类。那么为什么Java不能代理类呢,答案是Java的单继承机制。

深入Java动态代理的实现

Java的动态代理是怎么实现的呢?其实很简单,就是运行时生成一个代理类,该类实现了需要代理的接口,并返回这个代理类的实例对象给调用者。调试进入Proxy.newProxyInstance()的方法内部,可以看到在Proxy内部生成class字节码的方法:

// 生成的代理类名前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 生成的代理类名序号
private static final AtomicLong nextUniqueNumber = new AtomicLong();

// 序号值加1
long num = nextUniqueNumber.getAndIncrement();
// 代理类名:$ProxyN
String proxyName = proxyPkg + proxyClassNamePrefix + num;

...

// 生成代理类的class字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

不难看出,生成的代理类的类名是$ProxyN的形式,所以我们经常会看到$Proxy0这个类,就是动态代理在运行时生成的。

既然知道了动态代理是怎么生成代理类的了,那我们不妨把它生成的类打印出来看看,到底里面是怎么实现的。

// 调用Java生成字节码文件的方法
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        "com.test.$proxy0.class", new Class[]{Teacher.class}, Modifier.FINAL);
// 输出文件到本地
FileOutputStream out = new FileOutputStream(new File("/temp/$Proxy0.class"));
out.write(proxyClassFile);
out.flush();
out.close();

用java反编译软件打开生成的$Proxy0.class文件,内容如下:

package com.test.$proxy0;

import com.demos.java.basedemo.proxy.bean.Teacher;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class class extends Proxy
  implements Teacher
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;

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

  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 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);
    }
  }

  public final void teach()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException 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);
    }
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("com.demos.java.basedemo.proxy.bean.Teacher").getMethod("teach", 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());
    }
  }
}

可以看出,代理类不仅代理了teach()这个方法,还代理了toString()、equals()和hashCode()方法,因为java中所有的类都是继承Object类的,所以自然有这些方法。其中还有一个地方要特别注意:生成的代理类继承了Proxy这个类,因此它只能通过实现接口来代理其他的类,现在知道为什么Java动态代理只能代理接口了。

既然都讲到这个份上了,当然不能不继续深入了。接下来是真正压轴的环节,实现自己的动态代理类。

手动实现动态代理

首先分析一下实现动态代理需要的步骤:

1.拼接java类实现,并生成class字节码
2.加载class字节码到JVM
3.实现自己的InvocationHandler处理器
4.对外提供接口

既然知道了Java动态代理的原理,我们不妨借鉴Java生成的class文件格式,同时去掉默认继承的Proxy,使得我们自己的动态代理既可以代理接口,也可以代理类。先写出代理类的格式(假设代理的是类TeacherChan):

public class $Proxy0 extends TeacherChan {

private InvocationHandler handler;

private static Method m0;

static {
    try {
        // 利用反射获取TeacherChan的teach()方法
        m0 = TeacherChan.class.getMethod("teach", new Class[]{});
    } catch (NoSuchMethodException ne) {
        throw new NoSuchMethodError(ne.getMessage());
    }
}

// 构造方法中传入代理类处理器
public $Proxy0(InvocationHandler handler) {
    this.handler = handler;
}

public void teach() {
    try {
        // 收集teach()方法传入的参数,此处参数为空
        Object[] args = new Object[]{};
        // 执行代理类的teach()
        Object result = handler.invoke(this, m0, args);
        // 如果有返回值,此处要返回result
    } catch (Error|RuntimeException e) {
        throw e;
    } catch (Throwable t) {
        throw new UndeclaredThrowableException(t);
    }
}

}

好了,生成类的格式大概就是这样设计,实际编写代码时需要处理参数、返回值和异常的情况,略微有点繁琐。下面是动态类生成器:

public class MyProxyGenerator {

    // 换行符
    public static final String LINE_SEPARATOR = "
";
    // 动态代理类包名
    public static final String PROXY_CLASS_PACKAGE = "com.demos.proxy";
    // 动态代理类名前缀
    public static final String PROXY_CLASS_NAME_PREFIX = "$Proxy";
    // 动态代理类文件索引
    public static final AtomicLong INDEX_GENERATOR = new AtomicLong();
    // 动态代理生成文件临时目录
    public static final String PROXY_CLASS_FILE_PATH = "/temp";

    /**
     * 生成代理类并加载到JVM
     * @param clazz
     * @param methods
     * @throws Exception
     */
    public static Class generateAndLoadProxyClass(Class clazz, Method[] methods) throws Exception {
        long index = INDEX_GENERATOR.getAndIncrement();
        // 代理类类名
        String className = PROXY_CLASS_NAME_PREFIX + index;
        String fileName = PROXY_CLASS_FILE_PATH + File.separator + className + ".java";
        FileWriter writer = null;
        try {
            // 生成.java文件
            writer = new FileWriter(new File(fileName));
            writer.write(generateClassCode(PROXY_CLASS_PACKAGE, className, clazz, methods));
            writer.flush();
            // 编译.java文件
            compileJavaFile(fileName);
            // 加载class到JVM
            String classPath = PROXY_CLASS_FILE_PATH + File.separator + className + ".class";
            Class proxyClass = MyClassLoader.getInstance().findClass(classPath, PROXY_CLASS_PACKAGE + "." + className);
            return proxyClass;
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }

    /**
     * 编译.java文件
     * @param fileName
     * @throws IOException
     */
    private static void compileJavaFile(String fileName) throws IOException {
        compileByTools(fileName);
//        compileByExec(fileName);
    }

    /**
     * 使用Runtime执行javac命令
     * 注意: 需要指定classpath, 否则找不到依赖的类
     * 建议使用compileByTools()
     * @param fileName
     * @throws IOException
     */
    @Deprecated
    private static void compileByExec(String fileName) throws IOException {
        // 获取当前的classpath
        String classpath = MyProxyGenerator.class.getResource("/").getPath();
        // 运行命令: javac -classpath ${classpath} ${filepath}
        String command = "javac -classpath " + classpath + " " + fileName;
        Process process = Runtime.getRuntime().exec(command);
        // 等待执行, 并输出错误日志
        try {
            InputStream errorStream = process.getErrorStream();
            InputStreamReader inputStreamReader = new InputStreamReader(errorStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
            int exitVal = process.waitFor();
            System.out.println("Process exitValue: " + exitVal);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 使用JDK自带的JavaCompiler
     * @param fileName
     * @throws IOException
     */
    private static void compileByTools(String fileName) throws IOException {
        // 获取系统Java编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        // 获取标准文件管理器实例
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        try {
            Iterable units = fileManager.getJavaFileObjects(fileName);
            JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
            task.call();
        } finally {
            fileManager.close();
        }
    }

    /**
     * 拼接 class 代码片段
     * @param packageName   代理类包名
     * @param clazz         要代理的类型
     * @return
     */
    private static String generateClassCode(String packageName, String className, Class clazz, Method[] methods) throws Exception {

        StringBuilder classCodes = new StringBuilder();

        /*--------------------包名和依赖 start--------------------*/
        classCodes.append("package ").append(packageName).append(";").append(LINE_SEPARATOR);
        classCodes.append(LINE_SEPARATOR);
        classCodes.append("import java.lang.reflect.*;").append(LINE_SEPARATOR);
        classCodes.append(LINE_SEPARATOR);
        /*--------------------包名和依赖 start--------------------*/

        /*--------------------类定义 start--------------------*/
        classCodes.append("public class ").append(className);
        if (clazz.isInterface()) {
            classCodes.append(" implements ");
        } else {
            classCodes.append(" extends ");
        }
        classCodes.append(clazz.getName()).append(" {").append(LINE_SEPARATOR);
        classCodes.append(LINE_SEPARATOR);
        /*--------------------类定义 end--------------------*/

        /*--------------------声明变量InvocationHandler start--------------------*/
        classCodes.append("private InvocationHandler handler;").append(LINE_SEPARATOR);
        classCodes.append(LINE_SEPARATOR);
        /*--------------------声明变量InvocationHandler end--------------------*/

        /*--------------------声明代理方法 start--------------------*/
        for (int i = 0; i < methods.length; i++) {
            classCodes.append("private static Method m").append(i).append(";").append(LINE_SEPARATOR);
        }
        classCodes.append(LINE_SEPARATOR);
        /*--------------------声明代理方法 end--------------------*/

        /*--------------------代理方法对象初始化 start--------------------*/
        classCodes.append("static {").append(LINE_SEPARATOR);
        classCodes.append("    ").append("try {").append(LINE_SEPARATOR);
        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            classCodes.append("    ").append("    ").append("m").append(i).append(" = ").append(clazz.getName())
                    .append(".class.getMethod("").append(method.getName()).append("", new Class[]{");
            // 方法参数
            Parameter[] params = method.getParameters();
            if (params.length != 0) {
                for (int j = 0; j < params.length; j++) {
                    if (j != 0) {
                        classCodes.append(", ");
                    }
                    Parameter param = params[j];
                    classCodes.append(param.getType().getName()).append(".class");
                }
            }
            classCodes.append("});").append(LINE_SEPARATOR);
        }
        classCodes.append("    ").append("} catch (NoSuchMethodException ne) {").append(LINE_SEPARATOR);
        classCodes.append("    ").append("    ").append("throw new NoSuchMethodError(ne.getMessage());").append(LINE_SEPARATOR);
        classCodes.append("    ").append("}").append(LINE_SEPARATOR);
        classCodes.append("}").append(LINE_SEPARATOR);
        classCodes.append(LINE_SEPARATOR);
        /*--------------------代理方法对象初始化 end--------------------*/

        /*--------------------定义构造函数 start--------------------*/
        classCodes.append("public ").append(className).append("(InvocationHandler handler) {").append(LINE_SEPARATOR);
        classCodes.append("    ").append("this.handler = handler;").append(LINE_SEPARATOR);
        classCodes.append("}").append(LINE_SEPARATOR);
        classCodes.append(LINE_SEPARATOR);
        /*--------------------定义构造函数 end--------------------*/

        /*--------------------填充其他函数 start--------------------*/
        classCodes.append(generateMethodCode(clazz, methods));
        /*--------------------填充其他函数 end--------------------*/

        // 类结束
        classCodes.append("}").append(LINE_SEPARATOR);

        return classCodes.toString();
    }

    /**
     * 拼接 method 代码片段
     * @param clazz
     * @param methods
     * @return
     * @throws Exception
     */
    private static String generateMethodCode(Class clazz, Method[] methods) throws Exception {

        StringBuilder methodCodes = new StringBuilder();

        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            // 返回类型
            String returnType = method.getReturnType().getName();
            // 参数列表
            Parameter[] params = method.getParameters();
            // 异常列表
            Class[] exceptionTypes = method.getExceptionTypes();

            /*--------------------方法定义 start--------------------*/
            methodCodes.append("public ").append(returnType).append(" ").append(method.getName());
            methodCodes.append("(");
            // 填充参数
            if (params.length != 0) {
                for (int j = 0; j < params.length; j++) {
                    if (j != 0) {
                        methodCodes.append(", ");
                    }
                    Parameter param = params[j];
                    methodCodes.append(param.getType().getName()).append(" ").append(param.getName());
                }
            }
            methodCodes.append(")");
            // 填充异常
            if (exceptionTypes.length != 0) {
                methodCodes.append(" throws ");
                for (int j = 0; j < exceptionTypes.length; j++) {
                    if (j != 0) {
                        methodCodes.append(", ");
                    }
                    methodCodes.append(exceptionTypes[j].getName());
                }
            }
            methodCodes.append(" {").append(LINE_SEPARATOR);
            /*--------------------方法定义 end--------------------*/

            /*--------------------方法体 start--------------------*/
            methodCodes.append("    ").append("try {").append(LINE_SEPARATOR);
            // 方法参数
            methodCodes.append("    ").append("    ").append("Object[] args = new Object[]{");
            if (params.length != 0) {
                for (int j = 0; j < params.length; j++) {
                    if (j != 0) {
                        methodCodes.append(", ");
                    }
                    Parameter param = params[j];
                    methodCodes.append(param.getName());
                }
            }
            methodCodes.append("};").append(LINE_SEPARATOR);
            // 执行InvocationHandler.invoke()
            methodCodes.append("    ").append("    ").append("Object result = handler.invoke(this, m").append(i)
                    .append(", args);").append(LINE_SEPARATOR);
            // 返回结果
            if (!"void".equals(returnType)) {
                methodCodes.append("    ").append("    ").append("return (").append(returnType).append(") result;").append(LINE_SEPARATOR);
            }
            // 异常处理
            methodCodes.append("    ").append("} catch (Error|RuntimeException");
            for (Class exceptionType : exceptionTypes) {
                methodCodes.append("|").append(exceptionType.getName());
            }
            methodCodes.append(" e) {").append(LINE_SEPARATOR);
            methodCodes.append("    ").append("    ").append("throw e;").append(LINE_SEPARATOR);
            methodCodes.append("    ").append("} catch (Throwable t) {").append(LINE_SEPARATOR);
            methodCodes.append("    ").append("    ").append("throw new UndeclaredThrowableException(t);").append(LINE_SEPARATOR);
            methodCodes.append("    ").append("}").append(LINE_SEPARATOR);
            /*--------------------方法体 end--------------------*/

            // 方法结束
            methodCodes.append("}").append(LINE_SEPARATOR).append(LINE_SEPARATOR);
        }

        return methodCodes.toString();
    }

}

实际上只是拼接前面给出的代理类实现而已,代码量有点大,但并不难理解。

下一步,实现自己的类加载器,来加载生成的class字节码:

public class MyClassLoader extends ClassLoader {

    private static MyClassLoader loader;

    private MyClassLoader() {

    }

    public static MyClassLoader getInstance() {
        if (loader == null) {
            synchronized (MyClassLoader.class) {
                // 得到锁首先检查loader是否已经存在, 避免重复创建
                if (loader == null) {
                    loader = new MyClassLoader();
                }
            }
        }
        return loader;
    }

    /**
     * 加载class文件,并返回类型对象
     *
     * @param filePath
     * @param className
     * @return
     * @throws ClassNotFoundException
     */
    public Class findClass(String filePath, String className) throws ClassNotFoundException {
        try {
            // 读取指定class文件的字节码
            byte[] classBytes = Files.readAllBytes(Paths.get(filePath));
            // 加载类并返回class类型对象
            Class clazz = defineClass(className, classBytes, 0, classBytes.length);
            return clazz;
        } catch (IOException e) {
            e.printStackTrace();
        }
        throw new ClassNotFoundException(className);
    }

}

到这一步,复杂的工作基本做完了,接下来只剩下自定义处理器类和对外接口了

自定义处理器类与Java动态代理的方式相同:

public class MyInvocationHandler implements InvocationHandler {

    private Object proxied;

    public MyInvocationHandler(Object object) {
        this.proxied = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("-------------------老师好[by 自定义动态代理]-------------------");
        Object obj = method.invoke(proxied, args);
        System.out.println("-------------------老师再见[by 自定义动态代理]-------------------");
        return obj;
    }

}

对外接口:

public class MyDynamicProxy {

    public static  T newProxyInstance(Class clazz, InvocationHandler handler) throws Exception {
        // 要代理的方法: public & !final
        Method[] proxyMethods = Arrays.stream(clazz.getMethods())
                .filter(method -> !Modifier.isFinal(method.getModifiers()))
                .collect(Collectors.toList())
                .toArray(new Method[0]);
        // 生成的代理类
        Class proxyClass = MyProxyGenerator.generateAndLoadProxyClass(clazz, proxyMethods);
        // 代理类的构造方法
        Constructor c = proxyClass.getConstructor(InvocationHandler.class);
        // 创建代理类对象
        Object proxyObj = c.newInstance(handler);
        return (T) proxyObj;
    }

}

搞定,测试一下效果:

TeacherChan proxy1 = MyDynamicProxy.newProxyInstance(
        TeacherChan.class, new MyInvocationHandler(new TeacherChan()));
proxy1.teach();

TeacherCang proxy2 = MyDynamicProxy.newProxyInstance(
        TeacherCang.class, new MyInvocationHandler(new TeacherCang()));
proxy2.teach();

输出:

-------------------老师好[by 自定义动态代理]-------------------
大家好,我是陈老师,我教大家摄影!
-------------------老师再见[by 自定义动态代理]-------------------
-------------------老师好[by 自定义动态代理]-------------------
大家好,我是苍老师,我教大家生物!
-------------------老师再见[by 自定义动态代理]-------------------

完美!大功告成!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70841.html

相关文章

  • spring-cloud-feign源码深度解析

    摘要:内部使用了的动态代理为目标接口生成了一个动态代理类,这里会生成一个动态代理原理统一的方法拦截器,同时为接口的每个方法生成一个拦截器,并解析方法上的元数据,生成一个请求模板。的核心源码解析到此结束了,不知道是否对您有无帮助,可留言跟我交流。 Feign是一个声明式的Web服务客户端。这使得Web服务客户端的写入更加方便 要使用Feign创建一个界面并对其进行注释。它具有可插拔注释支持,包...

    vibiu 评论0 收藏0
  • 手把手教你基于Netty实现一个基础的RPC框架(通俗易懂)

    摘要:是一个分布式服务框架,以及治理方案。手写注意要点手写注意要点基于上文中对于协议的理解,如果我们自己去实现,需要考虑哪些技术呢其实基于图的整个流程应该有一个大概的理解。基于手写实现基于手写实现理解了协议后,我们基于来实现一个通信框架。阅读这篇文章之前,建议先阅读和这篇文章关联的内容。[1]详细剖析分布式微服务架构下网络通信的底层实现原理(图解)[2][年薪60W的技巧]工作了5年,你真的理解N...

    番茄西红柿 评论0 收藏2637
  • Java面试 32个核心必考点完全解析

    摘要:如问到是否使用某框架,实际是是问该框架的使用场景,有什么特点,和同类可框架对比一系列的问题。这两个方向的区分点在于工作方向的侧重点不同。 [TOC] 这是一份来自哔哩哔哩的Java面试Java面试 32个核心必考点完全解析(完) 课程预习 1.1 课程内容分为三个模块 基础模块: 技术岗位与面试 计算机基础 JVM原理 多线程 设计模式 数据结构与算法 应用模块: 常用工具集 ...

    JiaXinYi 评论0 收藏0
  • cockroach 爬虫:又一个 java 爬虫实现

    摘要:爬虫又一个爬虫实现原文简介小强当时不知道为啥选了这么个名字,又长又难记,导致编码的过程中因为单词的拼写问题耽误了好长时间。我是一个小强爬虫线程数健壮说到健壮,这里主要体现在以下几个方面应对封锁这里我们使用动态代理来解决这个问题。 cockroach 爬虫:又一个 java 爬虫实现 原文 简介 cockroach[小强] 当时不知道为啥选了这么个名字,又长又难记,导致编码的过程中因为单...

    liangzai_cool 评论0 收藏0
  • Spring自定义注解不生效原因解析及解决方法

    摘要:自定义注解不生效原因解析及解决方法背景项目中,自己基于实现了一套缓存注解。但是最近出现一种情况缓存竟然没有生效,大量请求被击穿到层,导致压力过大。至此,问题得到解决。 自定义注解不生效原因解析及解决方法 背景: 项目中,自己基于spring AOP实现了一套java缓存注解。但是最近出现一种情况:缓存竟然没有生效,大量请求被击穿到db层,导致db压力过大。现在我们看一下具体代码情形(代...

    xbynet 评论0 收藏0

发表评论

0条评论

whinc

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<