Mkyong 中文博客翻译(六十一)

原文:Mkyong

协议:CC BY-NC-SA 4.0

Spring 3 + Quartz 1.8.6 调度器示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring/spring-quartz-scheduler-example/

【2012 年 7 月 25 日更新–升级文章以使用 Spring 3 和 Quartz 1.8.6(原来是 Spring 2.5.6 和 Quartz 1.6)

在本教程中,我们将向您展示如何将 Spring 与 Quartz scheduler 框架集成。Spring 附带了许多方便的类来支持 Quartz,并将您的类解耦到 Quartz APIs。

使用的工具:

  1. 释放弹簧
  2. 石英
  3. Eclipse 4.2
  4. maven3

Why NOT Quartz 2?
Currently, Spring 3 is still NOT support Quartz 2 APIs, see this SPR-8581 bug report. Will update this article again once bug fixed is released.

1.项目依赖性

集成 Spring 3 和 Quartz 1.8.6 需要以下依赖项

文件:pom.xml

 ...
<dependencies>

	<!-- Spring 3 dependencies -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-core</artifactId>
		<version>3.1.2.RELEASE</version>
	</dependency>

	<!-- QuartzJobBean in spring-context-support.jar -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context-support</artifactId>
		<version>3.1.2.RELEASE</version>
	</dependency>

	<!-- Spring + Quartz need transaction -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-tx</artifactId>
		<version>3.1.2.RELEASE</version>
	</dependency>

	<!-- Quartz framework -->
	<dependency>
		<groupId>org.quartz-scheduler</groupId>
		<artifactId>quartz</artifactId>
		<version>1.8.6</version>
	</dependency>

</dependencies>
... 

2.调度程序任务

创建一个普通的 Java 类,这是您想要在 Quartz 中调度的类。

文件:RunMeTask.java

 package com.mkyong.common;

public class RunMeTask {
    
    
	public void printMe() {
    
    
		System.out.println("Spring 3 + Quartz 1.8.6 ~");
	}
} 

3.声明 Quartz 调度程序作业

使用 Spring,您可以通过两种方式声明 Quartz 作业:

3.1 MethodInvokingJobDetailFactoryBean
这是最简单直接的方法,适合简单的调度器。

 <bean id="runMeJob" 
 	class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">

	<property name="targetObject" ref="runMeTask" />
	<property name="targetMethod" value="printMe" />

</bean> 

3.2 JobDetailBeanT5QuartzJobBean更加灵活,适合复杂的调度器。你需要创建一个扩展 Spring 的QuartzJobBean类,并在executeInternal()方法中定义你想要调度的方法,并通过 setter 方法传递调度器任务(RunMeTask)。

文件:RunMeJob.java

 package com.mkyong.common;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class RunMeJob extends QuartzJobBean {
    
    
	private RunMeTask runMeTask;

	public void setRunMeTask(RunMeTask runMeTask) {
    
    
		this.runMeTask = runMeTask;
	}

	protected void executeInternal(JobExecutionContext context)
		throws JobExecutionException {
    
    

		runMeTask.printMe();

	}
} 

通过jobClass配置目标类,通过jobDataAsMap配置方法运行。

 <bean name="runMeJob" class="org.springframework.scheduling.quartz.JobDetailBean">

	<property name="jobClass" value="com.mkyong.common.RunMeJob" />

	<property name="jobDataAsMap">
		<map>
			<entry key="runMeTask" value-ref="runMeTask" />
		</map>
	</property>

</bean> 

4.引发

配置 Quartz 触发器以定义何时运行调度程序作业。支持两种类型触发器:

4.1 SimpleTrigger
它允许设置运行作业的开始时间、结束时间、重复间隔。

 <!-- Simple Trigger, run every 5 seconds -->
	<bean id="simpleTrigger" 
                class="org.springframework.scheduling.quartz.SimpleTriggerBean">

		<property name="jobDetail" ref="runMeJob" />
		<property name="repeatInterval" value="5000" />
		<property name="startDelay" value="1000" />

	</bean> 

4.2 CronTrigger
它允许 Unix cron 表达式指定运行作业的日期和时间。

 <!-- Cron Trigger, run every 5 seconds -->
	<bean id="cronTrigger"
		class="org.springframework.scheduling.quartz.CronTriggerBean">

		<property name="jobDetail" ref="runMeJob" />
		<property name="cronExpression" value="0/5 * * * * ?" />

	</bean> 

Note
The Unix cron expression is highly flexible and powerful, read more in following websites :

  1. http://en . Wikipedia . org/wiki/cron _ expression
  2. http://www.quartz-scheduler.org/docs/examples/Example3.html

5.调度程序工厂

创建一个调度器工厂 bean,将作业细节和触发器集成在一起。

 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
	<property name="jobDetails">
	   <list>
	      <ref bean="runMeJob" />
	   </list>
	</property>

	<property name="triggers">
	    <list>
		<ref bean="simpleTrigger" />
	    </list>
	</property>
   </bean> 

6.Spring Bean 配置文件

完成 Spring 的 bean 配置文件。

文件:Spring-Quartz.xml

 <beans 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

	<bean id="runMeTask" class="com.mkyong.common.RunMeTask" />

	<!-- Spring Quartz -->
	<bean name="runMeJob" class="org.springframework.scheduling.quartz.JobDetailBean">

		<property name="jobClass" value="com.mkyong.common.RunMeJob" />

		<property name="jobDataAsMap">
		  <map>
			<entry key="runMeTask" value-ref="runMeTask" />
		  </map>
		</property>

	</bean>

	<!-- 
	<bean id="runMeJob" 
            class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> 
		<property name="targetObject" ref="runMeTask" /> 
		<property name="targetMethod" value="printMe" /> 
	</bean> 
	-->

	<!-- Simple Trigger, run every 5 seconds -->
	<bean id="simpleTrigger" 
                class="org.springframework.scheduling.quartz.SimpleTriggerBean">

		<property name="jobDetail" ref="runMeJob" />
		<property name="repeatInterval" value="5000" />
		<property name="startDelay" value="1000" />

	</bean>

	<!-- Cron Trigger, run every 5 seconds -->
	<bean id="cronTrigger" 
                class="org.springframework.scheduling.quartz.CronTriggerBean">

		<property name="jobDetail" ref="runMeJob" />
		<property name="cronExpression" value="0/5 * * * * ?" />

	</bean>

	<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="jobDetails">
			<list>
				<ref bean="runMeJob" />
			</list>
		</property>

		<property name="triggers">
			<list>
				<ref bean="simpleTrigger" />
			</list>
		</property>
	</bean>

</beans> 

7.演示

跑吧~

 package com.mkyong.common;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App 
{
    
    
    public static void main( String[] args ) throws Exception
    {
    
    
    	new ClassPathXmlApplicationContext("Spring-Quartz.xml");
    }
} 

输出到控制台。

 Jul 25, 2012 3:23:09 PM org.springframework.scheduling.quartz.SchedulerFactoryBean startScheduler
INFO: Starting Quartz Scheduler now
Spring 3 + Quartz 1.8.6 ~ //run every 5 seconds
Spring 3 + Quartz 1.8.6 ~ 

下载源代码

Download it – Spring3-Quartz-Example.zip (25 KB)

