SpringMVC總結(jié)與RESTful風(fēng)格

  • 新建一個maven項目沸柔,并設(shè)置pom.xml文件,設(shè)置當前項目為web項目铲敛,將packaging的屬性值設(shè)置為war方式褐澎,添加spring mvc的依賴包, spring-webmvc(4.3.6)伐蒋,分別添加插件工三,jdk和tomcat。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com</groupId>
    <artifactId>SpringMVC</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- define the project compile level -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <!-- 添加tomcat插件 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <path>/</path>
                    <port>8081</port>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  • 在項目中添加web元素先鱼,webapp俭正, WEB-INF以及web.xml,其中在web.xml里面要添加spring mvc的引入焙畔,添加DispatcherServlet掸读,這個是spring mvc的核心的前端控制器,注意還要設(shè)置DispatcherServlet的contextConfigLocation宏多,如果不設(shè)置該屬性儿惫,則Spring MVC會自動的在WEB-INF下查找[servlet-name]-servlet.xml文件來作為SpringMVC的配置文件。
<?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">
    <servlet>
        <servlet-name>aaa</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>
    </servlet>
    <servlet-mapping>
        <servlet-name>aaa</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
  • 配置Spring MVC的配置文件伸但,classpath下的spring-mvc.xml文件,該文件在本案例中分別配置了視圖解析器肾请、消息資源、缺省servlet處理器更胖、注解驅(qū)動器铛铁、上下文包掃描。
  • 視圖解析器:InternalResourceViewResolver却妨, 該屬性里可以分別配置前綴和后綴饵逐,為了保證程序的安全性,可以將頁面放在/WEB-INF/view/下管呵,所以前綴可以直接配置為該值梳毙。如果沒有這方面的需求,不配置該屬性捐下,則前綴為/账锹,代表的是webapp目錄,后綴可以根據(jù)項目需要設(shè)置為.jsp或者.html
  • 消息資源:ReloadableResourceBundleMessageSource坷襟,該bean的配置有一個要求奸柬,id必須叫做messageSource,Spring MVC框架會讀取該id所對應(yīng)的bean對象來讀取資源配置文件婴程,里面設(shè)置了basename屬性廓奕,用作讀取該文件,該文件的配置只需要文件名,不能加后綴桌粉,為了更好的實現(xiàn)國際化蒸绩,我們可以在msg文件后面拼接語言和國家,比如msg_zh_CN, msg_en_US以及其他國家的語言均可以按照這種方式來設(shè)定铃肯。有些ide環(huán)境可能只認識resources患亿,則可以將msg文件放入resources目錄下,否則不同的ide環(huán)境找不到該文件
  • 缺省servlet處理器:mvc:default-servlet-handler押逼,該配置可以保證Spring MVC項目可以直接訪問靜態(tài)資源步藕,比如可以直接訪問index.html
  • 注解驅(qū)動器:mvc:annotation-driven,該配置使得當前項目可以使用注解來完成配置。在控制器類之上挑格,可以添加Controller注解咙冗,里面還有RequestMapping,GetMapping漂彤,PostMapping雾消,PathVariable等注解,可以完成各自的功能
  • 上下文的包掃描:context:component-scan显歧,使用該配置仪或,可以使得該basePackage所對應(yīng)的包下的所有Component組件直接被掃描出來使用,前提是需要在類之上添加@Component注解士骤,但是我們的Controller以及后面要用的Service和Repositoy也都是Component組件,所以可以直接被掃描出來進行使用
  • 該配置文件還配置了兩個bean蕾域,里面是name和class拷肌,那么要注意,name里對應(yīng)的值是url旨巷,name里面允許存放特殊字符巨缘,因為路徑字符串前面會有一個路徑符號/,所以這里只能使用name而不能使用id采呐,意思是該url請求發(fā)出來之后若锁,會自動交給后面的控制器類來實現(xiàn)處理的功能,該控制器類是實現(xiàn)了Controller接口的類斧吐,該類中有一個返回值為ModelAndView對象的方法名為handlerRequest的包含HttpServletRequest和HttpServletResponse兩個參數(shù)的方法又固。
  • ModelAndView對象是一個可以同時包含視圖和模型對象的對象,但是在使用的過程中煤率,有時候只需要顯示頁面仰冠,有時候可能在顯示頁面的同時,還需要數(shù)據(jù)的傳遞蝶糯。
  • 注意:Controller接口與Controller注解是兩個不同的東西洋只。
