12.spring-mvc

M:model模型,封装和映射数据
V:view视图,页面显示工作(.jsp)
C:Controller控制器:显示某个网站的跳转逻辑

1.web.xml文件的写法:按照上图可以方面理解前端控制器
    <!--
      springmvc思想中有个前端控制器拦截所有请求,并智能派发
      这个前端控制器其实是个servlet:DispatcherServlet,应该在web.xml中配置这个servlet拦截所有请求
    -->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!--contextConfigLocation:指定spring配置文件路径-->
            <param-name>contextConfigLocation</param-name>
            <!--
              servlet启动加载,servlet原本是第一次访问时创建对象
              load-on-startup:服务器启动时创建对象,并且值越小,创建的优先级越高
            -->
            <param-value>classpath:ioc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!--
          /*和/都是拦截所有请求
          /:会拦截所有请求,但是不会拦截jsp页面,能保证jsp页面正常访问
          /*:范围更大,还会拦截jsp页面,jsp页面拦截后就不会再显示了
        -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

2.spring的配置文件:
    <context:component-scan base-package="cn.com"></context:component-scan>
    <!--配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/WEB-INF/page/"></property>
        <!--后缀-->
        <property name="suffix" value=".jsp"></property>
    </bean>
    配置视图解析器的作用是在controller返回时是:前缀+返回字符串+后缀
3.登录页面:
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>登录</title>
    </head>
    <body>
        <a href="/hello">登录</a>
    </body>
    </html>

4.对应的controller代码:
    @Controller
    public class MyFirstController {
        @RequestMapping("/hello")
        public String myfirstCon() {
            System.out.println("hello请求传到后台了,正在处理!");
            return "success";
        }
    }
    这里用到了视图解析器:
        返回的是:前缀+success+后缀
        即:/WEB-INF/page/success.jsp
        
5.对应的success.jsp代码:
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>成功</title>
    </head>
    <body>
        <div>成功!div</div>
        成功
    </body>
    </html>

1.当浏览器输入地址:http://localhost:8080/spring_mvc/login.jsp,点击登录后

2.发现其跳转到success.jsp页面

结论:发现其地址栏没变:是http://localhost:8080/spring_mvc/hello,但是展示的却是success.jsp,其过程发生了转发 
1.转发:
    1.浏览器地址栏没有发生变化
    2.他们是一次请求
    3.他们共享Request域中的对象
    4.可以转发到WEB-INF目录下(解释:WEB-INF目录是对外不开放的,不能直接访问:即http://localhost:8080/spring_mvc/WEB-INF/page/success.jsp,不能直接访问)
    5.不可以访问工程外的资源
2.重定向:
    1.浏览器地址栏会发生变化
    2.两次请求
    3.两个不能共享Request域中的对象
    4.不能访问WEB-INF下的资源
    5.可以访问工程外的资源
页面显示成功的细节:l
1.运行流程:
    1.1:当客户端点击页面上的登录按钮时,会发送请求:http://localhost:8080/spring_mvc/hello
    1.2:来到tomcat服务器
    1.3:spring前端控制器收到所有请求
    1.4:来看请求地址和RequestMapping标注的哪个匹配,来找到到底使用哪个类的哪个方法
    1.5:前端控制器找到目标处理类和目标方法,直接利用反射执行方法
    1.6:放啊执行完成后会有一个返回值;springmvc认为这个放回值就是要去的页面地址
    1.7:拿到方法返回值后,用视图解析器进行字符串拼接得到完整的页面地址
    1.8:拿到页面地址,前端控制器帮我们转发到页面;

2.@RequestMapping("hello")
    2.1就是告诉springmvc:这个方法处理什么请求
    2.2这个/可以省略,即使省略了,也是默认从当前项目开始的,不过还是加上比较好
    
3.配置springmvc前端控制器时,也可以不指定spring的配置文件位置:
    web.xml中:
        1.通常做法:配置springmvc的前端控制器
            <servlet>
                <servlet-name>dispatcherServlet</servlet-name>
                <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
                <init-param>
                    <param-name>contextConfigLocation</param-name>
                    <param-value>classpath:ioc.xml</param-value>----------------------------------->指定了spring的配置文件位置:因为spring启动时必须有对应的spring配置文件
                </init-param>
                <load-on-startup>1</load-on-startup>
            </servlet>
            <servlet-mapping>
                <servlet-name>dispatcherServlet</servlet-name>
                <url-pattern>/</url-pattern>
            </servlet-mapping>
        2.也可以不指定配置文件位置:
                  <servlet>
                <servlet-name>dispatcherServlet</servlet-name>
                <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>----------------->不指定spring排位置文件位置,此时启动肯定会报错!
                <load-on-startup>1</load-on-startup>
            </servlet>
            <servlet-mapping>
                <servlet-name>dispatcherServlet</servlet-name>
                <url-pattern>/</url-pattern>
            </servlet-mapping>

 

报错如下:
nested exception is java.io.FileNotFoundException: Could not open ServletContext resource [/WEB-INF/dispatcherServlet-servlet.xml]
发现如果不指定spring配置文件,会默认去/WEB-INF/目录下找xxx-servlet.xml
这个xxx是在web.xml中配置的前端控制器的<servlet-name>dispatcherServlet</servlet-name>的值

/:拦截所有请求,不拦截jsp页面,*.jsp页面
/*:拦截所有请求,拦截jsp页面,*.jsp页面
处理*.jsp是tomcat做的事:所有项目的小web.xml都是继承于大的web.xml

DefaultServlet是Tomcat中处理静态资源的?
    除过jsp和servlet外剩下的都是静态资源
    index.html:静态资源,tomcat都会在服务器下找到这个资源并返回
    我们的前端控制器的/尽用了tomcat服务器中的DefaultServlet


1.服务器的大web.xml中有一个DefaultServlet是url-pattern=/
2.我们的配置中前端控制器url-pattern=/
    静态资源会来到DispatcherServlet(前端控制器)看哪个方法的RequestMapping是这个index.html,没有,故无法访问到静态资源
3.为什么jsp又能访问,因为我们没有覆盖服务器中的JspServlet中的配置
4./* 直接就是拦截所有请求;我们写/;也是为了迎合后面Rest风格的URL路径

 

RequestMapping
1.写在类上:为该类上的所有方法定一个基准:
    示例:
        @Controller
        @RequestMapping("/myFirstController")
        public class MyFirstController {
            @RequestMapping("/hello")
            public String myfirstCon() {
                System.out.println("hello请求传到后台了,正在处理!");
                return "success";
            }
        }
    该类的所有访问方法都是:/myFirstController+对应的方法名, @RequestMapping("/hello")的/可以省略,因为这里并不是简单的进行字符串拼接
    
2.RequestMapping的几个属性:
    2.1value:路径
    2.2method:处理对应的请求方式:默认是全部接收,是个数组
            HTTP协议中所有的请求方式:
                GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE;
            @RequestMapping(value = "/hello",method = {RequestMethod.GET,RequestMethod.POST})
            只接收处理get和post请求
    2.3params:规定请求参数
            2.3.1:params :params = {"username"}必须带上固定的请求参数,要不找不到对应处理报404
                @RequestMapping(value = "/hello",params = {"username"})
                也可以带上具体值:
               @RequestMapping(value = "/hello",params = {"username=123"})
            2.3.2:!params :params = {"!username"},必须不带固定请求参数,要不找不到对应处理报404
                @RequestMapping(value = "/hello",params = {"!username"})
                也可以写具体值:
                 @RequestMapping(value = "/hello",params = {"username!=123"})
                 此时不带username:http://localhost:8080/spring_mvc/myFirstController/hello可以正常访问
                 或:http://localhost:8080/spring_mvc/myFirstController/hello?username=456带的值不是123也可以正常访问!
             2.3.3:多条规则:
                 @RequestMapping(value = "/hello",params = {"username!=123","pwd","!age"})
                 必须同时满足上述的多条规则:username不等于123,且有pwd的参数,且没有age参数
    2.4headers:固定请求头,可以写简单的表达式
        例如简单需求:控制只能谷歌浏览器访问,请他浏览器访问不了,使用User-Agent进行控制,因为不同浏览器的User-Agent是不同的
        火狐的User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
        用法如下:
            @RequestMapping(value = "/hello",headers = {"User-Agent=Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36"})
            因为header是个数组,故也可以传入多个浏览器的User-Agent来进行控制
    2.5consumes:只接受内容类型是哪种的请求,规定请求头中的Context-Type
    2.6produces:告诉浏览器返回的内容类型是什么,给响应头头中加上Context-type:text/html;charset=UTF-8
RequestMapping的模糊匹配
1. ?:能替代任意一个字符
         @RequestMapping(value = "/hello?")匹配一个字符:/hello一个字符都可以访问
         模糊和精确多个匹配情况下,精确优先
         比如:
             @RequestMapping(value = "/hello?")
             public string test1(){}
             @RequestMapping(value = "/hello1")
             public string test2(){}
         当输入:/hello1时这两个都可以匹配,用的时精确匹配的方法,因为精确匹配优先
         
         
 2. *:能替代任意多个字符和一层路径
           @RequestMapping(value = "/hello*")
           匹配到多个字符,比如/hello多个字符都可以访问
           @RequestMapping(value = "/aa/*/hello")
             
 3. **:能替代多层路径
      @RequestMapping(value = "/aa/**/hello")
