Latke 在设计原理上非常类似 Spring,是 Spring 的极简版,该文档适合 Java Web 应用 框架初学者 以及 Latke 应用开发者,大家在看文档的同时,欢迎提出问题、评论。 概述 [链接]('l ɑ:tk ə,土豆饼)是一个简单易用的 Java Web 应用开发框架,包含 MVC、IoC/AOP、 ..

Latke 快速上手指南

本贴最后更新于 319 天前,其中的信息可能已经天翻地覆

Latke 在设计原理上非常类似 Spring,是 Spring 的极简版,该文档适合 Java Web 应用 框架初学者 以及 Latke 应用开发者,大家在看文档的同时,欢迎提出问题、评论。

概述

Latke('l ɑ:tk ə,土豆饼)是一个简单易用的 Java Web 应用开发框架,包含 MVC、IoC/AOP、事件通知、ORM、插件等组件。

在实体模型上使用 JSON 贯穿前后端,使应用开发更加快捷。这是 Latke 不同于其他框架的地方,非常适合小型应用的快速开发, 为什么又要造一个叫 Latke 的轮子

核心组件

Hello World!

Demo 项目完整代码: https://github.com/b3log/latke-demo

项目结构

└─hello
    │  nb-configuration.xml
    │  nbactions.xml
    │  pom.xml
    │
    └─src
        └─main
            ├─java
            │  └─org
            │      └─b3log
            │          └─latke
            │              └─demo
            │                  └─hello
            │                      │  HelloServletListener.java
            │                      │  Starter.java
            │                      │
            │                      ├─processor
            │                      │      HelloProcessor.java
            │                      │      RegisterProcessor.java
            │                      │
            │                      ├─repository
            │                      │      UserRepository.java
            │                      │
            │                      └─service
            │                              UserService.java
            │
            ├─resources
            │  │  latke.properties
            │  │  local.properties
            │  │  log4j.properties
            │  │  repository.json
            │  │
            │  └─META-INF
            │          beans.xml
            │
            └─webapp
                ├─skins
                │  └─classic
                │      │  hello.ftl
                │      │  index.ftl
                │      │  register.ftl
                │      │
                │      └─images
                │              logo.png
                │
                └─WEB-INF
                        static-resources.xml
                        web.xml

请求处理

客户端的 HTTP 请求会经过 Latke 分发到应用定义的请求处理器上(标注有 @RequestProcessor 的类),一个请求处理器可以包含多个请求处理方法(标注有 @RequestProcessing 的方法), 每个方法可以对应多个请求地址。可以看作是 SpringMVC 中控制器的简要实现,略有不同的是在响应的处理上。

@RequestProcessor
public class HelloProcessor {

    @RequestProcessing(value = {"/", "/index"}, method = HttpMethod.GET)
    public void index(final RequestContext context) {
        final AbstractFreeMarkerRenderer render = new SimpleFMRenderer();
        context.setRenderer(render);
        render.setTemplateName("index.ftl");

        final Map<String, Object> dataModel = render.getDataModel();
        dataModel.put("greeting", "Hello, Latke!");

        Requests.log(context.getRequest(), Level.DEBUG, LOGGER);
    }

    @RequestProcessing(value = "/greeting", method = {HttpMethod.GET, HttpMethod.POST})
    public void greeting(final RequestContext context) {
        final AbstractFreeMarkerRenderer render = new SimpleFMRenderer();
        context.setRenderer(render);
        render.setTemplateName("hello.ftl");

        final Map<String, Object> dataModel = render.getDataModel();
        dataModel.put("time", new Date());
        final String name = context.param("name");
        if (StringUtils.isNotBlank(name)) {
            dataModel.put("name", name);
        }
    }
}

通过使用不同的响应渲染器可以生成不同类型的响应,例如 HTML、Rss、PNG 等。

服务调用

通过 @Inject 注入需要的服务:

@RequestProcessor
public class RegisterProcessor {

    @Inject
    private UserService userService;

    @RequestProcessing(value = "/register")
    public void showRegister(final RequestContext context) {
        final AbstractFreeMarkerRenderer render = new SimpleFMRenderer();
        context.setRenderer(render);
        render.setTemplateName("register.ftl");
    }

    public void register(final RequestContext context) { // 函数式路由,在 HelloServletListener 中配置
        final AbstractFreeMarkerRenderer render = new SimpleFMRenderer();
        context.setRenderer(render);
        render.setTemplateName("register.ftl");
        final Map<String, Object> dataModel = render.getDataModel();

        final HttpServletRequest request = context.getRequest();
        final String name = request.getParameter("name");
        if (StringUtils.isNotBlank(name)) {
            dataModel.put("name", name);

            userService.saveUser(name, 3);
        }
    }
}

服务实现

@Service
public class UserService {

    private static final Logger LOGGER = Logger.getLogger(UserService.class.getName());

    @Inject
    private UserRepository userRepository;

    @Transactional
    public void saveUser(final String name, final int age) {
        final JSONObject user = new JSONObject();
        user.put("name", name);
        user.put("age", age);

        String userId;

        try {
            userId = userRepository.add(user);
        } catch (final RepositoryException e) {
            LOGGER.log(Level.ERROR, "Saves user failed", e);

            // Throws an exception to rollback transaction
            throw new IllegalStateException("Saves user failed");
        }

        LOGGER.log(Level.INFO, "Saves a user successfully [userId={0}]", userId);
    }
}

DAO

@Repository
public class UserRepository extends AbstractRepository {

    public UserRepository() {
        super("user");

        // Generates database tables
        JdbcRepositories.initAllTables();
    }
}

注意:初始化表 JdbcRepositories.initAllTables() 最好通过单独的初始化程序来做,调用这个方法后会根据 repository.json 的描述生成建表 SQL 并执行。

最佳实践

表名前缀

在 local.properties 中有一项配置 jdbc.tablePrefix,如果配置了该项,则初始化表(JdbcRepositories.initAllTables())时生成的表名就会带有前缀。

建议应用配置该项,以屏蔽不同数据库迁移数据时关键字对表名的影响。

实体模型

Lakte 使用 JSON 作为实体载体,管理 JSON 的键就是对实体的建模。实体的键对应了数据库表列名,实体内嵌的关联对象是服务中组装的。例如对于“用户”实体,键包含了简单类型属性:“name”、“age”,关联类型属性:”books”,构造的对象例如:

{
        “name”: “Daniel”,
        “age”: 23,
        “books”: [{
                “name”: “TAO of Life”
        }, ….]
}

键管理可以通过 User 类:

public class User {
    public static final String USER_NAME = name;
    public static final String USER_AGE = age;
    public static final String USER_T_BOOKS = books;
}

T(Transient) 表示这个属性是非持久化的(User 表中无此列),是通过在服务中组装而来的。

repository.json

这个文件可以手工编写,然后使用 JdbcRepositories#initAllTables 方法自动创建数据库;也可以使用 JdbcRepositories#initRepositoryJSON 方法从已有数据库表生成这个文件。

repository.json -> tables:

tables -> repository.json:

这两种方式没有什么本质上的区别,可由开发自由决定。

关联查询

实体 JSON 对象中的关联属性是通过组装而来,需要先把这个属性查询出来,再编程组装到这个实体 JSON 对象中。这一点相对于一些 ORM 框架(例如 Hibernate)来说是比较繁琐,但这样做的优势之一就是能够使实体变得更灵活、更容易加入缓存优化性能。

也支持自定义 SELECT SQL,请参考接口 repository#select。

  • Latke

    Latke 是一款以 JSON 为主的 Java Web 框架。

    54 引用 • 422 回帖 • 508 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    2355 引用 • 7823 回帖 • 890 关注
  • 框架
    31 引用 • 275 回帖
  • MVC
    7 引用 • 107 回帖
优质回帖
  • ydcun 1 1 赞同

    希望能为这个轮子做点贡献

  • umeone 1 赞同

    不错,以前还真没这么去想过,准备研究一下。。

  • 88250 1 赞同

    可行的,等后面有空再加吧。

72 回帖
请输入回帖内容...
  • zempty

    不错

  • R

    👍

  • youngski

    👍

  • nishiala

    不错

  • songboriceboy

    强大

  • relyn 1

    感觉对于 Java 初学者而言,还是讲得太专业了,框架处理流程图是必须要有的

  • 88250 1

    @relyn 好建议,稍后补

    1 回复
  • Angonger

    1473834665987
    我想知道这俩模块儿之间的关系,因为发现一些代码他俩都有

    1 回复
  • 88250

    parent 是父项目,方便管理模块,你看它的打包类型 <packaging>pom</packaging>

    1 回复
  • Angonger

    哦哦哦,那为什么还需要有其他模块已有的代码呢?父项目管理模块我能理解还

    1 回复
  • 88250

    repository 那几个模块是适配不同数据库的,应用按需选择。

    2 回复
  • Angonger

    ……我说 latke 和 latke-parent 这俩,剩下几个我理解,你看我截图呀 😂

    1 回复
  • 88250

    你看下文件系统上的目录结构,别管 IDE 整理好的就明白了。

    2 回复
  • Angonger

    哦哦哦 我在 GitHub 上看,其实就一个 对吧

  • Angonger 1

    我这就扔掉 eclipse

  • dreamFXB

    还有更详细的文档么?

    1 回复
  • 88250

    可以看下 [Latke] 这个标签下面的帖子哦

  • ydcun 1 1 赞同

    希望能为这个轮子做点贡献

  • devotion

    请问下,实体通过 JSON 传递,若表需要增加、减少字段,是如何处理的?

    1 回复
  • 88250

    表结构得自己维护

  • umeone 1 赞同

    不错,以前还真没这么去想过,准备研究一下。。

  • xuwangcheng14

    @88250 java.lang.ClassNotFoundException: org.b3log.latke.repository.mysql.MysqlJdbcDatabaseSolution
    请问这个报错?我看了源代码,确实需要这个类吧,但是我通过 maven 下来的 latke 里没找啊。
    麻烦帮忙看下什么原因?

  • xuwangcheng14

    仔细看了看 还是确实少了包。
    pom 没配置好。
    不好意思,打扰了。

    1 回复
  • 88250

    没打扰,有问题随时提 :)

    2 回复
  • xuwangcheng14

    {
        "description": "test",
        "version": "1.0.0.0, 2017.3.22",
        "authors": ["admin"],
        "repositories": [
            {
                "name": "user",
                "keys": [
                    {
                        "name": "user_id",
                        "type": "int"
                    },
                    {
                        "name": "username",
                        "type": "String",
                        "length": 255
                    },
                    {
                        "name": "password",
                        "type": "String",
                        "length": 255
                    },
                    {
                        "name": "power",
                        "type": "String",
                        "length": 1
                    }
                ]
            }
        ]
    }
    

    为什么建这个表的时候总是报错?
    ou have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')) ENGINE= InnoDB DEFAULT CHARSET= utf8' at line 1

    而且日志里没打 sql 语句。

    1 回复
  • 88250

    log4j 配置一下 log4j.logger.org.b3log.latke.repository.jdbc.util=TRACE 然后再看看,这样会输出 SQL

    1 回复
  • xuwangcheng14

    是通过 oId 来指定主键的啊?就是主键只能叫 oId 吗?

    1 回复
  • 88250

    我不记得代码了,估计是吧..

    1 回复
  • xuwangcheng14 1

    好吧,的自己研究源码了。
    文档不太多,新手用这个有点懵!

  • haojieli

    你说它到底是不是圆的呢。

    1 回复
  • 88250

    方了

    2 回复
  • haojieli

    再怎么也算是个轮子,修修补补还是能快速的跑起来。

    1 回复
  • 88250

    当然,现在跑得好好的,哈哈哈哈哈哈哈哈哈哈哈哈

    1 回复
  • haojieli 1

    👍 下一个项目就用这个轮子吧 。我要看看它是不是传说中的那样

  • OOO

    你好,请问这个论坛就是用的这个框架吗?

    1 回复
  • 88250

    是的😁

  • wthfeng

    关于 latke 的传参问题,看了看源码,是否不支持直接在方法参数列表中获取 http 的请求参数?

    1 回复
  • 88250

    支持的,不过用的比较少

    1 回复
  • wthfeng

    比如这种情况

    @RequestProcessing(value = "/test", method = HTTPRequestMethod.GET)
    public void test(final HTTPRequestContext context, String id ){
      //处理
    }
    
    

    id 这个值会是空的,查看代码后在 Converters.$PathVariableConvert.convert 发现这行
    Object ret = result.getMapValues().get(paramterName);
    mapValues 为空直接走异常了,是否本身不支持这种传参或是版本等其他问题?

    1 回复
  • 88250

    提交的 url 带了 ?id=xxx 么,我记得 GET 请求应该支持的

    1 回复
  • wthfeng

    用 a 链接试试了,应该是不行。我在 Sym 看了大部分是通过注解路径参数或直接 request 获取的,这种方式没看到。

    1 回复
  • 88250

    可能我记错了 :p

    1 回复
  • wthfeng

    😄 哈,谢楼主解答了。

  • json712

    这个 demo 用内嵌的服务器可以跑起来,用 Tomcat 部署老访问不到啊

  • linker

    请问,怎么在 repository.json 里面制定一个表的 Index?
    我看到 Solo 似乎有不少的 join 查询并没有走 Index。

    1 回复
  • 88250

    暂时没这特性,索引是手工建的。

    1 回复
  • linker

    我在想是不是可以自动呢?
    例如在所有的关联表上,自动加上对关联 Key 和状态字段的 Index,应该没啥副作用。

    1 回复
  • 88250

    Solo 里面应该没有 join,Sym 也只有在非常特殊的统计逻辑上才有。要用 join 的地方都是在程序里面多次查询实现的,或者在写数据的时候就计算持久化了结果。整个设计是非常弱化 DB 的,这也和早期使用 Google App Engine 的 low level datastore API 有关系,比较习惯 map 风格。

    1 回复
  • linker

    哦,那么自动在经常查询的过滤条件上建立 Index 是不是可行呢?

    1 回复
  • 88250 1 赞同

    可行的,等后面有空再加吧。

请输入回帖内容 ...