James 7 年 前
コミット
7b9c1b759e
28 ファイル変更746 行追加144 行削除
  1. 2 1
      src/main/java/com/jfinal/aop/Invocation.java
  2. 9 0
      src/main/java/com/jfinal/core/JFinal.java
  3. 10 0
      src/main/java/com/jfinal/ext/interceptor/NotAction.java
  4. 1 1
      src/main/java/com/jfinal/json/JFinalJson.java
  5. 1 1
      src/main/java/com/jfinal/kit/ReflectKit.java
  6. 13 8
      src/main/java/com/jfinal/plugin/activerecord/Model.java
  7. 3 3
      src/main/java/com/jfinal/plugin/activerecord/ModelBuilder.java
  8. 11 7
      src/main/java/com/jfinal/plugin/activerecord/Record.java
  9. 1 1
      src/main/java/com/jfinal/plugin/activerecord/builder/KeepByteAndShortModelBuilder.java
  10. 1 1
      src/main/java/com/jfinal/plugin/activerecord/builder/TimestampProcessedModelBuilder.java
  11. 1 1
      src/main/java/com/jfinal/plugin/activerecord/dialect/AnsiSqlDialect.java
  12. 1 1
      src/main/java/com/jfinal/plugin/activerecord/dialect/Dialect.java
  13. 9 0
      src/main/java/com/jfinal/server/JettyServerForIDEA.java
  14. 42 0
      src/main/java/com/jfinal/template/Engine.java
  15. 11 39
      src/main/java/com/jfinal/template/expr/ast/Field.java
  16. 58 0
      src/main/java/com/jfinal/template/expr/ast/FieldGetter.java
  17. 270 0
      src/main/java/com/jfinal/template/expr/ast/FieldGetters.java
  18. 128 0
      src/main/java/com/jfinal/template/expr/ast/FieldKeyBuilder.java
  19. 102 17
      src/main/java/com/jfinal/template/expr/ast/FieldKit.java
  20. 12 16
      src/main/java/com/jfinal/template/expr/ast/Method.java
  21. 2 3
      src/main/java/com/jfinal/template/expr/ast/MethodInfo.java
  22. 1 2
      src/main/java/com/jfinal/template/expr/ast/MethodInfoExt.java
  23. 8 5
      src/main/java/com/jfinal/template/expr/ast/MethodKeyBuilder.java
  24. 12 8
      src/main/java/com/jfinal/template/expr/ast/MethodKit.java
  25. 18 9
      src/main/java/com/jfinal/template/expr/ast/SharedMethod.java
  26. 3 4
      src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java
  27. 15 15
      src/main/java/com/jfinal/template/expr/ast/StaticMethod.java
  28. 1 1
      src/main/java/com/jfinal/template/stat/ast/Output.java

+ 2 - 1
src/main/java/com/jfinal/aop/Invocation.java

