spring MVC

spring MVC

1.web开发常见模式:

  • 同步开发:每次请求客户端发起url,服务端解析并渲染页面(jsp),输出流把渲染的静态html返回客户端。

  • 半分离的异步开发:

    ​ 前端html代码与后端java代码仍然在一个项目中,一起部署到tomcat,用户先请求tomcat获取html页,客户端拿到html后,再发起ajax异步请求获取数据,客户端拿到数据后再通过前端技术来渲染动态html页(DOM操作)。

  • 全分离异步开发(前后端分离):

    ​ 用户先请求nginx获取html页,再发起针对tomcat服务器的异步请求,拿到数据,客户端动态渲染页面。

    ​ 前端html代码独立项目,独立部署nginx静态服务器;

    ​ 后端java代码独立项目,独立部署tomcat动态服务器(处理数据);

http协议:

请求头:content-type:application/x-www-form-urlencoded:前端数据格式key=value&key=value

​ content-type:application/json:前端数据格式:{key:value,key:value,key:value}

​ content-type:multipart-formdata:文件上传场景:字节流

​ http-method:请求方法:get、post、put、delete,options

​ use-agent:客户端信息(浏览器,移动端)

响应头:content-type:text/html

​ application/json

​ ​

第二阶段的servlet中request.getParameter()不能获取json数据,只能获取key=value&key=value

​ (这个处理前端发来json字符串数据)request.getInputStream(),读取输入流中的数据为String,后端得到json字符串,再通过fastjson或gson组件把json字符串转对象。

2.springmvc作用

springMVC的spring框架中的一个子框架,是web模块中的一个小组件。

对servlet进行封装,解决以下一些servlet开发中繁琐的代码。

  • 一个servlet只能够处理一件事情;
  • 繁琐的getParameter,繁琐的转型;
  • 返回json数据麻烦。response.getWriter().print(JSON.toJsonString(Object obj));

3.springMVC初了解

搭建springMVC环境
  • 引入spring核心jar,引入spring-web核心jar(spring-web;spring-webMVC),引入springMVC内部依赖的json组件包jackson(三个:core;databind;annotation)
  • 创建springMVC的xml配置文件.
<context:component-scan base-package="com.javasm"></context:component-scan>

    <!--开启mvc注解识别:@RequestMapping,@GetMapping,@PostMapping,@PutMaping,@DateTimeFormated.@RequestBody,@RequestParam,@ResponseBody-->
    <!--@JSONFormated-->
    <mvc:annotation-driven></mvc:annotation-driven>

    <!--jsp视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/page/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

  • 在web.xml配置springmvc的前端控制器DispatcherServlet。

    两个作用:tomcat启动时加载spring的xml文件初始化容器;

    ​ 用户请求时,解析请求url,根据url去springmvc容器中找到对应的处理器对象。

  • 写springmvc风格的控制层对象controller(处理器对象)

例子:
//1.创建springMVC的xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.javasm"></context:component-scan>

    <!--开启mvc注解识别:
@RequestMapping,@GetMapping,@PostMapping,@PutMaping,@DateTimeFormated.@RequestBody,@RequestParam,@ResponseBody-->
    <!--@JSONFormated-->
     <!--选择描述最末尾是MVC的那个 别问为啥写这句  问就是我卡了一个晚上-->
    <mvc:annotation-driven></mvc:annotation-driven>   

    <!--jsp视图解析器   一般同步开发才用-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/page/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>
//2.在web.xml配置springmvc的前端控制器DispatcherServlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--init.service,doGet,dopost-->
    <!--任何请求全部进入DispatcherServlet,解析url分发请求,加载指定的spring风格的xml配置文件,初始化spring容器-->
    <!--servlet什么时候实例化,用户第一次请求才实例化这个servlet,希望在tomcat启动时实例化DispatcherServlet-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
         <!--初始化spring容器-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springMVC.xml</param-value>
        </init-param>
        <!--在tomcat启动时实例化DispatcherServle-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!--"/"---任何请求全部进入DispatcherServlet --> 
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
//Tomcat现在是设置的deployment 那个项目名简化成"/"
//3.写springmvc风格的控制层对象controller
@Controller
public class SysuserHandler {
    //用来做url映射,把一个url字符串映射到一个处理器方法上
    //http://localhost:8080/hc 走这个方法
    @RequestMapping("hc")  
    public String hellomvc(){
        System.out.println("进入了hellomvc方法");
        //跳转到"/page/hello.jsp"
        return "hello";
    }
}
springMVC执行流程
  • tomcat启动:DispatcherServlet加载springmvc.xml,初始化XmlWebApplicationContext容器对象;

    解析@Controller注解的类里面的@RequestMapping注解,进行url映射(url字符串/hm–>HandlerMethod)

    –>Map<String,HandlerMethod>.put("/hm",hellomvc)

  • 用户请求:

    • dispatcherServlet接收请求,解析url字符串(/hm或/login),RequestMappingHandlerMapping查找处理器方法,把找到的方法返回dispatcherServlet
    • 在dispatcherServlet中,调用RequestMappingHandlerAdapter对象执行处理器方法,获取前端参数,解析封装,调用处理器方法,调用service,调用doa,得到方法的返回值,把返回值返回给dispatcherServlet;
    • 在dispatcherServlet中,调用视图解析器InternalResourceViewResolver得到视图路径。后端进行页面渲染,返回给前端。

