Spring MVC项目

一、配置

(一)web.xml的配置

maven在为我们创建web项目时(具体可见博客:第一个Spring MVC的磕磕绊绊)会自动为我们生成一个web.xml,maven自动生成的web.xml是使用web 2.3的标准,在这个标准下jsp页面会自动的为我们吧EL表达式语言关闭,所以希望使用2.4版本。即将注释部分的2.3版本换成下面的2.4版本。这时候会报一个错误如下图所示。

这个错误的原因是因为xml的开始有多余的空格造成的,只要把多余的空格删除就没有问题了。

1.spring 容器的配置

在一个一般的项目中都需要使用的spring容器(hello spring mvc应该太简单所以并没有使用)

Spring容器是Spring的核心,一切Spring bean都存储在Spring容器内,并由其通过IoC技术管理。Spring容器也就是一个bean工厂(BeanFactory)。应用中bean的实例化,获取,销毁等都是由这个bean工厂管理的。

Spring容器究竟是什么。。。

org.springframework.context.ApplicationContext接口用于完成容器的配置,初始化,管理bean。一个Spring容器就是某个实现了ApplicationContext接口的类的实例。也就是说,从代码层面,Spring容器其实就是一个ApplicationContext。

在普通的JAVA工程中,我们可以通过代码显式new一个ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext来初始化一个Spring容器。

在Web工程中,我们一般是通过配置web.xml的方式来初始化Spring容器。

所以在web.xml中加入ContextLoaderListener及其它需要使用的配置文件的路径,即Spring应用上下文, 可以帮助理解理解层次化的ApplicationContext。

层次化的ApplicationContext: Spring应用上下文有一些它自己的相应的配置文件(/WEB-INF/configs/spring/applicationContext*.xml),  DispatcherServlet也有自己相关的配置文件(/WEB-INF/configs/spring/mvc-dispatcher-servlet.xml),两组配置文件就构成了他们不同的应用上下文的层次。

如上图所示,可以看到有连个web应用上下文,其中“3”有多个,它是可以理解成是我们的根的,对应的是ContexLoadListener所加载形成的上下文,为我们提供了所有应用公共使用的组件和服务,比如我们的service层、Dao层等等。这些服务是被整个应用所共享的,所以不能被局限在某一个Dispatcherservlet上下文中。

“2”是与特定Dispatcherservlet相关的上下文,比如MVC Dispatcherservlet与它相关的Controller、ViewResolver、HandlerMapping等等。

其中Dispatcherservlet是可能有多个的,而使用一个共用的通用的上下文就是为了在多个Dispatcherservlet下所共有。

有多个Dispatcherservlet的原因:互联网中需要提供的服务越来越多,比如在慕课网中,它一方面需要为学习的用分发在线的学习请求,另一方面也会为其他的机器以Web Service的方式提供课程的检索。在这种情况下,机器所需的服务场景和人显示是不同的,所以需要使用不同的Dispatcherservlet多不同的分发,从而可为不同的请求提供更好的服务 。

多个Dispatcherservlet应用:在对应的sevelet的servlet-mapping中的<url-pattern>/</url-pattern>标签内设置本个dispatcherservlet拦截的url地址(根下的请求),如<url-pattern>/ws</url-pattern>表示拦截${root}/ws/下的请求。如下图所示

(二)Dispatcherservlet相关的配置文件

根据web.xml中的配置在相应的文件夹(/WEB-INF/configs/spring)下创建相应名称的文件(mvc-dispatcher-servlet.xml),这是我们Dispatcherservlet所特定的配置,该文件的模板如下,可以根据项目的需要在<beans></beans>这对标签内添加相应的内容。

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


</beans>

1. 配置1:<context:annotation-config />

在<beans></beans>这对标签内添加<context:annotation-config />表示使用context命名空间下的 annotation-config,
作用:激活了Spring 基于注解(annotation)的依赖注入(DI)。比如:controller也是需要使用一些其他的服务的,来调用业务逻辑,这里需要使用依赖注入的方式来获取这些服务,这个配置就完成了这项功能

2. 配置2:<context:component-scan>

<context:component-scan base-package="com.mvc.demo">
	<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

整个上面这一段告诉我们DispatcherServlet上下文, 因为include-filter的限定,这里只管理@Controller类型的bean,只扫描Controller,忽略其他型的bean, 如@Service (Service会交给spring上下文来处理),(这个思想就是上面提到的层次化的上下文)

3. 配置3:HandlerMapping:

在讲解Spring MVC 概念的时候提到过

无需配置, Spring MVC可以默认启动。不进行配置将会启用默认配置。默认配置中会启动类:DefaultAnnotationHandlerMapping,这个类可以解析基于注解的AnnotationMapping

4. 配置4:<mvc:annotation-driven />

在<beans></beans>这对标签内添加<mvc:annotation-driven />,表示扩充了注解驱动,就可以将请求参数绑定到控制器参数,也就是说你的url查询参数中的某一个变量可以直接映射到controller中的某一个方法的输入参数。

5. 配置5:<mvc:resources>

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

