HowToDoInJava-Spring-教程-三-

龙哥盟 / 2024-11-20 / 原文

HowToDoInJava Spring 教程(三)

原文:HowToDoInJava

协议:CC BY-NC-SA 4.0

Spring @PostMapping示例 – @GetMapping示例

原文: https://howtodoinjava.com/spring5/webmvc/controller-getmapping-postmapping/

学习使用@Controller创建 Spring MVC 控制器,并使用请求映射注解来映射请求,例如 @RequestMapping@GetMapping@PostMapping@PutMapping@DeleteMapping@PatchMapping

1. Spring 控制器

1.1. @Controller注解

Spring MVC 提供了基于注解的方法,您无需扩展任何基类即可表达请求映射,请求输入参数异常处理等。 @Controller是类似的注解,将一个类标记为请求处理器。

HomeController.java

package com.howtodoinjava.web;

@Controller
public class HomeController 
{
	@GetMapping("/")
	public String homeInit(Model model) {
		return "home";
	}
}

在上面的代码中,HomeController类充当请求控制器。 它的homeInit()方法将处理对 URI "/"的所有传入请求。 它接受Model并返回视图名称home。 此视图名称由配置的视图解析器解析。

1.2. 启用组件扫描

要让 Spring 扫描和配置@Controller带注解的类,您需要在存储控制器的软件包上配置组件扫描。

WebConfig.java

@Configuration
@ComponentScan("com.howtodoinjava.web")
public class WebConfig {
	//Other configurations
}

//or

<context:component-scan base-package="com.howtodoinjava.web"/>

阅读更多: Spring MVC 示例

2. Spring @GetMapping示例

@GetMapping@RequestMapping注解的专用版本,用作@RequestMapping(method = RequestMethod.GET)的快捷方式。 @GetMapping带注解的方法处理与给定 URI 表达式匹配的 HTTP GET请求。 例如

@GetMapping Example

@GetMapping("/home")
public String homeInit(Model model) {
	return "home";
}

@GetMapping("/members/{id}")
public String getMembers(Model model) {
	return "member";
}

3. Spring @PostMapping示例

@PostMapping@RequestMapping注解的专用版本,用作@RequestMapping(method = RequestMethod.POST)的快捷方式。 @PostMapping带注解的方法处理与给定 URI 表达式匹配的 HTTP POST请求。 例如

@PostMapping Example

@PostMapping(path = "/members", consumes = "application/json", produces = "application/json")
public void addMember(@RequestBody Member member) {
	//code
}

consumesproduces属性还支持否定表达式,例如!text/plain表示除text/plain以外的任何媒体类型。

4. 共享的类级别属性

您可以在类级别声明共享的produces属性。 在类级别使用时,方法级别的produces属性将覆盖类级别的声明。

HomeController.java

package com.howtodoinjava.web;

@Controller
@RequestMapping(path = "/", produces = MediaType.APPLICATION_JSON_VALUE)
public class HomeController 
{
	@PostMapping(path = "/members")
	public void addMemberV1(@RequestBody Member member) {
		//code
	}

	@PostMapping(path = "/members", produces = MediaType.APPLICATION_XML_VALUE)
	public void addMemberV2(@RequestBody Member member) {
		//code
	}
}

在上面的示例中,addMemberV1()方法以默认媒体类型(即application/json)生成内容。 第二种方法addMemberV2()会覆盖produces属性,并将生成application/xml类型的内容。

