JFinal 极速开发框架的优点和不足的地方

2016-12-23 10:16:14来源:oschina作者:web4j人点击

第七城市

写这篇简短的博文,并不是要故意贬低开源项目,而且我没必要这么做,因为本人并没有写这一类的开源框架,不会形成竞争。 写这篇文章,我是从个人的真实感受去写的。本人敢说,JFinal框架还是一个不错的MVC框架,而且ORM和支持多种数据库,相比Servlet那可配置简单、功能强大的多的去了,比如JDBC操作、事务支持、ORM、AOP、JSON等。


先说JFinal的一些优点吧:

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

首先建立一个Maven Web项目:


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


xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4.0.0
com.rocbin.jfinal
jfinal-demo
war
1.0-SNAPSHOT
jfinal-demo Maven Webapp
http://maven.apache.org


junit
junit
4.12


com.jfinal
jfinal
2.0


javax.servlet
servlet-api
2.5
provided


c3p0
c3p0
0.9.1.2


net.sourceforge.jtds
jtds
1.3.1


javax.servlet
jstl
1.2


com.microsoft.sqlserver
sqljdbc4
4.0



jfinal-demo


二、建立个包包,写几个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
*


* 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
*


* Created by Rocbin on 2016/12/22.
*/
public class SystemUser extends Model {
public static final SystemUser dao = new SystemUser();
//~~~~~~~~~~~~~ 字段 ~~~~~~~~~~~~~~
//int id;
//String name;
//Integer age;
//String profile;
//Date createDate;
//~~~~~~~~~~~~~ DAO 方法 ~~~~~~~~~~~
public List 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 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
*


* 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/viewshttps://my.oschina.net/registry.jsp");
}
public void list() {
List 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" %>



Home
<script src="http://cdn.bootcss.com/jquery/2.2.4/jquery.js"></script>


<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.js"></script>


Hello JFinal demo!




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


页面截图:



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


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>



用户列表
<script src="http://cdn.bootcss.com/jquery/2.2.4/jquery.js"></script>


<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.js"></script>


Hello JFinal demo!























Id Name Age Profile CreateDate
${user.id} ${user.name} ${user.age} ${user.profile} ${user.createdate}


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


页面截图:



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


<%@ page contentType="text/html;charset=UTF-8" language="java" %>



注册新用户
<script src="http://cdn.bootcss.com/jquery/2.2.4/jquery.js"></script>


<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.js"></script>


注册新用户




About me





Name


Age


Profile