SpringMVC学习笔记

博主 2632 2020-07-11

第一章 SpringMVC概述

  1. SpringMVC简述
    SpringMVC是Spring为表现层提供的基于MVC设计理念的优秀的Web框架
    SpringMVC通过一套MVC注解,让POJO成为处理请求的控制器,而无需实现任何接口

SpringMVC就是一个Spring。 Spring是容器,ioc能够管理对象,使用, @Component, @Repository, @Service, @Controller
SpringMVC能够创建对象, 放入到容器中(SpringMVC容器), springmvc容器中放的是控制器对象,我们要做的是 使用@Contorller创建控制器对象, 把对象放入到springmvc容器中, 把创建的对象作为控制器使用
这个控制器对象能接收用户的请求, 显示处理结果,就当做是一个servlet使用。

使用@Controller注解创建的是一个普通类的对象, 不是Servlet。 springmvc赋予了控制器对象一些额外的功能。

web开发底层是servlet, springmvc中有一个对象是Servlet : DispatherServlet(中央调度器)

DispatherServlet: 负责接收用户的所有请求, 用户把请求给了DispatherServlet, 之后DispatherServlet把请求转发给Controller对象, 最后是Controller对象处理请求。

  1. SpringMVC优点
    1)基于MVC架构,分工明确,解耦
    2)上手快,使用简单
    3)作为Spring框架一部分,方便整合其他部分
    4)强化注解的使用

  2. SpringMVC第一个程序
    新建一个Web动态工程
    配置
    web.xml文件:

<?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">

    <!-- 配置DispatcherServlet-->
    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 配置DispatcherServlet的一个初始化参数,配置SpringMVC配置文件的位置和名称
            实际上可以不通过contextConfigLocation来配置SpringMVC的配置文件,而使用默认的
            默认的配置文件为: /WEB-INF/<servlet-name>-servlet.xml
        -->
<!--        <init-param>-->
<!--            <param-name>contextConfigLocation</param-name>-->
<!--            <param-value>classpath:springmvc.xml</param-value>-->
<!--        </init-param>-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Map all requests to the DispatcherServlet for handing -->
    <servlet-mapping>
        <servlet-name>springDispatcherServlet</servlet-name>
        <!-- url可以配置为 *.do *.action 或者斜杠/-->

<url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

springDispatcherServlet-servlet.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置自动扫描的包 -->
    <context:component-scan base-package="com.learn.springmvc.handlers"></context:component-scan>

    <!-- 配置视图解析器:如何把handler方法返回值解析为实际的物理视图-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>

接收请求的类:

@Controller
public class MyController {

    /**
     *
     * @return 表示本次请求的处理结果 Model:数据,要显示的数据 View视图 比如jsp等
     */
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(){
        ModelAndView mv = new ModelAndView();
        //添加数据,在请求的最后把数据放入到request作用域
        mv.addObject("msg", "Hello SpringMVC");
        mv.addObject("function", "执行的是doSome方法");
        //指定视图
        mv.setViewName("/show.jsp");
        return mv;
    }
}

在WEB-INF下新建views/success.jsp,随便写一点东西在页面上作为显示
在首页添加超链接
<a href="/01_demo1/helloWorld">Hello World</a>
发起请求
然后部署到tomcat即可在首页跳转到success.jsp页面

  1. SpringMVC组件
  2. SpringMVC执行流程
    1)用户向Tomcat发起一个请求
    2)tomcat(web.xml--url-pattern知道来自用户的请求给DispatcherServlet)
    3)DispatcherServlet(根据springmvc.xml配置知道 请求转发给控制器---控制器执行方法)
    4)DispatcherServlet把请求转发个MyController.doSome()方法
    5)框架执行doSome()把得到ModelAndView进行处理, 转发到show.jsp

执行流程补充:
image.png

  1. 用户发起请求some.do

  2. DispatcherServlet接收请求some.do,把请求转交给处理器映射器。
    处理器映射器:SpringMVC框架中的一种对象,框架把实现了HandlerMapping接口的类都叫做映射器(可能存在多个)。
    处理器映射器的作用:根据请求,从SpringMVC容器中获取处理器对象(也即创建控制器Controller的bean对象),然后框架把找到的处理器对象放到一个叫做处理器执行链(HandlerExecutionChain)的类中保存
    HandlerExecutionChain:类中保存着 1)处理器对象(Controller);2)项目中所有的拦截器对象保存到List集合(List interceptorList)