5. @PostMapping@RequestMapping

  1. 如上所述,@PostMapping注解是@RequestMapping注解的一种特殊版本,用于处理 HTTP POST 请求。

  2. @PostMapping@RequestMapping(method = RequestMethod.POST)的快捷方式。

    PostMapping.java

    @Target({ java.lang.annotation.ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @RequestMapping(method = { RequestMethod.POST })
    public @interface PostMapping 
    {
        //code
    }
    
    
  3. 在两个注解中,传递 URL 信息都是相同的。

让我们看看使用简单代码的 PostMapping 和@RequestMapping 注解之间的区别

@RequestMapping(value = "/employees", method = RequestMethod.POST)	//1

@PostMapping("/employees")	//2

6. 总结

Spring MVC 使编写请求处理器控制器类和方法变得非常容易。 只需添加一些注解,例如@GetMapping@PostMapping,然后将此类放置在组件扫描可以找到它们并在 Web 应用程序上下文中对其进行配置的位置。

在类级别创建属性也非常容易,以便所有处理器方法默认都继承它们,并可以在需要时覆盖它们。

同样,您可以使用其他请求映射注解,例如 @PutMapping@DeleteMapping@PatchMapping

学习愉快!

Spring Batch FlatFileItemReader – 读取 CSV 示例

原文: https://howtodoinjava.com/spring-batch/flatfileitemreader-read-csv-example/

学习使用FlatFileItemReader类从文件系统或资源文件夹中读取 CSV 文件。

项目结构

在这个项目中,我们将学习:

  1. input/inputData.csv读取 CSV 文件。
  2. 将数据写入控制台。

Project Structure

项目结构

使用FlatFileItemReader读取 CSV

您需要使用FlatFileItemReader从 CSV 文件中读取行。

BatchConfig.java

@Bean
public FlatFileItemReader<Employee> reader() 
{
	//Create reader instance
	FlatFileItemReader<Employee> reader = new FlatFileItemReader<Employee>();

	//Set input file location
	reader.setResource(new FileSystemResource("input/inputData.csv"));

	//Set number of lines to skips. Use it if file has header rows.
	reader.setLinesToSkip(1); 	

	//Configure how each line will be parsed and mapped to different values
	reader.setLineMapper(new DefaultLineMapper() {
		{
			//3 columns in each row
			setLineTokenizer(new DelimitedLineTokenizer() {
				{
					setNames(new String[] { "id", "firstName", "lastName" });
				}
			});
			//Set values in Employee class
			setFieldSetMapper(new BeanWrapperFieldSetMapper<Employee>() {
				{
					setTargetType(Employee.class);
				}
			});
		}
	});
	return reader;
}

inputData.csv

id,firstName,lastName
1,Lokesh,Gupta
2,Amit,Mishra
3,Pankaj,Kumar
4,David,Miller

将读取的行写入控制台

创建实现ItemWriter接口的ConsoleItemWriter类。

ConsoleItemWriter.java

import java.util.List;

import org.springframework.batch.item.ItemWriter;

public class ConsoleItemWriter<T> implements ItemWriter<T> { 
    @Override 
    public void write(List<? extends T> items) throws Exception { 
        for (T item : items) { 
            System.out.println(item); 
        } 
    } 
}

使用ConsoleItemWriter作为编写器。

BatchConfig.java

@Bean
public ConsoleItemWriter<Employee> writer() 
{
	return new ConsoleItemWriter<Employee>();
}

Maven 依赖

查看项目依赖项。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd;
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.howtodoinjava</groupId>
	<artifactId>App</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>App</name>
	<url>http://maven.apache.org</url>

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

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-batch</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>repository.spring.release</id>
			<name>Spring GA Repository</name>
			<url>http://repo.spring.io/release</url>
		</repository>
	</repositories>
</project>

示例

在运行该应用程序之前,请查看BatchConfig.java的完整代码。

BatchConfig.java

package com.howtodoinjava.demo.config;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;

import com.howtodoinjava.demo.model.Employee;

@Configuration
@EnableBatchProcessing
public class BatchConfig 
{
	@Autowired
	private JobBuilderFactory jobBuilderFactory;

	@Autowired
	private StepBuilderFactory stepBuilderFactory;

	@Bean
	public Job readCSVFilesJob() {
		return jobBuilderFactory
				.get("readCSVFilesJob")
				.incrementer(new RunIdIncrementer())
				.start(step1())
				.build();
	}

	@Bean
	public Step step1() {
		return stepBuilderFactory.get("step1").<Employee, Employee>chunk(5)
				.reader(reader())
				.writer(writer())
				.build();
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Bean
	public FlatFileItemReader<Employee> reader() 
	{
		//Create reader instance
		FlatFileItemReader<Employee> reader = new FlatFileItemReader<Employee>();

		//Set input file location
		reader.setResource(new FileSystemResource("input/inputData.csv"));

		//Set number of lines to skips. Use it if file has header rows.
		reader.setLinesToSkip(1); 	

		//Configure how each line will be parsed and mapped to different values
		reader.setLineMapper(new DefaultLineMapper() {
			{
				//3 columns in each row
				setLineTokenizer(new DelimitedLineTokenizer() {
					{
						setNames(new String[] { "id", "firstName", "lastName" });
					}
				});
				//Set values in Employee class
				setFieldSetMapper(new BeanWrapperFieldSetMapper<Employee>() {
					{
						setTargetType(Employee.class);
					}
				});
			}
		});
		return reader;
	}

	@Bean
	public ConsoleItemWriter<Employee> writer() 
	{
		return new ConsoleItemWriter<Employee>();
	}
}

Employee.java

public class Employee {

	String id;
	String firstName;
	String lastName;

	//public setter and getter methods
}

App.java

package com.howtodoinjava.demo;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

@SpringBootApplication
@EnableScheduling
public class App
{
    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    Job job;

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

    @Scheduled(cron = "0 */1 * * * ?")
    public void perform() throws Exception
    {
        JobParameters params = new JobParametersBuilder()
                .addString("JobID", String.valueOf(System.currentTimeMillis()))
                .toJobParameters();
        jobLauncher.run(job, params);
    }
}

application.properties

#Disable batch job's auto start 
spring.batch.job.enabled=false

spring.main.banner-mode=off

运行应用程序

将应用程序作为 Spring 运行应用程序运行,并观察控制台。 批处理作业将在每分钟开始时开始。 它将读取输入文件,并在控制台中打印读取的值。

Console

2018-07-10 13:28:35 INFO  - Starting App on FFC15B4E9C5AA with PID 12060 (C:\Users\user\app\App\target\classes started by zkpkhua in C:\Users\user\app\App)
2018-07-10 13:28:35 INFO  - No active profile set, falling back to default profiles: default
2018-07-10 13:28:35 INFO  - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@75329a49: startup date [Tue Jul 10 13:28:35 IST 2018]; root of context hierarchy
2018-07-10 13:28:37 INFO  - HikariPool-1 - Starting...
2018-07-10 13:28:38 INFO  - HikariPool-1 - Start completed.
2018-07-10 13:28:38 INFO  - No database type set, using meta data indicating: H2
2018-07-10 13:28:38 INFO  - No TaskExecutor has been set, defaulting to synchronous executor.
2018-07-10 13:28:39 INFO  - Executing SQL script from class path resource [org/springframework/batch/core/schema-h2.sql]
2018-07-10 13:28:39 INFO  - Executed SQL script from class path resource [org/springframework/batch/core/schema-h2.sql] in 74 ms.
2018-07-10 13:28:39 INFO  - Registering beans for JMX exposure on startup
2018-07-10 13:28:39 INFO  - Bean with name 'dataSource' has been autodetected for JMX exposure
2018-07-10 13:28:39 INFO  - Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
2018-07-10 13:28:39 INFO  - No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2018-07-10 13:28:39 INFO  - Started App in 4.485 seconds (JVM running for 5.33)

2018-07-10 13:29:00 INFO  - Job: [SimpleJob: [name=readCSVFilesJob]] launched with the following parameters: [{JobID=1531209540004}]

2018-07-10 13:29:00 INFO  - Executing step: [step1]

Employee [id=1, firstName=Lokesh, lastName=Gupta]
Employee [id=2, firstName=Amit, lastName=Mishra]
Employee [id=3, firstName=Pankaj, lastName=Kumar]
Employee [id=4, firstName=David, lastName=Miller]

2018-07-10 13:29:00 INFO  - Job: [SimpleJob: [name=readCSVFilesJob]] completed with the following parameters: [{JobID=1531209540004}] and the following status: [COMPLETED]

将我的问题放在评论部分。

学习愉快!

Spring Batch FlatFileItemWriter – 写入 CSV 文件

原文:https://howtodoinjava.com/spring-batch/flatfileitemwriter-write-to-csv-file/

学习使用FlatFileItemWriter写入 CSV 数据。 它是将数据写入文件或流的项目编写器。 输出文件的位置由Resource定义,并且必须表示可写文件。

项目结构

在这个项目中,我们将学习:

  1. 使用MultiResourceItemReaderinput/*.csv读取 3 个 CSV 文件。
  2. 使用FlatFileItemWriter将整个数据写入output/outputData.csv文件。

Project Structure

项目结构

使用FlatFileItemWriter写入数据 CSV 文件

您需要使用FlatFileItemWriter写入从 CSV 文件读取的行。 它将内容写入传递给writer.setResource()方法的任何Resource

BatchConfig.java

package com.howtodoinjava.demo.config;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.FlatFileItemWriter;
import org.springframework.batch.item.file.MultiResourceItemReader;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor;
import org.springframework.batch.item.file.transform.DelimitedLineAggregator;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

import com.howtodoinjava.demo.model.Employee;

@Configuration
@EnableBatchProcessing
public class BatchConfig 
{
	@Autowired
	private JobBuilderFactory jobBuilderFactory;

	@Autowired
	private StepBuilderFactory stepBuilderFactory;

	@Value("input/inputData*.csv")
	private Resource[] inputResources;

	private Resource outputResource = new FileSystemResource("output/outputData.csv");

	@Bean
	public FlatFileItemWriter<Employee> writer() 
	{
		//Create writer instance
		FlatFileItemWriter<Employee> writer = new FlatFileItemWriter<>();

		//Set output file location
		writer.setResource(outputResource);

		//All job repetitions should "append" to same output file
		writer.setAppendAllowed(true);

		//Name field values sequence based on object properties 
		writer.setLineAggregator(new DelimitedLineAggregator<Employee>() {
			{
				setDelimiter(",");
				setFieldExtractor(new BeanWrapperFieldExtractor<Employee>() {
					{
						setNames(new String[] { "id", "firstName", "lastName" });
					}
				});
			}
		});
		return writer;
	}

	@Bean
	public Job readCSVFilesJob() {
		return jobBuilderFactory
				.get("readCSVFilesJob")
				.incrementer(new RunIdIncrementer())
				.start(step1())
				.build();
	}

	@Bean
	public Step step1() {
		return stepBuilderFactory.get("step1").<Employee, Employee>chunk(5)
				.reader(multiResourceItemReader())
				.writer(writer())
				.build();
	}

	@Bean
	public MultiResourceItemReader<Employee> multiResourceItemReader() 
	{
		MultiResourceItemReader<Employee> resourceItemReader = new MultiResourceItemReader<Employee>();
		resourceItemReader.setResources(inputResources);
		resourceItemReader.setDelegate(reader());
		return resourceItemReader;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Bean
	public FlatFileItemReader<Employee> reader() 
	{
		//Create reader instance
		FlatFileItemReader<Employee> reader = new FlatFileItemReader<Employee>();

		//Set number of lines to skips. Use it if file has header rows.
		reader.setLinesToSkip(1); 	

		//Configure how each line will be parsed and mapped to different values
		reader.setLineMapper(new DefaultLineMapper() {
			{
				//3 columns in each row
				setLineTokenizer(new DelimitedLineTokenizer() {
					{
						setNames(new String[] { "id", "firstName", "lastName" });
					}
				});
				//Set values in Employee class
				setFieldSetMapper(new BeanWrapperFieldSetMapper<Employee>() {
					{
						setTargetType(Employee.class);
					}
				});
			}
		});
		return reader;
	}
}

Employee.java

public class Employee {

	String id;
	String firstName;
	String lastName;

	//public setter and getter methods
}

inputData1.csv

id,firstName,lastName
1,Lokesh,Gupta
2,Amit,Mishra
3,Pankaj,Kumar
4,David,Miller

inputData2.csv

id,firstName,lastName
5,Ramesh,Gupta
6,Vineet,Mishra
7,Amit,Kumar
8,Dav,Miller

inputData3.csv

id,firstName,lastName
9,Vikas,Kumar
10,Pratek,Mishra
11,Brian,Kumar
12,David,Cena

Maven 依赖

查看项目依赖项。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd;
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.howtodoinjava</groupId>
	<artifactId>App</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>App</name>
	<url>http://maven.apache.org</url>

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

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-batch</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>repository.spring.release</id>
			<name>Spring GA Repository</name>
			<url>http://repo.spring.io/release</url>
		</repository>
	</repositories>
</project>

示例

在运行该应用程序之前,请查看App.java的完整代码,该代码将其作为 Spring Boot 应用程序运行。

App.java

package com.howtodoinjava.demo;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

@SpringBootApplication
@EnableScheduling
public class App
{
    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    Job job;

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

    @Scheduled(cron = "0 */1 * * * ?")
    public void perform() throws Exception
    {
        JobParameters params = new JobParametersBuilder()
                .addString("JobID", String.valueOf(System.currentTimeMillis()))
                .toJobParameters();
        jobLauncher.run(job, params);
    }
}

application.properties

#Disable batch job's auto start 
spring.batch.job.enabled=false

spring.main.banner-mode=off

运行应用程序

将应用程序作为 Spring 运行应用程序运行,并观察控制台。 批处理作业将在每分钟开始时开始。 它将读取输入文件,并在控制台中打印读取的值。

outputData.csv

1,Lokesh,Gupta
2,Amit,Mishra
3,Pankaj,Kumar
4,David,Miller
5,Ramesh,Gupta
6,Vineet,Mishra
7,Amit,Kumar
8,Dav,Miller
9,Vikas,Kumar
10,Pratek,Mishra
11,Brian,Kumar
12,David,Cena

将我的问题放在评论部分。

学习愉快!

Spring Batch MultiResourceItemReader – 读取多个 CSV 文件示例

原文: https://howtodoinjava.com/spring-batch/multiresourceitemreader-read-multiple-csv-files-example/

学习使用MultiResourceItemReader类从文件系统或资源文件夹中读取多个 CSV 文件。 这些文件可能具有第一行作为标题,因此不要忘记跳过第一行。

项目结构

在此项目中,我们将:

  1. input/*.csv读取 3 个 CSV 文件。
  2. 将数据写入控制台。

Project Structure

项目结构

使用MultiResourceItemReader读取 CSV 文件

您需要使用MultiResourceItemReader从 CSV 文件中读取行。 它从多个资源顺序读取项目。

BatchConfig.java

@Value("input/inputData*.csv")
private Resource[] inputResources;

@Bean
public Job readCSVFilesJob() {
	return jobBuilderFactory
			.get("readCSVFilesJob")
			.incrementer(new RunIdIncrementer())
			.start(step1())
			.build();
}

@Bean
public Step step1() {
	return stepBuilderFactory.get("step1").<Employee, Employee>chunk(5)
			.reader(multiResourceItemReader())
			.writer(writer())
			.build();
}

@Bean
public MultiResourceItemReader<Employee> multiResourceItemReader() 
{
	MultiResourceItemReader<Employee> resourceItemReader = new MultiResourceItemReader<Employee>();
	resourceItemReader.setResources(inputResources);
	resourceItemReader.setDelegate(reader());
	return resourceItemReader;
}

@Bean
public FlatFileItemReader<Employee> reader() 
{
	//Create reader instance
	FlatFileItemReader<Employee> reader = new FlatFileItemReader<Employee>();

	//Set number of lines to skips. Use it if file has header rows.
	reader.setLinesToSkip(1); 	

	//Configure how each line will be parsed and mapped to different values
	reader.setLineMapper(new DefaultLineMapper() {
		{
			//3 columns in each row
			setLineTokenizer(new DelimitedLineTokenizer() {
				{
					setNames(new String[] { "id", "firstName", "lastName" });
				}
			});
			//Set values in Employee class
			setFieldSetMapper(new BeanWrapperFieldSetMapper<Employee>() {
				{
					setTargetType(Employee.class);
				}
			});
		}
	});
	return reader;
}

Employee.java

public class Employee {

	String id;
	String firstName;
	String lastName;

	//public setter and getter methods
}

inputData1.csv

id,firstName,lastName
1,Lokesh,Gupta
2,Amit,Mishra
3,Pankaj,Kumar
4,David,Miller

inputData2.csv

id,firstName,lastName
5,Ramesh,Gupta
6,Vineet,Mishra
7,Amit,Kumar
8,Dav,Miller

inputData3.csv

id,firstName,lastName
9,Vikas,Kumar
10,Pratek,Mishra
11,Brian,Kumar
12,David,Cena

将读取的行写入控制台

创建实现ItemWriter接口的ConsoleItemWriter类。

ConsoleItemWriter.java

import java.util.List;

import org.springframework.batch.item.ItemWriter;

public class ConsoleItemWriter<T> implements ItemWriter<T> { 
    @Override 
    public void write(List<? extends T> items) throws Exception { 
        for (T item : items) { 
            System.out.println(item); 
        } 
    } 
}

使用ConsoleItemWriter作为编写器。

BatchConfig.java

@Bean
public ConsoleItemWriter<Employee> writer() 
{
	return new ConsoleItemWriter<Employee>();
}

Maven 依赖

查看项目依赖项。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd;
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.howtodoinjava</groupId>
	<artifactId>App</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>App</name>
	<url>http://maven.apache.org</url>

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

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-batch</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>repository.spring.release</id>
			<name>Spring GA Repository</name>
			<url>http://repo.spring.io/release</url>
		</repository>
	</repositories>
</project>

示例

在运行该应用程序之前,请查看BatchConfig.java的完整代码。

BatchConfig.java

package com.howtodoinjava.demo.config;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.MultiResourceItemReader;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;

import com.howtodoinjava.demo.model.Employee;

@Configuration
@EnableBatchProcessing
public class BatchConfig 
{
	@Autowired
	private JobBuilderFactory jobBuilderFactory;

	@Autowired
	private StepBuilderFactory stepBuilderFactory;

	@Value("input/inputData*.csv")
	private Resource[] inputResources;

	@Bean
	public Job readCSVFilesJob() {
		return jobBuilderFactory
				.get("readCSVFilesJob")
				.incrementer(new RunIdIncrementer())
				.start(step1())
				.build();
	}

	@Bean
	public Step step1() {
		return stepBuilderFactory.get("step1").<Employee, Employee>chunk(5)
				.reader(multiResourceItemReader())
				.writer(writer())
				.build();
	}

	@Bean
	public MultiResourceItemReader<Employee> multiResourceItemReader() 
	{
		MultiResourceItemReader<Employee> resourceItemReader = new MultiResourceItemReader<Employee>();
		resourceItemReader.setResources(inputResources);
		resourceItemReader.setDelegate(reader());
		return resourceItemReader;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Bean
	public FlatFileItemReader<Employee> reader() 
	{
		//Create reader instance
		FlatFileItemReader<Employee> reader = new FlatFileItemReader<Employee>();

		//Set number of lines to skips. Use it if file has header rows.
		reader.setLinesToSkip(1); 	

		//Configure how each line will be parsed and mapped to different values
		reader.setLineMapper(new DefaultLineMapper() {
			{
				//3 columns in each row
				setLineTokenizer(new DelimitedLineTokenizer() {
					{
						setNames(new String[] { "id", "firstName", "lastName" });
					}
				});
				//Set values in Employee class
				setFieldSetMapper(new BeanWrapperFieldSetMapper<Employee>() {
					{
						setTargetType(Employee.class);
					}
				});
			}
		});
		return reader;
	}

	@Bean
	public ConsoleItemWriter<Employee> writer() 
	{
		return new ConsoleItemWriter<Employee>();
	}
}

App.java

package com.howtodoinjava.demo;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

@SpringBootApplication
@EnableScheduling
public class App
{
    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    Job job;

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

    @Scheduled(cron = "0 */1 * * * ?")
    public void perform() throws Exception
    {
        JobParameters params = new JobParametersBuilder()
                .addString("JobID", String.valueOf(System.currentTimeMillis()))
                .toJobParameters();
        jobLauncher.run(job, params);
    }
}

application.properties

#Disable batch job's auto start 
spring.batch.job.enabled=false

spring.main.banner-mode=off

运行应用程序

将应用程序作为 Spring 运行应用程序运行,并观察控制台。 批处理作业将在每分钟开始时开始。 它将读取输入文件,并在控制台中打印读取的值。

Console

2018-07-10 15:32:26 INFO  - Starting App on XYZ with PID 4596 (C:\Users\user\workspace\App\target\classes started by zkpkhua in C:\Users\user\workspace\App)
2018-07-10 15:32:26 INFO  - No active profile set, falling back to default profiles: default
2018-07-10 15:32:27 INFO  - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@3c9d0b9d: startup date [Tue Jul 10 15:32:27 IST 2018]; root of context hierarchy
2018-07-10 15:32:28 INFO  - HikariPool-1 - Starting...
2018-07-10 15:32:29 INFO  - HikariPool-1 - Start completed.
2018-07-10 15:32:29 INFO  - No database type set, using meta data indicating: H2
2018-07-10 15:32:29 INFO  - No TaskExecutor has been set, defaulting to synchronous executor.
2018-07-10 15:32:29 INFO  - Executing SQL script from class path resource [org/springframework/batch/core/schema-h2.sql]
2018-07-10 15:32:29 INFO  - Executed SQL script from class path resource [org/springframework/batch/core/schema-h2.sql] in 68 ms.
2018-07-10 15:32:30 INFO  - Registering beans for JMX exposure on startup
2018-07-10 15:32:30 INFO  - Bean with name 'dataSource' has been autodetected for JMX exposure
2018-07-10 15:32:30 INFO  - Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
2018-07-10 15:32:30 INFO  - No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2018-07-10 15:32:30 INFO  - Started App in 4.036 seconds (JVM running for 4.827)

2018-07-10 15:33:00 INFO  - Job: [SimpleJob: [name=readCSVFilesJob]] launched with the following parameters: [{JobID=1531216980005}]

2018-07-10 15:33:00 INFO  - Executing step: [step1]

Employee [id=1, firstName=Lokesh, lastName=Gupta]
Employee [id=2, firstName=Amit, lastName=Mishra]
Employee [id=3, firstName=Pankaj, lastName=Kumar]
Employee [id=4, firstName=David, lastName=Miller]
Employee [id=5, firstName=Ramesh, lastName=Gupta]
Employee [id=6, firstName=Vineet, lastName=Mishra]
Employee [id=7, firstName=Amit, lastName=Kumar]
Employee [id=8, firstName=Dav, lastName=Miller]
Employee [id=9, firstName=Vikas, lastName=Kumar]
Employee [id=10, firstName=Pratek, lastName=Mishra]
Employee [id=11, firstName=Brian, lastName=Kumar]
Employee [id=12, firstName=David, lastName=Cena]

2018-07-10 15:33:00 INFO  - Job: [SimpleJob: [name=readCSVFilesJob]] completed with the following parameters: [{JobID=1531216980005}] and the following status: [COMPLETED]

将我的问题放在评论部分。

学习愉快!

Spring Batch 读取后删除或存档文件

原文: https://howtodoinjava.com/spring-batch/delete-archive-files-after-read/

在 Spring Batch 作业中,在读取或处理后删除平面文件的最佳方法是创建单独的Tasklet,并在处理结束时在作业结束时执行它。

删除文件的任务

这是此类Tasklet的示例,它将在作业结束时从c:/temp/input/位置删除所有 CSV 文件。

FileDeletingTasklet.java

import java.io.File;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;

public class FileDeletingTasklet implements Tasklet, InitializingBean {

    private Resource[] resources;

    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {

    	for(Resource r: resources) {
    		File file = r.getFile();
    		boolean deleted = file.delete();
            if (!deleted) {
                throw new UnexpectedJobExecutionException("Could not delete file " + file.getPath());
            }
    	}
        return RepeatStatus.FINISHED;
    }

    public void setResources(Resource[] resources) {
        this.resources = resources;
    }

    public void afterPropertiesSet() throws Exception {
        Assert.notNull(resources, "directory must be set");
    }
}

存档文件

随时修改FileDeletingTasklet中的逻辑以将文件归档到其他位置或实现自己的归档逻辑。

简单的 IO 操作

如何使用FileDeletingTasklet

在主要步骤之后创建另一个要执行的步骤并执行FileDeletingTasklet

BatchConfig.java

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.MultiResourceItemReader;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;

import com.howtodoinjava.demo.model.Employee;

@Configuration
@EnableBatchProcessing
public class BatchConfig
{
	@Autowired
	private JobBuilderFactory jobBuilderFactory;

	@Autowired
	private StepBuilderFactory stepBuilderFactory;

	@Value("file:c:/temp/input/inputData*.csv")
	private Resource[] inputResources;

	@Bean
	public Job readCSVFilesJob() {
		return jobBuilderFactory
				.get("readCSVFilesJob")
				.incrementer(new RunIdIncrementer())
				.start(step1())
				.next(step2())
				.build();
	}

	@Bean
	public Step step1() {
		return stepBuilderFactory.get("step1").<Employee, Employee>chunk(5)
				.reader(multiResourceItemReader())
				.writer(writer())
				.build();
	}

	@Bean
    public Step step2() {
		FileDeletingTasklet task = new FileDeletingTasklet();
		task.setResources(inputResources);
        return stepBuilderFactory.get("step2")
        		.tasklet(task)
                .build();
    }

	@Bean
	public MultiResourceItemReader<Employee> multiResourceItemReader()
	{
		MultiResourceItemReader<Employee> resourceItemReader = new MultiResourceItemReader<Employee>();
		resourceItemReader.setResources(inputResources);
		resourceItemReader.setDelegate(reader());
		return resourceItemReader;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Bean
	public FlatFileItemReader<Employee> reader()
	{
		// Create reader instance
		FlatFileItemReader<Employee> reader = new FlatFileItemReader<Employee>();
		// Set number of lines to skips. Use it if file has header rows.
		reader.setLinesToSkip(1);
		// Configure how each line will be parsed and mapped to different values
		reader.setLineMapper(new DefaultLineMapper() {
			{
				// 3 columns in each row
				setLineTokenizer(new DelimitedLineTokenizer() {
					{
						setNames(new String[] { "id", "firstName", "lastName" });
					}
				});
				// Set values in Employee class
				setFieldSetMapper(new BeanWrapperFieldSetMapper<Employee>() {
					{
						setTargetType(Employee.class);
					}
				});
			}
		});
		return reader;
	}

	@Bean
	public ConsoleItemWriter<Employee> writer()
	{
		return new ConsoleItemWriter<Employee>();
	}
}

现在运行该应用程序并查看日志。

Console

2018-07-11 12:30:00 INFO  - Job: [SimpleJob: [name=readCSVFilesJob]] launched with the following parameters: [{JobID=1531292400004}]

2018-07-11 12:30:00 INFO  - Executing step: [step1]

Employee [id=1, firstName=Lokesh, lastName=Gupta]
Employee [id=2, firstName=Amit, lastName=Mishra]
Employee [id=3, firstName=Pankaj, lastName=Kumar]
Employee [id=4, firstName=David, lastName=Miller]
Employee [id=5, firstName=Ramesh, lastName=Gupta]
Employee [id=6, firstName=Vineet, lastName=Mishra]
Employee [id=7, firstName=Amit, lastName=Kumar]
Employee [id=8, firstName=Dav, lastName=Miller]
Employee [id=9, firstName=Vikas, lastName=Kumar]
Employee [id=10, firstName=Pratek, lastName=Mishra]
Employee [id=11, firstName=Brian, lastName=Kumar]
Employee [id=12, firstName=David, lastName=Cena]

2018-07-11 12:30:00 INFO  - Executing step: [step2]

Deleted file :: c:\temp\input\inputData1.csv
Deleted file :: c:\temp\input\inputData2.csv
Deleted file :: c:\temp\input\inputData3.csv

2018-07-11 12:30:00 INFO  - Job: [SimpleJob: [name=readCSVFilesJob]] completed with the following parameters: [{JobID=1531292400004}] and the following status: [COMPLETED]

将我的问题放在评论部分。

学习愉快!

Spring Batch 已处理记录的计数示例

原文: https://howtodoinjava.com/spring-batch/records-count-example/

了解如何使用ItemStreamChunkListener计数 Spring Batch 作业处理的记录数,并将记录数记录在日志文件或控制台中。

使用ItemStream计数记录

在给定的ItemStream实现下方,计算定期处理的记录数。

ItemCountItemStream.java

import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemStreamException;

public class ItemCountItemStream implements ItemStream {

	public void open(ExecutionContext executionContext) throws ItemStreamException {
	}

	public void update(ExecutionContext executionContext) throws ItemStreamException {
		System.out.println("ItemCount: "+executionContext.get("FlatFileItemReader.read.count"));
	}

	public void close() throws ItemStreamException {
	}
}

如何使用ItemCountItemStream

Tasklet中使用SimpleStepBuilder.stream()方法注册上面创建的ItemCountItemStream

BatchConfig.java

@Autowired
private JobBuilderFactory jobBuilderFactory;

@Autowired
private StepBuilderFactory stepBuilderFactory;

@Bean
public Job readCSVFilesJob() {
	return jobBuilderFactory
			.get("readCSVFilesJob")
			.incrementer(new RunIdIncrementer())
			.start(step1())
			.build();
}

@Bean
public Step step1() {
	return stepBuilderFactory
			.get("step1")
			.<Employee, Employee>chunk(1)
			.reader(reader())
			.writer(writer())
			.stream(stream())
			.build();
}

@Bean
public ItemCountItemStream stream() {
	return new ItemCountItemStream();
}

使用ChunkListener计数记录

在给定的ChunkListener实现下方,计算定期处理的记录数。

ItemCountListener.java

import org.springframework.batch.core.ChunkListener;
import org.springframework.batch.core.scope.context.ChunkContext;

public class ItemCountListener implements ChunkListener {

	@Override
	public void beforeChunk(ChunkContext context) {
	}

	@Override
	public void afterChunk(ChunkContext context) {

		int count = context.getStepContext().getStepExecution().getReadCount();
		System.out.println("ItemCount: " + count);
	}

	@Override
	public void afterChunkError(ChunkContext context) {
	}
}

如何使用ItemCountListener

Tasklet中使用SimpleStepBuilder.listener()方法注册上面创建的ItemCountListener

BatchConfig.java

@Autowired
private JobBuilderFactory jobBuilderFactory;

@Autowired
private StepBuilderFactory stepBuilderFactory;

@Bean
public Job readCSVFilesJob() {
	return jobBuilderFactory
			.get("readCSVFilesJob")
			.incrementer(new RunIdIncrementer())
			.start(step1())
			.build();
}

@Bean
public Step step1() {
	return stepBuilderFactory
			.get("step1")
			.<Employee, Employee>chunk(1)
			.reader(reader())
			.writer(writer())
			.listener(listener())
			.build();
}

@Bean
public ItemCountListener listener() {
	return new ItemCountListener();
}

计数记录演示

我正在使用上述ItemCountListener配置来处理此 CSV。

inputData.csv

id,firstName,lastName
1,Lokesh,Gupta
2,Amit,Mishra
3,Pankaj,Kumar
4,David,Miller
5,David,Walsh

完整的批处理配置如下所示:

BatchConfig.java

package com.howtodoinjava.demo.config;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.LineMapper;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;

import com.howtodoinjava.demo.model.Employee;

@Configuration
@EnableBatchProcessing
public class BatchConfig 
{
	@Autowired
	private JobBuilderFactory jobBuilderFactory;

	@Autowired
	private StepBuilderFactory stepBuilderFactory;

	@Value("/input/inputData.csv")
	private Resource inputResource;

	@Bean
	public Job readCSVFilesJob() {
		return jobBuilderFactory
				.get("readCSVFilesJob")
				.incrementer(new RunIdIncrementer())
				.start(step1())
				.build();
	}

	@Bean
	public Step step1() {
		return stepBuilderFactory
				.get("step1")
				.<Employee, Employee>chunk(1)
				.reader(reader())
				.writer(writer())
				.listener(listner())
				.build();
	}

	@Bean
	public ItemCountListener listner() {
		return new ItemCountListener();
	}

	@Bean
	public FlatFileItemReader<Employee> reader() {
		FlatFileItemReader<Employee> itemReader = new FlatFileItemReader<Employee>();
		itemReader.setLineMapper(lineMapper());
		itemReader.setLinesToSkip(1);
		itemReader.setResource(inputResource);
		return itemReader;
	}

	@Bean
	public LineMapper<Employee> lineMapper() {
		DefaultLineMapper<Employee> lineMapper = new DefaultLineMapper<Employee>();
		DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer();
		lineTokenizer.setNames(new String[] { "id", "firstName", "lastName" });
		lineTokenizer.setIncludedFields(new int[] { 0, 1, 2 });
		BeanWrapperFieldSetMapper<Employee> fieldSetMapper = new BeanWrapperFieldSetMapper<Employee>();
		fieldSetMapper.setTargetType(Employee.class);
		lineMapper.setLineTokenizer(lineTokenizer);
		lineMapper.setFieldSetMapper(fieldSetMapper);
		return lineMapper;
	}

	@Bean
	public ConsoleItemWriter<Employee> writer() {
		return new ConsoleItemWriter<Employee>();
	}
}

作为 Spring 启动应用程序启动该应用程序。 Spring 任务调度器将开始工作。

App.java

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

@SpringBootApplication
@EnableScheduling
public class App
{
    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    Job job;

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

    @Scheduled(cron = "0 */1 * * * ?")
    public void perform() throws Exception
    {
        JobParameters params = new JobParametersBuilder()
                .addString("JobID", String.valueOf(System.currentTimeMillis()))
                .toJobParameters();
        jobLauncher.run(job, params);
    }
}

现在观看控制台。

Console

2018-07-11 16:38:00 INFO  - Job: [SimpleJob: [name=readCSVFilesJob]] launched with the following parameters: [{JobID=1531307280004}]

2018-07-11 16:38:00 INFO  - Executing step: [step1]

Employee [id=1, firstName=Lokesh, lastName=Gupta]
ItemCount: 1
Employee [id=2, firstName=Amit, lastName=Mishra]
ItemCount: 2
Employee [id=3, firstName=Pankaj, lastName=Kumar]
ItemCount: 3
Employee [id=4, firstName=David, lastName=Miller]
ItemCount: 4
Employee [id=5, firstName=David, lastName=Walsh]
ItemCount: 5
ItemCount: 5

2018-07-11 16:38:00 INFO  - Job: [SimpleJob: [name=readCSVFilesJob]] completed with the following parameters: [{JobID=1531307280004}] and the following status: [COMPLETED]

将我的问题放在评论部分。

学习愉快!

参考:

ItemStream Java 文档

ChunkListener Java 文档

Spring Batch CSV 到数据库 – Java 注解配置示例

原文: https://howtodoinjava.com/spring-batch/csv-to-database-java-config-example/

学习使用 Spring Batch 从 CSV 文件读取记录,并使用JdbcBatchItemWriter插入数据库。 我正在使用嵌入式数据库 H2 演示此示例。

项目概况

在此应用程序中,我们将执行以下任务:

  1. 使用FlatFileItemReader从 CSV 文件读取员工记录
  2. 配置 H2 数据库并在其中创建EMPLOYEE
  3. JdbcBatchItemWriter将员工记录写入EMPLOYEE
  4. 使用ItemProcessor将日志项插入数据库
  5. 使用 H2 控制台验证插入的记录

项目结构

包结构

Maven 依赖

快速浏览构建此示例所需的 maven 依赖关系。 需要spring-boot-starter-web才能从浏览器窗口验证 H2 控制台中的数据。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd;
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.howtodoinjava</groupId>
	<artifactId>App</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>App</name>
	<url>http://maven.apache.org</url>

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

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-batch</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>repository.spring.release</id>
			<name>Spring GA Repository</name>
			<url>http://repo.spring.io/release</url>
		</repository>
	</repositories>
</project>

CSV 读取器和数据库写入器配置

  1. 我们将使用FlatFileItemReader读取 CSV 文件。 我们将使用涉及DefaultLineMapperDelimitedLineTokenizerBeanWrapperFieldSetMapper类的标准配置。
  2. 为了将记录写入数据库,我们将使用JdbcBatchItemWriter这是标准编写器,用于在数据库中为 Spring Batch 作业执行批处理查询。

BatchConfig.java

package com.howtodoinjava.demo.config;

import javax.sql.DataSource;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.LineMapper;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import com.howtodoinjava.demo.model.Employee;

@Configuration
@EnableBatchProcessing
public class BatchConfig {

	@Autowired
	private JobBuilderFactory jobBuilderFactory;

	@Autowired
	private StepBuilderFactory stepBuilderFactory;

	@Value("classPath:/input/inputData.csv")
	private Resource inputResource;

	@Bean
	public Job readCSVFileJob() {
		return jobBuilderFactory
				.get("readCSVFileJob")
				.incrementer(new RunIdIncrementer())
				.start(step())
				.build();
	}

	@Bean
	public Step step() {
		return stepBuilderFactory
				.get("step")
				.<Employee, Employee>chunk(5)
				.reader(reader())
				.processor(processor())
				.writer(writer())
				.build();
	}

	@Bean
    public ItemProcessor<Employee, Employee> processor() {
        return new DBLogProcessor();
    }

	@Bean
	public FlatFileItemReader<Employee> reader() {
		FlatFileItemReader<Employee> itemReader = new FlatFileItemReader<Employee>();
		itemReader.setLineMapper(lineMapper());
		itemReader.setLinesToSkip(1);
		itemReader.setResource(inputResource);
		return itemReader;
	}

	@Bean
	public LineMapper<Employee> lineMapper() {
		DefaultLineMapper<Employee> lineMapper = new DefaultLineMapper<Employee>();
		DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer();
		lineTokenizer.setNames(new String[] { "id", "firstName", "lastName" });
		lineTokenizer.setIncludedFields(new int[] { 0, 1, 2 });
		BeanWrapperFieldSetMapper<Employee> fieldSetMapper = new BeanWrapperFieldSetMapper<Employee>();
		fieldSetMapper.setTargetType(Employee.class);
		lineMapper.setLineTokenizer(lineTokenizer);
		lineMapper.setFieldSetMapper(fieldSetMapper);
		return lineMapper;
	}

	@Bean
	public JdbcBatchItemWriter<Employee> writer() {
		JdbcBatchItemWriter<Employee> itemWriter = new JdbcBatchItemWriter<Employee>();
		itemWriter.setDataSource(dataSource());
		itemWriter.setSql("INSERT INTO EMPLOYEE (ID, FIRSTNAME, LASTNAME) VALUES (:id, :firstName, :lastName)");
		itemWriter.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<Employee>());
		return itemWriter;
	}

	@Bean
	public DataSource dataSource(){
		EmbeddedDatabaseBuilder embeddedDatabaseBuilder = new EmbeddedDatabaseBuilder();
		return embeddedDatabaseBuilder.addScript("classpath:org/springframework/batch/core/schema-drop-h2.sql")
				.addScript("classpath:org/springframework/batch/core/schema-h2.sql")
				.addScript("classpath:employee.sql")
				.setType(EmbeddedDatabaseType.H2)
				.build();
	}
}

还创建DBLogProcessor,它将在写入数据库之前记录员工记录。 它是可选的。

DBLogProcessor.java

package com.howtodoinjava.demo.config;

import org.springframework.batch.item.ItemProcessor;
import com.howtodoinjava.demo.model.Employee;

public class DBLogProcessor implements ItemProcessor<Employee, Employee>
{
	public Employee process(Employee employee) throws Exception
	{
		System.out.println("Inserting employee : " + employee);
		return employee;
	}
}

模型类

Employee.java

package com.howtodoinjava.demo.config;
package com.howtodoinjava.demo.model;

public class Employee {

	String id;
	String firstName;
	String lastName;

	//Setter and getter methods
}

应用属性

application.properties

#Disable batch job's auto start 
spring.batch.job.enabled=false
spring.main.banner-mode=off

#batch input files location
input.dir=c:/temp/input

日志配置

logback.xml

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

	<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<charset>UTF-8</charset>
			<Pattern>%d{yyyy-MM-dd HH:mm:ss} %p %X{TXNID} - %m%n</Pattern>
		</encoder>
	</appender>

	<root level="INFO">
		<appender-ref ref="consoleAppender" />
	</root>
</configuration>

配置 H2 数据库

我们已经在BatchConfig.java中配置了数据源。

BatchConfig.java

@Bean
public DataSource dataSource(){
	EmbeddedDatabaseBuilder embeddedDatabaseBuilder = new EmbeddedDatabaseBuilder();
	return embeddedDatabaseBuilder.addScript("classpath:org/springframework/batch/core/schema-drop-h2.sql")
			.addScript("classpath:org/springframework/batch/core/schema-h2.sql")
			.addScript("classpath:employee.sql")
			.setType(EmbeddedDatabaseType.H2)
			.build();
}

创建EMPLOYEE

以上配置将自动生成默认表。 要生成EMPLOYEE表,请创建模式文件employee.sql并将其放置在resources文件夹中。

employee.sql

DROP TABLE EMPLOYEE IF EXISTS;

CREATE TABLE EMPLOYEE  (
	ID VARCHAR(10),  
	FIRSTNAME VARCHAR(100),  
	LASTNAME VARCHAR(100) 
) ;

启用 H2 控制台

要启用 H2 控制台,请向 Spring Web 注册org.h2.server.web.WebServlet

WebConfig.java

package com.howtodoinjava.demo.config;

import org.h2.server.web.WebServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@SuppressWarnings({"rawtypes","unchecked"})
@Configuration
public class WebConfig {

	@Bean
    ServletRegistrationBean h2servletRegistration(){
		ServletRegistrationBean registrationBean = new ServletRegistrationBean( new WebServlet());
        registrationBean.addUrlMappings("/console/*");
        return registrationBean;
    }
}

示例

我们的应用程序配置完成,作业准备执行。 让我们创建输入 CSV 文件。

inputData.csv

id,firstName,lastName
1,Lokesh,Gupta
2,Amit,Mishra
3,Pankaj,Kumar
4,David,Miller
5,David,Walsh

运行演示

要运行演示和批处理作业,请创建 Spring 运行应用程序类并启动应用程序。

App.java

package com.howtodoinjava.demo;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

@SpringBootApplication
@EnableScheduling
public class App
{
    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    Job job;

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

    @Scheduled(cron = "0 */1 * * * ?")
    public void perform() throws Exception
    {
        JobParameters params = new JobParametersBuilder()
                .addString("JobID", String.valueOf(System.currentTimeMillis()))
                .toJobParameters();
        jobLauncher.run(job, params);
    }
}

验证批处理作业结果

要验证批处理作业是否成功执行,请检查日志和 H2 控制台。

Console

2018-07-11 19:11:00 INFO  - Job: [SimpleJob: [name=readCSVFileJob]] launched with the following parameters: [{JobID=1531316460004}]

2018-07-11 19:11:00 INFO  - Executing step: [step]

Inserting employee : Employee [id=1, firstName=Lokesh, lastName=Gupta]
Inserting employee : Employee [id=2, firstName=Amit, lastName=Mishra]
Inserting employee : Employee [id=3, firstName=Pankaj, lastName=Kumar]
Inserting employee : Employee [id=4, firstName=David, lastName=Miller]
Inserting employee : Employee [id=5, firstName=David, lastName=Walsh]

2018-07-11 19:11:00 INFO  - Job: [SimpleJob: [name=readCSVFileJob]] completed with the following parameters: [{JobID=1531316460004}] and the following status: [COMPLETED]

H2 控制台

H2 Console Login

H2 控制台登录

Data in H2 Console

H2 控制台中的数据

将我的问题放在评论部分。

学习愉快!

下载源码

Spring Cloud

微服务 – 定义,原理和优势

原文: https://howtodoinjava.com/microservices/microservices-definition-principles-benefits/

微服务是业界最新的流行语,似乎每个人都以一种或另一种方式谈论它。 让我们了解什么是微服务? 在本教程中,我们将尝试了解微服务的定义,概念和原理

1. 微服务的定义

如今,微服务是继 SOA(面向服务的架构)之后越来越流行的架构模式之一。 如果您顺应行业趋势,那么您会意识到,如今的企业不再像几年前那样对开发大型应用程序来管理其端到端业务功能感兴趣,而是选择了快速而敏捷的应用程序,而这些应用程序的成本很高,他们的钱也减少了。

微服务有助于打破大型应用程序的界限,并在系统内部构建逻辑上独立的小型系统。 例如。 使用 Amazon AWS ,您可以毫不费力地构建云应用程序。 这是微服务可以做什么的一个很好的例子。

Monolithic vs MicroServices Architecture

整体与微服务架构

如上图所示,每个微服务都有其自己的业务层和数据库。 这样,对一种微服务的更改不会影响其他微服务。

通常,微服务使用广泛采用的轻量协议(例如 HTTP 和 REST)或消息传递协议(例如 JMS 或 AMQP)相互通信。 在特定情况下,它们也可以使用更专业的协议。

2. 微服务原理

现在,让我们研究微服务的“必备”原则。

  1. 单一责任原则

    单一责任原则是定义为 SOLID 设计模式一部分的原则之一。 这意味着一个单元(一个类,一个函数或一个微服务)应该只承担一个责任。

    在任何时候,一项微服务都不应承担一项以上的责任。

  2. 围绕业务能力构建

    微服务应专注于某些业务功能,并确保其有助于完成任务。 微服务绝不能限制自己采用最适合解决业务目的的适当技术栈或后端数据库存储。

    当我们设计整体应用程序时,这通常是约束条件,在这些应用程序中,我们试图解决多个业务解决方案并在某些切面做出一些妥协。 微服务可让您选择最适合当前问题的方法。

  3. 您创建它,就拥有它!

    这种设计的另一个重要切面与职责的前后发展有关。 在大型组织中,通常会有一个团队来开发应用程序位置,并且在进行一些知识转移之后,会将项目移交给维护团队。 在微服务中,构建服务的团队拥有它,并负责将来维护它。

    您构建它,拥有它!

    这种所有权使开发人员可以接触到其软件的日常操作,并且他们可以更好地了解现实世界中客户如何使用其内置产品。

  4. 基础设施自动化

    为微服务准备和构建基础架构是另一个非常重要的需求。 服务应是可独立部署的,并且应捆绑所有依赖项,包括库依赖项,甚至捆绑抽象物理资源的执行环境,例如 Web 服务器和容器或虚拟机。

    微服务和 SOA 之间的主要差异之一在于它们的自治程度。 虽然大多数 SOA 实现都提供服务级别的抽象,但是微服务却走得更远,并且抽象了实现和执行环境。

    在传统的应用程序开发中,我们先构建一个 WAR 或 EAR,然后将其部署到 JEE 应用程序服务器中,例如使用 JBoss,WebLogic,WebSphere 等。 我们可能将多个应用程序部署到同一个 JEE 容器中。 在理想情况下,在微服务方法中,每个微服务都将构建为胖 JAR,嵌入所有依赖项并作为独立的 Java 进程运行。

  5. 失败的设计

    微服务的设计应考虑到故障情况。 如果服务失败或停顿了一段时间该怎么办。 这些是非常重要的问题,必须在实际编码开始之前解决-清楚地估计服务故障将如何影响用户体验

    快速故障是用于构建容错,弹性系统的另一个概念。 这种哲学提倡的系统应该是失败的,而不是构建永远不会失败的系统。 由于服务随时可能发生故障,因此重要的是能够迅速检测到故障,并在可能的情况下自动恢复服务。

    微服务应用程序非常重视应用程序的实时监视,同时检查架构元素(数据库每秒收到多少请求)和业务相关指标(例如每分钟有多少订单) 收到)。 语义监视可以为发生错误的情况提供预警系统,从而触发开发团队进行跟进和调查。

