From ee50473830e62fe40ad6bc02422d29942742fc7d Mon Sep 17 00:00:00 2001 From: thinkgem Date: Mon, 13 Feb 2023 13:46:49 +0800 Subject: [PATCH] =?UTF-8?q?Java=20=E5=8F=8D=E5=B0=84=E9=87=87=E7=94=A8=20A?= =?UTF-8?q?SM=20=E5=B9=B6=E5=A2=9E=E5=8A=A0=E7=BC=93=E5=AD=98=EF=BC=8C?= =?UTF-8?q?=E9=AB=98=E5=B9=B6=E5=8F=91=E4=B8=8B=E5=A4=A7=E5=B9=85=E5=BA=A6?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E6=8F=90=E5=8D=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jeesite/common/reflect/ReflectUtils.java | 224 ++++--- .../common/reflect/asm/AccessClassLoader.java | 181 ++++++ .../common/reflect/asm/FieldAccess.java | 614 ++++++++++++++++++ .../common/reflect/asm/MethodAccess.java | 312 +++++++++ .../jeesite/common/reflect/asm/reflectasm.txt | 10 + .../common/utils/excel/ExcelExport.java | 78 +-- .../common/utils/excel/ExcelImport.java | 42 +- .../com/jeesite/test/ReflectUtilsTest.java | 577 ++++++++++++++++ 8 files changed, 1889 insertions(+), 149 deletions(-) create mode 100644 common/src/main/java/com/jeesite/common/reflect/asm/AccessClassLoader.java create mode 100644 common/src/main/java/com/jeesite/common/reflect/asm/FieldAccess.java create mode 100644 common/src/main/java/com/jeesite/common/reflect/asm/MethodAccess.java create mode 100644 common/src/main/java/com/jeesite/common/reflect/asm/reflectasm.txt create mode 100644 common/src/test/java/com/jeesite/test/ReflectUtilsTest.java diff --git a/common/src/main/java/com/jeesite/common/reflect/ReflectUtils.java b/common/src/main/java/com/jeesite/common/reflect/ReflectUtils.java index 9eec6b15..c8061f17 100644 --- a/common/src/main/java/com/jeesite/common/reflect/ReflectUtils.java +++ b/common/src/main/java/com/jeesite/common/reflect/ReflectUtils.java @@ -1,49 +1,39 @@ /** - * Copyright (c) 2005-2012 springside.org.cn - * - * Licensed under the Apache License, Version 2.0 (the "License"); + * Copyright (c) 2013-Now http://jeesite.com、springside.org.cn All rights reserved. + * No deletion without permission, or be held responsible to law. */ package com.jeesite.common.reflect; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Date; -import java.util.Map; - +import com.jeesite.common.collect.MapUtils; +import com.jeesite.common.lang.DateUtils; +import com.jeesite.common.lang.ObjectUtils; +import com.jeesite.common.reflect.asm.MethodAccess; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.poi.ss.usermodel.DateUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.jeesite.common.lang.DateUtils; -import com.jeesite.common.lang.ObjectUtils; +import java.lang.reflect.*; +import java.util.Date; +import java.util.Map; /** - * 反射工具类. - * 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. * @author calvin、ThinkGem - * @version 2015-11-12 + * @version 2023-2-6 */ @SuppressWarnings("rawtypes") public class ReflectUtils { - private static final String SETTER_PREFIX = "set"; - - private static final String GETTER_PREFIX = "get"; - - private static final String CGLIB_CLASS_SEPARATOR = "$$"; - private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class); - - private static Class baseEntityClass = null; + private static final String SETTER_PREFIX = "set"; + private static final String GETTER_PREFIX = "get"; + private static final String CGLIB_CLASS_SEPARATOR = "$$"; + private static Map> methodClassCache = MapUtils.newHashMap(); /** - * 调用Getter方法, + * 调用Getter方法,v5.3.0+ 变更为ASM方式,不支持私有方法调用,高性能, * 支持多级,如:对象名.对象名.方法, * 支持静态类及方法调用, * 支持Map @@ -56,20 +46,21 @@ public class ReflectUtils { object = ((Map)obj).get(name); }else{ String methodName = GETTER_PREFIX + StringUtils.capitalize(name); - object = invokeMethod(object, methodName, new Class[] {}, new Object[] {}); + // object = invokeMethodByName(object, methodName, new Object[] {}); + object = invokeMethodByAsm(object, methodName); } } return (E)object; } /** - * 调用Setter方法,仅匹配方法名, + * 调用Setter方法,仅匹配方法名,v5.3.0+ 变更为ASM方式,不支持私有方法调用,高性能, * 支持多级,如:对象名.对象名.方法, * 支持静态类及方法调用, * 支持Map */ @SuppressWarnings("unchecked") - public static void invokeSetter(Object obj, String propertyName, E value) { + public static void invokeSetter(Object obj, String propertyName, Object... args) { Object object = obj; String[] names = StringUtils.split(propertyName, "."); for (int i=0; i returnType = method.getReturnType(); - try { - if (baseEntityClass == null) { - baseEntityClass = Class.forName("com.jeesite.common.entity.BaseEntity"); - } - if (baseEntityClass.isAssignableFrom(returnType)) { - childObj = returnType.getDeclaredConstructor().newInstance(); - methodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); - invokeMethodByName(object, methodName, new Object[] { childObj }); - } - } catch (Exception e) { - e.printStackTrace(); - } + try { + //Method method = getAccessibleMethodByName(object, methodName, 0); + //if (method == null) { return; } + //Class returnType = method.getReturnType(); + System.out.println(object.getClass()); + MethodAccess ma = MethodAccess.get(object.getClass()); + Class returnType = ma.getReturnTypes()[ma.getIndex(methodName)]; + childObj = returnType.getDeclaredConstructor().newInstance(); + methodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + //invokeMethodByName(object, methodName, new Object[] { childObj }); + ma.invoke(object, methodName, childObj); + } catch (Exception e) { + // 如果找不到类或方法,则不报错,直接返回。 + logger.debug(object.getClass().getName(), e); + return; } } object = childObj; } }else{ if (obj instanceof Map){ - ((Map)obj).put(names[i], value); + ((Map)obj).put(names[i], args != null && args.length == 1 ? args[0] : args); }else{ String methodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); - invokeMethodByName(object, methodName, new Object[] { value }); + //invokeMethodByName(object, methodName, new Object[] { value }); + invokeMethodByAsm(object, methodName, args); } } } @@ -179,17 +172,16 @@ public class ReflectUtils { } /** - * 直接调用对象方法,无视private/protected修饰符, + * 根据方法名和值,直接调用对象方法,无视private/protected修饰符, * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用, * 只匹配函数名,如果有多个同名函数调用第一个, * 支持静态类及方法调用 */ @SuppressWarnings("unchecked") - public static E invokeMethodByName(final Object obj, final String methodName, final Object[] args) { + public static E invokeMethodByName(final Object obj, final String methodName, final Object... args) { Method method = getAccessibleMethodByName(obj, methodName, args.length); if (method == null) { - // 如果为空不报错,直接返回空。 -// throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + // 如果为空不报错,直接返回空。 throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); if (obj != null) { logger.debug("在 [" + (obj.getClass() == Class.class ? obj : obj.getClass()) + "] 中,没有找到 [" + methodName + "] 方法 "); } @@ -198,31 +190,7 @@ public class ReflectUtils { try { // 类型转换(将参数数据类型转换为目标方法参数类型) Class[] cs = method.getParameterTypes(); - for (int i=0; i E invokeMethodByAsm(final Object obj, final String methodName, final Object... args) { + Object object = obj; + if (object == null){ + return null; + } + Class clazz = object.getClass(); + if (clazz == Class.class){ + clazz = (Class) object; + object = null; + } + try { + MethodAccess ma = MethodAccess.get(clazz); + int idx = ma.getIndex(methodName); + Class[] cs = ma.getParameterTypes()[idx]; + methodParameterTypesConverter(cs, args); + return (E)ma.invoke(object, idx, args); + } catch (IllegalArgumentException e) { + // 如果找不到类或方法,则不报错,直接返回。 + logger.debug(clazz.getName(), e); + return null; + } + } + + /** + * 方法的参数类型转换 + */ + private static void methodParameterTypesConverter(Class[] cs, Object[] args) { + for (int i = 0; i< cs.length; i++){ + if (args[i] != null && !args[i].getClass().equals(cs[i])){ + if (cs[i] == String.class){ + args[i] = ObjectUtils.toString(args[i]); + if(StringUtils.endsWith((String) args[i], ".0")){ + args[i] = StringUtils.substringBefore((String) args[i], ".0"); + } + }else if (cs[i] == Integer.class){ + args[i] = ObjectUtils.toInteger(args[i]); + }else if (cs[i] == Long.class){ + args[i] = ObjectUtils.toLong(args[i]); + }else if (cs[i] == Double.class){ + args[i] = ObjectUtils.toDouble(args[i]); + }else if ( cs[i] == Float.class){ + args[i] = ObjectUtils.toFloat(args[i]); + }else if (cs[i] == Date.class){ + if (args[i] instanceof String){ + args[i] = DateUtils.parseDate(args[i]); + }else if (args[i] instanceof Double){ + // POI Excel 日期格式转换 + args[i] = DateUtil.getJavaDate((Double) args[i]); + } + }else{ + System.out.println(cs[i] + " " + args[i]); + } + } + } + } + /** * 循环向上转型,获取对象的DeclaredField,并强制设置为可访问, * 如向上转型到Object仍无法找到,返回null */ public static Field getAccessibleField(final Object obj, final String fieldName) { - // 为空不报错。直接返回 null - // Validate.notNull(obj, "object can't be null"); + // 为空不报错。直接返回 null // Validate.notNull(obj, "object can't be null"); if (obj == null){ return null; } Validate.notBlank(fieldName, "fieldName can't be blank"); - for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) { + Class clazz = obj.getClass(); + for (Class superClass = clazz; superClass != Object.class; superClass = superClass.getSuperclass()) { try { Field field = superClass.getDeclaredField(fieldName); makeAccessible(field); @@ -262,8 +291,7 @@ public class ReflectUtils { */ public static Method getAccessibleMethod(final Object obj, final String methodName, final Class... parameterTypes) { - // 为空不报错。直接返回 null - // Validate.notNull(obj, "object can't be null"); + // 为空不报错。直接返回 null // Validate.notNull(obj, "object can't be null"); if (obj == null){ return null; } @@ -272,9 +300,9 @@ public class ReflectUtils { clazz = (Class) obj; } Validate.notBlank(methodName, "methodName can't be blank"); - for (Class searchType = clazz; searchType != Object.class; searchType = searchType.getSuperclass()) { + for (Class superClass = clazz; superClass != Object.class; superClass = superClass.getSuperclass()) { try { - Method method = searchType.getDeclaredMethod(methodName, parameterTypes); + Method method = superClass.getDeclaredMethod(methodName, parameterTypes); makeAccessible(method); return method; } catch (NoSuchMethodException e) { @@ -285,15 +313,27 @@ public class ReflectUtils { return null; } + /** + * 缓存方法类,因 for methods 比较耗时,提高性能 + */ + private static Map getMethodClassCache(String className) { + Map classCache = methodClassCache.get(className); + if (classCache == null) { + classCache = MapUtils.newHashMap(); + methodClassCache.put(className, classCache); + } + return classCache; + } + /** * 循环向上转型,获取对象的DeclaredMethod,并强制设置为可访问, * 如向上转型到Object仍无法找到,返回null, * 只匹配函数名。 * 用于方法需要被多次调用的情况,先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + * 增加缓存提升性能 */ public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum) { - // 为空不报错。直接返回 null - // Validate.notNull(obj, "object can't be null"); + // 为空不报错。直接返回 null // Validate.notNull(obj, "object can't be null"); if (obj == null){ return null; } @@ -302,11 +342,18 @@ public class ReflectUtils { clazz = (Class) obj; } Validate.notBlank(methodName, "methodName can't be blank"); - for (Class searchType = clazz; searchType != Object.class; searchType = searchType.getSuperclass()) { - Method[] methods = searchType.getDeclaredMethods(); + Map methodCache = getMethodClassCache(clazz.getName()); + String cacheKey = methodName + "#" + argsNum; + Method cacheMethod = methodCache.get(cacheKey); + if (cacheMethod != null) { + return cacheMethod; + } + for (Class superClass = clazz; superClass != Object.class; superClass = superClass.getSuperclass()) { + Method[] methods = superClass.getDeclaredMethods(); for (Method method : methods) { if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) { makeAccessible(method); + methodCache.put(cacheKey, method); return method; } } @@ -405,4 +452,5 @@ public class ReflectUtils { } return new RuntimeException(msg, e); } + } diff --git a/common/src/main/java/com/jeesite/common/reflect/asm/AccessClassLoader.java b/common/src/main/java/com/jeesite/common/reflect/asm/AccessClassLoader.java new file mode 100644 index 00000000..028388f6 --- /dev/null +++ b/common/src/main/java/com/jeesite/common/reflect/asm/AccessClassLoader.java @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2008, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * 3. Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package com.jeesite.common.reflect.asm; + +import sun.misc.Unsafe; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.HashSet; +import java.util.WeakHashMap; + +class AccessClassLoader extends ClassLoader { + // Weak-references to class loaders, to avoid perm gen memory leaks, for example in app servers/web containters if the + // reflectasm library (including this class) is loaded outside the deployed applications (WAR/EAR) using ReflectASM/Kryo (exts, + // user classpath, etc). + // The key is the parent class loader and the value is the AccessClassLoader, both are weak-referenced in the hash table. + static private final WeakHashMap> accessClassLoaders = new WeakHashMap(); + + // Fast-path for classes loaded in the same ClassLoader as this class. + static private final ClassLoader selfContextParentClassLoader = getParentClassLoader(AccessClassLoader.class); + static private volatile AccessClassLoader selfContextAccessClassLoader = new AccessClassLoader(selfContextParentClassLoader); + + static private volatile Method defineClassMethod; + + private final HashSet localClassNames = new HashSet(); + + static { + try { + Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + Unsafe u = (Unsafe) theUnsafe.get(null); + Class cls = Class.forName("jdk.internal.module.IllegalAccessLogger"); + Field logger = cls.getDeclaredField("logger"); + u.putObjectVolatile(cls, u.staticFieldOffset(logger), null); + } catch (Exception e) { + // ignore + } + } + + private AccessClassLoader (ClassLoader parent) { + super(parent); + } + + /** Returns null if the access class has not yet been defined. */ + Class loadAccessClass (String name) { + // No need to check the parent class loader if the access class hasn't been defined yet. + if (localClassNames.contains(name)) { + try { + return loadClass(name, false); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(ex); // Should not happen, since we know the class has been defined. + } + } + return null; + } + + Class defineAccessClass (String name, byte[] bytes) throws ClassFormatError { + localClassNames.add(name); + return defineClass(name, bytes); + } + + protected Class loadClass (String name, boolean resolve) throws ClassNotFoundException { + // These classes come from the classloader that loaded AccessClassLoader. + if (name.equals(FieldAccess.class.getName())) return FieldAccess.class; + if (name.equals(MethodAccess.class.getName())) return MethodAccess.class; +// if (name.equals(ConstructorAccess.class.getName())) return ConstructorAccess.class; +// if (name.equals(PublicConstructorAccess.class.getName())) return PublicConstructorAccess.class; + // All other classes come from the classloader that loaded the type we are accessing. + return super.loadClass(name, resolve); + } + + Class defineClass (String name, byte[] bytes) throws ClassFormatError { + try { + // Attempt to load the access class in the same loader, which makes protected and default access members accessible. + return (Class)getDefineClassMethod().invoke(getParent(), + new Object[] {name, bytes, Integer.valueOf(0), Integer.valueOf(bytes.length), getClass().getProtectionDomain()}); + } catch (Exception ignored) { + // continue with the definition in the current loader (won't have access to protected and package-protected members) + } + return defineClass(name, bytes, 0, bytes.length, getClass().getProtectionDomain()); + } + + // As per JLS, section 5.3, + // "The runtime package of a class or interface is determined by the package name and defining class loader of the class or + // interface." + static boolean areInSameRuntimeClassLoader (Class type1, Class type2) { + if (type1.getPackage() != type2.getPackage()) { + return false; + } + ClassLoader loader1 = type1.getClassLoader(); + ClassLoader loader2 = type2.getClassLoader(); + ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); + if (loader1 == null) { + return (loader2 == null || loader2 == systemClassLoader); + } + if (loader2 == null) return loader1 == systemClassLoader; + return loader1 == loader2; + } + + static private ClassLoader getParentClassLoader (Class type) { + ClassLoader parent = type.getClassLoader(); + if (parent == null) parent = ClassLoader.getSystemClassLoader(); + return parent; + } + + static private Method getDefineClassMethod () throws Exception { + if (defineClassMethod == null) { + synchronized (accessClassLoaders) { + if (defineClassMethod == null) { + defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", + new Class[] {String.class, byte[].class, int.class, int.class, ProtectionDomain.class}); + try { + defineClassMethod.setAccessible(true); + } catch (Exception ignored) { + } + } + } + } + return defineClassMethod; + } + + static AccessClassLoader get (Class type) { + ClassLoader parent = getParentClassLoader(type); + // 1. fast-path: + if (selfContextParentClassLoader.equals(parent)) { + if (selfContextAccessClassLoader == null) { + synchronized (accessClassLoaders) { // DCL with volatile semantics + if (selfContextAccessClassLoader == null) + selfContextAccessClassLoader = new AccessClassLoader(selfContextParentClassLoader); + } + } + return selfContextAccessClassLoader; + } + // 2. normal search: + synchronized (accessClassLoaders) { + WeakReference ref = accessClassLoaders.get(parent); + if (ref != null) { + AccessClassLoader accessClassLoader = ref.get(); + if (accessClassLoader != null) + return accessClassLoader; + else + accessClassLoaders.remove(parent); // the value has been GC-reclaimed, but still not the key (defensive sanity) + } + AccessClassLoader accessClassLoader = new AccessClassLoader(parent); + accessClassLoaders.put(parent, new WeakReference(accessClassLoader)); + return accessClassLoader; + } + } + + static public void remove (ClassLoader parent) { + // 1. fast-path: + if (selfContextParentClassLoader.equals(parent)) { + selfContextAccessClassLoader = null; + } else { + // 2. normal search: + synchronized (accessClassLoaders) { + accessClassLoaders.remove(parent); + } + } + } + + static public int activeAccessClassLoaders () { + int sz = accessClassLoaders.size(); + if (selfContextAccessClassLoader != null) sz++; + return sz; + } +} diff --git a/common/src/main/java/com/jeesite/common/reflect/asm/FieldAccess.java b/common/src/main/java/com/jeesite/common/reflect/asm/FieldAccess.java new file mode 100644 index 00000000..b531af02 --- /dev/null +++ b/common/src/main/java/com/jeesite/common/reflect/asm/FieldAccess.java @@ -0,0 +1,614 @@ +/** + * Copyright (c) 2008, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * 3. Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package com.jeesite.common.reflect.asm; + +import com.jeesite.common.collect.MapUtils; +import org.springframework.asm.ClassWriter; +import org.springframework.asm.Label; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Type; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Map; + +import static org.springframework.asm.Opcodes.*; + +public abstract class FieldAccess { + private String[] fieldNames; + private Class[] fieldTypes; + private Field[] fields; + + private static Map cache = MapUtils.newHashMap(); + + public int getIndex (String fieldName) { + for (int i = 0, n = fieldNames.length; i < n; i++) + if (fieldNames[i].equals(fieldName)) return i; + throw new IllegalArgumentException("Unable to find non-private field: " + fieldName); + } + + public int getIndex (Field field) { + for (int i = 0, n = fields.length; i < n; i++) + if (fields[i].equals(field)) return i; + throw new IllegalArgumentException("Unable to find non-private field: " + field); + } + + public void set (Object instance, String fieldName, Object value) { + set(instance, getIndex(fieldName), value); + } + + public Object get (Object instance, String fieldName) { + return get(instance, getIndex(fieldName)); + } + + public String[] getFieldNames () { + return fieldNames; + } + + public Class[] getFieldTypes () { + return fieldTypes; + } + + public int getFieldCount () { + return fieldTypes.length; + } + + public Field[] getFields () { + return fields; + } + + public void setFields (Field[] fields) { + this.fields = fields; + } + + abstract public void set (Object instance, int fieldIndex, Object value); + + abstract public void setBoolean (Object instance, int fieldIndex, boolean value); + + abstract public void setByte (Object instance, int fieldIndex, byte value); + + abstract public void setShort (Object instance, int fieldIndex, short value); + + abstract public void setInt (Object instance, int fieldIndex, int value); + + abstract public void setLong (Object instance, int fieldIndex, long value); + + abstract public void setDouble (Object instance, int fieldIndex, double value); + + abstract public void setFloat (Object instance, int fieldIndex, float value); + + abstract public void setChar (Object instance, int fieldIndex, char value); + + abstract public Object get (Object instance, int fieldIndex); + + abstract public String getString (Object instance, int fieldIndex); + + abstract public char getChar (Object instance, int fieldIndex); + + abstract public boolean getBoolean (Object instance, int fieldIndex); + + abstract public byte getByte (Object instance, int fieldIndex); + + abstract public short getShort (Object instance, int fieldIndex); + + abstract public int getInt (Object instance, int fieldIndex); + + abstract public long getLong (Object instance, int fieldIndex); + + abstract public double getDouble (Object instance, int fieldIndex); + + abstract public float getFloat (Object instance, int fieldIndex); + + /** @param type Must not be the Object class, an interface, a primitive type, or void. */ + static public FieldAccess get (Class type) { + FieldAccess fa = cache.get(type); + if (fa != null) { + return fa; + } + + if (type.getSuperclass() == null) + throw new IllegalArgumentException("The type must not be the Object class, an interface, a primitive type, or void."); + + ArrayList fields = new ArrayList(); + Class nextClass = type; + while (nextClass != Object.class) { + Field[] declaredFields = nextClass.getDeclaredFields(); + for (int i = 0, n = declaredFields.length; i < n; i++) { + Field field = declaredFields[i]; + int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers)) continue; + if (Modifier.isPrivate(modifiers)) continue; + fields.add(field); + } + nextClass = nextClass.getSuperclass(); + } + + String[] fieldNames = new String[fields.size()]; + Class[] fieldTypes = new Class[fields.size()]; + for (int i = 0, n = fieldNames.length; i < n; i++) { + fieldNames[i] = fields.get(i).getName(); + fieldTypes[i] = fields.get(i).getType(); + } + + String className = type.getName(); + String accessClassName = className + "FieldAccess"; + if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName; + + Class accessClass; + AccessClassLoader loader = AccessClassLoader.get(type); + synchronized (loader) { + accessClass = loader.loadAccessClass(accessClassName); + if (accessClass == null) { + String accessClassNameInternal = accessClassName.replace('.', '/'); + String classNameInternal = className.replace('.', '/'); + + ClassWriter cw = new ClassWriter(0); + cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER + ACC_SYNTHETIC, accessClassNameInternal, null, "com/jeesite/common/reflect/asm/FieldAccess", + null); + insertConstructor(cw); + insertGetObject(cw, classNameInternal, fields); + insertSetObject(cw, classNameInternal, fields); + insertGetPrimitive(cw, classNameInternal, fields, Type.BOOLEAN_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.BOOLEAN_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.BYTE_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.BYTE_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.SHORT_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.SHORT_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.INT_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.INT_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.LONG_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.LONG_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.DOUBLE_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.DOUBLE_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.FLOAT_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.FLOAT_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.CHAR_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.CHAR_TYPE); + insertGetString(cw, classNameInternal, fields); + cw.visitEnd(); + accessClass = loader.defineAccessClass(accessClassName, cw.toByteArray()); + } + } + try { + FieldAccess access = (FieldAccess)accessClass.newInstance(); + access.fieldNames = fieldNames; + access.fieldTypes = fieldTypes; + access.fields = fields.toArray(new Field[fields.size()]); + cache.put(type, access); + return access; + } catch (Throwable t) { + throw new RuntimeException("Error constructing field access class: " + accessClassName, t); + } + } + + static private void insertConstructor (ClassWriter cw) { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "com/jeesite/common/reflect/asm/FieldAccess", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + + static private void insertSetObject (ClassWriter cw, String classNameInternal, ArrayList fields) { + int maxStack = 6; + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "set", "(Ljava/lang/Object;ILjava/lang/Object;)V", null, null); + mv.visitCode(); + mv.visitVarInsn(ILOAD, 2); + + if (!fields.isEmpty()) { + maxStack--; + Label[] labels = new Label[fields.size()]; + for (int i = 0, n = labels.length; i < n; i++) + labels[i] = new Label(); + Label defaultLabel = new Label(); + mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels); + + for (int i = 0, n = labels.length; i < n; i++) { + Field field = fields.get(i); + Type fieldType = Type.getType(field.getType()); + + mv.visitLabel(labels[i]); + mv.visitFrame(F_SAME, 0, null, 0, null); + mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, classNameInternal); + mv.visitVarInsn(ALOAD, 3); + + switch (fieldType.getSort()) { + case Type.BOOLEAN: + mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false); + break; + case Type.BYTE: + mv.visitTypeInsn(CHECKCAST, "java/lang/Byte"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false); + break; + case Type.CHAR: + mv.visitTypeInsn(CHECKCAST, "java/lang/Character"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false); + break; + case Type.SHORT: + mv.visitTypeInsn(CHECKCAST, "java/lang/Short"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false); + break; + case Type.INT: + mv.visitTypeInsn(CHECKCAST, "java/lang/Integer"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); + break; + case Type.FLOAT: + mv.visitTypeInsn(CHECKCAST, "java/lang/Float"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false); + break; + case Type.LONG: + mv.visitTypeInsn(CHECKCAST, "java/lang/Long"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false); + break; + case Type.DOUBLE: + mv.visitTypeInsn(CHECKCAST, "java/lang/Double"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false); + break; + case Type.ARRAY: + mv.visitTypeInsn(CHECKCAST, fieldType.getDescriptor()); + break; + case Type.OBJECT: + mv.visitTypeInsn(CHECKCAST, fieldType.getInternalName()); + break; + } + + mv.visitFieldInsn(PUTFIELD, field.getDeclaringClass().getName().replace('.', '/'), field.getName(), + fieldType.getDescriptor()); + mv.visitInsn(RETURN); + } + + mv.visitLabel(defaultLabel); + mv.visitFrame(F_SAME, 0, null, 0, null); + } + mv = insertThrowExceptionForFieldNotFound(mv); + mv.visitMaxs(maxStack, 4); + mv.visitEnd(); + } + + static private void insertGetObject (ClassWriter cw, String classNameInternal, ArrayList fields) { + int maxStack = 6; + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "get", "(Ljava/lang/Object;I)Ljava/lang/Object;", null, null); + mv.visitCode(); + mv.visitVarInsn(ILOAD, 2); + + if (!fields.isEmpty()) { + maxStack--; + Label[] labels = new Label[fields.size()]; + for (int i = 0, n = labels.length; i < n; i++) + labels[i] = new Label(); + Label defaultLabel = new Label(); + mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels); + + for (int i = 0, n = labels.length; i < n; i++) { + Field field = fields.get(i); + + mv.visitLabel(labels[i]); + mv.visitFrame(F_SAME, 0, null, 0, null); + mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, classNameInternal); + mv.visitFieldInsn(GETFIELD, field.getDeclaringClass().getName().replace('.', '/'), field.getName(), + Type.getDescriptor(field.getType())); + + Type fieldType = Type.getType(field.getType()); + switch (fieldType.getSort()) { + case Type.BOOLEAN: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); + break; + case Type.BYTE: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false); + break; + case Type.CHAR: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false); + break; + case Type.SHORT: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false); + break; + case Type.INT: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); + break; + case Type.FLOAT: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false); + break; + case Type.LONG: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); + break; + case Type.DOUBLE: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false); + break; + } + + mv.visitInsn(ARETURN); + } + + mv.visitLabel(defaultLabel); + mv.visitFrame(F_SAME, 0, null, 0, null); + } + insertThrowExceptionForFieldNotFound(mv); + mv.visitMaxs(maxStack, 3); + mv.visitEnd(); + } + + static private void insertGetString (ClassWriter cw, String classNameInternal, ArrayList fields) { + int maxStack = 6; + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getString", "(Ljava/lang/Object;I)Ljava/lang/String;", null, null); + mv.visitCode(); + mv.visitVarInsn(ILOAD, 2); + + if (!fields.isEmpty()) { + maxStack--; + Label[] labels = new Label[fields.size()]; + Label labelForInvalidTypes = new Label(); + boolean hasAnyBadTypeLabel = false; + for (int i = 0, n = labels.length; i < n; i++) { + if (fields.get(i).getType().equals(String.class)) + labels[i] = new Label(); + else { + labels[i] = labelForInvalidTypes; + hasAnyBadTypeLabel = true; + } + } + Label defaultLabel = new Label(); + mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels); + + for (int i = 0, n = labels.length; i < n; i++) { + if (!labels[i].equals(labelForInvalidTypes)) { + Field field = fields.get(i); + mv.visitLabel(labels[i]); + mv.visitFrame(F_SAME, 0, null, 0, null); + mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, classNameInternal); + mv.visitFieldInsn(GETFIELD, field.getDeclaringClass().getName().replace('.', '/'), field.getName(), + "Ljava/lang/String;"); + mv.visitInsn(ARETURN); + } + } + // Rest of fields: different type + if (hasAnyBadTypeLabel) { + mv.visitLabel(labelForInvalidTypes); + mv.visitFrame(F_SAME, 0, null, 0, null); + insertThrowExceptionForFieldType(mv, "String"); + } + // Default: field not found + mv.visitLabel(defaultLabel); + mv.visitFrame(F_SAME, 0, null, 0, null); + } + insertThrowExceptionForFieldNotFound(mv); + mv.visitMaxs(maxStack, 3); + mv.visitEnd(); + } + + static private void insertSetPrimitive (ClassWriter cw, String classNameInternal, ArrayList fields, + Type primitiveType) { + int maxStack = 6; + int maxLocals = 4; // See correction below for LLOAD and DLOAD + final String setterMethodName; + final String typeNameInternal = primitiveType.getDescriptor(); + final int loadValueInstruction; + switch (primitiveType.getSort()) { + case Type.BOOLEAN: + setterMethodName = "setBoolean"; + loadValueInstruction = ILOAD; + break; + case Type.BYTE: + setterMethodName = "setByte"; + loadValueInstruction = ILOAD; + break; + case Type.CHAR: + setterMethodName = "setChar"; + loadValueInstruction = ILOAD; + break; + case Type.SHORT: + setterMethodName = "setShort"; + loadValueInstruction = ILOAD; + break; + case Type.INT: + setterMethodName = "setInt"; + loadValueInstruction = ILOAD; + break; + case Type.FLOAT: + setterMethodName = "setFloat"; + loadValueInstruction = FLOAD; + break; + case Type.LONG: + setterMethodName = "setLong"; + loadValueInstruction = LLOAD; + maxLocals++; // (LLOAD and DLOAD actually load two slots) + break; + case Type.DOUBLE: + setterMethodName = "setDouble"; + loadValueInstruction = DLOAD; + maxLocals++; // (LLOAD and DLOAD actually load two slots) + break; + default: + setterMethodName = "set"; + loadValueInstruction = ALOAD; + break; + } + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, setterMethodName, "(Ljava/lang/Object;I" + typeNameInternal + ")V", null, + null); + mv.visitCode(); + mv.visitVarInsn(ILOAD, 2); + + if (!fields.isEmpty()) { + maxStack--; + Label[] labels = new Label[fields.size()]; + Label labelForInvalidTypes = new Label(); + boolean hasAnyBadTypeLabel = false; + for (int i = 0, n = labels.length; i < n; i++) { + if (Type.getType(fields.get(i).getType()).equals(primitiveType)) + labels[i] = new Label(); + else { + labels[i] = labelForInvalidTypes; + hasAnyBadTypeLabel = true; + } + } + Label defaultLabel = new Label(); + mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels); + + for (int i = 0, n = labels.length; i < n; i++) { + if (!labels[i].equals(labelForInvalidTypes)) { + Field field = fields.get(i); + mv.visitLabel(labels[i]); + mv.visitFrame(F_SAME, 0, null, 0, null); + mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, classNameInternal); + mv.visitVarInsn(loadValueInstruction, 3); + mv.visitFieldInsn(PUTFIELD, field.getDeclaringClass().getName().replace('.', '/'), field.getName(), + typeNameInternal); + mv.visitInsn(RETURN); + } + } + // Rest of fields: different type + if (hasAnyBadTypeLabel) { + mv.visitLabel(labelForInvalidTypes); + mv.visitFrame(F_SAME, 0, null, 0, null); + insertThrowExceptionForFieldType(mv, primitiveType.getClassName()); + } + // Default: field not found + mv.visitLabel(defaultLabel); + mv.visitFrame(F_SAME, 0, null, 0, null); + } + mv = insertThrowExceptionForFieldNotFound(mv); + mv.visitMaxs(maxStack, maxLocals); + mv.visitEnd(); + } + + static private void insertGetPrimitive (ClassWriter cw, String classNameInternal, ArrayList fields, + Type primitiveType) { + int maxStack = 6; + final String getterMethodName; + final String typeNameInternal = primitiveType.getDescriptor(); + final int returnValueInstruction; + switch (primitiveType.getSort()) { + case Type.BOOLEAN: + getterMethodName = "getBoolean"; + returnValueInstruction = IRETURN; + break; + case Type.BYTE: + getterMethodName = "getByte"; + returnValueInstruction = IRETURN; + break; + case Type.CHAR: + getterMethodName = "getChar"; + returnValueInstruction = IRETURN; + break; + case Type.SHORT: + getterMethodName = "getShort"; + returnValueInstruction = IRETURN; + break; + case Type.INT: + getterMethodName = "getInt"; + returnValueInstruction = IRETURN; + break; + case Type.FLOAT: + getterMethodName = "getFloat"; + returnValueInstruction = FRETURN; + break; + case Type.LONG: + getterMethodName = "getLong"; + returnValueInstruction = LRETURN; + break; + case Type.DOUBLE: + getterMethodName = "getDouble"; + returnValueInstruction = DRETURN; + break; + default: + getterMethodName = "get"; + returnValueInstruction = ARETURN; + break; + } + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, getterMethodName, "(Ljava/lang/Object;I)" + typeNameInternal, null, null); + mv.visitCode(); + mv.visitVarInsn(ILOAD, 2); + + if (!fields.isEmpty()) { + maxStack--; + Label[] labels = new Label[fields.size()]; + Label labelForInvalidTypes = new Label(); + boolean hasAnyBadTypeLabel = false; + for (int i = 0, n = labels.length; i < n; i++) { + if (Type.getType(fields.get(i).getType()).equals(primitiveType)) + labels[i] = new Label(); + else { + labels[i] = labelForInvalidTypes; + hasAnyBadTypeLabel = true; + } + } + Label defaultLabel = new Label(); + mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels); + + for (int i = 0, n = labels.length; i < n; i++) { + Field field = fields.get(i); + if (!labels[i].equals(labelForInvalidTypes)) { + mv.visitLabel(labels[i]); + mv.visitFrame(F_SAME, 0, null, 0, null); + mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, classNameInternal); + mv.visitFieldInsn(GETFIELD, field.getDeclaringClass().getName().replace('.', '/'), field.getName(), + typeNameInternal); + mv.visitInsn(returnValueInstruction); + } + } + // Rest of fields: different type + if (hasAnyBadTypeLabel) { + mv.visitLabel(labelForInvalidTypes); + mv.visitFrame(F_SAME, 0, null, 0, null); + insertThrowExceptionForFieldType(mv, primitiveType.getClassName()); + } + // Default: field not found + mv.visitLabel(defaultLabel); + mv.visitFrame(F_SAME, 0, null, 0, null); + } + mv = insertThrowExceptionForFieldNotFound(mv); + mv.visitMaxs(maxStack, 3); + mv.visitEnd(); + } + + static private MethodVisitor insertThrowExceptionForFieldNotFound (MethodVisitor mv) { + mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException"); + mv.visitInsn(DUP); + mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); + mv.visitInsn(DUP); + mv.visitLdcInsn("Field not found: "); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V", false); + mv.visitVarInsn(ILOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "", "(Ljava/lang/String;)V", false); + mv.visitInsn(ATHROW); + return mv; + } + + static private MethodVisitor insertThrowExceptionForFieldType (MethodVisitor mv, String fieldType) { + mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException"); + mv.visitInsn(DUP); + mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); + mv.visitInsn(DUP); + mv.visitLdcInsn("Field not declared as " + fieldType + ": "); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V", false); + mv.visitVarInsn(ILOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "", "(Ljava/lang/String;)V", false); + mv.visitInsn(ATHROW); + return mv; + } + +} diff --git a/common/src/main/java/com/jeesite/common/reflect/asm/MethodAccess.java b/common/src/main/java/com/jeesite/common/reflect/asm/MethodAccess.java new file mode 100644 index 00000000..995c9cb9 --- /dev/null +++ b/common/src/main/java/com/jeesite/common/reflect/asm/MethodAccess.java @@ -0,0 +1,312 @@ +/** + * Copyright (c) 2008, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * 3. Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package com.jeesite.common.reflect.asm; + +import com.jeesite.common.collect.MapUtils; +import org.springframework.asm.*; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; + +import static org.springframework.asm.Opcodes.*; + +public abstract class MethodAccess { + private String[] methodNames; + private Class[][] parameterTypes; + private Class[] returnTypes; + + private static Map cache = MapUtils.newHashMap(); + + abstract public Object invoke (Object object, int methodIndex, Object... args); + + /** Invokes the method with the specified name and the specified param types. */ + public Object invoke (Object object, String methodName, Class[] paramTypes, Object... args) { + return invoke(object, getIndex(methodName, paramTypes), args); + } + + /** Invokes the first method with the specified name and the specified number of arguments. */ + public Object invoke (Object object, String methodName, Object... args) { + return invoke(object, getIndex(methodName, args == null ? 0 : args.length), args); + } + + /** Returns the index of the first method with the specified name. */ + public int getIndex (String methodName) { + for (int i = 0, n = methodNames.length; i < n; i++) + if (methodNames[i].equals(methodName)) return i; + throw new IllegalArgumentException("Unable to find non-private method: " + methodName); + } + + /** Returns the index of the first method with the specified name and param types. */ + public int getIndex (String methodName, Class... paramTypes) { + for (int i = 0, n = methodNames.length; i < n; i++) + if (methodNames[i].equals(methodName) && Arrays.equals(paramTypes, parameterTypes[i])) return i; + throw new IllegalArgumentException("Unable to find non-private method: " + methodName + " " + Arrays.toString(paramTypes)); + } + + /** Returns the index of the first method with the specified name and the specified number of arguments. */ + public int getIndex (String methodName, int paramsCount) { + for (int i = 0, n = methodNames.length; i < n; i++) + if (methodNames[i].equals(methodName) && parameterTypes[i].length == paramsCount) return i; + throw new IllegalArgumentException( + "Unable to find non-private method: " + methodName + " with " + paramsCount + " params."); + } + + public String[] getMethodNames () { + return methodNames; + } + + public Class[][] getParameterTypes () { + return parameterTypes; + } + + public Class[] getReturnTypes () { + return returnTypes; + } + + /** Creates a new MethodAccess for the specified type. + * @param type Must not be a primitive type, or void. */ + static public MethodAccess get (Class type) { + MethodAccess ma = cache.get(type); + if (ma != null) { + return ma; + } + + boolean isInterface = type.isInterface(); + if (!isInterface && type.getSuperclass() == null && type != Object.class) + throw new IllegalArgumentException("The type must not be an interface, a primitive type, or void."); + + ArrayList methods = new ArrayList(); + if (!isInterface) { + Class nextClass = type; + while (nextClass != Object.class) { + addDeclaredMethodsToList(nextClass, methods); + nextClass = nextClass.getSuperclass(); + } + } else + recursiveAddInterfaceMethodsToList(type, methods); + + int n = methods.size(); + String[] methodNames = new String[n]; + Class[][] parameterTypes = new Class[n][]; + Class[] returnTypes = new Class[n]; + for (int i = 0; i < n; i++) { + Method method = methods.get(i); + methodNames[i] = method.getName(); + parameterTypes[i] = method.getParameterTypes(); + returnTypes[i] = method.getReturnType(); + } + + String className = type.getName(); + String accessClassName = className + "MethodAccess"; + if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName; + + Class accessClass; + AccessClassLoader loader = AccessClassLoader.get(type); + synchronized (loader) { + accessClass = loader.loadAccessClass(accessClassName); + if (accessClass == null) { + String accessClassNameInternal = accessClassName.replace('.', '/'); + String classNameInternal = className.replace('.', '/'); + + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + MethodVisitor mv; + cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, "com/jeesite/common/reflect/asm/MethodAccess", + null); + { + mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "com/jeesite/common/reflect/asm/MethodAccess", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "invoke", + "(Ljava/lang/Object;I[Ljava/lang/Object;)Ljava/lang/Object;", null, null); + mv.visitCode(); + + if (!methods.isEmpty()) { + mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, classNameInternal); + mv.visitVarInsn(ASTORE, 4); + + mv.visitVarInsn(ILOAD, 2); + Label[] labels = new Label[n]; + for (int i = 0; i < n; i++) + labels[i] = new Label(); + Label defaultLabel = new Label(); + mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels); + + StringBuilder buffer = new StringBuilder(128); + for (int i = 0; i < n; i++) { + mv.visitLabel(labels[i]); + if (i == 0) + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] {classNameInternal}, 0, null); + else + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitVarInsn(ALOAD, 4); + + buffer.setLength(0); + buffer.append('('); + + Class[] paramTypes = parameterTypes[i]; + Class returnType = returnTypes[i]; + for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { + mv.visitVarInsn(ALOAD, 3); + mv.visitIntInsn(BIPUSH, paramIndex); + mv.visitInsn(AALOAD); + Type paramType = Type.getType(paramTypes[paramIndex]); + switch (paramType.getSort()) { + case Type.BOOLEAN: + mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false); + break; + case Type.BYTE: + mv.visitTypeInsn(CHECKCAST, "java/lang/Byte"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false); + break; + case Type.CHAR: + mv.visitTypeInsn(CHECKCAST, "java/lang/Character"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false); + break; + case Type.SHORT: + mv.visitTypeInsn(CHECKCAST, "java/lang/Short"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false); + break; + case Type.INT: + mv.visitTypeInsn(CHECKCAST, "java/lang/Integer"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); + break; + case Type.FLOAT: + mv.visitTypeInsn(CHECKCAST, "java/lang/Float"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false); + break; + case Type.LONG: + mv.visitTypeInsn(CHECKCAST, "java/lang/Long"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false); + break; + case Type.DOUBLE: + mv.visitTypeInsn(CHECKCAST, "java/lang/Double"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false); + break; + case Type.ARRAY: + mv.visitTypeInsn(CHECKCAST, paramType.getDescriptor()); + break; + case Type.OBJECT: + mv.visitTypeInsn(CHECKCAST, paramType.getInternalName()); + break; + } + buffer.append(paramType.getDescriptor()); + } + + buffer.append(')'); + buffer.append(Type.getDescriptor(returnType)); + int invoke; + if (isInterface) + invoke = INVOKEINTERFACE; + else if (Modifier.isStatic(methods.get(i).getModifiers())) + invoke = INVOKESTATIC; + else + invoke = INVOKEVIRTUAL; + mv.visitMethodInsn(invoke, classNameInternal, methodNames[i], buffer.toString(), false); + + switch (Type.getType(returnType).getSort()) { + case Type.VOID: + mv.visitInsn(ACONST_NULL); + break; + case Type.BOOLEAN: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); + break; + case Type.BYTE: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false); + break; + case Type.CHAR: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false); + break; + case Type.SHORT: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false); + break; + case Type.INT: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); + break; + case Type.FLOAT: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false); + break; + case Type.LONG: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); + break; + case Type.DOUBLE: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false); + break; + } + + mv.visitInsn(ARETURN); + } + + mv.visitLabel(defaultLabel); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + } + mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException"); + mv.visitInsn(DUP); + mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); + mv.visitInsn(DUP); + mv.visitLdcInsn("Method not found: "); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V", false); + mv.visitVarInsn(ILOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "", "(Ljava/lang/String;)V", false); + mv.visitInsn(ATHROW); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + cw.visitEnd(); + byte[] data = cw.toByteArray(); + accessClass = loader.defineAccessClass(accessClassName, data); + } + } + try { + MethodAccess access = (MethodAccess)accessClass.newInstance(); + access.methodNames = methodNames; + access.parameterTypes = parameterTypes; + access.returnTypes = returnTypes; + cache.put(type, access); + return access; + } catch (Throwable t) { + throw new RuntimeException("Error constructing method access class: " + accessClassName, t); + } + } + + static private void addDeclaredMethodsToList (Class type, ArrayList methods) { + Method[] declaredMethods = type.getDeclaredMethods(); + for (int i = 0, n = declaredMethods.length; i < n; i++) { + Method method = declaredMethods[i]; + int modifiers = method.getModifiers(); + // if (Modifier.isStatic(modifiers)) continue; + if (Modifier.isPrivate(modifiers)) continue; + methods.add(method); + } + } + + static private void recursiveAddInterfaceMethodsToList (Class interfaceType, ArrayList methods) { + addDeclaredMethodsToList(interfaceType, methods); + for (Class nextInterface : interfaceType.getInterfaces()) + recursiveAddInterfaceMethodsToList(nextInterface, methods); + } +} diff --git a/common/src/main/java/com/jeesite/common/reflect/asm/reflectasm.txt b/common/src/main/java/com/jeesite/common/reflect/asm/reflectasm.txt new file mode 100644 index 00000000..5a57ac7a --- /dev/null +++ b/common/src/main/java/com/jeesite/common/reflect/asm/reflectasm.txt @@ -0,0 +1,10 @@ +Copyright (c) 2008, Nathan Sweet +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/common/src/main/java/com/jeesite/common/utils/excel/ExcelExport.java b/common/src/main/java/com/jeesite/common/utils/excel/ExcelExport.java index 2414d222..c90168a8 100644 --- a/common/src/main/java/com/jeesite/common/utils/excel/ExcelExport.java +++ b/common/src/main/java/com/jeesite/common/utils/excel/ExcelExport.java @@ -4,44 +4,6 @@ */ package com.jeesite.common.utils.excel; -import java.io.Closeable; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.math.BigDecimal; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.servlet.http.HttpServletResponse; - -import org.apache.poi.ss.usermodel.BorderStyle; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.CellStyle; -import org.apache.poi.ss.usermodel.Comment; -import org.apache.poi.ss.usermodel.FillPatternType; -import org.apache.poi.ss.usermodel.Font; -import org.apache.poi.ss.usermodel.HorizontalAlignment; -import org.apache.poi.ss.usermodel.IndexedColors; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.VerticalAlignment; -import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.ss.util.CellRangeAddress; -import org.apache.poi.xssf.streaming.SXSSFWorkbook; -import org.apache.poi.xssf.usermodel.XSSFClientAnchor; -import org.apache.poi.xssf.usermodel.XSSFRichTextString; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.jeesite.common.codec.EncodeUtils; import com.jeesite.common.collect.ListUtils; import com.jeesite.common.collect.MapUtils; @@ -53,6 +15,22 @@ import com.jeesite.common.utils.excel.annotation.ExcelField.Align; import com.jeesite.common.utils.excel.annotation.ExcelField.Type; import com.jeesite.common.utils.excel.annotation.ExcelFields; import com.jeesite.common.utils.excel.fieldtype.FieldType; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFRichTextString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.util.*; /** * 导出Excel文件(导出“XLSX”格式,支持大数据量导出 @see org.apache.poi.ss.SpreadsheetVersion) @@ -92,7 +70,9 @@ public class ExcelExport implements Closeable{ * 存储字段类型临时数据 */ private Map, FieldType> fieldTypes = MapUtils.newHashMap(); - + + private static Class dictUtilsClass = null; + /** * 构造函数 * @param title 表格标题,传“空值”,表示无标题 @@ -224,6 +204,7 @@ public class ExcelExport implements Closeable{ } ExcelField ef = f.getAnnotation(ExcelField.class); addAnnotation(annotationList, ef, f, type, groups); + ReflectUtils.makeAccessible(f); } // Get annotation method Method[] ms = cls.getDeclaredMethods(); @@ -236,6 +217,7 @@ public class ExcelExport implements Closeable{ } ExcelField ef = m.getAnnotation(ExcelField.class); addAnnotation(annotationList, ef, m, type, groups); + ReflectUtils.makeAccessible(m); } // Field sorting Collections.sort(annotationList, new Comparator() { @@ -542,16 +524,22 @@ public class ExcelExport implements Closeable{ val = ReflectUtils.invokeGetter(e, ef.attrName()); }else{ if (os[1] instanceof Field){ - val = ReflectUtils.invokeGetter(e, ((Field)os[1]).getName()); + //val = ReflectUtils.invokeGetter(e, ((Field)os[1]).getName()); + val = ((Field)os[1]).get(e); }else if (os[1] instanceof Method){ - val = ReflectUtils.invokeMethod(e, ((Method)os[1]).getName(), new Class[] {}, new Object[] {}); + //val = ReflectUtils.invokeMethod(e, ((Method)os[1]).getName(), new Class[] {}, new Object[] {}); + val = ((Method)os[1]).invoke(e); } } // If is dict, get dict label if (StringUtils.isNotBlank(ef.dictType())){ - Class dictUtils = Class.forName("com.jeesite.modules.sys.utils.DictUtils"); - val = dictUtils.getMethod("getDictLabels", String.class, String.class, - String.class).invoke(null, ef.dictType(), val==null?"":val.toString(), ""); + if (dictUtilsClass == null) { + dictUtilsClass = Class.forName("com.jeesite.modules.sys.utils.DictUtils"); + } + val = ReflectUtils.invokeMethodByAsm(dictUtilsClass, "getDictLabels", + ef.dictType(), val == null ? "" : val.toString(), ""); + //val = dictUtils.getMethod("getDictLabels", String.class, String.class, + // String.class).invoke(null, ef.dictType(), val==null?"":val.toString(), ""); //val = DictUtils.getDictLabel(val==null?"":val.toString(), ef.dictType(), ""); } }catch(Exception ex) { @@ -611,7 +599,7 @@ public class ExcelExport implements Closeable{ /** * 输出到文件 - * @param fileName 输出文件名 + * @param name 输出文件名 */ public ExcelExport writeFile(String name) throws FileNotFoundException, IOException{ FileOutputStream os = new FileOutputStream(name); diff --git a/common/src/main/java/com/jeesite/common/utils/excel/ExcelImport.java b/common/src/main/java/com/jeesite/common/utils/excel/ExcelImport.java index 3ec28a41..1d97dcee 100644 --- a/common/src/main/java/com/jeesite/common/utils/excel/ExcelImport.java +++ b/common/src/main/java/com/jeesite/common/utils/excel/ExcelImport.java @@ -62,7 +62,9 @@ public class ExcelImport implements Closeable { * 存储字段类型临时数据 */ private Map, FieldType> fieldTypes = MapUtils.newHashMap(); - + + private static Class dictUtilsClass = null; + /** * 构造函数 * @param path 导入文件对象,读取第一个工作表 @@ -75,7 +77,7 @@ public class ExcelImport implements Closeable { /** * 构造函数 - * @param path 导入文件对象,读取第一个工作表 + * @param file 导入文件对象,读取第一个工作表 * @param headerNum 标题行数,数据行号=标题行数+1 * @throws InvalidFormatException * @throws IOException @@ -87,7 +89,7 @@ public class ExcelImport implements Closeable { /** * 构造函数 - * @param path 导入文件对象 + * @param file 导入文件对象 * @param headerNum 标题行数,数据行号=标题行数+1 * @param sheetIndexOrName 工作表编号或名称,从0开始 * @throws InvalidFormatException @@ -100,7 +102,7 @@ public class ExcelImport implements Closeable { /** * 构造函数 - * @param file 导入文件对象 + * @param multipartFile 导入文件对象 * @param headerNum 标题行数,数据行号=标题行数+1 * @param sheetIndexOrName 工作表编号或名称,从0开始 * @throws InvalidFormatException @@ -113,7 +115,7 @@ public class ExcelImport implements Closeable { /** * 构造函数 - * @param path 导入文件对象 + * @param fileName 导入文件对象 * @param headerNum 标题行数,数据行号=标题行数+1 * @param sheetIndexOrName 工作表编号或名称 * @throws InvalidFormatException @@ -357,6 +359,7 @@ public class ExcelImport implements Closeable { } ExcelField ef = f.getAnnotation(ExcelField.class); addAnnotation(annotationList, ef, f, Type.IMPORT, groups); + ReflectUtils.makeAccessible(f); } // Get annotation method Method[] ms = cls.getDeclaredMethods(); @@ -369,6 +372,7 @@ public class ExcelImport implements Closeable { } ExcelField ef = m.getAnnotation(ExcelField.class); addAnnotation(annotationList, ef, m, Type.IMPORT, groups); + ReflectUtils.makeAccessible(m); } // Field sorting Collections.sort(annotationList, new Comparator() { @@ -397,15 +401,19 @@ public class ExcelImport implements Closeable { // If is dict type, get dict value if (StringUtils.isNotBlank(ef.dictType())){ try{ - Class dictUtils = Class.forName("com.jeesite.modules.sys.utils.DictUtils"); - val = dictUtils.getMethod("getDictValues", String.class, String.class, - String.class).invoke(null, ef.dictType(), val.toString(), ""); + if (dictUtilsClass == null) { + dictUtilsClass = Class.forName("com.jeesite.modules.sys.utils.DictUtils"); + } + val = ReflectUtils.invokeMethodByAsm(dictUtilsClass, "getDictValues", + ef.dictType(), val.toString(), ""); + //val = dictUtilsClass.getMethod("getDictValues", String.class, String.class, + // String.class).invoke(null, ef.dictType(), val.toString(), ""); + //val = DictUtils.getDictValue(val.toString(), ef.dictType(), ""); + //log.debug("Dictionary type value: ["+i+","+colunm+"] " + val); } catch (Exception ex) { log.info("Get cell value ["+i+","+column+"] error: " + ex.toString()); val = null; } - //val = DictUtils.getDictValue(val.toString(), ef.dictType(), ""); - //log.debug("Dictionary type value: ["+i+","+colunm+"] " + val); } // Get param type and type cast Class valType = Class.class; @@ -480,13 +488,15 @@ public class ExcelImport implements Closeable { ReflectUtils.invokeSetter(e, ef.attrName(), val); }else{ if (os[1] instanceof Field){ - ReflectUtils.invokeSetter(e, ((Field)os[1]).getName(), val); + //ReflectUtils.invokeSetter(e, ((Field)os[1]).getName(), val); + ((Field)os[1]).set(e, val); }else if (os[1] instanceof Method){ - String mthodName = ((Method)os[1]).getName(); - if ("get".equals(mthodName.substring(0, 3))){ - mthodName = "set"+StringUtils.substringAfter(mthodName, "get"); - } - ReflectUtils.invokeMethod(e, mthodName, new Class[] {valType}, new Object[] {val}); + //String mthodName = ((Method)os[1]).getName(); + //if ("get".equals(mthodName.substring(0, 3))){ + // mthodName = "set"+StringUtils.substringAfter(mthodName, "get"); + //} + //ReflectUtils.invokeMethod(e, mthodName, new Class[] {valType}, new Object[] {val}); + ((Method)os[1]).invoke(e, val); } } } diff --git a/common/src/test/java/com/jeesite/test/ReflectUtilsTest.java b/common/src/test/java/com/jeesite/test/ReflectUtilsTest.java new file mode 100644 index 00000000..a9c24bb7 --- /dev/null +++ b/common/src/test/java/com/jeesite/test/ReflectUtilsTest.java @@ -0,0 +1,577 @@ +/** + * Copyright (c) 2013-Now http://jeesite.com All rights reserved. + * No deletion without permission, or be held responsible to law. + */ +package com.jeesite.test; + +import com.jeesite.common.lang.DateUtils; +import com.jeesite.common.lang.ObjectUtils; +import com.jeesite.common.lang.TimeUtils; +import com.jeesite.common.reflect.ReflectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.apache.poi.ss.usermodel.DateUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Date; +import java.util.Map; + +/** + * 反射工具类 性能测试 + * @author ThinkGem + * @version 2023-2-6 + */ +@SuppressWarnings("rawtypes") +public class ReflectUtilsTest { + + public static void main(String[] args) { + int num = 100000; + for (int i = 0; i < 5; i++) { + getMethodTest(num, Type.FIELD); + getMethodTest(num, Type.ASM); + getMethodTest(num, Type.CACHE); + getMethodTest(num, Type.NOT_CACHE); + setMethodTest(num, Type.FIELD); + setMethodTest(num, Type.ASM); + setMethodTest(num, Type.CACHE); + setMethodTest(num, Type.NOT_CACHE); + } + ReflectUtils.invokeGetter(Test.class, "staticMethodTest"); + ReflectUtils.invokeSetter(Test.class, "staticMethodTest", "123"); + } + + public static void getMethodTest(int num, Type type) { + long start = System.currentTimeMillis(); + for (int i = 0; i < num; i++) { + Test test = new Test("1", "2", "3", 4, 5, 6, new Date()); + for (int j = 1; j <= 7; j++) { + String name = (j >= 1 && j <= 3 ? "str" : j >=4 && j<= 6 ? "int" : "date") + j; + Object v = null; + switch (type) { + case ASM: + v = ReflectUtils.invokeGetter(test, name); + break; + case CACHE: + v = ReflectUtils2.invokeGetterCache(test, name); + break; + case NOT_CACHE: + v = ReflectUtils2.invokeGetter(test, name); + break; + case FIELD: + v = ReflectUtils.getFieldValue(test, name); + break; + } + if (v == null) { + throw new RuntimeException("error."); + }else if (j == 1 && !v.equals(test.getStr1())) { + throw new RuntimeException("error."); + } else if (j == 4 && !v.equals(test.getInt4())) { + throw new RuntimeException("error."); + } else if (j == 7 && !v.equals(test.getDate7())) { + throw new RuntimeException("error."); + } + } + } + long total = System.currentTimeMillis() - start; + System.out.println("Get " + type.getVal() + ": " + TimeUtils.formatTime(total)); + } + + public static void setMethodTest(int num, Type type) { + long start = System.currentTimeMillis(); + for (int i = 0; i < num; i++) { + Test test = new Test(); + for (int j = 1; j <= 7; j++) { + String name = (j >= 1 && j <= 3 ? "str" : j >=4 && j<= 6 ? "int" : "date") + j; + Object v = (j >= 1 && j <= 3 ? ("str" + j) : j >=4 && j<= 6 ? (123 + j) : new Date()); + switch (type) { + case ASM: +// MethodAccess ma = MethodAccess.get(test.getClass()); +// ma.invoke(test, SETTER_PREFIX+StringUtils.capitalize(name), v); + ReflectUtils.invokeSetter(test, name, v); + break; + case CACHE: + ReflectUtils2.invokeSetterCache(test, name, v); + break; + case NOT_CACHE: + ReflectUtils2.invokeSetter(test, name, v); + break; + case FIELD: + ReflectUtils.setFieldValue(test, name, v); + break; + } + if (j == 1 && !v.equals(test.getStr1())) { + throw new RuntimeException("error."); + } else if (j == 4 && !v.equals(test.getInt4())) { + throw new RuntimeException("error."); + } else if (j == 7 && !v.equals(test.getDate7())) { + throw new RuntimeException("error."); + } + } + } + long total = System.currentTimeMillis() - start; + System.out.println("Set " + type.getVal() + ": " + TimeUtils.formatTime(total)); + } + + enum Type { + + FIELD("field"), + ASM("method asm"), + CACHE("method use cache"), + NOT_CACHE("method not use cache"); + + private String val; + + Type(String val) { + this.val = val; + } + + public String getVal() { + return val; + } + } + + public static class Test { + + private String str1; + private String str2; + private String str3; + private Integer int4; + private Integer int5; + private Integer int6; + private Date date7; + + public Test() { + + } + + public Test(String str1, String str2, String str3, Integer int4, Integer int5, Integer int6, Date date7) { + this.str1 = str1; + this.str2 = str2; + this.str3 = str3; + this.int4 = int4; + this.int5 = int5; + this.int6 = int6; + this.date7 = date7; + } + + public String getStr1() { + return str1; + } + + public void setStr1(String str1) { + this.str1 = str1; + } + + public String getStr2() { + return str2; + } + + public void setStr2(String str2) { + this.str2 = str2; + } + + public String getStr3() { + return str3; + } + + public void setStr3(String str3) { + this.str3 = str3; + } + + public Integer getInt4() { + return int4; + } + + public void setInt4(Integer int4) { + this.int4 = int4; + } + + public Integer getInt5() { + return int5; + } + + public void setInt5(Integer int5) { + this.int5 = int5; + } + + public Integer getInt6() { + return int6; + } + + public void setInt6(Integer int6) { + this.int6 = int6; + } + + public Date getDate7() { + return date7; + } + + public void setDate7(Date date7) { + this.date7 = date7; + } + + public static void getStaticMethodTest() { + System.out.println("This is a static get method"); + } + + public static void setStaticMethodTest(String val) { + System.out.println("This is a static set method, set val: " + val); + } + } +} + +/** + * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * @author calvin、ThinkGem + * @version 2015-11-12 + */ +class ReflectUtils2 { + + private static Logger logger = LoggerFactory.getLogger(ReflectUtils2.class); + private static final String SETTER_PREFIX = "set"; + private static final String GETTER_PREFIX = "get"; + private static Class baseEntityClass = null; + + /** + * 调用Getter方法, + * 支持多级,如:对象名.对象名.方法, + * 支持静态类及方法调用, + * 支持Map + */ + @SuppressWarnings("unchecked") + public static E invokeGetter(Object obj, String propertyName) { + Object object = obj; + for (String name : StringUtils.split(propertyName, ".")){ + if (obj instanceof Map){ + object = ((Map)obj).get(name); + }else{ + String methodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = invokeMethod(object, methodName, new Class[] {}, new Object[] {}); + } + } + return (E)object; + } + + /** + * 调用Getter方法, + * 支持多级,如:对象名.对象名.方法, + * 支持静态类及方法调用, + * 支持Map + */ + @SuppressWarnings("unchecked") + public static E invokeGetterCache(Object obj, String propertyName) { + Object object = obj; + for (String name : StringUtils.split(propertyName, ".")){ + if (obj instanceof Map){ + object = ((Map)obj).get(name); + }else{ + String methodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = ReflectUtils.invokeMethodByName(object, methodName); + } + } + return (E)object; + } + + /** + * 调用Setter方法,仅匹配方法名, + * 支持多级,如:对象名.对象名.方法, + * 支持静态类及方法调用, + * 支持Map + */ + @SuppressWarnings("unchecked") + public static void invokeSetter(Object obj, String propertyName, E value) { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + for (int i=0; i returnType = method.getReturnType(); + try { + if (baseEntityClass == null) { + baseEntityClass = Class.forName("com.jeesite.common.entity.BaseEntity"); + } + if (baseEntityClass.isAssignableFrom(returnType)) { + childObj = returnType.getDeclaredConstructor().newInstance(); + methodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + invokeMethodByName(object, methodName, new Object[] { childObj }); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + object = childObj; + } + }else{ + if (obj instanceof Map){ + ((Map)obj).put(names[i], value); + }else{ + String methodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + invokeMethodByName(object, methodName, new Object[] { value }); + } + } + } + } + + /** + * 调用Setter方法,仅匹配方法名, + * 支持多级,如:对象名.对象名.方法, + * 支持静态类及方法调用, + * 支持Map + */ + @SuppressWarnings("unchecked") + public static void invokeSetterCache(Object obj, String propertyName, E value) { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + for (int i=0; i returnType = method.getReturnType(); + try { + if (baseEntityClass == null) { + baseEntityClass = Class.forName("com.jeesite.common.entity.BaseEntity"); + } + if (baseEntityClass.isAssignableFrom(returnType)) { + childObj = returnType.getDeclaredConstructor().newInstance(); + methodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + ReflectUtils.invokeMethodByName(object, methodName, childObj); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + object = childObj; + } + }else{ + if (obj instanceof Map){ + ((Map)obj).put(names[i], value); + }else{ + String methodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + ReflectUtils.invokeMethodByName(object, methodName, value); + } + } + } + } + + /** + * 直接读取对象属性值,无视private/protected修饰符,不经过getter函数 + */ + @SuppressWarnings("unchecked") + public static E getFieldValue(final Object obj, final String fieldName) { + Field field = getAccessibleField(obj, fieldName); + if (field == null) { + //throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + if (obj != null) { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + } + return null; + } + E result = null; + try { + result = (E)field.get(obj); + } catch (IllegalAccessException e) { + logger.error("不可能抛出的异常: {}", e.getMessage()); + } + return result; + } + + /** + * 直接设置对象属性值,无视private/protected修饰符,不经过setter函数 + */ + public static void setFieldValue(final Object obj, final String fieldName, final E value) { + Field field = getAccessibleField(obj, fieldName); + if (field == null) { + //throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return; + } + try { + field.set(obj, value); + } catch (IllegalAccessException e) { + logger.error("不可能抛出的异常: {}", e.getMessage()); + } + } + + /** + * 直接调用对象方法,无视private/protected修饰符, + * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用, + * 同时匹配方法名+参数类型, + * 支持静态类及方法调用 + */ + @SuppressWarnings("unchecked") + public static E invokeMethod(final Object obj, final String methodName, final Class[] parameterTypes, + final Object[] args) { + if (obj == null || methodName == null){ + return null; + } + Method method = getAccessibleMethod(obj, methodName, parameterTypes); + if (method == null) { + //throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + if (obj != null) { + logger.debug("在 [" + (obj.getClass() == Class.class ? obj : obj.getClass()) + "] 中,没有找到 [" + methodName + "] 方法 "); + } + return null; + } + try { + return (E)method.invoke(obj.getClass() == Class.class ? null : obj, args); + } catch (Exception e) { + String msg = "method: "+method+", obj: "+obj+", args: "+args+""; + throw ReflectUtils.convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 直接调用对象方法,无视private/protected修饰符, + * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用, + * 只匹配函数名,如果有多个同名函数调用第一个, + * 支持静态类及方法调用 + */ + @SuppressWarnings("unchecked") + public static E invokeMethodByName(final Object obj, final String methodName, final Object[] args) { + Method method = getAccessibleMethodByName(obj, methodName, args.length); + if (method == null) { + // 如果为空不报错,直接返回空。 +// throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + if (obj != null) { + logger.debug("在 [" + (obj.getClass() == Class.class ? obj : obj.getClass()) + "] 中,没有找到 [" + methodName + "] 方法 "); + } + return null; + } + try { + // 类型转换(将参数数据类型转换为目标方法参数类型) + Class[] cs = method.getParameterTypes(); + for (int i=0; i clazz = obj.getClass(); + for (Class superClass = clazz; superClass != Object.class; superClass = superClass.getSuperclass()) { + try { + Field field = superClass.getDeclaredField(fieldName); + ReflectUtils.makeAccessible(field); + return field; + } catch (NoSuchFieldException e) {//NOSONAR + // Field不在当前类定义,继续向上转型 + continue;// new add + } + } + return null; + } + + /** + * 循环向上转型,获取对象的DeclaredMethod,并强制设置为可访问, + * 如向上转型到Object仍无法找到,返回null, + * 匹配函数名+参数类型。 + * 用于方法需要被多次调用的情况,先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethod(final Object obj, final String methodName, + final Class... parameterTypes) { + // 为空不报错。直接返回 null // Validate.notNull(obj, "object can't be null"); + if (obj == null){ + return null; + } + Class clazz = obj.getClass(); + if (clazz == Class.class){ + clazz = (Class) obj; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class superClass = clazz; superClass != Object.class; superClass = superClass.getSuperclass()) { + try { + Method method = superClass.getDeclaredMethod(methodName, parameterTypes); + ReflectUtils.makeAccessible(method); + return method; + } catch (NoSuchMethodException e) { + // Method不在当前类定义,继续向上转型 + continue;// new add + } + } + return null; + } + + /** + * 循环向上转型,获取对象的DeclaredMethod,并强制设置为可访问, + * 如向上转型到Object仍无法找到,返回null, + * 只匹配函数名。 + * 用于方法需要被多次调用的情况,先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum) { + // 为空不报错。直接返回 null // Validate.notNull(obj, "object can't be null"); + if (obj == null){ + return null; + } + Class clazz = obj.getClass(); + if (clazz == Class.class){ + clazz = (Class) obj; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class superClass = clazz; superClass != Object.class; superClass = superClass.getSuperclass()) { + Method[] methods = superClass.getDeclaredMethods(); + for (Method method : methods) { + if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) { + ReflectUtils.makeAccessible(method); + return method; + } + } + } + return null; + } + +}