springmvc总结

0.springmvc三大组件

    1.映射器 2.控制器 3.视图解析器

1.springmvc的解释

Spring MVC 通过一套 MVC 注解,让 POJO 成为处理请

求的控制器,而无须实现任何接口  

2.web.xml的配置

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

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns="http://java.sun.com/xml/ns/javaee"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-            app_2_5.xsd"

id="WebApp_ID" version="2.5">

 

<!-- 

配置 org.springframework.web.filter.HiddenHttpMethodFilter: 可以把 POST 请求转为 DELETE 或 POST 请求 

-->

<filter>

<filter-name>HiddenHttpMethodFilter</filter-name>

<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>HiddenHttpMethodFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

 

<!-- 配置 DispatcherServlet -->

<servlet>

<servlet-name>dispatcherServlet</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>//表示dispatcherServlet在web项目被加载的时候就被创建,而不是在第一次请求的时候创建

</servlet>

 

<servlet-mapping>

<servlet-name>dispatcherServlet</servlet-name>

<url-pattern>/</url-pattern>   //可以应答所有请求

</servlet-mapping>

</web-app>

3. springmvc.xml的配置

 

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

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd

        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

    <!-- 配置自定扫描的包 -->

    <context:component-scan base-package="com.atguigu.springmvc"></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>

    

    <!-- 配置视图  BeanNameViewResolver 解析器: 使用视图的名字来解析视图 -->

    <!-- 通过 order 属性来定义视图解析器的优先级, order 值越小优先级越高 -->

    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">

        <property name="order" value="100"></property>

    </bean>

    

    <!-- 配置国际化资源文件 -->

    <bean id="messageSource"

        class="org.springframework.context.support.ResourceBundleMessageSource">

        <property name="basename" value="i18n"></property>    

    </bean>

    

    <!-- 配置直接转发的页面 -->

    <!-- 可以直接相应转发的页面, 而无需再经过 Handler 的方法.  -->

    <mvc:view-controller path="/success" view-name="success"/>

    

    <!-- 在实际开发中通常都需配置 mvc:annotation-driven 标签 -->

    <mvc:annotation-driven></mvc:annotation-driven>

</beans>

 

 

3.请求流程

发出一个请求,例如用超链接

      <a href="helloworld">Hello World</a

---->

去web.xml中的

<servlet-mapping>

        <servlet-name>dispatcherServlet</servlet-name>

        <url-pattern>/</url-pattern>

    </servlet-mapping>
进行过滤
----->
然后到用handlers中的用@Controller注解的类中找到映射的@RequestMapping("/helloworld")的方法
----------->
找到请求匹配前缀和后缀

  <property name="prefix" value="/WEB-INF/views/"></property>   //寻找视图的前缀

  <property name="suffix" value=".jsp"></property>  

----------->

相应请求

 

4.知识点

①Spring MVC 使用 @RequestMapping 注解为控制器指定可

以处理哪些 URL 请求

在控制器的类及方法定义处都可标注
@RequestMapping
– 类义处:提供初步的请求映射信息。相对于 WEB 应用的根目录
– 方法处:提供进一步的细分映射信息。相对于类定义处的 URL。若
类定义处未标注 @RequestMapping,则方法处标记的 URL 相对于
WEB 应用的根目录  

如下:

package com.zhk.handler;

 

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

 

@RequestMapping("/classMappingrequst")

@Controller

public class HelloWord {

 

@RequestMapping("/hello")

public String helloWorld() {

System.out.println("输出helloword成功");

return "hello";

}

}

请求是:http://localhost:8080/springMVCHelloWord/classMappingrequst/hello

 

 

③@RequestMapping 除了可以使用请求 URL 映射请求外,

可以使用指定求方法  

如:

package com.zhk.handler;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

@RequestMapping("/classMappingrequst")

@Controller

public class HelloWord {

    @RequestMapping(value ="/hello",method=RequestMethod.POST)

    public String helloWorld() {

        System.out.println("输出helloword成功");

        return "hello";

    }

}

 

jsp:表单一定要用post提交,和上面对应

    <form action="classMappingrequst/hello" method="post">

        <input type="submit"/>

    </form>  

④@RequestMapping 的params 及 heads

求参数及的映射条件,他们之间是的关系,联合使用多个条件可让请求映射更加精确化

例如:

package com.zhk.handler;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

@RequestMapping("/classMappingrequst")

@Controller

public class HelloWord {

    @RequestMapping(value ="/hello",method=RequestMethod.POST,params={"userName","age != 10"},headers={"Accept-Language=en-US,zh;q=0.8"}) //表示传递的参数必须包括用户名,年龄必须不等于10,头信息headers中必须和所写一样

    public String helloWorld() {

        System.out.println("输出helloword成功");

        return "hello";

    }

}

⑤请求路径可以使用通配符

Ant 风格资源地址支持 3 种匹配符:
– ?:匹配文件名中的一个字符
– *:匹配文件名中的任意字符
– **:** 匹配多层路径 

 

例如:   

@RequestMapping(value ="/*/hello")

    public String helloWorld() {

        System.out.println("输出helloword成功");

        return "hello";

    }  
  我的请求:http://localhost:8080/springMVCHelloWord/classMappingrequst/ddddd/hello

⑥@PathVariable 映射 URL 绑定的占位符

带占位符的 URL 是 Spring3.0 新增的功能,该功能在,SpringMVC 向 REST 目标挺进发展过程中具有里程碑的意义  

通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过
@PathVariable("xxx") 绑定到操作方法的入参中。  

例如:

package com.zhk.handler;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/classMappingrequst")

