SpringInAction笔记(七)—— Spring MVC的高级技术(上)

       在第5章中,我们通过扩 展AbstractAnnotationConfigDispatcherServletInitializer快速搭建了Spring MVC环境。在这个便利的基础类中,假设我们需要基本的DispatcherServlet和ContextLoaderListener环境,并且Spring配置是使用Java的,而不是XML。
AbstractAnnotationConfigDispatcherServletInitializer剖析:
       在Servlet 3.0环境中,容器会在类路径中查找实现 javax.servlet.ServletContainerInitializer接口的类, 如果能发现的话,就会用它来配置Servlet容器。 Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring 3.2引入了一个便利的WebApplicationInitializer基础实现,也就 是AbstractAnnotationConfigDispatcherServletInitializer 。因为我们的Spittr-WebAppInitializer扩展了 AbstractAnnotationConfigDispatcherServletInitializer(同时也就实现了 WebApplicationInitializer),因此当部署到Servlet 3.0容器 中的时候,容器自动发现它,并用它来配置Servlet上下文。

7.1 Spring MVC配置的替代方案
7.1.1 自定义DispatcherServlet配置
       在SpittrWebAppInitializer中我们 所编写的三个方法仅仅是必须要重载的abstract方法。但实际上还有更多的方法可以进行重载,从而实现额外的配置。
   此类的方法之一就是customizeRegistration()。在AbstractAnnotationConfigDispatcherServletInitializer将DispatcherServlet注册到Servlet容器中之后,就会调用customizeRegistration(),并将Servlet注册后得到的Registration.Dynamic传递进来。通过重载customizeRegistration()方法,我们可以对DispatcherServlet进行额外的配置。 

7.1.2 添加其他的Servlet和Filter
       如果你想注册其他的Servlet、Filter或Listener的话,基于Java的初始化器(initializer)的一个好处就在于我们可以定义任意数量的初始化器类。因此,如果我们想往Web容器中注册其他组件的话,只需创建一个新的初始化器就可以了。最简单的方式就是实现Spring的WebApplicationInitializer接口
 

程序清单7.1 通过实现WebApplicationInitializer来注册Servlet

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;

import org.springframework.web.WebApplicationInitializer;

import spittr.servlet.MyServlet;

public class MySevletInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {

        Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class);//注册servlet
        myServlet.addMapping("/custom/**");  //添加映射Servlet
    }
}


类似地,还可以创建新的WebApplicationInitializer实现来注册Listener和Filter。

 

    程序清单7.2 注册Filter的WebApplicationInitializer

 

import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.springframework.web.WebApplicationInitializer;

import spittr.servlet.MyFilter;

public class MyFilterInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {

        Dynamic filter = servletContext.addFilter("MyFilter", MyFilter.class); //注册
        
        filter.addMappingForUrlPatterns(null, false, "/custom/*");   //添加Filter的映射路径
    }
}

如果要将应用部署到支持Servlet 3.0的容器中,那么WebApplicationInitializer提供了一种通用的方式,实现在 Java中注册Servlet、Filter和Listener。不过,如果你只是注册Filter, 并且该Filter只会映射到DispatcherServlet上的话,那么在AbstractAnnotationConfigDispatcherServletInitializer 中还有一种快捷方式。    

   为了注册Filter并将其映射到DispatcherServlet,所需要做的仅仅是重载AbstractAnnotationConfigDispatcherServletInitializer的getServlet-Filters()方法,来注册Filter。 

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[]{new MyFilter()};
    }

       这个方法返回的是一个javax.servlet.Filter的数组。在这里它只返回了一个Filter,但它实际上可以返回任意数量的Filter。在这里没有必要声明它的映射路径,getServletFilters()方法返回的所有Filter都会映射到DispatcherServlet上。

7.1.3 在web.xml中声明DispatcherServlet
    在典型的Spring MVC应用中,我们会需要DispatcherServlet和ContextLoaderListener。AbstractAnnotationConfigDispatcherServletInitializer会自动注册它们,但是如果需要在web.xml中注册的话,那就需要我们自己来完成这项任务了

   程序清单7.3 在web.xml中搭建Spring MVC

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" >

<!-- 设置root上下文配置文件位置 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>

<!-- 注册ContextLoaderListener -->
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

<!-- 注册DispatcherServlet -->
<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

<!--将DispatcherServlet映射到"/"-->
<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