中央调度器获取处理器执行链HandlerExecutionChain的方法: mappedHandler = getHandler(processedRequest);

  1. DispatcherServlet把2中的HandlerExecutionChain中的处理器对象交给了处理器适配器对象(可存在多个)
    处理器适配器:SpringMVC框架中的对象,需要实现HandlerAdapter接口。
    处理器适配器作用:执行处理器方法(调用MyController的doSome()方法),得到返回值ModelAndView

中央调度器获取适配器的方法: HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
通过适配器执行处理器方法:mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

  1. DispatcherServlet把3中获取到的ModelAndView交给视图解析器对象
    视图解析器:SpringMVC中的对象,需要实现ViewResoler接口(可以有多个)
    视图解析器作用:组成视图View完整路径,使用前缀,后缀。
    View是一个接口,用于表示视图,在框架中jsp,html不是String表示,而是使用View和它的实现类表示。eg:RedirectView,InternalResourceView
    InternalResourceView:视图类,表示jsp文件,视图解析器会创建InternalResourceView类的对象,在这个对象的里面,有一个属性url=prefix+逻辑视图名称+suffix eg:url=/WEB-INF/view/show.jsp

  2. DispatcherSerlvet把4步骤中创建的View对象获取到,调用View类自己的方法,把Model数据放入到request作用域,执行对象视图的forward,请求结束


第二章 SpringMVC注解式开发

2.1 @RequestMapping定义请求规则

1)当@RequestMapping放在类上时,value值表示这个类的地址的公共前缀,斜杠映射到Web应用的根目录
当@RequestMapping放在方法上时,value值表示这个方法的地址,如果没有类上的@RequestMapping则映射到根目录,否则映射到类标记的目录后面

@Controller
@RequestMapping("/test/")
public class MyController {

    /**
     *
     * @return 表示本次请求的处理结果 Model:数据,要显示的数据 View视图 比如jsp等
     */
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(){
        ModelAndView mv = new ModelAndView();
        //添加数据,在请求的最后把数据放入到request作用域
        mv.addObject("msg", "Hello SpringMVC");
        mv.addObject("function", "执行的是doSome方法");
        //指定视图
        mv.setViewName("show");
        return mv;
    }

    @RequestMapping(value = "/other.do")
    public ModelAndView doOther(){
        ModelAndView mv = new ModelAndView();
        //添加数据,在请求的最后把数据放入到request作用域
        mv.addObject("msg", "Hello SpringMVC");
        mv.addObject("function", "执行的是doOther方法");
        //指定视图
        mv.setViewName("show");
        return mv;
    }
}

2)method参数可以指定请求方式,它的值是RequestMethod的枚举值
eg:
@RequestMapping(value = "/some.do", method = RequestMethod.GET)
请求方式不合法错误码405

3)可以使用params和headers支持简单的表达式

/**
     *
     * @return
     */
    @RequestMapping(value = "/testParamsAndHeaders", params = {"username=root"}, headers = {"Connection!=keep-alive"})
    public String testParamsAndHeaders(){
        System.out.println("testParamsAndHeaders");
        return SUCCESS;
    }

4)使用通配符
使用通配符

@RequestMapping("testAntPath/*/abc")
    public String testAntPath(){
        return SUCCESS;
    }

2.2 处理器方法的参数

1)处理器方法可以有四个参数
HttpServletRequest
HttpServletResponse
HttpSession
请求中携带的参数
这些参数由系统自动赋值

2)接收用户提交的参数
逐个接收示例:
发起请求的页面表单

<form action="test/receiveProperty.do" method="post">
    姓名:<input type="text" name="name" value="user">
    年龄:<input type="text" name="age" value="18">
    <input type="submit" value="提交"/>
</form>

接收方法

@RequestMapping(value = "/receiveProperty.do")
    public ModelAndView doFirst(String name, int age){
        ModelAndView mv = new ModelAndView();
        mv.addObject("myname", name);
        mv.addObject("myage", age);

        //指定视图
        mv.setViewName("show");
        return mv;
    }

要求处理器方法的形参名和请求中参数名的name必须一致,框架接收方法时:
1.使用request对象接收请求参数
String strName = request.getParameter("name")
String strAge = request.getParameter("age")
DispatcherServlet调用控制器方法时,按名称对应,把接受的参数赋值给形参,会进行类型转换,可以把String转换成int,long,float,double等类型,所以形参可以直接写基本类型
当参数转换错误时,页面出现400错误码
形参使用包装类时,传递的参数可以为空,包装类的实例为空值null
2.当用post请求提交参数时,中文有乱码,需要使用过滤器处理乱码问题
过滤器可以自定义,也可以使用框架中提供的过滤器
在web.xml中添加过滤器
CharacterEncodingFilter类中doFilterInternal方法对请求和响应的编码进行设置