@Controller

public class HelloWord {

         /**

     * @PathVariable 可以来映射 URL 中的占位符到目标方法的参数中.

     * @param id

     * @return

     */  

    @RequestMapping("/hello/{id}")

    public String helloWorld(@PathVariable("id") Integer id) {

        

        System.out.println("输出helloword成功"+id);

        return "hello";

    }

}

我的请求是:http://localhost:8080/springMVCHelloWord/classMappingrequst/hello/121

121会被传入到id中

⑦请求参数从前台传参数

必要时可以方法及方法入参注相的注解

@PathVariable@RequestParam@RequestHeader )、Spring
MVC 框架会将 HTTP 请求的信息绑定到相应的方法入参中,并根据方法的返回值类型做出相应的后续处理

例如:

package com.zhk.handler;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestParam;

 

      /**

     * @RequestParam 来映射请求参数. 

         value 值即请求参数的参数名 

         required 该参数是否必须. 默认为 true

     * defaultValue 请求参数的默认值

     */  

@RequestMapping("/classMappingrequst")

@Controller

public class HelloWord {

    @RequestMapping("/hello")

    public String helloWorld(@RequestParam("userName") String userName,@RequestParam(value="passWord",required=false,defaultValue="0") Integer passWord ) {

        System.out.println("姓名"+userName+"/r/n"+"密码"+passWord);

        return "hello";

    }

}

⑧请求头参数

例如:

package com.zhk.handler;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestHeader;

import org.springframework.web.bind.annotation.RequestMapping;

/**

  * 了解: 映射请求头信息 用法同 @RequestParam

  */  

@RequestMapping("/classMappingrequst")

@Controller

public class HelloWord {

    @RequestMapping("/hello")

    public String helloWorld(@RequestHeader("Accept-Language") String heandPara) {

        

        System.out.println("姓名"+heandPara);

        return "hello";

    }

}

⑨使用 @CookieValue 绑定请求中的 Cookie 值

@CookieValue 可让处理方法入参绑定某个 Cookie 值  

package com.zhk.handler;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.CookieValue;

import org.springframework.web.bind.annotation.RequestMapping;

    /**

     * 了解:

     * 

     * @CookieValue: 映射一个 Cookie 值. 属性同 @RequestParam

     */  

@RequestMapping("/classMappingrequst")

@Controller

public class HelloWord {

    @RequestMapping("/hello")

    public String helloWorld(@CookieValue("JSESSIONID") String cookievalue) {

        System.out.println("cookie"+cookievalue);

        return "hello";

    }

}

⑩使用 POJO 对象绑定请求参数值  

Spring MVC 会按请求参数名和 POJO 属性名进行自动匹
配,自动为该对象填充属性值。支持级联属性。
如:dept.deptId、dept.address.tel 等 

例如:

 

/**

 * Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配, 自动为该对象填充属性值。支持级联属性。

 * 如:dept.deptId、dept.address.tel 等

 */

@RequestMapping("/testPojo")

public String testPojo(User user) {

System.out.println("testPojo: " + user);

return SUCCESS;

 

jsp:

<form action="springmvc/testPojo" method="post">

username: <input type="text" name="username"/>

<br>

password: <input type="password" name="password"/>

<br>

email: <input type="text" name="email"/>

<br>

age: <input type="text" name="age"/>

<br>

city: <input type="text" name="address.city"/>

<br>

province: <input type="text" name="address.province"/>

<br>

<input type="submit" value="Submit"/>

</form>

使用 Servlet API 作为入参 

      /**

 * 可以使用 Serlvet 原生的 API 作为目标方法的参数 具体支持以下类型

 * 

 * HttpServletRequest 

 * HttpServletResponse 

 * HttpSession

 * java.security.Principal 

 * Locale InputStream 

 * OutputStream 

 * Reader 

 * Writer

 * @throws IOException 

 */

@RequestMapping("/testServletAPI")

public void testServletAPI(HttpServletRequest request,

HttpServletResponse response, Writer out) throws IOException {

System.out.println("testServletAPI, " + request + ", " + response);

out.write("hello springmvc");

// return SUCCESS;

}

⑫    处理模型数据 

1)ModelAndView  

控制器处理方法的返回值如果为 ModelAndView, 则其

既包含视图信息,也包含模型数据信息 

 例如:

 

    

       /**

 * 目标方法的返回值可以是 ModelAndView 类型。 

 * 其中可以包含视图和模型信息

 * SpringMVC 会把 ModelAndView 的 model 中数据放入到 request 域对象中.

         *  集合的处理类似

 * @return

 */

@RequestMapping("/testModelAndView")

public ModelAndView testModelAndView(){

String viewName = SUCCESS;

ModelAndView modelAndView = new ModelAndView(viewName);

//添加模型数据到 ModelAndView 中.

modelAndView.addObject("time", new Date());

return modelAndView;

}

 

解释:

添加模型数据:

– MoelAndView addObject(String attributeName, Object  attributeValue)
– ModelAndView addAllObject(Map<String, ?> modelMap)
• 设置视图:
– void setView(View view)
– void setViewName(String viewName)

 

在jsp页面这么取:

 

<h4>Sucess Page</h4>

time: ${requestScope.time }    

<br><br>

2)Map 及 Model    (放在请求域中  request)

//放在请求域中