<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 配置SpringMVC的視圖解析器:可以分別指定前綴和后綴
            prefix: /WEB-INF/view/,那么控制器那邊會在虛擬視圖前拼接該字符串
            suffix:.jsp .html,那么控制器那邊會在虛擬視圖后面拼接該字符串
    -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view/" />
        <!--<property name="suffix" value=".jsp" />-->
    </bean>
    <!--配置缺省的servlet處理器识虚,靜態(tài)資源可以直接被訪問-->
    <mvc:default-servlet-handler />
    <!-- 上下文的組件掃描 -->
    <context:component-scan base-package="com.qfedu.controller" />
    <!-- 配置注解驅(qū)動 -->
    <mvc:annotation-driven />
    <!--
        bean的id屬性值不能包含特殊字符
        name可以肢扯,所以路徑需要使用name來標識一個控制器的路徑
            指定name對應(yīng)路徑交給哪個控制器來進行具體的處理
    -->
    <bean name="/ProductInput" class="com.qfedu.controller.ProductInputController" />
    <bean name="/SaveProductController" class="com.controller.SaveProductController" />
    <!-- 配置MessageSource,消息源担锤,該id必須叫做messageSource鹃彻,由SpringMVC框架自動讀取該id對應(yīng)的消息資源來講加載相對應(yīng)的配置文件 -->
    <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basename" value="/WEB-INF/msg" />
    </bean>
</beans>
  • 新增一個Emp的bean類