4.接收前端参数

4.1 key=value格式

接收key=value&key=value格式的参数,content-type:application/x-www-form-urlencoded

加形参,底层通过反射,获取处理器方法的形参数组,根据形参名去getParameter获取数据。

可以通过@DateTimeFormated指定日期格式

可以通过@RequestParam注解指定参数默认值,该注解只能用在简单类型上,获取key=value数据.

@Controller
public class SysuserHandler {
    
    //@DateTimeFormat(pattern = "yyyy-MM-dd")
    //简单类型,springMVC底层根据形参名去getParameter,习惯写包装类对象。
    //http://localhost:8080/feiqi_addUser?uname=111&upwd=222&ubirthday=2019-11-11
    @RequestMapping("feiqi_addUser")
    public String feiqi_addUser(String uname,String upwd,Integer uscore,@DateTimeFormat(pattern = "yyyy-MM-dd") Date ubirthday){
        System.out.println("进入了feiqi_addUser方法"+uname+"---"+upwd+"---"+ubirthday);
        return "hello";
    }

    //底层判断形参不是简单类型(String,date.包装对象),反射得到对象的成员变量列表,根据成员变量名去getParameter
    //实体类对象如果有时间类型date 可以在实体类里直接注解
    //    @DateTimeFormat(pattern = "yyyy-MM-dd")
    //    private String createTime;

    //http://localhost:8080/add?loginUserr=111&uname=222&createTime=2019-11-11
    @RequestMapping("add")
    public String addUser(Sysuser suser){
        System.out.println("进入了add方法"+suser);
        System.out.println("进入了add方法"+suser.getUname()+"---"+suser.getCreateTime());
        System.out.println(loginUser);
        return "hello";
    }

    //直接给两个对象的成员变量赋值
    //http://localhost:8080/addUserAndRole?uname=fyt&upwd=123&rname=users
    //Sysuser{uid=null, uname='fyt', upwd='123'}--Sysrole{rid=null, rname='users'}
    //如果两个对象有相同的成员变量 这个变量赋值的时候两个对象都会被赋值
    //http://localhost:8080/addUserAndRole?uname=fyt&upwd=123&rid=333&rname=users
    //Sysuser{uid=null, uname='fyt', upwd='123', rid=333}--Sysrole{rid=333, rname='users'}
    //如果是对象.成员变量=?? 这样值给不上
    //http://localhost:8080/addUserAndRole?uname=fyt&upwd=123&srole.rname=users
    //Sysuser{uid=null, uname='fyt', upwd='123'}--Sysrole{rid=null, rname='null'}
    @RequestMapping("addUserAndRole")
    public String addUserAndRole(Sysuser suser, Sysrole srole){
        System.out.println("进入了addUserAndRole方法"+suser+"--"+srole);
        return "hello";
    }

    //Sysuser实体类里持有Srole
    //http://localhost:8080/addUserAndRole2?uname=fyt&upwd=123&srole.rname=users
    //Sysuser{uid=null, uname='fyt', upwd='123', srole=Sysrole{rid=null, rname='users'}}
    @RequestMapping("addUserAndRole2")
    public String addUserAndRole2(Sysuser suser){
        System.out.println("进入了addUserAndRole2方法"+suser);
        return "hello";
    }

    //给传入的参数默认值
    @RequestMapping("usersList")
    public String getUserList(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue ="10") Integer pageSize){
//        if(pageNum==null)pageNum=1;
//        if(pageSize==null)pageSize=10;
        System.out.println("进入了usersList方法"+pageNum+"--"+pageSize);
        //进入了usersList方法1--10
        return "hello";
    }
}
4.2 json格式

接收前端{key:value,key:value}json格式的参数,即请求头content-type:application/json

处理器方法的形参加注解@RequestBody注解

百分百异步开发

@Controller
public class SysuserHandler {
    //一个实体类对象往前端传   @RequestBody不能注解简单数据类型
    @PostMapping("json")
    public ResponseEntity addRole(@RequestBody Sysrole role){  //在这里直接注解
        System.out.println("addRole:"+role);
        role.setRid(100);
        return ResponseEntity.ok(role);
    }
  
    //多个对象往前端传
    //参数以json传输,不能在通过@RequestBody直接注解两个对象,需要注解到map
    //再通过BeanUtils来分数据到对象。
    @PostMapping("addUserAndRole")
    public ResponseEntity addRole(@RequestBody Map map){
        System.out.println("addRole:"+map);
        //数据全部在map中,有时需要把map中的数据分到不同的对象中,apache:commons-beanUtils组件
        Sysuser user = new Sysuser();  //其中一个实体类对象
        Sysrole role = new Sysrole();   //另一个实体类对象
        try {
            BeanUtils.populate(user,map);
            BeanUtils.populate(role,map);
        } catch (Exception e) {
            e.printStackTrace();
        }
        map.put("aa","dd");
        return ResponseEntity.ok(map);
    }
    
//    {
//        "uname": "fyt",
//        "upwd": "111",
//        "rname": "sss",
//          "aa": "dd"
//    }
}

