因为视图层(客户端)的内容较多,且比较琐碎,所以先来介绍一下服务层的设计。服务层的基本思想就是可编程地依赖注入方式。
概述
现在在服务层次流行使用 Spring,一种非常优秀的依赖注入方式。GreenTea 不是取代或者否定 Spring 的设计(坦率地说,也没有此能力),只是想使开发更简单。Spring 的想法很好,但在 SSH 框架中使用得有点过度,正所谓“手里有把锤子,看什么都是钉子”。一般来说,完成一个普通的功能,Spring 需要一个接口、一个实现类和配置文件里的一条记录(很久没有使用 Spring 了,现在还是这样么?)。这体现了接口和实现分离的实现,很好,但是这种做法真地在实际中发挥作用了么?在我做过的项目中,我发现 95% 的实现类没有更换过,换句话说,95% 的接口是没起到应有作用,反倒是增加了开发的累赘。
面向接口编程的思想没问题,但是需要考虑他是否能起到实际的效用。工程技术是一种权衡,是利与弊的取舍,所以没有最完美,只有最优的解决方案。Spring 最初的想法是想对 EJB 的开发进行简化,但是我感觉还不够简化。因此,我在服务层这个地方采用了“编程式”的方式来完成大多数依赖注入的问题。当然,你也可以在这个层次采用Spring,只不过是将GreenTea 的 Service 层退化成 Struts(老版本)的Action而已,也未尝不可。
原理
闲言少叙,来介绍GreenTea 的服务层的想法,实现很简单,类图如下:
ProcessorFactory 是一个处理器工厂,其作用就是生成或者创建一个新的Processor 实例(当然也可以使用对象池)。需要注意的是:在应用的整个生命周期中只有一个实例,因此不是线程安全的,在使用需要注意。一个应用可能需要多个处理器工厂,可在配置文件中直接添加。
Processor 是真正干活的类,负责给Service 注入其需要的信息。不同的Service 可能需要不同的信息,因此 Processor 需要能识别Service(如:Service需要实现特定的接口)。Processor 方法包括了 Serivce 方法调用的整个生命周期,因此可以对提供给Service的资源信息进行完全的控制。
Service 不需多言,即业务逻辑。
实现
上面说得稍微有些抽象,给出一个具体的实现即可以很清楚的了解其结构。这个实现以JPA实现注入为例,仅供参考,如果需要在工作中使用还需调整和健壮。
首先来看处理器工厂类。
public class JPAProcessorFactory implements ProcessorFactory {
private EntityManagerFactory factory;
public void onInit() {
// 首先创建一个持久化工厂类(此处省略多行配置代码)
factory = Persistence.createEntityManagerFactory("emName", properties);
}
public synchronized Processor createProcessor(Class serviceClass) {
// 如果服务类需要使用JPA进行持久化处理,则为其创建一个持久化处理器。
if (JPAAware.class.isAssignableFrom(serviceClass)) {
EntityManager em = factory.createEntityManager();
JPAProcessor processor = new JPAProcessor(em);
return processor;
}
return null;
}
public void onDestroy() {
// 关闭工厂
factory.close();
}
}
再来看一下“Processor”,也很简单,由他来完成信息的注入,已经方法调用时生命周期的所有一切。
public class JPAProcessor implements Processor {
private final EntityManager entityMananger;
public JPAProcessor(EntityManager entityMananger) {
this.entityMananger = entityMananger;
}
public void onCreate(Class serviceClass, Session session, Context context) {
}
public Object[] onBefore(Object service, Method method, Object[] params) {
// 在方法调用之前启动一个事物,并赋值给服务类
entityMananger.getTransaction().begin();
((JPAAware) service).setEntityManager(entityMananger);
return params;
}
public void onAfter(Object service, Object ret) {
// 方法顺利结束就提交事务
entityMananger.getTransaction().commit();
}
public void onFinally() {
}
public void onThrowable(Throwable t) {
// 出了问题就回滚事务
entityMananger.getTransaction().rollback();
}
}
接下来是“JPAAware”,其主要用来识别类是不是需要使用JPA 进行持久化,并注入所需的“实体管理器”。
public interface JPAAware {
public void setEntityManager(EntityManager entityManager);
public EntityManager getEntityManager(entityManager);
}
最后我们再来实现类。
public class UserService implements IService, JPAAware {
private EntityManager entityManager;
public void register(User user) {
// 直接使用“被注入”的实体管理器
entityManager.merge(user);
}
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
public EntityManager getEntityManager() {
return entityManager;
}
}
从上述代码中可以看出,在编写业务逻辑时,程序员不需要关心怎么得到实体管理器,怎样控制事务,怎样关闭工厂,这些事情都是“Processor”统一进行处理的。
在实际使用当中,可以将最常用接口及其基本实现抽象到一个AbstractService里面,那样代码可以做到惊人的简洁。如果我们希望使用 Spring,完全可以实现照猫画虎地实现一个 Spring 的 Processor。
特点
-
开发简单,虽然开发“Processor”稍微多了一点内容,但是只在项目初期使用,在开发业务逻辑时应该已经非常简单明了。
-
无需配置,减少了不必要耦合点。
-
摆脱对容器的依赖,使开发和单元测试更加简单。另外,解除了这层耦合关系,更有利于服务的重用。
服务的扩展
如果我们确实需要一个接口,以便在项目中随时可替换,GreenTea 也提供了两种方式:配置和约定。
配置,顾名思义,GreenTea 将在配置文件中查找接口和实现的对照关系。在配置中添加如下代码即可:
<param name="serviceMapping" type="map">
<entry key="com.xx.yy.AService" value="com.xx.yy.AAService" />
</param>
注意:AService 也可以是一个具体类,即:不局限于接口与实现的关系,也可以是父类与子类的关系。
约定是指通过隐含的方式来确定这种对照关系。比如: com.xx.yy.AService是一个接口或者抽象类,框架将尝试查找“com.xx.yy.AServiceImpl”作为其实现类。
已有实现
系统已经提供了几种基本实现,以资参考。
名称 | 说明 |
---|---|
IPOPersistenceProcessor | GreenTea 自带的一个简化版的 O/R 映射机制。 |
InjectorProcessor | 当需要在服务中调用另外一个服务时,最好使用这种服务注入方式,以保证调用方法在一个事务之内。 |
SessionProcessor | 为服务提供了Session的相关信息。 |
FileServiceProcessor | 为服务提供了统一的文件处理接口。我在工作中经常遇到的一个问题是有的程序员在使用文件之后忘记关闭,造成内存泄漏,导致了系统的不稳定。使用此接口可以在一定程度上减少此类问题的发生。 |
结论
一切都是为了简洁,最大化的减少耦合点。杀鸡用牛刀会有什么后果呢?
-
虽然重点,多花了些力气,但是和用杀鸡的刀的效果是一样的;
-
鸡虽然杀了,但也伤了自己的手;
-
鸡虽然杀了,但是多砍掉了一条腿;
-
在你抡起厚重的牛刀时散了腰,还让鸡给跑了。