SpringMVC框架

JavaWeb


一、Spring与Web环境集成

1.1 ApplicationContext应用上下文获取方式

应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件) ,这样的弊端是配置文件加载多次,应用上下文对象创建多次。

在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域ServletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了。

1.2 Spring提供获取应用上下文的工具

上面的分析不用手动实现,Spring提供了一个监听器ContextLoaderListener就是对上述功能的封装,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工具WebApplicationContextUtils供使用者获得应用上下文对象。

所以我们需要做的只有两件事:

  • ①在web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)
  • ②使用WebApplicationContextUtils获得应用上下文对象ApplicationContext

1.3 导入Spring集成Web的坐标

        <!-- spring必要依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <!-- spring web依赖注意使用mvc后倒入webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.2</version>
        </dependency>
        <!--Jsp坐标-->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.0</version>
        </dependency>
        <!-- Servlet坐标 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>

1.4 配置ContextLoaderListener监听器

 <!--全局参数-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--Spring的监听器-->
<listener>
	<listener-class>
       org.springframework.web.context.ContextLoaderListener
   </listener-class>
 </listener>

1.5 通过工具获得应用上下文对象

@WebServlet(name = "TestServlet", value = "/test")
public class TestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = request.getServletContext();
        WebApplicationContext spring = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        System.out.println(spring.getBean("user"));
    }
}
  • 知识要点

    Spring集成Web环境步骤:

    • ①配置ContextLoaderListener监听器
    • ②使用WebApplicationContextUtils获得应用上下文
    • ③spring-web提供的监听器spring-webmvc页提供(同时使用会冲突)

二、SpringMVC的简介

2.1 SpringMVC概述

SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于SpringFrameWork 的后续产品,已经融合在 Spring Web Flow 中。

SpringMVC 已经成为目前最主流的MVC框架之一,并且随着Spring3.0 的发布,全面超越 Struts2,成为最优秀的 MVC 框架。它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持 RESTful 编程风格的请求。

2.2 SpringMVC简单实现

使用Servlet进行控制层处理每一个请求都需要书写一个Servlet,每个Servlet只修改处理请求的方法与名称,其余没有修改,SpringMVC可以看做提供了一个中央控制器对这些Servlet进行统一的分发管理并简化Servlet的书写。

①将所有的方法书写在一个Java类中 并书写相应地址映射

public class UserController {
    private Map<String,String> urls;

    public UserController() {
        urls=new HashMap<>();
        urls.put("/user/update","update");
        urls.put("/user/delete","delete");
    }

    public Map<String, String> getUrls() {
        return urls;
    }

    public void setUrls(Map<String, String> urls) {
        this.urls = urls;
    }

    public void update(){//update
        System.out.println("UserController.update");
    }

    public void delete(){//delete
        System.out.println("UserController.delete");
    }
}

②书写核心控制过滤器(应该是处理所有请求的Servlet)

@WebFilter("*")
public class CoreControllerFilter implements Filter {
    private Map<String, String> url=new HashMap<>();
    private UserController userController=new UserController();