public class Emp {
    private int eid;
    private String name;
    private double salary;
    public Emp() { }
    public Emp(int eid, String name, double salary) {
        this.eid = eid;
        this.name = name;
        this.salary = salary;
    }
    public int getEid() { return eid; }
    public void setEid(int eid) { this.eid = eid; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public double getSalary() { return salary; }
    public void setSalary(double salary) { this.salary = salary; }
    @Override
    public String toString() {
        return "Emp{" +
                "eid=" + eid +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                '}';
    }
}
  • 在controller包下新增EmpController類,用到的注解有Controller妻献,RequestMapping蛛株,GetMapping, PostMapping育拨,PathVariable
  • Controller谨履,代表當前類是一個控制器類,注意熬丧,通過查看源碼笋粟,我們發(fā)現(xiàn)該類也是一個Component,所以剛剛的配置包掃描可以直接掃描到當前類析蝴,并將其作為一個組件來使用
  • RequestMapping害捕,請求映射,目的是將某一個請求闷畸,映射到具體方法之上尝盼。該注解可以使用在類之上,也可以使用在方法之上佑菩。如果類和方法都有該配置盾沫,那么訪問該方法的時候,需要同時拼接類之上的路徑和方法之上的路徑才能夠訪問該具體的方法殿漠。該注解可以使用method來區(qū)分不同的請求赴精,method = RequestMethod.POST,或者GET可以分別來處理post和get請求
  • PostMapping和GetMapping也代表請求映射绞幌,使用起來會更直觀蕾哟,分別代表處理post和get的請求方式,但是這倆屬性只能用于spring 4.3之后的版本莲蜘。
  • PathVariable:路徑變量谭确,可以用來做路徑傳參功能,該功能相對于問號傳參更加方便菇夸,可以直接指定變量的數(shù)據(jù)類型琼富,而無需再做數(shù)據(jù)類型的轉(zhuǎn)換,也可以實現(xiàn)傳入多個參數(shù)庄新,/{abc}/{xyz},方法里面可以使用 public String getPath(@PathVariable int abc鞠眉,@PathVariable String xyz)方式來接收薯鼠。注意路徑傳參會多一級目錄,要注意訪問路徑
  • 該類中的updateEmp(Emp e)方法再特別說一下:該方法可以自動接收表單里面的數(shù)據(jù)并將其封裝為一個Emp對象械蹋,注意表單中的控件名一定要和Bean中的Emp類的屬性要完全一致出皇,否則找不到某些屬性,這個也是Spring MVC中非常便利的地方哗戈,可以省去類型轉(zhuǎn)換和封裝對象的過程
  • 該類中的方法都參數(shù)均很靈活郊艘,在需要的地方添加參數(shù)就可以直接使用
  • 該類中的方法都返回值為String的都代表最終的展示頁面。如果帶有redirect唯咬,則代表重定向纱注,意思是重定向到某一個具體的請求。
  • 一個控制器里可以同時存在相同的路徑url但是是不同的請求方式
  • 關(guān)于校驗這里胆胰,第一個GetMapping("/saveEmp")代表以get方式請求該資源狞贱,里面寫了一個ModelAndView對象,傳了三個參數(shù)蜀涨,第一個是viewname瞎嬉,視圖名,拼接上前后綴可以得到真正的物理視圖厚柳,來打開該物理視圖所對應(yīng)的頁面氧枣,第二個參數(shù)為modelname,模型名别垮,相當于給模型起名字便监,這里要注意,該模型名意識要被叫做bean對象的小寫形式Emp(emp) 宰闰,第三個參數(shù)為modelObject茬贵,模型對象,將該對象通過模型名傳遞給第一個參數(shù)viewname所對應(yīng)的頁面移袍,在那個頁面中可以渲染該數(shù)據(jù)
  • 關(guān)于校驗的第二個PostMapping("/saveEmp"),該注解的意思是頁面上的表單通過post請求將saveEmp的請求來在這里進行處理。該方法包含有三個參數(shù)老充,第一個是Emp對象葡盗,可以自動封裝表單中的屬性為Bean對象,第二個參數(shù)為BindingResult對象啡浊,該對象我們通過源碼可以發(fā)現(xiàn)是Spring中的Errors的子接口觅够,可以用來接收并存儲錯誤信息,這個對象可以接收從EmpValidate校驗類中產(chǎn)生的錯誤信息巷嚣,存儲以交給錯誤頁面的f:errors標簽來展示錯誤信息喘先,第三個參數(shù)是Model對象,可以用來儲存對象廷粒,目的是可以使的bean對象的錯誤數(shù)據(jù)進行回顯
@Controller
@RequestMapping("/emp")
public class EmpController {
    private IEmpService empService = new EmpServiceImpl();
    /**
     * 如果有了users請求窘拯,那么該方法會被調(diào)用红且,返回值為將來要渲染的頁面
     * @return
     */
    @RequestMapping("/emps")
    public String getUsersPage(Model model, HttpSession session){
        List<Emp> list = empService.getAllEmps();
        model.addAttribute("list", list);
        session.setAttribute("list",  list);
        return "emp.jsp";
    }
    @RequestMapping("/getEmpByEid")
    public String getEmpByEid(HttpServletRequest request, Model model){
        String seid = request.getParameter("eid");
        int eid = seid == null ? -1 : Integer.parseInt(seid);
        Emp emp = empService.getEmpByEid(eid);
        model.addAttribute("emp", emp);
        return "updateEmp.jsp";
    }
    //@RequestMapping(value = "/updateEmp", method = RequestMethod.POST)
    @PostMapping("/updateEmp")
    //public String updateEmp(HttpServletRequest request){
    public String updateEmp(Emp e){
        //System.out.println(request.getParameter("eid"));
        System.out.println(e);
        boolean flag = empService.updateEmp(e);
        if(flag){
            return "redirect:/emp/emps";
        }
        return "";
    }
    @GetMapping("/deleteByEid/{eid}")
    public String deleteByEid(@PathVariable int eid){
        //System.out.println(eid);
        boolean flag = empService.deleteEmpByEid(eid);
        if(flag){
            return "redirect:/emp/emps";
        }
        return "";
    }
    @GetMapping("/saveEmp")
    public ModelAndView saveEmp(){
        return new ModelAndView("saveEmp.jsp", "emp", new Emp());
    }
    /**
     * 完成表單中Emp對象的存儲
     * @param e 要存儲的Emp對象
     * @param errors,收集錯誤信息的對象
     * @param model
     * @return
     */
    @PostMapping("/saveEmp")
    public String saveEmp(Emp e, BindingResult errors, Model model){
        /**
         *  調(diào)用自己寫好的校驗類來完成對于Emp對象的校驗
         */
        EmpValidate ev = new EmpValidate();
        ev.validate(e, errors);
        if(errors.hasErrors()){
            model.addAttribute("emp", e);
            return "saveEmp.jsp";
        }
        return "redirect:/emp/emps";
    }
}
  • IEmpService.java, Service接口
public interface IEmpService {
    List<Emp> getAllEmps();
    Emp getEmpByEid(int eid);
    boolean updateEmp(Emp emp);
    boolean deleteEmpByEid(int eid);
}
  • EmpServiceImpl.java涤姊, service實現(xiàn)類
public class EmpServiceImpl implements IEmpService {
    private IEmpDao empDao = new EmpDaoImpl();
    @Override
    public List<Emp> getAllEmps() {
        return empDao.getAllEmps();
    }
    @Override
    public Emp getEmpByEid(int eid) {
        return empDao.getEmpByEid(eid);
    }
    @Override
    public boolean updateEmp(Emp emp) {
        return empDao.updateEmp(emp);
    }
    @Override
    public boolean deleteEmpByEid(int eid) {
        return empDao.deleteEmpByEid(eid);
    }
}
  • IEmpDao.java Dao接口
public interface IEmpDao {
    List<Emp> getAllEmps();
    Emp getEmpByEid(int eid);
    boolean updateEmp(Emp emp);
    boolean deleteEmpByEid(int eid);
}
  • EmpDaoImpl.java dao的實現(xiàn)類,使用List模擬一套數(shù)據(jù)源暇番,可以完成對于Emp對象的CRUD操作,注意如果服務(wù)器重新啟動思喊,則數(shù)據(jù)會恢復(fù)到最原始的狀態(tài)
public class EmpDaoImpl implements IEmpDao {
    private static List<Emp> emps = new ArrayList<>();
    static {
        for(int i = 0; i < 20; i++){
            emps.add(new Emp(i + 1, "name " + i, 8000 + i * 100));
        }
    }
    @Override
    public List<Emp> getAllEmps() {
        return emps;
    }
    @Override
    public Emp getEmpByEid(int eid) {
        return emps.get(eid - 1);
    }
    @Override
    public boolean updateEmp(Emp emp) {
        try{
            emps.set(emp.getEid() - 1, emp);
            return true;
        }catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }
    @Override
    public boolean deleteEmpByEid(int eid) {
        try{
            emps.remove(eid -1 );
            return true;
        }catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }
}
  • EmpValidate.java,用來對于Emp做校驗使用壁酬,有非空校驗,有合法性校驗恨课。
/**
 * 用來完成對于Emp類的校驗舆乔,有非空校驗,合法性校驗
 * 
 *  里面包含兩個方法剂公,supports和validate
 *         supports方法的意思是當前類用來對于哪個類實現(xiàn)校驗
 *          Emp.class.isAssignableFrom(clazz);意思是完成對與Emp類的校驗
 *         validate方法希俩,完成真正的校驗,一定是滿足了supports方法之后才會進入該方法來進行校驗
 */
public class EmpValidate implements Validator {
    /**
     * 指定當前類是否支持指定類型的校驗
     * @param clazz
     * @return
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return Emp.class.isAssignableFrom(clazz);
    }
    /**
     * 真正的校驗方法
     * @param target,校驗對象
     * @param errors,存儲錯誤信息
     */
    @Override
    public void validate(Object target, Errors errors) {
        Emp e = (Emp) target;
        /**
         *
         * 非空校驗
         *
         * 使用ValidationUtils工具類來實現(xiàn)對于某些非空字段的校驗变泄,該方法包含三個參數(shù):
         *      1.  錯誤對象本股,用來收集并存儲錯誤信息
         *      2.  要校驗的字段
         *      3.  錯誤碼,在msg配置文件中配置的key信息
         */
        ValidationUtils.rejectIfEmpty(errors, "eid", "emp.eid");
        ValidationUtils.rejectIfEmpty(errors, "name", "emp.name");
        ValidationUtils.rejectIfEmpty(errors, "salary", "emp.salary");
        double salary = e.getSalary();
        /**
         *  合法性校驗掀淘,使用errors對象的rejectValue()方法完成合法性校驗,里面包含了兩個參數(shù)
         *      1. field,在哪個字段上完成校驗
         *      2.  錯誤碼,會在msg文件中找到該key對應(yīng)的錯誤信息
         */
        if(salary < 0){
            errors.rejectValue("salary", "emp.salary.invalidate");
        }
    }
}
  • /WEB-INF/view/emp.jsp文件因块,用來展示所有的員工信息的頁面,該頁面包含兩個超鏈接籍铁,修改和刪除涡上,修改使用的時候問號傳參,刪除使用的是路徑傳參
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>user</title>
</head>
<body>
    <h1>this is users page.</h1>
    <c:if test="${list != null}">
        <table border="1" align="center" width="80%">
            <tr>
                <th>eid</th>
                <th>name</th>
                <th>salary</th>
                <th>manage</th>
            </tr>
            <c:forEach items="${list}" var="e">
                <tr>
                    <td>&nbsp; ${e.eid}</td>
                    <td>&nbsp; ${e.name}</td>
                    <td>&nbsp; ${e.salary}</td>
                    <td>&nbsp; <a href="/emp/getEmpByEid?eid=${e.eid}">update</a> <a href="/emp/deleteByEid/${e.eid}">delete</a></td>
                </tr>
            </c:forEach>
        </table>
    </c:if>
</body>
</html>
  • /WEB-INF/view/updateEmp.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>update Emp</title>
</head>
<body>
    <h1>this is emp update page.</h1>
    <form method="post" action="/emp/updateEmp">
        eid:<input type="text" name="eid" value="${emp.eid}" readonly="readonly" /><br />
        name:<input type="text" name="name" value="${emp.name}" /><br />
        salary:<input type="text" name="salary" value="${emp.salary}" /><br />
        <%--salary:<input type="text" name="birth.year" value="${emp.salary}" /><br />--%>
        <input type="submit" value="submit" /><br />
    </form>
</body>
</html>
  • /WEB-INF/view/saveEmp.jsp, 該頁面要注意拒名,引入了Spring MVC的form標簽吩愧,表單使用的就是SpringMVC的form表單
  • f:form:使用的是Spring MVC的form標簽,里面有一個屬性叫做commandName增显,這個值是從后端傳遞過來的對象名雁佳,注意要與bean的小寫方式一致
  • f:input類似于html中的input標簽,但是將name換成了path同云,代表的是屬性名
  • f:errors糖权,這個標簽可以用來展示如果當前表單有錯誤信息時,可以在對應(yīng)的域之上進行回顯炸站,一般都被放在對應(yīng)的f:input標簽之后星澳,用來描述該屬性的錯誤信息
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="f" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <%--<form action="" method="post">
        eid:<input type="text" name="eid" />
    </form>--%>
    <f:form method="post" action="/emp/saveEmp" commandName="emp">
        eid:<f:input path="eid" /><font color="red"><f:errors path="eid" /></font> <br>
        name:<f:input path="name" /><font color="red"> <f:errors path="name" /></font><br>
        salary:<f:input path="salary" /><font color="red"> <f:errors path="salary" /></font><br>
        <input type="submit" value="submit" /> <br>
    </f:form>
</body>
</html>

RESTful風(fēng)格

一、說明

