Servlet – Upload、Download、Async、动态注册
2017-09-27 22:05
447 查看
Upload-上传
随着3.0版本的发布,文件上传终于成为Servlet规范的一项内置特性,不再依赖于像Commons FileUpload之类组件,因此在服务端进行文件上传编程变得不费吹灰之力.客户端
要上传文件, 必须利用multipart/form-data设置HTML表单的enctype属性,且method必须为
POST:
服务端
服务端Servlet主要围绕着@MultipartConfig注解和
Part接口:
处理上传文件的Servlet必须用
@MultipartConfig注解标注:
@MultipartConfig属性 | 描述 |
---|---|
fileSizeThreshold | The size threshold after which the file will be written to disk |
location | The directory location where files will be stored |
maxFileSize | The maximum size allowed for uploaded files. |
maxRequestSize | The maximum size allowed for multipart/form-data requests |
Part,
HttpServletRequest中提供如下两个方法获取封装好的
Part:
HttpServletRequest | 描述 |
---|---|
Part getPart(String name) | Gets the Part with the given name. |
Collection<Part> getParts() | Gets all the Part components of this request, provided that it is of type multipart/form-data. |
Part中提供了如下常用方法来获取/操作上传的文件/数据:
Part | 描述 |
---|---|
InputStream getInputStream() | Gets the content of this part as an InputStream |
void write(String fileName) | A convenience method to write this uploaded item to disk. |
String getSubmittedFileName() | Gets the file name specified by the client(需要有Tomcat 8.x 及以上版本支持) |
long getSize() | Returns the size of this fille. |
void delete() | Deletes the underlying storage for a file item, including deleting any associated temporary disk file. |
String getName() | Gets the name of this part |
String getContentType() | Gets the content type of this part. |
Collection<String> getHeaderNames() | Gets the header names of this Part. |
String getHeader(String name) | Returns the value of the specified mime header as a String. |
文件流解析
通过抓包获取到客户端上传文件的数据格式: |
<input type="text"/>),将只包含一个请求头
Content-Disposition.B. 如果HTML表单输入项为文件(
<input type="file"/>), 则包含两个头:
Content-Disposition与
Content-Type.在Servlet中处理上传文件时, 需要:
<code>- 通过查看是否存在`Content-Type`标头, 检验一个Part是封装的普通表单域,还是文件域. - 若有`Content-Type`存在, 但文件名为空, 则表示没有选择要上传的文件. - 如果有文件存在, 则可以调用`write()`方法来写入磁盘, 调用同时传递一个绝对路径, 或是相对于`@MultipartConfig`注解的`location`属性的相对路径. </code>
SimpleFileUploadServlet
优化
善用WEB-INF
存放在
/WEB-INF/目录下的资源无法在浏览器地址栏直接访问, 利用这一特点可将某些受保护资源存放在WEB-INF目录下, 禁止用户直接访问(如用户上传的可执行文件,如JSP等),以防被恶意执行, 造成服务器信息泄露等危险.
当文件名包含中文时,可能会出现乱码,其解决方案与
POST相同:
如果上传同名文件,会造成文件覆盖.因此可以为每份文件生成一个唯一ID,然后连接原始文件名:
如果一个目录下存放的文件过多, 会导致文件检索速度下降,因此需要将文件打散存放到不同目录中, 在此我们采用Hash打散法(根据文件名生成Hash值, 取Hash值的前两个字符作为二级目录名), 将文件分布到一个二级目录中:
示例-简易存储图片服务器
需求: 提供上传图片功能, 为其生成外链, 并提供下载功能(见下)客户端
getSubmittedFileName()方法需要有Tomcat 8.X以上版本的支持, 因此为了通用期间, 我们自己解析
content-disposition请求头, 获取filename.
Download-下载
文件下载是向客户端响应二进制数据(而非字符),浏览器不会直接显示这些内容,而是会弹出一个下载框, 提示下载信息.为了将资源发送给浏览器, 需要在Servlet中完成以下工作:
使用
Content-Type响应头来规定响应体的MIME类型, 如image/pjpeg、application/octet-stream;
添加
Content-Disposition响应头,赋值为
attachment;filename=xxx.yyy, 设置文件名;
使用
response.getOutputStream()给浏览器发送二进制数据;
文件名中文乱码
当文件名包含中文时(attachment;filename=文件名.后缀名),在下载框中会出现乱码, 需要对文件名编码后在发送, 但不同的浏览器接收的编码方式不同:
<code> * FireFox: Base64编码 * 其他大部分Browser: URL编码 </code>
因此最好将其封装成一个通用方法:
示例-IFS下载功能
Async-异步处理
Servlet/
Filter默认会一直占用请求处理线程, 直到它完成任务.如果任务耗时长久, 且并发用户请求量大, Servlet容器将会遇到超出线程数的风险.
Servlet 3.0 中新增了一项特性, 用来处理异步操作. 当
Servlet/
Filter应用程序中有一个/多个长时间运行的任务时, 你可以选择将任务分配给一个新的线程, 从而将当前请求处理线程返回到线程池中,释放线程资源,准备为下一个请求服务.
异步Servlet/Filter
异步支持@WebServlet/
@WebFilter注解提供了新的
asyncSupport属性:
<async-supportted/>标签:
支持异步处理的
Servlet/
Filter可以通过在
ServletRequest中调用
startAsync()方法来启动新线程:
ServletRequest | 描述 |
---|---|
AsyncContext startAsync() | Puts this request into asynchronous mode, and initializes its AsyncContext with the original (unwrapped) ServletRequest and ServletResponse objects. |
AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) | Puts this request into asynchronous mode, and initializes its AsyncContext with the given request and response objects. |
1. 只能将原始的
ServletRequest/
ServletResponse或其包装器(Wrapper/Decorator,详见Servlet
– Listener、Filter、Decorator)传递给第二个
startAsync()方法.
2. 重复调用
startAsync()方法会返回相同的
AsyncContext实例, 如果在不支持异步处理的
Servlet/
Filter中调用, 会抛出
java.lang.IllegalStateException异常.
3.
AsyncContext的
start()方法不会造成方法阻塞.
这两个方法都返回
AsyncContext实例,
AsyncContext中提供了如下常用方法:
AsyncContext | 描述 |
---|---|
void start(Runnable run) | Causes the container to dispatch a thread, possibly from a managed thread pool, to run the specified Runnable. |
void dispatch(String path) | Dispatches the request and response objects of this AsyncContext to the given path. |
void dispatch(ServletContext context, String path) | Dispatches the request and response objects of this AsyncContext to the given path scoped to the given context. |
void addListener(AsyncListener listener) | Registers the given AsyncListener with the most recent asynchronous cycle that was started by a call to one of the ServletRequest.startAsync() methods. |
ServletRequest getRequest() | Gets the request that was used to initialize this AsyncContext by calling ServletRequest.startAsync() or ServletRequest.startAsync(ServletRequest, ServletResponse). |
ServletResponse getResponse() | Gets the response that was used to initialize this AsyncContext by calling ServletRequest.startAsync() or ServletRequest.startAsync(ServletRequest, ServletResponse). |
boolean hasOriginalRequestAndResponse() | Checks if this AsyncContext was initialized with the original or application-wrapped request and response objects. |
void setTimeout(long timeout) | Sets the timeout (in milliseconds) for this AsyncContext. |
Servlet/
Filter中需要完成以下工作, 才能真正达到异步的目的:
调用
AsyncContext的
start()方法, 传递一个执行长时间任务的
Runnable;
任务完成时, 在
Runnable内调用
AsyncContext的
complete()方法或
dispatch()方法
示例-改造文件上传
在前面的图片存储服务器中, 如果上传图片过大, 可能会耗时长久,为了提升服务器性能, 可将其改造为异步上传(其改造成本较小):Runnable提交给
Executor, 并立即返回即可.
AsyncListener
Servlet 3.0 还新增了一个AsyncListener接口, 以便通知用户在异步处理期间发生的事件, 该接口会在异步操作的启动/完成/失败/超时情况下调用其对应方法:
ImageUploadListener
@WebListener标注
AsyncListener的实现, 因此必须对有兴趣收到通知的每个
AsyncContext都手动注册一个
AsyncListener:
动态注册
动态注册是Servlet 3.0新特性,它不需要重新加载应用便可安装新的Web对象(Servlet/
Filter/
Listener等).
API支持
为了使动态注册成为可能,ServletContext接口添加了如下方法用于 创建/添加 Web对象:
ServletContext | 描述 |
---|---|
Create | |
<T extends Servlet> T createServlet(Class<T> clazz) | Instantiates the given Servlet class. |
<T extends Filter> T createFilter(Class<T> clazz) | Instantiates the given Filter class. |
<T extends EventListener> T createListener(Class<T> clazz) | Instantiates the given EventListener class. |
Add | |
ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) | Registers the given servlet instance with this ServletContext under the given servletName. |
FilterRegistration.Dynamic addFilter(String filterName, Filter filter) | Registers the given filter instance with this ServletContext under the given filterName. |
<T extends EventListener> void addListener(T t) | Adds the given listener to this ServletContext. |
Create & And | |
ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass) | Adds the servlet with the given name and class type to this servlet context. |
ServletRegistration.Dynamic addServlet(String servletName, String className) | Adds the servlet with the given name and class name to this servlet context. |
FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass) | Adds the filter with the given name and class type to this servlet context. |
FilterRegistration.Dynamic addFilter(String filterName, String className) | Adds the filter with the given name and class name to this servlet context. |
void addListener(Class<? extends EventListener> listenerClass) | Adds a listener of the given class type to this ServletContext. |
void addListener(String className) | Adds the listener with the given class name to this ServletContext. |
addServlet()/
addFilter()方法的返回值是
ServletRegistration.Dynamic/
FilterRegistration.Dynamic,他们都是
Registration.Dynamic的子接口,用于动态配置
Servlet/
Filter实例.
示例-DynamicServlet
动态注册DynamicServlet, 注意: 并未使用web.xml或@WebServlet静态注册
DynamicServlet实例, 而是用
DynRegListener在服务器启动时动态注册.
DynamicServlet
容器初始化
在使用类似SpringMVC这样的MVC框架时,需要首先注册DispatcherServlet到web.xml以完成URL的转发映射:
API支持
容器初始化的核心是javax.servlet.ServletContainerInitializer接口,他只包含一个方法:
ServletContainerInitializer | 描述 |
---|---|
void onStartup(Set<Class<?>> c, ServletContext ctx) | Notifies this ServletContainerInitializer of the startup of the application represented by the given ServletContext. |
ServletContext监听器之前, 由Servlet容器自动调用
onStartup()方法.
注意: 任何实现了
ServletContainerInitializer的类必须使用
@HandlesTypes注解标注, 以声明该初始化程序可以处理这些类型的类.
实例-SpringMVC初始化
利用Servlet容器初始化, SpringMVC可实现容器的零配置注册.SpringServletContainerInitializer
ServletContainerInitializer提供了实现类
SpringServletContainerInitializer通过查看源代码可以知道,我们只需提供
WebApplicationInitializer的实现类到classpath下, 即可完成对所需
Servlet/
Filter/
Listener的注册.
javax.servlet.ServletContainerInitializer
元数据文件<strong>javax.servlet.ServletContainerInitializer</strong>只有一行内容(即实现了<code style="font-style: inherit;">ServletContainerInitializer</code>类的全限定名),该文本文件必须放在jar包的<strong>META-INF/services</strong>目录下.
相关文章推荐
- Servlet - Upload、Download、Async、动态注册
- Servlet - Upload、Download、Async、动态注册
- Servlet - Upload、Download、Async、动态注册
- Servlet – Upload、Download、Async、动态
- servletFileUploadAndDownload
- servlet 3.0笔记之servlet的动态注册
- 【Servlet3.0新特性】第02节_Servlet的动态注册以及使用注解编写监听器和过滤器
- servlet 3.0笔记之servlet的动态注册
- 第十六章_动态注册和Servlet容器初始化
- servlet 3.0笔记之servlet的动态注册
- Servlet JSP Tutorial -- 1.8 Servlet Upload File and Download File Example
- Servlet3.0动态注册Servlet、Filter和Listener
- servlet 3.0笔记之servlet的动态注册
- java.lang.NoSuchMethodError: javaxservlet.http.HttpServletRequest.isAsyncStarted()Z
- Spring运行时动态注册bean的方法
- Oracle listener静态注册和动态注册总结
- 静态注册和动态注册 无序广播 有序广播
- ftpclient upload and download----^^very well
- File Upload Download For iOS
- 用servlet作为服务器进行登陆注册界面实现(项目篇)