Spring MVC 在内部使用了一个org.springframework.ui.Model 接口存
储模型数据
• 具体步骤
– Spring MVC 在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器。
– 如果方法的入参为 Map 或 Model ,Spring MVC 会将隐含模型的引用传
递给这些入参。在方法体内,开发者可以通过这个入参对象访问到模型中的所有数
据,也可以向模型中添加新的属性数据  

 

   /**

     * 目标方法可以添加 Map 类型(实际上也可以是 Model 类型或 ModelMap 类型)的参数. 

     * @param map

     * @return

     */

    @RequestMapping("/testMap")

    public String testMap(Map<String, Object> map){

        System.out.println(map.getClass().getName()); 

        map.put("names", Arrays.asList("Tom""Jerry""Mike"));

        return SUCCESS;

    }  

 

jsp:

<br><br>

    names: ${requestScope.names }

<br><br>


3)@SessionAttributes    (放在session域中)

@SessionAttributes(value={"user"}, types={String.class})

@RequestMapping("/springmvc")

@Controller

public class SpringMVCTest {

    private static final String SUCCESS = "success";

    

    /**

     * @SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外(实际上使用的是 value 属性值),

     * 还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中(实际上使用的是 types 属性值)

     * 

     * 注意: 该注解只能放在类的上面. 而不能修饰放方法. 

     */

    @RequestMapping("/testSessionAttributes")

    public String testSessionAttributes(Map<String, Object> map){

        User user = new User("Tom""123456""tom@atguigu.com", 15);

        map.put("user"user);

        map.put("school""atguigu");   //因为上面写了types={String.class},所有会自动把"school"放到session域中

        return SUCCESS;

    }  

⑬使用@ModelAttribute的场景

数据库里的记录有些字段不能被修改例如创建时间

⑭使用@ModelAttribute的原理

      /**

     * 1. 有 @ModelAttribute 标记的方法, 会在每个目标方法执行之前被 SpringMVC 调用! 

     * 2. @ModelAttribute 注解也可以来修饰目标方法 POJO 类型的入参, 其 value 属性值有如下的作用:

     * 1). SpringMVC 会使用 value 属性值在 implicitModel 中查找对应的对象, 若存在则会直接传入到目标方法的入参中.

     * 2). SpringMVC 会一 value 为 key, POJO 类型的对象为 value, 存入到 request 中. 

     */

    @ModelAttribute

    public void getUser(@RequestParam(value="id",required=false) Integer id

            Map<String, Object> map){

        System.out.println("modelAttribute method");

        if(id != null){

            //模拟从数据库中获取对象

            User user = new User(1, "Tom""123456""tom@atguigu.com", 12);

            System.out.println("从数据库中获取一个对象: " + user);

            

            map.put("user"user);

        }

    }

    

    /**

     * 运行流程:

     * 1. 执行 @ModelAttribute 注解修饰的方法: 从数据库中取出对象, 把对象放入到了 Map 中. 键为: user

     * 2. SpringMVC 从 Map 中取出 User 对象, 并把表单的请求参数赋给该 User 对象的对应属性.

     * 3. SpringMVC 把上述对象传入目标方法的参数. 

         *  4.因为使用是同一个域,所有表单提交的属性会覆盖掉从数据库得到的属性

     * 

     * 注意: 在 @ModelAttribute 修饰的方法中, 放入到 Map 时的键需要和目标方法入参类型的第一个字母小写的字符串一致!

     * 

     * SpringMVC 确定目标方法 POJO 类型入参的过程

     * 1. 确定一个 key:

     * 1). 若目标方法的 POJO 类型的参数木有使用 @ModelAttribute 作为修饰, 则 key 为 POJO 类名第一个字母的小写

     * 2). 若使用了  @ModelAttribute 来修饰, 则 key 为 @ModelAttribute 注解的 value 属性值. 

     * 2. 在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入

     * 1). 若在 @ModelAttribute 标记的方法中在 Map 中保存过, 且 key 和 1 确定的 key 一致, 则会获取到. 

     * 3. 若 implicitModel 中不存在 key 对应的对象, 则检查当前的 Handler 是否使用 @SessionAttributes 注解修饰, 

     * 若使用了该注解, 且 @SessionAttributes 注解的 value 属性值中包含了 key, 则会从 HttpSession 中来获取 key 所

     * 对应的 value 值, 若存在则直接传入到目标方法的入参中. 若不存在则将抛出异常. 

     * 4. 若 Handler 没有标识 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 则

     * 会通过反射来创建 POJO 类型的参数, 传入为目标方法的参数

     * 5. SpringMVC 会把 key 和 POJO 类型的对象保存到 implicitModel 中, 进而会保存到 request 中. 

     * 

     * 源代码分析的流程

     * 1. 调用 @ModelAttribute 注解修饰的方法. 实际上把 @ModelAttribute 方法中 Map 中的数据放在了 implicitModel 中.

     * 2. 解析请求处理器的目标参数, 实际上该目标参数来自于 WebDataBinder 对象的 target 属性

     * 1). 创建 WebDataBinder 对象:

     * ①. 确定 objectName 属性: 若传入的 attrName 属性值为 "", 则 objectName 为类名第一个字母小写. 

     * *注意: attrName. 若目标方法的 POJO 属性使用了 @ModelAttribute 来修饰, 则 attrName 值即为 @ModelAttribute 

     * 的 value 属性值 

     * 

     * ②. 确定 target 属性:

     *     > 在 implicitModel 中查找 attrName 对应的属性值. 若存在, ok

     *     > *若不存在: 则验证当前 Handler 是否使用了 @SessionAttributes 进行修饰, 若使用了, 则尝试从 Session 中

     * 获取 attrName 所对应的属性值. 若 session 中没有对应的属性值, 则抛出了异常. 

     *     > 若 Handler 没有使用 @SessionAttributes 进行修饰, 或 @SessionAttributes 中没有使用 value 值指定的 key

     * 和 attrName 相匹配, 则通过反射创建了 POJO 对象

     * 

     * 2). SpringMVC 把表单的请求参数赋给了 WebDataBinder 的 target 对应的属性. 

     * 3). *SpringMVC 会把 WebDataBinder 的 attrName 和 target 给到 implicitModel. 

     * 近而传到 request 域对象中. 

     * 4). 把 WebDataBinder 的 target 作为参数传递给目标方法的入参. 

     */