参考

  1. 使用带弹簧的石英调度器
  2. 石英官网
  3. 支柱 2 +弹簧 3 +石英 1.8 示例

Tags : integration quartz scheduler spring spring3

相关文章

从资源文件夹中读取文件

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/spring/spring-read-file-from-resources-folder/

在 Spring 中,我们可以使用ClassPathResourceResourceLoader轻松地从类路径中获取文件。

用弹簧 5.1.4 测试 P.S .释放

1.资源中心/主要/资源/

例如,src/main/resources/文件夹中的图像文件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.ClassPathResource

 import org.springframework.core.io.Resource;
import org.springframework.core.io.ClassPathResource;

import java.io.File;
import java.io.InputStream;

	Resource resource = new ClassPathResource("android.png");

	InputStream input = resource.getInputStream();

	File file = resource.getFile(); 

3.资源加载器

 import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import java.io.File;
import java.io.InputStream;

	@Autowired
    ResourceLoader resourceLoader;

	Resource resource = resourceLoader.getResource("classpath:android.png");

	InputStream input = resource.getInputStream();

	File file = resource.getFile(); 

4.资源工具

请不要使用这个ResourceUtils即使它有效,这个类主要是在框架内部使用。阅读 ResourceUtils JavaDocs

 import org.springframework.util.ResourceUtils;

	File file = ResourceUtils.getFile("classpath:android.png"); 

参考

带有 ResourceBundleMessageSource 示例的 Spring 资源包

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring/spring-resource-bundle-with-resourcebundlemessagesource-example/

在 Spring 中,您可以使用ResourceBundleMessageSource来解析来自属性文件的文本消息,基于所选择的地区。请参见以下示例:

1.目录结构

查看此示例的目录结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.属性文件

创建两个属性文件,一个用于英文字符(messages_en_US.properties),另一个用于中文字符(messages_zh_CN.properties)。将其放入项目类路径中(见上图)。

文件:messages_en_US.properties

 customer.name=Yong Mook Kim, age : {
    
    0}, URL : {
    
    1} 

文件:messages_zh_CN.properties

 customer.name=\ufeff\u6768\u6728\u91d1, age : {
    
    0}, URL : {
    
    1} 

'\ u eff \ u 6768 \ u 6728 \ u91d 1’为中文 Unicode 字符。

Note
To display the Chinese characters correctly, you have to use “native2ascii” tool to convert the Chinese characters into Unicode characters.

3.Bean 配置文件

将属性文件包含到 bean 配置文件中。“messages _ en _ us . properties”和“messages _ zh _ cn . properties”在 Spring 中都被认为是一个文件,你只需要包含文件名一次,Spring 就会自动找到正确的区域设置。

 <?xml version="1.0" encoding="UTF-8"?>
<beans 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<bean id="messageSource"
		class="org.springframework.context.support.ResourceBundleMessageSource">
		<property name="basename">
			<value>locale\customer\messages</value>
		</property>
	</bean>

</beans> 

假设这两个文件都位于“资源\区域\客户”文件夹。

4.运行它

 package com.mkyong.common;

import java.util.Locale;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    
    
	public static void main(String[] args) {
    
    

		ApplicationContext context 
			= new ClassPathXmlApplicationContext("locale.xml");

		String name = context.getMessage("customer.name", 
				new Object[] {
    
     28,"http://www.mkyong.com" }, Locale.US);

		System.out.println("Customer name (English) : " + name);

		String namechinese = context.getMessage("customer.name", 
				new Object[] {
    
    28, "http://www.mkyong.com" }, 
                                        Locale.SIMPLIFIED_CHINESE);

		System.out.println("Customer name (Chinese) : " + namechinese);

	}
} 

输出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传Note
Make sure your Eclipse is able to display Chinese output.

说明

1.在context.getMessage()中,第二个参数是消息参数,你必须将其作为对象数组传递。如果没有可用的参数值,可以只传递一个 null。

 context.getMessage("customer.name",null, Locale.US); 

2.地点。US 将从’messages _ en _ US . properties’中检索消息。简体中文将从“messages _ zh _ cn . properties中检索消息。

More …
Read this article to know how to access the MessageSource inside a bean.

下载源代码

Download it – Spring-MessageSource-Example.zipTags : resource bundle spring

带有 getResource()示例的 Spring 资源加载器

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring/spring-resource-loader-with-getresource-example/

Spring 的资源加载器提供了一个非常通用的 getResource() 方法来从文件系统、类路径或 URL 获取资源,比如(文本文件、媒体文件、图像文件……)。您可以从应用程序上下文中获取 getResource() 方法。

下面的例子展示了如何使用 getResource()

1。文件系统

 Resource resource = appContext.getResource("file:c:\\testing.txt"); 

2。URL 路径

 Resource resource = 
          appContext.getResource("url:http://www.yourdomain.com/testing.txt"); 

3。类别路径

 Resource resource = 
          appContext.getResource("classpath:com/mkyong/common/testing.txt"); 

您只需要指定资源位置,Spring 会处理剩下的工作并返回一个资源对象。

使用getResource()方法的完整示例。

 package com.mkyong.common;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;
public class App 
{
    
    
    public static void main( String[] args )
    {
    
    
    	ApplicationContext appContext = 
    	   new ClassPathXmlApplicationContext(new String[] {
    
    "If-you-have-any.xml"});

    	Resource resource = 
           appContext.getResource("classpath:com/mkyong/common/testing.txt");

    try{
    
    
     	  InputStream is = resource.getInputStream();
          BufferedReader br = new BufferedReader(new InputStreamReader(is));

          String line;
          while ((line = br.readLine()) != null) {
    
    
             System.out.println(line);
       	  } 
          br.close();

    	}catch(IOException e){
    
    
    		e.printStackTrace();
    	}

    }
} 

Bean 资源加载器(ResourceLoaderAware)

既然 bean 没有应用程序上下文访问权限,那么 bean 如何访问资源呢?解决方法是实现resource loader ware接口,并为 ResourceLoader 对象创建 setter 方法。Spring 会将资源加载器插入到 bean 中。

 package com.mkyong.customer.services;

import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

public class CustomerService implements ResourceLoaderAware
{
    
    
	private ResourceLoader resourceLoader;

	public void setResourceLoader(ResourceLoader resourceLoader) {
    
    
		this.resourceLoader = resourceLoader;
	}

	public Resource getResource(String location){
    
    
		return resourceLoader.getResource(location);
	}
} 

Bean 配置文件

 <beans 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

   <bean id="customerService" 
           class="com.mkyong.customer.services.CustomerService" />

</beans> 

运行它

 package com.mkyong.common;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;

import com.mkyong.customer.services.CustomerService;
public class App 
{
    
    
    public static void main( String[] args )
    {
    
    
    	ApplicationContext appContext = 
    	   new ClassPathXmlApplicationContext(new String[] {
    
    "Spring-Customer.xml"});

    	CustomerService cust = 
           (CustomerService)appContext.getBean("customerService");

    	Resource resource = 
            cust.getResource("classpath:com/mkyong/common/testing.txt");

    try{
    
    
          InputStream is = resource.getInputStream();
          BufferedReader br = new BufferedReader(new InputStreamReader(is));

          String line;
          while ((line = br.readLine()) != null) {
    
    
     	       System.out.println(line);
          } 
          br.close();

    	}catch(IOException e){
    
    
    		e.printStackTrace();
    	}

    }
} 

