首页 > 代码库 > VO(DTO)存在的必要性,以及使用工厂模式+模版模式+自省实现可拓展VO
VO(DTO)存在的必要性,以及使用工厂模式+模版模式+自省实现可拓展VO
引子:
想起以前第一个项目的时候,使用springMvc+mybatis+restful实现一个论坛的网站,那个时候因为还不知道VO层的存在(因为一直使用MVC三层架构)。为了不想重复写get,set方法(把po的数据封装到map或者新的bean),所以直接从数据库里面读取出来的po就直接封装成json反馈到前端,很多重要的数据字段如用户密码这些都直接抛给前端,数据泄漏了出去。
后来使用了数据库的视图方法,但是效果非常不好,视图的拓展性非常低,前端要求添加或者删除一个字段,都要从数据库底层开始改起,浪费了非常多的时间。直到后来,我认识到了VO,自省,反射,与工厂模式,模版模式(现在学了动态代理,应该可以在上面做文章,不过现在还没有好的想法)。
概念扫盲
我们现在大多数的应用,我想都是基于分层架构的:
Web层(Struts2 or SpringMVC等)App应用层(Service)Dao层(ORM)DB
PO:也就是一般概念上的Domain Object,持久化对象模型,如hibernate 中的Entity.一般用于Service层–Dao层间的数据传输。
DTO(VO):也就是一般意义上的VO,封装后的对象。一般用于Web层—Service层间的数据传输入。
为什么要使用VO:
因为当你封装JSON的时候很多时候不需要数据表里面的全部数据,且变化不定,如果有一天突然想要这个字段,又有一天想要这个表里面没有的字段,而需要通过连表或者懒加载别的表的字段,那你可以通过修改VO的属性能达到这个动态性的拓展。
JavaEE各层之间解耦,这是从设计角度来说的。也就是说Domain Object(PO)直接封死在Dao层。高内聚,低耦合是我们追求的一个目标。
如何实现可拓展VO封装
我的包现在是这样的
咱们先来看一下BaseVoUtil 类,实现对VO,PO想相同数据字段的封装
/**
*
*/
package com.ruiyi.utils;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.log4j.Logger;
/**
* @author jiangjintai
* @param <V>
*
*/
public class BaseVoUtil {
//T代表PO,V代表VO
public static <T,V> V getVo(T tb,Class<V> voClazz) throws IntrospectionException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
//获取vo的全部属性值
Field[] fields = voClazz.getDeclaredFields();//获取所有域名
//并创建一个VO对象
V vo=voClazz.newInstance();
//获取tb的全部属性名
Field[] fieldsTb = tb.getClass().getDeclaredFields();
List<String> fieldNameList = new ArrayList<String>();
for(Field field : fieldsTb){
fieldNameList.add(field.getName());
}
for(Field field : fields){
//获取vo里面的写方法
PropertyDescriptor voPropDesc=new PropertyDescriptor(field.getName(),voClazz);
Method methodWrite =voPropDesc.getWriteMethod();
//获取tb里面的读方法
//如果tb里面存在Vo里面的字段值,就会自动copy
if(fieldNameList.contains(field.getName())){
PropertyDescriptor tbPropDesc=new PropertyDescriptor(field.getName(),tb.getClass());
Method methodRead =tbPropDesc.getReadMethod();
methodWrite.invoke(vo,methodRead.invoke(tb));
}
}
//返回一个VO
return vo;
}
}
这样我们就可以实现基本的数据封装,那我们如何获取那些该PO里面没有,又存在另外一个表的字段值呢?
使用SpringUtil
这个没有什么特殊性,只要完全抄过去的可以了,然后在spring配置文件中注册就可以用了,用途是取spring对象池里面的对象
/**
*
*/
package com.haizhi.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* @author jiangjintai
*
*/
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext appContext;
/* (非 Javadoc)
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
@Override
public void setApplicationContext(ApplicationContext arg0)
throws BeansException {
//
SpringUtil.appContext=arg0;
}
public static Object getBean(String name){
return appContext.getBean(name);
}
}
<!-- 注解springbean提取工具 -->
<bean id="SpringUtil" class="com.haizhi.util.SpringUtil"></bean>
好,接下来就又工厂类来对VO进行分装建筑,我们使用的是抽象工厂类
先看看抽象工厂
BaseFactory
/**
*
*/
package com.ruiyi.vo.factory;
import java.beans.IntrospectionException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import com.ruiyi.utils.BaseVoUtil;
/**
* @author jiangjintai
*
*/
public abstract class BaseFactory<T,V extends Object> implements Factory<V>{
private T tb;
private Class<V> clazz;
private V vo;
//构造时需要传入PO,与VO的class
public BaseFactory(T t ,Class<V> clazz) throws InstantiationException, IllegalAccessException {
this.clazz = clazz;
this.tb=t;
}
//方便复用
public void setTb(T tb){
this.tb=tb;
}
//调用该方法造一个VO
public V build() throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException{
vo = BaseVoUtil.getVo(getTb(), clazz);//普通字段copy
doOrderThingForVo(vo);//特殊字段注入
return vo;
}
/**
* jiangjintai
* 2016年8月14日
* @param vo2
*/
//把需要处理的特殊字段交给子类
protected abstract void doOrderThingForVo(V vo);
//给子类提供一个途径可以访问po
protected T getTb(){
return this.tb;
}
}
好,现在为具体的VO做一个工厂,这里的VO假定是ClientOrderVo,这里的PO假定是TbOrder
ClientOrderVoFactory
/**
*
*/
package com.ruiyi.vo.factory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import com.ruiyi.entity.TbOrder;
import com.ruiyi.entity.TbOrderService;
import com.ruiyi.entity.TbService;
import com.ruiyi.entity.User;
import com.ruiyi.service.OrderService;
import com.ruiyi.service.OrderServiceService;
import com.ruiyi.service.SysUserService;
import com.ruiyi.service.UserService;
import com.ruiyi.utils.SpringUtil;
import com.ruiyi.vo.ClientOrderVo;
/**
* @author jiangjintai
*
*/
public class ClientOrderVoFactory extends BaseFactory<TbOrder, ClientOrderVo> {
//通过springUtils取得一个service
UserService userService = (UserService) SpringUtil.getBean("userService");
/**
* @param t
* @param clazz
* @throws InstantiationException
* @throws IllegalAccessException
*/
public ClientOrderVoFactory(TbOrder t)
throws InstantiationException, IllegalAccessException {
//在这里直接写入一个VO的class
super(t, ClientOrderVo.class);
}
//为VO的特殊字段做处理
/* (非 Javadoc)
* @see com.ruiyi.vo.factory.BaseFactory#doOrderThingForVo(java.lang.Object)
*/
@Override
protected void doOrderThingForVo(ClientOrderVo clientOrderVo) {
//下面的内容就是我为我的VO设置一些特殊的值,可以不用细看
clientOrderVo.setRegionName(getTb().getTbRegion().getRegionName());
clientOrderVo.setRegionId(this.getTb().getTbRegion().getRegionId());
User user = userService.getUser(this.getTb().getUserId());
clientOrderVo.setUserName(user.getName());
Set<TbOrderService> tbOrderServiceSet = this.getTb().getTbOrderServices();
if(tbOrderServiceSet!=null&&tbOrderServiceSet.size()>0){
List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
for(TbOrderService tbOrderService : tbOrderServiceSet){
TbService tbService = tbOrderService.getTbService();
Map<String,Object> map = new HashMap<String,Object>();
map.put("serviceName",tbService.getServiceName());
map.put("serviceCount", tbOrderService.getOrderServiceCount());
list.add(map);
}
clientOrderVo.setService(list);
}
}
}
你在控制器上面使用的时候直接使用工厂,在把需要转换的po传给他,就可以获取你想要的VO,控制器上面就不会存在封装VO的代码,也不用重复写这些封装的代码
ClientOrderVo clientOrderVo =new ClientOrderVoFactory(tbOrder).build();
当需求变动的时候怎么处理,只需要找到具体的VO工厂类,修改里面的东西就行,出入较大就重新搞一个VO,把具体的逻辑都装到工厂里面去。
小结
综合以上所述, 我认为VO(DTO)模式是非常必需的,特别是考虑到以后扩展性的问题。
是该看看企业应用架构模式喽
以下是在网上看到关于VO是否存在的观点
一、DTO与PO的不对称关系决定了二者不能互相代替
DTO与PO存在在映射关系,可能是多对一,也可能是一对多,最特殊的关系就是上面大家说的这种情况“一对一”。也就是在“一对一”的情况下可以实现DTO与PO的混用,而其他情况下,如果混用都需要PO进行冗余设计,考虑这些冗余设计会比直接的、简单的造一个新的DTO出现要耗费更多的脑细胞,同时这个东西又不利于后期维护,可以说“牵一发,动从上到下”。
二、性能上决定了PO代替DTO是个蹩脚的设计
PO是与数据库直接交互的对象,比如我要在页面上显示数据库中的数据,如果用PO来实现那么这个PO就得一直保持与数据库的连接,直到数据显示到页面上来。这样如果在service层有复杂运算的话,那么数据库方面的延时是非常可观的,而如果转换成DTO之后,数据库连接就可以尽快释放。所以从性能上来说应该使用DTO--当然对于性能不是很苛刻的情况下不用DTO也行 --不过,熟练的程序员应该养成按统一的方式做项目的习惯,我觉得这样会更高效。
VO(DTO)存在的必要性,以及使用工厂模式+模版模式+自省实现可拓展VO