不想用Spring全家桶?試試這個國產JFinal框架

qch發表於2021-08-03

  前言

  逃離北上廣從廣州回老家南寧,入職這家公司用的技術是JFinal,藉此機會得以學習這個國產的MVC框架,經過一段時間的學習,基於之前的經驗搭建一個通用專案jfinal-demo

  jfinal-demo是基於JFinal封裝的一個簡單通用專案,一套通用程式碼,實現增刪改查分頁等基礎功能,單表模組通過繼承通用模組實現該基礎功能,通過程式碼生成器可快速生成全套單表程式碼。

   技術棧:JFinal + MySql

 

  JFinal介紹

  JFinal已連續多次獲得GVP Gitee最有價值開源專案,gitee地址:https://gitee.com/jfinal/jfinal

  JFinal官方文件:https://jfinal.com/doc

  JFinal官方簡介:

  JFinal 是基於 Java 語言的極速 WEB + ORM + AOP + Template Engine 框架,其核心設計目標是開發迅速、程式碼量少、學習簡單、功能強大、輕量級、易擴充套件、Restful。在擁有Java語言所有優勢的同時再擁有ruby、python、php等動態語言的開發效率!為您節約更多時間,去陪戀人、家人和朋友 :)

  JFinal有如下主要特點:
  MVC架構,設計精巧,使用簡單
  遵循COC原則,支援零配置,無xml
  獨創Db + Record模式,靈活便利
  ActiveRecord支援,使資料庫開發極致快速
  自動載入修改後的java檔案,開發過程中無需重啟web server
  AOP支援,攔截器配置靈活,功能強大
  Plugin體系結構,擴充套件性強
  多檢視支援,支援FreeMarker、JSP、Velocity
  強大的Validator後端校驗功能
  功能齊全,擁有struts2的絕大部分功能
  體積小僅 723 KB,且無第三方依賴

 

  程式碼編寫

  專案結構

 

 

 

   jfinal.bat、jfinal.sh是啟動指令碼

 

  通用程式碼包括統一返回物件Result,分頁條件PageCondition,控制層CommonController,業務層CommonService/Impl

 

   資料庫表與實體類的關係對映需要在_MappingKit中手動進行維護(其實也可以做成自動維護,只是我們的程式碼生成器還不支援)

/**
 * 資料表、主鍵、實體類關係對映
 * 需要手動維護
 */
public class _MappingKit {

    /**
     * 表、實體、主鍵關係集合
     * 方便SqlUtil工具類拼接查詢sql
     */
    public static HashMap<String,String> tableMapping = new HashMap<>();
    public static HashMap<String,String> primaryKeyMapping = new HashMap<>();

    public static void mapping(ActiveRecordPlugin arp) {
        arp.addMapping("blog", "id", Blog.class);
        tableMapping.put(Blog.class.getName(),"blog");
        primaryKeyMapping.put(Blog.class.getName(),"id");

        arp.addMapping("user", "user_id", User.class);
        tableMapping.put(User.class.getName(),"user");
        primaryKeyMapping.put(User.class.getName(),"user_id");
    }
}

  表欄位全部在BaseModel中(禁止改動)

/**
 * 部落格表 BaseModel
 *
 * 作者:Auto Generator By 'huanzi-qch'
 * 生成日期:2021-07-26 09:31:41
 */
@SuppressWarnings("serial")
public abstract class BaseBlog<M extends BaseBlog<M>> extends Model<M> implements IBean {
    //部落格id
    private Integer id;
    public void setId(Integer id) {
        this.id = id;
        set("id", this.id);
    }
    public Integer getId() {
        this.id = get("id");
        return this.id;
    }

    //部落格標題
    private String title;
    public void setTitle(String title) {
        this.title = title;
        set("title", this.title);
    }
    public String getTitle() {
        this.title = get("title");
        return this.title;
    }

    //部落格內容
    private String content;
    public void setContent(String content) {
        this.content = content;
        set("content", this.content);
    }
    public String getContent() {
        this.content = get("content");
        return this.content;
    }

    //使用者id
    private String userId;
    public void setUserId(String userId) {
        this.userId = userId;
        set("user_id", this.userId);
    }
    public String getUserId() {
        this.userId = get("user_id");
        return this.userId;
    }


}

  如果需要加與資料庫表無關屬性(例如方便介面接參,新增其他屬性),在Model新增,另外,表關聯也可以在這裡維護

/**
 * 部落格表 Model
 *
 * 作者:Auto Generator By 'huanzi-qch'
 * 生成日期:2021-07-26 09:31:41
 */
@SuppressWarnings("serial")
public class Blog extends BaseBlog<Blog> {
    public static final Blog dao = new Blog().dao();

    /**
     * 表關聯操作在這裡維護
     * User.userId = Blog.userId
     */
    public Result<User> getUser(String userId){
        UserServiceImpl userService = Aop.get(UserServiceImpl.class);
        return userService.get(userId);
    }
}

 

  攔截器實現Controller層全域性異常處理

/**
 * Controller層全域性異常處理
 * 特殊情況外,禁止捕獲異常,所有異常都應交給這裡處理
 */
public class GlobalExceptionInterceptor implements Interceptor{

