Springmvc View源码分析

View

public interface View{
     String RESPONSE_STATUS_ATTRIBUTE = View. class .getName()+ ".responseStatus" ;
     String PATH_VARIABLES = View. class .getName()+ ".pathVariables" ;
     String getContentType();
     void r ender(Map<String,?>model,HttpServletRequest request,HttpServletResponse response) throws Exception;
}

getContentType : 获取 ContentType
render: 渲染视图方法,传入了 request,response,model ; model 中包含了在 controller 中设置的属性,在视图渲染时可以直接使用

AbstractView

AbstractView 中直接看 render 方法:

public void render(Map<String,?>model,HttpServletRequest request,HttpServletResponse response) throws Exception{
     if ( logger .isTraceEnabled()){
            logger .trace( "Renderingviewwithname'" + this . beanName + "'withmodel" +model+ "andstaticattributes" + this . staticAttributes) ;
     }
     Map<String,Object> mergedModel = createMergedOutputModel(model,request,response);
     prepareResponse(request,response);
     renderMergedOutputModel(mergedModel,request,response);
}

createMergedOutputModel :这个方法是合并所有的数据 ( 主要是路径参数,静态参数,以及 controller 中设置的 model)

protected Map<String,Object> createMergedOutputModel(Map<String,?>model,HttpServletRequest request,HttpServletResponse response){
     Map<String,Object> pathVars= this . exposePathVariables ?(Map<String,Object>)request.getAttribute(View. PATH_VARIABLES ): null ;
     // 计算出共有多少个属性
     int size= this . staticAttributes .size();
     size+=(model!= null )?model.size(): 0 ;
     size+=(pathVars!= null )?pathVars.size(): 0 ;
     // 创建 hashmap , 初始化大小 size
     Map<String,Object> mergedModel= new HashMap<String,Object>(size);
     mergedModel.putAll( this . staticAttributes );
     if (pathVars!= null ){
          mergedModel.putAll(pathVars);
     }
     if (model!= null ){
          mergedModel.putAll(model);
     }
     //ExposeRequestContext?
     if ( this . requestContextAttribute != null ){
          mergedModel.put( this . requestContextAttribute ,createRequestContext(request,response,mergedModel));
     }
     return mergedModel;
}

pre pareResponse : 准备方法,如果在渲染之前需要做一些工作,那么可以放入到这个方法中

protected void prepareResponse (HttpServletRequest request,HttpServletResponse response){
     if (generatesDownloadContent()){
          response.setHeader( "Pragma" , "private" );
          response.setHeader( "Cache-Control" , "private,must-revalidate" );
     }
}

在 AbstractView 中,默认是设置了 response 的 header 信息
renderMergedOutputModel :根据合并的 model 信息来渲染视图,这是个抽象方法,具体如何渲染根据子类来实现
protected abstract void renderMergedOutputModel(Map<String,Object> model,HttpServletRequest request,HttpServletResponse response) throws Exception;
AbstractUrlBasedView
这个类主要只完成一个功能,添加了一个属性url,主要是给视图指定一个url路径
InternalResourceView
这个是默认的视图类,主要是jsp或者其他视图资源的一个包装。把model中的数据存放到了request的attribute中
alwaysInclude属性来要求返回的结果使用include方式而不是forward方式
exposeContextBeansAsAttributes属性是否需要将当前spring 环境中的 beans作为request attritbutes来暴露到页面上。
exposedContextBeanNames属性来限制能够暴露到页面上的spring bean的名称列表。