3. 微服务的好处

与传统的多层单片架构相比,微服务提供了许多优势。 让我们列出它们:

  • 借助微服务,架构师和开发人员可以为每种微服务选择适用于特定体系结构和技术的多语言体系结构。 这样可以灵活地以更具成本效益的方式设计更适合的解决方案。
  • 由于服务相当简单且规模较小,因此企业可以承受尝试新流程,算法,业务逻辑等的费用。 它通过提供实验和快速失败的能力,使企业能够进行颠覆性创新。
  • 微服务能够实现选择性可伸缩性,即每个服务都可以独立地按比例放大或缩小,并且按比例缩放的成本要比单片方法小。
  • 微服务是自包含的独立部署模块,当第二个微服务未按我们的需要运行时,它可以用另一个类似的微服务代替。 它有助于做出正确的买进建造决策,而这通常是许多企业所面临的挑战。
  • 微服务可帮助我们构建本质上是有机的系统(有机系统是通过向其添加越来越多的功能而在一段时间内横向增长的系统)。 因为微服务都是关于独立可管理的服务的,所以它可以根据需要添加越来越多的服务,而对现有服务的影响却很小。
  • 技术变革是软件开发的障碍之一。 使用微服务,可以单独为每个服务更改或升级技术,而不是升级整个应用程序。
  • 由于微服务将服务运行时环境与服务本身打包在一起,因此可以在同一环境中共存多个版本的服务。
  • 最后,微服务还使小型,专注的敏捷团队得以开发。 将基于微服务的边界组织团队。

