1、自定义DispatcherServlet的配置
SpitterWebinitializer实现了AbstractAnnotationConfigDispatcherServletInitializer抽象类,并重写了三个必须的方法,实际上还可对更多方法进行重写,以便实现额外的配置。
如对customizeRegistration方法进行重写,该方法是AbstractDispatcherServletInitializer的方法,无实际的方法体。当AbstractAnnotationConfigDispatcherServletInitializer将DispatcherServlet注册到Servlet容器中后,就会调用customizeRegistration方法,并将Servlet注册后得到的Registration.Dynamic传入。可通过重写customizeRegistration方法设置MultipartConfigElement,如图所示:
@Override protected void customizeRegistration(Dynamic registration) { registration.setMultipartConfig( new MultipartConfigElement("/tmp/spittr/uploads")); }
添加其他Servlet和Filter
AbstractAnnotationConfigDispatcherServletInitializer会创建DispatcherServlet和ContextLoaderListener,当需要添加其他Servlet和Filter时,只需要创建一个新的初始化器即可,最简单的方式是实现WebApplicationInitializer接口。
public class MyServletInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { // 注册servlet Dynamic servlet = servletContext.addServlet("myServlet", MyServlet.class); // 映射servlet servlet.addMapping("/custom/**"); // 注册filter FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter", MyFilter.class); // 添加filter的映射路径 filter.addMappingForUrlPatterns(null, false, "/custom/*"); } }
使用AbstractAnnotationConfigDispatcherServletInitializer注册Filter并将其映射到DispatcherServlet
@Override protected Filter[] getServletFilters() { return new Filter[] { new MyFilter() }; }
2、处理multipart形式数据
处理multipart数据主要用于处理文件上传操作。需要配置multipart解析器读取multipart请求。
配置multipart解析器 DispatcherServlet并未实现任何解析multipart请求数据功能,它只是将任务委托给MultipartResolver策略接口实现,通过该实现解析multipart请求内容,Spring中内置了CommonsMultipartResolver和StandardServletMultipartResolver两个解析器。 使用StandardServletMultipartResolver使用Java配置如下:
@Override protected void customizeRegistration(Dynamic registration) { registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads",2079152, 4194304, 0)); }
通常来讲使用StandardServletMultipartResolver是最佳的选择,设置StandardServletMultipartResolver的,但是它的设置不是在Spring配置中进行的,而是在Servlet配置中。起码要配置一下存放临时文件的位置,进一步来讲,还要将multipart配置为DispatcherServlet的一部分。
(1)继承自WebMvcConfigurerAdapter的servlet初始化类中配置的DispatcherServlet,那么就可以在servlet注册时通过调用setMultipartConfig()方法来配置multipart详情。比如:
DispatcherServlet ds = new DispatcherServlet(); Dynamic registration = context.addServlet("appServlet", ds); registration.addMapping("/"); registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));
(2)继承自AbstractAnnotationConfigDispatcherServletInitializer或者AbstractDispatcherServletInitializer的servlet初始化类进行的配置,没有创建DispatcherServlet的实例或者使用servlet上下文对其进行注册。因此就没有直接的引用供Dynamicservlet注册来使用。重写customizeRegistration()方法来进行配置:
@Override protected void customizeRegistration(Dynamic registration) { registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads")); }
(3)MultipartConfigElement的唯一参数设置了上传文件时临时文件的存放位置。也可以进行其他一些设置:
文件上传的最大值(byte),默认没有限制;
所有multipart请求的文件最大值(byte),不管有多少个请求,默认无限制;
直接上传文件(不需存储到临时目录)的最大值(byte),默认是0,也就是所有的文件都要写入硬盘;
例如,你想设置文件大小不超过2MB,所有请求的总和不超过4MB,并且所有文件都要写入硬盘,那么就可以这样设置:@Override protected void customizeRegistration(Dynamic registration) { registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads", 2097152, 4194304, 0)); }
(4)传统的web.xml的方式来设置的DispatcherServlet,那么就需要使用多个元素,其默认值和MultipartConfigElement相同,并且是必填项:
<servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> <multipart-config> <location>/tmp/spittr/uploads</location> <max-file-size>2097152</max-file-size> <max-request-size>4194304</max-request-size> </multipart-config> </servlet>
(5)配置Jakarta Commons FileUpload解析器CommonsMultipartResolver
这里设置了文件的最大大小为2MB,最大的内存中大小为0,即每个上传文件都会直接写入磁盘的。但是它是无法设置multipart请求总的文件大小的:@Bean public MultipartResolver multipartResolver() throws IOException { CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); multipartResolver.setUploadTempDir(new FileSystemResource("/tmp/spittr/uploads")); multipartResolver.setMaxUploadSize(2097152); multipartResolver.setMaxInMemorySize(0); return multipartResolver; }
3、处理multipart请求
可在控制器的方法参数上添加@RequestPart注解,如下所示:
@RequestMapping(value="/register", method=POST) public String processRegistration( @RequestPart("profilePicture") byte[] profilePicture, @Valid Spittr spitter, Errors errors) { profilePicture.transferTo(new File("/data/spittr" + profilePicture.getOriginalFilename())); }
在SpringBoot项目中使用完成文件上传:(此案例只展示核心代码,如有需要,留言即可)
<!-- 添加产品 Modal --> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true" onclick="autoRefresh()">×</span></button> <h4 class="modal-title" id="myModalLabel">添加产品</h4> </div> <form action="/saveGoods" method="post" enctype="multipart/form-data" class="form-horizontal" name="goodsForm" id="goodsForm"> <div class="modal-body"> <!-- sssssssssssssssssssssssssss --> <div class="form-group"> <label class="col-sm-3 control-label">产品名称</label> <div class="col-sm-8"> <input type="text" name="goodsName" class="form-control" id="goodsName" placeholder="产品名称"> </div> </div> <div class="form-group"> <label class="col-sm-3 control-label">产品图样</label> <div class="col-sm-8"> <input type="file" name="goodsImgs" class="form-control" id="goodsImgs" accept="image/jpg, image/png, image/jpeg, image/gif"> </div> </div> <!--eeeeeeeeeeeeeeeeeeeeeeeeeee--> </div> <div class="modal-footer"> <input type="reset" value="重置" class="btn btn-danger"> <button type="button" class="btn btn-default" onclick="autoRefresh()" data-dismiss="modal">取消</button> <button type="button" id="btn-submit" onclick="saveGoods()" class="btn btn-primary">确定</button> </div> </form> </div> </div> </div> <script th:inline="javascript"> // 保存产品 function saveGoods(){ // document.getElementById("goodsForm").submit(); $("#goodsForm").bootstrapValidator('validate');//提交验证 if ($("#goodsForm").data('bootstrapValidator').isValid()) {//获取验证结果,如果成功,执行下面代码 // 点击添加确定按钮的时候,判断产品ID是否存在,存在就是修改,不存在就是添加 var gid = $("#titleSSID").val(); var pic = $('#goodsImgs')[0].files[0]; var formData = new FormData(); formData.append('goodsId',gid); formData.append('goodsName',$("#goodsName").val()); formData.append('goodsCode',$("#goodsCode").val()); formData.append('goodsImg',pic); $.ajax({ //几个参数需要注意一下 type: "POST",//方法类型 dataType: "json",//预期服务器返回的数据类型 cache: false, //上传文件不需要缓存 url: "/saveGoods",//url // data: $('#goodsForm').serialize(), data:formData, cache: false, contentType: false, processData: false, success: function (result) { if (result.succ == true) { if(confirm("添加成功,是否继续添加?")){ document.getElementById("goodsName").value = ""; document.getElementById("goodsCode").value = ""; }else { location.replace(location.href); } }else { alert(result.message); } }, error : function() { alert("系统繁忙,请稍后重试!"); } }); } } </script>
Controller接收:
@PostMapping("/saveGoods") @ResponseBody public JSONObject saveGoods(HttpServletRequest request,String goodsId, String goodsName, String goodsCode, MultipartFile goodsImg, ){ JSONObject jsonObject = new JSONObject(); // 这里我的逻辑处理是,把goodsImg转成IO流,上传到了阿里云 }
4、异常处理
Spring提供了多种方式将异常转换为响应
- 特定的异常将会自动映射为指定的HTTP状态码。
- 异常上可以添加@ResponseStatus注解,从而将其映射为某个HTTP状态码。
- 在方法上可添加@ExceptionHandler注解,使其用来处理异常。
编写异常处理方法
可在请求中直接使用try/catch处理异常,其与正常Java方法中的try/catch相同,同时,也可编写处理器来处理特定异常,当出现特定异常时,将有处理器委托方法进行处理。
@ExceptionHandler(DuplicateSpittleException.class) public String handleDuplicateSpittle() { return "error/duplicate"; }
为控制器添加通知
控制器通知是任意带有@ControllerAdvice注解的类,该类包含如下类型的一个或多个方法。
- @ExceptionHandler注解标注的方法。
- @InitBinder注解标注的方法。
- @ModelAttribute注解标注的方法。
上面方法会运用到整个应用程序所有控制器中带有@RequestMapping注解的方法上。
@ControllerAdvice public class AppWideExceptionHandler { @ExceptionHandler(DuplicateSpittleException.class) public String duplicateSpittleHandler() { return "error/duplicate"; } }
这样,任意控制器方法抛出了DuplicateSpittleException异常,都会调用这个duplicateSpittleHandler方法处理异常。
5、跨重定向请求传递数据
对于重定向而言,若需要从发起重定向的方法传递数据给处理重定向方法中,有如下两种方法:
- 使用URL模版以路径变量和或查询参数形式传递数据。
- 通过flash属性发送数据。
通过URL模版进行重定向
如前面讲到的通过redirect:/spitter/{username}进行重定向,该方法会直接根据username确定url,并非十分安全的做法,可使用进行如下处理:
model.addAttribute("username", spitter.getUsername()); return "redirect:/spitter/{username}";
当需要传递参数,如id时,可进行如下处理:
model.addAttribute("username", spitter.getUsername()); model.addAttribute("spitterId", spitter.getId()); return "redirect:/spitter/{username}";
若username为zhangsan;id为100。这样访问的url为/spitter/zhangsan?spitterId=100。这种方法只能传递简单的值,无法发送更为复杂的值,此时需要使用flash属性。
使用flash属性
在重定向发生之前,将数据放在会话中,在重定向完成之后,从会话中将其取出,并将会话中的数据清理掉。而 Spring 的实现略有不同,需要使用 RedirectAttributes 对象,会话中 flash 属性会一直携带这些数据,直到下一次请求,然后才会消失。所有通过 RedirectAttributes的addFlashAttribute 添加的属性会复制到会话中。在重定向后,存在会话中的 flash 属性会被取出,并从会话转移到模型中。处理重定向的方法就能从模型中访问 Spittle 对象了。
Spring提供了通过RedirectAttributes设置flash属性的方法,这个Spring3.1引入的Model的一个子接口。RedirectAttributes提供 了一组addFlashAttribute()方法来添加flash属性
@ReuqestMapping(value="/register", method=POST) public String processRegistration(Spitter spitter, RedirectAttributes model) { spitterRepository.save(spitter); model.addAttribute("username", spitter.getUsername()); model.addFlashAttribute("spitter", spitter); return "redirect:/spitter/{username}"; }
这样spitter对象也被传递到重定向页面中,可直接访问spitter对象。获取对象:
@RequestMapping(value="/{username}", method = RequestMethod.GET) public String showSpitterProfile(@PathVariable String username, Model model){ if(!model.containsAttribute("spitter")){ model.addAttribute(spitterRepository.findByUsername(username)); } return "profile"; }