<!--    注册声明过滤器,解决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>
<!--        强制请求对象和响应对象使用Encoding编码的值-->
        <init-param>
            <param-name>forceRequestEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
<!--    强制所有的请求先通过过滤器处理-->
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

使用@RequestParam解决请求中参数名和形参名不一样的问题
注解的属性value写请求的参数名name,位置在处理器方法的形参前

    @RequestMapping(value = "/receiveProperty.do")
    public ModelAndView doFirst(@RequestParam(value = "rname", required = false) String name, @RequestParam("rage") Integer age){
        ModelAndView mv = new ModelAndView();
        //添加数据,在请求的最后把数据放入到request作用域
        mv.addObject("myname", name);
        mv.addObject("myage", age);
        System.out.println("name:" + name);
        System.out.println("age:" + age);
        //指定视图
        mv.setViewName("show");
        return mv;
    }

此外@RequestParam还有一个参数required,默认为true,表示请求必须携带这个参数

对象接收:
使用一个对象去接收请求传递过来的参数,框架自动创建实例,并通过set方法注入属性值。要求属性值的名字和请求传递的参数名相同

    /**
     * 处理器方法形参是java对象,这个对象的属性名和请求中参数名是一样的
     * 框架会创建形参的实例,通过setXxx()方法注入属性
     * @return
     */
    @RequestMapping(value = "/receiveObject.do")
    public ModelAndView doReceiveObject(Student student){
        ModelAndView mv = new ModelAndView();
        //添加数据,在请求的最后把数据放入到request作用域
        mv.addObject("myname", student.getName());
        mv.addObject("myage", student.getAge());
        mv.addObject("student", student);
        System.out.println("name:" + student.getName());
        System.out.println("age:" + student.getAge());
        //指定视图
        mv.setViewName("show");
        return mv;
    }

2.3 处理器方法的返回值

1)返回ModelAndView
包括数据部分和视图部分,如果处理器方法处理完后,需要跳转到其他资源,且需要在跳转的资源间传递数据,使用ModelAndView返回值。如果只需要视图或者数据,不建议使用。
addObject相当于在request域中添加一个键值对,setViewName()请求转发到指定页面

2)返回String
处理器方法返回的字符串可以指定逻辑视图名,通过视图解析器可以将其转换为物理视图地址

3)返回void(较少使用)
无法表示数据和视图,一般在处理Ajax请求,可以使用void返回值
在方法中使用HttpServletResponse输出数据,响应ajax请求

使用一个按钮发起ajax请求

<button id="btn01" >发起ajax请求</button>

给按钮绑定单击事件

<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script type="text/javascript">
        $(function () {
            // alert("22");
            $("#btn01").click(function () {
                // alert("单击btn01");
                $.ajax({
                    url:"returnVoid-ajax.do",
                    data: {
                        name: "zhangsan",
                        age: 20
                    },
                    type:"post",
                    dataType:"json",
                    success:function (resp) {
			//返回的resp原本是一个字符串,JQuery会自动将其转换为json对象
                        alert(resp.name + " ," + resp.age);
                    }
                });
            });
        });
    </script>

接收请求的方法,获取参数,生成一个对象,返回json字符串

    @RequestMapping(value = "/returnVoid-ajax.do")
    public void doReturnVoidAjax(HttpServletResponse response, String name, Integer age) throws IOException {
        System.out.println("name:" + name + ",age:" + age);
        //处理ajax
        Student student = new Student();
        student.setName(name);
        student.setAge(age);
        ObjectMapper objectMapper = new ObjectMapper();
        String s = "";
        if(student != null){
            s = objectMapper.writeValueAsString(student);
            System.out.println("student转换的json" + s);

        }
        response.setContentType("application/json;charset=utf-8");

        PrintWriter writer = response.getWriter();
        writer.write(s);
        writer.flush();
        writer.close();
    }

手工实现ajax,json数据,代码存在重复部分:生成json字符串,设置响应返回值,于是引入对象返回值Object

4)返回对象Object
处理器方法返回Object对象,这个对象可以使Integer,String,自定义对象,Map,List等,但返回的对象不是作为逻辑视图出现的,而是作为直接在页面显示的数据出现的
如果需要返回对象,需要加上@ResponseBody注解,将转换后的JSON数据放入到响应体中。(相当于设置 response.setContentType("application/json;charset=utf-8")
)

SringMVC使用jackson工具处理json,需要引入依赖
需要在SpringMVC配置文件加入<mvc:annotation-driven>注解驱动(相当于写 json = objectMapper.writeValueAsString(obj))