protected void renderMergedOutputModel(Map<String,Object>  model,HttpServletRequest  request,HttpServletResponse  response)throws  Exception{
    //把WebApplicationContext中的一些Bean给RequestDispatcher
    HttpServletRequest  requestToExpose=getRequestToExpose(request);
    //把model中的数据作为reqest的attribute
    exposeModelAsRequestAttributes(model,requestToExpose);
    //空实现,子类可以覆盖,放一些东西到Reqest中
    exposeHelpers(requestToExpose);
    //获取到跳转地址
    String  dispatcherPath  =  prepareForRendering(requestToExpose,response);
    //通过request.getRequestDispatcher(path)获取到RequestDispatcher
    RequestDispatcher  rd=  getRequestDispatcher(requestToExpose,dispatcherPath);
    if(rd==null){
        Throw new  ServletException("CouldnotgetRequestDispatcherfor["+getUrl()+"]:Checkthatthecorrespondingfileexistswithinyourwebapplicationarchive!");
    }
    //检查这个Request是否已经include,或者response是否已经commited,如果有任一一种情况,那么就用include代替forward.
    //这样能够保证在调用response.flushBufer()之前渲染视图    This can be enforced by calling response.flushBuffer() (which will commit the response) before rendering the view.
    if(useInclude(requestToExpose,response)){
        response.setContentType(getContentType());
        if(logger.isDebugEnabled()){
            logger.debug("Includingresource["+getUrl()+"]inInternalResourceView'"+getBeanName()+"'");
        }
        rd.include(requestToExpose,response);
    }else{
        exposeForwardRequestAttributes(requestToExpose);
        if(logger.isDebugEnabled()){
            logger.debug("Forwardingtoresource["+getUrl()+"]inInternalResourceView'"+getBeanName()+"'");
        }
        rd.forward(requestToExpose,response);
    }
}

AbstractExcelView

@Override
protected final void renderMergedOutputModel(
Map<String,Object> model,HttpServletRequest request,HttpServletResponse response)throws Exception{
    HSSFWorkbook workbook;
    if(this.url!=null){
        //根据指定的excel文件路径创建HSSFWorkbook对象
        workbook=getTemplateSource(this.url,request);
    }
    else{
        workbook=new HSSFWorkbook();
        logger.debug("CreatedExcelWorkbookfromscratch");
    }
    //构建excel文件的具体内容,抽象类,让子类实现
    buildExcelDocument(model,workbook,request,response);
    //Setthecontenttype.
    response.setContentType(getContentType());
    //Flushbytearraytoservletoutputstream.
    ServletOutputStreamout=response.getOutputStream();
    workbook.write(out);
    out.flush();
}

完成通用的excel导出,通过设置excel模板地址,编写excel模板,使用model中的数据填充模板,最后导出excel
1.首先创建excel模板文件:excel-report.xls


约定F表示数据项,F表示数据项,P表示数据项之前的数据(标题等),V表示统计信息,V表示统计信息,V{nbsp}表示空白单元格也可以什么都不填;如果要设置数据行格式为数字:n;
默认的格式是:label
导出的最后效果图,数据都是测试数据:
image

2.创建模板解析类,解析出所有的单元格存放到对应的List中,parameterCells,fieldCells,variableCells

    /**
    *导出Excel的模板对象
    */
    public class ExcelTemplate{
        private List<HSSFCell> parameterCells=new ArrayList<>();
        private List<HSSFCell> fieldCells=new ArrayList<>();
        private List<HSSFCell> variableCells=new ArrayList<>();
        private HSSFWorkbook workbook;
        public ExcelTemplate(HSSFWorkbook workbook){
            Assert.notNull(workbook,"workbook can not is null");
            this.workbook=workbook;
            parse();
        }
        /**
        *解析Excel模板
        */
        private void parse(){
            HSSFSheet sheet=workbook.getSheetAt(0);
            Assert.notNull(sheet,"模板工作表对象不能为空!");
            for(intk=0;k<sheet.getLastRowNum()+1;k++){
                HSSFRow cells=sheet.getRow(k);
                for(int j=0;j<cells.getLastCellNum()+1;j++){
                    HSSFCellcell=cells.getCell(j);
                    if(cell==null){
                        continue;
                    }
                    StringcellContent=cell.getStringCellValue();
                    if(StringUtils.isEmpty(cellContent)){
                        continue;
                    }
                    if(cellContent.contains("$P")||cellContent.contains("$p")){
                        parameterCells.add(cell);
                    }elseif(cellContent.contains("$F")||cellContent.contains("$f")){
                        fieldCells.add(cell);
                    }elseif(cellContent.contains("$V")||cellContent.contains("$v")){
                        variableCells.add(cell);
                    }
                }
            }
        }
        /**
        *增加一个参数对象
        */
        public void addParameterCell(HSSFCell cell){
            parameterCells.add(cell);
        }
        /**
        *增加一个字段对象
        */
        public void addFieldCell(HSSFCell cell){
            fieldCells.add(cell);
        }
        //省略getter setter
    }