  • REST表示 Representational State Transfer(表示性狀態(tài)轉(zhuǎn)換)旱易。它是可以用來設(shè)計web services的框架禁偎,可以被不同的客戶端調(diào)用腿堤。
  • REST是一種架構(gòu)風(fēng)格,其核心是面向資源届垫,REST專門針對網(wǎng)絡(luò)應(yīng)用設(shè)計和開發(fā)方式释液,以降低開發(fā)的復(fù)雜性,提高系統(tǒng)的可伸縮性装处。

二误债、REST提出設(shè)計概念和準則

  • 網(wǎng)絡(luò)上的所有事物都可以被抽象為資源(resource)
  • 每一個資源都有唯一的資源標識(resource identifier),對資源的操作不會改變這些標識
  • 所有的操作都是無狀態(tài)的 使用簡單的HTTP協(xié)議來實現(xiàn)調(diào)用妄迁,而不是CORBA, RPC 或者 SOAP等負責的機制

三寝蹈、RESTful API 設(shè)計指南

1、概述
  • 網(wǎng)絡(luò)應(yīng)用程序登淘,分為前端和后端兩個部分箫老。當前的發(fā)展趨勢,就是前端設(shè)備層出不窮(手機黔州、平板耍鬓、桌面電腦、其他專用設(shè)備......)流妻。 因此牲蜀,必須有一種統(tǒng)一的機制,方便不同的前端設(shè)備與后端進行通信绅这。
  • 1.協(xié)議 API與用戶的通信協(xié)議涣达,總是使用HTTPs協(xié)議。
  • 2.域名 應(yīng)該盡量將API部署在專用域名之下证薇。 https://api.example.com 如果確定API很簡單度苔,不會有進一步擴展,可以考慮放在主域名下浑度。 https://example.org/api/
  • 3.版本(Versioning) 應(yīng)該將API的版本號放入URL寇窑。 https://api.example.com/v1/ 另一種做法是,將版本號放在HTTP頭信息中箩张,但不如放入URL方便和直觀疗认。Github采用這種做法。
  • 4.路徑(Endpoint) 路徑又稱"終點"(endpoint)伏钠,表示API的具體網(wǎng)址。 在RESTful架構(gòu)中谨设,每個網(wǎng)址代表一種資源(resource)熟掂,所以網(wǎng)址中不能有動詞,只能有名詞扎拣, 而且所用的名詞往往與數(shù)據(jù)庫的表格名對應(yīng)赴肚。 一般來說素跺,數(shù)據(jù)庫中的表都是同種記錄的"集合"(collection),所以API中的名詞也應(yīng)該使用復(fù)數(shù)誉券。
  • 5.HTTP動詞 對于資源的具體操作類型指厌,由HTTP動詞表示。 常用的HTTP動詞 GET(SELECT):從服務(wù)器取出資源(一項或多項)踊跟。 POST(CREATE):在服務(wù)器新建一個資源踩验。 PUT(UPDATE):在服務(wù)器更新資源(客戶端提供改變后的完整資源)。 PATCH(UPDATE):在服務(wù)器更新資源(客戶端提供改變的屬性)商玫。 DELETE(DELETE):從服務(wù)器刪除資源箕憾。 動詞舉例 GET /zoos:列出所有動物園 POST /zoos:新建一個動物園 GET /zoos/ID:獲取某個指定動物園的信息 PUT /zoos/ID:更新某個指定動物園的信息(提供該動物園的全部信息) PATCH /zoos/ID:更新某個指定動物園的信息(提供該動物園的部分信息) DELETE /zoos/ID:刪除某個動物園 GET /zoos/ID/animals:列出某個指定動物園的所有動物 DELETE /zoos/ID/animals/ID:刪除某個指定動物園的指定動物
  • 6.過濾信息(Filtering) 如果記錄數(shù)量很多,服務(wù)器不可能都將它們返回給用戶拳昌。API應(yīng)該提供參數(shù)袭异,過濾返回結(jié)果。 常見的參數(shù) ?limit=10:指定返回記錄的數(shù)量 ?offset=10:指定返回記錄的開始位置炬藤。 ?page=2&per_page=100:指定第幾頁御铃,以及每頁的記錄數(shù)。 ?sortby=name&order=asc:指定返回結(jié)果按照哪個屬性排序沈矿,以及排序順序上真。 ?animal_type_id=1:指定篩選條件 參數(shù)的設(shè)計允許存在冗余,即允許API路徑和URL參數(shù)偶爾有重復(fù)细睡。 比如谷羞,GET /zoo/ID/animals 與 GET /animals?zoo_id=ID 的含義是相同的。
  • 7.狀態(tài)碼(Status Codes) 服務(wù)器向用戶返回的狀態(tài)碼和提示信息 常見的狀態(tài)碼 200 OK - [GET]:服務(wù)器成功返回用戶請求的數(shù)據(jù)溜徙,該操作是冪等的(Idempotent)湃缎。 201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數(shù)據(jù)成功。 202 Accepted - []:表示一個請求已經(jīng)進入后臺排隊(異步任務(wù)) 204 NO CONTENT - [DELETE]:用戶刪除數(shù)據(jù)成功蠢壹。 400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發(fā)出的請求有錯誤嗓违,服務(wù)器沒有進行新建或修改數(shù)據(jù)的操作,該操作是冪等的图贸。 401 Unauthorized - []:表示用戶沒有權(quán)限(令牌蹂季、用戶名、密碼錯誤)疏日。 403 Forbidden - [] 表示用戶得到授權(quán)(與401錯誤相對)偿洁,但是訪問是被禁止的。 404 NOT FOUND - []:用戶發(fā)出的請求針對的是不存在的記錄沟优,服務(wù)器沒有進行操作涕滋,該操作是冪等的。 406 Not Acceptable - [GET]:用戶請求的格式不可得(比如用戶請求JSON格式挠阁,但是只有XML格式)宾肺。 410 Gone -[GET]:用戶請求的資源被永久刪除溯饵,且不會再得到的。 422 Unprocesable entity - [POST/PUT/PATCH] 當創(chuàng)建一個對象時锨用,發(fā)生一個驗證錯誤丰刊。 500 INTERNAL SERVER ERROR - [*]:服務(wù)器發(fā)生錯誤,用戶將無法判斷發(fā)出的請求是否成功增拥。
  • 8.錯誤處理(Error handling) 如果狀態(tài)碼是4xx啄巧,就應(yīng)該向用戶返回出錯信息。一般來說跪者,返回的信息中將error作為鍵名棵帽,出錯信息作為鍵值即可。 eg: { error: "Invalid API key" }
  • 9.返回結(jié)果 針對不同操作渣玲,服務(wù)器向用戶返回的結(jié)果應(yīng)該符合以下規(guī)范逗概。 GET /collection:返回資源對象的列表(數(shù)組) GET /collection/resource:返回單個資源對象 POST /collection:返回新生成的資源對象 PUT /collection/resource:返回完整的資源對象 PATCH /collection/resource:返回完整的資源對象 DELETE /collection/resource:返回一個空文檔
  • 10.Hypermedia API RESTful API最好做到Hypermedia,即返回結(jié)果中提供鏈接忘衍,連向其他API方法逾苫,使得用戶不查文檔,也知道下一步應(yīng)該做什么枚钓。
  • 11.其他 (1)API的身份認證應(yīng)該使用OAuth 2.0框架铅搓。 (2)服務(wù)器返回的數(shù)據(jù)格式,應(yīng)該盡量使用JSON搀捷,避免使用XML星掰。
2、返回數(shù)據(jù)類型
  • 盡管沒有限制必須返回的類型嫩舟,但是一般基于Web services的Rest返回JSON或者XML作為響應(yīng)氢烘。
  • 客戶端可以指定(使用HTTP Accept header)他們想要的資源類型嗎,服務(wù)器返回需要的資源家厌。 指明資源的Content-Type播玖。
3、REST API
  • GET 方式請求 /api/user/ 返回用戶列表
  • GET 方式請求 /api/user/1返回id為1的用戶
  • POST 方式請求 /api/user/ 通過user對象的JSON 參數(shù)創(chuàng)建新的user對象
  • PUT 方式請求 /api/user/3 更新id為3的發(fā)送json格式的用戶對象
  • DELETE 方式請求/api/user/4刪除 ID為 4的user對象
  • DELETE 方式請求/api/user/刪除所有user
4饭于、Spring4 Rest 注解
  • @RestController 此注解避免了每個方法都要加上@ResponseBody注解蜀踏。也就是說@RestController 自己戴上了 @ResponseBody注解,看以看作是 @Controller 和 @ResponseBody的結(jié)合體。
  • @RestController,表明該類的每個方法返回對象而不是視圖果覆。 它實際就是@Controller和@ResponseBody混合使用的簡寫方法。
  • @RequestBody 如果方法參數(shù)被 @RequestBody注解殖熟,Spring將綁定HTTP請求體到那個參數(shù)上燎猛。 如果那樣做,Spring將根據(jù)請求中的ACCEPT或者 Content-Type header(私下)使用 HTTP Message converters 來將http請求體轉(zhuǎn)化為domain對象。
  • @ResponseBody 如果方法加上了@ResponseBody注解候醒,Spring返回值到響應(yīng)體。如果這樣做的話杂瘸,Spring將根據(jù)請求中的 Content-Type header(私下)使用 HTTP Message converters 來將domain對象轉(zhuǎn)換為響應(yīng)體倒淫。
  • @ResponseBody的作用是將返回的對象放入響應(yīng)消息體中 ResponseEntity 是一個真實數(shù)據(jù).它代表了整個 HTTP 響應(yīng)(response). 它的好處是你可以控制任何對象放到它內(nèi)部。 可以指定狀態(tài)碼败玉、頭信息和響應(yīng)體敌土。它包含你想要構(gòu)建HTTP Response 的信息。
  • @PathVariable 此注解意味著一個方法參數(shù)應(yīng)該綁定到一個url模板變量[在'{}'里的一個]中 MediaType 帶著 @RequestMapping 注解,通過特殊的控制器方法你可以額外指定,MediaType來生產(chǎn)或者消耗运翼。
5返干、REST測試
  • POSTMAN測試 使用RestTemplate編寫測試用例
    • HTTP GET : getForObject, getForEntity
    • HTTP PUT : put(String url, Object request, String…urlVariables)
    • HTTP DELETE : delete
    • HTTP POST : postForLocation(String url, Object request, String… urlVariables), postForObject(String url, Object request, ClassresponseType, String… uriVariables)
    • HTTP HEAD : headForHeaders(String url, String… urlVariables)
    • HTTP OPTIONS : optionsForAllow(String url, String… urlVariables)
    • HTTP PATCH and others : exchange execute
6、others
  • REST的優(yōu)勢 由于REST強制所有的操作都必須是stateless的血淌,這就沒有上下文的約束矩欠,如果做分布式,集群都不需要考慮上下文和會話保持的問題悠夯。
  • 極大的提高系統(tǒng)的可伸縮性 Webservice選擇 SOAP偏向于面向活動癌淮,有嚴格的規(guī)范和標準,包括安全疗疟,事務(wù)等各個方面的內(nèi)容该默, 同時SOAP強調(diào)操作方法和操作對象的分離,有WSDL文件規(guī)范和XSD文件分別對其定義策彤。
  • REST強調(diào)面向資源 只要我們要操作的對象可以抽象為資源即可以使用REST架構(gòu)風(fēng)格栓袖。
  • REST ful 應(yīng)用問題 是否使用REST就需要考慮資源本身的抽象和識別是否困難,如果本身就是簡單的類似增刪改查的業(yè)務(wù)操作店诗,那么抽象資源就比較容易裹刮,而對于復(fù)雜的業(yè)務(wù)活動抽象資源并不是一個簡單的事情。
  • 比如校驗用戶等級庞瘸,轉(zhuǎn)賬捧弃,事務(wù)處理等,這些往往并不容易簡單的抽象為資源。 其次如果有嚴格的規(guī)范和標準定義要求违霞,而且前期規(guī)范標準需要指導(dǎo)多個業(yè)務(wù)系統(tǒng)集成和開發(fā)的時候嘴办,SOAP風(fēng)格由于有清晰的規(guī)范標準定義是明顯有優(yōu)勢的。我們可以在開始和實現(xiàn)之前就嚴格定義相關(guān)的接口方法和接口傳輸數(shù)據(jù)买鸽。

一涧郊、Spring AOP的第七種方式說明

在使用Spring AOP的第七種方式過程中,我遇到了一個問題:如下圖

[圖片上傳失敗...(image-567da7-1584003007772)]

image.png

自己的代碼中沒有EventListenerMethodProcessor眼五、DefaultEventListenerFactory與public abstract void org.springframework.beans.factory.SmartInitializingSingleton.afterSingletonsInstantiated()這些方法妆艘,為什么還會運行。經(jīng)過實驗看幼,這些方法是Spring bean在實例化與初始化的過程中會自動執(zhí)行的批旺,并且還和spring版本有關(guān),在4.3.16上會出現(xiàn)诵姜,在5.2.2上就不會出現(xiàn)汽煮,前兩個方法只會運行一次,最后一個方法有一個單例模式的bean初始化了就會執(zhí)行茅诱。

bean的生命周期:

在xml文件bean標簽中可以添加init-method與destroy-method分別指定bean對象的初始化方法與銷毀方法逗物,屬性值對應(yīng)的是該類中的方法:

<bean id="st" class="com.qianfeng.aop05.Student" init-method="drink" destroy-method="eat" />

二、Spring MVC

m:model:模型瑟俭,javabean

v:view:視圖翎卓,html/jsp

c:controller:控制器:servlet

為了保證數(shù)據(jù)的安全性,建議將一些比較重要的頁面放在WEB-INF目錄下摆寄,這樣外界就無法直接訪問失暴,服務(wù)器內(nèi)部可以通過轉(zhuǎn)發(fā)訪問。

1微饥、需要的依賴

使用maven方式創(chuàng)建web項目不需要導(dǎo)入Tomcat的包逗扒,只需要Tomcat插件,由于缺少了一些包欠橘,所以需要導(dǎo)入一些依賴:

<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.2</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.0.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

第一個依賴是jsp的依賴矩肩,第二個是servlet的依賴,第三個是jstl的依賴肃续。

3黍檩、轉(zhuǎn)發(fā)器

DispatcherServlet.java

package com.qianfeng.controller;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(urlPatterns = {"/ProductInput","/ProductDetail"})
public class DispatcherServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String uri = req.getRequestURI();
        uri = uri.substring(uri.lastIndexOf("/")+1);
        String url = null;
        Controller controller = null;
        if("ProductInput".equalsIgnoreCase(uri)){
            controller = new ProductInput();
        }else if("ProductDetail".equalsIgnoreCase(uri)){
            controller = new ProductDetail();
        }
        if(controller!=null){
            url = controller.handleRequest(req,resp);
            req.getRequestDispatcher(url).forward(req,resp);
        }

    }
}

