转载

每日一博 | JFinal 极速开发框架的优点和不足的地方

写这篇简短的博文,并不是要故意贬低开源项目,而且我没必要这么做,因为本人并没有写这一类的开源框架,不会形成竞争。

写这篇文章,我是从个人的真实感受去写的。本人敢说,JFinal框架还是一个不错的MVC框架,而且ORM和支持多种数据库,相比Servlet那可配置简单、功能强大的多的去了,比如JDBC操作、事务支持、ORM、AOP、JSON等。

先说JFinal的一些优点吧:

  • 一、配置简单、容易上手:

首先建立一个Maven Web项目:

Maven pom.xml加入JFinal包、数据库连接池等:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.rocbin.jfinal</groupId>
  <artifactId>jfinal-demo</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>jfinal-demo Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
    </dependency>
    <dependency>
      <groupId>com.jfinal</groupId>
      <artifactId>jfinal</artifactId>
      <version>2.0</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.1.2</version>
    </dependency>
    <dependency>
      <groupId>net.sourceforge.jtds</groupId>
      <artifactId>jtds</artifactId>
      <version>1.3.1</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>com.microsoft.sqlserver</groupId>
      <artifactId>sqljdbc4</artifactId>
      <version>4.0</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>jfinal-demo</finalName>
  </build>
</project>
  • 二、建立个包包,写几个Java代码吧:

建一个包:com.rocbin.jfinal

然后建立一个包config,写一个集成JFinalConfig的配置类:

package com.rocbin.jfinal.config;

import com.jfinal.config.*;
import com.jfinal.handler.Handler;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.activerecord.CaseInsensitiveContainerFactory;
import com.jfinal.plugin.activerecord.dialect.AnsiSqlDialect;
import com.jfinal.plugin.c3p0.C3p0Plugin;
import com.jfinal.render.ViewType;
import com.rocbin.jfinal.controllers.IndexController;
import com.rocbin.jfinal.models.SystemUser;

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

/**
 * JFinal web config
 * <p>
 * Created by Rocbin on 2016/12/22.
 */
public class WebSystemConfig extends JFinalConfig {
    // 为什么我要把jtds和microsoft的jdbc驱动都引入来呢?
    // 因为 ActiveRecordPlugin中 insert有一个很操蛋的java.util.Date转换的问题

    private static final String JDBC_URL_JTDS = "jdbc:jtds:sqlserver://192.168.1.109/demo_db";
    private static final String JDBC_URL_MS = "jdbc:sqlserver://192.168.1.109;DatabaseName=demo_db";
    private static final String DRIVER_JTDS = "net.sourceforge.jtds.jdbc.Driver";
    private static final String DRIVER_MS = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
    private static final String JDBC_USERNAME = "sa";
    private static final java.lang.String JDBC_PASSWORD = "sa@123";

    @Override
    public void configConstant(Constants me) {
        me.setDevMode(true);
        me.setEncoding("utf-8");
        me.setViewType(ViewType.JSP);
    }

    @Override
    public void configRoute(Routes me) {
        me.add("/", IndexController.class);
    }

    @Override
    public void configPlugin(Plugins me) {
        C3p0Plugin c3p0Plugin = new C3p0Plugin(JDBC_URL_MS, JDBC_USERNAME, JDBC_PASSWORD);
        c3p0Plugin.setDriverClass(DRIVER_MS);
        c3p0Plugin.setInitialPoolSize(10);
        me.add(c3p0Plugin);
        ActiveRecordPlugin activeRecordPlugin = new ActiveRecordPlugin(c3p0Plugin);
        activeRecordPlugin.setContainerFactory(new CaseInsensitiveContainerFactory(true));
        activeRecordPlugin.setDialect(new AnsiSqlDialect());
        activeRecordPlugin.setShowSql(true);
        configTableMapping(activeRecordPlugin);
        me.add(activeRecordPlugin);
    }

    private void configTableMapping(ActiveRecordPlugin arp) {
        arp.addMapping("SystemUser", SystemUser.class);
    }