SpringMVC处理器方法返回Object,可以转换为json输出到浏览器,响应ajax的内部原理

  1. <mvc:annotation-driven>注解驱动
    注册注解驱动实现的功能是完成java对象到json,xml,text,二进制等数据格式的转换,涉及到一个接口HttpMessageConvetor接口(消息转换器),接口定义了java对象到json,xml等数据格式的方法,这个接口有很多的实现类,分别完成到json、xml等数据格式的转换
    HttpMessageConvetor接口内,用canWrite()方法检查数据能否转换为某个格式,如果能则转换,返回true
    write()方法,调用jackson把对象转换json为字符串
    注解驱动会给HttpMessageConvetor接口创建多个不同实现类,包括常用的StringHttpMessageConvetor和MappingJackson2HttpMessageConvetor实现类
  2. @ResponseBody注解
    加上注解后,自动把生成的json字符串以json格式,编码集采用utf-8格式发送给浏览器

5)返回List

//处理器返回List 实际上返回了json数组[{json1}, {json2}]
    @RequestMapping(value = "/returnList-ajax.do")
    @ResponseBody
    public List<Student> doReturnList() throws IOException {
        //处理ajax
        Student student2 = new Student("张三", 22);
        Student student3 = new Student("啊宝", 13);

        return  new ArrayList<Student>(Arrays.asList(student2, student3));
    }

绑定单击事件,循环遍历获取得到的json数组

    <script type="text/javascript">
        $(function () {
            // alert("22");
            $("#btn01").click(function () {
                // alert("单击btn01");
                $.ajax({
                    url:"returnList-ajax.do",
                    data: {
                        name: "zhangsan",
                        age: 20
                    },
                    type:"post",
                    // dataType:"json",
                    success:function (resp) {

                        // alert(resp.name + " ," + resp.age);
                        $.each(resp, function (i, n) {
                            alert(n.name + "," + n.age);
                        })
                    }
                });
            });
        });
    </script>

6)返回String,但是表示数据,不表示视图
与返回视图的处理器方法的区分方式是是否有@ResponseBody注解,有这个注解表示返回数据
返回字符串中有中文乱码时,使用produces属性指定编码集(如下面的代码所示)
中文乱码是因为StringHttpMessageConvertor接口处理数据时按照其默认的字符集ISO-8859-1进行处理

    //处理器返回String 且加了@esponseBody注解,
    @RequestMapping(value = "/returnValueString-ajax.do", produces = "text/plain;charset=utf-8")
    @ResponseBody
    public String doReturnValueString(String name, Integer age) throws IOException {
        //处理ajax
        return "this is a return string method from 张三";
    }

2.4 解读中央调度器的url-pattern

<url-pattern/>
1)当你启动服务器,访问首页index.jsp(http://localhost:8080/05demo/)由tomcat服务器接收请求并进行处理
首页展示的图片,引入的JQuery文件,都由tomcat处理
2)访问http://localhost:8080/05demo/some.do,交给了我此处定义的中央调度器<url-pattern>*.do</url-pattern>进行处理的
3)访问http://localhost:8080/05demo/html/test.html,由tomcat处理

tomcat的conf目录下有一个web.xml文件,定义了一个DefaultServlet用来处理所有没有映射到其他Servlet的请求,可以用来处理静态资源


    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>default</servlet-name>

        <url-pattern>/</url-pattern>
    </servlet-mapping>

<url-pattern>/</url-pattern>斜杠/代表所有的请求都可以通过这个servlet,如果自己定义的servlet也设置url-pattern为/,则无法访问首页的图片和静态资源test.html
image.png
出现静态资源(html js css image )404错误码
但是我们定义的动态资源some.do可以访问,因为程序中存在控制器方法可以处理这个请求

解决静态资源访问的方式:

方式一:在springmvc配置文件中加入<mvc:default-servlet-handler>标签
原理是加入这个标签后,框架会创建控制器对象DefaultServletHttpRequestHandler(和我们定义的Controller类似),将请求转发给tomcat的DefaultServlet
加入标签后,我们发现静态资源可以访问了,然后惊奇的发现some.do不可以访问了!
因为DefaultServletHttpRequestHandler将所有请求都转发给了tomcat!
然后我们加入<mvc:annotation-driven></mvc:annotation-driven>注解驱动,来解决动态资源访问的冲突问题