ContextLoaderListener和 DispatcherServlet各自都会加载一个Spring应用上下文。上下文 参数contextConfigLocation指定了一个XML文件的地址,这个文件定义了根应用上下文,它会被ContextLoaderListener加载。如程序清单7.3所示,根上下文会从“/WEB-INF/spring/rootcontext.xml”中加载bean定义。

        DispatcherServlet会根据Servlet的名字找到一个文件,并基于该文件加载应用上下文。在程序清单7.3中,Servlet的名字 是appServlet,因此DispatcherServlet会从“/WEBINF/appServlet-context.xml”文件中加载其应用上下文。 如果你希望指定DispatcherServlet配置文件的位置的话,那么可以在Servlet上指定一个contextConfigLocation初始化参数。 如下的配置中,DispatcherServlet会从“/WEBINF/spring/appServlet/servlet-context.xml”加载它的bean:

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name> <!-- 指定的路径上加载应用上下文-->
        <param-value>
            /WEB-INF/spring/appServlet/servlet-context.xml
    </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

 

在web.xml中使用基于Java的配置       

 

   要在Spring MVC中使用基于Java的配置,需要告诉DispatcherServlet和ContextLoaderListener使用AnnotationConfigWebApplicationContext,这是一个WebApplicationContext的实现类,它会加载Java配置类,而不是使用XML。要实现这种配置,可以设置contextClass上下文参数以及DispatcherServlet的初始化参数。在下面文件中,它所搭建的Spring MVC使用基于Java的Spring配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" 
  id="WebApp_ID" version="3.1">

<context-param>
    <param-name>contextClass</param-name> 
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>

<!-- 设置root上下文配置文件位置 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>spittr.config.RootConfig</param-value>
</context-param>

<!-- 注册ContextLoaderListener -->
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

<!-- 注册DispatcherServlet -->
<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </init-param>
    <init-param>
        <param-name>contextConfigLocation</param-name> <!-- 指定应用上下文的加载路径 -->
        <param-value>
            spittr.config.WebConfig
        </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

 


7.2 处理multipart形式的数据
      尽管multipart请求看起来很负责,但在Spring MVC中处理它们却很容易。在编写控制器方法处理文件上传之前,我们必须要配置一个multipart解析器,通过它来告诉DispatcherServlet该如何读取multipart请求。

7.2.1 配置multipart解析器
        DispatcherServlet并没有实现任何解析multipart请求数据的功 能。它将该任务委托给了Spring中MultipartResolver策略接口的实现,通过这个实现类来解析multipart请求中的内容。从Spring 3.1开 始,Spring内置了两个MultipartResolver的实现供我们选择:

 

 

  • CommonsMultipartResolver:使用Jakarta Commons FileUpload解析multipart请求;

 

  • StandardServletMultipartResolver:依赖于Servlet 3.0 对multipart请求的支持(始于Spring 3.1)。

       一般来讲,在这两者之 间,StandardServletMultipartResolver可能会是优选的方 案。它使用Servlet所提供的功能支持,并不需要依赖任何其他的项 目。如果我们需要将应用部署到Servlet 3.0之前的容器中,或者还没 有使用Spring 3.1或更高版本,那么可能就需要 CommonsMultipartResolver了。

使用Servlet 3.0解析multipart请求

       兼容Servlet 3.0的StandardServletMultipartResolver没有构造器参数,也没有要设置的属性。这样,在Spring应用上下文中,将其声明为bean就会非常简单,如下所示:

public class WebConfig extends WebMvcConfigurerAdapter{

@Bean
public MultipartResolver multipartResolver()throws IOException{ return new StandardServletMultipartResolver();

}}

      虽然没办法直接通过StandardServletMultipartResolver配置限制条件的。但在Servlet中能指定multipart的配置。具体来讲,我们必须要在web.xml或Servlet初始化类中,将multipart的具体细节作为DispatcherServlet配置的一部分。

方法一:(没用过)

      如果采用Servlet初始化类的方式来配置DispatcherServlet的话,这个初始化类应该已经实现了WebApplicationInitializer,那我们可以在Servlet registration上调用setMultipartConfig()方法,传入一个MultipartConfig-Element实例。如下是最基本的DispatcherServlet multipart配置,它将临时路径设置为“/tmp/spittr/uploads”:

方法二:

      如果配置DispatcherServlet的Servlet初始化类继承了Abstract AnnotationConfigDispatcherServletInitializer或AbstractDispatcherServletInitializer的话,那么我们不会直接创建DispatcherServlet实例并将其注册到Servlet上下文中。这样的话,将不会有对Dynamic Servlet registration的引用供我们使用了。但是,我们可以通过重载customizeRegistration()方法(它会得到一个Dynamic作为参数)来配置multipart的具体细节:

