复习:
引入jar包
springmvc.xml
web.xml
实体类AxiosResult:
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AxiosResult {
private Integer code;
private String msg;
private Object data;
public AxiosResult(E e, Object data) {
this(e);
this.data = data;
}
public AxiosResult(Object data) {
this(E.SUC);
this.data = data;
}
public AxiosResult(E e) {
this.code = e.getCode();
this.msg = e.getMsg();
}
public static AxiosResult suc(){
return new AxiosResult(E.SUC);
}
public static AxiosResult suc(Object data){
return new AxiosResult(E.SUC,data);
}
public static AxiosResult error(){
return new AxiosResult(E.ERROR);
}
public static AxiosResult error(E e){
return new AxiosResult(e);
}
public static AxiosResult error(Object data){
return new AxiosResult(E.ERROR,data);
}
public static AxiosResult error(E e,Object data){
return new AxiosResult(e,data);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
响应码和响应信息放到枚举类:
public enum E {
SUC(20000,"suc"),
ERROR(50000,"ERROR"),
NOT_EXISTS(50001,"数据不存在"),
PWD_ERROR(50002,"密码错误"),
FILE_MAX_SIZE(50003,"上传文件过大" ),
NO_LOGIN(50004,"用户未登录" );
private Integer code;
private String msg;
E(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
控制层也可以叫handler(处理器,即springmvc的处理器对象)
获取用户列表,增加用户
springmvc执行流程:
①.Tomcat启动,实例化DispatcherServlet。加载springmvc.xml,创建容器。创建容器时做包扫描,将
RestController注册到容器。
同时引入springmvc的注解识别,会对RequestMapping和GetMapping做url映射,将映射结果存到处理器映射对象。
②.用户请求进来,进DispatcherServlet,解析url,根据url找对应的方法,执行它。执行需要参数,根据方法的参数结构(带不带RequestBody注解),拿到数据。
③.执行方法得到的结果,再通过json转换器,把对象转成json字符串,放在响应体里。
1.RESTURL(重要)
是一种url的写法风格,与之前url写法区别如下:
rest风格的url,没有动词
传统url | 参数 | 请求方法 | rest风格 | 参数 |
---|---|---|---|---|
/user/getById | ?id=12 | GET|GET | /user/12 | 参数属于uri的一部分 |
/user/add | 请求体json | POST|POST | /user | 请求体json |
/user/update | 请求体json | POST|PUT | /user | 请求体json |
/user/deleteByID | ?id=12 | GET|DELETE | /user/12 | 参数属于uri的一部 |
注意点1:请求方法与url只要有一个(url排除路径变量的)不同,就是不同的处理器方法.
(url一样,请求方法不同。get做查询,post做添加,put做更新,delete做删除)
注意点2:参数不再?追加到url后面,而是属于uri的一部分.
(/user/12,第二层url是参数)
改成rest风格的url
括号里是url路径变量,取该参数,用@PathVariable("路径变量名")
若路径变量名和形参名一致,直接写个注解即可。
若再来个根据名字取用户
他们的请求方法和url长度都一样,就无法区分。所谓的请求方法与url只要有一个不同,是url排除路径变量的不同,此时都是/user,此时需要再加一层。
路径变量可以有多个
注意点3:只有参数个数是固定的情况才使用rest风格的url,参数不能为""
(条件查询不能用该风格)
(若不传手机号,就是空路径,不支持)
(这种情况还是用原来的)
只有get请求和delete请求用路径变量。
put请求,默认无法获取请求体的k=v参数.本质来说tomcat就不支持put,delete请求.要用put,delete请求要求前端后端选择axios,springmvc都要支持put,delete. axios在进行put,delte请求时,底层仍然是发送post,method:put|delete,springmvc在解析method,对put,delete请求的请求再进行分发.
2.静态资源处理
在前端未分离开发模式下才需要静态资源处理.一旦前端代码独立项目部署,那么不需要静态资源处理.
静态资源:html,css,js,jpb,png.音视频,文件
新建main.css
body{
background-color: burlywood;
}
新建main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
main.html
</body>
</html>
在index.jsp中:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
index.jsp
</body>
</html>
访问:
但另两个访问不到。
为何html等访问不到?
当访问html,css,js等文件时,这些请求也进入DispatcherServlet,解析uri,查找HandlerMethod方法.找不到则404.
jsp为何可以?
访问jsp文件,在tomcat下有默认的JspServlet来处理jsp请求
Tomcat:
DefaultServlet此servlet映射路径/ 没有springmvc环境时,访问tomcat下的静态资源都是default来处理.有了springmvc的DisptcherServlet以后,覆盖了defaultServlet
JspServlet此servlet映射路径 *.jsp
解决方案:
在web.xml
法二:(建议使用)
在springmvc.xml
法三:
在springmvc.xml
相应的
一般写成和文件夹一样
3.异常处理(重要)
异常处理的目的1:服务端的异常的内存级别的错误信息,不返回给前端,正常返回前端错误码,文字信息.
异常处理的目的2:上线运行后,需要记录程序运行过程中产生的exception异常堆栈数据.
自定义的切面,写异常通知方法.织入到所有的控制层对象中.当控制层抛出异常后,进入异常通知方法进行处理.
在springmvc中已经有封装好的异常通知.
1.局部异常处理
缺点:只对某一个处理器类中的方法产生的异常生效.
优点:能够对不同的异常转到不同的方法进行处理.
@RestController
@RequestMapping("user")
public class UserHandler {
@GetMapping("{userid}")
public AxiosResult getById(@PathVariable("userid") Integer id){
int a =1/0;
SysUser u =new SysUser();
u.setId(id);
u.setUname("aaa");
return AxiosResult.suc(u);
}
@GetMapping("list")
public AxiosResult userlist(SysUser u,Integer pageNum,Integer pageSize) throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream("D:/aaa.txt");
if(pageNum==null)pageNum=1;
if(pageSize==null)pageSize=10;
List<SysUser> users = new ArrayList<>();
users.add(new SysUser("阿萨德","123123","fythnu"));
users.add(new SysUser("德克士","222222","dks"));
return AxiosResult.suc(users);
}
@GetMapping("uname/{uname}/{uwechat}")
public AxiosResult getByUname(@PathVariable String uname,@PathVariable String uwechat){
String str = null;
System.out.println(str.substring(1));
SysUser u =new SysUser();
u.setId(1);
u.setUname(uname);
return AxiosResult.suc(u);
}
@ExceptionHandler(ArithmeticException.class)
public AxiosResult doArithmeticException(ArithmeticException e){
System.out.println(e.getMessage());
return AxiosResult.error(e.getMessage());
}
@ExceptionHandler(FileNotFoundException.class)
public AxiosResult doFileNotFoundException(FileNotFoundException e){
System.out.println(e.getMessage());
return AxiosResult.error(e.getMessage());
}
@ExceptionHandler(Exception.class)
public AxiosResult doException(Exception e){
System.out.println(e.getMessage());
return AxiosResult.error(e.getMessage());
}
}
最后一个是空指针异常,进入到Exception.
2.全局异常处理
优点:对所有处理器类中的方法异常进行处理.
缺点:无法对不同异常分开处理.
新建MvcExceptionHandler类
@Component
public class MvcExceptionHandler implements HandlerExceptionResolver { //处理异常处理器
/**
*
* @param req :请求对象
* @param resp :响应对象
* @param o : HandlerMethod处理器方法对象
* @param e :异常对象
* @return 转错误视图,用在同步开发模式下
*/
@Override
public ModelAndView resolveException(HttpServletRequest req, HttpServletResponse resp, Object o, Exception e) {
if(o instanceof HandlerMethod){ //如果o是HandlerMethod类型
HandlerMethod m = (HandlerMethod)o; //强转
Object bean = m.getBean(); //是哪个类里的
Method method = m.getMethod(); //是哪个方法
//这个bean里的这个method方法产生异常
}
//如果需要对不同的异常返回给客户端不同的错误信息的话,需要重复的if-elseif判断,无上限的elseif.(也可原生方式处理异步)
AxiosResult a = AxiosResult.error();
PrintWriter writer = null;
try {
writer = resp.getWriter();
writer.print(JSON.toJSONString(a));
writer.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
return null;
}
}
3.全局统一异常处理(重要)----用这个
//@ControllerAdvice 控制器增强注解,当控制层出现异常时,进入ControllerAdvice注解的对象
//@ResponseBody
@RestControllerAdvice //前两个注解的复合
public class GlobExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public AxiosResult doArithmeticException(ArithmeticException e){
System.out.println(e.getMessage());
return AxiosResult.error(E.NOT_EXISTS,e.getMessage());
}
@ExceptionHandler(FileNotFoundException.class)
public AxiosResult doFileNotFoundException(FileNotFoundException e){
System.out.println(e.getMessage());
return AxiosResult.error(e.getMessage());
}
@ExceptionHandler(MvcException.class)
public AxiosResult doMvcException(MvcException e){
return AxiosResult.error(e.getE());
}
@ExceptionHandler(MultipartException.class)
public AxiosResult doMultipartException(MultipartException e){
return AxiosResult.error(E.FILE_MAX_SIZE);
}
@ExceptionHandler(Exception.class)
public AxiosResult doException(Exception e){
System.out.println(e.getMessage());
return AxiosResult.error(e.getMessage());
}
}
(自定义异常也在这里处理)
使用@RestControllerAdvice来对控制层对象进行统一异常处理,所有的异常类型都在此注解对应的类中进行处理,添加不同的方法,使用@ExceptionHandler注解来限定每个方法处理器的异常类型.
新建IUserService:
public interface IUserService {
SysUser getByUname(String uname);
}
新建:
@Service
public class UserServiceImpl implements IUserService {
@Override
public SysUser getByUname(String uname) {
if("admin".equals(uname)){
return new SysUser(uname,"111111","adminwechat","root");
}
return null;
}
}
在UserHandler类中增加用户名密码登录
@Autowired
private IUserService us;
@PostMapping("login")
public AxiosResult doLogin(@RequestBody SysUser u, HttpSession session) {
//用户名密码登录
String uname = u.getUname();
//1.根据用户名去查找用户对象.
SysUser byUname = us.getByUname(uname);
if(byUname==null)
throw new MvcException(E.NOT_EXISTS);
//return AxiosResult.error(E.NOT_EXISTS);
//2.比较密码
if(!u.getUpwd().equals(byUname.getUpwd()))
throw new MvcException(E.PWD_ERROR);
session.setAttribute("LOGIN_USER",byUname);
return AxiosResult.suc();
}
新建:
public class MvcException extends RuntimeException {
private E e;
public MvcException(E e) {
this.e = e;
}
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
}
(通过自定义异常来接收业务异常信息.)
总结:
@RestControllerAdvice+@ExceptionHandler+自定义RuntimeException+异常枚举对象
4.文件上传下载
springmvc对commons-fileupload组件进行了二次封装.使用更加简单.
添加commons-fileupload;commons-io包;
配置CommonsMultipartResolver对象到springmvc容器对象.此对象检测req请求是否content-type=multipart/form-data;内部要解析req对象,得到文件数据,把文件数据封装MuipartFile对象
在spring.mvc
<!--id必须是multipartResolver,因为springmvc内部从容器中获取文件解析器对象是按照固定id获取.-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property><!--支持中文文件名-->
<property name="maxInMemorySize" value="3000000"></property><!--临时文件域-->
<property name="maxUploadSize" value="10000000"></property><!--上传文件最大大小限制,如果不指定则不限制大小-->
<property name="uploadTempDir" value="/upload/tmp"></property><!--临时文件存储目录-->
</bean>
新建
新建文件控制层:
@RestController
public class FileHandler {
@Value("${mvc.root}")
private String root;
//Map<String,MultipartFile> :key:文件表单参数名, value:文件对象(文件名,大小,文件流)
@PostMapping("upload")
public AxiosResult doUpload(MultipartFile myfile,MultipartFile jl) throws IOException {
String originalFilename = myfile.getOriginalFilename();
long size = myfile.getSize();
//文件内容字节数组,小文件使用getBytes方法.一次性把InputStream流的数据读取到bytes数组
// byte[] bytes = myfile.getBytes();
// IOUtils.write(bytes,out);
//大文件使用流来慢慢的读
InputStream in = myfile.getInputStream();//文件内容,文件流
//把文件存到指定的磁盘目录
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
String time=df.format(new Date());
File file = new File(root+"/"+time);
if(!file.exists())file.mkdirs();
String filepath = time+"/"+originalFilename;
File target = new File(root,filepath);
FileOutputStream out = new FileOutputStream(target);
BufferedOutputStream bout = new BufferedOutputStream(out);
byte[] bytes= new byte[2048];
int r;
while((r=in.read(bytes))!=-1){
bout.write(bytes,0,r);
}
bout.flush();
bout.close();
//返回客户端什么数据?
Map<String,Object> map = new HashMap<>();
map.put("real_name",originalFilename);
map.put("file_size",size);
map.put("file_path",filepath);
map.put("upload_time",System.currentTimeMillis());
return AxiosResult.suc(map);
}
}
(可传多个文件,在形参加即可,比如第二个文件是简历)
文件下载:
main.xml中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/css/main.css">
</head>
<body>
main.html
<a href="/download?filePath=20220609/cpu模型.png&realName=cpu模型.png">下载</a>
</body>
</html>
@RestController
public class FileHandler {
...
//文件下载,返回值类型必须是ResponseEntity,因为文件下载必须指定content_disposition响应头
@GetMapping("download")
public ResponseEntity doDownload(String filePath,String realName) throws Exception {
File f = new File(root,filePath);
if(!f.exists())
throw new MvcException(E.NOT_EXISTS);
byte[] bytes = IOUtils.toByteArray(new FileInputStream(f));
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition","attachment;filename="+ URLEncoder.encode(realName, "utf-8"));
return new ResponseEntity(bytes,headers, HttpStatus.OK);
}
}
(想要下载文件时有进度条的效果,要设置响应头,响应头里的设置是百度出来的。
Content-Disposition通知浏览器,对发给他的响应体里的数据怎么处理。若不指定,则浏览器默认把数据展现在页面。attachment,通知浏览器对内容以附件的形式存储。filename,附件的名字指定一下。文件名应指定编码格式,防止乱码)
5.拦截器
interceptor拦截器,是控制层框架中的一个概念,类似于原生servlet中的filter过滤器的作用.
登录拦截器:
@Component
public class LoginInterceptor implements HandlerInterceptor { //HandlerInterceptor 处理器拦截器
//前拦截
@Override //一般啊只用该方法,不重写后两个方法
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
Object login_user = session.getAttribute("LOGIN_USER");
if(login_user==null) throw new MvcException(E.NO_LOGIN);
return true;
}
//返回
// @Override
// public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// System.out.println("postHandle");
// }
//
// //最终拦截
// @Override
// public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// System.out.println("afterCompletion");
// }
}
springmvc.xml配置拦截器
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--拦截url-->
<mvc:mapping path="/**"/>
<!--忽略url-->
<mvc:exclude-mapping path="/user/login"/>
<mvc:exclude-mapping path="/static/**"/>
<mvc:exclude-mapping path="/pages/**"/>
<ref bean="loginInterceptor"></ref>
</mvc:interceptor>
</mvc:interceptors>
用户发送请求,HandlerMapping解析url,查找该url对应的拦截器栈(集合)。即判断url在不在拦截范围或者忽略范围。
进入该拦截器,执行前拦截
前拦截返回true,再去执行对应的登录
拦截器可以配置多个
执行都是在dispatcherServlet类里:
public class UserHandler {
@Autowired
private IUserService us;
@PostMapping("login")
public AxiosResult doLogin(@RequestBody SysUser u, HttpSession session) {
//用户名密码登录
String uname = u.getUname();
//1.根据用户名去查找用户对象.
SysUser byUname = us.getByUname(uname);
if(byUname==null)
throw new MvcException(E.NOT_EXISTS);
//return AxiosResult.error(E.NOT_EXISTS);
//2.比较密码
if(!u.getUpwd().equals(byUname.getUpwd()))
throw new MvcException(E.PWD_ERROR);
session.setAttribute("LOGIN_USER",byUname);
return AxiosResult.suc();
}
@PostMapping
public AxiosResult addUser(@RequestBody SysUser u,HttpSession session){
Object login_user = session.getAttribute("LOGIN_USER");
System.out.println(login_user);
System.out.println("addUser:"+u);
return AxiosResult.suc();
}
}
@Component
public class LoginInterceptor implements HandlerInterceptor { //HandlerInterceptor 处理器拦截器
//前拦截
@Override //一般只用该方法,不重写后两个方法
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
Object login_user = session.getAttribute("LOGIN_USER");
if(login_user==null) throw new MvcException(E.NO_LOGIN);
return true;
}
6.跨域处理
什么情况会造成跨域?
当我们前端独立部署服务器的时,有自己的ip,port,在前端项目访问页面视图,页面视图中发起向服务端的请求,服务端也是独立部署,有自己的ip,port,服务端的ip与port与前端服务器的ip,port必定有不同.
浏览器不允许在页面中发起与当前路径不同的请求(协议,ip,port).如果发起源地址不同的请求,就会造成浏览器跨域异常.
解决跨域异常的方案:
服务端在收到客户端请求时,首先检测客户端ip,port,method,请求头是否在服务端配置白名单中.如果都允许,就处理请求,并在响应头放入跨域相关响应头.
springmvc解决方案:
1.在处理器类上添加CrossOrigin注解,没用(只对这一个处理器类有效)
@CrossOrigin(origins = "http://localhost:8088",methods = {RequestMethod.GET,RequestMethod.POST},allowedHeaders = "*")
(只有本地8088端口下的get和post请求能进该方法。允许的请求头,*表示全部支持)
2.在springmvc.xml配置,全局配置跨域,没用
(springmvc进行跨域是基于跨域拦截器来执行的,而且此拦截器是最后一个执行.与自定义拦截器一起使用时,有问题)
<mvc:cors>
<mvc:mapping path="/**"
allowed-origins="http://localhost:8088"
allowed-headers="*"
allowed-methods="*"
allow-credentials="true"
/>
</mvc:cors>
(mvc:mapping path="/**" 所有的url都进行跨域
allowed-origins 白名单客户端 allow-credential 允许客户端请求携带cookie)
cookie客户端存储信息的地方
3.基于过滤器进行跨域配置
(过滤器在整个servlet走之前走的,请求先进过滤器,再进DispatcherServlet)
第一步:向容器中注册CorsFilter对象
在springmvc.xml中:
<bean id="corsFilter" class="org.springframework.web.filter.CorsFilter">
<constructor-arg name="configSource">
<bean class="org.springframework.web.cors.UrlBasedCorsConfigurationSource">
<property name="corsConfigurations">
<map>
<entry key="/**">
<bean class="org.springframework.web.cors.CorsConfiguration">
<property name="allowCredentials" value="true"/>
<property name="allowedMethods">
<list>
<value>GET</value>
<value>POST</value>
<value>PUT</value>
<value>DELETE</value>
<!--预检请求-->
<value>OPTIONS</value>
</list>
</property>
<property name="allowedHeaders" value="*"/>
<property name="allowedOrigins" value="http://localhost:8088"/>
</bean>
</entry>
</map>
</property>
</bean>
</constructor-arg>
</bean>
(预检请求:当前后端分离时,浏览器请求分为简单请求与复杂请求. )
简单请求:
Get请求
content-type为urlEncoded,multipart/form-data的post请求
复杂请求:
post请求,contnet-type:applicaitn/json
put
delete请求
浏览器对复杂请求,先发送一次OPTIONS预检请求,此次预检请求不带请求参数,不带cookie;如果预检请求返回200,才发起第二次真正的请求.
第二步:在web.xml配置过滤器代理对象
<filter>
<filter-name>delegatingFilterProxy</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>corsFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>delegatingFilterProxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>