描述

上一篇(一个六年经验的python后端是怎么学习用java写API的(3)Java 开发环境搭建)开发环境准备完成后,就可以开始看java语法和框架了。

代码

parrot tag: first_api

dropwizard 与 django 的简单对比

dropwizard

java
└── com
    └── reworkplan
        ├── ParrotApplication.java
        ├── bundles                                                                                                    
        │   ├── CorsBundle.java
        │   └── MysqlBundle.java
        ├── common
        │   ├── Constants.java
        │   └── response
        │       ├── MetaListResponse.java
        │       └── MetaMapperResponse.java
        ├── config
        │   └── ParrotConfiguration.java
        ├── mappers
        │   ├── ArticleMapper.java
        │   └── handlers
        │       └── DateTimeTypeHandler.java
        ├── models
        │   └── Article.java
        ├── modules
        │   ├── ApplicationModule.java
        │   └── InjectorFactory.java
        └── resources
            ├── ArticlesResource.java
            └── BasePath.java

django

├── common
│   ├── __init__.py
│   ├── constants.py
│   ├── data_structure.py
│   ├── db.py
│   ├── exceptions.py
│   ├── oauth_utils.py
│   ├── permissions.py
│   ├── rest.py
│   ├── sms.py
│   ├── tools.py
│   ├── upload.py
│   └── weixin.py
├── config
│   ├── __init__.py
│   ├── settings
│   │   ├── __init__.py
│   │   ├── common.py
│   │   ├── development.py
│   │   ├── private.example.py
│   │   └── production.py
│   ├── urls.py
│   └── wsgi.py
├── extracter
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── manage.py  

web 框架的设计都是大同小异的,概念映射如下:

  • 主程序入口: django wsgi.py;dropwizard ParrotApplication.java
  • 环境变量等常量配置:django settings目录;dropwizard Config 目录 ParrotConfiguration.java,另外还有个配置文件config/environment.yml
  • 普通常量配置:django没有这个概念,我一般放在common.constants下;dropwizard我创建了个common Constants.java
  • 路由定义:django 的 urls.py;dropwizard 他是放在Resource类上通过Path注解实现,然后在ParrotApplication.java中注册Path类
  • 具体API实现:django 的 views.py 中继承自 APIView 的类,配置在urls.py中;dropwizard 的 Resource目录下的类,需要在然后在ParrotApplication.java中注册
  • 数据库的ORM:django 中 的 models.py;dropwizard 使用 mybatis,对应 mappers目录下的类,每个mappers还要在还需resources的xml目录下添加mybatis格式的xml去写具体的sql,并且需要写一个jdbc的sqlsession,需要在ParrotApplication中注册mapper和sqlsession,要自己做很多的基础工作(返回db的datetime需要自己写时间格式的handler等等)
  • 处理逻辑:django是直接在models上实现一些逻辑,这对应于dropwizard的service目录(代码写到这暂时没有逻辑需要处理所以没创建service),具体api接口需要柔和多个逻辑,django直接调用各个model上的逻辑+api内在写一部分;dropwizard则在resource.java里面调用各个service的Impl类组合逻辑
  • middleware:django 实现 middleware.py,在settings中配置,可以放在任何模块下;java放在一个叫做bundles的目录下,在ParrotApplication中注册

大概就是这样,另外java还有叫做注入的东西,所以我创建了modules目录 希望通过InjectorFactory做一些事情,暂时没调通

实现第一个 API Get /articles/{id}

实现 resource

  • 通过Path注解定义api的路由
  • Produces注解说返回一个json
  • GET注解说明的restful的get方法
  • 方法内的 PathParam注解是接受路由参数id
  • 方法内则需要一个mapper去执行数据库的操作
  • 定义一个自定义的reponse对象(或者不定义直接返回model对象),我习惯返回这两种格式: 列表 {"meta":{}, "data": []}, 单个元素或者普通数据 {"meta":{}, "data": {}}
@Path(BasePath.ARTICLE_API)
@Produces(APPLICATION_JSON)
public class ArticlesResource {
    private final SqlSessionFactory sessionFactory;

    public ArticlesResource(SqlSessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @Path("/{id}")
    @GET
    @Timed
    public MetaMapperResponse get(@NotNull @PathParam("id") Integer articleId) {
        MetaMapperResponse response = new MetaMapperResponse();
        try (SqlSession session = sessionFactory.openSession()) {
            ArticleMapper articleMapper = session.getMapper(ArticleMapper.class);
            Boolean isActive = true;
            Article article = articleMapper.select(articleId, isActive);
            response.setData(article);
        }
        return response;
    }
}

实现mapper