    @RequestMapping("/testModelAttribute")

    public String testModelAttribute(@ModelAttribute("user") User user){   //和上面红色部分呼应

        System.out.println("修改: " + user);

        return SUCCESS;

    }  

⑮由@SessionAttributes引发的异常 

org.springframework.web.HttpSessionRequiredException:
Session attribute 'user' required - not found in session

如果在处理类定义处标注了@SessionAttributes(“xxx”),则
尝试从会话中获取该属性,并将其赋给该入参,然后再用
请求消息填充该入参对象。如果在会中找不到对应的属
性,
抛出 HttpSessionRequiredException 

 

如何避免@SessionAttributes引发的异常 

@Controller
@RequestMapping("/user")
@SessionAttributes(“user”)
public class UserController {
@ModelAttribute("user")         // 该方法会往隐含模型中添加一个名为user的模型属性
public User getUser(){
User user = new User();
return user;
@
RequestMapping(value = "/handle71")
public String handle71(@ModelAttribute(“user”) User user){
...
}
@RequestMapping(value = "/handle72")
public String handle72(ModelMap modelMap,SessionStatus sessionStatus){
...
}
}

  

⑯Spring MVC如何解析视图 

把 handler 方法返回值解析为实际的物理视图   

org.springframework.web.servlet.view.InternalResourceViewResolver  

 

1).请求处理方法执行完成后,最终返回一个 ModelAndView

 

对象。对于那些返回 String,View 或 ModeMap 等类型的

处理方法,Spring MVC 也会在内部将它装配成一个

ModelAndView ,它包含了逻辑名和模型对象的视图

 
 
 

2)• Spring MVC 借助视图解析器(ViewResolver)得到最终

 

的视图对象(View),最终的视图可以是 JSP ,也可能是

Excel、JFreeChart 等各种表现形式的视图(把相应的包加入到lib中,view就会转换成相应的view,例如加入jstl的包,就会转成jstlvew

 
 

3)• 对于最终究竟采取何种视图对象对模型数据进行渲染,处

 

理器并不关心,处理器工作重点聚焦在生产模型数据的工

作上,从而实现 MVC 的充分解耦  

⑰mvc_view-controller标签

      <!-- 配置直接转发的页面 -->

    <!-- 可以直接相应转发的页面, 而无需再经过 Handler 的方法.  -->

    <mvc:view-controller path="/success" view-name="success"/>  

 

⑱自定义视图

dispatcherServlet-servlet.xml

 

       <!-- 配置视图  BeanNameViewResolver 解析器: 使用视图的名字来解析视图 -->

    <!-- 通过 order 属性来定义视图解析器的优先级, order 值越小优先级越高 -->

    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">

        <property name="order" value="100"></property>

    </bean>

 

      @RequestMapping("/testView")

    public String testView(){

        System.out.println("testView");

        return "helloView";

    }  

 

HelloView.java
 

package com.atguigu.springmvc.views;

 

import java.util.Date;

import java.util.Map;

 

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

 

import org.springframework.stereotype.Component;

import org.springframework.web.servlet.View;

 

@Component

public class HelloView implements View{  //和上述对应

 

@Override

public String getContentType() {

return "text/html";

}

 

@Override

public void render(Map<String, ?> model, HttpServletRequest request,

HttpServletResponse response) throws Exception {

response.getWriter().print("hello view, time: " + new Date());

}

 

}

⑲重定向redirect和转发forward

 

@RequestMapping("/testRedirect")

public String testRedirect(){

System.out.println("testRedirect");

return "redirect:/index.jsp";

}

   @RequestMapping("/testForward")

public String testForward(){

System.out.println("testForwar");

return "forward:/testRedirect";

}

⑳使用springmvc的form标签

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

       <!--  

1. WHY 使用 form 标签呢 ?

可以更快速的开发出表单页面, 而且可以更方便的进行表单值的回显

2. 注意:

可以通过 modelAttribute 属性指定绑定的模型属性,

若没有指定该属性,则默认从 request 域对象中读取 command 的表单 bean

如果该属性值也不存在,则会发生错误。

-->

21)post,delete,input

web.xml

<!-- 配置 HiddenHttpMethodFilter: 把 POST 请求转为 DELETE、PUT 请求 -->

    <filter>

        <filter-name>HiddenHttpMethodFilter</filter-name>

        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>

    </filter>

 

超链接一般是get请求


22)springmvc处理静态资源

<!--  

    SpringMVC 处理静态资源:

    1. 为什么会有这样的问题:

    优雅的 REST 风格的资源URL 不希望带 .html 或 .do 等后缀

    若将 DispatcherServlet 请求映射配置为 /, 

    则 Spring MVC 将捕获 WEB 容器的所有请求, 包括静态资源的请求, SpringMVC 会将他们当成一个普通请求处理, 

    因找不到对应处理器将导致错误。

    2. 解决: 在 SpringMVC 的配置文件中配置 <mvc:default-servlet-handler/> //判断请求是否被springmvc映射过,如果没映射过,springmvc会去找目标文件

        <mvc:annotation-driven ></mvc:annotation-driven>
