SpringMVC

SpringMVC

一、简介

1.1 引言

java开源框架,Spring Framework的一个独立模块。

MVC框架,在项目中开辟MVC层次架构

对控制器中的功能包装简化扩展践行工厂模式,功能架构在工厂之上

1.2 MVC架构

1.2.1 概念

名称 职责
Model 模型:即业务模型,负责完成业务中的数据通信处理,对应项目中的service和dao
View 视图:渲染数据,生成页面。对应项目中的Jsp
Controller 控制器:直接对接请求,控制MVC流程,调度模型,选择视图。对应项目中的Servlet

1.2.2 好处

  • MVC是现下软件开发中的最流行的代码结构形态;
  • 人们根据负责的不同逻辑,将项目中的代码分成MVC3个层次;
  • 层次内部职责单一,层次之间藕合度低;
  • 符合低精合高内聚的设计理念。也实际有利于项目的长期维护。

二、开发流程

2.1 导入依赖

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

2.2 配置核心(前端)控制器:DispatcherServlet

作为一个MVC框架,首先要解决的是:如何能够收到请求!

所以MVC框架大都会设计一款前端控制器,选型在Servlet或Filter两者之一,在框架最前沿率先工作,接收所有请求。

此控制器在接收到请求后,还会负责springMVC的核心的调度管理,所以既是前端又是核心。

web.xml

<!--SpringMVC前端(核心)控制器
        1.前端,按收所有请求
        2.启动SpringMVC工厂mvc.xml
        3.springMVC流程调度
    -->
<!-- SpringMVC前端控制器 -->
<servlet>
    <servlet-name>mvc_shine</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:mvc.xml</param-value>
    </init-param>
    <!--懒汉式 饿汉式加载 -->
    <load-on-startup>1</load-on-startup>
</servlet>

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

2.3 后端控制器

等价于之前定义的Servlet

@Controller     //声明控制器
@RequestMapping("/hello")	//访问路径
public class HelloController {

    @RequestMapping("/test1")	//访问路径
    public String hello1() { //service doGet doPost
        System.out.println("hello1");
        return "hello";     //跳转:hello.jsp
    }
    @RequestMapping("/test2")	//访问路径
    public String hello2() { //service doGet doPost
        System.out.println("hello2");
        return null;
    }
}

2.4 配置文件

默认名称:核心控制器名-servet.xml默认位置:WEB-INF

随意名称:mvc.xml随意位置:resources但需要配置在核心控制器中

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

    <!-- 注解扫描 告诉SpringMVC 哪些包中含有注解 -->
    <context:component-scan base-package="com.qf.web"/>

    <!-- 注解驱动 -->
    <mvc:annotation-driven></mvc:annotation-driven>

    <!-- 视图解析器
        作用:1.辅获后端控制器的返回值="hello"
             2.解析:在返回值的前后拼接==>"/hello.jsp"
    -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/"/>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

三、接受请求参数

3.1 基本类型参数

请求参数和方法的形参 同名即可

SpringMVC默认可以识别的日期字符串格式为:YYYY/MM/dd HH:mm:ss

通过@DateTimeFormat可以修改默认日志格式

// http://xxxx/param/test1?id=1&name=shine&gender=true&birth=2020-12-12 12:13:20
@RequestMapping("/test1")
public String test1(Integer id, 
                    String name, 
                    Boolean gender,
                    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date birth) {
    System.out.println("test1");
    System.out.println("id:" + id + "   name:" + name + "  gender:" + gender + "  birthday:" + birth);
    return "hello";
}

3.2 实体收参(重点)

请求参数和实体的属性 同名即可

//实体类
public class User {
    private Integer id;
    private String name;
    private Boolean gender;
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date birth;
}

// http://xxxx/param/test2?id=1&name=shine&gender=true&birth=2020-12-12 12:13:20
@RequestMapping("/test2")
public String test2(User user) {
    System.out.println("test2");
    System.out.println(user);
    return "hello";
}