    public void init(FilterConfig config) throws ServletException {
        //将指定控制器路径集合加入总集合
        url.putAll(userController.getUrls());
    }
    public void destroy() {
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        //获取请求地址
        HttpServletRequest httpRequest= (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();
        String s = url.get(requestURI);
        if(s==null){
            httpRequest.getRequestDispatcher("WEB-INF/404.jsp").forward(request,response);
        }else{
            Class aClass = userController.getClass();
            try {
                Method method = aClass.getDeclaredMethod(s);
                method.invoke(userController);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

2.3 SpringMVC快速入门

需求:客户端发起请求,服务器端接收请求,执行逻辑并进行视图跳转。

  • 开发步骤

    • ①导入SpringMVC相关坐标
    • ②配置SpringMVC核心控制器DispathcerServlet
    • ③创建Controller类和视图页面
    • ④使用注解配置Controller类中业务方法的映射地址
    • ⑤配置SpringMVC核心文件 spring-mvc.xml
    • ⑥客户端发起请求测试
  • 代码实现

①导入Spring和SpringMVC的坐标、导入Servlet和Jsp的坐标

 <!--Spring坐标-->
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-context</artifactId>
     <version>5.0.8.RELEASE</version>
 </dependency>
 <!--SpringMVC坐标-->
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-webmvc</artifactId>
     <version>5.0.8.RELEASE</version>
 </dependency>
    <!--Jsp坐标-->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.0</version>
    </dependency>
    <!-- Servlet坐标 -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
    </dependency>

②在web.xml配置SpringMVC的核心控制器与Spring加载监听器

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Archetype Created Web Application</display-name>
    <!-- 配置spring全局参数 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
    </context-param>
    <!-- 上下文加载监听器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- 配置springmvc前端控制器 -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

③创建Controller和业务方法

public class UserController {


    public ModelAndView select(ModelAndView mv,HttpServletRequest request){
        request.setAttribute("msg","查找返回的数据");
        mv.setViewName("/index.jsp");
        return mv;
    }

    public String update(ModelAndView mv,HttpServletRequest request){
        request.setAttribute("msg","更新返回的数据");
        return "/index.jsp";
    }
}

③创建视图页面index.jsp

<html>
<body>
    <h2>Hello SpringMVC!</h2>
    ${msg}
</body>
</html>

④配置注解

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/select.do")
    public ModelAndView select(ModelAndView mv,HttpServletRequest request){
        request.setAttribute("msg","查找返回的数据");
        mv.setViewName("/index.jsp");
        return mv;
    }

    @RequestMapping("/update.do")
    public String update(ModelAndView mv,HttpServletRequest request){
        request.setAttribute("msg","更新返回的数据");
        return "/index.jsp";
    }
}

⑤创建spring-mvc.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置注解扫描-->
    <context:component-scan base-package="com.yunhe.controller"/>
</beans>

2.4 SpringMVC的执行流程

在这里插入图片描述

①用户发送请求至前端控制器DispatcherServlet。
②DispatcherServlet收到请求调用HandlerMapping处理器映射器。
③处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
④DispatcherServlet调用HandlerAdapter处理器适配器。
⑤HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
⑥Controller执行完成返回ModelAndView。
⑦HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
⑧DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
⑨ViewReslover解析后返回具体View。
⑩DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户。

2.4.1 SpringMVC组件解析

  1. 前端控制器:DispatcherServlet

    用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。

  2. 处理器映射器:HandlerMapping

    HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

  3. 处理器适配器:HandlerAdapter

    通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

  4. 处理器:Handler

    它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由Handler 对具体的用户请求进行处理。

  5. 视图解析器:View Resolver

    View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。

  6. 视图:View

    SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。最常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面

2.4.2 SpringMVC注解解析

  • @RequestMapping

    作用:用于建立请求 URL 和处理请求方法之间的对应关系
    位置:

    • 类上,请求URL 的第一级访问目录。此处不写的话,就相当于应用的根目录
    • 方法上,请求 URL 的第二级访问目录,与类上的使用@ReqquestMapping标注的一级目录一起组成访问虚拟路径

    属性:

    • value:用于指定请求的URL。它和path属性的作用是一样的
    • method:用于指定请求的方式
    • params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样

    例如
    ​ params = {“accountName”},表示请求参数必须有accountName
    ​ params = {“moeny!100”},表示请求参数中money不能是100

2.4.3 SpringMVC的XML配置解析

1. mvc命名空间引入

命名空间:xmlns:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
约束地址: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

2. 组件扫描

SpringMVC基于Spring容器,所以在进行SpringMVC操作时,需要将Controller存储到Spring容器中,如果使用@Controller注解标注的话,就需要使用<context:component-scan base-package=“com.yh.controller"/>进行组件扫描。
SpringMVC有默认组件配置,默认组件都是DispatcherServlet.properties配置文件中配置的,该配置文件地址org/springframework/web/servlet/DispatcherServlet.properties,该文件中配置了默认的视图解析器,如下:

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

翻看该解析器源码,可以看到该解析器的默认设置,如下:

REDIRECT_URL_PREFIX = "redirect:"  --重定向前缀
FORWARD_URL_PREFIX = "forward:"    --转发前缀(默认值)
prefix = "";     --视图名称前缀
suffix = "";     --视图名称后缀

3. 视图解析器

我们可以通过属性注入的方式修改视图的的前后缀

<!--配置内部资源视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/views/"></property>
  <property name="suffix" value=".jsp"></property>
</bean>

2.5 SpringMVC的数据响应

2.5.1 数据响应方式

1) 页面跳转
①直接返回字符串
②通过ModelAndView(HttpServletRequest)对象返回

2) 回写数据
①直接返回字符串
②返回对象或集合

2.5.1.1 页面跳转-返回字符串形式

第一种形式:
在这里插入图片描述

这种方式进行页面跳转一般与HttpServletRequest对象一起使用,在参数列表中声明,将数据存储至作用域中进行传递。

第二种形式:

@RequestMapping(value="/quick4")
    public String save4(Model model){
        model.addAttribute("username","二狗子");
        return "success";
    }

这种方式同样会进行页面跳转,但是数据传递使用model对象,model对象的使用方式与HttpServletRequest类似,可以在jsp中使用el表达式通过key进行获取

2.5.1.2 页面跳转-通过ModelAndView对象返回

在Controller中方法返回ModelAndView对象,并且设置视图名称

@RequestMapping(value="/quick2")
    public ModelAndView save2(){
        /*
            Model:模型 作用封装数据
            View:视图 作用展示数据
         */
        ModelAndView modelAndView = new ModelAndView();
        //设置模型数据
        modelAndView.addObject("username","二狗子");
        //设置视图名称
        modelAndView.setViewName("success");
        return modelAndView;
    }

这种方法就是直接将返回的视图与数据一同交由视图解析器进行自动处理

当然也可以不创建ModelAndView对象而是在方法中声明,在Controller中方法形参上直接声明ModelAndView,无需在方法中自己创建,在方法中直接使用该对象设置视图,同样可以跳转页面

 @RequestMapping(value="/quick3")
    public ModelAndView save3(ModelAndView modelAndView){
        modelAndView.addObject("username","华华");
        modelAndView.setViewName("success");
        return modelAndView;
    }

2.5.2 回写数据-直接返回字符串

通过SpringMVC框架注入的response对象,使用response.getWriter().print(“hello world”) 回写数据,此时不需要视图跳转,业务方法返回值为void将需要回写的字符串直接返回,但此时需要通过@ResponseBody注解告知SpringMVC框架通知视图解析器,方法返回的字符串不是跳转是直接在http响应体中返回

@RequestMapping(value="/quick7")
    @ResponseBody  //告知SpringMVC框架 不进行视图跳转 直接进行数据响应
    public String save7() throws IOException {
        return "hello yh";
    }

    @RequestMapping(value="/quick6")
    public void save6(HttpServletResponse response) throws IOException {
        response.getWriter().print("hello yh");
    }

json字符串也是字符串,所以也可以通过这种方式返回json字符串数据

@RequestMapping(value="/quick8")
    @ResponseBody
    public String save8() throws IOException {
        return "{\"username\":\"zhangsan\",\"age\":18}";
    }

手动拼接json格式字符串的方式很麻烦,开发中往往要将复杂的java对象转换成json格式的字符串,我们可以使用web阶段学习过的json转换工具类进行转换后回写字符串(一般不用手动转换的方式)。

2.5.3 回写数据-返回对象或集合

通过SpringMVC帮助我们对对象或集合进行json字符串的转换并回写,为处理器适配器配置消息转换参数,指定使用jackson进行对象或集合的转换,因此需要在spring-mvc.xml中进行如下配置:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
            </list>
        </property>
    </bean>
@RequestMapping(value="/quick10")
    @ResponseBody
    //期望SpringMVC自动将User转换成json格式的字符串
    public User save10() throws IOException {
        User user = new User();
        user.setUsername("张三");
        user.setAge(18);
        return user;
    }

这样配置还是比较麻烦,配置的代码比较多,因此,我们可以使用mvc的注解驱动代替上述配置

<mvc:annotation-driven/>

在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。
使用<mvc:annotation-driven />自动加载 RequestMappingHandlerMapping(处理映射器)和RequestMappingHandlerAdapter( 处 理 适 配 器 ),可用在Spring-xml.xml配置文件中使用<mvc:annotation-driven />替代注解处理器和适配器的配置。同时使用<mvc:annotation-driven />默认底层就会集成jackson进行对象或集合的json格式字符串的转换

2.6 SpringMVC的数据请求

客户端请求参数的格式是:name=value&name=value……
服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC可以接收如下类型的参数:

  • ①基本类型参数
  • ②类类型参数
  • ③数组类型参数
  • ④集合类型参数

2.6.1 基本类型参数的获取

SpringMVC会自动进行解析,只需要在controller中的业务方法的参数名称与请求参数的name一致,参数值会自动映射匹配。并且能自动做类型转换;
自动的类型转换是指从String向其他类型的转换(注意类型转换失败的情况)

@RequestMapping(value="/quick11")
    @ResponseBody
    public void save11(String username,int age){
        System.out.println(username);
        System.out.println(age);
    }

Controller中的业务方法的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配。

当然也可以手动的进行配置获取请求指定的参数,使用RequestParam注解
@RequestParam有以下三个参数:

  • value:参数名字,即入参的请求参数名字,如username表示请求的参数区中的名字为username的参数的值将传入;
  • required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将抛出异常;
  • defaultValue:默认值,表示如果请求中没有同名参数时的默认值,设置该参数时,自动将required设为false。
public String requestparam(@RequestParam(value="username",required=false) String username)

2.6.2 类类型参数的获取

package com.yunhe.pojo;
public class User {
    private String username;
    private int age;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", age=" + age +
                '}';
    }
}

@RequestMapping(value="/quick12")
    @ResponseBody
    public void save12(User user){
        System.out.println(user);
    }

2.6.3 数组类型参数的获取

在前台页面提交的数据中,多个数据name相同,springmvc会自动解析成数组,但要求controller中的业务方法数组名称与请求参数的name一致,参数值会自动映射匹配。

@RequestMapping(value="/quick13")
    @ResponseBody
    public void save13(String[] strs) throws IOException {
        System.out.println(Arrays.asList(strs));
    }

2.6.4 集合类型参数的获取

springmvc会自动将对个name相同的数据解析为数组,所以在进行集合获取时需要将集合参数包装到一个POJO中才可以。

<form action="${pageContext.request.contextPath}/user/quick14" method="post">
        <%--表明是第一个User对象的username age--%>
        <input type="text" name="userList[0].username"><br/>
        <input type="text" name="userList[0].age"><br/>
        <input type="text" name="userList[1].username"><br/>
        <input type="text" name="userList[1].age"><br/>
        <input type="submit" value="提交">
    </form>
package com.yh.domain;

import java.util.List;

public class VO {
    private List<User> userList;
    public List<User> getUserList() {
        return userList;
    }
    public void setUserList(List<User> userList) {
        this.userList = userList;
    }
    @Override
    public String toString() {
        return "VO{" +
                "userList=" + userList +
                '}';
    }
}
@RequestMapping(value="/quick14")
    @ResponseBody
    public void save14(VO vo) throws IOException {
        System.out.println(vo);
    }

这种使用方式太过繁琐,对于不同的pojo类需要创建对应的集合类

使用ajax提交json数据获取集合类型参数数据

当使用ajax提交时,可以指定contentType为json形式,那么在方法参数位置使用@RequestBody可以直接接收集合数据而无需使用POJO进行包装

<script src="${pageContext.request.contextPath}/js/jquery-3.3.1.js"></script>
    <script>
        var userList = new Array();
        userList.push({username:"zhangsan",age:18});
        userList.push({username:"lisi",age:28});

