2023-03-13  阅读(3)
原文作者:青花鱼罐头 原文地址:https://blog.csdn.net/qq_32782279/article/details/107826317

1.概述

Java 中的反射虽然功能强大,但对大多数开发人员来说,写出高质量的反射代码还是 有一定难度的。MyBatis 中专门提供了反射模块,该模块对 Java 原生的反射进行了良好的封装,提了更加简洁易用的 API,方便上层使调用,并且对反射操作进行了一系列优化,例如缓存了类的元数据,提高了反射操作的性能。

2. Reflector

org.apache.ibatis.reflection.Reflector ,反射器,每个 Reflector 对应一个类。Reflector 会缓存反射操作需要的类的信息,例如:构造方法、属性名、setting / getting 方法等等。代码如下:

    public class Reflector {
    
        /**
         * 对应的类
         */
        private final Class<?> type;
        /**
         * 可读属性数组
         */
        private final String[] readablePropertyNames;
        /**
         * 可写属性集合
         */
        private final String[] writeablePropertyNames;
        /**
         * 属性对应的 setting 方法的映射。
         *
         * key 为属性名称
         * value 为 Invoker 对象
         */
        private final Map<String, Invoker> setMethods = new HashMap<>();
        /**
         * 属性对应的 getting 方法的映射。
         *
         * key 为属性名称
         * value 为 Invoker 对象
         */
        private final Map<String, Invoker> getMethods = new HashMap<>();
        /**
         * 属性对应的 setting 方法的方法参数类型的映射。{@link #setMethods}
         *
         * key 为属性名称
         * value 为方法参数类型
         */
        private final Map<String, Class<?>> setTypes = new HashMap<>();
        /**
         * 属性对应的 getting 方法的返回值类型的映射。{@link #getMethods}
         *
         * key 为属性名称
         * value 为返回值的类型
         */
        private final Map<String, Class<?>> getTypes = new HashMap<>();
        /**
         * 默认构造方法
         */
        private Constructor<?> defaultConstructor;
        /**
         * 不区分大小写的属性集合
         */
        private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
    
        public Reflector(Class<?> clazz) {
            // 设置对应的类
            type = clazz;
            // <1> 初始化 defaultConstructor
            addDefaultConstructor(clazz);
            // <2> // 初始化 getMethods 和 getTypes ,通过遍历 getting 方法
            addGetMethods(clazz);
            // <3> // 初始化 setMethods 和 setTypes ,通过遍历 setting 方法。
            addSetMethods(clazz);
            // <4> // 初始化 getMethods + getTypes 和 setMethods + setTypes ,通过遍历 fields 属性。
            addFields(clazz);
            // <5> 初始化 readablePropertyNames、writeablePropertyNames、caseInsensitivePropertyMap 属性
            readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
            writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
            for (String propName : readablePropertyNames) {
                caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
            }
            for (String propName : writeablePropertyNames) {
                caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
            }
        }
    
        // ... 省略一些方法
    }

2.1 addDefaultConstructor

addDefaultConstructor(Class<?> clazz) 方法,查找默认无参构造方法。

    private void addDefaultConstructor(Class<?> clazz) {
        // 获得所有构造方法
         Constructor<?>[] constructors = clazz.getDeclaredConstructors();
        Arrays.stream(constructors).filter(constructor -> constructor.getParameterTypes().length == 0)
          .findAny().ifPresent(constructor -> this.defaultConstructor = constructor);
    }

2.2 addGetMethods

addGetMethods(Class<?> cls) 方法,初始化 getMethods 和 getTypes ,通过遍历 getting 方法

      private void addGetMethods(Class<?> clazz) {
        Map<String, List<Method>> conflictingGetters = new HashMap<>();
        //获得所有方法
        Method[] methods = getClassMethods(clazz);
    //过滤获得get 方法
        Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
          .forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
        resolveGetterConflicts(conflictingGetters);
      }
2.2.1 getClassMethods