3.3 数组收参

简单类型的 数组

<form action="${pageContext.request.contextPath}/param/test3">
    <input type="checkbox" name="hobby" value="football">足球
    <input type="checkbox" name="hobby" value="basketball">篮球
    <input type="checkbox" name="hobby" value="volleyball">排球
    <input type="submit" value="提交">
</form>
@RequestMapping("/test3")
public String test3(String[] hobby) {
    System.out.println("test3");
    System.out.println(hobby);
    return "hello";
}

3.4 集合收参

//实体类
public class UserList {
    private List<User> users;
}

//http://xxx/param/test4?users[0].id=1&users[0].name=shine&...
@RequestMapping("/test4")
public String test4(UserList userList) {
    System.out.println("test4");
    for (User u :
         userList.getUsers()) {
        System.out.println(u);
    }
    return "hello";
}

3.5 路径参数

// {id} 命名规则
// {id} 等价于 /test5/1 test5/2  test5/xxx
@RequestMapping("/test5/{id}")
public String test5(@PathVariable("id") Integer id) {
    System.out.println("test5");
    System.out.println("id:" + id);
    return "hello";
}

3.6 中文乱码

tomcat中字符集设置,对get请求中,中文参数乱码有效

Tomcat配置:URIEncoding=utf-8

web.xml中加入编码过滤器,对post请求中,中文参数乱码有效

<!-- 编码过滤器 -->
<filter>
    <filter-name>encoding</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>encoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

四、跳转

4.1 转发

@RequestMapping("/test1")
public String test1() {
    System.out.println("test1");
    //        return "hello";     //转发
    return "forward:/hello.jsp";
}

@RequestMapping("/test2")
public String test2() {
    System.out.println("test2");
    //        return "forward:/jump/test1";       //转发
    return "forward:test1";       //相对路径转发

}

4.2 重定向

@RequestMapping("/test3")
public String test3() {
    System.out.println("test3");
    return "redirect:/hello.jsp";       //重定向
}

@RequestMapping("/test4")
public String test4() {
    System.out.println("test4");
    //        return "redirect:test3";       //重定向
    return "redirect:/jump/test3";       //重定向
}

4.3 跳转细节

在增删改之后,为了防止请求重复提交,重定向跳转

在查询之后,可以做转发跳转

五、传值

c得到数据后,跳转到V,并向V传递数据。进而V中可以渲染数据,让用户看到含有数据的页面

转发跳转:Request作用域

重定向跳转:Session作用域

5.1 Request和Session

//在形参中可以获得Request和Session
@RequestMapping("/test1")
public String test1(HttpServletRequest request, HttpSession session) {
    System.out.println("test1");
    request.setAttribute("name", "张三");
    session.setAttribute("age", 10);
    return "data";
}

5.2 JSP中取值

name:${requestScope.name}<br>
age:${sessionScope.age}<br>

5.3 Model

//model中的数据,会在V渲染之前,将数据复制一份给request
@RequestMapping("/test2")
public String test2(Model model) {
    model.addAttribute("city", "北京");
    model.addAttribute("street", "长安街");
    return "data2";
}

//JSP 通过EL表达式取值
city:${sessionScope.city}<br>
street:${sessionScope.street}

5.4 ModelAndView

//ModelAndView 可以集中管理跳转和数据
@RequestMapping("/test4")
public ModelAndView test4() {
    //新建ModelAndView对象
    ModelAndView modelAndView = new ModelAndView();
    //设置视图名即如何跳转
    modelAndView.setViewName("forward:/hello.jsp");
    //增加数据
    modelAndView.addObject("claz", "001");
    return modelAndView;
}

//JSP 通过EL表达式取值
${sessionScope.claz}

