python依赖注入_一个六年经验的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

依赖注入实现

大概就是需要继承 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 extends T> 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值