這個轉(zhuǎn)發(fā)器的作用相當于銀行的大堂經(jīng)理,它不負責處理具體的業(yè)務(wù)始锚,只負責轉(zhuǎn)發(fā)到響應(yīng)的頁面刽酱,這樣做可以將一些重復(fù)的工作提取出來,減少工作量瞧捌。

4棵里、控制器

Controller.java

package com.qianfeng.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface Controller {
    public String handleRequest(HttpServletRequest request, HttpServletResponse response);
}

這個接口的作用的指定規(guī)則润文,讓所有的控制器的業(yè)務(wù)類都實現(xiàn)這個接口,重寫處理業(yè)務(wù)的具體方法

ProductDetail.java

package com.qianfeng.controller;

import com.qianfeng.bean.Product;
import com.qianfeng.form.ProductForm;
import com.qianfeng.validate.ProductValidate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

public class ProductDetail implements Controller {

    @Override
    public String handleRequest(HttpServletRequest request, HttpServletResponse response) {
        String pname = request.getParameter("pname");
        String pid = request.getParameter("pid");
        String sprice = request.getParameter("price");
        String image = request.getParameter("image");

        double price = sprice == null||sprice.length()==0?0.0:Double.parseDouble(sprice);

        ProductForm pf = new ProductForm();
        pf.setPname(pname);
        pf.setPrice(price);
        pf.setImage(image);

        ProductValidate pv = new ProductValidate();
        List<String> errors = pv.validate(pf);
        String url = null;
        if(errors!=null && !errors.isEmpty()){
            request.setAttribute("errors",errors);
            url = "/WEB-INF/view/index.jsp";
        }else {
            Product p = new Product();
            p.setPid(Integer.parseInt(pid));
            p.setPname(pf.getPname());
            p.setPrice(pf.getPrice());
            p.setImage(pf.getImage());
            request.setAttribute("product",p);
            url = "/WEB-INF/view/product.jsp";
        }
        return url;
    }
}

