【SpringMVC】

响应数据和结果视图

返回值的分类

处理器返回String对象

转发到字符串指定的URL
处理器方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址.

在本例中,因为我们在Spring容器配置文件bean.xml中配置的视图解析器中注入prefix和suffix属性,所以视图解析器会把处理器返回的"字符串值"解析为"/WEB-INF/pages/字符串值.jsp",再请求对应视图.这是一个请求转发过程,浏览器地址栏不会发生变化.

bean.xml中配置的视图解析器如下:

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>

处理器方法如下:

@Controller
@RequestMapping("/user")
public class UserController {

@RequestMapping("/testString")
public String testString(Model model) {
	// 执行方法体...向隐式对象添加属性attribute_user,可以在jsp中通过 ${attribute_user} 获取到
	model.addAttribute("attribute_user", new User("张三", "123"));
    
    // 经过视图解析器的处理,SpringMVC会将请求转发到/WEB-INF/pages/succeess.jsp,但浏览器地址栏显示的一直是 项目域名/user/testString
    return "success";
}

}

处理器返回void: 转发到当前URL

若处理器返回void,表示执行完处理器方法体内代码后,不进行请求转发,而直接转发到当前URL.若没有在web.xml中配置当前对应的url-pattern,则会返回404错误.

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/testVoid")
    public void testVoid(Model model) {
		// 执行方法体...向隐式对象添加属性attribute_user,可以在jsp中通过 ${attribute_user} 获取到
		model.addAttribute("attribute_user", new User("张三", "123"));
        
        // 处理器没有返回值,则会将请求转发到当前 项目域名/user/testVoid 路径
        // 若在web.xml中没有配置 项目域名/user/testVoid 对应的url-pattern,则会返回404错误
        return;
    }
}

可以在返回语句之前执行请求转发,重定向或getWriter方法指定视图,示例如下:

@Controller
@RequestMapping("/user")
public class UserController {

	@RequestMapping("/testVoid")
    public void testVoid(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 执行方法体...向隐式对象添加属性attribute_user,可以在jsp中通过 ${attribute_user} 获取到
		model.addAttribute("attribute_user", new User("张三", "123"));
        
        // 通过下面三个方法之一,可以指定访问的视图
        // 指定视图的方式1: 请求转发
        request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);

        // 指定视图的方式2: 重定向
        response.sendRedirect(request.getContextPath() + "/index.jsp");

        // 指定视图的方式3: 通过Writer对象写入内容
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        response.getWriter().print("你好");

        return;
    }
}

处理器返回ModelAndView对象: 更灵活地添加属性和指定返回视图

ModelAndView为我们提供了一种更灵活地为页面添加属性和指定返回视图的方法,其主要方法如下:

public ModelMap getModelMap(): 返回当前页面的ModelMap对象.
public ModelAndView addObject(Object attributeValue): 向当前页面的ModelMap对象中添加属性
public void setViewName(@Nullable String viewName): 指定返回视图,viewName会先被视图解析器处理解析成对应视图.

@Controller
@RequestMapping("/user")
public class UserController {

	@RequestMapping("/testModelAndView")
    public ModelAndView testModelAndView() {
        
        // 创建ModelAndView对象
        ModelAndView mv = new ModelAndView();
        
        // 向model中存入属性attribute_user
		mv.addObject("attribute_user", new User("张三", "123"));

        // 指定返回视图,视图解析器将"success"解析为视图URL /WEB-INF/pages/succeess.jsp
        mv.setViewName("success");

        return mv;
    }
}

SpringMVC框架提供的请求转发和重定向

使用SpringMVC框架提供的请求转发

要使用SpringMVC框架提供的请求转发,只需要在处理器方法返回的viewName字符串首加上forward:即可,要注意的是,此时forward:后的地址不能直接被视图解析器解析,因此要写完整的相对路径.示例如下:

@Controller
@RequestMapping("/user")
public class UserController {
	@RequestMapping("/testForward")
    public String testForward() {
        // 在forward:要写完整的相对路径
        // return "forward:success"	// 错误,会将请求转发到 /项目名/user/success
        return "forward:/WEB-INF/pages/success.jsp";
    }
}

使用SpringMVC框架提供的重定向

要使用SpringMVC框架提供的请求重定向,只需要在处理器方法返回的viewName字符串首加上redirect:即可,要注意的是,此时redirect:后的地址要写相对于ContextPath的地址.示例如下:

@Controller
@RequestMapping("/user")
public class UserController {
	@RequestMapping("/testRedirct")
    public String testRedirct() {
        // 在forward:要写完整的相对路径
        // return "redirect:" + request.getContextPath() + "/index.jsp";	// 错误,会将请求转发到 /项目名/项目名/index.jsp
		return "redirect:/index.jsp";
    }
}

SpringMVC响应json数据

前期准备

  1. jsp在页面上引入jQuery以发送json数据,因此需要向服务器发起一个对jQuery的请求.像这种对静态资源的请求,不应当经过具体的某个处理器处理,而应当直接返回对应的静态资源.
    因此我们需要在Spring容器配置bean.xml中使用mvc:resources标签声明该资源为静态资源,否则请求该资源会报404错误.该标签的属性如下:
    1.location属性: 表示该资源在服务器上所在的位置,必须是一个有效的目录
    2.mapping属性: 指定匹配的URL