-->  

 

 

 <form action="" method="POST">

        <input type="hidden" name="_method" value="DELETE"/>  //name="_method"  隐藏域告诉过滤器HiddenHttpMethodFilter,这是个delete请求

    </form>

23)CRUD参见github项目

 

使用绝对路径: action="${pageContext.request.contextPath }/emp"

24)数据绑定

把字符串转成要保存的对象

 

     <form action="testConversionServiceConverer" method="POST">

        <!-- lastname-email-gender-department.id 例如: GG-gg@atguigu.com-0-105 -->

        Employee: <input type="text" name="employee"/>

        <input type="submit" value="Submit"/>

    </form>

 

 

 

@RequestMapping("/testConversionServiceConverer")

    public String testConverter(@RequestParam("employee") Employee employee){

        System.out.println("save: " + employee);

        employeeDao.save(employee);

        return "redirect:/emps";

    }  

 

 

<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>    

    

    <!-- 配置 ConversionService -->

    <bean id="conversionService"

        class="org.springframework.format.support.FormattingConversionServiceFactoryBean">

        <property name="converters">

            <set>

                <ref bean="employeeConverter"/>

            </set>

        </property>    

    </bean>  

 

 

package com.atguigu.springmvc.converters;

import org.springframework.core.convert.converter.Converter;

import org.springframework.stereotype.Component;

import com.atguigu.springmvc.crud.entities.Department;

import com.atguigu.springmvc.crud.entities.Employee;

@Component

public class EmployeeConverter implements Converter<String, Employee> {

    @Override

    public Employee convert(String source) {

        if(source != null){

            String [] vals = source.split("-");

            //GG-gg@atguigu.com-0-105

            if(vals != null && vals.length == 4){

                String lastName = vals[0];

                String email = vals[1];

                Integer gender = Integer.parseInt(vals[2]);

                Department department = new Department();

                department.setId(Integer.parseInt(vals[3]));

                

                Employee employee = new Employee(nulllastNameemailgenderdepartment);

                System.out.println(source + "--convert--" + employee);

                return employee;

            }

        }

        return null;

    }

}
25)使用annotation-driven配置的需要

①直接访问静态jsp页面

②为了解决访问静态js的问题,配置了<mvc:default-servlet-handler/>

配置了annotation-driven

③装配自定义转换器的时候,使用了annotation-driven

关于 mvc:annotation-driven
• <mvc:annotation-driven /> 会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter 与
ExceptionHandlerExceptionResolver 三个bean。
• 还将提供以下支持:
– 支持使用 ConversionService 实例对表单参数进行类型转换
– 支持使用 @NumberFormat annotation@DateTimeFormat
注解完成数据类型的格式化
– 支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证
– 支持使用 @RequestBody 和 @ResponseBody 注解  

26)数据格式化

package com.atguigu.springmvc.crud.entities;

import java.util.Date;

import javax.validation.constraints.Past;

import org.hibernate.validator.constraints.Email;

import org.hibernate.validator.constraints.NotEmpty;

import org.springframework.format.annotation.DateTimeFormat;

import org.springframework.format.annotation.NumberFormat;

public class Employee {

    private Integer id;

    @NotEmpty

    private String lastName;

    @Email

    private String email;

    //1 male, 0 female

    private Integer gender;

    

    private Department department;

    

    @Past

    @DateTimeFormat(pattern="yyyy-MM-dd")

    private Date birth;

    

    @NumberFormat(pattern="#,###,###.#")

    private Float salary;

    public Integer getId() {

        return id;

    }

    public void setId(Integer id) {

        this.id = id;

    }

    public String getLastName() {

        return lastName;

    }

    public void setLastName(String lastName) {

        this.lastName = lastName;

    }

    public String getEmail() {

        return email;

    }

    public void setEmail(String email) {

        this.email = email;

    }

    public Integer getGender() {

        return gender;

    }

    public void setGender(Integer gender) {

        this.gender = gender;

    }

    public Department getDepartment() {

        return department;

    }

    public void setDepartment(Department department) {

        this.department = department;

    }

    public Date getBirth() {

        return birth;

    }

    public void setBirth(Date birth) {

        this.birth = birth;

    }

    public Float getSalary() {

        return salary;

    }

    public void setSalary(Float salary) {

        this.salary = salary;

    }

    @Override

    public String toString() {

        return "Employee [id=" + id + ", lastName=" + lastName + ", email="

                + email + ", gender=" + gender + ", department=" + department

                + ", birth=" + birth + ", salary=" + salary + "]";

    }

    public Employee(Integer id, String lastName, String email, Integer gender,

            Department department) {

        super();

        this.id = id;

        this.lastName = lastName;

        this.email = email;

        this.gender = gender;

        this.department = department;

    }

    public Employee() {

        // TODO Auto-generated constructor stub

    }

}


27)显示转换错误信息

@RequestMapping(value="${pageContext.request.contextPath }/emp", method=RequestMethod.POST)

    public String save(@Valid Employee employeeErrors resultMap<String, Object> map){

        System.out.println("save: " + employee);

        

        if(result.getErrorCount() > 0){

            System.out.println("出错了!");

            

            for(FieldError error:result.getFieldErrors()){

                System.out.println(error.getField() + ":" + error.getDefaultMessage());

            }

            

            //若验证出错, 则转向定制的页面

            map.put("departments"departmentDao.getDepartments());

            return "input";

        }

        

        employeeDao.save(employee);

        return "redirect:/emps";

    }  

 

28)校验

 