ProductInput.java

package com.qianfeng.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ProductInput implements Controller {
    @Override
    public String handleRequest(HttpServletRequest request, HttpServletResponse response) {
        return "/WEB-INF/view/index.jsp";
    }
}

這兩個控制器實現(xiàn)類的作用就是處理具體的業(yè)務(wù)殿怜,并返回需要轉(zhuǎn)發(fā)的頁面典蝌。

ProductForm.java

package com.qianfeng.form;

public class ProductForm {
    private String pname;
    private double price;
    private String image;

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }
}

FormBean的作用是將JavaBean中的一些字段抽取出來單獨進行驗證,因為有些字段可能不符合格式或者沒有實際意義稳捆。

ProductValidate.java

package com.qianfeng.validate;

import com.qianfeng.form.ProductForm;

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

public class ProductValidate {
    public List<String> validate(ProductForm pf){
        List<String> errors = new ArrayList<>();

        if(pf.getPname()==null||pf.getPname().length()==0){
            errors.add("商品名稱不能為空赠法!");
        }

        if(pf.getPrice()<0){
            errors.add("商品價格不能為負!");
        }

        if(pf.getImage()==null||pf.getImage().length()==0){
            errors.add("商品圖片不能為空乔夯!");
        }
        return errors;
    }
}