方式二(常用):在springmvc.xml中添加标签<mvc:resources mapping="" location=""/>
原理是框架会创建ResourceHttpRequestHandler这个处理器对象,通过这个对象处理对静态资源的访问,不依赖tomcat服务器
存在两个属性mapping和location,mapping定义访问静态资源的url地址,使用通配符**,location定义资源在项目中的目录位置
例如<mvc:resources mapping="/image/**" location="/image/"/>
表示能访问url为http:ip:port/项目路径/image/+任意请求地址 后的所有静态资源,资源存储位置在/webapp/image/路径下
<mvc:resources mapping="/html/**" location="/html/"/>
表示能访问url为http:ip:port/项目路径/html/+任意请求地址 后的所有静态资源,资源存储位置在/webapp/html/路径下
同样该方式需要加入<mvc:annotation-driven></mvc:annotation-driven>注解驱动,来解决动态资源访问的冲突问题

在实际项目中,在web-app下建立static文件夹,把所有的静态资源文件和文件夹都放在static下,就可以配置<mvc:resources mapping="/static/**" location="/static/"/>来进行访问了

2.5 相对路径和绝对路径问题

在jsp,html中使用的地址,都是在前端页面中的地址,都是相对地址
地址分类:
1.绝对地址:带有协议名称的地址
eg: http://www.xxxx.com,ftp://202.111.222.33
2.相对地址:不带协议开头的
eg: user/some.do,/user/som.do
相对地址不能独立使用,必须有一个参考地址

案例分析:
1)
http://localhost:8080/06demo/index.jsp上发起请求,请求超链接地址不加斜杠
<a href="test/some.do">发起Some.do结尾的</a>
处理器方法的注解为:@RequestMapping(value = "/test/some.do")
单击超链接后访问到http://localhost:8080/06demo/test/some.do
结论:当页面的超链接没有斜杠打头时,访问地址是当前页面所在路径,加上超链接的href写的地址
http://localhost:8080/06demo/ + test/some.do
2)超链接改为<a href="/test/some.do">发起Some.do结尾的</a>
单击后访问http://localhost:8080/test/some.do
结论:页面地址以斜杠打头后映射到http://localhost:8080/,即http://ip:port/

解决方案:
加入EL表达式:<a href="${pageContext.request.contextPage}/test/some.do">发起Some.do结尾的</a>
加入base标签:加入后,页面当前的地址如果不以斜杠打头,都以此为参考地址

在xml文件开头加上:
<%
    String basePath = request.getScheme() + "://" + request.getServerName()
    + ":" + request.getServerPort() + request.getContextPath() + "/";
%>

在head标签里加上:
<base href="<%=basePath%>">

第三章 SSM整合

3.1搭建SSM开发环境

ssm整合开发
SSM: SpringMVC + Spring + MyBatis

SpringMVC: 视图层 负责接收请求,显示处理结果
Spring,业务层,管理service,dao,工具类对象
MyBatis:持久层,访问数据库

用户发起请求->SpringMVC接收->Spring中的Service对象->使用MyBatis处理数据->Service->SpringMVC->返回给前端

SSM整合也叫做SSI,整合中有容器
1.SpringMVC容器,管理Controller控制器对象
2.第二个容器Spring容器,管理Service,DAO,工具类对象
把使用的对象交给合适的容器创建,管理。把Controller还有web开发的相关对象交给SpringMVC容器,
写在SpringMVC的配置文件中
把service dao对象定义在spring的配置文件,让spring管理这些对象

SpringMVC容器和spring容器是有关系的,关系已经确定好了
SpringMVC容器是Spring的子容器

实现步骤
使用ssm数据库的student表
1. 新建maven web项目
2. 加入依赖
 三个框架 jackson mysql druid jsp  servlet
3. 写web.xml
    1)注册DispatcherServlet,目的:创建SpringMVC容器对象,才能创建Controller对象;创建Servlet,才能接受请求
    2)注册Spring的监听器,ContextLoaderListener,目的:创建Spring的容器对象,才能创建service,dao对象
    3)注册字符集过滤器,解决post请求乱码的问题

4. 创建包,Controller包,service包,dao包,实体类包名创建好
5. 写3个框架配置文件
    1)SpringMVC配置
    2)Spring配置文件
    3)MyBatis配置文件
    4)数据库的属性配置文件
6.编写代码,dao接口和mapper文件,service和实现类,controller,实体类
7.写jsp页面

3.1.1 maven pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.learn</groupId>
  <artifactId>ssmproject</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>


  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

<!--    servlet-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
<!--    jsp-->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.2.1-b03</version>
      <scope>provided</scope>
    </dependency>

    <dependency> <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</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-databind</artifactId>
        <version>2.9.0</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.9</version>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>

  </dependencies>

  <build>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>

    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

3.1.2 dispatcherServlet.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://mybatis.org/schema/mybatis-spring"
       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://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!--    springmvc的配置文件,声明controller 和其他相关对象-->