<!--  

            1. 数据类型转换

            2. 数据类型格式化

            3. 数据校验. 

            1). 如何校验 ? 注解 ?

            ①. 使用 JSR 303 验证标准

            ②. 加入 hibernate validator 验证框架的 jar 包(hibernate-validator-annotation-processor-5.0.0.CR2.jar,hibernate-validator-5.0.0.CR2.jar

            ③. 在 SpringMVC 配置文件中添加 <mvc:annotation-driven />

            ④. 需要在 bean 的属性上添加对应的注解

            ⑤. 在目标方法 bean 类型的前面添加 @Valid 注解

            2). 验证出错转向到哪一个页面 ?

            注意: 需校验的 Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的入参

            3). 错误消息 ? 如何显示, 如何把错误消息进行国际化

        --> 

对应以上④:package com.atguigu.springmvc.crud.entities;

import java.util.Date;

import javax.validation.constraints.Past;

import org.hibernate.validator.constraints.Email;

import org.hibernate.validator.constraints.NotEmpty;

import org.springframework.format.annotation.DateTimeFormat;

import org.springframework.format.annotation.NumberFormat;

public class Employee {

    private Integer id;

    @NotEmpty

    private String lastName;

    @Email

    private String email;

    //1 male, 0 female

    private Integer gender;

    

    private Department department;

    

    @Past

    @DateTimeFormat(pattern="yyyy-MM-dd")

    private Date birth;

    

    @NumberFormat(pattern="#,###,###.#")

    private Float salary;

    public Integer getId() {

        return id;

    }

    public void setId(Integer id) {

        this.id = id;

    }

    public String getLastName() {

        return lastName;

    }

    public void setLastName(String lastName) {

        this.lastName = lastName;

    }

    public String getEmail() {

        return email;

    }

    public void setEmail(String email) {

        this.email = email;

    }

    public Integer getGender() {

        return gender;

    }

    public void setGender(Integer gender) {

        this.gender = gender;

    }

    public Department getDepartment() {

        return department;

    }

    public void setDepartment(Department department) {

        this.department = department;

    }

    public Date getBirth() {

        return birth;

    }

    public void setBirth(Date birth) {

        this.birth = birth;

    }

    public Float getSalary() {

        return salary;

    }

    public void setSalary(Float salary) {

        this.salary = salary;

    }

    @Override

    public String toString() {

        return "Employee [id=" + id + ", lastName=" + lastName + ", email="

                + email + ", gender=" + gender + ", department=" + department

                + ", birth=" + birth + ", salary=" + salary + "]";

    }

    public Employee(Integer id, String lastName, String email, Integer gender,

            Department department) {

        super();

        this.id = id;

        this.lastName = lastName;

        this.email = email;

        this.gender = gender;

        this.department = department;

    }

    public Employee() {

        // TODO Auto-generated constructor stub

    }

}

 

对应以上⑤

 

@RequestMapping(value="${pageContext.request.contextPath }/emp", method=RequestMethod.POST)

    public String save(@Valid Employee employee, Errors result

            Map<String, Object> map){

        System.out.println("save: " + employee);

        

        if(result.getErrorCount() > 0){

            System.out.println("出错了!");

            

            for(FieldError error:result.getFieldErrors()){

                System.out.println(error.getField() + ":" + error.getDefaultMessage());

            }

            

            //若验证出错, 则转向定制的页面

            map.put("departments"departmentDao.getDepartments());

            return "input";

        }

        

        employeeDao.save(employee);

        return "redirect:/emps";

    }  

 

 

在页面上显示错误消息:jsp

    <form:radiobuttons path="gender" items="${genders }delimiter="<br>"/>

        <br>

        Department: <form:select path="department.id" 

            items="${departments }" itemLabel="departmentName" itemValue="id"></form:select>

        <br>

        <!--  

            1. 数据类型转换

            2. 数据类型格式化

            3. 数据校验. 

            1). 如何校验 ? 注解 ?

            ①. 使用 JSR 303 验证标准

            ②. 加入 hibernate validator 验证框架的 jar 包

            ③. 在 SpringMVC 配置文件中添加 <mvc:annotation-driven />

            ④. 需要在 bean 的属性上添加对应的注解

            ⑤. 在目标方法 bean 类型的前面添加 @Valid 注解

            2). 验证出错转向到哪一个页面 ?

            注意: 需校验的 Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的入参

            3). 错误消息 ? 如何显示, 如何把错误消息进行国际化

        -->

        Birth: <form:input path="birth"/>

        <form:errors path="birth"></form:errors>

        <br>

        Salary: <form:input path="salary"/>

        <br>

        <input type="submit" value="Submit"/>  

 自定义错误信息:

①配置国际化文件  i18n.properties

NotEmpty.employee.lastName=^^LastName\u4E0D\u80FD\u4E3A\u7A7A.

Email.employee.email=Email\u5730\u5740\u4E0D\u5408\u6CD5

Past.employee.birth=Birth\u4E0D\u80FD\u662F\u4E00\u4E2A\u5C06\u6765\u7684\u65F6\u95F4. 

typeMismatch.employee.birth=Birth\u4E0D\u662F\u4E00\u4E2A\u65E5\u671F.     //在绑定数据时,数据类型不匹配

i18n.user=User

i18n.password=Password  

 

 

配置xm文件

<!-- 配置国际化资源文件 -->

    <bean id="messageSource"

        class="org.springframework.context.support.ResourceBundleMessageSource">

        <property name="basename" value="i18n"></property>

    </bean>

 

 

29)返回json

1. 加入 jar 包:

 

