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

Posted hello_读书就是赚钱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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的主要内容,如果未能解决你的问题,请参考以下文章

java项目中VO和DTO以及Entity,各自是在啥情况下应用的

JavaBean,POJO,VO,DTO的区别和联系

Java 中的PO VO DTO BO

PO BO VO DTO POJO DAO DO

java术语(PO/POJO/VO/BO/DAO/DTO)

Java的常见术语(PO/POJO/VO/BO/DAO/DTO)