现在,您可以从 bean 中获取资源。

结论

如果没有这个 getResource()方法,您将需要用不同的解决方案处理不同的资源,比如文件系统资源的文件对象,URL 资源的 URL 对象。Spring 确实用这个超级通用的 getResource() 方法做得很好,它确实节省了我们处理资源的时间。

下载源代码

Download it – Spring-getResource-Example.zipspring

弹簧座错误处理示例

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/spring-boot/spring-rest-error-handling-example/

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在本文中,我们将向您展示 Spring Boot REST 应用程序中的错误处理。

使用的技术:

  • Spring Boot 2.1.2 .版本
  • 弹簧 5.1.4 释放
  • maven3
  • Java 8

1./错误

1.1 默认情况下,Spring Boot 为处理所有错误的/error映射提供了一个BasicErrorController控制器,并为生成包含错误细节、HTTP 状态和异常消息的 JSON 响应提供了一个getErrorAttributes

 {
    
    	
	"timestamp":"2019-02-27T04:03:52.398+0000",
	"status":500,
	"error":"Internal Server Error",
	"message":"...",
	"path":"/path"
} 

BasicErrorController.java

 package org.springframework.boot.autoconfigure.web.servlet.error;

//...

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
    
    

	//...

	@RequestMapping
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    
    
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<>(body, status);
	} 

在 IDE 中,在这个方法中放置一个断点,你将理解 Spring Boot 如何生成默认的 JSON 错误响应。

2.自定义异常

在 Spring Boot,我们可以使用@ControllerAdvice来处理自定义异常。

2.1 自定义异常。

BookNotFoundException.java

 package com.mkyong.error;

public class BookNotFoundException extends RuntimeException {
    
    

    public BookNotFoundException(Long id) {
    
    
        super("Book id not found : " + id);
    }

} 

如果没有找到图书 id,控制器抛出上述BookNotFoundException

BookController.java

 package com.mkyong;

//...

@RestController
public class BookController {
    
    

    @Autowired
    private BookRepository repository;

    // Find
    @GetMapping("/books/{id}")
    Book findOne(@PathVariable Long id) {
    
    
        return repository.findById(id)
                .orElseThrow(() -> new BookNotFoundException(id));
    }

	//...
} 

默认情况下,Spring Boot 生成以下 JSON 错误响应,http 500 error。

Terminal

 curl localhost:8080/books/5

{
    
    
	"timestamp":"2019-02-27T04:03:52.398+0000",
	"status":500,
	"error":"Internal Server Error",
	"message":"Book id not found : 5",
	"path":"/books/5"
} 

2.2 如果没有找到图书 id,它应该返回 404 错误而不是 500,我们可以像这样覆盖状态代码:

CustomGlobalExceptionHandler.java

 package com.mkyong.error;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
    
    

    // Let Spring BasicErrorController handle the exception, we just override the status code
    @ExceptionHandler(BookNotFoundException.class)
    public void springHandleNotFound(HttpServletResponse response) throws IOException {
    
    
        response.sendError(HttpStatus.NOT_FOUND.value());
    }

	//...
} 

2.3 它现在返回一个 404。

Terminal

 curl localhost:8080/books/5

{
    
    
	"timestamp":"2019-02-27T04:21:17.740+0000",
	"status":404,
	"error":"Not Found",
	"message":"Book id not found : 5",
	"path":"/books/5"
} 

2.4 此外,我们可以定制整个 JSON 错误响应:

CustomErrorResponse.java

 package com.mkyong.error;

import com.fasterxml.jackson.annotation.JsonFormat;

import java.time.LocalDateTime;

public class CustomErrorResponse {
    
    

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
    private LocalDateTime timestamp;
    private int status;
    private String error;

    //...getters setters
} 

CustomGlobalExceptionHandler.java

 package com.mkyong.error;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.time.LocalDateTime;

@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
    
    

    @ExceptionHandler(BookNotFoundException.class)
    public ResponseEntity<CustomErrorResponse> customHandleNotFound(Exception ex, WebRequest request) {
    
    

        CustomErrorResponse errors = new CustomErrorResponse();
        errors.setTimestamp(LocalDateTime.now());
        errors.setError(ex.getMessage());
        errors.setStatus(HttpStatus.NOT_FOUND.value());

        return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND);

    }

	//...
} 

Terminal

 curl localhost:8080/books/5
{
    
    
	"timestamp":"2019-02-27 12:40:45",
	"status":404,
	"error":"Book id not found : 5"
} 

3.JSR 303 验证错误

3.1 对于 Spring @valid验证错误,会抛出handleMethodArgumentNotValid

CustomGlobalExceptionHandler.java

 package com.mkyong.error;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
    
    

    //...

    // @Validate For Validating Path Variables and Request Parameters
    @ExceptionHandler(ConstraintViolationException.class)
    public void constraintViolationException(HttpServletResponse response) throws IOException {
    
    
        response.sendError(HttpStatus.BAD_REQUEST.value());
    }

    // error handle for @Valid
    @Override
    protected ResponseEntity<Object>
    handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                 HttpHeaders headers,
                                 HttpStatus status, WebRequest request) {
    
    

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("timestamp", new Date());
        body.put("status", status.value());

        //Get all fields errors
        List<String> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(x -> x.getDefaultMessage())
                .collect(Collectors.toList());

        body.put("errors", errors);

        return new ResponseEntity<>(body, headers, status);

    }

} 

4.ResponseEntityExceptionHandler

4.1 如果我们不确定 Spring Boot 抛出了什么异常,就在这个方法中放一个断点进行调试。

ResponseEntityExceptionHandler.java

 package org.springframework.web.servlet.mvc.method.annotation;

//...
public abstract class ResponseEntityExceptionHandler {
    
    

	@ExceptionHandler({
    
    
			HttpRequestMethodNotSupportedException.class,
			HttpMediaTypeNotSupportedException.class,
			HttpMediaTypeNotAcceptableException.class,
			MissingPathVariableException.class,
			MissingServletRequestParameterException.class,
			ServletRequestBindingException.class,
			ConversionNotSupportedException.class,
			TypeMismatchException.class,
			HttpMessageNotReadableException.class,
			HttpMessageNotWritableException.class,
			MethodArgumentNotValidException.class,
			MissingServletRequestPartException.class,
			BindException.class,
			NoHandlerFoundException.class,
			AsyncRequestTimeoutException.class
		})
	@Nullable
	public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
    
    
		HttpHeaders headers = new HttpHeaders();

		if (ex instanceof HttpRequestMethodNotSupportedException) {
    
    
			HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
			return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
		}
		else if (ex instanceof HttpMediaTypeNotSupportedException) {
    
    
			HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
			return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request);
		}
		//...
	}

	//...

} 

5.DefaultErrorAttributes

5.1 为了覆盖所有异常的默认 JSON 错误响应,创建一个 bean 并扩展DefaultErrorAttributes

CustomErrorAttributes.java

 package com.mkyong.error;

import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
    
    

    private static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
    
    

        // Let Spring handle the error first, we will modify later :)
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);

        // format & update timestamp
        Object timestamp = errorAttributes.get("timestamp");
        if (timestamp == null) {
    
    
            errorAttributes.put("timestamp", dateFormat.format(new Date()));
        } else {
    
    
            errorAttributes.put("timestamp", dateFormat.format((Date) timestamp));
        }

        // insert a new key
        errorAttributes.put("version", "1.2");

        return errorAttributes;

    }

} 