  • 实现一个interface,定义mapper的方法
  • 然后需要在 resources的xml目录下实现这个mapper定义方法的sql,例如 java中定义了个Article select(@Param("id") Integer id, @Param("isActive") Boolean isActive), 那么xml里面就需要有这个东西的定义 <select id="select" parameterType="hashmap" resultMap="ArticleResultMap">
  • xml中定义resultMap是指将sql返回的内容格式化到这个resultMap里面,类似django的serializer,但是并不智能,例如datetime他就不知道怎么处理,然后会报错,需要实现一个mappers.handlers.DateTimeTypeHandler, 然后在 bundles.MysqlBundle 里面注册这个handler,才能正确处理datetime格式的字段
  • xml中的parameterType="hashmap",对应于interface里面的Param注解,如果没有会报找不到参数
  • 回调resource里面发现,ArticleMapper articleMapper = session.getMapper(ArticleMapper.class); mapper需要通过session.getMapper的形式,而session是在ParrotApplication中注册并且添加了ArticleMapper这个类, SqlSessionFactory sessionFactory = mysqlBundle.getSqlSessionFactory(); sessionFactory.getConfiguration().addMapper(ArticleMapper.class);
public interface ArticleMapper {  
    Article select(@Param("id") Integer id, @Param("isActive") Boolean isActive);  
    List<Article> selectAll(@Param("isActive") Boolean isActive,  
                            @Param("offset") Integer offset,  
                            @Param("limit") Integer limit);  
    Integer countAll (@Param("isActive") Boolean isActive);  
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.reworkplan.mappers.ArticleMapper">

    <resultMap id="ArticleResultMap" type="com.reworkplan.models.Article">
        <id property="id" column="id"/>
        <result property="title" column="title"/>
        <result property="cover" column="cover"/>
        <result property="description" column="description"/>
        <result property="is_active" column="is_active"/>
        <result property="created" column="created" />
    </resultMap>

    <select id="select" parameterType="hashmap" resultMap="ArticleResultMap">
        SELECT id, title, cover, description, is_active, created
        FROM extracter_wxarticle WHERE id = #{id}
        and is_active=#{isActive} and is_delete=0
    </select>

    <select id="countAll" parameterType="hashmap" resultType="int">
        SELECT count(*)
        FROM extracter_wxarticle
        WHERE
        is_active=#{isActive} and is_delete=0
    </select>

</mapper>

ParrotApplication

  • 主入口在main,而run时候执行的命令是 server config/environment.yml,推测Enviroments读的是config/environment.yml
  • initialize和run的先后顺序目前我不清楚,可以看到initiallize里面注册的是middleware,run里面注册的是mapper和resource
public class ParrotApplication extends Application<ParrotConfiguration> {
    private final InjectorFactory injectorFactory;
    private final MysqlBundle mysqlBundle;

    public ParrotApplication() {
        this.injectorFactory = new InjectorFactory();
        this.mysqlBundle = new MysqlBundle();
    }

    public ParrotApplication(InjectorFactory injectorFactory) {
        this.injectorFactory = injectorFactory;
        this.mysqlBundle = new MysqlBundle();
    }
    @Override
    public void initialize(Bootstrap<ParrotConfiguration> bootstrap) {
        bootstrap.addBundle(new TemplateConfigBundle());
        bootstrap.addBundle(new Java8Bundle());
        bootstrap.addBundle(new MultiPartBundle());
        bootstrap.addBundle(new CorsBundle());
        bootstrap.addBundle(mysqlBundle);
        super.initialize(bootstrap);
    }

    @Override
    public void run(ParrotConfiguration configuration, Environment environment) throws Exception {
        SqlSessionFactory sessionFactory = mysqlBundle.getSqlSessionFactory();
        sessionFactory.getConfiguration().addMapper(ArticleMapper.class);

        environment.jersey().register(JodaTimeParamConverterProvider.class);
        environment.jersey().register(new ArticlesResource(sessionFactory));
    }
    public static void main(String[] args) throws Exception {
        new ParrotApplication(new InjectorFactory()).run(args);
    }
}

接下来

一个六年经验的python后端是怎么学习用java写API的(5) Service 和 google 依赖注入


D咄咄
1.7k 声望257 粉丝

Life is to short, please use python.