1. 实现图片上传
需求:在用户注册页面实现上传图片作为用户头像
1. springmvc中对多部件类型请求数据解析:在页面form中提交enctype="multipart/form-data"的数据时,需要springmvc对multipart类型的数据进行解析。在springmvc.xml中配置multipart类型解析器。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--指定上传文件的最大空间为10mb-->
<property name="maxUploadSize">
<value>1048576</value>
</property>
<!--上传文件的编码格式为utf-8-->
<property name="defaultEncoding">
<value>utf-8</value>
</property>
</bean>
2. 加入上传图片的jar,上边的解析内部使用下边的jar进行图片上传。
3. 在tomcat服务器中创建图片虚拟目录用于存储图片:通过图形化界面创建,
Document base就表示本地路径,而path就表示浏览器访问路径;也可以直接修改tomcat的配置:在conf/server.xml文件,添加虚拟目录 :
注意:在图片虚拟目录 中,一定将图片目录分级创建(提高i/o性能),一般我们采用按日期(年、月、日)进行分级创建。
3. jsp页面中对上传图片代码编写:主要有三个要编写的地方,form表单的enctype="multipart/form-data"和method="post"两个属性,以及<input type="file" name="">标签上传文件
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>注册页面</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<body>
<!--enctype="multipart/form-data"和method="post"这两个配置必须有,multipart/form-data表示上传多部件类型的数据,必须为post请求方式是因为post请求方式能发送包含文件的数据请求-->
<form action="/user_manage/user/regist.do" enctype="multipart/form-data" method="post">
<div>请输入名称:<input type="text" name="userName"></div>
<div>请输入密码:<input type="password" name="userPassword"></div>
<div>请输入年龄:<input type="text" name="userAge"></div>
<div>请输入地址:<input type="text" name="userAddress"></div>
<div>请输入手机:<input type="text" name="userTelephone"></div>
<!--主要通过<input type="file" name="">标签上传文件,不一定是图片-->
<div>请选择一张图片作为头像:<input type="file" name="img"></div>
<div><input type="submit" value="注册"></div>
</form>
</body>
</html>
4. 服务端controller方法接收图片文件:接收的文件会绑定为org.springframework.web.multipart.MultipartFile类型的对象形参,也就是说,发送的文件会保存在MultipartFile对象中,绑定规则和简单类型参数的绑定一样,形参名和请求中的key值相同
@RequestMapping("/regist")
public String userRegist(MultipartFile img,@Validated(value={validatorGroup1.class}) User user,BindingResult result,Model model){
String picPath=uploadPicture(img);//调用下面的方法,保存图片并返回该图片的访问地址
user.setUserImage(picPath);//将图片的访问地址保存到User对象中
userservice.insertUser(user);
model.addAttribute("userAccount", user.getUserAccount());
return "login";
}
public class User {
private Integer userAccount;
private String userName;
private String userPassword;
private Integer userAge;
private String userAddress;
private String userTelephone;
//添加图片路径属性
private String userImage;
//省略get/set方法
}
5. 将接收的文件发送到图片服务器的虚拟目录中:
//保存图片到服务器中,并生成该图片的访问路径
private static String uploadPicture(MultipartFile uploadFile){
String oldFileName=uploadFile.getOriginalFilename();//获取初始文件名
String fileSuffix=oldFileName.substring(oldFileName.lastIndexOf("."));//获取文件类型后缀
String newFileName=UUID.randomUUID().toString();//新图片名称的生成有多种方式,只需保证不重复即可
//本地服务器中存放图片所在文件夹的物理路径
String path="D:\\develop\\upload\\";
//拼接本地存放的完整路径
File newPicture=new File(path+newFileName+fileSuffix);
try {
//将图片保存在该路径下
uploadFile.transferTo(newPicture);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//返回完整的虚拟路径
return "http://localhost:8081/pic/"+newFileName+fileSuffix;
}
6. 在上面的上传图片实现中,图片上传后会保存在运行应用系统的服务器中,但如果是应用系统分布式部署环境下图片引用会出现问题,而且图片的上传下载会给服务器增加额外的压力
所以在实际开发中会采取分布式部署,保存用户上传下载文件的服务器不会和系统应用部署的服务器处于同一台服务器,而是分开部署的,这时就需要一些网络传输代码来进行两个服务器之间的数据上传与下载,一般采用FTP协议。这里不做具体实现
2. SpringMVC中实现json格式数据交互
1. 客户端与服务端的json数据交互过程:
- 客户端向服务端发送请求,请求中带有一些数据:一般来说,如果不进行特殊设置,提交的数据格式都为key/value格式的,比如form表单提交的字符串数据,请求中表示数据内容类型的请求头值为 contentType=application/x-www-form-urlen;但如果发送的数据为json格式,那么就需要设置请求头contentType=application/json
- 服务端接受数据:如果请求中的数据格式类型是key/value,那么只需要按照一些参数绑定规则即可通过Controller中的方法参数进行接收处理;但如果是接收一个json格式的字符串数据,如果想要将该字符串转为Java中的对象(简单类型、pojo类型),那么就需要@RequestBody注解
- 服务端向客户端页面响应json格式数据:必须通过@ResponseBody将java对象转成json串输出,在客户端页面中对该json串进行解析
2. 如果是请求的数据格式为json、响应的数据格式也为json,在前端页面中需要将请求的内容转成json,不太方便,所以常用请求的数据格式为key/value、响应的数据格式为json。
3. 使用json的原因就在于:json数据格式在接口调用中、html页面中较常用,json格式简单,解析方便。比如Ajax异步数据交互时常用json、还有最重要JSONP跨域请求数据资源
4. 简单使用示例:
- 首先必须导入json与java对象转换的相关jar包
- 定义一个html页面,通过jQuery框架中的ajax方法提交json数据,并接受返回响应的json数据
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>test page</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> <script type="text/javascript" src="../js/jquery-1.4.4.min.js"></script> <script type="text/javascript"> //设置提交的数据格式为json,即请求头contentType=application/json,接收json数据 function requestJson(){ $.ajax({ type:'post', url:'http://localhost:8081/user_manage/requestJson', contentType:'application/json;charset=utf-8', data:'{userAccount:"123456",userName="123",userPassword="123456"}', success:function(data){ alert(data); }, error:function(){ alert("请求失败"); } }); } //默认提交的数据格式为key/value,即请求头contentType=application/x-www-form-urlen,接收json数据 function requestKeyValue(){ $.ajax({ type:'post', url:'http://localhost:8081/user_manage/requestKeyValue', data:"userAccount=123456&userName=123&userPassword=123456", success:function(data){ alert(data); }, error:function(){ alert("请求失败"); } }); } </script> </head> <body> <button onclick="requestJson()">提交json数据</button> <button onclick="requestKeyValue()">提交key/value数据</button> </body> </html>
- Controller方法中接受json数据并绑定到一个pojo对象中,响应json数据
@RequestMapping("/requestJson") @ResponseBody//该注解就会把返回的对象user转为json字符串,而@RequestBody会把json字符串绑定到user形参中 public User JsonTest1(@RequestBody User user){ user.setUserPassword("12345678"); return user; } @RequestMapping("/requestKeyValue") @ResponseBody public User JsonTest2(User user){ user.setUserPassword("12345678"); return user; }
3. SpringMVC对RESTful格式请求路径的支持
1. 何为RESTful格式路径:
- 不使用RESTful格式的请求路径:比如https://my.oschina.net/ProgramerLife/blog/write/draft?id=2275002
- 使用RESTful格式的请求路径:则为https://my.oschina.net/ProgramerLife/blog/write/draft/2275002
- 也就是将参数写在请求路径中,在服务端通过请求路径提取参数,而不是把参数放在请求体中,作为请求参数发送给服务端
2. 实现一个需求作为示例:在用户管理界面,实现删除用户
2. 首先要在web.xml中配置好用于接收RESTful格式请求路径的前端控制器:
<!-- RESTful风格的路径配置 -->
<servlet>
<servlet-name>RESTful</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--SpringMVC加载的配置文件,这里是springweb应用上下文 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/springMVC.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>RESTful</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
2. 请求路径为:http://localhost:8081/user_manage/delete/1/20/1000000001
3. 服务端Controller方法:page表示该用户信息所在的页数,rows表示每页最大显示的用户信息数量,userAccount表示用户账号
@RequestMapping("/delete/{page}/{rows}/{userAccount}")
@ResponseBody
public String deleteUser(@PathVariable(value="page") int page,
@PathVariable(value="userAccount") int userAccount,
@PathVariable(value="rows") int rows){
userService.deleteUser(userAccount);
DataGridResult users=userService.findUserList(page, rows);
return users;
}
- @RequestMapping("/delete/{page}/{rows}/{userAccount}"):在@RequestMapping后所写的路径中,用{xxx}表示请求路径中哪几部分代表请求参数,也就是{xxx}相当于占位符
- @PathVariable(value="page") int page:@PathVariable该注解用于Controller方法中用于接收请求参数的形参之前,表示将请求URL中的模板变量映射到功能处理方法的参数上。如果形参名和{xxx}中的值相同,@PathVariable则不用指定(value="page")来映射在路径中的数据
4. RESTful格式请求路径所带来的问题:配置前端控制器的url-parttern中指定/,对静态资源的解析出现问题,因为此时前端控制器对于静态资源的请求路径也会拦截并在控制器映射器中寻找对应的Controller,而此时是无法寻找到对应的Controller的,所以会报404
5. 解决RESTful格式请求路径对静态资源的解析出现的问题:
- 在Spring3.*版本中,可以在SpringMVC的配置文件添加对静态资源的映射,比如
<mvc:resources location="/WEB-INF/js/" mapping="/js/**"/>
- 而在Spring4.*版本以上,不能使用上面的resources,可以在SpringMVC的配置文件添加另一行配置:
<mvc:default-servlet-handler/>
但是该配置必须保证静态资源文件在webapp目录下,而通过resources配置的可以将静态资源放在WEB-INF目录下
4. SpringMVC 拦截器
1. 自定义拦截器:实现org.springframework.web.servlet.HandlerInterceptor接口即可
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
//定义拦截器,实现HandlerInterceptor接口。接口中提供三个方法。
public class HandlerInterceptor1 implements HandlerInterceptor {
//进入 Handler(即Controller)方法之前执行
//用于身份认证、身份授权
//比如身份认证,如果认证通过表示当前用户没有登陆,需要此方法拦截不再向下执行
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//return false表示拦截,不向下执行
//return true表示放行
return false;
}
//进入Handler方法之后,返回modelAndView之前执行
//应用场景从modelAndView出发:将公用的模型数据(比如菜单导航)在这里传到视图,也可以在这里统一指定视图
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
//完成执行Handler后执行此方法
//应用场景:统一异常处理,统一日志处理
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
2. 配置全局拦截器:springmvc配置全局的拦截器,springmvc框架将配置的拦截器注入到每个处理器映射器中,springmvc拦截器针对HandlerMapping(处理器映射器)进行拦截设置,或者说会将拦截器配置到处理器映射器中,如果某个请求路径该HandlerMapping映射成功(找到对应的Handler),那么会先被拦截器进行拦截处理,在处理器返回响应之前在进行一次处理。在SpringMVC的配置文件(springmvc.xml)中添加如下配置
<!-- 如果配置多个拦截器,在mvc:interceptors中添加多个mvc:interceptor即可 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 拦截器类 -->
<bean class="user_manage.interceptor.HandlerInterceptor1"></bean>
<!-- 拦截器拦截的请求路径,/**表示拦截所有的url -->
<mvc:mapping path="/**"/>
</mvc:interceptor>
<mvc:interceptor>
<!-- 拦截器类 -->
<bean class="user_manage.interceptor.HandlerInterceptor2"></bean>
<!-- 拦截器拦截的请求路径,/**表示拦截所有的url -->
<mvc:mapping path="/**"/>
</mvc:interceptor>
</mvc:interceptors>
3. 拦截器执行顺序:如果配置了多个拦截器,那么就按照配置中所写拦截器的顺序来依次进行拦截,只有前一个拦截器对请求放行之后,后一个拦截器才能继续拦截。
4. 拦截器使用实例:登录认证拦截,除了登录请求和注册请求之外,其他请求必须通过登录认证拦截器拦截
public class LoginInterceptor implements HandlerInterceptor {
//进入 Handler方法之前执行
//用于身份认证、身份授权
//比如身份认证,如果认证通过表示当前用户没有登陆,需要此方法拦截不再向下执行
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//获取请求的url
String url = request.getRequestURI();
//判断url是否是公开 地址(实际使用时将公开 地址配置配置文件中)
//这里公开地址是登陆提交的地址
if(url.indexOf("login")>=0){
//如果是登陆请求的路径地址,放行
return true;
}
//判断session
HttpSession session = request.getSession();
//从session中取出用户身份信息
String username = (String) session.getAttribute("username");
if(username != null){
//身份存在,放行
return true;
}
//执行这里表示用户身份需要认证,跳转登陆页面
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
//return false表示拦截,不向下执行
//return true表示放行
return false;
}