5.同步开发中返回数据给前端

同步开发中:(服务器端渲染视图)request.setAttribute(key,value),在jsp页jstl+el。

​ springMVC中使用Model或者ModelAndView对象来把数据传输到视图层。

    //控制层 public class SysuserHandler代码
	//第一种:通过Mode
	@RequestMapping("login")
    public String loginPage(String uname, String upwd, Model m){
        System.out.println("进入了loginPage方法"+uname+"---"+upwd);
        m.addAttribute("modelDatas","model对象中的数据");//request.setAttribute()
        return "user/login";//   /page/user/login.jsp
    }

	//第二种:通过ModelAndView
    @RequestMapping("login")
    public ModelAndView loginPage(String uname, String upwd){
        System.out.println("进入了loginPage方法"+uname+"---"+upwd);
        ModelAndView mv = new ModelAndView();
        mv.setViewName("user/login");//视图名
        mv.addObject("modelDatas","modelAndView对象中的数据");//数据
        return mv;//   /page/user/login.jsp
    }


//login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
登录页面
<hr>
${modelDatas}   //使用后端传来的数据
</body>
</html>

6.异步请求返回json数据给前端

springMVC内部使用的jackson进行json处理。

  • 在处理器方法上加@ResponseBody注解,springMVC对方法返回值走json转换器(MappingJackson2HttpMessageConverter)进行转json字符串
  • 处理器方法的返回值类型改为ResponseEntity对象.
    • ResponseEntity.ok(数据)
    • new ResponseEntity(数据,响应头,状态码)主要用在下载,刷新token
@Controller
@RequestMapping("role")
public class SysroleHandler {

    //第一种方法
    @RequestMapping("add")
    @ResponseBody   //带这个注解,表示返回值不在走视图解析器,而是走json转换器,把对象转 json字符串
    public Sysrole addRole(Sysrole role){
        System.out.println("addRole:"+role);
        role.setRid(100);
        return role;
        //{"rid":100,"rname":null,"rdate":null}
    }

	//第二种方法(必须推荐使用)
    @RequestMapping("add2")
    public ResponseEntity addRole2( Sysrole role){
        System.out.println("addRole2:"+role);
        role.setRid(100);
        return ResponseEntity.ok(role);//该对象可以指定响应头,响应码,响应体

//        HttpHeaders headers = new HttpHeaders();
//        headers.add("token","adfadfasfd");//下载文件,前后端分离开发给前端返回token
//        return new ResponseEntity(role,null,HttpStatus.OK);
    }
}

7.如何限定请求方法:

  • 在RequestMapping注解进行url映射时,通过注解的属性method限定请求方法
@RequestMapping(path = "add",method = RequestMethod.POST) //注解的属性method限定请求方法
public ResponseEntity addRole(@RequestBody Sysrole role){
    .....................
}
  • 使用GetMapping,PostMapping,PutMapping,DeleteMapping进行url映射
@PostMapping("add")

8.乱码解决

get:tomcat8及以上版本不需要在server.xml中配置URIEncoding=UTF-8,useDefaultEncdoing=true。

post:加编码过滤器,CharacterEncodingFilter

//web,xml  post方法解决乱码加编码过滤器
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

9.如何获取servlet的核心对象

在处理器方法的形参上添加HttpServletRequest,HttpServletResponse,HttpSession。

如果需要获取ServletContext对象的话,通过request或session对象间接获取。

@Controller
public class SysuserHandler {
    //前端vue默认数据格式json,直接传后端
    @PostMapping("login2")
    public ResponseEntity loginPage2(@RequestBody Sysuser user, HttpSession session){
        //先将数值传进session
        session.setAttribute("loginUser",user);
        return ResponseEntity.ok("loginSuccess");
    }
    
    @RequestMapping("add")
    public String addUser(Sysuser suser,HttpSession session){
        System.out.println("进入了loginPage方法"+suser);
        //从session里取数值
        Object loginUser = session.getAttribute("loginUser");
        System.out.println(loginUser);
        return "user/login";//   /page/user/login.jsp
    }
}

11.springMVC的几个概念

  • 前端控制器(中央处理器):DispatcherServlet作用:
作用:初始化springMVC子容器;
接收请求,响应结果,相当于转发器,是springMVC的中央处理器。

处理器映射器:RequestMappingHandlerMapping:解析带有@Controller的类中的RequestMapping注解,进行url映射注册。当用户请求时,根据url得到对应的映射的处理器方法。

处理器适配器:RequestMappingHandlerAdapter:用来封装表单参数,执行处理器方法。

作用:找到Handler处理器方法对象后,由DispatcherServlet前端控制器调用HandlerAdapter来执行HandlerMethod,Handler处理器方法返回给适配器ModelAndView对象。