    @Override
    public void configInterceptor(Interceptors me) {

    }

    @Override
    public void configHandler(Handlers me) {
        me.add(new Handler() {
            @Override
            public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
                request.setAttribute("ctx", request.getContextPath());
                nextHandler.handle(target, request, response, isHandled);
            }
        });
    }
}

configConstant方法是必须要写一点东西的,基本上也没什么,它可以配置视图设置、字符编码、是否开发模式等几个。

configPlugin 方法呢主要配置JFinal内置的插件和我们自己的插件,让这些插件跟随系统启动和停止。

configRoute 是必须的,他做的事情就是将你的Controller 配置映射到某些你要的URL上面。

configInterceptor 呢,这个要看你怎么用了,比如你可以用这个来实现Shiro拦截URL请求权限等。

configHandler呢,你可以做更多的事情了,这可以拦截一切请求,包括不被JFinal Controller处理的请求类型,如果你希望使用ContextPath这个变量,而你希望他的名字是 ctx ,你希望在jsp页面里面使用 ${ctx} 来输出web上下文路径,就可以自定义一个Handler处理来实现的。

然后我们来建立一个Model吧,我们要做的是简单的用户注册、用户列表和用户中心。

建一个包models,建立一个SystemUser的类:

package com.rocbin.jfinal.models;

import com.jfinal.plugin.activerecord.Model;

import java.util.Date;
import java.util.List;

/**
 * System User model
 * <p>
 * Created by Rocbin on 2016/12/22.
 */
public class SystemUser extends Model<SystemUser> {

    public static final SystemUser dao = new SystemUser();

    //~~~~~~~~~~~~~ 字段 ~~~~~~~~~~~~~~

//    int id;
//    String name;
//    Integer age;
//    String profile;
//    Date createDate;

    //~~~~~~~~~~~~~ DAO 方法 ~~~~~~~~~~~

    public List<SystemUser> list() {
        return dao.find("SELECT * FROM SystemUser");
    }

    public SystemUser getById(int id) {
        return findById(id);
    }

    //~~~~~~~~~~~ Get Set ~~~~~~~~~~~

    public int getId() {
        return get("Id");
    }

    public SystemUser setId(int Id) {
        set("Id", Id);
        return this;
    }


    public String getName() {
        return get("Name");
    }

    public SystemUser setName(String Name) {
        set("Name", Name);
        return this;
    }


    public Integer getAge() {
        return get("Age");
    }

    public SystemUser setAge(Integer Age) {
        set("Age", Age);
        return this;
    }


    public String getProfile() {
        return get("Profile");
    }

    public SystemUser setProfile(String Profile) {
        set("Profile", Profile);
        return this;
    }


    public Date getCreateDate() {
        return get("CreateDate");
    }

    public SystemUser setCreateDate(Date createDate) {
        set("CreateDate", new java.sql.Timestamp(createDate.getTime())); // 这是什么鬼,等会你就知道啦
        return this;
    }

}

我是这样写Model的,我自己留了一手,先像写Bean一样写完里面的字段属性,然后呢再通过IDEA的LiveTemplate我自己定义的模板快速完成getter和setter,最后写上几个需要的DAO方法。我不喜欢那种定义个字段名的写法,因为我感觉那样子并没有我这么写的getter/setter方便。

被注释掉的那些属性,我就是为了随时替换掉Model而保留的,实际上然并卵,不如直接用工具生成实体类呢,更何况数据库又是100%和Bean对应的字段名,而且都是我设计的。

几个月前曾在OSC上面问答向作者@JFinal 反馈过这个 java.util.Date 兼容性的问题:

https://www.oschina.net/question/1269352_2197692

不知道是不是作者太自信了,没有深入研究这个问题的存在与否。

为了证实这个问题的存在,我之前一直用的 jtds 驱动,在写这篇博客的时候我怀疑过是否jtds引起的,然后换了microsoft的驱动 sqljdbc4.jar来 测试,发现ActiveRecord insert的时候还是有问题。

