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也行 --不过,熟练的程序员应该养成按统一的方式做项目的习惯,我觉得这样会更高效。
- VO(DTO)存在的必要性,以及使用工厂模式+模版模式+自省实现可拓展VO
- VO(DTO)模式在分层架构设计中是否需要的扯淡
- VO(DTO)模式在分层架构设计中是否需要的扯淡
- VO(DTO)模式在分层架构设计中是否需要的扯淡
- VO(DTO)模式在分层架构设计中是否需要的扯淡
- struts 学习笔记之MVC模式(附 :PO/POJO/BO/DTO/VO的区别 )
- vo、po、dto的区别
- ADF VO排序及VO的三种查询模式
- Oracle ADF VO排序及VO的查询模式
- PO/VO/DTO/POJO以及JavaBean介绍
- PO、VO、DTO、POJO以及JavaBean介绍
- PO/POJO/BO/DTO/VO的区别
- PO/POJO/BO/DTO/VO的区别
- PO/POJO/BO/DTO/VO的区别
- PO/POJO/BO/DTO/VO的区别
- PO/POJO/BO/DTO/VO的区别
- POJO,PO,VO,DTO的区别
- PO/POJO/BO/DTO/VO的区别
- JSP的type="text/template"使用异常
- 屏模式配
- Windows下如何获取usb device信息
- UML入门
- python自学笔记2之pylint
- VO(DTO)存在的必要性,以及使用工厂模式+模版模式+自省实现可拓展VO
- Html5 + android原生 混合式开发(二)
- 代码笔记 | jquery 测试密码的强度
- Configuration problem: Only one AsyncAnnotationBeanPostProcessor may exist within the context.
- 查找算法(基于二叉排序树的查找)
- 栈
- HDU 1429 状压bfs 胜利大逃亡
- Java Observable 模式
- 八皇后问题(DFS剪枝)