Spring实战4之SpringMVC高级篇

一、DispatcherServlet个性化配置
继承AbstractAnnotationConfigDispatcherServletInitializer时,同时还有许多其他方法可以重写从而可以实现更多的配置。
例如customizeRegistration()方法,在注册了DispatcherServlet之后,就会调用customizeRegistration()方法,并根据servlet的注册返回值传送ServletRegistration.Dynamic,通过对customizeRegistration()的重写,就可以对DispatcherServlet进行额外的配置。其中ServletRegistration.Dynamic作为入参,你可以做很多事情,比如调用setLoadOnStartup()来设置加载时优先级,调用setInitParameter()来设置初始化参数,调用setMultipartConfig()来设置Servlet3.0的多路支持

/**
 * Spring MVC如何处理多个请求和文件上传
 * 设置了多路支持的上传文件临时存储路径为:/tmp/spittr/uploads。
 */
@Override
protected void customizeRegistration(Dynamic registration) {
    registration.setMultipartConfig(
        new MultipartConfigElement("/tmp/spittr/uploads"));
}

二、添加额外的Servlet和Filter
1.注册额外的Servlet、Filter或者Listener
(1)最简单的方法就是实现Spring的WebApplicationInitializer接口。
例如,下面的代码展示了如何通过实现WebApplicationInitializer接口的方式来注册一个Servlet:

public  class MyServletInitializer implements WebApplicationInitializer {
            @Override
            public void onStartup(ServletContext servletContext) throws ServletException {
                // 定义Servlet
                Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class);
                // 映射Servlet
                myServlet.addMapping("/custom/**");
            }
        }

(2)使用AbstractAnnotationConfigDispatcherServletInitializer注册Filter并将其映射到DispatcherServlet

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

三、使用web.xml声明DispatcherServlet
1.典型的web.xml文件,其中对DispatcherServlet和ContextLoaderListener进行了声明:


<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <context-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>/WEB-INF/spring/root-context.xmlparam-value>
    context-param>
    <listener>
        
        <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
    listener>
    <servlet>
        <servlet-name>appServletservlet-name>
        
        <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
        <load-on-startup>1load-on-startup>
    servlet>
    
    <servlet-mapping>
        <servlet-name>appServletservlet-name>
        <url-pattern>/url-pattern>
    servlet-mapping>
web-app>

2.通过设置contextConfigLocation初始化参数的方式指定DispatcherServlet配置文件的位置,例如,下面的DispatcherServlet配置就会从/WEB-INF/spring/appServlet/servlet-context.xml文件中加载:

<servlet>
    <servlet-name>appServletservlet-name>
    
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    <init-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>
            /WEB-INF/spring/appServlet/servlet-context.xml
        param-value>
    init-param>
    <load-on-startup>1load-on-startup>
servlet>

3.基于Java的配置方式,通过设置DispatcherServlet的contextClass参数和初始化参数来实现
为了使用基于Java的配置,需要通知DispatcherServlet和ContextLoaderListener去使用AnnotationConfigWebApplicationContext,该类是WebApplicationContext接口的实现类,它可以对Java配置类进行加载。


<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    
    <context-param>
        <param-name>contextClassparam-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        param-value>
    context-param>

    
    <context-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>spittr.config.RootConfigparam-value>
    context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        listener-class>
    listener>
    <servlet>
        <servlet-name>appServletservlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
        
        <init-param>
            <param-name>contextClassparam-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            param-value>
        init-param>
        
        <init-param>
            <param-name>contextConfigLocationparam-name>
            <param-value>
                spittr.config.WebConfigConfig
            param-value>
        init-param>
        <load-on-startup>1load-on-startup>
    servlet>
    <servlet-mapping>
        <servlet-name>appServletservlet-name>
        <url-pattern>/url-pattern>
    servlet-mapping>
web-app>

四、处理multipart表单数据
1.配置multipart解析器
从Spring3.1开始,Spring提供了两种MultipartResolver实现类供选择:
CommonsMultipartResolver:使用Jakarta Commons FileUpload来解析multipart请求;
StandardServletMultipartResolver:依靠Servlet 3.0支持来解析(Spring 3.1及以上,第一选择);

设置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;
}

2.处理multipart请求
页面

<form method="POST" th:object="${spitter}" *enctype="multipart/form-data"*>
    <label>Profile Picturelabel>:
    <input type="file" name="profilePicture" accept="image/jpeg,image/png,image/gif" /><br/>
    <input type="submit" value="Register" />
 form>

(1) @RequestPart注解一个控制器参数获取到上传的文件

/**
 * 当注册表单提交时,请求部分的数据就会赋予到profilePicture属性中,
 * 如果用户没有选中一个文件,那么该数组就会是一个空值(不是null)
 */
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String processRegistration(@RequestPart("profilePicture") byte[] profilePicture, @Valid Spitter spitter, Errors errors) {

}