<!--    开启组件扫描-->
    <context:component-scan base-package="com.learn.controller"></context:component-scan>
<!--    视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
<!--    注解驱动-->
    <mvc:annotation-driven ></mvc:annotation-driven>
<!--    注解驱动的功能:响应ajax请求,返回json/ 解决静态资源访问问题-->
</beans>

3.1.3 applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">


<!--    spring配置文件:声明service,dao,工具类对象-->
<!--    声明数据源,连接数据库-->
    <context:property-placeholder location="classpath:conf/jdbc.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

<!--    声明SqlSessionFactoryBean创建SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:conf/mybatis.xml"></property>
    </bean>

<!--    声明mybatis的扫描器,创建dao对象-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
        <property name="basePackage" value="com.learn.dao"></property>
    </bean>

<!--    声明service的注解@Service所在的包名位置-->
    <context:component-scan base-package="com.learn.service"></context:component-scan>

<!--    事务配置-->

</beans>

3.1.4 jdbc.properties

jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456

3.1.5 mybatis.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--    <settings>-->
<!--        <setting name="logImpl" value="STDOUT_LOGGING"/>-->
<!--    </settings>-->
    <typeAliases>
<!--        name一般是实体类所在的包名-->
        <package name="com.learn.domain"/>
    </typeAliases>

    <mappers>
<!--        <mapper resource="com/learn/dao/StudentDao.xml"></mapper>-->
        <package name="com.learn.dao"/>
    </mappers>
</configuration>

第四章 SpringMVC核心技术

4.1 请求重定向和转发
1)请求转发,使用forward: + 资源全路径
特点:不经过视图解析器

    @RequestMapping(value = "/doForward.do")
    public ModelAndView doForWard(HttpServletRequest request, HttpServletResponse response,
                                  HttpSession session){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "你好,我是/WEB-INF/views/show.jsp页面");
        mv.addObject("function", "doForward.do");
        //指定视图
        //显式转发
        mv.setViewName("forward:/WEB-INF/views/show.jsp");
        return mv;
    }

2)重定向,使用redirect: + 资源全路径
同样不经过视图解析器
框架对重定向时,Model中的简单类型的数据,转为字符串string使用,作为hello.jsp的get请求的参数使用
目的是使doRedirect.do和hello.jsp两次请求之间传递数据

    @RequestMapping(value = "/doRedirect.do")
    public ModelAndView doRedirect(HttpServletRequest request, HttpServletResponse response,
                                  HttpSession session){
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "你好,我是/hello.jsp页面");
        mv.addObject("function", "doRedirect.do");
        //重定向
        System.out.println("***********doRedirect方法*********8");
        mv.setViewName("redirect:/hello.jsp");
        return mv;
    }

但是在hello.jsp中我们使用EL表达式无法得到数据

<h3>msg数据:${msg}</h3>
<h3>function数据:${function}</h3>

需要更改写法,从请求参数中获取parameter的值

<h2>hello.jsp页面</h2><br/>
<h3>msg数据:${param.msg}</h3>
<h3>function数据:${param.function}</h3>

注意重定向不能访问WEB-INF文件夹下的资源

4.2 异常处理
原始的异常处理中try,catch处理过于繁琐
在SpringMVC框架采用的是统一,全局的异常处理,SpringMVC把所有的异常处理都集中到一个地方,采用的是aop的思想。把业务逻辑和异常处理代码分开,解耦合。
使用到两个注解:1)@ExceptionHandler 2)@ControllerAdvice

异常处理步骤:
1.新建 maven web项目
2.加入依赖
3.新建一个自定义异常类 MyException,在定义它的子类NameException,AgeException
4.在Controller抛出NameException,AgeException
5.创建一个普通类,作用全局异常处理类
1)在类的上面加上@ControllerAdvice注解:给控制器增加异常处理功能
2)在类中定义方法,方法的上面加入@ExceptionHandler注解
6.创建处理异常的视图页面
7.创建SpringMVC的配置文件
1)组件扫描器,扫描@Controller注解
2)组件扫描器,扫描@ControllAdvice所在的包名
3)声明注解驱动

异常处理类:

package com.learn.handler;

