本章主要内容包含SpringMVC简介、MyBatis整合SpringMVC(主要是在前面的MyBatis整合Spring基础上进行)、Spring应用实例等。
1.1 SpringMVC简介
1.1.1 介绍
SpringMVC因Spring的名气而得到大范围地应用,当然了,除此之外SpringMVC的确简单易学,同时其本身与Struts2比较够轻量。
1.1.2 SpringMVC与Struts2比较
比较归纳为如下:
(1)入口不同:SpringMVC的入口是Servlet,Struts的入口是Filter。
(2)性能上:spring3 mvc是方法级别的拦截,拦截到方法后根据参数上的注解,把request数据注入进去,在spring3 mvc中,一个方法对应一个request上下文。而struts2框架是类级别的拦截,每次来了请求就创建一个Action,然后调用setter getter方法把request中的数据注入;struts2实际上是通过setter getter方法与request打交道的;struts2中,一个Action对象对应一个request上下文。
(3)拦截器实现机制上,Struts2有以自己的interceptor机制,SpringMVC用的是独立的AOP方式,这样导致Struts2的配置文件量还是比SpringMVC大。
(4)设计思想上,Struts2更加符合OOP的编程思想, SpringMVC就比较谨慎,在servlet上扩展。
(5)SpringMVC集成了Ajax,使用非常方便,只需一个注解@ResponseBody就可以实现,然后直接返回响应文本即可,而Struts2拦截器集成了Ajax,在Action中处理时一般必须安装插件或者自己写代码集成进去,使用起来也相对不方便。
(6)Spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高(当然Struts2也可以通过不同的目录结构和相关配置做到SpringMVC一样的效果,但是需要xml配置的地方不少)。
1.1.3 MVC模式
三层架构在Java中是比较出名的,三层架构无非是数据访问层、业务逻辑层、Web层(又称UI层)等。
其中Web层中就涉及到MVC模式(模型-视图-控制器),MVC模式可以以SpringMVC官方网站的一张图来表示:
1.2 整合SpringMVC
一般通常整合SpringMVC并不需要花多大力气,很简单的。如果是Spring+SpringMVC+MyBatis整合,最好还是先将Spring+MyBatis先整合好,这样可以省去很多不必要的麻烦。然后就可以无缝集成SpringMVC。
1.2.1 导入依赖
依赖复用可以参考MyBatis集成Spring
直接可以将里面的依赖复用
1.2.2 编写HelloController
package com.tutorialspoint;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.ui.ModelMap;
@Controller
@RequestMapping("/hello")
public class HelloController{
@RequestMapping(method = RequestMethod.GET)
public String printHello(ModelMap model) {
model.addAttribute("message", "Hello Spring MVC Framework!");
return "hello";
}
}
1.2.3 修改web.xml
Spring MVC Application
spring mvc servlet
springMvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:application-mvc.xml
1
springMvc
/
1.2.4 编写application-mvc.xml
1.2.5 在WEB-INF文件夹下新建jsp文件夹,并在jsp文件夹新建hello.jsp文件
<%@ page contentType="text/html; charset=UTF-8" %>
Hello World
${message}
1.2.6 启动项目并在浏览器输入对应的地址会显示对应的视图结果
1.3 SpringMVC应用实例
应用实例不少以下只列出这么几个?
(1)异常处理;
(2)文件上传;
(3)跨域请求;
(4)表单处理;
(5)重定向;
1.3.1 异常处理
异常处理,对于项目开发至关重要,总不能用户点击一个页面出错了,直接报500,那样用户体验多不好啊!
所以这里讲的是SpringMVC对异常的处理,希望能给大家带来一定的 帮助和启发。
(1)编写实体
package com.tutorialspoint;
public class Student {
private Integer age;
private String name;
private Integer id;
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
}
(2)编写异常
package com.tutorialspoint;
public class SpringException extends RuntimeException{
private String exceptionMsg;
public SpringException(String exceptionMsg) {
this.exceptionMsg = exceptionMsg;
}
public String getExceptionMsg(){
return this.exceptionMsg;
}
public void setExceptionMsg(String exceptionMsg) {
this.exceptionMsg = exceptionMsg;
}
}
(3)编写测试Controller
package com.tutorialspoint;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.ui.ModelMap;
@Controller
public class StudentController {
@RequestMapping(value = "/student", method = RequestMethod.GET)
public ModelAndView student() {
return new ModelAndView("student", "command", new Student());
}
@RequestMapping(value = "/addStudent", method = RequestMethod.POST)
@ExceptionHandler({SpringException.class})
public String addStudent( @ModelAttribute("HelloWeb")Student student,
ModelMap model) {
if(student.getName().length() < 5 ){
throw new SpringException("Given name is too short");
}else{
model.addAttribute("name", student.getName());
}
if( student.getAge() < 10 ){
throw new SpringException("Given age is too low");
}else{
model.addAttribute("age", student.getAge());
}
model.addAttribute("id", student.getId());
return "result";
}
}
(3)在application-mvc.xml补充如下内容
ExceptionPage
(4)编写JSP
student.jsp
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
Spring MVC Exception Handling
Student Information
Name
Age
id
error.jsp
Spring Error Page
An error occured, please contact webmaster.
ExceptionPage.jsp
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
Spring MVC Exception Handling
Spring MVC Exception Handling
${exception.exceptionMsg}
result.jsp
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
Spring MVC Form Handling
Submitted Student Information
Name
${name}
Age
${age}
ID
${id}
(5)运行项目
出现如图所示,表示成功
在SpringMVC中有两种处理异常的方式,那么就存在一个优先级的问题:
当发生异常的时候,SpringMVC会如下处理:
a.SpringMVC会先从配置文件找异常解析器HandlerExceptionResolver
b.如果找到了异常异常解析器,那么接下来就会判断该异常解析器能否处理当前发生的异常
c.如果可以处理的话,那么就进行处理,然后给前台返回对应的异常视图
d.如果没有找到对应的异常解析器或者是找到的异常解析器不能处理当前的异常的时候,就看当前的Controller中有没有提供对应的异常处理器,如果提供了就由Controller自己进行处理并返回对应的视图
e.如果配置文件里面没有定义对应的异常解析器,而当前Controller中也没有定义的话,那么该异常就会被抛出来。
1.3.2 文件上传
传统的图片服务器ftp,就现在而言已经没几个人在用了,当然了,wordpress相关的插件安装和主题下载就用到ftp。
当然了,还有不少企业将上传文件(包含图片等)放入线上tomcat某个文件夹下或者项目里面,这样的弊端使项目会越来越庞大,之前庞大是因为不断增长的需求,代码不得不越多,因此也会扩充容量。
不过目前很多企业通常采用比如腾讯云、阿里云、七牛云等对象存储,作为图片存储。
今天我们就以腾讯云的对象存储为例。
(1)导入依赖
com.qcloud
cos_api
5.2.4
(2)编写工具类
package com.custome;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectResult;
import com.qcloud.cos.region.Region;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.net.URL;
import java.util.Date;
import java.util.Random;
public class COSClientUtil {
//todo 这些变量信息自行到 腾讯云对象存储控制台 获取
private COSClient cOSClient;
private static final String ENDPOINT = "test.com"; //用户可以指定域名,不指定则为默认生成的域名
//secretId
private static final String secretId = "AKIDCJ";
// secretKey
private static final String secretKey = "CD7";
// 存储通名称
private static final String bucketName = "test";//公有读私有写
// 1 初始化用户身份信息(secretId, secretKey)
private static COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
// 2 设置bucket的区域, COS地域的简称请参照 https://cloud.tencent.com/document/product/436/6224
private static ClientConfig clientConfig = new ClientConfig(new Region("ap-beijing-1"));
// 3 生成cos客户端
private static COSClient cosclient = new COSClient(cred, clientConfig);
public COSClientUtil() {
cOSClient = new COSClient(cred, clientConfig);
}
/**
* 销毁
*/
public void destory() {
cOSClient.shutdown();
}
/**
* 上传图片
*
* @param url
*/
public void uploadImg2Cos(String url) throws Exception {
File fileOnServer = new File(url);
FileInputStream fin;
try {
fin = new FileInputStream(fileOnServer);
String[] split = url.split("/");
this.uploadFile2Cos(fin, split[split.length - 1]);
} catch (FileNotFoundException e) {
throw new Exception("图片上传失败");
}
}
public String uploadFile2Cos(MultipartFile file) throws Exception {
if (file.getSize() > 10 * 1024 * 1024) {
throw new Exception("上传图片大小不能超过10M!");
}
//图片名称
String originalFilename = file.getOriginalFilename();
System.out.println("originalFilename = " + originalFilename);
//图片后缀
String substring = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
System.out.println("substring = " + substring);
Random random = new Random();
//生成新的图片名称(随机数0-9999+系统当前时间+上传图片名)
String name = random.nextInt(10000) + System.currentTimeMillis() + "_" + substring;
try {
InputStream inputStream = file.getInputStream();
this.uploadFile2Cos(inputStream, name);
return name;
} catch (Exception e) {
throw new Exception("图片上传失败");
}
}
/**
* 获得图片路径
*
* @param fileUrl
* @return
*/
public String getImgUrl(String fileUrl) {
return getUrl(fileUrl);
}
/**
* 获得url链接
*
* @param key
* @return
*/
public String getUrl(String key) {
// 设置URL过期时间为10年 3600l* 1000*24*365*10
Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 10);
// 生成URL
URL url = cOSClient.generatePresignedUrl(bucketName, key, expiration);
if (url != null) {
return url.toString();
}
return null;
}
/**
* 上传到COS服务器 如果同名文件会覆盖服务器上的
*
* @param instream
* 文件流
* @param fileName
* 文件名称 包括后缀名
* @return 出错返回"" ,唯一MD5数字签名
*/
public String uploadFile2Cos(InputStream instream, String fileName) {
String ret = "";
try {
// 创建上传Object的Metadata
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(instream.available());
objectMetadata.setCacheControl("no-cache");
objectMetadata.setHeader("Pragma", "no-cache");
objectMetadata.setContentType(getcontentType(fileName.substring(fileName.lastIndexOf("."))));
objectMetadata.setContentDisposition("inline;filename=" + fileName);
// 上传文件
PutObjectResult putResult = cOSClient.putObject(bucketName, fileName, instream, objectMetadata);
ret = putResult.getETag();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (instream != null) {
instream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return ret;
}
/**
* Description: 判断Cos服务文件上传时文件的contentType
*
* @param filenameExtension 文件后缀
* @return String
*/
public static String getcontentType(String filenameExtension) {
if (filenameExtension.equalsIgnoreCase("bmp")) {
return "image/bmp";
}
if (filenameExtension.equalsIgnoreCase("gif")) {
return "image/gif";
}
if (filenameExtension.equalsIgnoreCase("jpeg") || filenameExtension.equalsIgnoreCase("jpg")
|| filenameExtension.equalsIgnoreCase("png")) {
return "image/jpeg";
}
if (filenameExtension.equalsIgnoreCase("html")) {
return "text/html";
}
if (filenameExtension.equalsIgnoreCase("txt")) {
return "text/plain";
}
if (filenameExtension.equalsIgnoreCase("vsd")) {
return "application/vnd.visio";
}
if (filenameExtension.equalsIgnoreCase("pptx") || filenameExtension.equalsIgnoreCase("ppt")) {
return "application/vnd.ms-powerpoint";
}
if (filenameExtension.equalsIgnoreCase("docx") || filenameExtension.equalsIgnoreCase("doc")) {
return "application/msword";
}
if (filenameExtension.equalsIgnoreCase("xml")) {
return "text/xml";
}
return "image/jpeg";
}
}
(3)编写测试Controller
@PostMapping(value="/uploadPicture",produces="application/json;charset=utf-8")
public JSONObject upModify(HttpServletRequest request, MultipartFile file) {
JSONObject json = new JSONObject();
try {
COSClientUtil cosClientUtil = new COSClientUtil();
//获取Cookie
String cookie = CookieUtils.getCookie(request,"userCode");
//解密后的userCode
String decodeStr = Base64.decodeStr(cookie);
if(!file.isEmpty()) {
String name = cosClientUtil.uploadFile2Cos(file);
//图片名称
System.out.println("name = " + name);
//上传到腾讯云
String img_url = cosClientUtil.getImgUrl(name);
System.out.println("img_url = " + img_url);
//数据库保存图片地址
String db_img_url = img_url.substring(0,img_url.indexOf("?"));
System.out.println("db_img_url = " + db_img_url);
SysUser user = new SysUser();
user.setUserCode(decodeStr);
user.setAvatar(db_img_url);
//调用修改逻辑
boolean isModifyUser = userService.updateById(user);
if(isModifyUser) {
json.put("returnCode", "000000");
json.put("returnMsg", "上传文件成功");
}else {
json.put("returnCode", "111111");
json.put("returnMsg", "上传文件失败");
}
}else {
json.put("returnCode", "222222");
json.put("returnMsg", "参数异常");
}
} catch (Exception e) {
e.printStackTrace();
json.put("returnCode", "333333");
json.put("returnMsg", "特殊异常");
}
return json;
}
(4)编写简单的html测试
上传图片测试
1.3.3 跨域请求
(1)编写拦截器
package com.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class CORSInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
response.addHeader("Access-Control-Allow-Origin", "*");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
}
}
(2)application-mvc.xml配置拦截器
(3)编写html测试
1.3.4 表单处理
(1)编写实体
实体可以复用前面的,之所以列出来防止一些读者不理解。
package com.tutorialspoint;
public class Student {
private Integer age;
private String name;
private Integer id;
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
}
(2)编写Controller
package com.tutorialspoint;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.ui.ModelMap;
@Controller
public class StudentController {
@RequestMapping(value = "/student", method = RequestMethod.GET)
public ModelAndView student() {
return new ModelAndView("student", "command", new Student());
}
@RequestMapping(value = "/addStudent", method = RequestMethod.POST)
public String addStudent(@ModelAttribute("SpringWeb")Student student,
ModelMap model) {
model.addAttribute("name", student.getName());
model.addAttribute("age", student.getAge());
model.addAttribute("id", student.getId());
return "result";
}
}
ModelMap同ModelAndView相同点,将数据已键值对形式返回到前台,而前台只需知道对应的键即可,就可以获得对应的值。
当然就视图与数据分离而言,尽量不要使用ModelAndView,尽量使用Model或者ModelMap也可以。
(3)编写jsp
student.jsp
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
Spring MVC Form Handling
Student Information
Name
Age
id
result.jsp
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
Spring MVC Form Handling
Submitted Student Information
Name
${name}
Age
${age}
ID
${id}
(4)测试
启动应用测试,结果如图:
1.3.5 重定向
说到重定向不得不提到一个转发。这里概述一下转发与重定向的区别:
重定向和转发有一个重要的不同:当使用转发时,JSP容器将使用一个内部的方法来调用目标页面,新的页面继续处理同一个请求,而浏览器将不会知道这个过程。 与之相反,重定向方式的含义是第一个页面通知浏览器发送一个新的页面请求。因为,当你使用重定向时,浏览器中所显示的URL会变成新页面的URL, 而当使用转发时,该URL会保持不变。重定向的速度比转发慢,因为浏览器还得发出一个新的请求。同时,由于重定向方式产生了一个新的请求,所以经过一次重 定向后,request内的对象将无法使用。
转发和重定向的区别
不要仅仅为了把变量传到下一个页面而使用session作用域,那会无故增大变量的作用域,转发也许可以帮助你解决这个问题。
重定向:以前的request中存放的变量全部失效,并进入一个新的request作用域。
转发:以前的request中存放的变量不会失效,就像把两个页面拼到了一起。
比如session的保存,就是转发的一个应用实例。
Session通过setAttribute以键值对的形式保存Session,而要获得该session,只需getAttribute对应的键即可,当然了,Session也有它的生命周期,即有效期,超过这个有效期则会发生session失效问题。
通常session有效默认为30分钟,可以通过web.xml配置修改
60
(1)编写示例
package com.tutorialspoint;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class WebController {
@RequestMapping(value = "/index", method = RequestMethod.GET)
public String index() {
return "index";
}
@RequestMapping(value = "/redirect", method = RequestMethod.GET)
public String redirect() {
return "redirect:finalPage";
}
@RequestMapping(value = "/finalPage", method = RequestMethod.GET)
public String finalPage() {
return "final";
}
}
(2)编写index.jsp和final.jsp
index.jsp
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
Spring Page Redirection
Spring Page Redirection
Click below button to redirect the result to new page
final.jsp
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
Spring Page Redirection
Redirected Page
(3)启动服务器,输入对应的地址
点击红色标记处,会出现如图,这样就表示正常,否则可能是500,那就是代码有问题或者环境问题
1.4 小结
本章内容主要有SpringMVC简介、MyBatis整合SpringMVC(主要是在前面的MyBatis整合Spring基础上进行)、Spring应用实例等。
通过对这些内容的介绍说明和示例讲解,相信你已经学会使用了。
本文部分内容主要来自笔者博客园