Spring MVC框架基础知识

Spring MVC 简介

Spring Web MVC是spring框架中事务用于web开发的一个模块,是Spring提供的基于MVC架构设计模式的Web开发框架,它本质上也是Servlet。

在MVC架构中controller负责持久层,(controller也可以细化dao层和service层);model负责ORM隐射;view负责视图渲染。

普通MVC框架

在web项目中三层架构:
在这里插入图片描述
controller目录下都是servlet处理业务逻辑,(也可以对servlet封装,细化为dao和service层);model是POJO,view是html,css,jsp等静态资源。

//servlet处理业务逻辑
package controller;

import com.alibaba.fastjson.JSON;
import factory.SqlsessionFactory;
import mapper.CommentMapper;
import model.Comment;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.net.URLDecoder;

@WebServlet(name = "AddCommentServlet", value = "/AddCommentServlet")
public class AddCommentServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        BufferedReader reader=request.getReader();
        String params=reader.readLine();
        String json=JSON.toJSONString(params);
        json=URLDecoder.decode(json,"utf-8");


        String list[]= json.split("&");
     
		/*mybatis对数据库的操作
        SqlSessionFactory sqlSessionFactory=new SqlsessionFactory().SqlsessionFactory();
        SqlSession session = sqlSessionFactory.openSession();
        CommentMapper mapper=session.getMapper(CommentMapper.class);
        Comment comment=new Comment();
        comment.setNickname(list[0].substring(10));
        comment.setTime(list[1].substring(5));
        comment.setPhone(list[2].substring(6));
        comment.setContent(list[3].substring(8,list[3].length()-1));
        mapper.addComment(comment);
        session.commit();
        session.close();
		*/
        response.getWriter().write("success");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

每个http请求都会有一个对应的servlet,由其实现持久化操作。再将处理的数据发送到jsp生成动态网页或者发送响应数据。

Servlet封装(controller层的dao)

Spring的核心IoC容器可以管理bean,servlet作为java web开发的类同样可以交由IoC容器管理,由于有众多的servlet,bean生产和装配式会遇到很大的困难。这是需要对servlet进行封装:

//例如:
addBookServlet
updateBookServlet
deleteBookServlet
//优化
/*
优化写具体的servlet只获取路径
book/*   在该路径下有以下方法
add
update
delete
*/

//优化的servlet就不能仅仅重写doGet等方法了,而要重写service方法,具体式:获取url,判断方法对应调用函数
public BaseServlet extends HttpServlet{
	@Override
	public void service(HttpServiceRequest req,HttpServiceResponse resp) throws ServletException{
	//获取请求路径	
	String uri=req.getRequestURI();
	//根据请求路径的最后一段调用方法
	int index=uri.lastIndexOf("/");
	String dirname=uri.substring(index+1);
	
	//现在已经获取到了方法名,问题是不同子类如何通过继承获取不同的方法名并调用方法,通过反射实现。
	/*this关键字,谁调用我,我代表谁
	*/
	//获取字节码文件
	Class<?extends BaseServlet> cls=this.getClass();
	//通过字节码文件获取方法
	Method method = cls.getMethod(dirname,HttpServiceRequest.class,HttpServiceResponse.class) ;
    //执行方法
    method.invoke(this,req,resp);
	
	//上面便于好看省略了异常
}

//其继承类
@WebSevlet("/book/*")
public bookServlet extends BaseServlet{
	public void add(HttpServiceRequest req,HttpServiceResponse resp) throws ServletException{
		...
	}
	
	public void show(HttpServiceRequest req,HttpServiceResponse resp) throws ServletException{
		...
	}
	...


	//这样http请求发送过来就会自动调用book后面同路径名的方法,不分get或post。
}
业务逻辑层封装(controller层的service)

业务逻辑层也可以直接放在servlet中,即在servlet实现对数据库的操作。但为了解耦,缩减代码的复杂型一般分开。

BookService:
public interface Service{
	public List<Book> selectAll();
}

BookServiceImple:
public BookServiceImple implements Service{
	//实现类中实现持久层操作
	static{
	SqlSessionFactory factory=SqlSessionFactoryUtils.getSqlSessionFactory();
	}
	@Override
	public List<Book> selectAll(){
		/*
		该部分式mybatis框架
		*/
		
		//新建数据库交互线程
		SqlSession session=factory.openSession();
		BookMapper mapper=session.getMapper(BookMapper.class);
		List<Book> list = mapper.selectAll();
		session.close();
		return list;
	}
}

在dao层调用service层的方法实现持久化操作:

//servlet中的show方法
private BrandService service=new ServiceImple();
...
public void show(HttpServiceRequest req,HttpServiceResponse resp) throws ServletException{
		List<Book> list=service.selectAll();
		//至此已经获取到数据库中的数据了,然后处理后发送给前端就可以了。
		...
	}

对于servlet的封装减少众多HttpServlet实现类的编写,也跟符合Resultfull设计风格,与spring框架结合式不用配置众多的servlet只需要配置,父类servlet即可更加方便IoC容器的管理。对于serive的封装也是满足与spring框架结合的需要,由上面的代码可以看出每个service的实现类都是通过new BookServiceImple()实例化的,如果没有接口ServiceIoC容器就要管理众多的XXXServiceImple()既不容易管理也不容易装配,基于java的多态性用父类声明子类:

Service service=new BookServiceImple();

就可以有不同的实现类,BoxServiceImple,PhoneServiceImpl等都可以用service声明,而IoC容器也只用管理service即可。简化了开发。

Spring MVC框架

普通MVC框架都是程序员封装的,包括Spring也得程序员来配置。Spring官方退出Spring MVC框架来解决MVC框架与spring结合的一系列问题。使用Spring MVC框架只需要程序员进行简单的配置就可以快速进行web开发。

Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架, 是结构最清晰的 Servlet+JSP+JavaBean 的实现。

既然Spring MVC是一个实用的MVC框架,Servet和Service层以及由官方封装好了,提供了各种接口来调用。其中DispatcherServlet是顶级Servlet它负责对请求的解析和分发并调用对应方法。IoC容器只管理这个顶级Servlet,其实现类通过注解和配置都可以,但一般是注解更方便。service层也定义了众多接口实现各种功能。

框架的部署及第一个程序:

  • 导入依赖包
    spring核心包、web和web-mvc的包日志包。

  • 配置DispatcherServlet顶级类web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://JAVA.sun.com/xml/ns/j2ee
        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">


    <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:spring-mvc.xml</param-value>
        </init-param>


        <load-on-startup>1</load-on-startup>


    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
  • 配置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: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-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

  
   <!--开启注解-->
    <context:annotation-config></context:annotation-config>
    <context:component-scan base-package="springdemo"/>
    <mvc:annotation-driven></mvc:annotation-driven>

</beans>
  • 编写controller的实现类
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/book")
public class BookCtrl {

    @RequestMapping("/add")
    @ResponseBody
    public String add(){
        return "add";
    }
    @RequestMapping("/list")
    public void list(){
        System.out.println("list");
    }

}

在这里插入图片描述

Spring MVC 执行流程

在这里插入图片描述
SpringMVC 的执行流程如下。

  1. 用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器);
  2. 由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain)。
  3. DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);
  4. HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
  5. Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
  6. HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
  7. DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
  8. ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
  9. DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
  10. 视图负责将结果显示到浏览器(客户端)。