现在,日期时间被格式化,一个新字段–version被添加到 JSON 错误响应中。

 curl localhost:8080/books/5

{
    
    
	"timestamp":"2019/02/27 13:34:24",
	"status":404,
	"error":"Not Found",
	"message":"Book id not found : 5",
	"path":"/books/5",
	"version":"1.2"
}

curl localhost:8080/abc

{
    
    
	"timestamp":"2019/02/27 13:35:10",
	"status":404,
	"error":"Not Found",
	"message":"No message available",
	"path":"/abc",
	"version":"1.2"
} 

完成了。

下载源代码

$ git clone https://github.com/mkyong/spring-boot.git
$ cd spring-rest-error-handling
$ mvn spring-boot:run

参考

春季休息你好世界示例

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/spring-boot/spring-rest-hello-world-example/

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在本文中,我们将向您展示如何开发一个 Spring Boot REST 风格的 web 服务来处理来自 H2 内存数据库的 CRUD 操作。

使用的技术:

  • Spring Boot 2.1.2 .版本
  • 弹簧 5.1.4 释放
  • 春季数据 JPA 2.1.4.RELEASE
  • H2 内存数据库 1.4.197
  • Tomcat Embed 9.0.14
  • JUnit 4.12
  • maven3
  • Java 8

1.项目目录

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.专家

包括用于 Spring MVC 和 REST 结构的spring-boot-starter-web,用于 CRUD 库的spring-boot-starter-data-jpa

pom.xml

 <?xml version="1.0" encoding="UTF-8"?>
<project 
         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>

    <artifactId>spring-rest-hello-world</artifactId>
    <packaging>jar</packaging>
    <name>Spring Boot REST API Example</name>
    <version>1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
    </parent>

    <!-- Java 8 -->
    <properties>
        <java.version>1.8</java.version>
        <downloadSources>true</downloadSources>
        <downloadJavadocs>true</downloadJavadocs>
    </properties>

    <dependencies>

        <!-- spring mvc, rest -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- jpa, crud repository -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- in-memory database  -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>

        <!-- unit test rest -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- test patch operation need this -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.7</version>
            <scope>test</scope>
        </dependency>

        <!-- hot swapping, disable cache for template, enable live reload -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

    </dependencies>

    <build>
        <plugins>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <addResources>true</addResources>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.0</version>
            </plugin>

        </plugins>

    </build>
</project> 

项目依赖关系。

 $ mvn dependency:tree

[INFO] org.springframework.boot:spring-rest-hello-world:jar:1.0
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.1.2.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.1.2.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.1.2.RELEASE:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.11.1:compile
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.11.1:compile
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.25:compile
[INFO] |  |  +- javax.annotation:javax.annotation-api:jar:1.3.2:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.23:runtime
[INFO] |  +- org.springframework.boot:spring-boot-starter-json:jar:2.1.2.RELEASE:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.9.8:compile
[INFO] |  |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.9.0:compile
[INFO] |  |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.9.8:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.9.8:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.9.8:compile
[INFO] |  |  \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.9.8:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.1.2.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.14:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.14:compile
[INFO] |  |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.14:compile
[INFO] |  +- org.hibernate.validator:hibernate-validator:jar:6.0.14.Final:compile
[INFO] |  |  +- javax.validation:validation-api:jar:2.0.1.Final:compile
[INFO] |  |  +- org.jboss.logging:jboss-logging:jar:3.3.2.Final:compile
[INFO] |  |  \- com.fasterxml:classmate:jar:1.4.0:compile
[INFO] |  +- org.springframework:spring-web:jar:5.1.4.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-beans:jar:5.1.4.RELEASE:compile
[INFO] |  \- org.springframework:spring-webmvc:jar:5.1.4.RELEASE:compile
[INFO] |     +- org.springframework:spring-aop:jar:5.1.4.RELEASE:compile
[INFO] |     +- org.springframework:spring-context:jar:5.1.4.RELEASE:compile
[INFO] |     \- org.springframework:spring-expression:jar:5.1.4.RELEASE:compile
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:2.1.2.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-aop:jar:2.1.2.RELEASE:compile
[INFO] |  |  \- org.aspectj:aspectjweaver:jar:1.9.2:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.1.2.RELEASE:compile
[INFO] |  |  +- com.zaxxer:HikariCP:jar:3.2.0:compile
[INFO] |  |  \- org.springframework:spring-jdbc:jar:5.1.4.RELEASE:compile
[INFO] |  +- javax.transaction:javax.transaction-api:jar:1.3:compile
[INFO] |  +- javax.xml.bind:jaxb-api:jar:2.3.1:compile
[INFO] |  |  \- javax.activation:javax.activation-api:jar:1.2.0:compile
[INFO] |  +- org.hibernate:hibernate-core:jar:5.3.7.Final:compile
[INFO] |  |  +- javax.persistence:javax.persistence-api:jar:2.2:compile
[INFO] |  |  +- org.javassist:javassist:jar:3.23.1-GA:compile
[INFO] |  |  +- net.bytebuddy:byte-buddy:jar:1.9.7:compile
[INFO] |  |  +- antlr:antlr:jar:2.7.7:compile
[INFO] |  |  +- org.jboss:jandex:jar:2.0.5.Final:compile
[INFO] |  |  +- org.dom4j:dom4j:jar:2.1.1:compile
[INFO] |  |  \- org.hibernate.common:hibernate-commons-annotations:jar:5.0.4.Final:compile
[INFO] |  +- org.springframework.data:spring-data-jpa:jar:2.1.4.RELEASE:compile
[INFO] |  |  +- org.springframework.data:spring-data-commons:jar:2.1.4.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-orm:jar:5.1.4.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-tx:jar:5.1.4.RELEASE:compile
[INFO] |  |  \- org.slf4j:slf4j-api:jar:1.7.25:compile
[INFO] |  \- org.springframework:spring-aspects:jar:5.1.4.RELEASE:compile
[INFO] +- com.h2database:h2:jar:1.4.197:compile
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.1.2.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-test:jar:2.1.2.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.1.2.RELEASE:test
[INFO] |  +- com.jayway.jsonpath:json-path:jar:2.4.0:test
[INFO] |  |  \- net.minidev:json-smart:jar:2.3:test
[INFO] |  |     \- net.minidev:accessors-smart:jar:1.2:test
[INFO] |  |        \- org.ow2.asm:asm:jar:5.0.4:test
[INFO] |  +- junit:junit:jar:4.12:test
[INFO] |  +- org.assertj:assertj-core:jar:3.11.1:test
[INFO] |  +- org.mockito:mockito-core:jar:2.23.4:test
[INFO] |  |  +- net.bytebuddy:byte-buddy-agent:jar:1.9.7:test
[INFO] |  |  \- org.objenesis:objenesis:jar:2.6:test
[INFO] |  +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] |  +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] |  +- org.skyscreamer:jsonassert:jar:1.5.0:test
[INFO] |  |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO] |  +- org.springframework:spring-core:jar:5.1.4.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-jcl:jar:5.1.4.RELEASE:compile
[INFO] |  +- org.springframework:spring-test:jar:5.1.4.RELEASE:test
[INFO] |  \- org.xmlunit:xmlunit-core:jar:2.6.2:test
[INFO] +- org.apache.httpcomponents:httpclient:jar:4.5.7:test
[INFO] |  +- org.apache.httpcomponents:httpcore:jar:4.4.10:test
[INFO] |  \- commons-codec:commons-codec:jar:1.11:test
[INFO] \- org.springframework.boot:spring-boot-devtools:jar:2.1.2.RELEASE:compile (optional)
[INFO]    +- org.springframework.boot:spring-boot:jar:2.1.2.RELEASE:compile
[INFO]    \- org.springframework.boot:spring-boot-autoconfigure:jar:2.1.2.RELEASE:compile 