@PathVariable获取URL绑定的占位符
路径上可以有占位符:占位符的语法就是可以在任意路径的地方写一个{变量名}
/user/admin      /user/wmd
路径上的占位符只能占用一层路径
    具体示例:使用@PathVariable注解获取路径上占位符的值
      @RequestMapping(value = "/hello/{path}")
        public String myfirstCon(@PathVariable("path") String path) {
            System.out.println("path:"+path);
            return "success";
        }
    若用多层的,可以这么写:
        @RequestMapping(value = "/{id}/{path}")
        public String myfirstCon(@PathVariable("path") String path,@PathVariable("id")String id) {
            System.out.println("path:"+path+"id:"+id);
            return "success";
        }
若有两个,一个是精确路径,一个是占位符
    @Controller
    @RequestMapping("/myFirstController")
    public class MyFirstController {
        @RequestMapping(value = "/hello/{path}")
        public String myfirstCon(@PathVariable("path") String path) {
            System.out.println("path:"+path);
            return "success";
        }
        @RequestMapping(value = "/hello/aa")
        public String myfirstCon1() {
            System.out.println("精确匹配");
            return "success";
        }
    }
此时输入;http://localhost:8080/spring_mvc/myFirstController/hello/aa
会走精确匹配的方法,因为精确匹配优先!
Rest请求
系统希望以非常简介的url地址来发送请求;
怎样表示对对一个资源的增删改查呢:以请求的方式来区分
那rest风格是什么样子的呢:
    之前我们若要做增删改查URL如下:
        /getBook?id=1                 :查询1号图书
        /deleteBook?id=1              :删除1号图书
        /updateBook?id=1              :更新1号图书
        /addBook                      :添加图书