#getClassMethods(Class<?> cls) 方法,获得所有方法。代码如下:

    Map<String, Method> uniqueMethods = new HashMap<>();
        Class<?> currentClass = clazz;
        while (currentClass != null && currentClass != Object.class) {
          addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
    
          // we also need to look for interface methods -
          // because the class may be abstract
          Class<?>[] interfaces = currentClass.getInterfaces();
          for (Class<?> anInterface : interfaces) {
            addUniqueMethods(uniqueMethods, anInterface.getMethods());
          }
    
          currentClass = currentClass.getSuperclass();
        }
    
        Collection<Method> methods = uniqueMethods.values();
    
        return methods.toArray(new Method[0]);
2.2.2 resolveGetterConflicts

resolveGetterConflicts(Map<String, List>) 方法,解决 getting 冲突方法。最终,一个属性,只保留一个对应的方法。代码如下:

      private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
        for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
          Method winner = null;
          String propName = entry.getKey();
          boolean isAmbiguous = false;
          for (Method candidate : entry.getValue()) {
            if (winner == null) {
              winner = candidate;
              continue;
            }
            Class<?> winnerType = winner.getReturnType();
            Class<?> candidateType = candidate.getReturnType();
            if (candidateType.equals(winnerType)) {
              if (!boolean.class.equals(candidateType)) {
                isAmbiguous = true;
                break;
              } else if (candidate.getName().startsWith("is")) {
                winner = candidate;
              }
              //isAssignableFrom()方法是判断是否为某个类的父类
            } else if (candidateType.isAssignableFrom(winnerType)) {
              // OK getter type is descendant
            } else if (winnerType.isAssignableFrom(candidateType)) {
              winner = candidate;
            } else {
              isAmbiguous = true;
              break;
            }
          }
          addGetMethod(propName, winner, isAmbiguous);
        }
      }

2.3 addSetMethods

addSetMethods(Class<?> cls) 方法,初始化 setMethods 和 setTypes ,通过遍历 setting 方法。代码如下:

        Map<String, List<Method>> conflictingSetters = new HashMap<>();
            //获得所有方法
        Method[] methods = getClassMethods(clazz);
        Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 1 && PropertyNamer.isSetter(m.getName()))
          .forEach(m -> addMethodConflict(conflictingSetters, PropertyNamer.methodToProperty(m.getName()), m));
        resolveSetterConflicts(conflictingSetters);

2.4 addFields

addFields(Class<?> clazz) 方法,初始化 getMethods + getTypes 和 setMethods + setTypes ,通过遍历 fields 属性。实际上,它是 #addGetMethods(…) 和 #addSetMethods(…) 方法的补充,因为有些 field ,不存在对应的 setting 或 getting 方法,所以直接使用对应的 field ,

      private void addFields(Class<?> clazz) {
      //获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
          if (!setMethods.containsKey(field.getName())) {
            // issue #379 - removed the check for final because JDK 1.5 allows
            // modification of final fields through reflection (JSR-133). (JGB)
            // pr #16 - final static can only be set by the classloader
            int modifiers = field.getModifiers();
            if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
              addSetField(field);
            }
          }
          if (!getMethods.containsKey(field.getName())) {
            addGetField(field);
          }
        }
         // 递归,处理父类
        if (clazz.getSuperclass() != null) {
          addFields(clazz.getSuperclass());
        }
      }

3. ReflectorFactory

org.apache.ibatis.reflection.ReflectorFactory ,Reflector 工厂接口,用于创建和缓存 Reflector 对象。代码如下:

    /**
         * @return 是否缓存 Reflector 对象
         */
        boolean isClassCacheEnabled();
    
        /**
         * 设置是否缓存 Reflector 对象
         *
         * @param classCacheEnabled 是否缓存
         */
        void setClassCacheEnabled(boolean classCacheEnabled);
    
        /**
         * 获取 Reflector 对象
         *
         * @param type 指定类
         * @return Reflector 对象
         */
        Reflector findForClass(Class<?> type);

3.1 DefaultReflectorFactory

