HowToDoInJava-Spring-教程-二-

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

HowToDoInJava Spring 教程(二)

原文:HowToDoInJava

协议:CC BY-NC-SA 4.0

Spring WebFlux 教程

原文: https://howtodoinjava.com/spring-webflux/spring-webflux-tutorial/

Spring 5.0 添加了反应堆 Web 框架 Spring WebFlux 。 它是完全无阻塞的,支持响应式背压,并在 Netty,Undertow 和 Servlet 3.1+ 容器等服务器上运行。 在这个 spring webflux 教程中,我们将学习响应式编程,webflux api 和功能齐全的 hello world 示例背后的基本概念。

1. 响应式编程

响应式编程是一种编程示例,可促进异步,非阻塞,事件驱动的数据处理方法。 响应式编程涉及将数据和事件建模为可观察的数据流,并实现数据处理例程以对这些流中的更改做出反应。

在深入研究响应式世界之前,首先要了解阻塞与非阻塞请求处理之间的区别。

1.1. 阻塞与非阻塞(异步)请求处理

1.1.1. 阻塞请求处理

在传统的 MVC 应用程序中,当请求到达服务器时,将创建一个 servlet 线程。 它将请求委托给工作线程进行 I/O 操作(例如数据库访问等)。在工作线程忙时,servlet 线程(请求线程)保持等待状态,因此被阻塞。 也称为同步请求处理

Blocking request processing

阻塞请求处理

由于服务器可以有一定数量的请求线程,因此限制了服务器在最大服务器负载下处理该数量请求的能力。 它可能会影响性能并限制服务器功能的完全利用。

1.1.2. 非阻塞请求处理

在非阻塞或异步请求处理中,没有线程处于等待状态。 通常只有一个请求线程接收该请求。

所有传入的请求都带有事件处理器和回调信息。 请求线程将传入的请求委托给线程池(通常为少量线程),该线程池将请求委托给其处理函数,并立即开始处理来自请求线程的其他传入请求。

处理器功能完成后,池中的线程之一将收集响应并将其传递给回调函数。

Non-blocking request processing

非阻塞请求处理

线程的非阻塞性质有助于扩展应用程序的性能。 线程数量少意味着内存利用率较低,上下文切换也较少。

1.2. 什么是响应式编程?

术语“响应式”是指围绕响应变化而构建的编程模型。 它是基于发布者-订阅者模式(观察者模式)构建的。 在响应式编程中,我们请求资源并开始执行其他事情。 当数据可用时,我们会收到通知以及回调函数的数据通知。 在回调函数中,我们根据应用程序/用户需求处理响应。

要记住的重要一件事是背压。 在非阻塞代码中,控制事件的速率变得很重要,这样快速生成器就不会淹没其目的地。

响应式 Web 编程非常适合具有流数据的应用程序以及使用该数据并将其流传输给用户的客户端。 这对开发传统的 CRUD 应用程序不是很好。 如果您要开发具有大量数据的下一个 Facebook 或 Twitter ,那么响应式 API 可能正是您想要的。

2. 响应式流 API

新的响应式流式 API 由 Netflix,Pivotal,Lightbend,RedHat,Twitter 和 Oracle 等工程师创建,现已成为 Java 9 的一部分。它定义了四个接口:

  • Publisher: 根据从订阅者收到的需求向订阅者发出一系列事件。 一个发布者可以为多个订阅者提供服务。

    它只有一个方法:

    Publisher.java

    public interface Publisher<T> 
    {
    	public void subscribe(Subscriber<? super T> s);
    }
    
    
  • Subscriber: 接收和处理发布者发出的事件。 请注意,在调用Subscription#request(long)发出信号表示需求之前,不会收到任何通知。

    它有四种方法来处理收到的各种响应。

    Subscriber.java

    public interface Subscriber<T> 
    {
    	public void onSubscribe(Subscription s);
    	public void onNext(T t);
    	public void onError(Throwable t);
    	public void onComplete();
    }
    
    
  • Subscription: 定义“发布者”和“订阅者”之间的一对一关系。 只能由单个“订阅者”使用一次。 它既用于表示对数据的需求,又用于取消需求(并允许清除资源)。

    Subscription.java

    public interface Subscription<T> 
    {
    	public void request(long n);
    	public void cancel();
    }
    
    
  • Processor: 表示由“订阅者”和“发布者”组成的处理阶段,并服从两者的契约。

    Processor.java

    public interface Processor<T, R> extends Subscriber<T>, Publisher<R> 
    {
    }
    
    

响应式的两种流行实现是 RxJava(https://github.com/ReactiveX/RxJava)和 Project Reactor(https://projectreactor.io/)。

3. 什么是 Spring WebFlux?

Spring WebFlux 是 Spring MVC 的并行版本,并支持完全无阻塞的响应式。 它支持背压概念,并使用 Netty 作为内置服务器来运行响应式应用程序。 如果您熟悉 Spring MVC 编程风格,则也可以轻松地使用 webflux。

Spring Webflux 使用项目反应器作为反应库。 Reactor 是 Reactive Streams 库,因此,它的所有运算符都支持无阻塞背压。 它是与 Spring 紧密合作开发的。

Spring WebFlux 大量使用两个发布者:

  • Mono:返回 0 或 1 个元素。

    Mono<String> mono = Mono.just("Alex");
    Mono<String> mono = Mono.empty();
    
    
  • Flux:返回0…N个元素。 Flux可以是无穷无尽的,这意味着它可以永远保持发光。 它还可以返回一系列元素,然后在返回所有元素后发送完成通知。

    Flux<String> flux = Flux.just("A", "B", "C");
    Flux<String> flux = Flux.fromArray(new String[]{"A", "B", "C"});
    Flux<String> flux = Flux.fromIterable(Arrays.asList("A", "B", "C"));
    
    //To subscribe call method
    
    flux.subscribe();
    
    

在 Spring WebFlux 中,我们称为响应式 API /函数,它们返回 mono 和 flux,而您的控制器将返回 mono 和 flux。 当您调用返回单声道或流量的 API 时,它将立即返回。 当它们可用时,函数调用的结果将通过单声道或磁通传递给您。

要构建真正的非阻塞应用程序,我们必须致力于将其所有组件创建/使用为非阻塞程序,即客户端,控制器,中间服务甚至数据库。 如果其中之一阻止了请求,我们的目标将会失败。

4. Spring Boot WebFlux 示例

在此 Spring Boot 2 应用程序中,我正在创建员工管理系统。 我选择它是因为在学习时,您可以将其与传统的 MVC 风格的应用程序进行比较。 为了使其完全无阻塞,我使用 mongodb 作为后端数据库。

4.1. Maven 依赖

包括spring-boot-starter-webfluxspring-boot-starter-data-mongodb-reactivespring-boot-starter-testreactor-test依赖项。

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>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.1.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<groupId>com.howtodoinjava</groupId>
	<artifactId>spring-webflux-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>spring-webflux-demo</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>javax.xml.bind</groupId>
			<artifactId>jaxb-api</artifactId>
			<version>2.3.0</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>

</project>

4.2. 配置

Webflux 配置

WebFluxConfig.java

import org.springframework.context.annotation.Configuration;

@Configuration
@EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer 
{	
}

MongoDb 配置

MongoConfig.java

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;

import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;

@Configuration
@EnableReactiveMongoRepositories(basePackages = "com.howtodoinjava.demo.dao")
public class MongoConfig extends AbstractReactiveMongoConfiguration 
{	
	@Value("${port}")
	private String port;

	@Value("${dbname}")
	private String dbName;

	@Override
	public MongoClient reactiveMongoClient() {
		return MongoClients.create();
	}

	@Override
	protected String getDatabaseName() {
		return dbName;
	}

	@Bean
	public ReactiveMongoTemplate reactiveMongoTemplate() {
		return new ReactiveMongoTemplate(reactiveMongoClient(), getDatabaseName());
	}
}

应用程序配置

AppConfig.java

import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

@Configuration
public class AppConfig 
{
	@Bean
	public static PropertyPlaceholderConfigurer getPropertyPlaceholderConfigurer() 
	{
		PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
		ppc.setLocation(new ClassPathResource("application.properties"));
		ppc.setIgnoreUnresolvablePlaceholders(true);
		return ppc;
	}
}

属性文件

application.properties

port=27017
dbname=testdb

日志配置

logback.xml

<configuration>

	<appender name="STDOUT"
		class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n
			</pattern>
		</encoder>
	</appender>

	<logger name="org.springframework" level="DEBUG"
		additivity="false">
		<appender-ref ref="STDOUT" />
	</logger>

	<root level="ERROR">
		<appender-ref ref="STDOUT" />
	</root>

</configuration>

Spring Boot 应用程序

WebfluxFunctionalApp.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebfluxFunctionalApp {

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

4.3. REST 控制器

EmployeeController.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.howtodoinjava.demo.model.Employee;
import com.howtodoinjava.demo.service.EmployeeService;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
public class EmployeeController {
	@Autowired
	private EmployeeService employeeService;

	@RequestMapping(value = { "/create", "/" }, method = RequestMethod.POST)
	@ResponseStatus(HttpStatus.CREATED)
	@ResponseBody
	public void create(@RequestBody Employee e) {
		employeeService.create(e);
	}

	@RequestMapping(value = "/{id}", method = RequestMethod.GET)
	@ResponseBody
	public ResponseEntity<Mono<Employee>> findById(@PathVariable("id") Integer id) {
		Mono<Employee> e = employeeService.findById(id);
		HttpStatus status = e != null ? HttpStatus.OK : HttpStatus.NOT_FOUND;
		return new ResponseEntity<Mono<Employee>>(e, status);
	}

	@RequestMapping(value = "/name/{name}", method = RequestMethod.GET)
	@ResponseBody
	public Flux<Employee> findByName(@PathVariable("name") String name) {
		return employeeService.findByName(name);
	}

	@RequestMapping(method = RequestMethod.GET, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
	@ResponseBody
	public Flux<Employee> findAll() {
		Flux<Employee> emps = employeeService.findAll();
		return emps;
	}

	@RequestMapping(value = "/update", method = RequestMethod.PUT)
	@ResponseStatus(HttpStatus.OK)
	public Mono<Employee> update(@RequestBody Employee e) {
		return employeeService.update(e);
	}

	@RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE)
	@ResponseStatus(HttpStatus.OK)
	public void delete(@PathVariable("id") Integer id) {
		employeeService.delete(id).subscribe();
	}

}

4.4. 服务类

IEmployeeService.java

import com.howtodoinjava.demo.model.Employee;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface IEmployeeService 
{
	void create(Employee e);

	Mono<Employee> findById(Integer id);

	Flux<Employee> findByName(String name);

	Flux<Employee> findAll();

	Mono<Employee> update(Employee e);

	Mono<Void> delete(Integer id);
}

EmployeeService.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.howtodoinjava.demo.dao.EmployeeRepository;
import com.howtodoinjava.demo.model.Employee;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Service
public class EmployeeService implements IEmployeeService {

	@Autowired
	EmployeeRepository employeeRepo;

	public void create(Employee e) {
		employeeRepo.save(e).subscribe();
	}

	public Mono<Employee> findById(Integer id) {
		return employeeRepo.findById(id);
	}

	public Flux<Employee> findByName(String name) {
		return employeeRepo.findByName(name);
	}

	public Flux<Employee> findAll() {
		return employeeRepo.findAll();
	}

	public Mono<Employee> update(Employee e) {
		return employeeRepo.save(e);
	}

	public Mono<Void> delete(Integer id) {
		return employeeRepo.deleteById(id);
	}

}

4.5. DAO 存储库

EmployeeRepository.java

import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;

import com.howtodoinjava.demo.model.Employee;

import reactor.core.publisher.Flux;

public interface EmployeeRepository extends ReactiveMongoRepository<Employee, Integer> {
	@Query("{ 'name': ?0 }")
	Flux<Employee> findByName(final String name);
}

4.6. 模型

Employee.java

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Scope(scopeName = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Document
public class Employee {

	@Id
	int id;
	String name;
	long salary;

	//Getters and setters

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
	}
}

5. 演示

启动应用程序并检查请求和响应。

  • HTTP POST http://localhost:8080/create

API 请求 1

{
	"id":1,
	"name":"user_1",
	"salary":101
}

API 请求 2

{
	"id":2,
	"name":"user_2",
	"salary":102
}

  • HTTP PUT http://localhost:8080/update

API 请求

{
	"id":2,
	"name":"user_2",
	"salary":103
}

  • HTTP GET http://localhost:8080/

API 响应

data:{"id":1,"name":"user_1","salary":101}

data:{"id":2,"name":"user_2","salary":102}

Spring WebFlux Demo

Spring WebFlux 演示

请注意,我正在使用 Postman chrome 浏览器扩展(是阻止客户端)测试 API。 仅当它已经收集了两个员工的响应时,才会显示结果。

要验证非阻塞响应功能,请直接在 chrome 浏览器中点击 URL。 结果将以事件的形式(文本/事件流)一一出现。 为了更好地查看结果,请考虑向控制器 API 添加延迟。

Spring WebFlux Demo - Event Stream

Spring WebFlux 示例 – 事件流

6. Spring WebFlux 教程 – 总结

Spring MVC 和 Spring WebFlux 都支持客户端-服务器体系结构,但是并发模型和用于阻止自然和线程的默认行为存在关键差异。 在 Spring MVC 中,假定应用程序可以阻止当前线程,而在 webflux 中,默认情况下线程是非阻止的。 这是 spring webflux 与 mvc 之间的主要区别。

反应和非阻塞通常不会使应用程序运行得更快。 响应式和非阻塞性的预期好处是能够以较少的固定数量的线程和较少的内存需求来扩展应用程序。 它使应用程序在负载下更具弹性,因为它们以更可预测的方式扩展。

请把关于这个 spring boot webflux 教程的问题交给我。

学习愉快!

下载源码

Spring Boot WebFlux WebSocket 示例

原文: https://howtodoinjava.com/spring-webflux/reactive-websockets/

在这个 spring webflux websocket 示例中,学习使用 spring webflux 创建支持客户端和服务器之间的 websocket 连接的响应式应用程序。

websocket 是 Web 浏览器和服务器之间的双向全双工持久连接。 建立连接后,它将保持打开状态,直到客户端或服务器决定关闭此连接。 Websocket 在具有多个用户相互连接并发送和接收消息的应用程序中具有实际用途,例如聊天应用程序。

1. Maven 依赖

我们需要具有spring-boot-starter-webfluxjavax.websocket-api依赖项。

Spring WebFlux 期望使用 WebSockets 版本 1.1 。 使用 1.0 时,代码将无法运行。

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.demo</groupId>
	<artifactId>spring-webflux-example</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.2.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<name>spring-webflux-example</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>

		<dependency>
			<groupId>javax.websocket</groupId>
			<artifactId>javax.websocket-api</artifactId>
			<version>1.1</version>
		</dependency>

	</dependencies>
</project>

2. WebSocketHandler – 消息处理器

在应用程序的中心,我们将有一个WebSocketHandler,它将处理 WebSocket 消息和生命周期事件。 给定的EchoHandler将收到一条消息,并以RECEIVED ON SERVER ::为前缀返回。

EchoHandler.java

package com.howtodoinjava.demo.handler;

import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Mono;

public class EchoHandler implements WebSocketHandler 
{
	@Override
	public Mono<Void> handle(WebSocketSession session) 
	{
		return session
				.send( session.receive()
						.map(msg -> "RECEIVED ON SERVER :: " + msg.getPayloadAsText())
						.map(session::textMessage) 
					);
	}
}

3. 配置 WebSocketHandler

首先,需要使用SimpleUrlHandlerMappingWebSocketHandler映射到 URL。 然后我们需要一个WebSocketHandlerAdapter来调用WebSocketHandler

最后,为了让WebSocketHandlerAdapter了解传入的响应式运行时请求,我们需要使用ReactorNettyRequestUpgradeStrategy配置WebSocketService(因为我们正在使用默认的 Netty 服务器)。

EchoApplication.java

package com.howtodoinjava.demo;

import java.util.HashMap;
import java.util.Map;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.server.WebSocketService;
import org.springframework.web.reactive.socket.server.support.HandshakeWebSocketService;
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy;

import com.howtodoinjava.demo.handler.EchoHandler;

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

	@Bean
	public EchoHandler echoHandler() {
		return new EchoHandler();
	}

	@Bean
	public HandlerMapping handlerMapping() {
		Map<String, WebSocketHandler> map = new HashMap<>();
		map.put("/echo", echoHandler());

		SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
		mapping.setUrlMap(map);
		mapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
		return mapping;
	}

	@Bean
	public WebSocketHandlerAdapter handlerAdapter() {
		return new WebSocketHandlerAdapter(webSocketService());
	}

	@Bean
	public WebSocketService webSocketService() {
		return new HandshakeWebSocketService(new ReactorNettyRequestUpgradeStrategy());
	}
}

4. Websocket 客户端

首先创建一个响应式网络客户端。 为了在浏览器中进行测试,我们有以下两个文件app.jsindex.html。 JS 文件具有用于连接/断开连接,发送消息并显示从服务器接收的消息的代码。

app.js

var ws = null;
var url = "ws://localhost:8080/echo";

function setConnected(connected) 
{
	document.getElementById('connect').disabled = connected;
	document.getElementById('disconnect').disabled = !connected;
	document.getElementById('echo').disabled = !connected;
}

function connect() 
{
	ws = new WebSocket(url);
	ws.onopen = function() {
		setConnected(true);
		log('Info: Connection Established.');
	};

	ws.onmessage = function(event) {
		log(event.data);
	};

	ws.onclose = function(event) {
		setConnected(false);
		log('Info: Closing Connection.');
	};
}

function disconnect() 
{
	if (ws != null) {
		ws.close();
		ws = null;
	}
	setConnected(false);
}

function echo() 
{
	if (ws != null) 
	{
		var message = document.getElementById('message').value;
		log('Sent to server :: ' + message);
		ws.send(message);
	} else {
		alert('connection not established, please connect.');
	}
}

function log(message) 
{
	var console = document.getElementById('logging');
	var p = document.createElement('p');
	p.appendChild(document.createTextNode(message));
	console.appendChild(p);
}

index.html

<!DOCTYPE html>
<html>
<head>
<link type="text/css" rel="stylesheet"
	href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.css" />
<script type="text/javascript" src="app.js"></script>
</head>
<body>
	<div>
		<div id="connect-container" class="ui centered grid">
			<div class="row">
				<button id="connect" onclick="connect();" class="ui green button ">Connect</button>
				<button id="disconnect" disabled="disabled" onclick="disconnect();"
					class="ui red button">Disconnect</button>
			</div>
			<div class="row">
				<textarea id="message" style="width: 350px" class="ui input"
					placeholder="Message to Echo"></textarea>
			</div>
			<div class="row">
				<button id="echo" onclick="echo();" disabled="disabled"
					class="ui button">Echo message</button>
			</div>
		</div>
		<div id="console-container">
			<h3>Logging</h3>
			<div id="logging"></div>
		</div>
	</div>
</body>
</html>

5. 测试 Spring webflux websocket 示例

在浏览器中输入 URL:http://localhost:8080/index.html

测试 websocket 的连接,断开功能,然后尝试发送一些消息。

Spring webflux + websocket example

Spring webflux + websocket 示例

请问您有关使用 spring webflux 与服务器建立响应式 Websocket 连接的问题。

学习愉快!

下载源码

使用@WebFluxTestWebTestClient进行 Spring Boot Webflux 测试

原文: https://howtodoinjava.com/spring-webflux/webfluxtest-with-webtestclient/

学习使用@WebFluxTest注解和WebTestClient来对 spring boot webflux 控制器进行单元测试,该测试用于通过 Junit 5 测试 webflux 端点。

1. 使用WebTestClient@WebFluxTest

1.1. Maven 依赖

添加reactor-test依赖。

pom.xml

<dependency>
	<groupId>io.projectreactor</groupId>
	<artifactId>reactor-test</artifactId>
	<scope>test</scope>
</dependency>

1.2. @WebFluxTest注解

它需要完全自动配置,而仅应用与 WebFlux 测试相关的配置(例如@Controller@ControllerAdvice@JsonComponentConverterWebFluxConfigurer bean,但没有@Component@Service@Repository bean)。

默认情况下,用@WebFluxTest注解的测试还将自动配置WebTestClient

通常,@WebFluxTest@MockBean@Import结合使用以创建@Controller bean 所需的任何协作者。

要编写需要完整应用程序上下文的集成测试,请考虑将@SpringBootTest@AutoConfigureWebTestClient结合使用。

1.3. WebTestClient

它是用于测试 Web 服务器的非阻塞式响应客户端,该客户端内部使用响应WebClient来执行请求,并提供流利的 API 来验证响应。

它可以通过 HTTP 连接到任何服务器,或使用模拟请求和响应对象直接绑定到 WebFlux 应用程序,而无需 HTTP 服务器。

WebTestClientMockMvc相似。 这些测试 Web 客户端之间的唯一区别是WebTestClient旨在测试 WebFlux 端点。

2. 测试 webflux 控制器

2.1. 用于 Webflux 控制器的 Junit 5 测试

在给定的示例中,我们正在测试EmployeeController类,该类包含用于 CRUD 操作的指令方法。

EmployeeControllerTest.java

import static org.mockito.Mockito.times;

import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.BodyInserters;
import com.howtodoinjava.demo.controller.EmployeeController;
import com.howtodoinjava.demo.dao.EmployeeRepository;
import com.howtodoinjava.demo.model.Employee;
import com.howtodoinjava.demo.service.EmployeeService;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@ExtendWith(SpringExtension.class)
@WebFluxTest(controllers = EmployeeController.class)
@Import(EmployeeService.class)
public class EmployeeControllerTest 
{
	@MockBean
	EmployeeRepository repository;

	@Autowired
	private WebTestClient webClient;

	@Test
	void testCreateEmployee() {
		Employee employee = new Employee();
		employee.setId(1);
		employee.setName("Test");
		employee.setSalary(1000);

		Mockito.when(repository.save(employee)).thenReturn(Mono.just(employee));

		webClient.post()
			.uri("/create")
			.contentType(MediaType.APPLICATION_JSON)
			.body(BodyInserters.fromObject(employee))
			.exchange()
			.expectStatus().isCreated();

		Mockito.verify(repository, times(1)).save(employee);
	}

	@Test
    void testGetEmployeesByName() 
	{
		Employee employee = new Employee();
		employee.setId(1);
		employee.setName("Test");
		employee.setSalary(1000);

		List<Employee> list = new ArrayList<Employee>();
		list.add(employee);

		Flux<Employee> employeeFlux = Flux.fromIterable(list);

        Mockito
            .when(repository.findByName("Test"))
            .thenReturn(employeeFlux);

        webClient.get().uri("/name/{name}", "Test")
        	.header(HttpHeaders.ACCEPT, "application/json")
	        .exchange()
	        .expectStatus().isOk()
	        .expectBodyList(Employee.class);

        Mockito.verify(repository, times(1)).findByName("Test");
    }

	@Test
    void testGetEmployeeById() 
	{
		Employee employee = new Employee();
		employee.setId(100);
		employee.setName("Test");
		employee.setSalary(1000);

        Mockito
            .when(repository.findById(100))
            .thenReturn(Mono.just(employee));

        webClient.get().uri("/{id}", 100)
	        .exchange()
	        .expectStatus().isOk()
	        .expectBody()
	        .jsonPath("$.name").isNotEmpty()
	        .jsonPath("$.id").isEqualTo(100)
	        .jsonPath("$.name").isEqualTo("Test")
	        .jsonPath("$.salary").isEqualTo(1000);

        Mockito.verify(repository, times(1)).findById(100);
    }

	@Test
    void testDeleteEmployee() 
	{
		Mono<Void> voidReturn  = Mono.empty();
        Mockito
            .when(repository.deleteById(1))
            .thenReturn(voidReturn);

        webClient.get().uri("/delete/{id}", 1)
	        .exchange()
	        .expectStatus().isOk();
    }
}

  • 我们正在使用@ExtendWith( SpringExtension.class )支持 Junit 5 中的测试。在 Junit 4 中,我们需要使用@RunWith(SpringRunner.class)
  • 我们使用@Import(EmployeeService.class)为应用程序上下文提供服务依赖,而使用@WebFluxTest时不会自动扫描该上下文。
  • 我们模拟了EmployeeRepository类型为ReactiveMongoRepository的模型。 这将阻止实际的数据库插入和更新。
  • WebTestClient用于命中控制器的特定端点并验证其是否返回正确的状态代码和主体。

2.2. 测试 Spring Boot Webflux 控制器

作为参考,让我们看看上面已经测试过的控制器。

EmployeeController.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.howtodoinjava.demo.model.Employee;
import com.howtodoinjava.demo.service.EmployeeService;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
public class EmployeeController 
{
    @Autowired
    private EmployeeService employeeService;

    @PostMapping(value = { "/create", "/" })
    @ResponseStatus(HttpStatus.CREATED)
    public void create(@RequestBody Employee e) {
        employeeService.create(e);
    }

    @GetMapping(value = "/{id}")
    @ResponseStatus(HttpStatus.OK)
    public ResponseEntity<Mono<Employee>> findById(@PathVariable("id") Integer id) {
        Mono<Employee> e = employeeService.findById(id);
        HttpStatus status = (e != null) ? HttpStatus.OK : HttpStatus.NOT_FOUND;
        return new ResponseEntity<>(e, status);
    }

    @GetMapping(value = "/name/{name}")
    @ResponseStatus(HttpStatus.OK)
    public Flux<Employee> findByName(@PathVariable("name") String name) {
        return employeeService.findByName(name);
    }

    @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @ResponseStatus(HttpStatus.OK)
    public Flux<Employee> findAll() {
    	return employeeService.findAll();
    }

    @PutMapping(value = "/update")
    @ResponseStatus(HttpStatus.OK)
    public Mono<Employee> update(@RequestBody Employee e) {
        return employeeService.update(e);
    }

    @DeleteMapping(value = "/delete/{id}")
    @ResponseStatus(HttpStatus.OK)
    public void delete(@PathVariable("id") Integer id) {
        employeeService.delete(id).subscribe();
    }
}

请使用@WebFluxTestWebTestClient将有关 spring webflux 控制器单元测试的问题交给我。

学习愉快!

下载源码

Spring – Bean 生命周期

原文: https://howtodoinjava.com/spring-core/spring-bean-life-cycle/

在本文中,了解SpringBean 生命周期。 我们将学习生命周期阶段,初始化和销毁​​回调方法。 我们将学习使用 XML 配置以及注解配置来控制 bean 生命周期事件。

1. Bean 的生命周期

当容器启动时 – 需要基于 Java 或 XML bean 定义实例化 Spring bean。 可能还需要执行一些初始化后的步骤,以使其进入可用状态。 相同的 bean 生命周期也适用于 SpringBoot 应用程序。

之后,当不再需要该 bean 时,它将被从 IoC 容器中删除。

Spring bean 工厂负责管理通过 spring 容器创建的 bean 的生命周期。

1.1. 生命周期回调

Spring bean 工厂控制 bean 的创建和销毁。 为了执行一些自定义代码,它提供了回调方法,这些方法可以大致分为两类:

  • 初始化后回调方法
  • 销毁回调方法

1.1. 生命周期图示

Spring Bean Life Cycle

Spring Bean Life Cycle

2. 生命周期回调方法

Spring 框架提供了以下 4 种方式来控制 Bean 的生命周期事件

  1. InitializingBeanDisposableBean回调接口
  2. *了解特定行为的界面
  3. bean 配置文件中的自定义init()destroy()方法
  4. @PostConstruct@PreDestroy注解

2.1. InitializingBeanDisposableBean

org.springframework.beans.factory.InitializingBean 接口允许 Bean 在容器上设置了所有必需的属性后执行初始化工作。

InitializingBean接口指定一种方法:

InitializingBean.java

void afterPropertiesSet() throws Exception;

这不是初始化 bean 的首选方法,因为它将 bean 类与 spring 容器紧密耦合。 更好的方法是在applicationContext.xml文件中的 bean 定义中使用“初始化方法”属性。

类似地,实现org.springframework.beans.factory.DisposableBean接口允许 Bean 在包含它的容器被销毁时获得回调。

DisposableBean接口指定一种方法:

DisposableBean.java

void destroy() throws Exception;

A sample bean implementing above interfaces would look like this:

package com.howtodoinjava.task;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class DemoBean implements InitializingBean, DisposableBean 
{
	//Other bean attributes and methods 

	@Override
	public void afterPropertiesSet() throws Exception
	{
		//Bean initialization code
	}

	@Override
	public void destroy() throws Exception 
	{
		//Bean destruction code
	}
}

2.2. 了解特定行为的接口

Spring 提供了一系列*Aware接口,这些接口允许 bean 向容器指示它们需要一定的基础结构依赖。 每个接口都将要求您实现一种将依赖项注入 bean 的方法。

这些接口可以概括为:

感知接口 覆盖方法 目的
ApplicationContextAware void setApplicationContext(ApplicationContext applicationContext) throws BeansException; 希望由其运行所要通知的ApplicationContext的任何对象实现的接口。
ApplicationEventPublisherAware void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher); 设置运行该对象的ApplicationEventPublisher
BeanClassLoaderAware void setBeanClassLoader(ClassLoader classLoader); 将 bean 类加载器提供给 bean 实例的回调。
BeanFactoryAware void setBeanFactory(BeanFactory beanFactory) throws BeansException; 将拥有的工厂提供给 Bean 实例的回调。
BeanNameAware void setBeanName(String name); 在创建此 bean 的 bean 工厂中设置 bean 的名称。
BootstrapContextAware void setBootstrapContext(BootstrapContext bootstrapContext); 设置该对象在其中运行的 BootstrapContext。
LoadTimeWeaverAware void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver); 设置此对象包含 ApplicationContext 的 LoadTimeWeaver。
MessageSourceAware void setMessageSource(MessageSource messageSource); 设置此对象在其中运行的 MessageSource。
NotificationPublisherAware void setNotificationPublisher(NotificationPublisher publisher); 为当前的托管资源实例设置NotificationPublisher实例。
PortletConfigAware void setPortletConfig(PortletConfig portletConfig); 设置运行该对象的 PortletConfig。
PortletContextAware void setPortletContext(PortletContext portletContext); 设置此对象在其中运行的 PortletContext。
ResourceLoaderAware void setResourceLoader(ResourceLoader resourceLoader); 设置运行该对象的 ResourceLoader。
ServletConfigAware void setServletConfig(ServletConfig servletConfig); 设置运行该对象的 ServletConfig。
ServletContextAware void setServletContext(ServletContext servletContext); 设置运行该对象的 ServletContext。

Java 程序展示了使用感知接口来控制字符串 bean 生命周期的用法。

DemoBean.java

package com.howtodoinjava.task;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.weaving.LoadTimeWeaverAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.jmx.export.notification.NotificationPublisher;
import org.springframework.jmx.export.notification.NotificationPublisherAware;

public class DemoBean implements ApplicationContextAware,
		ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware,
		BeanNameAware, LoadTimeWeaverAware, MessageSourceAware,
		NotificationPublisherAware, ResourceLoaderAware
{
	@Override
	public void setResourceLoader(ResourceLoader arg0) {
		// TODO Auto-generated method stub
	}

	@Override
	public void setNotificationPublisher(NotificationPublisher arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void setMessageSource(MessageSource arg0) {
		// TODO Auto-generated method stub
	}

	@Override
	public void setLoadTimeWeaver(LoadTimeWeaver arg0) {
		// TODO Auto-generated method stub
	}

	@Override
	public void setBeanName(String arg0) {
		// TODO Auto-generated method stub
	}

	@Override
	public void setBeanFactory(BeanFactory arg0) throws BeansException {
		// TODO Auto-generated method stub
	}

	@Override
	public void setBeanClassLoader(ClassLoader arg0) {
		// TODO Auto-generated method stub
	}

	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher arg0) {
		// TODO Auto-generated method stub
	}

	@Override
	public void setApplicationContext(ApplicationContext arg0)
			throws BeansException {
		// TODO Auto-generated method stub
	}
}

2.3. 自定义init()destroy()方法

可以通过两种方式定义 bean 配置文件中的默认initdestroy方法:

  • Bean 本地定义适用于单个 Bean
  • 全局定义适用于 bean 上下文中定义的所有 bean
2.3.1. Bean 本地定义

本地定义如下。

beans.xml

<beans>

	<bean id="demoBean" class="com.howtodoinjava.task.DemoBean" 
					init-method="customInit" 
					destroy-method="customDestroy"></bean>

</beans>

2.3.2. 全局定义

全局定义如下。 这些方法将为<beans>标签下给出的所有 bean 定义调用。 当您具有为所有 bean 一致定义通用方法名称(例如init()destroy())的模式时,它们很有用。 此功能可帮助您不必为所有 bean 单独提及initdestroy方法名称。

<beans default-init-method="customInit" default-destroy-method="customDestroy">   

    	<bean id="demoBean" class="com.howtodoinjava.task.DemoBean"></bean>

</beans>

显示在 bean XML 配置文件中配置的方法的 Java 程序。

DemoBean.java

package com.howtodoinjava.task;

public class DemoBean 
{
	public void customInit() 
	{
		System.out.println("Method customInit() invoked...");
	}

	public void customDestroy() 
	{
		System.out.println("Method customDestroy() invoked...");
	}
}

2.4. @PostConstruct@PreDestroy

从 Spring 2.5 开始,您还可以使用注解来指定使用@PostConstruct@PreDestroy注解的生命周期方法。

  • @PostConstruct带注解的方法将在使用默认构造函数构造 Bean 之后,并且在实例返回给请求对象之前被调用。
  • 在即将在 bean 容器内销毁 bean 之前,将调用@PreDestroy带注解的方法。

Java 程序,用于显示注解配置的使用以控制注解的使用。

package com.howtodoinjava.task;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class DemoBean 
{
	@PostConstruct
	public void customInit() 
	{
		System.out.println("Method customInit() invoked...");
	}

	@PreDestroy
	public void customDestroy() 
	{
		System.out.println("Method customDestroy() invoked...");
	}
}

因此,这一切都与 Spring 容器内的 spring bean 生命周期有关。 记住给定的生命周期事件类型,这是一个常见的问题Spring 面试问题。

学习愉快!

Spring 5

Spring BeanPostProcessor示例

原文: https://howtodoinjava.com/spring-core/spring-bean-post-processors/

bean 后处理器允许自定义修改 spring bean 工厂创建的新 bean 实例。 如果您想在 Spring 容器完成实例化,配置和初始化 bean 之后实现一些自定义逻辑,我们可以插入一个或多个BeanPostProcessor实现。

对于多个BeanPostProcessor实例,我们可以通过设置order属性或实现Ordered接口来控制顺序。

1. Spring BeanPostProcessor

BeanPostProcessor接口恰好由两个回调方法组成,即postProcessBeforeInitialization()postProcessAfterInitialization()

对于由容器创建的每个 bean 实例,后处理器都会同时从容器获取回调 – 在调用容器初始化方法之前以及任何 bean 初始化回调之后 。

Bean 后处理器通常检查回调接口,或者可以使用代理包装 Bean。 一些 Spring AOP 基础结构类(例如AbstractAdvisingBeanPostProcessor)被实现为 bean 后处理器,以提供代理包装逻辑。

1.1. 如何创建BeanPostProcessor

要在 Spring 创建一个 bean 后处理器:

  1. 实现BeanPostProcessor接口。
  2. 实现回调方法。

CustomBeanPostProcessor.java

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class CustomBeanPostProcessor implements BeanPostProcessor 
{
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException 
	{
		System.out.println("Called postProcessBeforeInitialization() for :" + beanName);
		return bean;
	}

	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException 
	{
		System.out.println("Called postProcessAfterInitialization() for :" + beanName);
		return bean;
	}
}

1.2. 如何注册BeanPostProcessor

ApplicationContext自动检测在实现BeanPostProcessor接口的配置元数据中定义的所有 bean。 它将这些 bean 注册为后处理器,以便以后在 bean 创建时可以调用它们。

然后,Spring 将在调用初始化回调方法之前和之后将每个 bean 实例传递给这两个方法,在这里您可以按自己喜欢的方式处理 bean 实例。

<beans>
     <bean id="customBeanPostProcessor" 
               class="com.howtodoinjava.demo.processors.CustomBeanPostProcessor" />
</beans>

2. 调用BeanPostProcessor方法时

通常,spring 的 DI 容器会执行以下操作来创建一个 bean,您需要:

  1. 通过构造函数或工厂方法创建 Bean 实例
  2. 设置值和对 bean 属性的 bean 引用
  3. 调用所有感知接口中定义的 setter 方法
  4. 将 bean 实例传递给每个 bean 后处理器的postProcessBeforeInitialization()方法
  5. 调用初始化回调方法
  6. 将 bean 实例传递给每个 bean 后处理器的postProcessAfterInitialization()方法
  7. Bean 已准备好使用
  8. 关闭容器后,调用销毁回调方法

3. Spring BeanPostProcessor示例

为了显示示例用法,我正在使用EmployeeDAOImpl类,如下所示:

EmployeeDAOImpl.java

public class EmployeeDAOImpl implements EmployeeDAO
{
	public EmployeeDTO createNewEmployee()
	{
		EmployeeDTO e = new EmployeeDTO();
		e.setId(1);
		e.setFirstName("Lokesh");
		e.setLastName("Gupta");
		return e;
	}

	public void initBean() {
		System.out.println("Init Bean for : EmployeeDAOImpl");
	}

	public void destroyBean() {
		System.out.println("Init Bean for : EmployeeDAOImpl");
	}
}

该 bean 及其后处理器的配置如下:

beans.xml

<bean id="customBeanPostProcessor" class="com.howtodoinjava.demo.processors.CustomBeanPostProcessor" />

<bean id="dao" class="com.howtodoinjava.demo.dao.EmployeeDAOImpl"  init-method="initBean" destroy-method="destroyBean"/>

现在,启动 DI 容器并查看输出:

Demo.java

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

Console

Called postProcessBeforeInitialization() for : dao
Init Bean for : EmployeeDAOImpl
Called postProcessAfterInitialization() for : dao

显然,在初始化方法之前和之后都调用了BeanPostProcessor方法。

学习愉快!

SpringBean 自动装配 – @Autowired

https://howtodoinjava.com/spring-core/spring-beans-autowiring-concepts/

在 Spring 框架中,遵循配置文件中的 bean 依赖关系是一种很好的做法,因此 Spring 容器能够自动关联 Bean 之间的关系。 这意味着可以通过检查BeanFactory的内容来自动让 Spring 为您的 bean 解决协作者(其他 bean)。 这称为 SpringBean 自动装配。

自动装配功能具有四种模式。 这些是nobyNamebyTypeconstructor

另一种自动装配模式autodetect已废弃。文档说,autodetect选项提供了太多的“魔力”,因此首选更明确的声明。

  • XML 配置中的默认自动装配模式为no
  • Java 配置中的默认自动装配模式为byType

Spring bean autowiring modes

Spring bean 自动装配模式

1. 自动装配模式

如上图所示,有五种自动装配模式。 让我们一一讨论。

  1. 没有

    该选项是 spring 框架的默认选项,这意味着自动装配为OFF。 您必须使用 bean 定义中的<property>标签显式设置依赖项。

  2. 按名字

    此选项启用基于 bean 名称的依赖项注入。 在 Bean 中自动装配属性时,属性名称用于在配置文件中搜索匹配的 Bean 定义。 如果找到这样的 bean,则将其注入属性。 如果找不到这样的 bean,则会引发错误。

    阅读更多: 按名字自动装配示例

  3. 按类型

    此选项启用基于 bean 类型的依赖项注入。 在 bean 中自动装配属性时,属性的类类型用于在配置文件中搜索匹配的 bean 定义。 如果找到这样的 bean,则将其注入属性。 如果找不到这样的 bean,则会引发错误。

    阅读更多:按类型自动装配示例

  4. 构造器

    constructor的自动装配与byType相似,但适用于构造函数参数。 在启用自动装配的 bean 中,它将查找构造函数参数的类类型,然后对所有构造函数参数执行自动装配bytype。 请注意,如果容器中没有一个完全属于构造函数参数类型的 bean,则会引发致命错误。

    阅读更多:通过构造器示例自动装配

2. @Autowired注解

除了 bean 配置文件中提供的自动装配模式外,还可以使用@Autowired注解在 bean 类中指定自动装配。 要在 bean 类中使用@Autowired注解,必须首先使用以下配置在 spring 应用程序中启用注解。

2.1. 启用注解配置

applicationContext.xml

<context:annotation-config />

使用配置文件中的AutowiredAnnotationBeanPostProcessor bean 定义可以实现相同的目的。

<bean class ="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>

2.2. 使用@Autowired注解

现在,启用注解配置后,您可以使用@Autowired随意地自动绑定 bean 依赖项。 这可以通过三种方式完成:

2.2.1. @Autowired属性

在属性上使用@Autowired时,等效于在配置文件中通过byType进行自动装配。

EmployeeBean.java

public class EmployeeBean
{
    @Autowired
    private DepartmentBean departmentBean;

    public DepartmentBean getDepartmentBean() {
        return departmentBean;
    }
    public void setDepartmentBean(DepartmentBean departmentBean) {
        this.departmentBean = departmentBean;
    }
	//More code
}

2.2.2. 属性设置器上的@Autowired

当在设置器上使用@Autowired时,它也等效于在配置文件中通过byType进行自动装配。

EmployeeBean.java

public class EmployeeBean
{
    private DepartmentBean departmentBean;

    public DepartmentBean getDepartmentBean() {
        return departmentBean;
    }

	@Autowired
    public void setDepartmentBean(DepartmentBean departmentBean) {
        this.departmentBean = departmentBean;
    }
	//More code
}

2.2.3. 构造函数上的@Autowired

在 bean 的构造函数上使用@Autowired时,它也等效于在配置文件中通过constructor自动装配。

EmployeeBean.java

package com.howtodoinjava.autowire.constructor;

public class EmployeeBean
{
    @Autowired
    public EmployeeBean(DepartmentBean departmentBean)
    {
        this.departmentBean = departmentBean;
    }

    private DepartmentBean departmentBean;

    public DepartmentBean getDepartmentBean() {
        return departmentBean;
    }
    public void setDepartmentBean(DepartmentBean departmentBean) {
        this.departmentBean = departmentBean;
    }
	//More code
}

3. @Qualifier解决冲突

我们了解到,如果我们在byType模式下使用自动装配,则会在依赖项中查找属性类类型。 如果找不到这样的类型,则会引发错误。 但是,如果有两个或更多相同类类型的 bean,该怎么办。

在这种情况下,spring 将无法选择正确的 bean 来注入属性,因此您将需要使用限定符来帮助容器。

要使用限定符解析特定的 bean,我们需要将@Qualifier注解与@Autowired注解一起使用,并将 bean 名称传递到注解参数中。 看看下面的例子:

EmployeeBean.java

public class EmployeeBean
{
    @Autowired
	@Qualifier("finance")
    private DepartmentBean departmentBean;

    public DepartmentBean getDepartmentBean() {
        return departmentBean;
    }
    public void setDepartmentBean(DepartmentBean departmentBean) {
        this.departmentBean = departmentBean;
    }
	//More code
}

其中重复的 Bean 如下:

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <context:annotation-config />

    <bean id="employee" class="com.howtodoinjava.autowire.constructor.EmployeeBean" autowire="constructor">
        <property name="fullName" value="Lokesh Gupta"/>
    </bean>

    <!--First bean of type DepartmentBean-->
    <bean id="humanResource" class="com.howtodoinjava.autowire.constructor.DepartmentBean" >
        <property name="name" value="Human Resource" />
    </bean>

	<!--Second bean of type DepartmentBean-->
	 <bean id="finance" class="com.howtodoinjava.autowire.constructor.DepartmentBean" >
        <property name="name" value="Finance" />
    </bean>
</beans>

4. 使用required=false进行错误安全的自动装配

即使您在自动装配 Bean 依赖项时已格外小心,仍然可能会发现奇怪的查找失败。 因此,要解决此问题,您将需要使自动装配成为可选的,以便在未找到依赖项的情况下,应用程序不应引发任何异常,而自动装配应被忽略。

这可以通过两种方式完成:

  • 如果要使特定 bean 对于特定 bean 属性非强制装配,请在@Autowired注解中使用required="false"属性。

    EmployeeBean.java

    @Autowired (required=false)
    @Qualifier ("finance")
    private DepartmentBean departmentBean;
    
    
  • 如果要在全局级别上应用可选自动装配,即对所有 bean 中的所有属性进行应用; 使用以下配置。

    beans.xml

    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor">
        <property name="requiredParameterValue" value="false" />
    </bean>
    
    

5. 从自动装配中排除 bean

默认情况下,自动装配扫描并匹配作用域中的所有 bean 定义。 如果要排除某些 bean 定义,以使它们无法通过自动装配方式注入,则可以使用设置为falseautowire-candidate来实现。

  1. 如果将autowire-candidate用作false,则将 bean 排除在自动装配候选之外。 它将特定的 bean 定义完全排除在自动装配基础结构之外。

    beans.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans>
        <context:annotation-config />
    
        <bean id="employee" class="com.howtodoinjava.autowire.constructor.EmployeeBean" autowire="constructor">
            <property name="fullName" value="Lokesh Gupta"/>
        </bean>
        <!--Will be available for autowiring-->
        <bean id="humanResource" class="com.howtodoinjava.autowire.constructor.DepartmentBean" >
            <property name="name" value="Human Resource" />
        </bean>
    
        <!--Will not participate in autowiring-->
         <bean id="finance"      class="com.howtodoinjava.autowire.constructor.DepartmentBean" autowire-candidate="false">
            <property name="name" value="Finance" />
        </bean>
    </beans>
    
    
  2. 另一个选择是在 bean 名称上基于模式匹配限制自动装配候选。 顶层的<beans />元素在其default-autowire-candidates属性中接受一个或多个模式。

    例如,要将自动装配候选状态限制为名称以Impl结尾的任何 bean,请提供值*Impl。 要提供多种模式,请在以逗号分隔的列表中定义它们。

    beans.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans default-autowire-candidates="*Impl,*Dao">
        <context:annotation-config />
    
        <bean id="employee" class="com.howtodoinjava.autowire.constructor.EmployeeBean" autowire="constructor">
            <property name="fullName" value="Lokesh Gupta"/>
        </bean>
        <!--Will be available for autowiring-->
        <bean id="humanResource" class="com.howtodoinjava.autowire.constructor.DepartmentBean" >
            <property name="name" value="Human Resource" />
        </bean>
    
        <!--Will not participate in autowiring-->
         <bean id="finance"      class="com.howtodoinjava.autowire.constructor.DepartmentBean" autowire-candidate="false">
            <property name="name" value="Finance" />
        </bean>
    </beans>
    
    

请注意,bean 定义的autowire-candidate属性的显式值truefalse始终优先,而对于此类 bean,模式匹配规则将不适用。

这就是 Spring bean 自动装配的全部内容。 如有疑问,请发表评论。

学习愉快!

下载源码

Spring 注解

原文: https://howtodoinjava.com/spring-core/spring-annotations/

了解最广泛使用的 Spring 注解。 在本教程中,我们将简要介绍 spring 核心提供的重要注解,这些注解用于定义 bean 和创建复杂的应用程序上下文配置。

1. Bean 注解

给定的注解列表用于在 Spring 中定义 bean 并在应用程序上下文中控制其注册首选项。

@Bean

方法级别的注解,用于声明 Spring bean。 当配置执行带注解的方法时,它将返回值注册为BeanFactory中的 Bean。默认情况下,bean 名称将与方法名称相同。 要自定义 Bean 名称,请使用其namevalue属性。

@Bean
EmployeeService employeeService() 
{
    return new EmployeeServiceImpl();
}

@Component

指示带注解的类是“组件”,并且在使用基于注解的配置和类路径扫描时将被自动检测。要使用此注解,请将其应用于类,如下所示:

@Component
public class EmployeeDAOImpl 
		implements EmployeeDAO {
    ...
}

@Repository

@Component注解的特化。 除了将带注解的 DAO 类导入 DI 容器之外,它还使未经检查的异常(从 DAO 方法抛出)有资格转换为 Spring DataAccessException

@Repository
public class EmployeeDAOImpl 
		implements EmployeeDAO
{
    //...
}

@Service

@Component注解的特化。 它表明一个类是“业务服务门面”或类似的名称。

@Service ("employeeManager")
public class EmployeeManagerImpl 
		implements EmployeeManager
{
    @Autowired
    EmployeeDAO dao;

    ...
}

@Controller

专门用于注解控制器(例如 Web 控制器)的@Component。 它与基于RequestMapping注解的处理器方法结合使用。

@Controller ("employeeController")
public class EmployeeController 
{
	...
}

@Qualifier

在自动装配过程中,如果容器中有多个同类型的 bean,那么容器将抛出运行时异常。 为了解决这个问题,我们必须使用此注解,专门告诉 spring 必须注入哪个 bean。在给定的示例中,如果有两个类型为存储库的 bean,则在运行时,将注入名称为fsRepository的 bean。

public class EmployeeService {

    @Autowired
    @Qualifier("fsRepository")
    private Repository repository;
}

@Autowired

将构造函数,字段,setter 方法或配置方法标记为通过依赖项注入自动装配。 我们可以使用required属性标记注解的依赖项是否是必需的(强制填充)。 默认情况下,其值为true

public class EmployeeService {

    @Autowired
    private EmployeeDao dao;
}

@Required

缺省的 bean 自动装配仅检查是否已设置依赖项。 它不检查分配的值是否为null。 使用@Required,我们可以检查设置的值是否为非空值。 现在已不推荐使用。

@Value

适用于字段或方法/构造函数参数级别,并指示受影响参数的默认值表达式。

public class SomeService {

    @Value("${ENV}")
    private String environment;
}

@Lazy

指示是否要延迟初始化 bean。 默认情况下,在 Spring DI 中,将进行立即初始化。当在任何 bean 上应用该 bean 时,只有在另一个 bean 引用或从封装的BeanFactory中显式检索到该 bean 时,才会初始化该 bean。

public class SomeService {

    @Autowired
    @Lazy
    private RemoteService remoting;
}

@DependsOn

在组件扫描期间,它用于指定当前 bean 所依赖的 bean。 保证指定的 bean 由容器在该 bean 之前创建。

public class SomeService {

    @Autowired
    @DependsOn ("pingService")
    private RemoteService remoting;
}

@Lookup

表示一种方法为“查找”方法。 最好用于将原型作用域的 bean 注入到单例 bean 中。

@Component
@Scope("prototype")
public class AppNotification {
    //prototype-scoped bean
}

@Component
public class NotificationService {

    @Lookup
    public AppNotification getNotification() {
        //return new AppNotification();
    }
}

@Primary

指示当多个候选者有资格自动装配单值依赖项时,应优先考虑 Bean。当不使用@Primary时,我们可能需要提供@Qualifier注解以正确注入 bean。在给定的示例中,当自动装配FooRepository时,将注入HibernateFooRepository的实例 – 直到使用@Qualifier注解。

@Component
public class JdbcFooRepository 
	extends FooRepository {
}

@Primary
@Component
public class HibernateFooRepository 
	extends FooRepository {
}

@Scope

指示用于带注解类型的实例的范围的名称。 在 Spring 5 中,bean 可以属于六个范围之一,即单例,原型,请求,会话,应用程序和 websocket。 |

2. 上下文配置注解

这些注解有助于将不同的 Bean 绑定在一起以形成运行时应用程序上下文。

@ComponentScan

@ComponentScan@Configuration一起用于启用和配置组件扫描。 默认情况下,如果不指定路径,它将扫描当前程序包及其所有子程序包中的组件。使用组件扫描,spring 可以自动扫描所有带有原型注解@Component@Controller@Service@Repository并使用BeanFactory对其进行配置。

@Configuration
@ComponentScan(basePackages = {com.howtodoinjava.data.jpa})
public class JpaConfig {

}

@Configuration

指示一个类声明一个或多个@Bean方法,并且与@ComponentScan一起使用时,容器可以对其进行处理以生成 bean 定义。

@Configuration
public class AppConfig {

	@Bean
	public AppUtils appUtils()
	{
		return new AppUnits();
	}
}

@Profile

指示当一个或多个指定的配置文件处于活动状态时,组件可以进行 Bean 注册。 配置文件是 Bean 的命名逻辑分组,例如开发,生产等。

@Bean
@Profile("dev")
public AppUtils appUtils()
{
	return new DevAppUnits();
}

@Bean
@Profile("prod")
public AppUtils appUtils()
{
	return new ProdAppUnits();
}

@Import

指示要导入的一个或多个组件类,通常是@Configuration类。导入的@Configuration类中声明的@Bean定义应使用@Autowired注入进行访问。

@Configuration
@Import({ JpaConfig.class, SchedulerConfig.class })
public class AppConfig {

}

@ImportResource

指示一个或多个包含要导入的 bean 定义的资源。 它用于 XML bean 定义,就像@Import用于使用@Bean的 Java 配置一样。

@Configuration  
@ImportResource( { "spring-context.xml" } )  
public class ConfigClass { 

}

请把关于上述 Spring 注解列表或解释的问题与我联系。

学习愉快!

参考:

上下文注解包
构造型注解包

Spring – 原型注解

原文: https://howtodoinjava.com/spring-core/stereotype-annotations/

在Spring 自动装配中,@Autowired注解仅处理装配部分。 我们仍然必须定义 bean,以便容器知道它们并可以为我们注入它们。

启用@Component@Repository@Service@Controller注解并启用自动组件扫描后,Spring 会自动将 bean 导入容器并注入依赖项。 这些注解也称为原型注解

在开始使用这些注解的示例之前,让我们学习有关这些注解的快速事实,这将有助于我们更好地决定何时使用哪种注解。

1. Spring bean 原型注解

1.1. @Component注解

@Component注解将 Java 类标记为 Bean,因此 spring 的组件扫描机制可以将其拾取并将其拉入应用程序上下文。 要使用此注解,请将其应用于类,如下所示:

@Component
public class EmployeeDAOImpl implements EmployeeDAO {
    ...
}

1.2. @Repository注解

尽管以上使用@Repository足够好,但是我们可以使用更合适的注解,该注解专门为 DAO 提供额外的好处,即@Repository注解。 @Repository注解是@Component注解的特化,具有相似的用途和功能。 除了将 DAO 导入 DI 容器之外,还使未经检查的异常(从 DAO 方法抛出)有资格将转换为 Spring DataAccessException

1.3. @Service注解

@Service注解也是组件注解的一种特殊形式。 它目前不提供@Component注解以外的任何其他行为,但是最好在服务层类中使用@Service而不是@Component,因为可以更好地指定意图。 此外,将来工具支持和其他行为可能会依赖它。

1.4. @Controller注解

@Controller注解将一个类标记为 Spring Web MVC 控制器。 它也是@Component专长,因此标有它的 bean 将自动导入 DI 容器中。 当我们将@Controller注解添加到类中时,我们可以使用另一个注解,即@RequestMapping; 将 URL 映射到类的实例方法。

实际使用中,我们将遇到非常罕见的情况,需要使用@Component注释。 在大多数情况下,我们将使用@Repository@Service@Controller注解。 如果该类不属于控制器,服务和 DAO 这三个类别中的任何一个,则应使用@Component

如果我们想定义将要在 DI 容器中注册的 bean 的名称,则可以在注解属性本身中传递该名称,例如@Service(employeeManager")

2. 启用组件扫描

以上四个注解仅在由 Spring 框架的 DI 容器扫描时才进行扫描和配置。 要启用此扫描,我们将需要在applicationContext.xml文件中使用context:component-scan标签。

applicationContext.xml

<context:component-scan base-package="com.howtodoinjava.demo.service" />
<context:component-scan base-package="com.howtodoinjava.demo.dao" />
<context:component-scan base-package="com.howtodoinjava.demo.controller" />

context:component-scan元素需要base-package属性,顾名思义,该属性指定了递归组件搜索的起点。 我们可能不希望将顶层软件包交给 spring,所以您应该声明三个component-scan元素,每个元素都具有指向另一个软件包的base-package属性。

声明组件扫描后,您不再需要声明context:annotation-config,因为在启用组件扫描时隐式启用了自动装配。

3. 使用@Component@Repository@Service@Controller注解

正如我已经说过的,您在 DAO,管理器和控制器类上使用@Repository@Service@Controller注解。 但是在现实生活中,在 DAO 和管理者层,我们经常有单独的类和接口。 用于定义合同的接口,以及用于定义合同实现的类。

在哪里使用这些注解? 让我们找出答案。

始终对具体类使用注解; 而不是通过接口。

public interface EmployeeDAO
{
    //...
}

@Repository
public class EmployeeDAOImpl implements EmployeeDAO
{
    //...
}

在 bean 上具有这些构造型注解后,就可以直接使用在具体类中定义的 bean 引用。 注意引用的类型为接口。 在这种情况下,Spring DI 容器足够聪明,可以注入正确的实例。

4. @Component@Bean注解之间的区别

在 Spring 中,两个注解都大不相同。

@Component用于使用类路径扫描自动检测和自动配置 bean。 在带注解的类和 Bean 之间存在隐式的一对一映射(即每个类一个 Bean)。

@Bean用于显式声明单个 bean,而不是让 Spring 为我们自动完成。

另一个很大的区别是@Component类级别注解,其中@Bean方法级别注解,默认情况下,该方法的名称用作 Bean 名称。

5. 演示

5.1. Bean 定义

EmployeeDAO.java and EmployeeDAOImpl.java

public interface EmployeeDAO 
{
	public EmployeeDTO createNewEmployee();
}

@Repository ("employeeDao")
public class EmployeeDAOImpl implements EmployeeDAO
{
	public EmployeeDTO createNewEmployee()
	{
		EmployeeDTO e = new EmployeeDTO();
		e.setId(1);
		e.setFirstName("Lokesh");
		e.setLastName("Gupta");
		return e;
	}
}

EmployeeManager.java and EmployeeManagerImpl.java

public interface EmployeeManager 
{
	public EmployeeDTO createNewEmployee();
}

@Service ("employeeManager")
public class EmployeeManagerImpl implements EmployeeManager
{
	@Autowired
	EmployeeDAO dao;

	public EmployeeDTO createNewEmployee()
	{
		return dao.createNewEmployee();
	}
}

EmployeeController.java

@Controller ("employeeController")
public class EmployeeController 
{
        @Autowired
	EmployeeManager manager;

	public EmployeeDTO createNewEmployee()
	{
		return manager.createNewEmployee();
	}
}

EmployeeDTO.java

public class EmployeeDTO {

	private Integer id;
	private String firstName;
	private String lastName;
}

5.2. 运行演示

让我们测试上述配置和注解:

TestSpringContext.java

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.howtodoinjava.demo.service.EmployeeManager;

public class TestSpringContext 
{
	public static void main(String[] args) 
	{
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

		//EmployeeManager manager = (EmployeeManager) context.getBean(EmployeeManager.class);

		//OR this will also work

		EmployeeController controller = (EmployeeController) context.getBean("employeeController");

		System.out.println(controller.createNewEmployee());
	}
}

程序输出。

Console

Jan 22, 2015 6:17:57 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1b2b2f7f: 
startup date [Thu Jan 22 18:17:57 IST 2015]; root of context hierarchy

Jan 22, 2015 6:17:57 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions

INFO: Loading XML bean definitions from class path resource [applicationContext.xml]

Employee [id=1, firstName=Lokesh, lastName=Gupta]

如果需要更多说明,请给我评论/查询。

学习愉快!

阅读更多:

@Component@Bean
@Component注解
@Repository注解
@Service注解
@Controller注解

Spring @Scheduled – 安排任务的 4 种方法

原文: https://howtodoinjava.com/spring-core/spring-scheduled-annotation/

Spring 使用@Scheduled注解为基于 cron 表达式的任务调度和异步方法执行提供了出色的支持。 可以将@Scheduled注解与触发器元数据一起添加到方法中。

在本文中,我将展示以 4 种不同方式使用@Scheduled功能的方法。

阅读更多: Spring timer 任务

1. Spring @Scheduled注解

@Scheduled注解用于任务调度。 触发信息需要与此注解一起提供。

1.1. fixedDelay vs FixedRate vs cron

您可以使用属性fixedDelay / fixedRate / cron提供触发信息。 在哪里:

  1. fixedRate使 Spring 定期运行任务,即使最后一次调用可能仍在运行。
  2. fixedDelay特别控制最后一次执行完成时的下一个执行时间。
  3. cron是源自 Unix cron 实用程序的功能,并根据您的要求提供各种选项。

用法示例如下:

@Scheduled Usages

@Scheduled(fixedDelay =30000)
public void demoServiceMethod () {... }

@Scheduled(fixedRate=30000)
public void demoServiceMethod () {... }

@Scheduled(cron="0 0 * * * *")
public void demoServiceMethod () {... }

1.2. 如何启用@Scheduled注解

要在 spring 应用程序中使用@Scheduled,必须首先在applicationConfig.xml文件中定义 xml 命名空间和模式位置定义。 还添加task:annotation-driven以启用基于注解的任务调度。

applicationConfig.xml

xmlns:task="http://www.springframework.org/schema/task"
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.0.xsd

<task:annotation-driven>

上面的添加是必要的,因为我们将使用基于注解的配置。

1.3. 使用@Scheduled注解

下一步是在类中创建一个类和一个方法,如下所示:

DemoService.java

public class DemoService
{
	@Scheduled(cron="*/5 * * * * ?")
	public void demoServiceMethod()
	{
		System.out.println("Method executed at every 5 seconds. Current time is :: "+ new Date());
	}
}

在上述示例中:

  1. 反过来,使用@Scheduled注解将使 Spring 容器了解该注解下面的方法将作为作业运行。
  2. 请记住,用@Scheduled注解的方法不应将参数传递给它们。
  3. 他们也不应返回任何值。
  4. 如果希望在@Scheduled方法中使用外部对象,则应使用自动装配将它们注入DemoService类,而不是将它们作为参数传递给@Scheduled方法。

2. fixedDelayfixedRate

在此方法中,fixedDelay属性与@Scheduled注解一起使用。 或者,也可以使用fixedRate

示例类如下所示:

DemoServiceBasicUsageFixedDelay.java

package com.howtodoinjava.service;

import java.util.Date;
import org.springframework.scheduling.annotation.Scheduled;

public class DemoServiceBasicUsageFixedDelay
{
	@Scheduled(fixedDelay = 5000)
	//@Scheduled(fixedRate = 5000)	//Or use this
	public void demoServiceMethod()
	{
		System.out.println("Method executed at every 5 seconds. Current time is :: "+ new Date());
	}
}

应用程序配置如下所示:

applicationContext.xml

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

    <task:annotation-driven />

    <bean id="demoServiceBasicUsageFixedDelay" class="com.howtodoinjava.service.DemoServiceBasicUsageFixedDelay"></bean>

</beans>

3. 使用 Cron 表达式的@scheduled

在此方法中,cron属性与@Scheduled注解一起使用。 此属性的值必须是 cron 表达式。

示例类如下所示:

DemoServiceBasicUsageCron.java

package com.howtodoinjava.service;

import java.util.Date;
import org.springframework.scheduling.annotation.Scheduled;

public class DemoServiceBasicUsageCron
{
	@Scheduled(cron="*/5 * * * * ?")
	public void demoServiceMethod()
	{
		System.out.println("Method executed at every 5 seconds. Current time is :: "+ new Date());
	}
}

应用程序配置如下所示:

applicationContext.xml

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

    <task:annotation-driven />

    <bean id="demoServiceBasicUsageCron" class="com.howtodoinjava.service.DemoServiceBasicUsageCron"></bean>

</beans>

4. 属性文件中的 Cron 表达式

在此方法中,cron属性与@Scheduled注解一起使用。 此属性的值必须为 cron 表达式,如先前方法 BUT 一样,该 cron 表达式将在属性文件中定义,并且相关属性的键将用于@Scheduled注解。

将使 cron 表达式与源代码脱钩,从而使更改变得容易。

DemoServicePropertiesExample.java

package com.howtodoinjava.service;

import java.util.Date;
import org.springframework.scheduling.annotation.Scheduled;

public class DemoServicePropertiesExample {

	@Scheduled(cron = "${cron.expression}")
	public void demoServiceMethod()
	{
		System.out.println("Method executed at every 5 seconds. Current time is :: "+ new Date());
	}

}

应用程序配置如下所示:

applicationContext.xml

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

    <task:annotation-driven />

    <util:properties id="applicationProps" location="application.properties" />

	<context:property-placeholder properties-ref="applicationProps" />

    <bean id="demoServicePropertiesExample" class="com.howtodoinjava.service.DemoServicePropertiesExample"></bean>

</beans>

5. 上下文配置中的 Cron 表达式

在这种方法中,在属性文件中配置了 cron 表达式,并使用属性密钥为 cron 表达式在配置文件中配置了作业调度。 主要变化是,您无需在任何方法上使用@Scheduled注解。 方法配置也在应用程序配置文件中完成。

示例类如下所示:

DemoServiceXmlConfig.java

package com.howtodoinjava.service;

import java.util.Date;

public class DemoServiceXmlConfig
{
	public void demoServiceMethod()
	{
		System.out.println("Method executed at every 5 seconds. Current time is :: "+ new Date());
	}

}

应用程序配置如下所示:

applicationContext.xml

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

    <task:annotation-driven />

    <util:properties id="applicationProps" location="application.properties" />

	<context:property-placeholder properties-ref="applicationProps" />

	<bean id="demoServiceXmlConfig" class="com.howtodoinjava.service.DemoServiceXmlConfig" />

	<task:scheduled-tasks>
	  	<task:scheduled ref="demoServiceXmlConfig" method="demoServiceMethod" cron="#{applicationProps['cron.expression']}"></task:scheduled>
	</task:scheduled-tasks>

</beans>

下载源码

让我知道我是否遗漏任何东西。

学习愉快!

参考:

http://forum.springsource.org/showthread.php?83053-Feature-Scheduled-with-Value-cron-expression

Spring 定时器任务

原文: https://howtodoinjava.com/spring-core/spring-timer-tasks/

Timer是一种实用程序类,用于安排一次和重复执行的任务。 通过计时器任务,Spring 框架为执行调度定期执行多次甚至是一次执行的任务提供支持。

还有其他方法可以在 Java 中实现调度程序功能,例如使用执行程序服务或使用 Quartz 等第三方 API 来无限循环地运行线程。 Timer恰好是其中之一。

请注意,Timer取决于系统时钟,因此将系统时钟设置为 n 小时将使下一次执行计时器任务的时间也减少 n 小时。

在 Spring 中,有两种使用计时器任务的方法:

  1. 配置MethodInvokingTimerTaskFactoryBean
  2. 扩展java.util.TimerTask

让我们使用示例演示每个用法。

1. 配置MethodInvokingTimerTaskFactoryBean

在此方法中,在org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean定义中配置了计时器任务 bean 及其内部要执行的方法。 这是一个工厂 Bean,它公开 TimerTask 对象,该对象将作业执行委派给指定的(静态或非静态)方法。 这避免了实现仅调用现有业务方法的单行TimerTask的需要。

Spring 配置示例如下所示:

applicationContext.xml

<beans>

    <bean id="demoTimerTask" class="com.howtodoinjava.task.DemoTimerTask"></bean>

    <bean id="timerTaskFactoryBean"	
    		class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
		<property name="targetObject" ref="demoTimerTask"></property>
		<property name="targetMethod" value="execute"></property>
	</bean>

    <bean id="scheduledTimerTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
		<property name="timerTask" ref="timerTaskFactoryBean"></property>
		<property name="period" value="5000"></property>
	</bean>

    <bean class="org.springframework.scheduling.timer.TimerFactoryBean">
		<property name="scheduledTimerTasks">
			<list>
				<ref local="scheduledTimerTask"></ref>
			</list>
		</property>
	</bean>
</beans>

上面的计时器任务将获得executed in every 5 seconds。 让我们写出演示计时器任务并对其进行测试。

DemoTimerTask.java

package com.howtodoinjava.task;

import java.util.Date;

/**
 * No need to implement any interface
 * */
public class DemoTimerTask {

	//Define the method to be called as configured
	public void execute()
	{
		System.out.println("Executed task on :: " + new Date());
	}
}

现在,让我们测试计时器任务。

TestDemoTimerTask.java

package com.howtodoinjava.timer;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestDemoTimerTask {
	public static void main(String[] args) {
		new ClassPathXmlApplicationContext("application-config.xml");
	}
}

Console

Executed task on :: Mon Apr 22 09:53:39 IST 2017
Executed task on :: Mon Apr 22 09:53:44 IST 2017

2. 扩展java.util.TimerTask

通过这种方式,我们通过扩展 java.util.TimerTask 来定义定时器任务,并将其传递给 spring 配置以便重复执行。

让我们来看看如何做:

applicationContext.xml

<beans>

    <bean id="demoTimerTask" class="com.howtodoinjava.task.DemoTimerTask2"></bean>

    <bean id="scheduledTimerTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
	    <!-- run every 3 secs -->
	    <property name="period" value="3000"></property>
	    <property name="timerTask" ref="demoTimerTask"></property>
	</bean>

	<bean class="org.springframework.scheduling.timer.TimerFactoryBean">
	    <property name="scheduledTimerTasks">
	        <list>
	            <ref local="scheduledTimerTask"></ref>
	        </list>
	    </property>
	</bean>

</beans>

以上任务将得到executed in every 3 seconds。 让我们从 java 提供的TimerTask中扩展计时器任务。

DemoTimerTask2.java

package com.howtodoinjava.task;

import java.util.Date;
import java.util.TimerTask;

public class DemoTimerTask2 extends TimerTask 
{
	public void run() 
	{
		System.out.println("DemoTimerTask2 running at: "
				+ new Date(this.scheduledExecutionTime()));
	}
}

让我们测试配置:

TestDemoTimerTask2.java

package com.howtodoinjava.timer;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestDemoTimerTask2 
{
	public static void main(String[] args) 
	{
		new ClassPathXmlApplicationContext("application-config2.xml");
	}
}

Console

DemoTimerTask2 running at: Mon Apr 22 10:01:33 IST 2013
DemoTimerTask2 running at: Mon Apr 22 10:01:36 IST 2013
DemoTimerTask2 running at: Mon Apr 22 10:01:39 IST 2013

下载源码

学习愉快!

Spring – 应用事件

原文: https://howtodoinjava.com/spring-core/how-to-publish-and-listen-application-events-in-spring/

了解在 Spring 应用程序中创建基于发布者-订阅者的事件。 Spring 创建应用程序事件,发布它们然后在事件处理器中监听提供了内置支持。 要创建/监听应用程序事件,需要遵循一些简单的准则:

  1. 事件应该扩展ApplicationEvent
  2. 发布者应该注入ApplicationEventPublisher对象
  3. 监听器应该实现ApplicationListener接口

1. 为什么是 Spring 事件

有时在 Spring 应用程序中,您可能需要添加用于监听特定应用程序事件的功能,以便可以根据应用程序逻辑处理这些事件。

这些事件的示例可以是添加/删除员工时; 或某种事务完成或回滚。 您始终可以将事件处理代码编写为现有应用程序代码中的另一种方法,但是它会与您现有的代码紧密结合,并且您以后将没有太多处理方法来更改它(假设您不想为这些事件处理这些事件)。 一定的时间)。

如果可以通过应用程序上下文文件配置事件处理器,则在大多数情况下,您无需更改应用程序代码以及事件处理器代码。 任何时候您需要关闭事件处理; 或者,您想要为该事件添加另一个事件处理器,那么只需更改上下文文件的配置即可。 听起来不错 !!

在基于 Spring 事件的通信模型中,发送方组件仅发布事件,而不知道谁将是。 实际上,可能有多个接收器组件。 同样,接收者也不必知道谁在发布事件。 监听器可以同时监听来自不同发送者的多个事件。 这样,发送方和接收方组件松散耦合。

让我们学习,如何在您的 Spring 应用程序中实现此发布和监听事件。

2. 发布和监听 Spring 应用程序事件

2.1. 创建自定义应用程序事件类

所有事件类都必须扩展ApplicationEvent类。

EmployeeEvent.java

public class EmployeeEvent extends ApplicationEvent
{
	private static final long serialVersionUID = 1L;

	private String eventType;
	private EmployeeDTO employee;

	//Constructor's first parameter must be source
	public EmployeeEvent(Object source, String eventType, EmployeeDTO employee) 
	{
		//Calling this super class constructor is necessary
		super(source);
		this.eventType = eventType;
		this.employee = employee;
	}

	public String getEventType() {
		return eventType;
	}

	public EmployeeDTO getEmployee() {
		return employee;
	}
}

2.2. 发布者 – 实现ApplicationEventPublisherAware接口

任何实现ApplicationEventPublisherAware接口的 bean,都可以使用其publishEvent()方法将事件发送给监听器。

EmployeeManagerImpl.java

@Service ("employeeManager")
public class EmployeeManagerImpl implements EmployeeManager, ApplicationEventPublisherAware
{
	@Autowired
	private EmployeeDAO dao;

	private ApplicationEventPublisher publisher;

	//You must override this method; It will give you acces to ApplicationEventPublisher
	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
		this.publisher = publisher;
	}

	public EmployeeDTO createNewEmployee()
	{
		EmployeeDTO employee =  dao.createNewEmployee();

		//publishing the veent here
		publisher.publishEvent(new EmployeeEvent(this, "ADD", employee));

		return employee;
	}
}

2.3. 订阅者/监听器 – 实现ApplicationListener接口

为了使 bean 监听某些事件,它必须实现ApplicationListener接口并以onApplicationEvent()方法处理事件。 实际上,Spring 会将所有事件通知给监听器,因此您必须自己过滤事件。

如果使用泛型,Spring 将仅传递与泛型类型参数匹配的消息。 在此示例中,我使用泛型代码仅监听EmployeeEvent

UserEventsProcessor.java

public class UserEventsProcessor implements ApplicationListener<EmployeeEvent> 
{
	public void onApplicationEvent(EmployeeEvent event) 
	{
		EmployeeEvent employeeEvent = (EmployeeEvent) event;

		System.out.println("Employee " + employeeEvent.getEventType() 
                          + " with details : " + employeeEvent.getEmployee());

		// Do more processing as per application logic
	}
}

2.4. 在应用程序上下文文件中配置 Bean

applicationContext.xml

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

<bean class="com.howtodoinjava.demo.processors.UserEventsProcessor" />

2.5. 示例

TestSpringContext.java

public class TestSpringContext 
{
	@SuppressWarnings("resource")
	public static void main(String[] args) throws Exception 
	{
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

		EmployeeController controller = context.getBean(EmployeeController.class);

		controller.createNewEmployee();
	}
}

观察输出。

Console

INFO: Loading XML bean definitions from class path resource [applicationContext.xml]
Employee ADD with details : Employee [id=1, firstName=Lokesh, lastName=Gupta, type=null]

很棒。 我们现在可以收听事件,然后可以按照自己的方式进行处理。

请注意,应用程序上下文本身还会发布容器事件,例如ContextClosedEventContextRefreshedEventRequestHandledEvent。 如果您的任何 bean 希望收到这些事件的通知,则可以实现ApplicationListener接口。

3. 本示例中使用的其他类文件

这里的EmployeeDTO类如下所示:

EmployeeDTO.java

public class EmployeeDTO 
{
	private Integer id;
	private String firstName;
	private String lastName;
	private String designation;

	public EmployeeDTO(String designation) 
	{
		this.id = -1;
		this.firstName = "dummy";
		this.lastName = "dummy";
		this.designation = designation;
	}

	public EmployeeDTO() {
		// TODO Auto-generated constructor stub
	}

	//Setters and Getters

	@Override
	public String toString() {
		return "Employee [id=" + id + ", firstName=" + firstName
				+ ", lastName=" + lastName + ", type=" + designation + "]";
	}
}

EmployeeDAO.java

public interface EmployeeDAO 
{
	public EmployeeDTO createNewEmployee();
}

EmployeeDAOImpl.java

@Repository ("employeeDao")
public class EmployeeDAOImpl implements EmployeeDAO
{
	public EmployeeDTO createNewEmployee()
	{
		EmployeeDTO e = new EmployeeDTO();
		e.setId(1);
		e.setFirstName("Lokesh");
		e.setLastName("Gupta");
		return e;
	}

	public void initBean() {
		System.out.println("Init Bean for : EmployeeDAOImpl");
	}

	public void destroyBean() {
		System.out.println("Init Bean for : EmployeeDAOImpl");
	}
}

EmployeeManager.java

public interface EmployeeManager 
{
	public EmployeeDTO createNewEmployee();
}

学习愉快!

Spring i18n – ResourceBundleMessageSource

https://howtodoinjava.com/spring-core/resolving-text-messages-in-spring-resourcebundlemessagesource-example/

对于支持国际化(i18n)的应用程序,它要求能够解析不同区域设置的文本消息。 Spring 的应用程序上下文可以通过其键解析目标区域设置的文本消息。 通常,一种语言环境的消息应存储在一个单独的属性文件中。 此属性文件称为资源包

MessageSource是定义几种解决消息的方法的接口。 ApplicationContext接口扩展了此接口,以便所有应用程序上下文都能够解析文本消息。

应用程序上下文将消息解析委托给确切名称为messageSource的 Bean。 ResourceBundleMessageSource是最常见的MessageSource实现,用于解析来自不同区域的资源包中的消息。

1. 使用ResourceBundleMessageSource解析消息

例如,您可以为美国英语创建以下资源包messages_en_US.properties资源包将从类路径的根目录加载

1.1. 创建资源包

在 spring 应用程序的类路径中创建文件名messages_en_US.properties

messages_en_US.properties

msg.text=Welcome to howtodoinjava.com

1.2. 配置ResourceBundleMessageSource bean

现在将ResourceBundleMessageSource类配置为 bean 名称"messageSource"。 另外,您必须为ResourceBundleMessageSource指定资源包的基本名称。

applicationContext.xml

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

2. 资源包查找顺序

  1. 对于此messageSource定义,如果您查找首选语言为英语的美国语言环境的文本消息,则将首先考虑与语言和国家/地区匹配的资源包messages_en_US.properties
  2. 如果没有此类资源包或找不到消息,则将考虑仅与该语言匹配的一个messages_en.properties
  3. 如果仍然找不到此资源包,则最终将选择所有语言环境的默认messages.properties

3. 演示 – 如何使用MessageSource

3.1. 直接获取消息

现在要检查是否可以加载消息,请运行以下代码:

TestSpringContext.java

public class TestSpringContext 
{
    @SuppressWarnings("resource")
    public static void main(String[] args) throws Exception 
    {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        String message = context.getMessage("msg.text", null, Locale.US);

        System.out.println(message);
    }
}

输出:

Console

Welcome to howtodoinjava.com

3.2. 在 bean 中实现MessageSourceAware接口

很好,因此我们能够解析消息。 但是,这里我们直接访问ApplicationContext,因此看起来很简单。 如果要在某个 bean 实例中访问消息,则需要实现ApplicationContextAware接口或MessageSourceAware接口。

像这样:

EmployeeController.java

public class EmployeeController implements MessageSourceAware {

    private MessageSource messageSource;

    public void setMessageSource(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    //Other code
}

现在,您可以使用此messageSource实例来检索/解析资源文件中定义的文本消息。

学习愉快!

Spring ResourceLoaderAware - 在 Spring 中读取文件

原文: https://howtodoinjava.com/spring-core/spring-resource-loader-aware/

了解将资源或文件(例如,文本文件,XML 文件,属性文件或图像文件)加载到 Spring 应用程序上下文中的不同方法。 Spring ResourceLoader为我们提供了统一的getResource()方法,以便我们通过资源路径检索外部资源。

1. 资源接口表示资源

资源是 Spring 中用于表示外部资源的通用接口。

Spring 为Resource接口提供了以下 6 种实现。

  1. UrlResource
  2. ClassPathResource
  3. FileSystemResource
  4. ServletContextResource
  5. InputStreamResource
  6. ByteArrayResource

我们可以指定不同的前缀来创建路径以从不同位置加载资源。

前缀 示例 说明
classpath: classpath:com/myapp/config.xml 从类路径加载。
file: file:///data/config.xml 从文件系统作为URL加载。
http: https://myserver/logo.png 加载为URL
(没有) /data/config.xml 取决于基础的ApplicationContext

2. ResourceLoader

它用于加载资源(例如类路径或文件系统资源)。 它有两种方法:

ResourceLoader methods

//Expose the ClassLoader used by this ResourceLoader.
ClassLoader getClassLoader()

//Return a Resource handle for the specified resource location.
Resource getResource(String location)

getResource()方法将根据资源路径决定实例化哪个Resource实现。

要获取ResourceLoader的引用,请实现ResourceLoaderAware接口。

How to get resource

Resource banner = resourceLoader.getResource("file:c:/temp/filesystemdata.txt");

3. 使用ApplicationContext加载资源

在 Spring 中,所有应用程序上下文都实现ResourceLoader接口。 因此,所有应用程序上下文都可用于获取资源实例。

要获取ApplicationContext的引用,请实现ApplicationContextAware接口。

How to get resource

Resource banner = ctx.getResource("file:c:/temp/filesystemdata.txt");

4. 使用ResourceLoaderAware加载资源

为了演示下面的各种示例,我将一个具有相同名称的文件放置在不同的位置,并且我将演示如何加载每个文件。

CustomResourceLoader.java编写如下,将已加载的资源文件的内容打印到控制台中。

CustomResourceLoader.java

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

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

public class CustomResourceLoader implements ResourceLoaderAware 
{
	private ResourceLoader resourceLoader;

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

	public void showResourceData() throws IOException 
	{
		//This line will be changed for all versions of other examples
		Resource banner = resourceLoader.getResource("file:c:/temp/filesystemdata.txt");

		InputStream in = banner.getInputStream();

		BufferedReader reader = new BufferedReader(new InputStreamReader(in));

		while (true) {
			String line = reader.readLine();
			if (line == null)
				break;
			System.out.println(line);
		}
		reader.close();
	}
}

该文件的applicationContext.xml文件条目如下:

applicationContext.xml

<bean id="customResourceLoader" class="com.howtodoinjava.demo.CustomResourceLoader"></bean>

要测试CustomResourceLoader bean 并调用showResourceData()方法,已使用以下代码:

Main.java

@SuppressWarnings("resource")
public static void main(String[] args) throws Exception 
{
	ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

	CustomResourceLoader customResourceLoader = (CustomResourceLoader) context.getBean("customResourceLoader");

	customResourceLoader.showResourceData();
}

spring-load-external-resource-example

由于我们正在通过 Spring 的资源加载器访问资源,因此自定义资源加载器必须实现ApplicationContextAware接口或ResourceLoaderAware接口。

5. 加载外部资源

5.1. 从应用程序根文件夹加载资源

要从应用程序文件夹加载文件,请使用以下模板:

Resource banner = resourceLoader.getResource("file:data.txt");

5.2. 从类路径加载资源

要从类路径加载文件,请使用以下模板:

Resource banner = resourceLoader.getResource("classpath:classpathdata.txt");

5.3. 从文件系统加载资源

要从应用程序文件夹外部的文件系统加载文件,请使用以下模板:

Resource banner = resourceLoader.getResource("file:c:/temp/filesystemdata.txt");

5.4. 从 URL 加载资源

要从任何 URL 加载文件,请使用以下模板:

Resource banner = resourceLoader.getResource("//howtodoinjava.com/readme.txt");

以上所有示例将从其位置加载资源文件,您可以按需要使用它们。

6. 如何注入外部文件

在上面的示例中,我们已经在CustomResourceLoader中对资源名称进行了硬编码,很多人可能不喜欢它,并且希望通过上下文文件对其进行配置。 使用下面的代码模板可以配置外部资源名称

beans.xml

<bean id="customResourceLoader" class="com.howtodoinjava.demo.CustomResourceLoader">

	<property name="resource">
		<value>classpath:classpathdata.txt</value>
		<!-- or -->
		<value>file:data.txt</value> 
	</property>

</bean>

CustomResourceLoader如下所示:

CustomResourceLoader.java

public class CustomResourceLoader {

	private Resource resource;

	public Resource getResource() {
		return resource;
	}

	public void setResource(Resource resource) {
		this.resource = resource;
	}
}

上下文初始化后,资源将注入到CustomResourceLoaderresource属性中。 在 spring boot ResourceLoader示例中可以使用相同的代码。

学习愉快!

Spring 属性编辑器 – CustomEditorConfigurer示例

原文: https://howtodoinjava.com/spring-core/registering-built-in-property-editors-in-spring-4-customeditorconfigurer-example/

属性编辑器是 JavaBeans API 的一项功能,用于在文本值之间来回转换属性值。 每个属性编辑器仅设计用于某种类型的属性。 您可能希望使用属性编辑器来简化 bean 配置。 在本教程中,我们将学习在您的应用程序中配置 spring 的内置CustomDateEditor类。

CustomEditorConfigurerCustomDateEditor配置

通常,您将在使用容器之前在容器中注册一个属性编辑器。 CustomEditorConfigurer类被实现为内置的 bean 工厂后处理器,供您在实例化任何 bean 之前注册自定义属性编辑器。

为什么选择CustomDateEditor

例如,在您的应用程序中,如果要将日期值从字符串格式转换为java.util.Date对象,反之亦然,则可以使用CustomDateEditor类。 Spring 附带的CustomDateEditor类用于将日期字符串转换为java.util.Date属性。

可以将CustomEditorConfigurer bean 声明到应用程序上下文中,如下所示:

beans.xml

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
	<property name="propertyEditorRegistrars">
		<list>
			<bean class="com.howtodoinjava.demo.processors.CustomDateEditorRegistrar" />
		</list>
	</property>
</bean>

注册/配置CustomDateEditor

spring 4.x 开始应该以以下方式声明CustomDateEditorRegistrar类。

CustomDateEditorRegistrar.java

public class CustomDateEditorRegistrar implements PropertyEditorRegistrar 
{
    public void registerCustomEditors(PropertyEditorRegistry registry) 
	{
        registry.registerCustomEditor(Date.class, 
				new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false));
    }
}

CustomDateEditor演示

现在,每次,当您以字符串格式(例如)传递 bean 属性值(类型java.util.Date)时, 2007-09-30,它将自动转换为Date对象。

让我们测试配置。 为了测试,我创建了一个EmployeeDTO bean,其中一个日期字段为dateOfBirth

public class EmployeeDTO {

	private Integer id;
	private String firstName;
	private String lastName;
	private String designation;
	private Date dateOfBirth;

	//Setters and Getters

	@Override
	public String toString() {
		return "EmployeeDTO [id=" + id + ", firstName=" + firstName
				+ ", lastName=" + lastName + ", designation=" + designation
				+ ", dateOfBirth=" + dateOfBirth + "]";
	}
}

applicationContext.xml文件中的Employee bean 定义如下:

applicationContext.xml

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
	<property name="propertyEditorRegistrars">
		<list>
			<bean class="com.howtodoinjava.demo.processors.CustomDateEditorRegistrar" />
		</list>
	</property>
</bean>

<!-- employeeDTO bean -->
<bean id="employeeDTO" class="com.howtodoinjava.demo.model.EmployeeDTO">
	<property name="firstName" value="Lokesh" />
	<property name="lastName" value="Gupta" />
	<property name="designation" value="Manager" />
	<property name="dateOfBirth" value="2007-09-30" />
</bean>

让我们从上下文中获取 bean。 应该在dateOfBirth字段中填充给定的日期值。

TestSpringContext.java

public class TestSpringContext 
{
	@SuppressWarnings("resource")
	public static void main(String[] args) throws Exception 
	{
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

		EmployeeDTO employeeDTO = (EmployeeDTO) context.getBean("employeeDTO");

		System.out.println(employeeDTO.getDateOfBirth());
	}
}

Output:

Sun Sep 30 00:00:00 IST 2007

很棒。Date值被设置。

学习愉快!

Spring 5 教程

原文: https://howtodoinjava.com/spring-5-tutorial/

在本 Spring 5 教程中,学习带有 Java 注解配置的新功能和 Hello World 示例。

1. Spring 5 有什么新功能?

Spring 5 是第一个主要版本,距 Spring Framework 4.0 差不多四年了。 在这段时间内,大多数增强功能都是在 Spring boot 项目中完成的。 在 Spring 5,这些值得一提的令人兴奋的功能:

  • 基准升级 – Spring 5 现在最低支持 JDK 8 和 Java EE 7。很少有增加了最低支持版本的库。 例如,Servlet 3.1,JMS 2.0,JPA 2.1,JAX-RS 2.0,Bean 验证 1.1,Hibernate 5,Jackson 2.6,EhCache 2.10,JUnit 5 和 Tiles 3。

    服务器的最低支持版本也已提高。 例如 – Tomcat 8.5,Jetty 9.4,WildFly 10 等。

  • JDK 9 运行时兼容性 – Spring 5 支持 Java 9,包括 Java 模块。

  • JDK 8 功能的使用 – Spring 5 具有基准版本 8,因此它也使用了 Java 8 和 9 的许多新功能。 实际上,它已经广泛使用了 lambda 函数。

  • 响应式编程支持 – Spring Framework 5 包含响应式流(中立的语言来定义响应式 API 的尝试)和 Reactor(Spring Pivotal 团队提供的响应式流的 Java 实现)供其自己的响应式使用以及它的许多核心 API。

  • 功能性 Web 框架 – Spring 5 也提供了功能性 Web 框架。 它提供了使用功能性编程风格定义端点的功能。

  • Kotlin 支持 – Spring 框架 5.0 对 Kotlin 有很好的支持。

阅读更多: Spring 5 新功能

2. Spring 5 示例

Spring 5 示例和教程列表。

2.1. Spring 5 核心

  1. Spring Bean Java 配置示例
  2. Spring Bean XML 配置示例
  3. Spring Bean 立即与延迟初始化
  4. Spring bean 范围

2.2. Spring 5 WebMVC

  1. Spring @GetMapping@PostMapping示例
  2. Spring DispatcherServlet教程
  3. Spring 5 MVC + Hibernate 5 示例

2.3. SpringSecurity 5

  1. 安全性 Java Config – @EnableWebSecurity示例
  2. 登录表单示例

Spring CORS 配置

3. Spring 5 教程资源

Spring 参考
Spring Security 5
Spring WebMVC

Spring – 使用JavaMailSender发送电子邮件

原文: https://howtodoinjava.com/spring-core/send-email-with-spring-javamailsenderimpl-example/

在 Spring 5 提供的JavaMailSender接口中学习发送电子邮件。 这是通过 gmail smtp 服务器发送电子邮件的分步示例。

我们将使用javax.mail maven 依赖来发送电子邮件,同时在实现JavaMailSender接口的JavaMailSenderImpl类中配置邮件属性。

了解更多:使用 Gmail SMTP 服务器发送电子邮件(javax.email)

1. Maven 依赖

请遵循 maven 项目创建示例来创建项目。 现在,将 spring 依赖项与javax.mail一起导入。

pom.xml

<properties>
	<spring.version>5.2.0.RELEASE</spring.version>
	<email.version>1.16.18</email.version>
</properties>

<!-- Spring Context Support -->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context-support</artifactId>
	<version>${spring.version}</version>
</dependency>

<dependency>
	<groupId>com.sun.mail</groupId>
	<artifactId>javax.mail</artifactId>
	<version>${email.version}</version>
</dependency>

2. JavaMailSender和电子邮件模板

2.1. Java 配置

给出了JavaMailSender的 Java 配置,该配置已配置为使用 Gmail SMTP 设置,并且我们配置了示例电子邮件模板,该模板已预先配置了发件人/收件人电子邮件和电子邮件文本。

您可以根据需要自定义配置。

EmailConfig.java

import java.util.Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;

@Configuration
public class EmailConfig 
{
	@Bean
	public JavaMailSender getJavaMailSender() 
	{
	    JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
	    mailSender.setHost("smtp.gmail.com");
	    mailSender.setPort(25);

	    mailSender.setUsername("admin@gmail.com");
	    mailSender.setPassword("password");

	    Properties props = mailSender.getJavaMailProperties();
	    props.put("mail.transport.protocol", "smtp");
	    props.put("mail.smtp.auth", "true");
	    props.put("mail.smtp.starttls.enable", "true");
	    props.put("mail.debug", "true");

	    return mailSender;
	}

	@Bean
	public SimpleMailMessage emailTemplate()
	{
		SimpleMailMessage message = new SimpleMailMessage();
		message.setTo("somebody@gmail.com");
		message.setFrom("admin@gmail.com");
	    message.setText("FATAL - Application crash. Save your job !!");
	    return message;
	}
}

2.2. XML 配置

在 Spring 上下文文件中,我们将创建一个通用的邮件发件人服务,该服务能够使用 gmail 的 smtp 服务器发送 HTTP 消息。

另外,我们正在制作一个预配置的消息,该消息可以即时实例化并用于发送消息。

applicationContext.xml

<beans>

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

    <!-- SET default mail properties -->
	<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
	  	<property name="host" value="smtp.gmail.com"/>
	  	<property name="port" value="25"/>
	  	<property name="username" value="admin@gmail.com"/>
	 	<property name="password" value="password"/>
	  	<property name="javaMailProperties">
			<props>
			    <prop key="mail.transport.protocol">smtp</prop>
			    <prop key="mail.smtp.auth">true</prop>
			    <prop key="mail.smtp.starttls.enable">true</prop>
			    <prop key="mail.debug">true</prop>
			</props>
  		</property>
    </bean>

    <!-- We can have some pre-configured messagess also which are ready to send -->

    <bean id="preConfiguredMessage" class="org.springframework.mail.SimpleMailMessage">
		<property name="to" value="somebody@gmail.com"></property>
		<property name="from" value="admin@gmail.com"></property>
  		<property name="subject" value="FATAL - Application crash. Save your job !!"/>
    </bean>
</beans>

3. 发送电子邮件

此类使用applicationContext.xml文件中配置的 bean,并使用它们发送消息。

EmailService.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Service;

@Service("emailService")
public class EmailService 
{
    @Autowired
    private JavaMailSender mailSender;

    @Autowired
    private SimpleMailMessage preConfiguredMessage;

    /**
     * This method will send compose and send the message 
     * */
    public void sendMail(String to, String subject, String body) 
    {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(to);
        message.setSubject(subject);
        message.setText(body);
        mailSender.send(message);
    }

    /**
     * This method will send a pre-configured message
     * */
    public void sendPreConfiguredMail(String message) 
    {
        SimpleMailMessage mailMessage = new SimpleMailMessage(preConfiguredMessage);
        mailMessage.setText(message);
        mailSender.send(mailMessage);
    }
}

4. 带有附件和内联资源的电子邮件

4.1. 电子邮件附件

要使用电子邮件附加文件,请使用MimeMessageHelper将文件附加MimeMessage

Attach file

public void sendMailWithAttachment(String to, String subject, String body, String fileToAttach) 
{
    MimeMessagePreparator preparator = new MimeMessagePreparator() 
    {
        public void prepare(MimeMessage mimeMessage) throws Exception 
        {
            mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
            mimeMessage.setFrom(new InternetAddress("admin@gmail.com"));
            mimeMessage.setSubject(subject);
            mimeMessage.setText(body);

            FileSystemResource file = new FileSystemResource(new File(fileToAttach));
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
            helper.addAttachment("logo.jpg", file);
        }
    };

    try {
        mailSender.send(preparator);
    }
    catch (MailException ex) {
        // simply log it and go on...
        System.err.println(ex.getMessage());
    }
}

4.2. 内联资源

有时,我们可能希望在电子邮件正文中附加内嵌资源,例如内嵌图像。

通过使用指定的Content-ID将内联资源添加到MimeMessage。 确保首先添加文本,然后添加资源。 如果您正相反进行操作,则此操作无效。

Inline image

public void sendMailWithInlineResources(String to, String subject, String fileToAttach) 
{
    MimeMessagePreparator preparator = new MimeMessagePreparator() 
    {
        public void prepare(MimeMessage mimeMessage) throws Exception 
        {
            mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
            mimeMessage.setFrom(new InternetAddress("admin@gmail.com"));
            mimeMessage.setSubject(subject);

            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

            helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);

            FileSystemResource res = new FileSystemResource(new File(fileToAttach));
            helper.addInline("identifier1234", res);
        }
    };

    try {
        mailSender.send(preparator);
    }
    catch (MailException ex) {
        // simply log it and go on...
        System.err.println(ex.getMessage());
    }
}

5. 演示

是时候测试 spring 邮件发送程序代码了。 我正在从测试代码发送两条消息。 一个实例化并由测试类本身组成,另一个是来自applicationContext.xml文件的预配置消息。

SpringEmailTest.java

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class SpringEmailTest 
{
	public static void main(String[] args) 
    {
        //Create the application context
        ApplicationContext context = new FileSystemXmlApplicationContext
        			("classpath:com/howtodoinjava/core/email/applicationContext.xml");

        //Get the mailer instance
        EmailService mailer = (EmailService) context.getBean("emailService");

        //Send a composed mail
        mailer.sendMail("somebody@gmail.com", "Test Subject", "Testing body");

        //Send a pre-configured mail
        mailer.sendPreConfiguredMail("Exception occurred everywhere.. where are you ????");
    }
}

上面的通话将发送电子邮件。

学习愉快!

Spring 的无版本模式(最新版本)

原文: https://howtodoinjava.com/spring-core/do-not-specify-version-numbers-in-spring-schema-references/

如果您从事过 Spring 框架项目,那么您必须已经看到 spring 上下文配置文件(例如applicationContext.xml),其中在标头中为各种 spring 模块指定了模式引用(.xsd)。 在模式引用中,我们提到了 xml 名称空间和模式版本号。

那么根本不需要指定模式版本号,您可以忽略它。 如果确实如此,您应该一直忽略它。 将其视为要遵循的最佳实践。

Spring 自动从项目依赖项(jar)中选择可用的最高版本。 另外,随着项目的发展和 Spring 版本的更新,我们不必维护所有 XML 配置文件即可看到新功能。

无版本 Spring 模式示例

有版本模式

applicationContext.xml

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

  <!-- Other bean definitions-->

</beans>

无版本模式

可以这样写:

applicationContext.xml

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

  <!-- Other bean definitions-->

</beans>

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

学习愉快!

Spring 面试问答

原文: https://howtodoinjava.com/interview-questions/top-spring-interview-questions-with-answers/

我已经尝试收集了这些Spring 面试问题,您可能会在下一次技术面试中面对这些问题。 对于其他 Spring 课程,我将分别分享面试问题和答案。 如果您能提出以前面试中遇到过的更多类似 Spring 面试问题,并考虑提出它们作为专家级 Spring 面试问题,我将不胜感激。 我将它们添加到此列表中。 这对于其他学习者也将有很大的帮助。

Spring 面试题

1.什么是 Spring Framework? 它的主要模块是什么?
2.使用 Spring Framework 有什么好处?
3.什么是控制反转(IoC)和依赖注入?
4.在 Spring Framework 中解释 IoC?
5. BeanFactoryApplicationContext之间的区别?
6.您可以通过多种方式将 Spring 配置到我们的应用程序中?
7.什么是基于 Spring XML 的配置?
8.什么是基于 Spring Java 的配置?
9.什么是基于 Spring 注解的配置?
10.解释 Spring Bean 的生命周期吗?
11.什么是不同的 Spring Bean 范围?
12. Spring 中的内部 bean 是什么?
13. 单例 bean 在 Spring Framework 中线程安全吗?
14.如何在 Spring 中注入 Java 集合? 举个例子?
15.如何将java.util.Properties注入 Spring Bean?
16.解释 Spring Bean 自动装配吗?
17.解释 Bean 自动装配的不同模式吗?
18.如何打开基于注解的自动装配?
19.用示例解释@Required注解?
20.用示例解释@Autowired注解?
21.用示例解释@Qualifier注解?
22.构造函数注入和 setter 注入之间的区别?
23. Spring 框架中不同类型的事件是什么?
24. FileSystemResourceClassPathResource之间的区别?
25.列举一些 Spring 框架中使用的设计模式吗?

1. 什么是 Spring 框架? 它的主要模块是什么?

Spring 框架是一个 Java 平台,为开发 Java 应用程序提供全面的基础架构支持。 Spring 处理基础结构部分,因此您可以专注于应用程序部分。 在其内部,Spring 框架将形式化的设计模式编码为一流的对象,您可以将它们集成到自己的应用程序中,而不必担心它们在后端如何工作。

目前,Spring 框架由组织为约 20 个模块的功能组成。 这些模块分为核心容器,数据访问/集成,Web,AOP(面向切面的编程),规范,消息传递和测试,如图所示。 下图。

spring modules

阅读更多: Spring 框架教程

2. 使用 Spring 框架有什么好处?

以下是使用 Spring Framework 的一些主要好处的清单:

  • 使用 依赖注入(DI) 方法,依赖在构造函数或 JavaBean 属性中是显式的且显而易见的。
  • IoC 容器往往是轻量级的,特别是例如与 EJB 容器相比时。 这对于在内存和 CPU 资源有限的计算机上开发和部署应用程序很有用。
  • Spring 并没有彻底改变这种状况,它确实利用了一些现有技术,例如几个 ORM 框架,日志记录框架,JEE,Quartz 和 JDK 计时器以及其他视图技术。
  • Spring 以模块化的方式组织。 即使包和类的数量很多,您也只需要担心需要的包而忽略其余的包。
  • 测试用 Spring 编写的应用程序 很简单,因为依赖于环境的代码已移入该框架。 此外,通过使用 JavaBean 风格的 POJO,使用依赖注入来注入测试数据变得更加容易。
  • Spring 的 Web 框架是精心设计的 Web MVC 框架,它提供了 Struts 之类的 Web 框架或其他经过精心设计或不太受欢迎的 Web 框架的绝佳替代。
  • Spring 提供了一个一致的事务管理接口,该接口可以按比例缩小到本地事务(例如,使用单个数据库),并可以扩大到全局事务(例如,使用 JTA)。

3. 什么是控制反转(IoC)和依赖注入?

在软件工程中,控制反转(IoC)是一种编程技术,其中对象耦合在运行时由汇编程序对象绑定,并且通常在编译时使用静态分析是未知的。 在传统编程中,业务逻辑的流程由静态分配给彼此的对象确定。 在控制反转的情况下,流程依赖于由汇编程序实例化的对象图,并且通过抽象定义对象交互来使流程成为可能。 绑定过程是通过“依赖注入”实现的。

控制反转是一种设计示例,旨在为您的应用程序的目标组件(实际上是在工作的组件)提供更多控制。

依赖注入是一种模式,用于创建其他对象依赖的对象实例,而在编译时不知道将使用哪个类来提供该功能。 控制反转依赖于依赖项注入,因为需要一种机制来激活提供特定功能的组件。 否则,如果框架不再受控制,框架将如何知道要创建哪些组件?

在 Java 中,依赖项注入可能通过 3 种方式发生:

  1. 构造函数注入
  2. 二传手注入
  3. 接口注入

4. 在 Spring Framework 中解释 IoC?

org.springframework.beansorg.springframework.context软件包为 Spring Framework 的 IoC 容器提供了基础。BeanFactory接口提供了一种高级配置机制,能够管理任何性质的对象。ApplicationContext接口建立在BeanFactory之上(它是一个子接口),并添加了其他功能,例如集成更容易,消息资源处理(用于国际化),事件传播和特定于应用程序层的上下文,例如WebApplicationContext用于 Web 应用程序。

org.springframework.beans.factory.BeanFactory是 Spring IoC 容器的实际表示,该容器负责包含和管理上述 bean。BeanFactory接口是 Spring 中的中央 IoC 容器接口。

5. BeanFactoryApplicationContext之间的区别?

BeanFactory就像一个工厂类,其中包含一组 bean。 BeanFactory 在其内部保存多个 Bean 的 Bean 定义,然后在客户要求时实例化 Bean。 BeanFactory能够在实例化协作对象之间创建关联。 这消除了 bean 本身和 bean 客户端的配置负担。 BeanFactory也参与了 bean 的生命周期,调用了自定义的初始化和销毁​​方法。

从表面上看,应用程序上下文与 Bean 工厂相同。 两者都装入 bean 定义,将 bean 装在一起,并根据请求分配 bean。 但它也提供:

  1. 解决文本消息的方法,包括对国际化的支持。
  2. 加载文件资源的通用方法。
  3. 注册为监听器的 bean 的事件。

ApplicationContext的三种常用实现是:

  1. ClassPathXmlApplicationContext:它从位于类路径中的 XML 文件中加载上下文定义,将上下文定义视为类路径资源。 使用代码从应用程序的类路径中加载应用程序上下文。

    ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);
    
  2. FileSystemXmlApplicationContext:它从文件系统中的 XML 文件加载上下文定义。 使用代码从文件系统中加载应用程序上下文。

    ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);
    
  3. XmlWebApplicationContext:它从 Web 应用程序中包含的 XML 文件中加载上下文定义。

6. 您可以通过多种方式将 Spring 配置到我们的应用程序中?

您可以通过 3 种方式在应用程序中配置 spring:

  1. 基于 XML 的配置
  2. 基于注解的配置
  3. 基于 Java 的配置

7. 什么是基于 Spring XML 的配置?

在 Spring 框架中,bean 需要的依赖项和服务在配置文件中指定,配置文件通常以 XML 格式。 这些配置文件通常以<beans>标记开头,并包含许多 bean 定义和特定于应用程序的配置选项。

Spring XML 配置的主要目标是使用 xml 文件配置所有 Spring 组件。
这意味着将不存在任何其他类型的 Spring 配置(例如注解或通过 Java 类进行的配置)。

Spring XML 配置使用 Spring 命名空间使配置中使用的 XML 标记集可用。 Spring 的主要名称空间包括:contextbeanjdbctxaopmvcaso

<beans>

	<!-- JSON Support -->
	<bean name="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
	<bean name="jsonTemplate" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>

	<bean id="restTemplate" class="org.springframework.web.client.RestTemplate"/>

</beans>

在下面仅配置DispatcherServlet的最简单的web.xml文件将为您的应用程序加载配置文件并为您配置运行时组件。

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <servlet>
		<servlet-name>spring</servlet-name>
			<servlet-class>
				org.springframework.web.servlet.DispatcherServlet
			</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>spring</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

</web-app>

8. 什么是基于 Spring Java 的配置?

Spring 的新 Java 配置支持中的主要工件是@Configuration注解的类和@Bean注解的方法。

@Bean注解用于指示方法实例化,配置和初始化要由 Spring IoC 容器管理的新对象。 @Bean注解与<bean/>元素具有相同的作用。

@Configuration注解一个类表示该类的主要目的是作为 Bean 定义的来源。 此外,@Configuration类允许通过简单地调用同一类中的其他@Bean方法来定义 Bean 之间的依赖关系。 最简单的@Configuration类如下:

@Configuration
public class AppConfig 
{
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

上面的 java 配置的等效 XML 配置为:

<beans>
    <bean id="myService" class="com.howtodoinjava.services.MyServiceImpl"/>
</beans>

要实例化此类配置,您将需要AnnotationConfigApplicationContext类的帮助。

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

要启用组件扫描,只需注解您的@Configuration类,如下所示:

@Configuration
@ComponentScan(basePackages = "com.howtodoinjava")
public class AppConfig  {
    ...
}

在上面的示例中,将扫描com.acme包,查找任何带有@Component注解的类,并将这些类注册为容器内的 Spring bean 定义。

如果在 Web 应用程序中使用上述配置,则将使用AnnotationConfigWebApplicationContext类。 在配置 Spring ContextLoaderListener Servlet 监听器,Spring MVC DispatcherServlet等时,可以使用此实现。

<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited fully-qualified @Configuration classes. Fully-qualified packages may also be specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.howtodoinjava.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.howtodoinjava.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

9. 什么是基于 Spring 注解的配置?

从 Spring 2.5 开始,可以使用注解配置依赖项注入。 因此,可以使用相关类,方法或字段声明上的注解,而不是使用 XML 来描述 bean 的连接,而可以将 bean 配置移入组件类本身。 注解注入在 XML 注入之前执行,因此对于通过两种方法连接的属性,后一种配置将覆盖前者。

默认情况下,Spring 容器中的注解装配未打开。 因此,在使用基于注解的连接之前,我们需要在 Spring 配置文件中启用它。 因此,如果您想在 Spring 应用程序中使用任何注解,请考虑使用以下配置文件。

<beans>

   <context:annotation-config/>
   <!-- bean definitions go here -->

</beans>

一旦配置了<context:annotation-config/>,就可以开始注解代码,以指示 Spring 应该自动将值连接到属性,方法和构造函数中。

在这种类型的配置中,很少使用的重要注解是:

  1. @Required@Required注解适用于 bean 属性设置器方法。
  2. @Autowired@Autowired注解可以应用于 bean 属性设置器方法,非设置器方法,构造函数和属性。
  3. @Qualifier@Qualifier注解与@Autowired一起使用,可以通过指定要连接的确切 bean 来消除混淆。
  4. JSR-250 注解:Spring 支持基于 JSR-250 的注解,包括@Resource@PostConstruct@PreDestroy注解。

10. 解释 Spring Bean 的生命周期吗?

易于理解的是 Spring bean 的生命周期。 实例化 Bean 时,可能需要执行一些初始化以使其进入可用状态。 同样,当不再需要 bean 并将其从容器中删除时,可能需要进行一些清理。

Spring bean 工厂负责管理通过 spring 容器创建的 bean 的生命周期。 bean 的生命周期由回调方法组成,这些方法可以大致分为两类

  1. 发布初始化回调方法
  2. 销毁前回调方法

Spring 框架提供了以下 4 种方法来控制 bean 的生命周期事件

  • InitializingBeanDisposableBean回调接口
  • 其他用于特定行为的Aware接口
  • Bean 配置文件中的自定义init()destroy()方法
  • @PostConstruct@PreDestroy注解

例如,customInit()customDestroy()方法是生命周期方法的示例。

<beans>
    <bean id="demoBean" class="com.howtodoinjava.task.DemoBean" init-method="customInit" destroy-method="customDestroy"></bean>
</beans>

阅读更多信息:SpringBean 生命周期

11. Spring Bean 范围有哪些不同?

可以在五个 bean 范围中创建 spring 容器中的 bean。 所有作用域名称都是不言自明的,但请明确说明它们,以免产生任何疑问。

  1. 单例:此 bean 范围是默认值,它强制容器在每个 spring 容器中只有一个实例,而不管您请求实例的时间是多少。 这种单例行为由 bean 工厂本身维护。
  2. 原型:该 bean 作用域只是反转单例作用域的行为,每次每次请求 bean 都产生一个新实例。
  3. 请求:在此 bean 作用域内,将为客户端的每个 Web 请求创建一个新的 bean 实例。 请求完成后,bean 将超出范围并被垃圾回收。
  4. 会话:就像请求范围一样,这确保了每个用户会话一个 bean 实例。 用户结束会话后,bean 不在范围内。
  5. 全局会话:全局会话是连接到 Portlet 应用程序的东西。 当您的应用程序在 Portlet 容器中工作时,它是由一定数量的 Portlet 构建的。 每个 portlet 都有其自己的会话,但是如果要在应用程序中为所有 portlet 全局存储变量,则应将其存储在全局回话中。 此作用域与基于 Servlet 的应用程序中的会话作用域没有任何特殊作用。

阅读更多:SpringBean 范围

12. 什么是 Spring Bean?

在 Spring 框架中,每当一个 bean 仅用于一个特定属性时,建议将其声明为一个内部 bean。 内部 bean 在 setter 注入property和构造函数注入constructor-arg中均受支持。

例如,假设我们引用了Person类的一个Customer类。 在我们的应用程序中,我们将只创建Person类的一个实例,并在Customer中使用它。

public class Customer 
{
	private Person person;

	//Setters and Getters
}

public class Person 
{
	private String name;
	private String address;
	private int age;

	//Setters and Getters
}

现在,内部 bean 声明将如下所示:

<bean id="CustomerBean" class="com.howtodoinjava.common.Customer">
	<property name="person">
		<!-- This is inner bean -->
		<bean class="com.howtodoinjava.common.Person">
			<property name="name" value="adminis"></property>
			<property name="address" value="India"></property>
			<property name="age" value="34"></property>
		</bean>
	</property>
</bean>

13. 在 Spring 框架中,单例 bean 线程安全吗?

对于单例 bean 的多线程行为,Spring 框架不做任何事情。 开发人员有责任处理单例 bean 的并发问题和 线程安全

实际上,大多数 SpringBean 没有可变状态(例如服务和 DAO 锁类),因此线程安全性很小。 但是如果您的 bean 处于可变状态(例如,查看模型对象),那么您需要确保线程安全。 解决此问题的最简单,最明显的方法是将可变 Bean 的范围从singleton更改为property

14. 如何在 Spring 中注入 Java 集合? 举个例子?

Spring 提供了四种类型的集合配置元素,如下所示:

  • <list>:这有助于装配,即注入值列表,允许重复。
  • <set>:这有助于装配一组值,但没有重复项。
  • <map>:这可以用于注入名称-值对的集合,其中名称和值可以是任何类型。
  • <prop>:这可用于注入名称-值对的集合,其中名称和值均为字符串。

我们来看每种类型的示例。

<beans>

   <!-- Definition for javaCollection -->
   <bean id="javaCollection" class="com.howtodoinjava.JavaCollection">

      <!-- java.util.List -->
      <property name="customList">
        <list>
           <value>INDIA</value>
           <value>Pakistan</value>
           <value>USA</value>
           <value>UK</value>
        </list>
      </property>

     <!-- java.util.Set -->
     <property name="customSet">
        <set>
           <value>INDIA</value>
           <value>Pakistan</value>
           <value>USA</value>
           <value>UK</value>
        </set>
      </property>

     <!-- java.util.Map -->
     <property name="customMap">

		<map>
           <entry key="1" value="INDIA"/>
           <entry key="2" value="Pakistan"/>
           <entry key="3" value="USA"/>
           <entry key="4" value="UK"/>
        </map>

      </property>

	  <!-- java.util.Properties -->
	<property name="customProperies">
		<props>
			<prop key="admin">admin@nospam.com</prop>
			<prop key="support">support@nospam.com</prop>
		</props>
	</property>

   </bean>

</beans>

15. 如何将java.util.Properties注入 Spring Bean?

第一种方法是使用<prop>标签,如下所示。

<bean id="adminUser" class="com.howtodoinjava.common.Customer">

	<!-- java.util.Properties -->
	<property name="emails">
		<props>
			<prop key="admin">admin@nospam.com</prop>
			<prop key="support">support@nospam.com</prop>
		</props>
	</property>

</bean>

您也可以使用util:命名空间从属性文件创建属性 bean,并使用 bean 引用进行 setter 注入。

<util:properties id="emails" location="classpath:com/foo/emails.properties" />

16. 解释 Spring Bean 自动装配吗?

在 spring 框架中,遵循配置文件中的 bean 依赖关系是一个好的做法,但是 spring 容器也能够自动装配协作 bean 之间的关系。 这意味着可以通过检查BeanFactory的内容来自动让 Spring 为您的 bean 解决协作者(其他 bean)。 自动装配 为每个 bean 指定,因此可以为某些 bean 启用,而其他 bean 将不会自动装配。

XML 配置文件的以下摘录显示了按名称自动装配的 Bean。

<bean id="employeeDAO" class="com.howtodoinjava.EmployeeDAOImpl" autowire="byName" />

除了 bean 配置文件中提供的自动装配模式外,还可以使用@Autowired注解在 bean 类中指定自动装配。 要在 bean 类中使用@Autowired注解,必须首先使用以下配置在 spring 应用程序中启用注解。

<context:annotation-config />

使用配置文件中的AutowiredAnnotationBeanPostProcessor bean 定义可以实现相同的目的。

<bean class ="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>

现在,启用注解配置后,您可以使用@Autowired随意地自动绑定 bean 依赖项。

@Autowired
public EmployeeDAOImpl ( EmployeeManager manager ) {
    this.manager = manager;
}

17. 解释 Bean 自动装配的不同模式吗?

Spring 框架中有五种自动装配模式。 让我们一一讨论。

  1. off:该选项是 spring 框架的默认选项,表示自动装配为 OFF。 您必须使用 bean 定义中的标签显式设置依赖项。
  2. byName:此选项启用基于 bean 名称的依赖项注入。 在 Bean 中自动装配属性时,属性名称用于在配置文件中搜索匹配的 Bean 定义。 如果找到这样的 bean,则将其注入属性。 如果找不到这样的 bean,则会引发错误。
  3. byType:此选项启用基于 bean 类型的依赖项注入。 在 bean 中自动装配属性时,属性的类类型用于在配置文件中搜索匹配的 bean 定义。 如果找到这样的 bean,则将其注入属性。 如果找不到这样的 bean,则会引发错误。
  4. constructor:构造函数自动装配与byType相似,但适用于构造函数参数。 在启用自动装配的 bean 中,它将查找构造函数参数的类类型,然后按类型对所有构造函数参数进行自动装配。 请注意,如果容器中没有一个完全属于构造函数参数类型的 bean,则会引发致命错误。
  5. autodetect:通过自动检测进行自动装配使用两种模式之一,即构造函数或byType模式。 首先它将尝试寻找带有参数的有效构造函数,如果找到,则选择构造函数模式。 如果在 bean 中没有定义构造函数,或者存在显式的默认 no-args 构造函数,则选择byType自动装配模式。

18. 如何打开基于注解的自动装配?

要启用@Autowired,您必须注册AutowiredAnnotationBeanPostProcessor,并且可以通过两种方式进行。

1.在 bean 配置文件中包含<context:annotation-config >

<beans>
	<context:annotation-config />
</beans>

2.将AutowiredAnnotationBeanPostProcessor直接包含在 bean 配置文件中。

<beans>
	<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
</beans>

19. 用示例解释@Required注解?

在生产规模的应用程序中,可能在 IoC 容器中声明了成百上千个 bean,并且它们之间的依赖关系通常非常复杂。 setter 注入的缺点之一是,您很难检查是否已设置所有必需的属性。 为了解决这个问题,您可以设置<bean>依赖项检查属性,并设置四个属性之一,即无,简单,对象或全部(默认为无)。

在现实生活中的应用程序中,您将不需要检查上下文文件中配置的所有 Bean 属性。 相反,您只想检查是否仅在某些特定的 Bean 中设置了特定的属性集。 Spring 使用依赖项检查属性的依赖项检查功能在这种情况下将无法为您提供帮助。 因此,要解决此问题,可以使用@Required注解。

要在类文件中的 bean 属性的 setter 方法上使用@Required注解,如下所示:

public class EmployeeFactoryBean extends AbstractFactoryBean<Object>
{
    private String designation;

    public String getDesignation() {
        return designation;
    }

    @Required
    public void setDesignation(String designation) {
        this.designation = designation;
    }

    //more code here
}

RequiredAnnotationBeanPostProcessor是一个 Spring bean 后处理器,用于检查是否已设置所有带有@Required注解的 bean 属性。 要启用此 bean 后处理器进行属性检查,必须在 Spring IoC 容器中注册它。

<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor" />

如果尚未设置任何带有@Required的属性,则此 bean 后处理器将抛出BeanInitializationException

20. 用示例解释@Autowired注解?

@Autowired注解提供了在何处以及如何实现自动装配的更细粒度的控制。 @Autowired注解可用于在 setter 方法上自动装配 bean,就像@Required注解,构造函数,具有任意名称和/或多个参数的属性或方法一样。

例如。 您可以在 setter 方法上使用@Autowired注解,以摆脱 XML 配置文件中的<property>元素。 当 Spring 找到用于 setter 方法的@Autowired注解时,它将尝试通过在方法上键入自动装配来执行

您也可以将@Autowired应用于构造函数。 构造函数@Autowired注解指示即使在 XML 文件中配置 Bean 时不使用<constructor-arg>元素,创建该 Bean 时也应自动构造该构造函数。

public class TextEditor {
   private SpellChecker spellChecker;

   @Autowired
   public TextEditor(SpellChecker spellChecker){
      System.out.println("Inside TextEditor constructor." );
      this.spellChecker = spellChecker;
   }

   public void spellCheck(){
      spellChecker.checkSpelling();
   }
}

而且它的配置没有构造函数参数。

<beans>

   <context:annotation-config/>

   <!-- Definition for textEditor bean without constructor-arg -->
   <bean id="textEditor" class="com.howtodoinjava.TextEditor">
   </bean>

   <!-- Definition for spellChecker bean -->
   <bean id="spellChecker" class="com.howtodoinjava.SpellChecker">
   </bean>

</beans>

21. 用示例解释@Qualifier注解?

@Qualifier表示哪个 bean 可以在字段上自动装配。 如果 Spring 否则无法使用@Qualifier注解消除 Bean 引用的歧义。

参见下面的示例,它将自动将person bean 连接到客户的person属性中。

public class Customer 
{
	@Autowired
	private Person person;
}

对于Person类,我们有两个 bean 定义。

<bean id="customer" class="com.howtodoinjava.common.Customer" />

<bean id="personA" class="com.howtodoinjava.common.Person" >
	<property name="name" value="lokesh" />
</bean>

<bean id="personB" class="com.howtodoinjava.common.Person" >
	<property name="name" value="alex" />
</bean>

Spring 会知道应该自动装配哪个人的 bean? 没有。 在上面的示例中运行时,它会在下面的异常中命中:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: 
	No unique bean of type [com.howtodoinjava.common.Person] is defined: 
		expected single matching bean but found 2: [personA, personB]

要解决上述问题,您需要@Quanlifier告诉 Spring 应该自动装配哪个 bean。

public class Customer 
{
	@Autowired
	@Qualifier("personA")
	private Person person;
}

22. 构造函数注入和 setter 注入之间的区别?

请在下面找到明显的区别:

  1. 在 Setter 注入中,可能会部分注入依赖项,这意味着如果我们有 3 个依赖项(如intstringlong),那么如果使用 setter 注入,则不必注入所有值。 如果您不注入,它将采用这些原语的默认值。 在构造函数注入中,不可能部分注入依赖项,因为调用构造函数时必须正确传递所有参数,否则,可能会出错。
  2. 如果我们为同一属性编写 setter 和构造函数注入,则 Setter 注入将覆盖构造函数注入值。 但是,构造函数注入不能覆盖 setter 注入的值。 显而易见,因为首先要调用构造函数才能创建实例。
  3. 使用 setter 注入,您不能保证是否注入了某些依赖关系,这意味着您可能拥有不完全依赖关系的对象。 另一切面,在准备好依赖项之前,构造函数注入不允许您构造对象。
  4. 在构造函数注入中,如果对象 A 和 B 相互依赖,即 A 依赖于 B,反之亦然,则 Spring 在创建 A 和 B 的对象时会抛出ObjectCurrentlyInCreationException,因为在创建 B 之前无法创建对象,反之亦然 。 因此 spring 可以通过 setter 注入解决循环依赖关系,因为对象是在调用 setter 方法之前构造的。

23. Spring 框架中有哪些不同类型的事件?

Spring 的ApplicationContext提供了在代码中支持事件和监听器的功能。 我们可以创建 bean 来监听通过ApplicationContext发布的事件。 通过ApplicationEvent类和ApplicationListener接口提供ApplicationContext中的事件处理。 因此,如果 bean 实现ApplicationListener,则每次ApplicationEvent发布到ApplicationContext时,都会通知该 bean。

public class AllApplicationEventListener implements ApplicationListener < ApplicationEvent > 
{
    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) 
	{
        //process event
    }
}

Spring 提供以下 5 个标准事件

  1. ContextRefreshedEvent:在初始化或刷新ApplicationContext时,将发布此事件。 也可以使用ConfigurableApplicationContext接口上的refresh()方法来提高它。
  2. ContextStartedEvent:使用ConfigurableApplicationContext接口上的start()方法启动ApplicationContext时,将发布此事件。 您可以轮询数据库,也可以在收到此事件后重新/启动任何已停止的应用程序。
  3. ContextStoppedEvent:当使用ConfigurableApplicationContext接口上的stop()方法停止ApplicationContext时,将发布此事件。 收到此活动后,您可以进行必要的整理工作。
  4. ContextClosedEvent:当使用ConfigurableApplicationContext接口上的close()方法关闭ApplicationContext时,将发布此事件。 关闭的环境已经到了生命的尽头。 它不能刷新或重新启动。
  5. RequestHandledEvent:这是一个特定于 Web 的事件,告诉所有 Bean HTTP 请求已得到服务。

除上述之外,您可以通过扩展ApplicationEvent类来创建自己的自定义事件。 例如

public class CustomApplicationEvent extends ApplicationEvent 
{ 
	public CustomApplicationEvent ( Object source, final String msg ) 
	{
		super(source);
		System.out.println("Created a Custom event");
	}
}

要监听此事件,请创建如下监听器:

public class CustomEventListener implements ApplicationListener < CustomApplicationEvent > 
{
	@Override
    public void onApplicationEvent(CustomApplicationEvent applicationEvent) {
		//handle event
	}
}

要发布此事件,您将需要ApplicationContext实例的帮助。

CustomApplicationEvent customEvent = new CustomApplicationEvent( applicationContext, "Test message" );
applicationContext.publishEvent ( customEvent ); 

24. FileSystemResourceClassPathResource之间的区别?

FileSystemResource中,您需要提供spring-config.xml(Spring 配置)文件相对于您的项目的路径或文件的绝对位置。

ClassPathResource中,spring 使用ClassPath查找文件,因此spring-config.xml应该包含在类路径中。 如果src中有spring-config.xml,则由于src默认情况下位于类路径中,因此我们只能给出其名称。

一句话,ClassPathResource在类路径中查找,而FileSystemResource在文件系统中查找。

25. 举出 Spring 框架中使用的一些设计模式吗?

有很多使用不同设计模式的负载,但是有一些显而易见的负载:

  • 代理-在 AOP 和远程调用中大量使用。
  • 单例 – Spring 配置文件中定义的 bean 默认为单例。
  • 模板方法 – 广泛用于处理样板重复代码,例如RestTemplateJmsTemplateJpaTemplate
  • 前端控制器 – Spring 提供DispatcherServlet以确保传入的请求被分派到您的控制器。
  • 视图助手 -Spring 具有许多自定义 JSP 标签和速度宏,以帮助将代码与视图中的表示分离。
  • 依赖项注入 – 以整个BeanFactory / ApplicationContext概念为中心。
  • 工厂模式-用于创建对象实例的BeanFactory

阅读更多:编写 Spring 配置文件的最佳实践

请分享最新的 Spring 面试问题,您可能在任何面试中都遇到过这些问题。

学习愉快!

参考文献:

Spring beans 文档
Spring 事务
Spring AOP 官方指南

编写配置文件的 13 个 Spring 最佳实践

原文: https://howtodoinjava.com/best-practices/13-best-practices-for-writing-spring-configuration-files/

Spring 是一个功能强大的框架,它使用多种配置选项。 它的最佳功能是为称为 bean 的纯 Java 对象(PO​​JO)提供企业服务。 Spring 使用依赖项注入(DI)来简化并提高可测试性。

Spring Bean,依赖项和 Bean 所需的服务在 xml 配置文件或注解中指定。 但是,XML 配置文件是冗长且更干净的。 如果未正确计划和编写,在大型项目中将变得非常难以管理。

在本文中,我将向您展示 13 个最佳实践,用于编写 spring XML 配置。 其中一些似乎是最佳实践而不是最佳实践,但我将它们包含在此处是因为它们与该主题高度相关。

注意:其他一些因素(例如应用程序设计)可能会影响 XML 配置决策,但是我主要关注 XML 配置的可读性和可维护性。

让我们详细讨论上面的每一个,以使其更有意义。

1)在每个配置文件中添加标题注解

我总是更加强调代码注解。 配置文件也是如此。 添加配置文件标头总是非常有帮助的,该标头总结了配置文件中定义的 bean /属性。

在 Spring 配置中,您可以像添加 XML 注解一样添加注解,也可以使用description元素。 例如:

applicationContext.xml

<beans>
	<description>
		This configuration file will have all beans 
		which may be used for controlling transactions.
	</description>
	...
</beans>

使用description标签的一个可能的优点是,某些工具可能会从此元素中获取描述,以在其他地方为您提供帮助。

2)使用一致的命名约定

在所有配置文件中使用相同的命名是非常重要的。 在整个项目中使用清晰,描述性和一致的名称约定,可以提高配置文件的可读性,并使其他开发人员可以轻松避免一些偶然的错误。

例如,对于 bean ID,可以遵循 Java 类字段名称约定。 EmployeeUpdateDAO实例的 bean ID 将是employeeUpdateDAO。 对于大型项目,可以将包名称添加为 Bean ID 的前缀。 例如 finance.employeeUpdateDAO

3)模式引用中没有版本号

我在之前的文章中也指出了此功能。 我再次将其包括在内,因为从长远来看,它对于改善可维护性至关重要且有益。 要刷新您的内存,根本不需要在 bean 配置文件中为引用的模式指定版本号,您可以忽略它。 如果确实如此,您应该一直忽略它。

Spring 自动从项目依赖项(jar)中选择可用的最高版本。 另外,随着项目的发展和 Spring 版本的更新,我们不必维护所有 XML 配置文件即可看到新功能。

阅读更多: Spring 版本较少的架构引用

一个示例示例将如下所示:

applicationContext.xml

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

  <!-- Other bean definitions-->

</beans>

4)优先使用 setter 注入而不是构造方法注入

Spring 提供了三种类型的依赖项注入:constructor注入,setter注入和method注入。 通常,我们都只使用前两种类型。

applicationContext.xml

<!-- Constructor injection -->
<bean id="employeeDAO"	class="com.howtodoinjava.dao.EmployeeDAO">
	<constructor-arg ref="datasource"/>
</bean>

<!-- Setter injection -->
<bean id="employeeDAO" class="com.howtodoinjava.dao.EmployeeDAO">
	<property name="datasource"	ref="datasource">
</bean>

构造函数注入可以提供最便宜的线程安全,即不可变对象。 此外,它还可以确保没有完成初始化就不会将对象移交给其他 bean。

塞特特注入剂提供了非常理想的功能,即灵活性或可维护性。 如果在 bean 中要设置多个属性,那么为构造函数创建一长串参数不是一个好主意。 同样,如果可能的话,某些属性可能是可选的。

更喜欢灵活性。 为了保持不变或线程安全,请遵循其他编程规则。

阅读更多:如何使 Java 类不可变

5)在构造函数注入中将类型优先于索引以进行构造函数参数匹配

最好避免使用constructor注入,而更喜欢使用setter注入进行依赖项注入。 但是,如果绝对需要使用constructor注入,则总是更喜欢基于类型而不是索引的参数匹配。

applicationContext.xml

<!-- Index based constructor injection -->
<bean id="employeeDAO" class="com.howtodoinjava.EmployeeDAO">
	<constructor-arg index="0" value="rest"/>
	<constructor-arg index="1" value="8080"/>
</bean>

<!-- Type based constructor injection -->
<bean id="employeeDAO" class="com.howtodoinjava.EmployeeDAO">
	<constructor-arg type="java.lang.String" value="rest"/>
	<constructor-arg type="int" value="8080"/>
</bean>

如您所见,基于类型的参数传递更具可读性,并且不易出错。 但是,只要基于类型的参数传递有任何歧义,就可以毫不犹豫地转到基于索引的参数传递。

6)在扩展形式上使用快捷方式形式

Spring bean 配置语义允许两种形式来指定属性值和其他 bean 引用。 一种是扩展形式,另一种是较短形式。 最好使用较短的版本。

applicationContext.xml

<!-- Expanded version -->
<bean id="employeeDAO" class="com.howtodoinjava.dao.EmployeeDAO">
	<property name="datasource">
		<ref bean="datasource"></ref>
		<value>datasource</value>
	 </property>
</bean>

<!-- Shorter/shortcut version -->
<bean id="employeeDAO" class="com.howtodoinjava.dao.EmployeeDAO">
	<property name="datasource"	ref="datasource" value="datasource">
</bean>

7)尽可能重用 bean 定义

Spring 提供了非常有用的功能,您应该在项目中广泛使用它,即 bean 定义可重用性。 在这里,我不是在谈论用于 setter 注入的 bean 参考。 而是我指出了在构造其他 bean 时重用 bean 定义。

以数据源定义的示例为例:

applicationContext.xml

<bean id="abstractDataSource" class="org.apache.commons.dbcp.BasicDataSource" 
	destroy-method="close"
	p:driverClassName="${jdbc.driverClassName}"
	p:username="${jdbc.username}"
	p:password="${jdbc.password}" />

<bean id="concreteDataSourceOne"
	parent="abstractDataSource"
	p:url="${jdbc.databaseurlOne}"/>

<bean id="concreteDataSourceTwo"
	parent="abstractDataSource"
	p:url="${jdbc.databaseurlTwo}"/>

阅读更多: Spring AbstractRoutingDataSource 示例

8)始终使用id作为 bean 标识符

Spring 允许 bean 使用两种类型的标识符。 使用属性idname。 您应该始终选择属性 ID 而不是名称。 通常,它既不会增加可读性,也不会增加任何性能。 这是行业标准的做法,所有其他开发人员都在全世界甚至在您的团队中遵循。

只是不要在这里是个奇怪的人。

9)尽量避免自动装配

如果可以长期管理,自动装配是一个很棒的功能。 通常,如果您的项目只有很少的 bean,并且几乎可以在内存中记住它们,那将是有益的。

一旦项目变大,自动装配就开始在确定要使用的正确依赖项切面造成麻烦。 我发现主要的缺点是无法将整个系统绑定在一起。 这是 spring 配置文件获胜的地方。 他们可以在几分钟之内将整个系统呈现给任何新手。

另外,当您开始调试一些复杂的问题时,所有在配置文件中的所有信息实际上都会有很大帮助。 自动装配使调试更加困难。

了解更多:Spring 自动装配

10)始终使用classpath前缀

导入资源,XML 配置,属性等时,请始终使用classpath:classpath*:前缀。 这提供了资源位置的一致性和清晰度。 并非 Spring 的每个功能都具有相同的行为,classpath:保证一致性

类路径由构建工具和 IDE 确定。 对于 Java 代码,通常为src/main/java,对于非 Java 依赖项和测试通常为src/main/resources,对于 Java 代码为src/test/java,对于非 Java 资源为src/test/resources

applicationContext.xml

<!-- Always use classpath: prefix-->
<import resource="classpath:/META-INF/spring/applicationContext-security.xml"/>

11)总是外部化属性

通常,存在与应用程序运行时间相关的多个配置参数。 它们被传递到 bean 配置上下文文件中的 bean 定义。 不要将它们硬编码在配置文件中。 而是将它们外部化到某些属性文件。

最好根据它们的用途或模块将它们分组在单独的文件中,即jdbc.properties文件中所有与 JDBC 数据源相关的属性。

applicationContext.xml

<bean id="abstractDataSource" class="org.apache.commons.dbcp.BasicDataSource" 
        destroy-method="close"
        p:driverClassName="${jdbc.driverClassName}"
        p:username="${jdbc.username}"
        p:password="${jdbc.password}" />

和属性文件是:

jdbc.properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=password

12)在开发阶段使用“依赖检查”

您应该将 Bean 定义上的dependency-check属性设置为simpleobjectsall(默认值为none,即不检查),以便容器可以为您执行显式依赖项验证。 当必须显式地或通过自动装配来设置 Bean 的所有属性(或某些类别的属性)时,这很有用。

applicationContext.xml

<bean id="abstractDataSource" class="org.apache.commons.dbcp.BasicDataSource" 
	destroy-method="close"
	p:driverClassName="${jdbc.driverClassName}"
	p:username="${jdbc.username}"
	p:password="${jdbc.password}" 
	dependency-check="all" />

在上面给出的示例中,容器将确保在应用程序初始化时间本身中设置数据源的所有属性/参数。

13)不要滥用/滥用依赖注入

最后,请不要滥用引入依赖注入的动机。

Java 提供new关键字来创建新对象。 在不需要 DI 的地方使用此精彩的关键字,例如 DTO 对象。 不要尝试玩得更聪明。 只需遵循基础知识即可。

如果您对以上几点有异议,请给我评论。

学习愉快!

SpringBoot 2

SpringBoot 教程

原文: https://howtodoinjava.com/spring-boot-tutorials/

Spring Boot 是一个 Spring 框架模块,为 Spring 框架提供 RAD(快速应用程序开发)功能。 它高度依赖入门模板功能,该功能非常强大且可以完美运行。

Spring boot modules

Spring boot 模块

1. 什么是入门模板?

Spring Boot 启动器是模板,其中包含启动特定功能所需的所有相关传递依赖项的集合。 例如,如果要创建 Spring WebMVC 应用程序,则在传统设置中,您自己会包含所有必需的依赖项。 这就留下了版本冲突的机会,这最终会导致更多运行时异常

使用 Spring boot,要创建 MVC 应用程序,只需导入spring-boot-starter-web依赖项。

pom.xml

<!-- Parent pom is mandatory to control versions of child dependencies -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.6.RELEASE</version>
    <relativePath />
</parent>

<!-- Spring web brings all required dependencies to build web application. -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

spring-boot-starter-web依赖关系之上,内部导入所有给定的依赖关系并将其添加到您的项目中。 请注意,某些依赖关系是直接的,某些依赖关系进一步引用了其他入门模板,这些模板可过渡地下载更多的依赖关系。

另外,请注意不需要将版本信息提供到子依赖项中。 相对于父级启动器的版本,已解决所有版本(在我们的示例中为2.0.4.RELEASE)。

Dependencies brought in by webmvc starter template

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-json</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-tomcat</artifactId>
	</dependency>
	<dependency>
		<groupId>org.hibernate.validator</groupId>
		<artifactId>hibernate-validator</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
	</dependency>
</dependencies>

阅读更多: Spring Boot 入门模板列表

2. Spring Boot 自动配置

通过@EnableAutoConfiguration注解启用自动配置。 Spring Boot 自动配置扫描类路径,在类路径中找到库,然后尝试为它们猜测最佳配置,最后配置所有此类 bean。

自动配置会尝试尽可能智能化,并且在您定义更多自己的配置时会自动退出。

自动配置始终在注册用户定义的 bean 之后应用。

Spring Boot 自动配置逻辑在spring-boot-autoconfigure.jar中实现。 Yoy 可以在此处验证软件包的列表。

Spring boot autoconfiguration packages

Spring boot 自动配置包

例如,查看 Spring AOP 的自动配置。 它执行以下操作

  1. 扫描类路径以查看是否存在EnableAspectJAutoProxyAspectAdviceAnnotatedElement类。
  2. 如果不存在类,则不会为 Spring AOP 进行自动配置。
  3. 如果找到了类,则使用 Java config 注解@EnableAspectJAutoProxy配置 AOP。
  4. 它检查属性spring.aop的值可以是truefalse
  5. 基于属性的值,设置proxyTargetClass属性。

AopAutoConfiguration.java

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
		AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration 
{

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
	public static class JdkDynamicAutoProxyConfiguration {

	}

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
	public static class CglibAutoProxyConfiguration {

	}

}

3. 嵌入式服务器

SpringBoot 应用程序始终将 tomcat 作为嵌入式服务器依赖项。 这意味着您可以从命令提示符运行 Spring Boot 应用程序,而无需使用复杂的服务器基础结构。

您可以根据需要排除 tomcat,并包括任何其他嵌入式服务器。 或者,您可以完全排除服务器环境。 全部基于配置。

例如,以下配置排除了 tomcat包括了 Jetty作为嵌入式服务器。

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

4. 运行应用程序

运行应用程序,我们需要使用@SpringBootApplication注解。 在幕后,它们相当于@Configuration@EnableAutoConfiguration@ComponentScan

它可以扫描配置类,文件并将它们加载到 spring 上下文中。 在下面的示例中,执行从main()方法开始。 它开始加载所有配置文件,对其进行配置,然后根据/resources文件夹中application.properties文件中的应用程序属性运行应用程序。

MyApplication.java

@SpringBootApplication
public class MyApplication 
{
    public static void main(String[] args) 
    {
        SpringApplication.run(Application.class, args);
    }
}

application.properties

### Server port #########
server.port=8080

### Context root ########
server.contextPath=/home

要执行该应用程序,可以从 IDE 中运行main()方法,例如 eclipse ,或者可以构建 jar 文件并从命令提示符下执行。

Console

$ java -jar spring-boot-demo.jar

5. Spring Boot 的优点

  • Spring Boot 帮助解决依赖冲突。 它标识所需的依赖项并为您导入它们。
  • 对于所有依赖项,它都具有兼容版本的信息。 它最大程度地减少了运行时类加载器问题。
  • 它的“预设默认配置”方法可帮助您配置幕后最重要的部分。 仅在需要时才覆盖它们。 否则,一切都会完美地进行。 它有助于避免样板代码,注解和 XML 配置。
  • 它提供了嵌入式 HTTP 服务器 Tomcat,因此您可以快速进行开发和测试。
  • 它与 eclipse 和智能想法等 IDE 具有出色的集成。

6. Spring Boot 配置

  1. Spring Boot 注解
  2. Spring Boot 登录指南
  3. Spring Boot – Spring Boot 入门模板
  4. Spring Boot – 入门级父项依赖项
  5. Spring Boot – 获取所有已加载的 bean
  6. Spring Boot – @SpringBootApplication和自动配置
  7. Spring Boot – 更改应用程序根目录
  8. Spring Boot – 配置 Jetty 服务器
  9. Spring Boot – Tomcat 默认端口
  10. Spring Boot – WAR 打包示例
  11. Spring Boot – 日志和 yml 配置
  12. Spring Boot – 日志和属性配置
  13. Spring Boot – SSL(https)
  14. Spring Boot – CommandLineRunner
  15. Spring Boot – 开发人员工具模块教程
  16. Spring Boot – 在@Bean@Compoment中注入应用程序参数
  17. Spring Boot 嵌入式服务器日志
  18. Spring Boot 嵌入式 tomcat 配置

7. 开发 REST API 和 SOAP Web 服务

  1. Spring Boot – REST API
  2. Spring Boot – Jersey
  3. Spring Boot – Spring HATEOAS 示例
  4. Spring Boot – 请求验证 REST API
  5. Spring Boot – 基于角色的安全性
  6. Spring Boot – SOAP Web 服务
  7. Spring Boot – SOAP 客户端
  8. Spring Boot 2 和 ehcache 3 示例

8. 其他有用的话题

  1. 在启动时禁用横幅
  2. Spring Boot – JSP 视图
  3. Spring Boot – 自定义PropertyEditor
  4. Spring Boot – @EnableScheduling
  5. Spring Boot – JMSTemplate
  6. Spring Boot – RSS / ATOM Feed
  7. Spring Boot 读取资源文件夹读取文件
  8. Spring Boot 面试问题

学习愉快!

spring-boot-starter-parent示例

https://howtodoinjava.com/spring-boot2/spring-boot-starter-parent-dependency/

在这个 Spring Boot 教程中,我们将学习spring-boot-starter-parent依赖关系,该依赖关系在内部由所有 Spring Boot 依赖关系使用。 我们还将学习此依赖项提供的所有配置以及如何覆盖它们。

什么是spring-boot-starter-parent依赖项?

spring-boot-starter-parent依赖项是父 POM,它为基于 Spring Boot 的应用程序提供依赖项和插件管理。 它包含要使用的默认 Java 版本,Spring Boot 使用的默认依赖版本以及 Maven 插件的默认配置。

该文件提供的一些重要配置如下。 请参考此链接以阅读完整的配置。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd;
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-dependencies</artifactId>
		<version>${revision}</version>
		<relativePath>../../spring-boot-dependencies</relativePath>
	</parent>
	<artifactId>spring-boot-starter-parent</artifactId>
	<packaging>pom</packaging>
	<name>Spring Boot Starter Parent</name>
	<description>Parent pom providing dependency and plugin management for applications
		built with Maven</description>
	<properties>
		<java.version>1.8</java.version>
		<resource.delimiter>@</resource.delimiter> <!-- delimiter that doesn't clash with Spring ${} placeholders -->
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<maven.compiler.source>${java.version}</maven.compiler.source>
		<maven.compiler.target>${java.version}</maven.compiler.target>
	</properties>

	...

	<resource>
		<directory>${basedir}/src/main/resources</directory>
		<filtering>true</filtering>
		<includes>
			<include>**/application*.yml</include>
			<include>**/application*.yaml</include>
			<include>**/application*.properties</include>
		</includes>
	</resource>

</project>

spring-boot-starter-parent依赖项还继承自spring-boot-dependencies,该定义在上述 POM 文件的顶部,行号:9。

该文件是实际文件,其中包含要用于所有库的默认版本的信息。 以下代码显示了spring-boot-dependencies中配置的各种依赖项的不同版本:

pom.xml

<properties>
	<!-- Dependency versions -->
	<activemq.version>5.15.3</activemq.version>
	<antlr2.version>2.7.7</antlr2.version>
	<appengine-sdk.version>1.9.63</appengine-sdk.version>
	<artemis.version>2.4.0</artemis.version>
	<aspectj.version>1.8.13</aspectj.version>
	<assertj.version>3.9.1</assertj.version>
	<atomikos.version>4.0.6</atomikos.version>
	<bitronix.version>2.1.4</bitronix.version>
	<byte-buddy.version>1.7.11</byte-buddy.version>
	<caffeine.version>2.6.2</caffeine.version>
	<cassandra-driver.version>3.4.0</cassandra-driver.version>
	<classmate.version>1.3.4</classmate.version>

	...
	...
</properties>

上面的列表很长,您可以在此链接中阅读完整的列表。

如何覆盖默认依赖项版本?

如您所见,spring boot 具有用于大多数依赖项的默认版本。 您可以在项目的pom.xml文件中的properties标签中覆盖您选择或项目需要的版本。

例如 SpringBoot 使用默认版本的 Google GSON 库作为2.8.2.

<groovy.version>2.4.14</groovy.version>
<gson.version>2.8.2</gson.version>
<h2.version>1.4.197</h2.version>

我想使用 gson 依赖项的2.7。 因此,我将在属性标签中提供此类信息。

<properties>
	<java.version>1.8</java.version>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<gson.version>2.7</gson.version>
</properties>

现在,在 Eclipse 编辑器中,您将看到以下消息:托管版本为 2.7。工件在org.springframework.boot:spring-boot-dependencies:2.0.0.RELEASE中进行管理。

GSON resolved dependency

GSON resolved dependency

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

学习愉快!

spring-boot-starter Maven 模板

原文: https://howtodoinjava.com/spring-boot2/spring-boot-starter-templates/

不久之前,随着库及其依赖项的数量呈指数级增长,依赖项管理已成为一项非常复杂的任务,需要大量的技术专家才能正确地完成它。 通过引入 String boot 启动器模板,如果您想在项目中使用任何流行的库,则可以在确定要在项目中使用的正确依赖项切面获得大量帮助。

Spring Boot 带有 50 多个不同的启动器模块,这些模块为许多不同的框架提供了现成的集成库,例如关系和 NoSQL 的数据库连接,Web 服务,社交网络集成,监视库,日志记录,模板渲染 ,并且列表一直在继续。

入门模板如何工作?

Spring Boot 启动器是模板,其中包含启动特定功能所需的所有相关传递依赖项的集合。 每个启动器都有一个特殊的文件,其中包含 Spring 提供的所有已提供依赖项的列表。

这些文件可以在相应的启动程序模块的pom.xml文件中找到。 例如

可以在 github 中找到spring-boot-starter-data-jpa入门 pom 文件。

这告诉我们,通过在我们的构建中包含spring-boot-starter-data-jpa作为依赖项,我们将自动获得spring-ormhibernate-entity-managerspring-data-jpa。 这些库将为我们提供开始编写 JPA / DAO 代码的所有基本知识。

因此,下次当您要为项目提供任何特定功能时,我建议您检查现有的入门模板,以查看是否可以直接使用它。 持续不断的社区添加一直在进行,因此此列表已经在增长,您也可以为它做出贡献。

流行的模板及其传递依赖

我列出了一些经常使用的 SpringBoot 器以及它们带来的依赖关系,仅供参考。

启动器 依赖项
spring-boot-starter Sspring-boot, spring-context, spring-beans
spring-boot-starter-jersey jersey-container-servlet-core, jersey-container-servlet, jersey-server
spring-boot-starter-actuator spring-boot-actuator, micrometer-core
spring-boot-starter-aop spring-aop, aspectjrt, aspectjweaver
spring-boot-starter-data-rest spring-hateoas, spring-data-rest-webmvc
spring-boot-starter-hateoas spring-hateoas
spring-boot-starter-logging logback-classic, jcl-over-slf4j, jul-to-slf4j
spring-boot-starter-log4j2 log4j2, log4j-slf4j-impl
spring-boot-starter-security spring-security-web, spring-security-config
spring-boot-starter-test spring-test, spring-boot,junit,mockito, hamcrest-library, assertj, jsonassert, json-path
spring-boot-starter-web-services spring-ws-core

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

学习愉快!

参考文献:

SpringBoot 启动器

使用启动启动器

Spring Boot 多模块 Maven 项目示例

原文: https://howtodoinjava.com/spring-boot2/sb-multi-module-maven-project/

学习创建具有多个模块的 spring boot 项目。 父项目将作为基础 Maven 配置的容器。 子模块将是实际的SpringBoot项目 – 继承父项目的 maven 属性。

1. Spring Boot Maven 父项目

父项目是单点触发所有模块的构建过程,并可选地生成可部署的程序包(例如,ear,war 等)。 它的pom.xml文件包含所有模块的列表以及子项目继承的常见依赖项和属性。

在创建 spring boot 项目时,我们将需要添加spring-boot-starter-parent依赖项。 它是父 POM,它为基于 Spring Boot 的应用程序提供依赖关系和插件管理。

它包含要使用的 Java 的默认版本,Spring Boot 使用的默认依赖版本以及 Maven 插件的默认配置

Console

$ mvn archetype:generate -DgroupId=com.howtodoinjava
                        -DartifactId=HelloWorldApp
                        -DarchetypeArtifactId=maven-archetype-quickstart
                        -DinteractiveMode=false

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.howtodoinjava</groupId>
	<artifactId>HelloWorldApp</artifactId>
	<packaging>pom</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>HelloWorldApp</name>
	<url>http://maven.apache.org</url>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>

	<modules>
		<module>HelloWorldApp-ear</module>
		<module>HelloWorldApp-service</module>
		<module>HelloWorldApp-rws</module>
	</modules>

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

</project>

2. 子模块 – EAR,WAR,JAR

子模块可以是任何项目,也可以创建任何包装。 我们可以在模块之间创建任何种类的依赖关系,也可以将它们捆绑在一起。 这是非常具体的个人要求。

有时,我们将创建一个 ear 文件,一个 war 文件和一个 jar 文件。 Jar 文件捆绑到 war 文件中,war 文件捆绑到 ear 文件中。 EAR 文件是要部署到应用程序服务器上的最终软件包。

Console

$ cd HelloWorldApp

$ mvn archetype:generate -DgroupId=com.howtodoinjava
                        -DartifactId=HelloWorldApp-ear
                        -DarchetypeArtifactId=maven-archetype-quickstart
                        -DinteractiveMode=false

$ mvn archetype:generate -DgroupId=com.howtodoinjava
                        -DartifactId=HelloWorldApp-service
                        -DarchetypeArtifactId=maven-archetype-quickstart
                        -DinteractiveMode=false

$ mvn archetype:generate -DgroupId=com.howtodoinjava
                        -DartifactId=HelloWorldApp-rws
                        -DarchetypeArtifactId=maven-archetype-webapp
                        -DinteractiveMode=false

请根据需要添加第三方库和依赖关系。

2.1. JAR 包

pom.xml

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

	<parent>
		<groupId>com.howtodoinjava</groupId>
		<artifactId>HelloWorldApp</artifactId>
		<version>1.0-SNAPSHOT</version>
	</parent>

	<artifactId>HelloWorldApp-service</artifactId>
	<name>HelloWorldApp-service</name>
	<url>http://maven.apache.org</url>

	<dependencies>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
	</dependencies>
</project>

2.2. WAR 包

pom.xml

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

	<parent>
		<groupId>com.howtodoinjava</groupId>
		<artifactId>HelloWorldApp</artifactId>
		<version>1.0-SNAPSHOT</version>
	</parent>

	<artifactId>HelloWorldApp-rws</artifactId>
	<packaging>war</packaging>
	<name>HelloWorldApp-rws Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- Package jar in the war file -->
		<dependency>
			<groupId>com.howtodoinjava</groupId>
			<artifactId>HelloWorldApp-service</artifactId>
			<version>1.0-SNAPSHOT</version>
		</dependency>

	</dependencies>

	<build>
		<finalName>HelloWorldApp-rws</finalName>
	</build>
</project>

2.3. EAR 包

pom.xml

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.howtodoinjava</groupId>
		<artifactId>HelloWorldApp</artifactId>
		<version>1.0-SNAPSHOT</version>
	</parent>
	<artifactId>HelloWorldApp-ear</artifactId>
	<name>HelloWorldApp-ear</name>
	<url>http://maven.apache.org</url>
	<packaging>ear</packaging>

	<dependencies>
		<!-- Package war in the ear file -->
		<dependency>
			<groupId>com.howtodoinjava</groupId>
			<artifactId>HelloWorldApp-rws</artifactId>
			<version>1.0-SNAPSHOT</version>
			<type>war</type>
		</dependency>
	</dependencies>

	<!-- Plugin to bundle the ear file-->
	<build>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-ear-plugin</artifactId>
					<version>3.0.1</version>
					<configuration>
						<finalName>HelloWorldApp-${project.version}</finalName>
						<modules>
							<webModule>
								<groupId>com.howtodoinjava</groupId>
								<artifactId>HelloWorldApp-rws</artifactId>
								<uri>HelloWorldApp-rws-1.0-SNAPSHOT.war</uri>
								<!-- Set custom context root -->
								<contextRoot>/application</contextRoot>
							</webModule>
						</modules>
					</configuration>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>
</project>

3. Maven 构建

要用单个命令编译和构建所有项目,请转到父项目并运行mvn clean install命令。 此命令将为我们生成一个名称为HelloWorldApp-1.0-SNAPSHOT.ear的 EAR 文件。

Console

E:\HelloWorldApp>mvn clean install
...
...
...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] HelloWorldApp 1.0-SNAPSHOT ......................... SUCCESS [  0.428 s]
[INFO] HelloWorldApp-service .............................. SUCCESS [  1.000 s]
[INFO] HelloWorldApp-rws Maven Webapp ..................... SUCCESS [  1.322 s]
[INFO] HelloWorldApp-ear 1.0-SNAPSHOT ..................... SUCCESS [  0.813 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.063 s
[INFO] Finished at: 2018-11-19T09:28:31+05:30
[INFO] ------------------------------------------------------------------------

学习愉快!

阅读更多:

使用控制台

使用 Eclipse 的多模块 maven 项目

Spring Boot 注解

原文: https://howtodoinjava.com/spring-boot2/spring-boot-annotations/

SpringBoot注解大部分放在org.springframework.boot.autoconfigureorg.springframework.boot.autoconfigure.condition封装中。 让我们了解一些常用的SpringBoot 注解以及在幕后工作的注解。

1. @SpringBootApplication

SpringBoot 主要是关于自动配置。 这种自动配置是通过组件扫描来完成的,即在classspath中找到@Component注解的所有类。 它还涉及扫描@Configuration注解并初始化一些额外的 bean。

@SpringBootApplication注解可一步实现所有功能。 它启用了三个功能:

  1. @EnableAutoConfiguration:启用自动配置机制
  2. @ComponentScan:启用@Component扫描
  3. @SpringBootConfiguration:在上下文中注册额外的 bean

带有@SpringBootApplication注解的 Java 类是 Spring Boot 应用程序的主要类,应用程序从此处开始。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication 
public class Application {

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

}

2. @EnableAutoConfiguration

该注解启用 Spring 应用上下文的自动配置,并根据类路径中预定义类的存在来尝试猜测和配置我们可能需要的 bean。

例如,如果我们在类路径上具有tomcat-embedded.jar,则可能需要TomcatServletWebServerFactory

由于此注解已经通过@SpringBootApplication包含在内,因此在主类上再次添加它不会产生影响。 还建议仅通过@SpringBootApplication将此注解包含一次。

自动配置类是常规的 Spring 配置 bean。 它们使用SpringFactoriesLoader机制定位(针对此类)。 通常,自动配置 bean 是@Conditional bean(最常使用@ConditionalOnClass@ConditionalOnMissingBean注解)。

3. @SpringBootConfiguration

它指示一个类提供 Spring Boot 应用程序配置。 可以用作 Spring 标准@Configuration注解的替代方法,以便可以自动找到配置。

应用程序只能包含一个@SpringBootConfiguration,并且大多数惯用的 Spring Boot 应用程序都将从@SpringBootApplication继承它。

两个注解的主要区别在于@SpringBootConfiguration允许自动定位配置。 这对于单元测试或集成测试特别有用。

4. @ImportAutoConfiguration

它仅导入和应用指定的自动配置类。 @ImportAutoConfiguration@EnableAutoConfiguration之间的区别在于,稍后尝试配置在扫描过程中在类路径中找到的 bean,而@ImportAutoConfiguration仅运行我们在注解中提供的配置类。

当我们不想启用默认自动配置时,应使用@ImportAutoConfiguration

@ImportAutoConfiguration example

@ComponentScan("path.to.your.controllers")
@ImportAutoConfiguration({WebMvcAutoConfiguration.class
    ,DispatcherServletAutoConfiguration.class
    ,EmbeddedServletContainerAutoConfiguration.class
    ,ServerPropertiesAutoConfiguration.class
    ,HttpMessageConvertersAutoConfiguration.class})
public class App 
{
	public static void main(String[] args) 
	{
		SpringApplication.run(App.class, args);
	}
}

5. @AutoConfigureBefore@AutoConfigureAfter@AutoConfigureOrder

如果我们的配置需要以特定顺序(在之前之前)应用,则可以使用@AutoConfigureAfter@AutoConfigureBefore注解。

如果我们要订购某些彼此之间不具有直接知识的自动配置,则也可以使用@AutoConfigureOrder。 该注解与常规@Order注解具有相同的语义,但为自动配置类提供了专用顺序。

@AutoConfigureAfter Example

@Configuration
@AutoConfigureAfter(CacheAutoConfiguration.class)
@ConditionalOnBean(CacheManager.class)
@ConditionalOnClass(CacheStatisticsProvider.class)
public class RedissonCacheStatisticsAutoConfiguration 
{
	@Bean
	public RedissonCacheStatisticsProvider redissonCacheStatisticsProvider(){
		return new RedissonCacheStatisticsProvider();
	}
}

5. 条件注解

所有自动配置类通常都具有一个或多个@Conditional注解。 它仅在条件满足时才允许注册 bean。 以下是一些有用的条件注解。

5.1. @ConditionalOnBean@ConditionalOnMissingBean

这些注解可根据是否存在特定 bean 来包含 bean。

value属性用于通过或by name类型指定 bean。search属性还允许我们限制在搜索 bean 时应考虑的ApplicationContext层次结构。

如果条件不匹配,则在类级别使用这些注解可防止将@Configuration类注册为 Bean。

在下面的示例中,只有在应用程序上下文中尚未定义类型JpaTransactionManager的 bean 时,才会加载 bean JpaTransactionManager

@Bean
@ConditionalOnMissingBean(type = "JpaTransactionManager")
JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) 
{
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(entityManagerFactory);
    return transactionManager;
}

5.2. @ConditionalOnClass@ConditionalOnMissingClass

这些注解可根据是否存在特定类来包含配置类。 请注意,注解元数据是通过使用 spring ASM 模块进行解析的,即使在运行时中可能不存在类,您仍然可以在注解中引用该类。

我们还可以使用value属性引用真实的类,或使用name属性通过使用字符串值来指定类名称。

仅当此类在运行时可用并且应用程序上下文中不存在其他具有相同名称的 bean 时,以下配置才会创建EmbeddedAcmeService

@Configuration
@ConditionalOnClass(EmbeddedAcmeService.class)
static class EmbeddedConfiguration 
{

	@Bean
	@ConditionalOnMissingBean
	public EmbeddedAcmeService embeddedAcmeService() { ... }

}

5.3. @ConditionalOnNotWebApplication@ConditionalOnWebApplication

这些注解根据应用程序是否为“Web 应用程序”而包含配置。 在 Spring,Web 应用程序是至少满足以下三个要求之一的应用程序:

  1. 使用 SpringWebApplicationContext
  2. 定义session范围
  3. 有一个StandardServletEnvironment

5.4. @ConditionalOnProperty

这个注解允许基于 Spring Environment属性的存在和值包括配置。

例如,如果我们针对不同的环境具有不同的数据源定义,则可以使用此注解。

@Bean
@ConditionalOnProperty(name = "env", havingValue = "local")
DataSource dataSource() 
{
    // ...
}

@Bean
@ConditionalOnProperty(name = "env", havingValue = "prod")
DataSource dataSource() 
{
    // ...
}

5.5. @ConditionalOnResource

该注解使配置仅在类路径中存在特定资源时才包括在内。 可以使用常规的 Spring 约定来指定资源。

@ConditionalOnResource(resources = "classpath:vendor.properties")
Properties additionalProperties() 
{
    // ...
}

5.6. @ConditionalOnExpression

该注解允许基于 SpEL 表达式的结果包括配置。 当要评估的条件很复杂且应作为一个条件评估时,请使用此注解。

@Bean
@ConditionalOnExpression("${env} && ${havingValue == 'local'}")
DataSource dataSource() 
{
    // ...
}

5.7. @ConditionalOnCloudPlatform

当指定的云平台处于活动状态时,此注解可包括配置。

@Configuration
@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
public class CloudConfigurationExample 
{
  @Bean
  public MyBean myBean(MyProperties properties) 
  {
    return new MyBean(properties.getParam);
  }
}

在评论中向我发送有关SpringBoot 注解的问题。

学习愉快!

参考: Spring Boot Docs

Spring 5 的新功能和增强功能

原文: https://howtodoinjava.com/spring5/spring5-features-and-enhancements/

Spring 5 是第一个主要版本,距 Spring Framework 4.0 差不多四年了。 在此期间,大多数增强功能都是在 Spring boot 项目中完成的。 在本文中,我们将快速介绍 Spring 5.0 发行版中的一些令人兴奋的功能。

基准升级

要构建和运行 Spring 5 应用程序,您将需要最低 JDK 8 和 Java EE7。不再支持以前的 JDK 和 Java EE 版本。 详细来说,Java EE 7 包括:

  • Servlet 3.1
  • JMS 2.0
  • JPA 2.1
  • JAX-RS 2.0
  • Bean 验证 1.1

与 Java 基准相似,许多其他框架的基准也有所变化。 例如

  • Hibernate 5
  • Jackson 2.6
  • EhCache 2.10
  • JUnit 5
  • Tiles 3

另外,记下各种服务器的最低支持版本。

  • Tomcat 8.5 以上
  • Jetty 9.4+
  • WildFly 10+
  • Netty 4.1+
  • Undertow 1.4 以上

JDK 9 运行时兼容性

Spring 5 发行版与 JDK 9 发行日期非常吻合。 我们的目标是让 Spring Framework 5.0 在 JDK 9 的 GA 之后立即成为 GA。 Spring 5.0 的候选版本已经在类路径以及 modulepath 上支持 Java 9。

您可以期望在 GA 版本中获得良好的 JDK 9 支持。

JDK 8 功能的用法

在 Spring 4.3 之前,JDK 的基线版本为 6。因此 Spring 4 必须支持 Java 6、7 和 8。为了保持向后兼容性,Spring 框架并没有采用 Java 8 本身带来的许多新功能,例如。 Lambda 编程。

Spring 5 具有基准版本 8,因此它也使用 Java 8 和 9 的许多新功能。 例如

  1. Spring 核心接口中的 Java 8 默认方法
  2. 基于 Java 8 反射增强的内部代码改进
  3. 在框架代码中使用功能编程-lambda 和流

响应式编程支持

响应式编程是 Spring Framework 5.0 的最重要功能之一。 响应式编程提供了另一种编程风格,专注于构建对事件做出反应的应用程序。 Spring Framework 5 包含响应式流(用于定义响应式 API 的中性语言尝试)和 Reactor (由 Spring Pivotal 团队提供的响应式流的 Java 实现),用于其自身的响应式使用以及其许多核心 API。

Spring Web Reactive 位于新的spring-web-reactive模块中,位于spring-webmvc模块中现有(流行的!)Spring Web MVC 的旁边。 请注意,在 Spring 5 中,传统的 Spring MVC 可以在任何 Servlet 3.1 堆栈上运行,包括 Java EE 7 服务器。

功能性网络框架

在响应功能的基础上,Spring 5 还提供了一个功能性的 Web 框架。 它提供了使用功能性编程风格定义端点的功能。 该框架引入了两个基本组件:HandlerFunctionRouterFunction

HandlerFunction代表处理传入请求并生成响应的功能。 RouterFunction可以替代@RequestMapping注解。 用于将传入的请求路由到处理函数。 例如

RouterFunction<String> route =
    route(GET("/hello-world"),
    request -> Response.ok().body(fromObject("Hello World")));

Kotlin 支持

Kotlin 是一种静态类型的 JVM 语言,它使代码具有表达力,简短性和可读性。 Spring framework 5.0 对 Kotlin 有很好的支持。

功能移除

随着 Java,Java EE 和其他一些框架的基准版本的增加,Spring Framework 5 删除了对一些框架的支持。 例如

  • Portlet
  • Velocity
  • JasperReports
  • XMLBeans
  • JDO
  • Guava

学习愉快!

参考:链接

Spring Boot2 @SpringBootApplication自动配置

原文: https://howtodoinjava.com/spring-boot2/springbootapplication-auto-configuration/

Spring Boot 非常易于使用,它在后台执行了许多操作,您可能没有意识到。 将来,一个好的开发人员将确切地了解 spring boot 自动配置背后的工作,如何以自己的喜好使用它,以及如何禁用某些不需要的部分到您的项目中。

为了了解 Spring Boot 背后的大多数基本知识,我们将创建一个具有单个依赖项和单个启动类文件的最小启动应用程序。 然后,我们将分析启动日志以获取见解。

使用启动类创建 Spring Boot 应用程序

  1. 在 Eclipse 中创建一个新的 Maven 项目,原型为maven-archetype-quickstart

  2. 使用spring-boot-starter-web依赖和插件信息更新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>springbootdemo</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<packaging>jar</packaging>
    
    	<name>springbootdemo</name>
    	<url>http://maven.apache.org</url>
    
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.0.0.RELEASE</version>
    	</parent>
    
    	<properties>
    		<java.version>1.8</java.version>
    		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    	</properties>
    
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</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>
    
    
  3. 创建启动应用。

    package com.howtodoinjava.demo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ApplicationContext;
    
    @SpringBootApplication
    public class App 
    {
    	public static void main(String[] args) 
    	{
    		ApplicationContext ctx = SpringApplication.run(App.class, args);
    	}
    }
    
    

    这个入门班是做什么的?

    上面的类称为 Spring Boot 应用程序启动类。 它曾经通过 Java main()方法来运行和启动 Spring 应用程序。 它通常会执行以下操作:

    • 创建 Spring 的ApplicationContext的实例。
    • 启用该功能以接受命令行参数并将其公开为 Spring 属性。
    • 根据配置加载所有 Spring bean。 您还可以根据项目需要进行其他操作。

@SpringBootApplication注解

此注解是在一条语句中应用 3 个注解的快捷方式:

  1. @SpringBootConfiguration

    @SpringBootConfiguration 是 Spring Boot 2 中的新注解。以前,我们一直在使用@Configuration注解。 您可以代替使用@Configuration。 两者都是同一回事。

    它指示一个类提供 Spring Boot 应用程序@Configuration。 它仅表示带注解的类是配置类,应对其进行扫描以获取进一步的配置和 Bean 定义。

  2. @EnableAutoConfiguration

    该注解用于启用 Spring 应用下上文的自动配置,尝试猜测和配置您可能需要的 bean。 通常根据您的类路径和定义的 bean 来应用自动配置类。

    自动配置会尝试尽可能智能化,并且在您定义更多自己的配置时会自动退出。 您始终可以使用两种方法手动排除您永远不想应用的任何配置:

    i)使用excludeName()

    ii)使用属性文件中的spring.autoconfigure.exclude属性。 例如

    @EnableAutoConfiguration(excludeName = {"multipartResolver","mbeanServer"})
    

    自动配置始终在注册用户定义的 bean 之后应用。

  3. @ComponentScan

    该注解提供与 Spring XML 的context:component-scan元素并行的支持。

    可以指定basePackageClasses()basePackages()定义要扫描的特定程序包。 如果未定义特定的程序包,则将从声明此注解的类的程序包中进行扫描。

运行启动应用程序并检查日志

让我们以最简单的选项开始运行它 - 作为 Java 应用程序运行。 在您的 IDE 中,右键单击应用程序类,然后将其作为 Java 应用程序运行。 为了了解已注册的 bean,我添加了如下修改的启动应用程序。

@SpringBootApplication
public class App 
{
	public static void main(String[] args) 
	{
		ApplicationContext ctx = SpringApplication.run(App.class, args);

		String[] beanNames = ctx.getBeanDefinitionNames();

		Arrays.sort(beanNames);

		for (String beanName : beanNames) {
			System.out.println(beanName);
		}
	}
}

现在查看日志:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.0.RELEASE)

2018-04-02 13:09:41.100  INFO 11452 --- [           main] com.howtodoinjava.demo.App               : Starting App on FFC15B4E9C5AA with PID 11452 (C:\Users\zkpkhua\IDPPaymentTransfers_Integrated\springbootdemo\target\classes started by zkpkhua in C:\Users\zkpkhua\IDPPaymentTransfers_Integrated\springbootdemo)
2018-04-02 13:09:41.108  INFO 11452 --- [           main] com.howtodoinjava.demo.App               : No active profile set, falling back to default profiles: default
2018-04-02 13:09:41.222  INFO 11452 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@4450d156: startup date [Mon Apr 02 13:09:41 IST 2018]; root of context hierarchy
2018-04-02 13:09:43.474  INFO 11452 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2018-04-02 13:09:43.526  INFO 11452 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-04-02 13:09:43.526  INFO 11452 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.28
2018-04-02 13:09:43.748  INFO 11452 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-04-02 13:09:43.748  INFO 11452 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2531 ms
2018-04-02 13:09:43.964  INFO 11452 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Servlet dispatcherServlet mapped to [/]
2018-04-02 13:09:43.969  INFO 11452 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-04-02 13:09:43.970  INFO 11452 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-04-02 13:09:43.970  INFO 11452 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-04-02 13:09:43.970  INFO 11452 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-04-02 13:09:44.480  INFO 11452 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@4450d156: startup date [Mon Apr 02 13:09:41 IST 2018]; root of context hierarchy
2018-04-02 13:09:44.627  INFO 11452 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-04-02 13:09:44.630  INFO 11452 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-04-02 13:09:44.681  INFO 11452 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-04-02 13:09:44.682  INFO 11452 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-04-02 13:09:44.747  INFO 11452 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-04-02 13:09:45.002  INFO 11452 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-04-02 13:09:45.070  INFO 11452 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2018-04-02 13:09:45.076  INFO 11452 --- [           main] com.howtodoinjava.demo.App               : Started App in 4.609 seconds (JVM running for 5.263)
app
basicErrorController
beanNameHandlerMapping
beanNameViewResolver
characterEncodingFilter
conventionErrorViewResolver
defaultServletHandlerMapping
defaultValidator
defaultViewResolver
dispatcherServlet
dispatcherServletRegistration
error
errorAttributes
errorPageCustomizer
errorPageRegistrarBeanPostProcessor
faviconHandlerMapping
faviconRequestHandler
handlerExceptionResolver
hiddenHttpMethodFilter
httpPutFormContentFilter
httpRequestHandlerAdapter
jacksonCodecCustomizer
jacksonObjectMapper
jacksonObjectMapperBuilder
jsonComponentModule
localeCharsetMappingsCustomizer
mappingJackson2HttpMessageConverter
mbeanExporter
mbeanServer
messageConverters
methodValidationPostProcessor
multipartConfigElement
multipartResolver
mvcContentNegotiationManager
mvcConversionService
mvcHandlerMappingIntrospector
mvcPathMatcher
mvcResourceUrlProvider
mvcUriComponentsContributor
mvcUrlPathHelper
mvcValidator
mvcViewResolver
objectNamingStrategy
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.condition.BeanTypeRegistry
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration
org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration
org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration$JacksonCodecConfiguration
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration
org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$ParameterNamesModuleConfiguration
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$TomcatWebServerFactoryCustomizerConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter$FaviconConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration
org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.event.internalEventListenerFactory
org.springframework.context.event.internalEventListenerProcessor
parameterNamesModule
preserveErrorControllerTargetClassPostProcessor
propertySourcesPlaceholderConfigurer
requestContextFilter
requestMappingHandlerAdapter
requestMappingHandlerMapping
resourceHandlerMapping
restTemplateBuilder
server-org.springframework.boot.autoconfigure.web.ServerProperties
servletWebServerFactoryCustomizer
simpleControllerHandlerAdapter
spring.http.encoding-org.springframework.boot.autoconfigure.http.HttpEncodingProperties
spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties
spring.jackson-org.springframework.boot.autoconfigure.jackson.JacksonProperties
spring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties
spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties
spring.security-org.springframework.boot.autoconfigure.security.SecurityProperties
spring.servlet.multipart-org.springframework.boot.autoconfigure.web.servlet.MultipartProperties
standardJacksonObjectMapperBuilderCustomizer
stringHttpMessageConverter
tomcatServletWebServerFactory
tomcatServletWebServerFactoryCustomizer
tomcatWebServerFactoryCustomizer
viewControllerHandlerMapping
viewResolver
webServerFactoryCustomizerBeanPostProcessor
websocketContainerCustomizer
welcomePageHandlerMapping

您会看到有多少个 bean 自动注册。 那是 SpringBoot 子的美。 如果您想更深入地研究为什么要注册任何特定的 bean? 通过在应用程序启动时放置调试标志,您可以看到这一点。

只需将-Ddebug=true作为 VM 参数传递。

现在,当您运行该应用程序时,您将获得许多调试日志,它们具有类似的信息:

CodecsAutoConfiguration.JacksonCodecConfiguration matched:
  - @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)

CodecsAutoConfiguration.JacksonCodecConfiguration#jacksonCodecCustomizer matched:
  - @ConditionalOnBean (types: com.fasterxml.jackson.databind.ObjectMapper; SearchStrategy: all) found bean 'jacksonObjectMapper' (OnBeanCondition)

DispatcherServletAutoConfiguration.DispatcherServletConfiguration matched:
  - @ConditionalOnClass found required class 'javax.servlet.ServletRegistration'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
  - Default DispatcherServlet did not find dispatcher servlet beans (DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition)

DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration matched:
  - @ConditionalOnClass found required class 'javax.servlet.ServletRegistration'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
  - DispatcherServlet Registration did not find servlet registration bean (DispatcherServletAutoConfiguration.DispatcherServletRegistrationCondition)

  ...
  ...
  ...

上面的日志说明了为什么将特定的 bean 注册到 spring 上下文中。 当您调试自动配置问题时,此信息非常有用。

同样,每次我们向 Spring Boot 项目添加新的依赖项时,Spring Boot 自动配置都会自动尝试基于该依赖项配置 Bean。

希望以上讨论的信息将来能在调试 Spring Boot 相关问题时为您提供帮助。

学习愉快!

Spring Boot 和 AOP

原文: https://howtodoinjava.com/spring-boot2/aop-aspectj/

学习在 Spring Boot 应用程序中实现 AOP,并使用 AspectJ 添加不同的 aop 建议,以支持诸如日志记录,性能分析,缓存和事务管理之类的跨领域关注点。

阅读更多: Spring AOP 教程

1. 使用 Spring Boot 设置 AOP

1.1. Maven

在 Spring Boot 中设置 AOP 需要包括spring-boot-starter-aop依赖项。 它将spring-aopaspectjweaver依赖项导入到应用程序中。 入门 pom.xml

pom.xml

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

1.2. 启用/禁用自动配置

导入以上依赖项会触发AopAutoConfiguration,这会使用@EnableAspectJAutoProxy注解启用 AOP。

如果application.properties中的spring.aop.auto = false不激活自动配置。

application.properties

#spring.aop.auto = false	//'false' disables the auto configuration

1.3. 使用@Aspect创建切面

可以在 Spring 运行中使用注解@Aspect注解创建一个切面,并使用@Component注解在 bean 容器中注册。

在切面类中,我们可以根据需要创建建议。 例如,下面的类在包com.howtodoinjava.aop中所有类内的方法上对建议应用。 它捕获方法的开始时间和结束时间,并将总的方法执行时间记录在日志文件中。

LoggingAspect.java

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

@Aspect
@Component
public class LoggingAspect 
{
	private static final Logger LOGGER = LogManager.getLogger(LoggingAspect.class);

	@Around("execution(* com.howtodoinjava.aop..*(..)))")
    public Object profileAllMethods(ProceedingJoinPoint proceedingJoinPoint) throws Throwable 
    {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();

        //Get intercepted method details
        String className = methodSignature.getDeclaringType().getSimpleName();
        String methodName = methodSignature.getName();

        final StopWatch stopWatch = new StopWatch();

        //Measure method execution time
        stopWatch.start();
        Object result = proceedingJoinPoint.proceed();
        stopWatch.stop();

        //Log method execution time
        LOGGER.info("Execution time of " + className + "." + methodName + " "
        					+ ":: " + stopWatch.getTotalTimeMillis() + " ms");

        return result;
    }
}

1.4. 示例

创建一个简单的服务类来测试以上建议。

DomainService.java

@Service
public class DomainService 
{
	public Object getDomainObjectById(Long id)
	{
		try {
			Thread.sleep(new Random().nextInt(2000));
		} catch (InterruptedException e) {
			//do some logging
		}
        return id;
    }
}

创建一个测试类并执行给定的方法并注意日志。

AopSpringBootTest.java

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class AopSpringBootTest 
{
	@Autowired
	private DomainService service;

	@Test
	public void testGetDomainObjectById() 
	{
		service.getDomainObjectById(10L);
	}
}

Console

2019-11-07T21:02:58.390+0530 INFO Execution time of DomainService.getDomainObjectById :: 1145 ms

如我们所见,AOP 建议已应用于服务方法。

2. 建议类型

在 Aspectj AOP 中有五种类型的建议。

  1. @Before :在连接点之前执行的建议,但是它不能阻止执行流程前进到连接点(除非它引发异常)。
  2. @AfterReturning :连接点正常完成后要执行的建议。
  3. @AfterThrowing :如果方法因抛出异常而退出,则要执行的建议。
  4. @After :无论连接点退出的方式如何(正常或异常返回),都将执行建议。
  5. @Around :围绕连接点的建议,例如方法调用。

3. 结论

使用 Spring 运行实现 AOP 只需很少的工作,并且一旦添加spring-boot-starter-aop依赖项,我们就准备添加切面。

每当我们想禁用 aop 自动配置时,我们都可以通过切换到 false轻松完成。

创建切面和建议就像添加一些注解一样简单,例如@Aspect@Around等。

学习愉快!

下载源码

Spring Boot 日志指南

原文: https://howtodoinjava.com/spring-boot2/logging/spring-boot-logging-configurations/

登录 Spring Boot 非常灵活且易于配置。 Spring Boot 通过一些简单的配置即可支持各种日志记录提供程序。 在本教程中,我们将介绍 Spring Boot 支持的各种日志记录选项和配置。

1. 默认零配置日志

启用了 Spring Boot 活动的日志记录由spring-boot-starter-logging工件确定,并且它是自动配置,可启用任何受支持的日志记录提供程序(java.util.logging,Log4J2 和 Logback)基于提供的配置。

如果我们不提供任何特定于日志记录的配置,我们仍将看到打印在“控制台”中的日志。 这是因为在 Spring Boot 中使用 Logback默认日志支持

Spring Boot 的内部日志记录是用 Apache Commons Logging 编写的,因此它是唯一且唯一的依赖项。 直到启动 1.x,我们都必须手动导入它。 从运行 2.x 开始,它会进行过渡下载。 更准确地说,spring-boot-starter-web取决于spring-boot-starter-logging,它为我们拉入spring-jcl

Spring Boot 自动配置提供使用Logback的默认日志记录,并且这些配置文件中提供了默认配置。

1.1. 添加日志语句

要在应用程序代码中添加日志语句,请使用 SLF4J 中的org.slf4j.Loggerorg.slf4j.LoggerFactory。 它提供了许多有用的日志记录方法,也使日志记录实现与应用程序脱钩。

Application.java

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application 
{
	private static final Logger LOGGER=LoggerFactory.getLogger(Application.class);

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

		LOGGER.info("Simple log statement with inputs {}, {} and {}", 1,2,3);
	}
}

Console

2019-07-28 12:16:57.129  INFO 3416 --- [main] 
com.howtodoinjava.demo.Application: Simple log statement with inputs 1, 2 and 3

1.2. 记录级别

Logback 支持将ERRORWARNINFODEBUGTRACE作为日志记录级别。 默认情况下,日志记录级别设置为INFO。 这意味着代码DEBUGTRACE消息不可见。

要启用调试或跟踪日志记录,我们可以在application.properties文件中设置日志记录级别。 另外,我们可以在启动应用程序时在命令行上传递 – debug或 – trace参数。

Configuration

# In properties file
debug=true

# In Console
$ java -jar target/my-app-0.0.1-SNAPSHOT.jar --trace

我们也可以将日志记录级别应用于特定的软件包。 可以在控制台或application.properties文件中完成。

Configuration

# In Console
-Dlogging.level.org.springframework=ERROR 
-Dlogging.level.com.howtodoinjava=TRACE

# In properties file
logging.level.org.springframework=ERROR 
logging.level.com.howtodoinjava=TRACE

如果使用不同的日志级别多次定义了程序包的日志级别,则将使用最低级别。 TRACE最低,ERROR最高。

1.3. 日志格式

defaults.xml文件中提到了默认的日志语句格式。

defaults.xml

<conversionRule conversionWord="clr" 
converterClass="org.springframework.boot.logging.logback.ColorConverter" />

<conversionRule conversionWord="wex" 
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />

<conversionRule conversionWord="wEx" 
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />

<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}})
{faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39})
{cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

<property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} 
${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

输出以下信息。

  • 日期和时间:毫秒精度,易于排序。
  • 日志级别ERRORWARNINFODEBUGTRACE
  • 进程 ID。
  • ---分隔符用于区分实际日志消息的开始。
  • 线程名称:放在方括号中(对于控制台输出,可能会被截断)。
  • 记录器名称:这通常是源类名称(通常缩写)。
  • 日志消息。

自定义日志格式,请使用logging.pattern.consolelogging.pattern.file属性。

application.properties

# Logging pattern for the console
logging.pattern.console= %d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %msg%n

# Logging pattern for file
logging.pattern.file= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%

1.4. 记录到文件

默认情况下,spring boot 日志仅记录到控制台。 如果要启用文件记录,可以使用简单属性logging.filelogging.path轻松进行。

使用logging.path时,它将在上述包中创建一个名为spring.log的文件。

application.properties

# Output to a temp_folder/file
logging.file=c:/temp/application.log

#logging.path=/my-folder/

# Logging pattern for file
logging.pattern.file= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%

2. Logback 日志记录

对于大多数用例,默认日志记录已经足够了。 但是有时在企业应用程序中,我们需要对具有其他复杂需求的日志进行更好的控制。 在那种情况下,具有专用的日志日志配置是合适的。

默认情况下,Spring Boot 使用 logback,因此要自定义其行为,我们只需要在类路径中添加logback.xml并定义该文件的自定义即可。

logback.xml

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

	<property name="LOG_LOCATION" value="c:/temp" />

	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
         	<pattern>%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %msg%n</pattern>
      	</encoder>
	</appender>

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
      	<File>{LOG_LOCATION}/mylog.log</File>
      	<encoder>
	         <pattern>%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %msg%n</pattern>
	    </encoder>
	    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_LOCATION}/archived/mylog-%d{yyyy-MM-dd}.%i.log
            </fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
   	</appender> 

   	<root level="INFO">
    	<appender-ref ref="CONSOLE"/>
    	<appender-ref ref="FILE"/>
   	</root>

   	<!-- Application logs at trace level -->
   	<logger name="com.howtodoinjava" level="trace" additivity="false">
        <appender-ref ref="RollingFile" />
        <appender-ref ref="Console" />
    </logger>

</configuration>

3. Log4j2 记录

步骤 1:排除 logback,并包括 log4j2

如前所述,spring boot 使用 logback 作为默认值。 因此,如果我们必须使用其他任何日志记录框架,例如 log4j2,我们必须从应用程序的类路径中排除登录。 另外,将spring-boot-starter-log4j2添加到类路径。

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

步骤 2:添加 log4j2 配置文件

现在,在类路径(通常在资源文件夹中)中添加特定于 log4j2 的配置文件。 可以将其命名为以下任意一种:

  • log4j2-spring.xml
  • log4j2.xml

如果我们在其他任何文件(例如log4j2.propertiesapplogs.xml等)中都有日志日志配置,则可以使用logging.file属性指定其路径application.properties文件。

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
    <Properties>
        <Property name="LOG_PATTERN">%d{yyyy-MM-dd'T'HH:mm:ss.SSSZ} %p %m%n</Property>
        <Property name="APP_LOG_ROOT">c:/temp</Property>
    </Properties>
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="${LOG_PATTERN}" />
        </Console>

        <RollingFile name="file"
            fileName="${APP_LOG_ROOT}/SpringBoot2App/application.log"
            filePattern="${APP_LOG_ROOT}/SpringBoot2App/application-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout pattern="${LOG_PATTERN}" />
            <Policies>
                <SizeBasedTriggeringPolicy size="19500KB" />
            </Policies>
            <DefaultRolloverStrategy max="1" />
        </RollingFile>

    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="console" />
            <AppenderRef ref="file" />
        </Root>
    </Loggers>
</Configuration>

步骤 3:有或没有 Slf4j

默认情况下,如果您正在使用 SLF4J 记录器类,即org.slf4j.Loggerorg.slf4j.LoggerFactory,则无需更改应用程序代码,所有日志语句将继续在目标附加程序中打印。

如果您打算仅使用 log4j2 特定的类,请使用org.apache.logging.log4j.Loggerorg.apache.logging.log4j.LogManager

我将建议使用 SLF4J 记录器类。

SLF4J logger classes

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SpringBootApplication
public class Application 
{
	private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);

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

		LOGGER.info("Simple log statement with inputs {}, {} and {}", 1,2,3);
	}
}

LOG4J2 logger classes

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

@SpringBootApplication
public class Application 
{
	private static Logger LOGGER = LogManager.getLogger(Application.class);

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

		LOGGER.info("Simple log statement with inputs 1, 2 and 3");
	}
}

4. 有关 Spring Boot 日志记录的更多示例

  1. Spring Boot 2 log4j2.xml示例
  2. Spring Boot 2 log4j2.properties示例
  3. application.yml和 Spring Boot 日志
  4. application.properties和 Spring 日志
  5. Tomcat 和 Jetty 的 SpringBoot 嵌入式服务器日志配置
  6. AspectJ AOP 和 Spring Boot 性能日志
  7. Lombok 和 Spring Boot 日志
  8. Spring Boot 多个日志文件示例
  9. Spring Boot 控制台日志配置示例
  10. Spring 配置文件特定的日志示例

请问一下您与 Spring Boot 中的日志配置有关的问题

学习愉快!

Spring Boot Devtools 教程

原文: https://howtodoinjava.com/spring-boot2/developer-tools-module-tutorial/

如果您使用最新的 UI 开发框架,例如 Node,Angular,gulp 等。那么,每当某些代码发生更改时,您一定已经意识到 UI 在浏览器中的自动重装。 它非常有用,可以节省大量时间。

好吧,使用spring-boot-devtools依赖关系提供的功能,可以在 Spring Boot 应用程序中使用相同的功能。 让我们了解有关启用和使用这些功能的信息。

启用开发工具模块

在 Spring Boot 应用程序中启用开发工具非常容易。 只需在构建文件中添加spring-boot-devtools依赖项即可。

Maven

pom.xml

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-devtools</artifactId>
		<optional>true</optional>
	</dependency>
</dependencies>

摇篮

build.gradle

dependencies {
	compile("org.springframework.boot:spring-boot-devtools")
}

静态资源缓存

为了提高性能,开发人员工具会缓存静态内容/模板文件,以将其更快地提供给浏览器/客户端。 这是生产中非常好的功能,每毫秒性能的提高都很重要。 但是在开发环境中,这可能是一个问题,并导致过时的缓存问题,并且您可能不会在浏览器中立即看到所做的更改。 开发工具模块通过设置很少的属性来提供此功能。

默认情况下,此功能处于禁用状态。 通过设置属性,可以使其在生产环境中使用。

有许多这样的 UI 模板库支持此功能。 例如 thymeleaf,freemarker,groovy,mustache 等

application.properties

#spring.freemarker.cache = true //set true in production environment
spring.freemarker.cache = false //set false in development environment; It is false by default.

//Other such properties

spring.thymeleaf.cache = false
spring.mustache.cache = false
spring.groovy.template.cache = false

自动刷新与自动重启 – 自动刷新(或自动加载)是指在浏览器中重新加载 UI,以查看静态内容更改。 自动重新启动是指重新加载服务器端代码和配置,然后重新启动服务器。

用户界面自动刷新

spring-boot-devtools模块包括一个嵌入式 LiveReload 服务器,该服务器可用于在更改资源时触发浏览器刷新。 前提条件是您的浏览器应支持扩展。 您可以在此链接中找到这种浏览器范围。

默认情况下,启用实时重新加载。 如果您出于某些原因希望禁用此功能,请将spring.devtools.livereload.enabled属性设置为false

application.properties

spring.devtools.livereload.enabled  = false #Set false to disable live reload

从自动重新加载中排除资源

默认情况下,自动重启在以下路径上起作用:

  1. /META-INF/maven
  2. /META-INF/resources
  3. /resources
  4. /static
  5. /public
  6. /templates

如果要在浏览器中为其中一些路径的文件禁用自动重装,请使用spring.devtools.restart.exclude属性。 例如

spring.devtools.restart.exclude=static/**,public/**

监视/排除其他路径

可能只有很少的文件不在类路径中,但是您仍然可能希望观看那些其他文件/路径以重新加载应用程序。 为此,请使用spring.devtools.restart.additional-paths属性。

spring.devtools.restart.additional-paths=script/**

同样,如果要保留这些默认值,并且添加其他排除项,请改用spring.devtools.restart.additional-exclude属性。

spring.devtools.restart.additional-exclude=styles/**

服务器自动重启

自动重启意味着在服务器端重新加载 Java 类和配置。 在动态地重新部署服务器端更改之后,服务器将重新启动并加载修改后的代码和配置。

启用/禁用自动配置更改的日志记录

默认情况下,每次应用程序重新启动时,都会记录一个报告,其中显示了条件评估增量。 该报告显示了您进行更改(例如添加或删除 Bean 以及设置配置属性)时对应用程序自动配置的更改。

要禁用报告的日志记录,请设置以下属性:

spring.devtools.restart.log-condition-evaluation-delta = false

禁用重启

要在非静态代码更改时禁用服务器重启,请使用属性spring.devtools.restart.enabled

spring.devtools.restart.enabled = false

使用触发文件

每次文件更改都不希望自动重启,并且由于频繁重启,有时可能会减慢开发时间。 要解决此问题,可以使用触发文件。 Spring Boot 将继续监视该文件,一旦检测到该文件中的任何修改,它将重新启动服务器并重新加载所有先前的更改。

使用spring.devtools.restart.trigger-file属性提及您的应用程序的触发文件。 它可以是任何外部或内部文件。

spring.devtools.restart.trigger-file = c:/workspace/restart-trigger.txt

全局设置文件

每次为所有 spring boot 项目或模块设置所有您喜欢的配置选项可能会成为重复的工作。 您可以使用全局设置文件将其最小化。 然后,各个项目/模块将从全局文件继承所有自定义设置,并且如果需要,它们可以覆盖每个项目基础的任何特定设置。

要创建全局文件,请转到系统用户的主目录并创建一个名为.spring-boot-devtools.properties的文件。 (请注意,文件名以点开头)。 不要使用此全局属性文件来配置全局可用选项。

.spring-boot-devtools.properties

spring.devtools.restart.trigger-file = c:/workspace/restart-trigger.txt

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

学习愉快!

参考: Spring boot 参考

Spring Boot WAR 包示例

原文: https://howtodoinjava.com/spring-boot2/war-packaging-example/

在 Spring Boot 应用程序中,默认包装是 jar,它部署在嵌入式服务器中。 如果要生成 war 文件以部署在单独的应用程序服务器实例(例如 Jboss,Weblogic 或 tomcat)中,请遵循以下说明。

步骤 1)声明包类型 WAR

逻辑上的第一步是在pom.xml文件中声明包装类型为war

<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;
  ...
  <packaging>war</packaging>
  ...
</project>

它指示项目的工件类型。 请注意,当未声明任何包装时,Maven 会假定工件为默认值:jar

步骤 2)将嵌入式服务器相关性范围设置为“提供”

由于其在快速开发生命周期中的有用性,我们可能希望在开发环境中使用嵌入式服务器(例如 tomcat),但我们当然不希望这些服务器 jar 包含在最终生成的 Maven 工件或 war 文件中。 为此,请将嵌入式服务器依赖项的范围设置为“已提供”。

范围“提供”表示您希望 JDK 或容器在运行时提供依赖项。 该作用域仅在编译和测试类路径上可用,并且不可传递。

阅读更多:依赖机制

WAR 包演示

在此演示中,我们在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>springbootdemo</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<packaging>war</packaging>

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

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

	<properties>
		<java.version>1.8</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-hateoas</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
		</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>

现在运行目标为clean installmaven build,它将在下面的目标文件夹中生成该项目的 war 文件。

Spring Boot War Packaging Example

Spring Boot War 打包示例

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

学习愉快!

Spring Boot 2 REST API 示例

原文: https://howtodoinjava.com/spring-boot2/rest/rest-api-example/

在本 Spring Rest 教程中,学习使用 Spring Boot 2 框架创建 REST API,该 API 将 JSON 响应返回给客户端。 在本 Spring Boot 2 REST API 教程中,我们将逐步创建两个简单的 GET 和 POST API 并进行测试。

1. Maven 依赖

首先,创建一个简单的 Maven Web 项目,并根据pom.xml文件中的 spring boot 依赖项进行更新。

重要的依赖项是spring-boot-starter-parent(阅读更多)和spring-boot-starter-web(阅读更多)。 入门级 Web 依赖关系可传递地包含更多依赖关系,以构建 Web 应用程序,例如spring-webmvcspring-webhibernate-validatortomcat-embed-coretomcat-embed-eltomcat-embed-websocketjackson-databindjackson-datatype-jdk8jackson-datatype-jsr310jackson-module-parameter-names

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

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

	<name>SpringBootDemo</name>
	<description>Spring Boot2 REST API Demo for https://howtodoinjava.com</description>

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

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

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

</project>

2. Spring Boot 2 REST API 控制器

  • 在 Spring 中,能够处理 REST API 请求的控制器类称为 rest 控制器。 应使用@RestController注解进行注解。
  • 资源 uri 在@RequestMapping注解中指定。 它可以同时应用于类级别和方法级别。 添加类级别路径和方法级别路径后,将解析 API 的完整 URI。
  • 我们应该始终写produceconsume属性来指定 API 的媒体类型属性。 切勿对假设作出响应。

在给定的控制器中,我们有两种 API 方法。 随时根据需要添加更多方法。

  1. HTTP GET /employee - 返回员工列表。
  2. HTTP POST /employee – 在员工集合中添加一个员工。

EmployeeController.java

package com.howtodoinjava.rest.controller;

import java.net.URI;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import com.howtodoinjava.rest.dao.EmployeeDAO;
import com.howtodoinjava.rest.model.Employee;
import com.howtodoinjava.rest.model.Employees;

@RestController
@RequestMapping(path = "/employees")
public class EmployeeController 
{
    @Autowired
    private EmployeeDAO employeeDao;

    @GetMapping(path="/", produces = "application/json")
    public Employees getEmployees() 
    {
        return employeeDao.getAllEmployees();
    }

    @PostMapping(path= "/", consumes = "application/json", produces = "application/json")
    public ResponseEntity<Object> addEmployee(@RequestBody Employee employee) 
    {
        Integer id = employeeDao.getAllEmployees().getEmployeeList().size() + 1;
        employee.setId(id);

        employeeDao.addEmployee(employee);

        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                                    .path("/{id}")
                                    .buildAndExpand(employee.getId())
                                    .toUri();

        return ResponseEntity.created(location).build();
    }
}

我们可以使用application.properties文件来控制和自定义许多实现细节。 但是为了使此演示简单,我将其保留为空白。

3. @SpringBootApplication

我们的 REST API 框架已准备就绪。 现在,我们需要配置 Spring 以检测我们的 rest 控制器(使用自动扫描)并在嵌入式 tomcat 服务器中部署 api。 幸运的是,Spring Boot 通过使用自动配置的概念使所有这些事情变得非常容易。

自动配置尝试猜测和配置您可能需要的 bean。 自动配置类通常基于应用程序类路径中的 jar 和我们在@Configuration类中另外定义的 bean 来应用。

在这种情况下,它会执行以下操作。

  1. 它检测到spring-webmvc,因此配置默认的 spring mvc 应用程序 bean。 它有助于扫描和配置@RestController和类似的注解。
  2. 它检测到嵌入的 tomcat jar,因此为我们配置嵌入式 tomcat。
  3. 它检测到 JSON jar,因此可以配置对 API 的 JSON 支持。

SpringBootDemoApplication.java

package com.howtodoinjava.rest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; 

@SpringBootApplication 
public class SpringBootDemoApplication {

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

4. 模型类和 DAO

这些类与 REST 没有直接关系。 仍然让我们看看它们是如何编写的。

Employee.java

package com.howtodoinjava.rest.model;

public class Employee {

    public Employee() {

    }

    public Employee(Integer id, String firstName, String lastName, String email) {
        super();
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

    private Integer id;
    private String firstName;
    private String lastName;
    private String email;

    //Getters and setters

    @Override
    public String toString() {
        return "Employee [id=" + id + ", firstName=" + firstName + ", 
        		lastName=" + lastName + ", email=" + email + "]";
    }
}

Employees.java

package com.howtodoinjava.rest.model;

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

public class Employees 
{
    private List<Employee> employeeList;

    public List<Employee> getEmployeeList() {
        if(employeeList == null) {
            employeeList = new ArrayList<>();
        }
        return employeeList;
    }

    public void setEmployeeList(List<Employee> employeeList) {
        this.employeeList = employeeList;
    }
}

DAO 类使用静态列表存储数据。 在这里,我们需要实现实际的数据库交互。

EmployeeDAO.java

package com.howtodoinjava.rest.dao;

import org.springframework.stereotype.Repository;

import com.howtodoinjava.rest.model.Employee;
import com.howtodoinjava.rest.model.Employees;

@Repository
public class EmployeeDAO 
{
    private static Employees list = new Employees();

    static 
    {
        list.getEmployeeList().add(new Employee(1, "Lokesh", "Gupta", "howtodoinjava@gmail.com"));
        list.getEmployeeList().add(new Employee(2, "Alex", "Kolenchiskey", "abc@gmail.com"));
        list.getEmployeeList().add(new Employee(3, "David", "Kameron", "titanic@gmail.com"));
    }

    public Employees getAllEmployees() 
    {
        return list;
    }

    public void addEmployee(Employee employee) {
        list.getEmployeeList().add(employee);
    }
}

5. Spring Boot REST 演示

要启动该应用程序,请运行SpringBootDemoApplication类中的main()方法。 它将启动嵌入式 tomcat 服务器。 在服务器日志中,您将看到已在 Spring 上下文中注册了 API。

Console

s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/employees/],methods=[GET],produces=[application/json]}" onto public com.howtodoinjava.rest.model.Employees com.howtodoinjava.rest.controller. EmployeeController.getEmployees()

s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/employees/],methods=[POST], consumes=[application/json], produces=[application/json]}" onto public org.springframework.http.ResponseEntity <java.lang.Object> com.howtodoinjava.rest.controller. EmployeeController.addEmployee( com.howtodoinjava.rest.model.Employee )

5.1. HTTP GET /employee

服务器启动后,使用其他客户端访问 API。

Spring Boot REST HTTP GET

Spring Boot REST HTTP GET

API response

{
	"employeeList": [
	{
		"id": 1,
		"firstName": "Lokesh",
		"lastName": "Gupta",
		"email": "howtodoinjava@gmail.com"
	},
	{
		"id": 2,
		"firstName": "Alex",
		"lastName": "Kolenchiskey",
		"email": "abc@gmail.com"
	},
		{
			"id": 3,
			"firstName": "David",
			"lastName": "Kameron",
			"email": "titanic@gmail.com"
		}
	],
}

5.2. HTTP POST /employee

Spring Boot REST HTTP POST

Spring Boot REST HTTP POST

Response headers

location: http://localhost:8080/employees/4
content-length: 0
date: Sat, 06 Oct 2018 04:33:37 GMT

再次点击 GET 请求,这一次我们也将获得新增的员工。

Spring Boot REST HTTP GET - Updated

Spring Boot REST HTTP GET – Updated

6. 更多例子

  1. Spring@Controller@RestController注解

让我知道您是否在此 spring boot restful web 服务 json 示例中进行查询。

学习愉快!

下载源码

参考文献:

SpringBoot 启动器

Spring Boot Crud 操作示例与 Hibernate

原文: https://howtodoinjava.com/spring-boot2/spring-boot-crud-hibernate/

学习在 spring boot 应用程序中为 CURD 操作创建 api / 方法,该应用程序使用 Hibernate / jpa 持久性 api 修改数据库中的数据。

1. 概述

在此示例中,我们将创建原始操作并通过 REST API 公开它们,以便 UI 客户端可以调用这些操作。 演示操作使客户端能够修改数据库中的员工记录。

该演示的目的是展示使这些交互成为可能的细节,而不涉及现实应用中涉及的业务逻辑的复杂性。

2. Maven 依赖

在此示例中,我们使用 maven 在项目中添加运行时 jar。 如果您使用 gradle,请查找相关的依赖项。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.howtodoinjava.demo</groupId>
    <artifactId>SpringBoot2Demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringBoot2Demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

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

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

</project>

  • spring-boot-starter-web:用于使用 Spring MVC 构建 Web 层,包括 REST API 和应用程序。 使用 Tomcat 作为默认的嵌入式容器。
  • spring-boot-starter-data-jpa:它包括 spring 数据,hibernate,HikariCP,JPA API ,JPA 实现(默认为 Hibernate),JDBC 和其他必需的库。
  • h2:尽管我们可以使用application.properties文件中的数据源属性轻松添加任何数据库,但我们仍在使用 h2 数据库来减少不必要的复杂性。
  • spring-boot-starter-test:用于通过包含 JUnit,Hamcrest 和 Mockito 的库来测试 Spring Boot 应用程序。

3. Hibernate 配置

3.1. 实体和存储库

使用数据库中数据的第一步是在 JPA 实体类中对其结构建模,并为其创建存储库接口。

尽可能扩展JpaRepository接口,以允许在运行时为任何给定的实体类自动创建存储库实现。 实体类别的类型及其 ID 字段在JpaRepository的通用参数中指定。

请记住,仅包含 JPA API 注解(javax.persistence.*)可使 Hibernate 与应用程序代码脱钩。

EmployeeEntity.java

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="TBL_EMPLOYEES")
public class EmployeeEntity {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name="first_name")
    private String firstName;

    @Column(name="last_name")
    private String lastName;

    @Column(name="email", nullable=false, length=200)
    private String email;

    //Setters and getters

    @Override
    public String toString() {
        return "EmployeeEntity [id=" + id + ", firstName=" + firstName + 
                ", lastName=" + lastName + ", email=" + email   + "]";
    }
}

EmployeeRepository.java

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

import com.howtodoinjava.demo.entity.EmployeeEntity;

@Repository
public interface EmployeeRepository 
        extends JpaRepository<EmployeeEntity, Long> {

}

3.2. 数据源配置

要连接到数据库,我们必须配置数据源。 我们正在使用 H2 数据库,因此将使用各自的属性。

另外,我们使用了几个其他属性来启用 H2 控制台和大量日志记录。

application.properties

spring.datasource.url=jdbc:h2:file:~/test
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

# Enabling H2 Console
spring.h2.console.enabled=true

# Custom H2 Console URL
spring.h2.console.path=/h2-console

# create database schema from SQL files
spring.jpa.hibernate.ddl-auto=none

#Turn Statistics on and log SQL stmts
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.generate_statistics=false
#logging.level.org.hibernate.type=trace
#logging.level.org.hibernate.stat=debug

logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n

4. 服务(使用存储库)

服务层是可选的 – 仍然建议执行其他业务逻辑(如果有)。 通常,我们将在此处与存储库连接以进行原始操作。

EmployeeService.java

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.howtodoinjava.demo.entity.EmployeeEntity;
import com.howtodoinjava.demo.exception.RecordNotFoundException;
import com.howtodoinjava.demo.repository.EmployeeRepository;

@Service
public class EmployeeService {

    @Autowired
    EmployeeRepository repository;

    public List<EmployeeEntity> getAllEmployees()
    {
        List<EmployeeEntity> employeeList = repository.findAll();

        if(employeeList.size() > 0) {
            return employeeList;
        } else {
            return new ArrayList<EmployeeEntity>();
        }
    }

    public EmployeeEntity getEmployeeById(Long id) throws RecordNotFoundException 
    {
        Optional<EmployeeEntity> employee = repository.findById(id);

        if(employee.isPresent()) {
            return employee.get();
        } else {
            throw new RecordNotFoundException("No employee record exist for given id");
        }
    }

    public EmployeeEntity createOrUpdateEmployee(EmployeeEntity entity) throws RecordNotFoundException 
    {
        Optional<EmployeeEntity> employee = repository.findById(entity.getId());

        if(employee.isPresent()) 
        {
            EmployeeEntity newEntity = employee.get();
            newEntity.setEmail(entity.getEmail());
            newEntity.setFirstName(entity.getFirstName());
            newEntity.setLastName(entity.getLastName());

            newEntity = repository.save(newEntity);

            return newEntity;
        } else {
            entity = repository.save(entity);

            return entity;
        }
    } 

    public void deleteEmployeeById(Long id) throws RecordNotFoundException 
    {
        Optional<EmployeeEntity> employee = repository.findById(id);

        if(employee.isPresent()) 
        {
            repository.deleteById(id);
        } else {
            throw new RecordNotFoundException("No employee record exist for given id");
        }
    } 
}

5. REST 控制器

最后,通过 MVC URL 或 REST 端点公开所有操作。 客户将与这些端点连接以获取/更新/删除员工记录。

请注意注解@RestController@RequestMapping@GetMapping@PostMapping@DeleteMapping,它们将各种 URI 映射到控制器方法。

EmployeeController.java

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.howtodoinjava.demo.entity.EmployeeEntity;
import com.howtodoinjava.demo.exception.RecordNotFoundException;
import com.howtodoinjava.demo.service.EmployeeService;

@RestController
@RequestMapping("/employees")
public class EmployeeController 
{
    @Autowired
    EmployeeService service;

    @GetMapping
    public ResponseEntity<List<EmployeeEntity>> getAllEmployees() {
        List<EmployeeEntity> list = service.getAllEmployees();

        return new ResponseEntity<List<EmployeeEntity>>(list, new HttpHeaders(), HttpStatus.OK);
    }

    @GetMapping("/{id}")
    public ResponseEntity<EmployeeEntity> getEmployeeById(@PathVariable("id") Long id) 
                                                    throws RecordNotFoundException {
        EmployeeEntity entity = service.getEmployeeById(id);

        return new ResponseEntity<EmployeeEntity>(entity, new HttpHeaders(), HttpStatus.OK);
    }

    @PostMapping
    public ResponseEntity<EmployeeEntity> createOrUpdateEmployee(EmployeeEntity employee)
                                                    throws RecordNotFoundException {
        EmployeeEntity updated = service.createOrUpdateEmployee(employee);
        return new ResponseEntity<EmployeeEntity>(updated, new HttpHeaders(), HttpStatus.OK);
    }

    @DeleteMapping("/{id}")
    public HttpStatus deleteEmployeeById(@PathVariable("id") Long id) 
                                                    throws RecordNotFoundException {
        service.deleteEmployeeById(id);
        return HttpStatus.FORBIDDEN;
    }

}

6. Spring Boot Crud 操作演示

现在,完成编码部分后,启动 spring boot 应用程序。 它将与 H2 数据库控制台一起使用所有 URL 端点。

  • HTTP GET http://localhost:8080/employees

    Console

    Hibernate: 
        select
            employeeen0_.id as id1_0_,
            employeeen0_.email as email2_0_,
            employeeen0_.first_name as first_na3_0_,
            employeeen0_.last_name as last_nam4_0_ 
        from
            tbl_employees employeeen0_
    
    

    API Response

    [
        {
            "id": 1,
            "firstName": "Lokesh",
            "lastName": "Gupta",
            "email": "abc@gmail.com"
        },
        {
            "id": 2,
            "firstName": "Deja",
            "lastName": "Vu",
            "email": "xyz@email.com"
        },
        {
            "id": 3,
            "firstName": "Caption",
            "lastName": "America",
            "email": "cap@marvel.com"
        }
    ]
    
    
  • HTTP GET http://localhost:8080/employees/2

    Console

    Hibernate: 
        select
            employeeen0_.id as id1_0_0_,
            employeeen0_.email as email2_0_0_,
            employeeen0_.first_name as first_na3_0_0_,
            employeeen0_.last_name as last_nam4_0_0_ 
        from
            tbl_employees employeeen0_ 
        where
            employeeen0_.id=?
    
    

    API Response

    {
        "id": 2,
        "firstName": "Deja",
        "lastName": "Vu",
        "email": "xyz@email.com"
    }
    
    

在有关在具有 JPA Hibernate来管理后端数据更新的 Spring Boot 应用程序中创建和公开 CRUD 操作的注解中,向您提问。

学习愉快!

下载源码

Spring Boot 2 – OAuth2 Auth 和资源服务器

原文: https://howtodoinjava.com/spring-boot2/oauth2-auth-server/

在本 Spring Security oauth2 教程中,学习构建授权服务器来对您的身份进行身份验证,以提供access_token,您可以使用该服务器向资源服务器请求数据

1. 概述

OAuth 2 是一种授权方法,用于通过 HTTP 协议提供对受保护资源的访问。 首先,oauth2 使第三方应用程序可以获得对 HTTP 服务的有限访问权限:

  • 通过协调资源所有者和 HTTP 服务之间的批准交互来代表资源所有者
  • 或允许第三方应用程序代表自己获取访问权限。

1.1. 角色

OAuth 定义了四个角色:

  • 资源所有者 – 应用程序的用户。
  • 客户端 – 需要访问资源服务器上用户数据的应用程序(用户正在使用)。
  • 资源服务器 – 存储用户数据和 http 服务,这些服务可以将用户数据返回到经过身份验证的客户端。
  • 授权服务器 – 负责验证用户的身份并提供授权令牌。 资源服务器接受此令牌并验证您的身份。

Oauth2 Flow

Oauth2 流程

1.2. 访问令牌与刷新令牌

访问令牌是表示颁发给客户端的授权的字符串。 令牌代表资源所有者授予并由资源服务器和授权服务器强制执行的特定访问范围和持续时间。

刷新令牌由授权服务器发布(与访问令牌一起)给客户端,用于在当前访问令牌变为无效或过期时获取新的访问令牌,或用于获取具有相同权限的其他访问令牌或更窄的范围(访问令牌的生存期和权限可能比资源所有者授权的要短)。 授权服务器可以决定是否发出刷新令牌。

  • 访问令牌的责任是在数据过期之前访问数据。
  • 当现有访问令牌过期时,刷新令牌的职责是请求新的访问令牌。

2. Oauth2 – 授权服务器

要使用 spring security oauth2 模块创建授权服务器,我们需要使用注解@EnableAuthorizationServer并扩展类AuthorizationServerConfigurerAdapter

OAuth2AuthorizationServer.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter 
{
	@Autowired
	private BCryptPasswordEncoder passwordEncoder;

	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		security
			.tokenKeyAccess("permitAll()")
			.checkTokenAccess("isAuthenticated()")
			.allowFormAuthenticationForClients();
	}

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients
			.inMemory()
			.withClient("clientapp").secret(passwordEncoder.encode("123456"))
			.authorizedGrantTypes("password", "authorization_code", "refresh_token")
			.authorities("READ_ONLY_CLIENT")
			.scopes("read_profile_info")
			.resourceIds("oauth2-resource")
			.redirectUris("http://localhost:8081/login")
			.accessTokenValiditySeconds(120)
			.refreshTokenValiditySeconds(240000);
	}
}

  • Spring Security oauth 公开了两个用于检查令牌的端点(/oauth/check_token/oauth/token_key),这些令牌默认情况下在denyAll()之后受到保护。tokenKeyAccess()checkTokenAccess()方法打开这些端点供使用。

  • ClientDetailsServiceConfigurer用于定义客户端详细信息服务的内存中或 JDBC 实现。我们使用了内存实现。它具有以下重要属性:

    clientId – (必填)客户端 ID。
    password – (对于受信任的客户端是必需的)客户端密码(如果有)。
    scope – 客户端受限的范围。 如果范围未定义或为空(默认值),则客户端不受范围的限制。
    authorizedGrantTypes – 授权给客户端使用的授权类型。 默认值为空。
    previlege – 授予客户端的权限(常规的 Spring Security 权限)。
    redirectUris – 将用户代理重定向到客户端的重定向端点。 它必须是一个绝对 URL。

3. Oauth2 – 资源服务器

要创建资源服务器组件,请使用@EnableResourceServer注解并扩展ResourceServerConfigurerAdapter类。

OAuth2ResourceServer.java

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter 
{
	@Override
	public void configure(HttpSecurity http) throws Exception {
		http
        	.authorizeRequests()
        	.antMatchers("/api/**").authenticated()
        	.antMatchers("/").permitAll();
	}
}

上面的 config 在所有从/api开始的端点上启用保护。 其他所有端点均可自由访问。

资源服务器还提供了一种对用户本身进行身份验证的机制。 在大多数情况下,它将是基于表单的登录名。

SecurityConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	http
        	.antMatcher("/**")
	        	.authorizeRequests()
	        	.antMatchers("/oauth/authorize**", "/login**", "/error**")
	        	.permitAll()
        	.and()
            	.authorizeRequests()
            	.anyRequest().authenticated()
        	.and()
        		.formLogin().permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
        	.inMemoryAuthentication()
            .withUser("humptydumpty").password(passwordEncoder().encode("123456")).roles("USER");
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){ 
        return new BCryptPasswordEncoder(); 
    }
}

WebSecurityConfigurerAdapter类上方,设置基于表单的登录页面,并使用permitAll()打开授权 URL。

4. Oauth2 保护的 REST 资源

出于演示目的,我仅创建了一个 API,该 API 会返回登录用户的姓名和电子邮件。

RestResource.java

import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class RestResource 
{
	@RequestMapping("/api/users/me")
	public ResponseEntity<UserProfile> profile() 
	{
		//Build some dummy data to return for testing
		User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
		String email = user.getUsername() + "@howtodoinjava.com";

		UserProfile profile = new UserProfile();
		profile.setName(user.getUsername());
		profile.setEmail(email);

		return ResponseEntity.ok(profile);
	}
}

UserProfile.java

public class UserProfile 
{
	private String name;
	private String email;

	//Setters and getters

	@Override
	public String toString() {
		return "UserProfile [name=" + name + ", email=" + email + "]";
	}
}

5. 演示

我们有一个 API http://localhost:8080/api/users/me,可以通过直接在登录名中输入用户名/密码来访问它,但是第三方应用程序不能像在浏览器中那样访问 API。 他们需要 oauth2 令牌。

5.1. 从用户获取授权授权代码

如上面的序列图所示,第一步是从 URL:http://localhost:8080/oauth/authorize?client_id=clientapp&response_type=code&scope=read_profile_info获得资源所有者的授权授予

它将带来一个登录页面。 提供用户名和密码。 对于此演示,请使用humptydumpty123456

Login page

登录页面

登录后,您将被重定向到“授予访问权限”页面,您可以在其中选择授予对第三方应用程序的访问权限。

Get authorization grant

获取权限授权

它将重定向到类似http://localhost:8081/login?code=EAR76A的 URL。 这里'EAR76A'是第三方应用程序的授权代码。

5.2. 从授权服务器获取访问令牌

现在,应用程序将使用授权授予来获取访问令牌。 在这里,我们需要提出以下要求。 使用此处第一步中获得的代码。

Access token request from postman

http://localhost:8080/oauth/token

Headers:

Content-Type: application/x-www-form-urlencoded
authorization: Basic Y2xpZW50YXBwOjEyMzQ1Ng==

Form data - application/x-www-form-urlencoded:

grant_type=authorization_code
code=EAR76A
redirect_uri=http://localhost:8081/login

它将在单独的窗口中询问客户端应用程序凭据。

Client auth

客户端授权

或从 cURL 发出类似请求。

Access token request from cURL

curl -X POST --user clientapp:123456 http://localhost:8081/oauth/token 
		-H "content-type: application/x-www-form-urlencoded" 
		-d "code=FfrzTj&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost%3A8082%2Flogin&scope=read_user_info"

Access token response

{
	"access_token": "59ddb16b-6943-42f5-8e2f-3acb23f8e3c1",
	"token_type": "bearer",
	"refresh_token": "cea0aa8f-f732-44fc-8ba3-5e868d94af64",
	"expires_in": 4815,
	"scope": "read_profile_info"
}

阅读更多:如何在 Windows 中执行 cURL 命令

5.3. 从资源服务器访问用户数据

获得访问令牌后,我们可以转到资源服务器以获取受保护的用户数据。

达到以下要求:

Get resource request

curl -X GET http://localhost:8080/api/users/me 
	 -H "authorization: Bearer 59ddb16b-6943-42f5-8e2f-3acb23f8e3c1"

它将返回响应。

Get resource response

{"name":"humptydumpty","email":"humptydumpty@howtodoinjava.com"}

6. Spring Security oauth2 应用程序的 Maven 依赖项

spring security 5 oauth2 示例使用的 pom 文件是:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.4.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.howtodoinjava</groupId>
	<artifactId>spring-oauth2-resource-server-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-oauth2-resource-server-demo</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.security.oauth.boot</groupId>
			<artifactId>spring-security-oauth2-autoconfigure</artifactId>
			<version>2.1.8.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

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

</project>

将您的问题留在我的评论中。

学习愉快!

参考文献:

Oauth2 协议

OAuth2 自动配置

下载源码

在 Spring Boot 2 中进行测试

原文: https://howtodoinjava.com/spring-boot2/testing/testing-support/

学习在 Spring Boot 应用程序中编写单元测试和集成测试。 了解单元测试和集成测试之间的区别,以及支持此类测试的注解。

1. 单元测试与集成测试

通常,任何软件应用程序都分为不同的模块和组件。 孤立地测试一个这样的组件时,称为单元测试。 编写该代码是为了验证一小段代码是否正在执行预期的操作。

单元测试无法验证应用程序代码是否与外部依赖项正确配合。 它专注于单个组件并模拟与该组件交互的所有依赖项。

一旦开发并集成了不同的模块,便会执行集成测试。 其主要目的是发现不同模块相互交互以端对端处理用户请求时的问题。

集成测试可以根据要测试的内容,将整个应用程序置于范围内或仅包含某些组件。 他们可能需要为数据库实例和硬件分配资源。 尽管也可以模拟这些交互以提高测试性能。

就典型的 Spring boot crud 应用程序而言,可以编写单元测试来分别测试 REST 控制器,DAO 层等。 甚至不需要嵌入式服务器。

在集成测试中,我们将专注于测试从控制器到持久层的完整请求处理。 应用程序应在嵌入式服务器中运行,以创建应用程序上下文和所有 bean。 这些 bean 中的某些可能会被覆盖以模拟某些行为。

2. 依赖项

2.1. Junit 4 测试(默认)

要在 Spring Boot 应用程序中编写测试,最好的方法是在pom.xml文件中包含spring-boot-starter-test。 它将 Junit 4,AssertJ,Hamcrest,Mockito,JSONassert 和 JsonPath 依赖项带入具有测试范围的应用程序中。

pom.xml

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

2.2. Junit 5 测试

Spring Boot 也支持 Junit 5 测试。 要使用 Junit 5,请包含它的依赖项,并从spring-boot-starter-test中排除 Junit 4。

编写集成测试时,嵌入式数据库依赖关系非常方便。

pom.xml

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

    <!-- exclude junit 4 -->
    <exclusions>
        <exclusion>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </exclusion>
    </exclusions>

</dependency>

<!-- junit 5 -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
    <version>1.4.194</version>
</dependency>

3. 运行器

用 Spring Boot 编写的测试可以多种方式运行。 让我们来看看几种最常见的方式。

3.1. @RunWith(SpringRunner.class) – [Junit 4]

默认情况下,编写的测试在 Junit 4 中进行。要运行此类测试,我们可以在类级别使用@RunWith注解使用SpringRunner类(扩展SpringJUnit4ClassRunner)。

SpringRunner example

@RunWith(SpringRunner.class)
@WebFluxTest(controllers = EmployeeController.class)
public class EmployeeRestControllerTest {
	//tests
}

3.2. @RunWith(MockitoJUnitRunner.class) – [Junit 4 with Mockito]

它测试使用@Mock对象,首选使用MockitoJUnitRunner对象。 它初始化带有 Mock 注解的模拟,因此不需要显式使用MockitoAnnotations.initMocks(Object)。在每种测试方法之前都要初始化模拟。

MockitoJUnitRunner example

@RunWith(MockitoJUnitRunner.class)
public class EmployeeRestControllerTest 
{
	@Mock
    private Repository repository;
}

3.3. @ExtendWith(SpringExtension.class) – [Junit 5]

SpringExtension将 Spring TestContext Framework 集成到 JUnit 5 的 Jupiter 编程模型中。

SpringExtension example

//@ExtendWith(SpringExtension.class)  // included in @WebFluxTest
@WebFluxTest(controllers = EmployeeController.class)
@Import(EmployeeService.class)
public class EmployeeControllerTest 
{
	//
}

3.4. @ExtendWith(MockitoExtension.class) – [Junit 5]

MockitoExtension初始化模拟并处理严格的存根。它等效于MockitoJUnitRunner

大多数测试注解都包含此注解,因此无需明确包含它。

MockitoExtension example

@ExtendWith(MockitoExtension.class)
public class EmployeeControllerTest 
{
	//
}

4. Spring Boot *Test注解

Spring Boot 提供了各种注解,以启用仅与应用程序的某些部分相关的测试基础结构。 它还提供了注解,这些注解也有助于集成测试。 让我们拜访他们。

4.1. @SpringBootTest

该注解有助于编写集成测试。 它启动嵌入式服务器并完全初始化应用程序上下文。 我们可以使用@Autowired注解将依赖项注入测试类中。

我们还可以使用嵌套@Configuration类或显式@TestConfiguration类提供特定于测试的 bean 配置。

它还为不同的webEnvironment模式提供支持,并在定义的或随机的端口上监听正在运行的 Web 服务器。 它还注册了TestRestTemplate和/或WebTestClient bean,用于 Web 测试。

@SpringBootTest example

@SpringBootTest(classes = SpringBootDemoApplication.class, 
		webEnvironment = WebEnvironment.RANDOM_PORT)
public class EmployeeControllerIntegrationTests 
{
	@LocalServerPort
	private int port;

	@Autowired
	private TestRestTemplate restTemplate;

	//tests
}

阅读更多:@SpringBootTest示例

4.2. @WebMvcTest

此注解用于 Spring MVC 测试。 它禁用完整的自动配置,而仅应用与 MVC 测试相关的配置。

它还会自动配置MockMvc实例。 通过将.class作为注解属性,我们只能初始化一个 Web 控制器。

@WebMvcTest example

@WebMvcTest(EmployeeRESTController.class)
public class TestEmployeeRESTController {

  	@Autowired
  	private MockMvc mvc;

 	//
}

阅读更多:@WebMvcTest示例

4.3. @WebFluxTest

该注解禁用了完全自动配置,而是仅应用与 WebFlux 测试相关的配置。 默认情况下,用@WebFluxTest注解的测试也将自动配置WebTestClient

通常,@WebFluxTest@MockBean@Import结合使用以创建控制器 bean 所需的任何协作者。

@WebMvcTest example

@WebFluxTest(controllers = EmployeeController.class)
@Import(EmployeeService.class)
public class EmployeeControllerTest 
{
	@MockBean
	EmployeeRepository repository;

	@Autowired
	private WebTestClient webClient;

	//tests
}

阅读更多:@WebFluxTest示例

4.4. 其他常用注解

  • @JdbcTest – 当测试仅针对基于 jdbc 的组件时,可以用于典型的 jdbc 测试。它禁用完全自动配置,而是仅应用与 jdbc 测试相关的配置。

    默认情况下,用@JdbcTest注解的测试是事务性的,并在每个测试结束时回滚。 注解配置内存中嵌入式数据库和JdbcTemplate

  • @JooqTest – 当测试仅关注基于 jOOQ 的组件时,可以使用它。 注意,默认情况下,带有@JooqTest注解的测试使用应用程序配置的数据库。 要使用嵌入式内存数据库,可以使用 @AutoConfigureTestDatabase注解来覆盖这些设置。

  • @JsonTest – 当测试仅关注 JSON 序列化时使用。 它初始化@JsonComponentJacksonTesterJsonbTesterGsonTester字段。

  • @DataJpaTest – 它可以用来测试 JPA 应用程序。 默认情况下,它将扫描@Entity类并配置 Spring Data JPA 存储库。 如果在类路径上有嵌入式数据库,它也将配置一个。

    默认情况下,数据 JPA 测试是事务性的,并在每次测试结束时回滚。

    数据 JPA 测试也可以注入TestEntityManager bean,它为专门设计用于测试的标准 JPA EntityManager提供了替代方法。

  • @DataMongoTest – 用于测试 MongoDB 应用程序。 默认情况下,它配置内存嵌入式 MongoDB(如果可用),配置MongoTemplate,扫描@Document类,并配置 Spring Data MongoDB 存储库。

  • @DataRedisTest – 用于测试 Redis 应用程序。 默认情况下,它会扫描@RedisHash类并配置 Spring Data Redis 存储库。

  • @DataLdapTest – 用于测试 LDAP 应用程序。 默认情况下,它配置内存嵌入式 LDAP(如果可用),配置LdapTemplate,扫描@Entry类,并配置 Spring Data LDAP 存储库。

  • @RestClientTest – 用于测试 REST 客户端。 默认情况下,它会自动配置 Jackson,GSON 和 Jsonb 支持,配置RestTemplateBuilder,并添加对MockRestServiceServer的支持。

5. 测试配置

@TestConfiguration@Configuration的特殊形式,可用于定义其他 bean 或测试的自定义项。

在 SpringBoot 中,任何在顶级类中配置并带有@TestConfiguration注解的 Bean 将不会通过组件扫描获得。 我们必须使用包含测试用例的类显式注册@TestConfiguration类。

最好的是,这些测试配置不会自动成为应用程序主要配置的一部分。 仅可通过以下两种方式之一按需使用它们以包括此附加测试配置,即

5.1. @Import注解

它称为将一个或多个配置类导入应用程序上下文或 Spring 测试上下文中。

Import test config

@Import(MyTestConfiguration.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SpringBootDemoApplicationTests
{  
    @LocalServerPort
    int randomServerPort;

    @Autowired
    DataSource datasource;

    //tests
}

5.2. 静态嵌套类

我们可以在测试类内部的嵌套类中定义测试配置。 嵌套类可以使用@Configuration@TestConfiguration注解进行注解。

  • 对于嵌套的@Configuration类,将使用给定的配置“代替”应用程序的主要配置。
  • 嵌套@TestConfiguration类用于“附加”应用程序的主要配置。

Import test config

@Import(MyTestConfiguration.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SpringBootDemoApplicationTests
{  
    @LocalServerPort
    int randomServerPort;

    @Autowired
    DataSource datasource;

    //tests
}

6. 模拟

对于使用或不使用 Mockito 的依赖关系,Spring Boot 都提供了出色的支持。

6.1. 带有 Mockito – @Mock

@Mock用于模拟创建。 它使测试类更具可读性。 在测试类中,要处理模拟注解,必须至少使用一次MockitoAnnotations.initMocks(testClass)

请注意,如果您正在使用RunWith(MockitoJUnitRunner.class),则无需显式使用MockitoAnnotations.initMocks()。 在每种测试方法之前都要初始化模拟。

在不需要 Spring 文本上下文的单元测试中使用@Mock

6.2. 没有 Mockito – @MockBean

@MockBean注解用于将模拟添加到 Spring ApplicationContext。它允许模拟类或接口,并记录和验证其行为。

有趣的是,上下文中定义的任何相同类型的现有 bean 都将被该模拟代替。 如果没有定义现有的 bean,将添加一个新的 bean。

@MockBean与 Mockito 的@Mock类似,但具有 Spring 支持。 我们通常将@MockBean@WebMvcTest@WebFluxTest注解一起使用。 这些注解适用于 Web 测试片,并且仅限于单个控制器。

在给定的示例中,我们在模拟EmployeeRepository bean。 这样,将调用所有应用程序代码,但将模拟所有存储库交互。

@WebFluxTest(controllers = EmployeeController.class)
@Import(EmployeeService.class)
public class EmployeeControllerTest
{
    @MockBean
    EmployeeRepository repository;

    @Autowired
    private WebTestClient webClient;

    //tests
}

7. 结论

Spring Boot 为应用程序及其各种模块的单元测试和集成测试提供了出色的支持。 我们将非常小心地通过使用注解来使用所提供的支持。

使用@SpringBootTest注解进行集成测试,而其他自动配置注解则用于特定组件的单元测试。

模拟特定行为是非常普遍的要求,为此,我们可以使用嘲笑者的@Mock或 Spring 的x@MockBean注解。

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

学习愉快!

Spring RestTemplate – Spring REST 客户端示例

原文: https://howtodoinjava.com/spring-boot2/resttemplate/spring-restful-client-resttemplate-example/

在学习为 XML 表示形式和 JSON 表示形式构建 Spring REST API 之后,让我们构建 Spring REST 客户端以使用我们编写的 API 在链接的示例中。

在 Spring 应用程序内部访问第三方 REST 服务的过程涉及 Spring RestTemplate类的使用。 RestTemplate类的设计原理与许多其他 Spring *Template类(例如JdbcTemplateJmsTemplate)相同,为执行复杂任务提供了具有默认行为的简化方法。

鉴于RestTemplate类是为调用 REST 服务设计的,因此它的主要方法与 REST 的基础紧密结合在一起就不足为奇了,REST 的基础是 HTTP 协议的方法:HEADGETPOSTPUTDELETEOPTION。 例如,RestTemplate类具有方法headForHeaders()getForObject()postForObject()put()delete()等。

阅读更多和源代码: Spring REST 示例 – JSON

阅读更多:带有 HttpClient 的 RestTemplate Java 配置

1. Spring RestTemplate – HTTP GET 方法示例

1.1. XML 响应

REST API 代码

用于 HTTP GET 方法的 Spring REST API。

@RequestMapping(value = "/employees", produces = MediaType.APPLICATION_XML_VALUE, method = RequestMethod.GET)
public String getAllEmployeesXML(Model model) 
{
	model.addAttribute("employees", getEmployeesCollection());
	return "xmlTemplate";
}

REST 客户端代码

Spring REST 客户端使用RestTemplate访问 HTTP GET api 请求。

private static void getEmployees()
{
	final String uri = "http://localhost:8080/springrestexample/employees.xml";

	RestTemplate restTemplate = new RestTemplate();
	String result = restTemplate.getForObject(uri, String.class);

	System.out.println(result);
}

1.2. JSON 响应

REST API 代码

@RequestMapping(value = "/employees", produces = MediaType.APPLICATION_JSON_VALUE,  method = RequestMethod.GET)
public String getAllEmployeesJSON(Model model) 
{
	model.addAttribute("employees", getEmployeesCollection());
	return "jsonTemplate";
}

REST 客户端代码

private static void getEmployees()
{
	final String uri = "http://localhost:8080/springrestexample/employees.json";

	RestTemplate restTemplate = new RestTemplate();
	String result = restTemplate.getForObject(uri, String.class);

	System.out.println(result);
}

1.3. 使用RestTemplate的自定义 HTTP 标头

REST API 代码

@RequestMapping(value = "/employees", produces = MediaType.APPLICATION_JSON_VALUE,  method = RequestMethod.GET)
public String getAllEmployeesJSON(Model model) 
{
	model.addAttribute("employees", getEmployeesCollection());
	return "jsonTemplate";
}

REST 客户端代码

private static void getEmployees()
{
	final String uri = "http://localhost:8080/springrestexample/employees";

	RestTemplate restTemplate = new RestTemplate();

	HttpHeaders headers = new HttpHeaders();
	headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
	HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);

	ResponseEntity<String> result = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);

	System.out.println(result);
}

1.4. 获取响应作为对象

REST API 代码

@RequestMapping(value = "/employees", produces = MediaType.APPLICATION_XML_VALUE, method = RequestMethod.GET)
public String getAllEmployeesXML(Model model) 
{
	model.addAttribute("employees", getEmployeesCollection());
	return "xmlTemplate";
}

REST 客户端代码

private static void getEmployees()
{
	final String uri = "http://localhost:8080/springrestexample/employees";
	RestTemplate restTemplate = new RestTemplate();

	EmployeeListVO result = restTemplate.getForObject(uri, EmployeeListVO.class);

	System.out.println(result);
}

1.5. URL 参数

REST API 代码

@RequestMapping(value = "/employees/{id}")
public ResponseEntity<EmployeeVO> getEmployeeById (@PathVariable("id") int id) 
{
	if (id <= 3) {
		EmployeeVO employee = new EmployeeVO(1,"Lokesh","Gupta","howtodoinjava@gmail.com");
		return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
	}
	return new ResponseEntity(HttpStatus.NOT_FOUND);
}

REST 客户端代码

private static void getEmployeeById()
{
	final String uri = "http://localhost:8080/springrestexample/employees/{id}";

	Map<String, String> params = new HashMap<String, String>();
	params.put("id", "1");

	RestTemplate restTemplate = new RestTemplate();
	EmployeeVO result = restTemplate.getForObject(uri, EmployeeVO.class, params);

	System.out.println(result);
}

2. Spring RestTemplate – HTTP POST 方法示例

REST API 代码

用于 HTTP POST 方法的 Spring REST API。

@RequestMapping(value = "/employees", method = RequestMethod.POST)
public ResponseEntity<String> createEmployee(@RequestBody EmployeeVO employee) 
{
	System.out.println(employee);
	return new ResponseEntity(HttpStatus.CREATED);
}

REST 客户端代码

Spring REST 客户端使用RestTemplate访问 HTTP POST api 请求。

private static void createEmployee()
{
	final String uri = "http://localhost:8080/springrestexample/employees";

	EmployeeVO newEmployee = new EmployeeVO(-1, "Adam", "Gilly", "test@email.com");

	RestTemplate restTemplate = new RestTemplate();
	EmployeeVO result = restTemplate.postForObject( uri, newEmployee, EmployeeVO.class);

	System.out.println(result);
}

3. Spring RestTemplate – HTTP PUT 方法示例

REST API 代码

用于 HTTP PUT 方法的 Spring REST API。

@RequestMapping(value = "/employees/{id}", method = RequestMethod.PUT)
public ResponseEntity<EmployeeVO> updateEmployee(@PathVariable("id") int id, @RequestBody EmployeeVO employee) 
{
	System.out.println(id);
	System.out.println(employee);
	return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
}

REST 客户端代码

Spring REST 客户端使用RestTemplate访问 HTTP PUT api 请求。

private static void updateEmployee()
{
	final String uri = "http://localhost:8080/springrestexample/employees/{id}";

	Map<String, String> params = new HashMap<String, String>();
	params.put("id", "2");

	EmployeeVO updatedEmployee = new EmployeeVO(2, "New Name", "Gilly", "test@email.com");

	RestTemplate restTemplate = new RestTemplate();
	restTemplate.put ( uri, updatedEmployee, params);
}

4. Spring RestTemplate – HTTP DELETE 方法示例

REST API 代码

用于 HTTP DELETE 方法的 Spring REST API。

@RequestMapping(value = "/employees/{id}", method = RequestMethod.DELETE)
public ResponseEntity<String> updateEmployee(@PathVariable("id") int id) 
{
	System.out.println(id);
	return new ResponseEntity(HttpStatus.OK);
}

REST 客户端代码

Spring REST 客户端使用RestTemplate访问 HTTP DELETE api 请求。

private static void deleteEmployee()
{
	final String uri = "http://localhost:8080/springrestexample/employees/{id}";

	Map<String, String> params = new HashMap<String, String>();
	params.put("id", "2");

	RestTemplate restTemplate = new RestTemplate();
	restTemplate.delete ( uri,  params );
}

随意复制和修改以上 Spring RestTemplate示例,以在您的 MVC 应用程序中构建 Spring REST 客户端

5. 更多的RestTemplate示例

Spring RestTemplate基本身份验证示例
Spring RestTemplate超时配置示例
Spring RestTemplateBuilder示例
Spring RestTemplateHttpClient配置示例
Spring Boot RestTemplate GET 示例
Spring Boot RestTemplate POST 示例
带有RestTemplate的 Spring Boot JUnit 示例
带有标头的 Spring boot TestRestTemplate POST 示例
带有RestTemplate的 Spring ClientHttpRequestInterceptor

学习愉快!

Spring 使用注解配置 Bean

原文: https://howtodoinjava.com/spring5/core/spring-bean-java-config/

学习使用 Java 配置创建 Spring Bean,并使用任何独立应用程序的注解。 我们将学习如何在不扫描组件注解和使用@Bean注解的情况下创建它。

1. 带有组件扫描的注解配置

使用组件扫描创建 bean 可以分两个步骤完成。

1.1. 用相应的组件注解为 bean 注解

我们将酌情使用以下四个注解之一。

  • @Component
  • @Repository
  • @Service
  • @Controller

阅读更多:Spring 组件注解

EmployeeManagerImpl.java

package com.howtodoinjava.spring.service.impl;

import org.springframework.stereotype.Service;
import com.howtodoinjava.spring.model.Employee;
import com.howtodoinjava.spring.service.EmployeeManager;

@Service
public class EmployeeManagerImpl implements EmployeeManager {

	@Override
	public Employee create() {
		Employee emp =  new Employee();
		emp.setId(1);
		emp.setName("Lokesh");
		return emp;
	}
}

1.2. 在@ComponentScan注解中包含 bean 程序包

AppConfig.java

@Configuration
@ComponentScan(basePackages = "com.howtodoinjava.spring.service")
public class AppConfig {

}

1.3. 示例

package com.howtodoinjava.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.howtodoinjava.spring.model.Employee;
import com.howtodoinjava.spring.service.EmployeeManager;

public class Main 
{
    public static void main( String[] args )
    {
    	//Method 1
    	//ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

    	//Method 2
    	AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.register(AppConfig.class);
        ctx.refresh();

    	EmployeeManager empManager = ctx.getBean(EmployeeManager.class);
    	Employee emp = empManager.create();

    	System.out.println(emp);
    }
}

程序输出:

Console

Employee [id=1, name=Lokesh]

2. 使用@Bean@Configuration注解

要使用@Bean注解创建 spring 应用程序内容,请使用以下步骤:

2.1. 创建 Java Bean 类(无需注解或任何其他操作)

EmployeeManagerImpl.java

public class EmployeeManagerImpl implements EmployeeManager {

	@Override
	public Employee create() {
		Employee emp =  new Employee();
		emp.setId(1);
		emp.setName("Lokesh");
		return emp;
	}
}

2.2. 在配置类中创建@Bean注解方法

AppConfig.java

package com.howtodoinjava.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.howtodoinjava.spring.service.EmployeeManager;
import com.howtodoinjava.spring.service.impl.EmployeeManagerImpl;

@Configuration
public class AppConfig {

    @Bean
    public EmployeeManager employeeManager() {
        return new EmployeeManagerImpl();
    }

}

2.3. 示例

package com.howtodoinjava.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.howtodoinjava.spring.model.Employee;
import com.howtodoinjava.spring.service.EmployeeManager;

public class Main 
{
    public static void main( String[] args )
    {
    	//Method 1
    	//ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

    	//Method 2
    	AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.register(AppConfig.class);
        ctx.refresh();

    	EmployeeManager empManager = ctx.getBean(EmployeeManager.class);
    	Employee emp = empManager.create();

    	System.out.println(emp);
    }
}

程序输出:

Console

Employee [id=1, name=Lokesh]

这就是使用纯 Java 代码和注解创建 Spring bean 的两种简单方法。

学习愉快!

下载源码

Spring Boot – CRUD 应用程序

原文: https://howtodoinjava.com/spring-boot2/crud-application-thymeleaf/

通过基于 Thymeleaf 和 spring mvc 支持的基于表单的 UI,学习构建支持 CRUD 操作的 Spring Boot Web 应用程序。

1. 概述

在本教程中,我们正在创建具有两个视图的 Web 应用程序:

  • 列出所有员工视图 – 以表格形式在 UI 中从数据库显示所有员工。 此外,还有指向“更新”或“删除”任何员工的链接。 该界面还具有一个单独的选项,可以导航到“创建”员工界面。

    Spring boot hibernate thymeleaf example

    列出所有员工的界面

  • 创建/更新员工视图 – 此界面用于添加新员工或编辑现有员工的详细信息。

    Add employee screen

    添加员工的界面

此示例中有两个主要组件需要重点关注-MVC 控制器和 UI 视图。

2. Spring MVC 控制器

控制器类具有 URL 映射及其处理器方法。 所有 CRUD 操作都有处理器方法,包括 POST 操作,以处理表单提交以创建/更新员工的过程。

注意给定的处理器方法如何将模型数据绑定到视图; 并且它们以字符串格式返回视图名称,该视图名称由 HTML 文件中的视图解析器解析。

EmployeeMvcController.java

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.howtodoinjava.demo.entity.EmployeeEntity;
import com.howtodoinjava.demo.exception.RecordNotFoundException;
import com.howtodoinjava.demo.service.EmployeeService;

@Controller
@RequestMapping("/")
public class EmployeeMvcController 
{
	@Autowired
	EmployeeService service;

	@RequestMapping
	public String getAllEmployees(Model model) 
	{
		List<EmployeeEntity> list = service.getAllEmployees();

		model.addAttribute("employees", list);
		return "list-employees";
	}

	@RequestMapping(path = {"/edit", "/edit/{id}"})
	public String editEmployeeById(Model model, @PathVariable("id") Optional<Long> id) 
							throws RecordNotFoundException 
	{
		if (id.isPresent()) {
			EmployeeEntity entity = service.getEmployeeById(id.get());
			model.addAttribute("employee", entity);
		} else {
			model.addAttribute("employee", new EmployeeEntity());
		}
		return "add-edit-employee";
	}

	@RequestMapping(path = "/delete/{id}")
	public String deleteEmployeeById(Model model, @PathVariable("id") Long id) 
							throws RecordNotFoundException 
	{
		service.deleteEmployeeById(id);
		return "redirect:/";
	}

	@RequestMapping(path = "/createEmployee", method = RequestMethod.POST)
	public String createOrUpdateEmployee(EmployeeEntity employee) 
	{
		service.createOrUpdateEmployee(employee);
		return "redirect:/";
	}
}

  • getAllEmployees() – 返回所有员工的列表,并映射到路径/。 这是应用程序的默认视图。
  • editEmployeeById() – 用于添加新员工或编辑现有员工。 两种操作都使用相同的 HTML 视图。 如果上下文中有一个员工 ID,则将对该员工进行编辑-否则将创建一个新员工。
  • deleteEmployeeById() – 通过 ID 删除员工的简单 URL 请求。
  • createOrUpdateEmployee() – 此方法处理用于创建新雇员或更新雇员的 HTTP POST 请求。 创建或更新操作取决于模型中是否存在员工 ID。

3. Thymeleaf 模板

如前所述,我们在此示例中使用两个视图。

list-employees.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <title>All Employees</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css">
</head>

<body>
    <div class="container my-2">
    <div class="card">
    <div class="card-body">
        <div th:switch="${employees}" class="container my-5">
            <p class="my-5">
                <a href="/edit" class="btn btn-primary">
                <i class="fas fa-user-plus ml-2"> Add Employee </i></a>
            </p>
            <div class="col-md-10">
                <h2 th:case="null">No record found !!</h2>
                <div th:case="*">
                    <table class="table table-striped table-responsive-md">
                        <thead>
                            <tr>
                                <th>First Name</th>
                                <th>Last Name</th>
                                <th>Email</th>
                                <th>Edit</th>
                                <th>Delete</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr th:each="employee : ${employees}">
                                <td th:text="${employee.firstName}"></td>
                                <td th:text="${employee.lastName}"></td>
                                <td th:text="${employee.email}"></td>
                                <td>
                                    <a th:href="@{/edit/{id}(id=${employee.id})}" 
                                             class="btn btn-primary">
                                        <i class="fas fa-user-edit ml-2"></i>
                                    </a>
                                </td>
                                <td>
                                    <a th:href="@{/delete/{id}(id=${employee.id})}" 
                                              class="btn btn-primary">
                                        <i class="fas fa-user-times ml-2"></i>
                                    </a>
                                </td>
                            </tr>
                        </tbody>
                    </table>
                </div>

            </div>
        </div>
    </div>
    </div>
    </div>
</body>

</html>

add-edit-employee.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <title>Add Employee</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css">
</head>

<body>
    <div class="container my-5">
        <h3> Add Employee</h3>
        <div class="card">
            <div class="card-body">
                <div class="col-md-10">
                    <form action="#" th:action="@{/createEmployee}" th:object="${employee}" 
                                                                       method="post">
                        <div class="row">
                            <div class="form-group col-md-8">
                                <label for="name" class="col-form-label">First Name</label> 
                                <input type="text" th:field="*{firstName}" class="form-control" 
                                            id="firstName" placeholder="First Name" />
                            </div>
                            <div class="form-group col-md-8">
                                <label for="name" class="col-form-label">Last Name</label> 
                                <input type="text" th:field="*{lastName}" class="form-control" 
                                            id="lastName" placeholder="Last Name" />
                            </div>
                            <div class="form-group col-md-8">
                                <label for="email" class="col-form-label">Email</label> 
                                <input type="text" th:field="*{email}" class="form-control" 
                                            id="email" placeholder="Email Id" />
                            </div>

                            <div class="col-md-6">
                                <input type="submit" class="btn btn-primary" value=" Submit ">
                            </div>

                            <input type="hidden" id="id" th:field="*{id}">

                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</body>

</html>

4. 实体和存储库

我们已经将EmployeeEntity类作为模型绑定到 UI。

EmployeeEntity.java

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="TBL_EMPLOYEES")
public class EmployeeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name="first_name")
    private String firstName;

    @Column(name="last_name")
    private String lastName;

    @Column(name="email", nullable=false, length=200)
    private String email;

    //Setters and getters

    @Override
    public String toString() {
        return "EmployeeEntity [id=" + id + ", firstName=" + firstName + 
                ", lastName=" + lastName + ", email=" + email   + "]";
    }
}

为了将数据持久存储在数据库中,我们使用 H2(内存中)数据库,并使用 Spring 数据的CrudRepository接口。 它为简单的 CRUD 操作提供了开箱即用的内置方法。

EmployeeRepository.java

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.howtodoinjava.demo.entity.EmployeeEntity;

@Repository
public interface EmployeeRepository 
			extends CrudRepository<EmployeeEntity, Long> {

}

请注意,使用两个 SQL 文件初始化了存储库,这两个 SQL 文件创建数据库表并向其中填充默认数据。

schema.sql

DROP TABLE IF EXISTS TBL_EMPLOYEES;

CREATE TABLE TBL_EMPLOYEES (
  id INT AUTO_INCREMENT  PRIMARY KEY,
  first_name VARCHAR(250) NOT NULL,
  last_name VARCHAR(250) NOT NULL,
  email VARCHAR(250) DEFAULT NULL
);

data.sql

INSERT INTO 
	TBL_EMPLOYEES (first_name, last_name, email) 
VALUES
  	('Lokesh', 'Gupta', 'howtodoinjava@gmail.com'),
  	('John', 'Doe', 'xyz@email.com');

5. 服务类

另一个重要的类是EmployeeService类,控制器通过该类与存储库进行交互。 它包含要执行的其他业务逻辑。

EmployeeService.java

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.howtodoinjava.demo.entity.EmployeeEntity;
import com.howtodoinjava.demo.exception.RecordNotFoundException;
import com.howtodoinjava.demo.repository.EmployeeRepository;

@Service
public class EmployeeService {

	@Autowired
	EmployeeRepository repository;

	public List<EmployeeEntity> getAllEmployees()
	{
		List<EmployeeEntity> result = (List<EmployeeEntity>) repository.findAll();

		if(result.size() > 0) {
			return result;
		} else {
			return new ArrayList<EmployeeEntity>();
		}
	}

	public EmployeeEntity getEmployeeById(Long id) throws RecordNotFoundException 
	{
		Optional<EmployeeEntity> employee = repository.findById(id);

		if(employee.isPresent()) {
			return employee.get();
		} else {
			throw new RecordNotFoundException("No employee record exist for given id");
		}
	}

	public EmployeeEntity createOrUpdateEmployee(EmployeeEntity entity) 
	{
		if(entity.getId()  == null) 
		{
			entity = repository.save(entity);

			return entity;
		} 
		else 
		{
			Optional<EmployeeEntity> employee = repository.findById(entity.getId());

			if(employee.isPresent()) 
			{
				EmployeeEntity newEntity = employee.get();
				newEntity.setEmail(entity.getEmail());
				newEntity.setFirstName(entity.getFirstName());
				newEntity.setLastName(entity.getLastName());

				newEntity = repository.save(newEntity);

				return newEntity;
			} else {
				entity = repository.save(entity);

				return entity;
			}
		}
	} 

	public void deleteEmployeeById(Long id) throws RecordNotFoundException 
	{
		Optional<EmployeeEntity> employee = repository.findById(id);

		if(employee.isPresent()) 
		{
			repository.deleteById(id);
		} else {
			throw new RecordNotFoundException("No employee record exist for given id");
		}
	} 
}

6. 添加 Spring Boot 和 Thymeleaf Maven 依赖项

在 spring boot 项目中,我们只需要添加spring-boot-starter-thymeleaf依赖项,并使用默认配置为项目本身自动配置 thymeleaf。 它从/src/main/resources/templates文件夹中读取 HTML 模板。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.5.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.howtodoinjava</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

7. Spring Boot thymeleaf Crud 教程演示

以 Spring Boot 应用程序的形式启动此应用程序,该应用程序将在嵌入式 tomcat 服务器中启动 Web 应用程序。

点击网址:http://localhost:8080/

验证是否使用data.sql文件中的两个默认员工详细信息渲染了屏幕。

玩应用程序。 创建几个新员工,编辑现有员工。 删除一些员工。

如果在上述 spring boot mvc 示例中遇到任何错误,请告诉我。

下载源码

学习愉快!

Spring Boot Hibernate 配置示例

原文: https://howtodoinjava.com/spring-boot2/hibernate-configuration-example/

学习在 Spring Boot2 应用程序中配置 Hibernate / JPA 支持,以及创建实体类和扩展内置的JpaRepository接口。

1. Maven 依赖

在此示例中,我们使用 maven 在项目中添加运行时 jar。 如果您使用 gradle,请查找相关的依赖项。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.howtodoinjava.demo</groupId>
    <artifactId>SpringBoot2Demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringBoot2Demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

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

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

</project>

  • spring-boot-starter-data-jpa(必填):它包含 spring 数据,hibernate,HikariCP, JPA API,JPA 实现(默认以 Hibernate 实现),JDBC 和其他必需的库。
  • h2:尽管我们可以使用application.properties文件中的数据源属性轻松添加任何数据库,但我们正在使用 h2 数据库来减少不必要的复杂性。

2. 创建 JPA 实体类

在类路径中包含必需的 jar 之后,根据项目需要创建几个实体类。 例如,我们在这里创建一个这样的实体EmployeeEntity

请记住,仅包含 JPA API 注解(javax.persistence.*)可使 Hibernate 与应用程序代码脱钩。

EmployeeEntity.java

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="TBL_EMPLOYEES")
public class EmployeeEntity {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name="first_name")
    private String firstName;

    @Column(name="last_name")
    private String lastName;

    @Column(name="email", nullable=false, length=200)
    private String email;

    //Setters and getters left out for brevity.

    @Override
    public String toString() {
        return "EmployeeEntity [id=" + id + ", firstName=" + firstName + 
                ", lastName=" + lastName + ", email=" + email   + "]";
    }
}

  • 我们无需执行任何操作即可使此类可扫描。 Spring Boot 将查找所有@Entity注解的类,并将它们默认配置为 JPA 实体。
  • 默认情况下,表格的名称是实体类的名称,例如在上述情况下,应为EmployeeEntity。 我们可以使用@Table注解及其name属性来自定义表格名称。
  • id属性带有@Id注解,因此 JPA 会将其识别为对象的 ID。 同样,@GeneratedValue注解启用其自动生成的值。
  • 要自定义列的名称,允许使用null值或列大小等,请使用@Column注解。
  • 我建议重写toString()方法以在日志中打印员工的基本详细信息。

阅读更多: JPA 注解

3. 创建 JPA 存储库

扩展JpaRepository接口,以允许在运行时为任何给定的实体类自动创建存储库实现。 实体类别的类型及其 ID 字段在JpaRepository的通用参数中指定。

EmployeeRepository.java

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

import com.howtodoinjava.demo.entity.EmployeeEntity;

@Repository
public interface EmployeeRepository extends JpaRepository<EmployeeEntity, Long> {

}

通过此简单扩展,EmployeeRepository继承了用于处理Employee持久性的几种方法,包括保存,删除和查找Employee实体的方法。

除了提供的默认方法外,我们还可以向此接口添加我们自己的自定义方法和查询。

4. 属性配置

4.1. 数据源

application.properties文件中提供数据源连接属性,这将有助于将数据库连接到 JPA 代码。

在给定的配置中,我们正在配置 h2 数据库。

application.properties

spring.datasource.url=jdbc:h2:file:~/test
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

# Enabling H2 Console
spring.h2.console.enabled=true

# Custom H2 Console URL
spring.h2.console.path=/h2-console

4.2. Hibernate 打印 SQL 和日志记录

查看组件如何工作的一个好方法是启用大量日志记录。 仅在很少的属性条目下进行操作即可。

application.properties

#Turn Statistics on and log SQL stmts

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

#If want to see very extensive logging
spring.jpa.properties.hibernate.generate_statistics=true
logging.level.org.hibernate.type=trace
logging.level.org.hibernate.stat=debug

4.3. 数据库初始化

在基于 JPA 的应用程序中,我们可以选择让 Hibernate 使用实体类创建架构,也可以使用schema.sql,但是我们不能两者都做。

如果使用schema.sql,请确保禁用spring.jpa.hibernate.ddl-auto

application.properties

#Schema will be created using schema.sql and data.sql files

spring.jpa.hibernate.ddl-auto=none

schama.sql

DROP TABLE IF EXISTS TBL_EMPLOYEES;

CREATE TABLE TBL_EMPLOYEES (
    id INT AUTO_INCREMENT  PRIMARY KEY,
    first_name VARCHAR(250) NOT NULL,
    last_name VARCHAR(250) NOT NULL,
    email VARCHAR(250) DEFAULT NULL
);

data.sql

INSERT INTO TBL_EMPLOYEES 
    (first_name, last_name, email) 
VALUES
    ('Lokesh', 'Gupta', 'abc@gmail.com'),
    ('Deja', 'Vu', 'xyz@email.com'),
    ('Caption', 'America', 'cap@marvel.com');

5. Spring Boot Hibernate 演示

要使用 Spring Boot 测试 Hibernate 配置,我们需要在类中自动装配EmployeeRepository依赖项,并使用其方法保存或获取员工实体。

让我们在@SpringBootApplication带注解的类中并使用CommandLineRunner接口进行此测试。 应用程序启动后立即执行CommandLineRunner中的run()方法。

SpringBoot2DemoApplication.java

package com.howtodoinjava.demo;

import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.howtodoinjava.demo.entity.EmployeeEntity;
import com.howtodoinjava.demo.repository.EmployeeRepository;

@SpringBootApplication
public class SpringBoot2DemoApplication implements CommandLineRunner {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    EmployeeRepository repository;

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

    @Override
    public void run(String... args) throws Exception 
    {       
        Optional<EmployeeEntity> emp = repository.findById(2L);

        logger.info("Employee id 2 -> {}", emp.get());
    }
}

运行该应用程序并观察输出。 请注意,要在日志中打印有限的信息,我在应用程序中使用属性logging.pattern.console=&m%n

Console

Tomcat initialized with port(s): 8080 (http)
Starting service [Tomcat]
Starting Servlet engine: [Apache Tomcat/9.0.19]
Initializing Spring embedded WebApplicationContext
Root WebApplicationContext: initialization completed in 5748 ms

HikariPool-1 - Starting...
HikariPool-1 - Start completed.
HHH000204: Processing PersistenceUnitInfo [
    name: default
    ...]
HHH000412: Hibernate Core {5.3.10.Final}
HHH000206: hibernate.properties not found
HCANN000001: Hibernate Commons Annotations {5.0.4.Final}
HHH000400: Using dialect: org.hibernate.dialect.H2Dialect

Initialized JPA EntityManagerFactory for persistence unit 'default'
Initializing ExecutorService 'applicationTaskExecutor'
spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. 
Explicitly configure spring.jpa.open-in-view to disable this warning
Tomcat started on port(s): 8080 (http) with context path ''
Started SpringBoot2DemoApplication in 17.638 seconds (JVM running for 19.1)

Hibernate: 
    select
        employeeen0_.id as id1_0_0_,
        employeeen0_.email as email2_0_0_,
        employeeen0_.first_name as first_na3_0_0_,
        employeeen0_.last_name as last_nam4_0_0_ 
    from
        tbl_employees employeeen0_ 
    where
        employeeen0_.id=?

Employee id 2 -> EmployeeEntity [id=2, firstName=Deja, lastName=Vu, email=xyz@email.com]

显然,已经配置了 Hibernate 模式,并且我们能够使用 JPA 存储库接口与数据库进行交互。

将我的问题留在 Spring Boot 和配置 Hibernate 有关的评论部分。

6. Spring Boot Hibernate 教程

  1. 使用 Hibernate 的 Spring boot crud 操作示例

  2. 使用 Thymeleaf 和 Hibernate 的 Spring Boot CRUD 应用程序

  3. Spring Boot 分页和排序示例

学习愉快!

下载源码

Spring Boot – 数据源配置

原文: https://howtodoinjava.com/spring-boot2/datasource-configuration/

了解什么是数据源以及如何在 Spring Boot 应用程序中创建和自定义DataSource bean。

1. 什么是数据源

数据源是用于连接任何物理数据源的工厂。 DriverManager工具的替代品。 它使用 URL 以及一些凭据来建立数据库连接。

实现javax.sql.DataSource接口的对象通常会在 JNDI 服务中注册,并且可以使用其 JNDI 名称进行发现。

数据源可用于获取:

  • 标准Connection对象
  • 可以在连接池中使用的连接
  • 可以在分布式事务和连接池中使用的连接

2. 数据源配置

Spring Boot 允许以两种方式定义数据源配置,即 Java 配置和属性配置。 DataSourceAutoConfiguration在为我们配置DataSource bean 之前,先在类路径上检查DataSource.class(或EmbeddedDatabaseType.class)。

2.1. Maven

如果尚未定义,请包含spring-boot-starter-data-jpa进行投影。 它带来了所有必要的依赖关系,包括用于各种数据库的 JDBC 驱动程序,例如mysql-connector-java用于连接到 mysql 。

如果我们计划在某个步骤(例如测试)使用嵌入式数据库,则可以单独导入 H2 db。

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.4.1</version> 
    <scope>runtime</scope> 
</dependency>

2.2. application.properties

application.properties文件中的外部配置属性(spring.datasource.*)提供了DataSource配置。

属性配置将配置与应用程序代码分离。 这样,我们甚至可以从配置提供程序系统中导入数据源配置。

下面给出的配置显示了 H2,MySQL,Oracle 和 SQL Server 数据库的示例属性。

我们通常不需要指定driver-class-name,因为 Spring Boot 可以从 url 推断出大多数数据库。

# H2
spring.datasource.url=jdbc:h2:file:C:/temp/test
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

# MySQL
#spring.datasource.url=jdbc:mysql://localhost:3306/test
#spring.datasource.username=dbuser
#spring.datasource.password=dbpass
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

# Oracle
#spring.datasource.url=jdbc:oracle:thin:@localhost:1521:orcl
#spring.datasource.username=dbuser
#spring.datasource.password=dbpass
#spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
#spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect

# SQL Server
#spring.datasource.url=jdbc:sqlserver://localhost;databaseName=springbootdb
#spring.datasource.username=dbuser
#spring.datasource.password=dbpass
#spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
#spring.jpa.hibernate.dialect=org.hibernate.dialect.SQLServer2012Dialect

2.3. DataSource Bean

推荐的创建DataSource bean 的方法是在带有@Configuration注解的类中使用DataSourceBuilder类。 数据源也使用基础连接池。

JpaConfig.java

@Configuration
public class JpaConfig {

    @Bean
    public DataSource getDataSource() 
    {
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.driverClassName("org.h2.Driver");
        dataSourceBuilder.url("jdbc:h2:file:C:/temp/test");
        dataSourceBuilder.username("sa");
        dataSourceBuilder.password("");
        return dataSourceBuilder.build();
    }
}

2.4. JNDI 数据源

如果我们将 Spring Boot 应用程序部署到应用服务器,则可能需要使用应用服务器的内置功能来配置和管理DataSource,并使用 JNDI 对其进行访问。

我们可以使用spring.datasource.jndi-name属性来执行此操作。 例如

#JBoss defined datasource using JNDI

spring.datasource.jndi-name = java:jboss/datasources/testDB

3. 连接池

3.1. HikariCP,tomcat 池和公用 DBCP2

对于要创建的池数据源,Spring Boot 会验证有效的Driver类是否可用。 如果我们设置spring.datasource.driver-class-name属性,则该驱动程序类必须是可加载的。

自动配置首先尝试查找和配置HikariCP。 如果HikariCP可用,则始终选择它。 否则,如果找到 Tomcat 池,则对其进行配置。

如果 HikariCP 和 Tomcat 池数据源均不可用,并且 Commons DBCP2 不可用,则使用它。

spring-boot-starter-data-jpa起动器自动获得对HikariCP的依赖。

3.2. 自定义设置

还可以通过使用它们各自的前缀(spring.datasource.hikari.*spring.datasource.tomcat.*spring.datasource.dbcp2.*)微调实现特定的设置。

例如,我们可以使用以下属性来定制 DBCP2 连接池。

spring.datasource.dbcp2.initial-size = 50
spring.datasource.dbcp2.max-idle = 50
spring.datasource.dbcp2.default-query-timeout = 10000
spring.datasource.dbcp2.default-auto-commit = true

...

4. 使用 Spring Boot 的多个数据源

要配置多个数据源,请创建所需的任意多个 bean 定义,但将DataSource实例之一标记为@Primary,因为各种自动配置都希望能够按类型获取。

请记住,如果我们创建自己的数据源,则会取消自动配置。 因此,我们负责为所有数据源 bean 提供配置。

JpaConfig.java

@Configuration
public class JpaConfig {

    @Bean(name = "h2DataSource")
    public DataSource h2DataSource() 
    {
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.driverClassName("org.h2.Driver");
        dataSourceBuilder.url("jdbc:h2:file:C:/temp/test");
        dataSourceBuilder.username("sa");
        dataSourceBuilder.password("");
        return dataSourceBuilder.build();
    }

    @Bean(name = "mySqlDataSource")
    @Primary
    public DataSource mySqlDataSource() 
    {
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.url("jdbc:mysql://localhost/testdb");
        dataSourceBuilder.username("dbuser");
        dataSourceBuilder.password("dbpass");
        return dataSourceBuilder.build();
    }
}

在自动装配数据源时,Spring Boot 将首选主数据源,即mySqlDataSource。 要自动装配另一个非主要数据源,请使用@Qualifier注解。

Autowire primary datasource

@Autowired
DataSource dataSource;

Autowire NON-primary datasource

@Autowired
@Qualifier("h2DataSource") 
DataSource dataSource;

5. 结论

Spring Boot 提供了非常简单的方法来创建数据源 bean – 使用属性配置或使用 java 配置@Bean。 Spring Boot 提供了现成的自动配置以供使用,可以通过application.properties文件中的高级选项进一步自定义。

Spring Boot 首先尝试查找和配置连接池,然后是 HikariCP,然后是 Tomcat 池,然后是 Commons DBCP2。 HikariCP内置有spring-boot-starter-jdbcspring-boot-starter-data-jpa起动器。

我们可以配置多个数据源,并且其中之一必须标记为@Primary。 默认情况下,主数据源是自动装配的,其他数据源需要与@Qualifier注解一起自动装配。

学习愉快!

下载源码

Spring Boot 异常处理 – @ExceptionHandler示例

原文: https://howtodoinjava.com/spring-boot2/spring-rest-request-validation/

在本SpringBoot 异常处理器教程中,我们将学习验证发送到 PUT / POST REST API 的请求正文。 我们还将学习在验证错误的 API 响应中添加自定义错误消息。

在这个 SpringBoot 示例中,我们将主要看到两个主要的验证案例:

  1. HTTP POST /employees和请求正文不包含有效值,或者某些字段丢失。 它将在响应正文中返回 HTTP 状态代码 400,并带有正确的消息。
  2. HTTP GET /employees/{id}和无效 ID 在请求中发送。 它将在响应正文中返回带有正确消息的 HTTP 状态代码 404。

阅读更多: HTTP 状态代码

1. 创建 REST API 和模型类

给定的 REST API 来自员工管理模块。

EmployeeRESTController.java

@PostMapping(value = "/employees")
public ResponseEntity<EmployeeVO> addEmployee (@RequestBody EmployeeVO employee)
{
    EmployeeDB.addEmployee(employee);
    return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
}

@GetMapping(value = "/employees/{id}") 
public ResponseEntity<EmployeeVO> getEmployeeById (@PathVariable("id") int id)
{
    EmployeeVO employee = EmployeeDB.getEmployeeById(id);

    if(employee == null) {
    	 throw new RecordNotFoundException("Invalid employee id : " + id);
    }
    return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
}

EmployeeVO.java

@XmlRootElement(name = "employee")
@XmlAccessorType(XmlAccessType.FIELD)
public class EmployeeVO extends ResourceSupport implements Serializable
{
	private Integer employeeId;
	private String firstName;
	private String lastName;
	private String email;

	public EmployeeVO(Integer id, String firstName, String lastName, String email) {
		super();
		this.employeeId = id;
		this.firstName = firstName;
		this.lastName = lastName;
		this.email = email;
	}

	public EmployeeVO() {
	}

	//Removed setter/getter for readability
}

2. Spring Boot 异常处理 – REST 请求验证

2.1. 默认的 Spring 验证支持

要应用默认验证,我们只需要在适当的位置添加相关注解即可。 即

  1. 使用所需的验证特定注解(例如@NotEmpty@Email等)注解模型类

    @XmlRootElement(name = "employee")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class EmployeeVO extends ResourceSupport implements Serializable
    {
    	private static final long serialVersionUID = 1L;
    
    	public EmployeeVO(Integer id, String firstName, String lastName, String email) {
    		super();
    		this.employeeId = id;
    		this.firstName = firstName;
    		this.lastName = lastName;
    		this.email = email;
    	}
    
    	public EmployeeVO() {
    	}
    
    	private Integer employeeId;
    
    	@NotEmpty(message = "first name must not be empty")
    	private String firstName;
    
    	@NotEmpty(message = "last name must not be empty")
    	private String lastName;
    
    	@NotEmpty(message = "email must not be empty")
    	@Email(message = "email should be a valid email")
    	private String email;
    
    	//Removed setter/getter for readability
    }
    
    
  2. 通过@Valid注解启用验证请求正文

    @PostMapping(value = "/employees")
    public ResponseEntity<EmployeeVO> addEmployee (@Valid @RequestBody EmployeeVO employee)
    {
        EmployeeDB.addEmployee(employee);
        return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
    }
    
    

2.2. 异常模型类

默认的 Spring 验证有效并提供有关错误的信息过多,这就是为什么我们应根据应用程序的需要对其进行自定义。 我们将仅提供措辞非常明确的必要错误信息。 不建议提供其他信息。

创建有意义的异常并充分描述问题始终是一个很好的建议。 一种方法是创建单独的类来表示特定的业务用例失败,并在该用例失败时返回它们。

阅读更多: Java 异常处理 – 新的应用程序

例如我为所有通过 ID 要求资源但在系统中找不到资源的场景创建了RecordNotFoundException类。

RecordNotFoundException.java

package com.howtodoinjava.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class RecordNotFoundException extends RuntimeException 
{
	public RecordNotFoundException(String exception) {
		super(exception);
	}
}

同样,我编写了一个特殊的类,将为所有失败情况返回该类。 所有 API 具有一致的错误消息结构,可帮助 API 使用者编写更健壮的代码。

ErrorResponse.java

import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "error")
public class ErrorResponse 
{
	public ErrorResponse(String message, List<String> details) {
		super();
		this.message = message;
		this.details = details;
	}

	//General error message about nature of error
	private String message;

	//Specific errors in API request processing
	private List<String> details;

	//Getter and setters
}

2.3. 自定义ExceptionHandler

现在添加一个扩展ResponseEntityExceptionHandler的类,并使用@ControllerAdvice注解对其进行注解。

ResponseEntityExceptionHandler是一个方便的基类,用于通过@ExceptionHandler方法跨所有@RequestMapping方法提供集中式异常处理。 @ControllerAdvice更多用于在应用程序启动时启用自动扫描和配置。

用于@ControllerAdvice异常处理示例的 Java 程序。

CustomExceptionHandler.java

package com.howtodoinjava.demo.exception;

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

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.ObjectError;
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;

@SuppressWarnings({"unchecked","rawtypes"})
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler 
{
	@ExceptionHandler(Exception.class)
	public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
		List<String> details = new ArrayList<>();
		details.add(ex.getLocalizedMessage());
		ErrorResponse error = new ErrorResponse("Server Error", details);
		return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler(RecordNotFoundException.class)
	public final ResponseEntity<Object> handleUserNotFoundException(RecordNotFoundException ex, WebRequest request) {
		List<String> details = new ArrayList<>();
		details.add(ex.getLocalizedMessage());
		ErrorResponse error = new ErrorResponse("Record Not Found", details);
		return new ResponseEntity(error, HttpStatus.NOT_FOUND);
	}

	@Override
	protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
		List<String> details = new ArrayList<>();
		for(ObjectError error : ex.getBindingResult().getAllErrors()) {
			details.add(error.getDefaultMessage());
		}
		ErrorResponse error = new ErrorResponse("Validation Failed", details);
		return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
	}
}

上面的类处理多个异常,包括RecordNotFoundException; 并且还可以处理@RequestBody带注解的对象中的请求验证错误。 让我们看看它是如何工作的。

3. Spring Boot 异常处理 – 演示

1)HTTP GET /employees/1 [有效]

HTTP Status : 200

{
    "employeeId": 1,
    "firstName": "John",
    "lastName": "Wick",
    "email": "howtodoinjava@gmail.com",
}

2)HTTP GET /employees/23 [无效]

HTTP Status : 404

{
    "message": "Record Not Found",
    "details": [
        "Invalid employee id : 23"
    ]
}

3)HTTP POST /employees [无效]

Request

{
    "lastName": "Bill",
    "email": "ibill@gmail.com"
}

Response

HTTP Status : 400

{
    "message": "Validation Failed",
    "details": [
        "first name must not be empty"
    ]
}

4)HTTP POST /employees [无效]

Request

{
    "email": "ibill@gmail.com"
}

Response

HTTP Status : 400

{
    "message": "Validation Failed",
    "details": [
        "last name must not be empty",
        "first name must not be empty"
    ]
}

5)HTTP POST /employees [无效]

Request

{
	"firstName":"Lokesh",
    "email": "ibill_gmail.com" //invalid email in request
}

Response

HTTP Status : 400

{
    "message": "Validation Failed",
    "details": [
        "last name must not be empty",
        "email should be a valid email"
    ]
}

4. REST 请求验证注解

在上面的示例中,我们仅使用了很少的注解,例如@NotEmpty@Email。 还有更多此类注解可用于验证请求数据。 必要时将其签出。

注解 用法
@AssertFalse 带注解的元素必须为false
@AssertTrue 带注解的元素必须为true
@DecimalMax 带注解的元素必须是一个数字,其值必须小于或等于指定的最大值。
@DecimalMin 带注解的元素必须是一个数字,其值必须大于或等于指定的最小值。
@Future 带注解的元素必须是将来的瞬间,日期或时间。
@Max 带注解的元素必须是一个数字,其值必须小于或等于指定的最大值。
@Min 带注解的元素必须是一个数字,其值必须大于或等于指定的最小值。
@Negative 带注解的元素必须是严格的负数。
@NotBlank 带注解的元素不能为null,并且必须至少包含一个非空白字符。
@NotEmpty 带注解的元素不能为null,也不能为空。
@NotNull 带注解的元素不能为null
@Null 带注解的元素必须为null
@Pattern 带注解的CharSequence必须与指定的正则表达式匹配。
@Positive 带注解的元素必须是严格的正数。
@Size 带注解的元素大小必须在指定的边界(包括在内)之间。

5. 总结

在此Spring REST 验证教程中,我们学习了:

  • 通过 ID 提取资源时验证 ID。
  • 验证 POST / PUT API 中的请求正文字段。
  • 在 API 响应中发送一致且结构化的错误响应。

将有关 spring rest 异常处理的问题交给我。

学习愉快!

参考:javax.validation.constraints

Spring Boot 缓存示例教程

原文: https://howtodoinjava.com/spring-boot2/spring-boot-cache-example/

在本 Spring Boot 教程中,从 Spring 框架缓存支持中学习轻松管理应用程序缓存。 Spring 在缓存切面具有一些不错的功能, spring cache API 上的抽象非常易于使用。

1. 什么是缓存?

缓存是一种增强系统性能的机制。 它是位于应用程序和持久数据库之间的临时内存。 高速缓存存储器存储最近使用的数据项,以尽可能减少数据库命中的次数。

1.1. 为什么我们需要缓存?

缓存应用程序中经常使用的数据是提高应用程序性能的一种非常流行的技术。 通过缓存,我们将此类经常访问的数据存储在内存中,以避免每次用户请求数据时都访问昂贵的后端。 与从数据库,文件系统或其他服务调用中获取数据相比,从内存中进行数据访问总是更快。

1.2. 应该缓存什么数据?

这主要是关于应该驻留在缓存中并经过缓存生命周期的数据类型的决定。 在不同的情况下以及对我们可以容忍过时的数据的要求切面,它会有所不同。

因此,每个项目的缓存候选者将有所不同,但仍然只是缓存的少数几个示例:

  • 电子商务商店中可用的产品列表
  • 任何不经常更改的主数据
  • 任何经常使用的数据库读取查询,其中至少在特定时间段内,每个调用中的结果都不会更改。

2. 缓存类型

通常,可以看到以下类型的缓存。

2.1. 内存中缓存

这是最常使用的区域,在该区域广泛使用缓存来提高应用程序的性能。 内存缓存(例如 Memcached 和 Radis)是应用程序和数据存储之间的键值存储。 由于数据保存在 RAM 中,因此它比将数据存储在磁盘上的典型数据库快得多。

RAM 比磁盘更受限制,因此高速缓存失效算法(例如最近最少使用(LRU))可以帮助使“冷”条目失效并将“热”数据保留在 RAM 中。 Memcached是一种内存缓存,其中Redis更高级,它允许我们备份和还原功能,它是分布式缓存工具,可以管理分布式集群中的缓存。

2.2. 数据库缓存

您的数据库通常在默认配置中包括某种程度的缓存,这些缓存针对通用用例进行了优化。 针对特定的使用模式调整这些设置可以进一步提高性能。 在该领域流行的是Hibernate或任何 ORM 框架的一级缓存。

2.3. Web 服务器缓存

反向代理和缓存(例如 Varnish)可以直接提供静态和动态内容。 Web 服务器还可以缓存请求,返回响应而无需联系应用程序服务器。 在当今的 API 时代,如果我们想在网络服务器级别缓存 API 响应,则此选项是可行的。

2.4. CDN 缓存

缓存可以位于客户端(操作系统或浏览器),服务器端或不同的缓存层中。

3. Spring Boot 缓存注解

Spring 框架为不同的缓存提供程序提供缓存抽象 api 。 API 的用法非常简单,但功能非常强大。 今天,我们将在缓存中看到基于注解的 Java 配置。 注意,我们也可以通过 XML 配置实现类似的功能。

3.1. @EnableCaching

它启用了 Spring 的注解驱动的缓存管理功能。 在 spring boot 项目中,我们需要将其添加到带有@SpringBootApplication注解的启动应用程序类中。 Spring 提供了一个并发哈希图作为默认缓存,但是我们可以重写CacheManager来轻松注册外部缓存提供程序。

3.2. @Cacheable

它在方法级别上用于使 spring 知道该方法的响应是可缓存的。 Spring 管理此方法对注解属性中指定的缓存的请求/响应。 例如,@Cacheable ("cache-name1", “cache-name2”)

@Cacheable注解具有更多选项。 就像我们可以从方法的请求中指定缓存的键一样。 如果未指定任何内容,spring 将使用所有类字段并将其用作缓存键(主要是HashCode)来维护缓存,但是我们可以通过提供键信息来覆盖此行为。

@Cacheable(value="books", key="#isbn")
public Book findStoryBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(value="books", key="#isbn.rawNumber")
public Book findStoryBook (ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(value="books", key="T(classType).hash(#isbn)")
public Book findStoryBook (ISBN isbn, boolean checkWarehouse, boolean includeUsed)

我们也可以使用条件缓存。 例如,

@Cacheable(value="book", condition="#name.length < 50")
public Book findStoryBook (String name)

3.3. @CachePut

有时我们需要手动操作缓存,以在方法调用之前放置(更新)缓存。 这将允许我们更新缓存,也将允许执行该方法。 该方法将始终执行,并将其结果放入缓存(根据@CachePut选项)。

它支持与@Cacheable相同的选项,应该用于缓存填充,而不是方法流程优化。

请注意,一般不建议在同一方法上使用@CachePut和`注解批注,因为它们的行为不同。 后者导致通过使用缓存跳过方法执行,而前者则强制执行以便执行缓存更新。

这会导致意外的行为,并且除了特定的极端情况(例如具有相互排斥条件的注解)外,应避免此类声明。

3.4. @CacheEvict

当我们需要移出(删除)先前加载的主数据的缓存时使用它。 当将执行CacheEvict注解的方法时,它将清除缓存。

我们可以在此处指定键以删除缓存,如果需要删除缓存的所有条目,则需要使用allEntries=true。 当需要清除整个缓存区域时,此选项非常有用 – 而不是逐出每个条目(由于效率低下,这将需要很长时间),所有条目都将在一次操作中被删除。

3.5. @Cache

当我们同时需要CachePutCacheEvict时,需要此注解。

4. 如何在 Spring Boot 中注册缓存引擎

Spring Boot 提供了与以下缓存提供程序的集成。 如果在类路径中存在默认选项,并且我们已通过 Spring Boot 应用程序中的@EnableCaching启用了缓存,则 Spring Boot 会使用默认选项进行自动配置。

  • JCache(JSR-107)(EhCache 3,Hazelcast,Infinispan 等)
  • EhCache 2.x
  • Hazelcast
  • Infinispan
  • Couchbase
  • RedisC
  • Caffeine
  • SimpleCache

我们可以通过覆盖特定于缓存提供程序的设置来覆盖 Spring 运行中的特定缓存行为,例如:

spring.cache.infinispan.config=infinispan.xml

有关详细信息,我们可以在此处查看官方的 Spring Boot 文档。

5. Spring Boot 缓存示例

在此 spring boot cahce 配置示例中,我们将看到如何在 spring boot 中启用默认缓存,并为一种业务方法启用缓存。 最后,我们将在重复调用相同方法的情况下测试应用程序性能。

我们将使用Thread.sleep()方法来模拟实际方法调用中的延迟,以感受缓存的效果。 因此,让我们遵循创建项目和测试的简单步骤。

5.1 创建 Spring Boot 项目

创建一个名为spring-cache且具有spring-boot-web依赖关系的简单 Spring Boot 项目,以将其托管在 Web 服务器中。

为此,我们需要转到 https://start.spring.io/ 并提供 Maven 坐标并选择依赖项。 下载包含框架项目的 zip 文件。 然后,一旦解压缩到合适的文件夹中,我们就需要将其导入 eclipse 中。 进行初始 mvn 全新安装,以将所有必需的依赖项下载到本地存储库。

Spring Boot Project Creation

Spring Boot 项目创建

5.2 创建 HTTP GET REST API

使用 GET 请求创建一个 REST 服务,它将成为搜索服务。 我们的主要目标是将方法的响应缓存在服务层中,在此我们将引入一个故意的延迟来模拟实际的后端服务调用以获取结果。 在第一个匹配中,响应将被延迟,因为我们在应用程序中会有一些模拟的延迟,但是在随后的调用中,我们将获得更快的响应。

Student.java

package com.example.springcache.domain;

public class Student {

	String id;
	String name;
	String clz;

	public Student(String id, String name, String clz) {
		super();
		this.id = id;
		this.name = name;
		this.clz = clz;
	}

	//Setters and getters
}

StudentService.java

package com.example.springcache.service;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.example.springcache.domain.Student;

@Service
public class StudentService 
{
	@Cacheable("student")
	public Student getStudentByID(String id) 
	{
		try 
		{
			System.out.println("Going to sleep for 5 Secs.. to simulate backend call.");
			Thread.sleep(1000*5);
		} 
		catch (InterruptedException e) 
		{
			e.printStackTrace();
		}

		return new Student(id,"Sajal" ,"V");
	}
}

StudentController.java

package com.example.springcache.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.example.springcache.domain.Student;
import com.example.springcache.service.StudentService;

@RestController
public class StudentController 
{

	@Autowired
	StudentService studentService;

	@GetMapping("/student/{id}")
	public Student findStudentById(@PathVariable String id) 
	{
		System.out.println("Searching by ID  : " + id);

		return studentService.getStudentByID(id);
	}
}

请注意:

  • 服务层方法使用@Cacheable("student")进行了注解,如上所述,此注解启用了该特定方法中的缓存,并且缓存名称为Student
  • getStudentByID()方法中,使用Thread.sleep(1000*5)有意延迟 5 秒。 这仅仅是为了了解响应是来自缓存还是真实的后端。

5.3 启用 Spring 托管缓存

为此,我们只需要在 Spring Boot 应用程序类中添加@EnableCaching注解。

SpringCacheApplication.java

package com.example.springcache;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class SpringCacheApplication {

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

5.4 演示

现在我们可以测试 Spring 缓存的默认缓存行为。 我们已经添加了必需的配置,并且通过 Spring Boot 变得更加容易。

要进行测试,只需通过$ mvn clean install命令再次构建项目,然后从命令行 Java 命令运行应用程序,或者仅从 IDE 中运行SpringCacheApplication。 它将在localhost 8080端口中启动应用程序。

要测试,请转到 url

http://localhost:8080/student/1

您将获得一个Student对象的JSON响应。 需要注意的是,第一次响应至少需要 5 秒钟,然后相同 URL 的后续响应会更快。 如果您难以理解差异,则可以更改服务等级中的延迟时间。

现在更改 URL 以通过http://localhost:8080/student/2获得学生 ID 2,您将再次遇到延迟,但是在随后的调用中,响应将从缓存提供。

这是我系统上关于此的最后几行日志。 当调用实际服务时,我将获得Going to sleep for 5 Secs.. to simulate backend call.日志,而在后续调用中,我未得到该日志,这意味着从缓存提供响应。

Console

Searching by ID  : 1
Going to sleep for 5 Secs.. to simulate backend call.

Searching by ID  : 1
Searching by ID  : 1
Searching by ID  : 1
Searching by ID  : 1
Searching by ID  : 1

Searching by ID  : 2
Going to sleep for 5 Secs.. to simulate backend call.

Searching by ID  : 2
Searching by ID  : 2

7. Spring Boot 缓存总结

最后,今天我们已经看到了 spring 框架在特定于应用程序缓存的缓存区域中提供了什么。 此外,我们还看到了 Spring 中存在哪些注解来支持这一点。

希望本教程对您有所帮助。 在本文中,我们使用了备用缓存提供程序,即后台的ConcurrentHashMap。 下一步将是配置其他受支持的缓存引擎,例如 RedisEhcache 等。

下载源码

学习愉快!

参考文献:

SpringBoot 缓存文档

Spring 5 缓存文档

使用 Spring Boot 的 SpringRetry 模块示例

原文: https://howtodoinjava.com/spring-boot2/spring-retry-module/

在此 Spring Boot 教程中,学习如何使用 spring retry 模块工具来构建应用程序,在该工具中,我们必须调用某些有时会期望出现异常的方法,并且必须重试该请求。

在这种情况下,如果必须在任何后端服务调用上实现任何重试功能,则通常我们使用循环和中断条件来实现,并继续重试至某些重试限制。 但是这种解决方案容易出错。

Spring 为我们提供了一个围绕这个叫做spring-retry的简单框架,可以使用注解进行配置。 我们可以定义重试限制,后备方法等。

1. 为什么我们需要重试?

在我们的大多数项目中,通常会遇到一些场景,如果它是第一次下降,则可以重试很少的操作。 例如,假设在调用任何后端外部服务的过程中,该服务可能由于诸如网络中断,服务器关闭,网络故障,死锁等原因而关闭。在这种情况下,通常在发送特定错误到客户端程序之前,我们尝试重试该操作几次,以使处理更健壮,更不容易出错。

有时,它有助于自动重试失败的操作,以防后续尝试成功执行。 我想你们所有人都已经遇到了这个问题,并且作为一种解决方法,您很可能会通过循环并在达到重试限制后中断该循环来解决此问题,但是现在借助spring-retry模块,我们不必编写这样的代码来处理重试方案。

2. spring-retry

spring 系列的spring-retry是另一个实用程序模块,可以帮助我们以标准方式处理任何特定操作的重试。 在spring-retry中,所有配置都是基于简单注解的。

2.1. SpringRetry 注解

  • @EnableRetry – 在 Spring Boot 项目中启用 SpringRetry
  • @Retryable – 表示可以重试的任何方法
  • @Recover – 指定后备方法!

3. SpringRetry 示例

3.1. 开发环境

我们将使用以下技术栈在下面的示例应用中尝试spring-retry

  • Java,Eclipse,Maven 作为开发环境
  • Spring Boot 作为应用程序框架
  • spring-retry模块作为重试模块
  • AspectJ 作为spring-retry的依赖

3.2. 演示概述

  1. 创建一个 Spring 运行项目以公开一次示例 Rest API,它将调用一个容易失败的后端操作,我们将模拟这种失败情况以启动重试。

  2. 一个服务类实际上将调用远程 api,并在失败时发送异常,我们将基于此自定义异常设计重试,就像一旦收到此异常,我们将重试 3 次并最终返回客户端。

    在这 3 次尝试中,如果我们从后端服务获得成功响应,则将返回该成功响应,否则将调用标准后备方法。

3.3. 创建 SpringBoot 项目

第一步,我们将从 spring 初始化器站点创建一个 spring boot 项目,在此我们将测试 SpringRetry 功能。

为此,我们需要转到 https://start.spring.io/ 并选择依赖项webretry。 下载包含框架项目的 zip 文件,然后导入到 maven。

Spring Boot 初始化器

3.4. Maven 依赖

Spring 初始化器会在项目中自动添加spring-boot-starter-data-restspring-boot-starter-security依赖项。 为了使测试更加有效,我们将不需要这些,因此我们将从pom.xml中删除这两个依赖项。

此外,spring-retry取决于框架项目中未包含的 Aspectj,因此我们将在pom.xml文件中添加以下依赖项。

pom.xml

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>${version}</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
	<version>${version}</version>
</dependency>

3.5. 创建 Rest API 端点

创建一个示例 Rest 控制器,该控制器将调用后端服务类,在该类中我们将模拟异常,并且spring-retry模块将自动重试。

MyRestController.java

package com.example.springretry;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyRestController {

	@Autowired
	BackendAdapter backendAdapter;

	@GetMapping("/retry")
	@ExceptionHandler({ Exception.class })
	public String validateSPringRetryCapability(@RequestParam(required = false) boolean simulateretry,
								@RequestParam(required = false) boolean simulateretryfallback) 
	{
		System.out.println("===============================");
		System.out.println("Inside RestController mathod..");

		return backendAdapter.getBackendResponse(simulateretry, simulateretryfallback);
	}
}

在 Rest Api 中,我们将添加两个可选的请求参数。

  • Simulationretry - 模拟异常情况的参数,以便 spring 可以重试。
  • Simulationretryfallback – 由于我们正在模拟异常,因此在重试某些时间后,我们可以期待成功的后端调用或所有重试失败。 在这种情况下,我们将使用后退方法来获取硬编码/错误响应。 现在,此参数将确保所有重试都将失败,并且我们将仅回退路径。

3.6. @EnableRetry注解

为了启用spring-retry,我们需要在 Spring Boot 应用类中放置一个注解。 因此,打开SpringRetryApplication类并在类级别添加@EnableRetry

SpringRetryApplication.java

package com.example.springretry;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;

@EnableRetry
@SpringBootApplication
public class SpringRetryApplication {

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

3.7. 后端服务适配器接口和实现

现在,我们将创建一个用于调用外部服务的接口/实现。 在这里,我们实际上不会调用任何外部服务调用,而是通过添加一些随机逻辑来模拟成功/失败方案,如下所示。

BackendAdapter.java

package com.example.springretry;

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;

public interface BackendAdapter {

	@Retryable(value = { RemoteServiceNotAvailableException.class }, maxAttempts = 3, backoff = @Backoff(delay = 1000))
	public String getBackendResponse(boolean simulateretry, boolean simulateretryfallback);

	@Recover
	public String getBackendResponseFallback(RuntimeException e);

}

BackendAdapterImpl.java

package com.example.springretry;

import java.util.Random;
import org.springframework.stereotype.Service;

@Service
public class BackendAdapterImpl implements BackendAdapter {

	@Override
	public String getBackendResponse(boolean simulateretry, boolean simulateretryfallback) {

		if (simulateretry) {
			System.out.println("Simulateretry is true, so try to simulate exception scenario.");

			if (simulateretryfallback) {
				throw new RemoteServiceNotAvailableException(
						"Don't worry!! Just Simulated for Spring-retry..Must fallback as all retry will get exception!!!");
			}
			int random = new Random().nextInt(4);

			System.out.println("Random Number : " + random);
			if (random % 2 == 0) {
				throw new RemoteServiceNotAvailableException("Don't worry!! Just Simulated for Spring-retry..");
			}
		}

		return "Hello from Remote Backend!!!";
	}

	@Override
	public String getBackendResponseFallback(RuntimeException e) {
		System.out.println("All retries completed, so Fallback method called!!!");
		return "All retries completed, so Fallback method called!!!";
	}
}

  • @Retryable – 这是@EnableRetry之后的主要注解。 此注解表明,如果我们从方法中获取RemoteServiceNotAvailableException,则在发送响应之前最多重试 3 次。 另外,每次重试都会引入 1 秒的延迟。
  • @Recover – 后备方法中的内容表示,如果 3 次重试后我们未获得任何成功响应,则响应将来自此后备方法。 确保将预期的异常作为参数传递,否则 spring 将很难找到确切的方法。
  • 在调用远程服务的实际方法中,我们添加了一些自定义逻辑以基于simulateretrysimulateretryfallback参数控制异常。 代码很简单,只要满足条件就返回期望的异常重试,否则我们将返回成功响应。 此外,我们还基于“随机数”添加了一些随机逻辑来模拟故障的随机性。
  • 后备方法实现发送简单的后备响应。

4. 测试应用程序

测试部分非常简单。 我们将在 REST 请求中传递适当的参数以模拟重试请求。

4.1. 重试 - 成功或者失败

让我们从浏览器中的'http://localhost:8080/retry?simulateretry=true&simulateretryfallback=false'开始。 基于该参数,我们期望后端服务调用中出现异常,并且与simulateretryfallback=false同时出现,我们取决于随机逻辑(随机数%2 == 0 –> 偶随机数),我们可以期望重试时的成功响应。

因此,一旦我们在浏览器中命中了请求,我们可能会在后端获得异常,并且 spring 将多次重试相同的方法。 结果可能是来自后端的成功响应。 这是我尝试重试的我的请求之一中的几行日志。

Console

===============================
Inside RestController method..
Simulateretry is true, so try to simulate exception scenario.
Random Number : 1

===============================
Inside RestController mathod..
Simulateretry is true, so try to simulate exception scenario.
Random Number : 2
Simulateretry is true, so try to simulate exception scenario.
Random Number : 2
Simulateretry is true, so try to simulate exception scenario.
Random Number : 0
All retries completed, so Fallback method called!!!

第一次我找到了成功,第二次我进入了后备道路。

4.2. 重试 - 仅回退

现在尝试使用'http://localhost:8080/retry?simulateretry=true&simulateretryfallback=true',每次我们抛出RuntimeException时,您都会在重试限制之后得到回退响应,从代码中获取。 这是我的最后几行代码,它在发送响应之前尝试了 3 次。

Console

===============================
Inside RestController method..
Simulateretry is true, so try to simulate exception scenario.
Simulateretry is true, so try to simulate exception scenario.
Simulateretry is true, so try to simulate exception scenario.
All retries completed, so Fallback method called!!!

5. SpringRetry 总结

因此,我们已经知道可以使用 spring retry 模块轻松实现基于Exception的重试。 因此,下次如果您需要这种要求,则可以使用这种方法。 如果您对此有任何疑问,请在下面评论。

下载源码

学习愉快!

参考文献:

SpringRetry github 存储库和文档

Spring Boot Security Rest 基本身份验证示例

原文: https://howtodoinjava.com/spring-boot2/security-rest-basic-auth-example/

学习使用基本身份验证保护在 Spring Boot 应用程序内部创建的其余 api。 受保护的 rest api 在访问其安全的数据之前会询问身份验证详细信息。

1. Maven 依赖

为了保护其余的 api,我们必须在项目运行时中包含与 spring security 相关的 jar 文件。 添加所有必需 jar 的最简单方法是添加spring-boot-starter-security依赖项。

pom.xml

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

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-security</artifactId>
	</dependency>
</dependencies>

2. 配置WebSecurityConfigurerAdapter

为了在 Spring Boot Rest API 中启用身份验证和授权支持,我们可以配置实用程序类WebSecurityConfigurerAdapter。 它有助于要求用户在访问我们应用程序中的任何配置的 URL(或所有 URL)之前先进行身份验证。

SecurityConfig.java

package com.howtodoinjava.rest.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Override
    protected void configure(HttpSecurity http) throws Exception 
    {
        http
         .csrf().disable()
         .authorizeRequests().anyRequest().authenticated()
         .and()
         .httpBasic();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) 
            throws Exception 
    {
        auth.inMemoryAuthentication()
        	.withUser("admin")
        	.password("{noop}password")
        	.roles("USER");
    }
}

3. Spring Boot Security Rest 基本身份验证演示

出于演示目的,我们可以编写下面给出的简单 REST API。

3.1. REST API

EmployeeController.java

@RestController
@RequestMapping(path = "/employees")
public class EmployeeController 
{
    @Autowired
    private EmployeeDAO employeeDao;

    @GetMapping(path="/", produces = "application/json")
    public Employees getEmployees() 
    {
        return employeeDao.getAllEmployees();
    }
}

3.2. 访问没有“授权”标头的 REST API

访问 URL 的 Rest API:HTTP GET http://localhost:8080/employees/

Require username and password

需要用户名和密码

3.3. 使用“授权”标头访问 rest api

在传递带有已编码的基本身份验证用户名和密码组合的授权请求标头后,我们将能够访问其余的 api 响应。

访问 URL 的 Rest API:HTTP GET http://localhost:8080/employees/

Successful api call

成功的 API 调用

4. 结论

在这个 SpringBoot 安全性 REST 基本认证示例中,我们学习了用基本认证来保护 REST API。 它分两个步骤完成。 第一步是包括所需的依赖关系,例如 spring-boot-starter-security。 第二步是配置WebSecurityConfigurerAdapter并添加身份验证详细信息。

下载源码

参考文献:

Spring Security 参考

HTTP 基本认证

Spring Boot 和 H2 数据库

原文: https://howtodoinjava.com/spring-boot2/h2-database-example/

学习使用 SpringBoot 配置 H2 数据库,以在运行时创建和使用内存数据库,通常用于单元测试或 POC。 请记住,在应用程序启动时将创建/初始化内存数据库; 并在应用程序关闭时销毁。

1. 什么是 H2 数据库?

H2 是用 Java 编写的流行的内存数据库之一。 它可以嵌入 Java 应用程序中或以客户端-服务器模式运行。

Spring Boot 使用简单属性配置为 H2 提供了出色的集成支持。

为了使其更加有用,H2 还提供了一个控制台视图来维护数据库表和数据并与之交互。

2. Maven 依赖

要在 Spring Boot 应用程序中使用 H2,我们要做的就是将 H2 运行时 jar 添加到依赖项中。 最好的添加方法是通过 Maven。

pom.xml

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

3. H2 配置选项

3.1. 简单配置

Spring 提供了非常简单的配置选项,可以使用简单的属性连接到任何数据库。 下面是配置属性,我们将在application.properties文件中。

application.properties

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

请注意,默认情况下,Spring Boot 使用用户名'sa'和空密码' '配置内存数据库连接。 如果要更改这些值,请在以上属性选项中覆盖它们。

3.2. 数据持久性配置

默认情况下,内存数据库是易失性的,当我们重新启动应用程序时,所有存储的数据都会丢失。 在这种情况下,数据被写入临时内存,并且一旦 JVM 停止,数据就会被刷新。

为了拥有一个持久的数据存储区,能够在应用程序启动/停止之间存储数据,我们应该将数据存储在文件中。 为此,请更改spring.datasource.url属性。

application.properties

# temporary data storage
spring.datasource.url = jdbc:h2:mem:testdb

# temporary data storage
spring.datasource.url = jdbc:h2:file:/data/sample
spring.datasource.url = jdbc:h2:file:C:/data/sample (Windows only)

阅读更多: H2 数据库连接 URL

4. 创建模式并在初始化时插入数据

我们可能要使用一些固定的模式(DDL)初始化数据库,然后在应用程序准备好运行业务用例之前将默认数据(DML)插入表中。

我们可以通过将 sql 文件放入资源文件夹(/src/main/resources/)来实现此目的。

  • schema.sql – 初始化模式,即创建表和依赖项。
  • data.sql – 插入默认数据行。

schema.sql

DROP TABLE IF EXISTS TBL_EMPLOYEES;

CREATE TABLE TBL_EMPLOYEES (
  id INT AUTO_INCREMENT  PRIMARY KEY,
  first_name VARCHAR(250) NOT NULL,
  last_name VARCHAR(250) NOT NULL,
  email VARCHAR(250) DEFAULT NULL
);

data.sql

INSERT INTO TBL_EMPLOYEES (first_name, last_name, email) VALUES
  ('Lokesh', 'Gupta', 'abc@gmail.com'),
  ('Deja', 'Vu', 'xyz@email.com'),
  ('Caption', 'America', 'cap@marvel.com');

5. H2 控制台

5.1. 启用 H2 控制台

缺省情况下,H2 数据库的控制台视图处于关闭状态。 我们必须使它能够在浏览器中查看和访问它。 请注意,我们可以自定义 H2 控制台的 URL,默认情况下为'/h2'

application.properties

# Enabling H2 Console
spring.h2.console.enabled=true

# Custom H2 Console URL
spring.h2.console.path=/h2

5.2. 访问 H2 控制台

启动 spring boot 应用程序,并使用 URL http://localhost:8080/h2在浏览器中访问控制台。

我们可以看到这样的控制台。

H2 Database Console Login Window

H2 数据库控制台登录窗口

现在输入配置的用户名和密码。 我们可以验证通过 SQL 文件插入的表结构和默认数据。

H2 Console View

H2 控制台视图

5.3. 其他配置选项

Spring Boot 提供了两个更多的属性来进一步自定义 H2 控制台的行为。 即,我们可以启用/禁用数据库跟踪日志,并且可以启用/禁用 H2 控制台的远程访问。

默认情况下,两个属性均为false

application.properties

# Whether to enable trace output.
spring.h2.console.settings.trace=false 

# Whether to enable remote access.
spring.h2.console.settings.web-allow-others=false 

根据要求使用这些属性。

六,结论

在此H2 数据库和 Spring Boot 教程中,我们学习了通过 Spring Boot 应用程序使用简单的属性配置选项来配置,初始化和访问 H2 数据库。

将您的问题留在我的评论中。

学习愉快!

参考文献:

H2 数据库教程

下载源码

Spring Boot 2 和 ehcache 3 示例

原文: https://howtodoinjava.com/spring-boot2/ehcache3-config-example/

学习使用 ehcache 3.x 在 spring boot 应用程序中配置缓存。 学习使用 基于注解的缓存配置 ,以及使用CacheManager手动更新缓存。

1. Maven 依赖

在此示例中,我们使用的是 Spring 运行版本2.1.6.RELEASE。 较早的 spring boot 版本支持net.sf.ehcache软件包下的ehcache 2.x。 Spring Boot 支持ehcache 3.x的新版本可在org.ehcache软件包下获得。

Ehcache 版本 3 是 JSR-107 缓存管理器的实现。

我们需要以下依赖项来添加缓存功能。

  • spring-boot-starter-cache
  • ehcacheorg.ehcache
  • 缓存 APIjavax.cache

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.company</groupId>
	<artifactId>SpringBootEhcache</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

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

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

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<skipTests>true</skipTests>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>
		<dependency>
			<groupId>org.ehcache</groupId>
			<artifactId>ehcache</artifactId>
		</dependency>
		<dependency>
			<groupId>javax.cache</groupId>
			<artifactId>cache-api</artifactId>
		</dependency>
	</dependencies>

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

2. application.properties

Spring 的自动配置可以找到 Ehcache 的 JSR-107 实现。 但是,默认情况下不会创建任何缓存,因为 Spring 和 Ehcache 都不会寻找默认的ehcache.xml文件。 我们必须专门提供必须使用的 ehcache 配置文件路径。

application.properties

spring.cache.jcache.config=classpath:ehcache.xml

3. ehcache.xml

ehcache.xml文件中,配置缓存名称及其属性。

在 ehcache 文档中找到属性的完整列表。

ehcache.xml

<config
        xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
        xmlns='http://www.ehcache.org/v3'
        xmlns:jsr107='http://www.ehcache.org/v3/jsr107'>

    <service>
        <jsr107:defaults enable-statistics="true"/>
    </service>

    <cache alias="employeeCache">
        <key-type>java.lang.Long</key-type>
        <value-type>com.company.Employee</value-type>
        <expiry>
            <ttl unit="seconds">10000</ttl>
        </expiry>
        <listeners>
            <listener>
                <class>com.company.CustomCacheEventLogger</class>
                <event-firing-mode>ASYNCHRONOUS</event-firing-mode>
                <event-ordering-mode>UNORDERED</event-ordering-mode>
                <events-to-fire-on>CREATED</events-to-fire-on>
                <events-to-fire-on>UPDATED</events-to-fire-on>
                <events-to-fire-on>EXPIRED</events-to-fire-on>
                <events-to-fire-on>REMOVED</events-to-fire-on>
                <events-to-fire-on>EVICTED</events-to-fire-on>
            </listener>
        </listeners>
        <resources>
            <heap unit="entries">2000</heap>
            <offheap unit="MB">100</offheap>
        </resources>
    </cache>
</config>

4. CacheEventListener

我们还使用了事件监听器,该事件监听器将在创建,删除或更新缓存条目时记录缓存事件。

CustomCacheEventLogger.java

package com.company;

import org.ehcache.event.CacheEvent;
import org.ehcache.event.CacheEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CustomCacheEventLogger implements CacheEventListener<Object, Object> {

	private static final Logger LOG = LoggerFactory.getLogger(CustomCacheEventLogger.class);

	@Override
	public void onEvent(CacheEvent cacheEvent) {
		LOG.info("Cache event = {}, Key = {},  Old value = {}, New value = {}", cacheEvent.getType(),
				cacheEvent.getKey(), cacheEvent.getOldValue(), cacheEvent.getNewValue());
	}
}

5. @EnableCaching

它启用 Spring 的注解驱动的缓存管理功能,并在调用@Cacheable注解方法时启用对代理拦截器的支持。

CacheConfig.java

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class CacheConfig {

}

6. @Cacheable注解

要缓存从方法调用返回的数据,我们可以在方法上使用@Cacheable注解。 使用其属性cacheNameskey来指代缓存和缓存条目的键属性。

EmployeeManager.java

import java.util.HashMap;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class EmployeeManager 
{
	static HashMap<Long, Employee> db = new HashMap<>();

	static {
		db.put(1L, new Employee(1L, "Alex", "Gussin"));
		db.put(2L, new Employee(2L, "Brian", "Schultz"));
	}

	@Cacheable(cacheNames="employeeCache", key="#id")
	public Employee getEmployeeById(Long id) {
		System.out.println("Getting employee from DB");
		return db.get(id);
	}
}

7. Spring CacheManager API

有时,在使用注解似乎不是完美解决方案的情况下,我们可能需要使用缓存。 在这种情况下,我们可以使用org.springframework.cache.CacheManagerorg.springframework.cache.Cache抽象来访问并利用 ehcache 添加和访问缓存条目。

要使用CacheManager,我们必须首先自动装配到 spring 组件中,并使用它的getCache(name)方法来按名称获取缓存实例。

访问缓存后,我们可以使用其get()put()方法来添加和访问缓存条目。

//1

@Autowired
private CacheManager cacheManager;

//2

Cache cache = cacheManager.getCache("myCache");
cache.put(3L, "Hello");
String value = cache.get(3L).get();

8. Spring Boot ehcache 3 示例 – 演示

我正在创建一个模型类Employee,其实例将被缓存。

Employee.java

import java.io.Serializable;

public class Employee implements Serializable 
{
	private static final long serialVersionUID = 5517244812959569947L;

	private Long id;
	private String firstName;
	private String lastName;

	public Employee() {
		super();
	}

	public Employee(Long id, String firstName, String lastName) {
		super();
		this.id = id;
		this.firstName = firstName;
		this.lastName = lastName;
	}

	//Getters and setters

	@Override
	public String toString() {
		return "Employee [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + "]";
	}
}

现在在演示应用程序类中,我正在使用@Cacheable注解测试基于注解的缓存访问,并使用CacheManager进行手动缓存访问。

Application.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

	@Autowired
	private CacheManager cacheManager;

	@Autowired
	private EmployeeManager employeeManager;

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

	@Bean
	public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
		return args -> {

			//This will hit the database 
			System.out.println(employeeManager.getEmployeeById(1L));

			//This will hit the cache - verify the message in console output 
			System.out.println(employeeManager.getEmployeeById(1L));

			//Access cache instance by name
			Cache cache = cacheManager.getCache("employeeCache");

			//Add entry to cache
			cache.put(3L, new Employee(3L, "Charles", "Dave"));

			//Get entry from cache
			System.out.println(cache.get(3L).get());
		};
	}
}

程序输出。

Console

//First hit to "employeeManager.getEmployeeById(1L)"

Getting employee from DB
Employee [id=1, firstName=Alex, lastName=Gussin]
2019-07-10 15:42:32.371  INFO 11956 --- [e [_default_]-0] com.company.CustomCacheEventLogger       
: Cache event = CREATED, Key = 1,  Old value = null, New value = Employee [id=1, firstName=Alex, lastName=Gussin]

//Second hit to "employeeManager.getEmployeeById(1L)"
Employee [id=1, firstName=Alex, lastName=Gussin]

//cache.put(3L, new Employee(3L, "Charles", "Dave"));
2019-07-10 15:42:32.399  INFO 11956 --- [e [_default_]-0] com.company.CustomCacheEventLogger       : Cache event = CREATED, Key = 3,  Old value = null, New value = Employee [id=3, firstName=Charles, lastName=Dave]

//System.out.println(cache.get(3L).get());
Employee [id=3, firstName=Charles, lastName=Dave]

请在评论中使用 ehcache 3 将有关此 spring boot 2 缓存示例的问题交给我。

学习愉快!

下载源码

Spring Boot 2 与 Gson

原文: https://howtodoinjava.com/spring-boot2/gson-with-spring-boot/

了解将 Gson 配置为 Spring Boot 应用程序作为首选 json 映射器。 默认情况下,Spring Boot 为此使用 Jackson。

1. 包括 Gson 依赖

通过添加适当的 Gson 依赖项在 Spring Boot 应用程序中包含 gson。

build.gradle

dependencies {
    compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
}

pom.xml

<dependencies>
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.8.5</version>
    </dependency>
</dependencies>

2. Gson 自动配置

通过包含依赖关系,Spring Boot 可以检测到对类路径的 Gson 依赖关系,并创建一个具有合理默认值的Gson bean。

2.1. 默认 Gson 实例

Spring Boot 会检测到Gson.class的存在,并使用 GsonAutoConfiguration 来配置 Gson 实例。

如果我们需要直接访问Gson实例,则可以直接在 spring bean 中自动装配:

Autowire Gson instance

@Autowire
private Gson gson;

2.2. 自定义 Gson 实例

要自定义 Gson 实例的默认行为,请从下面的列表中配置相应的属性。 GsonAutoConfiguration在初始化Gson实例时使用这些属性。

application.properties

# Format to use when serializing Date objects.
spring.gson.date-format= 

# Whether to disable the escaping of HTML characters such as '<', '>', etc.
spring.gson.disable-html-escaping= 

# Whether to exclude inner classes during serialization.
spring.gson.disable-inner-class-serialization= 

# Whether to enable serialization of complex map keys (i.e. non-primitives).
spring.gson.enable-complex-map-key-serialization= 

# Whether to exclude all fields from consideration for serialization or deserialization that do not have the "Expose" annotation.
spring.gson.exclude-fields-without-expose-annotation= 

# Naming policy that should be applied to an object's field during serialization and deserialization.
spring.gson.field-naming-policy= 

# Whether to generate non executable JSON by prefixing the output with some special text.
spring.gson.generate-non-executable-json= 

# Whether to be lenient about parsing JSON that doesn't conform to RFC 4627.
spring.gson.lenient= 

# Serialization policy for Long and long types.
spring.gson.long-serialization-policy= 

# Whether to output serialized JSON that fits in a page for pretty printing.
spring.gson.pretty-printing= 

# Whether to serialize null fields.
spring.gson.serialize-nulls= 

3. 使 Gson 成为首选 json 映射器

要将 Spring Boot 配置为使用Gson作为首选 Jackson 映射器,请在application.properties文件中使用此属性。

application.properties

spring.http.converters.preferred-json-mapper=gson

现在,即使 Jackson 在类路径中可用,Spring 运行也会使用 Gson 来序列化和反序列化 JSON 载荷。

4. 完全排除 Jackson

如果我们想将 Jackson 完全排除在应用程序运行时之外,可以通过排除spring-boot-starter-json来实现。

4.1. 从项目依赖项中排除 Jackson

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- Exclude the default Jackson dependency -->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-json</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.5</version>
    </dependency>
</dependencies>

4.2. 禁用自动配置

如果我们只想在 SpringBoot 配置中排除 Jackson,可以通过禁用自动配置类JacksonAutoConfiguration来排除它。

SpringBootApplication.java

@SpringBootApplication(exclude = {JacksonAutoConfiguration.class})
public class SpringBootApplication {

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

请问您有关使用 Spring Boot 2 配置 Gson 的问题。

学习愉快!

Spring bean – XML 配置

原文: https://howtodoinjava.com/spring5/core/spring-bean-xml-config/

在此 spring bean XML 配置示例中,学习创建定义和创建 spring bean 以及在任何 spring 应用程序中填充应用程序上下文。 本示例使用 xml 配置定义 bean 。 我们将使用 maven 管理 Spring 依赖项,并使用 Eclipse 构建和运行代码。

1. Spring Maven 依赖

要创建能够创建和管理 bean 的 spring 应用程序上下文,我们至少需要三个 maven 依赖项,即spring-corespring-beansspring-context

  1. spring-core模块具有与其他 Spring 模块一起使用所需的最基本的类。
  2. spring-beans模块提供了与 Spring bean 一起使用所需的org.springframework.beans.factory.BeanFactory接口。
  3. spring-context模块提供org.springframework.context.ApplicationContext接口,该接口可启用其他功能,例如消息资源, AOP 功能,特定类型的应用程序上下文和 bean 生命周期事件监听器。

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.spring.webmvc</groupId>
	<artifactId>SpringDemos</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

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

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<spring.version>5.2.0.RELEASE</spring.version>
	</properties>

	<dependencies>
		<!-- Spring Dependencies -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
	</dependencies>
</project>

2. xml 配置中的 Spring bean 定义

2.1. 具有 bean 定义的单个配置文件

您可以在单个 xml 文件中定义所有 SpringBean 及其传递依赖项。 此 xml 文件可用于创建应用程序上下文

beans.xml

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

	<bean id="operations" 	class="com.howtodoinjava.spring.beans.Operations"></bean>
	<bean id="employee" 	class="com.howtodoinjava.spring.beans.Employee"></bean>
	<bean id="department" 	class="com.howtodoinjava.spring.beans.Department"></bean>

</beans> 

2.2. 在多个配置文件中定义 bean 并导入到主文件中

该方法在编写模块化代码时更有用。 您可以在单独的 xml 文件中定义 bean,然后将文件导入主 xml 文件。

employee.xml

<beans>

	<bean id="employee" class="com.howtodoinjava.spring.beans.Employee"></bean>

</beans> 

department.xml

<beans>

	<bean id="department" class="com.howtodoinjava.spring.beans.Department"></bean>

</beans> 

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans>

	<import resource="employee.xml"/>
	<import resource="department.xml"/>

	<bean id="operations" class="com.howtodoinjava.spring.beans.Operations"></bean>

</beans> 

3. Spring bean 示例

创建ApplicationContext,我们可以使用它作为可用实现的一种特定实现,例如 ClassPathXmlApplicationContextFileSystemXmlApplicationContextStaticApplicationContextXmlWebApplicationContext等。

我们将需要将 bean 配置文件名称作为使用类的构造函数参数传递。 不要忘记将文件归档在classpath或资源文件夹中。

Main.java

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class XmlConfigExample 
{
    public static void main( String[] args )
    {
	@SuppressWarnings("resource")
	ApplicationContext ctx = new 
                  ClassPathXmlApplicationContext( "com/howtodoinjava/core/demo/beans/beans.xml" );

        Employee employee = ctx.getBean(Employee.class);

        Department department = ctx.getBean(Department.class);

        Operations operations = ctx.getBean(Operations.class);

        System.out.println(department);
        System.out.println(employee);

        operations.helloWorld();
    }
}

程序输出:

Console

Jan 02, 2018 3:10:27 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [beans.xml]

Jan 02, 2018 3:10:27 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [employee.xml]

Jan 02, 2018 3:10:27 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [department.xml]

Employee [id=0, name=null]
Department [id=0, name=null]
Hello World !!

4. 示例中使用的项目结构和其他类

4.1. 项目结构

Spring XML Config Project Structure

Spring XML Config Project Structure

4.2. Bean

Employee.java

@Getter
@Setter
@ToString
public class Employee 
{
	private long id;
	private String name;
}

Department.java

@Getter
@Setter
@ToString
public class Department 
{
    private long id;
    private String name;
}

Operations.java

public class Operations 
{
	public void helloWorld(){
		System.out.println("Hello World !!");
	}
}

在评论部分中,将您与基于 Spring XML 配置基于 spring 容器有关的问题交给我。

学习愉快!

有关:

Spring Java 配置示例
Spring Boot2 配置示例

下载源码

Spring Boot Remoting – Spring RMI 注解示例

原文: https://howtodoinjava.com/spring-boot2/spring-remoting-rmi-hessian/

在此 Spring boot 2 rmi 示例中,了解 spring 如何提供不同的机制来调用驻留在不同 JVM 中并且很可能在不同服务器中的远程方法。 这称为 RMI(远程方法调用)概念。

RMI 出现在 EJB 的早期。 在当今的 HTTP / HTTPS 世界中,SOAP 和 REST 在实现任何服务中都占主导地位,但是在 Spring 框架中,远程调用仍然是一种选择。 今天,我们将在这个领域看到两个新协议,主要是 HessianRMI

1. 什么是远程调用?

Spring 框架提供了一系列工具,这些工具统称为 Spring 远程调用。 这些工具使我们可以在远程系统上调用远程服务,就像它们在本地可用一样。

通常不需要,但是在某些情况下,我们仍然倾向于使用远程调用。 使用远程调用的主要原因是性能和安全性。 基于 HTTP 的服务会影响性能,因为我们必须处理请求和响应(封送/拆组)。 更重要的是,如果仅使用这些技术就已经将服务器公开为远程服务,则客户端除了 RMI 别无选择。

2. Spring 远程调用技术

以下主要是 spring 帮助我们实现远程调用的方法。

  • 远程方法调用(RMI) – 在 Spring,通过在服务器端使用RmiServiceExporter和在客户端使用RmiProxyFactoryBean实现了 RMI。

  • Spring 的 HTTP 调用程序 – 这是通过使用HttpInvokerProxyFactoryBeanHttpInvokerServiceExporter进行远程调用的另一种方式。 这基本上是通过 HTTP 的 Java 序列化,支持任何 Java 接口。

  • Hessian – 它是 Caucho 提供的基于 HTTP 的轻量级二进制协议。 我们可以使用 Hessian Spring 的HessianProxyFactoryBeanHessianServiceExporter的 Spring 包装器来公开和使用远程服务。 内部在传输时使用二进制数据

  • Burlap – Burlap 是用于远程处理的基于 XML 的协议。 它也是由 Caucho 开发的。 它类似于 Hessian,唯一的区别是 Hessian 是二进制,而 Burlap 是 XML。 与 Hessian 一样,Burlap 需要通过 HTTP 托管。与 Hessian 相似,它具有BurlapServiceExporterBurlapProxyFactoryBean类。

    请注意,由于尚未积极开发 Burlap,自 Spring 4.0 起就不再支持它。

3. Spring RMI 远程调用 – 创建 RMI 服务

如上所述,我们将使用 Spring Boot 为 RMI 和 Hessian 开发项目。 我们将创建两个 Maven 项目。 服务器的第一个项目是托管实现,另一个是客户端部分。 客户端应用程序将通过相应的远程协议与服务器通信,以调用远程方法。

3.1. 创建 Maven 项目

创建两个名为spring-rmi-serverspring-rmi-client的简单的 Spring Boot 项目,它们具有spring-boot-web依赖,以将其托管在 Web 服务器中。

为此,我们需要转到 https://start.spring.io/ 并提供 Maven 坐标并选择依赖项。 下载包含框架项目的 zip 文件。 然后,一旦解压缩到合适的文件夹中,我们就需要将其导入 eclipse 中。

客户端项目中不需要 Web 依赖项,因为我们将其作为独立的应用程序运行。

Spring 项目创建

3.2. 创建 Spring RMI 服务

编写我们要公开的 Java 接口和 RMI 服务的实现。 在本教程中,我们将创建一个简单的 hello world 应用程序,该应用程序将根据输入向用户打招呼。 这是必需的接口及其实现。

HelloWorldRMI.java

package com.example.springrmiserver.service;

public interface HelloWorldRMI 
{
	public String sayHelloRmi(String msg);
}

HelloWorldRMIimpl.java

package com.example.springrmiserver.service;

import java.util.Date;

public class HelloWorldRMIimpl implements HelloWorldRMI {

	@Override
	public String sayHelloRmi(String msg) {
		System.out.println("================Server Side ========================");
		System.out.println("Inside Rmi IMPL - Incoming msg : " + msg);
		return "Hello " + msg + " :: Response time - > " + new Date();
	}
}

3.3. Spring RMI 服务配置

在服务器项目中创建一个 spring bean 配置类,以使用RmiServiceExporter将接口和实现注册到 RMI 导出器。 我们需要提供它的端点名称以进行公开。 这将在基于 rmi 的 url(例如rmi://localhost:1099?)中公开实现类。

  • ServiceInterface – 我们需要提供要发布的接口的类。
  • Service – 这是服务实现类的实例。 为了简单起见,在这里我创建了一个新实例,但是我们可以在此处轻松给出我们要公开的另一个 spring bean。
  • ServiceName – 如上所述,这是我们要与 rmi url 中的服务关联的服务名称,客户端将使用该服务名称来调用服务。

Config.java

package com.example.springrmiserver;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiServiceExporter;
import org.springframework.remoting.support.RemoteExporter;

import com.example.springrmiserver.service.HelloWorldRMI;
import com.example.springrmiserver.service.HelloWorldRMIimpl;

@Configuration
public class Config {

	@Bean
	RemoteExporter registerRMIExporter() {

		RmiServiceExporter exporter = new RmiServiceExporter();
		exporter.setServiceName("helloworldrmi");
		exporter.setServiceInterface(HelloWorldRMI.class);
		exporter.setService(new HelloWorldRMIimpl());

		return exporter;
	}
}

3.4. 构建和部署 RMI 服务

现在,我们可以完成最终构建并启动提供的 tomcat 服务器。 确保服务器正常启动,并记下特别是 RMI 端口的端口,该端口应为1099。 我们将在客户端使用此端口访问服务。

Console

2018-09-07 21:37:22.872  INFO 6452 --- [main] o.s.remoting.rmi.RmiServiceExporter      
: Binding service 'helloworldrmi' to RMI registry: 
RegistryImpl[UnicastServerRef [liveRef: [endpoint:[192.168.1.7:1099](local),objID:[0:0:0, 0]]]]

4. Spring 远程调用 – 创建 RMI 客户端

4.1. 在客户端项目中创建完全相同的服务接口

在客户端应用程序中创建相同的 rmi 接口。 这很重要,rmi 将使用此接口作为服务器端接口的框架,否则我们将获得异常。

4.2. Spring RMI 客户端配置

在客户端项目中配置RmiProxyFactoryBean bean 与远程服务连接。 这是所需的简单默认配置。

SpringRmiClientApplication.java

package com.example.springrmiclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;

import com.example.springrmiserver.service.HelloWorldRMI;

@SpringBootApplication
public class SpringRmiClientApplication {

	@Bean
	RmiProxyFactoryBean rmiProxy() {
		RmiProxyFactoryBean bean = new RmiProxyFactoryBean();
		bean.setServiceInterface(HelloWorldRMI.class);
		bean.setServiceUrl("rmi://localhost:1099/helloworldrmi");

		return bean;
	}

	public static void main(String[] args) 
	{
		HelloWorldRMI helloWorldRMI = SpringApplication.run(SpringRmiClientApplication.class, args)
												.getBean(HelloWorldRMI.class);

		System.out.println("================Client Side ========================");

		System.out.println(helloWorldRMI.sayHelloRmi("Sajal"));
	}
}

4.3. Spring RMI 远程调用示例

现在,我们需要创建一个主要方法,在该方法中,我们将从 spring 上下文中查找实现接口 bean。 有了实例后,我们现在就可以像调用任何其他本地 java 方法一样调用服务器端方法。

如果一切顺利,将调用您的服务器端方法,检查服务器和客户端中的日志,该日志将可见。

================Server Side ========================
Inside Rmi IMPL - Incoming msg : Sajal
2018-09-07 21:39:14.776  INFO Registering beans for JMX exposure on startup
2018-09-07 21:39:14.808  INFO Started SpringRmiClientApplication in 1.866 seconds (JVM running for 2.368)
================Client Side ========================
Hello Sajal :: Response time - > Fri Sep 07 21:39:14 IST 2018

5. Spring Hessian 远程

Hessian 远程调用也与 RMI 远程调用非常相似。 唯一的变化是我们需要服务器端的 Hessian 相关服务导出器和客户端端的基于 Hessian 的代理 bean 工厂。

5.1. Hessian 的 Maven 依赖

pom.xml


<dependency>
	<groupId>com.caucho</groupId>
	<artifactId>hessian</artifactId>
	<version>4.0.51</version>
</dependency> 

5.2. Hessian 的服务接口和实现

像 RMI 一样,我们将创建一对接口及其实现。 我们也可以重用 RMI,但是我在工作区中创建了一个新集。

HelloWorld.java

package com.howtodoinjava.hessianserver.hessian;

public interface HelloWorld {
	public String sayHelloWithHessian(String msg);
}

HelloWorldImpl.java

package com.howtodoinjava.hessianserver.hessian;

import java.util.Date;

public class HelloWorldImpl implements HelloWorld {

	@Override
	public String sayHelloWithHessian(String msg) {
		System.out.println("=============server side==============");
		System.out.println("msg : " + msg);
		return "Hello " + msg + " Response time :: " + new Date();
	}
}

5.3. Hessian 服务器端配置 – 导出器

这是服务导出程序,它与 RMI 相同。 我刚刚更改了粗麻布相关的课程。

HessianConfiguration.java

package com.howtodoinjava.hessianserver;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.caucho.HessianServiceExporter;
import org.springframework.remoting.support.RemoteExporter;

import com.howtodoinjava.hessianserver.hessian.HelloWorld;
import com.howtodoinjava.hessianserver.hessian.HelloWorldImpl;

@Configuration
public class HessianConfiguration {

	@Bean(name = "/hellohessian")
	RemoteExporter sayHelloServiceHessian() {
		HessianServiceExporter exporter = new HessianServiceExporter();
		exporter.setService(new HelloWorldImpl());
		exporter.setServiceInterface(HelloWorld.class);
		return exporter;
	}

}

5.4. Hessian 的客户端配置 – 代理 Bean

此处的配置与 RMI 相同,但 URL 不同。Hessian 使用 HTTP 作为传输协议。 这是一个二进制协议,意味着所有数据都以二进制格式传输到服务器。 其余的复杂性由 Hessian 和 spring 处理。

HessianConfiguration.java

package com.howtodoinjava.example.hessianclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.remoting.caucho.HessianProxyFactoryBean;

import com.howtodoinjava.hessianserver.hessian.HelloWorld;

@SpringBootApplication
public class HessianClientApplication {

	@Bean
	public HessianProxyFactoryBean hessianInvoker() {
		HessianProxyFactoryBean invoker = new HessianProxyFactoryBean();
		invoker.setServiceUrl("http://localhost:8080/hellohessian");
		invoker.setServiceInterface(HelloWorld.class);
		return invoker;
	}

	public static void main(String[] args) {
		ConfigurableApplicationContext context =  SpringApplication.run(HessianClientApplication.class, args);
		System.out.println("========Client Side===============");
		HelloWorld helloWorld =   	context.getBean(HelloWorld.class);
		System.out.println(helloWorld.sayHelloWithHessian("Sajal"));
	}
}

5.5. Spring Hessian 远程调用示例

一旦运行服务器,就运行客户端应用程序。 我们应该看到已经调用了服务器端方法。

这是来自服务器和客户端的日志。

Server logs

2018-09-07 21:46:09.367  INFO 1264 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-09-07 21:46:09.403  INFO 1264 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 36 ms
=============server side==============
msg : Sajal

Client logs

2018-09-07 21:46:08.903  INFO 6856 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-09-07 21:46:09.197  INFO 6856 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 9090 (http) with context path ''
2018-09-07 21:46:09.205  INFO 6856 --- [           main] c.h.e.h.HessianClientApplication         : Started HessianClientApplication in 6.069 seconds (JVM running for 7.092)
========Client Side===============
Hello Sajal Response time :: Fri Sep 07 21:46:09 IST 2018

6. 总结

今天,我们已经看到了如何将远程方法公开为服务,以及如何使用不同的远程协议从客户端使用这些远程方法。 在此演示中,与网络服务不同,我们没有任何这样的载荷,在网络服务中,我们需要进行额外的处理以进行封送/编组。

通过这种方法,可以避免载荷处理的开销,但是需要注意的是,服务器和客户端都应仅是 Java,而不能从其他语言/运行时调用,例如 REST,我们可以轻松地从 JavaScript 调用。

牢记所有这些,我们可以选择所需的适当方法。

学习愉快!

参考文献:

Spring 5 远程调用文档

下载源码

SpringBoot – 发送带有附件的电子邮件

原文: https://howtodoinjava.com/spring-boot2/send-email-with-attachment/

了解如何借助JavaMailSender在 spring boot 应用程序中发送电子邮件,以发送简单电子邮件以及带有附件的电子邮件。

1. Maven

在 Spring Boot 应用程序中,包括spring-boot-starter-mail依赖项。

pom.xml

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

<dependencies>
	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
</dependencies>

2. SMTP 配置

使用 spring boot,我们可以在application.properties文件中配置 SMTP 设置。

2.1. 邮箱

application.properties

debug=true

spring.mail.host=smtp.gmail.com
spring.mail.port=25
spring.mail.username=admin@gmail.com
spring.mail.password=xxxxxx

# Other properties
spring.mail.properties.mail.debug=true
spring.mail.properties.mail.transport.protocol=smtp
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=5000
spring.mail.properties.mail.smtp.writetimeout=5000

# TLS , port 587
spring.mail.properties.mail.smtp.starttls.enable=true

# SSL, post 465
#spring.mail.properties.mail.smtp.socketFactory.port = 465
#spring.mail.properties.mail.smtp.socketFactory.class = javax.net.ssl.SSLSocketFactory

为了能够使用两因素身份验证并关闭“允许不太安全的应用程序”选项,强烈建议使用应用程序密码代替 Gmail 密码。

请参阅:https://support.google.com/accounts/answer/185833

如果您可以访问下面的 SMTP 服务器,请在下面自由使用。

2.2. Outlook

如果它是某些公司服务器,请从管理员那里查找此信息。

Outlook Configuration

spring.mail.host=smtp-mail.outlook.com
spring.mail.port=587
spring.mail.username=outlookuserid@outlook.com
spring.mail.password=xxxxxx

spring.mail.properties.mail.protocol=smtp
spring.mail.properties.mail.tls=true

spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.ssl.trust=smtp-mail.outlook.com

2.3. AWS SES

从您的 AWS 设置页面中找到相关配置。阅读更多。

AWS SES Configuration

spring.mail.host=email-smtp.us-east-1.amazonaws.com
spring.mail.port=465
spring.mail.username=xxxxxxxxxxx
spring.mail.password=xxxxxxxxxxx

spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.properties.mail.smtp.ssl.enavle=true

spring.mail.properties.mail.protocol=smtps

spring.mail.properties.mail.smtps.auth=true

3. 预先配置的电子邮件模板

我们可以创建电子邮件模板以供每次重用 – 我们必须定期或在任何特定事件或持续时间内发送电子邮件。 在给定的示例中,我们正在使用SimpleMailMessage创建一个非常简单的纯文本电子邮件。

EmailConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.SimpleMailMessage;

@Configuration
public class EmailConfig 
{
	@Bean
	public SimpleMailMessage emailTemplate()
	{
		SimpleMailMessage message = new SimpleMailMessage();
		message.setTo("user@gmail.com");
		message.setFrom("admin@gmail.com");
		message.setSubject("Important email");
	    message.setText("FATAL - Application crash. Save your job !!");
	    return message;
	}
}

4. 发送简单的电子邮件

为了发送不同类型的电子邮件,我们创建了一个EmailService类,该类可以采用在发送电子邮件之前在电子邮件中设置所需的参数。

EmailService.java

@Service("emailService")
public class EmailService 
{
    @Autowired
    private JavaMailSender mailSender;

    @Autowired
    private SimpleMailMessage preConfiguredMessage;

    /**
     * This method will send compose and send the message 
     * */
    public void sendMail(String to, String subject, String body) 
    {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(to);
        message.setSubject(subject);
        message.setText(body);
        mailSender.send(message);
    }

    /**
     * This method will send a pre-configured message
     * */
    public void sendPreConfiguredMail(String message) 
    {
        SimpleMailMessage mailMessage = new SimpleMailMessage(preConfiguredMessage);
        mailMessage.setText(message);
        mailSender.send(mailMessage);
    }
}

5. 发送带有附件的电子邮件

使用MimeMessageHelper发送多媒体电子邮件,该电子邮件用于配置MimeMessage。 这些 mime 消息是富文本电子邮件。

Email with attachment

@Autowired
private JavaMailSender mailSender;

public void sendMailWithAttachment(String to, String subject, String body, String fileToAttach) 
{
	MimeMessagePreparator preparator = new MimeMessagePreparator() 
	{
        public void prepare(MimeMessage mimeMessage) throws Exception 
        {
            mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
            mimeMessage.setFrom(new InternetAddress("admin@gmail.com"));
            mimeMessage.setSubject(subject);
            mimeMessage.setText(body);

            FileSystemResource file = new FileSystemResource(new File(fileToAttach));
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
            helper.addAttachment("logo.jpg", file);
        }
    };

    try {
        mailSender.send(preparator);
    }
    catch (MailException ex) {
        // simply log it and go on...
        System.err.println(ex.getMessage());
    }
}

6. 发送带有内嵌图像的电子邮件

富文本格式包括介于文本内容之间的媒体内容。 为此,我们必须使用MimeMessageHelperaddInline()方法。

Email with inline images

@Autowired
private JavaMailSender mailSender;

public void sendMailWithInlineResources(String to, String subject, String fileToAttach) 
{
	MimeMessagePreparator preparator = new MimeMessagePreparator() 
	{
        public void prepare(MimeMessage mimeMessage) throws Exception 
        {
            mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
            mimeMessage.setFrom(new InternetAddress("admin@gmail.com"));
            mimeMessage.setSubject(subject);

            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

            helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);

            FileSystemResource res = new FileSystemResource(new File(fileToAttach));
            helper.addInline("identifier1234", res);
        }
    };

    try {
        mailSender.send(preparator);
    }
    catch (MailException ex) {
        // simply log it and go on...
        System.err.println(ex.getMessage());
    }
}

7. Spring Boot 发送电子邮件演示

要使用上述配置发送电子邮件,请更新application.properties文件中的spring.mail.usernamespring.mail.password属性。

现在执行EmailService的方法发送电子邮件。

Application.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application implements CommandLineRunner 
{
	@Autowired
    private EmailService emailService;

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

    @Override
    public void run(String... args) 
    {
    	emailService.sendMail("lokeshgupta1981@gmail.com", "Hi", "Ho ho ho");

    	emailService.sendPreConfiguredMail("Ho ho ho");
	}
}

请给我发送有关通过 SpringBoot发送电子邮件的问题。

学习愉快!

下载源码

Spring Boot 面试问题

原文: https://howtodoinjava.com/interview-questions/spring-boot-interview-questions/

Spring Boot 使应用程序开发更加容易,但是在测试您对所有工作原理的了解时,我们可能会遇到一些艰难的面试问题。 使用给定的 Spring Boot 面试问题和答案为下一次面试做好准备。

1. 什么是 Spring Boot? 它与 Spring 框架有何不同?

Spring boot modules

Spring boot 模块

Spring Boot 是一个 Spring 框架模块,借助入门模板和自动配置功能,它们向 Spring 框架提供 RAD(快速应用程序开发)功能,这些功能非常强大且可以完美运行。

它采用 Spring 平台和第三方库的观点视图。 这意味着只要我们将任何依赖项包含到应用程序中,Spring Boot 就会假定其具有通用性,并自动将库中最常用的类配置为具有合理默认值的 Spring bean。

例如,如果我们要创建 Web MVC 应用程序,则包含 maven 依赖项spring-boot-starter-web仅会使用 Spring webMVC 带来用于构建 Web 的所有 jar / 库,包括 RESTful 应用程序。 Ut 还包括 Tomcat 作为默认的嵌入式容器。

它还提供了一系列非功能性功能,例如嵌入式服务器,安全性,指标,运行状况检查和开箱即用的外部配置,而无需额外的配置。

如果我们必须确定 Spring 框架和 Spring Boot 之间的区别,那么可以说 Spring Boot 基本上是 Spring 框架的扩展,它消除了设置可正常生产的应用程序所需的样板配置。

它对导入到项目中的 Spring 和第三方库采取了自以为是的观点,并为我们配置了行为。 区别就这么简单。

2. SpringBoot 的优缺点?

运行的两个最佳优势通过启动器 POM 以及最常用的库和行为的有意识的自动配置,简化了&版本无冲突依赖管理。

嵌入式 jar 可以将 Web 应用程序打包为 jar 文件,可以在任何地方运行。

它的执行器模块提供 HTTP 端点来访问应用程序内部,例如详细指标,应用程序内部工作状态,运行状况等。

劣势方面,它们很少。 仍然有许多开发人员可能将初学者 poms 包含的传递性依赖项视为部署打包的负担。

另外,它的自动配置功能可能会启用许多我们在应用程序生命周期中从未使用过的功能,并且这些功能会一直被初始化和完全配置。 这可能会导致不必要的资源利用。

3. 什么是自动配置? 如何启用或禁用某些配置?

Spring Boot 自动配置扫描类路径,在类路径中找到库,然后尝试为它们猜测最佳配置,最后配置所有此类 bean。

自动配置会尝试尽可能智能化,并且会随着我们定义更多自己的自定义配置而退出。 它始终在注册用户定义的 bean 之后应用。

自动配置在@Conditional注解(例如@ConditionalOnBean@ConditionalOnClass)的帮助下工作。

例如,查看AopAutoConfiguration类。 如果类路径扫描找到EnableAspectJAutoProxy,切面,建议和AnnotatedElement类,并且spring.aop.auto=false在属性文件中不存在,则启动 Spring 将为我们配置 Spring AOP 模块。

AopAutoConfiguration.java

@Configuration

@ConditionalOnClass({ EnableAspectJAutoProxy.class, 
			Aspect.class, 
			Advice.class, 
			AnnotatedElement.class })

@ConditionalOnProperty(prefix = "spring.aop", 
			name = "auto", 
			havingValue = "true", 
			matchIfMissing = true)

public class AopAutoConfiguration 
{
	//code
}

4. 什么是启动器依赖项?

Spring Boot 启动器是 Maven 模板,其中包含启动特定功能所需的所有相关传递依赖项的集合。

例如,如果我们要创建 Spring WebMVC 应用程序,则在传统设置中,我们将自己包括所有必需的依赖项。 它留下了版本冲突的机会,最终导致更多的运行时异常。

使用 Spring boot,要创建 Web MVC 应用程序,我们需要导入的只是spring-boot-starter-web依赖项。 以传递方式,它引入了所有其他必需的依赖关系以构建 Web 应用程序,例如spring-webmvcspring-webhibernate-validatortomcat-embed-coretomcat-embed-eltomcat-embed-websocketjackson-databindjackson-datatype-jdk8jackson-datatype-jsr310jackson-module-parameter-name

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

5. Spring Boot 常用注解?

最常用和最重要的SpringBoot 注解如下:

  • @EnableAutoConfiguration – 启用自动配置机制。
  • @ComponentScan – 在应用程序类路径中启用组件扫描。
  • @SpringBootApplication – 一步启用上述所有三项功能,即启用自动配置机制,启用组件扫描并在上下文中注册额外的 bean。
  • @ImportAutoConfiguration – 仅导入并应用指定的自动配置类。 当我们不想启用默认自动配置时,应使用此选项。
  • @AutoConfigureBefore, @AutoConfigureAfter, @AutoConfigureOrder – 如果需要以特定顺序应用配置(在之后之前),则应使用@AutoConfigureBefore, @AutoConfigureAfter, @AutoConfigureOrder
  • @Conditional – 诸如@ConditionalOnBean@ConditionalOnWebApplication@ConditionalOnClass之类的注解仅在条件满足时才注册 Bean。

6. Spring Boot 是否支持嵌入式服务器?

Spring Boot 应用程序始终将 tomcat 作为嵌入式服务器依赖项。 这意味着您可以从命令提示符运行 Spring Boot 应用程序,而无需使用复杂的服务器基础结构。

如果需要的话,我们可以排除 tomcat,并根据需要包括其他任何嵌入式服务器。 或者我们可以完全排除服务器环境。 全部基于配置。

例如,以下配置排除了 tomcat,并包括了 jetty 作为嵌入式服务器。

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

7. 为什么我们使用 spring boot maven 插件?

它在 Maven 中提供了 Spring Boot 支持,使我们可以打包可执行 jar 或 war 归档文件并“就地”运行应用程序。 要使用它,我们必须使用 Maven 3.2(或更高版本)。

插件提供了使用 Spring Boot 应用程序的几个目标:

  • spring-boot:repackage:创建一个可自动执行的 jar 或 war 文件。 它可以替换常规工件,或者可以使用单独的分类器附加到构建生命周期。
  • spring-boot:run:运行带有多个选项的 Spring Boot 应用程序以将参数传递给它。
  • spring-boot:startstop:将 Spring Boot 应用程序集成到integration-test阶段,以便应用程序先于它启动。
  • spring-boot:build-info:生成执行器可以使用的构建信息。

8. 如何创建和运行一个简单的启动应用程序?

  • 要创建任何简单的 spring boot 应用程序,我们需要先创建一个pom.xml文件。 将spring-boot-starter-parent添加为项目的父项,这使其成为 Spring Boot 应用程序。

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.example</groupId>
        <artifactId>myproject</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.1.RELEASE</version>
        </parent>
    </project>
    
    

    上面是最简单的 Spring Boot 应用程序,可以将其打包为 jar 文件。 现在,将项目导入到 IDE 中(可选)。

  • 现在,我们可以开始添加在开发特定类型的应用程序时可能需要的其他启动程序依赖项。 例如,对于 Web 应用程序,我们添加了spring-boot-starter-web依赖项。

    pom.xml

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    
    
  • 在此步骤开始添加应用程序业务逻辑,视图和域数据。 默认情况下,Maven 从src/main/java编译源代码,因此仅在此文件夹中创建应用程序代码。

    作为最初的软件包,添加应用程序运行类并添加@SpringBootApplication注解。 此类将用于运行应用程序。 它的main()方法充当应用程序的入口点。

    MyApplication.java

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class MyApplication 
    {
    	public static void main(String[] args) {
    		SpringApplication.run(MyApplication.class, args);
    	}
    }
    
    
  • 上面main()方法中提供的SpringApplication类运行我们的应用程序,启动 Spring,该 Spring 反过来又启动了自动配置的 Tomcat Web 服务器。 MyApplication(方法参数)指示主要的 Spring 组件。

  • 由于我们使用了spring-boot-starter-parent父 POM,因此我们有一个有用的“运行”目标,可用于启动应用程序。 在根项目目录中键入'mvn spring-boot:run'以启动应用程序。

    它将通过单击 URL:localhost:8080启动我们可以在控制台日志以及浏览器中验证的应用程序。

9. 如何将 Web 应用程序打包为可执行 jar 或 war 文件?

可执行 jar(有时称为“胖 jar”)是包含已编译类以及应用程序需要运行的所有 jar 依赖项的归档文件。

为了创建一个可执行 jar ,我们应该在pom.xml中添加spring-boot-maven-plugin。 默认情况下,此插件仅将应用程序打包为.jar文件。

pom.xml

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

创建 war 文件,第一步是在pom.xml文件中声明打包类型war

其次,将嵌入式服务器依赖项的范围设置为“已提供”,因为服务器依赖项将由将要部署 war 文件的应用程序服务器提供。

pom.xml

<packaging>war</packaging>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>

10. 如何启用调试日志记录?

要启用调试日志记录,

  1. 我们可以使用--debug开关启动应用程序。
  2. 我们可以在application.properties文件中设置logging.level.root=debug属性。
  3. 我们可以在提供的日志配置文件中设置根记录器的日志记录级别。

11. 什么是 Spring 执行器? 它有什么优势?

借助 Spring Boot 的执行器模块,我们可以监视和管理生产环境中的应用程序使用情况,而无需对其进行编码和配置。 这些监视和管理信息通过 REST(如端点 URL)公开。

启用功能的最简单方法是向spring-boot-starter-actuator启动程序 pom 文件添加依赖项。

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Spring Boot 包含许多内置端点,我们可以添加自己的端点。 此外,每个单独的端点也可以启用或禁用。

一些重要且广泛使用的执行器端点如下:

端点 用法
/env 返回当前环境中的属性列表
/health 返响应用程序运行状况信息。
/auditevents 返回所有自动配置的候选对象以及应用它们“被”或“未被”的原因。
/beans 返响应用程序中所有 Spring Bean 的完整列表。
/trace 返回跟踪日志(默认情况下,最近的 100 个 HTTP 请求)。
/dump 它执行线程转储。
/metrics 它显示了一些有用的指标信息,例如 JVM 内存使用情况,系统 CPU 使用情况,打开的文件等等。

12. 什么是 Spring Boot 中的宽松绑定?

Spring Boot 使用一些宽松的规则来解析配置属性名称,以便可以用多种方式编写一个简单的属性名称。

例如,可以通过以下方式编写简单的属性log.level.my-package,并且所有内容都是正确的,并将由框架根据属性来源对其值进行解析。

log.level.my-package = debug    	//Kebab case
log.level.my_package = debug 		//Underscore notation
log.level.myPackage = debug 		//Camel case
LOG.LEVEL.MY-PACKAGE = debug 		//Upper case format

以下是每个属性源的宽松绑定规则列表。

属性源 允许的类型
属性文件 Camel 大小写,Kebab 大小写或下划线
YAML 文件 Camel 大小写,Kebab 大小写或下划线
环境变量 以下划线作为定界符的大写格式。 _不应在属性名称中使用
系统属性 Camel 大小写,Kebab 大小写或下划线

13. 如何在 Spring Boot 应用程序中进行单元测试和集成测试?

通常,任何软件应用程序都分为不同的模块和组件。 单独测试一个这样的组件时,称为单元测试

单元测试不能验证应用程序代码是否正确地与外部依赖项一起工作。 它专注于单个组件并模拟与该组件交互的所有依赖项。

要执行单元测试,我们可以借助特殊的注解,例如:

  • @JdbcTest – 当测试仅关注基于 jdbc 的组件时,可用于典型的 jdbc 测试。
  • @JsonTest – 当测试仅关注 JSON 序列化时使用。
  • @RestClientTest – 用于测试 REST 客户端。
  • @WebMvcTest – 用于 Spring MVC 测试,其配置仅与 MVC 测试相关。

集成测试可以根据要测试的内容,将整个应用程序置于范围内或仅包含某些组件。 他们可能需要为数据库实例和硬件分配资源。 尽管也可以模拟这些交互以提高测试性能。

在集成测试中,我们将专注于测试从控制器到持久层的完整请求处理。

@SpringBootTest注解有助于编写集成测试。 它启动嵌入式服务器并完全初始化应用程序上下文。 我们可以使用@Autowired注解将依赖项注入测试类中。

我们还可以使用嵌套@Configuration类或显式@TestConfiguration类提供特定于测试的 bean 配置。

它还注册了TestRestTemplate和/或WebTestClient bean,用于 Web 测试。

EmployeeControllerIntegrationTests.java

@SpringBootTest(classes = SpringBootDemoApplication.class, 
        webEnvironment = WebEnvironment.RANDOM_PORT)
public class EmployeeControllerIntegrationTests 
{
    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    //tests
}

14. 如何在浏览器中启用热部署和实时重载?

大多数现代 IDE 支持字节码的热交换,并且大多数代码更改应干净地重新加载而没有副作用。 此外,spring-boot-devtools模块还支持在类路径上的文件更改时自动重新启动应用程序。

默认情况下,将监视类路径上指向文件夹的任何条目的更改。 请注意,某些资源(例如静态资产和视图模板)不需要重新启动应用程序。

spring-boot-devtools模块包括一个嵌入式 LiveReload 服务器,该服务器可用于在更改资源时触发浏览器刷新。 可从 livereload.com 免费获得适用于 Chrome,Firefox 和 Safari 的 LiveReload 浏览器扩展。

要启用/禁用 LiveReload 服务器,请将spring.devtools.livereload.enabled属性的值更改为true(默认值)或false

15. 如何在 Spring Boot 中启用 HTTPS / SSL 支持?

可以通过application.properties并添加以下条目来添加 Spring Boot 项目中的 SSL 支持。

application.properties

server.port=8443
server.ssl.key-alias=selfsigned_localhost_sslserver
server.ssl.key-password=changeit
server.ssl.key-store=classpath:ssl-server.jks
server.ssl.key-store-provider=SUN
server.ssl.key-store-type=JKS

请与我们分享您过去遇到的 SpringBoot 面试问题。

学习愉快!

SpringBoot

SpringBoot – CommandLineRunner接口示例

原文: https://howtodoinjava.com/spring-boot/command-line-runner-interface-example/

在应用程序初始化后,Spring Boot 的CommandLineRunner接口仅在应用程序生存期内运行一次代码块。

如何使用CommandLineRunner

您可以通过三种方式使用CommandLineRunner界面:

1)使用CommandLineRunner作为@Component

这很容易。

@Component
public class ApplicationStartupRunner implements CommandLineRunner {
	protected final Log logger = LogFactory.getLog(getClass());

	@Override
	public void run(String... args) throws Exception {
		logger.info("ApplicationStartupRunner run method Started !!");
	}
}

2)在@SpringBootApplication中实现CommandLineRunner

这也是可能的。 示例代码如下:

@SpringBootApplication
public class SpringBootWebApplication extends SpringBootServletInitializer implements CommandLineRunner {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		return application.sources(SpringBootWebApplication.class);
	}

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

	@Override
	public void run(String... args) throws Exception {
		logger.info("Application Started !!");
	}
}

3)使用CommandLineRunner作为 Bean

您可以在SpringBootApplication中定义一个 bean,该 bean 返回实现CommandLineRunner接口的类。

ApplicationStartupRunner.java

public class ApplicationStartupRunner implements CommandLineRunner {
	protected final Log logger = LogFactory.getLog(getClass());
	@Override
	public void run(String... args) throws Exception {
		logger.info("Application Started !!");
	}
}

注册 ApplicationStartupRunner bean

@SpringBootApplication
public class SpringBootWebApplication extends SpringBootServletInitializer {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		return application.sources(SpringBootWebApplication.class);
	}

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

	@Bean
	public ApplicationStartupRunner schedulerRunner() {
		return new ApplicationStartupRunner();
	}
}

重要的是要注意,如果在run(String... args)方法内引发任何异常,这将导致上下文关闭并关闭应用程序。 因此,总是将有风险的代码放在try-catch块中。

如果有多个CommandLineRunner接口实现,请使用@Order

您可能具有CommandLineRunner接口的多种实现。 默认情况下,spring boot 扫描其所有run()方法并执行。 但是,如果要在其中强加一些顺序,请使用@Order注解。

@Order(value=3)
@Component
class ApplicationStartupRunnerOne implements CommandLineRunner {
	protected final Log logger = LogFactory.getLog(getClass());

	@Override
	public void run(String... args) throws Exception {
		logger.info("ApplicationStartupRunnerOne run method Started !!");
	}
}

@Order(value=2)
@Component
class ApplicationStartupRunnerTwo implements CommandLineRunner {
	protected final Log logger = LogFactory.getLog(getClass());

	@Override
	public void run(String... args) throws Exception {
		logger.info("ApplicationStartupRunnerTwo run method Started !!");
	}
}

验证日志。

2017-03-08 13:55:04 - ApplicationStartupRunnerTwo run method Started !!
2017-03-08 13:55:04 - ApplicationStartupRunnerOne run method Started !!

为什么要使用CommandLineRunner接口

  • 命令行运行程序是一种有用的功能,可以在应用程序启动后立即执行仅需运行一次的各种类型的代码。
  • 仅供参考,Spring Batch 依靠这些运行程序来触发作业的执行。
  • 我们可以利用依赖注入来发挥优势,以便在run()方法实现中连接所需的任何依赖以及所需的任何方式。

学习愉快!

Spring Boot – 配置 Jetty 服务器

原文: https://howtodoinjava.com/spring-boot/configure-jetty-server/

默认情况下,Spring Boot 使用嵌入式 tomcat 服务器来运行应用程序。 有时,您可能需要使用 Jetty 服务器来代替 tomcat 服务器。 Spring Boot 将 Tomcat 和 Jetty 依赖项捆绑在一起,作为单独的启动器,以帮助简化此过程。 您可以通过以下简单步骤使用 Jetty。

添加spring-boot-starter-jetty依赖

您将需要更新pom.xml并添加对spring-boot-starter-jetty的依赖。 另外,您将需要排除默认添加的spring-boot-starter-tomcat依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

在 gradle 中,可以通过以下方式实现有效的更改:

configurations {
    compile.exclude module: "spring-boot-starter-tomcat"
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web:2.0.0.BUILD-SNAPSHOT")
    compile("org.springframework.boot:spring-boot-starter-jetty:2.0.0.BUILD-SNAPSHOT")
}

配置 Jetty 选项

要覆盖,默认 Jetty 运行时配置 – 您可以在application.properties文件中对其进行配置。

application.properties

server.port=8080
server.servlet.context-path=/home

####Jetty specific properties########

server.jetty.acceptors= # Number of acceptor threads to use.
server.jetty.max-http-post-size=0 # Maximum size in bytes of the HTTP post or put content.
server.jetty.selectors= # Number of selector threads to use.

另外,您可以使用JettyEmbeddedServletContainerFactory bean 以编程方式配置这些选项。

@Bean
public JettyEmbeddedServletContainerFactory  jettyEmbeddedServletContainerFactory() {
    JettyEmbeddedServletContainerFactory jettyContainer = 
    	new JettyEmbeddedServletContainerFactory();

    jettyContainer.setPort(9000);
    jettyContainer.setContextPath("/home");
    return jettyContainer;
}

Spring Boot 2.0.0.RELEASE 的更新

上面的代码段对 spring boot spanshot 版本有效。 在 Spring boot 2.0.0.RELEASE可用之后,您将使用ConfigurableServletWebServerFactoryJettyServletWebServerFactory类。

Create bean of type ConfigurableServletWebServerFactory

@Bean
public ConfigurableServletWebServerFactory webServerFactory() 
{
	JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
	factory.setPort(9000);
	factory.setContextPath("/myapp");
	factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/notfound.html"));
	return factory;
}

将您的问题留给我,有关在任何 spring boot 应用程序中使用 jetty 代替 tomcat 服务器的问题。

学习愉快!

参考:配置 Jetty

Spring Boot 更改嵌入式服务器的默认端口

原文: https://howtodoinjava.com/spring-boot/change-server-default-port/

默认情况下,Spring Boot 应用程序从嵌入式 tomcat 服务器启动,并从默认端口 8080开始。 我们可以使用以下任何一种方法,将的默认嵌入式服务器端口更改为任何其他端口。

提示 – 要扫描可用端口(使用操作系统本机来防止冲突),请使用server.port=0。 现在,spring boot 将为我们找到任何未分配的 http 端口。

1. 从属性文件更改默认服务器端口

我们只需在任何 Spring Boot 应用程序的应用程序属性文件中输入很少的内容,就可以做很多有趣的事情。 更改服务器端口是其中之一。

1.1. application.properties

application.properties

server.port=9000

1.1. application.yml

application.yml

server:
  port : 9000

2. 以编程方式更改服务器端口

EmbeddedServletContainerCustomizer接口用于自定义嵌入式 tomcat 配置。 在启动容器本身之前,任何这种类型的 bean 都会在容器工厂中获得回调,因此我们可以设置portaddresserror pages等。

2.1. Spring Boot2 – WebServerFactoryCustomizer接口

通过实现ConfigurableWebServerFactory接口,在 spring boot2 应用程序中更改默认服务器端口。

AppContainerCustomizer.java

@Component
public class AppContainerCustomizer 
			implements WebServerFactoryCustomizer< ConfigurableWebServerFactory > {

    @Override
    public void customize(ConfigurableWebServerFactory factory) {
        factory.setPort(9000);
    }
}

2.2. Spring Boot 1.x – EmbeddedServletContainerCustomizer接口

通过实现EmbeddedServletContainerCustomizer接口,在 Spring Boot 1.x 应用程序中更改默认服务器端口。

AppContainerCustomizer.java

@Component
public class AppContainerCustomizer implements EmbeddedServletContainerCustomizer {

	@Override
	public void customize(ConfigurableEmbeddedServletContainer container) {

		container.setPort(9000);
	}
}

3. Spring Boot 从命令行更改默认端口

如果应用程序是作为超级 jar 构建的,我们也可以考虑使用此选项。 在这项技术中,我们将在应用程序运行命令期间传递server.port参数。

$ java -jar -Dserver.port=9000 spring-boot-demo.jar

让我知道是否您可以通过其他任何方式来完成更改 Spring Boot 嵌入式服务器默认端口

学习愉快!

Spring Boot – 更改上下文路径

原文: https://howtodoinjava.com/spring-boot/change-application-root-context-path/

默认情况下,Spring 运行应用程序是通过上下文路径/访问的,这是嵌入式服务器的默认路径,即我们可以通过http://localhost:PORT/直接访问该应用程序。

但是在生产中,我们将在某个上下文根目录下部署该应用程序 - 以便我们可以引用其他地方的 URL。 另外,还需要配置安全性,然后我们将需要应用程序的上下文根。

1. 在application.properties中更改上下文根

我们可以使用属性文件中的简单条目来更改上下文根路径。

application.properties

### Spring boot 1.x #########
server.contextPath=/ClientApp

### Spring boot 2.x #########
server.servlet.context-path=/ClientApp

2. Java 配置

Spring boot 2.x 中,我们可以自定义 bean WebServerFactoryCustomizer。 我们可以使用它来更改应用程序上下文路径,端口,地址,错误页面等。

WebMvcConfig.java

@Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>
   webServerFactoryCustomizer() {
       return factory -> factory.setContextPath("/ClientApp");
}

Spring boot 1.x 中,EmbeddedServletContainerCustomizer接口用于自定义自动配置的嵌入式 Servlet 容器。

AppContainerCustomizer.java

@Component
public class AppContainerCustomizer implements EmbeddedServletContainerCustomizer {

	@Override
	public void customize(ConfigurableEmbeddedServletContainer container) {

		container.setContextPath("/ClientApp");

	}
}

3. 应用参数

如果应用程序是作为超级 jar 构建的,我们也可以考虑使用此选项。

java -jar -Dserver.servlet.context-path=/ClientApp spring-boot-demo.jar

让我知道您是否知道在 spring boot 修改上下文路径中完成此更改的任何其他方法。

学习愉快!

Spring Boot SSL(HTTPS)示例

原文: https://howtodoinjava.com/spring-boot/spring-boot-ssl-https-example/

在此 SpringBoot 示例中,学习配置 Web 应用程序以使用自签名证书在 SSL(HTTPS)上运行。 也要学习创建 SSL 证书

不耐烦的 SSL 配置

Spring Boot HTTPS 配置

server.port=8443
server.ssl.key-alias=selfsigned_localhost_sslserver
server.ssl.key-password=changeit
server.ssl.key-store=classpath:ssl-server.jks
server.ssl.key-store-provider=SUN
server.ssl.key-store-type=JKS

从 HTTP 重定向到 HTTPS

private Connector redirectConnector() {
  Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
  connector.setScheme("http");
  connector.setPort(8080);
  connector.setSecure(false);
  connector.setRedirectPort(8443);
  return connector;
}

有关如何设置整体的详细教程,请继续阅读。

术语

在继续之前,让我们了解 SSL 或 TLS 等特定术语的含义。

SSL – 表示安全套接字层。 它是行业标准协议,用于通过保护在两个系统之间发送的所有敏感数据来保护 Internet 连接的安全,从而防止黑客读取和修改任何传输的信息。

TLS – (传输层安全性)是 SSL 的更新,更安全的版本。 它增加了更多功能。 如今,证书颁发机构提供的证书仅基于 TLS。 但是,关于网络上的安全通信,SSL 一词仍然很普遍,因为它很老,并且在社区中很流行。

HTTPS – 通过 SSL 证书保护网站时,URL 中显示(安全的超文本传输​​协议)。 它是 HTTP 协议的安全版本。

信任库和密钥库 – 用于在 Java 中存储 SSL 证书的证书,但它们之间没有什么区别。 truststore用于存储公共证书,而keystore用于存储客户端或服务器的私有证书。

创建自己的自签名 SSL 证书

要为我们的应用程序获取 SSL 数字证书,我们有两种选择:

  1. 创建一个自签名证书
  2. 为了从证书颁发机构(CA)获得 SSL 证书,我们将其称为 CA 证书。

为了今天的演示目的,我们将创建由 Java keytool命令生成的自签名证书。 我们需要在命令提示符下运行keytool -genkey命令。

这是我们将使用的确切命令:

keytool -genkey -alias selfsigned_localhost_sslserver -keyalg RSA -keysize 2048 -validity 700 -keypass changeit -storepass changeit -keystore ssl-server.jks

让我们了解以上命令:

  • -genkey – 是用于生成证书的keytool命令,实际上keytool是一种多功能且功能强大的工具,具有多个选项
  • -alias selfsigned_localhost_sslserver – 表示证书的别名,由 SSL / TLS 层使用
  • -keyalg RSA -keysize 2048 -validity 700 – 是自描述参数,指示加密算法,密钥大小和证书有效性。
  • -keypass changeit -storepass changeit – 是我们的信任库和密钥库的密码
  • -keystore ssl-server.jks – 是将存储证书和公用/专用密钥的实际密钥库。 在这里,我们从 Java 密钥库使用 JKS,密钥库还有其他格式。

一旦执行了以上命令,它将要求某些信息,最后看起来像这样。

生成证书的keytool

至此,我们就需要生成证书。 这将在执行keytool命令的目录中生成ssl-server.jks密钥库文件,其中包含我们的自签名证书。

要查看此密钥库中的内容,我们可以再次使用keytool -list命令。

keytool -list -keystore ssl-server.jks

输出将类似于:

keytool -list选项

创建 Spring Boot 项目并配置 SSL

生成 Spring Boot 项目

从 SPRING 初始化器站点创建一个具有依赖项WebRest Repositories的 spring boot 项目。 选择依赖项并给出适当的 Maven GAV 坐标后,我们将获得压缩格式的下载选项。 下载框架项目,解压缩,然后将其作为 Maven 项目导入 eclipse 中。

Spring boot 项目生成

添加 REST 端点

为了进行测试,我们将使用一个简单的 REST 端点。 为此,请打开已生成的带有@SpringBootApplication注解的 spring boot 应用程序类,并添加此代码。 这将在服务器中公开一个带有相对 URL /secured的其余端点。

@RestController
class SecuredServerController{

	@RequestMapping("/secured")
	public String secured(){
		System.out.println("Inside secured()");
		return "Hello user !!! : " + new Date();
	}
}

这就是我们需要在应用程序中添加 Web 内容的全部。 您可以添加更多内容,例如添加页面,图像以创建功能完善的 Web 应用程序。

Spring Boot SSL 配置

首先,我们需要将生成的密钥库文件(ssl-server.jks)复制到resources文件夹中,然后打开application.properties并添加以下条目。

server.port=8443
server.ssl.key-alias=selfsigned_localhost_sslserver
server.ssl.key-password=changeit
server.ssl.key-store=classpath:ssl-server.jks
server.ssl.key-store-provider=SUN
server.ssl.key-store-type=JKS

这就是我们启用 https 所需要的。 很简单,对吧? 借助 SpringBoot,使一切变得非常容易。

示例

现在是时候通过命令mvn clean install进行最终的 Maven 构建,并通过java -jar target\ssl-server-0.0.1-SNAPSHOT.jar命令启动应用程序。 这将在localhost 8443端口启动我们的安全应用程序,我们的端点 URL 将为 https://localhost:8443/secured。

由于我们的 REST 端点是通过 GET 公开的,因此我们只能通过浏览器进行测试。 转到 https://localhost:8443/secured,您将收到一些浏览器警告,例如未从受信任的证书颁发机构颁发证书,在浏览器中添加例外,您将收到刚刚由你创建的 HTTPS 服务器的响应。

浏览器输出

将 HTTP 请求重定向到 HTTPS

如果您要将 HTTP 流量重定向到 HTTPS,从而使整个站点变得安全,这是一个可选步骤。 为此,在 SpringBoot 时,我们需要在8080端口添加 HTTP 连接器,然后需要设置重定向端口8443。 这样8080中通过 http 发出的任何请求都将自动重定向到8443和 https。

为此,您只需要添加以下配置。

@Bean
public EmbeddedServletContainerFactory servletContainer() {
  TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory() {
      @Override
      protected void postProcessContext(Context context) {
        SecurityConstraint securityConstraint = new SecurityConstraint();
        securityConstraint.setUserConstraint("CONFIDENTIAL");
        SecurityCollection collection = new SecurityCollection();
        collection.addPattern("/*");
        securityConstraint.addCollection(collection);
        context.addConstraint(securityConstraint);
      }
    };

  tomcat.addAdditionalTomcatConnectors(redirectConnector());
  return tomcat;
}

private Connector redirectConnector() {
  Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
  connector.setScheme("http");
  connector.setPort(8080);
  connector.setSecure(false);
  connector.setRedirectPort(8443);

  return connector;
}

通过命令mvn clean install进行最终的 Maven 构建,然后启动应用程序。 测试 http://localhost:8080/secured。 它将自动重定向到 HTTPS 安全 URL。

总结

因此,今天我们了解了如何在 Spring Bean 应用程序中启用 HTTPS,并且还了解了如何将 HTTP 流量重定向至 HTTPS 。 我们还学会了创建自签名 SSL 证书

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

下载源码

学习愉快!

Spring Boot – 获取所有已加载的带有类类型信息的 bean

原文: https://howtodoinjava.com/spring-boot/get-loaded-beans-class-type-in​​formation/

Spring Boot 在内部加载了大量的 bean,以最小的配置运行您的应用程序。 在此示例中,我们将学习找出所有这些 SpringBoot 加载的 Bean 及其类类型信息。

使用ApplicationContext获取所有已加载的 bean

若要自动执行方法,请在应用程序完全加载后使用CommandLineRunner接口。CommandLineRunner用于指示当 bean 包含在 Spring 应用程序中时,它应该运行

1)使用ApplicationContext.getBeanDefinitionNames()查找所有已加载 bean 的名称

2)使用ApplicationContext.getBean(beanName)获取包含其运行时类型信息的 bean。

package com.howtodoinjava.app.controller;

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringBootWebApplication extends SpringBootServletInitializer implements CommandLineRunner {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		return application.sources(SpringBootWebApplication.class);
	}

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

	@Autowired
    private ApplicationContext appContext;

	@Override
    public void run(String... args) throws Exception 
	{
        String[] beans = appContext.getBeanDefinitionNames();
        Arrays.sort(beans);
        for (String bean : beans) 
        {
            System.out.println(bean + " of Type :: " + appContext.getBean(bean).getClass());
        }
    }
}

运行上面的应用程序将在控制台中打印 bean 名称和类型信息,如下所示:

2017-03-06 13:22:50 - Tomcat started on port(s): 8080 (http)

basicErrorController of Type :: class org.springframework.boot.autoconfigure.web.BasicErrorController
beanNameHandlerMapping of Type :: class org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
beanNameViewResolver of Type :: class org.springframework.web.servlet.view.BeanNameViewResolver
characterEncodingFilter of Type :: class org.springframework.boot.web.filter.OrderedCharacterEncodingFilter
conventionErrorViewResolver of Type :: class org.springframework.boot.autoconfigure.web.DefaultErrorViewResolver
defaultServletHandlerMapping of Type :: class org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport$EmptyHandlerMapping
defaultViewResolver of Type :: class org.springframework.web.servlet.view.InternalResourceViewResolver
dispatcherServlet of Type :: class org.springframework.web.servlet.DispatcherServlet
dispatcherServletRegistration of Type :: class org.springframework.boot.web.servlet.ServletRegistrationBean
duplicateServerPropertiesDetector of Type :: class org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration$DuplicateServerPropertiesDetector
embeddedServletContainerCustomizerBeanPostProcessor of Type :: class org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor
error of Type :: class org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelView
errorAttributes of Type :: class org.springframework.boot.autoconfigure.web.DefaultErrorAttributes
...
...
...

我已经截断了输出。 您可以自己验证整个列表。

学习愉快!

Spring – @Lazy加载

https://howtodoinjava.com/spring5/core/spring-bean-eager-vs-lazy-init/

默认情况下,Spring“应用程序上下文”会在应用程序启动期间主动创建并初始化所有范围为的 Bean。 在大多数情况下,它有助于早期发现 Bean 配置问题。 但是有时,由于项目要求不同,您可能需要标记部分或全部 bean 进行延迟初始化。

Spring 提供了两种简单的方法来配置 bean 的延迟初始化,具体取决于您使用的是哪种配置,即基于 XML 的配置或基于 Java 的配置。

1. @Lazy初始化的 bean – Java 配置

1.1. 延迟加载特定的 bean

要仅延迟加载特定的 bean,请在 Java 配置中使用@Lazy注解和@Bean注解。

AppConfig.java

import org.springframework.context.annotation.Lazy;

@Configuration
public class AppConfig {

    @Lazy
    @Bean
    public EmployeeManager employeeManager() {
        return new EmployeeManagerImpl();
    }

}

1.2. 延迟加载所有 bean

要延迟加载所有 bean,请在 Java 配置中使用@Lazy注解以及@Bean注解。

AppConfig.java

import org.springframework.context.annotation.Lazy;

@Lazy
@Configuration
public class AppConfig {

    @Bean
    public EmployeeManager employeeManager() {
        return new EmployeeManagerImpl();
    }

}

1.3. @Autowired延迟 Bean

通常,使用@Autowired注解将 bean 注入其他组件。 在这种情况下,我们必须在两个地方都使用惰性标注:

  • 您要延迟加载的 bean 定义
  • 它与@Autowired注解一起注入的位置
@Lazy
@Service
public class EmployeeManagerImpl implements EmployeeManager {
  //
}

@Controller
public class EmployeeController {

	@Lazy
	@Autowired
	EmployeeManager employeeManager;
}

在两个地方都没有使用@Lazy注解,它将无法正常工作。

2. XML 配置中的延迟初始化的 bean

2.1. 仅延迟加载特定的 bean

要为特定 bean 启用延迟加载,请在 bean 配置 xml 文件中的 bean 定义上使用lazy-init="true"属性。

beans.xml

<beans>

<bean id="employeeManager" class="com.howtodoinjava.spring.service.impl.EmployeeManagerImpl" 
	lazy-init="true"/>

<beans>

2.2. 延迟全局加载所有 bean

要为所有 bean 启用延迟加载,请在 bean 配置 xml 文件的beans标签上使用default-lazy-init="true"属性。

beans.xml

<beans default-lazy-init="true">

<bean id="employeeManager" class="com.howtodoinjava.spring.service.impl.EmployeeManagerImpl" />

<beans>

3. Spring 延迟加载演示

让我们看一下 bean 的代码,我们正在尝试延迟加载。

@Lazy
@Service
public class EmployeeManagerImpl implements EmployeeManager {

	@Override
	public Employee create() {
		Employee emp =  new Employee();
		emp.setId(1);
		emp.setName("Lokesh");
		return emp;
	}

	@PostConstruct
	public void onInit(){
		System.out.println("EmployeeManagerImpl Bean is Created !!");
	}
}

我已放置@PostConstruct注解以检测何时创建 bean。

让我们使用 – 初始化应用程序上下文

3.1. 没有延迟加载

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.howtodoinjava.spring.model.Employee;
import com.howtodoinjava.spring.service.EmployeeManager;

public class Main 
{
    public static void main( String[] args )
    {
    	ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

    	System.out.println("Bean Factory Initialized !!");

    	EmployeeManager empManager = ctx.getBean(EmployeeManager.class);
    	Employee emp = empManager.create();

    	System.out.println(emp);
    }
}

程序输出。

Console

EmployeeManagerImpl Bean is Created !!
Bean Factory Initialized !!
Employee [id=1, name=Lokesh]

在这里,在 bean 工厂完全初始化之前,已创建并初始化了第一个 bean。

3.2. 延迟加载

Console

Bean Factory Initialized !!
EmployeeManagerImpl Bean is Created !!
Employee [id=1, name=Lokesh]

启用 bean 延迟加载后,bean 工厂首先进行完全初始化。 稍后,当我们请求EmployeeManager bean 时,然后工厂创建实例并返回它。

请在注解部分中将与的延迟加载和紧急加载之间的差异相关的问题放在 Spring 中。

学习愉快!

下载源码

Spring Boot – 自定义PropertyEditor配置示例

原文: https://howtodoinjava.com/spring-boot/custom-property-editor-example/

PropertyEditor最初是 JavaBeans 规范的一部分。 Spring 还大量使用PropertyEditor来以与对象本身不同的方式表示属性,例如从 http 请求参数解析人类可读的输入,或在视图层中显示纯 Java 对象的人类可读的值。

Spring 在org.springframework.beans.propertyeditors包中有许多内置的PropertyEditor,例如用于BooleanCurrencyURL。 这些编辑器中的某些默认情况下已注册,而某些则在需要时需要注册。

您还可以创建自定义的PropertyEditor,以防万一 – 默认属性编辑器无用。 假设我们正在创建一个用于图书管理的应用程序。 现在,人们也可以通过 ISBN 搜索图书。 另外,您将需要在网页中显示 ISBN 详细信息。

创建自定义PropertyEditor

要创建自定义属性编辑器,您将需要扩展java.beans.PropertyEditorSupport类。

IsbnEditor.java

package com.howtodoinjava.app.editors;

import java.beans.PropertyEditorSupport;
import org.springframework.util.StringUtils;
import com.howtodoinjava.app.model.Isbn;

public class IsbnEditor extends PropertyEditorSupport {
	@Override
	public void setAsText(String text) throws IllegalArgumentException {
		if (StringUtils.hasText(text)) {
			setValue(new Isbn(text.trim()));
		} else {
			setValue(null);
		}
	}

	@Override
	public String getAsText() {
		Isbn isbn = (Isbn) getValue();
		if (isbn != null) {
			return isbn.getIsbn();
		} else {
			return "";
		}
	}
}

Isbn 类别如下:

Isbn.java

package com.howtodoinjava.app.model;

public class Isbn {
	private String isbn;

	public Isbn(String isbn) {
		this.isbn = isbn;
	}

	public String getIsbn() {
		return isbn;
	}

	public String getDisplayValue() {
		return isbn;
	}
}

注册自定义PropertyEditor

下一步是在 spring 应用程序中注册自定义属性编辑器。 要注册,您将需要创建一个带有注解的方法 – @InitBinder。 在应用程序启动时,将扫描此注解,并且所有检测到的方法均应具有接受WebDataBinder作为参数的签名。

永远记住,PropertyEditor不是线程安全的。 您应该始终为每个Web请求创建一个新的自定义编辑器实例,并将其注册到WebDataBinder

HomeController.java

@Controller
public class HomeController {

	//...

	@InitBinder
	public void initBinder(WebDataBinder binder) {
	  binder.registerCustomEditor(Isbn.class, new IsbnEditor());
	}
}

使用自定义属性编辑器接受输入并显示值

现在,创建并注册自定义属性编辑器后,就可以使用它。 您可以在控制器中使用它来接受输入,如下所示:

HomeController.java

@Controller
public class HomeController {

	private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());

	@RequestMapping(value = "/books/{isbn}", method = RequestMethod.GET)
	public String getBook(@PathVariable Isbn isbn, Map<String, Object> model) 
	{
		LOGGER.info("You searched for book with ISBN :: " + isbn.getIsbn());
		model.put("isbn", isbn);
		return "index";
	}

	@InitBinder
	public void initBinder(WebDataBinder binder) {
	  binder.registerCustomEditor(Isbn.class, new IsbnEditor());
	}
}

只是看看我们如何不直接在@PathVariable Isbn isbn变量中接受 ISBN 值。 我们的IsbnEditor非常简单,但是您可以在那里拥有完整的规则和验证,并且可以使用。

要显示提供的值,不需要特殊的方法。 只是普通的旧 Spring 方式。

index.jsp

<!DOCTYPE html>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html lang="en">
<body>
	<h2>ISBN You searched is :: ${ isbn.displayValue }</h2>
</body>
</html>

示例

现在,通过运行 spring boot 应用程序来测试应用程序。

SpringBootWebApplication.java

package com.howtodoinjava.app.controller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.support.SpringBootServletInitializer;

@SpringBootApplication
public class SpringBootWebApplication extends SpringBootServletInitializer {

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

现在,使用以下网址访问浏览器:http://localhost:8080/books/978-3-16-148410-0

验证服务器中的日志,并在浏览器中显示为 isbn 作为请求输入正确接收的路径参数。

2017-03-16 13:40:00 - You searched for book with ISBN :: 978-3-16-148410-0

Spring Custom Property Editor Example

Spring 自定义属性编辑器示例

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

学习愉快!

Spring Boot @Scheduled注解示例

原文: https://howtodoinjava.com/spring-boot/enable-scheduling-scheduled-job-example/

为了使 Spring Boot 应用程序中的作业定期运行,Spring Boot 提供了@EnableScheduling@Scheduled注解。 让我们学习如何使用 Spring boot @Scheduled 注解。

假设您要每隔 10 秒运行一次作业。 您可以通过以下步骤实现此作业调度:

1. 将@EnableScheduling添加到SpringBootApplication

在您的 Spring Boot 应用程序类中添加@EnableScheduling注解。 @EnableScheduling是一个 Spring 上下文模块注解。 它通过@Import(SchedulingConfiguration.class)指令在内部导入SchedulingConfiguration

@SpringBootApplication
@EnableScheduling
public class SpringBootWebApplication {

}

阅读更多:在 Spring 中安排任务的 4 种方法

2. 将 Spring boot @Scheduled注解添加到方法中

现在,您可以在要调度的方法上添加@Scheduled注解。 唯一的条件是方法应没有参数

导入的SchedulingConfiguration将创建的ScheduledAnnotationBeanPostProcessor将扫描所有已声明的 bean,以查看是否存在@Scheduled注解。

对于每个没有参数的带注解的方法,将创建适当的执行程序线程池。 该线程池将管理带注解的方法的调度调用。

2.1. 以固定速率安排任务

在固定的速率执行任务:

@Scheduled(initialDelay = 1000, fixedRate = 10000)
public void run() {
	logger.info("Current time is :: " + Calendar.getInstance().getTime());
}

现在,在控制台中观察输出:

2017-03-08 15:02:55 - Current time is :: Wed Mar 08 15:02:55 IST 2017
2017-03-08 15:03:05 - Current time is :: Wed Mar 08 15:03:05 IST 2017
2017-03-08 15:03:15 - Current time is :: Wed Mar 08 15:03:15 IST 2017
2017-03-08 15:03:25 - Current time is :: Wed Mar 08 15:03:25 IST 2017
2017-03-08 15:03:35 - Current time is :: Wed Mar 08 15:03:35 IST 2017

2.2. 固定延迟安排任务

将任务配置为在固定延迟后运行。 在给定的示例中,上一次执行的结束与下一次执行的开始之间的持续时间是固定的。 该任务始终等待直到上一个任务完成。

@Scheduled(fixedDelay = 10000)
public void run() {
	logger.info("Current time is :: " + Calendar.getInstance().getTime());
}

2.3. Spring Boot Cron 工作示例

@Scheduled注解非常灵活,也可以采用 cron 表达式

@Scheduled(cron = "0 10 10 10 * ?")
public void run() {
	logger.info("Current time is :: " + Calendar.getInstance().getTime());
}

在这个 Spring 任务调度程序注解示例中,向我提出您的问题。

学习愉快!

Spring Boot Jersey 示例

原文: https://howtodoinjava.com/spring-boot/spring-boot-jersey-example/

学习使用 Spring BootJersey 框架来配置和创建 JAX-RS 2.0 REST API。 此示例应用程序使用 Jersey 的ServletContainer部署 REST API。

项目结构

在本教程中创建的应用程序的项目结构如下:

Spring Boot Jersey Project Structure

Spring Boot Jersey 项目结构

从 Spring 初始化器创建 Spring Boot 应用程序

  1. 转到 Spring 初始化器页面并创建具有 Jersey(JAX-RS)依赖项的 spring boot 应用程序。

    Select Jersey in Spring Boot Initializr

    Spring Boot 初始化器中选择 Jersey

  2. 将项目生成为 zip 文件。 将其解压缩到计算机中的某个位置。 将项目作为“现有 Maven 应用程序”导入到 eclipse 中。

  3. 检查 Maven 文件中是否应具有依赖项。

    <dependencies>
    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-jersey</artifactId>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-test</artifactId>
    		<scope>test</scope>
    	</dependency>
    </dependencies>
    
    

创建 JAX-RS REST 资源

现在创建一些 JAX-RS 资源,我们将进入测试阶段。 我创建了UserResource类。

UserResource.java

package com.howtodoinjava.jerseydemo;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "users")
@Path("/users")
public class UserResource 
{
	private static Map<Integer, User> DB = new HashMap<>(); 

	@GET
	@Produces("application/json")
	public Users getAllUsers() {
		Users users = new Users();
		users.setUsers(new ArrayList<>(DB.values()));
		return users;
	}

	@POST
	@Consumes("application/json")
	public Response createUser(User user) throws URISyntaxException 
	{
		if(user.getFirstName() == null || user.getLastName() == null) {
			return Response.status(400).entity("Please provide all mandatory inputs").build();
		}
		user.setId(DB.values().size()+1);
		user.setUri("/user-management/"+user.getId());
		DB.put(user.getId(), user);
		return Response.status(201).contentLocation(new URI(user.getUri())).build();
	}

	@GET
	@Path("/{id}")
	@Produces("application/json")
	public Response getUserById(@PathParam("id") int id) throws URISyntaxException 
	{
		User user = DB.get(id);
		if(user == null) {
			return Response.status(404).build();
		}
		return Response
				.status(200)
				.entity(user)
				.contentLocation(new URI("/user-management/"+id)).build();
	}

	@PUT
	@Path("/{id}")
	@Consumes("application/json")
	@Produces("application/json")
	public Response updateUser(@PathParam("id") int id, User user) throws URISyntaxException 
	{
		User temp = DB.get(id);
		if(user == null) {
			return Response.status(404).build();
		}
		temp.setFirstName(user.getFirstName());
		temp.setLastName(user.getLastName());
		DB.put(temp.getId(), temp);
		return Response.status(200).entity(temp).build();
	}

	@DELETE
	@Path("/{id}")
	public Response deleteUser(@PathParam("id") int id)	throws URISyntaxException {
		User user = DB.get(id);
		if(user != null) {
			DB.remove(user.getId());
			return Response.status(200).build();
		}
		return Response.status(404).build();
	}

	static 
	{
		User user1 = new User();
		user1.setId(1);
		user1.setFirstName("John");
		user1.setLastName("Wick");
		user1.setUri("/user-management/1");

		User user2 = new User();
		user2.setId(2);
		user2.setFirstName("Harry");
		user2.setLastName("Potter");
		user2.setUri("/user-management/2");

		DB.put(user1.getId(), user1);
		DB.put(user2.getId(), user2);
	}
}

Users.java

package com.howtodoinjava.jerseydemo;

import java.util.ArrayList;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "users")
public class Users {

    @XmlElement(name="user")
    private ArrayList<User> users;

    public ArrayList<User> getUsers() {
        return users;
    }

    public void setUsers(ArrayList<User> users) {
        this.users = users;
    }
}

User.java

package com.howtodoinjava.jerseydemo;

import java.io.Serializable;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "user")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @XmlAttribute(name = "id")
    private int id;

    @XmlAttribute(name="uri")
    private String uri;

    @XmlElement(name = "firstName")
    private String firstName;

    @XmlElement(name = "lastName")
    private String lastName;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public String getUri() {
        return uri;
    }
    public void setUri(String uri) {
        this.uri = uri;
    }
}

Jersey 配置

  1. 现在我们有了一个 JAX-RS 资源,我们想从包含 Jersey 依赖项的 spring boot 应用程序中访问它。 让我们将此资源注册为 Jersey 资源。

    package com.howtodoinjava.jerseydemo;
    
    import org.glassfish.jersey.server.ResourceConfig;
    import org.springframework.stereotype.Component;
    
    @Component
    public class JerseyConfig extends ResourceConfig 
    {
    	public JerseyConfig() 
    	{
    		register(UserResource.class);
    	}
    }
    
    

    查看@Component注解。 它可以在 Spring Boot 自动扫描源文件夹中的 java 类时注册此类。

  2. ResourceConfig提供高级功能以简化 JAX-RS 组件的注册。

  3. SpringBootServletInitializer扩展 spring boot 应用程序。

    package com.howtodoinjava.jerseydemo;
    
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.boot.web.support.SpringBootServletInitializer;
    
    @SpringBootApplication
    public class JerseydemoApplication extends SpringBootServletInitializer 
    {
    	public static void main(String[] args) 
    	{
    		new JerseydemoApplication().configure(new SpringApplicationBuilder(JerseydemoApplication.class)).run(args);
    	}
    }
    
    

示例

将项目作为 Spring 启动应用程序运行。 现在测试其余资源。

访问/users资源

users resource

用户资源

访问/users/1资源

user resource

user resource

下载源代码

要下载此示例的源代码,请单击下面提供的下载链接。

下载源码

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

学习愉快!

Spring Boot SOAP Web 服务示例

原文: https://howtodoinjava.com/spring-boot/spring-boot-soap-webservice-example/

了解如何利用 Spring Boot 的简便性来快速创建 SOAP Web 服务。 REST 和微服务每天都在流行,但是 SOAP 在某些情况下仍然有自己的地位。 在本 SpringBoot SOAP 教程中,我们将仅关注与 SpringBoot 相关的配置,以了解我们可以多么轻松地创建我们的第一个 SOAP Webservice

我们将构建一个简单的合同优先的 SOAP Web 服务,在其中我们将使用硬编码后端实现学生搜索功能,以进行演示。

1. 技术栈

  • JDK 1.8,Eclipse,Maven – 开发环境
  • SpringBoot – 基础应用程序框架
  • wsdl4j – 为我们的服务发布 WSDL
  • SOAP-UI – 用于测试我们的服务
  • JAXB maven 插件 – 用于代码生成

2. 项目结构

为该演示创建的类和文件如下所示。

Spring Boot SOAP WS Project Structure

Spring Boot SOAP WS 项目结构

3. 创建 Spring Boot 项目

仅从具有Web Services依赖关系的 SPRING 初始化器站点创建一个 spring boot 项目。 选择依赖项并提供适当的 Maven GAV 坐标后,以压缩格式下载项目。 解压缩,然后将 eclipse 中的项目导入为 maven 项目。

Spring boot 项目生成

添加 Wsdl4j 依赖关系

编辑pom.xml并将此依赖项添加到您的项目中。

<dependency>
	<groupId>wsdl4j</groupId>
	<artifactId>wsdl4j</artifactId>
</dependency>

4. 创建 SOAP 域模型并生成 Java 代码

当我们遵循合同优先的方法来开发服务时,我们需要首先为我们的服务创建域(方法和参数)。 为简单起见,我们将请求和响应都保留在相同的 XSD 中,但在实际的企业用例中,我们将有多个 XSD 相互导入以形成最终定义。

student.xsd

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="https://www.howtodoinjava.com/xml/school" 
targetNamespace="https://www.howtodoinjava.com/xml/school" elementFormDefault="qualified">

    <xs:element name="StudentDetailsRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="name" type="xs:string"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="StudentDetailsResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="Student" type="tns:Student"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="Student">
        <xs:sequence>
            <xs:element name="name" type="xs:string"/>
            <xs:element name="standard" type="xs:int"/>
            <xs:element name="address" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>

</xs:schema>

将以上文件放置在项目的resources文件夹中。

将 XSD 的 JAXB maven 插件添加到 Java 对象生成

我们将使用jaxb2-maven-plugin有效地生成域类。 现在,我们需要将以下 Maven 插件添加到项目的pom.xml文件的插件部分。


<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>jaxb2-maven-plugin</artifactId>
	<version>1.6</version>
	<executions>
		<execution>
			<id>xjc</id>
			<goals>
				<goal>xjc</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<schemaDirectory>${project.basedir}/src/main/resources/</schemaDirectory>
		<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
		<clearOutputDir>false</clearOutputDir>
	</configuration>
</plugin>

该插件使用 XJC 工具作为代码生成引擎。 XJC 将 XML 模式文件编译为完全注解的 Java 类。

现在执行上面的 maven 插件以从 XSD 生成 Java 代码。

5. 创建 SOAP Web 服务端点

StudentEndpoint类将处理对服务的所有传入请求,并将调用委派给数据存储库的finder方法。

package com.example.howtodoinjava.springbootsoapservice;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
import com.howtodoinjava.xml.school.StudentDetailsRequest;
import com.howtodoinjava.xml.school.StudentDetailsResponse;

@Endpoint
public class StudentEndpoint 
{
	private static final String NAMESPACE_URI = "https://www.howtodoinjava.com/xml/school";

	private StudentRepository StudentRepository;

	@Autowired
	public StudentEndpoint(StudentRepository StudentRepository) {
		this.StudentRepository = StudentRepository;
	}

	@PayloadRoot(namespace = NAMESPACE_URI, localPart = "StudentDetailsRequest")
	@ResponsePayload
	public StudentDetailsResponse getStudent(@RequestPayload StudentDetailsRequest request) {
		StudentDetailsResponse response = new StudentDetailsResponse();
		response.setStudent(StudentRepository.findStudent(request.getName()));

		return response;
	}
}

这里有一些关于注解的细节:

  1. @Endpoint向 Spring WS 注册该类,作为处理传入 SOAP 消息的潜在候选者。
  2. 然后,Spring WS 使用@PayloadRoot根据消息的名称空间和 localPart 选择处理器方法。 请注意此注解中提到的命名空间 URL 和请求载荷根请求。
  3. @RequestPayload表示传入的消息将被映射到方法的请求参数。
  4. @ResponsePayload注解使 Spring WS 将返回的值映射到响应载荷。

创建数据存储库

如前所述,我们将使用硬编码的数据作为此演示的后端,让我们添加一个名为StudentRepository.java并带有 Spring @Repository注解的类。 它只会将数据保存在HashMap中,并且还会提供一种称为findStudent()的查找器方法。

阅读更多 – @Repository注解

package com.example.howtodoinjava.springbootsoapservice;

import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import com.howtodoinjava.xml.school.Student;

@Component
public class StudentRepository {
	private static final Map<String, Student> students = new HashMap<>();

	@PostConstruct
	public void initData() {

		Student student = new Student();
		student.setName("Sajal");
		student.setStandard(5);
		student.setAddress("Pune");
		students.put(student.getName(), student);

		student = new Student();
		student.setName("Kajal");
		student.setStandard(5);
		student.setAddress("Chicago");
		students.put(student.getName(), student);

		student = new Student();
		student.setName("Lokesh");
		student.setStandard(6);
		student.setAddress("Delhi");
		students.put(student.getName(), student);

		student = new Student();
		student.setName("Sukesh");
		student.setStandard(7);
		student.setAddress("Noida");
		students.put(student.getName(), student);
	}

	public Student findStudent(String name) {
		Assert.notNull(name, "The Student's name must not be null");
		return students.get(name);
	}
}

6. 添加 SOAP Web 服务配置 Bean

创建带有@Configuration注解的类以保存 bean 定义。

package com.example.howtodoinjava.springbootsoapservice;

import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;

@EnableWs
@Configuration
public class Config extends WsConfigurerAdapter 
{
	@Bean
	public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) 
	{
		MessageDispatcherServlet servlet = new MessageDispatcherServlet();
		servlet.setApplicationContext(applicationContext);
		servlet.setTransformWsdlLocations(true);
		return new ServletRegistrationBean(servlet, "/service/*");
	}

	@Bean(name = "studentDetailsWsdl")
	public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) 
	{
		DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
		wsdl11Definition.setPortTypeName("StudentDetailsPort");
		wsdl11Definition.setLocationUri("/service/student-details");
		wsdl11Definition.setTargetNamespace("https://www.howtodoinjava.com/xml/school");
		wsdl11Definition.setSchema(countriesSchema);
		return wsdl11Definition;
	}

	@Bean
	public XsdSchema countriesSchema() 
	{
		return new SimpleXsdSchema(new ClassPathResource("school.xsd"));
	}
}

  • Config类扩展了WsConfigurerAdapter,它配置了注解驱动的 Spring-WS 编程模型。

  • MessageDispatcherServlet – Spring-WS 使用它来处理 SOAP 请求。 我们需要向该 servlet 注入ApplicationContext,以便 Spring-WS 找到其他 bean。 它还声明了请求的 URL 映射。

  • DefaultWsdl11Definition使用XsdSchema公开了标准的 WSDL 1.1。 Bean 名称studentDetailsWsdl将是将公开的 wsdl 名称。 它可以在 http:// localhost:8080 / service / studentDetailsWsdl.wsdl 下找到。 这是在 Spring 公开合约优先的 wsdl 的最简单方法。

    此配置还在内部使用 WSDL 位置 servlet 转换servlet.setTransformWsdlLocations( true )。 如果我们看到导出的 WSDL,则soap:address将具有localhost地址。 同样,如果我们改为从分配给已部署机器的面向公众的 IP 地址访问 WSDL,我们将看到该地址而不是localhost。 因此,端点 URL 根据部署环境是动态的。

7. Spring Boot SOAP Web 服务演示

使用mvn clean install进行 maven 构建,然后使用java -jar target\spring-boot-soap-service-0.0.1-SNAPSHOT.jar命令启动应用程序。 这将在默认端口8080中启动一台 tomcat 服务器,并将在其中部署应用程序。

1)现在转到http://localhost:8080/service/studentDetailsWsdl.wsdl,查看 WSDL 是否正常运行。

WSDL generated

WSDL 已生成

2)一旦成功生成了 WSDL,就可以使用该 WSDL 在 SOAP ui 中创建一个项目并测试该应用程序。 样品请求和响应如下。

请求:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sch="https://www.howtodoinjava.com/xml/school">
   <soapenv:Header/>
   <soapenv:Body>
      <sch:StudentDetailsRequest>
         <sch:name>Sajal</sch:name>
      </sch:StudentDetailsRequest>
   </soapenv:Body>
</soapenv:Envelope>

响应:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
      <ns2:StudentDetailsResponse xmlns:ns2="https://www.howtodoinjava.com/xml/school">
         <ns2:Student>
            <ns2:name>Sajal</ns2:name>
            <ns2:standard>5</ns2:standard>
            <ns2:address>Pune</ns2:address>
         </ns2:Student>
      </ns2:StudentDetailsResponse>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

SOAP UI Example

SOAP UI 示例

8. 总结

在上面的示例中,我们学习了使用 Spring Boot 创建 SOAP Web 服务。 我们还学习了从 WSDL 生成 Java 代码。 我们了解了处理 SOAP 请求所需的 bean。

如果您在执行上述项目时遇到任何困难,请随时发表评论。

本文的源码

学习愉快!

阅读更多:

Spring Boot Soap Web 服务客户端示例

Spring Boot SOAP 客户端 – WebServiceTemplate示例

原文: https://howtodoinjava.com/spring-boot/spring-soap-client-webservicetemplate/

学习使用 Spring Boot Soap 客户端使用 SOAP Web 服务以及使用 JAXB maven 插件自动生成客户端代理类。 创建 SOAP Web 服务不在本教程的讨论范围内,但是您可以在中学习它。

阅读更多:在 Spring Framework 中创建 SOAP WS。

先决条件

在运行此示例之前,我们需要准备好一个 SOAP 服务,该服务将从该客户端代码中调用。 为此,您可以下载附件的 maven 项目(在文章结尾),然后在本地工作区中运行它并使用它。

运行此 SOAP 服务器项目后,您将从http://localhost:8080/service/studentDetailsWsdl.wsdl获取 WSDL。 将 WSDL 下载为studentDetailsWsdl.wsdl,稍后将其放置在客户端项目的resources/wsdl文件夹中,该文件夹将在下一步创建以生成客户端代理代码。

Spring Boot Soap 客户端的技术栈

  • JDK 1.8,Eclipse,Maven – 开发环境
  • SpringBoot – 基础应用程序框架
  • maven-jaxb2-plugin插件 – 用于生成 JAXB 存根
  • SpringBoot CommandLineRunner – 测试客户端代码

项目结构

为该演示创建的类和文件如下所示。

SOAP client project structure

SOAP 客户端项目结构

使用WebServiceTemplate创建 Spring 客户端

创建启动项目

仅从具有Web Services依赖关系的 SPRING 初始化器站点创建一个 spring boot 项目。 选择依赖项并提供适当的 Maven GAV 坐标后,以压缩格式下载项目。 解压缩,然后将 eclipse 中的项目导入为 maven 项目。

Generate Spring boot project

Spring boot 项目生成

生成 SOAP 域类

现在使用maven-jaxb2-plugin maven 插件生成 JAXB 注解的存根类。 为此,将此 maven 插件添加到项目的pom.xml中。

pom.xml

<plugin>
	<groupId>org.jvnet.jaxb2.maven2</groupId>
	<artifactId>maven-jaxb2-plugin</artifactId>
	<version>0.13.2</version>
	<executions>
		<execution>
			<goals>
				<goal>generate</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<generatePackage>com.example.howtodoinjava.schemas.school</generatePackage>
		<generateDirectory>${project.basedir}/src/main/java</generateDirectory>
		<schemaDirectory>${project.basedir}/src/main/resources/wsdl</schemaDirectory>
		<schemaIncludes>
			<include>*.wsdl</include>
		</schemaIncludes>
	</configuration>
</plugin>	

此插件将在项目的src目录的com.example.howtodoinjava.springbootsoapclient包中生成类,并且此插件将检查类的生成时间戳,以便仅在WSDL中发生任何更改时才生成这些类。

使用WebServiceTemplate创建 SOAP 客户端

创建一个名为SOAPConnector.java的类,该类将充当对 Web 服务的所有请求的通用 Web 服务客户端。

SOAPConnector.java

package com.example.howtodoinjava.springbootsoapclient;

import org.springframework.ws.client.core.support.WebServiceGatewaySupport;

public class SOAPConnector extends WebServiceGatewaySupport {

	public Object callWebService(String url, Object request){
		return getWebServiceTemplate().marshalSendAndReceive(url, request);
	}
}

  1. SOAPConnector类是对WebServiceGatewaySupport的扩展,它基本上是通过getWebServiceTemplate()方法提供的WebServiceTemplate内部实现注入一个接口。
  2. 我们将使用此WebServiceTemplate来调用 SOAP 服务。
  3. 该类还期望注入一个名为MarshallerUnmarshaller的 spring bean,它们将由配置类提供,我们将在下面看到。

Spring bean 配置

现在,我们需要创建一个用@Configuration注解的配置类,该类将具有SOAPConnector所需的必需的 bean 定义,以使其正常工作。

Config.java

package com.example.howtodoinjava.springbootsoapclient;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

@Configuration
public class Config {
	@Bean
	public Jaxb2Marshaller marshaller() {
		Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
		// this is the package name specified in the <generatePackage> specified in
		// pom.xml
		marshaller.setContextPath("com.example.howtodoinjava.schemas.school");
		return marshaller;
	}

	@Bean
	public SOAPConnector soapConnector(Jaxb2Marshaller marshaller) {
		SOAPConnector client = new SOAPConnector();
		client.setDefaultUri("http://localhost:8080/service/student-details");
		client.setMarshaller(marshaller);
		client.setUnmarshaller(marshaller);
		return client;
	}
}

  1. WebServiceGatewaySupport需要MarshallerUnmarshaller,它们是Jaxb2Marshaller类的实例。
  2. 它使用com.example.howtodoinjava.schemas.school作为 JAXB 类的基本包。 它将使用此包创建 JAXB 上下文。
  3. 我们将使用此Jaxb2Marshaller bean 作为SOAPConnector bean 的Marshaller/Unmarshaller

使用CommandLineRunner测试

为简单起见,我们将创建一个 Spring Boot 命令行运行程序,该加载程序将加载 spring 上下文并调用处理器方法,并将命令行参数传递给该方法。 实时地,我们需要用一些其他代码替换此命令行运行程序,这些代码将更适合企业。

我们需要在SpringBootApplication类中添加此命令行运行器 bean,如下。

SpringBootSoapClientApplication.java

package com.example.howtodoinjava.springbootsoapclient;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.example.howtodoinjava.schemas.school.StudentDetailsRequest;
import com.example.howtodoinjava.schemas.school.StudentDetailsResponse;

@SpringBootApplication
public class SpringBootSoapClientApplication {

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

	@Bean
	CommandLineRunner lookup(SOAPConnector soapConnector) {
		return args -> {
			String name = "Sajal";//Default Name
			if(args.length>0){
				name = args[0];
			}
			StudentDetailsRequest request = new StudentDetailsRequest();
			request.setName(name);
			StudentDetailsResponse response =(StudentDetailsResponse) soapConnector.callWebService("http://localhost:8080/service/student-details", request);
			System.out.println("Got Response As below ========= : ");
			System.out.println("Name : "+response.getStudent().getName());
			System.out.println("Standard : "+response.getStudent().getStandard());
			System.out.println("Address : "+response.getStudent().getAddress());
		};
	}
}

在这里,我们从命令行获取搜索参数,并创建StudentDetailsRequest对象,并使用SOAPConnector调用 SOAP Web 服务。

一些可选配置

打开application.properties并添加以下配置

application.properties

server.port = 9090
logging.level.org.springframework.ws=TRACE

在这里,我们用server.port = 9090将默认端口覆盖为9090,因为您已经注意到我们的示例 SOAP 服务在默认端口8080中运行,因为两个 Java 进程不能在同一端口中运行。

另外,我们正在通过logging.level.org.springframework.ws=TRACEorg.springframework.ws软件包启用TRACE日志记录。 这将在控制台中打印 SOAP 负载。

这就是我们使用 Spring Boot 消费 SOAP 服务所需要做的一切,现在是时候进行测试了。

示例

现在使用 maven 命令mvn clean install来构建应用程序。 我们可以从命令提示符下通过命令java -jar target\spring-boot-soap-client-0.0.1-SNAPSHOT.jar Lokesh调用命令行运行程序。

请注意,我们在此处传递了一个命令行参数Lokesh,该参数将在CommandLineRunner bean 的查找方法中使用。 如果没有传递任何名称,我们将在该方法中传递一个默认名称。

调用命令行运行程序后,我们应该看到 SOAP 服务输出,并且响应已正确解组到 JAXB 对象StudentDetailsResponse。 同样,我们可以在 TRACE 日志中看到完整的 SOAP 请求/响应,如下所示。

输出

2017-10-09 23:20:45.548 TRACE 9204 --- [           main] o.s.ws.client.MessageTracing.received    : Received response [<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns2:StudentDetailsResponse xmlns:ns2="https://www.howtodoinjava.com/xml/school"><ns2:Student><ns2:name>Sajal</ns2:name><ns2:standard>5</ns2:standard><ns2:address>Pune</ns2:address></ns2:Student></ns2:StudentDetailsResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>] for request [<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><ns2:StudentDetailsRequest xmlns:ns2="https://www.howtodoinjava.com/xml/school"><ns2:name>Sajal</ns2:name></ns2:StudentDetailsRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>]
Got Response As below ========= :
Name : Lokesh
Standard : 6
Address : Delhi

总结

在本 SOAP 教程中,我们学习了如何轻松地从 Spring Boot Soap 客户端中使用来使用 SOAP 服务。 每当需要使用任何此类 SOAP 服务时,都可以使用此方法。 希望这对您有用。

请在评论部分添加您的反馈。

下载源码

学习愉快!

带有嵌入式 ActiveMQ 的 Spring Boot JMSTemplate

原文: https://howtodoinjava.com/spring-boot/spring-boot-jmstemplate-activemq/

了解如何使用嵌入式 ActiveMQ 配置 Spring Boot 应用程序,以便借助 JMSTemplate 发送和接收 JMS 消息。

项目结构

请在 Eclipse 中创建一个 Maven 应用程序,然后在给定的文件夹结构下创建。

Spring Boot JMSTemplate - Project Structure

Spring Boot JMSTemplate – 项目结构

要运行该示例,您将需要在运行时中使用 Java 1.8.

Maven 配置

使用 Spring Boot 和 ActiveMQ 依赖项更新pom.xml文件。 此外,对于对象到 JSON 转换,我们需要 Jackson。

<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.demo</groupId>
	<artifactId>SpringJMSTemplate</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

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

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

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-activemq</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.activemq</groupId>
			<artifactId>activemq-broker</artifactId>
		</dependency>

		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
		</dependency>
	</dependencies>
</project>

@EnableJmsJmsListenerContainerFactory配置

通过使用@SpringBootApplication注解对其进行注解来创建 Spring Boot 应用程序类。 将此代码添加到类中。

package com.howtodoinjava.demo;

import javax.jms.ConnectionFactory;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessageType;

@SpringBootApplication
@EnableJms
public class JMSApplication 
{
	@Bean
	public JmsListenerContainerFactory<?> myFactory(
							ConnectionFactory connectionFactory,
							DefaultJmsListenerContainerFactoryConfigurer configurer) 
	{
		DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
		// This provides all boot's default to this factory, including the message converter
		configurer.configure(factory, connectionFactory);
		// You could still override some of Boot's default if necessary.
		return factory;
	}

	@Bean
	public MessageConverter jacksonJmsMessageConverter() 
	{
		MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
		converter.setTargetType(MessageType.TEXT);
		converter.setTypeIdPropertyName("_type");
		return converter;
	}
}

  • @SpringBootApplication注解等效于使用@Configuration@EnableAutoConfiguration@ComponentScan及其默认属性。 这是使用注解配置轻松配置 spring 应用程序的快捷方式。
  • @EnableJms启用由JmsListenerContainerFactory在封面下创建的@JmsListener带注解的端点。
  • JmsListenerContainerFactory负责创建负责特定端点的监听器容器。 作为DefaultJmsListenerContainerFactory的典型实现提供了基础MessageListenerContainer支持的必要配置选项。
  • MappingJackson2MessageConverter用于将Message的有效载荷从序列化形式转换为类型化对象,反之亦然。
  • 我们已经配置了MessageType.TEXT。 此消息类型可用于传输基于文本的消息。 客户端收到TextMessage时,处于只读模式。 如果客户端此时尝试写入消息,则会抛出MessageNotWriteableException

使用@JmsListener的 JMS 消息接收器

消息接收器类是带有注解@JmsListener的单一方法的非常简单的类。 @JmsListener允许您将托管 Bean 的方法公开为 JMS 监听器端点。

因此,只要在配置的队列上有任何消息可用(在此示例中,队列名称为jms.message.endpoint),就会调用带注解的方法(即receiveMessage)。

@JmsListener是一个可重复的注释,因此您可以在同一方法上多次使用它,以将多个 JMS 目标注册到同一方法。

package com.howtodoinjava.demo;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

@Component
public class MessageReceiver {

    @JmsListener(destination = "jms.message.endpoint")
    public void receiveMessage(Message msg) 
    {
        System.out.println("Received " + msg );
    }
}

Message类是简单的 POJO 类。

package com.howtodoinjava.demo;

import java.io.Serializable;
import java.util.Date;

public class Message implements Serializable {

	private static final long serialVersionUID = 1L;

	private Long id;
	private String content;
	private Date date;

	public Message() {
	}

	public Message(Long id, String content, Date date) {
		super();
		this.id = id;
		this.content = content;
		this.date = date;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	public Date getDate() {
		return date;
	}

	public void setDate(Date date) {
		this.date = date;
	}

	@Override
	public String toString() {
		return "Message [id=" + id + ", content=" + content + ", date=" + date + "]";
	}
}

使用JmsTemplate发送消息

要发送 JMS 消息,您将需要来自 spring 容器的 bean 类JmsTemplate的引用。 调用它的convertAndSend()方法发送消息。

package com.howtodoinjava.demo;

import java.util.Date;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.jms.core.JmsTemplate;

public class Main 
{
	public static void main(String[] args) 
    {
        // Launch the application
        ConfigurableApplicationContext context = SpringApplication.run(JMSApplication.class, args);

        //Get JMS template bean reference
        JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);

        // Send a message
        System.out.println("Sending a message.");
        jmsTemplate.convertAndSend("jms.message.endpoint", new Message(1001L, "test body", new Date()));
    }
}

示例

执行上述Main类的main()方法。 这将启动 Spring Boot 应用程序,然后将消息发送到队列jms.message.endpoint

MessageReceiver.receiveMessage()已经在监听此队列地址。 因此此方法将接收到消息,可以通过将消息打印到控制台进行验证。

在控制台中,输出将如下所示:

Sending a message.
Received Message [id=1001, content=test body, date=Fri Jul 07 14:19:19 IST 2017]

显然,邮件已成功发送和接收。 这就是使用嵌入式 ActiveMQ 的 Spring JMSTemplate的快速示例。

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

学习愉快!

Spring Boot Hello World 示例 – Spring Boot REST 示例

原文: https://howtodoinjava.com/spring-boot/spring-boot-tutorial-with-hello-world-example/

Spring boot 是 spring 框架的开发人员开发的子项目 – 用最小的配置创建独立的生产级应用程序。 Spring Boot 应用程序通常打包为胖/超级 jar 文件,并且可以作为简单的 jar 文件部署在任何平台上。 这就是为什么 Spring Boot 应用程序是在 Java 中构建微服务的最佳选择的原因。

让我们从 Eclipse 中的 spring boot hello world 示例开始逐步学习。

1. 创建 spring boot hello world 项目模板

要为 spring boot 应用程序创建模板,我建议使用 http://start.spring.io/。 在这里,您可以选择当前考虑的所有依赖关系,并生成项目。

Spring Boot Options

Spring Boot 选项

我选择了 Jersey,Spring Web,Spring HATEOAS,Spring JPA 和 Spring Security 等相关性。您可以在下载并导入项目后或将来出现需求时添加更多依赖项。

Generate Project按钮将生成一个.zip文件。 下载文件并将其解压缩到您的工作区中。

2. 导入 spring boot 项目到 eclipse

下一步是将生成的项目导入到 IDE 中。 我为此使用了 eclipse。

1)将 spring boot 项目导入为现有的 maven 项目。

Import Existing Maven Project into Eclipse

将现有 Maven 项目导入 Eclipse

2)选择pom.xml文件将其导入。

Select pom.xml file to import maven project

选择pom.xml文件来导入 Maven 项目

3)将导入项目,并且在生成 zip 文件时添加的依赖项将自动下载并添加到classpath中。

Imported Spring Boot Project Structure

导入的 Spring Boot 项目结构

您现在已经成功导入了 spring boot 应用程序。 现在,让我们看看它已经为您配置了什么。

3. Spring Boot 自动配置

使用 Spring Boot 时,好事是您添加了一个依赖项(例如 Spring security)时,它会做出合理的假设并自动为您配置一些默认值。 因此,您可以立即开始。

Spring Boot 通过扫描类路径中可用的依赖库来使用约定而非配置。 对于 POM 文件中的每个spring-boot-starter-*依赖项,Spring Boot 都会执行一个默认的AutoConfiguration类。 AutoConfiguration类使用*AutoConfiguration词汇模式,其中*代表库。 例如,SpringSecurity 的自动配置是通过SecurityAutoConfiguration完成的。

同时,如果您不想对任何项目都使用自动配置,则此操作非常简单。 只需使用如下所示的exclude = SecurityAutoConfiguration.class

@SpringBootApplication (exclude = SecurityAutoConfiguration.class)
public class SpringBootDemoApplication {
   public static void main(String[] args) 
   {
      SpringApplication.run(SpringBootDemoApplication.class, args);
   }
}

也可以使用src/main/resources文件夹中的application.properties文件覆盖默认配置值。

4. Spring Boot 注解

现在查看@SpringBootApplication注解的实际作用。

4.1. @SpringBootApplication注解

SpringBootApplication定义如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
public @interface SpringBootApplication 
{
   //more code
}

它为应用程序配置目的添加了 3 个重要的注解。

  1. @SpringBootConfiguration
    @Configuration
    public @interface SpringBootConfiguration 
    {
       //more code
    }
    
    

    该注解将@Configuration注解添加到类中,将该类标记为应用程序上下文的 bean 定义的源

  2. @EnableAutoConfiguration

    这告诉 Spring Boot 通过基于类路径设置,其他 bean 和各种属性设置添加 bean,从而根据pom.xml中添加的依赖关系自动配置重要的 bean 定义。

  3. @ComponentScan

    该注解告诉 Spring Boot 扫描基本软件包,查找其他 bean /组件并对其进行配置。

5. 如何通过 Spring Boot 验证自动配置的 Bean

如果您想知道在 spring boot hello world 应用程序中已自动配置了所有 bean,请使用此代码并运行它。

SpringBootDemoApplication.java

import java.util.Arrays;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.context.ApplicationContext;

@SpringBootApplication (exclude = SecurityAutoConfiguration.class)
public class SpringBootDemoApplication {

   public static void main(String[] args) 
   {
      ApplicationContext ctx = SpringApplication.run(SpringBootDemoApplication.class, args);

        String[] beanNames = ctx.getBeanDefinitionNames();

        Arrays.sort(beanNames);

        for (String beanName : beanNames) 
        {
            System.out.println(beanName);
        }
   }
}

使用我的pom.xml文件,它将生成以下 bean 名称以及其他许多springframework.boot.autoconfigure依赖项。

Console

simpleControllerHandlerAdapter
sortResolver
spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
spring.hateoas-org.springframework.boot.autoconfigure.hateoas.HateoasProperties
spring.http.encoding-org.springframework.boot.autoconfigure.web.HttpEncodingProperties
spring.http.multipart-org.springframework.boot.autoconfigure.web.MultipartProperties
spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties
spring.jackson-org.springframework.boot.autoconfigure.jackson.JacksonProperties
spring.jpa-org.springframework.boot.autoconfigure.orm.jpa.JpaProperties
spring.jta-org.springframework.boot.autoconfigure.transaction.jta.JtaProperties
spring.mvc-org.springframework.boot.autoconfigure.web.WebMvcProperties
spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties
springBootDemoApplication
standardJacksonObjectMapperBuilderCustomizer
stringHttpMessageConverter
tomcatEmbeddedServletContainerFactory
tomcatPoolDataSourceMetadataProvider
transactionAttributeSource
transactionInterceptor
transactionManager
transactionTemplate
viewControllerHandlerMapping
viewResolver
websocketContainerCustomizer

6. Spring Boot REST API 示例

现在是时候将任何功能内置到 hello world 应用程序中了。 您可以根据需要添加功能,我正在添加 REST API 。

6.1. 创建 REST 控制器

创建一个包com.howtodoinjava.demo.controller,并在其中创建 rest 控制器。

EmployeeController.java

import java.util.ArrayList;
import java.util.List;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.howtodoinjava.demo.model.Employee;

@RestController
public class EmployeeController 
{
   @RequestMapping("/")
    public List<Employee> getEmployees() 
    {
      List<Employee> employeesList = new ArrayList<Employee>();
      employeesList.add(new Employee(1,"lokesh","gupta","howtodoinjava@gmail.com"));
      return employeesList;
    }
}

6.2. 建立模型

创建模型类Employee

Employee.java

public class Employee {

   public Employee() {

   }
   public Employee(Integer id, String firstName, String lastName, String email) {
      super();
      this.id = id;
      this.firstName = firstName;
      this.lastName = lastName;
      this.email = email;
   }

   private Integer id;
   private String firstName;
   private String lastName;
   private String email;

   //getters and setters

   @Override
   public String toString() {
      return "Employee [id=" + id + ", firstName=" + firstName
            + ", lastName=" + lastName + ", email=" + email + "]";
   }
}

7. Spring Boot Hello World 示例演示

现在,通过运行SpringBootDemoApplication中的main()方法来启动应用程序。 它将在端口8080上启动嵌入式 tomcat 服务器。

由于我们已将演示 REST API URL 配置为根 URL,因此您可以在http;//localhost:8080/本身上对其进行访问。

Verify Spring Boot REST API

验证 Spring Boot REST API

您将在测试工具或浏览器中收到以下响应。

[{"id":1,"firstName":"lokesh","lastName":"gupta","email":"howtodoinjava@gmail.com"}]

这是 spring boot rest hello world 示例和简单的 rest api 示例的全部内容。

让我问您有关如何使用 Maven 在 Eclipse 中创建 Spring Boot 项目的问题。

学习愉快!

下载源码

资源:

Spring Boot 项目

Spring 初始化器

@SpringBootApplication注解

Spring Boot JSP 视图解析器示例

原文: https://howtodoinjava.com/spring-boot/spring-boot-jsp-view-example/

学习创建和配置 spring boot jsp 视图解析器,该解析器使用 JSP 模板文件渲染视图层。 本示例使用嵌入式 Tomcat 服务器运行该应用程序。

源代码结构

该应用程序中的文件作为给定的结构放置在图像中。

Spring Boot Application Structure

Spring Boot 应用结构

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/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.howtodoinjava</groupId>
	<artifactId>spring-boot-demo</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-boot-demo Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.1.RELEASE</version>
	</parent>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<!-- Web -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- Tomcat Embed -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<!-- JSTL -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
		</dependency>
		<!-- To compile JSP files -->
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
			<scope>provided</scope>
		</dependency>
	</dependencies>
</project>

Spring Boot 应用程序初始化器

产生可部署 war 文件的第一步是提供SpringBootServletInitializer子类并覆盖其configure()方法。 这利用了 Spring Framework 的 Servlet 3.0 支持,并允许您在 Servlet 容器启动应用程序时对其进行配置。

package com.howtodoinjava.app.controller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

@SpringBootApplication
public class SpringBootWebApplication extends SpringBootServletInitializer {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		return application.sources(SpringBootWebApplication.class);
	}

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

Spring 控制器

控制器类可以将方法映射到应用程序中的特定 URL。 在给定的应用程序中,它具有两个视图,即//next

package com.howtodoinjava.app.controller;

import java.util.Map;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {

	@RequestMapping("/")
	public String home(Map<String, Object> model) {
		model.put("message", "HowToDoInJava Reader !!");
		return "index";
	}

	@RequestMapping("/next")
	public String next(Map<String, Object> model) {
		model.put("message", "You are in new page !!");
		return "next";
	}

}

Spring Boot JSP ViewResolver配置

要解决 JSP 文件的位置,可以采用两种方法。

1)在application.properties中添加条目

spring.mvc.view.prefix=/WEB-INF/view/
spring.mvc.view.suffix=.jsp

//For detailed logging during development

logging.level.org.springframework=TRACE
logging.level.com=TRACE

2)配置InternalResourceViewResolver服务 JSP 页面

package com.howtodoinjava.app.controller;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Configuration
@EnableWebMvc
@ComponentScan
public class MvcConfiguration extends WebMvcConfigurerAdapter
{
    @Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
		InternalResourceViewResolver resolver = new InternalResourceViewResolver();
		resolver.setPrefix("/WEB-INF/view/");
		resolver.setSuffix(".jsp");
		resolver.setViewClass(JstlView.class);
		registry.viewResolver(resolver);
	}
}

JSP 文件

下面是这个 Spring Boot JSP 示例中使​​用的两个 JSP 文件。

index.jsp

<!DOCTYPE html>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html lang="en">
<body>
	<div>
		<div>
			<h1>Spring Boot JSP Example</h1>
			<h2>Hello ${message}</h2>

			Click on this <strong><a href="next">link</a></strong> to visit another page.
		</div>
	</div>
</body>
</html>

next.jsp

<!DOCTYPE html>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html lang="en">
<body>
	<div>
		<div>
			<h1>Another page</h1>
			<h2>Hello ${message}</h2>

			Click on this <strong><a href="/">link</a></strong> to visit previous page.
		</div>
	</div>
</body>
</html>

示例

编写完所有代码并将其放置在文件夹中之后,通过执行SpringBootWebApplication类中的main()方法来运行应用程序。

现在点击 URL:http://localhost:8080/

Spring Boot Application - index

Spring Boot 应用 – index

点击下一个链接

Spring Boot Application - next

Spring Boot 应用 – next

Spring Boot JSP 示例源代码

用下面的链接下载该应用程序的源代码。

下载源码

在本教程中,我们通过示例学习了 Spring Boot JSP ViewResolver,以及其他可能使用的多个视图解析器。

学习愉快!

参考:

Spring Boot

Spring Boot 应用程序属性

Spring MVC

SpringBoot – 执行器

原文: https://howtodoinjava.com/spring-boot/actuator-endpoints-example/

在此 Spring Boot 执行器教程中,了解可用于任何运行应用程序的内置 HTTP 端点,以用于不同的监视和管理目的。 在 spring 框架之前,如果我们必须在应用程序中引入这种类型的监视功能,则必须手动开发所有这些组件,并且这些组件也非常符合我们的需求。 但是通过 Spring Boot,我们有了Actuator模块,这非常容易。

我们只需要配置几件事就可以完成 – 所有管理和监视相关信息都可以轻松获得。 让我们学习配置 Spring Boot 执行器端点

1. SpringBoot 执行器模块

使用 Spring Boot 的模块Actuator,您可以监视和管理生产环境中的应用程序使用情况,而无需对其进行编码和配置。 这些监视和管理信息通过 REST(如端点 URL)公开。

1.1. 执行器 Maven 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

1.2. 重要的执行器端点

一些重要且广泛使用的执行器端点如下:

端点 用法
/env 返回当前环境中的属性列表
/health 返响应用程序运行状况信息。
/auditevents 返回所有自动配置的候选对象以及应用它们“被”或“未被”的原因。
/beans 返响应用程序中所有 Spring Bean 的完整列表。
/trace 返回跟踪日志(默认情况下,最近的 100 个 HTTP 请求)。
/dump 它执行线程转储。
/metrics 它显示了一些有用的指标信息,例如 JVM 内存使用情况,系统 CPU 使用情况,打开的文件等等。

1.3. 与安全性相关的属性

默认情况下,所有执行器端点均启用 SpringSecurity。它是内置的基于表单的身份验证,其中的用户 ID 为用户,并随机生成一个密码。 然后需要以下条目才能为您的敏感端点自定义基本认证安全性。

management.security.enabled = true
management.security.roles = ADMIN

security.basic.enabled = true
security.user.name = admin
security.user.password = admin

请注意,默认情况下,要访问执行器受限的端点,您必须具有ACTUATOR角色。 您需要通过management.security.roles属性覆盖此配置。

1.4. 使用WebSecurityConfigurerAdapter的执行器安全性

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");

    }
}

1.5. CORS 支持

CORS 支持默认为禁用,并且仅在设置了endpoints.cors.allowed-origins属性后才启用。

endpoints.cors.allowed-origins = http://example.com
endpoints.cors.allowed-methods = GET,POST

2. Spring Boot 执行器端点示例

在此示例中,我们将创建一个简单的字符串启动应用程序,并访问执行器端点以进一步了解它们。

2.1. 开发环境

  • JDK 1.8,Eclipse,Maven – 开发环境
  • SpringBoot – 基础应用程序框架
  • SpringBoot 执行器 – 管理端点

2.2. 创建 Maven 项目

首先从 Spring 初始化器站点创建一个具有WebRest RepositoriesActuator依赖项的 spring boot 项目。 以压缩格式下载项目。 解压缩,然后将 eclipse 中的项目导入为 maven 项目。

Spring boot actuator project

Spring boot 执行器项目

2.3. 添加简单的 Rest 端点

现在,向应用程序添加一个简单的 Rest 端点/example

package com.example.springbootmanagementexample;

import java.util.Date;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SimpleRestController {
    @GetMapping("/example")
    public String example() {
        return "Hello User !! " + new Date();
    }
}

3. Spring Boot 执行器端点演示

我在application.properties文件中添加了management.security.enabled = false条目以禁用执行器安全性。 在这里,我对执行器端点响应更感兴趣。

使用mvn clean install进行 maven 构建,然后使用java -jar target\spring-boot-management-example-0.0.1-SNAPSHOT.jar命令启动应用程序。 这将在默认端口8080中启动一台 tomcat 服务器,并将在其中部署应用程序。

在浏览器中访问/example API,以在服务器上生成少量监视信息。

  • http://localhost:8080/env

    这将提供有关服务器的所有环境配置。

    Endpoint env Output

    env端点输出

  • http://localhost:8080/beans

    这将在上下文中加载所有 SpringBean。

    Endpoint beans Output

    beans端点输出

  • http://localhost:8080/dump

    这将给出当前服务器线程转储。

    Endpoint dump Output

    dump端点输出

  • http://localhost:8080/health

    这将提供应用程序和服务器的一般运行状况。

    Endpoint health Output

    health端点输出

  • http://localhost:8080/metrics

    /metrics端点列出了所有可用于跟踪的指标。

    {
    	"mem": 316656,
    	"mem.free": 169495,
    	"processors": 4,
    	"instance.uptime": 1449726,
    	"uptime": 1463662,
    	"systemload.average": -1.0,
    	"heap.committed": 263168,
    	"heap.init": 131072,
    	"heap.used": 93672,
    	"heap": 1846272,
    	"nonheap.committed": 54400,
    	........
    }
    
    

这些端点将在浏览器中提供标准信息。 这些是我们通常引用的基本重要端点,但是如链接所述,Spring Boot 提供了更多端点。

4. 执行器高级配置选项

4.1. 更改管理端点上下文路径

默认情况下,所有端点都位于应用程序的默认上下文路径中。 仍然,如果我们需要将这些端点公开在不同的端点中,则需要在application.properties中指定它们。

management.context-path=/manage

现在,您将可以在新 URL 下访问所有执行器端点。 例如

  • /manage/health
  • /manage/dump
  • /manage/env
  • /manage/bean

4.2. 定制管理服务器端口

要自定义管理端点端口,我们需要将此条目添加到application.properties文件中。

management.port=8081

5. 总结

在此 SpringBoot 执行器示例中,我们学习了使用很少的简单配置即可配置管理和监视端点。 因此,下次,您需要添加应用程序运行状况检查或添加监视支持,则应考虑添加 Spring 执行器项目并使用这些端点。

随时在评论部分中提出您的问题。

下载源码

学习愉快!

Spring Boot – 带有 JAX-RS 注解的基于角色的安全性

原文: https://howtodoinjava.com/spring-boot/role-based-security-jaxrs-annotations/

学习使用 Spring Boot 和 Jersey 框架创建 JAX-RS 2.0 REST API ,并使用 JAX-RS 注解添加基于角色的安全性,例如 @PermitAll@RolesAllowed@DenyAll

项目结构

在本教程中创建的应用程序的项目结构如下:

Spring Boot JAX-RS Security Demo - Project Structure

Spring Boot JAX-RS 安全性示例 – 项目结构

创建 REST API

  1. 创建 Spring Boot 项目

    转到 Spring 初始化器页面,并创建具有 Jersey(JAX-RS)依赖项的 spring boot 应用程序。

    Select Jersey in Spring Boot Initializr

    在 Spring 初始化器中选择 Jersey

  2. 在 Eclipse 中导入

    将项目生成为 zip 文件。 将其解压缩到计算机中的某个位置。 将项目作为“现有 maven 应用程序”导入 eclipse。

  3. 检查 Maven 依赖项

    检查 Maven 文件中是否具有依赖项

    <dependencies>
    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-jersey</artifactId>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-test</artifactId>
    		<scope>test</scope>
    	</dependency>
    </dependencies>
    
    
  4. 创建 REST API

    现在创建一些 JAX-RS 资源,我们将进入测试阶段。 我创建了UserResource类。

    UserResource.java

    package com.howtodoinjava.jerseydemo;
    
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Map;
    import javax.ws.rs.Consumes;
    import javax.ws.rs.DELETE;
    import javax.ws.rs.GET;
    import javax.ws.rs.POST;
    import javax.ws.rs.PUT;
    import javax.ws.rs.Path;
    import javax.ws.rs.PathParam;
    import javax.ws.rs.Produces;
    import javax.ws.rs.core.Response;
    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    import javax.xml.bind.annotation.XmlRootElement;
    
    @XmlAccessorType(XmlAccessType.NONE)
    @XmlRootElement(name = "users")
    @Path("/users")
    public class UserResource 
    {
    	private static Map<Integer, User> DB = new HashMap<>(); 
    
    	@GET
    	@Produces("application/json")
    	public Users getAllUsers() {
    		Users users = new Users();
    		users.setUsers(new ArrayList<>(DB.values()));
    		return users;
    	}
    
    	@POST
    	@Consumes("application/json")
    	public Response createUser(User user) throws URISyntaxException 
    	{
    		if(user.getFirstName() == null || user.getLastName() == null) {
    			return Response.status(400).entity("Please provide all mandatory inputs").build();
    		}
    		user.setId(DB.values().size()+1);
    		user.setUri("/user-management/"+user.getId());
    		DB.put(user.getId(), user);
    		return Response.status(201).contentLocation(new URI(user.getUri())).build();
    	}
    
    	@GET
    	@Path("/{id}")
    	@Produces("application/json")
    	public Response getUserById(@PathParam("id") int id) throws URISyntaxException 
    	{
    		User user = DB.get(id);
    		if(user == null) {
    			return Response.status(404).build();
    		}
    		return Response
    				.status(200)
    				.entity(user)
    				.contentLocation(new URI("/user-management/"+id)).build();
    	}
    
    	@PUT
    	@Path("/{id}")
    	@Consumes("application/json")
    	@Produces("application/json")
    	public Response updateUser(@PathParam("id") int id, User user) throws URISyntaxException 
    	{
    		User temp = DB.get(id);
    		if(user == null) {
    			return Response.status(404).build();
    		}
    		temp.setFirstName(user.getFirstName());
    		temp.setLastName(user.getLastName());
    		DB.put(temp.getId(), temp);
    		return Response.status(200).entity(temp).build();
    	}
    
    	@DELETE
    	@Path("/{id}")
    	public Response deleteUser(@PathParam("id") int id)	throws URISyntaxException {
    		User user = DB.get(id);
    		if(user != null) {
    			DB.remove(user.getId());
    			return Response.status(200).build();
    		}
    		return Response.status(404).build();
    	}
    
    	static 
    	{
    		User user1 = new User();
    		user1.setId(1);
    		user1.setFirstName("John");
    		user1.setLastName("Wick");
    		user1.setUri("/user-management/1");
    
    		User user2 = new User();
    		user2.setId(2);
    		user2.setFirstName("Harry");
    		user2.setLastName("Potter");
    		user2.setUri("/user-management/2");
    
    		DB.put(user1.getId(), user1);
    		DB.put(user2.getId(), user2);
    	}
    }
    
    

    Users.java

    package com.howtodoinjava.jerseydemo;
    
    import java.util.ArrayList;
    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    
    @XmlAccessorType(XmlAccessType.NONE)
    @XmlRootElement(name = "users")
    public class Users {
    
        @XmlElement(name="user")
        private ArrayList<User> users;
    
        public ArrayList<User> getUsers() {
            return users;
        }
    
        public void setUsers(ArrayList<User> users) {
            this.users = users;
        }
    }
    
    

    User.java

    package com.howtodoinjava.jerseydemo;
    
    import java.io.Serializable;
    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    import javax.xml.bind.annotation.XmlAttribute;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    
    @XmlAccessorType(XmlAccessType.NONE)
    @XmlRootElement(name = "user")
    public class User implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        @XmlAttribute(name = "id")
        private int id;
    
        @XmlAttribute(name="uri")
        private String uri;
    
        @XmlElement(name = "firstName")
        private String firstName;
    
        @XmlElement(name = "lastName")
        private String lastName;
    
        // Getters and Setters
    }
    
    
  5. 配置 Jersey

    现在我们有了一个 JAX-RS 资源,我们想从包含 Jersey 依赖项的 spring boot 应用程序中访问它。 让我们将此资源注册为 Jersey 资源。

    package com.howtodoinjava.jerseydemo;
    
    import org.glassfish.jersey.server.ResourceConfig;
    import org.springframework.stereotype.Component;
    
    @Component
    public class JerseyConfig extends ResourceConfig 
    {
    	public JerseyConfig() 
    	{
    		register(SecurityFilter.class);
    		register(UserResource.class);
    	}
    }
    
    
    • 查看@Component注解。 它可以在 Spring Boot 自动扫描源文件夹中的 java 类时注册此类。
    • ResourceConfig提供高级功能以简化 JAX-RS 组件的注册。
    • SecurityFilter类是实际的身份验证详细信息处理器,我们将在本教程的后面部分看到。

    SpringBootServletInitializer扩展 spring boot 应用程序。

    package com.howtodoinjava.jerseydemo;
    
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.boot.web.support.SpringBootServletInitializer;
    
    @SpringBootApplication
    public class JerseydemoApplication extends SpringBootServletInitializer 
    {
    	public static void main(String[] args) 
    	{
    		new JerseydemoApplication().configure(new SpringApplicationBuilder(JerseydemoApplication.class)).run(args);
    	}
    }
    
    

使用 JAX-RS 注解的安全 REST API

现在,当我们的 API 准备就绪时,我们将开始保护它们。 让我们根据 JAX-RS 注解对其进行注解,具体取决于它们的期望访问级别和允许访问它们的用户角色。

package com.howtodoinjava.jerseydemo;

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "users")
@Path("/users")
public class UserResource 
{
	private static Map<Integer, User> DB = new HashMap<>(); 

	@GET
	@PermitAll
	@Produces("application/json")
	public Users getAllUsers() {
		Users users = new Users();
		users.setUsers(new ArrayList<>(DB.values()));
		return users;
	}

	@POST
	@Consumes("application/json")
	@RolesAllowed("ADMIN")
	public Response createUser(User user) throws URISyntaxException 
	{
		if(user.getFirstName() == null || user.getLastName() == null) {
			return Response.status(400).entity("Please provide all mandatory inputs").build();
		}
		user.setId(DB.values().size()+1);
		user.setUri("/user-management/"+user.getId());
		DB.put(user.getId(), user);
		return Response.status(201).contentLocation(new URI(user.getUri())).build();
	}

	@GET
	@Path("/{id}")
	@Produces("application/json")
	@PermitAll
	public Response getUserById(@PathParam("id") int id) throws URISyntaxException 
	{
		User user = DB.get(id);
		if(user == null) {
			return Response.status(404).build();
		}
		return Response
				.status(200)
				.entity(user)
				.contentLocation(new URI("/user-management/"+id)).build();
	}

	@PUT
	@Path("/{id}")
	@Consumes("application/json")
	@Produces("application/json")
	@RolesAllowed("ADMIN")
	public Response updateUser(@PathParam("id") int id, User user) throws URISyntaxException 
	{
		User temp = DB.get(id);
		if(user == null) {
			return Response.status(404).build();
		}
		temp.setFirstName(user.getFirstName());
		temp.setLastName(user.getLastName());
		DB.put(temp.getId(), temp);
		return Response.status(200).entity(temp).build();
	}

	@DELETE
	@Path("/{id}")
	@RolesAllowed("ADMIN")
	public Response deleteUser(@PathParam("id") int id)	throws URISyntaxException {
		User user = DB.get(id);
		if(user != null) {
			DB.remove(user.getId());
			return Response.status(200).build();
		}
		return Response.status(404).build();
	}

	static 
	{
		User user1 = new User();
		user1.setId(1);
		user1.setFirstName("John");
		user1.setLastName("Wick");
		user1.setUri("/user-management/1");

		User user2 = new User();
		user2.setId(2);
		user2.setFirstName("Harry");
		user2.setLastName("Potter");
		user2.setUri("/user-management/2");

		DB.put(user1.getId(), user1);
		DB.put(user2.getId(), user2);
	}
}

您可以在上面突出显示的行中查看与安全相关的 JAX-RS 注解。

使用 JAX-RS ContainerRequestFilter编写安全过滤器

现在是时候编写我们的安全过滤器了,该过滤器将检查传入的请求,获取授权信息(在此示例中为基本认证),然后匹配用户名和密码,最后将通过其角色来验证用户的访问级别。 如果一切都匹配,则将访问 API,否则用户将获得拒绝访问响应。

package com.howtodoinjava.jerseydemo;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;

/**
 * This filter verify the access permissions for a user based on 
 * user name and password provided in request
 * */
@Provider
public class SecurityFilter implements javax.ws.rs.container.ContainerRequestFilter
{
    private static final String AUTHORIZATION_PROPERTY = "Authorization";
    private static final String AUTHENTICATION_SCHEME = "Basic";
    private static final Response ACCESS_DENIED = Response.status(Response.Status.UNAUTHORIZED).build();
    private static final Response ACCESS_FORBIDDEN = Response.status(Response.Status.FORBIDDEN).build();
    private static final Response SERVER_ERROR = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();

    @Context
    private ResourceInfo resourceInfo;

    @Override
    public void filter(ContainerRequestContext requestContext)
    {
        Method method = resourceInfo.getResourceMethod();
        //Access allowed for all 
        if( ! method.isAnnotationPresent(PermitAll.class))
        {
            //Access denied for all 
            if(method.isAnnotationPresent(DenyAll.class))
            {
                requestContext.abortWith(ACCESS_FORBIDDEN);
                return;
            }

            //Get request headers
            final MultivaluedMap<String, String> headers = requestContext.getHeaders();

            //Fetch authorization header
            final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY);

            //If no authorization information present; block access
            if(authorization == null || authorization.isEmpty())
            {
                requestContext.abortWith(ACCESS_DENIED);
                return;
            }

            //Get encoded username and password
            final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");

            //Decode username and password
            String usernameAndPassword = null;
            try {
                usernameAndPassword = new String(Base64.getDecoder().decode(encodedUserPassword));
            } catch (Exception e) {
                requestContext.abortWith(SERVER_ERROR);
                return;
            }

            //Split username and password tokens
            final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
            final String username = tokenizer.nextToken();
            final String password = tokenizer.nextToken();

            //Verifying Username and password
            if(!(username.equalsIgnoreCase("admin") && password.equalsIgnoreCase("password"))){
            	requestContext.abortWith(ACCESS_DENIED);
                return;
            }

            //Verify user access
            if(method.isAnnotationPresent(RolesAllowed.class))
            {
                RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
                Set<String> rolesSet = new HashSet<String>(Arrays.asList(rolesAnnotation.value()));

                //Is user valid?
                if( ! isUserAllowed(username, password, rolesSet))
                {
                    requestContext.abortWith(ACCESS_DENIED);
                    return;
                }
            }
        }
    }
    private boolean isUserAllowed(final String username, final String password, final Set<String> rolesSet) 
    {
        boolean isAllowed = false;

        //Step 1\. Fetch password from database and match with password in argument
        //If both match then get the defined role for user from database and continue; else return isAllowed [false]
        //Access the database and do this part yourself
        //String userRole = userMgr.getUserRole(username);
        String userRole = "ADMIN";

        //Step 2\. Verify user role
        if(rolesSet.contains(userRole))
        {
            isAllowed = true;
        }
        return isAllowed;
    }
}

示例

将项目作为 Spring 启动应用程序运行。 现在测试其余资源。

访问 GET /users资源

GET Users collection

GET 用户集合

访问 POST /users资源,没有身份验证详细信息

查看返回的状态代码 401 。

Auth Required for POST APIs

POST API 所需的认证

已添加身份验证详细信息的访问 POST /users资源

使用此链接生成 base64 编码的用户名和密码组合,以传递到Authorization标头中。

Request with Auth Details Success

使用认证信息请求成功

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

学习愉快!

Spring DispatcherServlet – 它是如何工作的?

原文: https://howtodoinjava.com/spring5/webmvc/spring-dispatcherservlet-tutorial/

了解 Spring 的DispatcherServlet类,其职责以及如何使用示例进行配置。

1. 什么是 Spring DispatcherServlet

DispatcherServlet充当基于 Spring 的 Web 应用程序的前控制器。 它提供了一种用于请求处理的机制,其中实际工作由可配置的委托组件执行。 它继承自javax.servlet.http.HttpServlet,通常在web.xml文件中进行配置。

Web 应用程序可以定义任意数量的DispatcherServlet实例。 每个 servlet 将在其自己的名称空间中运行,并使用映射,处理器等加载其自己的应用程序上下文。只有ContextLoaderListener加载的根应用程序上下文(如果有)将被共享。 在大多数情况下,应用程序仅具有上下文根 URL (/)的单个DispatcherServlet,即,到达该域的所有请求都将由该域处理。

DispatcherServlet使用 Spring 配置类发现请求映射,视图解析,异常处理等所需的委托组件。

2. 如何使用WebApplicationContext

让我们了解调度程序 servlet 在内部如何工作? 在基于 Spring 的应用程序中,我们的应用程序对象位于对象容器中。 该容器创建对象和对象之间的关联,并管理它们的完整生命周期。 这些容器对象称为 Spring 管理的 Bean(或简称为 Bean),在 Spring 世界中,该容器称为应用程序上下文(通过类ApplicationContext)。

WebApplicationContext是普通ApplicationContext的扩展。 它是网络感知的ApplicationContext,即它具有 Servlet 上下文信息。 加载DispatcherServlet后,它将查找WebApplicationContext的 bean 配置文件并对其进行初始化。

通过访问 Servlet 上下文,任何实现ServletConextAware接口的 spring bean – 都可以访问ServletContext实例并用它做很多事情。 例如,它可以获取上下文初始化参数,获取上下文根信息以及获取 Web 应用程序文件夹内的资源位置。

3. DispatcherServlet XML 配置

让我们看看典型的DispatcherServlet声明和初始化的样子。

web.xml

<web-app>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/dispatcher-servlet-context.xml</param-value>
  </context-param>

  <servlet>
    <servlet-name>dispatcher-servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value></param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>dispatcher-servlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

在上面的代码中,dispatcher-servlet-context.xml文件将包含所有可用于DispatcherServlet的 bean 定义和关联。 这些 bean 定义将覆盖在全局范围内用相同名称定义的任何 bean 的定义。 例如

applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/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="viewResolver"
    	class="org.springframework.web.servlet.view.InternalResourceViewResolver" >
        <property name="prefix">
            <value>/WEB-INF/views/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

</beans>

4. DispatcherServlet Java 配置

从 Servlet 3.0 开始,除了web.xml文件中的声明性配置外,还可以通过实现或扩展 Spring 提供的这三个支持类之一来以编程方式配置DispatcherServlet

  • WebAppInitializer界面
  • AbstractDispatcherServletInitializer抽象类
  • AbstractAnnotationConfigDispatcherServletInitializer抽象类

4.1. WebAppInitializer示例

在下面的类中,WebApplicationInitializer确保SpringServletContainerInitializer检测到类ApplicationInitializer(它本身会自动运行),并用于初始化任何 Servlet 3 容器。

spring boot DispatcherServlet映射的示例。

ApplicationInitializer.java

public class ApplicationInitializer implements WebApplicationInitializer 
{
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException 
	{
		XmlWebApplicationContext appContext = new XmlWebApplicationContext();
		appContext.setConfigLocation("/WEB-INF/dispatcher-servlet-context.xml");

		ServletRegistration.Dynamic registration = servletContext
					.addServlet("rootDispatcher", new DispatcherServlet(appContext));
		registration.setLoadOnStartup(1);
		registration.addMapping("/");
	}
}

4.2. 完整的基于 Java 的初始化

ApplicationInitializer.java

public class ApplicationInitializer implements WebApplicationInitializer 
{
	@Override
	public void onStartup(ServletContext container) 
	{
		// Create the 'root' Spring application context
		AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
		rootContext.register(AppConfig.class);

		// Manage the lifecycle of the root application context
		container.addListener(new ContextLoaderListener(rootContext));

		// Create the dispatcher servlet's Spring application context
		AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
		dispatcherContext.register(DispatcherConfig.class);

		ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", 
						new DispatcherServlet(dispatcherContext));
		dispatcher.setLoadOnStartup(1);
		dispatcher.addMapping("/");
	}
}

在上面的代码中,AppConfigDispatcherConfig类定义将在 Web 应用程序上下文中的 spring 托管 bean。

4.3. AbstractDispatcherServletInitializer示例

这是在 Servlet 上下文中注册DispatcherServletWebApplicationInitializer实现的基类。

ApplicationInitializer.java

public class ApplicationInitializer extends AbstractDispatcherServletInitializer {

	@Override
	protected WebApplicationContext createRootApplicationContext() {
	        return null;
	}

	@Override
	protected WebApplicationContext createServletApplicationContext() {
	        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
	        cxt.setConfigLocation("/WEB-INF/dispatcher-servlet-context.xml");
	        return cxt;
	}

	@Override
	protected String[] getServletMappings() {
	        return new String[] { "/" };
	}

	//Register filters
	@Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
    }
}

请注意,如果您需要自定义DispatcherServlet,则可以覆盖createDispatcherServlet()方法。

4.4. AbstractAnnotationConfigDispatcherServletInitializer示例

该类扩展了AbstractDispatcherServletInitializer并隐式地执行了一些操作,否则您可能会自己做。 另一个优点是,您现在可以使用 Spring 提供的便利类,而不必手动配置DispatcherServlet和/或ContextLoaderListener

对于使用基于 Java 的 Spring 配置的应用程序,这是首选方法。 它使您能够启动 servlet 应用程序上下文以及根应用程序上下文。

AppInitializer.java

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

   @Override
   protected Class<?>[] getRootConfigClasses() {
      return new Class[] { RootConfig.class };
   }

   @Override
   protected Class<?>[] getServletConfigClasses() {
      return new Class[] { WebMvcConfig.class };
   }

   @Override
   protected String[] getServletMappings() {
      return new String[] { "/" };
   }
}

这里RootConfigWebMvcConfig类用于在 root 和 servlet 上下文范围内配置 bean。

阅读更多: Spring 5 MVC 示例

5. 支持DispatcherServlet的 Bean

收到网络请求后,DispatcherServlet将执行一组操作以进行请求处理。 为此,它使用了一组支持 bean。 下表列出了这些默认配置的 Bean 及其职责:

Bean 职责范围
HandlerMapping 将传入的 Web 请求映射到处理器以及预处理器和后处理器
HandlerAdapter 调用用于解析参数和依赖项的处理器,例如用于 URL 映射的控制器方法端点的带注解的参数
HandlerExceptionResolver 允许以编程方式处理异常并将异常映射到视图
ViewResolver 解析逻辑视图名称以查看实例
LocaleResolver 解决客户的语言环境以实现国际化
LocaleContextResolver LocaleResolver的更扩展,带有时区信息
ThemeResolver 解决了在您的应用中配置的主题,以增强用户体验
MultipartResolver 处理多部分文件上传作为 HTTP 请求的一部分
FlashMapManager 管理FlashMap实例,这些实例在彼此重定向的请求之间存储临时 Flash 属性

如果要更改任何 bean 的任何特定行为,则需要覆盖它。

6. Spring DispatcherServlet示例

为了演示DispatcherServlet的使用,我编写了一个非常简单的应用程序,该应用程序仅配置了调度程序 servlet 并覆盖了视图解析程序 bean。

6.1. 项目结构

Spring5 MVC Project Structure

Spring5 MVC 项目结构

6.2. AppInitializer.java

AppInitializer.java

package com.howtodoinjava.demo.spring.config;

public class AppInitializer extends 
	AbstractAnnotationConfigDispatcherServletInitializer {

   @Override
   protected Class<?>[] getRootConfigClasses() {
      return new Class[] { };
   }

   @Override
   protected Class<?>[] getServletConfigClasses() {
      return new Class[] { WebMvcConfig.class };
   }

   @Override
   protected String[] getServletMappings() {
      return new String[] { "/" };
   }
}

6.3. WebMvcConfig.java

WebMvcConfig.java

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.howtodoinjava.demo.spring"})
public class WebMvcConfig implements WebMvcConfigurer {

   @Bean
   public InternalResourceViewResolver resolver() {
      InternalResourceViewResolver resolver = new InternalResourceViewResolver();
      resolver.setViewClass(JstlView.class);
      resolver.setPrefix("/WEB-INF/views/");
      resolver.setSuffix(".jsp");
      return resolver;
   }
}

6.4. HomeController.java

HomeController.java

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

home.jsp

home.jsp

<html>
<head>
	<title>Spring 5 Web MVC Example</title>
</head>
<body>
	<h1>HowToDoInJava.com</h1>
	<h2>Spring 5 Web MVC DispatcherServlet Example</h2>
</body>
</html>

6.5. pom.xml

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.spring5.mvc</groupId>
	<artifactId>spring5-webmvc-demo</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring5-webmvc-demo Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<properties>
		<failOnMissingWebXml>false</failOnMissingWebXml>
		<spring.version>5.2.0.RELEASE</spring.version>
		<jstl.version>1.2.1</jstl.version>
		<tld.version>1.1.2</tld.version>
		<servlets.version>3.1.0</servlets.version>
		<jsp.version>2.3.1</jsp.version>
	</properties>
	<dependencies>
		<!-- Spring MVC Dependency -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- JSTL Dependency -->
		<dependency>
			<groupId>javax.servlet.jsp.jstl</groupId>
			<artifactId>javax.servlet.jsp.jstl-api</artifactId>
			<version>${jstl.version}</version>
		</dependency>

		<dependency>
			<groupId>taglibs</groupId>
			<artifactId>standard</artifactId>
			<version>${tld.version}</version>
		</dependency>

		<!-- Servlet Dependency -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>${servlets.version}</version>
			<scope>provided</scope>
		</dependency>

		<!-- JSP Dependency -->
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>javax.servlet.jsp-api</artifactId>
			<version>${jsp.version}</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<sourceDirectory>src/main/java</sourceDirectory>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
			</resource>
		</resources>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.5.1</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.tomcat.maven</groupId>
				<artifactId>tomcat7-maven-plugin</artifactId>
				<version>2.2</version>
				<configuration>
					<path>/</path>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

6.6. 运行应用程序

要运行应用程序,请执行 maven 目标:tomcat7:run。 现在在浏览器中打开http://localhost:8080

Spring DispatcherServlet Demo Screen

Spring DispatcherServlet示例截图

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

下载源码

学习愉快!

Spring Boot RSS feed 和 ROAM

https://howtodoinjava.com/spring-boot/spring-boot-rome-rss-and-atom-feed/

在本教程中,学习从 SpringBoot 应用程序创建和使用 RSS 和 Atom 提要。 您一定已经在各种网站(如 RSS 提要之类的)上以文本或图像按钮的形式看到了这一点,并邀请您“通过 RSS 订阅”。RSS 是简单的联合 API,通常称为富站点摘要。 RSS 彻底改变了用户与在线内容进行交互的方式。

与 RSS 相似,Atom 也是基于 XML 的 Web 内容和元数据联合格式,并且是用于发布和编辑属于定期更新的网站的 Web 资源的应用程序级协议。 所有 Atom 提要必须是格式为application/atom+xml的 XML 文档。

总览

在这个运行示例中,我们将使用 Spring Boot API 公开 RSS 和 ATOM 的两个简单端点,并且我们将了解如何使用 java 客户端使用这些提要。

技术栈

  • JDK 1.8,Eclipse,Maven – 开发环境
  • SpringBoot – 基础应用程序框架
  • ROME 库 – 用于发布提要

怎么运行的?

基本上,使用 Spring 框架发布 RSS 或 Atom 提要非常容易。 在 spring 框架中,有两个 http 消息转换器(RssChannelHttpMessageConverterAtomFeedHttpMessageConverter)可以将 spring 控制器方法的响应转换为 XML feed 格式(如果返回类型与以下任何一种相关) 饲料。

这两个转换器都依赖于 ROME 库,当 Spring 框架在类路径上发现库时,Spring 框架会自动注册这两个转换器。 我们要做的就是将 ROME 库添加为pom.xml的依赖。

项目结构

下面给出了为此演示创建的类和文件。

项目

创建 RSS / ATOM 提要生成器

创建 Spring Boot 项目

首先从 Spring 初始化器站点创建一个仅具有Web依赖项的 spring boot 项目。 选择依赖项并提供适当的 Maven GAV 坐标后,以压缩格式下载项目。 解压缩,然后将 eclipse 中的项目导入为 maven 项目。

Spring boot 项目生成

添加 ROAM 依赖关系

现在,我们需要在新创建的项目的pom.xml中添加 ROAM 依赖项。

<dependency>
    <groupId>com.rometools</groupId>
    <artifactId>rome</artifactId>
    <version>1.8.0</version>
</dependency>

创建控制器

现在添加一个 Spring 控制器并添加两个端点/rss/atom分别公开 RSS 和 Atom 提要。 正如我们已经提到的,仅添加此控制器将自动适用于我们的情况,因为内部 spring 框架将注册两个 http 消息转换器(RssChannelHttpMessageConverterAtomFeedHttpMessageConverter),一旦我们在类路径中具有 ROAM 依赖项,它们便将被注册。

我们唯一需要做的就是从控制器方法中返回正确的Feed类型的对象。 在我们的情况下,提要对象的 RSS 类型为com.rometools.rome.feed.rss.Channel,Atom 类型为com.rometools.rome.feed.atom.Feed。 因此,在添加提要的内容以及有关通道的其他详细信息之后,我们的控制器将如下所示。

package com.example.howtodoinjava.rss;

import java.util.Collections;
import java.util.Date;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.rometools.rome.feed.atom.Category;
import com.rometools.rome.feed.atom.Content;
import com.rometools.rome.feed.atom.Entry;
import com.rometools.rome.feed.atom.Feed;
import com.rometools.rome.feed.atom.Link;
import com.rometools.rome.feed.atom.Person;
import com.rometools.rome.feed.rss.Channel;
import com.rometools.rome.feed.rss.Description;
import com.rometools.rome.feed.rss.Image;
import com.rometools.rome.feed.rss.Item;
import com.rometools.rome.feed.synd.SyndPerson;

@RestController
public class FeedController {

	@GetMapping(path = "/rss")
	public Channel rss() {
		Channel channel = new Channel();
		channel.setFeedType("rss_2.0");
		channel.setTitle("HowToDoInJava Feed");
		channel.setDescription("Different Articles on latest technology");
		channel.setLink("https://howtodoinjava.com");
		channel.setUri("https://howtodoinjava.com");
		channel.setGenerator("In House Programming");

		Image image = new Image();
		image.setUrl("https://howtodoinjava.com/wp-content/uploads/2015/05/howtodoinjava_logo-55696c1cv1_site_icon-32x32.png");
		image.setTitle("HowToDoInJava Feed");
		image.setHeight(32);
		image.setWidth(32);
		channel.setImage(image);

		Date postDate = new Date();
		channel.setPubDate(postDate);

		Item item = new Item();
		item.setAuthor("Lokesh Gupta");
		item.setLink("https://howtodoinjava.com/spring5/webmvc/spring-mvc-cors-configuration/");
		item.setTitle("Spring CORS Configuration Examples");
		item.setUri("https://howtodoinjava.com/spring5/webmvc/spring-mvc-cors-configuration/");
		item.setComments("https://howtodoinjava.com/spring5/webmvc/spring-mvc-cors-configuration/#respond");

		com.rometools.rome.feed.rss.Category category = new com.rometools.rome.feed.rss.Category();
		category.setValue("CORS");
		item.setCategories(Collections.singletonList(category));

		Description descr = new Description();
		descr.setValue(
				"CORS helps in serving web content from multiple domains into browsers who usually have the same-origin security policy. In this example, we will learn to enable CORS support in Spring MVC application at method and global level."
						+ "The post <a rel=\"nofollow\" href=\"https://howtodoinjava.com/spring5/webmvc/spring-mvc-cors-configuration/\">Spring CORS Configuration Examples</a> appeared first on <a rel=\"nofollow\" href=\"https://howtodoinjava.com\">HowToDoInJava</a>.");
		item.setDescription(descr);
		item.setPubDate(postDate);

		channel.setItems(Collections.singletonList(item));
		//Like more Entries here about different new topics
		return channel;
	}

	@GetMapping(path = "/atom")
	public Feed atom() {
		Feed feed = new Feed();
		feed.setFeedType("atom_1.0");
		feed.setTitle("HowToDoInJava");
		feed.setId("https://howtodoinjava.com");

		Content subtitle = new Content();
		subtitle.setType("text/plain");
		subtitle.setValue("Different Articles on latest technology");
		feed.setSubtitle(subtitle);

		Date postDate = new Date();
		feed.setUpdated(postDate);

		Entry entry = new Entry();

		Link link = new Link();
		link.setHref("https://howtodoinjava.com/spring5/webmvc/spring-mvc-cors-configuration/");
		entry.setAlternateLinks(Collections.singletonList(link));
		SyndPerson author = new Person();
		author.setName("Lokesh Gupta");
		entry.setAuthors(Collections.singletonList(author));
		entry.setCreated(postDate);
		entry.setPublished(postDate);
		entry.setUpdated(postDate);
		entry.setId("https://howtodoinjava.com/spring5/webmvc/spring-mvc-cors-configuration/");
		entry.setTitle("spring-mvc-cors-configuration");

		Category category = new Category();
		category.setTerm("CORS");
		entry.setCategories(Collections.singletonList(category));

		Content summary = new Content();
		summary.setType("text/plain");
		summary.setValue(
				"CORS helps in serving web content from multiple domains into browsers who usually have the same-origin security policy. In this example, we will learn to enable CORS support in Spring MVC application at method and global level."
						+ "The post <a rel=\"nofollow\" href=\"https://howtodoinjava.com/spring5/webmvc/spring-mvc-cors-configuration/\">Spring CORS Configuration Examples</a> appeared first on <a rel=\"nofollow\" href=\"https://howtodoinjava.com\">HowToDoInJava</a>.");
		entry.setSummary(summary);

		feed.setEntries(Collections.singletonList(entry));
		//Like more Entries here about different new topics
		return feed;
	}
}

示例

启动 spring boot 应用程序,使用mvn clean install进行 maven 构建,然后使用java -jar target\spring-boot-rss-feed-example-0.0.1-SNAPSHOT.jar命令启动应用程序。 这将在默认端口8080中启动一台 tomcat 服务器,并将在其中部署应用程序。

现在,从浏览器转到 http://localhost:8080/rss 和 http://localhost:8080/atom,您应该会看到 RSS 和 Atom 提要中的主题已添加到控制器中。

RSS Feed

RSS feed

Atom Feed

Atom Feed

创建 RSS Feed 阅读器

我们已经有很多 Feed 阅读器可用,但是如果您需要以编程方式使用此 Feed,也可以使用 ROAM 库通过以下几行代码来完成此操作。

package com.example.howtodoinjava.rss;

import java.net.URL;
import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.io.SyndFeedInput;
import com.rometools.rome.io.XmlReader;

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

		try {
			String url = "http://localhost:8080/rss";

			try (XmlReader reader = new XmlReader(new URL(url))) {
				SyndFeed feed = new SyndFeedInput().build(reader);
				System.out.println(feed.getTitle());
				System.out.println("***********************************");
				for (SyndEntry entry : feed.getEntries()) {
					System.out.println(entry);
					System.out.println("***********************************");
				}
				System.out.println("Done");
			}
		}  catch (Exception e) {
			e.printStackTrace();
		}

	}
}

这里的 URL 用于 RSS feed,如果将 URL 更改为 Atom feed,同样的代码也可以工作。

输出

这是 feed 客户端的控制台输出。

HowToDoInJava Feed
***********************************
SyndEntryImpl.comments=https://howtodoinjava.com/spring5/webmvc/spring-mvc-cors-configuration/#respond
SyndEntryImpl.author=Lokesh Gupta
SyndEntryImpl.wireEntry=null
SyndEntryImpl.link=https://howtodoinjava.com/spring5/webmvc/spring-mvc-cors-configuration/
SyndEntryImpl.description.mode=null
SyndEntryImpl.description.type=text/html
SyndEntryImpl.description.interface=interface com.rometools.rome.feed.synd.SyndContent
SyndEntryImpl.description.value=CORS helps in serving web content from multiple domains into browsers who usually have the same-origin security policy. In this example, we will learn to enable CORS support in Spring MVC application at method and global level.The post <a rel="nofollow" href="https://howtodoinjava.com/spring5/webmvc/spring-mvc-cors-configuration/">Spring CORS Configuration Examples</a> appeared first on <a rel="nofollow" href="https://howtodoinjava.com">HowToDoInJava</a>.
SyndEntryImpl.foreignMarkup=[]
SyndEntryImpl.source=null
SyndEntryImpl.updatedDate=null
SyndEntryImpl.title=Spring CORS Configuration Examples
SyndEntryImpl.interface=interface com.rometools.rome.feed.synd.SyndEntry
SyndEntryImpl.uri=https://howtodoinjava.com/spring5/webmvc/spring-mvc-cors-configuration/
SyndEntryImpl.enclosures=[]
SyndEntryImpl.modules[0].date=Sat Oct 14 10:57:12 IST 2017
SyndEntryImpl.modules[0].formats=[]
SyndEntryImpl.modules[0].sources=[]
SyndEntryImpl.modules[0].rightsList=[]
SyndEntryImpl.modules[0].subject=null
SyndEntryImpl.modules[0].creators[0]=Lokesh Gupta
SyndEntryImpl.modules[0].description=null
SyndEntryImpl.modules[0].language=null
SyndEntryImpl.modules[0].source=null
SyndEntryImpl.modules[0].type=null
SyndEntryImpl.modules[0].title=null
SyndEntryImpl.modules[0].interface=interface com.rometools.rome.feed.module.DCModule
SyndEntryImpl.modules[0].descriptions=[]
SyndEntryImpl.modules[0].coverages=[]
SyndEntryImpl.modules[0].relation=null
SyndEntryImpl.modules[0].contributor=null
SyndEntryImpl.modules[0].rights=null
SyndEntryImpl.modules[0].publishers=[]
SyndEntryImpl.modules[0].coverage=null
SyndEntryImpl.modules[0].identifier=null
SyndEntryImpl.modules[0].creator=Lokesh Gupta
SyndEntryImpl.modules[0].types=[]
SyndEntryImpl.modules[0].languages=[]
SyndEntryImpl.modules[0].identifiers=[]
SyndEntryImpl.modules[0].subjects=[]
SyndEntryImpl.modules[0].format=null
SyndEntryImpl.modules[0].dates[0]=Sat Oct 14 10:57:12 IST 2017
SyndEntryImpl.modules[0].titles=[]
SyndEntryImpl.modules[0].uri=http://purl.org/dc/elements/1.1/
SyndEntryImpl.modules[0].publisher=null
SyndEntryImpl.modules[0].contributors=[]
SyndEntryImpl.modules[0].relations=[]
SyndEntryImpl.contents=[]
SyndEntryImpl.links=[]
SyndEntryImpl.publishedDate=Sat Oct 14 10:57:12 IST 2017
SyndEntryImpl.contributors=[]
SyndEntryImpl.categories[0].taxonomyUri=null
SyndEntryImpl.categories[0].name=CORS
SyndEntryImpl.categories[0].interface=interface com.rometools.rome.feed.synd.SyndCategory
SyndEntryImpl.titleEx.mode=null
SyndEntryImpl.titleEx.type=null
SyndEntryImpl.titleEx.interface=interface com.rometools.rome.feed.synd.SyndContent
SyndEntryImpl.titleEx.value=Spring CORS Configuration Examples
SyndEntryImpl.authors=[]
***********************************
Done

总结

在此示例中,我们了解到如何轻松地将 RSS 和 Atom feed 配置到我们的 spring boot 项目中。 我们也看到了如何从 Java 代码中使用它们。 就是今天的话题。 我在这里附上完整的 Eclipse 项目,以供您参考。

请在评论部分添加您的反馈。

下载源码

学习愉快!

Spring Boot ehcache 2 示例

原文: https://howtodoinjava.com/spring-boot/ehcache2-config-example/

了解如何使用 ehcache 2.x 在 spring boot 应用程序中配置缓存。 学习使用基于注解的缓存配置以及使用CacheManager手动更新缓存。

1. Maven 依赖

在此示例中,我们使用的是 Spring 运行版本1.5.13.RELEASE。 较早的 spring boot 版本支持net.sf.ehcache软件包下的ehcache 2.x

我们需要以下依赖项来添加缓存功能。

  • spring-boot-starter-cache
  • ehcachenet.sf.ehcache
  • 缓存 APIjavax.cache

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.company</groupId>
	<artifactId>SpringBootEhcache</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

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

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

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<skipTests>true</skipTests>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>
		<dependency>
			<groupId>net.sf.ehcache</groupId>
			<artifactId>ehcache</artifactId>
		</dependency>
		<dependency>
			<groupId>javax.cache</groupId>
			<artifactId>cache-api</artifactId>
		</dependency>
	</dependencies>

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

2. ehcache.xml

Spring Boot 会自动检测classpath中是否存在ehcache.xml并对其进行配置。 在这里,我们提供缓存名称,到期时间等。

在 ehcache 文档中找到属性的完整列表。

ehcache.xml

<ehcache>
	<diskStore path="java.io.tmpdir" />

	<defaultCache maxElementsInMemory="2000" 
			eternal="true"
			overflowToDisk="false" 
			timeToLiveSeconds="1200" />

	<cache name="employeeCache" 
			maxElementsInMemory="2000"
			eternal="false" 
			overflowToDisk="false" 
			timeToLiveSeconds="10000" />
</ehcache>

3. @EnableCaching

它启用 Spring 的注解驱动的缓存管理功能,并在调用@Cacheable注解方法时启用对拦截器的支持。

CacheConfig.java

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class CacheConfig {

}

4. @Cacheable注解

要缓存从方法调用返回的数据,我们可以在方法上使用@Cacheable注解。 使用其属性cacheNameskey来指代缓存和缓存条目的键属性。

EmployeeManager.java

import java.util.HashMap;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class EmployeeManager 
{
	static HashMap<Long, Employee> db = new HashMap<>();

	static {
		db.put(1L, new Employee(1L, "Alex", "Gussin"));
		db.put(2L, new Employee(2L, "Brian", "Schultz"));
	}

	@Cacheable(cacheNames="employeeCache", key="#id")
	public Employee getEmployeeById(Long id) {
		System.out.println("Getting employee from DB");
		return db.get(id);
	}
}

5. CacheManager API

有时,在使用注解似乎不是完美解决方案的情况下,我们可能需要使用缓存。 在这种情况下,我们可以使用org.springframework.cache.CacheManagerorg.springframework.cache.Cache抽象来访问并利用 ehcache 添加和访问缓存条目。

要使用CacheManager,我们必须首先自动装配到 spring 组件中,并使用它的getCache(name)方法来按名称获取缓存实例。

访问缓存后,我们可以使用其get()put()方法来添加和访问缓存条目。

//1

@Autowired
private CacheManager cacheManager;

//2

Cache cache = cacheManager.getCache("myCache");
cache.put(3L, "Hello");
String value = cache.get(3L).get();

6. Spring boot ehcache 2 示例 – 演示

我正在创建一个模型类Employee,其实例将被缓存。

Employee.java

import java.io.Serializable;

public class Employee implements Serializable 
{
	private static final long serialVersionUID = 5517244812959569947L;

	private Long id;
	private String firstName;
	private String lastName;

	public Employee() {
		super();
	}

	public Employee(Long id, String firstName, String lastName) {
		super();
		this.id = id;
		this.firstName = firstName;
		this.lastName = lastName;
	}

	//Getters and setters

	@Override
	public String toString() {
		return "Employee [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + "]";
	}
}

现在在演示应用程序类中,我正在使用@Cacheable注解测试基于注解的缓存访问,并使用CacheManager进行手动缓存访问。

Application.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

	@Autowired
	private CacheManager cacheManager;

	@Autowired
	private EmployeeManager employeeManager;

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

	@Bean
	public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
		return args -> {

			//This will hit the database 
			System.out.println(employeeManager.getEmployeeById(1L));

			//This will hit the cache - verify the message in console output 
			System.out.println(employeeManager.getEmployeeById(1L));

			//Access cache instance by name
			Cache cache = cacheManager.getCache("employeeCache");

			//Add entry to cache
			cache.put(3L, "Hello");

			//Get entry from cache
			System.out.println(cache.get(3L).get());
		};
	}
}

程序输出。

Console

Getting employee from DB
Employee [id=1, firstName=Alex, lastName=Gussin]

Employee [id=1, firstName=Alex, lastName=Gussin]		//Fetched from cache

Hello

请在评论中使用 ehcache 将有关此 spring boot 缓存示例的问题交给我。

学习愉快!

下载源码

SpringBatch

Spring Batch + Spring Boot Java 配置示例

原文: https://howtodoinjava.com/spring-batch/java-config-multiple-steps/

学习使用 Java 配置创建 Spring Batch 作业(具有多个步骤)。 它使用 Spring Boot 2Spring Batch 4H2 数据库来执行批处理作业。

项目结构

在这个项目中,我们将创建一个包含两步任务的简单作业,并执行该作业以观察日志。 作业执行流程将是:

  1. 开始工作
  2. 执行任务一
  3. 执行任务二
  4. 完成工作

Spring Batch Java Config Example

Spring Batch Java 配置示例

Maven 依赖

我们需要包括spring-boot-starter-batch依赖。 Spring Batch 依赖于持久性数据存储的作业存储库。 因此,我们也需要一个数据库。 我正在使用 H2(内存数据库),它与 Spring Batch 很好地集成在一起。

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>

添加任务

第一步是创建一些任务,这些任务将按一定顺序运行以形成作业。 在 Spring Batch 中,它们实现为Tasklet

MyTaskOne.java

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;

public class MyTaskOne implements Tasklet {

	public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception 
	{
		System.out.println("MyTaskOne start..");

	    // ... your code

	    System.out.println("MyTaskOne done..");
	    return RepeatStatus.FINISHED;
	}    
}

MyTaskTwo.java

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;

public class MyTaskTwo implements Tasklet {

	public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception 
	{
		System.out.println("MyTaskTwo start..");

	    // ... your code

	    System.out.println("MyTaskTwo done..");
	    return RepeatStatus.FINISHED;
	}    
}

Spring Batch 配置

这是主要步骤,您可以定义所有与作业相关的配置及其执行逻辑。

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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.howtodoinjava.demo.tasks.MyTaskOne;
import com.howtodoinjava.demo.tasks.MyTaskTwo;

@Configuration
@EnableBatchProcessing
public class BatchConfig {

	@Autowired
	private JobBuilderFactory jobs;

	@Autowired
	private StepBuilderFactory steps;

	@Bean
	public Step stepOne(){
	    return steps.get("stepOne")
	            .tasklet(new MyTaskOne())
	            .build();
	}

	@Bean
	public Step stepTwo(){
	    return steps.get("stepTwo")
	            .tasklet(new MyTaskTwo())
	            .build();
	}   

	@Bean
	public Job demoJob(){
	    return jobs.get("demoJob")
	    		.incrementer(new RunIdIncrementer())
	            .start(stepOne())
	            .next(stepTwo())
	            .build();
	}
}

示例

现在,我们的简单作业'demoJob'已配置并准备执行。 当应用程序完全启动时,我正在使用CommandLineRunner界面通过JobLauncher自动执行作业。

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.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App implements CommandLineRunner
{
	@Autowired
	JobLauncher jobLauncher;

	@Autowired
	Job job;

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

	@Override
	public void run(String... args) throws Exception 
	{
		JobParameters params = new JobParametersBuilder()
					.addString("JobID", String.valueOf(System.currentTimeMillis()))
					.toJobParameters();
		jobLauncher.run(job, params);
	}
}

注意控制台日志。

Console Logs

o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=demoJob]] launched with 
the following parameters: [{JobID=1530697766768}]

o.s.batch.core.job.SimpleStepHandler     : Executing step: [stepOne]
MyTaskOne start..
MyTaskOne done..

o.s.batch.core.job.SimpleStepHandler     : Executing step: [stepTwo]
MyTaskTwo start..
MyTaskTwo done..

o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=demoJob]] completed with 
the following parameters: [{JobID=1530697766768}] and the following status: [COMPLETED]

Spring 还自动运行配置的批处理作业。 要禁用作业的自动运行,您需要使用application.properties文件中的spring.batch.job.enabled属性。

application.properties

spring.batch.job.enabled=false

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

学习愉快!

Spring Batch 事件监听器

原文: https://howtodoinjava.com/spring-batch/spring-batch-event-listeners/

学习创建和配置 Spring Batch 的JobExecutionListener(作业前后),StepExecutionListener(步骤前后),ItemReadListenerItemProcessListenerItemWriteListenerSkipListener实现。

JobExecutionListener

JobExecutionListener监听器示例

JobExecutionListener Example

import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;

public class JobResultListener implements JobExecutionListener {

	public void beforeJob(JobExecution jobExecution) {
		System.out.println("Called beforeJob().");
	}

	public void afterJob(JobExecution jobExecution) {
		System.out.println("Called afterJob().");
	}
}

如何配置JobExecutionListener

JobExecutionListener Configuration

@Bean
public Job demoJob(){
    return jobs.get("demoJob")
            .incrementer(new RunIdIncrementer())
            .listener(new JobResultListener())
            .start(stepOne())
            .next(stepTwo())
            .build();
}

StepExecutionListener

StepExecutionListener监听器示例

StepExecutionListener Example

import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;

public class StepResultListener implements StepExecutionListener {

	@Override
	public void beforeStep(StepExecution stepExecution) {
		System.out.println("Called beforeStep().");
	}

	@Override
	public ExitStatus afterStep(StepExecution stepExecution) {
		System.out.println("Called afterStep().");
		return ExitStatus.COMPLETED;
	}
}

如何配置StepExecutionListener

StepExecutionListener Configuration

@Bean
public Step stepOne(){
    return steps.get("stepOne")
            .tasklet(new MyTaskOne())
            .listener(new StepResultListener())
            .build();
}

@Bean
public Step stepTwo(){
    return steps.get("stepTwo")
            .tasklet(new MyTaskTwo())
            .listener(new StepResultListener())
            .build();
}  

ItemReadListener

ItemReadListener监听器示例

ItemReadListener Example

import org.springframework.batch.core.ItemReadListener;

public class StepItemReadListener implements ItemReadListener<String> {

	@Override
	public void beforeRead() {
		System.out.println("ItemReadListener - beforeRead");
	}

	@Override
	public void afterRead(String item) {
		System.out.println("ItemReadListener - afterRead");
	}

	@Override
	public void onReadError(Exception ex) {
		System.out.println("ItemReadListener - onReadError");
	}
}

如何配置ItemReadListener

ItemReadListener Configuration

@Bean
public Step stepOne(){
    return steps.get("stepOne")
            .tasklet(new MyTaskOne())
            .listener(new StepItemReadListener())
            .build();
}

ItemProcessListener

ItemProcessListener监听器示例

ItemProcessListener Example

import org.springframework.batch.core.ItemProcessListener;

public class StepItemProcessListener implements ItemProcessListener<String, Number> {

	@Override
	public void beforeProcess(String item) {
		System.out.println("ItemProcessListener - beforeProcess");
	}

	@Override
	public void afterProcess(String item, Number result) {
		System.out.println("ItemProcessListener - afterProcess");
	}

	@Override
	public void onProcessError(String item, Exception e) {
		System.out.println("ItemProcessListener - onProcessError");
	}
}

如何配置ItemProcessListener

ItemProcessListener Configuration

@Bean
public Step stepOne(){
    return steps.get("stepOne")
            .tasklet(new MyTaskOne())
            .listener(new StepItemProcessListener())
            .build();
}

ItemWriteListener

ItemWriteListener监听器示例

ItemWriteListener Example

import java.util.List;
import org.springframework.batch.core.ItemWriteListener;

public class StepItemWriteListener implements ItemWriteListener<Number> {

	@Override
	public void beforeWrite(List<? extends Number> items) {
		System.out.println("ItemWriteListener - beforeWrite");
	}

	@Override
	public void afterWrite(List<? extends Number> items) {
		System.out.println("ItemWriteListener - afterWrite");
	}

	@Override
	public void onWriteError(Exception exception, List<? extends Number> items) {
		System.out.println("ItemWriteListener - onWriteError");
	}
}

如何配置ItemWriteListener

ItemWriteListener Configuration

@Bean
public Step stepOne(){
    return steps.get("stepOne")
            .tasklet(new MyTaskOne())
            .listener(new StepItemWriteListener())
            .build();
}

SkipListener

SkipListener监听器示例

SkipListener Example

import org.springframework.batch.core.SkipListener;

public class StepSkipListener implements SkipListener<String, Number> {

	@Override
	public void onSkipInRead(Throwable t) {
		System.out.println("StepSkipListener - onSkipInRead");
	}

	@Override
	public void onSkipInWrite(Number item, Throwable t) {
		System.out.println("StepSkipListener - afterWrite");
	}

	@Override
	public void onSkipInProcess(String item, Throwable t) {
		System.out.println("StepSkipListener - onWriteError");
	}
}

如何配置SkipListener

SkipListener Configuration

@Bean
public Step stepOne(){
    return steps.get("stepOne")
            .tasklet(new MyTaskOne())
            .listener(new StepSkipListener())
            .build();
}

使用和实现非常简单。 在评论部分让我知道您的问题。

学习愉快!

参考文献:

  1. JobExecutionListener JavaDoc
  2. StepExecutionListener JavaDoc
  3. ItemReadListener JavaDoc
  4. ItemProcessListener JavaDoc
  5. ItemWriteListener JavaDoc
  6. StepSkipListener JavaDoc

Spring Batch ItemProcessor示例

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

在读取输入并将其传递给写入器以写入文件/数据库之前,学习使用ItemProcessor添加业务逻辑。 请注意,虽然返回的数据类型可能不同于输入提供的数据类型,但这不是必需的。

ItemProcessor返回null表示不应继续处理该项目。

如何编写ItemProcessor

下面给出的ItemProcessor实现执行以下任务:

  • 验证是否设置了'id'字段。
  • 验证'id'字段是否可解析为整数。
  • 验证'id'字段是否为大于零的正整数。
  • 如果验证失败,则返回null,表示不处理记录。
  • 如果验证成功,则按原样返回Employee对象。

ValidationProcessor.java

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

public class ValidationProcessor implements ItemProcessor<Employee,Employee> 
{
	public Employee process(Employee employee) throws Exception 
	{
		if (employee.getId() == null){
			System.out.println("Missing employee id : " + employee.getId());
			return null;
		} 

		try 
		{
			if(Integer.valueOf(employee.getId()) <= 0) {
				System.out.println("Invalid employee id : " + employee.getId());
				return null;
			}
		}
		catch (NumberFormatException e) {
			System.out.println("Invalid employee id : " + employee.getId());
			return null;
		}
		return employee;
	}
}

如何使用ItemProcessor

在步骤中设置 Tasklet 的过程中,应使用SimpleStepBuilder.processor()设置处理器实例。

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.ItemProcessor;
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())
				.processor(processor())
				.writer(writer())
				.build();
	}

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

	@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>();
	}
}

ItemProcessor演示

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

inputData.csv

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

开始作业并查看控制台。

Console

2018-07-11 14:59:00 INFO  - Job: [SimpleJob: [name=readCSVFilesJob]] launched with the following parameters: [{JobID=1531301340005}]

2018-07-11 14:59: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]

Invalid employee id : abc

Employee [id=4, firstName=David, lastName=Walsh]

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

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

学习愉快!

参考: ItemProcessor Java 文档

使用 Spring TaskScheduler进行 Spring Batch 作业调度

原文: https://howtodoinjava.com/spring-batch/job-scheduler-example/

在企业应用程序中,您将需要将 cron 表达式传递给 Spring TaskScheduler,在固定的时间表上定期执行 Spring Batch 作业。 在此示例中,我们将使用 spring 的内置调度功能执行示例 Spring Batch 作业。

配置批处理作业调度程序

要配置,批处理作业调度分两个步骤完成:

  1. 启用带有@EnableScheduling注解的调度。
  2. 创建带有@Scheduled注解的方法,并使用 cron 作业提供重复详细信息。 在此方法内添加作业执行逻辑。

Cron scheduling on spring batch job

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);
	}
}

批处理作业将在应用程序启动后每隔一分钟运行一次。

要引用作业和任务源代码,请阅读 Spring batch Java 配置示例。

示例

现在,如果您运行该应用程序并验证日志,您将看到该作业每分钟都在运行。

Console

2018-07-04 15:57:00.073  INFO 7320 --- [pool-1-thread-1] o.s.b.c.l.support.SimpleJobLauncher      
: Job: [SimpleJob: [name=demoJob]] launched with the following parameters: [{JobID=1530700020003}]

2018-07-04 15:57:00.097  INFO 7320 --- [pool-1-thread-1] o.s.batch.core.job.SimpleStepHandler     
: Executing step: [stepOne]

MyTaskOne start..
MyTaskOne done..

2018-07-04 15:57:00.118  INFO 7320 --- [pool-1-thread-1] o.s.batch.core.job.SimpleStepHandler     
: Executing step: [stepTwo]

MyTaskTwo start..
MyTaskTwo done..

2018-07-04 15:57:00.125  INFO 7320 --- [pool-1-thread-1] o.s.b.c.l.support.SimpleJobLauncher      
: Job: [SimpleJob: [name=demoJob]] completed with the following parameters: [{JobID=1530700020003}] 
and the following status: [COMPLETED]

2018-07-04 15:58:00.007  INFO 7320 --- [pool-1-thread-1] o.s.b.c.l.support.SimpleJobLauncher      
: Job: [SimpleJob: [name=demoJob]] launched with the following parameters: [{JobID=1530700080002}]

2018-07-04 15:58:00.011  INFO 7320 --- [pool-1-thread-1] o.s.batch.core.job.SimpleStepHandler     
: Executing step: [stepOne]

MyTaskOne start..
MyTaskOne done..

2018-07-04 15:58:00.021  INFO 7320 --- [pool-1-thread-1] o.s.batch.core.job.SimpleStepHandler     
: Executing step: [stepTwo]

MyTaskTwo start..
MyTaskTwo done..

2018-07-04 15:58:00.029  INFO 7320 --- [pool-1-thread-1] o.s.b.c.l.support.SimpleJobLauncher      
: Job: [SimpleJob: [name=demoJob]] completed with the following parameters: [{JobID=1530700080002}] 
and the following status: [COMPLETED]

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

学习愉快!

Spring Batch Quartz Java 配置示例

原文: https://howtodoinjava.com/spring-batch/batch-quartz-java-config-example/

学习配置Quartz调度程序以运行使用 Spring Boot Java 配置配置的 Spring Batch 作业。 尽管 Spring 的默认调度程序也不错,但是 Quartz 可以更好地并且以更可配置的方式更好地调度和调用任务。 这使 Spring Batch 仅专注于创建批处理作业,而让 Quartz 执行它们。

项目概述和结构

在此带有 Quartz 示例的 SpringBatch 中,我们将创建两个具有不同步骤数的不同批处理作业。 然后,我们将使用夸脱作业数据映射和触发器安排作业执行时间,并在控制台中观察输出。

Spring Batch Quartz Java Config Example

Spring Batch Quartz Java 配置示例

Maven 依赖

Spring Boot 具有对 Quartz 的内置支持,因此您所需要做的就是导入依赖项,例如spring-boot-starter-quartzspring-boot-starter-batch

请注意,quartz 需要至少一个数据库来存储作业执行详细信息。 在这个例子中,我使用的是 Spring Boot 支持的H2数据库。 因此也包括'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-quartz</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>

创建具有多个任务的批处理作业

  1. 创建任务

    首先创建几个小任务。 这些是在单个批处理作业中一步处理的策略。

    MyTaskOne.java

    import org.springframework.batch.core.StepContribution;
    import org.springframework.batch.core.scope.context.ChunkContext;
    import org.springframework.batch.core.step.tasklet.Tasklet;
    import org.springframework.batch.repeat.RepeatStatus;
    
    public class MyTaskOne implements Tasklet {
    
    	public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
    		System.out.println("MyTaskOne start..");
    
    	    // ... your code
    
    	    System.out.println("MyTaskOne done..");
    	    return RepeatStatus.FINISHED;
    	}    
    }
    
    

    MyTaskTwo.java

    public class MyTaskTwo implements Tasklet {
    
    	public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
    		System.out.println("MyTaskTwo start..");
    
    	    // ... your code
    
    	    System.out.println("MyTaskTwo done..");
    	    return RepeatStatus.FINISHED;
    	}    
    }
    
    
  2. 创建批处理作业

    现在创建 SpringBatch,并在其中配置步骤。 我正在创建两个批处理作业。

    • demoJobOne具有 2 个步骤,即stepOnestepTwo
    • demoJobTwo和一个步骤,即stepOne

    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.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import com.howtodoinjava.demo.tasks.MyTaskOne;
    import com.howtodoinjava.demo.tasks.MyTaskTwo;
    
    @Configuration
    @EnableBatchProcessing
    public class BatchConfig {
    
    	@Autowired
    	private JobBuilderFactory jobs;
    
    	@Autowired
    	private StepBuilderFactory steps;
    
    	@Bean
    	public Step stepOne(){
    	    return steps.get("stepOne")
    	            .tasklet(new MyTaskOne())
    	            .build();
    	}
    
    	@Bean
    	public Step stepTwo(){
    	    return steps.get("stepTwo")
    	            .tasklet(new MyTaskTwo())
    	            .build();
    	}   
    
    	@Bean(name="demoJobOne")
    	public Job demoJobOne(){
    	    return jobs.get("demoJobOne")
    	            .start(stepOne())
    	            .next(stepTwo())
    	            .build();
    	}
    
    	@Bean(name="demoJobTwo")
    	public Job demoJobTwo(){
    	    return jobs.get("demoJobTwo")
    	            .flow(stepOne())
    	            .build()
    	            .build();
    	}
    }
    
    

使用QuartzJobBean创建 Quartz 作业运行器

现在我们需要创建标准的QuartzJobBean实例,该实例将用于执行批处理作业。

CustomQuartzJob.java

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class CustomQuartzJob extends QuartzJobBean {

	private String jobName;
	private JobLauncher jobLauncher;
	private JobLocator jobLocator;

	public String getJobName() {
		return jobName;
	}

	public void setJobName(String jobName) {
		this.jobName = jobName;
	}

	public JobLauncher getJobLauncher() {
		return jobLauncher;
	}

	public void setJobLauncher(JobLauncher jobLauncher) {
		this.jobLauncher = jobLauncher;
	}

	public JobLocator getJobLocator() {
		return jobLocator;
	}

	public void setJobLocator(JobLocator jobLocator) {
		this.jobLocator = jobLocator;
	}

	@Override
	protected void executeInternal(JobExecutionContext context) throws JobExecutionException 
	{
		try 
		{
			Job job = jobLocator.getJob(jobName);
			JobParameters params = new JobParametersBuilder()
					.addString("JobID", String.valueOf(System.currentTimeMillis()))
					.toJobParameters();

			jobLauncher.run(job, params);
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}
}

SchedulerFactoryBean中配置 Quartz 作业详细信息和触发器

  1. 现在可以配置 QuartzJobDetailtriggers,并将它们注册到 spring 的上下文中。
  2. JobDetail实例包含有关QuartzJobBean的信息和要使用JobDataMap注入的信息。
  3. 还创建Trigger实例以配置批处理作业的执行时间和频率。
  4. 最后,在SchedulerFactoryBean中添加作业详细信息和触发器,以配置 Spring 并管理其生命周期(作为 Spring 应用程序上下文的一部分)。 它会在初始化时自动启动调度程序,并在销毁时将其关闭。

QuartzConfig.java

import java.io.IOException;
import java.util.Properties;

import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import com.howtodoinjava.demo.jobs.CustomQuartzJob;

@Configuration
public class QuartzConfig 
{
	@Autowired
	private JobLauncher jobLauncher;

	@Autowired
	private JobLocator jobLocator;

	@Bean
    public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
        JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor();
        jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry);
        return jobRegistryBeanPostProcessor;
    }

	@Bean
	public JobDetail jobOneDetail() {
		//Set Job data map
		JobDataMap jobDataMap = new JobDataMap();
		jobDataMap.put("jobName", "demoJobOne");
		jobDataMap.put("jobLauncher", jobLauncher);
		jobDataMap.put("jobLocator", jobLocator);

		return JobBuilder.newJob(CustomQuartzJob.class)
				.withIdentity("demoJobOne")
				.setJobData(jobDataMap)
				.storeDurably()
				.build();
	}

	@Bean
	public JobDetail jobTwoDetail() {
		//Set Job data map
		JobDataMap jobDataMap = new JobDataMap();
		jobDataMap.put("jobName", "demoJobTwo");
		jobDataMap.put("jobLauncher", jobLauncher);
		jobDataMap.put("jobLocator", jobLocator);

		return JobBuilder.newJob(CustomQuartzJob.class)
				.withIdentity("demoJobTwo")
				.setJobData(jobDataMap)
				.storeDurably()
				.build();
	}

	@Bean
	public Trigger jobOneTrigger() 
	{
		SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
				.simpleSchedule()
				.withIntervalInSeconds(10)
				.repeatForever();

		return TriggerBuilder
				.newTrigger()
				.forJob(jobOneDetail())
				.withIdentity("jobOneTrigger")
				.withSchedule(scheduleBuilder)
				.build();
	}

	@Bean
	public Trigger jobTwoTrigger() 
	{
		SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
				.simpleSchedule()
				.withIntervalInSeconds(20)
				.repeatForever();

		return TriggerBuilder
				.newTrigger()
				.forJob(jobTwoDetail())
				.withIdentity("jobTwoTrigger")
				.withSchedule(scheduleBuilder)
				.build();
	}

	@Bean
	public SchedulerFactoryBean schedulerFactoryBean() throws IOException 
	{
		SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
		scheduler.setTriggers(jobOneTrigger(), jobTwoTrigger());
		scheduler.setQuartzProperties(quartzProperties());
		scheduler.setJobDetails(jobOneDetail(), jobTwoDetail());
		return scheduler;
	}

	@Bean
	public Properties quartzProperties() throws IOException 
	{
		PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
		propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
		propertiesFactoryBean.afterPropertiesSet();
		return propertiesFactoryBean.getObject();
	}
}

使用属性文件自定义 Quartz

在 Quartz 中,您可以从简单的属性文件中控制很多东西。 例如

quartz.properties

#scheduler name will be "MyScheduler"
org.quartz.scheduler.instanceName = MyScheduler

#maximum of 3 jobs can be run simultaneously
org.quartz.threadPool.threadCount = 3

#All of Quartz data is held in memory (rather than in a database). 
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

示例

在运行应用程序之前,请确保已在application.properties文件中禁用了批处理作业自动启动功能。

application.properties

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

#enable h2 databse console
spring.h2.console.enabled=true

现在,将该应用程序作为 Spring Batch 应用程序运行,并检查日志。

App.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

Console

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.3.RELEASE)

2018-07-05 10:50:53 INFO  - Starting App on FFC15B4E9C5AA with PID 9740 (C:\Users\zkpkhua\DigitalDashboard_Integrated\App\target\classes started by zkpkhua in C:\Users\zkpkhua\DigitalDashboard_Integrated\App)
2018-07-05 10:50:53 INFO  - No active profile set, falling back to default profiles: default
2018-07-05 10:50:53 INFO  - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@78b1cc93: startup date [Thu Jul 05 10:50:53 IST 2018]; root of context hierarchy
2018-07-05 10:50:54 INFO  - Bean 'org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO  - Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$EnhancerBySpringCGLIB$169797f6] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO  - Bean 'org.springframework.boot.context.properties.ConversionServiceDeducer$Factory' of type [org.springframework.boot.context.properties.ConversionServiceDeducer$Factory] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO  - Bean 'spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO  - Bean 'dataSource' of type [com.zaxxer.hikari.HikariDataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO  - Bean 'org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO  - Bean 'org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration' of type [org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$EnhancerBySpringCGLIB$165a725c] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO  - Bean 'jobLauncher' of type [com.sun.proxy.$Proxy41] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO  - Bean 'jobRegistry' of type [com.sun.proxy.$Proxy43] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:54 INFO  - Bean 'quartzConfig' of type [com.howtodoinjava.demo.config.QuartzConfig$EnhancerBySpringCGLIB$7dc0f057] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-05 10:50:55 INFO  - HikariPool-1 - Starting...
2018-07-05 10:50:55 INFO  - HikariPool-1 - Start completed.
2018-07-05 10:50:55 INFO  - No database type set, using meta data indicating: H2
2018-07-05 10:50:55 INFO  - No TaskExecutor has been set, defaulting to synchronous executor.
2018-07-05 10:50:56 INFO  - Executing SQL script from class path resource [org/quartz/impl/jdbcjobstore/tables_h2.sql]
2018-07-05 10:50:56 INFO  - Executed SQL script from class path resource [org/quartz/impl/jdbcjobstore/tables_h2.sql] in 69 ms.
2018-07-05 10:50:56 INFO  - Using default implementation for ThreadExecutor
2018-07-05 10:50:56 INFO  - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2018-07-05 10:50:56 INFO  - Quartz Scheduler v.2.3.0 created.
2018-07-05 10:50:56 INFO  - RAMJobStore initialized.
2018-07-05 10:50:56 INFO  - Scheduler meta-data: Quartz Scheduler (v2.3.0) 'schedulerFactoryBean' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

2018-07-05 10:50:56 INFO  - Quartz scheduler 'schedulerFactoryBean' initialized from an externally provided properties instance.
2018-07-05 10:50:56 INFO  - Quartz scheduler version: 2.3.0
2018-07-05 10:50:56 INFO  - JobFactory set to: org.springframework.scheduling.quartz.AdaptableJobFactory@1e7f2e0f
2018-07-05 10:50:56 INFO  - Executing SQL script from class path resource [org/springframework/batch/core/schema-h2.sql]
2018-07-05 10:50:56 INFO  - Executed SQL script from class path resource [org/springframework/batch/core/schema-h2.sql] in 30 ms.
2018-07-05 10:50:57 INFO  - Registering beans for JMX exposure on startup
2018-07-05 10:50:57 INFO  - Bean with name 'dataSource' has been autodetected for JMX exposure
2018-07-05 10:50:57 INFO  - Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
2018-07-05 10:50:57 INFO  - Starting beans in phase 2147483647
2018-07-05 10:50:57 INFO  - Starting Quartz Scheduler now
2018-07-05 10:50:57 INFO  - Scheduler schedulerFactoryBean_$_NON_CLUSTERED started.
2018-07-05 10:50:57 INFO  - Started App in 4.164 seconds (JVM running for 4.941)
2018-07-05 10:50:57 INFO  - Job: [SimpleJob: [name=demoJobOne]] launched with the following parameters: [{JobID=1530768057055}]
2018-07-05 10:50:57 INFO  - Job: [FlowJob: [name=demoJobTwo]] launched with the following parameters: [{JobID=1530768057057}]
2018-07-05 10:50:57 INFO  - Executing step: [stepOne]
MyTaskOne start..
MyTaskOne done..
2018-07-05 10:50:57 INFO  - Executing step: [stepTwo]
MyTaskTwo start..
MyTaskTwo done..
2018-07-05 10:50:57 INFO  - Executing step: [stepOne]
2018-07-05 10:50:57 INFO  - Job: [SimpleJob: [name=demoJobOne]] completed with the following parameters: [{JobID=1530768057055}] and the following status: [COMPLETED]
MyTaskOne start..
MyTaskOne done..
2018-07-05 10:50:57 INFO  - Job: [FlowJob: [name=demoJobTwo]] completed with the following parameters: [{JobID=1530768057057}] and the following status: [COMPLETED]

2018-07-05 10:51:05 INFO  - Job: [SimpleJob: [name=demoJobOne]] launched with the following parameters: [{JobID=1530768065955}]
2018-07-05 10:51:05 INFO  - Executing step: [stepOne]
MyTaskOne start..
MyTaskOne done..
2018-07-05 10:51:05 INFO  - Executing step: [stepTwo]
MyTaskTwo start..
MyTaskTwo done..
2018-07-05 10:51:05 INFO  - Job: [SimpleJob: [name=demoJobOne]] completed with the following parameters: [{JobID=1530768065955}] and the following status: [COMPLETED]

2018-07-05 10:51:15 INFO  - Job: [SimpleJob: [name=demoJobOne]] launched with the following parameters: [{JobID=1530768075955}]
2018-07-05 10:51:15 INFO  - Executing step: [stepOne]
MyTaskOne start..
MyTaskOne done..
2018-07-05 10:51:15 INFO  - Executing step: [stepTwo]
MyTaskTwo start..
MyTaskTwo done..
2018-07-05 10:51:15 INFO  - Job: [SimpleJob: [name=demoJobOne]] completed with the following parameters: [{JobID=1530768075955}] and the following status: [COMPLETED]

2018-07-05 10:51:15 INFO  - Job: [FlowJob: [name=demoJobTwo]] launched with the following parameters: [{JobID=1530768075980}]
2018-07-05 10:51:15 INFO  - Executing step: [stepOne]
MyTaskOne start..
MyTaskOne done..
2018-07-05 10:51:16 INFO  - Job: [FlowJob: [name=demoJobTwo]] completed with the following parameters: [{JobID=1530768075980}] and the following status: [COMPLETED]

显然,两个 Spring Batch 作业都基于在 Quartz 触发器中配置的调度执行。

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

学习愉快!

Spring Batch + Quartz + H2 Jdbcjobstore 示例

原文: https://howtodoinjava.com/spring-batch/quartz-h2-jdbcjobstore-example/

学习使用 Quartz 调度程序和 Quartz 用于记录作业和触发信息的持久性数据库存储记录来执行多个 Spring Batch 作业。 我在启用 Web 控制台的情况下使用H2数据库来查看数据库表中的数据。 您可以根据需要选择数据库。

项目结构

目标

在本教程中,我们将创建一个 Spring 应用程序并执行以下任务。

  1. 创建 2 个 Spring Batch 作业。 每个作业都有多个步骤。
  2. 创建 Quartz 作业和触发器,它们将运行在步骤 1 中创建的 SpringBatch。
  3. Quartz 作业和触发器将存储在文件系统中的持久性 H2 数据库中。
  4. 通过 H2 控制台验证存储在 DB 中的触发信息。
  5. 使用LoggingTriggerHistoryPlugin在日志中记录完整的触发器历史记录。

项目结构

Project Structure

项目结构

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-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-batch</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-quartz</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>com.mchange</groupId>
			<artifactId>c3p0</artifactId>
			<version>0.9.5.2</version>
		</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>

配置 Spring Batch 作业和任务

创建一些Tasklets和批处理作业以执行。

MyTaskOne.java and MyTaskTwo.java

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;

public class MyTaskOne implements Tasklet {

	public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
		System.out.println("MyTaskOne start..");

	    // ... your code

	    System.out.println("MyTaskOne done..");
	    return RepeatStatus.FINISHED;
	}    
}

public class MyTaskTwo implements Tasklet {

	public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
		System.out.println("MyTaskTwo start..");

	    // ... your code

	    System.out.println("MyTaskTwo done..");
	    return RepeatStatus.FINISHED;
	}    
}

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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.howtodoinjava.demo.tasks.MyTaskOne;
import com.howtodoinjava.demo.tasks.MyTaskTwo;

@Configuration
@EnableBatchProcessing
public class BatchConfig {

	@Autowired
	private JobBuilderFactory jobs;

	@Autowired
	private StepBuilderFactory steps;

	@Bean
	public Step stepOne(){
	    return steps.get("stepOne")
	            .tasklet(new MyTaskOne())
	            .build();
	}

	@Bean
	public Step stepTwo(){
	    return steps.get("stepTwo")
	            .tasklet(new MyTaskTwo())
	            .build();
	}   

	@Bean(name="demoJobOne")
	public Job demoJobOne(){
	    return jobs.get("demoJobOne")
	            .start(stepOne())
	            .next(stepTwo())
	            .build();
	}

	@Bean(name="demoJobTwo")
	public Job demoJobTwo(){
	    return jobs.get("demoJobTwo")
	            .flow(stepOne())
	            .build()
	            .build();
	}
}

application.properties

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

配置 Quartz 作业和触发器

现在创建 Quartz 作业,它将运行 SpringBatch。

CustomQuartzJob.java

import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class CustomQuartzJob extends QuartzJobBean {

	private String jobName;
	private JobLauncher jobLauncher;
	private JobLocator jobLocator;

	public String getJobName() {
		return jobName;
	}

	public void setJobName(String jobName) {
		this.jobName = jobName;
	}

	@Override
	protected void executeInternal(JobExecutionContext context) throws JobExecutionException 
	{
		try 
		{
			ApplicationContext applicationContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext");
			jobLocator = (JobLocator) applicationContext.getBean(JobLocator.class);
			jobLauncher = (JobLauncher) applicationContext.getBean(JobLauncher.class);
			Job job = jobLocator.getJob(jobName);
			JobParameters params = new JobParametersBuilder()
					.addString("JobID", String.valueOf(System.currentTimeMillis()))
					.toJobParameters();

			jobLauncher.run(job, params);
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}
}

QuartzConfig.java

import java.io.IOException;
import java.util.Properties;

import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import com.howtodoinjava.demo.jobs.CustomQuartzJob;

@Configuration
public class QuartzConfig
{	
	@Bean
    public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
        JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor();
        jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry);
        return jobRegistryBeanPostProcessor;
    }

	@Bean
	public JobDetail jobOneDetail() {
		//Set Job data map
		JobDataMap jobDataMap = new JobDataMap();
		jobDataMap.put("jobName", "demoJobOne");

		return JobBuilder.newJob(CustomQuartzJob.class)
				.withIdentity("demoJobOne",null)
				.setJobData(jobDataMap)
				.storeDurably()
				.build();
	}

	@Bean
	public JobDetail jobTwoDetail() {
		//Set Job data map
		JobDataMap jobDataMap = new JobDataMap();
		jobDataMap.put("jobName", "demoJobTwo");

		return JobBuilder.newJob(CustomQuartzJob.class)
				.withIdentity("demoJobTwo",null)
				.setJobData(jobDataMap)
				.storeDurably()
				.build();
	}

	@Bean
	public Trigger jobOneTrigger() 
	{
		SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
				.simpleSchedule()
				.withIntervalInSeconds(10)
				.repeatForever();

		return TriggerBuilder
				.newTrigger()
				.forJob(jobOneDetail())
				.withIdentity("jobOneTrigger",null)
				.withSchedule(scheduleBuilder)
				.build();
	}

	@Bean
	public Trigger jobTwoTrigger() 
	{
		SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
				.simpleSchedule()
				.withIntervalInSeconds(20)
				.repeatForever();

		return TriggerBuilder
				.newTrigger()
				.forJob(jobTwoDetail())
				.withIdentity("jobTwoTrigger",null)
				.withSchedule(scheduleBuilder)
				.build();
	}

	@Bean
	public SchedulerFactoryBean schedulerFactoryBean() throws IOException, SchedulerException 
	{
		SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
		scheduler.setTriggers(jobOneTrigger(), jobTwoTrigger());
		scheduler.setQuartzProperties(quartzProperties());
		scheduler.setJobDetails(jobOneDetail(), jobTwoDetail());
		scheduler.setApplicationContextSchedulerContextKey("applicationContext");
		return scheduler;
	}

	public Properties quartzProperties() throws IOException 
	{
		PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
		propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
		propertiesFactoryBean.afterPropertiesSet();
		return propertiesFactoryBean.getObject();
	}
}

quartz.properties

#scheduler name will be "MyScheduler"
org.quartz.scheduler.instanceName=TestScheduler
org.quartz.scheduler.instanceId=AUTO

#maximum of 3 jobs can be run simultaneously
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=50

#Log trigger history
org.quartz.plugin.triggerHistory.class=org.quartz.plugins.history.LoggingTriggerHistoryPlugin
org.quartz.plugin.triggerHistory.triggerFiredMessage=Trigger [{1}.{0}] fired job [{6}.{5}] scheduled at: {2, date, dd-MM-yyyy HH:mm:ss.SSS}, next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}
org.quartz.plugin.triggerHistory.triggerCompleteMessage=Trigger [{1}.{0}] completed firing job [{6}.{5}] with resulting trigger instruction code: {9}. Next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}
org.quartz.plugin.triggerHistory.triggerMisfiredMessage=Trigger [{1}.{0}] misfired job [{6}.{5}]. Should have fired at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}

配置 H2 数据库

使用quartz.properties文件中的属性将 quartz 配置为使用 H2 数据库。

quartz.properties

#Quartz persistent jobStore config
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix=QRTZ_	
org.quartz.jobStore.dataSource=myDS
org.quartz.jobStore.useProperties=false
org.quartz.jobStore.isClustered=false

#Quartz dataSource
org.quartz.dataSource.myDS.driver=org.h2.Driver
org.quartz.dataSource.myDS.URL=jdbc:h2:file:~/h2/testdb;INIT=RUNSCRIPT FROM 'classpath:schema.sql'
org.quartz.dataSource.myDS.user=sa
org.quartz.dataSource.myDS.password =
org.quartz.dataSource.myDS.maxConnections=5
org.quartz.dataSource.myDS.validationQuery=select 1

Quartz 不会自动在数据库中创建必要的表,您需要在应用程序启动时创建它们。 我已经使用了上面的org.quartz.dataSource.myDS.URL属性。 模式查询位于schema.sql文件中。

schema.sql

DROP TABLE  IF EXISTS QRTZ_CALENDARS;
DROP TABLE  IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE  IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE  IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE  IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE  IF EXISTS QRTZ_LOCKS;
DROP TABLE  IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE  IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE  IF EXISTS qrtz_simprop_triggers;
DROP TABLE  IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE  IF EXISTS QRTZ_TRIGGERS;
DROP TABLE  IF EXISTS qrtz_simprop_triggers;

CREATE TABLE QRTZ_CALENDARS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  CALENDAR_NAME VARCHAR (200)  NOT NULL ,
  CALENDAR IMAGE NOT NULL
);

CREATE TABLE QRTZ_CRON_TRIGGERS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  TRIGGER_NAME VARCHAR (200)  NOT NULL ,
  TRIGGER_GROUP VARCHAR (200)  NOT NULL ,
  CRON_EXPRESSION VARCHAR (120)  NOT NULL ,
  TIME_ZONE_ID VARCHAR (80) 
);

CREATE TABLE QRTZ_FIRED_TRIGGERS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  ENTRY_ID VARCHAR (95)  NOT NULL ,
  TRIGGER_NAME VARCHAR (200)  NOT NULL ,
  TRIGGER_GROUP VARCHAR (200)  NOT NULL ,
  INSTANCE_NAME VARCHAR (200)  NOT NULL ,
  FIRED_TIME BIGINT NOT NULL ,
  SCHED_TIME BIGINT NOT NULL ,
  PRIORITY INTEGER NOT NULL ,
  STATE VARCHAR (16)  NOT NULL,
  JOB_NAME VARCHAR (200)  NULL ,
  JOB_GROUP VARCHAR (200)  NULL ,
  IS_NONCONCURRENT BOOLEAN  NULL ,
  REQUESTS_RECOVERY BOOLEAN  NULL 
);

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  TRIGGER_GROUP VARCHAR (200)  NOT NULL 
);

CREATE TABLE QRTZ_SCHEDULER_STATE (
  SCHED_NAME VARCHAR(120) NOT NULL,
  INSTANCE_NAME VARCHAR (200)  NOT NULL ,
  LAST_CHECKIN_TIME BIGINT NOT NULL ,
  CHECKIN_INTERVAL BIGINT NOT NULL
);

CREATE TABLE QRTZ_LOCKS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  LOCK_NAME VARCHAR (40)  NOT NULL 
);

CREATE TABLE QRTZ_JOB_DETAILS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  JOB_NAME VARCHAR (200)  NOT NULL ,
  JOB_GROUP VARCHAR (200)  NOT NULL ,
  DESCRIPTION VARCHAR (250) NULL ,
  JOB_CLASS_NAME VARCHAR (250)  NOT NULL ,
  IS_DURABLE BOOLEAN  NOT NULL ,
  IS_NONCONCURRENT BOOLEAN  NOT NULL ,
  IS_UPDATE_DATA BOOLEAN  NOT NULL ,
  REQUESTS_RECOVERY BOOLEAN  NOT NULL ,
  JOB_DATA IMAGE NULL
);

CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  TRIGGER_NAME VARCHAR (200)  NOT NULL ,
  TRIGGER_GROUP VARCHAR (200)  NOT NULL ,
  REPEAT_COUNT BIGINT NOT NULL ,
  REPEAT_INTERVAL BIGINT NOT NULL ,
  TIMES_TRIGGERED BIGINT NOT NULL
);

CREATE TABLE qrtz_simprop_triggers
  (          
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INTEGER NULL,
    INT_PROP_2 INTEGER NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 BOOLEAN NULL,
    BOOL_PROP_2 BOOLEAN NULL,
);

CREATE TABLE QRTZ_BLOB_TRIGGERS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  TRIGGER_NAME VARCHAR (200)  NOT NULL ,
  TRIGGER_GROUP VARCHAR (200)  NOT NULL ,
  BLOB_DATA IMAGE NULL
);

CREATE TABLE QRTZ_TRIGGERS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  TRIGGER_NAME VARCHAR (200)  NOT NULL ,
  TRIGGER_GROUP VARCHAR (200)  NOT NULL ,
  JOB_NAME VARCHAR (200)  NOT NULL ,
  JOB_GROUP VARCHAR (200)  NOT NULL ,
  DESCRIPTION VARCHAR (250) NULL ,
  NEXT_FIRE_TIME BIGINT NULL ,
  PREV_FIRE_TIME BIGINT NULL ,
  PRIORITY INTEGER NULL ,
  TRIGGER_STATE VARCHAR (16)  NOT NULL ,
  TRIGGER_TYPE VARCHAR (8)  NOT NULL ,
  START_TIME BIGINT NOT NULL ,
  END_TIME BIGINT NULL ,
  CALENDAR_NAME VARCHAR (200)  NULL ,
  MISFIRE_INSTR SMALLINT NULL ,
  JOB_DATA IMAGE NULL
);

ALTER TABLE QRTZ_CALENDARS  ADD
  CONSTRAINT PK_QRTZ_CALENDARS PRIMARY KEY  
  (
    SCHED_NAME,
    CALENDAR_NAME
  );

ALTER TABLE QRTZ_CRON_TRIGGERS  ADD
  CONSTRAINT PK_QRTZ_CRON_TRIGGERS PRIMARY KEY  
  (
    SCHED_NAME,
    TRIGGER_NAME,
    TRIGGER_GROUP
  );

ALTER TABLE QRTZ_FIRED_TRIGGERS  ADD
  CONSTRAINT PK_QRTZ_FIRED_TRIGGERS PRIMARY KEY  
  (
    SCHED_NAME,
    ENTRY_ID
  );

ALTER TABLE QRTZ_PAUSED_TRIGGER_GRPS  ADD
  CONSTRAINT PK_QRTZ_PAUSED_TRIGGER_GRPS PRIMARY KEY  
  (
    SCHED_NAME,
    TRIGGER_GROUP
  );

ALTER TABLE QRTZ_SCHEDULER_STATE  ADD
  CONSTRAINT PK_QRTZ_SCHEDULER_STATE PRIMARY KEY  
  (
    SCHED_NAME,
    INSTANCE_NAME
  );

ALTER TABLE QRTZ_LOCKS  ADD
  CONSTRAINT PK_QRTZ_LOCKS PRIMARY KEY  
  (
    SCHED_NAME,
    LOCK_NAME
  );

ALTER TABLE QRTZ_JOB_DETAILS  ADD
  CONSTRAINT PK_QRTZ_JOB_DETAILS PRIMARY KEY  
  (
    SCHED_NAME,
    JOB_NAME,
    JOB_GROUP
  );

ALTER TABLE QRTZ_SIMPLE_TRIGGERS  ADD
  CONSTRAINT PK_QRTZ_SIMPLE_TRIGGERS PRIMARY KEY  
  (
    SCHED_NAME,
    TRIGGER_NAME,
    TRIGGER_GROUP
  );

ALTER TABLE QRTZ_SIMPROP_TRIGGERS  ADD
  CONSTRAINT PK_QRTZ_SIMPROP_TRIGGERS PRIMARY KEY  
  (
    SCHED_NAME,
    TRIGGER_NAME,
    TRIGGER_GROUP
  );

ALTER TABLE QRTZ_TRIGGERS  ADD
  CONSTRAINT PK_QRTZ_TRIGGERS PRIMARY KEY  
  (
    SCHED_NAME,
    TRIGGER_NAME,
    TRIGGER_GROUP
  );

ALTER TABLE QRTZ_CRON_TRIGGERS ADD
  CONSTRAINT FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY
  (
    SCHED_NAME,
    TRIGGER_NAME,
    TRIGGER_GROUP
  ) REFERENCES QRTZ_TRIGGERS (
    SCHED_NAME,
    TRIGGER_NAME,
    TRIGGER_GROUP
  ) ON DELETE CASCADE;

ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD
  CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY
  (
    SCHED_NAME,
    TRIGGER_NAME,
    TRIGGER_GROUP
  ) REFERENCES QRTZ_TRIGGERS (
    SCHED_NAME,
    TRIGGER_NAME,
    TRIGGER_GROUP
  ) ON DELETE CASCADE;

ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD
  CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY
  (
    SCHED_NAME,
    TRIGGER_NAME,
    TRIGGER_GROUP
  ) REFERENCES QRTZ_TRIGGERS (
    SCHED_NAME,
    TRIGGER_NAME,
    TRIGGER_GROUP
  ) ON DELETE CASCADE;

ALTER TABLE QRTZ_TRIGGERS ADD
  CONSTRAINT FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS FOREIGN KEY
  (
    SCHED_NAME,
    JOB_NAME,
    JOB_GROUP
  ) REFERENCES QRTZ_JOB_DETAILS (
    SCHED_NAME,
    JOB_NAME,
    JOB_GROUP
  );

COMMIT;

运行演示

配置日志记录并启动应用程序

为了格式化日志语句,我创建了自定义logback.xml

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>

作为 Spring 启动应用程序启动该应用程序。

App.java

package com.howtodoinjava.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

验证作业执行

在服务器控制台中验证正在执行的 Spring Batch 作业。

Console

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.3.RELEASE)

2018-07-06 09:29:07 INFO  - Starting App on MACHINE-ID with PID 13456 (C:\Users\lokesh\workspace\App\target\classes started by lokesh in C:\Users\lokesh\workspace\App)
2018-07-06 09:29:07 INFO  - No active profile set, falling back to default profiles: default
2018-07-06 09:29:08 INFO  - Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@23529fee: startup date [Fri Jul 06 09:29:08 IST 2018]; root of context hierarchy
2018-07-06 09:29:10 INFO  - Bean 'quartzConfig' of type [com.howtodoinjava.demo.config.QuartzConfig$EnhancerBySpringCGLIB$78eca775] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-06 09:29:10 INFO  - Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$EnhancerBySpringCGLIB$11c34f14] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-06 09:29:10 INFO  - Bean 'org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-06 09:29:10 INFO  - Bean 'org.springframework.boot.context.properties.ConversionServiceDeducer$Factory' of type [org.springframework.boot.context.properties.ConversionServiceDeducer$Factory] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-06 09:29:10 INFO  - Bean 'spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-06 09:29:10 INFO  - Bean 'dataSource' of type [com.zaxxer.hikari.HikariDataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-06 09:29:10 INFO  - HikariPool-1 - Starting...
2018-07-06 09:29:11 INFO  - HikariPool-1 - Start completed.
2018-07-06 09:29:11 INFO  - Executing SQL script from URL [file:/C:/Users/lokesh/workspace/App/target/classes/schema.sql]
2018-07-06 09:29:11 INFO  - Executed SQL script from URL [file:/C:/Users/lokesh/workspace/App/target/classes/schema.sql] in 66 ms.
2018-07-06 09:29:11 INFO  - Bean 'org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-06 09:29:11 INFO  - Bean 'org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration' of type [org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$EnhancerBySpringCGLIB$1186297a] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-06 09:29:11 INFO  - Bean 'jobRegistry' of type [com.sun.proxy.$Proxy49] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-07-06 09:29:12 INFO  - Tomcat initialized with port(s): 8080 (http)
2018-07-06 09:29:12 INFO  - Initializing ProtocolHandler ["http-nio-8080"]
2018-07-06 09:29:12 INFO  - Starting service [Tomcat]
2018-07-06 09:29:12 INFO  - Starting Servlet Engine: Apache Tomcat/8.5.31
2018-07-06 09:29:12 INFO  - Initializing Spring embedded WebApplicationContext
2018-07-06 09:29:12 INFO  - Root WebApplicationContext: initialization completed in 4505 ms
2018-07-06 09:29:12 INFO  - Servlet webServlet mapped to [/console/*]
2018-07-06 09:29:12 INFO  - Servlet dispatcherServlet mapped to [/]
2018-07-06 09:29:12 INFO  - Mapping filter: 'characterEncodingFilter' to: [/*]
2018-07-06 09:29:12 INFO  - Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-07-06 09:29:12 INFO  - Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-07-06 09:29:12 INFO  - Mapping filter: 'requestContextFilter' to: [/*]
2018-07-06 09:29:13 INFO  - No database type set, using meta data indicating: H2
2018-07-06 09:29:13 INFO  - No TaskExecutor has been set, defaulting to synchronous executor.
2018-07-06 09:29:13 INFO  - Executing SQL script from class path resource [org/quartz/impl/jdbcjobstore/tables_h2.sql]
2018-07-06 09:29:13 INFO  - Executed SQL script from class path resource [org/quartz/impl/jdbcjobstore/tables_h2.sql] in 21 ms.
2018-07-06 09:29:13 INFO  - Using ConnectionProvider class 'org.quartz.utils.C3p0PoolingConnectionProvider' for data source 'myDS'
2018-07-06 09:29:13 INFO  - MLog clients using slf4j logging.
2018-07-06 09:29:13 INFO  - Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
2018-07-06 09:29:13 INFO  - Using default implementation for ThreadExecutor
2018-07-06 09:29:14 INFO  - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2018-07-06 09:29:14 INFO  - Quartz Scheduler v.2.3.0 created.
2018-07-06 09:29:14 INFO  - Using thread monitor-based data access locking (synchronization).
2018-07-06 09:29:14 INFO  - JobStoreTX initialized.
2018-07-06 09:29:14 INFO  - Scheduler meta-data: Quartz Scheduler (v2.3.0) 'schedulerFactoryBean' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 50 threads.
  Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is not clustered.

2018-07-06 09:29:14 INFO  - Quartz scheduler 'schedulerFactoryBean' initialized from an externally provided properties instance.
2018-07-06 09:29:14 INFO  - Quartz scheduler version: 2.3.0
2018-07-06 09:29:14 INFO  - JobFactory set to: org.springframework.scheduling.quartz.AdaptableJobFactory@3db64bd4
2018-07-06 09:29:14 INFO  - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 8jgsvm9wug78a3djgl4a|73ab3aac, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> org.h2.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 8jgsvm9wug78a3djgl4a|73ab3aac, idleConnectionTestPeriod -> 50, initialPoolSize -> 3, jdbcUrl -> jdbc:h2:file:~/h2/testdb;INIT=RUNSCRIPT FROM 'classpath:schema.sql', maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 5, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, preferredTestQuery -> select 1, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> true, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
2018-07-06 09:29:15 INFO  - Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-07-06 09:29:15 INFO  - Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@23529fee: startup date [Fri Jul 06 09:29:08 IST 2018]; root of context hierarchy
2018-07-06 09:29:15 INFO  - Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-07-06 09:29:15 INFO  - Mapped "{[/error],produces=}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-07-06 09:29:15 INFO  - Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-07-06 09:29:15 INFO  - Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-07-06 09:29:15 INFO  - Executing SQL script from class path resource [org/springframework/batch/core/schema-h2.sql]
2018-07-06 09:29:16 INFO  - Executed SQL script from class path resource [org/springframework/batch/core/schema-h2.sql] in 105 ms.
2018-07-06 09:29:16 INFO  - Registering beans for JMX exposure on startup
2018-07-06 09:29:16 INFO  - Bean with name 'dataSource' has been autodetected for JMX exposure
2018-07-06 09:29:16 INFO  - Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
2018-07-06 09:29:16 INFO  - Starting beans in phase 2147483647
2018-07-06 09:29:16 INFO  - Starting Quartz Scheduler now
2018-07-06 09:29:16 INFO  - Freed 0 triggers from 'acquired' / 'blocked' state.
2018-07-06 09:29:16 INFO  - Recovering 0 jobs that were in-progress at the time of the last shut-down.
2018-07-06 09:29:16 INFO  - Recovery complete.
2018-07-06 09:29:16 INFO  - Removed 0 'complete' triggers.
2018-07-06 09:29:16 INFO  - Removed 0 stale fired job entries.
2018-07-06 09:29:16 INFO  - Scheduler schedulerFactoryBean_$_NON_CLUSTERED started.
2018-07-06 09:29:16 INFO  - Starting ProtocolHandler ["http-nio-8080"]
2018-07-06 09:29:16 INFO  - Using a shared selector for servlet write/read
2018-07-06 09:29:16 INFO  - Tomcat started on port(s): 8080 (http) with context path ''
2018-07-06 09:29:16 INFO  - Started App in 9.709 seconds (JVM running for 10.821)
2018-07-06 09:29:17 INFO  - Trigger [DEFAULT.jobOneTrigger] fired job [DEFAULT.demoJobOne] scheduled at:  06-07-2018 09:29:13.524, next scheduled at:  06-07-2018 09:29:23.524
2018-07-06 09:29:17 INFO  - Trigger [DEFAULT.jobTwoTrigger] fired job [DEFAULT.demoJobTwo] scheduled at:  06-07-2018 09:29:13.565, next scheduled at:  06-07-2018 09:29:33.565
2018-07-06 09:29:17 INFO  - Job: [FlowJob: [name=demoJobTwo]] launched with the following parameters: [{JobID=1530849557198}]
2018-07-06 09:29:17 INFO  - Executing step: [stepOne]
MyTaskOne start..
MyTaskOne done..
2018-07-06 09:29:17 INFO  - Job: [FlowJob: [name=demoJobTwo]] completed with the following parameters: [{JobID=1530849557198}] and the following status: [COMPLETED]
2018-07-06 09:29:17 INFO  - Trigger [DEFAULT.jobTwoTrigger] completed firing job [DEFAULT.demoJobTwo] with resulting trigger instruction code: DO NOTHING. Next scheduled at:  06-07-2018 09:29:33.565
2018-07-06 09:29:17 INFO  - Job: [SimpleJob: [name=demoJobOne]] launched with the following parameters: [{JobID=1530849557093}]
2018-07-06 09:29:17 INFO  - Executing step: [stepOne]
MyTaskOne start..
MyTaskOne done..
2018-07-06 09:29:17 INFO  - Executing step: [stepTwo]
MyTaskTwo start..
MyTaskTwo done..
2018-07-06 09:29:17 INFO  - Job: [SimpleJob: [name=demoJobOne]] completed with the following parameters: [{JobID=1530849557093}] and the following status: [COMPLETED]
2018-07-06 09:29:17 INFO  - Trigger [DEFAULT.jobOneTrigger] completed firing job [DEFAULT.demoJobOne] with resulting trigger instruction code: DO NOTHING. Next scheduled at:  06-07-2018 09:29:23.524
2018-07-06 09:29:23 INFO  - Trigger [DEFAULT.jobOneTrigger] fired job [DEFAULT.demoJobOne] scheduled at:  06-07-2018 09:29:23.524, next scheduled at:  06-07-2018 09:29:33.524
2018-07-06 09:29:23 INFO  - Job: [SimpleJob: [name=demoJobOne]] launched with the following parameters: [{JobID=1530849563538}]
2018-07-06 09:29:23 INFO  - Executing step: [stepOne]
MyTaskOne start..
MyTaskOne done..
2018-07-06 09:29:23 INFO  - Executing step: [stepTwo]
MyTaskTwo start..
MyTaskTwo done..
2018-07-06 09:29:23 INFO  - Job: [SimpleJob: [name=demoJobOne]] completed with the following parameters: [{JobID=1530849563538}] and the following status: [COMPLETED]
2018-07-06 09:29:23 INFO  - Trigger [DEFAULT.jobOneTrigger] completed firing job [DEFAULT.demoJobOne] with resulting trigger instruction code: DO NOTHING. Next scheduled at:  06-07-2018 09:29:33.524

验证 H2 控制台

使用浏览器 URL 访问 H2 控制台:http://localhost:8080/console/

H2 Console

H2 控制台

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

学习愉快!

在 Quartz 作业中注入 Spring 依赖项

原文: https://howtodoinjava.com/spring-batch/spring-beans-in-quartz-job/

默认情况下,Quartz 无法识别在applicationContext.xml或带有@Bean注解的 SpringBean。 如果尝试以 Quartz JobQuartzJobBean@Autowired这些 bean,您将得到NullPointerException

将 Spring 上下文注入QuartzJobBean

解决方案是将 Spring 的ApplicationContext实例注入到org.quartz.SchedulerContext中,该实例可通过executeInternal()方法的org.quartz.JobExecutionContext参数使用。

  1. ApplicationContext注入SchedulerContext

    这是典型的SchedulerFactoryBean bean 条目。 使用setApplicationContextSchedulerContextKey()方法。 此方法设置要在SchedulerContext中公开的ApplicationContext引用的键。

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException, SchedulerException 
    {
    	SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
    	scheduler.setTriggers(jobOneTrigger(), jobTwoTrigger());
    	scheduler.setQuartzProperties(quartzProperties());
    	scheduler.setJobDetails(jobOneDetail(), jobTwoDetail());
    	scheduler.setApplicationContextSchedulerContextKey("applicationContext");
    	return scheduler;
    }
    
    
  2. 在 Quartz QuartzJobBean 中访问注入 bean

    现在,您需要做的就是 – 获得applicationContext参考,并根据需要开始获取 bean。

    CustomQuartzJob.java

    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.springframework.batch.core.Job;
    import org.springframework.batch.core.JobParameters;
    import org.springframework.batch.core.JobParametersBuilder;
    import org.springframework.batch.core.configuration.JobLocator;
    import org.springframework.batch.core.launch.JobLauncher;
    import org.springframework.context.ApplicationContext;
    import org.springframework.scheduling.quartz.QuartzJobBean;
    
    public class CustomQuartzJob extends QuartzJobBean {
    
    	private String jobName;
    	private JobLauncher jobLauncher;
    	private JobLocator jobLocator;
    
    	public String getJobName() {
    		return jobName;
    	}
    
    	public void setJobName(String jobName) {
    		this.jobName = jobName;
    	}
    
    	@Override
    	protected void executeInternal(JobExecutionContext context) throws JobExecutionException 
    	{
    		try 
    		{
    			ApplicationContext applicationContext = (ApplicationContext) 
    							context.getScheduler().getContext().get("applicationContext");
    
    			jobLocator = (JobLocator) applicationContext.getBean(JobLocator.class);
    			jobLauncher = (JobLauncher) applicationContext.getBean(JobLauncher.class);
    
    			Job job = jobLocator.getJob(jobName);
    			JobParameters params = new JobParametersBuilder()
    					.addString("JobID", String.valueOf(System.currentTimeMillis()))
    					.toJobParameters();
    
    			jobLauncher.run(job, params);
    		} 
    		catch (Exception e) 
    		{
    			e.printStackTrace();
    		}
    	}
    }
    
    

现在,当您运行 Quartz 作业时,将通过executeInternal()方法获得jobLocatorjobLauncher实例。

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

学习愉快!