@@ -84,7 +84,8 @@ public class Invocation {
 			}
 			catch (InvocationTargetException e) {
 				Throwable t = e.getTargetException();
-				throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(e);
+				if (t == null) {t = e;}
+				throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
 			}
 			catch (RuntimeException e) {
 				throw e;

+ 9 - 0
src/main/java/com/jfinal/core/JFinal.java

@@ -163,12 +163,21 @@ public final class JFinal {
 	}
 	
 	/**
+	 * jfinal 3.5 更新(2018-09-01):
+	 * 		由于 jfinal 3.5 解决了 IDEA 下 JFinal.start(四个参数) 无法启动的问题,
+	 * 		此方法已被废弃,建议使用 JFinal.start(四个参数) 带四个参数的 start()
+	 * 		方法来启动项目,IDEA 下也支持热加载,注意要先配置自动编译,jfinal 是
+	 * 		通过监测被编译的 class 文件的修改来触发热加载的
+	 * 
+	 * 
+	 * 
 	 * 用于在 IDEA 中,通过创建 main 方法的方式启动项目,不支持热加载
 	 * 本方法存在的意义在于此方法启动的速度比 maven 下的 jetty 插件要快得多
 	 * 
 	 * 注意:不支持热加载。建议通过 Ctrl + F5 快捷键,来快速重新启动项目,速度并不会比 eclipse 下的热加载慢多少
 	 *     实际操作中是先通过按 Alt + 5 打开 debug 窗口,才能按 Ctrl + F5 重启项目
 	 */
+	@Deprecated
 	public static void start(String webAppDir, int port, String context) {
 		server = new JettyServerForIDEA(webAppDir, port, context);
 		server.start();

+ 10 - 0
src/main/java/com/jfinal/ext/interceptor/NotAction.java

@@ -21,7 +21,17 @@ import com.jfinal.aop.Invocation;
 
 /**
  * NotAction
+ * 
+ * 自 jfinal 3.5 开始,不建议使用 NotAction 拦截器,而是使用
+ * com.jfinal.core 包下面的 @NotAction 注解来取代,具体
+ * 用法是:
+ *    @Before(NotAction.class) 改成 @NotAction
+ * 
+ * 
+ * 注意: 这两个文件名都是 NotAction,但后者在 com.jfinal.core 包下面
+ * 
  */
+@Deprecated
 public class NotAction implements Interceptor {
 	public void intercept(Invocation inv) {
 		inv.getController().renderError(404);

+ 1 - 1
src/main/java/com/jfinal/json/JFinalJson.java

@@ -45,7 +45,7 @@ import com.jfinal.plugin.activerecord.Record;
 @SuppressWarnings({"rawtypes", "unchecked"})
 public class JFinalJson extends Json {
 	
-	private static int defaultConvertDepth = 15;
+	private static int defaultConvertDepth = 16;
 	
 	protected int convertDepth = defaultConvertDepth;
 	protected String timestampPattern = "yyyy-MM-dd HH:mm:ss";

+ 1 - 1
src/main/java/com/jfinal/kit/ReflectKit.java

@@ -24,7 +24,7 @@ public class ReflectKit {
 	public static Object newInstance(Class<?> clazz) {
 		try {
 			return clazz.newInstance();
-		} catch (Exception e) {
+		} catch (ReflectiveOperationException e) {
 			throw new RuntimeException(e);
 		}
 	}

+ 13 - 8
src/main/java/com/jfinal/plugin/activerecord/Model.java

@@ -853,32 +853,37 @@ public abstract class Model<M extends Model> implements Serializable {
 		sb.append('{');
 		boolean first = true;
 		for (Entry<String, Object> e : attrs.entrySet()) {
-			if (first)
+			if (first) {
 				first = false;
-			else
+			} else {
 				sb.append(", ");
-			
+			}
 			Object value = e.getValue();
-			if (value != null)
+			if (value != null) {
 				value = value.toString();
+			}
 			sb.append(e.getKey()).append(':').append(value);
 		}
 		sb.append('}');
 		return sb.toString();
 	}
 	
+	// set 方法在影响 modifyFloag 的同时也会影响 attrs,所以比较 attrs 即可
 	public boolean equals(Object o) {
 		if (!(o instanceof Model))
-            return false;
-		if (_getUsefulClass() != ((Model)o)._getUsefulClass())
 			return false;
 		if (o == this)
 			return true;
-		return this.attrs.equals(((Model)o).attrs);
+		Model mo = (Model)o;
+		if (getClass() != mo.getClass())
+			return false;
+		return attrs.equals(mo.attrs);
 	}
 	
+	// hashCode 用于在容器中定位落桶,确保有较好的散列值分布即可,没必要用上所有字段
 	public int hashCode() {
-		return (attrs == null ? 0 : attrs.hashCode()) ^ (_getModifyFlag() == null ? 0 : _getModifyFlag().hashCode());
+		// return (attrs == null ? 0 : attrs.hashCode()) ^ (_getModifyFlag() == null ? 0 : _getModifyFlag().hashCode());
+		return attrs.hashCode();
 	}
 	
 	/**

+ 3 - 3
src/main/java/com/jfinal/plugin/activerecord/ModelBuilder.java

@@ -37,7 +37,7 @@ public class ModelBuilder {
 	public static final ModelBuilder me = new ModelBuilder();
 	
 	@SuppressWarnings({"rawtypes", "unchecked"})
-	public <T> List<T> build(ResultSet rs, Class<? extends Model> modelClass) throws SQLException, InstantiationException, IllegalAccessException {
+	public <T> List<T> build(ResultSet rs, Class<? extends Model> modelClass) throws SQLException, ReflectiveOperationException {
 		List<T> result = new ArrayList<T>();
 		ResultSetMetaData rsmd = rs.getMetaData();
 		int columnCount = rsmd.getColumnCount();
@@ -125,7 +125,7 @@ public class ModelBuilder {
 	
 	/* backup before use columnType
 	@SuppressWarnings({"rawtypes", "unchecked"})
-	static final <T> List<T> build(ResultSet rs, Class<? extends Model> modelClass) throws SQLException, InstantiationException, IllegalAccessException {
+	static final <T> List<T> build(ResultSet rs, Class<? extends Model> modelClass) throws SQLException, ReflectiveOperationException {
 		List<T> result = new ArrayList<T>();
 		ResultSetMetaData rsmd = rs.getMetaData();
 		int columnCount = rsmd.getColumnCount();
@@ -152,7 +152,7 @@ public class ModelBuilder {
 	
 	/* backup
 	@SuppressWarnings({"rawtypes", "unchecked"})
-	static final <T> List<T> build(ResultSet rs, Class<? extends Model> modelClass) throws SQLException, InstantiationException, IllegalAccessException {
+	static final <T> List<T> build(ResultSet rs, Class<? extends Model> modelClass) throws SQLException, ReflectiveOperationException {
 		List<T> result = new ArrayList<T>();
 		ResultSetMetaData rsmd = rs.getMetaData();
 		List<String> labelNames = getLabelNames(rsmd);

+ 11 - 7
src/main/java/com/jfinal/plugin/activerecord/Record.java

@@ -313,18 +313,22 @@ public class Record implements Serializable {
 	}
 	
 	public String toString() {
+		if (columns == null) {
+			return "{}";
+		}
 		StringBuilder sb = new StringBuilder();
 		sb.append('{');
 		boolean first = true;
 		for (Entry<String, Object> e : getColumns().entrySet()) {
-			if (first)
+			if (first) {
 				first = false;
-			else
+			} else {
 				sb.append(", ");
-			
+			}
 			Object value = e.getValue();
-			if (value != null)
+			if (value != null) {
 				value = value.toString();
+			}
 			sb.append(e.getKey()).append(':').append(value);
 		}
 		sb.append('}');
@@ -333,14 +337,14 @@ public class Record implements Serializable {
 	
 	public boolean equals(Object o) {
 		if (!(o instanceof Record))
-            return false;
+			return false;
 		if (o == this)
 			return true;
-		return this.getColumns().equals(((Record)o).getColumns());
+		return getColumns().equals(((Record)o).getColumns());
 	}
 	
 	public int hashCode() {
-		return getColumns() == null ? 0 : getColumns().hashCode();
+		return getColumns().hashCode();
 	}
 	
 	/**

+ 1 - 1
src/main/java/com/jfinal/plugin/activerecord/builder/KeepByteAndShortModelBuilder.java

@@ -42,7 +42,7 @@ public class KeepByteAndShortModelBuilder extends ModelBuilder {
 	public static final KeepByteAndShortModelBuilder me = new KeepByteAndShortModelBuilder();
 	
 	@SuppressWarnings({"rawtypes", "unchecked"})
-	public <T> List<T> build(ResultSet rs, Class<? extends Model> modelClass) throws SQLException, InstantiationException, IllegalAccessException {
+	public <T> List<T> build(ResultSet rs, Class<? extends Model> modelClass) throws SQLException, ReflectiveOperationException {
 		List<T> result = new ArrayList<T>();
 		ResultSetMetaData rsmd = rs.getMetaData();
 		int columnCount = rsmd.getColumnCount();

+ 1 - 1
src/main/java/com/jfinal/plugin/activerecord/builder/TimestampProcessedModelBuilder.java

@@ -37,7 +37,7 @@ public class TimestampProcessedModelBuilder extends ModelBuilder {
 	public static final TimestampProcessedModelBuilder me = new TimestampProcessedModelBuilder();
 	
 	@SuppressWarnings({"rawtypes", "unchecked"})
-	public <T> List<T> build(ResultSet rs, Class<? extends Model> modelClass) throws SQLException, InstantiationException, IllegalAccessException {
+	public <T> List<T> build(ResultSet rs, Class<? extends Model> modelClass) throws SQLException, ReflectiveOperationException {
 		List<T> result = new ArrayList<T>();
 		ResultSetMetaData rsmd = rs.getMetaData();
 		int columnCount = rsmd.getColumnCount();

+ 1 - 1
src/main/java/com/jfinal/plugin/activerecord/dialect/AnsiSqlDialect.java

@@ -349,7 +349,7 @@ public class AnsiSqlDialect extends Dialect {
 	}
 	
 	@SuppressWarnings({"rawtypes", "unchecked"})
-	public final <T> List<T> buildModel(ResultSet rs, Class<? extends Model> modelClass, int pageSize) throws SQLException, InstantiationException, IllegalAccessException {
+	public final <T> List<T> buildModel(ResultSet rs, Class<? extends Model> modelClass, int pageSize) throws SQLException, ReflectiveOperationException {
 		List<T> result = new ArrayList<T>();
 		ResultSetMetaData rsmd = rs.getMetaData();
 		int columnCount = rsmd.getColumnCount();

+ 1 - 1
src/main/java/com/jfinal/plugin/activerecord/dialect/Dialect.java

@@ -126,7 +126,7 @@ public abstract class Dialect {
 	}
 	
 	@SuppressWarnings("rawtypes")
-	public <T> List<T> buildModelList(ResultSet rs, Class<? extends Model> modelClass) throws SQLException, InstantiationException, IllegalAccessException {
+	public <T> List<T> buildModelList(ResultSet rs, Class<? extends Model> modelClass) throws SQLException, ReflectiveOperationException {
 		return modelBuilder.build(rs, modelClass);
 	}
 	

+ 9 - 0
src/main/java/com/jfinal/server/JettyServerForIDEA.java

@@ -33,11 +33,20 @@ import com.jfinal.kit.PathKit;
 import com.jfinal.kit.StrKit;
 
 /**
+ * jfinal 3.5 更新(2018-09-01):
+ * 		由于 jfinal 3.5 解决了 IDEA 下 JFinal.start(四个参数) 无法启动的问题,
+ * 		此类已被废弃,建议使用 JFinal.start(四个参数) 带四个参数的 start()
+ * 		方法来启动项目,IDEA 下也支持热加载,注意要先配置自动编译,jfinal 是
+ * 		通过监测被编译的 class 文件的修改来触发热加载的
+ * 
+ * 
+ * 
  * IDEA 专用于在 IDEA 之下用 main 方法启动,原启动方式在 IDEA 下会报异常
  * 注意:用此方法启动对热加载支持不完全,只支持方法内部修改的热加载,不支持添加、删除方法
  *      不支持添加类文件热加载,建议开发者在 IDEA 下使用 jrebel 或者 maven 下的 jetty
  *      插件支持列为完全的热加载
  */
+@Deprecated
 public class JettyServerForIDEA implements IServer {
 	
 	private String webAppDir;

+ 42 - 0
src/main/java/com/jfinal/template/Engine.java

@@ -22,6 +22,9 @@ import java.util.Map;
 import com.jfinal.kit.HashKit;
 import com.jfinal.kit.StrKit;
 import com.jfinal.kit.SyncWriteMap;
+import com.jfinal.template.expr.ast.FieldGetter;
+import com.jfinal.template.expr.ast.FieldKeyBuilder;
+import com.jfinal.template.expr.ast.FieldKit;
 import com.jfinal.template.expr.ast.MethodKit;
 import com.jfinal.template.source.ClassPathSourceFactory;
 import com.jfinal.template.source.ISource;
@@ -500,6 +503,45 @@ public class Engine {
 	public static void removeExtensionMethod(Class<?> targetClass, Class<?> extensionClass) {
 		MethodKit.removeExtensionMethod(targetClass, extensionClass);
 	}
+	
+	/**
+	 * 添加 FieldGetter 实现类到指定的位置
+	 * 
+	 * 系统当前默认 FieldGetter 实现类及其位置如下:
+	 * GetterMethodFieldGetter  ---> 调用 getter 方法取值
+	 * ModelFieldGetter			---> 调用 Model.get(String) 方法取值
+	 * RecordFieldGetter			---> 调用 Record.get(String) 方法取值
+	 * MapFieldGetter			---> 调用 Map.get(String) 方法取值 
+	 * RealFieldGetter			---> 直接获取 public 型的 object.field 值
+	 * ArrayLengthGetter			---> 获取数组长度
+	 * 
+	 * 根据以上次序,如果要插入 IsMethodFieldGetter 到 GetterMethodFieldGetter
+	 * 之后的代码如下:
+	 * Engine.addFieldGetter(1, new IsMethodFieldGetter());
+	 * 
+	 * 注:IsMethodFieldGetter 系统已经提供,只是默认没有启用。该实现类通过调用
+	 *    target.isXxx() 方法获取 target.xxx 表达式的值,其中 xxx 字段必须是
+	 *    Boolean/boolean 类型
+	 */
+	public static void addFieldGetter(int index, FieldGetter fieldGetter) {
+		FieldKit.addFieldGetter(index, fieldGetter);
+	}
+	
+	public static void addFieldGetterToLast(FieldGetter fieldGetter) {
+		FieldKit.addFieldGetterToLast(fieldGetter);
+	}
+	
+	public static void addFieldGetterToFirst(FieldGetter fieldGetter) {
+		FieldKit.addFieldGetterToFirst(fieldGetter);
+	}
+	
+	public static void removeFieldGetter(Class<? extends FieldGetter> fieldGetterClass) {
+		FieldKit.removeFieldGetter(fieldGetterClass);
+	}
+	
+	public static void setToFastFieldKeyBuilder() {
+		FieldKeyBuilder.setToFastFieldKeyBuilder();
+	}
 }
 
 

+ 11 - 39
src/main/java/com/jfinal/template/expr/ast/Field.java

@@ -16,11 +16,8 @@
 
 package com.jfinal.template.expr.ast;
 
-import java.lang.reflect.Array;
 import com.jfinal.kit.HashKit;
 import com.jfinal.kit.StrKit;
-import com.jfinal.plugin.activerecord.Model;
-import com.jfinal.plugin.activerecord.Record;
 import com.jfinal.template.TemplateException;
 import com.jfinal.template.stat.Location;
 import com.jfinal.template.stat.ParseException;
@@ -68,49 +65,24 @@ public class Field extends Expr {
 			throw new TemplateException("Can not accessed by \"" + fieldName + "\" field from null target", location);
 		}
 		
-		Class<?> targetClass = target.getClass();
-		Long key = buildFieldKey(targetClass);
 		
-		MethodInfo getter;
 		try {
-			getter = MethodKit.getGetterMethod(key, targetClass, getterName);
-		} catch (Exception e) {
-			throw new TemplateException(e.getMessage(), location, e);
-		}
-		
-		try {
-			if (getter != null) {
-				return getter.invoke(target, ExprList.NULL_OBJECT_ARRAY);
-			}
-			if (target instanceof Model) {
-				return ((Model<?>)target).get(fieldName);
-			}
-			if (target instanceof Record) {
-				return ((Record)target).get(fieldName);
-			}
-			if (target instanceof java.util.Map) {
-				return ((java.util.Map<?, ?>)target).get(fieldName);
-			}
-			// if (target instanceof com.jfinal.kit.Ret) {
-				// return ((com.jfinal.kit.Ret)target).get(fieldName);
-			// }
-			java.lang.reflect.Field field = FieldKit.getField(key, targetClass, fieldName);
-			if (field != null) {
-				return field.get(target);
-			}
-			
-			// 支持获取数组长度: array.length
-			if ("length".equals(fieldName) && target.getClass().isArray()) {
-				return Array.getLength(target);
+			Class<?> targetClass = target.getClass();
+			Object key = FieldKeyBuilder.instance.getFieldKey(targetClass, getterNameHash);
+			FieldGetter fieldGetter = FieldKit.getFieldGetter(key, targetClass, fieldName);
+			if (fieldGetter.notNull()) {
+				return fieldGetter.get(target, fieldName);
 			}
+		} catch (TemplateException | ParseException e) {
+			throw e;
 		} catch (Exception e) {
 			throw new TemplateException(e.getMessage(), location, e);
 		}
 		
+		
 		if (scope.getCtrl().isNullSafe()) {
 			return null;
 		}
-		
 		if (expr instanceof Id) {
 			String id = ((Id)expr).getId();
 			throw new TemplateException("public field not found: \"" + id + "." + fieldName + "\" and public getter method not found: \"" + id + "." + getterName + "()\"", location);
@@ -118,9 +90,9 @@ public class Field extends Expr {
 		throw new TemplateException("public field not found: \"" + fieldName + "\" and public getter method not found: \"" + getterName + "()\"", location);
 	}
 	
-	private Long buildFieldKey(Class<?> targetClass) {
-		return targetClass.getName().hashCode() ^ getterNameHash;
-	}
+	// private Long buildFieldKey(Class<?> targetClass) {
+		// return targetClass.getName().hashCode() ^ getterNameHash;
+	// }
 }
 
 

+ 58 - 0
src/main/java/com/jfinal/template/expr/ast/FieldGetter.java

@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.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.jfinal.template.expr.ast;
+
+/**
+ * FieldGetter 用于支持 target.field 表达式的取值,
+ * 以及支持用户扩展自定义的 FieldGetter 实现方式
+ */
+public abstract class FieldGetter {
+	
+	/**
+	 * 接管 target.fieldName 表达式,如果可以接管则返回接管对象,否则返回 null
+	 * @param targetClass target.fieldName 表达式中 target 的 Class 类型
+	 * @param fieldName target.fieldName 表达式中的 fieldName 部分
+	 * @return 如果可以接管 targetClass.fieldName 则返回接管对象,否则返回 null
+	 */
+	public abstract FieldGetter takeOver(Class<?> targetClass, String fieldName);
+	
+	/**
+	 * 获取 target.fieldName 表达式的值
+	 * @param target 目标对象
+	 * @param fieldName 字段名称
+	 * @return target.fieldName 表达式的值
+	 */
+	public abstract Object get(Object target, String fieldName) throws Exception;
+	
+	/**
+	 * 仅仅 NullFieldGetter 会覆盖此方法并返回 false
+	 * 
+	 * 用于消除 FieldKit.getFieldGetter(...) 中的 instanceof 判断,
+	 * 并且让 Map fieldGetterCache 的 value 值不必使用 Object 类型,
+	 * 还消除了 Field.exec(...) 中的 null 值判断
+	 */
+	public boolean notNull() {
+		return true;
+	}
+}
+
+
+
+
+
+
+

+ 270 - 0
src/main/java/com/jfinal/template/expr/ast/FieldGetters.java

@@ -0,0 +1,270 @@
+/**
+ * Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.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.jfinal.template.expr.ast;
+
+import java.lang.reflect.Array;
+import com.jfinal.kit.StrKit;
+
+/**
+ * FieldGetters 封装官方默认 FieldGetter 实现
+ */
+public class FieldGetters {
+	
+	/**
+	 * NullFieldGetter
+	 * 
+	 * 用于消除 FieldKit.getFieldGetter(...) 中的 instanceof 判断,并且让 Map fieldGetterCache
+	 * 中的 value 不必使用 Object 类型。还消除了 Field.exec(...) 中的 null 值判断
+	 */
+	public static class NullFieldGetter extends FieldGetter {
+		
+		public static final NullFieldGetter me = new NullFieldGetter();
+		
+		public boolean notNull() {
+			return false;
+		}
+		
+		public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
+			throw new RuntimeException("The method takeOver(Class, String) of NullFieldGetter should not be invoked");
+		}
+		
+		public Object get(Object target, String fieldName) throws Exception {
+			throw new RuntimeException("The method get(Object, String) of NullFieldGetter should not be invoked");
+		}
+	}
+	
+	/**
+	 * GetterMethodFieldGetter
+	 * 
+	 * 使用 getter 方法获取 target.field 表达式的值
+	 */
+	public static class GetterMethodFieldGetter extends FieldGetter {
+		
+		protected java.lang.reflect.Method getterMethod;
+		
+		public GetterMethodFieldGetter(java.lang.reflect.Method getterMethod) {
+			this.getterMethod = getterMethod;
+		}
+		
+		public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
+			if (MethodKit.isForbiddenClass(targetClass)) {
+				throw new RuntimeException("Forbidden class: " + targetClass.getName());
+			}
+			
+			String getterName = "get" + StrKit.firstCharToUpperCase(fieldName);
+			java.lang.reflect.Method[] methodArray = targetClass.getMethods();
+			for (java.lang.reflect.Method method : methodArray) {
+				if (method.getName().equals(getterName) && method.getParameterTypes().length == 0) {
+					if (MethodKit.isForbiddenMethod(getterName)) {
+						throw new RuntimeException("Forbidden method: " + getterName);
+					}
+					
+					return new GetterMethodFieldGetter(method);
+				}
+			}
+			
+			return null;
+		}
+		
+		public Object get(Object target, String fieldName) throws Exception {
+			return getterMethod.invoke(target, ExprList.NULL_OBJECT_ARRAY);
+		}
+		
+		public String toString() {
+			return getterMethod.toString();
+		}
+	}
+	
+	/**
+	 * IsMethodFieldGetter
+	 * 
+	 * 使用 target.isXxx() 方法获取值,默认不启用该功能,用户可以通过如下方式启用:
+	 * Engine.addLastFieldGetter(new FieldGetters.IsMethodFieldGetter(null));
+	 */
+	public static class IsMethodFieldGetter extends FieldGetter {
+		
+		protected java.lang.reflect.Method isMethod;
+		
+		// 此构造方法仅为了方便在 Engine.addFieldGetter(...) 添加时不用为构造方法传参
+		public IsMethodFieldGetter() {
+		}
+		
+		public IsMethodFieldGetter(java.lang.reflect.Method isMethod) {
+			this.isMethod = isMethod;
+		}
+		
+		public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
+			if (MethodKit.isForbiddenClass(targetClass)) {
+				throw new RuntimeException("Forbidden class: " + targetClass.getName());
+			}
+			
+			String isMethodName = "is" + StrKit.firstCharToUpperCase(fieldName);
+			java.lang.reflect.Method[] methodArray = targetClass.getMethods();
+			for (java.lang.reflect.Method method : methodArray) {
+				if (method.getName().equals(isMethodName) && method.getParameterTypes().length == 0) {
+					Class<?> returnType = method.getReturnType();
+					if (returnType == Boolean.class || returnType == boolean.class) {
+						return new IsMethodFieldGetter(method);
+					}
+				}
+			}
+			
+			return null;
+		}
+		
+		public Object get(Object target, String fieldName) throws Exception {
+			return isMethod.invoke(target, ExprList.NULL_OBJECT_ARRAY);
+		}
+		
+		public String toString() {
+			return isMethod.toString();
+		}
+	}
+	
+	/**
+	 * ModelFieldGetter
+	 * 
+	 * 使用 Model.get(String) 获取值
+	 */
+	public static class ModelFieldGetter extends FieldGetter {
+		
+		// 所有 Model 可以共享 ModelFieldGetter 获取属性
+		static final ModelFieldGetter singleton = new ModelFieldGetter();
+		
+		public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
+			if (com.jfinal.plugin.activerecord.Model.class.isAssignableFrom(targetClass)) {
+				return singleton;
+			} else {
+				return null;
+			}
+		}
+		
+		public Object get(Object target, String fieldName) throws Exception {
+			return ((com.jfinal.plugin.activerecord.Model<?>)target).get(fieldName);
+		}
+	}
+	
+	/**
+	 * RecordFieldGetter
+	 * 
+	 * 使用 Record.get(String) 获取值
+	 */
+	public static class RecordFieldGetter extends FieldGetter {
+		
+		// 所有 Record 可以共享 RecordFieldGetter 获取属性
+		static final RecordFieldGetter singleton = new RecordFieldGetter();
+		
+		public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
+			if (com.jfinal.plugin.activerecord.Record.class.isAssignableFrom(targetClass)) {
+				return singleton;
+			} else {
+				return null;
+			}
+		}
+		
+		public Object get(Object target, String fieldName) throws Exception {
+			return ((com.jfinal.plugin.activerecord.Record)target).get(fieldName);
+		}
+	}
+	
+	/**
+	 * MapFieldGetter
+	 * 
+	 * 使用 Map.get(Object) 获取值
+	 */
+	public static class MapFieldGetter extends FieldGetter {
+		
+		// 所有 Map 可以共享 MapFieldGetter 获取属性
+		static final MapFieldGetter singleton = new MapFieldGetter();
+		
+		public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
+			if (java.util.Map.class.isAssignableFrom(targetClass)) {
+				return singleton;
+			} else {
+				return null;
+			}
+		}
+		
+		public Object get(Object target, String fieldName) throws Exception {
+			return ((java.util.Map<?, ?>)target).get(fieldName);
+		}
+	}
+	
+	/**
+	 * RealFieldGetter
+	 * 
+	 * 使用 target.field 获取值
+	 */
+	public static class RealFieldGetter extends FieldGetter {
+		
+		protected java.lang.reflect.Field field;
+		
+		public RealFieldGetter(java.lang.reflect.Field field) {
+			this.field = field;
+		}
+		
+		public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
+			java.lang.reflect.Field[] fieldArray = targetClass.getFields();
+			for (java.lang.reflect.Field field : fieldArray) {
+				if (field.getName().equals(fieldName)) {
+					return new RealFieldGetter(field);
+				}
+			}
+			
+			return null;
+		}
+		
+		public Object get(Object target, String fieldName) throws Exception {
+			return field.get(target);
+		}
+		
+		public String toString() {
+			return field.toString();
+		}
+	}
+	
+	/**
+	 * ArrayLengthGetter
+	 * 
+	 * 获取数组长度: array.length
+	 */
+	public static class ArrayLengthGetter extends FieldGetter {
+		
+		// 所有数组可以共享 ArrayLengthGetter 获取属性
+		static final ArrayLengthGetter singleton = new ArrayLengthGetter();
+		
+		public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
+			if ("length".equals(fieldName) && targetClass.isArray()) {
+				return singleton;
+			} else {
+				return null;
+			}
+		}
+		
+		public Object get(Object target, String fieldName) throws Exception {
+			return Array.getLength(target);
+		}
+	}
+}
+
+
+
+
+
+
+
+

