看了俩月基础知识,有点枯燥了,研究一个极速开发框架JFinal。在大家的劝说下,作者这哥们终于开源了,大概扫了一下源码,做了个小工程,谈一下感受。后面会给大家做一个JFinal的源码解析。
“快”
JFinal确实够快,主要体现在了对servlet的封装上,比struts要方便许多,与springMVC的modelAndView的模式有一些像,但感觉JFinal还是要更精简一些。但是这个快是有牺牲的,那所谓的牺牲就是牺牲了OO思想,好像回归到了最原始的servlet中去了。现在很多小伙伴对servlet的掌握都渐渐淡忘了,这块需要重新捡起来,复习一下基本的servlet的用法。
还有一点就是,除了web.xml之外,他确实是无配置,这点跟spring的ioc思想是有着不同之处,甚至说是冲突的。我个人认为啊,世界上的事情发展都遵循一个道理,“简单-复杂-简单”。那么,这里我是站在JFinal这边的,面向注解编程已经证明了我的观点。
/**
* Config route
* 访问路由
*/
@Override
public void configRoute(Routes me) {
// 可直接添加路由封装类
me.add(new AnimalRoutes());
}
/**
* you must implement config method and use add method to config route
*/
@Override
public void config() {
// 将http://localhost/hello/的访问路径配置到HelloController上
// 如果/hello/后没有任何字符,则默认访问index()方法,如果有字符,则调用后跟字符同名的方法。
// 例如http://localhost/hello/say,则调用HelloController的say方法
add("/cat", CatController.class);
}
}
/**
* url输入地址http://localhost/cat/{methodName}就可以访问到对应路径。
* 输入http://localhost/cat/run,则可进入该方法。
*/
public void run() {
renderText("it`s running");
}
注释虽然很多,但其实只有三行代码,这就搞定了servlet的跳转问题,这个我觉得是JFinal的最漂亮的地方之一,连一个注解都不需要~“DB+ActiveRecord”
这是JFinal觉得优秀的地方,但也是我要吐槽的地方。JFinal在数据库映射方面做的确实要比Hibernate和mybatis要优秀,首先在Model映射上,JFinal不需要你去特意制作与数据库匹配的POJO,它会自动帮你完成映射。而我们平时知道,一个这样的POJO会带来多么大的代码量,而且复用性会很差,这点JFinal做的足够出色。
但是,这里我要说但是,就是JFinal在DB操作这得封装不够成熟,很多功能都没能实现好。举个例子,如果要写一个Dynamic Sql的时候,JFinal并没提供封装的方法,而必须要手动的去构建那种带?的statement。并且,这着实让我坑了一把,JFinal的源码,在传入?对应的参数的时候,采用的是Object... 的形式。这个代表什么呢,代表可以传1~N个Object对象。表面上看,这样似乎没什么问题,但实际上这块需要我们自己来处理这得顽疾。
请看下面代码:
StringBuilder sql = new StringBuilder("select * from animal where 1=1 ");
if (StringUtils.isNotBlank(type)) {
sql.append("and type = ? ");
}
if (StringUtils.isNotBlank(name)) {
sql.append("and name = ? ");
}
if (age != null) {
sql.append("and age = ? ");
}
List<AnimalModel> resultList = AnimalModel.dao.find(sql.toString(),<span style="font-family:Microsoft YaHei;">type,name,age</span>);
上面这段代码看出来什么问题了吗?如果没有的话,往下看:
/**
* Find model.
* @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
* @param paras the parameters of sql
* @return the list of Model
*/
public List<M> find(String sql, Object... paras) {
Config config = getConfig();
Connection conn = null;
try {
conn = config.getConnection();
return find(conn, sql, paras);
} catch (Exception e) {
throw new ActiveRecordException(e);
} finally {
config.close(conn);
}
}
这是JFinal的源码Model.java,这块存在一个大坑,就是会造成?与参数数组不匹配的问题。为啥会这样,就因为Object...
Object... 这种,如果传入1个以上的参数,会转换成Object[]的形式,那么数组的长度。type name age三个参数如果都有值,OK,那没有问题。但如果有一个以上的null的值得时候,那么在前一个编写statement的逻辑里,则会对应少一个以上的?,案例来说Object[]也要相应减少。但问题来了,Object[]的length永远是3,即便你传入的3个参数都是null,他也是3,只不过是3个null。那这样,就造成了?少,而Object[]不变。
这里描述的费劲了点,但解决方案也简单:
/**
* 条件查询animal表
* @return
*/
public List<AnimalModel> findCat(String type,String name,Integer age) {
StringBuilder sql = new StringBuilder("select * from animal where 1=1 ");
List editList = new ArrayList();
if (StringUtils.isNotBlank(type)) {
sql.append("and type = ? ");
editList.add(type);
}
if (StringUtils.isNotBlank(name)) {
sql.append("and name = ? ");
editList.add(name);
}
if (age != null) {
sql.append("and age = ? ");
editList.add(age);
}
List<AnimalModel> resultList = AnimalModel.dao.find(sql.toString(),editList.toArray());
return resultList;
}
用ArrayList<T>来解决。
我为了描述这个问题,已经有点开始变得啰嗦了,因为我还是想说明他的源码还是有一些不方便的,想做到mybatis那样成熟,它还需要时间。当然,你也可以不适用DB+ActiveRecord模式,JFinal也支持spring,再通过spring装在mybatis进行后台及DB操作。
下面我们开始讲一下JFinal的用法。
一、下载文件:
官方地址:http://www.jfinal.com/
需要下载三个文件,一个运行文件,一个源码(不必须),一个操作手册。
二、建立Maven工程
这里我用velocity做展示,数据库用的mysql,数据源用c3p0,用jetty8来当服务器。
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>jetty-server</artifactId>
<version>8.1.8</version>
</dependency>
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>jfinal</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.20</version>
</dependency>
<build>
<finalName>jfinal_demo_for_maven</finalName>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>8.1.8.v20121106</version>
<configuration>
<stopKey>stop</stopKey>
<stopPort>5599</stopPort>
<webAppConfig>
<contextPath>/</contextPath>
</webAppConfig>
<scanIntervalSeconds>5</scanIntervalSeconds>
<connectors>
<connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>80</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
</configuration>
</plugin>
</plugins>
</build>
下面看一下主要工程目录:
介绍一下这几个目录
config是核心配置文件,几乎所有的配置都在这里定义,容器启动时,通过web.xml指向FinalConfig.java文件,来读取配置。
controller是跳转,与springMVC里的controller作用是一样的。
interceptor是拦截器。
model是DB操作文件,相当于dao,这里可能会有点疑问,model不应该是dto吗?这就是JFinal的特殊之处,无需POJO自定义字段映射。
routes是路由,这个也可以没有,是为了独立不同模块编码的一种方式。
三、web.xml入口配置。
<web-app>
<filter>
<filter-name>jfinal</filter-name>
<filter-class>com.jfinal.core.JFinalFilter</filter-class>
<init-param>
<param-name>configClass</param-name>
<param-value>com.demo.config.FinalConfig</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jfinal</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
此处com.demo.config.FinalConfig,是我们自定义的核心配置文件。JFinalFilter是核心跳转的源码,在后面的篇章我会介绍。
四、解析FinalConfig
/**
* Created with IntelliJ IDEA.
* User: 菜鸟大明
* Date: 14-7-24
* Time: 上午10:11
* To change this template use File | Settings | File Templates.
*/
public class FinalConfig extends JFinalConfig {
/**
* Config constant
* 此方法用来配置 JF inal 常量 值,如开发模式常量 值,
* 如开发模式devMode devMode 的配置
*/
@Override
public void configConstant(Constants me) {
// JFinal 支持 JSP FreeMarker、Velocity三种常用视图 。
me.setViewType(ViewType.VELOCITY);
}
/**
* Config route
* 访问路由
*/
@Override
public void configRoute(Routes me) {
// 可直接添加路由封装类
me.add(new AnimalRoutes());
}
/**
* Config plugin
* 配置插件
* JFinal有自己独创的 DB + ActiveRecord模式
* 此处需要导入ActiveRecord插件
*/
@Override
public void configPlugin(Plugins me) {
// 读取db配置文件
loadPropertyFile("db.properties");
// 采用c3p0数据源
C3p0Plugin c3p0Plugin = new C3p0Plugin(getProperty("jdbcUrl"),getProperty("user"), getProperty("password"));
me.add(c3p0Plugin);
// 采用DB+ActiveRecord模式
ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin);
me.add(arp);
// 进行DB映射
arp.addMapping("animal", AnimalModel.class);
}
/**
* Config interceptor applied to all actions.
* 全局拦截器
*/
@Override
public void configInterceptor(Interceptors me) {
me.add(new GlobalInterceptor());
}
/**
* Config handler
*/
@Override
public void configHandler(Handlers me) {
}
}
(1)JFinal支持JSP、FreeMarker、Velocity及其他展示层。
public void configConstant(Constants me) {
// JFinal 支持 JSP FreeMarker、Velocity三种常用视图 。
me.setViewType(ViewType.VELOCITY);
}
工程里我们采用velocity。
(2)配置路由
public void configRoute(Routes me) {
// 可直接添加路由封装类
me.add(new AnimalRoutes());
}
public void config() {
// 将http://localhost/hello/的访问路径配置到HelloController上
// 如果/hello/后没有任何字符,则默认访问index()方法,如果有字符,则调用后跟字符同名的方法。
// 例如http://localhost/hello/say,则调用HelloController的say方法
add("/cat", CatController.class);
add("/dog", DogController.class);
}
(三)添加插件
public void configPlugin(Plugins me) {
// 读取db配置文件
loadPropertyFile("db.properties");
// 采用c3p0数据源
C3p0Plugin c3p0Plugin = new C3p0Plugin(getProperty("jdbcUrl"),getProperty("user"), getProperty("password"));
me.add(c3p0Plugin);
// 采用DB+ActiveRecord模式
ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin);
me.add(arp);
// 进行DB映射
arp.addMapping("animal", AnimalModel.class);
}
(四)拦截器
拦截器共分三个级别,global,controller,action的。
1、global的需要在核心配置文件的configInterceptor方法里添加。
public void configInterceptor(Interceptors me) {
me.add(new GlobalInterceptor());
}
2、controller级别的需要子啊XXXController的类声明处添加,例:AnimalInterceptor.class@Before(AnimalInterceptor.class)
public class CatController extends Controller {
@Before(CatInterceptor.class)
public void index() {
setAttr("cat"," 这是一只猫");
// redirect("/cat/run");
render("/vm/cat.vm");
}
}
3、action级别的需要在XXXController的方法声明处添加,例:上图的CatInterceptor.class
当然,还有很多种用法,例如 组合拦截器等等,具体请参照JFinal手册。
(五)处理器
此方法用来配置JFinal的Handler,如下代码配置了名为ResourceHandler的处理器,Handler可以接管所有的web请求,并拥有完全的控制权,可以很方便的实现更高层的功能性扩展。
public void configHandler(Handler me) {
me.add(new ResourceHandler());
}
以上,就是JFinal的一些基础知识,JFinal自带用户手册在这方面讲的更详细,建议大家好好去看看,毕竟是中国人编写的,很容易看懂。下次我会带来JFinal的源码解析,我们一起看看JFinal的一些漂亮功能背后的源码。