Java高并发秒杀API之web层

---1-1 前端交互设计--------------------------------------------------------------

WEB相关课程:
1. 前端交互设计
2. Restful
3. SpringMVC
4. bootstrap + jquery

---1-2 学习Restful接口设计---------------------------------------------------------------

 1.什么是Restful接口:

  1.兴起于Rails

  2.一种优雅的URI表达方式

  3.资源的状态和状态转移

  Restful示例

    GET /seckill/list

2.Restful规范
GET->查询操作
POST->添加/修改操作
PUT->修改操作
DELETE->删除操作

POST和PUT一般情况下没有太严格的区分,PUT和POST体现在幂等性上

3.URL设计

/模块/资源/{标示}/集合1/... ...

标示:表示出是哪一个资源
集合:对该资源进行什么操作?或者说指该资源的哪一部分......

/user/{uid}/frends->frends list

/user/{uid}/followers->followers list

4.秒杀API的URL设计

GET / seckill/ list       秒杀列表

GET / seckill/{id}/ detail    详情页

GET / seckill/time/now    系统时间    

GET / seckill/{id}/exposer        暴露秒杀

GET / seckill/{id}/{md5}/execution  执行秒杀

---2-1 使用SpringMVC理论---------------------------------------------------------------

1.围绕Handler开发

  Handler ->  数据Model

        页面View

2.SpringMVC运行流程

3.HTTP请求地址映射原理

 

4.注解映射技巧

@RequestMapping注解:

(1)支持标准的URL

(2)Ant风格URL(即?,*,**等字符)

(3)带{xxx}占位符的URL。

例如:

/user/*/creation

  匹配/user/aaa/creation  /user/bbb/creation等URL

/user/**/creation

  匹配/user/creation /user/aaa/bbb/creation 等URL

/user/{userId}

  匹配user/123,user/abc等URL。 ID以参数形式传入

/company/{companyId}/user/{userId}/detail

  匹配/company/123/user/456/detail等URL

5.请求方法细节处理

1.请求参数绑定

2.请求方式限制

3.请求转发和重定向

4.数据模型赋值

5.返回json数据

6.cookie访问

蓝色:{seckillId},@PathVariable("seckillId")绑定方法的参数,对应到URL的占位符

绿色:method = RequestMethod.GET 只允许GET方法访问

黄色Model model:返回给用户的数据

红色:redirect和forward 通过字符串控制 请求转发和重定向

return "detail";//view 字符串返回jsp页面detail.jsp

 6.返回JSON数据

produces = {"application/json;charset=UTF-8"}告诉浏览器这是一个application/json,编码为UTF-8

@ResponseBody 返回JSON数据

 7.Cooki访问:

 @CookieValue(value = "killPhone",required = false)

 1) required = false,不强制传入"killPhone" 这个cookie

     没有killPhone这个cookie时,不进行拦截

 2) 默认情况系,value = “killPhone”写入之后,会强制匹配,cookie中没有killPhone会报异常

---2-2 整合配置SpringMVC框架---------------------------------------------------------------

web.xml

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

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1"
  metadata-complete="true">
  <!-- 修改servlet版本为3.1 -->
  <!-- 配置DispatcherServlet -->
  <servlet>
      <servlet-name>seckill-dispatcher</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <!-- 配置springMVC需要加载的配置文件
          spring.dao.xml,spring-service.xml,spring-web.xml
          Mybatis      -> spring ->         springMVC(springMVC就是spring)
       -->
       <!-- 参数
           加载配置:spring resouece体系下的前缀classpath
               spring/下的spring-开头的配置文件
        -->
       <init-param>
           <param-name>contextConfigLocation</param-name>
           <param-value>classpath:spring/spring-*.xml</param-value>
       </init-param>
  </servlet>
  <servlet-mapping>
      <servlet-name>seckill-dispatcher</servlet-name>
      <!-- 默认匹配所有请求 -->
      <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

 spring-web.xml

spring官方文档:

  http://docs.spring.io/spring/docs/4.2.9.RELEASE/spring-framework-reference/htmlsingle/

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        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-4.1.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-4.1.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- 配置springMVC -->
    <!-- 1:开启SpringM注解模式 -->
    <!--  简化配置:
            (1)自动注册DefaultAnnotationHandlerMapping(映射),AnnotationMethodHandlerAdapter(基于注解方法的Handler适配器)
            (2)默认提供一系列功能:数据绑定,数字和日期的format  @NumberFormat,@DataTimeFormat,
                xml,json默认读写支持。
    -->
    <mvc:annotation-driven/>

    <!-- 2:静态资源默认servlet配置
        2.1:加入对静态资源的处理:js,gif,png
        2.2:允许使用"/"做整体映射
     -->
         <!-- servle-mapping 映射路径:"/" (springMVC基本上让用"*.action"这样的注解)
    使用"/"需要使用静态资源默认servlet配置-->
    <mvc:default-servlet-handler/>
    <!--3:配置jsp顯示ViewResolver  -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
        <property name="prefix" value="/WEB-INF/jsp"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!-- 4:掃描web相關的bean -->
    <context:component-scan base-package="org.seckill.web"/>