• 2. 编写目标方法,使其返回 JSON 对应的对象或集合

    @ResponseBody

    @RequestMapping("/testJson")

    public Collection<Employee> testJson(){

        return employeeDao.getAll();

    }  
• 3. 在方法上添加 @ResponseBody 注解

 以上
30)HttpMessageConverter<T>

 

 

@RequestBody :用来修饰目标方法入参@ResponseBody : 用来修改方法

HttpEntity<T> :作为目标方法的入参

 ResponseEntity<T> :目标方法的返回在值

 

根据泛型来转换使用哪个

 

 

 模拟上传下载文件:

上传:

 

<form action="testHttpMessageConverter" method="POST" enctype="multipart/form-data">

        File: <input type="file" name="file"/>

        Desc<input type="text" name="desc"/>

        <input type="submit" value="Submit"/>

    </form>  

 

 

@ResponseBody

    @RequestMapping("/testHttpMessageConverter")

    public String testHttpMessageConverter(@RequestBody String body){

        System.out.println(body);

        return "helloworld! " + new Date();

    }

      

下载:

@RequestMapping("/testResponseEntity")

    public ResponseEntity<byte[]> testResponseEntity(HttpSession sessionthrows IOException{

        byte [] body = null;

        ServletContext servletContext = session.getServletContext();

        InputStream in = servletContext.getResourceAsStream("/files/abc.txt");

        body = new byte[in.available()];

        in.read(body);

        

        HttpHeaders headers = new HttpHeaders();

        headers.add("Content-Disposition""attachment;filename=abc.txt");

        

        HttpStatus statusCode = HttpStatus.OK;

        

        ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(bodyheadersstatusCode);

        return response;

    }  


31)文件上传

<!-- 配置 MultipartResolver -->

    <bean id="multipartResolver"

        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

        <property name="defaultEncoding" value="UTF-8"></property>  //默认编码

        <property name="maxUploadSize" value="1024000"></property>    //最大上传大小

                  ……

                  ……

    </bean>      

 

jsp:

    <form action="testFileUpload" method="POST" enctype="multipart/form-data">

        File: <input type="file" name="file"/>

        Desc<input type="text" name="desc"/>

        <input type="submit" value="Submit"/>

    </form>

     

@RequestMapping("/testFileUpload")

    public String testFileUpload(@RequestParam("desc") String desc

            @RequestParam("file") MultipartFile filethrows IOException{

        System.out.println("desc: " + desc);

        System.out.println("OriginalFilename: " + file.getOriginalFilename());

        System.out.println("InputStream: " + file.getInputStream());

        return "success";

    }

33)自定义拦截器

 package com.atguigu.springmvc.interceptors;

 

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

 

import org.springframework.web.servlet.HandlerInterceptor;

import org.springframework.web.servlet.ModelAndView;

 

public class FirstInterceptor implements HandlerInterceptor{

 

/**

 * 该方法在目标方法之前被调用.

 * 若返回值为 true, 则继续调用后续的拦截器和目标方法. 

 * 若返回值为 false, 则不会再调用后续的拦截器和目标方法. 

 * 

 * 可以考虑做权限. 日志, 事务等. 

 */

@Override

public boolean preHandle(HttpServletRequest request,

HttpServletResponse response, Object handler) throws Exception {

System.out.println("[FirstInterceptor] preHandle");

return true;

}

 

/**

 * 调用目标方法之后, 但渲染视图之前. 

 * 可以对请求域中的属性或视图做出修改. 

 */

@Override

public void postHandle(HttpServletRequest request,

HttpServletResponse response, Object handler,

ModelAndView modelAndView) throws Exception {

System.out.println("[FirstInterceptor] postHandle");

}

 

/**

 * 渲染视图之后被调用. 释放资源

 */

@Override

public void afterCompletion(HttpServletRequest request,

HttpServletResponse response, Object handler, Exception ex)

throws Exception {

System.out.println("[FirstInterceptor] afterCompletion");

}

 

}

 

拦截器方法调用顺序

 

 

xml:

<mvc:interceptors>

        <!-- 配置自定义的拦截器 -->

        <bean class="com.atguigu.springmvc.interceptors.FirstInterceptor"></bean>

        

        <!-- 配置拦截器(不)作用的路径 -->

        <mvc:interceptor>

            <mvc:mapping path="/emps"/>

            <bean class="com.atguigu.springmvc.interceptors.SecondInterceptor"></bean>

        </mvc:interceptor>

        

        <!-- 配置 LocaleChanceInterceptor -->

        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>

    </mvc:interceptors>  

34).处理异常

    @ExceptionHandler({RuntimeException.class})

    public ModelAndView handleArithmeticException2(Exception ex){

        System.out.println("[出异常了]: " + ex);

        ModelAndView mv = new ModelAndView("error");

        mv.addObject("exception", ex);

        return mv;

    }  

/**

     * 1. 在 @ExceptionHandler 方法的入参中可以加入 Exception 类型的参数, 该参数即对应发生的异常对象

     * 2. @ExceptionHandler 方法的入参中不能传入 Map. 若希望把异常信息传导页面上, 需要使用 ModelAndView 作为返回值

     * 3. @ExceptionHandler 方法标记的异常有优先级的问题. 

     * 4. @ControllerAdvice: 如果在当前 Handler 中找不到 @ExceptionHandler 方法来出来当前方法出现的异常, 

     * 则将去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常. 

     */  

@ExceptionHandler({ArithmeticException.class})

    public ModelAndView handleArithmeticException(Exception ex){

        System.out.println("出异常了: " + ex);

        ModelAndView mv = new ModelAndView("error");

        mv.addObject("exception", ex);

        return mv;

    }  

 