Spring Web MVC框架的特点

  • 清晰的角色划分:控制器(controller)、验证器(validator)、 命令对象(command object)、表单对象(form object)、模型对象(model object)、 Servlet分发器(DispatcherServlet)、 处理器映射(handler mapping)、视图解析器(view resolver)等等。 每一个角色都可以由一个专门的对象来实现。

  • 强大而直接的配置方式:将框架类和应用程序类都能作为JavaBean配置,支持跨多个context的引用,例如,在web控制器中对业务对象和验证器(validator)的引用。

  • 可适配、非侵入:可以根据不同的应用场景,选择合适的控制器子类 (simple型、command型、form型、wizard型、multi-action型或者自定义),而不是从单一控制器 (比如Action/ActionForm)继承。

  • 可重用的业务代码:可以使用现有的业务对象作为命令或表单对象,而不需要去扩展某个特定框架的基类。

  • 可定制的handler mapping和view resolution:Spring提供从最简单的URL映射, 到复杂的、专用的定制策略。与某些web MVC框架强制开发人员使用单一特定技术相比,Spring显得更加灵活。

  • 灵活的model转换:在Springweb框架中,使用基于Map的 键/值对来达到轻易地与各种视图技术的集成。

  • JSP表单标签库:在Spring2.0中引入的表单标签库,使得在JSP中编写 表单更加容易。

  • Spring Bean的生命周期可以被限制在当前的HTTP Request或者HTTP Session。 准确的说,这并非Spring MVC框架本身特性,而应归属于Sping MVC使用的WebApplicationContext容器。

Spring的web框架围绕DispatcherServlet设计。 DispatcherServlet的作用是将请求分发到不同的处理器。 Spring的web框架包括可配置的处理器(handler)映射、视图(view)解析、本地化(local)解析、 主题(theme)解析以及对文件上传的支持。Spring的Web框架中缺省的处理器是==Controller 接口,这是一个非常简单的接口,仅包含ModelAndView handleRequest(request, response) ==方法。可以通过实现这个接口来创建自己的控制器(也可以称之为处理器),但是更推荐继承Spring提供的一系列控制器, 比如AbstractController、AbstractCommandController 和SimpleFormController。注意,需要选择正确的基类:如果没有表单,就不需要一个FormController。

DispatcherServlet

DispatcherServlet实际上是一个Servlet (它继承了HttpServlet)。与其它Servlet一样, DispatcherServlet定义在web应用的web.xml文件中。 DispatcherServlet处理的请求必须在同一个web.xml文件里使用url-mapping定义映射。

Spring的web MVC框架是一个请求驱动的web框架,其设计围绕一个中心的servlet进行, 它能将请求分发给控制器,并提供其它功能帮助web应用开发。然而,Spring的DispatcherServlet 所做的不仅仅是这些,它和Spring的IoC容器完全集成在一起,从而允许你使用Spring的其它功能。

配置DispatcherServlet

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://JAVA.sun.com/xml/ns/j2ee
        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">


    <servlet>
        <servlet-name>Spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring-mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

上面的配置中所有<url-pattern>/</url-pattern>请求都会由请求的名为spring-mvc 的DispatcherServlet处理。这只是配置Spring Web MVC 的第一步。接下来需要配置DispatcherServlet本身和Spring Web MVC 框架用到的其他的bean。他们都由IoC容器管理。
在这里插入图片描述
在DispatcherServlet的初始化过程中,框架会在web应用的 WEB-INF文件夹下寻找名为[servlet-name]-servlet.xml 的配置文件,生成文件中定义的bean。这些bean会覆盖在全局范围(global cope)中定义的同名的bean。在控制器中就可以使用这些对象。

可以在WEB-INF下创建spring-mvc.xml的spring配置文件同上spring配置文件在beans中进行bean生产和和装配。

也可以Spring MVC 的配置文件存放在应用程序目录中的任何地方,默认是WEB-INF。但需要使用 servlet 的 init-param 元素加载配置文件,通过 contextConfigLocation 参数来指定 Spring MVC 配置文件的位置,示例代码如下(这样可以将配置文件放在maven项目的resources中)。**/"表示的是任意目录
<param-value>classpath*:**/applicationContext-*.xml</param-value>表示任意目录下的以"applicationContext-"开头的XML文件。最好把所有Spring配置文件都放在一个统一的目录下:

<!-- 部署 DispatcherServlet -->
<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-servlet.xml</param-value>
    </init-param>
    <!-- 表示容器再启动时立即加载servlet -->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

此处使用 Spring 资源路径的方式进行指定,即 classpath:springmvc-servlet.xml。该 Servlet 是 DispatcherServlet 类型,它就是 Spring MVC 的入口,并通过 1 配置标记容器在启动时就加载此 DispatcherServlet,即自动启动。然后通过 servlet-mapping 映射到“/”,即 DispatcherServlet 需要截获并处理该项目的所有 URL 请求。

默认DispatcherServlet配置
如上一节中所述,对每种特殊的bean,DispatcherServlet都会维护一个默认可用的实现的列表。此信息保存在包org.springframework.web.servlet中的文件DispatcherServlet.properties中。

所有的特殊bean都有一些合理的默认值,虽然迟早您将需要对这些bean提供的一个或多个属性进行调整。例如,配置InternalResourceViewResolver设置它的前缀属性为视图文件的上级目录,这是很常见的。

不管细节,这里要理解的重要概念是,一旦在WebApplicationContext中配置了特殊的bean(比如InternalResourceViewResolver)就有效地覆盖了默认实现的列表,否则这个默认实现的列表将被用于该特殊bean类型。举例来说,如果配置了InternalResourceViewResolver则会忽略ViewResolver实现的默认列表。

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      <property name="prefix" value="/WEB-INF/jsp/" />
      <property name="suffix" value=".jsp" />
   </bean>

WebApplicationContext

使用spring MVC框架后上下文连接不再是ApplicationContext对象了而是WebApplicationContext,它仅仅是一个拥有web应用必要功能的普通 ApplicationContext。配置Bean.xml文件后通过ApplicationContext获取bean对象实现管理,伴随着DispatcherServlet产生的WebApplicationContext是获取DispatcherServlet对象的上下文连接。

Bean类型功能
控制器(Controller)实现dao层
处理器映射(Handler mapping)处理器映射包含预处理器(pre-processor), 后置处理器(post-processor)和控制器的列表,它们在符合某种条件时才被执行(例如符合控制器指定的URL)
视图解析器(View resolver)视图解析器 可以将视图名解析为对应的视图,或者说将基于字符串的逻辑视图名解析为实际的View类型
本地化解析器(LocaleResolver & LocaleContextResolver)解析客户端正在使用的区域设置以及可能的时区,以便能够提供国际化的视图
处理器异常解析器(Handler exception resolver)处理器异常解析器可以将异常对应到视图,或者实现更加复杂的异常处理逻辑
文件上传解析器(Multipart File resolver)文件上传解析器提供HTML表单文件上传功能
主题解析器(Theme resolver)主题解析器能够解析你的web应用所使用的主题,以提供个性化的布局
FlashMap管理器(FlashMapManager)存储并检索可用于将属性从一个请求传递到另一个请求的“输入”和“输出”FlashMap,通常是通过重定向
处理器适配器(HandlerAdapter)帮助DispatcherServlet调用映射到请求的处理程序,而不考虑实际被调用的这个处理程序的细节。例如,调用一个带注解的控制器需要解析各种注解,因此,HandlerAdapter的主要目的就是将DispatcherServlet从这些细节中屏蔽。

在这里插入图片描述
下面是对应于 DispatcherServlet 传入 HTTP 请求的事件序列:

  1. 收到一个 HTTP 请求后,DispatcherServlet 根据 HandlerMapping (处理器映射)来选择并且调用适当的控制器。

  2. 控制器(Controller)接受请求,并基于使用的 GET 或 POST 方法来调用适当的 service 方法。Service 方法将设置基于定义的业务逻辑的模型数据,并返回视图名称到 DispatcherServlet 中。

  3. DispatcherServlet 会从 ViewResolver(视图解析器) 获取帮助,为请求检取定义视图。

  4. 一旦确定视图,DispatcherServlet 将把模型数据传递给视图(view),最后呈现在浏览器中。

上面所提到的所有组件,即 HandlerMapping、Controller 和 ViewResolver 是 WebApplicationContext 的一部分,而 WebApplicationContext 是带有一些对 web 应用程序必要的额外特性的 ApplicationContext 的扩展。

DispatcherServlet初始化参数