4。结论

通常,您做出体系结构决策的真正后果只有在您做出决策后的几年才会显现出来。

在本文中,我仅列举了一些关于微服务的正面评价,而这些知识在我有限的知识中已在许多组织中看到。 具有强大设计和出色编码人员支持的单片应用程序也可以证明是一个不错的决定,并且产品可以保留足够长的时间来支持该决定。

与微服务类似,糟糕的设计决策将被证明是昂贵的。 它们似乎在简化组件,但它们可能会增加它们之间的通信复杂性,并且更难控制和管理。

最后,好的设计和熟练的团队将为您带来胜利。 技术水平低下的团队总是会创建一个糟糕的系统,很难说微服务在这种情况下是减少混乱还是使其变得更糟。

我建议您从整体应用程序设计开始,并且当您觉得它使系统变得复杂时,请尝试使用微服务,以检查它们是否使应用程序变得不太复杂。 这样,您将有一个更明智的决定。

学习愉快!

资源:

Martin Fowler 关于微服务

服务监控 – Hystrix,Eureka 管理员和 Spring Boot 管理员

原文: https://howtodoinjava.com/spring-cloud/microservices-monitoring/

Spring Boot 和 Spring Cloud 在交付基于微服务的应用程序时被广泛使用。 最终,基于在不同主机上运行的 Spring 运行应用程序监视微服务成为必要。 有许多工具可用来监视这些微服务的各种运行状况。

