From 1ce7068072073448a02b48f90efb802f38e6adbd Mon Sep 17 00:00:00 2001 From: James Date: Sun, 12 Aug 2018 12:15:01 +0800 Subject: [PATCH] enjoy 3.5 --- .../java/com/jfinal/kit/SyncWriteMap.java | 92 +++++++++++++++++++ src/main/java/com/jfinal/template/Engine.java | 8 +- .../com/jfinal/template/EngineConfig.java | 17 ++-- src/main/java/com/jfinal/template/Env.java | 2 +- .../template/OutputDirectiveFactory.java | 34 ------- .../jfinal/template/expr/ast/FieldKit.java | 3 +- .../com/jfinal/template/expr/ast/Method.java | 11 +++ .../jfinal/template/expr/ast/MethodKit.java | 11 ++- .../template/expr/ast/SharedMethodKit.java | 3 +- .../template/ext/directive/DateDirective.java | 22 +++-- .../ext/directive/EscapeDirective.java | 5 +- .../ext/directive/RenderDirective.java | 4 +- .../template/ext/spring/JFinalView.java | 17 +++- .../template/source/ClassPathSource.java | 27 ++---- .../OutputDirectiveFactory.java} | 20 ++-- .../java/com/jfinal/template/stat/Symbol.java | 2 +- 16 files changed, 183 insertions(+), 95 deletions(-) create mode 100644 src/main/java/com/jfinal/kit/SyncWriteMap.java delete mode 100644 src/main/java/com/jfinal/template/OutputDirectiveFactory.java rename src/main/java/com/jfinal/template/{IOutputDirectiveFactory.java => stat/OutputDirectiveFactory.java} (73%) diff --git a/src/main/java/com/jfinal/kit/SyncWriteMap.java b/src/main/java/com/jfinal/kit/SyncWriteMap.java new file mode 100644 index 0000000..6919bb2 --- /dev/null +++ b/src/main/java/com/jfinal/kit/SyncWriteMap.java @@ -0,0 +1,92 @@ +/** + * 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.kit; + +import java.util.HashMap; +import java.util.Map; + +/** + * SyncWriteMap 同步写 HashMap + * 创建原因是 HashMap扩容时,遇到并发修改可能造成 100% CPU 占用 + * + * SyncWriteMap 拥有 HashMap 的性能,但不保障并发访问的线程安全 + * 只用于读多写少且不用保障线程安全的场景 + * + * 例如 MethodKit 中用于缓存 MethodInfo 的 cache,被写入的数据 + * 不用保障是单例,读取之后会做 null 值判断 + * + * ActionMapping 中的 HashMap 是系统启动时在独立线程内初始化的, + * 不存在并发写,只存在并发读的情况,所以仍然可以使用 HashMap + */ +public class SyncWriteMap extends HashMap { + + private static final long serialVersionUID = -7287230891751869148L; + + public SyncWriteMap() { + } + + public SyncWriteMap(int initialCapacity) { + super(initialCapacity); + } + + public SyncWriteMap(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); + } + + public SyncWriteMap(Map m) { + super(m); + } + + @Override + public V put(K key, V value) { + synchronized (this) { + return super.put(key, value); + } + } + + @Override + public V putIfAbsent(K key, V value) { + synchronized (this) { + return super.putIfAbsent(key, value); + } + } + + @Override + public void putAll(Map m) { + synchronized (this) { + super.putAll(m); + } + } + + @Override + public V remove(Object key) { + synchronized (this) { + return super.remove(key); + } + } + + @Override + public void clear() { + synchronized (this) { + super.clear(); + } + } +} + + + + diff --git a/src/main/java/com/jfinal/template/Engine.java b/src/main/java/com/jfinal/template/Engine.java index 6d71ec7..96b8c3c 100644 --- a/src/main/java/com/jfinal/template/Engine.java +++ b/src/main/java/com/jfinal/template/Engine.java @@ -21,11 +21,13 @@ import java.util.HashMap; 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.MethodKit; import com.jfinal.template.source.ClassPathSourceFactory; import com.jfinal.template.source.ISource; import com.jfinal.template.source.ISourceFactory; import com.jfinal.template.source.StringSource; +import com.jfinal.template.stat.OutputDirectiveFactory; import com.jfinal.template.stat.Parser; import com.jfinal.template.stat.ast.Stat; @@ -41,7 +43,7 @@ public class Engine { public static final String MAIN_ENGINE_NAME = "main"; private static Engine MAIN_ENGINE; - private static Map engineMap = new HashMap(); + private static Map engineMap = new HashMap(64, 0.5F); // Create main engine static { @@ -54,7 +56,7 @@ public class Engine { private EngineConfig config = new EngineConfig(); private ISourceFactory sourceFactory = config.getSourceFactory(); - private Map templateCache = new HashMap(); + private Map templateCache = new SyncWriteMap(2048, 0.5F); /** * Create engine without management of JFinal @@ -276,7 +278,7 @@ public class Engine { /** * Set output directive factory */ - public Engine setOutputDirectiveFactory(IOutputDirectiveFactory outputDirectiveFactory) { + public Engine setOutputDirectiveFactory(OutputDirectiveFactory outputDirectiveFactory) { config.setOutputDirectiveFactory(outputDirectiveFactory); return this; } diff --git a/src/main/java/com/jfinal/template/EngineConfig.java b/src/main/java/com/jfinal/template/EngineConfig.java index 3469016..33fafe8 100644 --- a/src/main/java/com/jfinal/template/EngineConfig.java +++ b/src/main/java/com/jfinal/template/EngineConfig.java @@ -35,6 +35,7 @@ import com.jfinal.template.source.ISource; import com.jfinal.template.source.ISourceFactory; import com.jfinal.template.source.StringSource; import com.jfinal.template.stat.Location; +import com.jfinal.template.stat.OutputDirectiveFactory; import com.jfinal.template.stat.Parser; import com.jfinal.template.stat.ast.Define; import com.jfinal.template.stat.ast.Output; @@ -48,14 +49,14 @@ public class EngineConfig { WriterBuffer writerBuffer = new WriterBuffer(); - private Map sharedFunctionMap = new HashMap(); + private Map sharedFunctionMap = createSharedFunctionMap(); // new HashMap(512, 0.25F); private List sharedFunctionSourceList = new ArrayList(); // for devMode only Map sharedObjectMap = null; - private IOutputDirectiveFactory outputDirectiveFactory = OutputDirectiveFactory.me; + private OutputDirectiveFactory outputDirectiveFactory = OutputDirectiveFactory.me; private ISourceFactory sourceFactory = new FileSourceFactory(); - private Map> directiveMap = new HashMap>(); + private Map> directiveMap = new HashMap>(64, 0.5F); private SharedMethodKit sharedMethodKit = new SharedMethodKit(); private boolean devMode = false; @@ -179,7 +180,7 @@ public class EngineConfig { * 开发者可直接使用模板注释功能将不需要的 function 直接注释掉 */ private synchronized void reloadSharedFunctionSourceList() { - Map newMap = new HashMap(); + Map newMap = createSharedFunctionMap(); for (int i = 0, size = sharedFunctionSourceList.size(); i < size; i++) { ISource source = sharedFunctionSourceList.get(i); String fileName = source instanceof FileSource ? ((FileSource)source).getFileName() : null; @@ -194,9 +195,13 @@ public class EngineConfig { this.sharedFunctionMap = newMap; } + private Map createSharedFunctionMap() { + return new HashMap(512, 0.25F); + } + public synchronized void addSharedObject(String name, Object object) { if (sharedObjectMap == null) { - sharedObjectMap = new HashMap(); + sharedObjectMap = new HashMap(64, 0.25F); } else if (sharedObjectMap.containsKey(name)) { throw new IllegalArgumentException("Shared object already exists: " + name); } @@ -210,7 +215,7 @@ public class EngineConfig { /** * Set output directive factory */ - public void setOutputDirectiveFactory(IOutputDirectiveFactory outputDirectiveFactory) { + public void setOutputDirectiveFactory(OutputDirectiveFactory outputDirectiveFactory) { if (outputDirectiveFactory == null) { throw new IllegalArgumentException("outputDirectiveFactory can not be null"); } diff --git a/src/main/java/com/jfinal/template/Env.java b/src/main/java/com/jfinal/template/Env.java index bfea4cf..9d6e05d 100644 --- a/src/main/java/com/jfinal/template/Env.java +++ b/src/main/java/com/jfinal/template/Env.java @@ -35,7 +35,7 @@ import com.jfinal.template.stat.ast.Define; public class Env { protected EngineConfig engineConfig; - protected Map functionMap = new HashMap(); + protected Map functionMap = new HashMap(16, 0.5F); // 代替 Template 持有该属性,便于在 #include 指令中调用 Env.addSource() protected List sourceList = null; diff --git a/src/main/java/com/jfinal/template/OutputDirectiveFactory.java b/src/main/java/com/jfinal/template/OutputDirectiveFactory.java deleted file mode 100644 index aeff017..0000000 --- a/src/main/java/com/jfinal/template/OutputDirectiveFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 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; - -import com.jfinal.template.expr.ast.ExprList; -import com.jfinal.template.stat.Location; -import com.jfinal.template.stat.ast.Output; - -/** - * OutputDirectiveFactory - */ -public class OutputDirectiveFactory implements IOutputDirectiveFactory { - - public static final OutputDirectiveFactory me = new OutputDirectiveFactory(); - - public Output getOutputDirective(ExprList exprList, Location location) { - return new Output(exprList, location); - } -} - diff --git a/src/main/java/com/jfinal/template/expr/ast/FieldKit.java b/src/main/java/com/jfinal/template/expr/ast/FieldKit.java index de2b8e1..f233f38 100644 --- a/src/main/java/com/jfinal/template/expr/ast/FieldKit.java +++ b/src/main/java/com/jfinal/template/expr/ast/FieldKit.java @@ -18,13 +18,14 @@ package com.jfinal.template.expr.ast; import java.lang.reflect.Field; import java.util.HashMap; +import com.jfinal.kit.SyncWriteMap; /** * FieldKit */ public class FieldKit { - private static final HashMap fieldCache = new HashMap(); + private static final HashMap fieldCache = new SyncWriteMap(512, 0.5F); public static Field getField(Long key, Class targetClass, String fieldName) { Object field = fieldCache.get(key); diff --git a/src/main/java/com/jfinal/template/expr/ast/Method.java b/src/main/java/com/jfinal/template/expr/ast/Method.java index e145975..4c68e37 100644 --- a/src/main/java/com/jfinal/template/expr/ast/Method.java +++ b/src/main/java/com/jfinal/template/expr/ast/Method.java @@ -24,6 +24,17 @@ import com.jfinal.template.stat.Scope; /** * Method : expr '.' ID '(' exprList? ')' + * + * 每次通过 MethodKit.getMethod(...) 取 MethodInfo 而不是用属性持有其对象 + * 是为了支持 target 对象的动态类型,MethodInfo 中的 Method 被调用 15 次以后 + * 会被 JDK 动态生成 GeneratedAccessorXXX 字节码,性能不是问题 + * 唯一的性能损耗是从 HashMap 中获取 MethodInfo 对象,可以忽略不计 + * + * 如果在未来通过结合 #dynamic(boolean) 指令来优化,需要在 Ctrl 中引入一个 + * boolean dynamic = false 变量,而不能在 Env、Scope 引入该变量 + * + * 还需要引入一个 NullMethodInfo 以及 isNull() 方法,此优化复杂度提高不少, + * 暂时不做此优化 */ public class Method extends Expr { diff --git a/src/main/java/com/jfinal/template/expr/ast/MethodKit.java b/src/main/java/com/jfinal/template/expr/ast/MethodKit.java index c5dfd21..5286cc0 100644 --- a/src/main/java/com/jfinal/template/expr/ast/MethodKit.java +++ b/src/main/java/com/jfinal/template/expr/ast/MethodKit.java @@ -23,6 +23,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; import com.jfinal.kit.ReflectKit; +import com.jfinal.kit.SyncWriteMap; import com.jfinal.template.ext.extensionmethod.ByteExt; import com.jfinal.template.ext.extensionmethod.DoubleExt; import com.jfinal.template.ext.extensionmethod.FloatExt; @@ -37,10 +38,10 @@ import com.jfinal.template.ext.extensionmethod.StringExt; public class MethodKit { private static final Class[] NULL_ARG_TYPES = new Class[0]; - private static final Set forbiddenMethods = new HashSet(); - private static final Set> forbiddenClasses = new HashSet>(); - private static final Map, Class> primitiveMap = new HashMap, Class>(); - private static final HashMap methodCache = new HashMap(); + private static final Set forbiddenMethods = new HashSet(64); + private static final Set> forbiddenClasses = new HashSet>(64); + private static final Map, Class> primitiveMap = new HashMap, Class>(64); + private static final Map methodCache = new SyncWriteMap(2048, 0.25F); // 初始化在模板中调用 method 时所在的被禁止使用类 static { @@ -307,7 +308,7 @@ public class MethodKit { } } - private static final Map, Class> primitiveToBoxedMap = new HashMap, Class>(); + private static final Map, Class> primitiveToBoxedMap = new HashMap, Class>(64); // 初始化 primitive type 到 boxed type 的映射 static { diff --git a/src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java b/src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java index 8fccaea..d853c76 100644 --- a/src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java +++ b/src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java @@ -27,6 +27,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import com.jfinal.kit.HashKit; import com.jfinal.kit.ReflectKit; +import com.jfinal.kit.SyncWriteMap; /** * SharedMethodKit @@ -44,7 +45,7 @@ public class SharedMethodKit { } private final List sharedMethodList = new ArrayList(); - private final HashMap methodCache = new HashMap(); + private final HashMap methodCache = new SyncWriteMap(512, 0.25F); public SharedMethodInfo getSharedMethodInfo(String methodName, Object[] argValues) { Class[] argTypes = MethodKit.getArgTypes(argValues); diff --git a/src/main/java/com/jfinal/template/ext/directive/DateDirective.java b/src/main/java/com/jfinal/template/ext/directive/DateDirective.java index b578730..d1709ca 100644 --- a/src/main/java/com/jfinal/template/ext/directive/DateDirective.java +++ b/src/main/java/com/jfinal/template/ext/directive/DateDirective.java @@ -79,23 +79,25 @@ public class DateDirective extends Directive { private void outputWithoutDatePattern(Env env, Scope scope, Writer writer) { Object value = valueExpr.eval(scope); - if (value != null) { + if (value instanceof Date) { write(writer, (Date)value, env.getEngineConfig().getDatePattern()); + } else if (value != null) { + throw new TemplateException("The first parameter date of #date directive must be Date type", location); } } private void outputWithDatePattern(Env env, Scope scope, Writer writer) { Object value = valueExpr.eval(scope); - if (value == null) { - return ; + if (value instanceof Date) { + Object datePattern = this.datePatternExpr.eval(scope); + if (datePattern instanceof String) { + write(writer, (Date)value, (String)datePattern); + } else { + throw new TemplateException("The sencond parameter datePattern of #date directive must be String", location); + } + } else if (value != null) { + throw new TemplateException("The first parameter date of #date directive must be Date type", location); } - - Object datePattern = this.datePatternExpr.eval(scope); - if ( !(datePattern instanceof String) ) { - throw new TemplateException("The sencond parameter datePattern of #date directive must be String", location); - } - - write(writer, (Date)value, (String)datePattern); } private void write(Writer writer, Date date, String datePattern) { diff --git a/src/main/java/com/jfinal/template/ext/directive/EscapeDirective.java b/src/main/java/com/jfinal/template/ext/directive/EscapeDirective.java index f57344b..1940d98 100644 --- a/src/main/java/com/jfinal/template/ext/directive/EscapeDirective.java +++ b/src/main/java/com/jfinal/template/ext/directive/EscapeDirective.java @@ -52,11 +52,12 @@ public class EscapeDirective extends Directive { case '>': ret.append(">"); break; - case '\"': + case '"': ret.append("""); break; case '\'': - ret.append("'"); // IE 不支持 ' 考虑 ' + // ret.append("'"); // IE 不支持 ' 考虑 ' + ret.append("'"); break; case '&': ret.append("&"); diff --git a/src/main/java/com/jfinal/template/ext/directive/RenderDirective.java b/src/main/java/com/jfinal/template/ext/directive/RenderDirective.java index dfe2e16..c80dc79 100644 --- a/src/main/java/com/jfinal/template/ext/directive/RenderDirective.java +++ b/src/main/java/com/jfinal/template/ext/directive/RenderDirective.java @@ -16,8 +16,8 @@ package com.jfinal.template.ext.directive; -import java.util.HashMap; import java.util.Map; +import com.jfinal.kit.SyncWriteMap; import com.jfinal.template.Directive; import com.jfinal.template.EngineConfig; import com.jfinal.template.Env; @@ -60,7 +60,7 @@ import com.jfinal.template.stat.ast.StatList; public class RenderDirective extends Directive { private String parentFileName; - private Map statInfoCache = new HashMap(); + private Map statInfoCache = new SyncWriteMap(16, 0.5F); public void setExprList(ExprList exprList) { int len = exprList.length(); diff --git a/src/main/java/com/jfinal/template/ext/spring/JFinalView.java b/src/main/java/com/jfinal/template/ext/spring/JFinalView.java index 9162fab..23d57d3 100644 --- a/src/main/java/com/jfinal/template/ext/spring/JFinalView.java +++ b/src/main/java/com/jfinal/template/ext/spring/JFinalView.java @@ -16,6 +16,7 @@ package com.jfinal.template.ext.spring; +import java.io.IOException; import java.io.OutputStream; import java.util.Enumeration; import java.util.HashMap; @@ -56,8 +57,20 @@ public class JFinalView extends AbstractTemplateView { } } - OutputStream os = response.getOutputStream(); - JFinalViewResolver.engine.getTemplate(getUrl()).render(model, os); + try { + OutputStream os = response.getOutputStream(); + JFinalViewResolver.engine.getTemplate(getUrl()).render(model, os); + } catch (Exception e) { // 捕获 ByteWriter.close() 抛出的 RuntimeException + Throwable cause = e.getCause(); + if (cause instanceof IOException) { // ClientAbortException、EofException 直接或间接继承自 IOException + String name = cause.getClass().getSimpleName(); + if ("ClientAbortException".equals(name) || "EofException".equals(name)) { + return ; + } + } + + throw e; + } } @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) diff --git a/src/main/java/com/jfinal/template/source/ClassPathSource.java b/src/main/java/com/jfinal/template/source/ClassPathSource.java index dd981fd..87c11cd 100644 --- a/src/main/java/com/jfinal/template/source/ClassPathSource.java +++ b/src/main/java/com/jfinal/template/source/ClassPathSource.java @@ -17,12 +17,11 @@ package com.jfinal.template.source; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.net.JarURLConnection; import java.net.URL; -import java.net.URLConnection; import com.jfinal.template.EngineConfig; /** @@ -72,17 +71,12 @@ public class ClassPathSource implements ISource { } protected void processIsInJarAndlastModified() { - try { - URLConnection conn = url.openConnection(); - if ("jar".equals(url.getProtocol()) || conn instanceof JarURLConnection) { - isInJar = true; - lastModified = -1; - } else { - isInJar = false; - lastModified = conn.getLastModified(); - } - } catch (IOException e) { - throw new RuntimeException(e); + if ("file".equalsIgnoreCase(url.getProtocol())) { + isInJar = false; + lastModified = new File(url.getFile()).lastModified(); + } else { + isInJar = true; + lastModified = -1; } } @@ -120,12 +114,7 @@ public class ClassPathSource implements ISource { } protected long getLastModified() { - try { - URLConnection conn = url.openConnection(); - return conn.getLastModified(); - } catch (IOException e) { - throw new RuntimeException(e); - } + return new File(url.getFile()).lastModified(); } /** diff --git a/src/main/java/com/jfinal/template/IOutputDirectiveFactory.java b/src/main/java/com/jfinal/template/stat/OutputDirectiveFactory.java similarity index 73% rename from src/main/java/com/jfinal/template/IOutputDirectiveFactory.java rename to src/main/java/com/jfinal/template/stat/OutputDirectiveFactory.java index 0dae7bd..6f1dda5 100644 --- a/src/main/java/com/jfinal/template/IOutputDirectiveFactory.java +++ b/src/main/java/com/jfinal/template/stat/OutputDirectiveFactory.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package com.jfinal.template; +package com.jfinal.template.stat; import com.jfinal.template.expr.ast.ExprList; -import com.jfinal.template.stat.Location; import com.jfinal.template.stat.ast.Output; /** - * IOutputDirectiveFactory + * OutputDirectiveFactory * 用于定制自定义输出指令,替换系统默认输出指令,满足个性化需求 * * 用法: @@ -32,12 +31,15 @@ import com.jfinal.template.stat.ast.Output; * } * * public void exec(Env env, Scope scope, Writer writer) { - * write(writer, exprList.eval(scope)); + * Object value = exprList.eval(scope); + * if (value != null) { + * write(writer, value.toString()); + * } * } * } * * 2:定义 MyOutputDirectiveFactory - * public class MyOutputDirectiveFactory implements IOutputDirectiveFactory { + * public class MyOutputDirectiveFactory extends OutputDirectiveFactory { * public Output getOutputDirective(ExprList exprList) { * return new MyOutput(exprList); * } @@ -46,11 +48,13 @@ import com.jfinal.template.stat.ast.Output; * 3:配置 * engine.setOutputDirectiveFactory(new MyOutputDirectiveFactory()) */ -public interface IOutputDirectiveFactory { +public class OutputDirectiveFactory { - public Output getOutputDirective(ExprList exprList, Location location); + public static final OutputDirectiveFactory me = new OutputDirectiveFactory(); + public Output getOutputDirective(ExprList exprList, Location location) { + return new Output(exprList, location); + } } - diff --git a/src/main/java/com/jfinal/template/stat/Symbol.java b/src/main/java/com/jfinal/template/stat/Symbol.java index a3b0ce2..3228cc1 100644 --- a/src/main/java/com/jfinal/template/stat/Symbol.java +++ b/src/main/java/com/jfinal/template/stat/Symbol.java @@ -58,7 +58,7 @@ enum Symbol { * 扩展指令在得到 # id ( 序列以后才要求得到正确的后续 Token 序列,否则仅仅 return fail() */ @SuppressWarnings("serial") - private static final Map keywords = new HashMap() {{ + private static final Map keywords = new HashMap(64) {{ put(Symbol.IF.getName(), IF); put(Symbol.ELSEIF.getName(), ELSEIF); put(Symbol.ELSE.getName(), ELSE);