参数描述
contextClass实现WebApplicationContext接口的类,当前的servlet用它来创建上下文。如果这个参数没有指定, 默认使用XmlWebApplicationContext
contextConfigLocation传给上下文实例(由contextClass指定)的字符串,用来指定上下文的位置。这个字符串可以被分成多个字符串(使用逗号作为分隔符) 来支持多个上下文(在多上下文的情况下,如果同一个bean被定义两次,后面一个优先)
namespaceWebApplicationContext命名空间。默认值是[server-name]-servlet

DispatcherServlet的处理顺序

在设立了一个DispatcherServlet并且收到一个对该特定DispatcherServlet的请求之后,这个DispatcherServlet将按如下顺序开始处理该请求:

  1. 搜索WebApplicationContext,并将其绑定为请求中的一个属性,以使控制器和进程中的其他元素能够使用它。默认情况下,绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE键下。
  2. 区域解析器被绑定到请求中,以使进程中的元素在处理请求(渲染视图、准备数据等等)时能解析要使用的区域。如果不需要区域解析则不需要该解析器。
  3. 主题解析器被绑定到请求中,以使诸如视图之类的元素确定使用哪个主题。如果不使用主题则可以忽略它。
  4. 如果指定了一个multipart文件解析器,则在该请求中检查multipart;如果找到了,则将请求包装在MultipartHttpServletRequest中,以便进程中的其他元素后续处理。
  5. 搜索合适的处理程序。如果找到了,则执行与处理程序(预处理程序、后处理程序和控制器)关联的执行链,以便准备model或渲染。
  6. 如果返回了model,则渲染视图。如果没有返回model(可能由于预处理程序或后处理程序出于安全原因拦截了该请求),不会渲染任何视图,因为该请求可能已经被完成了。

在WebApplicationContext中声明的程序异常解析器收集处理请求过程中抛出的异常。使用这些异常解析器允许您定义自定义的行为来处理异常。

控制器(Controller)

控制器的概念是MVC设计模式的一部分(确切地说,是MVC中的C)。应用程序的行为通常被定义为服务接口, 而控制器使得用户可以访问应用所提供的服务。控制器解析用户输入,并将其转换成合理的模型数据,从而可以进一步由视图展示给用户。

Spring本身包含表单控制器、命令控制器、向导型控制器等多种多样的控制器。SpringWeb框架的所有控制器都返回一个ModelAndView实例实现Model接口。

定义控制器
@Controller
@RequestMapping("/hello")
public class HelloController{
   @RequestMapping(method = RequestMethod.GET)
   public String printHello(ModelMap model) {
      model.addAttribute("message", "Hello Spring MVC Framework!");
      return "hello";
   }
}

@Controller & @RequestMapping
@Controller 注释定义该类作为一个 Spring MVC 控制器。在这里,第一次使用的 @RequestMapping 表明在该控制器中处理的所有方法都是相对于 /hello 路径的。下一个注释 @RequestMapping(method = RequestMethod.GET) 用于声明 printHello() 方法作为控制器的默认 service 方法来处理 HTTP GET 请求。你可以在相同的 URL 中定义其他方法来处理任何 POST 请求。

你可以用另一种形式来编写上面的控制器,你可以在 @RequestMapping 中添加额外的属性,如下所示:

@Controller
public class HelloController{
   @RequestMapping(value = "/hello", method = RequestMethod.GET)
   public String printHello(ModelMap model) {
      model.addAttribute("message", "Hello Spring MVC Framework!");
      return "hello";
   }
}

@Controller注解表明了一个类是作为控制器的角色而存在的。Spring不要求你去继承任何控制器基类,也不要求你去实现Servlet的那套API。注解可以认为是被标注类的原型(stereotype),表明了这个类所承担的角色。分派器(DispatcherServlet)会扫描所有注解了@Controller的类,检测其中通过@RequestMapping注解配置的方法。通过java继承或xml配置也可以实现。

@RequestMapping属性

@RequestMapping注解来将请求URL,映射到整个类上或某个特定的处理器方法上。servlet封装章节由讲解。

  • value 属性
    value 属性是 @RequestMapping 注解的默认属性,因此如果只有 value 属性时,可以省略该属性名,如果有其它属性,则必须写上 value 属性名称。

  • path属性
    path 属性和 value 属性都用来作为映射使用。

  • name属性
    name属性相当于方法的注释,使方法更易理解。

  • method属性
    method 属性用于表示该方法支持哪些 HTTP 请求。如果省略 method 属性,则说明该方法支持全部的 HTTP 请求。@RequestMapping(value = “toUser”,method = RequestMethod.GET) 表示该方法只支持 GET 请求。也可指定多个 HTTP 请求,如 @RequestMapping(value = “toUser”,method = {RequestMethod.GET,RequestMethod.POST}),说明该方法同时支持 GET 和 POST 请求。

  • params属性
    params 属性用于指定请求中规定的参数。

@RequestMapping(value = "toUser",params = "type")
public String toUser() {
    
    return "showUser";
}

/*
以上代码表示请求中必须包含 type 参数时才能执行该请求。
即 http://localhost:8080/toUser?type=xxx 能够正常访问 toUser() 方法,
而 http://localhost:8080/toUser 则不能正常访问 toUser() 方法。
*/


@RequestMapping(value = "toUser",params = "type=1")
public String toUser() {
    
    return "showUser";
}

/*
以上代码表示请求中必须包含 type 参数,且 type 参数为 1 时才能够执行该请求。
即 http://localhost:8080/toUser?type=1 能够正常访问 toUser() 方法,
而 http://localhost:8080/toUser?type=2 则不能正常访问 toUser() 方法。
*/
  • header属性
    header 属性表示请求中必须包含某些指定的 header 值。@RequestMapping(value = “toUser”,headers = “Referer=http://www.xxx.com”) 表示请求的 header 中必须包含了指定的“Referer”请求头,以及值为“http://www.xxx.com”时,才能执行该请求。

  • consumers属性
    consumers 属性用于指定处理请求的提交内容类型(Content-Type),例如:application/json、text/html。如 @RequestMapping(value = “toUser”,consumes = “application/json”)。

  • produces属性
    produces 属性用于指定返回的内容类型,返回的内容类型必须是 request 请求头(Accept)中所包含的类型。如 @RequestMapping(value = “toUser”,produces = “application/json”)。
    除此之外,produces 属性还可以指定返回值的编码。如 @RequestMapping(value = “toUser”,produces = “application/json,charset=utf-8”),表示返回 utf-8 编码。
    使用 @RequestMapping 来完成映射,具体包括 4 个方面的信息项:请求 URL、请求参数、请求方法和请求头。

  • 其中特别重要的类型是 org.springframework.ui.Model 类型
    该类型是一个包含 Map 的 Spring MVC类型。在每次调用请求处理方法时 Spring MVC 都将创建 org.springframework.ui.Model 对象。还有ViewModel和Map。

  • 请求处理方法常见的返回类型
    请求处理方法可以返回如下类型的对象:

  1. ModelAndView
  2. Model
  3. 包含模型属性的 Map
  4. View
  5. 代表逻辑视图名的 String
  6. void
  7. 其它任意Java类型
@RequestMapping注解方法参数

@PathVariable
URI模板是一个类似于URI的字符串,只不过其中包含了一个或多个的变量名。当你使用实际的值去填充这些变量名的时候,模板就退化成了一个URI。
比如说,一个这个URI模板http://www.example.com/users/{userId}就包含了一个变量名userId。将值fred赋给这个变量名后,它就变成了一个URI:http://www.example.com/users/fred

@RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    model.addAttribute("owner", owner);
    return "displayOwner";
}

为了处理@PathVariables注解,Spring MVC必须通过变量名来找到URI模板中相对应的变量。你可以在注解中直接声明:

@RequestMapping(path="/owners/{ownerId}}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
    // 具体的方法代码…
}

或者,如果URI模板中的变量名与方法的参数名是相同的,则你可以不必再指定一次。只要你在编译的时候留下debug信息,Spring MVC就可以自动匹配URL模板中与方法参数名相同的变量名。

@RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
    // 具体的方法代码…
}

一个方法可以拥有任意数量的@PathVariable注解:

@RequestMapping(path="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    Pet pet = owner.getPet(petId);
    model.addAttribute("pet", pet);
    return "displayPet";
}