public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
  @Override
    protected void customizeRegistration(Dynamic registration) {
        registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));
    }
}

 到目前为止,我们所使用是只有一个参数的 MultipartConfigElement构造器,这个参数指定的是文件系统 中的一个绝对目录,上传文件将会临时写入该目录中。但是,我们还可以通过其他的构造器来限制上传文件的大小。除了临时路径的位 置,其他的构造器所能接受的参数如下:

  •  上传文件的最大容量(以字节为单位)。默认是没有限制的。
  •  整个multipart请求的最大容量(以字节为单位),不会关心有多 少个part以及每个part的大小。默认是没有限制的。
  •  在上传的过程中,如果文件大小达到了一个指定最大容量(以字节为单位),将会写入到临时文件路径中。默认值为0,也就是所有上传的文件都会写入到磁盘上。

      例如,假设我们想限制文件的大小不超过2MB,整个请求不超过 4MB,而且所有的文件都要写到磁盘中。下面的代码使 用MultipartConfigElement设置了这些临界值:

	@Override
	protected void customizeRegistration(Dynamic registration) {
		// TODO Auto-generated method stub
		registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads", 2097152,4194304,0));
	}

注意:在这里设置了临时存储的路径,但是在tomcat中并没有该文件目录,所以后面执行程序时会报错。解决办法就是:在tomcat所在目录\work\Catalina\localhost\spittr 目录下新建tmp\spittr\uploads。

      或者在xml中配置:使用<servlet>中的<multipart-config>元素:


<multipart-config>的默认值与MultipartConfigElement相同。与MultipartConfigElement一样,必须要配置的是<location>。
 

 7.2.2 处理multipart请求       

      接下来就可以编写控制器方法来接收上传的文件。要实现这一点,最常见的方式就是在某个控制器方法参数上添 加@RequestPart注解。      

       需要修改表单,以允许用户选择要上传的图片,同时还需要修 改SpitterController 中的processRegistration()方法来接 收上传的图片。

程序清单1 SpitterForm.java:

package spittr.form;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Email;
import org.springframework.web.multipart.MultipartFile;

import spittr.Spitter;

public class SpitterForm {

    //非空,5到16个字符
    @NotNull
    @Size(min=5, max=16, message="{username.size}")
    private String username;

    @NotNull
    @Size(min=5, max=25, message="{password.size}")
    private String password;
    
    @NotNull
    @Size(min=2, max=30, message="{firstName.size}")
    private String firstName;

    @NotNull
    @Size(min=2, max=30, message="{lastName.size}")
    private String lastName;
    
    @NotNull
    @Email
    private String email;
  
    private MultipartFile profilePicture;
  
    public String getUsername() {    
    	return username;
    }
  
    public void setUsername(String username) {    
    	this.username = username;
    }
  
    public String getPassword() {   
    	return password;
    }
  
    public void setPassword(String password) {  
    	this.password = password;
    }

    public String getFirstName() {
    	
    	return firstName;
    }
  
    public void setFirstName(String firstName) {    
    	this.firstName = firstName;
    }
  
    public String getLastName() {     
    	return lastName;
    }
  
    public void setLastName(String lastName) {    
    	this.lastName = lastName;
    }
    
    public String getEmail() {    
    	return email;
    }
    
    public void setEmail(String email) {    
    	this.email = email;
    }

    public MultipartFile getProfilePicture() {  
    	return profilePicture;
    }

    public void setProfilePicture(MultipartFile profilePicture) {  
    	this.profilePicture = profilePicture;
    }
 
    public Spitter toSpitter() {  
    	return new Spitter(username, password, firstName, lastName, email);
    }
  
}

 