视图解析器:ViewResolver:设置视图的公共前缀和后缀,通过返回的ModelAndView对象得到完整的视图路径。

json转换器:Converter,MappingJackson2HttpMessageConverter,把对象转json字符串。

12. springMVC请求处理流程图

在这里插入图片描述

12.与DateTimeFormat结合使用的JsonFormated

通过DateTimeFormat注解把获取的日期字符串指定日期格式,转成Date对象

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")   //把毫秒格式转换成yyyy-MM-dd HH:mm:ss
//jackson在序列化对象的时候,把日期序列化这种格式字符串,timezone = "GMT+8"
private Date rdate;

小总结:

  • 做url映射:

​ @RequestMapping注解,用来做url映射,把url映射一个处理器方法上。可以限定客户端提交请求的方法。

​ @GetMapping,@PostMapping,@PutMapping,@DeleteMapping

  • 用于接收参数:

​ @RequestParam,针对key=value格式的参数,注解到简单类型的形参上(String,Date,包装类,基本类型),可以设置默认值。

​ @RequestBody,针对{key:value}json格式的参数,注解到对象类型的形参上。也可以注解到Map上。

  • 用于返回数据:

​ @ResponseBody,表示处理器方法返回值不执行视图解析器,而是执行jackson消息转换器把返回值对象转 json字符串。

​ ResponseEntity:处理器方法的返回值类型,灵活的设置响应体,响应头,状态码.

​ ResponseEntity.ok(响应体数据)

​ new ResponseEntity(Obejct,HttpHeaders,HttpStatus)

# 13. 转发与重定向:不重要

同步开发中,处理器方法之间的转发与重定向。

在返回的String以forward:或redirect:开头.

@Controller
@RequestMapping("role")
public class SysroleHandler {

    @GetMapping("select")
    public String select(){
//        int a=1/0;
        System.out.println("查询角色列表");
        return "roles";
    }

    @GetMapping("add")
    public String add(){
        System.out.println("添加角色");
        return "redirect:select";      //return "forward:select";
        //forward是转发  页面还是http://localhost:8080/role/add
        //redirect是重定向 服务器发起二次请求 http://localhost:8080/role/select
    }
}                       

14. resturl:重要

同步开发中不能使用,前后端分离的项目中使用最多

rest是对url的写法,描述性状态转移,url中不含动词

注意: 1. 前端以post方法提交---- 对应 —>add方法

​ 2. 每个类里只能有一个post/get/put/delete方法提交的 resturl

url (以前)RequestMethoddataresturl(现在)RequestMethod
/user/addpost请求体:{k:v,k,v}/userPOST添加
/user/selectById?uid=1GET/user/1GET查询
/user/updateByIdPOST请求体:{k:v,k:v}/userPUT修改
/user/deleteById?uid=1GET/user/1DELETE删除
   //四种注解对应四种请求方式
   //请求方式:GET负责查询、POST负责添加、DELETE负责删除、PUT负责更新
   //注解后面没有"id" 就代表这四个方法

	@GetMapping("{uid}")
	//例: "/user/1"
	//@PathVariable("uid")可以在 通过@GetMapping("{uid}") 取得值 
    public ResponseEntity selectUserById(@PathVariable("uid") Integer uid){
        System.out.println("selectUserById:"+uid);
        Sysuser user = us.selectUserById(uid);
        return ResponseEntity.ok(user);
    }

    // /user
    @PostMapping
    public ResponseEntity addUser(@RequestBody Sysuser user){
        //添加用户,暂时不维护用户的角色
        boolean isok = us.addUser(user);
        return ResponseEntity.ok("suc");
    }

    @PutMapping
    public ResponseEntity updateUserById(Sysuser user){
        System.out.println("updateUserByID:"+user);
        boolean isok = us.addUser(user);
        return ResponseEntity.ok("suc");
    }

//    /user/1
    @DeleteMapping("{uid}")
    public ResponseEntity delUserById(@PathVariable("uid") Integer uid,@RequestBody Sysuser user){
        System.out.println("delUserById:"+uid);
        return ResponseEntity.ok("suc");
    }

注意点:

put和delete请求:如果请求体中的数据是key=value&key=value格式的话,收不到数据。可以通过配置FormContentFilter过滤器来接收数据。

   //web.xml中配置
	<filter>
        <filter-name>formContentFilter</filter-name>
        <filter-class>org.springframework.web.filter.FormContentFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>formContentFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

15. 静态资源处理

同步开发,半分离异步开发。(前端资源和后端代码在一个项目中)

在web目录下的前端资源无法直接访问。

为什么jsp能访问,也是因为tomcat,tomcat中有默认的serlvet:default,jsp,当访问XXX.jsp进入JspServlet中处理。当我们访问XXX.html,XXX.css等非jsp后缀的时候,进入DispatcherServlet,前端控制器解析url,找处理器对象,找不到,则404.

  • 解决方法1:在web.xml中配置defaultServlet
<servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.html</url-pattern>
        <url-pattern>*.css</url-pattern>
        <url-pattern>*.jpg</url-pattern>
        <url-pattern>*.png</url-pattern>
        <url-pattern>*.js</url-pattern>
    </servlet-mapping>

  • 解决方式2:springMVC中有标签自动配置defaultServlet,建议使用这种。
