From bee3f83deab3042ed3cc6ad8e5df6b5bfba28202 Mon Sep 17 00:00:00 2001 From: yulichang <570810310@qq.com> Date: Fri, 20 Sep 2024 13:54:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5mp=E7=9B=B8=E5=85=B3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yulichang/injector/MPJSqlInjector.java | 10 +- .../interceptor/pagination/ParseHelper.java | 6 +- .../github/yulichang/toolkit/ClassUtils.java | 3 +- .../yulichang/toolkit/MPJReflectionKit.java | 11 ++ .../yulichang/toolkit/ReflectionKit.java | 46 ++++-- .../toolkit/reflect/GenericTypeUtils.java | 29 ++++ .../reflect/TypeParameterResolver.java | 135 ++++++++++++++++++ 7 files changed, 213 insertions(+), 27 deletions(-) create mode 100644 mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/reflect/TypeParameterResolver.java diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/injector/MPJSqlInjector.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/injector/MPJSqlInjector.java index 284c298..83a30e1 100644 --- a/mybatis-plus-join-core/src/main/java/com/github/yulichang/injector/MPJSqlInjector.java +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/injector/MPJSqlInjector.java @@ -9,14 +9,13 @@ import com.baomidou.mybatisplus.core.injector.methods.*; import com.baomidou.mybatisplus.core.mapper.Mapper; import com.baomidou.mybatisplus.core.metadata.TableInfo; import com.baomidou.mybatisplus.core.toolkit.ArrayUtils; -import com.baomidou.mybatisplus.core.toolkit.ClassUtils; import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils; import com.github.yulichang.adapter.base.tookit.VersionUtils; import com.github.yulichang.adapter.v3431.AbstractMethodV3431; import com.github.yulichang.method.*; import com.github.yulichang.toolkit.MPJTableMapperHelper; +import com.github.yulichang.toolkit.ReflectionKit; import com.github.yulichang.toolkit.TableHelper; -import com.github.yulichang.toolkit.reflect.GenericTypeUtils; import lombok.Getter; import org.apache.ibatis.builder.MapperBuilderAssistant; import org.apache.ibatis.session.Configuration; @@ -173,7 +172,7 @@ public class MPJSqlInjector extends DefaultSqlInjector { @Override public void inspectInject(MapperBuilderAssistant builderAssistant, Class mapperClass) { - Class modelClass = getSuperClassGenericType(mapperClass, Mapper.class, 0); + Class modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0); super.inspectInject(builderAssistant, mapperClass); MPJTableMapperHelper.init(modelClass, mapperClass); Supplier> supplier = () -> { @@ -186,11 +185,6 @@ public class MPJSqlInjector extends DefaultSqlInjector { TableHelper.init(modelClass, supplier.get()); } - public static Class getSuperClassGenericType(final Class clazz, final Class genericIfc, final int index) { - Class[] typeArguments = GenericTypeUtils.resolveTypeArguments(ClassUtils.getUserClass(clazz), genericIfc); - return null == typeArguments ? null : typeArguments[index]; - } - @SuppressWarnings("IfStatementWithIdenticalBranches") protected Class extractModelClassOld(Class mapperClass) { Type[] types = mapperClass.getGenericInterfaces(); diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/ParseHelper.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/ParseHelper.java index f996c71..1db8d2f 100644 --- a/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/ParseHelper.java +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/ParseHelper.java @@ -1,7 +1,7 @@ package com.github.yulichang.interceptor.pagination; import com.baomidou.mybatisplus.core.toolkit.Assert; -import com.github.yulichang.toolkit.ReflectionKit; +import com.github.yulichang.toolkit.MPJReflectionKit; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.scripting.xmltags.DynamicSqlSource; @@ -102,8 +102,8 @@ public final class ParseHelper { public static String getOriginalSql(Object parameter, DynamicSqlSource sqlSource) { Assert.notNull(sqlSource, "sqlSource must not be null"); SqlSourceWrapper sqlSourceWrapper = SQL_SOURCE_CACHE.computeIfAbsent(sqlSource, key -> { - Configuration configuration = ReflectionKit.getFieldValue(sqlSource, "configuration"); - SqlNode sqlNode = ReflectionKit.getFieldValue(sqlSource, "rootSqlNode"); + Configuration configuration = MPJReflectionKit.getFieldValue(sqlSource, "configuration"); + SqlNode sqlNode = MPJReflectionKit.getFieldValue(sqlSource, "rootSqlNode"); return new SqlSourceWrapper(configuration, sqlNode); }); return ParseHelper.decode(sqlSourceWrapper.getSql(parameter), null, ParseHelper.format.apply("")); diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/ClassUtils.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/ClassUtils.java index 71f6cd0..139fb00 100644 --- a/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/ClassUtils.java +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/ClassUtils.java @@ -130,8 +130,7 @@ public final class ClassUtils { Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); return constructor.newInstance(); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | - NoSuchMethodException e) { + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw ExceptionUtils.mpe("实例化对象时出现错误,请尝试给 %s 添加无参的构造方法", e, clazz.getName()); } } diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/MPJReflectionKit.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/MPJReflectionKit.java index cfefb68..ba0cb2d 100644 --- a/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/MPJReflectionKit.java +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/MPJReflectionKit.java @@ -110,4 +110,15 @@ public final class MPJReflectionKit { Assert.notNull(clazz, "Class must not be null"); return (clazz.isPrimitive() || PRIMITIVE_WRAPPER_TYPE_MAP.containsKey(clazz)); } + + public static T getFieldValue(Object object, String fieldName) { + try { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + //noinspection unchecked + return (T) field.get(object); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } } diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/ReflectionKit.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/ReflectionKit.java index 5680df5..30acbf5 100644 --- a/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/ReflectionKit.java +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/ReflectionKit.java @@ -19,9 +19,12 @@ import com.baomidou.mybatisplus.core.toolkit.Assert; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils; import com.github.yulichang.toolkit.reflect.GenericTypeUtils; +import com.github.yulichang.toolkit.reflect.TypeParameterResolver; +import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.security.AccessController; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; @@ -70,14 +73,19 @@ public final class ReflectionKit { * @param entity 实体 * @param fieldName 字段名称 * @return 属性值 + * @deprecated 3.5.4 */ - public static T getFieldValue(Object entity, String fieldName) { + @Deprecated + public static Object getFieldValue(Object entity, String fieldName) { + Class cls = entity.getClass(); + Map fieldMaps = getFieldMap(cls); try { - Field field = entity.getClass().getDeclaredField(fieldName); + Field field = fieldMaps.get(fieldName); + Assert.notNull(field, "Error: NoSuchField in %s for %s. Cause:", cls.getSimpleName(), fieldName); field.setAccessible(true); - return (T) field.get(entity); + return field.get(entity); } catch (ReflectiveOperationException e) { - throw ExceptionUtils.mpe("Error: Cannot read field in %s. Cause:", e, entity.getClass().getSimpleName()); + throw ExceptionUtils.mpe("Error: Cannot read field in %s. Cause:", e, cls.getSimpleName()); } } @@ -92,9 +100,13 @@ public final class ReflectionKit { * @return Class */ public static Class getSuperClassGenericType(final Class clazz, final Class genericIfc, final int index) { - //update by noear @2021-09-03 - Class[] typeArguments = GenericTypeUtils.resolveTypeArguments(ClassUtils.getUserClass(clazz), genericIfc); - return null == typeArguments ? null : typeArguments[index]; + // 这里泛型逻辑提取进行了调整,如果在Spring项目情况或者自定义了泛型提取,那就优先走这里,否则使用框架内置的进行泛型提取. + Class userClass = ClassUtils.getUserClass(clazz); + if (GenericTypeUtils.hasGenericTypeResolver()) { + Class[] typeArguments = GenericTypeUtils.resolveTypeArguments(userClass, genericIfc); + return null == typeArguments ? null : typeArguments[index]; + } + return (Class) TypeParameterResolver.resolveClassIndexedParameter(userClass, genericIfc, index); } /** @@ -120,7 +132,7 @@ public final class ReflectionKit { if (Objects.isNull(clazz)) { return Collections.emptyList(); } - return computeIfAbsent(CLASS_FIELD_CACHE, clazz, k -> { + return CLASS_FIELD_CACHE.computeIfAbsent(clazz, k -> { Field[] fields = k.getDeclaredFields(); List superFields = new ArrayList<>(); Class currentClass = k.getSuperclass(); @@ -181,11 +193,17 @@ public final class ReflectionKit { return (clazz.isPrimitive() && clazz != void.class ? PRIMITIVE_TYPE_TO_WRAPPER_MAP.get(clazz) : clazz); } - public static V computeIfAbsent(Map concurrentHashMap, K key, Function mappingFunction) { - V v = concurrentHashMap.get(key); - if (v != null) { - return v; - } - return concurrentHashMap.computeIfAbsent(key, mappingFunction); + /** + * 设置可访问对象的可访问权限为 true + * + * @param object 可访问的对象 + * @param 类型 + * @return 返回设置后的对象 + * @deprecated 3.5.4 {@link java.security.AccessController} + */ + @Deprecated + public static T setAccessible(T object) { + return AccessController.doPrivileged(new SetAccessibleAction<>(object)); } + } diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/reflect/GenericTypeUtils.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/reflect/GenericTypeUtils.java index 7065aad..44fbfe5 100644 --- a/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/reflect/GenericTypeUtils.java +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/reflect/GenericTypeUtils.java @@ -15,6 +15,8 @@ */ package com.github.yulichang.toolkit.reflect; +import com.github.yulichang.toolkit.ClassUtils; + /** * 泛型类工具(用于隔离Spring的代码) * @@ -24,6 +26,22 @@ package com.github.yulichang.toolkit.reflect; */ @SuppressWarnings("ALL") public class GenericTypeUtils { + + /** + * 能否加载SpringCore包 + * + * @since 3.5.4 + */ + private static boolean loadSpringCore = false; + + static { + try { + ClassUtils.toClassConfident("org.springframework.core.GenericTypeResolver"); + loadSpringCore = true; + } catch (Exception exception) { + // ignore + } + } private static IGenericTypeResolver GENERIC_TYPE_RESOLVER; /** @@ -43,4 +61,15 @@ public class GenericTypeUtils { public static void setGenericTypeResolver(IGenericTypeResolver genericTypeResolver) { GENERIC_TYPE_RESOLVER = genericTypeResolver; } + + /** + * 判断是否有自定泛型提取类或能否加载SpringCore进行泛型提取 + * + * @return 是否有实现 + * @since 3.5.4 + */ + public static boolean hasGenericTypeResolver() { + return GENERIC_TYPE_RESOLVER != null || loadSpringCore; + } + } diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/reflect/TypeParameterResolver.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/reflect/TypeParameterResolver.java new file mode 100644 index 0000000..148c6d2 --- /dev/null +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/reflect/TypeParameterResolver.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2011-2024, baomidou (jobob@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.yulichang.toolkit.reflect; + +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * 类型参数实现收集器,采集类型实现中各个类型参数的实际值 + *

+ * Create by hcl at 2023/9/25 + */ +public class TypeParameterResolver { + private final Map, Type> map; + private final Set distinct; + + protected TypeParameterResolver(Map, Type> map) { + this.map = map; + this.distinct = new HashSet<>(); + } + + /** + * 获取类型上指定索引位置参数的实现信息 + * + * @param source 类型 + * @param index 索引 + * @param type 实现类型 + * @return 返回类型实现或者 null + */ + public static Type resolveClassIndexedParameter(Type type, Class source, int index) { + return calculateParameterValue(resolveParameterValues(type), source.getTypeParameters()[index]); + } + + /** + * 计算参数值 + * + * @param map 变量 Map + * @param parameter 参数 + * @return 返回参数值 + */ + public static Type calculateParameterValue(Map, Type> map, TypeVariable parameter) { + Type res = map.get(parameter); + while (res instanceof TypeVariable) { + res = map.get(res); + } + return res; + } + + /** + * 解析指定类型下的泛型参数实现信息 + * + * @param from 起始类型 + * @return 返回全部的泛型参数及其映射类型值 + */ + public static Map, Type> resolveParameterValues(Type from) { + Map, Type> map = new HashMap<>(); + new TypeParameterResolver(map).visitType(from); + return map; + } + + /** + * 访问类型,类型中需要关注两个:{@link Class} 和 {@link ParameterizedType} + * + * @param type 类型 + */ + public void visitType(Type type) { + if (!distinct.add(type)) { + return; + } + + if (type instanceof Class) { + visitClass((Class) type); + return; + } + + if (type instanceof ParameterizedType) { + visitParameterizedType((ParameterizedType) type); + } + + } + + /** + * 访问类型,类型的树可以分解为父类和接口,这两个地方都要解析。 + * + * @param c 类 + */ + private void visitClass(Class c) { + visitType(c.getGenericSuperclass()); + for (Type i : c.getGenericInterfaces()) { + visitType(i); + } + } + + /** + * 访问参数化类型,类型参数映射的主要逻辑就在这里 + * + * @param parameterized 参数化类型 + */ + private void visitParameterizedType(ParameterizedType parameterized) { + Type raw = parameterized.getRawType(); + visitType(raw); + + if (raw instanceof GenericDeclaration) { + GenericDeclaration declaration = (GenericDeclaration) raw; + TypeVariable[] parameters = declaration.getTypeParameters(); + Type[] arguments = parameterized.getActualTypeArguments(); + for (int i = 0; i < parameters.length; i++) { + TypeVariable parameter = parameters[i]; + Type argument = arguments[i]; + map.put(parameter, argument); + visitType(argument); + } + } + } + +}