如何实现spring mvc 前端框架mvc将返回的给前端的pdf文件放在浏览器里预览

SpringMVC使用PDF模板生成PDF文件 -
- ITeye技术网站
博客分类:
本文先叙述,如何操作PDF模板生成PDF文件,再说明在SpringMVC中如何根据PDF模板生成PDF文件。
使用PDF模板生成PDF文件需要以下几个步骤:
1. 使用Microsoft Office Word画好模板
此步骤就不详述了,就是一个普通的Word文件(template.docx)。给个示例截图:
2. 使用Adobe Acrobat X Pro将Word文件转换为带表单字段的PDF模板文件
1) 打开Adobe Acrobat X Pro
2) 选择“创建PDF表单”
3) 选择源:(PDF、Word、Excel或其它文件类型),下一步
4) 定位Word文件路径,下一步
5) Adobe Acrobat X Pro会自动猜测表单字段位置,如图
6) 一般生成的表单字段都不符合我们的要求,选中删除即可。建议删除自动生成的表单域,不然可能会出现无法显示(中文)内容等问题。
7) 点击右键选择文本框,拖动到适当的位置,设置好域名称,字号,字体等。
8) 保存模板文件。(template.pdf)
3. 使用itext操作PDF模板,填充数据,生成PDF文件
1) 需要jar包:itext.jar、itextAsian.jar
2) 核心代码:
package personal.hutao.
import java.io.ByteArrayOutputS
import java.io.FileOutputS
import java.io.IOE
import java.io.OutputS
import java.util.HashM
import java.util.M
import org.junit.T
import com.lowagie.text.DocumentE
import com.lowagie.text.pdf.AcroF
import com.lowagie.text.pdf.PdfR
import com.lowagie.text.pdf.PdfS
public class TestPdf {
public void test() throws IOException, DocumentException {
String fileName = "D:/template.pdf"; // pdf模板
PdfReader reader = new PdfReader(fileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
PdfStamper ps = new PdfStamper(reader, bos);
AcroFields fields = ps.getAcroFields();
fillData(fields, data());
ps.setFormFlattening(true);
ps.close();
OutputStream fos = new FileOutputStream("D:/contract.pdf");
fos.write(bos.toByteArray());
public void fillData(AcroFields fields, Map&String, String& data) throws IOException, DocumentException {
for (String key : data.keySet()) {
String value = data.get(key);
fields.setField(key, value);
public Map&String, String& data() {
Map&String, String& data = new HashMap&String, String&();
data.put("borrower", "胡桃同学");
3) 打开contract.pdf,如图
至此,就实现了根据PDF模板生成PDF文件。
SpringMVC的视图中已提供了对PDF模板文件的支持:org.springframework.web.servlet.view.document.AbstractPdfStamperView。那么只需要配置好此视图就可以了。
具体分为以下步骤:
1) 实现抽象类AbstractPdfStamperView
package personal.hutao.
import java.io.IOE
import java.util.M
import javax.servlet.http.HttpServletR
import javax.servlet.http.HttpServletR
import org.springframework.web.servlet.view.document.AbstractPdfStamperV
import com.lowagie.text.DocumentE
import com.lowagie.text.pdf.AcroF
import com.lowagie.text.pdf.PdfS
public class PdfStamperView extends AbstractPdfStamperView {
public static final String DATA = "data";
public static final String FILENAME = "mergePdfFileName";
@SuppressWarnings("unchecked")
protected void mergePdfDocument(Map&String, Object& model,
PdfStamper stamper, HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.setHeader("Content-Disposition", "filename=" + new String(model.get(FILENAME).toString().getBytes(), "ISO8859-1"));
AcroFields fields = stamper.getAcroFields();
fillData(fields, (Map&String, String&) model.get(DATA));
stamper.setFormFlattening(true);
private void fillData(AcroFields fields, Map&String, String& data)
throws IOException, DocumentException {
for (String key : data.keySet()) {
String value = data.get(key);
fields.setField(key, value);
2) 在SpringMVC的配置文件中配置视图
&!-- 按照BeanName解析视图 --&
&bean class="org.springframework.web.servlet.view.BeanNameViewResolver"&
&property name="order" value="1" /& &!-- 优先使用此视图解析,避免先被解析为JSP --&
&!-- 定义Pdf模版视图 --&
&bean id="contract" class="personal.hutao.view.PdfStamperView"&
&property name="url" value="/WEB-INF/template/template.pdf" /&
3) Controller中的业务逻辑处理
package personal.hutao.
import static personal.hutao.view.PdfStamperView.DATA;
import static personal.hutao.view.PdfStamperView.FILENAME;
import java.util.HashM
import java.util.M
import org.springframework.stereotype.C
import org.springframework.ui.M
import org.springframework.web.bind.annotation.RequestM
@RequestMapping("/contract")
@Controller
public class TestController {
@RequestMapping("/export/pdf")
public String exportPdf(Model model) {
model.addAttribute(DATA, data());
model.addAttribute(FILENAME, "XXX贷款合同");
return "contract"; // 与personal.hutao.view.PdfStamperView的bean id匹配
private Map&String, String& data() {
Map&String, String& data = new HashMap&String, String&();
data.put("borrower", "胡桃同学");
OK,这样就可以通过SpringMVC加载PDF模板来导出PDF文件了。赶快试试吧~
浏览: 6496 次
来自: 北京
你好,请问我生成的验证码不是4位,但是设置的是4位,这是什么情 ...
那么模版文件从哪里读取的
请问下你url注入,可是这个类没写那个属性啊
wheat 写道感谢.............客气~ 呵呵
感谢.............Spring Web MVC中的页面缓存支持 ——跟我学SpringMVC系列 - Spring,MVC,SpringMVC,缓存 - Java - ITeye论坛
Spring Web MVC中的页面缓存支持 ——跟我学SpringMVC系列
& 上一页 1
锁定老帖子
该帖已经被评为精华帖
jinnianshilongnian
文章: 1112
积分: 2280
发表时间:&&
最后修改:
相关知识库:
注:本章讲的是Spring2的
4.2、Controller接口
package org.springframework.web.servlet.
public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws E
这是控制器接口,此处只有一个方法handleRequest,用于进行请求的功能处理,处理完请求后返回ModelAndView(Model模型数据部分 和 View视图部分)。
还记得第二章的HelloWorld吗?我们的HelloWorldController实现接口,Spring默认提供了一些Controller接口的实现以方便我们使用,具体继承体系如图4-1:
、WebContentGenerator
用于提供如浏览器缓存控制、是否必须有session开启、支持的请求方法类型(GET、POST等)等,该类主要有如下属性:
Set&String&
supportedMethods:设置支持的请求方法类型,默认支持“GET”、“POST”、“HEAD”,如果我们想支持“PUT”,则可以加入该集合“PUT”。
boolean requireSession = false:是否当前请求必须有session,如果此属性为true,但当前请求没有打开session将抛出HttpSessionRequiredException异常;
boolean useExpiresHeader = true:是否使用HTTP1.0协议过期响应头:如果true则会在响应头添加:“Expires:”;需要配合cacheSeconds使用;
boolean useCacheControlHeader = true:是否使用HTTP1.1协议的缓存控制响应头,如果true则会在响应头添加;需要配合cacheSeconds使用;
boolean useCacheControlNoStore = true:是否使用HTTP 1.1协议的缓存控制响应头,如果true则会在响应头添加;需要配合cacheSeconds使用;
private int cacheSeconds = -1:缓存过期时间,正数表示需要缓存,负数表示不做任何事情(也就是说保留上次的缓存设置),
1、cacheSeconds =0时,则将设置如下响应头数据:
Pragma:no-cache
// HTTP 1.0的不缓存响应头
Expires:1L
// useExpiresHeader=true时,HTTP 1.0
Cache-Control :no-cache
// useCacheControlHeader=true时,HTTP 1.1
Cache-Control :no-store
// useCacheControlNoStore=true时,该设置是防止Firefox缓存
2、cacheSeconds&0时,则将设置如下响应头数据:
Expires:System.currentTimeMillis() + cacheSeconds * 1000L
// useExpiresHeader=true时,HTTP 1.0
Cache-Control :max-age=cacheSeconds
// useCacheControlHeader=true时,HTTP 1.1
3、cacheSeconds&0时,则什么都不设置,即保留上次的缓存设置。
此处简单说一下以上响应头的作用,缓存控制已超出本书内容:
HTTP1.0缓存控制响应头
Pragma:no-cache:表示防止客户端缓存,需要强制从服务器获取最新的数据;
Expires:HTTP1.0响应头,本地副本缓存过期时间,如果客户端发现缓存文件没有过期则不发送请求,HTTP的日期时间必须是格林威治时间(GMT),如“Expires:Wed, 14 Mar :32 GMT”;
HTTP1.1缓存控制响应头
Cache-Control :no-cache
强制客户端每次请求获取服务器的最新版本,不经过本地缓存的副本验证;
Cache-Control :no-store
强制客户端不保存请求的副本,该设置是防止Firefox缓存
Cache-Control:max-age=[秒]
客户端副本缓存的最长时间,类似于HTTP1.0的Expires,只是此处是基于请求的相对时间间隔来计算,而非绝对时间。
还有相关缓存控制机制如Last-Modified(最后修改时间验证,客户端的上一次请求时间 在 服务器的最后修改时间 之后,说明服务器数据没有发生变化 返回304状态码)、ETag(没有变化时不重新下载数据,返回304)。
该抽象类默认被AbstractController和WebContentInterceptor继承。
4.4、AbstractController
该抽象类实现了Controller,并继承了WebContentGenerator(具有该类的特性,具体请看4.3),该类有如下属性:
boolean synchronizeOnSession = false:表示该控制器是否在执行时同步session,从而保证该会话的用户串行访问该控制器。
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
//委托给WebContentGenerator进行缓存控制
checkAndPrepare(request, response, this instanceof LastModified);
//当前会话是否应串行化访问.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
return handleRequestInternal(request, response);
return handleRequestInternal(request, response);
可以看出AbstractController实现了一些特殊功能,如继承了WebContentGenerator缓存控制功能,并提供了可选的会话的串行化访问功能。而且提供了handleRequestInternal方法,因此我们应该在具体的控制器类中实现handleRequestInternal方法,而不再是handleRequest。
AbstractController使用方法:
首先让我们使用AbstractController来重写第二章的HelloWorldController:
public class HelloWorldController extends AbstractController {
protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws Exception {
//1、收集参数
//2、绑定参数到命令对象
//3、调用业务对象
//4、选择下一个页面
ModelAndView mv = new ModelAndView();
//添加模型数据 可以是任意的POJO对象
mv.addObject("message", "Hello World!");
//设置逻辑视图名,视图解析器会根据该名字解析到具体的视图页面
mv.setViewName("hello");
可以看出AbstractController实现了一些特殊功能,如继承了WebContentGenerator缓存控制功能,并提供了可选的会话的串行化访问功能。而且提供了handleRequestInternal方法,因此我们应该在具体的控制器类中实现handleRequestInternal方法,而不再是handleRequest。
AbstractController使用方法:
首先让我们使用AbstractController来重写第二章的HelloWorldController:
public class HelloWorldController extends AbstractController {
protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws Exception {
//1、收集参数
//2、绑定参数到命令对象
//3、调用业务对象
//4、选择下一个页面
ModelAndView mv = new ModelAndView();
//添加模型数据 可以是任意的POJO对象
mv.addObject("message", "Hello World!");
//设置逻辑视图名,视图解析器会根据该名字解析到具体的视图页面
mv.setViewName("hello");
&!— 在chapter3-servlet.xml配置处理器 --&
&bean name="/hello" class="cn.javass.chapter3.web.controller.HelloWorldController"/&
从如上代码我们可以看出:
1、继承AbstractController
2、实现handleRequestInternal方法即可。
直接通过response写响应
如果我们想直接在控制器通过response写出响应呢,以下代码帮我们阐述:
public class HelloWorldWithoutReturnModelAndViewController extends AbstractController {
protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws Exception {
resp.getWriter().write("Hello World!!");
//如果想直接在该处理器/控制器写响应 可以通过返回null告诉DispatcherServlet自己已经写出响应了,不需要它进行视图解析
&!— 在chapter3-servlet.xml配置处理器 --&
&bean name="/helloWithoutReturnModelAndView" class="cn.javass.chapter3.web.controller.HelloWorldWithoutReturnModelAndViewController"/&
从如上代码可以看出如果想直接在控制器写出响应,只需要通过response写出,并返回null即可。
强制请求方法类型:
&!— 在chapter3-servlet.xml配置处理器 --&
&bean name="/helloWithPOST" class="cn.javass.chapter3.web.controller.HelloWorldController"&
&property name="supportedMethods" value="POST"&&/property&
以上配置表示只支持POST请求,如果是GET请求客户端将收到“HTTP Status 405 - Request method 'GET' not supported”。
比如注册/登录可能只允许POST请求。
当前请求的session前置条件检查,如果当前请求无session将抛出HttpSessionRequiredException异常:
&!— 在chapter3-servlet.xml配置处理器 --&
&bean name="/helloRequireSession"
class="cn.javass.chapter3.web.controller.HelloWorldController"&
&property name="requireSession" value="true"/&
在进入该控制器时,一定要有session存在,否则抛出HttpSessionRequiredException异常。
Session同步:
即同一会话只能串行访问该控制器。
缓存控制:
1、缓存5秒,cacheSeconds=5
package cn.javass.chapter3.web.
//省略import
public class HelloWorldCacheController extends AbstractController {
protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws Exception {
//点击后再次请求当前页面
resp.getWriter().write("&a href=''&this&/a&");
&!— 在chapter3-servlet.xml配置处理器 --&
&bean name="/helloCache"
class="cn.javass.chapter3.web.controller.HelloWorldCacheController"&
&property name="cacheSeconds" value="5"/&
如上配置表示告诉浏览器缓存5秒钟:
开启chrome浏览器调试工具:
服务器返回的响应头如下所示:
添加了“Expires:Wed, 14 Mar :32 GMT” 和“Cache-Control:max-age=5” 表示允许客户端缓存5秒,当你点“this”链接时,会发现如下:
而且服务器也没有收到请求,当过了5秒后,你再点“this”链接会发现又重新请求服务器下载新数据。
注:下面提到一些关于缓存控制的一些特殊情况:
1、对于一般的页面跳转(如超链接点击跳转、通过js调用window.open打开新页面都是会使用浏览器缓存的,在未过期情况下会直接使用浏览器缓存的副本,在未过期情况下一次请求也不发送);
2、对于刷新页面(如按F5键刷新),会再次发送一次请求到服务器的;
2、不缓存,cacheSeconds=0
&!— 在chapter3-servlet.xml配置处理器 --&
&bean name="/helloNoCache"
class="cn.javass.chapter3.web.controller.HelloWorldCacheController"&
&property name="cacheSeconds" value="0"/&
以上配置会要求浏览器每次都去请求服务器下载最新的数据:
3、cacheSeconds&0,将不添加任何数据
响应头什么缓存控制信息也不加。
4、Last-Modified缓存机制
(1、在客户端第一次输入url时,服务器端会返回内容和状态码200表示请求成功并返回了内容;同时会添加一个“Last-Modified”的响应头表示此文件在服务器上的最后更新时间,如“Last-Modified:Wed, 14 Mar :42 GMT”表示最后更新时间为( 10:22);
(2、客户端第二次请求此URL时,客户端会向服务器发送请求头 “If-Modified-Since”,询问服务器该时间之后当前请求内容是否有被修改过,如“If-Modified-Since: Wed, 14 Mar :42 GMT”,如果服务器端的内容没有变化,则自动返回 HTTP 304状态码(只要响应头,内容为空,这样就节省了网络带宽)。
客户端强制缓存过期:
(1、可以按ctrl+F5强制刷新(会添加请求头 HTTP1.0 Pragma:no-cache和 HTTP1.1 Cache-Control:no-cache 、If-Modified-Since请求头被删除)表示强制获取服务器内容,不缓存。
(2、在请求的url后边加上时间戳来重新获取内容,加上时间戳后浏览器就认为不是同一份内容:
? 和 4 是两次不同的请求。
Spring也提供了Last-Modified机制的支持,只需要实现LastModified接口,如下所示:
package cn.javass.chapter3.web.
public class HelloWorldLastModifiedCacheController extends AbstractController implements LastModified {
private long lastM
protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws Exception {
//点击后再次请求当前页面
resp.getWriter().write("&a href=''&this&/a&");
public long getLastModified(HttpServletRequest request) {
if(lastModified == 0L) {
//TODO 此处更新的条件:如果内容有更新,应该重新返回内容最新修改的时间戳
lastModified = System.currentTimeMillis();
return lastM
&!— 在chapter3-servlet.xml配置处理器 --&
&bean name="/helloLastModified"
class="cn.javass.chapter3.web.controller.HelloWorldLastModifiedCacheController"/&
HelloWorldLastModifiedCacheController只需要实现LastModified接口的getLastModified方法,保证当内容发生改变时返回最新的修改时间即可。
(1、发送请求到服务器,如(),则服务器返回的响应为:
(2、再次按F5刷新客户端,返回状态码304表示服务器没有更新过:
(3、重启服务器,再次刷新,会看到200状态码(因为服务器的lastModified时间变了)。
Spring判断是否过期,通过如下代码,即请求的“If-Modified-Since” 大于等于当前的getLastModified方法的时间戳,则认为没有修改:
this.notModified = (ifModifiedSince &= (lastModifiedTimestamp / 1000 * 1000));
5、ETag(实体标记)缓存机制
(1:浏览器第一次请求,服务器在响应时给请求URL标记,并在HTTP响应头中将其传送到客户端,类似服务器端返回的格式:“ETag:"0f8b0c86fe2c0c7a208e3"”
(2:浏览器第二次请求,客户端的查询更新格式是这样的:“If-None-Match:"0f8b0c86fe2c0c7a208e3"”,如果ETag没改变,表示内容没有发生改变,则返回状态304。
Spring也提供了对ETag的支持,具体需要在web.xml中配置如下代码:
&filter-name&etagFilter&/filter-name&
&filter-class&org.springframework.web.filter.ShallowEtagHeaderFilter&/filter-class&
&filter-mapping&
&filter-name&etagFilter&/filter-name&
&servlet-name&chapter3&/servlet-name&
&/filter-mapping&
此过滤器只过滤到我们DispatcherServlet的请求。
1):发送请求到服务器:“”,服务器返回的响应头中添加了(ETag:"0f8b0c86fe2c0c7a208e3"):
2):浏览器再次发送请求到服务器(按F5刷新),请求头中添加了“If-None-Match:
"0f8b0c86fe2c0c7a208e3"”,响应返回304代码,表示服务器没有修改,并且响应头再次添加了“ETag:"0f8b0c86fe2c0c7a208e3"”(每次都需要计算):
那服务器端是如何计算ETag的呢?
protected String generateETagHeaderValue(byte[] bytes) {
StringBuilder builder = new StringBuilder("\"0");
DigestUtils.appendMd5DigestAsHex(bytes, builder);
builder.append('"');
return builder.toString();
bytes是response要写回到客户端的响应体(即响应的内容数据),是通过MD5算法计算的内容的摘要信息。也就是说如果服务器内容不发生改变,则ETag每次都是一样的,即服务器端的内容没有发生改变。
此处只列举了部分缓存控制,详细介绍超出了本书的范围,强烈推荐: (中文版) 详细了解HTTP缓存控制及为什么要缓存。
缓存的目的是减少相应延迟 和 减少网络带宽消耗,比如css、js、图片这类静态资源应该进行缓存。
实际项目一般使用反向代理服务器(如nginx、apache等)进行缓存。
等级: 初级会员
来自: 沈阳
发表时间:&&
LZ什么时候出个视频啥的啊?
请登录后投票
jinnianshilongnian
文章: 1112
积分: 2280
发表时间:&&
忙 光写文档就累死啦,只能周末和晚上写
请登录后投票
jinnianshilongnian
文章: 1112
积分: 2280
发表时间:&&
lc 写道LZ什么时候出个视频啥的啊?
有时间 一定会出,谢谢支持
请登录后投票
等级: 初级会员
来自: 上海
发表时间:&&
介绍的很详细,一直关注楼主的spring系列文章,非常不错。
请登录后投票
积分: 1950
来自: 长沙
发表时间:&&
写的非常不错,支持一个
请登录后投票
等级: 初级会员
来自: 上海
发表时间:&&
可惜是浏览器缓存,我还以为是服务端页面缓存呢
请登录后投票
jinnianshilongnian
文章: 1112
积分: 2280
发表时间:&&
feiyu86 写道介绍的很详细,一直关注楼主的spring系列文章,非常不错。
谢谢支持,继续写
请登录后投票
jinnianshilongnian
文章: 1112
积分: 2280
发表时间:&&
yin_bp 写道写的非常不错,支持一个
哈,谢谢啦!
请登录后投票
jinnianshilongnian
文章: 1112
积分: 2280
发表时间:&&
coollzh 写道可惜是浏览器缓存,我还以为是服务端页面缓存呢
昨天有人问这个 就把刚写的这块发上来了,不是服务端的
请登录后投票
& 上一页 1
跳转论坛:移动开发技术
Web前端技术
Java企业应用
编程语言技术SpringMvc实战--在一个页面显示Controller中所有的方法信息 - beenoisy - 博客园
随笔 - 29, 文章 - 0, 评论 - 1, 引用 - 0
本博客记录的是如何显示SpringMVC框架中所有使用@RequestMapping注解标注的方法.
由于项目需要,web框架使用SpringMVC.前端\客户端\后端是分开的不同组的人,所以不可避免的要编写\更新大量的接口说明文档.这大大降低了效率,因此实现了显示SpringMVC中所有接受请求的方法的信息的功能.
总体思想:从Spring容器中找到所有加了RequestMapping注解的方法,并且集中显示.
显示SpringMVC中所有加了@RequestMapping注解的方法信息
2.0.环境说明
使用maven开发,别的不多说了.直接上pom
&dependency&
&groupId&org.springframework&/groupId&
&artifactId&spring-webmvc&/artifactId&
&version&${org.springframework.version}&/version&
&/dependency&
&dependency&
&groupId&org.springframework&/groupId&
&artifactId&spring-web&/artifactId&
&version&${org.springframework.version}&/version&
&/dependency&
&dependency&
&groupId&org.springframework&/groupId&
&artifactId&spring-test&/artifactId&
&version&${org.springframework.version}&/version&
&scope&test&/scope&
&/dependency&
&org.springframework.version&3.2.13.RELEASE&/org.springframework.version&
底层的spring core之类的也是3.2.13.RELEASE
2.1.显示所有请求信息
细心的同学可能发现了.在springMVC项目启动的时候,会出现很多的信息.
其中,和咱们这个文章相关的就是这个类输出的信息了:
12:18:47,253 INFO
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/guide/display/search],methods=[GET],params=[],headers=[],consumes=[],produces=[application/charset=UTF-8],custom=[]}" onto public java.lang.String com.renren.toro.waltz.web.controller.guide.DisplayController.search()
12:18:47,253 INFO
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/guide/display],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView com.renren.toro.waltz.web.controller.guide.DisplayController.index()
12:18:47,253 INFO
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/guide/display/detail],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView com.renren.toro.waltz.web.controller.guide.DisplayController.detail(int)
12:18:47,255 INFO
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/logout],methods=[GET],params=[],headers=[],consumes=[],produces=[application/charset=UTF-8],custom=[]}" onto public java.lang.String com.renren.toro.waltz.web.controller.LogoutController.logout(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
还有很多,只是截取了一段.
这个RequestMappingHandlerMapping 类会输出所有的Map信息.
下面介绍信息中的数据
这里以这一条输出作为实例:
12:18:47,255 INFO
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/logout],methods=[GET],params=[],headers=[],consumes=[],produces=[application/charset=UTF-8],custom=[]}" onto public java.lang.String com.renren.toro.waltz.web.controller.LogoutController.logout(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
对应的接受请求的url
接受请求的方法
请求的参数
请求的header信息
接受请求的类型
返回的数据类型
后边会输出对应的类和方法信息:
onto public java.lang.String com.renren.toro.waltz.web.controller.LogoutController.logout(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
&因此从这里入手.
我这里使用了annotation-driven,所以可以直接
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerM
这样就可以在一个handler方法中获取所有的添加了@RequestMapping的方法了.
// request methods
Map&RequestMappingInfo, HandlerMethod& handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
Set&RequestMappingInfo& keySet = handlerMethods.keySet();
for (RequestMappingInfo requestMappingInfo : keySet) {
// 请求路径
String path = requestMappingInfo.getPatternsCondition().toString();
// 请求方法
String requestMethod = requestMappingInfo.getMethodsCondition().toString();
// 返回header类型
String responseType = requestMappingInfo.getProducesCondition().toString();
RequestMethodItem item = new RequestMethodItem();
item.setPath(path);26
item.setMethod(handlerMethod.toString().replace(" ", "&br&"));
item.setResponseType(responseType);
items.add(item);
这里需要写一个Item对象来存储这些信息,方便一起"打包"传给jsp页面,方便显示,由于只是一个POJO,就不帖代码了.
2.2.显示请求所对应的handler方法信息
可以使用requestMappingInfo中的getMethodsCondition()方法获取Controller类
从Map&RequestMappingInfo, HandlerMethod&这个Map中,使用对应的RequestMappingInfo作为key到这个map中查找对应的HandlerMethod
我们来具体看一下这个类:
HandlerMethod
* Encapsulates information about a handler method consisting of a {@linkplain #getMethod() method}
* and a {@linkplain #getBean() bean}. Provides convenient access to method parameters,
* method return value, method annotations.
* &p&The class may be created with a bean instance or with a bean name (e.g. lazy-init bean,
* prototype bean). Use {@link #createWithResolvedBean()} to obtain a {@link HandlerMethod}
* instance with a bean instance resolved through the associated {@link BeanFactory}.
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
一个包含了getMethod和getBean的类.
里边有用的方法:
getMethodParameters()&获得参数列表
通过这个方法,可以获得这个方法所对应的参数信息.该方法返回的是:MethodParameter[]
2.3.显示handler方法的参数信息
遍历2.2中说到的MethodParameter[] 可以获得具体的每一个参数的信息
使用其中的方法:
getParameterName(); 参数名
getParameterType(); 参数类型
getParameterAnnotations(); 参数注解
一切似乎就这样美妙的完成了.但是,学习过java class 规范的同学应该记得,javac在编译的时候,会抹去参数名称信息;
那么,getParameterName()获取到的会是什么呢?
看注释,果然如我所料:
注意returns描述的括号中的内容.
有可能是null,如果参数名称没有在class文件中,或者在开始时没有设置ParameterNameDiscoverer.
查看javac -help
果断加上-g参数再进行一次编译
在maven中这个参数应该加在org.apache.maven.plugins插件配置中
  &plugin&
&groupId&org.apache.maven.plugins&/groupId&
&artifactId&maven-compiler-plugin&/artifactId&
&configuration&
&compilerArguments&
&!-- show all debug info --&
&/compilerArguments&
&/configuration&
在complierArguments中添加一个&g/&
那么,第一个问题解决了,参数名称信息已经包含在了class文件中了.
第二个问题,怎么在开始的时候指定ParameterNameDiscoverer?
查遍google stackoverflow无果...
spring doc也翻了翻,没找到,无奈,只能debug.查调用方.
思路如下,在接受参数的时候,如果不加@RequestParam注解,spring也能实现参数注入.一顿折腾.发现spring直接在spring-core中使用了asm的代码来操作字节码.最终完成了读取class文件中对应方法的的参数名
而初始化ParameterNameDiscoverer的代码在org.springframework.web.method.support.InvocableHandlerMethod类中找到了使用的方式.
直接声明了一个本地变量,使用LocalVariableTableParameterNameDiscoverer,实现类;然后调用initParameterNameDiscovery方法完成了初始化.
仔细看过LocalVariableTableParameterNameDiscoverer里边有很多虚拟机内缓存的实践,有大量的map结构用于存储数据,进而减少字节码操作.
鉴于这里只要springmvc容器启动,就不会出现class文件变动的情况,所以在本地变量中加上了static.
至此,就完成了显示所有@RequestMapping的信息的工作.
2.4.扩展功能
但是,由于只知道了对应的handlerMethod的参数,类,请求方式等信息,还是会出现可能的歧义,不够明确的指出接口的作用和意义.
所以定义了一个注解
@Inherited
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WaltzDocument {
* 注释内容,推荐使用html标签进行编辑&br&
* 日:下午4:27:30&br&
String info();
* 返回给页面的参数,推荐使用html标签进行编辑 &br&
* 日:下午4:05:32&br&
String[] params() default "";
* 接口提供者 &br&
* 日:下午4:27:43&br&
String author();
* 接口状态 &br&
* 日:下午4:34:06&br&
Status status();
public static enum Status {
developing,
* 开发完成
在扫描对应的handlerMethod的同时,获取对应的注解:
handlerMethod.getMethodAnnotation(WaltzDocument.class);
之后把这个注解中对应的参数一同设置到Item中.方便显示.
同时还结合了javadoc的功能,可以直接跳转到具体方法的javadoc页面查看具体说明文档.
页面上,结合一下Bootstrap和datatables就可以完成分页\筛选\搜索\排序的功能,进一步方便使用.
3.1.spring中"虚拟机内"缓存的使用
在spring中,有很多的"虚拟机内换存",所谓虚拟机内缓存,利用虚拟机内的内存空间来实现一些变量的存储,当调用查询方法的时候,先使用查询的key在对应的缓存map中进行查找,这样可以大大降低底层IO操作的次数和频度.像本文中提到的LocalVariableTableParameterNameDiscoverer类,下层的获取方法参数名的方式是使用asm处理字节码.使用ClassReader和LocalVariableTableParameterNameDiscoverer提供的内部类ParameterNameDiscoveringVisitor来实现字节码操作.
数据要求不可变:这种虚拟机内缓存,对数据也是有一定的要求的.要数据的key不可变.试想,调用put方法是的hash值是A0,而想要get时hash值却变成了A1,这样就不能正确的获取数据,反而会造成缓存map越来越大,map的命中率越来越低和不断的扩容.最终导致的是大量无用内存占用和性能下降,最差情况会到导致OOM.
缓存对象大小要求有限:这种虚拟机内缓存只是用于数量有限(放置于map中的对象小于内存大小要求)的情况,试想,如果是一个无限大小的数据集合要做缓存,那最终的结构就是OOM了.否则要考虑使用一些软连接,虚连接形式的对象声明来避免这个问题了.
3.2.java参数抹去
jvm在执行方法的时候,实际上是不需要知道参数的名称的,jvm关心的只是参数的类型.所以,javac抹去了所有的参数信息,替换成paramOfString0....
测试代码如下:
public class Test {
public static void main(
String[] mainArgs) {
System.out.println("beenoisy");
public static void test(
int thisIsAIntArg,
String thisIsAObjectArg) {
System.out.println("Test.test()");
javac Test.java编译出来的字节码:
而同样的代码,添加了-g参数,就会带上参数名称信息,相信这是对class文件压缩的一种体现,但是却某种程度上的削弱了jvm运行时的一些功能.
同样,java对集合类型的泛型也是采取抹去处理的.在thinking in java中也说过,java的泛型不是真正的泛型.估计也是类似的考量.
3.3.怎样提高前后端开发效率
尽量减少无用功.
代码是最好的文档,然文档跟着代码改变.
一体化的文档体系\bug追踪体系\需求分派体系对于效率提升十分重要.
当然,这个小工具还是开发开始阶段紧急弄出来的,很多地方没有很好的遵循规范,后期如果有时间,考虑做成一个通用工具来集成到spring中.
最后,附上完整代码:
Controller:
1 import java.util.A
2 import java.util.C
3 import java.util.L
4 import java.util.M
5 import java.util.S
7 import org.springframework.beans.factory.annotation.A
8 import org.springframework.core.LocalVariableTableParameterNameD
9 import org.springframework.core.MethodP
10 import org.springframework.stereotype.C
11 import org.springframework.web.bind.annotation.RequestM
12 import org.springframework.web.method.HandlerM
13 import org.springframework.web.servlet.ModelAndV
14 import org.springframework.web.servlet.mvc.method.RequestMappingI
15 import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerM
17 import mon.collect.L
18 import com.renren.mon.annotation.WaltzD
19 import com.renren.mon.annotation.WaltzDocument.S
20 import com.renren.mon.interceptor.performance.CountT
21 import com.renren.toro.waltz.dev.controller.support.RequestMethodI
22 import com.renren.toro.waltz.dev.controller.support.RequestMethodP
* 显示所有请求 &br&
* 日:下午5:03:51
* @author Keen &br&
30 @Controller
31 @RequestMapping("dev/param")
32 public class ParamController {
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerM
private static LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@RequestMapping("")
@WaltzDocument(info = "参数显示页面", author = "ziyi.wang", status = Status.done)
@CountTime
public ModelAndView exe() {
List&RequestMethodItem& items = Lists.newArrayList();
// request methods
Map&RequestMappingInfo, HandlerMethod& handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
Set&RequestMappingInfo& keySet = handlerMethods.keySet();
for (RequestMappingInfo requestMappingInfo : keySet) {
// 请求路径
String path = requestMappingInfo.getPatternsCondition().toString();
// 请求方法
String requestMethod = requestMappingInfo.getMethodsCondition().toString();
// Controller的处理方法
HandlerMethod handlerMethod = handlerMethods.get(requestMappingInfo);
MethodParameter[] methodParameters = handlerMethod.getMethodParameters();
// 返回header类型
String responseType = requestMappingInfo.getProducesCondition().toString();
List&RequestMethodParameter& parameters = Lists.newArrayListWithExpectedSize(methodParameters.length);
for (MethodParameter methodParameter : methodParameters) {
// 参数名称
// 如果没有discover参数会是null.参考LocalVariableTableParameterNameDiscoverer
methodParameter.initParameterNameDiscovery(discoverer);
String parameterName = methodParameter.getParameterName();
// 参数类型
Class&?& parameterType = methodParameter.getParameterType();
// 参数注解
Object[] parameterAnnotations = methodParameter.getParameterAnnotations();
String annoation = Arrays.toString(parameterAnnotations);
RequestMethodParameter parameter = new RequestMethodParameter();
parameter.setAnnoation(annoation);
parameter.setName(parameterName);
parameter.setType(parameterType.toString());
parameters.add(parameter);
WaltzDocument documentAnnotation = handlerMethod.getMethodAnnotation(WaltzDocument.class);
RequestMethodItem item = new RequestMethodItem();
item.setPath(path);
item.setRequestMethod(requestMethod);
item.setParameters(parameters);
item.setMethod(handlerMethod.toString().replace(" ", "&br&"));
item.setDocument(documentAnnotation);
item.setResponseType(responseType);
items.add(item);
Collections.sort(items);
ModelAndView mav = new ModelAndView("dev/param");
mav.addObject("items", items);
1 import java.util.L
3 import com.renren.mon.annotation.WaltzD
* 条目 &br&
* 日:下午3:26:15
* @author Keen &br&
11 public class RequestMethodItem implements Comparable&RequestMethodItem& {
private String requestM
private String responseT
private List&RequestMethodParameter&
private WaltzD
public String getPath() {
return path.replace("[", "").replace("]", "");
public void setPath(
String path) {
this.path =
public String getRequestMethod() {
return requestM
public void setRequestMethod(
String requestMethod) {
this.requestMethod = requestM
public String getMethod() {
String a = getA();
return method +
private String getA() {
String methodName = method.split("&br&")[2];
String classPath = methodName.split("\\(")[0];
String[] packageSplit = classPath.split("\\.");
StringBuilder sb = new StringBuilder();
for (int i = 0; i & packageSplit. i++) {
sb.append(packageSplit[i]);
if (i & packageSplit.length - 1) {
if (i == packageSplit.length - 2) {
sb.append(".html#");
sb.append("/");
sb.append("(");
String methodPath = methodName.split("\\(")[1];
sb.append(methodPath.replace(",", ", "));
String href = sb.toString();
String a = "&br&\n&a href='http://svn./toro/waltz/renren-toro-waltz-web/trunk/doc/" + href
+ "' target='_blank'&详细接口文档&/a&";
public void setMethod(
String method) {
this.method =
public List&RequestMethodParameter& getParameters() {
public void setParameters(
List&RequestMethodParameter& parameters) {
this.parameters =
public WaltzDocument getDocument() {
public void setDocument(
WaltzDocument document) {
this.document =
public String getResponseType() {
return responseT
public void setResponseType(
String responseType) {
this.responseType = responseT
public String getResponseParams() {
WaltzDocument document = this.getDocument();
if (document == null) {
return "";
String[] params = document.params();
StringBuilder sb = new StringBuilder();
for (int i = 0; i & params. i++) {
sb.append(params[i]).append("&hr&\n");
return sb.toString();
public String toString() {
return "Item [path=" + path + ", requestMethod=" + requestMethod + ", method=" + method + ", responseType="
+ responseType + ", parameters=" + parameters + ", document=" + document + "]";
public int compareTo(
RequestMethodItem o) {
return this.getPath().compareTo(o.getPath());
* 参数 &br&
* 日:下午3:26:31
* @author Keen &br&
7 public class RequestMethodParameter {
public String getName() {
public void setName(
String name) {
this.name =
public String getAnnoation() {
public void setAnnoation(
String annoation) {
this.annoation =
public String getType() {
public void setType(
String type) {
this.type =
public String toString() {
return "Parameter [name=" + name + ", annoation=" + annoation + ", type=" + type + "]";
1 &%@ page language="java" pageEncoding="UTF-8"%&
2 &%@ taglib uri="/jsp/jstl/core" prefix="c"%&
3 &%@ page import="java.util.Arrays"%&
6 &title&Waltz 接口查看页&/title&
8 &!-- 新 Bootstrap 核心 CSS 文件 --&
9 &link rel="stylesheet" href="/bootstrap/3.3.4/css/bootstrap.min.css"&
11 &!-- 可选的Bootstrap主题文件(一般不用引入) --&
12 &link rel="stylesheet" href="/bootstrap/3.3.4/css/bootstrap-theme.min.css"&
14 &!-- jQuery文件。务必在bootstrap.min.js 之前引入 --&
15 &script src="/jquery/1.11.2/jquery.min.js"&&/script&
17 &!-- 最新的 Bootstrap 核心 JavaScript 文件 --&
18 &script src="/bootstrap/3.3.4/js/bootstrap.min.js"&&/script&
21 &!-- datatables --&
22 &script type="text/javascript" charset="utf8" src="http://cdn.datatables.net/1.10.7/js/jquery.dataTables.min.js"&&/script&
23 &script type="text/javascript" charset="utf8" src="http://cdn.datatables.net/plug-ins/1.10.7/integration/bootstrap/3/dataTables.bootstrap.js"&&/script&
26 &/head&
&table id="paramTable" class="table table-striped table-hover table-bordered table-condensed display"&
&th&No.&/th&
&th&docment&/th&
&th&author&/th&
&th&status&/th&
&th&path&/th&
&th&requestMethod&/th&
&th&responseType&/th&
&th&responseParams&/th&
&th&controllerMethod&/th&
&th&parameters&/th&
&c:forEach items="${items}" var="i" varStatus="x"&
&td&${x.count}&/td&
&td&${()}&/td&
&td&${i.document.author()}&/td&
&td&${i.document.status()}&/td&
&td&${i.path}&br&&a href='${i.path}' target='_blank'&跳转查看&/a&&/td&
&td&${i.requestMethod}&/td&
&td&${i.responseType}&/td&
&td&${i.responseParams}&/td&
&td&${i.method}&/td&
&c:if test="${i.parameters.size() & 0 }"&
&table class="table table-striped table-hover table-bordered table-condensed"&
&th&parameterName&/th&
&th&parameterType&/th&
&th&parameterAnnotations&/th&
&c:forEach items="${i.parameters}" var="p"&
&td&${p.name}&/td&
&td&${p.type}&/td&
&td&${p.annoation}&/td&
&/c:forEach&
&/c:forEach&
&th&No.&/th&
&th&docment&/th&
&th&author&/th&
&th&status&/th&
&th&path&/th&
&th&requestMethod&/th&
&th&responseType&/th&
&th&responseParams&/th&
&th&controllerMethod&/th&
&th&parameters&/th&
&script type="text/javascript"&
$(document).ready(function() {
// Setup - add a text input to each footer cell
$('#paramTable tfoot th').each( function () {
var title = $('#paramTable thead th').eq( $(this).index() ).text();
$(this).html( '&input type="text" placeholder="Search '+title+'" /&' );
// DataTable
var table = $('#paramTable').DataTable({
paging: false
// Apply the search
table.columns().eq(0).each(function(colIdx){
$('input', table.column(colIdx).footer()).on('keyup change',function () {
table.column( colIdx ).search( this.value ).draw();
119 &/body&
120 &/html&}

我要回帖

更多关于 springmvc向前端传值 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信