我使用的是sql server2008 R2数据库,这个问题困扰了我大半年了,我头疼得很,我尝试了好几种方法,最后选择上面的解决办法,幸好 java.sql.Timestampjava.util.Date 的子类。

好了,我们继续吧。

我们写完了Model,充血模型他就充当了实体类和DAO,现在可以写Service了,Service少不了的,不建议直接使用DAO来做事情,因为你很难保证别人是否有些逻辑在此DAO相应的Service里面控制的。

建立一个包services:

package com.rocbin.jfinal.services;

import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.IAtom;
import com.rocbin.jfinal.models.SystemUser;
import com.sun.istack.internal.NotNull;
import com.sun.istack.internal.Nullable;

import java.sql.SQLException;
import java.util.Date;
import java.util.List;

/**
 * Created by Rocbin on 2016/12/22.
 */
public class SystemUserService {

    public SystemUser getById(int id) {
        return SystemUser.dao.getById(id);
    }

    public List<SystemUser> list() {
        return SystemUser.dao.list();
    }

    public SystemUser addUser(@NotNull String name, @Nullable Integer age, @Nullable String profile) {
        final SystemUser systemUser = new SystemUser().setName(name).setAge(age).setProfile(profile).setCreateDate(new Date());
        Db.tx(8, new IAtom() {
            public boolean run() throws SQLException {
                return systemUser.save();
            }
        });
        return systemUser;
    }
}

比较简单的一个Service,我用 @NotNull @Nullable 来规范这个属性是否可以传入null值(传入null的时候可能是可选的参数),而且IDE也会有相应的提示的。

基本上没什么,就在addUser加了一个事务,没错,这就是JFinal为我们提供的一种简便的控制数据库事务的方式,JFinal 自己会处理好这些事务嵌套的情况,如果外层事务是READ_COMMITED而内层事务是SERIABLIZABLE的话,JFinal会自动提升Connection的事务级别为SERIALIZABLE。JFinal的Connection基于ThreadLocal实现的,她不会有Spring的事务传播一说。另外JFinal的事务也可以在Controller上面加注解实现的 @Before(value={TxSerializable.class}) 在你的Controller Class或者Controller方法之上,JFinal Tx的包在 com.jfinal.plugin.activerecord.tx。

然后建立一个Controller类:

package com.rocbin.jfinal.controllers;

import com.jfinal.core.Controller;
import com.rocbin.jfinal.models.SystemUser;
import com.rocbin.jfinal.services.SystemUserService;

import java.util.List;

/**
 * Index Controller
 * <p>
 * Created by Rocbin on 2016/12/22.
 */
public class IndexController extends Controller {

    static SystemUserService userService = new SystemUserService();

//    @Before(value={TxSerializable.class})
    public void index() {
        renderJsp("/WEB-INF/views/index.jsp");
    }

    public void registry() {
        renderJsp("/WEB-INF/views/registry.jsp");
    }

    public void list() {
        List<SystemUser> list = userService.list();
        getRequest().setAttribute("list", list);
        renderJsp("/WEB-INF/views/user-list.jsp");
    }

    public void add() {
        SystemUser systemUser = userService.addUser(getPara("name"), getParaToInt("age"), getPara("profile"));
        redirect("/home?id=" + systemUser.getId());
    }

    public void home() {
        SystemUser systemUser = userService.getById(getParaToInt("id"));
        getRequest().setAttribute("user", systemUser);
        renderJsp("/WEB-INF/views/user-home.jsp");
    }
}

Controller 主要实现几个功能:首页index、用户注册页面registry、用户列表list、添加用户接口add、用户中心home。

剩下的几个jsp页面,代码都比较简单,为了一点点的容颜,就用bootstrap来修饰一下吧。