我们在bean.xml中配置各静态文件的位置如下:

<!-- 配置静态文件的路径于对应的URL -->
<!-- location属性必须是一个有效的目录,因此必须以 / 结尾 -->
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/images/" mapping="/images/**"/>
<mvc:resources location="/js/" mapping="/js/**"/>

2.要将json字符串与JavaBean对象相互转换,我们需要引用jackson的jar包,在pom.xml中添加依赖座标如下:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.0</version>
</dependency

在jsp中编写代码发送json数据

在jsp页面中编写代码发送json请求如下:

<script>
// 页面加载,绑定单击事件
$(function () {
    $("#btn").click(function () {
        // 发送ajax请求
        $.ajax({
            // 配置请求参数
            url: "user/testAjax",
            contentType: "application/json;charset=UTF-8",
            dataType: "json",
            type: "post",
            // 请求的json数据
            data: '{"username":"myname","password":"mypassowrd","age":30}',
            // 回调函数,处理服务器返回的数据returnData
            success: function (returnData) {
                // 我们假定服务器返回的是一个user对象,将其输出在控制台上
                console.log(returnData);            }
        });
    });
});
</script>

在控制器中编写代码响应json数据

使用@RequestBody注解将请求体绑定到控制器方法参数上,使用@ResponseBody注解表示将该方法的返回值直接写回到HTTP响应中,而不是存入Model或解析为视图名.

我们引入的jackson包自动完成从Java实体类到json数据之间的相互转换.

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/testAjax")
    @ResponseBody
    public User testAjax(@RequestBody User user) {

        System.out.println(user);
        
        // 将user对象返回给前端页面
        return user;
    }
}

参考链接:参考博客链接

文件上传

前提条件

  1. 表单的enctype属性取值必须是multipart/form-data(默认值是application/x-www-form-urlencoded),表示表单内容是分块的。这时request对象的getParameter()方法将失效。
  2. 表单的method属性取值必须是post,因为get请求长度有限制。
  3. 提供一个标签,用来选择上传文件.
<form action="/fileUpload/uploadHandler" method="post" enctype="multipart/form-data">
    param1<input type="text" name="param1"/><br/>
    param2<input type="text" name="param2"/><br/>
    选择文件<input type="file" name="fileParam"/><br/>
    <input type="submit" value="上传文件"/>
</form>

  1. 引用文件上传的相关jar包
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

文件表单内容

因为我们设置了enctype属性取值为multipart/form-data,因此在请求参数头中会有一项Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryOMtUEa1sSZ3ayCfC,表示当前表单内容数据是分块的,每两块之间以----WebKitFormBoundaryOMtUEa1sSZ3ayCfC分界.
服务器通过遍历每一块数据,找到文件所在的数据块并执行保存.

文件上传的三种实现

使用JavaEE进行文件上传

@Controller
@RequestMapping("/user")
public class UserController {
    //文件上传,传统方式
    @RequestMapping("/fileupload1")
    public String fileupload1(HttpServletRequest request) throws Exception {
        System.out.println("文件上传");
        //使用fileupload组件完成文件上传
        //上传的位置
        String path = request.getSession().getServletContext().getRealPath("/uploads/");
        //判断路径是否存在
        File file = new File(path);
        if (!file.exists()) {
            //创建该文件夹
            file.mkdir();
        }
        //解析request对象,获取上传文件项
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
        //解析request
        List<FileItem> items = upload.parseRequest(request);
        // 遍历解析的结果,寻找上传文件项
        for (FileItem item : items) {
            // 进行判断,当前item对象是否是上传文件项
            if (!item.isFormField()) {//不是普通表单项
                // 服务器中保存的文件名
                String filename = UUID.randomUUID().toString().replace("-", "") + "_" + item.getName();
                // 上传文件
                item.write(new File(path, filename));
                System.out.println(path);
                // 删除临时文件
                item.delete();
            }
        }
        return "success";
    }
}

使用SpringMVC进行单服务器文件上传

可以使用SpringMVC提供的文件解析器实现文件上传,在Spring容器中注入文件解析器CommonsMultipartResolver对象如下:

<!-- 配置文件解析器,其id是固定的,必须为multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 设置文件的最大尺寸 -->
    <property name="maxUploadSize" value="10485760"/>
</bean>

只要在处理器方法的参数列表中定义一个与表单文件项同名的MultipartFile参数,就可以将上传的文件绑定到该MultipartFile对象上,调用其transferTo(File file)方法即可保存文件.

@Controller
@RequestMapping("/fileUpload")
public class FileUploadController {

	@RequestMapping("/springMVC")
    public String fileupload2(HttpServletRequest request, @RequestParam("fileParam") MultipartFile upload) throws Exception {
        
        // 创建目录保存上传的文件
        String path = request.getSession().getServletContext().getRealPath("/uploads/");
        File file = new File(path);
        if (!file.exists()) {
            file.mkdirs();
        }

        // 服务器中保存的文件名
        String filename = UUID.randomUUID().toString().replace("-", "") + "_" + upload.getOriginalFilename();
        // 上传文件
        upload.transferTo(new File(path,filename));

        return "success";
    }
}

使用SpringMVC进行跨服务器文件上传

我们可以引入jersey库进行服务器间通信,实现将文件上传到一个专用的文件服务器,需要在pom.xml中引入jersey库的座标如下:

<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-core</artifactId>
    <version>1.18.1</version>
</dependency>
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-client</artifactId>
    <version>1.18.1</version>
</dependency>

在处理器方法中创建Client对象实现服务器间通信,将文件上传到文件服务器上,代码如下:

@Controller
@RequestMapping("/fileUpload")
public class FileUploadController {

	@RequestMapping("/betweenServer")
    public String fileupload3(@RequestParam("fileParam") MultipartFile upload) throws Exception {
        System.out.println("跨服务器文件上传...");

        // 文件服务器URL
        String fileServerPath = "http://localhost:9090/uploads/";	
        
        // 获取服务器中保存的文件名
        String filename = UUID.randomUUID().toString().replace("-", "") + "_" + upload.getOriginalFilename();

        // 创建客户端对象并在文件服务器上创建资源
        Client client = Client.create();
        WebResource webResource = client.resource(fileServerPath + filename);
        webResource.put(upload.getBytes());

        return "success";
    }
}

编写处理文件的工具类

我们将上述程序中对文件的处理封装成抽象类FileUtil:

public class FileUtil {

    // 上传文件
    public static void uploadFile(byte[] file, String filePath, String fileName) throws Exception {
        File targetFile = new File(filePath);
        if (!targetFile.exists()) {
            targetFile.mkdirs();
        }
        FileOutputStream out = new FileOutputStream(filePath + fileName);
        out.write(file);
        out.flush();
        out.close();
    }

    // 删除文件,返回值表示是否删除成功
    public static boolean deleteFile(String fileName) {
        File file = new File(fileName);
        // 如果文件路径所对应的文件存在,并且是一个文件,则直接删除
        if (file.exists() && file.isFile()) {
            if (file.delete()) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    // 重命名文件
    public static String renameToUUID(String fileName) {
        return UUID.randomUUID() + "." + fileName.substring(fileName.lastIndexOf(".") + 1);
    }
}

保存文件的操作可简化为如下三句:

String fileName = FileUtil.renameToUUID(uploadFile.getOriginalFilename());
String filePath = request.getSession().getServletContext().getRealPath("/uploads/");
FileUtil.uploadFile(uploadFile.getBytes(), filePath, fileName);

SpringMVC中的拦截器

Spring MVC 的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。
拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
它和过滤器是有几分相似,但是也有区别:

  • 过滤器是servlet规范中的一部分,任何java web工程都可以使用。 拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能用。
  • 过滤器在url-pattern中配置了/*之后,可以对所有要访问的资源拦截。 拦截器它是只会拦截访问的控制器方法,如果访问的是jsp,html,css,image或者js是不会进行拦截的。
  • 它也是AOP思想的具体应用。
  • 要想自定义拦截器, 要求必须实现:HandlerInterceptor接口。
  • 过滤器 > 拦截器

第一步:编写一个普通类实现HandlerInterceptor接口

重写接口中的preHandle方法,该方法返回一个布尔类型:
返回true表示放行,执行下一个拦截器,如果没有,就执行controller中的方法。返回false表示拦截,不载进行执行,而是进行转发或者重定向。

public class MyInterceptor1 implements HandlerInterceptor {
    //预处理,controller方法执行前
    //返回true表示放行,执行下一个拦截器,如果没有,就执行controller中的方法。
    // 返回false表示拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor1执行了。。");
        return true;
    }
}

第二步:配置拦截器

    <!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--要拦截的具体方法-->
            <mvc:mapping path="/user/*"/>
            <!--不要拦截的具体方法,和不要拦截的方法二选一-->
            <!--<mvc:exclude-mapping path=""></mvc:exclude-mapping>-->
            <!--配置拦截器对象-->
            <bean class="com.lwl.MyInterceptor1"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

jsp页面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>拦截器</title>
</head>

<body>
    <h3>拦截器</h3>
    <a href="user/testInterceptor">拦截</a>
</body>
</html>

测试运行结果:

在点击index中的超链接访问user/testInterceptor的方法时,会首先被拦截器拦截,打印MyInterceptor1执行了。。,因为返回的是true,因此放行执行testInterceptor方法,最后访问success页面。
在这里插入图片描述

拦截器中方法的说明

  • preHandle:
    • 只要配置了都会调用(方法执行前调用)
    • 如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true。
    • 如果程序员决定不需要再调用其他的组件去处理请求,则返回false。
  • postHandle
    • 在拦截器链内所有拦截器返成功调用
    • 在业务处理器处理完请求后,但是DispatcherServlet向客户端返回响应前被调用。
    • 在该方法中对用户请求request进行处理。
  • afterCompletion
    • 只有preHandle返回true才调用
    • 在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章