而rest风格:
    rest推荐url地址这么起名:/资源名/资源标识符
        /book/1         :get请求--查询1号图书
        /book/1         :put请求--更新1号图书
        /book/1         :delete请求--删除1号图书
        /book           :post请求---添加图书
系统的URL地址就是这么设计即可,简洁的URL提交请求,以请求的方式区分对资源的操作
问题:
    从页面上只能发送get/post请求,其他方式无法发送,那应该如何应用呢
springmvc如何获取请求带来的参数
springmvc获取请求带来的更重信息
    1.默认方式获取请求参数:
        直接给方法入参上写一个和请求参数名相同的变量,这个参数就来接收请求参数的值
        例子如下:
            @Controller
            public class MyFirstController {
                @RequestMapping("/hello")
                public String hello(String username){
                    System.out.println("username:"+username);
                    return "success";
                }
            }
        当路径输入:http://localhost:8080/spring_mvc/hello?username=wmd,输出username:wmd,可以获取到传来的值
        但当输入:http://localhost:8080/spring_mvc/hello,输出username:null
        结论:带-->有值    没带--->null
    
    2.@RequestParam:获取请求参数,参数默认是必须带的:
            value :指定要获取的参数的key
            required:指定这个参数是否必须要带,布尔值
            defaultValue:默认值。没带就是null
            示例如下:
                @RequestMapping("/hello")
                public String hello(@RequestParam(value = "name") String username){
                    System.out.println("username:"+username);
                    return "success";
                }
            请求路径是:http://localhost:8080/spring_mvc/hello?name=wmd
            输出:username=mwd
            若没有该请求参数:http://localhost:8080/spring_mvc/hello
            会直接报错:HTTP Status 400 - Required String parameter 'name' is not present
            
            2.2也可以设置该参数不是必须(默认是必须)
            2.3也可以设置没带的默值:
                public String hello(@RequestParam(value = "name",required = false,defaultValue = "你没带") String username){}
            
                请求地址是:http://localhost:8080/spring_mvc/hello,也不会报错,输出:username=你没带
           
            注意点:
                @RequestParam和@PathVariable的区别
                如下实例
                    @RequestMapping("/hello/{name}")
                    public String hello(@RequestParam(value = "name") String username,@PathVariable("name")String name){
                        System.out.println("username:"+username);
                        return "success";
                    } 
             @RequestParam获取的是请求参数name的值,@PathVariable获取的是请求路径name的值,两者互不影响!  
         
     3.@RequestHeader获取请求头的信息
             例如:获取浏览器请求头中User-Agent的值,不同浏览器的User-Agent值不同
                 @Controller
                public class MyFirstController {
                    @RequestMapping("/hello")
                    public String hello(@RequestHeader("User-Agent") String userAgent){
                        //原生的servlet获取请求头做法;request.getHeader("User-Agent")
                        System.out.println("User-Agent:"+userAgent);
                        return "success";
                    }
                }
            若获取的是请求头中没有的信息: public String hello(@RequestHeader("wmd") String wmd){}因为请求头中没有该wmd信息,所以请求会报错和@RequestParam一样
            @RequestHeader也有三个相同属性:用法和@RequestParam一致
                value :指定要获取的参数的key
                required:指定这个参数是否必须要带,布尔值
                defaultValue:默认值。没带就是null
                
    4.@CookieValue获取cookie中的某个值
            4.1原生的获取方法:
                cookies[] cks=request.getCookies();
                for(Cookie ck:cks){
                    if(ck.getName().equals("JSESSIONID")){
                        String cv=ck.getValue();
                    }
                }
            4.2现在的做法
                @RequestMapping("/hello")
                public String hello(@CookieValue("JSESSIONID")String jession){
                    System.out.println("JSESSIONID:"+jession);
                    return "success";
                }
            若获取cookie没有的值:@CookieValue("wmd")String wmd  cookie中没有这个key,所以会报错,@CookieValue也有三个对应的属性,用法和上述一致
                 value :指定要获取的参数的key
                 required:指定这个参数是否必须要带,布尔值
                 defaultValue:默认值。没带就是null
