急速WEB框架JFinal及DB+ActiveRecord模式浅析

本文介绍了JFinal框架的基础使用方法,包括快速开发特性、DB+ActiveRecord模式的优点与不足,以及如何搭建开发环境。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       看了俩月基础知识,有点枯燥了,研究一个极速开发框架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的一些漂亮功能背后的源码。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值