    private static Log log = Log.getLog(GlobalExceptionInterceptor.class);

    public void intercept(Invocation inv) {
        Result result = null;

        try {
            inv.invoke();
        }
        //業務異常
        catch (ServiceException e){
            e.printStackTrace();
            result = Result.error(e.getErrorEnum());
        }
        //空指標、非法引數
        catch (NullPointerException | IllegalArgumentException e){
            e.printStackTrace();
            result = Result.error(ErrorEnum.INTERNAL_SERVER_ERROR);
        }
        
        //...
        
        //未知異常(放在最後)
        catch (Exception e){
            e.printStackTrace();
            result = Result.error(ErrorEnum.UNKNOWN);
        }

        if(StrKit.notNull(result)){
            inv.getController().renderJson(result);
        }
    }
}

  需要在AppConfig中配置Routes級別全域性攔截器

    /**
     * 配置路由
     */
    public void configRoute(Routes me) {
        // 掃描僅會在該包以及該包的子包下進行
        me.scan("cn.huanzi.qch.");

        //該方法用於配置是否要將控制器父類中的 public方法對映成 action
        me.setMappingSuperClass(true);

        // 此處配置 Routes 級別的攔截器,可配置多個
        me.addInterceptor(new GlobalExceptionInterceptor());
    }

  所有的異常資訊都應該在ErrorEnum中維護

/**
 * 自定義異常列舉類
 */
public enum ErrorEnum {
    //自定義系列
    USER_NAME_IS_NOT_NULL(10001,"【引數校驗】使用者名稱不能為空"),
    PWD_IS_NOT_NULL(10002,"【引數校驗】密碼不能為空"),

    //400系列
    BAD_REQUEST(400,"請求的資料格式不符!"),
    UNAUTHORIZED(401,"登入憑證過期!"),
    FORBIDDEN(403,"抱歉,你無許可權訪問!"),
    NOT_FOUND(404, "請求的資源找不到!"),

    //500系列
    INTERNAL_SERVER_ERROR(500, "伺服器內部錯誤!"),
    SERVICE_UNAVAILABLE(503,"伺服器正忙,請稍後再試!"),

    //未知異常
    UNKNOWN(10000,"未知異常!");


    /** 錯誤碼 */
    private Integer code;

    /** 錯誤描述 */
    private String msg;

    ErrorEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

  測試介面

    Controller
    
    public void errorTest(){
        throw new ServiceException(ErrorEnum.USER_NAME_IS_NOT_NULL);
    }

    public void errorTest2(){
        renderJson(blogService.errorTest2());
    }

    public void errorTest3(){
        renderJson(blogService.errorTest3());
    }
    
    
    ServiceImpl

    @Override
    public String errorTest2() {
        int i = 1/0;
        return "失敗乃成功之母!";
    }

    @Override
    public String errorTest3() {
        throw new NullPointerException();
    }

 

  自定義請求處理器

/**
 * 自定義處理器
 */
public class MyActionHandler extends Handler {

    public MyActionHandler() {
    }

    @Override
    public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
        //應用路徑
        request.setAttribute("ctx", request.getContextPath());

        Action action = JFinal.me().getAction(target, new String[]{null});

        boolean flag = false;
        List<String> allActionKeys = JFinal.me().getAllActionKeys();
        if(!allActionKeys.contains(target)){
            int i = target.lastIndexOf(47);
            if (i != -1) {
                String substring = target.substring(0, i);
                if (!allActionKeys.contains(substring) || action.getControllerPath().equals(substring)) {
                    flag = true;
                }
            }
        }

        /*
            404
            其他靜態資源可直接訪問,但.html頁面禁止直接訪問
         */
        if ((target.contains(".html") || !target.contains(".")) && flag) {
            try {
                response.setCharacterEncoding("UTF-8");
                response.setContentType("application/json; charset=utf-8");
                PrintWriter out = response.getWriter();
                out.print(JsonKit.toJson(Result.error(ErrorEnum.NOT_FOUND)));
                out.flush();
                out.close();
                response.flushBuffer();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else{
            this.next.handle(target, request, response, isHandled);
        }

    }
}

 

 

  效果演示

  get

 

   page

 

   list

 

 

  save

  id不存在新增

 

 

 

 

 

   id存在則更新

 

 

 

 

  delete

 

 

 

 

 

  一個簡單頁面,包括CRUD、分頁

 

   異常處理

  統一Controller層介面異常處理

 

 

 

 

 

 

 

   非controller介面錯誤,會跳轉去配置好的500.html頁面

 

  後記

  習慣了Spring全家桶,一時可能接受不了JFinal的風格,經過改造封裝,jfinal-demo專案的程式設計風格儘量與我們之前的習慣一致

  JFinal的生態遠沒有SpringBoot的好,碰到問題基本上靠百度是搜不到什麼解決方案的,好在這個框架並不複雜,依賴的東西也很少,大部分都可以按照需要進行魔改、擴充套件

 

  程式碼開源

 

  程式碼已經開源、託管到我的GitHub、碼雲:

  GitHub:https://github.com/huanzi-qch/jfinal-demo

  碼雲:https://gitee.com/huanzi-qch/jfinal-demo

 

相關文章