+ 128 - 0
src/main/java/com/jfinal/template/expr/ast/FieldKeyBuilder.java

@@ -0,0 +1,128 @@
+/**
+ * Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.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.jfinal.template.expr.ast;
+
+/**
+ * FieldKeyBuilder
+ * 
+ * 用于生成缓存 FieldGetter 的 key
+ */
+public abstract class FieldKeyBuilder {
+	
+	public abstract Object getFieldKey(Class<?> targetClass, long fieldFnv1a64Hash);
+	
+	// 假定是超大规模项目,并且假定其 Map/Model/Record + field 组合数量超级庞大,默认使用 StrictFieldKeyBuilder
+	static FieldKeyBuilder instance = new StrictFieldKeyBuilder();
+	
+	public static FieldKeyBuilder getInstance() {
+		return instance;
+	}
+	
+	/**
+	 * 设置为官方提供的 FastFieldKeyBuilder 实现,性能更高
+	 */
+	public static void setToFastFieldKeyBuilder() {
+		instance = new FastFieldKeyBuilder();
+	}
+	
+	/**
+	 * 设置为自定义 FieldKeyBuilder
+	 */
+	public static void setFieldKeyBuilder(FieldKeyBuilder fieldKeyBuilder) {
+		if (fieldKeyBuilder == null) {
+			throw new IllegalArgumentException("fieldKeyBuilder can not be null");
+		}
+		instance = fieldKeyBuilder;
+	}
+	
+	// ------------------------------------------------------------------------
+	
+	/**
+	 * FastFieldKeyBuilder
+	 */
+	public static class FastFieldKeyBuilder extends FieldKeyBuilder {
+		public Object getFieldKey(Class<?> targetClass, long fieldFnv1a64Hash) {
+			return targetClass.getName().hashCode() ^ fieldFnv1a64Hash;
+		}
+	}
+	
+	// ------------------------------------------------------------------------
+	
+	/**
+	 * StrictFieldKeyBuilder
+	 */
+	public static class StrictFieldKeyBuilder extends FieldKeyBuilder {
+		public Object getFieldKey(Class<?> targetClass, long fieldFnv1a64Hash) {
+			return new FieldKey(targetClass.getName().hashCode(), fieldFnv1a64Hash);
+		}
+	}
+	
+	// ------------------------------------------------------------------------
+	
+	/**
+	 * FieldKey
+	 * 
+	 * FieldKey 用于封装 targetClass、fieldName 这两部分的 hash 值,
+	 * 确保不会出现 key 值碰撞
+	 * 
+	 * 这两部分 hash 值在不同 class 与 field 的组合下出现碰撞的
+	 * 概率完全可以忽略不计
+	 * 
+	 * 备忘:
+	 * 可以考虑用 ThreadLocal 重用 FieldKey 对象,但要注意放入 Map fieldGetterCache
+	 * 中的 FieldKey 对象需要 clone 出来,确保线程安全。由于 FieldKey 占用空间不大,
+	 * 所以 ThreadLocal 方案大概率并无优势,从 ThreadLocal 中获取数据时,除了耗时也无法
+	 * 避免创建对象
+	 */
+	public static class FieldKey {
+		
+		final long classHash;
+		final long fieldHash;
+		
+		public FieldKey(long classHash, long fieldHash) {
+			this.classHash = classHash;
+			this.fieldHash = fieldHash;
+		}
+		
+		public int hashCode() {
+			return (int)(classHash ^ fieldHash);
+		}
+		
+		/**
+		 * FieldKey 的核心价值在于此 equals 方法通过比较两部分 hash 值,避免超大规模场景下可能的 key 值碰撞
+		 * 
+		 * 不必判断 if (fieldKey instanceof FieldKey),因为所有 key 类型必须要相同
+		 * 不必判断 if (this == fieldKey),因为每次用于取值的 FieldKey 都是新建的
+		 */
+		public boolean equals(Object fieldKey) {
+			FieldKey fk = (FieldKey)fieldKey;
+			return classHash == fk.classHash && fieldHash == fk.fieldHash;
+		}
+		
+		public String toString() {
+			return "classHash = " + classHash + "\nfieldHash = " + fieldHash;
+		}
+	}
+}
+
+
+
+
+
+
+
+