import com.learn.exception.AgeException;
import com.learn.exception.NameException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class GlobalExceptionHand {

    /**
     * 处理异常的方法和控制器方法的定义一样,可以有ModelAndView,可以有多个参数,可以返回String  void
     */

    /**
     *
     * @param e 表示Controller抛出的异常对象
     * @return
     */
    @ExceptionHandler(value = NameException.class)
    public ModelAndView doNameException(Exception e){
        ModelAndView modelAndView = new ModelAndView();

        System.out.println("name exception");
        /**
         * 异常发生处理逻辑:
         * 1. 需要把异常记录下来,记录到数据库,日志文件,记录异常发生的时间,哪个方法发生的,异常错误内容
         * 2. 发送通知,把异常的信息通过邮件,短信,微信发送给相关的人员
         * 3. 给用户有好的提示
         */

        modelAndView.addObject("msg", "姓名必须是zs");
        modelAndView.addObject("exception", e);
        modelAndView.setViewName("nameError");

        return modelAndView;
    }

    @ExceptionHandler(value = AgeException.class)
    public ModelAndView doAgeException(Exception e){
        ModelAndView modelAndView = new ModelAndView();

        /**
         * 异常发生处理逻辑:
         * 1. 需要把异常记录下来,记录到数据库,日志文件,记录异常发生的时间,哪个方法发生的,异常错误内容
         * 2. 发送通知,把异常的信息通过邮件,短信,微信发送给相关的人员
         * 3. 给用户有好的提示
         */

        modelAndView.addObject("msg", "age不能为空,不能大于80岁");
        modelAndView.addObject("exception", e);
        modelAndView.setViewName("ageError");

        return modelAndView;
    }

    /**
     * 处理其他异常
     * @param e
     * @return
     */
    @ExceptionHandler
    public ModelAndView doOtherException(Exception e){
        ModelAndView modelAndView = new ModelAndView();

        /**
         * 异常发生处理逻辑:
         * 1. 需要把异常记录下来,记录到数据库,日志文件,记录异常发生的时间,哪个方法发生的,异常错误内容
         * 2. 发送通知,把异常的信息通过邮件,短信,微信发送给相关的人员
         * 3. 给用户有好的提示
         */

        modelAndView.addObject("msg", "出现了未知错误!");
        modelAndView.addObject("exception", e);
        modelAndView.setViewName("defaultError");

        return modelAndView;
    }

}

在jsp页面使用el表达式获取异常处理方法传递的数据

提示信息:${msg}<br/>
异常信息:${exception.message}

4.3 拦截器
1)拦截器是SpringMVC中的一种,需要实现HandlerInterceptor接口
2)拦截器和过滤器类似,功能方向侧重点不同,过滤器是用来过滤请求参数,设置编码字符集等;拦截器是拦截用户的请求,对请求做判断处理的
3)拦截器是全局的,可以对多个Controller做拦截。一个项目中可以有0个或多个拦截器,他们在一起拦截用户的请求。拦截器常用在:用户登录处理,权限检查,记录日志。

拦截器的使用步骤:
1)在自定义类实现HandlerInterceptor接口
2)在SpringMVC配置文件中,声明拦截器,让框架知道拦截器的存在

拦截器的执行时间:
1)在请求处理之前,也就是Controller类中的方法执行之前先被拦截
2)在控制器方法执行之后也会执行拦截器
3)在请求处理完成之后也会执行拦截器

拦截器步骤:
1.新建web项目
2.加入依赖
3.创建Controller类
4.创建一个普通类,作为拦截器使用
1)实现HandlerInterceptor接口
2)实现接口中的三个方法
5.创建show.jsp
6.创建SpringMVC的配置文件
1)组件扫描器,扫描@Controller注解
2)声明拦截器,并指定拦截的请求url

拦截器:

public class MyInterceptor implements HandlerInterceptor {