在此 SpringCloud 教程中,我们将学习使用三种监视工具,即 Hystrix 仪表板Eureka 管理仪表板Spring boot 管理仪表板

1. 概述

在此演示中,我们将创建三个应用程序。

  1. 员工服务 – 此微服务应用程序负责获取员工的数据。
  2. Api 网关 – 此应用程序将在访问不同的微服务时提供公共网关。 在以下示例中,它将充当上述员工服务的网关。
  3. Eureka 服务器 – 此微服务应用程序将提供上述微服务的服务发现和注册。

该演示是围绕 Netflix Eureka 创建的,以集中管理和监视注册的应用程序。 您可能已经知道 Netflix Eureka 服务器是用于构建服务注册表服务器和关联的 Eureka 客户端的,它们将注册自己以查找其他服务并通过 REST API 进行通信。

2. 技术栈

  • Java 1.8
  • Spring 工具套件
  • SpringCloud
  • SpringBoot
  • SpringRest
  • Maven

3. 员工服务

  • 从 Spring boot 初始化器 / Spring 工具套件创建一个 Spring Boot 项目。具有依赖 Eureka Discovery执行器WebREST 存储库

  • 主应用程序类EmployeeServiceApplication,用于启动 Spring Boot 应用程序。

    EmployeeServiceApplication.java

    package com.howtodoinjava.example.employee;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    
    @SpringBootApplication
    @EnableEurekaClient
    public class EmployeeServiceApplication {
    
      public static void main(String[] args) 
      {
        SpringApplication.run(EmployeeServiceApplication.class, args);
      }
    }
    

    @EnableEurekaClient – 此注解在下面创建的 Eureka Server 应用程序中将该服务注册为 Eureka 客户端。

  • 创建一个 Rest 控制器类EmployeeServiceController以公开Employee数据。

    EmployeeServiceController.java

    package com.howtodoinjava.example.employee.controller;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.howtodoinjava.example.employee.beans.Employee;
    
    @RestController
    public class EmployeeServiceController {
    
    	private static final Map<Integer, Employee> employeeData = new HashMap<Integer,Employee() {
    
        	private static final long serialVersionUID = -3970206781360313502L;
        	{
        		put(111,new Employee(111,"Employee1"));
        		put(222,new Employee(222,"Employee2"));
        	}
        };
    
        @RequestMapping(value = "/findEmployeeDetails/{employeeId}", method = RequestMethod.GET)
        public Employee getEmployeeDetails(@PathVariable int employeeId) {
            System.out.println("Getting Employee details for " + employeeId);
    
            Employee employee = employeeData.get(employeeId);
            if (employee == null) {
    
            	employee = new Employee(0, "N/A");
            }
            return employee;
        }
    }
    
    

    关联的Employee Bean 类如下。

    Employee.java

    package com.howtodoinjava.example.employee.beans;
    
    public class Employee {
    
    	private String name;
    	private int id;
    
    	@Override
    	public String toString() {
    		return "Employee [name=" + name + ", id=" + id + "]";
    	}
    }
    
    
  • src/main/resources目录中创建application.yml

    application.yml

    server:
      port: 8011    
    
    eureka:         
      instance:
        leaseRenewalIntervalInSeconds: 5
        leaseExpirationDurationInSeconds: 2
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
        healthcheck:
          enabled: true
        lease:
          duration: 5
    
    spring:    
      application:
        name: employee-service   
    
    management:
      security:
        enabled: false  
    
    logging:
      level:
        com.self.sprintboot.learning.employee: DEBUG
    
    
  • 启动此应用,可访问http://localhost:8011/findEmployeeDetails/111