5.5 @SessionAttributes

  • @SessionAttributes(name={"city","street}) Model中的name和gender会存入session中

  • SessionStatus 移除Session

@Controller
@RequestMapping("/data")
@SessionAttributes(names = {"city", "street"})
public class DataController {

    @RequestMapping("/test2")
    public String test2(Model model) {
        model.addAttribute("city", "北京");
        model.addAttribute("street", "长安街");
        return "data2";
    }

    @RequestMapping("/test3")
    public String test3(SessionStatus status) {
        //清空所有通过model存入的session
        status.setComplete();
        return "data2";
    }
}

六、静态资源

6.1 静态资源问题

静态资源:html,js文件,css文件,图片文件

  • 静态文件没有url-pattern,所以默认是访问不到的

  • 之所以可以访问,是因为tomcat中有一个全局的servlet:
    org.apache.catalina.servlets.DefaultServlet,它的url-pattern是"/"

  • 它是全局默认的Servlet.所以每个项目中不用匹配的静态资源的请求,由这个Servlet来处理即可。

  • 但在SpringMVC中DispatcherServlet也采用了“/"作为url-pattern,所以项目中不会再使用全局的Serlvet,则静态资源不能完成访问,

6.2 解决方案1

DispathcerServlet采用其他的url-pattern

此时,所有访问handler的路径都要以action结尾!!

web.xml文件中:

<servlet>
    <servlet-name>mvc_shine</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>mvc_shine</servlet-name>
    <url-pattern>*.action</url-pattern>
</servlet-mapping>

6.3 解决方案2

DispathcerServlet的url-pattern依然采用"/",但追加配置

mvc.xml文件中:

<!--
   额外的增加一个handler,且其requestMapping:  "/**" 可以匹配所有请求,但是优先级最低
  所以如果其他所有的handler都匹配不上,请求会转向 "/**" ,恰好,这个handler就是处理静态资源的
  处理方式:将请求转会到tomcat中名为default的Servlet

  RequestMapping  /*   /a   /b  /c   /dxxxx    /a/b
                  /**
 -->
<mvc:default-servlet-handler/>

解决方案3

mapping是访问路径,location是静态资源存放的路径

将/html/** 中 /**匹配到的内容,拼接到/hhh/ 后 http://.../html/a.html 访问 /hhh/a.html

mvc.xml文件中

<mvc:resources mapping="/html/**" location="/hhh/"/>

七、Json处理

7.1 导入依赖

<!-- SpringMVC默认的Json解决方案是Jackson,所以只需要导入jackson的jar,即可使用。-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.0</version>
</dependency>

7.2 发送Json数据

7.2.1 使用@ResponseBody

@Controller
@RequestMapping("/json")
public class JsonController {

    @RequestMapping("/test1")
    @ResponseBody	//handler的返回值,转换成json,并将json响应给客户端。
    public User test1() {
        System.out.println("test1");
        User user = new User(1, "张三",new Date());
        return user;
    }

    @RequestMapping("/test2")
    //@ResponseBody还可以用在handler的返回值上
    public @ResponseBody List<User> test2() {
        System.out.println("test2");
        User user = new User(1, "张三");
        User user1 = new User(1, "李四");
        List<User> users = Arrays.asList(user, user1);
        return users;
	}

    //如果返回值已经是字符串,则不需要转json,直接将字符串响应给客户端
    @RequestMapping(value="/test3",produces ="text/html;charset=utf-8")//produces防止中文乱码
    @ResponseBody
    public String test3() {
        System.out.println("test3");
        //        return "ok";
        return "你好";
    }
}

7.2.2 使用@RestController

Controller类上加了@RestController注解,等价于在类中的每个方法上都加了@ResponseBody

7.3 接收Json数据

7.3.1 使用@RequestBody

  • 接收数据

    public class User {
        private Integer id;
        private String username;
        private String password;
        private Boolean gender;
    }
    
    @RequestMapping("/test4")
    public String test4(@RequestBody User user){//@RequestBody将请求体中的json数据转换为java对象
        System.out.println(user);
        return "ok";
    }
    
  • Ajax发送数据

    function send_json() {
        var user = {id: 1, name: "shine"};
        var json = JSON.stringify(user);
        $.ajax({
            url: "${pageContext.request.contextPath}/json/test4",
            type: "post",
            data: json,
            contentType: "application/json",
            success: function (ret) {
                alert(ret);
            }
        })
    }
    

7.4 Jackson常用注解

7.4.1 日期格式化

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")

public class User {
    private Integer id;
    private String name;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date birth;
}

7.4.2 属性名修改

//不再使用原属性名 而是“new_id”
@JsonProperty("new_id")
private Integer id;

7.4.3 属性忽略

@JsonIgnore

//生成Json时,忽略此属性
@JsonIgnore
private String name;

7.4.4 null和empty属性排除

Jackson默认会输出null值的属性,如果不需要,可以排除。

@JsonInclude(JsonInclude.Include.NON_NULL) 属性不输出null值

@JsonInclude(JsonInclude.Include.NON_EMPTY) 不输出empty属性(空串,长度为0的集合,null值)

7.4.5 自动化序列

@JsonSerialize(using = MySerializer.class)
private Double salary = 10000.126;      //在输出此属性时,使用MySerializer输出
public class MySerializer extends JsonSerializer<Double> {

    @Override
    public void serialize(Double aDouble, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        //将Double salary 的值 四舍五入
        String number = BigDecimal.valueOf(aDouble).setScale(2, BigDecimal.ROUND_HALF_UP).toString();
        //输出 四舍五入之后的值
        jsonGenerator.writeNumber(number);

    }
}

7.5 FastJson

7.5.1 导入依赖

<!-- FastJson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.54</version>
</dependency>

7.5.2 安装FastJson

web.xml中安装FastJson

<!-- 注解驱动 -->
<mvc:annotation-driven>
    <!-- 安装FastJson转换器 -->
    <mvc:message-converters>
        <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
            <!-- 声明转换类型 -->
            <property name="supportedMediaTypes">
                <list>
                    <value>application/json</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

7.5.3 使用FastJson

@ResponseBody @RequestBody @RestController 使用方法不变

7.5.4 常用注解

  • 日期格式化:@JSONField(format="yyyy/MM/dd")
  • 属性名修改:@JSONField(name="birth")
  • 忽略属性:@JSONField(serialize=false)
  • 包含null值:@JSONField(serialzeFeatures=SerializerFeature.WriteMapNullValue)默认会忽略所有null值,有此注解会输出null
    • @JSONField(serialzeFeatures=SerializerFeature.WriteNullStringAsEmpty)null的String输出为""
  • 自定义序列化:@JSONField(serializeUsing=MySerializer2.class)
public class User2 {
    @JSONField(serialize = false)
    private Integer id;
    @JSONField(name = "NAME", serialzeFeatures = SerializerFeature.WriteNullStringAsEmpty)
    private String name;// ""
    @JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue)
    private String city;// null
    @JSONField(format = "yyyy/MM/dd")
    private Date birth;
    @JSONField(serializeUsing = MySerializer2.class)
    private Double salary; // 元
}
public class MySerializer2 implements ObjectSerializer {

    @Override
    public void write(JSONSerializer jsonSerializer, Object o, Object o1, Type type, int i) throws IOException {
        Double value = (Double) o;
        String text = value + "元";      //拼接“元”
        jsonSerializer.write(text);

    }
}

八、异常解析器

8.1 现有方案,分散处理

Controller中的每个Handler自己处理异常

此种处理方案,异常处理逻辑,分散在各个handler中,不利于集中管理

8.2 异常解析器,统一处理

Controller中的每个Handler不再自己处理异常,而是直接throws所有异常。

定义一个“异常解析器”集中捕获处理所有异常

此种方案,在集中管理异常方面,更有优势!

// 异常解析器
//任何一个Handler中抛出异常
public class MyExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        ModelAndView modelAndView = new ModelAndView();
        if (e instanceof MyException1) {
            //errorl.jsp
            modelAndView.setViewName("redirect:/error1.jsp");
        } else if (e instanceof MyException2) {
            //error2.jsp
            modelAndView.setViewName("redirect:/error2.jsp");
        } else if (e instanceof MyException3) {
            //error3.jsp
            modelAndView.setViewName("redirect:/error3.jsp");
        } else if (e instanceof MaxUploadSizeExceededException){
            modelAndView.setViewName("redirect:/uploadError.jsp");
        }
            return modelAndView;
    }
}

mvc.xml中声明异常解析器

<!-- 异常解析器 -->
<bean class="异常解析器类名"/>

九、拦截器

9.1 作用

作用:抽取handler中的冗余功能

9.2 定义拦截器

执行顺序:preHandle-->postHandle-->afterCompletion

public class MyInterceptor implements HandlerInterceptor {

    //在handler之前执行
    //再次定义 handler中冗余的功能
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断登录状态
        HttpSession session = request.getSession();
        if (session.getAttribute("state") != null) {
            return true;    //放行 执行后续的handler
        }
        //中断之前响应请求
        response.sendRedirect("/login.jsp");
        return false;   //中断请求,不再执行后续的handler

    }

    //在handler之后执行 响应之前执行
    // 改动请求中的数据
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("post Handle");
    }

    //在视图渲染完毕后
    //资源回收
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("after completion");
    }
}

9.3 配置拦截路径

mvc.xml中添加:

<!-- 拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!--            <mvc:mapping path="/inter/test1"/>-->
        <!--            <mvc:mapping path="/inter/test2"/>-->
        <mvc:mapping path="/inter/*"/>
        <mvc:exclude-mapping path="/inter/login"/>
        <bean class="拦截器类名"/>
    </mvc:interceptor>
</mvc:interceptors>

十、上传

10.1 导入依赖

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
    <exclusions>
        <exclusion>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>

10.2 上传表单

<form action="${pageContext.request.contextPath}/upload/test1" method="post" enctype="multipart/form-data">
    file: <input type="file" name="source"><br>
    <input type="submit" value="上传">
</form>

10.3 上传解析器

<!-- 
上传解析器 id必须是"multipartResolver"
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 最大可上传的文件大小 单位byte 超出后会抛出异常 -->
    <property name="maxUploadSize" value="1048576"/>
</bean>

10.4 Handler

@Controller
@RequestMapping("/upload")
public class UploadController {

    @RequestMapping("/test1")
    public String test1(MultipartFile source, HttpSession session) throws IOException {
        System.out.println("test1");
        //获取上传文件的名称
        String filename = source.getOriginalFilename();
        //获取上传文件的类型
        String contentType = source.getContentType();

        //生成一个唯一的文件名
        String uniqueFilename = UUID.randomUUID().toString();
        //获取文件后缀
        String extension = FilenameUtils.getExtension(filename);
        //拼接唯一文件名
        String uniqueFilename2 = uniqueFilename + "." + extension;

        System.out.println(filename);
        System.out.println(contentType);

        //保存文件
//        source.transferTo(new File("d:/abd.img"));
        String realPath = session.getServletContext().getRealPath("/upload");
        System.out.println("realpath" + realPath);
        source.transferTo(new File(realPath + "\\" + uniqueFilename2));
        return "index";
    }
}

十一、下载

11.1 超链

<a href="${pageContext.request.contextPath}/download/test1?name=c93d8b03ecf53.jpg">下载</a>

11.2 Handler

@Controller
@RequestMapping("/download")
public class DownloadController {

    @RequestMapping("/test1")
    public void test1(String name, HttpSession session, HttpServletResponse response) throws IOException {
        String realPath = session.getServletContext().getRealPath("/upload");
        String filePath = realPath + "\\" + name;

        //设置响应头告知浏览器,要以附件的形式保存内容 filename=浏览器显示的下载文件名
        response.setHeader("content-disposition", "attachment;filename=" + name);

        //响应过程
        IOUtils.copy(new FileInputStream(filePath), response.getOutputStream());

    }
}

十二、验证码

12.1 导入依赖

<!-- Kaptcha -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
    <exclusions>
        <exclusion>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>

12.2 声明验证码组件

web.xml中添加:

<!-- 验证码组件 -->
<servlet>
    <servlet-name>cap</servlet-name>
    <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>

    <!-- 验证码的图片是否有边框 -->
    <init-param>
        <param-name>kaptcha.border</param-name>
        <param-value>no</param-value>
    </init-param>

    <!-- 验证码字符长度 -->
    <init-param>
        <param-name>kaptcha.textproducer.char.length</param-name>
        <param-value>4</param-value>
    </init-param>

    <!-- 验证码字符集 -->
    <init-param>
        <param-name>kaptcha.textproducer.char.string</param-name>
        <param-value>abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789</param-value>
    </init-param>

    <!-- 图片底色 -->
    <init-param>
        <param-name>kaptcha.backgroud.clear.to</param-name>
        <param-value>211,229,237</param-value>
    </init-param>

    <!-- 将验证码存入session -->
    <init-param>
        <!--session.setAttribute("captchd","验证码")-->
        <param-name>kaptcha.session.key</param-name>
        <param-value>captcha</param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>cap</servlet-name>
    <!-- 生成验证码的路径 -->
    <url-pattern>/captcha</url-pattern>	
</servlet-mapping>

十三、REST

13.1 开发风格

是一种开发风格,遵从此风格开发软件,符合REST风格,则RESTFUL。

两个核心要求:

  • 每个资源都有唯一的标识(URL)
  • 不同的行为,使用对应的http-method
访问标识 资源
http://localhost:8989/xxx/users 所有用户
http://localhost:8989/xxx/users/1 用户1
http://localhost:8989/xxx/users/1/orders 用户1的所有订单
请求方式 标识 意图
GET http://localhost:8989/xxx/users 查询所有用户
POST http://localhost:8989/xxx/users 在所有用户中增加一个
PUT http://localhost:8989/xx/users 在所有用户中修改一个
DELETE http://localhost:8989/xxx/users/1 删除用户1
GET http://localhost:8989/xxx/users/1 查询用户1
GET http://localhost:8989/xxx/users/1/orders 查询用户1的所有订单
POST http://localhost:8989/xxx/users/1/orders 在用户1的所有订单中增加一个

13.2 使用

13.2.1定义Rest风格的Controller

@RequestMapping(value="/users",method=RequestMethod.GET)

等价于

@GetMapping("/users")

/**
 * 查询:所有用户 id=xx 某一个用户
 * 删除:id=xx 某一个用户
 * 增加:在所有用户中 增加一个
 * 修改:在所有用户中 修改一个
 * <p>
 * 资源:所有用户 /users
 * id=xx 某一个用户 /users/{id}
 */
@RestController()
public class MyRestController {

    @GetMapping("/users")
    public List<User> queryUsers() {
        System.out.println("query users with get");
        User user = new User(1, "张三");
        User user1 = new User(2, "李四");
        return Arrays.asList(user, user1);
    }

    @GetMapping("/users/{id}")
    public User queryOne(@PathVariable Integer id) {
        System.out.println("query one user with get:" + id);
        return new User(1, "张三");
    }

    @DeleteMapping("/users/{id}")
    public String deleteOne(@PathVariable Integer id) {
        System.out.println("delete one user with delete:" + id);
        return "ok";
    }

    @PostMapping("/users")
    public String saveUser(@RequestBody User user) {
        System.out.println("save user with post:" + user);
        return "ok";
    }

    @PutMapping("/users")
    public String updateUser(@RequestBody User user) {
        System.out.println("update user with put:" + user);
        return "ok";
    }
}

13.2.2 Ajax

<input type="button" value="queryALL" onclick="queryAll();">
<input type="button" value="queryOne" onclick="queryOne();">
<input type="button" value="saveUser" onclick="saveUser();">
<input type="button" value="updateUser" onclick="updateUser();">
<input type="button" value="deleteUser" onclick="deleteUser();">
<script>
    function queryAll() {
        $.ajax({
            type: "get",
            url: "${pageContext.request.contextPath}/users",
            success: function (ret) {
                console.log("查询所有:");
                console.log(ret);
            }
        });
    }

    function queryOne() {
        $.ajax({
            type: "get",
            url: "${pageContext.request.contextPath}/users/100",
            success: function (ret) {
                console.log("查询单个用户");
                console.log(ret);
            }
        });
    }

    function saveUser() {
        var user = {name: "shine", birth: "2020-12-12 12:12:20"}
        $.ajax({
            type: "post",
            url: "${pageContext.request.contextPath}/users",
            data: JSON.stringify(user),
            contentType: "application/json",
            success: function (ret) {
                console.log("增加用户");
                console.log(ret);
            }
        });
    }

    function updateUser() {
        var user = {id: 1, name: "shine2", birth: "2020-12-13 12:12:20"}
        $.ajax({
            type: "put",
            url: "${pageContext.request.contextPath}/users",
            data: JSON.stringify(user),
            contentType: "application/json",
            success: function (ret) {
                console.log("更新用户");
                console.log(ret);
            }
        });
    }

    function deleteUser() {
        $.ajax({
            type: "delete",
            url: "${pageContext.request.contextPath}/users/200",
            success: function (ret) {
                console.log("删除用户");
                console.log(ret);
            }
        });
    }
</script>

十四、跨域请求

14.1 域

域:协议+IP+端口

14.2 Ajax跨域问题

Ajax发送请求时,不允许跨域,以防用户信息泄露。

当Ajax跨域请求时,响应会被浏览器拦截(同源策略),并报错。即浏览器默认不允许ajax跨域得到响应内容。

互相信任的域之间如果需要ajax访问,(比如前后端分离项目中,前端项目和后端项目之间),则需要额外的设置才可正常请求。

14.3 解决方案

允许其他域访问

在被访问方的Controller类上,添加注解

@Crossorigin("http://localhost:8080")//允许此域发请求访问
  • 携带对方cookie,使得session可用
  • 在访问方,ajax中添加属性:withCredentials:true

十五、SpringMVC执行流程

十六、Spring整合

16.1 整合思路

此时项目中有两个工厂

  • DispatcherServlet启动的springMVC工厂=负责生产C及springMVC自己的系统组件
  • ContextLoaderListener启动的spring工厂==负责生产其他所有组件
  • springMVC的工厂会被设置为spring工厂的子工厂,可以随意获取spring工厂中的组件
  • 整合过程,就是累加:代码+依赖+配置。然后将service注入给controller即可

16.2 整合技巧

两个工厂不能有彼此侵入,即,生产的组件不能有重合。

mvc.xml中添加:

<!--
告知SpringMVC哪些包中存在被注解的类
use-default-filters=true 凡是被 @Controller @Service @Repository 注解的类,都会被扫描
use-default-filters=false 默认不扫描包内的任何类,只扫描include-filter中指定的类
只扫描被@Controller注解的类
-->
<context:component-scan base-package="com.techoc" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

applicationContext.xml中添加:

<!--
告知Spring
唯独不扫描eController注解的类
-->
<context:component-scan base-package="com.techoc" use-default-filters="true">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
posted @ 2020-12-30 20:18  Techoc  阅读(72)  评论(0编辑  收藏  举报