    /**
     *  preHandler叫做预处理方法
     *  特点:>在控制器方法之前先执行的,用户的请求首先到达此方法
     *       >在这个方法中,可以获取请求的信息,验证请求是否符合要求,可以验证用户是否登录,验证用户是否有权限访问某个链接url
     *       >如果验证失败,可以截断请求,请求不能不诶处理
     *       >如果验证成功,可以放行请求,此时控制器方法才能执行
     * @param request
     * @param response
     * @param handler 被拦截的控制器对象的引用
     * @return 一个布尔值,true/false true表示放行,false表示拦截
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截器的MyInterceptor的preHandle()");

        return false;
    }

    /**
     * postHandle 后处理方法
     * 特点:
     *      >在处理器方法之后执行
     *      >能够获取到处理器方法的返回值ModelAndView,可以修改ModelAndView的数据和视图,可以影响到最后的执行结果
     *      >主要是对数据的修正和处理
     * @param request
     * @param response
     * @param handler 被拦截的处理器对象Controller对象
     * @param modelAndView 处理器方法的返回值
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                           Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("拦截器的MyInterceptor的postHandle");
    }

    /**
     * afterCompletion:最后执行的方法,在请求完成之后执行
     * 特点:
     *      >在请求处理完成后执行的,框架中规定是当你的视图处理完成后,对视图执行了forward,就认为请求处理完成
     *      >一般做资源回收工作的,程序请求过程中创建了一些对象,在这里可以删除,把占用内存回收。
     * @param request
     * @param response
     * @param handler 被拦截的处理器对象
     * @param ex 程序中发生的异常
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        System.out.println("拦截器的MyInterceptor的afterCompletion()");

    }
}

拦截器的配置(在springmvc.xml中)

<!--    声明拦截器 可以有0个或多个-->
    <mvc:interceptors>
        <mvc:interceptor>
<!--            指定拦截的请求uri地址
                path:就是uri地址,可以使用通配符**
                **: 表示任意的字符,文件或者多级目录和目录中的文件
                在这里以/user/xxx的uri都会被MyInterceptor拦截器拦截
-->
            <mvc:mapping path="/user/**"/>
<!--            声明拦截器对象-->
            <bean class="com.learn.handler.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

preHandle是整个项目的入口,门户,当preHandle返回true,请求可以被处理,返回false,则请求到此截止

拦截器:看作是多个Controller公用的功能,集中到拦截器统一处理,使用的aop的思想


多个拦截器声明:

    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/user/**"/>
            <bean class="com.learn.handler.MyInterceptor"/>
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/user/**"/>
            <bean class="com.learn.handler.MyInterceptor2"/>
        </mvc:interceptor>
    </mvc:interceptors>

执行顺序由声明的顺序决定:
当两个拦截器的preHandel都返回true时
执行顺序是:拦截器1的preHandle->拦截器2的preHandle->控制器方法
->拦截器2的postHandle->拦截器1的postHandle->中央调度器(视图解析器)->拦截器2的afterCompletion->拦截器1的afterCompletion

当拦截器1的prHandle返回true,拦截器2的preHandle返回false
执行顺序:拦截器1的preHandle->拦截器2的preHandle->拦截器1的afterCompletion

当拦截器1的preHandle返回false,与拦截器2无关
执行顺序:拦截器1的preHandle


拦截器和过滤器的区别:

  1. 过滤器是servlet中的对象;拦截器是框架中的对象
  2. 过滤器实现Filter接口的对象;拦截器实现HandlerInterceptor
  3. 过滤器是用来设置request,response的参数,属性的,侧重对数据过滤的;拦截器是用来验证请求的,能截断请求
  4. 过滤器是在中央调度器之前执行;拦截器在中央调度器之后执行
  5. 过滤器是tomcat服务器创建的对象;拦截器是SpringMVC容器中创建的对象
  6. 过滤器只有一个执行时间;拦截器有3个执行时间点
  7. 过滤器可以处理jsp,js,html等等;拦截器侧重拦截对Controller的对象,如果你的请求不能被DispatcherServlet接收,这个请求不会执行拦截器的内容

实现一个简单登录流程,用拦截器验证用户是否登录
步骤:
其他基本步骤不再列出
1)创建一个login.jsp,模拟登录(把用户的信息放入到session)
2)创建一个logout.jsp,模拟退出系统(把用户信息从session中删除)
3)创建拦截器,从session中获取用户的登录数据,验证是否能访问系统
4)创建一个验证的jsp,如果验证失败,给出提示

login.jsp
通过设置session域模拟登录

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>
    zs登录系统
    <%
        session.setAttribute("name", "zs");
    %>
</body>
</html>

logout.jsp
删除session域

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登出</title>
</head>
<body>
    登出系统
    <%
        session.removeAttribute("name");
    %>
</body>
</html>

拦截器方法:

public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("1111111拦截器的MyInterceptor的preHandle()");

        //验证用户登录信息
        HttpSession session = request.getSession();
        Object name = session.getAttribute("name");
        String loginName = null;
        if (name != null){
            loginName = (String)name;
        }

        if (!"zs".equals(loginName)){
            //无法登录,给出提出
            request.getRequestDispatcher("/tips.jsp").forward(request, response);
            return false;
        }
        return true;
    }
}

index.jsp

<%
    String basePath = request.getScheme() + "://" + request.getServerName()
    + ":" + request.getServerPort() + request.getContextPath() + "/";
%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <base href="<%=basePath%>">
</head>
<body>
    <p>拦截器</p>
    <form method="post" action="user/some.do">
        姓名:<input type="text" name="name"><br/>
        年龄:<input type="text" name="age"><br/>
        <input type="submit" value="提交">
    </form>


</body>
</html>

这样不进入login.jsp时,在首页登录会被拦截器拦截。进入logn.jsp后,在首页登录不会被拦截,进入logout.jsp,清除session域的属性,在首页登录就会被拦截器拦截