</beans>

---3-1 使用SpringMVC实现Restful接口---------------------------------------------------------------

springMVC:Spring MVC 中的 forward 和 redirect

  http://blog.csdn.net/zzjjiandan/article/details/19491253

转自 [Spring MVC] - view的redirect和forward

可以通过redirect/forward:url方式转到另一个Action进行连续的处理。
可以通过redirect:url 防止表单重复提交 。
写法如下:
return "forward:/order/add";
return "redirect:/index.jsp";

带参数重定向--RedirectAttributes
用户保存或修改后,为了防止用户刷新浏览器(F5)导致表单重复提交,一般在保存或修改操作之后会redirect到一个结果页面(不是forward),同时携带参数,如操作成功的提示信息。因为是Redirect,Request里的attribute不会传递过去。Spring在3.1才提供了这个能力--RedirectAttributes。 反复按F5,操作成功的提示信息也不会再次出来(总共只出现一次),效果很理想。

Java代码

public String save(@ModelAttribute("group") Group group, RedirectAttributes redirectAttributes) {
    accountManager.saveGroup(group);
    redirectAttributes.addFlashAttribute("message", "操作成功");
    return "redirect:/account/group/";
}

SeckillResult.java

public class SeckillResult<T>//<T>泛型类

    //泛型类型的数据
    private T data;

package org.seckill.dto;

//<T>泛型类
//所有ajax请求返回类型,封装json结果
public class SeckillResult<T> {

    private boolean success;
    //泛型类型的数据
    private T data;
    private String error;
    public SeckillResult(boolean success, T data) {
        super();
        this.success = success;
        this.data = data;
    }
    public SeckillResult(boolean success, String error) {
        super();
        this.success = success;
        this.error = error;
    }
    
    public boolean isSuccess() {
        return success;
    }
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
    public String getError() {
        return error;
    }
    public void setError(String error) {
        this.error = error;
    }
    public void setSuccess(boolean success) {
        this.success = success;
    }

}

SeckillController.java :

  package org.seckill.web

package org.seckill.web;

import java.util.Date;
import java.util.List;

import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.dto.SeckillResult;
import org.seckill.entity.Seckill;
import org.seckill.enums.SeckillStatEnum;
import org.seckill.exception.RepeatKillExeception;
import org.seckill.exception.SeckillCloseException;
import org.seckill.service.SeckillService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller//類似@Service @component 
@RequestMapping("/seckill")
//url:/模塊/資源/{id}/細分 http://localhost:8080/seckill/seckill/list
//去掉@RequestMapping("/seckill")变为http://localhost:8080/seckill/list
public class SeckillController {
    
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Autowired
    private SeckillService seckillService;
    
    @RequestMapping(value="/list",method = RequestMethod.GET)
    //二級URL
    public String list(Model model){
        //獲取列表頁
        List<Seckill> list = seckillService.getSeckillList();
        model.addAttribute("list",list);
        //list.jsp + model = ModelAndView
        return "list";//spring-web.xml配置了prefix和suffix ->WEB-INF/jsp/list.jsp
        
    }
    
    //
    @RequestMapping(value = "/{seckillId}/detail",method = RequestMethod.GET)
    //也可不写@PathVariable("seckillId"),默认能识别出,但最好还写出
    /*使用基本类型接收数据还是用包装类?
        使用@PathVariable时注意两点:
        1:参数接收类型使用基本类型
        2:不用基本类型时,给defaultValue值
        推荐使用包装类*/
    public String detail(@PathVariable("seckillId")Long seckillId,Model model){
        //null时,redirect到list.jsp
        if(seckillId == null){
            return "redirect:/seckill/list";
        }
        Seckill seckill = seckillService.getById(seckillId);
        if(seckill == null){
            return "forward:/seckill/list";
        }
        model.addAttribute("seckill",seckill);
        return "detail";
    }
    
    /*ajax json   RequestMethod.POST直接输入地址无效
     * @ResponseBody 当springmvc看到@ResponseBody的时候会将SeckillResult<Exposer>返回值封装成json

     * produces = {"application/json;charset=UTF-8"}解决json中的数据乱码问题
     * */
    @RequestMapping(value = "/{seckillId}/exposer",
            method = RequestMethod.POST,
            produces = {"application/json;charset=UTF-8"})
    @ResponseBody
    //public void/*TODO*/ exposer(Long seckillId){
    public SeckillResult<Exposer> exposer(Long seckillId){

        SeckillResult<Exposer> result;
        try{
            Exposer exposer =seckillService.exportSeckillUrl(seckillId);
            result = new SeckillResult<Exposer>(true,exposer);
        }catch (Exception e){
            logger.error(e.getMessage(),e);
            result = new SeckillResult<Exposer>(false,e.getMessage());
        }
        return result;
    }
    