第一个页面 首页index.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
    <title>Home</title>
    <script src="http://cdn.bootcss.com/jquery/2.2.4/jquery.js"></script>
    <link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
    <link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap-theme.css" rel="stylesheet">
    <script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.js"></script>
</head>
<body>
    <h2 class="page-header">Hello JFinal demo!</h2>
    <div class="btn-group">
        <a href="/list" class="btn btn-default">用户列表</a>
        <a href="/registry" class="btn btn-success">注册</a>
    </div>
</body>
</html>

上面只要就两个按钮,一个点击跳转到用户列表页,另一个是点击跳转到用户注册页。

页面截图:

每日一博 | JFinal 极速开发框架的优点和不足的地方

第二个页面是 用户列表页user-list.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
    <title>用户列表</title>
    <script src="http://cdn.bootcss.com/jquery/2.2.4/jquery.js"></script>
    <link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
    <link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap-theme.css" rel="stylesheet">
    <script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.js"></script>
</head>
<body>
<h2 class="page-header">Hello JFinal demo!</h2>
<table class="table table-striped table-hover">
    <thead>
    <tr>
        <th>Id</th>
        <th>Name</th>
        <th>Age</th>
        <th>Profile</th>
        <th>CreateDate</th>
    </tr>
    </thead>
    <tbody>
    <c:forEach var="user" items="${list}">

    <tr>
        <td>${user.id}</td>
        <td><a href="/home?id=${user.id}">${user.name}</a></td>
        <td>${user.age}</td>
        <td>${user.profile}</td>
        <td>${user.createdate}</td>
    </tr>
    </c:forEach>
    </tbody>
</table>
</body>
</html>

这个页面就主要展示系统里面所有的用户信息,我不考虑分页实现,因为这里没必要做分页,如果你想要做分页,在JFinal里面,model或record都有一个分页查询的方法paginate。

页面截图:

每日一博 | JFinal 极速开发框架的优点和不足的地方

第三个页面:用户注册页面 registry.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>注册新用户</title>
    <script src="http://cdn.bootcss.com/jquery/2.2.4/jquery.js"></script>
    <link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
    <link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap-theme.css" rel="stylesheet">
    <script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.js"></script>
</head>
<body>
<h1 class="page-header">注册新用户</h1>

<div class="panel panel-default">
    <div class="panel-header">
        <h5 class="panel-title">About me</h5>
    </div>
    <div class="panel-body">
        <form action="/add" method="post">
            <dl>
                <dt>Name</dt>
                <dd><input type="text" name="name" id="name" class="form-control"></dd>
                <dt>Age</dt>
                <dd><input type="number" name="age" id="age" class="form-control" min="0" max="120"></dd>
                <dt>Profile</dt>
                <dd><textarea name="profile" id="profile" cols="50" rows="3" class="form-control"></textarea></dd>
            </dl>
            <button type="submit" class="btn btn-success">注册</button>
        </form>

    </div>
</div>
</body>
</html>

我一个javascript也没写,数据校验都忽略了,正常情况下最好先用javascript做一次校验,然后在后端也要对表单填写的数据进行一次严格的校验(这个你必须要这么做、否则就呵呵哒)。

效果截图:

每日一博 | JFinal 极速开发框架的优点和不足的地方

第四个页面是用户中心 user-home.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
    <title>用户中心</title>
    <script src="http://cdn.bootcss.com/jquery/2.2.4/jquery.js"></script>
    <link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
    <link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap-theme.css" rel="stylesheet">
    <script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.js"></script>
</head>
<body>
<h1 class="page-header">用户中心</h1>

<div class="panel panel-default">
    <div class="panel-header">
        <h5 class="panel-title">About me</h5>
    </div>
    <div class="panel-body">
        <dl>
            <dt>Name</dt>
            <dd>${user.name}</dd>
            <dt>Age</dt>
            <dd>${user.age}</dd>
            <dt>Profile</dt>
            <dd>${user.profile}</dd>
            <dt>CreateDate</dt>
            <dd>${user.createdate}</dd>
        </dl>
    </div>
    <div class="panel-footer">
        <a href="javascript:history.back(true);" class="btn btn-default">返回</a>
    </div>