可以使用pojo(对象)来接收请求参数:
代码如下:如果页面传来的参数较多
    实体类代码:
        public class Person {
            private String name;
            private Integer age;
            private Book book;
            ...
        }
        public class Book {
            private String bookName;
            private Double price;
            ...
        }
    页面代码:注意是name的值必须和实体类的属性值一致,且此处可以使用级联属性,即对象中如果包含对象,
        <form action="hello">
            姓名:<input name="name"></br>
            年龄:<input name="age"></br>
            //注意:必须使用book.bookName,级联属性来获取,若直接写成:bookName,会获取不到对应的参数值
            书名:<input name="book.bookName"></br>
            价格:<input name="book.price"></br>
            <input type="submit" value="提交">
        </form>
    处理器代码:
        @RequestMapping("/hello")
        //在此处传入对象即可
        public String hello(Person person){
            System.out.println("person:"+person);
            return "success";
        }

 springmvc可以直接在参数上写原生的API:

例子:
        @RequestMapping("/hello")---->直接在方法上引入对应的原生对象即可
        public String hello(HttpSession httpSession, 
                            HttpServletRequest request, 
                            HttpServletResponse response){
            可以利用原生对象做原生的操作!
            return "success";
        }
注意;并不是支持所有的原生对象,支持以下对象:
    1.HttpServletRequest 
    2.HttpServletResponse 
    3.HttpSession 
    4.Principal
    5.locale:国际化的区域信息对象
    6.InputStream
        ServletInputStream inputStream = request.getInputStream();
    7.OutPutStream
        ServletOutputStream outputStream = response.getOutputStream();
    8.Reader
        BufferedReader reader = request.getReader();
    9.Writer
        PrintWriter writer = response.getWriter();