当@PathVariable注解被应用于Map<String, String>类型的参数上时,框架会使用所有URI模板变量来填充这个map。

带正则表达式的URI模板
@RequestMapping注解支持你在URI模板变量中使用正则表达式。语法是{varName:regex},其中第一部分定义了变量名,第二部分就是你所要应用的正则表达式。比如下面的代码样例:

@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
    public void handle(@PathVariable String version, @PathVariable String extension) {
        // 代码部分省略...
    }
}

后缀模式匹配

Spring MVC默认采用"."的后缀模式匹配来进行路径匹配,因此,一个映射到/person路径的控制器也会隐式地被映射到/person.。这使得通过URL来请求同一资源文件的不同格式变得更简单(比如/person.pdf,/person.xml)。

矩阵变量
矩阵变量可以在任何路径段落中出现,每对矩阵变量之间使用一个分号“;”隔开。比如这样的URI:"/cars;color=red;year=2012"。多个值可以用逗号隔开"color=red,green,blue",或者重复变量名多次"color=red;color=green;color=blue"。

// GET /pets/42;q=11;r=22

@RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET)
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11

}

由于任意路径段落中都可以含有矩阵变量,在某些场景下,你需要用更精确的信息来指定一个矩阵变量的位置:

// GET /owners/42;q=11/pets/21;q=22

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET)
public void findPet(
    @MatrixVariable(name="q", pathVar="ownerId") int q1,
    @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22

}

也可以通过一个Map来存储所有的矩阵变量:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET)
public void findPet(
    @MatrixVariable Map<String, String> matrixVars,
    @MatrixVariable(pathVar="petId") Map<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 11, "s" : 23]

}

@RequestMapping支持的方法参数类型

使用@RequestMapping注解的处理方法可以拥有非常灵活的方法签名,它支持的方法参数及返回值类型。

  • HttpSession类型的会话对象(Servlet API)。使用该类型的参数将要求这样一个session的存在,因此这样的参数永不为null。
  • 当前请求的地区信息java.util.Locale,由已配置的最相关的地区解析器解析得到。在MVC的环境下,就是应用中配置的LocaleResolver或LocaleContextResolver。
  • 与当前请求绑定的时区信息java.util.TimeZone(java 6以上的版本)/java.time.ZoneId(java 8),由LocaleContextResolver解析得到。
  • 用于存取请求正文的java.io.InputStream或java.io.Reader。该对象与通过Servlet API拿到的输入流/Reader是一样的。
  • 用于生成响应正文的java.io.OutputStream或java.io.Writer。该对象与通过Servlet API拿到的输出流/Writer是一样的。
  • org.springframework.http.HttpMethod。可以拿到HTTP请求方法。
  • 包装了当前被认证用户信息的java.security.Principal。
  • 带@PathVariable注解的方法参数,其存放了URI模板变量中的值。
  • 带@MatrixVariable注解的方法参数,其存放了URI路径段中的键值对。
  • 带@RequestParam注解的方法参数,其存放了Servlet请求中所指定的参数。参数的值会被转换成方法参数所声明的类型。
  • 带@RequestHeader注解的方法参数,其存放了Servlet请求中所指定的HTTP请求头的值。参数的值会被转换成方法参数所声明的类型。
  • 带@RequestBody注解的参数,提供了对HTTP请求体的存取。参数的值通过HttpMessageConverter被转换成方法参数所声明的类型。
  • 带@RequestPart注解的参数,提供了对一个"multipart/form-data请求块(request part)内容的存取。
  • HttpEntity<?>类型的参数,其提供了对HTTP请求头和请求内容的存取。请求流是通过HttpMessageConverter被转换成entity对象的。
  • java.util.Map/org.springframework.io.Model/org.springframework.ui.ModelMap类型的参数,用以增强默认暴露给视图层的模型(model)的功能。
  • org.springframework.web.util.UriComponentsBuilder构造器对象,用于构造当前请求URL相关的信息,比如主机名、端口号、资源类型(scheme)、上下文路径、servlet映射中的相对部分(literal part)等。
  • org.springframework.web.util.UriComponentsBuilder构造器对象,用于构造当前请求URL相关的信息,比如主机名、端口号、资源类型(scheme)、上下文路径、servlet映射中的相对部分(literal part)等。
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }

@RequestMapping支持的方法返回值类型

  • ModelAndView对象,其中model隐含填充了命令对象,以及注解了@ModelAttribute字段的存取器被调用所返回的值。
  • Model对象,其中视图名称默认由RequestToViewNameTranslator决定,model隐含填充了命令对象以及注解了@ModelAttribute字段的存取器被调用所返回的值。
  • Map对象,用于暴露model,其中视图名称默认由RequestToViewNameTranslator决定,model隐含填充了命令对象以及注解了@ModelAttribute字段的存取器被调用所返回的值。
  • View对象。其中model隐含填充了命令对象,以及注解了@ModelAttribute字段的存取器被调用所返回的值。handler方法也可以增加一个Model类型的方法参数来增强model。
  • String对象,其值会被解析成一个逻辑视图名。其中,model将默认填充了命令对象以及注解了@ModelAttribute字段的存取器被调用所返回的值。handler方法也可以增加一个Model类型的方法参数来增强model。
  • void。如果处理器方法中已经对response响应数据进行了处理(比如在方法参数中定义一个ServletResponse或HttpServletResponse类型的参数并直接向其响应体中写东西),那么方法可以返回void。handler方法也可以增加一个Model类型的方法参数来增强model。
  • HttpEntity<?>或ResponseEntity<?>对象,用于提供对Servlet HTTP响应头和响应内容的存取。对象体会被HttpMessageConverters转换成响应流。
  • HttpHeaders对象,返回一个不含响应体的response。
  • Callable<?>对象。当应用希望异步地返回方法值时使用,这个过程由Spring MVC自身的线程来管理。
  • ResponseBodyEmitter对象,可用它异步地向响应体中同时写多个对象。
  • SseEmitter对象,可用它异步地向响应体中写服务器端事件。
  • StreamingResponseBody对象,可用它异步地向响应对象的输出流中写东西。

其他任何返回类型,都会被处理成model的一个属性并返回给视图,该属性的名称为方法级的@ModelAttribute所注解的字段名(或者以返回类型的类名作为默认的属性名)。model隐含填充了命令对象以及注解了@ModelAttribute字段的存取器被调用所返回的值。

上面描述了该注解定义的方法的参数和返回值类型,如何利用这些命令是西安具体的功能呢?

@RequestParam获取请求行参数

@Controller
@RequestMapping("/pets?petId=1")
public class EditPetForm {
    // ...
    @RequestMapping(method = RequestMapping.GET)
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
        model.addAttribute("petId", petId);
        return "petForm";
    }

    // ,..
}

若所注解的方法参数类型不是String,则类型转换会自动地发生。

@RequestBody获取请求体参数
请求体到方法参数的转换是由HttpMessageConverter完成的。HttpMessageConverter负责将HTTP请求信息转换成对象,以及将对象转换回一个HTTP响应体。对于@RequestBody注解,RequestMappingHandlerAdapter提供了以下几种默认的HttpMessageConverter支持:

  • ByteArrayHttpMessageConverter用以转换字节数组
  • StringHttpMessageConverter用以转换字符串
  • FormHttpMessageConverter用以将表格数据转换成MultiValueMap<String, String>或从MultiValueMap<String, String>中转换出表格数据

@ResponseBody注解映射响应体
@ResponseBody注解可被应用于方法上,标志该方法的返回值被直接写回到HTTP响应体中去(而不会被被放置到Model中或被解释为一个视图名,没有该注解会将返回值解析为jsp文件,除String会自动匹配)

@RestController注解创建REST控制器
当今让控制器实现一个REST API是非常常见的,这种场景下控制器只需要提供JSON、XML或其他自定义的媒体类型内容即可。你不需要在每个@RequestMapping方法上都增加一个@ResponseBody注解,更简明的做法是,给你的控制器加上一个@RestController的注解。