        $.ajax({
            type:"POST",
            url:"${pageContext.request.contextPath}/user/quick15",
            data:JSON.stringify(userList),
            contentType:"application/json;charset=utf-8"
        });

    </script>
@RequestMapping(value="/quick15")
    @ResponseBody
    public void save15(@RequestBody List<User> userList) throws IOException {
        System.out.println(userList);
    }

2.7 SpringMVC静态资源的访问

当有静态资源需要加载时,比如jquery文件,通过谷歌开发者工具抓包发现,没有加载到jquery文件,原因是SpringMVC的前端控制器DispatcherServlet的url-pattern配置的是/,代表对所有的资源都进行过滤操作,我们可以通过以下两种方式指定放行静态资源:

在spring-mvc.xml配置文件中指定放行的资源

<mvc:resources mapping="/js/**"location="/js/"/>

使用<mvc:default-servlet-handler/>标签

<!--开发资源的访问-->
    <!--<mvc:resources mapping="/js/**" location="/js/"/>
    <mvc:resources mapping="/img/**" location="/img/"/>-->

    <mvc:default-servlet-handler/>

2.8 SpringMVC配置编码过滤器

使用springmvc与servlet一样,当编码不一致时会出现乱码问题,springmvc提供了CharacterEncodingFilter全局过滤器进行全局的编码配置

<!--配置全局过滤的filter-->
    <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>

2.9 SpringMVC配置编码过滤器

当请求的参数名称与Controller的业务方法参数名称不一致时,就需要通过 @RequestParam 注解显式的绑定

<form action="${pageContext.request.contextPath}/quick16" method="post">
    <input type="text" name="name"><br>
    <input type="submit" value="提交"><br>
</form>

@RequestMapping(value="/quick16")
    @ResponseBody
    public void save16(@RequestParam(value="name",required = false,defaultValue = "yh") String username) throws IOException {
        System.out.println(username);
    }

属性:

  • value :页面数据的name
  • required :是否必填(如果页面没有传递则报错默认为false)
  • defaultValue :默认值,当页面没有传递数据时的赋值(默认为数据类型默认值)

2.10 SpringMVC配置请求类型转换器

SpringMVC 默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。
但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。

@component
public class DateConverter implements Converter<String, Date> {
    public Date convert(String dateStr) {
        //将日期字符串转换成日期对象 返回
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        try {
            date = format.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}
@RequestMapping(value="/quick18")
    @ResponseBody
    public void save18(Date date) throws IOException {
        System.out.println(date);
    }
 <mvc:annotation-driven conversion-service="conversionService"/>
        <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
            <property name="converters">
                <set>
                    <ref bean="dateConverter"/>
               </set>
           </property>
</bean>

2.11 请求头信息信息的获取

使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)
@RequestHeader注解的属性如下:

  • value:请求头的名称
  • required:是否必须携带此请求头
@RequestMapping(value="/quick20")
    @ResponseBody
    public void save20(@RequestHeader(value = "User-Agent",required = false) String user_agent) throws IOException {
        System.out.println(user_agent);
    }

2.12 请求头cookie数据的获取

使用@CookieValue可以获得指定Cookie的值
@CookieValue注解的属性如下:

  • value:指定cookie的名称
  • required:是否必须携带此cookie
 @RequestMapping(value="/quick21")
    @ResponseBody
    public void save21(@CookieValue(value = "JSESSIONID") String jsessionId) throws IOException {
        System.out.println(jsessionId);
    }

2.13 SpringMVC使用Restful风格参数的获取

Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。

Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:

  • GET:用于获取资源
  • POST:用于新建资源
  • PUT:用于更新资源
  • DELETE:用于删除资源

例如:
/user/1 GET : 得到 id = 1 的 user
/user/1 DELETE: 删除 id = 1 的 user
/user/1 PUT: 更新 id = 1 的 user
/user POST: 新增 user

上述url地址/user/1中的1就是要获得的请求参数,form表单提交数据的方式支持get与post,但是在SpringMVC中可以使用占位符进行参数绑定。地址/user/1可以写成/user/{id},占位符{id}对应的就是1的值。在业务方法中我们可以使用@PathVariable注解进行占位符的匹配获取工作。

http://localhost:8080/springmvc1/quick17/zhangsan

@RequestMapping(value="/quick17/{name}")
@ResponseBody
 public void save17(@PathVariable(value="name") String username) throws IOException {
        System.out.println(username);
 }

springmvc使用RESTful风格就是使用占位符的形式,将数据与地址进行绑定,通过@PathVariable注解进行获取,完成通过对不同路径请求完成类似功能的需求

实例

在这里插入图片描述
但是对于http请求get与post请求的书写没有问题,但是对于put与delete请求并没有看过,表单或者ajax没有提供,我们在发送这两个请求时本质还是使用post请求,以ajax为例额外添加了_method:“put/delete”
在这里插入图片描述
ajax里面type写成Put、Delete是可以访问到对应的方法的,但是参数却无法传递过去,所有传递过去的参数都是null

2.14 SpringMVC拦截器简单使用

SpringMVC的处理器拦截器,类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。