    @RequestMapping(value = "/{seckillId}/{md5}/execution",
            method = RequestMethod.POST,
            produces = {"application/json;charset=UTF-8"})
    public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId")Long seckillId,
                                                   @PathVariable("md5")String md5,
                                                   @CookieValue(value = "killPhone",required = false)Long phone){
        /*请求的requestpattern中没有这个cookie killPhone时,springMVC 会报错,required = false表示killPhone不是必须,就不会报错,验证逻辑放入程序*/
        //springMVC valid
        if(phone == null){
            return new SeckillResult<SeckillExecution>(false,"未注册");
        }
        SeckillResult<SeckillExecution> result;
        try{
            SeckillExecution execution = seckillService.executeSeckill(seckillId, phone, md5);
            return new SeckillResult<SeckillExecution>(true,execution);
        } catch (SeckillCloseException e) {
            SeckillExecution execution = new SeckillExecution(seckillId,SeckillStatEnum.REPEAT_KILL);
            return new SeckillResult<SeckillExecution>(false,execution);
        } catch (RepeatKillExeception e) {
            SeckillExecution execution = new SeckillExecution(seckillId,SeckillStatEnum.END);
            return new SeckillResult<SeckillExecution>(false,execution);
        }catch (Exception e){
            logger.error(e.getMessage(),e);
            SeckillExecution execution = new SeckillExecution(seckillId,SeckillStatEnum.INNER_ERROR);
            return new SeckillResult<SeckillExecution>(false,execution);
        }
    } 
    @RequestMapping(value = "/time/now",
                method = RequestMethod.GET)
    public SeckillResult<Long> time(){
        Date now = new Date();
        return new SeckillResult(true,now.getTime());
    }

}

---4 基于bootstrap开发页面结构---------------------------------------------------------------

BootStrap环境配置:
参照http://www.runoob.com/bootstrap/bootstrap-environment-setup.html
使用HTML模板和Bootstrap CDN版本,使用CDN版本时需要联网
1.替换模板内的:
      <!-- jQuery (Bootstrap 的 JavaScript 插件需要引入 jQuery) -->
      <script src="https://code.jquery.com/jquery.js"></script>
      <!-- 包括所有已编译的插件 -->
      <script src="js/bootstrap.min.js"></script>
2.主题文件一般不使用,去掉
    <!-- 可选的Bootstrap主题文件(一般不使用) -->
    <script src="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap-theme.min.css"></script>
3.新 Bootstrap 核心 CSS 文件与模板内重复,去掉
    <!-- 新 Bootstrap 核心 CSS 文件 -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
 
 通用部分放入common/head.jsp
                 common/tag.jsp <!-- 引入标签库 -->
 使用静态包含  head.jsp和tag.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- 引入jstl -->
<%@include file="common/tag.jsp" %>
<!DOCTYPE html>
<html>
   <head>
      <title>秒杀詳情</title>
    <%@include file="common/head.jsp" %>
   </head>
   <body>
   <!-- 頁面顯示部分 :推荐放入一个div中-->
           <div class="container">
           </div>
   </body> 
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
 
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</html>

 
 静态包含和动态包含的区别
 <%@include...%>   静态包含:会将引用的源代码原封不动的附加过来,合并过来成一个jsp,对应一个servlet。
<jsp:include...>  动态包含:分别编译,被包含的jsp独立编译成servlet,然后和包涵的jsp页面编译生成的静态文档html做合并;总是会检查所包含文件的变化,适合包含动态文件。
 静态包含是被包含的JSP合并到该servlet中。(一个servlet)
 动态包含是被包含的JSP先运行servlet,再把运行结果合并到包含的html中(多个servlet)。

 使用的class都是bootstrap的css
 EL表达式:${}
 
 在配置的时候,注意suffix的值不要写成*.jsp,否则也会出错的,以前都是写成*,写习惯了。
<bean 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>
    
  错误:

Error 500
HTTPステータス 500 - Internal Server Error


Type Exception Report

メッセージ Error instantiating servlet class org.springframework.web.servlet.DispatcherServlet

説明 The server encountered an unexpected condition that prevented it from fulfilling the request.

例外
javax.servlet.ServletException: Error instantiating servlet class org.springframework.web.servlet.DispatcherServlet
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80)
    org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
    org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799)
    org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
    org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455)
    org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    java.lang.Thread.run(Thread.java:748)