org.apache.ibatis.reflection.DefaultReflectorFactory ,实现 ReflectorFactory 接口,默认的 ReflectorFactory 实现类。代码如下:

      public class DefaultReflectorFactory implements ReflectorFactory {
    
        /**
         * 是否缓存
         */
        private boolean classCacheEnabled = true;
        /**
         * Reflector 的缓存映射
         *
         * KEY:类
         * VALUE:Reflector 对象
         */
        private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
    
        public DefaultReflectorFactory() {
        }
    
        @Override
        public boolean isClassCacheEnabled() {
            return classCacheEnabled;
        }
    
        @Override
        public void setClassCacheEnabled(boolean classCacheEnabled) {
            this.classCacheEnabled = classCacheEnabled;
        }
    
        @Override
        public Reflector findForClass(Class<?> type) {
            // 开启缓存,则从 reflectorMap 中获取
            if (classCacheEnabled) {
                // synchronized (type) removed see issue #461
                return reflectorMap.computeIfAbsent(type, Reflector::new); // 不存在,则进行创建
            // 关闭缓存,则创建 Reflector 对象
            } else {
                return new Reflector(type);
            }
        }
    
    }

4. Invoker

org.apache.ibatis.reflection.invoker.Invoker ,调用者接口。代码如下:

    // Invoker.java
    
    public interface Invoker {
    
        /**
         * 执行调用
         *
         * @param target 目标
         * @param args 参数
         * @return 结果
         * @throws IllegalAccessException
         * @throws InvocationTargetException
         */
        Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;
    
        /**
         * @return 类
         */
        Class<?> getType();
    
    }

4.1 GetFieldInvoker

org.apache.ibatis.reflection.invoker.GetFieldInvoker ,实现 Invoker 接口,获得 Field 调用者。代码如下:

    // GetFieldInvoker.java
    
    public class GetFieldInvoker implements Invoker {
    
        /**
         * Field 对象
         */
        private final Field field;
    
        public GetFieldInvoker(Field field) {
            this.field = field;
        }
    
        // 获得属性
        @Override
        public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
            return field.get(target);
        }
    
        // 返回属性类型
        @Override
        public Class<?> getType() {
            return field.getType();
        }
    
    }

4.2 SetFieldInvoker

org.apache.ibatis.reflection.invoker.SetFieldInvoker ,实现 Invoker 接口,设置 Field 调用者。代码如下:

    public class SetFieldInvoker implements Invoker {
      private final Field field;
    
      public SetFieldInvoker(Field field) {
        this.field = field;
      }
    
      @Override
      public Object invoke(Object target, Object[] args) throws IllegalAccessException {
        try {
          field.set(target, args[0]);
        } catch (IllegalAccessException e) {
          if (Reflector.canControlMemberAccessible()) {
            field.setAccessible(true);
            field.set(target, args[0]);
          } else {
            throw e;
          }
        }
        return null;
      }
    
      @Override
      public Class<?> getType() {
        return field.getType();
      }
    }

4.3 MethodInvoker

org.apache.ibatis.reflection.invoker.MethodInvoker ,实现 Invoker 接口,指定方法的调用器。代码如下:

    // MethodInvoker.java
    
    public class MethodInvoker implements Invoker {
    
        /**
         * 类型
         */
        private final Class<?> type;
        /**
         * 指定方法
         */
        private final Method method;
    
        public MethodInvoker(Method method) {
            this.method = method;
    
            // 参数大小为 1 时,一般是 setting 方法,设置 type 为方法参数[0]
            if (method.getParameterTypes().length == 1) {
                type = method.getParameterTypes()[0];
            // 否则,一般是 getting 方法,设置 type 为返回类型
            } else {
                type = method.getReturnType();
            }
        }
    
        // 执行指定方法
        @Override
        public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
            return method.invoke(target, args);
        }
    
        @Override
        public Class<?> getType() {
            return type;
        }
    
    }

5. ObjectFactory

org.apache.ibatis.reflection.factory.ObjectFactory ,Object 工厂接口,用于创建指定类的对象。代码如下:

    // ObjectFactory.java
    
    public interface ObjectFactory {
    
      /**
       * 设置 Properties
       *
       * Sets configuration properties.
       * @param properties configuration properties
       */
      void setProperties(Properties properties);
    
      /**
       * 创建指定类的对象,使用默认构造方法
       *
       * Creates a new object with default constructor.
       * @param type Object type
       * @return 对象
       */
      <T> T create(Class<T> type);
    
      /**
       * Creates a new object with the specified constructor and params.
       *
       * 创建指定类的对象,使用特定的构造方法
       *
       * @param type Object type
       * @param constructorArgTypes Constructor argument types 指定构造方法的参数列表
       * @param constructorArgs Constructor argument values 参数数组
       * @return 对象
       */
      <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
    
      /**
       * Returns true if this object can have a set of other objects.
       * It's main purpose is to support non-java.util.Collection objects like Scala collections.
       *
       * 判断指定类是否为集合类
       *
       * @param type Object type
       * @return whether it is a collection or not
       * @since 3.1.0
       */
      <T> boolean isCollection(Class<T> type);
    
    }