+ 102 - 17
src/main/java/com/jfinal/template/expr/ast/FieldKit.java

@@ -16,39 +16,121 @@
 
 package com.jfinal.template.expr.ast;
 
-import java.lang.reflect.Field;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
 import com.jfinal.kit.SyncWriteMap;
+import com.jfinal.template.expr.ast.FieldGetters.*;
 
 /**
  * FieldKit
  */
 public class FieldKit {
 	
-	private static final HashMap<Long, Object> fieldCache = new SyncWriteMap<Long, Object>(512, 0.5F);
+	private static FieldGetter[] getters = init();
 	
-	public static Field getField(Long key, Class<?> targetClass, String fieldName) {
-		Object field = fieldCache.get(key);
-		if (field == null) {
-			field = doGetField(targetClass, fieldName);
-			if (field != null) {
-				fieldCache.put(key, field);
+	private static final HashMap<Object, FieldGetter> fieldGetterCache = new SyncWriteMap<Object, FieldGetter>(1024, 0.25F);
+	
+	/**
+	 * 初始化官方默认 FieldGetter
+	 * 
+	 * 注意:
+	 * 默认不启用 IsMethodFieldGetter,用户可以通过下面的代码启用:
+	 * Engine.addLastFieldGetter(new FieldGetters.IsMethodFieldGetter());
+	 * 
+	 * 也可以通过直接调用 target.isXxx() 方法来达到与 target.xxx 表达式相同的目的
+	 */
+	private static FieldGetter[] init() {
+		LinkedList<FieldGetter> ret = new LinkedList<FieldGetter>();
+		
+		ret.addLast(new GetterMethodFieldGetter(null));
+		ret.addLast(new ModelFieldGetter());
+		ret.addLast(new RecordFieldGetter());
+		ret.addLast(new MapFieldGetter());
+		ret.addLast(new RealFieldGetter(null));
+		ret.addLast(new ArrayLengthGetter());
+		
+		return ret.toArray(new FieldGetter[ret.size()]);
+	}
+	
+	public static FieldGetter getFieldGetter(Object key, Class<?> targetClass, String fieldName) {
+		FieldGetter fieldGetter = fieldGetterCache.get(key);
+		if (fieldGetter == null) {
+			fieldGetter = doGetFieldGetter(targetClass, fieldName);	// 已确保不会返回 null
+			fieldGetterCache.putIfAbsent(key, fieldGetter);
+		}
+		return fieldGetter;
+	}
+	
+	private static FieldGetter doGetFieldGetter(Class<?> targetClass, String fieldName) {
+		FieldGetter ret;
+		for (FieldGetter fg : getters) {
+			ret = fg.takeOver(targetClass, fieldName);
+			if (ret != null) {
+				return ret;
+			}
+		}
+		return NullFieldGetter.me;
+	}
+	
+	public static void addFieldGetter(int index, FieldGetter fieldGetter) {
+		addFieldGetter(fieldGetter, index, true);
+	}
+	
+	public static void addFieldGetterToLast(FieldGetter fieldGetter) {
+		addFieldGetter(fieldGetter, null, true);
+	}
+	
+	public static void addFieldGetterToFirst(FieldGetter fieldGetter) {
+		addFieldGetter(fieldGetter, null, false);
+	}
+	
+	// 当 Integer index 不为 null 时,boolean addLast 为无效参数
+	private static synchronized void addFieldGetter(FieldGetter fieldGetter, Integer index, boolean addLast) {
+		checkParameter(fieldGetter);
+		
+		LinkedList<FieldGetter> ret = getCurrentFieldGetters();
+		if (index != null) {
+			ret.add(index, fieldGetter);
+		} else {
+			if (addLast) {
+				ret.addLast(fieldGetter);
 			} else {
-				// 对于不存在的 Field,只进行一次获取操作,主要为了支持 null safe,未来需要考虑内存泄漏风险
-				fieldCache.put(key, Void.class);
+				ret.addFirst(fieldGetter);
 			}
 		}
-		return field instanceof Field ? (Field)field : null;
+		getters = ret.toArray(new FieldGetter[ret.size()]);
 	}
 	
-	private static Field doGetField(Class<?> targetClass, String fieldName) {
-		Field[] fs = targetClass.getFields();
-		for (Field f : fs) {
-			if (f.getName().equals(fieldName)) {
-				return f;
+	private static LinkedList<FieldGetter> getCurrentFieldGetters() {
+		LinkedList<FieldGetter> ret = new LinkedList<FieldGetter>();
+		for (FieldGetter fieldGetter : getters) {
+			ret.add(fieldGetter);
+		}
+		return ret;
+	}
+	
+	private static void checkParameter(FieldGetter fieldGetter) {
+		if (fieldGetter == null) {
+			throw new IllegalArgumentException("The parameter fieldGetter can not be null");
+		}
+		for (FieldGetter fg : getters) {
+			if (fg.getClass() == fieldGetter.getClass()) {
+				throw new RuntimeException("FieldGetter already exists : " + fieldGetter.getClass().getName());
 			}
 		}
-		return null;
+	}
+	
+	public static synchronized void removeFieldGetter(Class<? extends FieldGetter> fieldGetterClass) {
+		LinkedList<FieldGetter> ret = getCurrentFieldGetters();
+		
+		for (Iterator<FieldGetter> it = ret.iterator(); it.hasNext();) {
+			if (it.next().getClass() == fieldGetterClass) {
+				it.remove();
+			}
+		}
+		
+		getters = ret.toArray(new FieldGetter[ret.size()]);
 	}
 }
 
@@ -56,3 +138,6 @@ public class FieldKit {
 
 
 
+
+
+

+ 12 - 16
src/main/java/com/jfinal/template/expr/ast/Method.java

@@ -33,7 +33,7 @@ import com.jfinal.template.stat.Scope;
  * 如果在未来通过结合 #dynamic(boolean) 指令来优化,需要在 Ctrl 中引入一个
  * boolean dynamic = false 变量,而不能在 Env、Scope 引入该变量
  * 
- * 还需要引入一个 NullMethodInfo 以及 isNull() 方法,此优化复杂度提高不少,
+ * 还需要引入一个 NullMethodInfo 以及 notNull() 方法,此优化复杂度提高不少,
  * 暂时不做此优化
  */
 public class Method extends Expr {
@@ -76,28 +76,24 @@ public class Method extends Expr {
 		}
 		
 		Object[] argValues = exprList.evalExprList(scope);
-		MethodInfo methodInfo;
 		try {
-			methodInfo = MethodKit.getMethod(target.getClass(), methodName, argValues);
-		} catch (Exception e) {
-			throw new TemplateException(e.getMessage(), location, e);
-		}
-		if (methodInfo == null) {
+			
+			MethodInfo methodInfo = MethodKit.getMethod(target.getClass(), methodName, argValues);
+			if (methodInfo != null) {
+				return methodInfo.invoke(target, argValues);
+			}
+			
 			if (scope.getCtrl().isNullSafe()) {
 				return null;
 			}
 			throw new TemplateException(buildMethodNotFoundSignature("public method not found: " + target.getClass().getName() + ".", methodName, argValues), location);
-		}
-		
-		try {
-			return methodInfo.invoke(target, argValues);
+			
+		} catch (TemplateException | ParseException e) {
+			throw e;
 		} catch (InvocationTargetException e) {
 			Throwable t = e.getTargetException();
-			if (t != null) {
-				throw new TemplateException(t.getMessage(), location, t);
-			} else {
-				throw new TemplateException(e.getMessage(), location, e);
-			}
+			if (t == null) {t = e;}
+			throw new TemplateException(t.getMessage(), location, t);
 		} catch (Exception e) {
 			throw new TemplateException(e.getMessage(), location, e);
 		}

+ 2 - 3
src/main/java/com/jfinal/template/expr/ast/MethodInfo.java

@@ -17,7 +17,6 @@
 package com.jfinal.template.expr.ast;
 
 import java.lang.reflect.Array;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 
@@ -41,7 +40,7 @@ public class MethodInfo {
 		this.paraTypes = method.getParameterTypes();
 	}
 	
-	public Object invoke(Object target, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+	public Object invoke(Object target, Object... args) throws ReflectiveOperationException {
 		if (isVarArgs) {
 			return invokeVarArgsMethod(target, args);
 		} else {
@@ -49,7 +48,7 @@ public class MethodInfo {
 		}
 	}
 	
-	protected Object invokeVarArgsMethod(Object target, Object[] argValues) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+	protected Object invokeVarArgsMethod(Object target, Object[] argValues) throws ReflectiveOperationException {
 		Object[] finalArgValues = new Object[paraTypes.length];
 		
 		int fixedParaLength = paraTypes.length - 1;

+ 1 - 2
src/main/java/com/jfinal/template/expr/ast/MethodInfoExt.java

@@ -16,7 +16,6 @@
 
 package com.jfinal.template.expr.ast;
 
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 
 /**
@@ -37,7 +36,7 @@ public class MethodInfoExt extends MethodInfo {
 		// this.paraTypes = newParaTypes;
 	}
 	
-	public Object invoke(Object target, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+	public Object invoke(Object target, Object... args) throws ReflectiveOperationException {
 		Object[] finalArgs = new Object[args.length + 1];
 		finalArgs[0] = target;
 		

+ 8 - 5
src/main/java/com/jfinal/template/expr/ast/MethodKeyBuilder.java

@@ -43,17 +43,20 @@ public abstract class MethodKeyBuilder {
 	 *   如果希望将 configEngine(Engine me) 中的 Engine 切换到 StrictMethodKeyBuilder,
 	 *   需要在 YourJFinalConfig extends JFinalConfig 中利用如下代码块才能生效:
 	 * 	  static {
-	 * 			MethodKeyBuilder.useStrictMethodKeyBuilder();
+	 * 			MethodKeyBuilder.setToStrictMethodKeyBuilder();
 	 *    }
 	 * 
-	 *   原因是在 com.jfinal.core.Config 中 new Engine() 时 useStrictMethodKeyBuilder()
+	 *   原因是在 com.jfinal.core.Config 中 new Engine() 时 setToStrictMethodKeyBuilder()
 	 *   方法并未生效,所以 extension method 生成 method key 时仍然使用的是  FastMethodKeyBuilder
 	 *   以至于在运行时,使用 StrictMethodKeyBuilder 生成的 key 找不到 extension method
+	 *   
+	 *   后续版本考虑在调用 setToStrictMethodKeyBuilder() 以后重新初始化一下 MethodKit 中的变量
+	 *   原先的 static 初始化方式重构出 synchronized void init() 方法来方便调用
 	 * 
 	 * </pre>
 	 */
-	public static void useStrictMethodKeyBuilder() {
-		MethodKeyBuilder.instance = new StrictMethodKeyBuilder();
+	public static void setToStrictMethodKeyBuilder() {
+		instance = new StrictMethodKeyBuilder();
 	}
 	
 	/**
@@ -63,7 +66,7 @@ public abstract class MethodKeyBuilder {
 		if (methodKeyBuilder == null) {
 			throw new IllegalArgumentException("methodKeyBuilder can not be null");
 		}
-		MethodKeyBuilder.instance = methodKeyBuilder;
+		instance = methodKeyBuilder;
 	}
 	
 	/**

+ 12 - 8
src/main/java/com/jfinal/template/expr/ast/MethodKit.java

@@ -41,7 +41,7 @@ public class MethodKit {
 	private static final Set<String> forbiddenMethods = new HashSet<String>(64);
 	private static final Set<Class<?>> forbiddenClasses = new HashSet<Class<?>>(64);
 	private static final Map<Class<?>, Class<?>> primitiveMap = new HashMap<Class<?>, Class<?>>(64);
-	private static final Map<Long, Object> methodCache = new SyncWriteMap<Long, Object>(2048, 0.25F);
+	private static final SyncWriteMap<Long, Object> methodCache = new SyncWriteMap<Long, Object>(2048, 0.25F);
 	
 	// 初始化在模板中调用 method 时所在的被禁止使用类
 	static {
@@ -94,6 +94,10 @@ public class MethodKit {
 		return forbiddenClasses.contains(clazz);
 	}
 	
+	public static void addForbiddenClass(Class<?> clazz) {
+		forbiddenClasses.add(clazz);
+	}
+	
 	public static boolean isForbiddenMethod(String methodName) {
 		return forbiddenMethods.contains(methodName);
 	}
@@ -109,10 +113,10 @@ public class MethodKit {
 		if (method == null) {
 			method = doGetMethod(key, targetClass, methodName, argTypes);
 			if (method != null) {
-				methodCache.put(key, method);
+				methodCache.putIfAbsent(key, method);
 			} else {
 				// 对于不存在的 Method,只进行一次获取操作,主要为了支持 null safe,未来需要考虑内存泄漏风险
-				methodCache.put(key, Void.class);
+				methodCache.putIfAbsent(key, Void.class);
 			}
 		}
 		return method instanceof MethodInfo ? (MethodInfo)method : null;
@@ -121,19 +125,19 @@ public class MethodKit {
 	/**
 	 * 获取 getter 方法
 	 * 使用与 Field 相同的 key,避免生成两次 key值
-	 */
+	 * ---> jfinal 3.5 已将此功能转移至 FieldKit
 	public static MethodInfo getGetterMethod(Long key, Class<?> targetClass, String methodName) {
 		Object getterMethod = methodCache.get(key);
 		if (getterMethod == null) {
 			getterMethod = doGetMethod(key, targetClass, methodName, NULL_ARG_TYPES);
 			if (getterMethod != null) {
-				methodCache.put(key, getterMethod);
+				methodCache.putIfAbsent(key, getterMethod);
 			} else {
-				methodCache.put(key, Void.class);
+				methodCache.putIfAbsent(key, Void.class);
 			}
 		}
 		return getterMethod instanceof MethodInfo ? (MethodInfo)getterMethod : null;
-	}
+	} */
 	
 	static Class<?>[] getArgTypes(Object[] argValues) {
 		if (argValues == null || argValues.length == 0) {
@@ -280,7 +284,7 @@ public class MethodKit {
 				}
 				
 				MethodInfoExt mie = new MethodInfoExt(objectOfExtensionClass, key, extensionClass/* targetClass */, method);
-				methodCache.put(key, mie);
+				methodCache.putIfAbsent(key, mie);
 			}
 		}
 	}

+ 18 - 9
src/main/java/com/jfinal/template/expr/ast/SharedMethod.java

@@ -26,9 +26,14 @@ import com.jfinal.template.stat.Scope;
  * SharedMethod
  * 
  * 用法:
- * engine.addSharedMethod(object);
- * engine.addSharedStaticMethod(Xxx.class);
- * #(method(para))
+ * engine.addSharedMethod(new StrKit());
+ * engine.addSharedStaticMethod(MyKit.class);
+ * 
+ * #if (notBlank(para))
+ *     ....
+ * #end
+ * 
+ * 上面代码中的 notBlank 方法来自 StrKit
  */
 public class SharedMethod extends Expr {
 	
@@ -48,14 +53,18 @@ public class SharedMethod extends Expr {
 	
 	public Object eval(Scope scope) {
 		Object[] argValues = exprList.evalExprList(scope);
-		SharedMethodInfo sharedMethodInfo = sharedMethodKit.getSharedMethodInfo(methodName, argValues);
 		
-		// ShareMethod 相当于是固定的静态的方法,不支持 null safe,null safe 只支持具有动态特征的用法
-		if (sharedMethodInfo == null) {
-			throw new TemplateException(Method.buildMethodNotFoundSignature("Shared method not found: ", methodName, argValues), location);
-		}
 		try {
-			return sharedMethodInfo.invoke(argValues);
+			SharedMethodInfo sharedMethodInfo = sharedMethodKit.getSharedMethodInfo(methodName, argValues);
+			if (sharedMethodInfo != null) {
+				return sharedMethodInfo.invoke(argValues);
+			} else {
+				// ShareMethod 相当于是固定的静态的方法,不支持 null safe,null safe 只支持具有动态特征的用法
+				throw new TemplateException(Method.buildMethodNotFoundSignature("Shared method not found: ", methodName, argValues), location);
+			}
+			
+		} catch (TemplateException | ParseException e) {
+			throw e;
 		} catch (Exception e) {
 			throw new TemplateException(e.getMessage(), location, e);
 		}

+ 3 - 4
src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java

@@ -22,7 +22,6 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.HashMap;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import com.jfinal.kit.HashKit;
@@ -54,9 +53,9 @@ public class SharedMethodKit {
 		if (method == null) {
 			method = doGetSharedMethodInfo(methodName, argTypes);
 			if (method != null) {
-				methodCache.put(key, method);
+				methodCache.putIfAbsent(key, method);
 			}
-			// shared method 不支持 null safe,不缓存: methodCache.put(key, Void.class)
+			// shared method 不支持 null safe,不缓存: methodCache.putIfAbsent(key, Void.class)
 		}
 		return method;
 	}
@@ -174,7 +173,7 @@ public class SharedMethodKit {
 			this.target = target;
 		}
 		
-		public Object invoke(Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+		public Object invoke(Object... args) throws ReflectiveOperationException {
 			return super.invoke(target, args);
 		}
 		

+ 15 - 15
src/main/java/com/jfinal/template/expr/ast/StaticMethod.java

@@ -57,23 +57,23 @@ public class StaticMethod extends Expr {
 	
 	public Object eval(Scope scope) {
 		Object[] argValues = exprList.evalExprList(scope);
-		MethodInfo methodInfo;
-		try {
-			methodInfo = MethodKit.getMethod(clazz, methodName, argValues);
-		} catch (Exception e) {
-			throw new TemplateException(e.getMessage(), location, e);
-		}
-		
-		// StaticMethod 是固定的存在,不支持 null safe,null safe 只支持具有动态特征的用法
-		if (methodInfo == null) {
-			throw new TemplateException(Method.buildMethodNotFoundSignature("public static method not found: " + clazz.getName() + "::", methodName, argValues), location);
-		}
-		if (!methodInfo.isStatic()) {
-			throw new TemplateException(Method.buildMethodNotFoundSignature("Not public static method: " + clazz.getName() + "::", methodName, argValues), location);
-		}
 		
 		try {
-			return methodInfo.invoke(null, argValues);
+			MethodInfo methodInfo = MethodKit.getMethod(clazz, methodName, argValues);
+			
+			if (methodInfo != null) {
+				if (methodInfo.isStatic()) {
+					return methodInfo.invoke(null, argValues);
+				} else {
+					throw new TemplateException(Method.buildMethodNotFoundSignature("Not public static method: " + clazz.getName() + "::", methodName, argValues), location);
+				}
+			} else {
+				// StaticMethod 是固定的存在,不支持 null safe,null safe 只支持具有动态特征的用法
+				throw new TemplateException(Method.buildMethodNotFoundSignature("public static method not found: " + clazz.getName() + "::", methodName, argValues), location);
+			}
+			
+		} catch (TemplateException | ParseException e) {
+			throw e;
 		} catch (Exception e) {
 			throw new TemplateException(e.getMessage(), location, e);
 		}

+ 1 - 1
src/main/java/com/jfinal/template/stat/ast/Output.java

@@ -71,7 +71,7 @@ public class Output extends Stat {
 			} else if (value != null) {
 				writer.write(value.toString());
 			}
-		} catch(TemplateException e) {
+		} catch(TemplateException | ParseException e) {
 			throw e;
 		} catch(Exception e) {
 			throw new TemplateException(e.getMessage(), location, e);