3.弹簧支架

3.1 一个 REST 控制器创建以下 REST API 端点:

HTTP 方法 上呼吸道感染 描述
得到 /书籍 列出所有书籍。
邮政 /书籍 存一本书。
得到 /books/{id} 找到一本 id = {:id}的书。
/books/{id} 在 id = {:id}处更新或保存图书。
修补 /books/{id} 更新单个字段,其中 id = {:id}。
删除 /books/{id} 删除 id = {:id}的图书。

BookController.java

 package com.mkyong;

import com.mkyong.error.BookNotFoundException;
import com.mkyong.error.BookUnSupportedFieldPatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
public class BookController {
    
    

    @Autowired
    private BookRepository repository;

    // Find
    @GetMapping("/books")
    List<Book> findAll() {
    
    
        return repository.findAll();
    }

    // Save
    //return 201 instead of 200
    @ResponseStatus(HttpStatus.CREATED)
    @PostMapping("/books")
    Book newBook(@RequestBody Book newBook) {
    
    
        return repository.save(newBook);
    }

    // Find
    @GetMapping("/books/{id}")
    Book findOne(@PathVariable Long id) {
    
    
        return repository.findById(id)
                .orElseThrow(() -> new BookNotFoundException(id));
    }

    // Save or update
    @PutMapping("/books/{id}")
    Book saveOrUpdate(@RequestBody Book newBook, @PathVariable Long id) {
    
    

        return repository.findById(id)
                .map(x -> {
    
    
                    x.setName(newBook.getName());
                    x.setAuthor(newBook.getAuthor());
                    x.setPrice(newBook.getPrice());
                    return repository.save(x);
                })
                .orElseGet(() -> {
    
    
                    newBook.setId(id);
                    return repository.save(newBook);
                });
    }

    // update author only
    @PatchMapping("/books/{id}")
    Book patch(@RequestBody Map<String, String> update, @PathVariable Long id) {
    
    

        return repository.findById(id)
                .map(x -> {
    
    

                    String author = update.get("author");
                    if (!StringUtils.isEmpty(author)) {
    
    
                        x.setAuthor(author);

                        // better create a custom method to update a value = :newValue where id = :id
                        return repository.save(x);
                    } else {
    
    
                        throw new BookUnSupportedFieldPatchException(update.keySet());
                    }

                })
                .orElseGet(() -> {
    
    
                    throw new BookNotFoundException(id);
                });

    }

    @DeleteMapping("/books/{id}")
    void deleteBook(@PathVariable Long id) {
    
    
        repository.deleteById(id);
    }

} 

4.春季数据 JPA

4.1 用 JPA 注释标注的模型。

Book.java

 package com.mkyong;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.math.BigDecimal;

@Entity
public class Book {
    
    

    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private String author;
    private BigDecimal price;

    //setters, getters, constructors...
} 

4.2 创建一个类并扩展JpaRepository,它包含所有的 CRUD 操作。

BookRepository.java

 package com.mkyong;

import org.springframework.data.jpa.repository.JpaRepository;

// Spring Data magic :)
public interface BookRepository extends JpaRepository<Book, Long> {
    
    
} 

5.基本错误处理

创建一个@ControllerAdvice来处理控制器抛出的定制异常。

CustomGlobalExceptionHandler

 package com.mkyong.error;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
    
    

    // Let Spring handle the exception, we just override the status code
    @ExceptionHandler(BookNotFoundException.class)
    public void springHandleNotFound(HttpServletResponse response) throws IOException {
    
    
        response.sendError(HttpStatus.NOT_FOUND.value());
    }

    @ExceptionHandler(BookUnSupportedFieldPatchException.class)
    public void springUnSupportedFieldPatch(HttpServletResponse response) throws IOException {
    
    
        response.sendError(HttpStatus.METHOD_NOT_ALLOWED.value());
    }

} 

BookNotFoundException.java

 package com.mkyong.error;

public class BookNotFoundException extends RuntimeException {
    
    

    public BookNotFoundException(Long id) {
    
    
        super("Book id not found : " + id);
    }

} 

BookUnSupportedFieldPatchException.java

 package com.mkyong.error;

import java.util.Set;

public class BookUnSupportedFieldPatchException extends RuntimeException {
    
    

    public BookUnSupportedFieldPatchException(Set<String> keys) {
    
    
        super("Field " + keys.toString() + " update is not allow.");
    }

} 

6.Spring Boot

6.1 创建一个@SpringBootApplication类来启动 REST web 应用程序,并将 3 个预定义的 book 对象插入到 H2 内存数据库中进行演示。

StartBookApplication.java

 package com.mkyong;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.math.BigDecimal;

@SpringBootApplication
public class StartBookApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(StartBookApplication.class, args);
    }

	// init bean to insert 3 books into h2 database.
    @Bean
    CommandLineRunner initDatabase(BookRepository repository) {
    
    
        return args -> {
    
    
            repository.save(new Book("A Guide to the Bodhisattva Way of Life", "Santideva", new BigDecimal("15.41")));
            repository.save(new Book("The Life-Changing Magic of Tidying Up", "Marie Kondo", new BigDecimal("9.69")));
            repository.save(new Book("Refactoring: Improving the Design of Existing Code", "Martin Fowler", new BigDecimal("47.99")));
        };
    }
} 

7.演示

启动 Spring Boot 应用程序,用curl命令测试 REST API 端点。

 $ mvn spring-boot:run 

7.1 查找全部—GET /books

 > curl -v localhost:8080/books

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /books HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
>
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 06:33:57 GMT
<

[
	{
    
    "id":1,"name":"A Guide to the Bodhisattva Way of Life","author":"Santideva","price":15.41},
	{
    
    "id":2,"name":"The Life-Changing Magic of Tidying Up","author":"Marie Kondo","price":9.69},
	{
    
    "id":3,"name":"Refactoring: Improving the Design of Existing Code","author":"Martin Fowler","price":47.99}
] 

7.2 找到一个—GET /books/1

 > curl -v localhost:8080/books/1

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /books/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
>
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 06:37:26 GMT
<

{
    
    
	"id":1,
	"name":"A Guide to the Bodhisattva Way of Life",
	"author":"Santideva",
	"price":15.41
} 

7.3 试验 404-GET /books/5

 > curl -v localhost:8080/books/5

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /books/5 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
>
< HTTP/1.1 404
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 06:38:45 GMT
<

{
    
    
	"timestamp":"2019-02-19T06:38:45.743+0000",
	"status":404,
	"error":"Not Found",
	"message":"Book id not found : 5",
	"path":"/books/5"
} 

