iOS 11无法连接App store怎么办 iOS 11 Appapple store无法连接接怎么办

重拾后端之Spring Boot(一):REST API的搭建可以这样简单 - 简书
重拾后端之Spring Boot(一):REST API的搭建可以这样简单
Spring Boot
话说我当年接触Spring的时候着实兴奋了好一阵,IoC的概念当初第一次听说,感觉有种开天眼的感觉。记得当时的web框架和如今的前端框架的局面差不多啊,都是群雄纷争。但一晃好多年没写过后端,代码这东西最怕手生,所以当作重新学习了,顺便写个学习笔记。
Spring Boot是什么?
还恍惚记得当初写Spring的时候要配置好多xml(在当时还是相对先进的模式),虽然实现了松耦合,但这些xml却又成为了项目甩不掉的负担 -- 随着项目越做越大,这些xml的可读性和可维护性极差。后来受.Net平台中Annotation的启发,Java世界中也引入了元数据的修饰符,Spring也可以使用这种方式进行配置。到了近些年,随着Ruby on Rails的兴起而流行开的 Convention over configuration 理念开始深入人心。那什么是 Convention over configuration 呢?简单来说就是牺牲一部分的自由度来减少配置的复杂度,打个比方就是如果你如果遵从我定义的一系列规则(打个比方,文件目录结构必须是blablabla的样子,文件命名必须是nahnahnah 的样子),那么你要配置的东西就非常简单甚至可以零配置。既然已经做到这个地步了,各种脚手架项目就纷纷涌现了,目的只有一个:让你更专注在代码的编写,而不是浪费在各种配置上。这两年前端也有类似趋势,各种前端框架的官方CLI纷纷登场:create-react-app,angular-cli,vue-cli等等。
那么Spring Boot就是Spring框架的脚手架了,它可以帮你快速搭建、发布一个Spring应用。官网列出了Spring Boot的几个主要目标
提供一种快速和广泛适用的Spring开发体验
开箱即用却又可以适应各种变化
提供一系列开发中常用的“非功能性”的特性(比如嵌入式服务器、安全、度量、自检及外部配置等)
不生成任何代码,不需要xml配置
安装Spring Boot
官方推荐的方式是通过sdkman(
)来进行安装,当然这是对 *nix 而言。题外话,如果你使用的是Windows 10,真心希望大家安装Windows 10的Linux子系统,微软官方出品、原生支持,比虚拟机不知道快到那里去了 具体安装过程可以参考
。安装 sdkman 的步骤非常简单,就两步:
打开一个terminal,输入 curl -s "https://get.sdkman.io" | bash
安装结束后,重启terminal,输入 source "$HOME/.sdkman/bin/sdkman-init.sh"
可以在terminal中验证一下是否安装成功 sdk version,如果你看到了版本号就是安装好了。
接下来,就可以安装Spring Boot了,还是打开terminal输入 sdk install springboot就ok了。
当然其实Mac的童鞋可以省略掉之前的sdkman安装直接使用 brew 安装,也是两步:
在terminal中输入 brew tap pivotal/tap
然后 brew install springboot
验证的话可以输入 spring --version 看看是否正常输出了版本号。
创建一个工程
有很多种方法可以创建一个Spring Boot项目,其中最简单的一种是通过一个叫Spring Initializr的在线工具 http://start.spring.io/ 进行工程的生成。如下图所示,只需填写一些参数就可以生成一个工程包了。
使用Spring Initializr进行工程的生成
如果你使用Intellij IDEA进行开发,里面也集成了这个工具,大家可以自行尝试。
Intellij IDEA中集成了 Spring Initializr
但下面我们要做的不是通过这种方式,而是手动的通过命令行方式创建。创建的是gradle工程,而不是maven的,原因呢是因为个人现在对于xml类型的配置文件比较无感;-),官方推荐使用gradle 2.14.1版本,请自行安装gradle。下面来建立一个gradle工程,其实步骤也不算太难:
新建一个工程目录 mkdir todo
在此目录下使用gradle进行初始化 gradle init(就和在node中使用 npm init 的效果类似)
这个命令帮我们建立一个一个使用gradle进行管理的模版工程:
build.gradle:有过Android开发经验的童鞋可能觉得很亲切的,这个就是我们用于管理和配置工程的核心文件了。
gradlew:用于 *nix 环境下的gradle wrapper文件。
gradlew.bat:用于 Windows 环境下的gradle wrapper文件
setting.gradle:用于管理多项目的gradle工程时使用,单项目时可以不做理会。
gradle目录:wrapper的jar和属性设置文件所在的文件夹。
简单说两句什么是 gradle wrapper。你是否有过这样的经历?在安装/编译一个工程时需要一些先决条件,需要安装一些软件或设置一些参数。如果这一切比较顺利还好,但很多时候我们会发现这样那样的问题,比如版本不对,参数没设置等等。gradle wrapper 就是这样一个让你不会浪费时间在配置问题上的方案。它会对应一个开发中使用的gradle版本,以确保任何人任何时候得到的结果是一致的。
./gradlew &task&:
在 *nix 平台上运行,例如Linux或Mac OS X
gradlew &task& 在Windows平台运行(是通过gradlew.bat来执行的)
更多关于wrapper的知识可以去
那么下面我们打开默认生成的 build.gradle 文件,将其改造成下面的样子:
* 这个build文件是由Gradle的 `init` 任务生成的。
* 更多关于在Gradle中构建Java项目的信息可以查看Gradle用户文档中的
* Java项目快速启动章节
* https://docs.gradle.org/3.3/userguide/tutorial_java_projects.html
// 在这个段落中你可以声明你的build脚本需要的依赖和解析下载该依赖所使用的仓储位置
buildscript {
springBootVersion = '1.4.3.RELEASE'
repositories {
mavenCentral()
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
* 在这个段落中你可以声明使用哪些插件
* apply plugin: 'java' 代表这是一个Java项目,需要使用java插件
* 如果想生成一个 `Intellij IDEA` 的工程,类似的如果要生成
* eclipse工程,就写 apply plugin: 'eclipse'
* 同样的我们要学的是Spring Boot,所以应用Spring Boot插件
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
// 在这个段落中你可以声明编译后的Jar文件信息
baseName = 'todo'
version = '0.0.1-SNAPSHOT'
// 在这个段落中你可以声明在哪里可以找到你的项目依赖
repositories {
// 使用 'jcenter' 作为中心仓储查询解析你的项目依赖。
// 你可以声明任何 Maven/Ivy/file 类型的依赖类库仓储位置
mavenCentral()
// 在这个段落中你可以声明源文件和目标编译后的Java版本兼容性
sourceCompatibility = 1.8
targetCompatibility = 1.8
// 在这个段落你可以声明你的项目的开发和测试所需的依赖类库
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
首先脚本依赖中的 spring-boot-gradle-plugin 有什么作用呢?它提供了以下几个功能:
简化执行和发布:它可以把所有classpath的类库构建成一个单独的可执行jar文件,这样可以简化你的执行和发布等操作。
自动搜索入口文件:它会扫描 public static void main() 函数并且标记这个函数的宿主类为可执行入口。
简化依赖:一个典型的Spring应用还是需要很多依赖类库的,想要配置正确这些依赖挺麻烦的,所以这个插件提供了内建的依赖解析器会自动匹配和当前Spring Boot版本匹配的依赖库版本。
在最后一个段落中,我们看到我们的项目依赖两个类库,一个是 spring-boot-starter-web ,另一个是 spring-boot-starter-test。Spring Boot提供了一系列依赖类库的“模版”,这些“模版”封装了很多依赖类库,可以让我们非常方便的引用自己想实现的功能所需要的类库。如果我们去看看这个 spring-boot-starter-web 中究竟引用了什么,我们可以看看它的artifact文件(到
可以查看):
&?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&org.springframework.boot&/groupId&
&artifactId&spring-boot-starters&/artifactId&
&version&1.4.3.RELEASE&/version&
&artifactId&spring-boot-starter-web&/artifactId&
&name&Spring Boot Web Starter&/name&
&description&Starter for building web, including RESTful, applications using Spring
MVC. Uses Tomcat as the default embedded container&/description&
&url&http://projects.spring.io/spring-boot/&/url&
&organization&
&name&Pivotal Software, Inc.&/name&
&url&http://www.spring.io&/url&
&/organization&
&properties&
&main.basedir&${basedir}/../..&/main.basedir&
&/properties&
&dependencies&
&dependency&
&groupId&org.springframework.boot&/groupId&
&artifactId&spring-boot-starter&/artifactId&
&/dependency&
&dependency&
&groupId&org.springframework.boot&/groupId&
&artifactId&spring-boot-starter-tomcat&/artifactId&
&/dependency&
&dependency&
&groupId&org.hibernate&/groupId&
&artifactId&hibernate-validator&/artifactId&
&/dependency&
&dependency&
&groupId&com.fasterxml.jackson.core&/groupId&
&artifactId&jackson-databind&/artifactId&
&/dependency&
&dependency&
&groupId&org.springframework&/groupId&
&artifactId&spring-web&/artifactId&
&/dependency&
&dependency&
&groupId&org.springframework&/groupId&
&artifactId&spring-webmvc&/artifactId&
&/dependency&
&/dependencies&
&/project&
一般做Java开发,大部分团队还是喜欢用一个IDE,虽然我还是更偏爱文本编辑器类型的(比如sublime,vscode,atom等)。但是如果非挑一个重型IDE的话,我更喜欢Intellij IDEA。
使用IDEA的import project功能选中 build.gradle,将工程导入。由于是个gradle工程,请把 View-&Tools Window-&Gradle 的视图窗口调出来。
Gradle工具窗口
点击左上角的刷新按钮可以将所有依赖下载类库下来。注意IDEA有时提示是否要配置wrapper使用带源码的gradle包。
提示使用带源码的gradle以便有API的文档
如果遇到不知道什么原因导致一直刷新完成不了的情况,请在项目属性中选择 Use local gradle distribution
image_1b77vqast1d2h1qioeepsdm1tkb9.png-192.5kB
第一个Web API
那么我们的源代码目录在哪里呢?我们得手动建立一个,这个目录一般情况下是 src/main/java。好的,下面我们要开始第一个RESTful的API搭建了,首先还是在 src/main/java 下新建一个 package。既然是本机的就叫 dev.local 吧。我们还是来尝试建立一个 Todo 的Web API,在 dev.local 下建立一个子 package: todo,然后创建一个Todo的领域对象:
package dev.local.
* Todo是一个领域对象(domain object)
* Created by wangpeng on .
public class Todo {
public String getId() {
public void setId(String id) {
public String getDesc() {
public void setDesc(String desc) {
this.desc =
public boolean isCompleted() {
public void setCompleted(boolean completed) {
这个对象很简单,只是描述了todo的几个属性: id 、 desc 和 completed 。我们的API返回或接受的参数就是以这个对象为模型的类或集合。
构造Controller
我们经常看到的RESTful API是这样的:http://local.dev/todos、http://local.dev/todos/1 。Controller就是要暴露这样的API给外部使用。现在我们同样的在 todo 下建立一个叫 TodoController 的java文件
package dev.local.
import org.springframework.web.bind.annotation.RequestM
import org.springframework.web.bind.annotation.RestC
import java.util.ArrayL
import java.util.L
* 使用@RestController来标记这个类是个Controller
@RestController
public class TodoController {
// 使用@RequstMapping指定可以访问的URL路径
@RequestMapping("/todos")
public List&Todo& getAllTodos() {
List&Todo& todos = new ArrayList&&();
Todo item1 = new Todo();
item1.setId("1");
item1.setCompleted(false);
item1.setDesc("go swimming");
todos.add(item1);
Todo item2 = new Todo();
item2.setId("1");
item2.setCompleted(true);
item2.setDesc("go for lunch");
todos.add(item2);
上面这个文件也比较简单,但注意到以下几个事情:
@RestController 和 @RequestMapping 这两个是元数据注释,原来在.Net中很常见,后来Java也引进过来。一方面它们可以增加代码的可读性,另一方面也有效减少了代码的编写。具体机理就不讲了,简单来说就是利用Java的反射机制和IoC模式结合把注释的特性或属性注入到被注释的对象中。
我们看到 List&Todo& getAllTodos() 方法中简单的返回了一个List,并未做任何转换成json对象的处理,这个是Spring会自动利用 Jackson 这个类库的方法将其转换成了json。
我们到这就基本接近成功了,但是现在缺少一个入口,那么在 dev.local 包下面建立一个 Applicaiton.java 吧。
package dev.
import org.springframework.boot.SpringA
import org.springframework.boot.autoconfigure.SpringBootA
* Spring Boot 应用的入口文件
* Created by wangpeng on .
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
同样的我们只需标注这个类是 @SpringBootApplication 就万事大吉了。可以使用下面任意一种方法去启动我们的Web服务:
命令行中: ./gradlew bootRun
IDEA中在 Application 中右键选择 Run 'Application'
IDE中右键启动应用
启动后,打开浏览器访问 http://localhost:8080/todos 就可以看到我们的json形式的返回结果了。
浏览器直接访问一下的效果
配置Spring Beans工具
由于使用的是Spring框架,Spring工具窗口也是需要的,一般来说如果你安装了Spring插件的话,IDEA会自动探测到你的项目是基于Spring的。一般在你增加了Applicaiton入口后,会提示是否添加context。
检测到Spring配置,但提示尚未关联
遇到这种情况,请点提示框的右方的下箭头展开提示。
展开后的提示框
点击 Create Default Context 会将目前的所有没有map的Spring配置文件都放在这个默认配置的上下文中。在Spring的工具窗口中可以看到下图效果。
创建Spring配置的默认上下文
本章代码:
大叔级码农,技术初级、视野中级、眼镜度数高级Spring Secure在SpringBoot中的集成 - 经过一个过程,然后成为自己 - ITeye技术网站
博客分类:
Spring Secure4在使用上和Secure3差别不大,基本上怎么使用3就可以怎么使用4。而且4也是推荐使用命名空间进行配置,不过由于SpringBoot推荐不使用xml配置,所以我们这里说的都是不使用xml的。sprngboot默认引入的是3,4也类似。
要在项目中通过maven引入spring secure有两种方式,如果使用springboot的starter是这样的:
&dependency&
&groupId&org.springframework.boot&/groupId&
&artifactId&spring-boot-starter-security&/artifactId&
&/dependency&
也可以直接使用secure的引入方式:
&dependency&
&groupId&org.springframework.security&/groupId&
&artifactId&spring-security-web&/artifactId&
&/dependency&
&dependency&
&groupId&org.springframework.security&/groupId&
&artifactId&spring-security-config&/artifactId&
&/dependency&
现在的springboot会把Secure3.2.8引过来。
接下来开始进行配置,只需要新建一个类即可:
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
@EnableWebMvcSecurity
public class WebAuthConfiguration extends WebSecurityConfigurerAdapter {}
这里我把这个类命名为webauthconfiguration,它需要继承自org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter。
然后给这个类添加Configuration注解,来让springboot启动的时候加载该配置。然后加上enablewebmvcsecurity注解启动secure配置。至于enableglobalmethodsecurity注解加不加都可以,开启方法级别配置的。
到现在secure就添加好了而且可以正常工作,但是使用的是默认配置:
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
.authorizeRequests()
.anyRequest().authenticated()
.formLogin().and()
.httpBasic();
默认配置说所有的请求都需要进行安全认证,登陆使用默认的form表单(会弹出一个可怕的登陆框),并使用http Basic 认证,就是通过密码和角色。
我们重新这个方法,并根据自己的需要增加配置:
http.authorizeRequests().antMatchers("/assets/", "/").permitAll()
.anyRequest().authenticated()
.and().formLogin().usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login").loginPage("/login")
.and().logout().permitAll().logoutUrl("/logout").logoutSuccessUrl("/login")
.logoutSuccessHandler(logoutSuccessHandler)
.invalidateHttpSession(true).addLogoutHandler(logoutHandler).deleteCookies(new String[] { "cookie名字" })
.and().rememberMe();
第一行是说访问/和匹配/assets/**模式的url都可以直接访问,下面说其他的需要认证。使用form表单登录,为什么要指定这个呢?因为spring会从请求中查找username和password域参数。从哪个请求中呢?默认是/login,可以通过login(String s)自定义。表单提交的参数也可以通过usernameParameter()和passwordParamter()自定义。如果不使用默认的弹出框而使用自己的页面,表单的action必须和loginProcessingUrl()指定的一样,当然也需要是post方式。
再往下是允许spring控制登出。默认访问/logout会执行登出,spring会使session无效,并清理rememberMe生成的cookie。logoutUrl()可以自定义登出的url,成功登出后的跳转url由logoutSuccessUrl()指定,默认是/login?logout,你可以这个页面判断有Logout参数就提示用户登出成功。
再往后配置自动登录,使用rememberMe()方法。自动登录有两种方法,一个是由org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices类配置,另一个由org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices配置,差别是用不用数据库。
登录认证需要在这个类中新建一个autowired方法configureGlobal(auth):
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username,password, enabled from users
where username=?")
.authoritiesByUsernameQuery("select username, role from user_roles
where username=?");
有四种方法,第一种使用内存配置,就是直接使用常量串,我感觉用的很少;第二种是上面这段代码用的jdbc方式,第三种是ldap方式;第四种是使用userDetailsService:
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.eraseCredentials(false).userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
因为需要自动登录,就把eraseCredentials设为false。
到现在为止,就把登录认证包括自动登录和url访问认证以及登出配好了。可以往数据库加入一个用户试一下,刚才看到登录的时候使用了passwordEncoder,因此插入用户的时候也要这样加密密码。
somefuture
浏览: 418552 次
来自: 上海
请问,对于自定义的登录页面,我的ajax请求方式该怎么写呢,是 ...
gradle+maven+springboot实战课程观看地址 ...
翻译的内容在哪里?
关键是这有啥用?
我用的比较传统的方法,就是写一个supportDao的类,把基 ...您的位置: >>
  前后端分离已经是业界所共识的一种开发/部署模式了。所谓的前后端分离,并不是传统行业中的按部门划分,一部分人纯做前端(HTML/CSS/JavaScript/Flex),另一部分人纯做后端,因为这种方式是不工作的:比如很多团队采取了后端的模板技术(JSP, FreeMarker, ERB等等),前端的开发和调试需要一个后台Web容器的支持,从而无法做到真正的分离(更不用提在部署的时候,由于动态内容和静态内容混在一起,当设计动态静态分流的时候,处理起来非常麻烦)。关于前后端开发的另一个讨论可以。
  即使通过API来解耦前端和后端开发过程,前后端通过RESTFul的接口来通信,前端的静态内容和后端的动态计算分别开发,分别部署,集成仍然是一个绕不开的问题 & 前端/后端的应用都可以独立的运行,但是集成起来却不工作。我们需要花费大量的精力来调试,直到上线前仍然没有人有信心所有的接口都是工作的。
  一点背景
  一个典型的Web应用的布局看起来是这样的:
  前后端都各自有自己的开发流程,构建工具,测试集合等等。前后端仅仅通过接口来编程,这个接口可能是JSON格式的RESTFul的接口,也可能是XML的,重点是后台只负责数据的提供和计算,而完全不处理展现。而前端则负责拿到数据,组织数据并展现的工作。这样结构清晰,关注点分离,前后端会变得相对独立并松耦合。
  上述的场景还是比较理想,我们事实上在实际环境中会有非常复杂的场景,比如异构的网络,异构的操作系统等等:
  在实际的场景中,后端可能还会更复杂,比如用C语言做数据采集,然后通过Java整合到一个数据仓库,然后该数据仓库又有一层Web Service,最后若干个这样的Web Service又被一个Ruby的聚合Service整合在一起返回给前端。在这样一个复杂的系统中,后台任意端点的失败都可能阻塞前端的开发流程,因此我们会采用mock的方式来解决这个问题:
  这个mock服务器可以启动一个简单的HTTP服务器,然后将一些静态的内容serve出来,以供前端代码使用。这样的好处很多:
前后端开发相对独立
后端的进度不会影响前端开发
启动速度更快
前后端都可以使用自己熟悉的技术栈(让前端的学maven,让后端的用gulp都会很不顺手)
  但是当集成依然是一个令人头疼的难题。我们往往在集成的时候才发现,本来协商的数据结构变了:deliveryAddress字段本来是一个字符串,现在变成数组了(业务发生了变更,系统现在可以支持多个快递地址);price字段变成字符串,协商的时候是number;用户邮箱地址多了一个层级等等。这些变动在所难免,而且时有发生,这会花费大量的调试时间和集成时间,更别提修改之后的回归测试了。
  所以仅仅使用一个静态服务器,然后提供mock数据是远远不够的。我们需要的mock应该还能做到:
前端依赖指定格式的mock数据来进行UI开发
前端的开发和测试都基于这些mock数据
后端产生指定格式的mock数据
后端需要测试来确保生成的mock数据正是前端需要的
  简而言之,我们需要商定一些契约,并将这些契约作为可以被测试的中间格式。然后前后端都需要有测试来使用这些契约。一旦契约发生变化,则另一方的测试会失败,这样就会驱动双方协商,并降低集成时的浪费。
  一个实际的场景是:前端发现已有的某个契约中,缺少了一个address的字段,于是就在契约中添加了该字段。然后在UI上将这个字段正确的展现了(当然还设置了字体,字号,颜色等等)。但是后台生成该契约的服务并没有感知到这一变化,当运行生成契约部分测试(后台)时,测试会失败了 & 因为它并没有生成这个字段。于是后端工程师就找前端来商量,了解业务逻辑之后,他会修改代码,并保证测试通过。这样,当集成的时候,就不会出现UI上少了一个字段,但是谁也不知道是前端问题,后端问题,还是数据库问题等。
  而且实际的项目中,往往都是多个页面,多个API,多个版本,多个团队同时进行开发,这样的契约会降低非常多的调试时间,使得集成相对平滑。
  在实践中,契约可以定义为一个JSON文件,或者一个XML的payload。只需要保证前后端共享同一个契约集合来做测试,那么集成工作就会从中受益。一个最简单的形式是:提供一些静态的mock文件,而前端所有发往后台的请求都被某种机制拦截,并转换成对该静态资源的请求。
,基于Java
,基于Java
,基于Ruby
  看到sinatra被列在这里,可能熟悉Ruby的人会反对:它可是一个后端全功能的的程序库啊。之所以列它在这里,是因为sinatra提供了一套简洁优美的DSL,这个DSL非常契合Web语言,我找不到更漂亮的方式来使得这个mock server更加易读,所以就采用了它。
  一个例子
  我们以这个应用为示例,来说明如何在前后端分离之后,保证代码的质量,并降低集成的成本。这个应用场景很简单:所有人都可以看到一个条目列表,每个登陆用户都可以选择自己喜欢的条目,并为之加星。加星之后的条目会保存到用户自己的个人中心中。用户界面看起来是这样的:
  不过为了专注在我们的中心上,我去掉了诸如登陆,个人中心之类的页面,假设你是一个已登录用户,然后我们来看看如何编写测试。
  前端开发
  根据通常的做法,前后端分离之后,我们很容易mock一些数据来自己测试:
"url": "/2015/03/list-comprehension-in-python/",
"title": "Python中的 list comprehension 以及 generator",
"publicDate": "日"
"url": "/2015/03/build-monitor-script-based-on-inotify/",
"title": "使用inotify/fswatch构建自动监控脚本",
"publicDate": "日"
"url": "/2015/02/build-sample-application-by-using-underscore-and-jquery/",
"title": "使用underscore.js构建前端应用",
"publicDate": "日"
  然后,一个可能的方式是通过请求这个json来测试前台:
$(function() {
$.get('/mocks/feeds.json').then(function(feeds) {
var feedList = new Backbone.Collection(extended);
var feedListView = new FeedListView(feedList);
$('.container').append(feedListView.render());
  这样当然是可以工作的,但是这里发送请求的url并不是最终的,当集成的时候我们又需要修改为真实的url。一个简单的做法是使用Sinatra来做一次url的转换:
get '/api/feeds' do
content_type 'application/json'
File.open('mocks/feeds.json').read
  这样,当我们和实际的服务进行集成时,只需要连接到那个服务器就可以了。
  注意,我们现在的核心是mocks/feeds.json这个文件。这个文件现在的角色就是一个契约,至少对于前端来说是这样的。紧接着,我们的应用需要渲染加星的功能,这就需要另外一个契约:找出当前用户加星过的所有条目,因此我们加入了一个新的契约:
"url": "/2015/02/build-sample-application-by-using-underscore-and-jquery/",
"title": "使用underscore.js构建前端应用",
"publicDate": "日"
  然后在sinatra中加入一个新的映射:
get '/api/fav-feeds/:id' do
content_type 'application/json'
File.open('mocks/fav-feeds.json').read
  通过这两个请求,我们会得到两个列表,然后根据这两个列表的交集来绘制出所有的星号的状态(有的是空心,有的是实心):
$.when(feeds, favorite).then(function(feeds, favorite) {
var ids = _.pluck(favorite[0], 'id');
var extended = _.map(feeds[0], function(feed) {
return _.extend(feed, {status: _.includes(ids, feed.id)});
var feedList = new Backbone.Collection(extended);
var feedListView = new FeedListView(feedList);
$('.container').append(feedListView.render());
  剩下的一个问题是当点击红心时,我们需要发请求给后端,然后更新红心的状态:
toggleFavorite: function(event) {
event.preventDefault();
var that =
$.post('/api/feeds/'+this.model.get('id')).done(function(){
var status = that.model.get('status');
that.model.set('status', !status);
  这里又多出来一个请求,不过使用Sinatra我们还是可以很容易的支持它:
post '/api/feeds/:id' do
  可以看到,在没有后端的情况下,我们一切都进展顺利 & 后端甚至还没有开始做,或者正在由一个进度比我们慢的团队在开发,不过无所谓,他们不会影响我们的。
  不仅如此,当我们写完前端的代码之后,可以做一个End2End的测试。由于使用了mock数据,免去了数据库和网络的耗时,这个End2End的测试会运行的非常快,并且它确实起到了端到端的作用。这些测试在最后的集成时,还可以用来当UI测试来运行。所谓一举多得。
#encoding: utf-8
require 'spec_helper'
describe 'Feeds List Page' do
let(:list_page) {FeedListPage.new}
list_page.load
it 'user can see a banner and some feeds' do
expect(list_page).to have_banner
expect(list_page).to have_feeds
it 'user can see 3 feeds in the list' do
expect(list_page.all_feeds).to have_feed_items count: 3
it 'feed has some detail information' do
first = list_page.all_feeds.feed_items.first
expect(first.title).to eql("Python中的 list comprehension 以及 generator")
  关于如何编写这样的测试,可以参考之前写的。
  后端开发
  我在这个示例中,后端采用了spring-boot作为示例,你应该可以很容易将类似的思路应用到Ruby或者其他语言上。
  首先是请求的入口,FeedsController会负责解析请求路径,查数据库,最后返回JSON格式的数据。
@Controller
@RequestMapping("/api")
public class FeedsController {
@Autowired
private FeedsService feedsS
@Autowired
private UserService userS
public void setFeedsService(FeedsService feedsService) {
this.feedsService = feedsS
public void setUserService(UserService userService) {
this.userService = userS
@RequestMapping(value="/feeds", method = RequestMethod.GET)
@ResponseBody
public Iterable&Feed& allFeeds() {
return feedsService.allFeeds();
@RequestMapping(value="/fav-feeds/{userId}", method = RequestMethod.GET)
@ResponseBody
public Iterable&Feed& favFeeds(@PathVariable("userId") Long userId) {
return userService.favoriteFeeds(userId);
  具体查询的细节我们就不做讨论了,感兴趣的可以在文章结尾处找到代码库的链接。那么有了这个Controller之后,我们如何测试它呢?或者说,如何让契约变得实际可用呢?
  sprint-test提供了非常优美的DSL来编写测试,我们仅需要一点代码就可以将契约用起来,并实际的监督接口的修改:
private MockMvc mockM
private FeedsService feedsS
private UserService userS
public void setup() {
feedsService = mock(FeedsService.class);
userService = mock(UserService.class);
FeedsController feedsController = new FeedsController();
feedsController.setFeedsService(feedsService);
feedsController.setUserService(userService);
mockMvc = standaloneSetup(feedsController).build();
  建立了mockmvc之后,我们就可以编写Controller的单元测试了:
public void shouldResponseWithAllFeeds() throws Exception {
when(feedsService.allFeeds()).thenReturn(Arrays.asList(prepareFeeds()));
mockMvc.perform(get("/api/feeds"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/charset=UTF-8"))
.andExpect(jsonPath("$", hasSize(3)))
.andExpect(jsonPath("$[0].publishDate", is(notNullValue())));
  当发送GET请求到/api/feeds上之后,我们期望返回状态是200,然后内容是application/json。然后我们预期返回的结果是一个长度为3的数组,然后数组中的第一个元素的publishDate字段不为空。
  注意此处的prepareFeeds方法,事实上它会去加载mocks/feeds.json文件 & 也就是前端用来测试的mock文件:
private Feed[] prepareFeeds() throws IOException {
URL resource = getClass().getResource("/mocks/feeds.json");
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(resource, Feed[].class);
  这样,当后端修改Feed定义(添加/删除/修改字段),或者修改了mock数据等,都会导致测试失败;而前端修改mock之后,也会导致测试失败 & 不要惧怕失败 & 这样的失败会促进一次协商,并驱动出最终的service的契约。
  对应的,测试/api/fav-feeds/{userId}的方式类似:
public void shouldResponseWithUsersFavoriteFeeds() throws Exception {
when(userService.favoriteFeeds(any(Long.class)))
.thenReturn(Arrays.asList(prepareFavoriteFeeds()));
mockMvc.perform(get("/api/fav-feeds/1"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/charset=UTF-8"))
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].title", is("使用underscore.js构建前端应用")))
.andExpect(jsonPath("$[0].publishDate", is(notNullValue())));
  前后端分离是一件容易的事情,而且团队可能在短期可以看到很多好处,但是如果不认真处理集成的问题,分离反而可能会带来更长的集成时间。通过面向契约的方式来组织各自的测试,可以带来很多的好处:更快速的End2End测试,更平滑的集成,更安全的分离开发等等。
  前后端的代码我都放到了Gitbub上,感兴趣的可以clone下来自行研究:
Web前端热门文章
Web前端最新文章

我要回帖

更多关于 mac appstore无法连接 的文章

 

随机推荐