程序清单2 registerForm.html:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" type="text/css" 
          th:href="http://blog.163.com/yetong1985@126/blog/@{/resources/style.css}"></link>
  </head>
  <body>
    <div id="header" th:include="page :: header"></div>

    <div id="content">
      <h1>Register</h1>
  
      <form method="POST" th:object="${${spitterForm}}" enctype="multipart/form-data">
        <div class="errors" th:if="${#fields.hasErrors('*')}">
          <ul>
            <li th:each="err : ${#fields.errors('*')}" 
                th:text="${err}">Input is incorrect</li>
          </ul>
        </div>
        <label th:class="${#fields.hasErrors('firstName')}? 'error'">First Name</label>: 
          <input type="text" th:field="*{firstName}"  
                 th:class="${#fields.hasErrors('firstName')}? 'error'" /><br/>
  
        <label th:class="${#fields.hasErrors('lastName')}? 'error'">Last Name</label>: 
          <input type="text" th:field="*{lastName}"
                 th:class="${#fields.hasErrors('lastName')}? 'error'" /><br/>
  
        <label th:class="${#fields.hasErrors('email')}? 'error'">Email</label>: 
          <input type="text" th:field="*{email}"
                 th:class="${#fields.hasErrors('email')}? 'error'" /><br/>
  
        <label th:class="${#fields.hasErrors('username')}? 'error'">Username</label>: 
          <input type="text" th:field="*{username}"
                 th:class="${#fields.hasErrors('username')}? 'error'" /><br/>
  
        <label th:class="${#fields.hasErrors('password')}? 'error'">Password</label>: 
          <input type="password" th:field="*{password}"  
                 th:class="${#fields.hasErrors('password')}? 'error'" /><br/>
                 
        <label>Profile Picture</label>:
          <input type="file"
                 name="profilePicture"
                 accept="image/jpeg,image/png,image/gif" /><br/>  
                        
        <input type="submit" value="Register" />       
      </form>
    </div>
    <div id="footer" th:include="page :: copy"></div>
  </body>
</html>

<form>标签现在将enctype属性设置为multipart/formdata,这会告诉浏览器以multipart数据的形式提交表单,而不是以表 单数据的形式进行提交。在multipart中,每个输入域都会对应一个 part。       

       除了注册表单中已有的输入域,我们还添加了一个新的<input> 域,其type为file。这能够让用户选择要上传的图片文件。accept 属性用来将文件类型限制为JPEG、PNG以及GIF图片。根据其name 属性,图片数据将会发送到multipart请求中的profilePicture part 之中。       

       现在,我们需要修改processRegistration()方法,使其能够接 受上传的图片。其中一种方式是添加byte数组参数,并为其添 加@RequestPart注解。如下为示例:

	@RequestMapping(value="/register", method=RequestMethod.POST)
	public String processRegistration(
			@RequestPart("profilePicture") byte[] profilePicture,
			@Valid Spitter spitter, 
			Errors errors) {
            ...
	}

当注册表单提交的时候,profilePicture属性将会给定一个byte 数组,这个数组中包含了请求中对应part的数据(通过 @RequestPart指定)。如果用户提交表单的时候没有选择文件,那么这个数组会是空(而不是null)。获取到图片数据 后,processRegistration()方法剩下的任务就是将文件保存到 某个位置。

接受MultipartFile
       使用上传文件的原始byte比较简单但是功能有限。因此,Spring还提供了MultipartFile接口,它为处理multipart数据提供了内容更为丰富的对象。

       MultipartFile提供了获取上传文件byte的方式,还能获得原始的文件名、大小以及内容类型。它还提供了一个InputStream,用来将文件数据以流的方式进行读取。

       MultipartFile还提供了一个便利的transferTo()方法,它能够帮助我们将上传的文件写入到文件系统中。

	@RequestMapping(value="/register", method=RequestMethod.POST)  
	public String processRegistration(    
			@Valid SpitterForm spitterForm,      
			Errors errors) throws IllegalStateException, IOException {
	     
		if (errors.hasErrors()) {
	      return "registerForm";
	    }
	    Spitter spitter = spitterForm.toSpitter();
	    spitterRepository.save(spitter);
	    MultipartFile profilePicture = spitterForm.getProfilePicture();
	    profilePicture.transferTo(new File(spitter.getUsername() + ".jpg"));
	    return "redirect:/spitter/" + spitter.getUsername();  
	}

上述代码将图片保存到 本地tomcat位置\work\Catalina\localhost\spittr\tmp\spittr\uploads 下。

 

以Part的形式接受上传的文件

        如果需要将应用部署到Servlet 3.0的容器中,Spring MVC也能接受 javax.servlet.http.Part作为控制器方法的参数。如果使用 Part来替换MultipartFile的话,那么processRegistration() 的方法签名将会变成如下的形式: 

	@RequestMapping(value="/register", method=RequestMethod.POST)
	public String processRegistration(
			@RequestPart("profilePicture") byte[] profilePicture,
			@Valid Spitter spitter, 
			Errors errors) {
			...
	}


Part接口与MultipartFile并没有 太大的差别:

 


 

 如果在编写控制器方法的时候,通过Part参数的形式 接受文件上传,那么就没必要配置MultipartResolver了。只有使用MultipartFile的时候,我们才需要 MultipartResolver。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值