3.创建存放模板需要展示的数据类ExcelData

    /**
    *Excel数据对象
    */
    public class ExcelData{
        //下载文件时显示的名称
        private String downloadFileName;
        /**
        *Excel参数元数据对象
        */
        private Map parameters=new HashMap();
        private Map variables=new HashMap();
        /**
        *Excel集合元对象
        */
        private List fields=new ArrayList<>();
        public ExcelData(){
        }
        /**
        *构造函数
        *
        *@param parameters元参数对象
        *@param pList集合元对象
        */
        public ExcelData(Map parameters,Map variables,List pList){
            setParameters(parameters);
            setFields(pList);
            setVariables(variables);
        }
        @SuppressWarnings("unchecked")
        public ExcelData addField(Object value){
            this.fields.add(value);
            returnthis;
        }
        @SuppressWarnings("unchecked")
        public ExcelData addParameter(String key,Object value){
            this.parameters.put(key,value);
            returnthis;
        }
        @SuppressWarnings("unchecked")
        public ExcelData addVariable(String key,Object value){
            this.variables.put(key,value);
            returnthis;
        }
        //省略getter setter...
    }

4.创建数据填充器ExcelFiller,根据ExcelTempleate解析出来的模板和ExcelData来填充excel

/**
*Excel数据填充器
*/
public class ExcelFiller{
    /**
    *Excel模板数据类型<br>
    *number:数字类型
    */
    public static final StringE XCEL_TPL_DATA_TYPE_NUMBER="number";
    /**
    *Excel模板数据类型<br>
    *number:文本类型
    */
    public static final String EXCEL_TPL_DATA_TYPE_LABEL="label";
    private static final Logger logger=LoggerFactory.getLogger(ExcelFiller.class);
    private ExcelTemplate excelTemplate=null;
    private ExcelData excelData=null;
    /**
    *构造函数
    *
    *@parampExcelTemplate
    *@parampExcelData
    */
    publicExcelFiller(ExcelTemplate pExcelTemplate,ExcelData pExcelData){
        setExcelData(pExcelData);
        setExcelTemplate(pExcelTemplate);
        ConvertUtils.deregister(Date.class);
        DateConverter dateConverter=new DateConverter();
        dateConverter.setPattern("yyyy-MM-ddHH:mm:ss");
        ConvertUtils.register(new ConverterFacade(dateConverter),Date.class);
    }
    /**
    *数据填充将ExcelData填入excel模板
    *
    *@returnByteArrayOutputStream
    */
    publicvoidfill(){
        try{
            HSSFWorkbook workbook=this.excelTemplate.getWorkbook();
            HSSFSheet sheet=workbook.getSheetAt(0);
            fillParameters(sheet);
            fillFields(sheet);
        }catch(Exceptione){
            logger.error("基于模板生成可写工作表出错了!");
            e.printStackTrace();
        }
    }
    /**
    *写入参数对象
    *
    *@paramwSheet
    */
    private void fillParameters(HSSFSheet wSheet){
        List<HSSFCell> parameterCells=getExcelTemplate().getParameterCells();
        Map parameters=getExcelData().getParameters();
        for(HSSFCellcell:parameterCells){
            String cellContent=cell.getStringCellValue().trim();
            String key=getKey(cellContent);
            String type=getType(cellContent);
            HSSFRow row=wSheet.getRow(cell.getRowIndex());
            row.removeCell(cell);
            HSSFCell hssfCell=row.createCell(cell.getColumnIndex());
            hssfCell.setCellStyle(cell.getCellStyle());
            if(type.equalsIgnoreCase(EXCEL_TPL_DATA_TYPE_NUMBER)){
                BigDecimal convertValue=(BigDecimal)ConvertUtils.convert(parameters.get(key),BigDecimal.class);
                hssfCell.setCellType(Cell.CELL_TYPE_NUMERIC);
                hssfCell.setCellValue(convertValue.doubleValue());
            }else{
                String convertValue=(String)ConvertUtils.convert(parameters.get(key),String.class);
                hssfCell.setCellType(Cell.CELL_TYPE_STRING);
                hssfCell.setCellValue(convertValue);
            }
        }
    }
    /**
    *写入表格字段对象
    *
    *@paramwSheet
    *@throwsException
    */
    private void fillFields(HSSFSheet wSheet) throws Exception{
        List<HSSFCell> fieldCells=getExcelTemplate().getFieldCells();
        List fields=getExcelData().getFields();
        for(intj=0;j<fields.size();j++){
            BeanWrapper beanWrapper=new BeanWrapperImpl(fields.get(j));
            HSSFRowrow=null;
            for(HSSFCellcell:fieldCells){
                String cellContent=cell.getStringCellValue().trim();
                String key=getKey(cellContent);
                String type=getType(cellContent);
                if(row==null){
                    row=wSheet.createRow(cell.getRowIndex()+j);
                }
                HSSFCellhssfCell=row.createCell(cell.getColumnIndex());
                hssfCell.setCellStyle(cell.getCellStyle());
                if(type.equalsIgnoreCase(EXCEL_TPL_DATA_TYPE_NUMBER)){
                    BigDecimal convertValue=(BigDecimal)ConvertUtils.convert(beanWrapper.getPropertyValue(key),BigDecimal.class);
                    hssfCell.setCellType(Cell.CELL_TYPE_NUMERIC);
                    hssfCell.setCellValue(convertValue.doubleValue());
                }else{
                    String convertValue=(String)ConvertUtils.convert(beanWrapper.getPropertyValue(key),String.class);
                    hssfCell.setCellType(Cell.CELL_TYPE_STRING);
                    hssfCell.setCellValue(convertValue);
                }
            }
        }
        HSSFCell cell=fieldCells.get(0);
        int rowIndex=fields.size()+cell.getRowIndex();
        fillVariables(wSheet,rowIndex);
    }
    /**
    *写入变量对象
    */
    private void fillVariables(HSSFSheet wSheet,int rowIndex){
        List<HSSFCell> variableCells=getExcelTemplate().getVariableCells();
        Map variables=getExcelData().getVariables();
        HSSFRow row=wSheet.createRow(rowIndex);
        for(HSSFCellcell:variableCells){
            String cellContent=cell.getStringCellValue().trim();
            String key=getKey(cellContent);
            String type=getType(cellContent);
            HSSFCell hssfCell=row.createCell(cell.getColumnIndex());
            hssfCell.setCellStyle(cell.getCellStyle());
            if(type.equalsIgnoreCase(EXCEL_TPL_DATA_TYPE_NUMBER)){
                BigDecimal convertValue=(BigDecimal)ConvertUtils.convert(variables.get(key),BigDecimal.class);
                hssfCell.setCellType(Cell.CELL_TYPE_NUMERIC);
                hssfCell.setCellValue(convertValue.doubleValue());
            }else{
                String content=(String)ConvertUtils.convert(variables.get(key),String.class);
                if(StringUtils.isEmpty(content)&&!key.equalsIgnoreCase("nbsp")){
                    content=key;
                }
                hssfCell.setCellType(Cell.CELL_TYPE_STRING);
                hssfCell.setCellValue(content);
            }
        }
    }
    /**
    *获取模板键名
    *
    *@parampKey模板元标记
    *@return键名
    */
    private static String getKey(String pKey){
        String key=null;
        int index=pKey.indexOf(":");
        if(index==-1){
            key=pKey.substring(3,pKey.length()-1);
        }else{
            key=pKey.substring(3,index);
        }
        return key;
    }
    /**
    *获取模板单元格标记数据类型
    *
    *@parampType模板元标记
    *@return数据类型
    */
    private static String getType(String pType){
        String type=EXCEL_TPL_DATA_TYPE_LABEL;
        if(pType.contains(":n")||pType.contains(":N")){
            type=EXCEL_TPL_DATA_TYPE_NUMBER;
        }
        return type;
    }
    //省略getter setter
}