  • (1)过滤器:
    依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。
  • (2)拦截器:
    依赖于web框架,在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。

1、常见应用场景

  • 1)日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
  • 2)权限检查:如登录检测,进入处理器检测是否登录,如果没有直接返回到登录页面;
  • 3)性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
  • 4)通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个Controller中的处理方法都需要的,我们就可以使用拦截器实现。
  • 5)OpenSessionInView:如Hibernate,在进入处理器打开Session,在完成后关闭Session。

本质也是AOP(面向切面编程),也就是说符合横切关注点的所有功能都可以放入拦截器实现。

SpringMVC提供的拦截器接口:HandlerInterceptor

public interface HandlerInterceptor {
	boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
	    throws Exception;
 
	void postHandle(
			HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
			throws Exception;
 
	void afterCompletion(
			HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception;
}

拦截器一个有3个回调方法,而一般的过滤器Filter才两个:

  • preHandle:预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
  • postHandle:后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
  • afterCompletion:整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中preHandle返回true的拦截器才会执行afterCompletion。

拦截器适配器:HandlerInterceptorAdapter

有时我们可能只需要实现三个回调方法中的某一个,如果实现HandlerInterceptor接口的话,三个方法必须实现,此时spring提供了一个HandlerInterceptorAdapter适配器(一种适配器设计模式的实现),允许我们只实现需要的回调方法

public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
		throws Exception {
		return true;
	}
 
	public void postHandle(
			HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
			throws Exception {
	}
 
	public void afterCompletion(
			HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
	}
 
	public void afterConcurrentHandlingStarted(
			HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
	}
}

简单实现

①自定义两个拦截器(继承拦截器适配器:HandlerInterceptorAdapter):HandlerInterceptor1 和 HandlerInterceptor2

public class HandlerInterceptor1 extends HandlerInterceptorAdapter{
 
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.out.println("--1--HandlerInterceptor1.preHandle");
		return true;
	}
 
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		System.out.println("--1--HandlerInterceptor1.postHandle");
	}
 
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		System.out.println("--1--HandlerInterceptor1.afterCompletion");
	}
}

②在 springmvc.xml 配置文件中配置拦截器:
(1)拦截所有Controller类里的所有处理方法

	<!-- 配置拦截器:-->
	<mvc:interceptors>
		<!-- 会拦截所有Controller类里的所有处理方法 -->
		<bean class="com.yunhe.interceptor.HandlerInterceptor1"></bean>
		<bean class="com.yunhe.interceptor.HandlerInterceptor2"></bean>
	</mvc:interceptors>