<!--所有的静态资源都走DefaultServlet, default-servlet-name="default"-->
<mvc:default-servlet-handler></mvc:default-servlet-handler>
  • 解决方式3:springMVC中通过静态资源映射器,对静态资源进行处理。
<!--mapping:uri以/static/ /page/开头的请求,都进入静态资源处理器。
该处理器对/static/css/a.css-->
<!--如果mapping="/a/**" (可随意更改) 地址就是/a/css/a.css -->
<mvc:resources mapping="/static/**" location="/static/"></mvc:resources>
<mvc:resources mapping="/page/**" location="/page/"></mvc:resources>

16. 异常处理:重要

把服务器端处理器方法内部产生的各种信息进行处理。

  • 在web.xml中对不同的错误码进行错误页面配置(只适合前后端未分离)。
	<error-page>
        <error-code>404</error-code>
        <location>/404.html</location>
    </error-page>
    <error-page>
        <error-code>500</error-code>
        <location>/500.html</location>
    </error-page>

缺点:提示信息无法动态定义。只能够返回页面,在前端分离的情况下,不能使用。

  • 局部异常处理:只对一个处理器类中的异常生效。
//只对该类写了这个的有效
//也可以返回页面 返回ModelAndView
@ExceptionHandler(Exception.class)
    public ResponseEntity doException(Exception e){
        String message = e.getMessage();
        Map<String,String> datas = new HashMap<>();
        datas.put("msg",message);
        datas.put("date",System.currentTimeMillis()+"");
        return ResponseEntity.ok(datas);
    }

缺点:范围太小,只对一个类有效

  • 全局异常处理:对所有处理器类中的异常生效。
//异常处理类
public class MyExceptionHandler implements HandlerExceptionResolver {

    //适合于同步开发,当出现异常,转到一个自定义的异常页。
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        HandlerMethod hm = (HandlerMethod)o;
        Method method = hm.getMethod();
        String clzName = method.getDeclaringClass().getName();
        String methodName = method.getName();
        ModelAndView mv = new ModelAndView();
        mv.setViewName("error");                   //转到error页面
        mv.addObject("msg",e.getMessage());
        mv.addObject("date",System.currentTimeMillis()+"");

        return mv;
    }

缺点:只能够同步开发中使用。异步开发中不支持。

  • 全局统一异常处理(真正使用):

    项目中使用,在局部异常处理的基础上结合着@ControllerAdvice注解一起使用

前端程序员调用后端接口,后端需要把执行结果返回前端(数字状态码,中文文字信息,数据)。

状态码枚举类;StatusBean;ResponseBean;自定义异常;定义全局的异常处理类;

//1.创建枚举
public enum StatusCode {
    OPS_SUC(20000,"操作成功"),
    OPS_ERROR(50001,"操作失败"),
    PWD_ERROR(50002,"密码错误"),
    UNAME_ERROR(50003,"用户名未注册"),
    ;
    private Integer code;
    private String msg;

    StatusCode(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;}
}

//2.用于返回不带数据的异常信息类
public class StatusBean {
    private Integer code;
    private String msg;

    public StatusBean(StatusCode sc) {
        this.code =sc.getCode();
        this.msg = sc.getMsg();
    }

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

//3.用于返回带数据的异常信息类
public class ResponseBean extends StatusBean{
    private Object data;

    public ResponseBean(StatusCode sc,Object data) {
        super(sc);
        this.data = data;
    }

    public Object getData() { return data; }
    public void setData(Object data) {this.data = data;}
}
//5.定义一个每个项目都会创建的属于项目的异常
//用于一些非正确操作来抛出异常 返回枚举数据
public class MvcException extends RuntimeException {
    private StatusCode sc;
    
    public MvcException(StatusCode sc) {this.sc=sc;}
    public StatusCode getSc() {return sc;}
    public void setSc(StatusCode sc) {this.sc = sc;}
}

//6.配置全局的异常发生后的处理
//处理器增强注解,对所有的RequestMapping生效。
@ControllerAdvice
public class MyGlobExceptionHandler {
    //Exception发生类型的异常处理
    @ExceptionHandler(Exception.class)
    public ResponseEntity doException(Exception e){
        return ResponseEntity.ok(new StatusBean(StatusCode.OPS_ERROR));
    }
	//MvcException发生类型的异常处理
    @ExceptionHandler(MvcException.class)
    public ResponseEntity doException(MvcException e){
        return ResponseEntity.ok(new StatusBean(e.getSc()));
    }
}
@Controller
@RequestMapping("user")
public class SysuserHandler {
    @Resource
    private ISysuserService us;

