VO(DTO)存在的必要性,以及使用工厂模式+模版模式+自省实现可拓展VO

来源:互联网 发布:linux强制改语言 编辑:程序博客网 时间:2024/05/16 17:27

引子:
想起以前第一个项目的时候,使用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也行 --不过,熟练的程序员应该养成按统一的方式做项目的习惯,我觉得这样会更高效。

0 0
原创粉丝点击