5.创建excel视图类AppExcelView

public class AppExcelView extends AbstractExcelView{
        /**
        *excel的数据对象ExcelData放到model中的key
        */
        public static final String EXCEL_DATA_MODEL_KEY = AppExcelView.class + "_ExcelData";
        public AppExcelView(){
        }
        @Override
        protected void buildExcelDocument(Map<String,Object>model,HSSFWorkbook workbook,HttpServletRequest request,HttpServletResponse response)throwsException{
            ExcelData excelData=(ExcelData)model.get(EXCEL_DATA_MODEL_KEY);
            String downloadFileName=WebUtils.encodeChineseDownloadFileName(request,excelData.getDownloadFileName());
            response.setHeader("Content-Disposition","attachment;filename="+downloadFileName+";");
            ExcelFiller excelFiller=new ExcelFiller(new ExcelTemplate(workbook),excelData);
            excelFiller.fill();
        }
    }

6.创建一个视图的解析器ReportViewResolver

public class ReportViewResolver extends AbstractCachingViewResolver implements Ordered{
        private static final String EXCEL_URL_PREFIX="excel:";
        private String prefix="";
        private in torder=Integer.MAX_VALUE;
        @Override
        protected ViewloadView(String viewName,Locale locale)throws Exception{
            //Checkforspecial"excel:"prefix.
            if(viewName.startsWith(EXCEL_URL_PREFIX)){
                String excelViewName=viewName.substring(EXCEL_URL_PREFIX.length());
                AppExcelView appExcelView=BeanUtils.instantiateClass(AppExcelView.class);
                appExcelView.setUrl(getPrefix()+excelViewName);
                return applyLifecycleMethods(excelViewName,appExcelView);
            }
            returnnull;
        }
        private View applyLifecycleMethods(String viewName,AbstractView view){
        return(View)getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view,viewName);
        }
        //getter  setter
    }

7.在dispatchServlet.xml中配置视图解析器

<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.ReportViewResolver">
    <property name="prefix" value="/report/"/>
    <property name="order" value="1"/>
</bean>

8.编写controller代码,创建ExcelData对象

    @Controller
    @RequestMapping("/report/")
    publicclassReportController{
        @RequestMapping(value="excel",method=RequestMethod.GET)
        publicStringexcel(Modelmodel){
            ExcelData excelData=new ExcelData();
            excelData.addParameter("reportTitle","test");
            excelData.addParameter("jbr","SilentWu");
            excelData.addParameter("amount",100);
            excelData.addParameter("date",new Date());
            excelData.addVariable("count",50);
            excelData.setFields(loadProjects());
            excelData.setDownloadFileName("test.xls");
            //把ExcelData对象放入到model中,key要用这个
            model.addAttribute(AppExcelView.EXCEL_DATA_MODEL_KEY,excelData);
            return"excel:excel/excel-report";
        }
        private List<Project> loadProjects(){
            List<Project> projects=new ArrayList<>();
            for(inti=0;i<10;i++){
                Project project=new Project();
                project.setProjectId(i);
                project.setProjectName("test-"+i);
                projects.add(project);
            }
            return projects;
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容