这里有个小问题是,idea创建的spring web maven项目,可能不能直接在类中使用原生对象,这是因为没有导入tomcat的servlet-api.jar,需要手动导下!
后台乱码的解决办法:

 

输出:
发现后台乱码:
    1.请求乱码:即上述的java后台乱码
        1.1GET请求:改tomcat的server.xml,位置:配置tomcat端口8080处,增加:URIEncoding="UTF-8"
        1.2post请求:
            在第一次请求参数前设置:
                request.setCharacterEncoding("UTF-8")
                
     2.响应乱码:
         response.setContextType("text/html;charset=utf-8")
         
         
    3.最好的解决办法:使用spring的filter
        在web.xml中配置:
            <!--配置spring过滤器解决请求乱码问题-->
            <filter>
                <filter-name>characterEncodingFilter</filter-name>
                <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
                <!--encoding:指定请求POST请求乱码-->
                <init-param>
                    <param-name>encoding</param-name>
                    <param-value>UTF-8</param-value>
                </init-param>
                <!--
                    forceEncoding:顺手解决响应乱码问题,response.setContextType("text/html;charset=utf-8")
                    值为布尔值:默认是false,当是true时,使用和上面配置的encoding一样的编码!
                -->
                <init-param>
                    <param-name>forceEncoding</param-name>
                    <param-value>true</param-value>
                </init-param>
            </filter>
            <filter-mapping>
                <filter-name>characterEncodingFilter</filter-name>
                <url-pattern>/*</url-pattern>
            </filter-mapping>
    乱码问题得以解决:
        person:Person{name='吴孟达', age=18, book=Book{bookName='java开发', price=12.0}}
得出结论:
spring-web的开发步骤:
开始时,不管什么,先在web.xml
1.配置spring的前端控制器,并设置spring容器在tomat启动时就创建
        <!--
          springmvc思想中有个前端控制器拦截所有请求,并只能派发
          这个前端控制器其实是个servlet:DispatcherServlet,应该在web.xml中配置这个servlet拦截所有请求
        -->
        <servlet>
            <servlet-name>dispatcherServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>dispatcherServlet</servlet-name>
            <!--
              /*和/都是拦截所有请求
              /:会拦截所有请求,但是不会拦截jsp页面,能保证jsp页面正常访问
              /*:范围更大,还会拦截jsp页面,jsp页面拦截后就不会再显示了
            -->
            <url-pattern>/</url-pattern>
        </servlet-mapping>
2.配置spring的filter拦截器:解决请求的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>
            <!--
                forceEncoding:顺手解决响应乱码问题,response.setContextType("text/html;charset=utf-8")
                值为布尔值:默认是false,当是true时,使用和上面配置的encoding一样的编码!
            -->
            <init-param>
                <param-name>forceEncoding</param-name>
                <param-value>true</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>CharacterEncodingFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>

3.解决get请求的乱码问题:
    在tomcat的servlet.xml,8080端口处,加上:URIEncoding="UTF-8" 
注意:
  字符编码的filter一般都在其他的filter之前,因为是按照顺序执行的,若放在最后,之前的其他filter可能已经获取到编码,而后字符编码的filter设置就已经失效了  

posted @ 2022-05-08 21:38  努力的达子  阅读(86)  评论(0编辑  收藏  举报