V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
88250
V2EX  ›  分享创造

Java Web 框架 Latke v2.4.39,重写控制器层

  •  1
     
  •   88250 ·
    88250 · 2018-12-21 12:03:53 +08:00 · 2222 次点击
    这是一个创建于 2166 天前的主题,其中的信息可能已经有所发展或是发生改变。

    简介

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

    在实体模型上使用 JSON 贯穿前后端,使应用开发更加快捷。这是 Latke 不同于其他框架的地方,非常适合小型应用的快速开发。

    特性

    • 注解式、函数式路由
    • 依赖注入
    • 多种数据库 ORM
    • 多语言
    • 内存 /Redis 缓存
    • 事件机制
    • 插件机制

    案例

    • Demo:简单的 Latke 应用示例
    • Solo:一款小而美的 Java 博客系统
    • Symphony:一款用 Java 实现的现代化社区(论坛 /BBS/社交网络 /博客)平台

    安装

    <dependency>
        <groupId>org.b3log</groupId>
        <artifactId>latke-core</artifactId>
        <version>${latke.version}</version>
    </dependency>
    

    控制器层用法

    注解声明式路由

    @RequestProcessing("/")
    public void index(final RequestContext context) {
        context.setRenderer(new SimpleFMRenderer("index.ftl"));
        final Map<String, Object> dataModel = context.getRenderer().getRenderDataModel();
        dataModel.put("greeting", "Hello, Latke!");
    }
    

    函数式路由

    DispatcherServlet.post("/register", registerProcessor::register);
    DispatcherServlet.mapping();
    

    路径变量和查询字符串

    @RequestProcessing("/var/{pathVar}")
    public void paraPathVar(final RequestContext context) {
        final String paraVar = context.param("paraVar");
        final String pathVar = context.pathVar("pathVar");
        context.renderJSON(new JSONObject().put("paraVar", paraVar).put("pathVar", pathVar));
    }
    

    JSON 解析

    final JSONObject requestJSON = context.requestJSON();
    

    Servlet 封装

    final String remoteAddr = context.remoteAddr();
    final String requestURI = context.requestURI();
    final Object att = context.attr("name");
    final String method = context.method();
    context.sendRedirect("https://b3log.org");
    final HttpServletRequest request = context.getRequest();
    final HttpServletResponse response = context.getResponse();
    

    服务层用法

    依赖注入、事务

    @Service
    public class UserService {
    
        private static final Logger LOGGER = Logger.getLogger(UserService.class);
    
        @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);
    
                // 抛出异常后框架将回滚事务
                throw new IllegalStateException("Saves user failed");
            }
    
            LOGGER.log(Level.INFO, "Saves a user successfully [userId={0}]", userId);
        }
    }
    

    持久层用法

    构造 ORM

    @Repository
    public class UserRepository extends AbstractRepository {
    
        public UserRepository() {
            super("user");
        }
    }
    

    单表 CRUD

    public interface Repository {
        String add(final JSONObject jsonObject) throws RepositoryException;
        void update(final String id, final JSONObject jsonObject) throws RepositoryException;
        void remove(final String id) throws RepositoryException;
        void remove(final Query query) throws RepositoryException;
        JSONObject get(final String id) throws RepositoryException;
        long count(final Query query) throws RepositoryException;
    }
    

    条件查询

    public JSONObject getByName(final String name) throws RepositoryException {
        final List<JSONObject> records = getList(new Query().
                setFilter(new PropertyFilter("name", FilterOperator.EQUAL, name)));
        if (records.isEmpty()) {
            return null;
        }
    
        return records.get(0);
    }
    

    分页查询

    new Query().setCurrentPageNum(1).setPageSize(50)
    

    按字段排序

    new Query().addSort("name", SortDirection.DESCENDING);
    

    仅获取需要字段

    new Query().addProjection("name", String.class);
    

    原生 SQL

    final List<JSONObject> records = select("SELECT * FROM `user` WHERE `name` = ?", name);
    

    文档

    社区

    鸣谢

    Latke 的诞生离不开以下开源项目:

    10 条回复    2018-12-25 07:35:09 +08:00
    kidlj
        1
    kidlj  
       2018-12-21 12:34:41 +08:00 via iPhone
    哇,看到 golang 的 gin
    blueskea
        2
    blueskea  
       2018-12-21 13:45:28 +08:00
    用过 solo 博客,功能和外观上没话说,但是会将账号、用户评论这些信息同步到你们那边,感觉不爽,后来想自己改改,发现底层的框架里也封装了这些东西,改起来好麻烦。不过还是很感谢作者付出的辛苦。
    88250
        3
    88250  
    OP
       2018-12-21 14:01:46 +08:00
    @blueskea 同步可以选择关闭,没必要改代码啊。
    cnbobolee
        4
    cnbobolee  
       2018-12-21 14:20:39 +08:00
    瞄过 gin ?
    88250
        5
    88250  
    OP
       2018-12-21 15:03:58 +08:00
    @cnbobolee
    @kidlj

    我们另一个项目用的是 Gin https://github.com/b3log/pipe
    blueskea
        6
    blueskea  
       2018-12-21 17:18:01 +08:00
    @88250 在哪边改啊,一直没找到
    88250
        7
    88250  
    OP
       2018-12-21 17:27:28 +08:00
    @blueskea 在黑客派社区的个人设置中,更新一下 B3 Key 就行了。 关于同步内容这件事的详细介绍可以了解一下 https://hacpai.com/b3log
    dezhou9
        8
    dezhou9  
       2018-12-23 17:05:58 +08:00 via Android
    star 一下
    fox0001
        9
    fox0001  
       2018-12-24 12:59:35 +08:00 via Android
    当年 Spring 说自己是轻量级,把 EJB 那一套干掉了。但是,如果要再轻量级,为什么不考虑其它语言而死守 Java ?

    另外有个疑问,关于采用 json 代替 class 贯穿全局,如果数据库字段名改,改 json 不是更麻烦?毕竟 IDE 不能检查 json 对象 get 属性名是否正确。这样的话,只能全局字符串替换。class 的话,属性方法名变化后,IDE 直接报错,不是更方便。
    88250
        10
    88250  
    OP
       2018-12-25 07:35:09 +08:00
    @fox0001 如果项目大了,get 的字段名可以定义一下,这样也方便重构。不过改字段这事情一般很少碰到,数据库一般都是一次性就设计好吧。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2963 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 14:58 · PVG 22:58 · LAX 06:58 · JFK 09:58
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.