(2)使用MultipartFile接口保存上传文件
MultipartFile提供获取上传文件的方法,同时提供了很多其他方法,比如原始文件名称、大小和内容类型等。另外还提供了一个InputStream可以将文件数据作为数据流读取。
另外,MultipartFile还提供了一个方便的transferTo()方法帮助你将上传文件写入到文件系统

public interface MultipartFile {
    String getName();
    String getOriginalFilename();
    String getContentType();
    boolean isEmpty();
    long getSize();
    byte[] getBytes() throws IOException;
    InputStream getInputStream() throws IOException;
    void transferTo(File dest) throws IOException;
}

(3)将文件保存到Amazon S3管理文件
下面的代码可以将上传的图像保存到Amazon S3:

private void saveImage(MultipartFile image) throws ImageUploadException {
    try {
        //设置Amazon Web Service (AWS)认证,你需要提供S3的密钥和私钥,这些在注册S3服务时Amazon都会给你的
        AWSCredentials awsCredentials = new AWSCredentials(s3AccessKey, s2SecretKey);
        // 配置S3服务
        S3Service s3 = new RestS3Service(awsCredentials);
        // 创建S3 bucket对象
        S3Bucket bucket = s3.getBucket("spittrImages");
        S3Object imageObject = new S3Object(image.getOriginalFilename());
        // 设置图像数据
        imageObject.setDataInputStream(image.getInputStream());
        imageObject.setContentLength(image.getSize());
        imageObject.setContentType(image.getContentType());
        AccessControlList acl = new AccessControlList();
        // 设置权限
        acl.setOwner(bucket.getOwner());
        acl.grantPermission(GroupGrantee.ALL_USERS, Permission.PERMISSION_READ);
        imageObject.setAcl(acl);
        // 保存图片
        s3.putObject(bucket, imageObject);
    } catch (Exception e) {
        throw new ImageUploadException("Unable to save image", e);
    }

(4)在Servlet 3.0的容器上,Spring MVC也可以将javax.servlet.http.Part作为控制器的入参

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

   }

Part接口

public interface Part {
    public InputStream getInputStream() throws IOException;
    public String getContentType();
    public String getName();
    public String getSubmittedFileName();
    public long getSize();
    public void write(String fileName) throws IOException;
    public void delete() throws IOException;
    public String getHeader(String name);
    public Collection getHeaders(String name);
    public Collection getHeaderNames();
}

五、异常处理
(1)@ResponseStatus来将其映射到404。

@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Spittle Not Found")
public class SpittleNotFoundException extends Exception {

}

(2)编写异常处理方法
@ExceptionHandler注解的方法在同一个控制器里是通用的,即无论SpittleController的哪一个方法抛出DuplicateSpittleException异常,handleDuplicateSpittle()方法都可以对其进行处理,而不再需要在每一个出现异常的地方进行捕获

@ExceptionHandler(DuplicateSpittleException.class)
public String handleDuplicateSpittle() {
    return "error/duplicate";
}

(3)@ControllerAdvice控制器增强类
使用@ControllerAdvice控制器增强类,不论哪一个controller抛出DuplicateSpittleException,都会调用handleDuplicateSpittle()方法来处理。

// 声明控制器增强
@ControllerAdvice
public class AppWideExceptionHandler {

    // 定义异常处理方法
    @ExceptionHandler(DuplicateSpittleException.class)
    public String handleDuplicateSpittle() {
        return "error/duplicate";
    }

    @ExceptionHandler(SpittleNotFoundException.class)
    public String handleSpittleNotFound() {
        return "error/duplicate";
    }

}

六、在redirect请求中携带数据进行传递
跳转的路径:
return “redirect:/spitter/” + spitter.getUsername();

两种方法用来从重定向的方法中获取数据:
(1)将数据转换为路径参数或者查询参数
使用URL模版重定向:使用路径参数和查询参数传递数据比较简单,它只适用于传递简单值,比如String和数字,不能传递比较复杂的东西。

由于model中的spitterId属性并没有映射到URL中的占位符,它会自动作为查询参数。
如果username是habuma,spitterId是42,那么返回的重定向路径将是/spitter/habuma?spitterId=42。

@RequestMapping(value="/register", method=POST)
public String processRegistration(Spitter spitter, Model model) {
    spitterRepository.save(spitter);
    model.addAttribute("username", spitter.getUsername());
    model.addAttribute("spitterId", spitter.getId());
    return "redirect:/spitter/{username}";
}

(2)在flash属性中发送数据
使用flash属性
在重定向中传送一个Spitter对象。
在重定向之前,所有的flash属性都会拷贝到session中,在重定向之后,存储在session中的flash属性会从session中移出到model中。然后处理重定向请求的方法就可以使用Spitter对象了

@RequestMapping(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}";
}

你可能感兴趣的:(spring,in,action)