package com.atguigu.springmvc.test;

 

import org.springframework.web.bind.annotation.ControllerAdvice;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.servlet.ModelAndView;

 

@ControllerAdvice

public class SpringMVCTestExceptionHandler {

 

@ExceptionHandler({ArithmeticException.class})

public ModelAndView handleArithmeticException(Exception ex){

System.out.println("----> 出异常了: " + ex);

ModelAndView mv = new ModelAndView("error");

mv.addObject("exception", ex);

return mv;

}

}


35)ResponseStatusExceptionResolver  (状态码异常)

 

       //可以在类上加注解,也可以在方法上加注解

      @ResponseStatus(reason="测试",value=HttpStatus.NOT_FOUND)

    @RequestMapping("/testResponseStatusExceptionResolver")

    public String testResponseStatusExceptionResolver(@RequestParam("i"int i){

        if(i == 13){

            throw new UserNameNotMatchPasswordException();

        }

        System.out.println("testResponseStatusExceptionResolver...");

        

        return "success";

    }  

 

package com.atguigu.springmvc.test;

 

import org.springframework.http.HttpStatus;

import org.springframework.web.bind.annotation.ResponseStatus;

 

@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用户名和密码不匹配!")

public class UserNameNotMatchPasswordException extends RuntimeException{

 

/**

 * 

 */

private static final long serialVersionUID = 1L;

}

 

36)DefaultHandlerExceptionResolver

对springmvc的特定异常进行处理

如NoSuchRequestHandlingMethodException、

HttpRequestMethodNotSupportedException、

HttpMediaTypeNotSupportedException、

HttpMediaTypeNotAcceptableException
等。  

 

例如:指定是请求方式是post,而不是以post方式请求的,会抛HttpRequestMethodNotSupportedException、

 

    @RequestMapping(value="/testDefaultHandlerExceptionResolver",method=RequestMethod.POST)

    public String testDefaultHandlerExceptionResolver(){

        System.out.println("testDefaultHandlerExceptionResolver...");

        return "success";

    }

      


37)SimpleMappingExceptionResolver

    @RequestMapping("/testSimpleMappingExceptionResolver")

    public String testSimpleMappingExceptionResolver(@RequestParam("i"int i){

        String [] vals = new String[10];

        System.out.println(vals[i]);

        return "success";

    }

 

 

xml:

     <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">

        <property name="exceptionMappings">

            <props>

                <prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>

            </props>

        </property>

    </bean>     

 

 

jsp:

 

<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Insert title here</title>

</head>

<body>

    <h4>Error Page</h4>

    ${requestScope.exception }

</body>

</html> 

 

37).springmvc的运行流程

 

38).springmvc需要整合spring框架吗?

<!--  

        需要进行 Spring 整合 SpringMVC 吗 ?

        还是否需要再加入 Spring 的 IOC 容器 ?

        是否需要再 web.xml 文件中配置启动 Spring IOC 容器的 ContextLoaderListener ?

        

        1. 需要: 通常情况下, 类似于数据源, 事务, 整合其他框架都是放在 Spring 的配置文件中(而不是放在 SpringMVC 的配置文件中).

        实际上放入 Spring 配置文件对应的 IOC 容器中的还有 Service 和 Dao

        2. 不需要: 都放在 SpringMVC 的配置文件中. 也可以分多个 Spring 的配置文件, 然后使用 import 节点导入其他的配置文件

    --> 

39)spring 整合springmvc

在web.xml中加入

<!-- 配置启动 Spring IOC 容器的 Listener -->

    <!-- needed for ContextLoaderListener -->

    <context-param>

        <param-name>contextConfigLocation</param-name>

        <param-value>classpath:beans.xml</param-value>

    </context-param>

    <!-- Bootstraps the root web application context before servlet initialization -->

    <listener>

        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

    </listener>

 

beans.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-4.0.xsd">

    <context:component-scan base-package="com.atguigu.springmvc">

        <context:exclude-filter type="annotation" 

            expression="org.springframework.stereotype.Controller"/>

        <context:exclude-filter type="annotation" 

            expression="org.springframework.web.bind.annotation.ControllerAdvice"/>

    </context:component-scan>

    <!-- 配置数据源, 整合其他框架, 事务等. -->

</beans> 

  40)整合的问题

①<!--  

        问题: 若 Spring 的 IOC 容器和 SpringMVC 的 IOC 容器扫描的包有重合的部分, 就会导致有的 bean 会被创建 2 次.

        解决:

        1. 使 Spring 的 IOC 容器扫描的包和 SpringMVC 的 IOC 容器扫描的包没有重合的部分. 

        2. 使用 exclude-filter 和 include-filter 子节点来规定只能扫描的注解

    --> 

<!--  

        SpringMVC 的 IOC 容器中的 bean 可以来引用 Spring IOC 容器中的 bean. 

        返回来呢 ? 反之则不行. Spring IOC 容器中的 bean 却不能来引用 SpringMVC IOC 容器中的 bean!

    -->

41) springmvc和struts的对比

   ①. Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
• ②. Spring MVC 会稍微比 Struts2 快些. Spring MVC 是基
于方法设计, 而 Sturts2 是基于类, 每次发一次请求都会实
例一个 Action.
• ③. Spring MVC 使用更加简洁, 开发效率Spring MVC确实
比 struts2 高: 支持 JSR303, 处理 ajax 的请求更方便
• ④. Struts2 的 OGNL 表达式使页面的开发效率相比
Spring MVC 更高些  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序猿的十万个为什么

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

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

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

打赏作者

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

抵扣说明:

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

余额充值