5.1 DefaultObjectFactory

org.apache.ibatis.reflection.factory.DefaultObjectFactory ,实现 ObjectFactory、Serializable 接口,默认 ObjectFactory 实现类。

5.1.1 create
    // DefaultObjectFactory.java
    
    @Override
    public <T> T create(Class<T> type) {
        return create(type, null, null);
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        // <1> 获得需要创建的类
        Class<?> classToCreate = resolveInterface(type);
        // we know types are assignable
        // <2> 创建指定类的对象
        return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
    }

调用instantiateClass(Class type, List<Class<?>> constructorArgTypes, List constructorArgs) 方法,创建指定类的对象。代码如下:

    private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        try {
            Constructor<T> constructor;
            // <x1> 通过无参构造方法,创建指定类的对象
            if (constructorArgTypes == null || constructorArgs == null) {
                constructor = type.getDeclaredConstructor();
                if (!constructor.isAccessible()) {
                    constructor.setAccessible(true);
                }
                return constructor.newInstance();
            }
            // <x2> 使用特定构造方法,创建指定类的对象
            constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
            if (!constructor.isAccessible()) {
                constructor.setAccessible(true);
            }
            return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
        } catch (Exception e) {
            // 拼接 argTypes
            StringBuilder argTypes = new StringBuilder();
            if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
                for (Class<?> argType : constructorArgTypes) {
                    argTypes.append(argType.getSimpleName());
                    argTypes.append(",");
                }
                argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
            }
            // 拼接 argValues
            StringBuilder argValues = new StringBuilder();
            if (constructorArgs != null && !constructorArgs.isEmpty()) {
                for (Object argValue : constructorArgs) {
                    argValues.append(String.valueOf(argValue));
                    argValues.append(",");
                }
                argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
            }
            // 抛出 ReflectionException 异常
            throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
        }
    }

6. Property 工具类

6.1 PropertyCopier

org.apache.ibatis.reflection.property.PropertyCopier ,属性复制器。代码如下:

    // PropertyNamer.java
    
    public final class PropertyCopier {
    
        private PropertyCopier() {
            // Prevent Instantiation of Static Class
        }
    
        /**
         * 将 sourceBean 的属性,复制到 destinationBean 中
         *
         * @param type 指定类
         * @param sourceBean 来源 Bean 对象
         * @param destinationBean 目标 Bean 对象
         */
        public static void copyBeanProperties(Class<?> type, Object sourceBean, Object destinationBean) {
            // 循环,从当前类开始,不断复制到父类,直到父类不存在
            Class<?> parent = type;
            while (parent != null) {
                // 获得当前 parent 类定义的属性
                final Field[] fields = parent.getDeclaredFields();
                for (Field field : fields) {
                    try {
                        // 设置属性可访问
                        field.setAccessible(true);
                        // 从 sourceBean 中,复制到 destinationBean 去
                        //Field.set()向对象的这个Field属性设置新值value
                        field.set(destinationBean, field.get(sourceBean));
                    } catch (Exception e) {
                        // Nothing useful to do, will only fail on final fields, which will be ignored.
                    }
                }
                // 获得父类
                parent = parent.getSuperclass();
            }
        }
    
    }

6.2 PropertyNamer