它与普通的@Controller无异,@RestController也可以与@ControllerAdvicebean配合使用。

使用HTTP实体HttpEntity
HttpEntity与@RequestBody和@ResponseBody很相似。除了能获得请求体和响应体中的内容之外,HttpEntity(以及专门负责处理响应的ResponseEntity子类)还可以存取请求头和响应头。

@RequestMapping("/something")
public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
    String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader");
    byte[] requestBody = requestEntity.getBody();

    // do something with request header and body

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("MyResponseHeader", "MyValue");
    return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}

@ModelAttribute注解
@ModelAttribute注解可被应用在方法或方法参数上。说明了方法的作用是用于添加一个或多个属性到model上。@ModelAttribute方法通常被用来填充一些公共需要的属性或数据。

@ModelAttribute方法的两种风格。在第一种写法中,方法通过返回值的方式默认地将添加一个属性;在第二种写法中,方法接收一个Model对象,然后可以向其中添加任意数量的属性。

注解在方法参数上的@ModelAttribute说明了该方法参数的值将由model中取得。在model中存在以后,请求中所有名称匹配的参数都会填充到该参数中。随意一般都携带model参数。

数据绑定
数据的绑定。WebDataBinder类能将请求参数——包括字符串的查询参数和表单字段等——通过名称匹配到model的属性上。成功匹配的字段在需要的时候会进行一次类型转换(从String类型到目标字段的类型),然后被填充到model对应的属性中。

进行了数据绑定后,则可能会出现一些错误,比如没有提供必须的字段、类型转换过程的错误等。若想检查这些错误,可以在注解了@ModelAttribute的参数紧跟着声明一个BindingResult参数。

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

请求之间使用@SessionAttributes注解,使用HTTP会话保存模型数据

类型级别的@SessionAttributes注解声明了某个特定处理器所使用的会话属性。通常它会列出该类型希望存储到session。该注解通过model对象获取参数,必须存在model参数。除了用该注解快速存储session外,由于控制器继承HttpServlet,可以同普通Servlet的getSession等方法获取及存储。此时蚕食中就要传递HttpSession了


@CookieValue注解映射cookie值
@CookieValue注解能将一个方法参数与一个HTTP cookie的值进行绑定。若注解的目标方法参数不是String类型,则类型转换会自动进行。

@RequestMapping("/display")
public void displayHeaderInfo(@CookieValue("info") String cookie) {
    //...
}

使用@RequestHeader注解映射请求头属性
在这里插入图片描述

@RequestMapping("/header")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
        @RequestHeader("Keep-Alive") long keepAlive) {
    //...
}
控制器类型

Controller
Spring控制器架构的基础是org.springframework.mvc.Controller接口:

public interface Controller {

    /**
     * Process the request and return a ModelAndView object which the DispatcherServlet
     * will render.
     */
    ModelAndView handleRequest(
        HttpServletRequest request,
        HttpServletResponse response) throws Exception;

}

可以发现Controller接口仅仅声明了一个方法,它负责处理请求并返回合适的模型和视图。

AbstractController
为提供一套基础设施,所有的Spring控制器都继承了 AbstractController ,AbstractController 提供了诸如缓存支持和mimetype设置这样的功能。

方法描述
supportedMethods指定这个控制器应该接受什么样的请求方法。通常它被设置成同时支持GET和POST,但是可以选择你想支持的方法。如果控制器不支持请求发送的方法, 客户端会得到通知(通常是抛出一个ServletException)
requiresSession表明这个控制器是否需要HTTP session才能正常工作。如果控制器在没有session的情况下接收到请求,客户端会因为抛出ServletException 而得到通知
synchronizeOnSession指定controller是否同步用户的HTTP session
cacheSeconds指定controller通知客户端对数据内容缓存的秒数,一般为大于零的整数。默认值为-1,即不缓存
useExpiresHeader指定Controller在响应请求时是否兼容HTTP 1.0 Expires header。缺省值为true
useCacheHeader指定Controller在相应请求时是否兼容HTTP 1.1 Cache-Control header。默认值为true
public class SampleController extends AbstractController {

    public ModelAndView handleRequestInternal(
        HttpServletRequest request,
        HttpServletResponse response) throws Exception {

        ModelAndView mav = new ModelAndView("hello");
        mav.addObject("message", "Hello World!");
        return mav;        
    }
}
<bean id="sampleController" class="samples.SampleController">
    <property name="cacheSeconds" value="120"/>
</bean>

MultiActionController

Spring提供了MultiActionController来将多个请求处理方法合并在一个控制器里,这样可以把相关功能组合在一起。它可以定义页面请求到控制器方法名的映射, 然后在处理相应请求时调用该方法。当你有很多比较小的且相关的功能时使用MultiActionController很方便,这样就不必为每个小功能创建 一个单独的Controller了。

其它的简单控制器
管可以继承AbstractController来实现自己的控制器,不过Spring提供的众多控制器减轻了我们开发简单MVC应用时的负担。

  • ParameterizableViewController基本上和上面例子中的一样,不同的是,可以在application context中指定返回的视图名称(从而 避免了在Java代码中的硬编码)。
  • UrlFilenameViewController会检查URL,获取文件请求的文件名,并把它作为视图名加以使用。。例如, http://www.springframework.org/index.html对应的视图文件名是index。

命令控制器
命令控制器提供了一种和数据对象交互的方式,并动态地将来自HttpServletRequest的参数绑定到指定的数据对象上。

  • AbstractCommandController- 可以使用该抽象命令控制器来创建自己的命令控制器,它能够将请求参数绑定到指定的命令对象。 这个类并不提供任何表单功能,但是它提供验证功能,并且让你在控制器中去实现如何处理由请求参数值产生的命令对象。
  • AbstractFormController- 一个支持表单提交的抽象控制器类。 使用这个控制器,可以定义表单,并使用从控制器获取的数据对象构建表单。 当用户输入表单内容,AbstractFormController将用户输入的内容绑定到命令对象,验证表单内容, 并将该对象交给控制器,完成相应的操作。它支持的功能有防止重复提交、表单验证以及一般的表单处理流程。 子类需要实现自己的方法来指定采用哪个视图来显示输入表单,哪个视图显示表单正确提交后的结果。 如果需要表单,但不想在应用上下文中指定显示给用户的视图,可使用该控制器。
  • SimpleFormController- 这是一个form controller,当需要根据命令对象来创建相应的form的时候,该类可以提供更多的支持。 可以为其指定一个命令对象,显示表单的视图名,当表单提交成功后显示给用户的视图名等.

控制器传递参数

  • 通过实体 Bean 接收请求参数
@RequestMapping("/login")
public String login(User user, Model model) {
    if ("bianchengbang".equals(user.getName())
            && "123456".equals(user.getPwd())) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}
  • 通过处理方法的形参接收请求参数
@RequestMapping("/login")
public String login(String name, String pwd, Model model) {
    if ("bianchengbang".equals(user.getName())
            && "123456".equals(user.getPwd())) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}
  • 通过 HttpServletRequest 接收请求参数