4. 带有 Hystrix 的 API 网关

  • 从 Spring boot 初始化器 / Spring 工具套件创建一个 Spring Boot 项目。具有依赖Eureka Discovery, Actuator, Web, Hystrix, Hystrix Dashboard, Rest repositories

  • 主应用程序类ApiGatewayApplication,用于启动 Spring Boot 应用程序。

    ApiGatewayApplication.java

    package com.howtodoinjava.example.apigateway;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
    
    @SpringBootApplication
    @EnableEurekaClient
    @EnableHystrixDashboard
    @EnableCircuitBreaker
    public class ApiGatewayApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(ApiGatewayApplication.class, args);
    	}
    }
    
    

    @EnableHystrixDashBoard – 提供 Hystrix 流的仪表板视图。

    @EnableCircuitBreaker – 启用断路器实现。

  • 创建一个 REST 控制器类EmployeeController以公开Employee数据。

    EmployeeController.java

    package com.howtodoinjava.example.apigateway.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.core.ParameterizedTypeReference;
    import org.springframework.http.HttpMethod;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    
    @RestController
    public class EmployeeController {
    
    	@Autowired
        RestTemplate restTemplate;
    
        @RequestMapping(value = "/employeeDetails/{employeeid}", method = RequestMethod.GET)
        @HystrixCommand(fallbackMethod = "fallbackMethod")
        public String getStudents(@PathVariable int employeeid)
        {
            System.out.println("Getting Employee details for " + employeeid);
    
            String response = restTemplate.exchange("http://employee-service/findEmployeeDetails/{employeeid}",
                                    HttpMethod.GET, null, new ParameterizedTypeReference<String>() {}, employeeid).getBody();
    
            System.out.println("Response Body " + response);
    
            return "Employee Id -  " + employeeid + " [ Employee Details " + response+" ]";
        }
    
        public String  fallbackMethod(int employeeid){
    
        	return "Fallback response:: No employee details available temporarily";
        }
    
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    
    
  • src/main/resources目录中创建application.yml

    application.yml

    server:
      port: 8010    #port number
    
    eureka:
      instance:
        leaseRenewalIntervalInSeconds: 5
        leaseExpirationDurationInSeconds: 2
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
        healthcheck:
          enabled: true
        lease:
          duration: 5
    
    spring:    
      application:
        name: api-gateway   
    
    management:
      security:
        enabled: false  
    
    logging:
      level:
        com.self.sprintboot.learning.apigateway: DEBUG
    
    
  • 启动应用程序,可访问http://localhost:8010/employeeDetails/111

5. Hystrix 仪表板视图

  • 通过 Hystrix 仪表板进行监控,请在http://localhost:8010/hystrix打开 Hystrix 仪表板。

    这是主页,需要放置事件流 URL 进行监视。

  • 现在在信息中心中查看 Hystrix 流http://localhost:8010/hystrix.stream

    这提供了所有 Hystrix 命令和线程池的实时信息。

6. Eureka 管理仪表板视图

现在让我们学习如何使用 Eureka 管理控制台视图。

  • 从 Spring boot 初始化器 / Spring 工具套件创建一个 Spring Boot 项目,具有以下依赖 Eureka Server执行器WebSpring Boot 管理员服务器

  • 主应用程序类EurekaServerApplication,用于启动 Spring Boot 应用程序。

    EurekaServerApplication.java

    package com.howtodoinjava.example.eureka;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    import de.codecentric.boot.admin.config.EnableAdminServer;
    
    @SpringBootApplication
    @EnableEurekaServer
    @EnableAdminServer
    
    public class EurekaServerApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(EurekaServerApplication.class, args);
    	}
    }
    
    

    @EnableEurekaServer – 此注解将使此应用程序充当微服务注册表和发现服务器。

    @EnableAdminServer – 此注解提供 Spring Boot Admin 配置。

  • src/main/resources目录中创建application.ymlbootstrap.yml

  • 使用给定的配置添加application.yml。 请注意,对于 Spring boot admin 服务器,提供了一个不同的上下文路径/admin,以免与/eureka冲突。

    application.yml

    server:
      port: ${PORT:8761}
    
    eureka:
      client:
        registryFetchIntervalSeconds: 5
        registerWithEureka: false
        serviceUrl:
          defaultZone: ${DISCOVERY_URL:http://localhost:8761}/eureka/
      instance:
        leaseRenewalIntervalInSeconds: 10
    
    management:
      security:
        enabled: false
    spring:
      boot:
        admin:
          context-path: /admin  #A different context path for Spring boot admin server has been provided avoiding conflict with eureka
    
    
  • 创建bootstrap.yml并进行配置。

    bootstrap.yml

    spring:
      application:
        name: Eureka-Server
      cloud:
        config:
          uri: ${CONFIG_SERVER_URL:http://localhost:8888}
    
    
  • 启动应用程序。 但在此之前,请确保之前启动了上面提到的其余客户端应用程序,以便查看所有已注册的应用程序。 该应用程序可通过http://localhost:8761访问。

7. Spring Boot 管理仪表板视图

  • 要通过 Spring Boot Admin 服务器进行监视,请调用此 URL,该 URL 运行在不同的上下文路径 - http://localhost:8761/admin中。

  • 该管理界面提供应用程序概述,桌面通知,应用程序运行状况检查,日志文件浏览,JMX Bean,线程堆转储等。要查看单个应用程序的运行状况并监视其指标,请单击详细信息按钮。 它将带您到各个应用程序的管理仪表板。

  • 使用仪表板管理日志级别。

  • 使用仪表板管理运行时环境属性。

  • 您也可以使用它来查看 HTTP 跟踪。

从 Spring 运行管理员的角度来看,目前是这样。 请随时添加任何评论/查询。 我们很乐意解决同样的问题。

下载员工服务

下载 API 网关

下载 Eureka 服务器

学习愉快!

Hoverfly – 微服务虚拟化示例

https://howtodoinjava.com/microservices/hoverfly-microservices-virtualization-tutorial/

微服务虚拟化是一种模拟异构组件基于应用程序(例如 API 驱动的应用程序,基于云的应用程序或面向服务的体系结构)中特定组件行为的技术。

详细了解服务虚拟化概念,并研究流行且有用的服务虚拟化工具 – Hoverfly 。 另请参阅如何在 Hoverfly 将以仿真模式运行时如何使用 Hoverfly 以代理模式捕获请求/响应并使用那些捕获的响应。

什么是微服务虚拟化?

如今,云应用程序使用大量微服务,这些微服务相互交互以完成某些业务功能。 在开发这类生态系统时,我们有时会遇到一些普遍的问题,这些问题通常会影响整个团队的生产力,例如

  • 生态系统中的所有服务目前可能不可用。 可能是其他团队也在开发这些。
  • 一些旧版服务并非免费拥有开发环境,否则可能会很昂贵,并且由于明显的原因,我们不能使用正式版进行测试。
  • 由于某些原因,某些服务可能会关闭。
  • 管理测试数据的问题。 通常,要编写适当的测试,您需要对模拟或存根中的数据进行细粒度的控制。 与多个团队一起在大型项目中管理测试数据会引入影响交付时间的瓶颈。

因此,我们可以很容易地理解到,上述问题将影响当前产品的开发,并可能影响交付时间表,这与该产品的开发成本成正比。 那么有什么解决方案呢?

  • 我们可以考虑使用一些流行的模拟框架来模拟这些服务。 它也有一些缺点,例如模拟通常是特定于场景的,并且要为这些服务创建模拟响应需要花费很多精力,并且模拟仅在单元测试阶段才最适合(Junit)。
  • 我们可以使用存根(stubbed)服务,在其中我们将开发一些伪造的服务以及硬编码的响应 – 同样,这些伪造也是没有意义的,因为我们需要开发一些东西来使这项工作有效。
  • 现在又是几天,我们需要在开发时进行持续集成,在这种情况下,模拟和存根服务都不是很好的选择。

基于我们讨论的常见问题,我们针对这些类型的相似技术,撰写了一篇非常好的 infoq 文章。

由于模拟服务和存根服务都无法有效使用,因此,为了解决上述问题,我们采用了一种称为服务虚拟化的技术,可以捕获/模拟实际服务。 Hoverfly 是一种这样的工具,它是使用新的 JVM 语言 GO 新开发的,它提供了非常简单而现实的步骤来解决该问题。

为了更好的理解,下面是运行虚拟化服务时的时序图。

捕获模式下的 Hoverfly – 充当真实服务的代理服务

模拟模式下的 Hoverfly – 无需实际服务即可直接响应

示例概述

我们将按照给定的步骤演示 Hoverfly 作为服务垂直化工具的用法。

  • 我们将创建一个小型的微服务生态系统,它们将彼此交互。
  • 当处于捕获模式时,我们将使用 Hoverfly 拦截实际的请求/响应。
  • 最后,我们将了解 Hoverfly 如何在模拟模式下充当服务虚拟化服务器以发送回已捕获的请求/响应。
  • 我们还将检查基本服务停机时间不会对我们的开发产生太大影响。
  • 我们还将看到我们如何轻松切换 Hoverfly 以返回捕获模式并将请求传递给实际服务。

先决条件

在开始演示 Hoverfly 功能之前,请确保已安装以下先决条件:

  • Hoverfly 安装指南
  • JDK 8
  • Eclipse
  • Maven

创建下游服务

让我们使用 Spring Boot 创建服务,以缩短开发时间。 请按照以下步骤启动此服务。

创建 Spring Boot 项目

从 spring 初始化器页面创建一个具有相关性(即WebRest Repositories)的 Spring Boot 项目。 如下所示,给出其他 Maven GAV 坐标并下载项目。

将项目解压缩并将其作为existing maven project导入 Eclipse。 在此步骤中,使用命令mvn clean install重新构建 Maven,以便正确下载所有 Maven 依赖项。

添加一个简单的 REST 端点

添加一个RestController类,它将公开一个简单的端点/service/hoverfly。 此端点将由我们在此之后开发的第二项服务消耗。 为简单起见,我们只是返回一些硬编码的值,并在响应中添加了响应时间。

package com.example.hoverflyactualservice;

import java.util.Date;
import java.util.UUID;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class HoverflyActualServiceApplication {

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

@RestController
class MyRestController {

	@RequestMapping(value = "/service/hoverfly")
	public HoverflyServiceResponse getSampleResponse() {
		System.out.println("Inside HoverflyActualServiceApplication::getSampleResponse()");
		return new HoverflyServiceResponse("returned value from HoverflyActualServiceApplication", new Date().toString(), UUID.randomUUID().toString());
	}
}

class HoverflyServiceResponse {
	private String message;
	private String responseTime;
	private String transactionid;

	public HoverflyServiceResponse(String message, String responseTime, String transactionid) {
		super();
		this.message = message;
		this.responseTime = responseTime;
		this.transactionid = transactionid;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public String getResponseTime() {
		return responseTime;
	}

	public void setResponseTime(String responseTime) {
		this.responseTime = responseTime;
	}

	public String getTransactionid() {
		return transactionid;
	}

	public void setTransactionid(String transactionid) {
		this.transactionid = transactionid;
	}
}

验证服务

使用属性server.port = 9080将应用程序端口更改为9080。 通过运行命令java -jar target\hoverfly-actual-service-0.0.1-SNAPSHOT.jar作为 spring boot 应用程序启动该项目。

服务器启动后,转到浏览器并测试此端点是否正常工作 – http://localhost:9080/service/hoverfly

浏览器中的下游服务响应

因此,我们的第一个下游微服务已启动并正在运行。 现在,我们将创建第二个微服务,该微服务将调用该服务。

创建客户服务

再次按照上述步骤创建此服务。 在 eclipse 中导入项目后,添加控制器代码。

添加 REST 端点

添加一个RestController类,它将公开一个简单的端点/invoke。 该终结点方法将在内部调用我们刚刚开发的下游服务(hoverfly-actual-service)。

另外,我们在通过使用一个称为mode的系统属性创建RestTemplate bean 时添加了逻辑。

如果我们在启动服务时传递mode=proxy,则对此的所有请求将首先通过 Hoverfly 代理路由。
如果我们通过mode=production,则对此的所有请求将直接转到实际服务中。

请仔细观察restTemplate()方法以了解代理模式。 注意 Hoverfly 代理服务器将在http://localhost:8500上运行。

package com.example.hoverflyactualserviceclient;

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Proxy.Type;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class HoverflyActualServiceClientApplication {

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

@RestController

class TestController {

	private static final int HOVERFLY_PORT = 8500;
	private static final String HOVERFLY_HOST = "localhost";
	private static final String PROXY = "proxy";

	@RequestMapping("/invoke")
	public String invoke() {
		System.out.println("inside TestController::invoke()");
		String url = "http://localhost:9080/service/hoverfly";
		String response = restTemplate.exchange(url, HttpMethod.GET, null,
				new ParameterizedTypeReference<String>() {
				}).getBody();
		System.out.println("Actual Response : " + response);
		return response;
	}

	@Bean
	public RestTemplate restTemplate() {

		String mode = System.getProperty("mode");
		System.out.println("##################### Mode ################# " + mode);

		SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
		Proxy proxy = new Proxy(Type.HTTP, new InetSocketAddress(HOVERFLY_HOST, HOVERFLY_PORT));
		requestFactory.setProxy(proxy);
		RestTemplate template = null;

		if (mode != null && mode.equalsIgnoreCase(PROXY)) {
			System.out.println("######### Running application in PROXY mode so 
									that we can use simulated hoverfly server!!!!");
			template = new RestTemplate(requestFactory);
		} else {
			System.out.println("######### Running application in PRODUCTION mode 
									so that we can use simulated hoverfly server!!!!");
			template = new RestTemplate();
		}

		return template;
	}

	@Autowired
	RestTemplate restTemplate;
}

验证服务

该服务在本地的默认端口8080中运行。 使用命令mvn clean install进行 Maven 构建,并通过运行命令java -jar -Dmode=proxy target\hoverfly-actual-service-client-0.0.1-SNAPSHOT.jar作为 Spring Boot 应用程序启动该项目。

在代理模式下启动服务时,请不要传递-Dmode=proxy。 在生产等实际环境中,我们不会传递此参数。 在浏览器中调用 API。

http://localhost:8080/invoke

客户端服务以代理方式运行

因此,我们还开发了客户端应用程序,我们还可以测试客户端服务并从下游服务获得响应。 我们还在客户端代码中正确配置了 Hoverfly 代理服务器,以便接下来可以轻松集成 hoverfly。

Hoverfly 演示

现在,我们将在本地启动 Hoverfly,并测试不同的模式,并查看它在服务停机时的实际作用。 Hoverfly 提供 4 种不同的模式,capturesimulatemodifysynthesize。 在此演示中,我们仅寻找capturesimulate模式。

在捕获模式下启动 Hoverfly

打开一个指向 Hoverfly 目录(未压缩目录)的命令窗口,然后键入hoverctl start命令。 它将在proxy mode的本地工作站中启动 hoverfly,并在8888的端口中启动 admin UI,并在8500的端口中启动代理服务器。

现在,在同一命令提示符中键入hoverctl mode capture以更改 hoverfly 模式以捕获。 在这两个命令之后,命令提示符窗口将类似于:

在捕获模式下启动 Hoverfly

现在转到浏览器http://localhost:8888/dashboard,它将显示管理 UI,我们还可以在其中更改模式,还可以看到已捕获或模拟了多少个请求。

Hoverfly 管理员界面

捕获请求/响应

现在有了这些设置,当 hoverfly 处于捕获模式时,在浏览器窗口中运行客户端服务几次。 现在,再次进入管理用户界面,请注意,捕获计数器已增加到您在浏览器中访问客户端服务应用程序的次数。

Hoverfly 已捕获事务

导出/导入捕获的请求

将模拟的请求和响应存储在其他位置是个好主意,因此我们不必一直运行 Hoverfly。 每当我们需要它时,我们都将导入保存的请求/响应并开始模拟服务。

现在,我们将捕获的请求导出到 JSON 文件中,然后将该文件导入 Hoverfly,并以仿真模式启动 Hoverfly。

要导出,请打开 Hoverfly 命令窗口并键入hoverctl export simulations.json命令,这将已捕获(在本例中为 3 个事务)连同所有 URL,请求等一起导出到 json 文件simulations.json中。导出后,该文件将被导出。 在 hoverfly 的主目录中创建。

导出到 JSON 文件

要导入回simulations.json文件,可以键入hoverctl import simulations.json命令以导入捕获的定义。

导入后,我们将通过hoverctl mode simulate命令将 Hoverfly 模式更改为simulate。 我们也可以从 Hoverfly 管理员界面页面执行此操作。

导入/导出和模拟命令

现在我们准备将 Hoverfly 模式切换为simulate并进行测试。

在模拟模式下测试

使用此命令hoverctl mode simulate进入模拟模式。 执行以下简单步骤:

  • 在浏览器中打开客户端应用程序,然后点击刷新按钮,查看响应在浏览器中没有变化(注意响应时间和事务 ID 字段),这意味着 Hoverfly 处于活动状态,并正在发送响应给所有与导入文件匹配的 URL 模式。
  • 无需进入 Hoverfly 管理界面并查看模拟计数器已增加到在模拟模式下访问客户端应用程序的次数。
  • 现在停止下游服务并单击客户端应用程序,您可以轻松地看到 Hoverfly 代表模拟的下游服务进行响应。 在我们要在实际服务中断时测试功能的实际情况中,这很棒,并且确实有帮助。
  • 再次启动下游服务,并将 Hoverfly 的模式从命令提示符或管理界面更改为capture,并看到对客户端服务的任何请求都被往返,直到下游服务和 Hoverfly 的捕获计数器增加。 因此,很容易在capturesimulate模式之间进行切换,这对于我们要捕获下游服务的新型请求是必需的。

Hoverfly 模拟响应

总结

因此,今天我们了解了如何有效,轻松地使用服务虚拟化工具 Hoverfly 并将其集成到我们的微服务生态系统中。 我们只看到了极少数的 Hoverfly 功能,有关更多详细信息,请访问其漂亮的文档。

我建议您在下一个应用程序设计中考虑它。 你会喜欢它。

下载源码

学习愉快!