7.4 测试保存—POST /books -d {json}

 #Run this on Windows, need \"

> curl -v -X POST localhost:8080/books -H "Content-type:application/json" -d "{\"name\":\"Spring REST tutorials\",\"author\":\"mkyong\",\"price\":\"9.99\"}"

Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /books HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
> Content-type:application/json
> Content-Length: 65
>
* upload completely sent off: 65 out of 65 bytes
< HTTP/1.1 201
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 07:33:01 GMT
<
{
    
    
	"id":4,
	"name":
	"Spring REST tutorials",
	"author":"mkyong",
	"price":9.99
} 

7.5 测试更新—PUT /books/4 -d {json}

 > curl -v -X PUT localhost:8080/books/4 -H "Content-type:application/json" -d "{\"name\":\"Spring Forever\",\"author\":\"pivotal\",\"price\":\"9.99\"}"

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> PUT /books/4 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
> Content-type:application/json
> Content-Length: 59
>
* upload completely sent off: 59 out of 59 bytes
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 07:36:49 GMT
<
{
    
    
	"id":4,
	"name":"Spring Forever",
	"author":"pivotal",
	"price":9.99
}

> curl localhost:8080/books/4
{
    
    "id":4,"name":"Spring Forever","author":"pivotal","price":9.99} 

7.6 测试更新“作者”字段—PATCH /books/4 -d {json}

 > curl -v -X PATCH localhost:8080/books/4 -H "Content-type:application/json" -d "{\"author\":\"oracle\"}"
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> PATCH /books/4 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
> Content-type:application/json
> Content-Length: 19
>
* upload completely sent off: 19 out of 19 bytes
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 07:39:53 GMT

{
    
    	
	"id":4,
	"name":"Spring Forever",
	"author":"oracle",
	"price":9.99
} 

7.6.1 测试更新“名称”字段—PATCH /books/4 -d {json}

 > curl -v -X PATCH localhost:8080/books/4 -H "Content-type:application/json" -d "{\"name\":\"New Spring REST\"}"
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> PATCH /books/4 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
> Content-type:application/json
> Content-Length: 26
>
* upload completely sent off: 26 out of 26 bytes
< HTTP/1.1 405
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 07:40:47 GMT
<
{
    
    	
	"timestamp":"2019-02-19T07:40:47.740+0000",
	"status":405,
	"error":"Method Not Allowed",
	"message":"Field [name] update is not allow.",
	"path":"/books/4"
} 

7.7 测试删除-DELETE /books/4

 > curl -v -X DELETE localhost:8080/books/4

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> DELETE /books/4 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
>
< HTTP/1.1 200
< Content-Length: 0
< Date: Tue, 19 Feb 2019 07:44:24 GMT
<

> curl localhost:8080/books/4
{
    
    
	"timestamp":"2019-02-19T07:44:39.432+0000",
	"status":404,
	"error":"Not Found",
	"message":"Book id not found : 4",
	"path":"/books/4"
}

> curl localhost:8080/books
[
	{
    
    "id":1,"name":"A Guide to the Bodhisattva Way of Life","author":"Santideva","price":15.41},
	{
    
    "id":2,"name":"The Life-Changing Magic of Tidying Up","author":"Marie Kondo","price":9.69},
	{
    
    "id":3,"name":"Refactoring: Improving the Design of Existing Code","author":"Martin Fowler","price":47.99}
] 

下载源代码

$ git clone https://github.com/mkyong/spring-boot.git
$ cd spring-rest-hello-world
$ mvn spring-boot:run

参考

弹簧座集成测试示例

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/spring-boot/spring-rest-integration-test-example/

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在本文中,我们将向您展示如何测试 Spring Boot REST 应用程序。通常,我们使用MockMvcTestRestTemplate进行集成测试。

使用的技术:

  • Spring Boot 2.1.2 .版本
  • 弹簧 5.1.4 释放
  • maven3
  • Java 8

pom.xml

 <!-- spring integration test -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>

	<!-- spring integration test for security-->
	<dependency>
		<groupId>org.springframework.security</groupId>
		<artifactId>spring-security-test</artifactId>
		<scope>test</scope>
	</dependency> 