org.apache.ibatis.reflection.property.PropertyNamer ,属性名相关的工具类方法。代码如下:

    public final class PropertyNamer {
    
        private PropertyNamer() {
            // Prevent Instantiation of Static Class
        }
    
        /**
         * 根据方法名,获得对应的属性名
         *
         * @param name 方法名
         * @return 属性名
         */
        public static String methodToProperty(String name) {
            // is 方法
            if (name.startsWith("is")) {
                name = name.substring(2);
            // get 或者 set 方法
            } else if (name.startsWith("get") || name.startsWith("set")) {
                name = name.substring(3);
            // 抛出 ReflectionException 异常,因为只能处理 is、set、get 方法
            } else {
                throw new ReflectionException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
            }
    
            // 首字母小写
            if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
                name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
            }
    
            return name;
        }
    
        /**
         * 判断是否为 is、get、set 方法
         *
         * @param name 方法名
         * @return 是否
         */
        public static boolean isProperty(String name) {
            return name.startsWith("get") || name.startsWith("set") || name.startsWith("is");
        }
    
        /**
         * 判断是否为 get、is 方法
         *
         * @param name 方法名
         * @return 是否
         */
        public static boolean isGetter(String name) {
            return name.startsWith("get") || name.startsWith("is");
        }
    
        /**
         * 判断是否为 set 方法
         *
         * @param name 方法名
         * @return 是否
         */
        public static boolean isSetter(String name) {
            return name.startsWith("set");
        }
    
    }

7. MetaClass

org.apache.ibatis.reflection.MetaClass ,类的元数据,基于 Reflector 和 PropertyTokenizer ,提供对指定类的各种骚操作。

7.1 构造方法

      private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
        this.reflectorFactory = reflectorFactory;
        this.reflector = reflectorFactory.findForClass(type);
      }

8.ParamNameResolver

org.apache.ibatis.reflection.ParamNameResolver ,参数名解析器。

8.1 构造方法

    public ParamNameResolver(Configuration config, Method method) {
        final Class<?>[] paramTypes = method.getParameterTypes();
        final Annotation[][] paramAnnotations = method.getParameterAnnotations();
        final SortedMap<Integer, String> map = new TreeMap<>();
        int paramCount = paramAnnotations.length;
        // get names from @Param annotations
        for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
            // 忽略,如果是特殊参数
            if (isSpecialParameter(paramTypes[paramIndex])) {
                // skip special parameters
                continue;
            }
            String name = null;
            // 首先,从 @Param 注解中获取参数
            for (Annotation annotation : paramAnnotations[paramIndex]) {
                if (annotation instanceof Param) {
                    hasParamAnnotation = true;
                    name = ((Param) annotation).value();
                    break;
                }
            }
            if (name == null) {
                // @Param was not specified.
                // 其次,获取真实的参数名
                if (config.isUseActualParamName()) { // 默认开启
                    name = getActualParamName(method, paramIndex);
                }
                // 最差,使用 map 的顺序,作为编号
                if (name == null) {
                    // use the parameter index as the name ("0", "1", ...)
                    // gcode issue #71
                    name = String.valueOf(map.size());
                }
            }
            // 添加到 map 中
            map.put(paramIndex, name);
        }
        // 构建不可变集合
        names = Collections.unmodifiableSortedMap(map);
    }
    
    private String getActualParamName(Method method, int paramIndex) {
        return ParamNameUtil.getParamNames(method).get(paramIndex);
    }
    
    private static boolean isSpecialParameter(Class<?> clazz) {
        return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
    }

8.2 getNamedParams

#getNamedParams(Object[] args) 方法,获得参数名与值的映射。代码如下:

    public Object getNamedParams(Object[] args) {
        final int paramCount = names.size();
        // 无参数,则返回 null
        if (args == null || paramCount == 0) {
            return null;
        // 只有一个非注解的参数,直接返回首元素
        } else if (!hasParamAnnotation && paramCount == 1) {
            return args[names.firstKey()];
        } else {
            // 集合。
            // 组合 1 :KEY:参数名,VALUE:参数值
            // 组合 2 :KEY:GENERIC_NAME_PREFIX + 参数顺序,VALUE :参数值
            final Map<String, Object> param = new ParamMap<>();
            int i = 0;
            // 遍历 names 集合
            for (Map.Entry<Integer, String> entry : names.entrySet()) {
                // 组合 1 :添加到 param 中
                param.put(entry.getValue(), args[entry.getKey()]);
                // add generic param names (param1, param2, ...)
                // 组合 2 :添加到 param 中
                final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
                // ensure not to overwrite parameter named with @Param
                if (!names.containsValue(genericParamName)) {
                    param.put(genericParamName, args[entry.getKey()]);
                }
                i++;
            }
            return param;
        }
    }

Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文