SpringBoot是快速开发企业应用的一条捷径,尤其是在Spring4以后,项目完全不需要xml配置以来,让开发者感到巨大的流畅感。
我们使用Eclipse搭建一个基于maven的SpringBoot项目,不了解的可以看一下《用maven搭建springboot环境》。
新建maven项目,把需要的源包都建好,可以删掉web.xml文件,然后打开pom文件。这里我们的依赖要比上面文章里的多一些。
首先删除自动生成的Junit依赖,我们的单元测试虽然也是Junit,不过SpringBoot会自动添加依赖。
然后按照上面那篇文章添加parent
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.2.6.RELEASE</version> </parent>
这里使用的是最新的126发布版,所以不需要像文章中提到的那样增加下面的库
<!-- 使用 Spring repositories --> <!-- (我们使用的是SNAPSHOT版本,如果用RELEASE版本下面的偶不用写) --> <repositories> <repository> <id>spring-snapshots</id> <url>http://repo.spring.io/snapshot</url> <snapshots><enabled>true</enabled></snapshots> </repository> <repository> <id>spring-milestones</id> <url>http://repo.spring.io/milestone</url> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-snapshots</id> <url>http://repo.spring.io/snapshot</url> </pluginRepository> <pluginRepository> <id>spring-milestones</id> <url>http://repo.spring.io/milestone</url> </pluginRepository> </pluginRepositories>
接下来添加我们这里需要的几个依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.hateoas</groupId> <artifactId>spring-hateoas</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
第一个是SpringBoot的单元测试依赖;第二个是最主要的,用来开发webapp的web依赖;第三个是SpringBoot开发Restful服务的HATEOAS依赖;第四个是管理员使用的接口依赖。
然后我们新建一个Model
public class Greeting extends ResourceSupport { private long gid; private String content; @JsonCreator @JsonIgnoreProperties(ignoreUnknown = true) public Greeting(@JsonProperty(value = "gid") long gid, @JsonProperty(value = "content") String content) { this.gid = gid; this.content = content; }
这里只列出了类定义和构造器,你需要自己添加getter和setter方法。
这里的注解不是必须的,你可以都删掉。他们的作用可以自己看一下javadoc。
然后建一个controller
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; @RestController public class GreetingController { private static final String template = "Hello, %s"; private final AtomicLong counter = new AtomicLong(); @RequestMapping("/greeting") public Greeting greeting(@RequestParam(value = "name", defaultValue = "world") String name) { Greeting greeting = new Greeting(counter.incrementAndGet(), String.format(template, name)); GreetingController methodOn = methodOn(GreetingController.class); // GreetingController methodOn = DummyInvocationUtils.methodOn(GreetingController.class); Greeting greeting2 = methodOn.greeting(name); ControllerLinkBuilder linkTo = linkTo(greeting2); Link withSelfRel = linkTo.withSelfRel(); greeting.add(withSelfRel); return greeting; } @RequestMapping("/show") public Greeting show(){ RestTemplate template = new RestTemplate(); Greeting greeting = template.getForObject("http://localhost:8080/greeting?name=323", Greeting.class); System.err.println(greeting); return greeting; } }
@restController注解表明里面的每个action都返回的不是view。
然后我们使用内置的Tomcat服务器部署,这个服务器的性能和你本机下载的绿色版是一样的
@SpringBootApplication public class Application implements CommandLineRunner{ public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(Application.class, args); System.out.println("hohoho"); String[] names = context.getBeanDefinitionNames(); Arrays.sort(names); for (String string : names) { System.err.println(string); } } @Override public void run(String... args) throws Exception { RestTemplate template = new RestTemplate(); Greeting greeting = template.getForObject("http://localhost:8080/greeting?name=323", Greeting.class); System.err.println(greeting); } }
接下来我们分步验收我们的成果(就算成果吧)。
首先运行main方法,在浏览器里访问http://localhost:8080/greeting,你应该能看到你想看到的;然后访问http://localhost:8080/greeting?name=WhoAmI,应该和你预料的也一样,都是返回的JSON串。每个串里都有当前的访问路径,这就是HATEAOS的作用,人们认为调用服务的时候路径拼接不对很影响开发效率就非要加上这个。
我们已经验证了web依赖和hateaos依赖工作正常,接下来看一下actuator。不知道你注意到没有,应用启动的时候输出了很多类似
Mapped "{[/greeting],methods=[GET]}" onto ... Mapped "{[/env],methods=[GET]}" onto ... Mapped "{[/info],methods=[GET]}" onto ... Mapped "{[/health],methods=[GET]}" onto ...
的东西,是程序输出的而非我们打印的。
也行看到第一行你就会明白我们可以尝试访问http://localhost:8080/env 、 http://localhost:8080/health 、 http://localhost:8080/info等路径。actuator提供了很多管理员工具,更多的可以去官网了解。
接下来看一下单元测试。
在我们的GreetingController上面右键-新建-JUnit Test Case,把测试类的目录改到src/test/java下面。然后修改里面的代码,因为我们用不到它默认的测试方法
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = MockServletContext.class) @WebAppConfiguration public class GreetingControllerTest { private MockMvc mvc; @Before public void setUp() { mvc = MockMvcBuilders.standaloneSetup(new GreetingController()).build(); } @Test public void getHello() throws Exception { ResultActions actions = mvc.perform(MockMvcRequestBuilders.get("/greeting").accept(MediaType.APPLICATION_JSON)); actions.andExpect(status().isOk()); // actions.andExpect(content().string(equalTo("Hello world"))); } }
这里面用到了几个静态导入,eclipse对静态导入的处理简直令人发指,你可以copy进去:
import static org.hamcrest.Matchers.equalTo; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
运行它,应该可以通过。然后把最下面一行的注释去掉,也就是增加一个新断言,认为返回的内容是Hello world。这次一定通不过了。
最后我们看一下主运行文件里面的那个run方法
@Override public void run(String... args) throws Exception { RestTemplate template = new RestTemplate(); Greeting greeting = template.getForObject("http://localhost:8080/greeting?name=323", Greeting.class); System.err.println(greeting); }
看到上面的复写注解就知道它来自哪里了是吧,没错,就是实现的那个接口CommandLineRunner(所以完全可以不实现)。实现了这个接口的类(需要是一个bean,不过@SpringBootApplication注解有这个功能)在成功运行后会调用这个run方法。
我们在run中把返回的json重新组装成Greeting实例。为了更清晰,你最好给Greeting类增加
@Override public String toString()
这个能成功的关键是构造器的参数中使用了@JsonProperty注解,并且使用了value参数。你可以删掉试一下效果。
既然是web service,所以我们的最后一步就是打开CORS访问,不然异步访问不了。我不太明白为什么一个随时随地可以同步访问的地址在异步的时候非要限制。新建一个FIlter
class SimpleCORSFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); chain.doFilter(req, res); }
这个类实现了javax.servlet.Filter接口。既然我们没有了web.xml文件,怎么才能让它拦截请求呢?我们可以给这个类加上自动配置的注解,比如org.springframework.stereotype.Component或org.springframework.stereotype.Repository。