    @GetMapping("login")
    public ResponseEntity login(Sysuser user){
        //判断用户名是否已注册
        Sysuser isRegisuser = us.isRegisUser(user.getUname());
        if(isRegisuser==null){
            //非正确操作
            throw new MvcException(StatusCode.UNAME_ERROR);
        }
        //密码校验执行登录
        boolean isLogin = isRegisuser.getUpwd().equals(user.getUpwd());
        if(!isLogin){
            //非正确操作
            throw new MvcException(StatusCode.PWD_ERROR);
        }
        return ResponseEntity.ok(new ResponseBean(StatusCode.OPS_SUC,isRegisuser));
    }
}

17. 文件上传:重要

springMVC的文件上传仍然基于apache common-fileupload。

  • 添加fileupload;io两个文件上传的jar包;
  • 在springmvc.xml中配置文件上传解析对象。
 <!--文件上传解析器-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8"></property><!--上传中文文件名-->
        <property name="maxUploadSize" value="50000000"></property><!--上传文件最大大小-->
        <property name="maxInMemorySize" value="10000000"></property><!--临时文件的存储域-->
        <property name="uploadTempDir" value="/upload/tmp"></property><!--临时文件存储目录-->
    </bean>
  • 写文件上传处理器
@Controller
public class FileHandler {
    //文件上传
    @PostMapping("upload")
    //形参传前端表单参数名  MultipartFile-文件名 大小 输入流都在这里放着
    public ResponseEntity doupload(MultipartFile ifile, HttpServletRequest req){
        String name = ifile.getName();//参数名
        String fileName = ifile.getOriginalFilename();//文件名
        long size = ifile.getSize();//文件大小
        String realPath = req.getServletContext().getRealPath("/");   //得到根路径
        String savePath = "/upload/"+fileName;
        String saveFile = realPath+savePath;        //项目最终上传文件的路径位置

        try {
            byte[] bytes = ifile.getBytes();//文件内容,适合于小文件
//            InputStream inputStream = ifile.getInputStream();//文件内容,适合于大文件
            FileUtils.writeByteArrayToFile(new File(saveFile),bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
        Map<String,String> map = new HashMap<>();
        map.put("SAVE_PATH",savePath);// /upload/aaaa.md
        map.put("REAL_NAME",fileName);
        map.put("UPLOAD_TIME",DateUtils.getCurrentTime());   //传给前端上传时间
        return ResponseEntity.ok(new ResponseBean(StatusCode.OPS_SUC,map));
        //上传成功到out里  E:\code\Java\javaSpringa\out\artifacts\day0915mvc_war_exploded\upload
    }

    
    //同步文件下载,前端不能使用ajax或axios异步调用。
    @GetMapping("down")
    public ResponseEntity download(String path,String realName,HttpServletRequest req){
        System.out.println(path);
        String realPath = req.getServletContext().getRealPath("/");
        String absPath = realPath+path;
        //想把这个文件读取到内存,byte[]
        byte[] bytes = null;
        HttpHeaders headers = new HttpHeaders();
        try {
            bytes = FileUtils.readFileToByteArray(new File(absPath));
            //需要指定响应头  可百度header("Content-Disposition: attachment; filename=abc.txt"); //指定文件名
            //Content-type自动会找不需要设置
            headers.add("Content-Disposition","attachment;filename="+URLEncoder.encode(realName,"UTF-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ResponseEntity(bytes,headers,HttpStatus.OK);
    }
}


//时间工具类
public class DateUtils {
    public static String getCurrentTime(){
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return df.format(new Date());
    }
}

可用postman测试

在这里插入图片描述

18. 拦截器:interceptor

​ 类似于过滤器filter,拦截器是框架中的概念。

1.登陆拦截器是个项目中都会使用。2.权限拦截器

  • 编写拦截器类
public class LoginInterceptor implements HandlerInterceptor {

    //前拦截  只重写这个就行了
    @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      
    	//一旦跨域了 拦截器里需要 对预检请求放行
        String method = request.getMethod();
        if("OPTIONS".equals(method))
            return true;

        HttpSession session = request.getSession();
        Object login_user = session.getAttribute("LOGIN_USER");
        if (login_user != null) {
            return true;
        }
        throw new MvcException(StatusEnum.NO_LOGIN);
    }
}
  • 配置拦截器
  <!--springmvc.xml-->
	<mvc:interceptors>
        <mvc:interceptor>
            <!--拦截路径-->
            <mvc:mapping path="/**"/>
            <!--忽略路径-->
            <mvc:exclude-mapping path="/u/login"></mvc:exclude-mapping>
            <!--拦截器类-->
            <bean class="com.javasm.common.interceptor.LoginInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

19. 跨域处理:

发起的请求与当前页面所在服务, 协议,ip,端口三者只要有一个不同,违反了浏览器的同源策略,造成跨域问题。

需要在服务器端配置响应头,告诉浏览器服务端允许这个客户端的请求:

响应头需要配置如下信息:

​ 允许的域:http://127.0.0.1:8848

​ 允许的请求头:

​ 允许的请求方法:GET/POST/PUT/DELETE/OPTIONS预检.

​ 是否允许请求携带cookie信息:

​ 可以暴露给客户端的响应头:

跨域产生的问题:

问题1:

请求不能正常响应。因为浏览器的同源保护(推荐 springmvc.xml中配置全局跨域)

方法1:@CrossOrigin注解

该注解添加到在处理器方法或类上。

在这里插入图片描述

​ 缺点:只能够针对当个类处理。

* 方法2:springMVC的全局配置
   //spring,xml
	<mvc:cors>
        <mvc:mapping path="/**" allowed-origins="http://127.0.0.1:8848" allowed-methods="*" allowed-headers="*"/>
    </mvc:cors>
方法3:CrosFilter过滤器
<!-- 这个配置需要放到Spring的配置文件中,不能放到SpringMVC的配置文件,因为SpringMVC的加载是基于Servlet,它是晚于Filter的 -->
<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>HEAD</value>
                                    <value>PUT</value>
                                     <value>Delete</value>
                                    <value>OPTIONS</value>
                                </list>
                            </property>
                            <property name="allowedHeaders" value="*"/>
                            <property name="allowedOrigins" value="*"/>
                        </bean>
                    </entry>
                </map>
            </property>
        </bean>
        </constructor-arg>
</bean>
<!-- web.xml
由于CorsFilter跟通常的Filter不一样,Spring对其做了很多改造,所以加载的方式要使用DelegatingFilterProxy,通过Spring的方式把它放到容器中
-->
<filter>
    <filter-name>myCorsFilter</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>myCorsFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
方法4:自定义跨域处理过滤器
public class MyCrosFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
         try {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;

            // 预检请求,请求头Origin是客户端地址,要求跨域头不能是*
            String origin = httpRequest.getHeader("Origin");
            if (origin == null) {
                httpResponse.addHeader("Access-Control-Allow-Origin", "*");
            } else {
                httpResponse.addHeader("Access-Control-Allow-Origin", origin);
            }
            httpResponse.addHeader("Access-Control-Allow-Headers",
                    "Origin, x-requested-with, Content-Type, Accept,X-Cookie,token");
//httpResponse.addHeader("Access-Control-Expose-Headers", "token");

            httpResponse.addHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.addHeader("Access-Control-Allow-Methods", "GET,POST,PUT,OPTIONS,DELETE");
            //预检请求,直接通过。
            if (httpRequest.getMethod().equals("OPTIONS")) {
                httpResponse.setStatus(HttpServletResponse.SC_OK);
                return;
            }
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        }
    }
    @Override
        public void destroy() {
    }
}
方法5:springboot的解决方式:
@Configuration
public class MyConfiguration {

 //springmvc中 没有FilterRegistrationBean这个类
	@Bean
	public FilterRegistrationBean这个类 corsFilter() {
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		CorsConfiguration config = new CorsConfiguration();
		config.setAllowCredentials(true);
		config.addAllowedOrigin("http://domain1.com");
		config.addAllowedHeader("*");
		config.addAllowedMethod("*");
		source.registerCorsConfiguration("/**", config);
		FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
		bean.setOrder(0);
		return bean;
	}
}
方法6:其他方案(了解)

​ 基于nginx做url代理;未学习nginx,暂不学习。

​ 基于node的一个包http-proxy-middleware,基于node组件启动一个后端程序做代理,把所有程序转发到服务器。(属于前端技能)。

问题2:

httpSession保存用户会话信息:每次请求服务器都会创建新的HttpSession对象,无法做到多次请求之间共享session中数据。

​ 解决方法:前端axios请求配置withCredentials=true,表示请求时携带cookie

服务端跨越配置中允许接收客户端cookie,allow-credentials="true"
如果使用chrome发现配置后仍然不行,开发中暂时先chrome://flags/,把samesite禁用了

仍然存在的问题:
tomcat分布式部署的时候,多服务器之间的session共享问题(通过spring-session,结合redis使用)。
chrome浏览器的samesite同站点默认配置lax,不保存第三方的cookie问题。		
//vue
//base.js
axios.defaults.baseURL = 'http://localhost:8080';  //这里省略axios网址的书写
axios.defaults.withCredentials = true;      //客户端请求仍然携带cookie信息。

//axios前拦截
axios.interceptors.request.use(function (config) {
	//前端进行登录校验,前端存储用户的登录信息。
    return config;
  }, function (error) {
    return Promise.reject(error);
});

//axios后拦截
axios.interceptors.response.use(function (response) {
	//服务端返回的错误状态码,统一处理
	//token的刷新重置。
	//统一获取数据
    return response.data;
  }, function (error) {
    return Promise.reject(error);
  });

//
methods: {
            doLogin() {
                //这里axios网址前缀就是http://localhost:8080;了
                axios.post('/u/login',this.loginForm).then(resp => {
                    // debugger;
                    if(resp.code==50004){
                        this.showLoginMsg = true;
                    }else{
                        debugger;
                        this.showLoginMsg = false;
                        localStorage.setItem("loginuser",resp.data);
                        location.href="main.html";
                    }
                });
            }
}
//后端部分
//跨域这里加一句 allow-credentials="true" 允许请求携带cookie
	<mvc:cors>
        <mvc:mapping path="/**" allowed-origins="http://127.0.0.1:8848" allowed-methods="*" allowed-headers="*" allow-credentials="true"/>
    </mvc:cors>

​ 解决方法2:JWT组件,token工具包

	服务端不保存会话状态信息,当用户登陆,服务端生成token字符串,返回客户端保存localStorage或cookie。每次请求都携带token信息在请求头中,发送服务端,服务端校验是否合法请求。
	 依赖组件:auto0或jjwt,两者都可以用来生成token。
	 缺点:token过长,效率低;服务端无法控制token强制失效,不安全;需要自己实现token刷新。

注意点:

在跨域的情况下,浏览器对POST,PUT,DELETE三种请求,会先向服务器发送预检请求,(检查服务端是否支持客户端的本次请求).

20. aop事务切面

定义出事务管理器对象。

定义事务切面,切入点表达式使用注解方式。

//service
public interface ISysuserService {
    public boolean addUsers() throws Exception;
    public boolean delUserByID(Integer uid);
}

@Service
public class SysuserServiceImpl implements ISysuserService {
    @Resource
    private ISysuseDao ud;

    //addUsers()执行多次添加在一个事务里
    @TransAnno   //表示该方法进行事务管理
    @Override
    public boolean addUsers() throws Exception {
        Sysuser u = new Sysuser("aaa","aaa");
        ud.addUser(u);
        Sysuser u1 = new Sysuser("bbd","bbb");
        ud.addUser(u1);
        return true;
    }

    @Override
    public boolean delUserByID(Integer uid) {
        ud.delUser(uid);
        return false;
    }
}
//dao
public interface ISysuseDao {
    public Boolean addUser(Sysuser u) throws Exception;
    void delUser(Integer uid);
}

@Repository
public class SysuserDaoImpl implements ISysuseDao {

    @Resource
    private CurrentConnection cc;

    @Override
    public Boolean addUser(Sysuser u) throws Exception{
            PreparedStatement pst = cc.getCurrentConnection().prepareStatement("insert into sysuser(uname,upwd) values(?,?)");
            pst.setString(1,u.getUname());
            pst.setString(2,u.getUpwd());
            return pst.execute();
    }

    @Override
    public void delUser(Integer uid) {
        try {
            Connection currentConnection = cc.getCurrentConnection();
            PreparedStatement pst = currentConnection.prepareStatement("delete from sysuser where uid=?");
            pst.setInt(1,uid);
            pst.execute();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
//tm
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TransAnno {
}

@Component
@Aspect
public class TmAspect {

    @Resource
    private MyTransactionManager tm;

    //不用另一种方法是因为这个具体
    //不可能每个方法都要事务管理
    @Pointcut("@annotation(com.javasm.tm.TransAnno)")  
    public void tmpc(){}

    @Pointcut("execution(* com.javasm.service.*.*(..))")
    public void all(){}

    @After("all()")
    public void closeConnection(){
        tm.close();
    }

    @Around("tmpc()")
    public Object execute(ProceedingJoinPoint jp){
        Object proceed = null;
        try {
            tm.openConnection();
            tm.beginTransaction();
            proceed = jp.proceed();
            tm.commit();
        } catch (Throwable throwable) {
            tm.rollback();
        }finally {
            tm.close();
        }
        return proceed;
    }


}
@Component
public class MyTransactionManager {

    @Resource
    private CurrentConnection cc;

    public void openConnection(){
        cc.openConneciton();
    }

    public void beginTransaction(){
        Connection currentConnection = cc.getCurrentConnection();
        try {
            currentConnection.setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void commit(){
        Connection currentConnection = cc.getCurrentConnection();
        try {
            currentConnection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void rollback(){
        Connection currentConnection = cc.getCurrentConnection();
        try {
            currentConnection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void close(){
        Connection currentConnection = cc.getCurrentConnection();
        try {
            if(currentConnection!=null){
                currentConnection.close();
                currentConnection=null;
                cc.removeThreadLocal();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
@Component
public class CurrentConnection {
    //线程变量ThreadLocal,为每个独立的线程创建一个变量副本。set/get
    private ThreadLocal<Connection> conn=new ThreadLocal<>();

    @Resource
    private DataSource ds;

    public void openConneciton(){
        try {
            Connection connection = ds.getConnection();
            conn.set(connection);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public Connection getCurrentConnection(){
        Connection connection = conn.get();
        //如果没有事务帮我打开 那就自己打开
        if(connection==null){
            try {
                connection = ds.getConnection();
                //如果没有事务打开
                //这个在后面tm.close()事务中用于connection关闭
                conn.set(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return connection;
    }

    public void removeThreadLocal() {
        conn.remove();
    }
}
//spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.javasm"></context:component-scan>

    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

</beans>
//test
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class TestTransaction {

    @Resource
    private ISysuserService us;

    @Test
    public void addUsers() throws Exception {
        us.addUsers();
    }

    @Test
    public void delUser() throws Exception {
        us.delUserByID(30);
    }
}

其他

Dom4j解析xml:核心对象:SAXReader

jdk子代的Properties对象解析.properties:new Properties().load()

spring的xml配置文件的方式是在ssm整合框架中使用。

spring的类配置的方式是在springboot框架中使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值