</div>
</body>
</html>

这个页面都比较简单的,上面显示的数据都是JFinal处理过的,jsp页面直接用el表达式处理输出,我有考虑过XSS问题,但这里只是个例子,不考虑例如user.profile内容里面可能会出现有XSS(XSS很危险,如果你不了解就赶紧去补补)的问题。

页面截图:

每日一博 | JFinal 极速开发框架的优点和不足的地方

基本上,JFinal完成的这个简单的需求就这要这些代码,没有XML配置、不会繁琐。

实际上,我好像写了一篇JFinal的入门教程,跟JFinal官方的blog很是类似。

前面的片段也有说了几个不爽的"bug",接下来我要说JFinal中缺少的东西。

  • 测试很不方便,虽然我自己写过一个类用于启动WebConfig、Plugins。

但是Controller你永远也只能启动Tomcat来测试,因为他内部依赖了Request、Response(这是servlet-api的,而Spring MVC是很不错的、他还有一个Model,虽然我只是刚入门)。

如果你要用了EhCache、CacheKit,要做一个测试简直就有噩梦,烦死人的找不到ehcache.xml文件的问题,只能每个人都改一改才可以,CacheKit的init方法是package可见级别的,为了EhCache能启动我也是豁出去了,不用Plugins,自己new EhCacheMange(file)然后用反射暴力了CacheKit的init方法。

没有依赖注入的日子里,我得靠着Holder、单例来过着写代码的日子,虽然可以简单的解决问题,但也带来烦恼,我宁愿有一个IOC容器。

如果有IOC,那是不是可以将JFinal的Duang做进去呢,AOP做到业务层上而不再仅限于Controller,然后顺便也把Spring 事务吸收下,把Db.tx 减少下使用频率,反而变得简单了。

我喜欢用注解,注解可以代替XML完成模块装配,JFinal官方可以多加一些注解,比如扫描Controller配置、依赖注入、Model映射等(我知道有非官方出品的)。

JFinal的Controller还是要吐槽一下,getPara、getParaToXXX是一个繁琐API,像Spring MVC他支持action方法参数注入,而且还可以扩展一下比如自动做数据校验等(如果你写JSON API服务呢……),而且可以注入Request、Response实例,从而可以解决JFinal Controller硬性依赖Request、Response的问题。

还要吐槽一下renderJson(null)的问题,我认为这是一个bug,他不支持render null,一旦业务返回结果null,renderJson就会报错。而实际上json是支持传递null的(我测试过在浏览器中)。

每日一博 | JFinal 极速开发框架的优点和不足的地方

很不爽吧,逼得你非得要加个判断(我不确定最新的版本是否解决了这个问题,我写这篇文章用的是2.0,我项目用的1.9都有这个问题)。

还要吐槽一下ActiveRecord中的问题,我发现,当我的SystemUser表里面没有数据的时候,插入第一笔数据他返回的主键的数据类型不是我期望的int类型,而是BigDecimal,这个也非常麻烦,怎么会遇到这个问题呢?刚刚试了一下,没有重现,抱歉。

JFinal 要有更多的人来支持才能做得更好,虽然你打着极速开发的旗帜,但也不要太过Geek,和Spring背着走,要吸取一些优秀的精华,另外,JFinal的JavaEE框架集成都比较麻烦,要自己来写着(去年搞定了Activiti工作流集成到JFinal数据源中,但至今也不敢打保票不会出现BUG),MyBatis想集成到JFinal中,感觉就是噩梦,做集成算了我不如研究下SpringMVC外加集成我想要的框架(前几个晚上就搞定了)。

休息了,有空再来续。

个人水平比较一般,全栈型(中小企业的宝)、和码农不沾边的Axure我也做着。

2016-12-22 23:53:00

原文  https://my.oschina.net/twisst/blog/810561
正文到此结束
Loading...