依赖注入工具 jBeanBox 的作者 drinkjava 同学最近在 Actframework gitee 项目 的提出了如下评论:
你这个DI工具的出发点可能有问题,一个MVC工具为什么要引入DI依赖注入? 这个和Spring或Guice的功能重叠了。直接引入Spring或Guice的不好吗? 按我的观点,DI唯一比较经典的用法只是用来进行声明式事务才需要,从这个角度出发,就必须要DI支持AOP切面功能,而你却没有加入这个功能,这就很尴尬了,当需要声明式事务的时候,不得不引入一个支持AOP的DI工具,例如Spring/Guice/jFinal,这就造成了使用ACT的项目随时都具备了2套DI工具,也就是说你自带的DI工具实际上是多余的,尤其在流行的Boot环境下,各种配置都是建立在Spring-Core这个IOC/AOP工具基础上,别人不大可能移到Genie内核上。
另外考虑一下支持多种模板输出,如包括PDF输出,这才是MVC的V层要做的事,可以参见SpringMVC和Jfinal,必要时可抄它们的源码。jFinal的问题是DAO、IOC、MVC混成一团,是优点,更是一个大缺点,希望你将主要精力集中在MVC,将它做成一个精致、干净的后台表现层,不要介入任何DI、AOP、DAO、事务的工作,这方面优秀的、流行的工具太多,没必要重造轮子。你现在这个HTTP内核也是自已做的,为了一点点效率或钻牛角尖的小特性,放弃通用性,也是冒风险的,在项目MVC架构选型的第一步就可能被Pass,如果能将HTTP内核和MVC分开,比如说ACT的MVC即可以和自已的内核合用,也可以和Jetty或内嵌Tomcat内核合用,这才是一个比较优秀的架构。
首先感谢 drinkjava 同学的意见,看得出来是问题是认真思考之后提出来的. 下面就问题中的两段意见分别作答.
这个问题有两个地方值得商榷:
下面我们就 javadrink 同学上面的关切来谈谈 Act 的依赖注入机制与应用.
这里是来自 Wikipedia 对依赖注入的定义 "In software engineering, dependency injection is a technique whereby one object supplies the dependencies of another object". 简单地说就是对象的状态不由自己来创建,而是交给另外的对象来注入. 举个例子:
public class UserService {
@Inject
private Dao<User> userDao;
@GetAction("/users/{userId}")
public User get(String userId) {
return userDao.findById(userId);
}
}
上面是一个简单的 UserService 端口. 其中需要使用对应与 User
实体类的 Dao
. 在上面的代码中我们没有看到 userDao
是如何初始化的, 因为 userDao
是 Act 框架在实例化 UserService
的时候注入的. 这就是一个典型的 Act 应用依赖注入的方式. 当然 Act 对于依赖注入的使用还有其他的扩展. 我们稍后会提到.
现在我来回答 drinkjava 的这个问题: "直接引入 Spring 或 Guice 的不好吗?". 实际上在开发 Genie 之前, Act 尝试过另外两种依赖注入:
在 Act 正式发布之前, 这上面两种注入都曾经在 act 0.x 版本中进入过实际项目 (当然是老码农所在公司的 - 开坑自己先踩是老码农做开源的基本原则).最终我选择了自己开发 Genie 来提供 Act 的依赖注入, 主要原因有一下几点:
Feather 简洁的代码实现最终激励了我启动了 Genie 项目, 这个依赖注入库完整实现了 JSR 330, 同时提供了一些有趣且实用的扩展, 比如注入集合类型数据以及注入值数据 等, 这些扩展对实现 Act 里面的 @LoadResource
, @LoadConfig
还有 @Configuration
至关重要. 另外因为代码实现比较紧凑, 运行时效率也很不错, 在多项测试中都领先 Guice; 具体数据可以参考这个项目
接下来说说 Act 对传统依赖注入的第一个扩展扩展: 注入请求处理方法参数.
上面那个 UserService
的例子是经典的依赖注入使用方式, 除了将依赖注入字段, Act 还允许直接将服务注入请求处理方法, 例如:
@GetAction("/users/userId")
public User get(String userId, Dao<User> userDao) {
return userDao.findById(userId);
}
上面代码中, get
方法有两个参数, userId
和 userDao
, 其中 userId
绑定到 URL 路径参数, 假如请求是 /users/abc123
, 那 userId
的值就是 abc123
; 而第二个参数 userDao
就是依赖注入了, 这个和前面注入到 userDao
字段是一样的. 但
又例如:
@PostAction("login")
public void login(String email, char[] password, ActionContext context) {
User user = userDao.findByEmail(email);
badRequestIf(null == user, MSG_BAD_EMAIL_PASSWORD);
badRequestIfNot(user.verifyPassword(password), MSG_BAD_EMAIL_PASSWORD);
context.login(user);
}
上面代码中的 ActionContext
也是注入的对象.
得益于 Genie 的扩展机制, Act 中可以很轻易地注入加载资源和配置参数.
public static class Dao extends MorphiaDao<Contact> {
@LoadResource("industry_type.mapping")
private Map<String, String> industryTypeMapping;
@Configuration("sql.url")
private String jdbcUrl;
...
}
上面的代码来自实际项目, 其中使用了两种扩展注入:
@LoadResource("industry_type.mapping)
将资源文件 industry_type.mapping
的内容加载进 Map<String, String> industryTypeMapping
字段@Configuration("sql.url")
将配置项 sql.url
的值加载进 String jdbcUrl
字段资源文件 industry_type.mapping
的内容为:
AG1 - Agriculture=Agriculture
AG2 - Agribusiness=Agribusiness
AG3 - Hospitality=Tourism and Hospitality
AG4 - Food Manufacturing/Processing=Food Manufacturing/ Processing
...
可以看出依赖注入在这种场景的使用减少了 boilerplate 代码的使用, 让应用代码变得更加简洁易懂.
通过上面关于依赖注入机制的介绍, 可以看出依赖注入在 Act 应用中是基本的机制, 而 drinkjava 同学在问题中表达的观点 "DI唯一比较经典的用法只是用来进行声明式事务才需要" 完全不能阐述依赖注入在 Act 框架的作用.
drinkjava 同学提出 "DI唯一比较经典的用法只是用来进行声明式事务才需要,从这个角度出发,就必须要DI支持AOP切面功能,而你却没有加入这个功能". 这个我完全不能理解, DI 和 AOP 是完全不同的概念, 我从来不知道 DI 需要支持 AOP, DI 的 Java 标准 JSR 330 也完全没有提到这个特性. 而 Wikipedia 上 AOP 的页面 也根本没有谈到 Dependency Injection
的概念. 把这两个放在一起 Google 了一下, 发现这篇文章详细分解了一下这两个概念, 有兴趣的同学可以点击进去看看.
Act 目前不支持 AOP, 但 Act 提供的 SQL DB 插件, 包括 act-ebean, act-hibernate 以及 act-eclipselink 都支持声明式事务. 具体应用代码可以参考下面几个示例项目:
act-ebean
和 act-hibernate
, act-eclipselink
对声明式事务的实现机制是不同的.
我并不认为 AOP 对于应用开发来说是一个非常重要的特性, 也一直没有动手做 AOP 的支持. 我的理由如下:
反过来讲, AOP 的适用场景都能采用专门的机制来实现, 对于应用来讲可以写出更加简洁容易的代码, 而且没有性能上的损耗. 这里我可以断言 drinkjava 同学评论中的说法 "当需要声明式事务的时候,不得不引入一个支持AOP的DI工具,例如Spring/Guice/jFinal,这就造成了使用ACT的项目随时都具备了2套DI工具,也就是说你自带的DI工具实际上是多余的" 是不成立的. 在 Act 中使用声明式事务以及我上面提到的另外两种 AOP 应用场景都不需要 AOP.
这其实不是问题, 是一条建议. 看到这个建议我感觉 drinkjava 同学可能还不太熟悉 Act 的模板输出机制. views 示例项目展示了 Act 中同时使用多种不同的模板引擎的特性, 包括:
而 response-type 示例项目中展示了 Excel 模板的输出 (采用 JXLS 引擎). 可以说 Act 的模板输出框架是足够满足 (同时) 使用多种模板的. 当然到目前位置我还没有开发 PDF 的模板插件, 这个可以作为今后的一个工作.
这里回答 drinkjava 同学评论的最后一部分:
"jFinal的问题是DAO、IOC、MVC混成一团,是优点,更是一个大缺点,希望你将主要精力集中在MVC,将它做成一个精致、干净的后台表现层,不要介入任何DI、AOP、DAO、事务的工作,这方面优秀的、流行的工具太多,没必要重造轮子。你现在这个HTTP内核也是自已做的,为了一点点效率或钻牛角尖的小特性,放弃通用性,也是冒风险的,在项目MVC架构选型的第一步就可能被Pass,如果能将HTTP内核和MVC分开,比如说ACT的MVC即可以和自已的内核合用,也可以和Jetty或内嵌Tomcat内核合用,这才是一个比较优秀的架构。"
最后再次感谢 drinkjava 的评论, 很认真, 信息量很多, 所以我也很认真地使用一篇来回答你的评论.