“围城”式困境中的依赖注入模式及Spring(2)
我所经历的项目中的IOC
的真实使用
古诗云,“只在此山中,云深不知处”。我们对新技术的使用同样会出现这样的困惑:我们往往把新技术拿来,根据一些教程或DEMO上的例子来一一实施,而不管这些技术对于我们的实际项目是否适用。
闲话少说,我们现在就来看看我们的一个实际的项目是怎么实现Spring框架的依赖注入的。
这个项目中对依赖注入模式的使用主要表现在两个方面:一是业务层对持久层数据的使用;二是Action类对业务层的使用。下面我将一一列出这个项目具体是如何使用依赖注入模式的,并且在列出实际应用的时候提出我的一些不同想法。
同时,也欢迎大家一起讨论。
1)
对持久层的依赖注入模式的使用情况
首先是在持久层做一个正面接口,如下:
package …….persistence;
public interface IndividualFacade
{
public BasicInfoDAO getBasicInfoDAO();
public DevelopmentDAO getDevelopmentDAO();
public ExperienceDAO getExperienceDAO();
public ServicesFbDAO getServicesFbDAO();
public ServicesGeneralDAO getServicesGeneralDAO();
public HrLogDAO getHrLogDAO();
public SkillDAO getSkillDAO();
public void setServicesGeneralDAO(ServicesGeneralDAO dao);
public void setServicesFbDAO(ServicesFbDAO dao);
public void setExperienceDAO(ExperienceDAO dao);
public void setDevelopmentDAO(DevelopmentDAO dao);
public void setBasicInfoDAO(BasicInfoDAO dao);
public void setSkillDAO(SkillDAO skillDAO);
public void setHrLogDAO(HrLogDAO hrLogDAO);
public void setOtherDAO(OtherDAO otherDAO);
public OtherDAO getOtherDAO();
}
很明显,这个接口没有其他的作用,正是为了我们使用依赖注入而特意增加的一个接口。在这个接口里,只有set和get方法。set方法说明在Spring框架里使用的依赖注入模式是赋值注入,而get方法是用来取得容器所注入的依赖对象。
这种对依赖注入模式的使用应该是我们在实际项目中最常用到的一个实例。即客户端所要使用的对象是组件或者服务,客户端不能控制;或者客户端所使用的对象是可能变化的,也不为客户端所能够控制。
上面的例子都可能出现这两种情况,即要么DAO的创建者和使用者不是同一个开发者所开发,这种情况在项目组也很常见;要么DAO的情况很复杂,极有可能会变化,如有可能在某些情况下是对数据库的访问,某些情况下又是对XML文件的访问,还有可能是对文本文件的访问。
在上面所列举的情况下,正是使用依赖注入模式的最好场合。我们只需要简单的做一个正面(Façade模式的使用),这个正面里面只有set和get方法。我们DAO的开发者通过IOC容器将DAO的实现set到该正面里面去,然后DAO的使用者通过该正面的get方法将DAO的实例取出来,就可以使用了。
这样可以做到将DAO的开发和DAO的客户层的开发分开,只是到了测试的时候,将这两个部分分别插入到容器去即可。
通过上面的分析,可以看出:这里正是使用依赖注入模式的最好地方。但我们仔细看看上面的代码,仍然可以很容易的提出一些疑问。
第一个疑问就是:每一个set和get方法都依赖于一个具体的实现类,而不是依赖于一个抽象,这正是我们在面向对象的编程中所忌讳的。很明显,这样的依赖注入不能很好地实现扩展。如果我现在的DAO实现的是一个对数据库的访问,我们将不能将它扩展到对XML文件的访问。
大家可能会反驳我,我根本就没想过要更换数据源,因为对于我的项目来说,数据源不太可能会发生变化。好,我再问一句,你的项目的前后台设计编码是分开的吗?果然你的回答还是否定的话,那么在这里对依赖注入模式的使用是毫无作用的,甚至能延缓你的项目开发进度。因为你不得不花费很多时间来做IOC容器的XML配置,这种配置是极为繁琐和容易出错的。
即使你的项目的前后台编码是分开的,但如果在项目的进行中,前后台编码人员容易交流的话,这种情况下对依赖注入的使用也未必有开发人员之间直接交流好。因为对XML配置文件的处理很容易出错,而前后台开发人员之间的直接交流则容易得多。
在这样的一种情况下,前后台的开发人员不容易交流,如他们分布在两个不同的城市里。在这种情况下,使用依赖注入模式才真正的发挥了它的作用。
第二个疑问就是,我们不明白为什么要使用这个接口?这个接口对于我们来说,毫无价值可言。我们完全可以直接使用一个有set和get方法的POJO类。
我们再来看看该接口的实现类:
package …….persistence;
public class IndividualImpl implements IndividualFacade
{
private BasicInfoDAO basicInfoDAO;
private DevelopmentDAO developmentDAO;
private ExperienceDAO experienceDAO;
private ServicesFbDAO servicesFbDAO;
private ServicesGeneralDAO servicesGeneralDAO;
private HrLogDAO hrLogDAO;
private SkillDAO skillDAO;
private OtherDAO otherDAO;
/**
* @return Returns the otherDAO.
*/
public OtherDAO getOtherDAO() {
return otherDAO;
}
/**
* @param otherDAO The otherDAO to set.
*/
public void setOtherDAO(OtherDAO otherDAO) {
this.otherDAO = otherDAO;
}
public BasicInfoDAO getBasicInfoDAO() {
return basicInfoDAO;
}
public void setBasicInfoDAO(BasicInfoDAO basicInfoDAO) {
this.basicInfoDAO = basicInfoDAO;
}
public ServicesGeneralDAO getServicesGeneralDAO() {
return servicesGeneralDAO;
}
public void setServicesGeneralDAO(ServicesGeneralDAO servicesGeneralDAO) {
this.servicesGeneralDAO = servicesGeneralDAO;
}
public DevelopmentDAO getDevelopmentDAO() {
return developmentDAO;
}
public void setDevelopmentDAO(DevelopmentDAO developmentDAO) {
this.developmentDAO = developmentDAO;
}
public ExperienceDAO getExperienceDAO() {
return experienceDAO;
}
public void setExperienceDAO(ExperienceDAO experienceDAO) {
this.experienceDAO = experienceDAO;
}
public ServicesFbDAO getServicesFbDAO() {
return servicesFbDAO;
}
public void setServicesFbDAO(ServicesFbDAO servicesFbDAO) {
this.servicesFbDAO = servicesFbDAO;
}
public HrLogDAO getHrLogDAO()
{
return hrLogDAO;
}
public void setHrLogDAO(HrLogDAO hrLogDAO)
{
this.hrLogDAO = hrLogDAO;
}
public SkillDAO getSkillDAO()
{
return skillDAO;
}
public void setSkillDAO(SkillDAO skillDAO)
{
this.skillDAO = skillDAO;
}
}
看了上面的这个实现类,更加证明了我们上面的说法,即IndividualFacade接口是毫无意义的。我们在IndividualImpl类里去掉IndividualFacade接口的话,对我们的功能和扩展有什么影响吗?
IndividualFacade接口到底对我们有什么作用?
第三个疑问是,IndividualFacade和IndividualImpl在一个包里。假如IndividualFacade真的需要有不同的扩展的话,这两个类在一个包里能够被扩展吗?
下面是一些配置文件:
……
……
从上面的配置示例中,我们可以看到,Spring的配置是比较繁琐的,这样繁琐的配置也是极为容易出错的。所以在我们使用Spring而没有对我们的项目开发有太大的好处时,我们不要勉强使用Spring,因为我们极有可能在这些配置上浪费了大量的时间。
2)
对业务层的依赖注入模式的使用情况
前面提出了使用依赖注入模式的两种情况,即第一是所注入的依赖容易发生改变的情况;第二是依赖和对依赖的使用分开开发的情况。
很显然,在业务层的开发过程中,第一种情况是不大可能发生的,因为相对于表现层,业务层是不大可能发生变化的。在实际情况下,我们多数会遇到对于同一个业务,有不同的表现层。而很少有对于相同的表现层有不同的业务层的情况。
所以对于业务层使用依赖注入模式的原因只有一个,那就是业务层和表现层分开开发,最后他们以插件的形式集成到一起,形成一个完整的系统。
下面我们来看看我们是怎么使用Spring的:
package com.freeborders.psa.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.freeborders.psa.domain.individual.BasicInfoModel;
import com.freeborders.psa.domain.individual.CertificationModel;
/**
* This interface is used for BasicInfoCertificationManager to implement.
* It defines the actions for the basic info and Certification of an employee.
*/
public interface BasicInfoCertificationService
{
/**
* @param employeeId
* @return int
* @roseuid 42E66C35032C
*/
public int deleteStaffInfo(long employeeId) throws Exception;
/**
* @param employeeId
* @return BasicInfoModel
* @roseuid 42E66C89030D
*/
public BasicInfoModel queryBasicInfo(long employeeId) throws Exception;
/**
* @param basicInfo
* @return int
* @roseuid 42E66CE302FD
*/
public int saveBasicInfo(BasicInfoModel basicInfo) throws Exception;
public int updateBasicInfo(BasicInfoModel basicInfo) throws Exception;
/**
* @param employeeId
* @return CertificationModel
* @roseuid 42E66D6B00DA
*/
public CertificationModel queryServicesFb(long employeeId,int type) throws Exception;
/**
* @param servicesFb
* @return int
* @roseuid 42E66DBD01C5
*/
public int saveServicesFb(CertificationModel servicesFb,int type) throws Exception;
public ArrayList queryNestedDictById(long id) throws Exception;
public Collection queryAllBasic() throws Exception;
public void saveLog(long employeeId) throws Exception;
public String queryLog(long employeeId) throws Exception;
public List queryOrg() throws Exception;
}
首先还是一个接口,前面已经分析过了,业务层不太可能发生变化,在这种情况下,这个接口就显得有点多余。不错,面向对象的编程强调的是对接口编程。但是,对接口的使用也不能滥用。比如,这个接口很难看出它有什么用处。
下面我们来看具体对这个接口的实现:
public class BacicInfoCertificationManager implements BasicInfoCertificationService
{
private HrLogDAO hrLogDAO = null;
private BasicInfoDAO basicInfoDAO = null;
private DictDAO dictDAO = null;
private ServicesGeneralDAO serviceGeneralDAO = null;
public BacicInfoCertificationManager(ApplicationContext applicationContext)
{
this.hrLogDAO = applicationContext.getIndividualFacade().getHrLogDAO();
this.basicInfoDAO = applicationContext.getIndividualFacade().getBasicInfoDAO();
this.dictDAO = applicationContext.getAdminFacade().getDictDAO();
this.serviceGeneralDAO = applicationContext.getIndividualFacade().getServicesGeneralDAO();
}
//list all of the employee info.
public Collection queryAllBasic() throws Exception
{
return this.basicInfoDAO.queryAll();
}
/**
* This function should be kept for extending the system.
*/
public int deleteStaffInfo(long employeeId) throws Exception
{
return 0;
}
public List queryOrg() throws Exception
{
return this.basicInfoDAO.queryOrg();
}
/**
* get the basic info of the employee listed.
*/
public BasicInfoModel queryBasicInfo(long employeeId) throws Exception
{
//get the other basic info
BasicInfoModel model = this.basicInfoDAO.query(employeeId);
//get department
List col = this.basicInfoDAO.queryEmpOrg(employeeId);
if(col.size()>0)
{
model.setDepartmentId(Long.parseLong((String)col.get(0)));
}
//get roles
ArrayList list1 = this.basicInfoDAO.queryRole(employeeId);
int[] roleIds = new int[list1.size()];
for (int i=0; i
{
roleIds[i] = Integer.parseInt((String) list1.get(i));
}
model.setRoleIds(roleIds);
//get educations.
ArrayList list2 = this.basicInfoDAO.queryEdu(model.getCandidateId());
model.setEducations((EducationModel[])list2.toArray(new EducationModel[0]));
return model;
}
……
}
我们可以看到,在BacicInfoCertificationManager类里,如果不实现BasicInfoCertificationService接口,对我们的整个功能也不会产生什么影响。
而且这一个部分对依赖注入模式的使用,没有持久层那么好,如果在这里也像在持久层那样,做一个正面,那么看起来情况就会好一些。
下面我们来看,它是怎么被注入的:
public static Object getBasicService()
{
return appCtx.getBean("basicInfoCertificationService");
}
以上就是我们在一个实际的项目过程中使用Spring的依赖注入模式的情况。在使用过程中的一些概念的混淆,给我们造成了很多的麻烦。到最后,我们也没有体会到使用Spring的依赖注入模式的好处,反倒在配置文件的编写中遇到了很多的麻烦。正是项目中遇到的问题,促使我们深入的探讨Spring及依赖注入模式。
正是在这些探讨过程中,我们有了很多的感触,促使我把这些感触写出来,和大家一起讨论。