静态资源处理,静态资源包括 css, js, imgs。没有这项配置将无法获取静态资源,这里将/resources/** 映射到本地目录/resources/下,这里一般放置我们的css, js, imgs。

6. 配置6:ViewResolver

在一个DispatcherServlet中可以依次配置多个ViewResolver,使用order属性排序。注意:InternalResourceViewResolver需要放在最后,因为它必定会返回一个对象,这个对象有可能是我们所需要的。也可以只配置一个ViewResolver(即InternalResourceViewResolver)。如下所示。

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

注: 以上配置都直接添加在<beans></beans>标签内

(三)Spring上下文相关的配置文件

用来放置它或者它的兄弟配置文件,就共同组成了我们整个应用中通用的组件和spring的bean管理。基础文件与Dispatcherservlet上下文配置文件一样。

1. 配置1:<context:annotation-config />: 

使它来启动基于annotation的DI管理

2.配置2:<context:component-scan>

<context:component-scan base-package="com.mvc.demo">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

这里用了exclude-filter,告诉它我们不需要管理Controller了,因为Controller前面通过include-filter交给Dispatcherservlet管理了。

二、Controller的开发

本章以开发一个课程管理系统为例。

(一)预备内容

需要新建以下几类文件

  • model:课程相关的类如course.java。可以根据自己的目的总结业务需求
  • service:课程相关的业务服务类,如courseService.java(可以包括方法:根据Id来获取课程getCourseId(Integer CourseId))
  • impl:业务服务类的实现类,如courseServiceImpl.java,我们的例子中没有使用数据库,直接使用模拟的bean代码的方式来完成某一个课程的组装。
  • 展示:course_overview.jsp,jsp的位置对应于Dispatcherservlet上下文配置文件(mvc-dispatcher-servlet.xml)中ViewResolver中文件前后缀的配置/WEB-INF/jsps/。在jsp内通过jstl标签和EL表达式完成对这个对象的输出。在jsp中使用到css静态文件,加载的位置放在Dispatcherservlet上下文配置文件中<mvc:resources>标签内配置的位置(前面配置的是/resources/)。本例中的具体位置如下:
<link rel="stylesheet"	href="<%=request.getContextPath()%>/resources/css/main.css" type="text/css" />
  • log4j的配置:放在项目的resources下(${projectName}/src/main/resources),区别于前面静态文件的放置位置(${projectName}/src/main/webapp/resources)这是一种标准的放置方式.

具体文件如下:

1.course.java

package com.mvc.demo.model;

import java.util.List;

public class Course {
	private Integer CourseId;
	private String title;
	private String imgPath;
	private Integer learningNum;
	private Long duration;
	private Integer level;
	private String levelDesc;
	private String descr;
	private List<Chapter> chapterList;
	public Integer getCourseId() {
		return CourseId;
	}
	public void setCourseId(Integer courseId) {
		CourseId = courseId;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getImgPath() {
		return imgPath;
	}
	public void setImgPath(String imgPath) {
		this.imgPath = imgPath;
	}
	public Integer getLearningNum() {
		return learningNum;
	}
	public void setLearningNum(Integer learningNum) {
		this.learningNum = learningNum;
	}
	public Long getDuration() {
		return duration;
	}
	public void setDuration(Long duration) {
		this.duration = duration;
	}
	public Integer getLevel() {
		return level;
	}
	public void setLevel(Integer level) {
		this.level = level;
	}
	public String getLevelDesc() {
		return levelDesc;
	}
	public void setLevelDesc(String levelDesc) {
		this.levelDesc = levelDesc;
	}
	public String getDescr() {
		return descr;
	}
	public void setDescr(String descr) {
		this.descr = descr;
	}
	public List<Chapter> getChapterList() {
		return chapterList;
	}
	public void setChapterList(List<Chapter> chapterList) {
		this.chapterList = chapterList;
	}
	
	

}

2. courseService.java

package com.mvc.demo.service;

import org.springframework.stereotype.Service;

import com.mvc.demo.model.Course;
@Service
public interface CourseService {
	Course getCoursebyId(Integer courseId);
}

 3. courseServiceImpl.java

package com.mvc.demo.service.impl;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Service;

import com.mvc.demo.model.Chapter;
import com.mvc.demo.model.Course;
import com.mvc.demo.service.CourseService;

@Service("courseService")
//public class CourseServiceImpl {
public class CourseServiceImpl implements CourseService{
public Course getCoursebyId(Integer courseId) {
		
		Course course = new Course();
		
		course.setCourseId(courseId);
		course.setTitle("深入浅出Java多线程");
		course.setImgPath("resources/imgs/course-img.jpg");
		course.setLearningNum(12345);
		course.setLevel(2);
		course.setLevelDesc("中级");
		course.setDuration(7200l);
		course.setDescr("多线程是日常开发中的常用知识,也是难用知识。bala bala...");
		
		List<Chapter> chapterList = new ArrayList<Chapter>();
		
		warpChapterList(courseId,chapterList);
		
		course.setChapterList(chapterList);
		
		return course;
	}

	private void warpChapterList(Integer courseId,List<Chapter> chapterList) {
		Chapter chapter = new Chapter();
		chapter.setId(1);
		chapter.setCourseId(courseId);
		chapter.setOrder(1);
		chapter.setTitle("第1章 多线程背景知识介绍");
		chapter.setDescr("本章将介绍与多线程编程相关的背景概念");	
		chapterList.add(chapter);
		
		chapter = new Chapter();
		chapter.setId(2);
		chapter.setCourseId(courseId);
		chapter.setOrder(2);
		chapter.setTitle("第2章 Java 线程初体验");
		chapter.setDescr("Java语言层面对线程的支持,如何创建,启动和停止线程。如何使用常用的线程方法。用隋唐演义理解线程的代码");
		chapterList.add(chapter);
		
		chapter = new Chapter();
		chapter.setId(3);
		chapter.setCourseId(courseId);
		chapter.setOrder(3);
		chapter.setTitle("第3章 Java 线程的正确停止");
		chapter.setDescr("本章讨论如何正确的停止一个线程,既要线程停得了,还得线程停得好。");		
		chapterList.add(chapter);
		
		chapter = new Chapter();
		chapter.setId(4);
		chapter.setCourseId(courseId);
		chapter.setOrder(4);
		chapter.setTitle("第4章 线程交互");
		chapter.setDescr("争用条件,线程的交互,及死锁的成因及预防。");		
		chapterList.add(chapter);
		
		chapter = new Chapter();
		chapter.setId(5);
		chapter.setCourseId(courseId);
		chapter.setOrder(5);
		chapter.setTitle("第5章 进阶展望");
		chapter.setDescr("简单介绍 Java 并发相关的类,及常用的多线程编程模型。");		
		chapterList.add(chapter);
		
	}
}

4.course_overview.jsp

<%@ page language="java" 
	contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!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>我不是真正的慕课网</title>

<link rel="stylesheet"
	href="<%=request.getContextPath()%>/resources/css/main.css"
	type="text/css" />
</head>
<body>
	<div id="main">
		<div class="newcontainer" id="course_intro">
			<div class="course-title">${course.title}</div>
			<div class="course_info">
				<div class="course-embed l">
					<div id="js-course-img" class="img-wrap">
						<img width="600" height="340" alt=""
							src="<%=request.getContextPath()%>/${course.imgPath}"
							class="course_video" />
					</div>
					<div id="js-video-wrap" class="video" style="display: none">
						<div class="video_box" id="js-video"></div>
					</div>
				</div>
				<div class="course_state">
					<ul>
						<li><span>学习人数</span> <em>${course.learningNum }</em></li>
						<li class="course_hour"><span>课程时长</span> <em class="ft-adjust"><span>${course.duration }</span>秒</em></li>
						<li><span>课程难度</span> <em>${course.levelDesc }</em></li>
					</ul>
				</div>
<!--  
				<div class="course_intro">
					<div class="concerned_course add_concerned_course">
						<a href="javascript:void(0)" data-id="202"
							class="btn-add-follow js-btn-follow"> <i></i> <em
							class="concerned-icon">关注此课程</em>
						</a>
					</div>
					<div class="curse_btn">
						<a href="#">开始学习</a>
					</div>
				</div>
	-->			
			</div>
			<div class="course_list">
				<div class="outline">
					<h3 class="chapter_introduces">课程介绍</h3>
					<div class="course_shortdecription">${course.descr}</div>

					<h3 class="chapter_catalog">课程提纲</h3>
					<ul id="couList">
						<c:forEach items="${course.chapterList}" var="chapter">
							<li class="clearfix open"><a href="#">
									<div class="openicon"></div>
									<div class="outline_list l">
										<!-- <em class="outline_zt"></em> -->
										<h5 class="outline_name">${chapter.title }</h5>
										<p class="outline_descr">${chapter.descr }</p>
									</div>
							</a></li>
						</c:forEach>
					</ul>
				</div>
			</div>
		</div>
	</div>
</body>
</html>

5.log4j.properties

log4j.appender.Cons=org.apache.log4j.ConsoleAppender
log4j.appender.Cons.layout=org.apache.log4j.PatternLayout
log4j.appender.Cons.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

# Root logger set to DEBUG using the A2 appender defined above.
log4j.rootLogger=info, Cons 
log4j.additivity=false

#Application Logger+
log4j.logger.com.mvc.demo=debug, Cons
#log4j.logger.org.springframework=debug, Cons
log4j.additivity.com=false

目录结构可以如下:

 (二)具体开发

场景:根据课程Id查询课程详情

1、courseController业务逻辑实现

(1)业务的依赖,就是courseService,需要依赖这个service来完成我们具体课程的查询功能,同时为它编写set方法,并将它声明为@Autowired(一种注解),这样就可以使用spring的容器管理了我们依赖关系

(2)提供一个logger完成日志信息,我们需要的是sl4j这个日志库,而这个库现在还没有添加到我们的依赖管理中,所以需要在pom.xml依赖管理中添加我们需要的api。

pom.xml中添加依赖管理

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>${slf4j.version}</version>
</dependency>

courseController.java 

package com.mvc.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import com.mvc.demo.service.CourseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CourseController {

	private static Logger log = LoggerFactory.getLogger(CourseController.class);
	private CourseService courseService;

	@Autowired
	public void setCourseService(CourseService courseService) {
		this.courseService = courseService;
	}
}

 (3)编写业务的方法viewCourse,完成根据某一个课程Id查询课程内容的业务逻辑。传入参数Integer courseId和Model model(Spring MVC特有的类型,我们想model中添加我们模型对象),我们可以直接通过courseService检索我们对应的课程,将检索到的课程放到model中,通过model的addAttribute方法,加入course的对象(直接使用小写的course的名称)(在jsp页面上就是直接使用这个名称course。通过请求中的Attribute Name,获得课程对象)。

之后,就可以返回到我们的页面,由于我们使用了spring MVC的 InternalResourceViewResolver,配置了它的前缀和后缀,所以返回的时候只需要给定名称就可以了。我们是使用course_overview.jsp展示我们的信息,所有我们只需要返回 view的名字就是jsp的名称部分。到此业务逻辑实现完毕。方法的代码如下:

public String viewCourse(Integer courseId ,Model model){
		Course course = courseService.getCoursebyId(courseId);
		model.addAttribute(course);
		return "course_overview";
}

2. 初级响应

有了清晰的业务逻辑怎么样让SpringMVC识别并让它为我们提供服务,需要一些注解配置使得Spring识别这个controller,并将我们的请求映射到正确的方法。

(1)告诉Spring的dispatcherServlet这是一个Controller,在CourseController 类上添加注解,用@Controller标签来完成这件事,在它标识为一个Controller之后它会被spring的dispatcherServlet的上下文所管理,并且完成它的依赖注入。

(2)指明这个controller应该负责处理哪一个或者哪一种类型的url,因此我们在类级别上添加一个标签,@RequestMapping,它处理这个controller处理的根url,@RequestMapping("\courses")表示:是根下的courses,任何/courses/**下的路径都会被这个controller所拦截,这是类级别的RequestMapping。

(3)映射到方法。在方法上也添加一个方法级别的RequestMapping。方法级别的RequestMapping和类级别的RequestMapping一起构成我们最终要处理的URL,这里添加一个value属性,value="/view",它处理的url就是:/courses/view;同时可以进一步的限制它,我们只负责处理从 get方法过来的请求,所以可以添加一个属性method=RequestMethod.GET。这样整个注解表示该方法仅仅处理/courses/view 这个URL,并且是由get方法传递过来的请求 

(4)查询参数的处理。比如get请求带了一个参数(查询字符串),如courseId这个关键属性,即url为: /courses/view?courseId=123,这个url需要被我们的方法识别。就可以使用参数上绑定的标签:@RequestParam,可以显示指明属性的名称,即查询字符串的查询变量,如: @RequestParam("courseId")将我们查询字符串的变量绑定到我们函数的入参上。

 通过log的输出断定这个courseId就是我们传入的courseId。通过日志用来准确的观察到这个绑定行为。

(5)第一阶段测试,参照《第一个Spring MVC的磕磕绊绊》的启动方法,可以mvn jetty:run ,也可以部署到Tomcat运行。启动成功后在浏览器输入url地址。

jetty:http://localhost:8080/courses/view?courseId=123

Tomcat:http://localhost:8080/spring-mvc-study/courses/view?courseId=123

正确结果:跳转到课程页面,同时控制台日志输出了查询变量绑定的字符串是courseId=123,验证了我们的绑定是成功的。将我们查询字符串的变量绑定到了我们函数的入参上。

3.响应rest风格的URL 

上一节中响应的url形式太老土,现在查询字符串时大多使用rest风格的URL。
(1)新写一个函数,其中使用另一种类型Map<String, Object>来处理我们的model。(在讲Spring MVC概念中提到过,SpringMVC中有提到过model有三种形式),由于是Map所以第二步在model中添加属性时使用put,属性名需要显示的制定为jsp中使用的属性名:course
 此时,期望处理的url为:/courses/view/{courseId},对将要绑定到url中某一个部分的入参通过标签@PathVariable("courseId") 来处理,同时在url路径中显示的声明url路径的形式,使用{}包裹courseId表示{courseId}是一个路径变量,同时在方法中使用@PathVariable("courseId") 指明该路径变量,对应于这个入参Integer 。这样Spring就帮助我们完成了变量的绑定和类型的转换。

(2)测试 

jetty:http://localhost:8080/courses/view/123

Tomcat:http://localhost:8080/spring-mvc-study/courses/view/123

结果应该与上一节的结果一样。

4.实现HttpServletRequest对象

想要在代码中实现最古老的HttpServletRequest对象,如何在Spring MVC中实现类似的集成,具体步骤如下:
 (1)方法接受HttpServletRequest类型的参数,由于HttpServletRequest需要依赖javax.servlet包,该包还未引入,所以需要到pom.xml的依赖管理中引入相应的servlet api。内容如下:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
</dependency>

(2)通过request来获取查询参数courseId
(3)根据couserId检索课程
(4)将检索到的课程放到request中供我们的页面使用
(5)使用类似的日志表明我们courseId是通过我们Request 获取到的
(6)告诉springMVC方法处理的URL,添加RequestMapping注解

代码实现如下,该方法将处理的URL形式为:/courses/view3?courseId=123

@RequestMapping("/view3")   //(6)
public String viewCourse3(HttpServletRequest request){    //(1)
    Integer courseId = Integer.valueOf(request.getParameter("courseId"));    //(2)
    log.debug("In viewCourse3 courseId = {}",courseId);    //(5)
    Course course = courseService.getCoursebyId(courseId);    //(3)
    request.setAttribute("course", course);    //(4)
    return "course_overview";
}

可以按照第2节,第3节的方式做类似的测试。

5. 小结 

(1)URL请求模板

使用三种不同形式的开发一个controller,发现URL请求模板非常的强大,既可以书写一个传统样式的URL也可以书写一个restfor形式的url,甚至可以使用传统的ServletRequest对象获取我们的参数。

(2)注解的使用

  •   @Controller 声明一个Controller
  •   @RequestMapping 映射了URL和方法,通常出现在类级别和方法级别,二者组合完成了我们对URL请求的拦截
  •   URL 模板结合使用参数上的标记(@RequestParam 和@PathVariable)可以将URL路径中的参数绑定到controller 的method中的入参
  •   可以通过HTTPServletRequest and/or HttpSession 获取我们想要的结果

三、绑定

绑定:将请求中的字段按照名字匹配的原则填入模型对象

我们的页面是由一些html标签代表的各种页面控件所组成的,都是平坦的对象(?我也不了解什么是平坦的对象),比如说input字段是有一个名字的。页面上所有的字段共同构成表单数据,而后台是由模型对象代表了现实世界,将二者关联起来就是绑定。

(一)编写一个课程添加及维护的界面

1.编写一个“跳转到添加页面”的方法:该方法会将我们引向一个编辑页面edit.jsp.

(1)直接返回需要的页面,比如需要返回course_admin/edit.jsp页面,直接return "course_admin/edit",这里展示了如何映射到多目录结构的jsp管理方式,即直接在根下书写相对路径即可。
(2)按照Controller的写法,为方法指定细粒度的URL, 指定value和method,同时增加一个限制,要求它应该有一个请求参数 "add"。

方法代码如下:相应的url为:http://localhost:8080/spring-mvc-study/courses/admin?add(Tomcat中)

@RequestMapping(value="/admin",method = RequestMethod.GET,params = "add")
public String createCourse(){
    return "course_admin/edit";//展示了如何去映射多目录结构jsp管理方式,直接在根下书写相对路径即可
}

edit.jsp代码如下:

<%@ page language="java" 
	contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!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>我不是真正的慕课网</title>
<!-- 使用了工程中的main.css静态文件 -->
<link rel="stylesheet"
	href="<%=request.getContextPath()%>/resources/css/main.css"
	type="text/css" />
</head>
<body>
	<div id="main">
		<div class="newcontainer" id="course_intro">
		<!--form表单制定了后端controller所要处理的URL路径  ,method中指定了这是一个post方法,表单会通过post的方式到达controller
		注意页面对象中的内容属性应该与模型(model)对象中的属性一致-->
		  <form name="mainForm" action="<%= request.getContextPath()%>/courses/save" method="post">
		    <div>
		       <span>课程名称:</span><input type="text" id="title" name="title">
		    </div>
		    <div>
		       <span>课程时长:</span><input type="text" id="duration" name="duration"> 秒
		    </div> 
		    <div>
		       <span>课程难度:</span>
		       <select id="level" name="level">
                  <option value="0">初级</option>
                  <option value="1" selected="selected">中级</option>
                  <option value="2">高级</option>
                </select>
		    </div> 
		    <div>
		       <span>课程介绍:</span>
		       <textarea id="descr" name="descr" rows="5" style="width:480px"></textarea>
		    </div> 
		    <div>
		    <!-- 最后的提交按钮将我们的请求提交到我们的/courses/save -->
		       <input type="submit" id="btnPass" value="提交" />
		    </div> 
		  </form>
		</div>
	</div>
</body>
</html>

 说明:

(1)edit.jsp在<body>中存放了form表单,form表单指定了后端controller所要处理的URL路径,同时method中指定了这是一个post方法,表单会通过post的方式到达controller;

(2)注意页面控件的内容属性名称(如name="title",name="duration")应该与模型(model)对象中的属性名称一致,因为绑定是遵循名称匹配。

(3)提交按钮<input type="submit" id="btnPass" value="提交" />将我们的请求提交到我们的/courses/save(<form>标签中指定的url),因此我们的controller中需要提供这样的一个“/save”的url的拦截处理。
    
2.“/save” URL的拦截处理

(1)编写一个方法(我们取名为dosave),拦截的URL的value为“/save”,表单传过来的方法为POST方法。

(2)从表单获取模型对象,按照Spring MVC的写法在参数里加上一个对象(Course course)

(3)在方法里可以做一些业务操作,比如数据库持久化操作,我们仅仅做一个简单些写法,设置course的Id,即setCourseId。

@RequestMapping(value="/save",method = RequestMethod.POST)
public String doSave(Course course){
    //进行业务操作如数据库持久化
    course.setCourseId(123);
    return "";
}

(4)完成业务操作之后希望返回一个界面,显示当前操作的结果,古老的代码编写方式中采用RequestDespatcher的类进行转发或者重定向。

请求转发与请求重定向:
以请求重定向为例:

方法:不需要使用任何特殊的类只需要使用一个字符串redirect+:+路径,如,redirect:view2/123

可以设置一个日志信息确认是否真的拦截到了表单的数据,并且真正的把它绑定到了我们我们的模型对象里。具体实现时我们需要用到apache.commons.lang.builder包中的一个类:ReflectionToStringBuilder,它有一个toString的方法,该方法接收的参数就是某个对象。这个类在我们进行日志输出来调试我们的程序的时候非常常见,本例中它接收的参数就是课程类,它会将课程类的内容以键值对的形式输出到日志中,借此我们可以观察我们是否已经完整的映射到了对象 。

@RequestMapping(value="/save",method = RequestMethod.POST)
public String doSave(Course course){
    log.debug("Info of Course:");
    log.debug(ReflectionToStringBuilder.toString(course));
    //进行业务操作如数据库持久化
    course.setCourseId(123);
    //请求重定向
    return "redirect:view2/"+course.getCourseId();
}

测试:

(1)按照前面介绍的方法启动项目

(2)以Tomcat为例,在浏览器中输入:http://localhost:8080/spring-mvc-study/courses/admin?add,输入课程信息,点击“提交”按钮。后台通过一系列的处理之后页面会跳转到展示页面。可以在后台看到日志的输出course对象的内容。

可以按照前面的方法进行测试。

附:springmvc中也提供了做类似的工作标签:@ModelAttribute,没有参数,是一个放在方法参数级别的标签,通过它一样可以完成绑定参数的过程。

@RequestMapping(value="/save",method = RequestMethod.POST)
public String doSave(@ModelAttribute Course course){
    log.debug("Info of Course:");
    log.debug(ReflectionToStringBuilder.toString(course));
    //进行业务操作如数据库持久化
    course.setCourseId(123);
    //请求重定向
    //return "redirect:view2/"+course.getCourseId();
    //请求转发
    return "forward:/view2/"+course.getCourseId();
}

(二)总结:

本章讲了在controller参数上通过@ModelAttribute标签实现模型对象和页面数据的绑定,同时介绍了如何在springmvc中重定向和请求转发(redirect/forward)。
  
问题:感觉标签的作用没有体现出来,使用和不使用一样 别人的回答,我看不懂:@ModelAttribute通常使用在Controller方法的参数注解中,用于解释model entity.此时分两种情况:从Model中获取或者从Form表单/URL参数中获取,如果是后者,则不添加此注释实际也能拿到对象。但同时@ModelAttribute也可以放在方法注解里, 如果把@ModelAttribute放在方法的注解上时,代表的是:该Controller的所有方法在调用前,先执行此@ModelAttribute方法。

四、单文件上传

场景:图片使如何上传到服务器的呢?这个问题就引出“文件上传”。

Spring MVC我们提供了文件上传的内置支持,将它作为一个公共的服务,我们只需要一些简单的配置,之后就可以使用Spring暴露给我们的接口来实现文件上传的工作。下面看一下Spring是如何完成这些配置和编码工作。

(一)配置dispatcher相关的上下文

找到与dispatcher相关的上下文,配置这样的一个bean——CommonsMultipartResolver,专门为我们上载的文件也就是Multipart部分。这个bean可以配置几个参数,本例中我们配置了maxUploadSize(限制上传文件大小),defaultEncoding(默认编码),resolveLazily(是否使用延迟加载,可以提高性能,在真正需要的时候才进行上传文件的解析)。

<!--resolveLazily属性启用是为了推迟文件解析,以便捕获文件大小异常 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--参数: -->
    <property name="maxUploadSize" value="209715200" /><!--200*1024*1024=200MB -->
    <property name="defaultEncoding" value="UTF-8" />
    <!--是否延迟加载,我们选择true,用于提高性能,在真正需要的时候再进行上传文件的解析 -->
    <property name="resolveLazily" value="true" />
</bean>	

(二)配置依赖管理

上一节中的bean叫commons,它依赖的了Apache.commons.fileupload这个包,所以需要在依赖管理中引入这个包 在pom.xml依赖管理中引入Apache.commons.fileupload这个包。

<dependency>
	<groupId>commons-fileupload</groupId>
	<artifactId>commons-fileupload</artifactId>
	<version>1.3.1</version>
</dependency>

此时在Dependency Hierarchy标签下可以看到这个包commons-fileupload又依赖于Apache的commons.io包,刚好我们编码中也需要使用这个包,我们就不需要显式的引入它了。

(三)编写文件上传的访问入口

引入包之后,在controller中为文件上传提供一个简单的访问入口,编写showUploadPage方法,让它直接跳转到course_admin/file.jsp(放置在jsps/course_admin下),代码如下:

@RequestMapping(value="uplod",method=RequestMethod.GET)
public String showUpload(){
    return "course_admin/file";
}

file.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>我不是真正的慕课网</title>

<link rel="stylesheet" href="<%=request.getContextPath()%>/resources/css/main.css" type="text/css" />
</head>
<body>
<div align="center">

<h1>上传附件</h1>
<form method="post" action="/courses/doUpload" enctype="multipart/form-data">
<input type="file" name="file"/>
<input type="submit"/>
</form>
</div>
</body>
</html>

说明:

(1)file.jsp里面有一个表单,特别之处在于:form中加入了 enctype="multipart/form-data",这是我们在文件上传时必须显式指明的一个属性,如果没有这个属性就无法完成文件上传工作。

(2)action指明了我们要访问的URL是:/courses/doUpload,显然我们要为我们的controller提供一个方法,来响应这个url,并且希望文件上传之后来到“success”页面(放置在jsps文件夹下)。

success.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>
<link rel="stylesheet" href="<%=request.getContextPath()%>/resources/css/main.css" type="text/css" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<div align="center">
<h1>Success</h1>
</div>
</body>
</html>

(四)doUpload响应方法的编写

1.方法初始框架

在controller中编写一个方法响应"/doUpload"这个URL,期望的返回界面是“success.jsp”,所以直接返回“success”,同时需要有一个映射,设置value="/doUpload",同时文件上传我们希望是一个post的方式得来的,所以响应的方法是post。

@RequestMapping(value="/doUpload",method=RequestMethod.POST)
public String doUploadFile(MultipartFile file){
    return "success";
}

前面提到过通过一些简单的配置,就可以直接通过我们的Spring MVC暴露给我们的接口来完成编程这个接口就是类:MultipartFile,这是Springmvc提供的一个类(可以按F3去看看源代码:如何用maven下的源代码可以自己在网上查),可以看到这个类提供以下一些方法,访问文件(点击eclipse中的Outline查看)。

  •  getName():获取文件名
  •  getOriginalFilename():本地文件名
  •  getContentType():文件类型
  •  getBytes():文件二进制
  •  getSize():文件大小
  •  isEmpty():是否为空文件
  •  getInputStream():获取流,进行读取操作

2.MultipartFile接口与表单元素关联

可以在方法参数上使用@RequestParam("file")使其与表单元素相关联,这个“file”与页面中的控件属性名称相对应。jsp页面中控件 <input type="file" name="file"/>中name="file"。由于是post方法,Spring MVC会将请求中name为file的元素的值绑定到我们controller的参数上,它的类型会被转化成MultipartFile,中间的一切复杂的过程由Spring MVC完成,我们只需使用这个暴露给我们的接口MultipartFile。

@RequestMapping(value="/doUpload",method=RequestMethod.POST)
public String doUploadFile(@RequestParam("file") MultipartFile file){
    return "success";
}

3.  MultipartFile接口具体使用

考虑实际情况:服务器端获取了某个文件之后一定是要将它存储到某个地方,供我们其他应用所使用,所以需要把文件拷贝到服务器的某个挂载点供我们的web服务使用,本例中把它拷贝到H盘的某个目录H:\temp,拷贝到这里后就可以供其他的应用使用。

(1)判断文件是否为空

(2)文件不为空,将它拷贝指定的位置,使用FileUtils做文件的拷贝(commons.io中的类,它还可以做其他文件流的操作),接收两个参数,一个是文件拷贝源的流(使用MultipartFile提供的方法getInputStream()),一个输出地址(服务器的本地地址)使用new File,文件地址就是“H:\temp”,文件名将我们原始的文件名前面加上时间戳。

(3)处理异常,直接抛出异常,让上一级去处理这个事情

@RequestMapping(value="/doUpload",method=RequestMethod.POST)
public String doUploadFile(MultipartFile file) throws IOException{
    if(!file.isEmpty()){
        log.debug("process file {}",file.getOriginalFilename());
        FileUtils.copyInputStreamToFile(file.getInputStream(), new File("H:\\temp", System.currentTimeMillis()+file.getOriginalFilename()));
    }
    return "success";
}

 spring MVC的强大之处,它并没有限制controller方法的形式,可以用任意的参数,任意的名称,任意的返回值和任意的异常

4. 测试

(1)启动项目

(2)访问地址:http://localhost:8080/spring-mvc-study/courses/upload(Tomcat)

注意:路径可能出错,将jsp中的相对路径该成了绝对路径

四、JSON

本章内容主要是说Spring MVC如何同json协同工作

(一)基本概念

json——程序届的明星,它的出现和广泛使用改变了我们的开发方式,越来越多的系统可以采用一种单页面的开发模式,同时也使得前端和后端的分离可以更加的彻底。

定义:json是一种轻量级的数据交换格式(强调这是一种格式,这对我们理解spring mvc如何看待json是非常重要的)

json 样例:

{"employees":[
{"firstName":"Jone", "lastName":"Doe"},
{"firstName":"Anna", "lastName":"Smith"},
{"firstName":"Peter", "lastName":"Jones"}
]}

它既有xml良好的结构特点,也没有冗长的书写格式,即融合了对人和对机器服务的优点,基于这种优点json在restful web service中发挥了重要作用,也使得restful web service逐渐成为业界对Web Service的一种事实的标准,逐渐取代了非常重的serve

(二)spring mvc中的json

spring mvc提供了对json的协同机制。

1. spring mvc看待json

json和html是数据模型的不同表现形式。具体说明如下:

考虑下面一个典型的业务场景:

data:其实在我们spring mvc中就是我们的数据模型model。一方面,当人访问一个应用时,我们希望得到model数据的一种呈现,就是通过html页面和浏览器呈现出一种人可理解的格式;另一方面很多公司提供了APP的应用,比如用于后端的大数据分析、机器学习等等的一些消耗数据的应用,这时我们的app就变成数据的消费者,此时同样这些APP也可以访问我们的数据,这时json就是一种更好的选择。这个过程中数据模型本身并没有改变,变化的是它的呈现方式。这就是spring mvc看待json的方式:json和html是数据模型的不同表现形式。

2. spring mvc处理json的方式

spring mvc使用了一种ViewResolver的机制来处理这种针对相同数据不同呈现方式要求的应用场景,用ContentNegotiatingViewResolver来处理我们不同的数据呈现格式:如果你是一个人类用户希望得到的是一个html格式的数据呈现,它就会将我们的数据代理给JSPView;如果你是一个机器View,你需要的是json数据形式的呈现,它就将这个请求代理给jsonView。

(三)代码实现

1. ViewResolver的配置

我们希望再spring mvc中启用对json的支持,我们首先可以配置一个ViewResolver,它可以将我们spring mvc中相同的数据以不同的数据表现形式呈现。因为我们重点关注的是json所以我们选取了defaultViews是MappingJackson2JsonView,它将使用Jackson这个工具类库将我们的模型数据转化成json格式。

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
    <property name="order" value="1" />
    <property name="mediaTypes">
        <map>
            <entry key="json" value="application/json" />
            <entry key="xml" value="application/xml" />
            <entry key="htm" value="text/html" />
        </map>
    </property>

    <property name="defaultViews">
        <list>
            <!-- JSON View -->
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"></bean>
        </list>
    </property>
    <property name="ignoreAcceptHeader" value="true" />
</bean>

2.引入依赖

使用jackson这项目工程来实现javeBean到json的数据转换,使用的版本是2.5.4,在pom.xml中引入对jackson的依赖,它就会在佛自动的拿到我们所需要的所有依赖的jackson的包

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${jackson.version}</version>
</dependency>

3. 在controller中编写一个支持json的方法

(1)ResponseBody的方式实现

这是最简洁的方式,它没有太多的其他的配置就可以帮助我们完成这项事情,我们也可以用其他的方式实现

   a. 用RequestMapping标签来映射URL

   b. 响应URL的函数返回Course 类,这个Course类前面被一个注解标记@ResponseBody所标识,说明这个Course会被我们的响应所使用。

@RequestMapping(value="/{courseId}",method = RequestMethod.GET)
public @ResponseBody Course getCourseJson (Integer courseId){
    return courseService.getCoursebyId(courseId);
}

这种情况下我们的浏览器会如何看待我们的数据呢?可以通过实验直接观察结果,项目启动后,在浏览器输入http://localhost:8080/spring-mvc-study/courses/123(Tomcat)

结果如下:

{"title":"深入浅出Java多线程","imgPath":"resources/imgs/course-img.jpg","learningNum":12345,"duration":7200,
"level":2,"levelDesc":"中级","descr":"多线程是日常开发中的常用知识,也是难用知识。bala bala...","chapterList":
[
{"id":1,"courseId":null,"order":1,"title":"第1章 多线程背景知识介绍","descr":"本章将介绍与多线程编程相关的背景概念"},
{"id":2,"courseId":null,"order":2,"title":"第2章 Java 线程初体验","descr":"Java语言层面对线程的支持,如何创建,启动和停止线程。如何使用常用的线程方法。用隋唐演义理解线程的代码"},
{"id":3,"courseId":null,"order":3,"title":"第3章 Java 线程的正确停止","descr":"本章讨论如何正确的停止一个线程,既要线程停得了,还得线程停得好。"},
{"id":4,"courseId":null,"order":4,"title":"第4章 线程交互","descr":"争用条件,线程的交互,及死锁的成因及预防。"},
{"id":5,"courseId":null,"order":5,"title":"第5章 进阶展望","descr":"简单介绍 Java 并发相关的类,及常用的多线程编程模型。"}
],"courseId":null}

所以我们从浏览器端访问了一个url请求,这个url请求把一个模型数据就是我们的课程数据呈现出了一种json的数据格式。

(2)使用@pathVariable

 a. 用RequestMapping标签来映射URL

 b. 指定method使用@pathVariable来关联我们的查询参数和方法参数

 c. 方法的返回值是一个泛型类ResponseEntity<Course>, ResponseEntity是一个Spring MVC为我们抽象的实体,它用其中泛型的方式来包裹我们的类,我们只需要返回ResponseEntity这个类的实例就可以了,这种情况下不再需要引入@ResponseBody这种标记,只需要使用它的实例就可以了,它的构造方式包括实际的业务模型数据(course)和http状态(我们赋值为OK),这样就可以向我们的浏览器返回一个json的数据

@RequestMapping(value="/jsontype/{courseId}",method = RequestMethod.GET)
public ResponseEntity<Course> getCourseJson2(@PathVariable Integer courseId){
    Course course = courseService.getCoursebyId(courseId);
    return new ResponseEntity<Course>(course,HttpStatus.OK);
}

 d. 按照上面相同的方式进行测试

(三)异步方式获取数据

1、理解

异步方式获取数据,前端通过JavaScript代码完成页面整合的一种方式,也是当今很多应用单页面开发方式时所常用的一种方式。这种方式彻底的将后端的业务逻辑和前端的页面呈现相分离,甚至不会在很多的方法中去组装很多数据模型。比如说某个页面我们即需要课程也需要教师等等,但其实这两个业务模型是相对独立的,这种情况下,这种模式将更好的将我们业务逻辑和页面呈现相分离。我只需要某个方法异步的去取一下课程信息,另外一个方法再去获取我们的讲师信息即可。这样通过在前端对返回的json数据做一个JavaScript的处理,动态就可以生成我们的页面。

2、具体实现

(1)上一节(二)已经在我们server端提供能够提供json格式支持的业务逻辑的controller方法,(1)(2)都可以

(2)course_json.jsp:放在webapp下(即根目录下),这个文件不能放在WEB-INF下,因为WEB-INF下都是私有的,一定要暴露在我们的公共领域。

course_json.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>我不是真正的慕课网</title>

<link rel="stylesheet"
	href="<%=request.getContextPath()%>/resources/css/main.css"
	type="text/css" />
<script type="text/javascript"
	src="<%=request.getContextPath()%>/resources/js/jquery-1.11.3.min.js"></script>

</head>
<script>
jQuery(function($){
	//指定了url
	var urlStr = "<%=request.getContextPath()%>/courses/<%=request.getParameter("courseId")%>";
	//alert("Before Call:"+urlStr);
	$.ajax({
		method: "GET",//指定了方法是get方法,因为服务器端是get方法
		url: urlStr,
		success:function(data,status,jqXHR){//通过ajax的方式访问终端请求拿回数据之后
			//alert("Success:"+data);
			var course = data;
			var path = "<%=request.getContextPath()%>/";	
			//通过jQuery的动态加载方式DOM的方式实现页面的呈现
			$(".course-title").html(course.title);
			$(".course_video").attr("src", path+course.imgPath);
			$("#learningNum").text(course.learningNum);
			$("#duration").text(course.duration);
			$("#levelDesc").text(course.levelDesc);
			$(".course_shortdecription").html(course.descr);
			
			var chapterList = course.chapterList;
			var chapter;
			
			for(var i = 0;i<chapterList.length;i++){
				chapter = chapterList[i];	
				
				var liObj = $("li",$("#chapterTemplate")).clone();				 
				$(".outline_name", liObj).text(chapter.title);
				$(".outline_descr", liObj).text(chapter.descr);				
				liObj.appendTo("#couList");				
			}// ~ end for			
		}
	}); // end ajax
});
</script>
<body>


	<div id="main">

		<div class="newcontainer" id="course_intro">
			<div class="course-title"></div>
			<div class="course_info">
				<div class="course-embed l">
					<div id="js-course-img" class="img-wrap">
						<img width="600" height="340" alt=""
							class="course_video" />
					</div>
					<div id="js-video-wrap" class="video" style="display: none">
						<div class="video_box" id="js-video"></div>
					</div>
				</div>
				<div class="course_state">
					<ul>
						<li><span>学习人数</span> <em id="learningNum"></em></li>
						<li class="course_hour"><span>课程时长</span> <em
							class="ft-adjust"><span id="duration"></span>秒</em></li>
						<li><span>课程难度</span> <em id="levelDesc"></em></li>
					</ul>
				</div>

			</div>
			<div class="course_list">
				<div class="outline">
					<h3 class="chapter_introduces">课程介绍</h3>
					<div class="course_shortdecription"></div>

					<h3 class="chapter_catalog">课程提纲</h3>
					<ul id="couList">
						
					</ul>
				</div>

			</div>
		</div>

	</div>

    <div id="chapterTemplate"  style="display:none">
       <li class="clearfix open"><a href="#">
				<div class="openicon"></div>
				<div class="outline_list l">
						<h5 class="outline_name"></h5>
						<p class="outline_descr"></p>
				</div>
		 </a></li>
    </div>

</body>
</html>

 

细节说明:

a.引用了静态资源下的jquery-1.11.3.min.js

<script type="text/javascript" src="<%=request.getContextPath()%>/resources/js/jquery-1.11.3.min.js"></script>

b.页面呈现只是一些控件没有任何数据,我们通过jQuery的方式,页面加载完成后,我们通过ajax的方法去异步的访问服务器端的请求,/courses/下的某个课程,这是一个restful的形式,实际访问的url是http://localhost:8080/course_json.jsp?courseId=123,同时我们指定了方法是get方法,因为服务器端我们要求这是一个get方法。通过ajax的方式访问终端请求拿回数据之后,通过jQuery的动态加载方式DOM的方式实现页面的呈现。

(四)总结

(1)spring mvc中通过使用ContentNegotiatingViewResolver这个类将我们不同的数据呈现请求转化到不同的view,这里他是将相同格式的数据转化到不同的数据呈现请求,其中包含json。这是一个通用的处理方式。

(2)在我们服务端代码中,使用ResponseEntity这样一个泛型的类来处理我们的返回结果,将返回的数据模型包裹在这个类之中,就可以将模型数据转化为json格式

(3)使用@ResponseBody来处理我们的返回数据,类似的还有@RequestBody来过去我们的页面通过json方式提交的格式,@ResponseBody和@RequestBody使得我们通过JavaScript直接和服务器交互成为可能,不仅仅可以通过get方法来查询数据,同时也可以post或者DELETE方法进行我们的修改数据,它可以将我们的json数据推送到我们的服务端。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值