RT学习了一下jfinal 从一个快速入门案例开始学习
demo来自连接:http://www.jfinal.com/
ALT+Z用jetty启动web项目:
http://localhost:8080/jfinal_demo_for_maven/
项目启动完毕
从工程目录结构看,jfinal也是三层架构MVC
controller,service、dao层
其中DAO层和model层无缝连接了 ,从源码可以发现:
service引用dao层的时候:
package com.demo.blog;
import com.demo.common.model.Blog;
import com.jfinal.plugin.activerecord.Page;
/**
* 本 demo 仅表达最为粗浅的 jfinal 用法,更为有价值的实用的企业级用法
* 详见 JFinal 俱乐部: http://jfinal.com/club
*
* BlogService
* 所有 sql 与业务逻辑写在 Service 中,不要放在 Model 中,更不
* 要放在 Controller 中,养成好习惯,有利于大型项目的开发与维护
*/
public class BlogService {
/**
* 所有的 dao 对象也放在 Service 中
*/
private static final Blog dao = new Blog().dao();
public Page<Blog> paginate(int pageNumber, int pageSize) {
return dao.paginate(pageNumber, pageSize, "select *", "from blog order by id asc");
}
public Blog findById(int id) {
return dao.findById(id);
}
public void deleteById(int id) {
dao.deleteById(id);
}
}
Blog.java
/**
* 本 demo 仅表达最为粗浅的 jfinal 用法,更为有价值的实用的企业级用法
* 详见 JFinal 俱乐部: http://jfinal.com/club
*
* Blog model.
* 数据库字段名建议使用驼峰命名规则,便于与 java 代码保持一致,如字段名: userId
*/
@SuppressWarnings("serial")
public class Blog extends BaseBlog<Blog> {
}
BaseBlog<Blog>这个泛型类再点进去看:
package com.demo.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings({"serial", "unchecked"})
public abstract class BaseBlog<M extends BaseBlog<M>> extends Model<M> implements IBean {
public M setId(java.lang.Integer id) {
set("id", id);
return (M)this;
}
public java.lang.Integer getId() {
return get("id");
}
public M setTitle(java.lang.String title) {
set("title", title);
return (M)this;
}
public java.lang.String getTitle() {
return get("title");
}
public M setContent(java.lang.String content) {
set("content", content);
return (M)this;
}
public java.lang.String getContent() {
return get("content");
}
}
BaseBlog最终是继承Model类的
注意这段,物理分页列表的实现
public Page<Blog> paginate(int pageNumber, int pageSize) {
return dao.paginate(pageNumber, pageSize, "select *", "from blog order by id asc");
}
传入了页码、页面大小,选择的列,排序方式
paginat(int pageNumber, int pageSize)方法在内部实现了分页字段填充、ResultSet拆解,实体类属性注入等操作(代码不贴了,就是操作JDBC),最后如你所料,返回的是一个带Blog泛型的Page对象
值得一提,在getConnection()的时候使用了ThreadLocal<Connection>
/**
* Get Connection. Support transaction if Connection in ThreadLocal
*/
public final Connection getConnection() throws SQLException {
Connection conn = threadLocal.get();
if (conn != null)
return conn;
return showSql ? new SqlReporter(dataSource.getConnection()).getConnection() : dataSource.getConnection();
}
SqlReporter这里使用了代理模式(JDK动态代理),有点意思~
在JDBC 的prepareStatement()执行前加了切面,就是输出SQL日志,代码如下:
/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. */
/**
* Copyright (c) 2011-2017, James Zhan 瑭规尝 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.plugin.activerecord;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import com.jfinal.log.Log;
/**
* SqlReporter.
*/
public class SqlReporter implements InvocationHandler {
private Connection conn;
private static boolean logOn = false;
private static final Log log = Log.getLog(SqlReporter.class);
SqlReporter(Connection conn) {
this.conn = conn;
}
public static void setLog(boolean on) {
SqlReporter.logOn = on;
}
@SuppressWarnings("rawtypes")
Connection getConnection() {
Class clazz = conn.getClass();
return (Connection)Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Connection.class}, this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (method.getName().equals("prepareStatement")) {
String info = "Sql: " + args[0];
if (logOn)
log.info(info);
else
System.out.println(info);
}
return method.invoke(conn, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
}
至此,jfinal的 ORM部分就看完了,不得不佩服jfinal的代码写的真的很简介,看起来并不难
视线转移到前端,看控制器部分,也就是MVC中的C。
一个典型的controller代码如下所示:
package com.demo.blog;
import com.jfinal.aop.Before;
import com.jfinal.core.Controller;
import com.demo.common.model.Blog;
@Before(BlogInterceptor.class)
public class BlogController extends Controller {
static BlogService service = new BlogService();
public void index() {
setAttr("blogPage", service.paginate(getParaToInt(0, 1), 10));
render("blog.html");
}
}
setAttr没什么好说的,就是servlet基础知识中的request.setAttribute(name, value);
值得看的是最后一行的render("blog.html");很容易让人想起springMVC的 return "xxx.jsp"
没错,这就是返回视图的意思!
render()方法点进去看源码:
/**
* Render with view use default type Render configured in JFinalConfig
*/
public void render(String view) {
render = renderManager.getRenderFactory().getRender(view);
}
最后返回的render是一个 abstract class ,这里用了抽象工厂模式返回的是一个具体的视图实现类
Render的实现类有这么多,look~
但是还有不清楚的地方,按理说一个servlet处理完业务之后不是应该有类似如下的一些转发操作吗(重定向同理)
request.getRequestDispatcher(view).forward(request, response);
但是我点跟进
render("index.html");
这个源码并没有看到有这种操作啊 。原因在哪里呢 ,一时还看不出来,继续跟源码吧
我先启动工程运行到render("index.html")处打上断点,然后进入 com.jfinal.core.Controller
看到Factory类的具体实现类是renderFactory,这个renderFactory是IRenderFactory的一个实现类
熟悉工厂设计模式的朋友肯定想到了, 先找一下给抽象工厂注入具体实现的调用处
那么你或许能找到蛛丝马迹
在getRenderFactory()处按下 ctrl+shift+G,看看能搜出什么来
果然搜出来了 ,就在上图这个ActionHandler这里
继续跟源码进去看
找到这个
点进render()方法进去瞧一瞧,应该快水落石出了
强大的eclipse告诉我,有如下类实现了抽象类Render的抽象方法render()
我先选个JspRender进去看下实现吧,看到如下代码。这下终于跑不掉了吧
public void render() {
// 在 jsp 页面使用如下指令则无需再指字符集, 否则是重复指定了,与页面指定的不一致时还会出乱码
// <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
// response.setContentType(contentType);
// response.setCharacterEncoding(encoding);
try {
if (isSupportActiveRecord)
supportActiveRecord(request);
request.getRequestDispatcher(view).forward(request, response);
} catch (Exception e) {
throw new RenderException(e);
}
}
其中 终于出现了我们熟悉的servletAPI:
request.getRequestDispatcher(view).forward(request, response);
问题解决但是还存在疑问
好好的代码怎么就莫名其妙的和这个什么handler勾搭上了?
怎么调用的?答案就在就这个过滤器里面
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<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.common.DemoConfig</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jfinal</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
好了,以上就是jfinal源码的一点小探索,重点就是分享一下阅读源码的思路