(2)只拦截某个请求路径的处理方法

	<!-- 配置拦截器:-->
	<mvc:interceptors>
		<!-- 会拦截所有Controller类里的所有处理方法 -->
		<bean class="com.yunhe.interceptor.HandlerInterceptor1"></bean>
		<mvc:interceptor>
			<!-- 只拦截该路径 -->
			<mvc:mapping path="/users"/>
			<bean class="com.yunhe.interceptor.HandlerInterceptor2"></bean>
		</mvc:interceptor>
	</mvc:interceptors>

(3)拦截器深入配置:注意 /** (任意分层路径下) ,/* (该任意单路径下) 和 配置順序(先所有后排除)

	<!-- 配置拦截器:-->
	<mvc:interceptors>
		<!-- 会拦截所有Controller类里的所有处理方法 -->
		<bean class="com.yunhe.interceptor.HandlerInterceptor1"></bean>
		<mvc:interceptor>
			<!-- 只拦截该路径 -->
			<mvc:mapping path="/users"/>
			<bean class="com.yunhe.interceptor.HandlerInterceptor2"></bean>
		</mvc:interceptor>
		<mvc:interceptor>
			<!-- 拦截所有请求,排除拦截 /toAdd 请求 -->
			<mvc:mapping path="/**"/>
			<mvc:exclude-mapping path="/toAdd"/>
			<bean class="com.yunhe.interceptor.HandlerInterceptor3"></bean>
		</mvc:interceptor>
	</mvc:interceptors>

三、SSM整合

①导入相应的依赖坐标

    <!-- spring核心依赖坐标 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.5</version>
    </dependency>

    <!-- springaop织入依赖 -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.6</version>
    </dependency>

    <!-- springtx依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.3.5</version>
    </dependency>

    <!-- lombok工具依赖 -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.2</version>
    </dependency>


    <!-- spring jdbc依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.5</version>
    </dependency>

    <!-- mysql依赖-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.39</version>
    </dependency>
 
    <!-- Druid连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.10</version>
    </dependency>

    <!-- springmvc依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.3.5</version>
    </dependency>

    <!-- servlet依赖 -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
    </dependency>
    
    <!-- jsp依赖 -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.0</version>
    </dependency>

    <!-- jackson依赖 -->
    <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>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.9.0</version>
    </dependency>
    
    <!-- mybatis依赖 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>

    <!--  mybatis sping整合依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.6</version>
    </dependency>
    
    <!--log4j依赖-->
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>

②创建相应的包

-- src
	-- main
		--java
			-- com
				-- yunhe
					--controller		#控制层 
					-- mapper	   		#数据层 
					-- pojo				#数据层 
					-- service			#服务层
					-- test				#测试类
					-- util				#工具类
		-- resources		
			-- mapper	   				#数据层

③书写mybatis核心配置文件

<?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>
    <!-- 加载properties配置文件 -->
    <!--<properties url=""></properties>-->
    
    <!-- 为指定的包下的类设置别名(设置pojo 使用首字母小写形式的别名) -->
    <!--<typeAliases>
        <package name=""/>
    </typeAliases>-->

    <!-- 配置mybatis数据源选择操作 -->
    <!--<environments default="">
        <environment id="">
            <transactionManager type=""></transactionManager>
            <dataSource type=""></dataSource>
        </environment>
    </environments>-->
    
    
    <!-- mybatis的全局配置 基本使用默认 其他可以通过mapper配置文件进行设置 -->
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/><!-- 开启懒加载 -->
        <setting name="aggressiveLazyLoading" value="false"/><!-- 设置懒加载侵入方式 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/><!-- 使用mybatis 自带的日志管理 -->
    </settings>
    
    <!-- 配置mybatis扫描xml文件所在的包 -->
    <!--<mappers>
        <package name=""/>
    </mappers>-->

</configuration>

④书写log4j与数据连接配置文件

db.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root

log4j.properties

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

⑤配置spring相关核心配置文件

<?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:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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/tx
       http://www.springframework.org/schema/tx/spring-tx.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
       http://www.springframework.org/schema/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
    <!-- spring组件扫描 -->
    <!-- 将含有对应注解的类交由spring管理 可以实现IOC与DI AOP也必须切入由spring管理的类 -->
    <context:component-scan base-package="com.yunhe"/>
    
    <!-- 加载properties -->
    <!-- 可以在当前的配置文件中使用${key}的形式获取对应的value值 -->
    <context:property-placeholder location="classpath:db.properties"/>
    
    <!-- 配置数据源 -->
    <!-- 使用指定的连接池对数据源进行管理 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 配置spring与mybatis整合 -->
    <!-- 配置SqlSessionFactoryBean对象,整合加载mybatis配置文件 使用mybatis进行数据源操作 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 将数据源交由mybatis进行管理 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 配置mybatis配置文件 -->
        <!-- 加载mybatis核心配置文件(进行修改时) -->
        <property name="configLocation" value="classpath:mybatis.xml"/>
        <!-- 配置mapper映射文件 -->
        <!-- 在创建mybatis时加载所有的mapper.xml -->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>

    </bean>


    <!-- mybatis.spring自动映射,DAO接口所在包名,Spring会自动查找其下的类 -->
    <!-- 根据加载的mapper.xml创建相应的实现类 并交由spring容器管理 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.yunhe.mapper" />
    </bean>
    
    <!--平台事务管理器-->
    <!-- 创建相应的事务代理对象交由spring容器管理 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 开启事务的注解驱动 -->
    <!-- 将拥有事务处理的方法进行事务代理 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
    <!-- 设置aop织入自动代理 -->
    <!-- 才会将相应的切面类织入指定的切入点(方法) -->
    <aop:aspectj-autoproxy/>
    
    <!-- springmvc视图解析器 -->
    <!-- 在controller返回的视图名进行拼接返回对应视图 -->
    <!-- 如果需要使用其他的视图模板或插件也是在视图解析器进行配置 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

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

    <!-- 开启静态资源默认过滤 -->
    <mvc:default-servlet-handler/>
    
    <!-- 指定静态资源过滤 -->
     <!--<mvc:resources mapping="" location=""></mvc:resources>-->
    
</beans>

⑥配置web.xml

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<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">
    <!-- springmvc前端控制器 -->
    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- springmvc编码控制器 -->
    <filter>
        <filter-name>Encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>Encoding</filter-name>
        <url-pattern>*</url-pattern>
    </filter-mapping>
</web-app>

⑦书写相应的代码测试ssm整合是否成功

User.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int uid;
    private String uusername;
    private String upassword;
}

UserMapper.java

public interface UserMapper {
    ArrayList<User> selectAll();
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yunhe.mapper.UserMapper">
    
    
    <select id="selectAll" resultType="com.yunhe.pojo.User">
        select * from user
    </select>
    
    
</mapper>

UserService.java

public interface UserService {
    public ArrayList<User> findAll();
}

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public ArrayList<User> findAll() {
        return userMapper.selectAll();
    }
}

UserController.java

@Controller
public class UserController {
    @Autowired
    private UserService userServiceImpl;
    @RequestMapping("/all")
    public String find(HttpServletRequest request){
        request.setAttribute("msg",userServiceImpl.findAll());
        return "success";
    }
}

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
${msg}
</body>
</html>

UserController.java

@Controller
public class UserController {
    @Autowired
    private UserService userServiceImpl;
    @RequestMapping("/all")
    public String find(HttpServletRequest request){
        request.setAttribute("msg",userServiceImpl.findAll());
        return "success";
    }
}

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
${msg}
</body>
</html>

每日一点点进步
不进则退

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

璃尔 °

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值