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

描述

上一篇(一个六年经验的python后端是怎么学习用java写API的(4) RestAPI,dropwizard 的第一组API)写完第一组API后发现,每次实现一个resource,都需要在 Application.java里面的sessionFactory的config里面addMapper,然后在具体使用mybatis查询的时候也需要先拿到 session 然后再把这个mapper类拿出来,非常不方便 session.getMapper(ArticleMapper.class)

查阅资料发现java的一些web框架是通过一种叫做依赖注入的方式减少这些操作的,google-guice的这个wiki就讲解了依赖注入的核心。大概就是说,代码不需要导出new东西了,通过@inject注解减少很多代码。

代码

parrot tag: google_injector

依赖注入实现

https://github.com/google/gui...

大概就是需要继承 AbstractModule 然后 通过 configure 将各种类注册,然后需要的地方可以直接使用 injector.getInstance(注册的类.class);,或者直接在构造方法等地方使用 @Inject 等注解。

代码结构如下,可以看到主要是添加了module 和 service

 $ tree
.
└── com
    ├── loginbox
    │   └── dropwizard
    │       └── mybatis
    │           └── EnhancedMybatisBundle.java
    └── reworkplan
        ├── ParrotApplication.java
        ├── bundles
        │   ├── CorsBundle.java
        │   ├── GuiceBundle.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
        │   ├── MysqlMapperModule.java
        │   ├── ServiceModule.java
        │   └── provider
        │       └── MysqlMapperProvider.java
        ├── resources
        │   ├── ArticlesResource.java
        │   └── BasePath.java
        └── service
            ├── ArticleService.java
            └── ArticleServiceImpl.java        

InjectorFactory 的 createInjector 里面添加需要注册的各个Module,有些需要拿到config上下文。

public class InjectorFactory {  
    public Injector get(ParrotConfiguration configuration, Environment environment) {  
        Injector intjector = Guice.createInjector(  
                new ApplicationModule(configuration, environment),  
                new MysqlMapperModule(configuration, environment),  
                new ServiceModule()  
        );  
        return intjector ;  
    }  
}

先说简单的 service module,django的api的重逻辑是建议直接写在models.py里面的,跟django的逻辑不同的是,java除了直接操作数据库的dao层外,还需要一个service 层,而且很烦的一点在于要先实现一个service 抽象类,在实现一个serviceImpl的实现类,所以这个model主要就是将抽象类和实现类绑定。

public class ServiceModule extends AbstractModule {

    @Override
    protected void configure() {
        bindService(ArticleService.class, ArticleServiceImpl.class);
    }

    private  void bindService(Class interfaceClass, Class implClass) {
        bind(interfaceClass).to(implClass).in(Scopes.SINGLETON);
    }
}

写完这个之后再 Application.java 里面就可以直接这么写,environment.jersey().register(injector.getInstance(ArticlesResource.class));,然后在Resource初始化的时候直接使用 @Inject注解就完事了。

public class ArticlesResource {
    private final ArticleService articleService;

    @Inject
    public ArticlesResource(ArticleService articleService) {
        this.articleService = articleService;
    }
    ...
}

MysqlMapperModule类似,核心逻辑在于将 Mapper类在 config上注册,而之前知道在注册mapper的时候其实还用到了sessionFactory,所以还要在写一个实现了javax.inject.Provider的 MysqlMapperProvider

public class MysqlMapperModule extends AbstractModule {
    private final ParrotConfiguration configure;
    private final Environment enviroment;

    public MysqlMapperModule(ParrotConfiguration configuration, Environment environment) {
        this.configure = configuration;
        this.enviroment = environment;
    }

    @Override
    protected void configure() {
        addMapper(ArticleMapper.class);
    }

    @Provides
    @Singleton
    @Named("mysql")
    public SqlSessionManager getMysqlSqlSessionManager(@Named("mysql") SqlSessionFactory sqlSessionFactory) {
        return SqlSessionManager.newInstance(sqlSessionFactory);
    }

    @Provides
    @Singleton
    @Named("mysql")
    public SqlSessionFactory getMysqlSqlSessionFactory() {
        return this.configure.getMysqlSqlSessionFactory();
    }

    private  void addMapper(Class mapperType) {
        bind(mapperType).toProvider(guicify(new MysqlMapperProvider<>(mapperType))).in(Scopes.SINGLETON);
    }
}

import javax.inject.Provider;

public class MysqlMapperProvider  implements Provider {

    private final Class mapperType;