@RequestMapping("/login")
public String login(HttpServletRequest request, Model model) {
    String name = request.getParameter("name");
    String pwd = request.getParameter("pwd");
   
    if ("bianchengbang".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

  • 通过 @PathVariable 接收 URL 中的请求参数
@RequestMapping("/login/{name}/{pwd}")
public String login(@PathVariable String name, @PathVariable String pwd, Model model) {
   
    if ("bianchengbang".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}
  • 通过 @RequestParam 接收请求参数
@RequestMapping("/login")
public String login(@RequestParam String name, @RequestParam String pwd, Model model) {
   
    if ("bianchengbang".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}
  • 通过 @ModelAttribute 接收请求参数
@RequestMapping("/login")
public String login(@ModelAttribute("user") User user, Model model) {
   
    if ("bianchengbang".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

处理器映射(Handler mapping)

通过处理器映射,可以将web请求映射到正确的处理器(handler)上。 Spring内置了很多处理器映射策略,例如:SimpleUrlHandlerMapping或者BeanNameUrlHandlerMapping。 现在我们先来看一下HandlerMapping的基本概念。

HandlerMapping的基本功能是将请求传递到HandlerExecutionChain上。 首先,这个HandlerExecutionChain必须包含一个能处理该请求的处理器。 其次,这个链也可以包含一系列可以拦截请求的拦截器。 当收到请求时,DispatcherServlet将请求交给处理器映射,让它检查请求并找到一个适当的HandlerExecutionChain。 然后,DispatcherServlet执行定义在链中的处理器和拦截器(interceptor)。

Spring中最常用的两个处理器映射。 它们都是AbstractHandlerMapping的子类,同时继承了下面这些属性:

  • interceptors: 在映射中使用的拦截器列表。

  • defaultHandler: 缺省的处理器。 当没有合适的处理器可以匹配请求时,该处理器就会被使用。

  • order: 根据每个映射的order属性值 (由org.springframework.core.Ordered 接口定义),Spring 将上下文中可用的映射进行排序,然后选用第一个和请求匹配的处理器。

  • alwaysUseFullPath:如果这个属性被设成true,Spring 将会使用绝对路径在当前的servlet context中寻找合适的处理器。 这个属性的默认值是false,在这种情况下,Spring会使用当前servlet context中的相对路径。 例如,如果一个servlet在servlet-mapping中用的值是/testing/*,当alwaysUseFullPath 设成true时, 处理器映射中的URL格式应该使用/testing/viewPage.html,当这个属性设成false,同一个URL应该写成 /viewPage.html。

  • urlDecode:这个属性的默认值是true。 如果想比较编码后的路径,可以把这个属性设为false。 不过,需要注意的是,HttpServletRequest总是返回解码后的servlet路径, 与编码后的格式进行比较时可能不会匹配。

  • lazyInitHandlers:这个属性允许设置是否延迟singleton处理器的初始化工作(prototype处理器的初始化都是延迟的)。 这个属性的默认值是false。

BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping是一个简单但很强大的处理器映射,它将收到的HTTP请求映射到bean的名称(这些bean需要在web应用上下文中定义)。

<beans>
  <bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

  <bean name="/editaccount.form" class="org.springframework.web.servlet.mvc.SimpleFormController">
    <property name="formView" value="account"/>
 
  </bean>
<beans>
<web-app>
    ...
    <servlet>
        <servlet-name>sample</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

   <!-- maps the sample dispatcher to *.form -->
    <servlet-mapping>
        <servlet-name>sample</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>
    ...
</web-app>

SimpleUrlHandlerMapping

另一个更加强大的处理器映射是SimpleUrlHandlerMapping。 它在应用上下文中可以进行配置,并且有Ant风格的路径匹配功能。

<web-app>
    ...
    <servlet>
        <servlet-name>sample</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- maps the sample dispatcher to *.form -->
    <servlet-mapping>
        <servlet-name>sample</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>

    <!-- maps the sample dispatcher to *.html -->
    <servlet-mapping>
        <servlet-name>sample</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
    ...
</web-app>

上面的web.xml设置允许所有以.html和.form结尾的请求都由这个sample DispatcherServlet处理。

<beans>
        
    <!-- no 'id' required, HandlerMapping beans are automatically detected by the DispatcherServlet -->
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <value>
                /*/account.form=editAccountFormController
                /*/editaccount.form=editAccountFormController
                /ex/view*.html=helpController
                /**/help.html=helpController
            </value>
        </property>
    </bean>

    <bean id="helpController"
          class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>

    <bean id="editAccountFormController"
          class="org.springframework.web.servlet.mvc.SimpleFormController">
        <property name="formView" value="account"/>
       
    </bean>
<beans>

这个处理器映射首先将对所有目录中文件名为help.html的请求传递给helpController。 helpController是一个UrlFilenameViewController (要了解更多关于控制器的信息,请参阅第 13.3 节 “控制器”)。 对ex目录中所有以view开始,以.html 结尾的请求都会被传递给helpController。

拦截器(HandlerInterceptor)

Spring的处理器映射支持拦截器。当你想要为某些请求提供特殊功能时,例如对用户进行身份认证,这就非常有用。

处理器映射中的拦截器必须实现org.springframework.web.servlet包中的HandlerInterceptor接口。 这个接口定义了三个方法,preHandle(..),它在处理器实际执行 之前 会被执行; postHandle(..),它在处理器执行 完毕 以后被执行; afterCompletion(..),它在 整个请求处理完成 之后被执行。

拦截器可以通过interceptors属性来配置,该选项在所有继承了AbstractHandlerMapping的处理器映射类HandlerMapping都提供了配置的接口。

<beans>
    <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="officeHoursInterceptor"/>
            </list>
        </property>
    </bean>

    <bean id="officeHoursInterceptor" class="samples.TimeBasedAccessInterceptor">
     
    </bean>
<beans>
package samples;

public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter {


    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) throws Exception {
  
        response.sendRedirect("http://host.com/outsideOfficeHours.html");
        return false;
    }
}

在上面的例子中,所有被此处理器处理的请求都会被TimeBasedAccessInterceptor拦截器拦截。

Spring的拦截器适配器HandlerInterceptorAdapter让继承HandlerInterceptor接口变得更简单了。

视图与视图解析器 (View & View resolver)

所有web应用的MVC框架都有它们定位视图的方式。 Spring提供了视图解析器供你在浏览器显示模型数据,而不必被束缚在特定的视图技术上。 Spring内置了对JSP,Velocity模版和XSLT视图的支持。

视图解析器(ViewResolver)

ViewResolver和View是Spring的视图处理方式中特别重要的两个接口。 ViewResolver提供了从视图名称到实际视图的映射。 View处理请求的准备工作,并将该请求提交给某种具体的视图技术。
SpringWeb框架的所有控制器都返回一个ModelAndView实例。 Sprnig中的视图以名字为标识,视图解析器通过名字来解析视图。Spring提供了多种视图解析器。

ViewResolver描述
AbstractCachingViewResolver抽象视图解析器实现了对视图的缓存。在视图被使用之前,通常需要进行一些准备工作。 从它继承的视图解析器将对要解析的视图进行缓存。
XmlViewResolverXmlViewResolver实现ViewResolver,支持XML格式的配置文件。 该配置文件必须采用与Spring XML Bean Factory相同的DTD。默认的配置文件是 /WEB-INF/views.xml。
ResourceBundleViewResolverResourceBundleViewResolver实现ViewResolver, 在一个ResourceBundle中寻找所需bean的定义。 这个bundle通常定义在一个位于classpath中的属性文件中。默认的属性文件是views.properties。
UrlBasedViewResolverUrlBasedViewResolver实现ViewResolver, 将视图名直接解析成对应的URL,不需要显式的映射定义。 如果你的视图名和视图资源的名字是一致的,就可使用该解析器,而无需进行映射。
InternalResourceViewResolver作为UrlBasedViewResolver的子类, 它支持InternalResourceView(对Servlet和JSP的包装), 以及其子类JstlView和TilesView。 通过setViewClass方法,可以指定用于该解析器生成视图使用的视图类。
VelocityViewResolver / FreeMarkerViewResolver作为UrlBasedViewResolver的子类, 它能支持VelocityView(对Velocity模版的包装)和FreeMarkerView以及它们的子类。

当使用JSP作为视图层技术时,就可以使用UrlBasedViewResolver。 这个视图解析器会将视图名解析成URL,并将请求传递给RequestDispatcher来显示视图。其依赖于jstl需要导入依赖并注入IoC容器。

<bean id="viewResolver"
      class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceViewResolver"/> <!--不能省略-->
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

当返回的视图名为test时, 这个视图解析器将请求传递给RequestDispatcher,RequestDispatcher再将请求传递给/WEB-INF/jsp/test.jsp。

ResourceBundleViewResolver

上面的解析器特定解析器,当在一个web应用中混合使用不同的视图技术时,可以使用ResourceBundleViewResolver。

<bean id="viewResolver"
      class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
    <property name="defaultParentView" value="parentView"/>
</bean>

ResourceBundleViewResolver通过basename所指定的ResourceBundle解析视图名。 对每个待解析的视图,ResourceBundle里的[视图名].class所对应的值就是实现该视图的类。 同样,[视图名].url所对应的值是该视图所对应的URL。

视图解析链

Spring支持多个视图解析器一起使用。可以把它们当作一个解析链。 这样有很多好处,比如在特定情况下重新定义某些视图。 定义视图解析链很容易,只要在应用上下文中定义多个解析器就可以了。 必要时,也可以通过order属性来声明每个解析器的序列。 要记住的是,某个解析器的order越高, 它在解析链中的位置越靠后。

<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
  <property name="prefix" value="/WEB-INF/jsp/"/>
  <property name="suffix" value=".jsp"/>
</bean>

<bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
  <property name="order" value="1"/>
  <property name="location" value="/WEB-INF/views.xml"/>
</bean>

<!-- in views.xml -->

<beans>
  <bean name="report" class="org.springframework.example.ReportExcelView"/>
</beans>

如果某个解析器没有找到合适的视图,Spring会在上下文中寻找是否配置了其它的解析器。 如果有,它会继续进行解析,否则,Srping会抛出一个Exception。

要记住,当一个视图解析器找不到合适的视图时,它可能 返回null值。 但是,不是每个解析器都这么做。这是因为,在某些情况下,解析器可能无法侦测出符合要求的视图是否存在。 比如,InternalResourceViewResolver在内部调用了RequestDispatcher。 请求分发是检查一个JSP文件是否存在的唯一方法,不幸的是,这个方法只能用一次。 同样的问题在VelocityViewResolver和其它解析器中也有。 当使用这些解析器时,最好仔细阅读它们的Javadoc,看看需要的解析器是否无法发现不存在的视图。 这个问题产生的副作用是,如果InternalResourceViewResolver解析器没有放在链的末端, InternalResourceViewResolver后面的那些解析器根本得不到使用, 因为InternalResourceViewResolver总是返回一个视图!

重定向(Rediret)到另一个视图

一个控制器通常会返回视图名,然后由视图解析器解析到某种视图实现。 对于像JSP这样实际上由Servlet/JSP引擎处理的视图, 我们通常使用InternalResourceViewResolver和InternalResourceView。 这种视图实现最终会调用Servlet API的RequestDispatcher.forward(…)方法或RequestDispatcher.include()方法将用户指向最终页面。 对于别的视图技术而言(比如Velocity、XSLT等等),视图本身就会生成返回给用户的内容。

RedirectView
在控制器中强制重定向的方法之一是让控制器创建并返回一个Spring的RedirectView的实例。 在这种情况下,DispatcherServlet不会使用通常的视图解析机制, 既然它已经拿到了一个(重定向)视图,它就让这个视图去完成余下的工作。

RedirectView会调用HttpServletResponse.sendRedirect()方法, 其结果是给用户的浏览器发回一个HTTP redirect。所有的模型属性都被转换成以HTTP请求的访问参数。 这意味着这个模型只能包含可以被简便的转换成string形式的HTTP请求访问参数的对象,比如String或者可以被转换成String的类型。

如果使用RedirectView视图,并且它是由控制器创建的, 重定向的URL最好是用Spring所提供的IoC功能注射到控制器中。 这样这个URL就可以和视图名一起在上下文中被声明,而不是固化在控制器内。

redirect:前缀
尽管使用RedirectView帮我们达到了目的,但是如果控制器生成RedirectView的话, 控制器不可避免地要知道某个请求的结果是让用户重定向到另一个页面。这不是最佳的实现,因为这使得系统不同模块之间结合得过于紧密。 其实控制器不应该过问返回结果是如何生成的,通常情况下,它应该只关心注入给它的视图名称。

解决上述问题的方法是依靠redirect:前缀。 如果返回的视图名包含redirect:前缀,UrlBasedViewResolver (以及它的子类) 会知道系统要生成一个HTTP redirect。 视图名其余的部分会被当作重定向URL。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class WebController {
   @RequestMapping(value = "/index", method = RequestMethod.GET)
   public String index() {
       return "index";
   }   
   @RequestMapping(value = "/redirect", method = RequestMethod.GET)
   public String redirect() {     
      return "redirect:finalPage";
   }   
   @RequestMapping(value = "/finalPage", method = RequestMethod.GET)
   public String finalPage() {     
      return "final";
   }
}

forward:前缀
类似的,我们也可以使用包含有forward:前缀的视图名。 这些视图名会被UrlBasedViewResolver和它的子类正确解析。 解析的内部实现是生成一个InternalResourceView, 这个视图最终会调用RequestDispatcher.forward()方法,将forward视图名的其余部分作为URL。 所以,当使用InternalResourceViewResolver/InternalResourceView, 并且你所用的视图技术是JSP时,你没有必要使用这个前缀。 但是,当你主要使用其它的视图技术,但仍需要对Servlet/JSP engine处理的页面强制forward时, 这个forward前缀还是很有用的。

转发

控制器类中处理方法的 return 语句默认就是转发实现,只不过实现的是转发到视图。return将相当于响应。

@RequestMapping("/register")
public String register() {
    return "register";  //转发到register.jsp
}

在 Spring MVC 框架中,不管是重定向或转发,都需要符合视图解析器的配置,如果直接转发到一个不需要 DispatcherServlet 的资源,例如:

return "forward:/html/my.html";
<mvc:resources location="/html/" mapping="/html/**" />
这样配置后html目录下的文件不需要经过DispatcherServlet 处理。

Spring MVC 使用闪存属性

Flash属性(flash attributes)提供了一个请求为另一个请求存储有用属性的方法。这在重定向的时候最常使用,比如常见的 POST/REDIRECT/GET 模式。Flash属性会在重定向前被暂时地保存起来(通常是保存在session中),重定向后会重新被下一个请求取用并立即从原保存地移除。

为支持flash属性,Spring MVC提供了两个抽象。FlashMap被用来存储flash属性,而用FlashMapManager来存储、取回、管理FlashMap的实例。

对flash属性的支持默认是启用的,并不需要显式声明,不过没用到它时它绝不会主动地去创建HTTP会话(session)。对于每个请求,框架都会“传进”一个FlashMap,里面存储了从上个请求(如果有)保存下来的属性;同时,每个请求也会“输出”一个FlashMap,里面保存了要给下个请求使用的属性。两个FlashMap实例在Spring MVC应用中的任何地点都可以通过RequestContextUtils工具类的静态方法取得。

控制器通常不需要直接接触FlashMap。一般是通过@RequestMapping方法去接受一个RedirectAttributes类型的参数,然后直接地往其中添加flash属性。通过RedirectAttributes对象添加进去的flash属性会自动被填充到请求的“输出”FlashMap对象中去。类似地,重定向后“传进”的FlashMap属性也会自动被添加到服务重定向URL的控制器参数Model中去。

本地化解析器(LocaleResolver)

Spring架构的绝大部分都支持国际化,Spring的web MVC框架也不例外。 DispatcherServlet 允许使用客户端本地化信息自动解析消息。 这个工作由LocaleResolver对象完成。

当收到请求时,DispatcherServlet查找一个本地化解析器,如果找到,就使用它设置本地化信息。 通过RequestContext.getLocale()方法,总可以获取由本地化解析器解析的客户端的本地化信息。

AcceptHeaderLocaleResolver

这个本地化解析器检查请求中客户端浏览器发送的accept-language头信息, 通常这个HTTP Header包含客户端操作系统的本地化信息。

CookieLocaleResolver

这个本地化解析器检查客户端中的Cookie是否包含本地化信息。 如果有的话,就使用。当配置这个解析器的时候,可以指定cookie名,以及cookie的最长生存期(Max Age)。

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">

    <property name="cookieName" value="clientlanguage"/>
    
    <!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
    <property name="cookieMaxAge" value="100000">

</bean>

在这里插入图片描述

SessionLocaleResolver

SessionLocaleResolver允许从用户请求相关的session中获取本地化信息。它可以侦测请求中某个特定的参数,然后调用上下文中的LocaleResolver中的 setLocale()方法,相应地修改本地化信息SessionLocaleResolver仅会简单地从与当前请求HttpServletRequest相关的HttpSession对象中,取出对应的属性,并修改其值,仅此而已。

<bean id="localeChangeInterceptor"
      class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="siteLanguage"/>
</bean>

<bean id="localeResolver"
      class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

<bean id="urlMapping"
      class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="localeChangeInterceptor"/>
        </list>
    </property>
    <property name="mappings">
        <value>/**/*.view=someController</value>
    </property>
</bean>

LocaleChangeInterceptor

可以使用LocaleChangeInterceptor修改本地化信息。 这个拦截器需要被添加到处理器映射中

主题解析器(Theme resolver)

Sping的web MVC框架允许你通过主题(theme)来控制网页的风格,这将进一步改善用户的体验。 简单来说,一个主题就是一组静态的资源(比如样式表和图片),它们可以影响应用程序的视觉效果。

为了在web应用中使用主题,需要设置org.springframework.ui.context.ThemeSource。 WebApplicationContext是从ThemeSource扩展而来, 但是它本身并没有实现ThemeSource定义的方法,它把这些任务转交给别的专用模块。 如果没有明确设置,真正实现ThemeSource的类是org.springframework.ui.context.support.ResourceBundleThemeSource。 这个类在classpath的根部(比如在/WEB-INF/classes目录下)寻找合适的属性文件来完成配置。 如果想自己实现ThemeSource接口, 或者需要配置ResourceBundleThemeSource所需的属性文件的前缀名(basename prefix), 可以在应用上下文中定义一个名为"themeSource"的bean(注意,必须用这个名字)。 web application context会自动检测并使用这个bean。

在使用ResourceBundleThemeSource时,每个主题是用一个属性文件来配置的。 这个属性文件中列举了构成一个主题所需的资源。

处理器异常解析器(Handler exception resolver)

Spring的处理器异常解析器HandlerExceptionResolver接口的实现负责处理各类控制器执行过程中出现的异常。

实现HandlerExceptionResolver接口并非实现异常处理的唯一方式,它只是提供了resolveException(Exception, Hanlder)方法的一个实现而已,方法会返回一个ModelAndView

SimpleMappingExceptionResolver允许你获取可能抛出的异常类的名字,并把它映射到一个视图名上去。这与Servlet API提供的异常映射特性是功能等价的,但你也可以基于此实现粒度更精细的异常映射。

@ExceptionHandler注解的方法则会在异常抛出时被调用以处理该异常。这样的方法可以定义在@Controller注解的控制器类里,也可以定义在@ControllerAdvice类中,后者可以使该异常处理方法被应用到更多的@Controller控制器中。

如果@ExceptionHandler方法是在控制器内部定义的,那么它会接收并处理由控制器(或其任何子类)中的@RequestMapping方法抛出的异常。如果你将@ExceptionHandler方法定义在@ControllerAdvice类中,那么它会处理相关控制器中抛出的异常。

@Controller
public class SimpleController {

    // @RequestMapping methods omitted ...

    @ExceptionHandler(IOException.class)
    public ResponseEntity<String> handleIOException(IOException ex) {
        // prepare responseEntity
        return responseEntity;
    }

}

在这里插入图片描述
业务异常可以使用@ResponseStatus来注解。当异常被抛出时,ResponseStatusExceptionResolver会设置相应的响应状态码。DispatcherServlet会默认注册一个ResponseStatusExceptionResolver 以供使用。

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
 
@ResponseStatus(value=HttpStatus.FORBIDDEN,reason="用户不匹配")
public class UserNotMatchException extends RuntimeException{
}

文件上传解析器(Multipart File resolver)

Spring支持web应用中的分段文件上传。这种支持是由即插即用的MultipartResolver来实现。 这些解析器都定义在org.springframework.web.multipart包里。 Spring提供了现成的MultipartResolver可以支持Commons FileUpload。

通常情况下,Spring是不处理文件上传的,因为一些开发者想要自己处理它们。 如果想使用Spring的这个功能,需要在web应用的上下文中添加分段文件解析器。 这样,每个请求就会被检查是否包含文件上传。如果没有,这个请求就被正常的处理, 否则,应用上下文中已经定义的MultipartResolver就会被调用。 然后,请求中的文件属性就会像其它属性一样被处理。

styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html>
   <head>
      <link rel="stylesheet" href="<spring:theme code="styleSheet"/>" type="text/css"/>
   </head>
   <body background="<spring:theme code="background"/>">
      ...
   </body>
</html>

DispatcherServlet会查找一个名称为themeResolver的bean以确定使用哪个ThemeResolver的实现。主题解析器的工作原理与地区解析器LocaleResolver的工作原理大同小异。
在这里插入图片描述

MultipartResolver

<bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

    <!-- file size in bytes -->
    <property name="maxUploadSize" value="100000"/>
</bean>

CosMultipartResolver

<bean id="multipartResolver" class="org.springframework.web.multipart.cos.CosMultipartResolver">

    <!-- file size in bytes -->
    <property name="maxUploadSize" value="100000"/>
</bean>

当然,需要在classpath中为分段文件解析器提供正确的jar文件。 如果是CommonsMultipartResolver, 需要使用commons-fileupload.jar,如果是CosMultipartResolver, 则使用cos.jar

当Spring的DispatcherServlet发现文件上传请求时,它会激活定义在上下文中的解析器来处理请求。 这个解析器随后是将当前的HttpServletRequest封装成MultipartHttpServletRequest,后者支持分段文件上传。 使用MultipartHttpServletRequest,可以获取请求所包含的上传信息,甚至可以在控制器中获取分段文件的内容。

<html>
    <head>
        <title>Upload a file please</title>
    </head>
    <body>
        <h1>Please upload a file</h1>
        <form method="post" action="upload.form" enctype="multipart/form-data">
            <input type="file" name="file"/>
            <input type="submit"/>
        </form>
    </body>
</html>

表单里有一个input元素,这个元素的名字(“file”)和服务器端处理这个表单的bean(在下面将会提到)中类型为byte[]的属性名相同。 在这个表单里我们也声明了编码参数(enctype=“multipart/form-data”)以便让浏览器知道如何对这个文件上传表单进行编码。

<beans>
	<!-- lets use the Commons-based implementation of the MultipartResolver interface -->
    <bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>

    <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <value>
                /upload.form=fileUploadController
            </value>
        </property>
    </bean>

    <bean id="fileUploadController" class="examples.FileUploadController">
        <property name="commandClass" value="examples.FileUploadBean"/>
        <property name="formView" value="fileuploadform"/>
        <property name="successView" value="confirmation"/>
    </bean>

</beans>
public class FileUploadController extends SimpleFormController {

    protected ModelAndView onSubmit(
        HttpServletRequest request,
        HttpServletResponse response,
        Object command,
        BindException errors) throws ServletException, IOException {

         // cast the bean
        FileUploadBean bean = (FileUploadBean) command;

         let's see if there's content there
        MultipartFile file = bean.getFile();
        if (file == null) {
             // hmm, that's strange, the user did not upload anything
        }

         // well, let's do nothing with the bean for now and return
        return super.onSubmit(request, response, command, errors);
    }
}

public class FileUploadBean {

    private MultipartFile file;

    public void setFile(MultipartFile file) {
        this.file = file;
    }

    public MultipartFile getFile() {
        return file;
    }
}

为了将上传的二进制数据存成bean的属性, 必须通过ServletRequestDatabinder注册一个属性编辑器。 Spring中内置了几个这样的编辑器,它们可以处理文件,然后将结果存成bean的属性。 比如,StringMultipartEditor可以将文件转换成一个字符串(使用用户声明的字符集)。 ByteArrayMultipartEditor可以以将文件转换为byte数组。 他们的功能和CustomDateEditor相似。

总而言之,为了使用(HTML)表单上传文件,需要声明一个解析器,一个控制器,再将文件上传的URL映射到控制器来处理这个请求。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

xvwen

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

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

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

打赏作者

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

抵扣说明:

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

余额充值