這個類的具體作用就是對FormBean中的字段進行驗證

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市款侵,隨后出現(xiàn)的幾起案子末荐,更是在濱河造成了極大的恐慌,老刑警劉巖新锈,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甲脏,死亡現(xiàn)場離奇詭異,居然都是意外死亡妹笆,警方通過查閱死者的電腦和手機块请,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拳缠,“玉大人墩新,你說我怎么就攤上這事】咦” “怎么了海渊?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哲鸳。 經(jīng)常有香客問我臣疑,道長,這世上最難降的妖魔是什么徙菠? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任讯沈,我火速辦了婚禮,結(jié)果婚禮上婿奔,老公的妹妹穿的比我還像新娘缺狠。我一直安慰自己,他們只是感情好脸秽,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布儒老。 她就那樣靜靜地躺著,像睡著了一般记餐。 火紅的嫁衣襯著肌膚如雪驮樊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機與錄音囚衔,去河邊找鬼挖腰。 笑死,一個胖子當著我的面吹牛练湿,可吹牛的內(nèi)容都是我干的猴仑。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼肥哎,長吁一口氣:“原來是場噩夢啊……” “哼辽俗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起篡诽,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤崖飘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后杈女,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朱浴,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年达椰,在試婚紗的時候發(fā)現(xiàn)自己被綠了翰蠢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡啰劲,死狀恐怖梁沧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情呈枉,我是刑警寧澤趁尼,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站猖辫,受9級特大地震影響酥泞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜啃憎,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一芝囤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辛萍,春花似錦悯姊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辉阶,卻和暖如春先壕,著一層夾襖步出監(jiān)牢的瞬間瘩扼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工垃僚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留集绰,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓谆棺,卻偏偏與公主長得像栽燕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子改淑,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容