1. MockMvc

 @RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class BookControllerTest {
    
    

    @Autowired
    private MockMvc mockMvc; 

1.1 本 Spring REST Hello World 示例的 CRUD 测试

 package com.mkyong;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

//@WebMvcTest(BookController.class)
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {
    
    

    private static final ObjectMapper om = new ObjectMapper();

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookRepository mockRepository;

    @Before
    public void init() {
    
    
        Book book = new Book(1L, "Book Name", "Mkyong", new BigDecimal("9.99"));
        when(mockRepository.findById(1L)).thenReturn(Optional.of(book));
    }

    @Test
    public void find_bookId_OK() throws Exception {
    
    

        mockMvc.perform(get("/books/1"))
                /*.andDo(print())*/
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.name", is("Book Name")))
                .andExpect(jsonPath("$.author", is("Mkyong")))
                .andExpect(jsonPath("$.price", is(9.99)));

        verify(mockRepository, times(1)).findById(1L);

    }

    @Test
    public void find_allBook_OK() throws Exception {
    
    

        List<Book> books = Arrays.asList(
                new Book(1L, "Book A", "Ah Pig", new BigDecimal("1.99")),
                new Book(2L, "Book B", "Ah Dog", new BigDecimal("2.99")));

        when(mockRepository.findAll()).thenReturn(books);

        mockMvc.perform(get("/books"))
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[0].id", is(1)))
                .andExpect(jsonPath("$[0].name", is("Book A")))
                .andExpect(jsonPath("$[0].author", is("Ah Pig")))
                .andExpect(jsonPath("$[0].price", is(1.99)))
                .andExpect(jsonPath("$[1].id", is(2)))
                .andExpect(jsonPath("$[1].name", is("Book B")))
                .andExpect(jsonPath("$[1].author", is("Ah Dog")))
                .andExpect(jsonPath("$[1].price", is(2.99)));

        verify(mockRepository, times(1)).findAll();
    }

    @Test
    public void find_bookIdNotFound_404() throws Exception {
    
    
        mockMvc.perform(get("/books/5")).andExpect(status().isNotFound());
    }

    @Test
    public void save_book_OK() throws Exception {
    
    

        Book newBook = new Book(1L, "Spring Boot Guide", "mkyong", new BigDecimal("2.99"));
        when(mockRepository.save(any(Book.class))).thenReturn(newBook);

        mockMvc.perform(post("/books")
                .content(om.writeValueAsString(newBook))
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                /*.andDo(print())*/
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.name", is("Spring Boot Guide")))
                .andExpect(jsonPath("$.author", is("mkyong")))
                .andExpect(jsonPath("$.price", is(2.99)));

        verify(mockRepository, times(1)).save(any(Book.class));

    }

    @Test
    public void update_book_OK() throws Exception {
    
    

        Book updateBook = new Book(1L, "ABC", "mkyong", new BigDecimal("19.99"));
        when(mockRepository.save(any(Book.class))).thenReturn(updateBook);

        mockMvc.perform(put("/books/1")
                .content(om.writeValueAsString(updateBook))
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.name", is("ABC")))
                .andExpect(jsonPath("$.author", is("mkyong")))
                .andExpect(jsonPath("$.price", is(19.99)));

    }

    @Test
    public void patch_bookAuthor_OK() throws Exception {
    
    

        when(mockRepository.save(any(Book.class))).thenReturn(new Book());
        String patchInJson = "{\"author\":\"ultraman\"}";

        mockMvc.perform(patch("/books/1")
                .content(patchInJson)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk());

        verify(mockRepository, times(1)).findById(1L);
        verify(mockRepository, times(1)).save(any(Book.class));

    }

    @Test
    public void patch_bookPrice_405() throws Exception {
    
    

        String patchInJson = "{\"price\":\"99.99\"}";

        mockMvc.perform(patch("/books/1")
                .content(patchInJson)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andExpect(status().isMethodNotAllowed());

        verify(mockRepository, times(1)).findById(1L);
        verify(mockRepository, times(0)).save(any(Book.class));
    }

    @Test
    public void delete_book_OK() throws Exception {
    
    

        doNothing().when(mockRepository).deleteById(1L);

        mockMvc.perform(delete("/books/1"))
                /*.andDo(print())*/
                .andExpect(status().isOk());

        verify(mockRepository, times(1)).deleteById(1L);
    }

    private static void printJSON(Object object) {
    
    
        String result;
        try {
    
    
            result = om.writerWithDefaultPrettyPrinter().writeValueAsString(object);
            System.out.println(result);
        } catch (JsonProcessingException e) {
    
    
            e.printStackTrace();
        }
    }

} 

1.2 此弹簧座验证的测试

 package com.mkyong;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {
    
    

    private static final ObjectMapper om = new ObjectMapper();

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookRepository mockRepository;

    /*
        {
            "timestamp":"2019-03-05T09:34:13.280+0000",
            "status":400,
            "errors":["Author is not allowed.","Please provide a price","Please provide a author"]
        }
     */
    @Test
    public void save_emptyAuthor_emptyPrice_400() throws Exception {
    
    

        String bookInJson = "{\"name\":\"ABC\"}";

        mockMvc.perform(post("/books")
                .content(bookInJson)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.timestamp", is(notNullValue())))
                .andExpect(jsonPath("$.status", is(400)))
                .andExpect(jsonPath("$.errors").isArray())
                .andExpect(jsonPath("$.errors", hasSize(3)))
                .andExpect(jsonPath("$.errors", hasItem("Author is not allowed.")))
                .andExpect(jsonPath("$.errors", hasItem("Please provide a author")))
                .andExpect(jsonPath("$.errors", hasItem("Please provide a price")));

        verify(mockRepository, times(0)).save(any(Book.class));

    }

    /*
        {
            "timestamp":"2019-03-05T09:34:13.207+0000",
            "status":400,
            "errors":["Author is not allowed."]
        }
     */
    @Test
    public void save_invalidAuthor_400() throws Exception {
    
    

        String bookInJson = "{\"name\":\" Spring REST tutorials\", \"author\":\"abc\",\"price\":\"9.99\"}";

        mockMvc.perform(post("/books")
                .content(bookInJson)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.timestamp", is(notNullValue())))
                .andExpect(jsonPath("$.status", is(400)))
                .andExpect(jsonPath("$.errors").isArray())
                .andExpect(jsonPath("$.errors", hasSize(1)))
                .andExpect(jsonPath("$.errors", hasItem("Author is not allowed.")));

        verify(mockRepository, times(0)).save(any(Book.class));

    }

} 

1.3 用@WithMockUser对该弹簧座安全进行测试

 package com.mkyong;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.math.BigDecimal;
import java.util.Optional;

import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {
    
    

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookRepository mockRepository;

    @Before
    public void init() {
    
    
        Book book = new Book(1L, "A Guide to the Bodhisattva Way of Life", "Santideva", new BigDecimal("15.41"));
        when(mockRepository.findById(1L)).thenReturn(Optional.of(book));
    }

    //@WithMockUser(username = "USER")
    @WithMockUser("USER")
    @Test
    public void find_login_ok() throws Exception {
    
    

        mockMvc.perform(get("/books/1"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.name", is("A Guide to the Bodhisattva Way of Life")))
                .andExpect(jsonPath("$.author", is("Santideva")))
                .andExpect(jsonPath("$.price", is(15.41)));
    }

    @Test
    public void find_nologin_401() throws Exception {
    
    
        mockMvc.perform(get("/books/1"))
                .andDo(print())
                .andExpect(status().isUnauthorized());
    }

} 

2.TestRestTemplate

 @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // for restTemplate
@ActiveProfiles("test")
public class BookControllerRestTemplateTest {
    
    

    @Autowired
    private TestRestTemplate restTemplate; 

2.1 本 Spring REST Hello World 示例的 CRUD 测试

 package com.mkyong;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.json.JSONException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // for restTemplate
@ActiveProfiles("test")
public class BookControllerRestTemplateTest {
    
    

    private static final ObjectMapper om = new ObjectMapper();

    @Autowired
    private TestRestTemplate restTemplate;

    @MockBean
    private BookRepository mockRepository;

    @Before
    public void init() {
    
    
        Book book = new Book(1L, "Book Name", "Mkyong", new BigDecimal("9.99"));
        when(mockRepository.findById(1L)).thenReturn(Optional.of(book));
    }

    @Test
    public void find_bookId_OK() throws JSONException {
    
    

        String expected = "{id:1,name:\"Book Name\",author:\"Mkyong\",price:9.99}";

        ResponseEntity<String> response = restTemplate.getForEntity("/books/1", String.class);

        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertEquals(MediaType.APPLICATION_JSON_UTF8, response.getHeaders().getContentType());

        JSONAssert.assertEquals(expected, response.getBody(), false);

        verify(mockRepository, times(1)).findById(1L);

    }

    @Test
    public void find_allBook_OK() throws Exception {
    
    

        List<Book> books = Arrays.asList(
                new Book(1L, "Book A", "Ah Pig", new BigDecimal("1.99")),
                new Book(2L, "Book B", "Ah Dog", new BigDecimal("2.99")));

        when(mockRepository.findAll()).thenReturn(books);

        String expected = om.writeValueAsString(books);

        ResponseEntity<String> response = restTemplate.getForEntity("/books", String.class);

        assertEquals(HttpStatus.OK, response.getStatusCode());
        JSONAssert.assertEquals(expected, response.getBody(), false);

        verify(mockRepository, times(1)).findAll();
    }

    @Test
    public void find_bookIdNotFound_404() throws Exception {
    
    

        String expected = "{status:404,error:\"Not Found\",message:\"Book id not found : 5\",path:\"/books/5\"}";

        ResponseEntity<String> response = restTemplate.getForEntity("/books/5", String.class);

        assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
        JSONAssert.assertEquals(expected, response.getBody(), false);

    }

    @Test
    public void save_book_OK() throws Exception {
    
    

        Book newBook = new Book(1L, "Spring Boot Guide", "mkyong", new BigDecimal("2.99"));
        when(mockRepository.save(any(Book.class))).thenReturn(newBook);

        String expected = om.writeValueAsString(newBook);

        ResponseEntity<String> response = restTemplate.postForEntity("/books", newBook, String.class);

        assertEquals(HttpStatus.CREATED, response.getStatusCode());
        JSONAssert.assertEquals(expected, response.getBody(), false);

        verify(mockRepository, times(1)).save(any(Book.class));

    }

    @Test
    public void update_book_OK() throws Exception {
    
    

        Book updateBook = new Book(1L, "ABC", "mkyong", new BigDecimal("19.99"));
        when(mockRepository.save(any(Book.class))).thenReturn(updateBook);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(om.writeValueAsString(updateBook), headers);

        ResponseEntity<String> response = restTemplate.exchange("/books/1", HttpMethod.PUT, entity, String.class);

        assertEquals(HttpStatus.OK, response.getStatusCode());
        JSONAssert.assertEquals(om.writeValueAsString(updateBook), response.getBody(), false);

        verify(mockRepository, times(1)).findById(1L);
        verify(mockRepository, times(1)).save(any(Book.class));

    }

    @Test
    public void patch_bookAuthor_OK() {
    
    

        when(mockRepository.save(any(Book.class))).thenReturn(new Book());
        String patchInJson = "{\"author\":\"ultraman\"}";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(patchInJson, headers);

        ResponseEntity<String> response = restTemplate.exchange("/books/1", HttpMethod.PUT, entity, String.class);

        assertEquals(HttpStatus.OK, response.getStatusCode());

        verify(mockRepository, times(1)).findById(1L);
        verify(mockRepository, times(1)).save(any(Book.class));

    }

    @Test
    public void patch_bookPrice_405() throws JSONException {
    
    

        String expected = "{status:405,error:\"Method Not Allowed\",message:\"Field [price] update is not allow.\"}";

        String patchInJson = "{\"price\":\"99.99\"}";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(patchInJson, headers);

        ResponseEntity<String> response = restTemplate.exchange("/books/1", HttpMethod.PATCH, entity, String.class);

        assertEquals(HttpStatus.METHOD_NOT_ALLOWED, response.getStatusCode());
        JSONAssert.assertEquals(expected, response.getBody(), false);

        verify(mockRepository, times(1)).findById(1L);
        verify(mockRepository, times(0)).save(any(Book.class));
    }

    @Test
    public void delete_book_OK() {
    
    

        doNothing().when(mockRepository).deleteById(1L);

        HttpEntity<String> entity = new HttpEntity<>(null, new HttpHeaders());
        ResponseEntity<String> response = restTemplate.exchange("/books/1", HttpMethod.DELETE, entity, String.class);

        assertEquals(HttpStatus.OK, response.getStatusCode());

        verify(mockRepository, times(1)).deleteById(1L);
    }

    private static void printJSON(Object object) {
    
    
        String result;
        try {
    
    
            result = om.writerWithDefaultPrettyPrinter().writeValueAsString(object);
            System.out.println(result);
        } catch (JsonProcessingException e) {
    
    
            e.printStackTrace();
        }
    }

} 

2.2 此弹簧座验证的测试

 package com.mkyong;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.json.JSONException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // for restTemplate
@ActiveProfiles("test")
public class BookControllerRestTemplateTest {
    
    

    private static final ObjectMapper om = new ObjectMapper();

    @Autowired
    private TestRestTemplate restTemplate;

    @MockBean
    private BookRepository mockRepository;

    /*
        {
            "timestamp":"2019-03-05T09:34:13.280+0000",
            "status":400,
            "errors":["Author is not allowed.","Please provide a price","Please provide a author"]
        }
     */
    @Test
    public void save_emptyAuthor_emptyPrice_400() throws JSONException {
    
    

        String bookInJson = "{\"name\":\"ABC\"}";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(bookInJson, headers);

        // send json with POST
        ResponseEntity<String> response = restTemplate.postForEntity("/books", entity, String.class);
        //printJSON(response);

        String expectedJson = "{\"status\":400,\"errors\":[\"Author is not allowed.\",\"Please provide a price\",\"Please provide a author\"]}";
        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
        JSONAssert.assertEquals(expectedJson, response.getBody(), false);

        verify(mockRepository, times(0)).save(any(Book.class));

    }

    /*
        {
            "timestamp":"2019-03-05T09:34:13.207+0000",
            "status":400,
            "errors":["Author is not allowed."]
        }
     */
    @Test
    public void save_invalidAuthor_400() throws JSONException {
    
    

        String bookInJson = "{\"name\":\" Spring REST tutorials\", \"author\":\"abc\",\"price\":\"9.99\"}";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(bookInJson, headers);

        //Try exchange
        ResponseEntity<String> response = restTemplate.exchange("/books", HttpMethod.POST, entity, String.class);

        String expectedJson = "{\"status\":400,\"errors\":[\"Author is not allowed.\"]}";
        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
        JSONAssert.assertEquals(expectedJson, response.getBody(), false);

        verify(mockRepository, times(0)).save(any(Book.class));

    }

    private static void printJSON(Object object) {
    
    
        String result;
        try {
    
    
            result = om.writerWithDefaultPrettyPrinter().writeValueAsString(object);
            System.out.println(result);
        } catch (JsonProcessingException e) {
    
    
            e.printStackTrace();
        }
    }

} 

2.3 本弹簧座安全性测试

 package com.mkyong;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.Optional;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
public class BookControllerRestTemplateTest {
    
    

    private static final ObjectMapper om = new ObjectMapper();

    //@WithMockUser is not working with TestRestTemplate
    @Autowired
    private TestRestTemplate restTemplate;

    @MockBean
    private BookRepository mockRepository;

    @Before
    public void init() {
    
    
        Book book = new Book(1L, "A Guide to the Bodhisattva Way of Life", "Santideva", new BigDecimal("15.41"));
        when(mockRepository.findById(1L)).thenReturn(Optional.of(book));
    }

    @Test
    public void find_login_ok() throws Exception {
    
    

        String expected = "{id:1,name:\"A Guide to the Bodhisattva Way of Life\",author:\"Santideva\",price:15.41}";

        ResponseEntity<String> response = restTemplate
                .withBasicAuth("user", "password")
                .getForEntity("/books/1", String.class);

        printJSON(response);

        assertEquals(MediaType.APPLICATION_JSON_UTF8, response.getHeaders().getContentType());
        assertEquals(HttpStatus.OK, response.getStatusCode());

        JSONAssert.assertEquals(expected, response.getBody(), false);

    }

    @Test
    public void find_nologin_401() throws Exception {
    
    

        String expected = "{\"status\":401,\"error\":\"Unauthorized\",\"message\":\"Unauthorized\",\"path\":\"/books/1\"}";

        ResponseEntity<String> response = restTemplate
                .getForEntity("/books/1", String.class);

        printJSON(response);

        assertEquals(MediaType.APPLICATION_JSON_UTF8, response.getHeaders().getContentType());
        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());

        JSONAssert.assertEquals(expected, response.getBody(), false);

    }

    private static void printJSON(Object object) {
    
    
        String result;
        try {
    
    
            result = om.writerWithDefaultPrettyPrinter().writeValueAsString(object);
            System.out.println(result);
        } catch (JsonProcessingException e) {
    
    
            e.printStackTrace();
        }
    }

} 

下载源代码

$ git clone https://github.com/mkyong/spring-boot.git

$ CD spring-rest-hello-world
$ mvn 测试

$ cd 弹簧支架-验证
$ mvn 测试

$ cd 弹簧-支架-安全
$ mvn 测试

参考

spring boot spring test unit test

猜你喜欢

转载自blog.csdn.net/wizardforcel/article/details/143497742