原因
java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet
    org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1285)
    org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1119)
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80)
    org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
    org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799)
    org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
    org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455)
    org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    java.lang.Thread.run(Thread.java:748)

解决办法:
You need to add the "Maven Dependency" in the Deployment Assembly
    right click on your project and choose properties.
    click on Deployment Assembly.
    click add
    click on "Java Build Path Entries"
    select Maven Dependencies"
    click Finish.
Rebuild and deploy again
Note: This is also applicable for non maven project.


@Controller//類似@Service @component 
@RequestMapping("/seckill")
//url:/模塊/資源/{id}/細分 http://localhost:8080/seckill/seckill/list
//去掉@RequestMapping("/seckill")变为http://localhost:8080/seckill/list
public class SeckillController {
    
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Autowired
    private SeckillService seckillService;
    
    @RequestMapping(value="/list",method = RequestMethod.GET)
    //二級URL
    public String list(Model model){
        //獲取列表頁
        List<Seckill> list = seckillService.getSeckillList();
        model.addAttribute("list",list);
        //list.jsp + model = ModelAndView
        return "list";//spring-web.xml配置了prefix和suffix ->WEB-INF/jsp/list.jsp
        
    }
    ...
}

页面:

使用bootstrap,遵守bootstrap的css

WEB-INF/jsp

    list.jsp

    detail.jsp

    common/head.jsp

        tag.jsp

list.jsp

 <a class="btn btn-info" href="/seckill/seckill/${sk.seckillId}/detail" target="_blank">link</a>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- 引入jstl -->
<%@include file="common/tag.jsp" %>
<!DOCTYPE html>
<html>
   <head>
      <title>秒杀列表页</title>
    <%@include file="common/head.jsp" %>
   </head>
   <body>
           <!-- 頁面顯示部分 :推荐放入一个div中-->
           <div class="container">
               <div class="panel panel-default">
                   <div class="panel-heading text-center">
                       <h2>秒杀列表</h2>
                   </div>
                   <div class="panel-body">
                       <table class="table table-hover">
                           <thead>
                               <tr>
                                   <th>名称</th>
                                   <th>库存</th>
                                   <th>开始时间</th>
                                   <th>结束时间</th>
                                   <th>创建时间</th>
                                   <th>详情页</th>
                               </tr>
                           </thead>
                           <tbody>
                               <!-- 通过标签迭代:list -->
                            <c:forEach var ="sk" items="${list}">
                                 <tr>
                                   <td>${sk.name}</td>
                                   <td>${sk.number}</td>
                                   <td>
                                       <fmt:formatDate value = "${sk.startTime}" pattern="yyyy-MM-dd HH:mm:ss"/>
                                   </td>
                                   <td>
                                       <fmt:formatDate value = "${sk.endTime}" pattern="yyyy-MM-dd HH:mm:ss"/>
                                   </td>
                                   <td>
                                       <fmt:formatDate value = "${sk.createTime}" pattern="yyyy-MM-dd HH:mm:ss"/>
                                   </td>
                                   <td>
                                       <a class="btn btn-info" href="/seckill/seckill/${sk.seckillId}/detail" target="_blank">link</a>
                                   </td>
                                 </tr>
                               </c:forEach>
                           </tbody>
                       </table>
                   </div>
               </div>
           </div>
   </body> 
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
 
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</html>

 

detail.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- 引入jstl -->
<%@include file="common/tag.jsp" %>
<!DOCTYPE html>
<html>
   <head>
      <title>秒杀詳情</title>
    <%@include file="common/head.jsp" %>
   </head>
   <body>
   <!-- 頁面顯示部分 :推荐放入一个div中-->
           <div class="container">
               <div class="panel panel-default">
                   <div class="panel-heading text-center">
                       <h2>秒杀详情</h2>
                       <div class="panel-heading">${seckill.name}</div>
                   </div>
                   <div class="panel-body">
                    <!-- 开发交互时补全 -->
                   </div>
               </div>
           </div>
   </body> 
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
 
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</html>

common/:共通部分

head.jsp

<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
 
<!-- HTML5 Shim 和 Respond.js 用于让 IE8 支持 HTML5元素和媒体查询 -->
<!-- 注意: 如果通过 file://  引入 Respond.js 文件,则该文件无法起效果 -->
<!--[if lt IE 9]>
     <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
     <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->

tag.jsp 引入标签库

<!-- 引入标签库 -->
<%@ taglib prefix="c"  uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt"  uri="http://java.sun.com/jsp/jstl/fmt"%>

---1-2 学习Restful接口设计---------------------------------------------------------------

---1-2 学习Restful接口设计---------------------------------------------------------------

---1-2 学习Restful接口设计---------------------------------------------------------------

---1-2 学习Restful接口设计---------------------------------------------------------------

原文地址:https://www.cnblogs.com/charles999/p/7145702.html