    @Inject
    @Named("mysql")
    private SqlSessionManager sqlSessionManager;

    public MysqlMapperProvider(Class mapperType) {
        this.mapperType = mapperType;
    }

    public void setSqlSessionManager(SqlSessionManager sqlSessionManager) {
        this.sqlSessionManager = sqlSessionManager;
    }

    @Override
    public T get() {
        return this.sqlSessionManager.getMapper(mapperType);

    }
}

写完这个之后对应的 serviceImpl 和 resouce这么写,

public class ArticleServiceImpl implements ArticleService {
    private final ArticleMapper articleMapper;

    @Inject
    public ArticleServiceImpl(ArticleMapper articleMapper) {
        this.articleMapper = articleMapper;
    }

    @Override
    public List
getList(Boolean isActive, Integer offset, Integer limit) { List
articles = articleMapper.selectAll(isActive, offset, limit); return articles; } @Override public Article get(Integer id, Boolean isActive) { return articleMapper.select(id, isActive); } @Override public Integer count(Boolean isActive) { return articleMapper.countAll(isActive); } } @Path(BasePath.ARTICLE_API) @Produces(APPLICATION_JSON) public class ArticlesResource { private final ArticleService articleService; @Inject public ArticlesResource(ArticleService articleService) { this.articleService = articleService; } @GET @Timed public MetaListResponse get(@DefaultValue(Constants.DEFAULT_PARAM_OFFSET) @QueryParam("offset") Integer offset, @DefaultValue(Constants.DEFAULT_PARAM_LIMIT) @QueryParam("limit") Integer limit){ MetaListResponse response = new MetaListResponse(); response.putMeta("count", 0); Boolean isActive = true; Integer count = articleService.count(isActive); List
articles = articleService.getList(isActive, offset, limit); response.putMeta("count", count); response.setData(articles); return response; } @Path("/{id}") @GET @Timed public MetaMapperResponse get(@NotNull @PathParam("id") Integer articleId) { MetaMapperResponse response = new MetaMapperResponse(); Boolean isActive = true; Article article = articleService.get(articleId, isActive); response.setData(article); return response; } }

对比下之前版本的Resource可以发现明显代码干净多了, 不用每次都在sessionFactory去拿Mapper.class了,之后实现完mapper、service后只要在对应的module的config里面添加一行即可。

@Path(BasePath.ARTICLE_API)
@Produces(APPLICATION_JSON)
public class ArticlesResource {
    private final SqlSessionFactory sessionFactory;

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

    @GET
    @Timed
    public MetaListResponse get(@DefaultValue(Constants.DEFAULT_PARAM_OFFSET) @QueryParam("offset") Integer offset,
                                @DefaultValue(Constants.DEFAULT_PARAM_LIMIT) @QueryParam("limit") Integer limit){
        MetaListResponse response = new MetaListResponse();
        response.putMeta("count", 0); 
        try (SqlSession session = sessionFactory.openSession()) {
            ArticleMapper articleMapper = session.getMapper(ArticleMapper.class);
            Boolean isActive = true;
            Integer count = articleMapper.countAll(isActive);
            List
articles = articleMapper.selectAll(isActive, offset, limit); response.putMeta("count", count); response.setData(articles); } return response; } @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; } }

一些感悟

至于为什么java要多出一层service和serviceImpl,看到目前我的感受就是静态语言太过于笨重,语言上有private、public本身的设计模式,model上要实现一个Bean就要写一大堆的东西,而这堆东西仅仅是基本的字段赋值取值,即使用了mybatis做orm,这个model层已然很重了,不适合做多个model之间的数据柔和等逻辑。

而django的model在orm拿出即用,可以说所有属性都是public的,所以不需要封装bean,全public的特性就使得直接揉其他model在语言特性上就轻的多。

当然java可以说我定义了一个service就不用关心具体子类是怎么实现的,但是写工程代码多了可能会听说过这句话,约定大于配置,同样的轻语言在各个不同子方法(类或者直接就是方法)实现的时候只要遵循约定的接口即可。

另外虽然java有强编译的特性,但是很多时候依然会遇到运行时各种空指针问题,所以说大工程下java天生强于轻语言是很蠢的一种说法,工程项目靠的还是大量的测试(单元、集成),部署监控策略(测试环境、灰度、放量上线)。

接下来

todo: 一个六年经验的python后端是怎么学习用java写API的(6) 基本的Auth

你可能感兴趣的:(java,dropwizard,依赖注入)