JFinal

登录 注册
  • i++
    以下为自定义FileRender代码。只能重写整个类。就为了不打印个异常。建议下个版本把这个异常去掉。


    import java.io.BufferedInputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;

    import javax.servlet.ServletContext;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import com.jfinal.kit.LogKit;
    import com.jfinal.kit.PathKit;
    import com.jfinal.kit.StrKit;
    import com.jfinal.render.Render;
    import com.jfinal.render.RenderException;
    import com.jfinal.render.RenderManager;


    /**
    * 描述:自定义FileRender.为了不打印org.apache.catalina.connector.ClientAbortException: 远程主机强迫关闭了一个现有的连接。
    * @author Robin Zhang
    * @created 2017年10月19日 下午6:00:02
    */
    public class FileRender extends Render {

    private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";

    private File file;
    private static String baseDownloadPath;
    private static ServletContext servletContext;
    private String downloadFileName = null;

    public FileRender(File file) {
    if (file == null) {
    throw new IllegalArgumentException("file can not be null.");
    }
    this.file = file;
    init();
    }

    public FileRender(File file, String downloadFileName) {
    this(file);

    if (StrKit.isBlank(downloadFileName)) {
    throw new IllegalArgumentException("downloadFileName can not be blank.");
    }
    this.downloadFileName = downloadFileName;
    init();
    }

    public FileRender(String fileName) {
    if (StrKit.isBlank(fileName)) {
    throw new IllegalArgumentException("fileName can not be blank.");
    }

    String fullFileName;
    fileName = fileName.trim();
    if (fileName.startsWith("/") || fileName.startsWith("\\")) {
    if (baseDownloadPath.equals("/")) {
    fullFileName = fileName;
    } else {
    fullFileName = baseDownloadPath + fileName;
    }
    } else {
    fullFileName = baseDownloadPath + File.separator + fileName;
    }

    this.file = new File(fullFileName);
    init();
    }

    public FileRender(String fileName, String downloadFileName) {
    this(fileName);

    if (StrKit.isBlank(downloadFileName)) {
    throw new IllegalArgumentException("downloadFileName can not be blank.");
    }
    this.downloadFileName = downloadFileName;
    init();
    }

    private void init() {
    String downloadPath = RenderManager.me().getConstants().getBaseDownloadPath();
    downloadPath = downloadPath.trim();
    downloadPath = downloadPath.replaceAll("\\\\", "/");

    String baseDownloadPath;
    // 如果为绝对路径则直接使用,否则把 downloadPath 参数作为项目根路径的相对路径
    if (PathKit.isAbsolutelyPath(downloadPath)) {
    baseDownloadPath = downloadPath;
    } else {
    baseDownloadPath = PathKit.getWebRootPath() + File.separator + downloadPath;
    }

    // remove "/" postfix
    if (baseDownloadPath.equals("/") == false) {
    if (baseDownloadPath.endsWith("/")) {
    baseDownloadPath = baseDownloadPath.substring(0, baseDownloadPath.length() - 1);
    }
    }
    FileRender.baseDownloadPath = baseDownloadPath;
    FileRender.servletContext = RenderManager.me().getServletContext();
    }

    public void render() {
    if (file == null || !file.isFile()) {
    RenderManager.me().getRenderFactory().getErrorRender(404).setContext(request, response).render();
    return;
    }

    // ---------
    response.setHeader("Accept-Ranges", "bytes");
    String fn = downloadFileName == null ? file.getName() : downloadFileName;
    response.setHeader("Content-disposition", "attachment; " + encodeFileName(request, fn));
    String contentType = servletContext.getMimeType(file.getName());
    response.setContentType(contentType != null ? contentType : DEFAULT_CONTENT_TYPE);

    // ---------
    if (StrKit.isBlank(request.getHeader("Range"))) {
    normalRender();
    } else {
    rangeRender();
    }
    }

    protected String encodeFileName(String fileName) {
    try {
    // return new String(fileName.getBytes("GBK"), "ISO8859-1");
    return new String(fileName.getBytes(getEncoding()), "ISO8859-1");
    } catch (UnsupportedEncodingException e) {
    return fileName;
    }
    }

    /**
    * 依据浏览器判断编码规则
    */
    public String encodeFileName(HttpServletRequest request, String fileName) {
    String userAgent = request.getHeader("User-Agent");
    try {
    String encodedFileName = URLEncoder.encode(fileName, "UTF8");
    // 如果没有UA,则默认使用IE的方式进行编码
    if (userAgent == null) {
    return "filename=\"" + encodedFileName + "\"";
    }

    userAgent = userAgent.toLowerCase();
    // IE浏览器,只能采用URLEncoder编码
    if (userAgent.indexOf("msie") != -1) {
    return "filename=\"" + encodedFileName + "\"";
    }

    // Opera浏览器只能采用filename*
    if (userAgent.indexOf("opera") != -1) {
    return "filename*=UTF-8''" + encodedFileName;
    }

    // Safari浏览器,只能采用ISO编码的中文输出,Chrome浏览器,只能采用MimeUtility编码或ISO编码的中文输出
    if (userAgent.indexOf("safari") != -1 || userAgent.indexOf("applewebkit") != -1
    || userAgent.indexOf("chrome") != -1) {
    return "filename=\"" + new String(fileName.getBytes("UTF-8"), "ISO8859-1") + "\"";
    }

    // FireFox浏览器,可以使用MimeUtility或filename*或ISO编码的中文输出
    if (userAgent.indexOf("mozilla") != -1) {
    return "filename*=UTF-8''" + encodedFileName;
    }

    return "filename=\"" + encodedFileName + "\"";
    } catch (UnsupportedEncodingException e) {
    throw new RuntimeException(e);
    }
    }

    private void normalRender() {
    response.setHeader("Content-Length", String.valueOf(file.length()));
    InputStream inputStream = null;
    OutputStream outputStream = null;
    try {
    inputStream = new BufferedInputStream(new FileInputStream(file));
    outputStream = response.getOutputStream();
    byte[] buffer = new byte[1024];
    for (int len = -1; (len = inputStream.read(buffer)) != -1;) {
    outputStream.write(buffer, 0, len);
    }
    outputStream.flush();
    } catch (IOException e) {
    if (getDevMode()) {
    throw new RenderException(e);
    }
    } catch (Exception e) {
    throw new RenderException(e);
    } finally {
    if (inputStream != null)
    try {
    inputStream.close();
    } catch (IOException e) {
    LogKit.error(e.getMessage(), e);
    }
    if (outputStream != null)
    try {
    outputStream.close();
    } catch (IOException e) {
    if (e.getClass().getName().equals("org.apache.catalina.connector.ClientAbortException")) {
    // LogKit.error(e.getMessage());
    } else {
    LogKit.error(e.getMessage(), e);
    }
    }
    }
    }

    private void rangeRender() {
    Long[] range = { null, null };
    processRange(range);

    String contentLength = String.valueOf(range[1].longValue() - range[0].longValue() + 1);
    response.setHeader("Content-Length", contentLength);
    response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // status =
    // 206

    // Content-Range: bytes 0-499/10000
    StringBuilder contentRange = new StringBuilder("bytes ").append(String.valueOf(range[0])).append("-")
    .append(String.valueOf(range[1])).append("/").append(String.valueOf(file.length()));
    response.setHeader("Content-Range", contentRange.toString());

    InputStream inputStream = null;
    OutputStream outputStream = null;
    try {
    long start = range[0];
    long end = range[1];
    inputStream = new BufferedInputStream(new FileInputStream(file));
    if (inputStream.skip(start) != start)
    throw new RuntimeException("File skip error");
    outputStream = response.getOutputStream();
    byte[] buffer = new byte[1024];
    long position = start;
    for (int len; position <= end && (len = inputStream.read(buffer)) != -1;) {
    if (position + len <= end) {
    outputStream.write(buffer, 0, len);
    position += len;
    } else {
    for (int i = 0; i < len && position <= end; i++) {
    outputStream.write(buffer[i]);
    position++;
    }
    }
    }
    outputStream.flush();
    } catch (IOException e) {
    if (getDevMode())
    throw new RenderException(e);
    } catch (Exception e) {
    throw new RenderException(e);
    } finally {
    if (inputStream != null)
    try {
    inputStream.close();
    } catch (IOException e) {
    LogKit.error(e.getMessage(), e);
    }
    if (outputStream != null)
    try {
    outputStream.close();
    } catch (IOException e) {
    if (e.getClass().getName().equals("org.apache.catalina.connector.ClientAbortException")) {
    // LogKit.error(e.getMessage());
    } else {
    LogKit.error(e.getMessage(), e);
    }
    }
    }
    }

    /**
    * Examples of byte-ranges-specifier values (assuming an entity-body of
    * length 10000): The first 500 bytes (byte offsets 0-499, inclusive):
    * bytes=0-499 The second 500 bytes (byte offsets 500-999, inclusive):
    * bytes=500-999 The final 500 bytes (byte offsets 9500-9999, inclusive):
    * bytes=-500 Or bytes=9500-
    */
    private void processRange(Long[] range) {
    String rangeStr = request.getHeader("Range");
    int index = rangeStr.indexOf(',');
    if (index != -1)
    rangeStr = rangeStr.substring(0, index);
    rangeStr = rangeStr.replace("bytes=", "");

    String[] arr = rangeStr.split("-", 2);
    if (arr.length < 2)
    throw new RuntimeException("Range error");

    long fileLength = file.length();
    for (int i = 0; i < range.length; i++) {
    if (StrKit.notBlank(arr[i])) {
    range[i] = Long.parseLong(arr[i].trim());
    if (range[i] >= fileLength)
    range[i] = fileLength - 1;
    }
    }

    // Range format like: 9500-
    if (range[0] != null && range[1] == null) {
    range[1] = fileLength - 1;
    }
    // Range format like: -500
    else if (range[0] == null && range[1] != null) {
    range[0] = fileLength - range[1];
    range[1] = fileLength - 1;
    }

    // check final range
    if (range[0] == null || range[1] == null || range[0].longValue() > range[1].longValue())
    throw new RuntimeException("Range error");
    }
    }
    10-19 18:08 回复
  • i++
    @JFinal
    FileRender中的代码:

    } finally {
    if (inputStream != null)
    try {inputStream.close();} catch (IOException e) {LogKit.error(e.getMessage(), e);}
    if (outputStream != null)
    try {outputStream.close();} catch (IOException e) {LogKit.error(e.getMessage(), e);}
    }

    186行使用LogKit.error打印了,没有向外面抛,所以在自定义FileRender中无法try做处理。我试过自己实现super.render()功能。但。。。servletContext为null.因为RenderManager中写死的FileRender对象,所以要在自己的FileRender去取servletContext...
    10-19 18:02 回复
  • i++
    @JFinal 其实我自己不是用SqlKit.SQL_PARA_KEY 我自己用自己的常量了。只是发出来时改成这个了。没注意看这个不是public的。我改下
    08-26 16:31 回复
  • i++
    @JFinal 今天看了3.2这部份源码。我并不认同这种把问题抛给使用者以自选的方式处理。原因
    1.没有几个使用者会认真区分这两个方法的真正区别,大部份人只知道做下表面的试验,能用就好了。而这个问题在平时的开发使用过程中是很难发现问题的。而当应用服务出现问题时他们的第一反应是:jfinal这个框架不行。谨慎点的开发者看到了注释为了安全起见,他们往往会默认选择不缓存。不缓存性能相对就差点。
    2.模板的主要特点就是可重用性,我是真想不到有什么业务场景要用到模板但只执行一次的。既然要使用多次,那么进行缓存肯定是比较好的。所以做为应用的底层默认是要缓存的。
    3.这个问题的根源是在缓存方法中默认使用content的md5当key,做为一个开发框架的底层不应该使用这种有局限性的实现方法。所以要让方法通用,应该让使用者自己设置缓存的key。提供默认不缓存engine.getTemplateByString(content) 和默认缓存engine.getTemplateByString(key,content)两个方法。再提供engine.setTemplateCacheMaxSize()设置最大缓存条数,实现当缓存达到最大值时自动清除。或用设置有效期的方式,在jfinal框架本身把这个问题解决掉。而不是让使用者自己选择只能缓存某一类的数据,这样的约定在开发过程中的业务层是很常见,但在开发的框架底层使用这样的约定就会影响到使用业务的范围。
    08-14 15:37 回复
  • i++
    @JFinal 按你说的改了。但是我不是用value做判断,而是去判断有没有参传。所以参会值还是可以传null的,不会有你说的那两个问题。代码如下
    if (index == -1) {
    Expr[] exprArray = exprList.getExprArray();
    Object ret = null;
    for (Expr expr : exprArray) {
    boolean hasPara = scope.getData().containsKey(expr.toString());
    if (hasPara) {
    ret = expr.eval(scope);
    } else {//没有传参数抛异常
    throw new TemplateException(
    "The parameter '"+expr.toString()+"' must be assigned",
    location);
    }
    }
    sqlPara.addPara(ret);
    // sqlPara.addPara(exprList.eval(scope));
    }

    有没有传参和传的是不是null值是两码事
    08-12 16:49 回复
  • i++
    我觉得jfinal在赋值sqlPara时,发现sql语句是的?没有对应的参数时,应该按正常的业务思维逻辑抛出参数缺失的异常,因为是通用接口,所以我不好在接口中对参数进行校验。也不能在sqltemplate中写#if()判断。@jfinal
    08-12 11:36 回复
  • i++
    @jfinal 不知道这么扩展行不行?帮我看下。因为templateCache 是私有的,在子类里也是无法用的。只能在子类里自己维护一个mainTemplateCache 。所以所有template有关的都要重写。

    import java.util.HashMap;
    import java.util.Map;

    import com.jfinal.kit.HashKit;
    import com.jfinal.log.Log;
    import com.jfinal.template.Engine;
    import com.jfinal.template.EngineConfig;
    import com.jfinal.template.Env;
    import com.jfinal.template.FileStringSource;
    import com.jfinal.template.IStringSource;
    import com.jfinal.template.MemoryStringSource;
    import com.jfinal.template.Template;
    import com.jfinal.template.stat.Parser;
    import com.jfinal.template.stat.ast.Stat;


    /**
    * 描述:自定义Engine
    * 优化mainTemplateCache,防止mainTemplateCache内存占用过大。
    * @author Robin Zhang
    * @created 2017年7月6日 下午5:18:50
    */
    public class MainEngine extends Engine {

    /**
    * 描述:
    */
    private static Log log = Log.getLog(MainEngine.class);

    /**
    * 描述:模板缓存
    */
    private static Map mainTemplateCache = new HashMap();

    static {
    Runnable runnable = new Runnable() {
    public void run() {
    while (true) {
    try {
    int len = mainTemplateCache.size();
    if(len>200){
    mainTemplateCache.clear();
    log.debug("MainEngine.mainTemplateCache的模板缓存大于200已被清理。");
    } else {
    try {
    Thread.sleep(60000l);
    } catch (InterruptedException e) {
    log.error("MainEngine.mainTemplateCache.Thread.sleep()出错:",e);
    }
    }
    } catch (Exception e) {
    log.error("MainEngine.mainTemplateCache.保存访问记录缓存到数据库定时任务出错",e);
    try {
    Thread.sleep(60000l);
    } catch (InterruptedException e1) {
    log.error("MainEngine.mainTemplateCache.Thread.sleep()出错:",e);
    }
    }
    }
    }
    };
    Thread thread = new Thread(runnable);
    thread.start();
    }

    /**
    * 描述:Get template with file name
    * @author Robin Zhang
    * @created 2017年7月6日 下午5:53:25
    * @param fileName
    * @return
    * @see com.jfinal.template.Engine#getTemplate(java.lang.String)
    */
    public Template getTemplate(String fileName) {
    if (fileName.charAt(0) != '/') {
    char[] arr = new char[fileName.length() + 1];
    fileName.getChars(0, fileName.length(), arr, 1);
    arr[0] = '/';
    fileName = new String(arr);
    }

    Template template = mainTemplateCache.get(fileName);
    if (template == null) {
    template = buildTemplateByFileStringSource(fileName);
    mainTemplateCache.put(fileName, template);
    } else if (getDevMode()) {
    if (template.isModified()) {
    template = buildTemplateByFileStringSource(fileName);
    mainTemplateCache.put(fileName, template);
    }
    }
    return template;
    }

    /**
    * 描述:
    * @author Robin Zhang
    * @created 2017年7月6日 下午5:54:23
    * @param fileName
    * @return
    */
    private Template buildTemplateByFileStringSource(String fileName) {
    EngineConfig config = getEngineConfig();
    FileStringSource fileStringSource = new FileStringSource(config.getBaseTemplatePath(), fileName, config.getEncoding());
    Env env = new Env(config);
    Parser parser = new Parser(env, fileStringSource.getContent(), fileName);
    if (getDevMode()) {
    env.addStringSource(fileStringSource);
    }
    Stat stat = parser.parse();
    Template template = new Template(env, stat);
    return template;
    }


    /**
    * 描述:Get template by string content
    * @author Robin Zhang
    * @created 2017年7月6日 下午5:54:34
    * @param content
    * @return
    * @see com.jfinal.template.Engine#getTemplateByString(java.lang.String)
    */
    public Template getTemplateByString(String content) {
    String key = HashKit.md5(content);
    Template template = mainTemplateCache.get(key);
    if (template == null) {
    template = buildTemplateByStringSource(new MemoryStringSource(content));
    mainTemplateCache.put(key, template);
    } else if (getDevMode()) {
    if (template.isModified()) {
    template = buildTemplateByStringSource(new MemoryStringSource(content));
    mainTemplateCache.put(key, template);
    }
    }
    return template;
    }

    /**
    * 描述:Get template with implementation of IStringSource
    * @author Robin Zhang
    * @created 2017年7月6日 下午5:54:43
    * @param stringSource
    * @return
    * @see com.jfinal.template.Engine#getTemplate(com.jfinal.template.IStringSource)
    */
    public Template getTemplate(IStringSource stringSource) {
    String key = stringSource.getKey();
    Template template = mainTemplateCache.get(key);
    if (template == null) {
    template = buildTemplateByStringSource(stringSource);
    mainTemplateCache.put(key, template);
    } else if (getDevMode()) {
    if (template.isModified()) {
    template = buildTemplateByStringSource(stringSource);
    mainTemplateCache.put(key, template);
    }
    }
    return template;
    }

    /**
    * 描述:
    * @author Robin Zhang
    * @created 2017年7月6日 下午5:54:53
    * @param stringSource
    * @return
    */
    private Template buildTemplateByStringSource(IStringSource stringSource) {
    EngineConfig config = getEngineConfig();
    Env env = new Env(config);
    Parser parser = new Parser(env, stringSource.getContent(), null);
    if (getDevMode()) {
    env.addStringSource(stringSource);
    }
    Stat stat = parser.parse();
    Template template = new Template(env, stat);
    return template;
    }

    /**
    * 描述:
    * @author Robin Zhang
    * @created 2017年7月6日 下午5:06:26
    * @param templateKey
    * @see com.jfinal.template.Engine#removeTemplateCache(java.lang.String)
    */
    @Override
    public void removeTemplateCache(String templateKey) {
    super.removeTemplateCache(templateKey);
    mainTemplateCache.remove(templateKey);
    }

    /**
    * 描述:
    * @author Robin Zhang
    * @created 2017年7月6日 下午5:06:26
    * @see com.jfinal.template.Engine#removeAllTemplateCache()
    */
    @Override
    public void removeAllTemplateCache() {
    super.removeAllTemplateCache();
    mainTemplateCache.clear();
    }

    /**
    * 描述:获取模板缓存大小
    * @author Robin Zhang
    * @created 2017年7月6日 下午5:18:03
    * @return
    */
    public int getTemplateCacheSize() {
    return mainTemplateCache.size();
    }
    }





    config.java中注册mainEngine
    @Override
    public void configEngine(Engine engine) {
    Engine.setMainEngine(new MainEngine());
    }
    07-06 19:56 回复
  • i++
    @JFinal String 型 Template 做缓存还是很有必要的。建议别去掉。即使String 型的不做templateCache。只有文件类型的做templateCache,templateCache也会一直增大的。因为文件(html)模板会重命名,会删除,而且文件模板不会太小。要是怕做先进先出的缓存影响性能,至少做个最大量的限制,超过了就全部删除,总比让他无限增长,内存用尽好。这个最大量让使用者可以自己根据服务器内存情况设置。
    06-23 15:50 回复
  • i++
    @JFinal 手动 remove cache时需要知道key。但我们并不知道无用的key是什么。请问有办法获取templateCache的大小吗。当templateCache等于一定量时,我们用removeAllTemplateCache()也行啊。
    06-23 15:35 回复
  • i++
    @jfinal
    3.0版,这个问题还是没改。这个框架没有做单元测试吧。把DbPro.java中的两处:
    int index = 0;
    // the same as the iterator in Dialect.forModelSave() to ensure the order of the attrs
    for (Entry e: attrs.entrySet())
    attrNames[index++] = e.getKey();
    String columns = StrKit.join(attrNames, ",");
    代码改成:
    StringBuffer columns = new StringBuffer();
    for (Entry e : attrs.entrySet()){
    String key = e.getKey();
    if (attrs.get(key) instanceof String && config.dialect.isOracle() && ((String)attrs.get(key)).endsWith(".nextval")) {
    } else {
    columns.append(key+",");
    }
    }
    if (columns.length()>1) {
    columns = columns.deleteCharAt(columns.length()-1);
    }

    就可以了。
    03-20 19:23 回复