Spring

Spring

「转」Spring Boot 2.0 - WebFlux framework

Spring Framework泥瓦匠BYSocket 发表了文章 • 0 个评论 • 238 次浏览 • 4 天前 • 来自相关话题

1、介绍
1.1 什么是响应式编程(Reactive Programming)?

简单来说,响应式编程是针对异步和事件驱动的非阻塞应用程序,并且需要少量线程来垂直缩放(即在 JVM 内)而不是水平(即通过集群)。

响应式应用的一个关键方面是“背压(backpressure)”的概念,这是确保生产者不会压倒消费者的机制。例如,当HTTP连接太慢时,从数据库延伸到HTTP响应的反应组件的流水线、数据存储库也可以减慢或停止,直到网络容量释放。

响应式编程也导致从命令式到声明异步组合逻辑的重大转变。与使用Java 8的 CompletableFuture 编写封锁代码相比,可以通过 lambda
表达式编写后续操作。

1.2 响应式 API(Reactive API)和 构建块(Building Blocks)

Spring Framework 5 将 Reactive Streams 作为通过异步组件和库进行背压通信的合同。Reactive Streams 是通过行业协作创建的规范,也已在Java 9中被采用为 java.util.concurrent.Flow。

Spring Framework 在内部使用 Reactor 自己的响应支持。Reactor 是一个 Reactive Streams 实现,进一步扩展基本的 Reactive Streams Publisher 、Flux 和 Mono 可组合的API类型,以提供对 0..N 和 0..1 的数据序列的声明性操作。

Spring Framework 在许多自己的 Reactive API 中暴露了 Flux 和
Mono。然而,在应用级别,一如既往,Spring 提供了选择,并完全支持使用RxJava。有关的更多信息,请查看 Sebastien Deleuze 发表的 "Understanding Reactive Types" 。

2、Spring WebFlux 模块

Spring Framework 5 包括一个新的 spring-webflux 模块。该模块包含对响应式 HTTP 和 WebSocket 客户端的支持,以及对REST,HTML浏览器和 WebSocket风格交互的响应式服务器Web应用程序的支持。

2.1、服务器端

在服务器端 WebFlux 支持2种不同的编程模型:

基于注解的 @Controller 和其他注解也支持 Spring MVC
Functional 、Java 8 lambda 风格的路由和处理





 

WebFlux 可以在支持 Servlet 3.1 非阻塞 IO API 以及其他异步运行时(如 Netty 和 Undertow )的 Servlet 容器上运行。每个运行时都适用于响应型 ServerHttpRequest 和 ServerHttpResponse,将请求和响应的正文暴露为 Flux<DataBuffer>,而不是具有响应背压的InputStream 和 OutputStream 。顶部作为 Flux<Object> 支持REST风格的 JSON 和 XML 序列化和反序列化,HTML视图呈现和服务器发送事件也是如此。

基于注解的编程模式

WebFlux中也支持相同的 @Controller 编程模型和 Spring MVC 中使用的相同注解。主要区别在于底层核心框架契约(即 HandlerMapping HandlerAdapter )是非阻塞的,并且在响应型 ServerHttpRequest
和 ServerHttpResponse 上运行,而不是在 HttpServletRequest 和HttpServletResponse 上运行。以下是一个响应式 Controller 的例子:
$(document).ready(function() {$('pre code').each(function(i, block) { hljs.highlightBlock( block); }); });@RestController
public class PersonController {

private final PersonRepository repository;

public PersonController(PersonRepository repository) {
this.repository = repository;
}

@PostMapping("/person")
Mono<Void> create(@RequestBody Publisher<Person> personStream) {
return this.repository.save(personStream).then();
}

@GetMapping("/person")
Flux<Person> list() {
return this.repository.findAll();
}

@GetMapping("/person/{id}")
Mono<Person> findById(@PathVariable String id) {
return this.repository.findOne(id);
}
}


 
函数式编程模式

HandlerFunctions

传入的HTTP请求由 HandlerFunction 处理,HandlerFunction 本质上是一个接收 ServerRequest 并返回 Mono<ServerResponse> 的函数。处理函数的注解对应方法将是一个 @RequestMapping 的方法。

ServerRequest 和 ServerResponse 是提供JDK-8友好访问底层HTTP消息的不可变接口。两者都通过在反应堆顶部建立完全反应:请求将身体暴露为 Flux 或 Mono; 响应接受任何 Reactive Streams Publisher 作为主体。

ServerRequest 可以访问各种HTTP请求元素:方法,URI,查询参数,以及通过单独的ServerRequest.Headers 接口 - 头。通过 body方法 提供对 body 的访问。例如,这是如何将请求体提取为 Mono <String>:
Mono<String> string = request.bodyToMono(String.class);
 
这里是如何将身体提取为 Flux,其中 Person 是可以从body内容反序列化的类(即如果body包含JSON,则由Jackson支持,或者如果是XML,则为JAXB)。
Flux<Person> people = request.bodyToFlux(Person.class);
 
上面的两个方法(bodyToMono 和 bodyToFlux)实际上是使用通用ServerRequest.body(BodyExtractor)函数的便利方法。
BodyExtractor 是一个功能策略界面,允许您编写自己的提取逻辑,但在 BodyExtractors 实用程序类中可以找到常见的 BodyExtractor 实例。所以,上面的例子可以替换为:
Mono<String> string = request.body(BodyExtractors.toMono(String.class);
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class);
 
类似地,ServerResponse 提供对HTTP响应的访问。由于它是不可变的,您可以使用构建器创建一个 ServerResponse 。构建器允许您设置响应状态,添加响应标题并提供正文。例如,这是如何使用200 OK状态创建响应,JSON内容类型和正文:Mono<Person> person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);这里是如何使用201创建的状态,位置标题和空白体来构建响应:
URI location = ...
ServerResponse.created(location).build();
将这些组合在一起可以创建一个 HandlerFunction。例如,这里是一个简单的“Hello World”处理程序 lambda 的示例,它返回一个200状态的响应和一个基于 String 的主体:
HandlerFunction<ServerResponse> helloWorld = request -> ServerResponse.ok().body(fromObject("Hello World"));
 
使用 lambda 写处理函数,就像我们上面所说的那样很方便,但是在处理多个函数时可能缺乏可读性,变得不那么容易维护。因此,建议将相关处理函数分组到一个处理程序或控制器类中。例如,这是一个暴露了一个响应式的 Person 存储库的类:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;

public class PersonHandler {

private final PersonRepository repository;

public PersonHandler(PersonRepository repository) {
this.repository = repository;
}

// 1
public Mono<ServerResponse> listPeople(ServerRequest request) {
Flux<Person> people = repository.allPeople();
return ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Person.class);
}

// 2
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ServerResponse.ok().build(repository.savePerson(person));
}

// 3
public Mono<ServerResponse> getPerson(ServerRequest request) {
int personId = Integer.valueOf(request.pathVariable("id"));
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
Mono<Person> personMono = this.repository.getPerson(personId);
return personMono
.flatMap(person -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person)))
.switchIfEmpty(notFound);
}
}
1/ listPeople 是一个处理函数,它将数据库中发现的所有 Person
对象返回为JSON。

2/ createPerson 是一个处理函数,用于存储请求正文中包含的新Person。请注意,PersonRepository.savePerson(Person) 返回Mono<Void>:发出完成信号的空 Mono,当人从请求中读取并存储时,发出完成信号。因此,当接收到完成信号时,即当 Person 已被保存时,我们使用 build(Publisher<Void>) 方法来发送响应。

3/ getPerson 是一个处理函数,它通过路径变量id来标识一个人。我们通过数据库检索该Person,并创建一个JSON响应(如果找到)。如果没有找到,我们使用 switchIfEmpty(Mono<T>) 来返回 404 Not Found 响应。

RouterFunctions

传入请求将路由到处理函数,并使用一个 RouterFunction,它是一个服务器 ServerRequest 的函数,并返回一个 Mono<HandlerFunction>。如果请求与特定路由匹配,则返回处理函数; 否则返回一个空的 Mono。RouterFunction 与 @Controller 类中的 @RequestMapping 注解类似。

通常,您不要自己编写路由器功能,而是使用
RouterFunctions.route(RequestPredicate, HandlerFunction), 使用请求谓词和处理函数创建一个。如果谓词适用,请求将路由到给定的处理函数; 否则不执行路由,导致 404 Not Found 响应。虽然您可以编写自己的 RequestPredicate ,但是您不需要:RequestPredicates 实用程序类提供常用的谓词,基于路径,HTTP方法,内容类型等进行匹配。使用路由,我们可以路由到我们的 “Hello World” 处理函数:
RouterFunction<ServerResponse> helloWorldRoute =
RouterFunctions.route(RequestPredicates.path("/hello-world"),
request -> Response.ok().body(fromObject("Hello World")));

 
两个路由功能可以组成一个新的路由功能,路由到任一处理函数:如果第一个路由的谓词不匹配,则第二个被评估。组合的路由器功能按顺序进行评估,因此在通用功能之前放置特定功能是有意义的。您可以通过调用 RouterFunction.and(RouterFunction) 或通过调用
RouterFunction.andRoute(RequestPredicate, HandlerFunction) 来组成两个路由功能,这是 RouterFunction.and() 与 RouterFunctions.route() 的一种方便组合。

给定我们上面显示的 PersonH​​andler,我们现在可以定义路由功能,路由到相应的处理函数。我们使用 方法引用(method-references) 来引用处理函数:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> personRoute =
route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson)
.andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
.andRoute(POST("/person").and(contentType(APPLICATION_JSON)), handler::createPerson);

 
除路由功能之外,您还可以通过调用
RequestPredicate.and(RequestPredicate) 或
RequestPredicate.or(RequestPredicate) 来构成请求谓词。这些工作正如预期的那样:如果给定的谓词匹配,则生成的谓词匹配; 或者如果任一谓词都匹配。RequestPredicates 中发现的大多数谓词是组合的。例如,RequestPredicates.GET(String) 是
RequestPredicates.method(HttpMethod) 和RequestPredicates.path(String) 的组合。

启动服务器

现在只有一个难题遗留:在HTTP服务器中运行路由功能。您可以使用
RouterFunction<ServerResponse> route = ...
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create(HOST, PORT);
server.newHandler(adapter).block();
 
对于 Tomcat ,它看起来像这样:
RouterFunction<ServerResponse> route = ...
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler);
Tomcat server = new Tomcat();
Context rootContext = server.addContext("", System.getProperty("java.io.tmpdir"));
Tomcat.addServlet(rootContext, "servlet", servlet);
rootContext.addServletMapping("/", "servlet");
tomcatServer.start();
 
待完成:DispatcherHandler
HandlerFilterFunction
路由功能映射的路由可以通过调用
RouterFunction.filter(HandlerFilterFunction) 进行过滤,其中
HandlerFilterFunction 本质上是一个接收 ServerRequest 和
HandlerFunction 的函数,并返回一个 ServerResponse 。处理函数参数表示链中的下一个元素:通常是路由到的 HandlerFunction ,但是如果应用了多个过滤器,也可以是另一个 FilterFunction 。使用注解,可以使用 @ControllerAdvice 和 / 或 ServletFilter 来实现类似的功能。让我们在我们的路由中添加一个简单的安全过滤器,假设我们有一个 SecurityManager 可以确定是否允许特定的路径:
import static org.springframework.http.HttpStatus.UNAUTHORIZED;

SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = ...

RouterFunction<ServerResponse> filteredRoute =
route.filter(request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
});
 
在这个例子中可以看到,调用 next.handle(ServerRequest) 是可选的:我们只允许在允许访问时执行处理函数。

2.2 客户端(Client Side)

WebFlux 包括一个 functional, reactive WebClient,它为 RestTemplate 提供了一种完全无阻塞和响应式的替代方案。
它将网络输入和输出公开为客户端 HttpRequest 和ClientHttpResponse ,其中请求和响应的主体是 Flux <DataBuffer>而不是 InputStream 和 OutputStream。此外,它还支持与服务器端相同的响应式 JSON,XML和SSE 序列化机制,因此您可以使用类型化对象。以下是使用需要 ClientHttpConnector 实现的 WebClient 插入特定HTTP客户端(如 Reactor Netty)的示例:
WebClient client = WebClient.create("http://example.com");

Mono<Account> account = client.get()
.url("/accounts/{id}", 1L)
.accept(APPLICATION_JSON)
.exchange(request)
.flatMap(response -> response.bodyToMono(Account.class));
 
AsyncRestTemplate 还支持非阻塞交互。主要区别在于它不支持非阻塞流,例如 Twitter one ,因为它基本上仍然依赖于 InputStream 和
OutputStream。

2.4 请求体和响应体的转换(Request and Response Body Conversion)

spring-core 模块提供了响应式 Encoder (编码器) 和 Decoder (解码器),使得能够串行化字符串与类型对象的转换。spring-web 模块添加了 JSON(Jackson)和 XML(JAXB)实现,用于Web应用程序以及其他用于SSE流和零拷贝文件传输。

支持以下 Reactive API:

Reactor 3.x 支持开箱即用
io.reactivex.rxjava2:rxjava 依赖项在类路径上时支持 RxJava 2.x
当 ·io.reactivex:rxjava和io.reactivex:rxjava-reactive-streams`(RxJava 和 Reactive Streams 之间的适配器)依赖关系在类路径上时,支持 RxJava 1.x

例如,请求体可以是以下方式之一,它将在注解和功能编程模型中自动解码:

Account account - 在调用控制器之前,account 将无阻塞地被反序列化。
Mono<Account> account - controller 可以使用 Mono 来声明在反序列化 account 后执行的逻辑。
Single<Account> account - 和 Mono 类似,但是用的是 RxJava
Flux<Account> accounts - 输入流场景
Observable<Account> accounts - RxJava 的 输入流场景

响应体(response body)可以是以下之一:

Mono<Account> - 当 Mono 完成时,序列化而不阻塞给定的Account。
Single<Account> - 与上类似,但是使用的 RxJava
Flux<Account> - 流式场景,可能是SSE,具体取决于所请求的内容类型。
Observable<Account> - 与上类似, 但是使用的 RxJava Observable 类型
Flowable<Account> - 与上类似, 但是使用的 RxJava 2 Flowable 类型。
Publisher<Account> 或 Flow.Publisher<Account> - 支持任何实现Reactive Streams Publisher 的类型。
Flux<ServerSentEvent> - SSE 流。
Mono<Void> - 当 Mono 完成时,请求处理完成。
Account - 序列化而不阻塞给定的Account; 意味着同步、非阻塞的
Controller 方法。
Void - 特定于基于注解的编程模型,方法返回时,请求处理完成;
意味着同步、非阻塞的 Controller 方法。

当使用像 Flux 或 Observable 这样的流类型时,请求/响应或映射/路由级别中指定的媒体类型用于确定数据应如何序列化和刷新。例如,返回 Flux<Account> 的REST端点将默认序列化如下:

application/json : Flux<Account> 作为异步集合处理,并在完成事件发布时将其序列化为具有显式刷新的JSON数组。
application/stream+json : 一个 Flux<Account> 将作为一系列的Account 元素处理,作为以新行分隔的单个JSON对象,并在每个元素之后显式刷新。WebClient 支持JSON流解码,因此这对于服务器到服务器的用例来说是一个很好的用例。
text/event-stream : 一个 Flux<Account> 或 Flux<ServerSentEvent<Account >> 将作为一个 Stream 或ServerSentEvent 元素的流处理,作为单独的 SSE 元素,使用默认的JSON进行数据编码和每个元素之间的显式刷新。这非常适合将流暴露给浏览器客户端。WebClient 也支持读取SSE流。

2.4 响应式 Websocket 支持

WebFlux 包括响应式 WebSocket 客户端和服务器支持。Java WebSocket API(JSR-356),Jetty,Undertow和Reactor Netty都支持客户端和服务器。

在服务器端,声明一个 WebSocketHandlerAdapter,然后简单地添加映射到基于 WebSocketHandler 的端点:
@Bean
public HandlerMapping webSocketMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/foo", new FooWebSocketHandler());
map.put("/bar", new BarWebSocketHandler());

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

@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
在客户端,为上面列出的支持的库之一创建一个 WebSocketClient:
WebSocketClient client = new ReactorNettyWebSocketClient();
client.execute("ws://localhost:8080/echo"), session -> {... }).blockMillis(5000);
 
2.5 测试

spring-test 模块包括一个 WebTestClient,可用于测试具有或不具有正在运行的服务器的 WebFlux 服务器端点。

没有运行服务器的测试与来自Spring MVC的 MockMvc 相当,其中使用模拟请求和响应,而不是使用套接字通过网络连接。然而,WebTestClient 也可以针对正在运行的服务器执行测试。

更多请查看 sample tests

3、开始入门
3.1 Spring Boot Starter

通过 http://start.spring.io 提供的 Spring Boot WebFlux 启动器是最快的入门方式。它做所有必要的,所以你开始像Spring MVC一样编写@Controller类。只需转到 http://start.spring.io ,选择版本
2.0.0.BUILD-SNAPSHOT,并在依赖关系框中键入 respond。
默认情况下,启动器使用 Reactor Netty 运行,但依赖关系可以像往常一样通过 Spring Boot 更改为不同的运行时。有关更多详细信息和说明,请参阅 Spring Boo t参考文档页面。

3.2 手动引导(Manual Bootstrapping)

对于依赖关系,从 spring-webflux 和 spring-context 开始。
然后添加jackson-databind 和 io.netty:netty-buffer(暂时见SPR-14528)以获得JSON支持。最后添加一个支持的运行时的依赖项:

Tomcat — org.apache.tomcat.embed:tomcat-embed-core
Jetty — org.eclipse.jetty:jetty-server 和 org.eclipse.jetty:jetty-servlet
Reactor Netty — io.projectreactor.ipc:reactor-netty
Undertow — io.undertow:undertow-core

基于注解编程模式的引导:
ApplicationContext context = new AnnotationConfigApplicationContext(DelegatingWebFluxConfiguration.class); // (1)
HttpHandler handler = DispatcherHandler.toHttpHandler(context); // (2)
 
以上加载默认的 Spring Web 框架配置(1),然后创建一个DispatcherHandler,主类驱动请求处理(2),并适应 HttpHandler - 响应式HTTP请求处理的最低级别的Spring抽象。

函数编程模式的引导:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // (1)
context.registerBean(FooBean.class, () -> new FooBeanImpl()); // (2)
context.registerBean(BarBean.class); // (3)
context.refresh();

HttpHandler handler = WebHttpHandlerBuilder
.webHandler(RouterFunctions.toHttpHandler(...))
.applicationContext(context)
.build(); // (4)
以上创建了一个 AnnotationConfigApplicationContext 实例(1),可以利用新的功能 bean 注册API(2)使用 Java 8 供应商注册 bean,或者只需通过指定其类(3)即可。HttpHandler 是使用WebHttpHandlerBuilder(4)创建的。

然后可以将 HttpHandler 安装在支持的运行服务器之一中:
// Tomcat and Jetty (also see notes below)
HttpServlet servlet = new ServletHttpHandlerAdapter(handler);
...

// Reactor Netty
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create(host, port).newHandler(adapter).block();

// Undertow
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();
对于特别是使用 WAR 部署的 Servlet 容器,可以使用作为WebApplicationInitializer 的
AbstractAnnotationConfigDispatcherHandlerInitializer,并由 Servlet容器自动检测。它负责注册 ServletHttpHandlerAdapter ,如上所示。您将需要实现一个抽象方法来指向您的 Spring 配置。

3.3 Examples

您将在以下项目中找到有助于构建反应式 Web 应用程序的代码示例:

Functional programming model sample
Spring Reactive Playground: playground for most Spring Web reactive features
Reactor website: the spring-functional
branch is a Spring 5 functional, Java 8 lambda-style application
Spring Reactive University session: live-coded project from this Devoxx BE 2106 university talk
Reactive Thymeleaf Sandbox
Mix-it 2017 website: Kotlin + Reactive + Functional web and bean registration API application
Reactor by example: code snippets coming from this InfoQ article
Spring integration tests: various features tested with Reactor StepVerifier


Webflux 实战项目

demo地址: https://anyim.cfapps.io/

作者:Anoyi
链接:http://www.jianshu.com/p/f4ff6d74ad4a

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  查看全部
1、介绍
1.1 什么是响应式编程(Reactive Programming)?

简单来说,响应式编程是针对异步和事件驱动的非阻塞应用程序,并且需要少量线程来垂直缩放(即在 JVM 内)而不是水平(即通过集群)。

响应式应用的一个关键方面是“背压(backpressure)”的概念,这是确保生产者不会压倒消费者的机制。例如,当HTTP连接太慢时,从数据库延伸到HTTP响应的反应组件的流水线、数据存储库也可以减慢或停止,直到网络容量释放。

响应式编程也导致从命令式到声明异步组合逻辑的重大转变。与使用Java 8的 CompletableFuture 编写封锁代码相比,可以通过 lambda
表达式编写后续操作。

1.2 响应式 API(Reactive API)和 构建块(Building Blocks)

Spring Framework 5 将 Reactive Streams 作为通过异步组件和库进行背压通信的合同。Reactive Streams 是通过行业协作创建的规范,也已在Java 9中被采用为 java.util.concurrent.Flow。

Spring Framework 在内部使用 Reactor 自己的响应支持。Reactor 是一个 Reactive Streams 实现,进一步扩展基本的 Reactive Streams Publisher 、Flux 和 Mono 可组合的API类型,以提供对 0..N 和 0..1 的数据序列的声明性操作。

Spring Framework 在许多自己的 Reactive API 中暴露了 Flux 和
Mono。然而,在应用级别,一如既往,Spring 提供了选择,并完全支持使用RxJava。有关的更多信息,请查看 Sebastien Deleuze 发表的 "Understanding Reactive Types" 。

2、Spring WebFlux 模块

Spring Framework 5 包括一个新的 spring-webflux 模块。该模块包含对响应式 HTTP 和 WebSocket 客户端的支持,以及对REST,HTML浏览器和 WebSocket风格交互的响应式服务器Web应用程序的支持。

2.1、服务器端

在服务器端 WebFlux 支持2种不同的编程模型:

基于注解的 @Controller 和其他注解也支持 Spring MVC
Functional 、Java 8 lambda 风格的路由和处理

3424642-7922d13b6c20ee6e.png

 

WebFlux 可以在支持 Servlet 3.1 非阻塞 IO API 以及其他异步运行时(如 Netty 和 Undertow )的 Servlet 容器上运行。每个运行时都适用于响应型 ServerHttpRequest 和 ServerHttpResponse,将请求和响应的正文暴露为 Flux<DataBuffer>,而不是具有响应背压的InputStream 和 OutputStream 。顶部作为 Flux<Object> 支持REST风格的 JSON 和 XML 序列化和反序列化,HTML视图呈现和服务器发送事件也是如此。

基于注解的编程模式

WebFlux中也支持相同的 @Controller 编程模型和 Spring MVC 中使用的相同注解。主要区别在于底层核心框架契约(即 HandlerMapping HandlerAdapter )是非阻塞的,并且在响应型 ServerHttpRequest
和 ServerHttpResponse 上运行,而不是在 HttpServletRequest 和HttpServletResponse 上运行。以下是一个响应式 Controller 的例子:
@RestController
public class PersonController {

private final PersonRepository repository;

public PersonController(PersonRepository repository) {
this.repository = repository;
}

@PostMapping("/person")
Mono<Void> create(@RequestBody Publisher<Person> personStream) {
return this.repository.save(personStream).then();
}

@GetMapping("/person")
Flux<Person> list() {
return this.repository.findAll();
}

@GetMapping("/person/{id}")
Mono<Person> findById(@PathVariable String id) {
return this.repository.findOne(id);
}
}


 
函数式编程模式

HandlerFunctions

传入的HTTP请求由 HandlerFunction 处理,HandlerFunction 本质上是一个接收 ServerRequest 并返回 Mono<ServerResponse> 的函数。处理函数的注解对应方法将是一个 @RequestMapping 的方法。

ServerRequest 和 ServerResponse 是提供JDK-8友好访问底层HTTP消息的不可变接口。两者都通过在反应堆顶部建立完全反应:请求将身体暴露为 Flux 或 Mono; 响应接受任何 Reactive Streams Publisher 作为主体。

ServerRequest 可以访问各种HTTP请求元素:方法,URI,查询参数,以及通过单独的ServerRequest.Headers 接口 - 头。通过 body方法 提供对 body 的访问。例如,这是如何将请求体提取为 Mono <String>:
Mono<String> string = request.bodyToMono(String.class);

 
这里是如何将身体提取为 Flux,其中 Person 是可以从body内容反序列化的类(即如果body包含JSON,则由Jackson支持,或者如果是XML,则为JAXB)。
Flux<Person> people = request.bodyToFlux(Person.class);

 
上面的两个方法(bodyToMono 和 bodyToFlux)实际上是使用通用ServerRequest.body(BodyExtractor)函数的便利方法。
BodyExtractor 是一个功能策略界面,允许您编写自己的提取逻辑,但在 BodyExtractors 实用程序类中可以找到常见的 BodyExtractor 实例。所以,上面的例子可以替换为:
Mono<String> string = request.body(BodyExtractors.toMono(String.class); 
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class);

 
类似地,ServerResponse 提供对HTTP响应的访问。由于它是不可变的,您可以使用构建器创建一个 ServerResponse 。构建器允许您设置响应状态,添加响应标题并提供正文。例如,这是如何使用200 OK状态创建响应,JSON内容类型和正文:Mono<Person> person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);这里是如何使用201创建的状态,位置标题和空白体来构建响应:
URI location = ... 
ServerResponse.created(location).build();

将这些组合在一起可以创建一个 HandlerFunction。例如,这里是一个简单的“Hello World”处理程序 lambda 的示例,它返回一个200状态的响应和一个基于 String 的主体:
HandlerFunction<ServerResponse> helloWorld = request -> ServerResponse.ok().body(fromObject("Hello World"));

 
使用 lambda 写处理函数,就像我们上面所说的那样很方便,但是在处理多个函数时可能缺乏可读性,变得不那么容易维护。因此,建议将相关处理函数分组到一个处理程序或控制器类中。例如,这是一个暴露了一个响应式的 Person 存储库的类:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;

public class PersonHandler {

private final PersonRepository repository;

public PersonHandler(PersonRepository repository) {
this.repository = repository;
}

// 1
public Mono<ServerResponse> listPeople(ServerRequest request) {
Flux<Person> people = repository.allPeople();
return ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Person.class);
}

// 2
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ServerResponse.ok().build(repository.savePerson(person));
}

// 3
public Mono<ServerResponse> getPerson(ServerRequest request) {
int personId = Integer.valueOf(request.pathVariable("id"));
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
Mono<Person> personMono = this.repository.getPerson(personId);
return personMono
.flatMap(person -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person)))
.switchIfEmpty(notFound);
}
}

1/ listPeople 是一个处理函数,它将数据库中发现的所有 Person
对象返回为JSON。

2/ createPerson 是一个处理函数,用于存储请求正文中包含的新Person。请注意,PersonRepository.savePerson(Person) 返回Mono<Void>:发出完成信号的空 Mono,当人从请求中读取并存储时,发出完成信号。因此,当接收到完成信号时,即当 Person 已被保存时,我们使用 build(Publisher<Void>) 方法来发送响应。

3/ getPerson 是一个处理函数,它通过路径变量id来标识一个人。我们通过数据库检索该Person,并创建一个JSON响应(如果找到)。如果没有找到,我们使用 switchIfEmpty(Mono<T>) 来返回 404 Not Found 响应。

RouterFunctions

传入请求将路由到处理函数,并使用一个 RouterFunction,它是一个服务器 ServerRequest 的函数,并返回一个 Mono<HandlerFunction>。如果请求与特定路由匹配,则返回处理函数; 否则返回一个空的 Mono。RouterFunction 与 @Controller 类中的 @RequestMapping 注解类似。

通常,您不要自己编写路由器功能,而是使用
RouterFunctions.route(RequestPredicate, HandlerFunction), 使用请求谓词和处理函数创建一个。如果谓词适用,请求将路由到给定的处理函数; 否则不执行路由,导致 404 Not Found 响应。虽然您可以编写自己的 RequestPredicate ,但是您不需要:RequestPredicates 实用程序类提供常用的谓词,基于路径,HTTP方法,内容类型等进行匹配。使用路由,我们可以路由到我们的 “Hello World” 处理函数:
RouterFunction<ServerResponse> helloWorldRoute =
RouterFunctions.route(RequestPredicates.path("/hello-world"),
request -> Response.ok().body(fromObject("Hello World")));

 
两个路由功能可以组成一个新的路由功能,路由到任一处理函数:如果第一个路由的谓词不匹配,则第二个被评估。组合的路由器功能按顺序进行评估,因此在通用功能之前放置特定功能是有意义的。您可以通过调用 RouterFunction.and(RouterFunction) 或通过调用
RouterFunction.andRoute(RequestPredicate, HandlerFunction) 来组成两个路由功能,这是 RouterFunction.and() 与 RouterFunctions.route() 的一种方便组合。

给定我们上面显示的 PersonH​​andler,我们现在可以定义路由功能,路由到相应的处理函数。我们使用 方法引用(method-references) 来引用处理函数:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> personRoute =
route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson)
.andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
.andRoute(POST("/person").and(contentType(APPLICATION_JSON)), handler::createPerson);

 
除路由功能之外,您还可以通过调用
RequestPredicate.and(RequestPredicate) 或
RequestPredicate.or(RequestPredicate) 来构成请求谓词。这些工作正如预期的那样:如果给定的谓词匹配,则生成的谓词匹配; 或者如果任一谓词都匹配。RequestPredicates 中发现的大多数谓词是组合的。例如,RequestPredicates.GET(String) 是
RequestPredicates.method(HttpMethod) 和RequestPredicates.path(String) 的组合。

启动服务器

现在只有一个难题遗留:在HTTP服务器中运行路由功能。您可以使用
RouterFunction<ServerResponse> route = ...
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create(HOST, PORT);
server.newHandler(adapter).block();

 
对于 Tomcat ,它看起来像这样:
RouterFunction<ServerResponse> route = ...
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler);
Tomcat server = new Tomcat();
Context rootContext = server.addContext("", System.getProperty("java.io.tmpdir"));
Tomcat.addServlet(rootContext, "servlet", servlet);
rootContext.addServletMapping("/", "servlet");
tomcatServer.start();

 
待完成:DispatcherHandler
HandlerFilterFunction
路由功能映射的路由可以通过调用
RouterFunction.filter(HandlerFilterFunction) 进行过滤,其中
HandlerFilterFunction 本质上是一个接收 ServerRequest 和
HandlerFunction 的函数,并返回一个 ServerResponse 。处理函数参数表示链中的下一个元素:通常是路由到的 HandlerFunction ,但是如果应用了多个过滤器,也可以是另一个 FilterFunction 。使用注解,可以使用 @ControllerAdvice 和 / 或 ServletFilter 来实现类似的功能。让我们在我们的路由中添加一个简单的安全过滤器,假设我们有一个 SecurityManager 可以确定是否允许特定的路径:
import static org.springframework.http.HttpStatus.UNAUTHORIZED;

SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = ...

RouterFunction<ServerResponse> filteredRoute =
route.filter(request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
});

 
在这个例子中可以看到,调用 next.handle(ServerRequest) 是可选的:我们只允许在允许访问时执行处理函数。

2.2 客户端(Client Side)

WebFlux 包括一个 functional, reactive WebClient,它为 RestTemplate 提供了一种完全无阻塞和响应式的替代方案。
它将网络输入和输出公开为客户端 HttpRequest 和ClientHttpResponse ,其中请求和响应的主体是 Flux <DataBuffer>而不是 InputStream 和 OutputStream。此外,它还支持与服务器端相同的响应式 JSON,XML和SSE 序列化机制,因此您可以使用类型化对象。以下是使用需要 ClientHttpConnector 实现的 WebClient 插入特定HTTP客户端(如 Reactor Netty)的示例:
WebClient client = WebClient.create("http://example.com";);

Mono<Account> account = client.get()
.url("/accounts/{id}", 1L)
.accept(APPLICATION_JSON)
.exchange(request)
.flatMap(response -> response.bodyToMono(Account.class));

 
AsyncRestTemplate 还支持非阻塞交互。主要区别在于它不支持非阻塞流,例如 Twitter one ,因为它基本上仍然依赖于 InputStream 和
OutputStream。

2.4 请求体和响应体的转换(Request and Response Body Conversion)

spring-core 模块提供了响应式 Encoder (编码器) 和 Decoder (解码器),使得能够串行化字符串与类型对象的转换。spring-web 模块添加了 JSON(Jackson)和 XML(JAXB)实现,用于Web应用程序以及其他用于SSE流和零拷贝文件传输。

支持以下 Reactive API:

Reactor 3.x 支持开箱即用
io.reactivex.rxjava2:rxjava 依赖项在类路径上时支持 RxJava 2.x
当 ·io.reactivex:rxjava和io.reactivex:rxjava-reactive-streams`(RxJava 和 Reactive Streams 之间的适配器)依赖关系在类路径上时,支持 RxJava 1.x

例如,请求体可以是以下方式之一,它将在注解和功能编程模型中自动解码:

Account account - 在调用控制器之前,account 将无阻塞地被反序列化。
Mono<Account> account - controller 可以使用 Mono 来声明在反序列化 account 后执行的逻辑。
Single<Account> account - 和 Mono 类似,但是用的是 RxJava
Flux<Account> accounts - 输入流场景
Observable<Account> accounts - RxJava 的 输入流场景

响应体(response body)可以是以下之一:

Mono<Account> - 当 Mono 完成时,序列化而不阻塞给定的Account。
Single<Account> - 与上类似,但是使用的 RxJava
Flux<Account> - 流式场景,可能是SSE,具体取决于所请求的内容类型。
Observable<Account> - 与上类似, 但是使用的 RxJava Observable 类型
Flowable<Account> - 与上类似, 但是使用的 RxJava 2 Flowable 类型。
Publisher<Account> 或 Flow.Publisher<Account> - 支持任何实现Reactive Streams Publisher 的类型。
Flux<ServerSentEvent> - SSE 流。
Mono<Void> - 当 Mono 完成时,请求处理完成。
Account - 序列化而不阻塞给定的Account; 意味着同步、非阻塞的
Controller 方法。
Void - 特定于基于注解的编程模型,方法返回时,请求处理完成;
意味着同步、非阻塞的 Controller 方法。

当使用像 Flux 或 Observable 这样的流类型时,请求/响应或映射/路由级别中指定的媒体类型用于确定数据应如何序列化和刷新。例如,返回 Flux<Account> 的REST端点将默认序列化如下:

application/json : Flux<Account> 作为异步集合处理,并在完成事件发布时将其序列化为具有显式刷新的JSON数组。
application/stream+json : 一个 Flux<Account> 将作为一系列的Account 元素处理,作为以新行分隔的单个JSON对象,并在每个元素之后显式刷新。WebClient 支持JSON流解码,因此这对于服务器到服务器的用例来说是一个很好的用例。
text/event-stream : 一个 Flux<Account> 或 Flux<ServerSentEvent<Account >> 将作为一个 Stream 或ServerSentEvent 元素的流处理,作为单独的 SSE 元素,使用默认的JSON进行数据编码和每个元素之间的显式刷新。这非常适合将流暴露给浏览器客户端。WebClient 也支持读取SSE流。

2.4 响应式 Websocket 支持

WebFlux 包括响应式 WebSocket 客户端和服务器支持。Java WebSocket API(JSR-356),Jetty,Undertow和Reactor Netty都支持客户端和服务器。

在服务器端,声明一个 WebSocketHandlerAdapter,然后简单地添加映射到基于 WebSocketHandler 的端点:
@Bean
public HandlerMapping webSocketMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/foo", new FooWebSocketHandler());
map.put("/bar", new BarWebSocketHandler());

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

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

在客户端,为上面列出的支持的库之一创建一个 WebSocketClient:
WebSocketClient client = new ReactorNettyWebSocketClient();
client.execute("ws://localhost:8080/echo"), session -> {... }).blockMillis(5000);

 
2.5 测试

spring-test 模块包括一个 WebTestClient,可用于测试具有或不具有正在运行的服务器的 WebFlux 服务器端点。

没有运行服务器的测试与来自Spring MVC的 MockMvc 相当,其中使用模拟请求和响应,而不是使用套接字通过网络连接。然而,WebTestClient 也可以针对正在运行的服务器执行测试。

更多请查看 sample tests

3、开始入门
3.1 Spring Boot Starter

通过 http://start.spring.io 提供的 Spring Boot WebFlux 启动器是最快的入门方式。它做所有必要的,所以你开始像Spring MVC一样编写@Controller类。只需转到 http://start.spring.io ,选择版本
2.0.0.BUILD-SNAPSHOT,并在依赖关系框中键入 respond。
默认情况下,启动器使用 Reactor Netty 运行,但依赖关系可以像往常一样通过 Spring Boot 更改为不同的运行时。有关更多详细信息和说明,请参阅 Spring Boo t参考文档页面。

3.2 手动引导(Manual Bootstrapping)

对于依赖关系,从 spring-webflux 和 spring-context 开始。
然后添加jackson-databind 和 io.netty:netty-buffer(暂时见SPR-14528)以获得JSON支持。最后添加一个支持的运行时的依赖项:

Tomcat — org.apache.tomcat.embed:tomcat-embed-core
Jetty — org.eclipse.jetty:jetty-server 和 org.eclipse.jetty:jetty-servlet
Reactor Netty — io.projectreactor.ipc:reactor-netty
Undertow — io.undertow:undertow-core

基于注解编程模式的引导:
ApplicationContext context = new AnnotationConfigApplicationContext(DelegatingWebFluxConfiguration.class);  // (1)
HttpHandler handler = DispatcherHandler.toHttpHandler(context); // (2)

 
以上加载默认的 Spring Web 框架配置(1),然后创建一个DispatcherHandler,主类驱动请求处理(2),并适应 HttpHandler - 响应式HTTP请求处理的最低级别的Spring抽象。

函数编程模式的引导:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // (1)
context.registerBean(FooBean.class, () -> new FooBeanImpl()); // (2)
context.registerBean(BarBean.class); // (3)
context.refresh();

HttpHandler handler = WebHttpHandlerBuilder
.webHandler(RouterFunctions.toHttpHandler(...))
.applicationContext(context)
.build(); // (4)

以上创建了一个 AnnotationConfigApplicationContext 实例(1),可以利用新的功能 bean 注册API(2)使用 Java 8 供应商注册 bean,或者只需通过指定其类(3)即可。HttpHandler 是使用WebHttpHandlerBuilder(4)创建的。

然后可以将 HttpHandler 安装在支持的运行服务器之一中:
// Tomcat and Jetty (also see notes below)
HttpServlet servlet = new ServletHttpHandlerAdapter(handler);
...

// Reactor Netty
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create(host, port).newHandler(adapter).block();

// Undertow
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();

对于特别是使用 WAR 部署的 Servlet 容器,可以使用作为WebApplicationInitializer 的
AbstractAnnotationConfigDispatcherHandlerInitializer,并由 Servlet容器自动检测。它负责注册 ServletHttpHandlerAdapter ,如上所示。您将需要实现一个抽象方法来指向您的 Spring 配置。

3.3 Examples

您将在以下项目中找到有助于构建反应式 Web 应用程序的代码示例:

Functional programming model sample
Spring Reactive Playground: playground for most Spring Web reactive features
Reactor website: the spring-functional
branch is a Spring 5 functional, Java 8 lambda-style application
Spring Reactive University session: live-coded project from this Devoxx BE 2106 university talk
Reactive Thymeleaf Sandbox
Mix-it 2017 website: Kotlin + Reactive + Functional web and bean registration API application
Reactor by example: code snippets coming from this InfoQ article
Spring integration tests: various features tested with Reactor StepVerifier


Webflux 实战项目

demo地址: https://anyim.cfapps.io/

作者:Anoyi
链接:http://www.jianshu.com/p/f4ff6d74ad4a

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

自制Mybatis插件--doge-mapper,欢迎使用和吐槽

开源项目throwable 发表了文章 • 4 个评论 • 122 次浏览 • 5 天前 • 来自相关话题

项目简介
   doge-mapper致力于打造一个轻量级、可扩展、灵活的Mybatis插件,配套一个实体、DAO代码生成器doge-mapper-generator(基于JavaFx编写)。目前此插件只支持Mysql和单表,需要Jdk1.8。
 
项目地址
Github:https://github.com/zjcscut/doge-mapper
 
项目文档
doge-mapper文档:https://github.com/zjcscut/doge-mapper/blob/master/readme/readme_zh_cn.md doge-mapper-generator文档:https://github.com/zjcscut/dog ... cn.md

项目使用快照:
    实体:




    Mapper:
 




 
    使用:




 
写在最后
   欢迎吐槽和Star,如果使用过程出现任何问题,直接发送邮件给我,第一时间回答和解决,邮箱见https://github.com/zjcscut/doge-mapper/的Contact部分。

 
  查看全部
项目简介
   doge-mapper致力于打造一个轻量级、可扩展、灵活的Mybatis插件,配套一个实体、DAO代码生成器doge-mapper-generator(基于JavaFx编写)。目前此插件只支持Mysql和单表,需要Jdk1.8。
 
项目地址

 
项目文档


项目使用快照:
    实体:
entity.png

    Mapper:
 
mapper.png

 
    使用:
usage.png

 
写在最后
   欢迎吐槽和Star,如果使用过程出现任何问题,直接发送邮件给我,第一时间回答和解决,邮箱见https://github.com/zjcscut/doge-mapper/的Contact部分。

 
 

Spring学习(一): 使用AbstractRoutingDataSource类进行多数据源配置

Spring Datazane 发表了文章 • 0 个评论 • 159 次浏览 • 2017-09-12 12:33 • 来自相关话题

多数据源在项目中很常见,举几个需求,如页面展示的数据需要从不同的数据库中去查询,修改等,例如数据库的读写分离等

配置多数据源要考虑到如下的情况
单个数据源是如下这种情况:

如果是多个数据源的话,将会变成下面这种情况



这样,每增加一个数据源就要去配置一个sessionFactory,这样比较麻烦,于是spring的
AbstractRoutingDataSource类产生了


它的实现原理是扩展Spring的`AbstractRoutingDataSource`抽象类(该类充当了`DataSource`的路由中介, 能在运行时, 根据某种key值来动态切换到真正的`DataSource`上。)
 从AbstractRoutingDataSource的源码中:public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}

public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}

获取连接的方法中,重点是determineTargetDataSource()方法,看源码:protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。
看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:

接下来,将展示一个多数据源配置demo 

1.配置一个config类,配置3个数据源(下面是JAVA配置写法)/**
* Created by Youjie on 2017/7/23.
*/
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

@Bean
@ConfigurationProperties(prefix = "datasource.db_a")
@Qualifier("dataSourceDefault")
public DataSource dataSourceDefault(){
return new DruidDataSource();
}

@Bean
@ConfigurationProperties(prefix = "datasource.db_b")
@Qualifier("dataSourceB")
public DataSource dataSourceB(){
return new DruidDataSource();
}

@Bean
@ConfigurationProperties(prefix = "datasource.db_c")
@Qualifier("dataSourceC")
public DataSource dataSourceC(){
return new DruidDataSource();
}


@Bean
@Primary
public DataSource multipleDataSource(){
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSourceDefault());

Map<Object,Object> dataSourcesType = new HashMap();
dataSourcesType.put(DataSources.DATASOURCE_A,dataSourceDefault());
dataSourcesType.put(DataSources.DATASOURCE_B,dataSourceB());
dataSourcesType.put(DataSources.DATASOURCE_C,dataSourceC());

dynamicDataSource.setTargetDataSources(dataSourcesType);

return dynamicDataSource;
}



@Bean
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(multipleDataSource());
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
sqlSessionFactoryBean.setTypeAliasesPackage("com.may.model");
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("configuration.xml"));
return sqlSessionFactoryBean.getObject();
}

@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(multipleDataSource());
}



}对应的 application.properties如下server.port=9002
server.context-path=/


#A user
datasource.db_a.driver-class-name=com.mysql.jdbc.Driver
datasource.db_a.url=jdbc:mysql://127.0.0.1:3306/test_A?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_a.username=root
datasource.db_a.password=123456

#B city
datasource.db_b.driver-class-name=com.mysql.jdbc.Driver
datasource.db_b.url=jdbc:mysql://127.0.0.1:3306/test_B?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_b.username=root
datasource.db_b.password=123456

#C role
datasource.db_c.driver-class-name=com.mysql.jdbc.Driver
datasource.db_c.url=jdbc:mysql://127.0.0.1:3306/test_C?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_c.username=root
datasource.db_c.password=123456

2、写一个ThreadLocalDynamicDataSource类继承AbstractRoutingDataSource,并实现determineCurrentLookupKey方法public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceTypeManager.get();
}
}3、编写DataSourceTypeManager执行类,并利用ThreadLocal以空间换时间的方式解决线程安全问题public class DataSourceTypeManager {
private static final ThreadLocal<DataSources> dataSourceTypes = new ThreadLocal<DataSources>() {
@Override
protected DataSources initialValue() {
return DataSources.DATASOURCE_DEFAULT;
}
};

public static DataSources get() {
return dataSourceTypes.get();
}

public static void set(DataSources dataSourceType) {
dataSourceTypes.set(dataSourceType);
}

public static void reset() {
dataSourceTypes.set(DataSources.DATASOURCE_DEFAULT);
}

public static void clearDataSources () {
dataSourceTypes.remove();
}
}




上面代码利用initialValue方法,调用初始化时指定的DataSources 类型,这里DataSources 为枚举类型,里面定义三种数据源的名称,代码如下:public enum DataSources {
DATASOURCE,DATASOURCE_DEFAULT,DATASOURCE_ORACLE
}
4、multipleDataSource多数据源配置的xml配置<bean id="multipleDataSource"
class="com.hxqc.basic.dependency.datasource.ThreadLocalRountingDataSource">
<property name="defaultTargetDataSource" ref="ncDataSource"/>
<property name="targetDataSources">
<map key-type="com.hxqc.basic.dependency.datasource.DataSources">
<entry key="DATASOURCE" value-ref="erpDataSource"/>
<entry key="DATASOURCE_DEFAULT" value-ref="ncDataSource"/>
<entry key="DATASOURCE_ORACLE" value-ref="oracleDataSource"/>
</map>
</property>
</bean>



multipleDataSource多数据源配置的Java配置(上文已经贴出该配置,这里再强调一下)@Bean
@Primary
public DataSource multipleDataSource(){
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSourceDefault());

Map<Object,Object> dataSourcesType = new HashMap();
dataSourcesType.put(DataSources.DATASOURCE_A,dataSourceDefault());
dataSourcesType.put(DataSources.DATASOURCE_B,dataSourceB());
dataSourcesType.put(DataSources.DATASOURCE_C,dataSourceC());

dynamicDataSource.setTargetDataSources(dataSourcesType);

return dynamicDataSource;
}
5.在DAOImpl中切换数据源 或者配置aop切面根据不同的包下面的数据,采用不同的数据源



1,在Dao层直接切换数据源,如切换到默认数据源DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT)
2.根据aop进行动态切换
aop xml配置<bean id="dataSourceInterceptor" class="com.hxqc.basic.dependency.interceptor.DataSourceInterceptor"/>
<aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
<aop:pointcut id="datasource" expression="execution(* com.hxqc.data.gather.core.erp.*.mapper..*.*(..))"/>
<aop:pointcut id="datasource_oracle" expression="execution(* com.hxqc.data.gather.core.oracle.*.mapper..*.*(..))"/>
<aop:pointcut id="datasource_default" expression="execution(* com.hxqc.data.gather.core.nc.*.mapper..*.*(..))"/>
<aop:before method="setDataSource" pointcut-ref="datasource"/>
<aop:before method="setDataSourceDefault" pointcut-ref="datasource_default"/>
<aop:before method="setDataSourceOracle" pointcut-ref="datasource_oracle"/>
</aop:aspect>
</aop:config>aop java 配置@Aspect
@Component
public class DataSourceAspect {

private Logger log = LoggerFactory.getLogger(getClass());

@Pointcut("execution(* com.may.dataA.mapper.*.*(..))")
public void pointCutA(){}

@Pointcut("execution(* com.may.dataB.mapper.*.*(..))")
public void pointCutB(){}

@Pointcut("execution(* com.may.dataC.mapper.*.*(..))")
public void pointCutC(){}
@Pointcut("execution(* com.may..*.*(..))")
public void annotation(){}



public void setDataSourceDefault(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_A);
log.info("切换数据源","切换到默认数据源A");

}
public void setDataSourceB(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_B);
log.info("切换数据源","切换到数据源B");
}

public void setDataSourceC(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_C);
log.info("切换数据源","切换到数据源C");
}

@Before("@annotation(ds)")
public void setAnnotationDataSource(JoinPoint jp,DataSource ds){
// 获取当前的指定的数据源;
String dsId = ds.value();
// 如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
/*if (!DataSourceManager.containsDataSource(dsId)) {

log.error("数据源[{}]不存在,使用默认数据源 > {}", ds.value(), jp.getSignature());

} else {

log.debug("Use DataSource : {} > {}", ds.value(), jp.getSignature());

// 找到的话,那么设置到动态数据源上下文中。
DynamicDataSourceContextHolder.setDataSourceType(ds.name());
}*/

//将string值转换为enum
DataSourceManager.set(Enum.valueOf(DataSources.class,ds.value()));

log.info("切换数据源"+ds.value(),"切换到默认数据源"+ds.value());
}



}
解释,这里定义三个切面,分别对应三个数据源,如在oracle包下的,动态切换到oracle数据源,上面配置的最后部分是用到了自定义注解DataSource注解,该注解在方法上,通过切面来进行数据源的切换,具体看源码

然后配置切面类/**
*
*
* @author maple
*
*/
public class DataSourceInterceptor {

public void setDataSourceDefault(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT);
LogU.i("切换数据源","切换到NC数据源");
System.out.println(jp.getTarget().getClass().getSimpleName());
}

public void setDataSource(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE);
LogU.i("切换数据源","切换到ERP数据源");
}

public void setDataSourceOracle(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE_ORACLE);
LogU.i("切换数据源","切换到Oracle数据源");
}
}





`问题:`多数据源切换是成功了,但牵涉到事务呢?单数据源事务是ok的,但如果多数据源需要同时使用一个事务呢?这个问题有点头大,网络上有人提出用atomikos开源项目实现JTA分布式事务处理。你怎么看?

注意:

本文源码放在码云上,注意,源码将XML配置的内容都改成了JAVA配置,项目是基于Spring-boot,也提供了自定义注解来改变动态数据源!

本文源码:http://git.oschina.net/youjie1/may-dynamic-datasource
原文

  查看全部


多数据源在项目中很常见,举几个需求,如页面展示的数据需要从不同的数据库中去查询,修改等,例如数据库的读写分离等


配置多数据源要考虑到如下的情况
单个数据源是如下这种情况:

如果是多个数据源的话,将会变成下面这种情况



这样,每增加一个数据源就要去配置一个sessionFactory,这样比较麻烦,于是spring的
AbstractRoutingDataSource类产生了


它的实现原理是扩展Spring的`AbstractRoutingDataSource`抽象类(该类充当了`DataSource`的路由中介, 能在运行时, 根据某种key值来动态切换到真正的`DataSource`上。)
 从AbstractRoutingDataSource的源码中:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean


我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:
public Connection getConnection() throws SQLException {  
return determineTargetDataSource().getConnection();
}

public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}


获取连接的方法中,重点是determineTargetDataSource()方法,看源码:
protected DataSource determineTargetDataSource() {  
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。
看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:

接下来,将展示一个多数据源配置demo 

1.配置一个config类,配置3个数据源(下面是JAVA配置写法)
/**
* Created by Youjie on 2017/7/23.
*/
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

@Bean
@ConfigurationProperties(prefix = "datasource.db_a")
@Qualifier("dataSourceDefault")
public DataSource dataSourceDefault(){
return new DruidDataSource();
}

@Bean
@ConfigurationProperties(prefix = "datasource.db_b")
@Qualifier("dataSourceB")
public DataSource dataSourceB(){
return new DruidDataSource();
}

@Bean
@ConfigurationProperties(prefix = "datasource.db_c")
@Qualifier("dataSourceC")
public DataSource dataSourceC(){
return new DruidDataSource();
}


@Bean
@Primary
public DataSource multipleDataSource(){
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSourceDefault());

Map<Object,Object> dataSourcesType = new HashMap();
dataSourcesType.put(DataSources.DATASOURCE_A,dataSourceDefault());
dataSourcesType.put(DataSources.DATASOURCE_B,dataSourceB());
dataSourcesType.put(DataSources.DATASOURCE_C,dataSourceC());

dynamicDataSource.setTargetDataSources(dataSourcesType);

return dynamicDataSource;
}



@Bean
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(multipleDataSource());
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
sqlSessionFactoryBean.setTypeAliasesPackage("com.may.model");
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("configuration.xml"));
return sqlSessionFactoryBean.getObject();
}

@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(multipleDataSource());
}



}
对应的 application.properties如下
server.port=9002
server.context-path=/


#A user
datasource.db_a.driver-class-name=com.mysql.jdbc.Driver
datasource.db_a.url=jdbc:mysql://127.0.0.1:3306/test_A?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_a.username=root
datasource.db_a.password=123456

#B city
datasource.db_b.driver-class-name=com.mysql.jdbc.Driver
datasource.db_b.url=jdbc:mysql://127.0.0.1:3306/test_B?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_b.username=root
datasource.db_b.password=123456

#C role
datasource.db_c.driver-class-name=com.mysql.jdbc.Driver
datasource.db_c.url=jdbc:mysql://127.0.0.1:3306/test_C?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_c.username=root
datasource.db_c.password=123456


2、写一个ThreadLocalDynamicDataSource类继承AbstractRoutingDataSource,并实现determineCurrentLookupKey方法
public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceTypeManager.get();
}
}
3、编写DataSourceTypeManager执行类,并利用ThreadLocal以空间换时间的方式解决线程安全问题
public class DataSourceTypeManager {
private static final ThreadLocal<DataSources> dataSourceTypes = new ThreadLocal<DataSources>() {
@Override
protected DataSources initialValue() {
return DataSources.DATASOURCE_DEFAULT;
}
};

public static DataSources get() {
return dataSourceTypes.get();
}

public static void set(DataSources dataSourceType) {
dataSourceTypes.set(dataSourceType);
}

public static void reset() {
dataSourceTypes.set(DataSources.DATASOURCE_DEFAULT);
}

public static void clearDataSources () {
dataSourceTypes.remove();
}
}




上面代码利用initialValue方法,调用初始化时指定的DataSources 类型,这里DataSources 为枚举类型,里面定义三种数据源的名称,代码如下:
public enum DataSources {
DATASOURCE,DATASOURCE_DEFAULT,DATASOURCE_ORACLE
}

4、multipleDataSource多数据源配置的xml配置
<bean id="multipleDataSource"
class="com.hxqc.basic.dependency.datasource.ThreadLocalRountingDataSource">
<property name="defaultTargetDataSource" ref="ncDataSource"/>
<property name="targetDataSources">
<map key-type="com.hxqc.basic.dependency.datasource.DataSources">
<entry key="DATASOURCE" value-ref="erpDataSource"/>
<entry key="DATASOURCE_DEFAULT" value-ref="ncDataSource"/>
<entry key="DATASOURCE_ORACLE" value-ref="oracleDataSource"/>
</map>
</property>
</bean>



multipleDataSource多数据源配置的Java配置(上文已经贴出该配置,这里再强调一下)
@Bean
@Primary
public DataSource multipleDataSource(){
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSourceDefault());

Map<Object,Object> dataSourcesType = new HashMap();
dataSourcesType.put(DataSources.DATASOURCE_A,dataSourceDefault());
dataSourcesType.put(DataSources.DATASOURCE_B,dataSourceB());
dataSourcesType.put(DataSources.DATASOURCE_C,dataSourceC());

dynamicDataSource.setTargetDataSources(dataSourcesType);

return dynamicDataSource;
}

5.在DAOImpl中切换数据源 或者配置aop切面根据不同的包下面的数据,采用不同的数据源



1,在Dao层直接切换数据源,如切换到默认数据源
DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT)

2.根据aop进行动态切换
aop xml配置
<bean id="dataSourceInterceptor" class="com.hxqc.basic.dependency.interceptor.DataSourceInterceptor"/>
<aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
<aop:pointcut id="datasource" expression="execution(* com.hxqc.data.gather.core.erp.*.mapper..*.*(..))"/>
<aop:pointcut id="datasource_oracle" expression="execution(* com.hxqc.data.gather.core.oracle.*.mapper..*.*(..))"/>
<aop:pointcut id="datasource_default" expression="execution(* com.hxqc.data.gather.core.nc.*.mapper..*.*(..))"/>
<aop:before method="setDataSource" pointcut-ref="datasource"/>
<aop:before method="setDataSourceDefault" pointcut-ref="datasource_default"/>
<aop:before method="setDataSourceOracle" pointcut-ref="datasource_oracle"/>
</aop:aspect>
</aop:config>
aop java 配置
@Aspect
@Component
public class DataSourceAspect {

private Logger log = LoggerFactory.getLogger(getClass());

@Pointcut("execution(* com.may.dataA.mapper.*.*(..))")
public void pointCutA(){}

@Pointcut("execution(* com.may.dataB.mapper.*.*(..))")
public void pointCutB(){}

@Pointcut("execution(* com.may.dataC.mapper.*.*(..))")
public void pointCutC(){}
@Pointcut("execution(* com.may..*.*(..))")
public void annotation(){}



public void setDataSourceDefault(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_A);
log.info("切换数据源","切换到默认数据源A");

}
public void setDataSourceB(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_B);
log.info("切换数据源","切换到数据源B");
}

public void setDataSourceC(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_C);
log.info("切换数据源","切换到数据源C");
}

@Before("@annotation(ds)")
public void setAnnotationDataSource(JoinPoint jp,DataSource ds){
// 获取当前的指定的数据源;
String dsId = ds.value();
// 如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
/*if (!DataSourceManager.containsDataSource(dsId)) {

log.error("数据源[{}]不存在,使用默认数据源 > {}", ds.value(), jp.getSignature());

} else {

log.debug("Use DataSource : {} > {}", ds.value(), jp.getSignature());

// 找到的话,那么设置到动态数据源上下文中。
DynamicDataSourceContextHolder.setDataSourceType(ds.name());
}*/

//将string值转换为enum
DataSourceManager.set(Enum.valueOf(DataSources.class,ds.value()));

log.info("切换数据源"+ds.value(),"切换到默认数据源"+ds.value());
}



}

解释,这里定义三个切面,分别对应三个数据源,如在oracle包下的,动态切换到oracle数据源,上面配置的最后部分是用到了自定义注解DataSource注解,该注解在方法上,通过切面来进行数据源的切换,具体看源码

然后配置切面类
/**
*
*
* @author maple
*
*/
public class DataSourceInterceptor {

public void setDataSourceDefault(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT);
LogU.i("切换数据源","切换到NC数据源");
System.out.println(jp.getTarget().getClass().getSimpleName());
}

public void setDataSource(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE);
LogU.i("切换数据源","切换到ERP数据源");
}

public void setDataSourceOracle(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE_ORACLE);
LogU.i("切换数据源","切换到Oracle数据源");
}
}





`问题:`多数据源切换是成功了,但牵涉到事务呢?单数据源事务是ok的,但如果多数据源需要同时使用一个事务呢?这个问题有点头大,网络上有人提出用atomikos开源项目实现JTA分布式事务处理。你怎么看?

注意:

本文源码放在码云上,注意,源码将XML配置的内容都改成了JAVA配置,项目是基于Spring-boot,也提供了自定义注解来改变动态数据源!

本文源码:http://git.oschina.net/youjie1/may-dynamic-datasource
原文

 

官方Spring周报-20170905

最新动态strongant 发表了文章 • 0 个评论 • 829 次浏览 • 2017-09-07 21:58 • 来自相关话题

Spring的粉丝们,欢迎来到本周的另一期官方周报!这个星期我在香港看这个惊人的YOW!然后是新加坡的YOW。这些会议都是来自澳大利亚顶尖的会议,而且会议很新颖。如果你在这两个地方,我希望你会来。

http://www.yowconference.hk/

http://www.yowconference.sg/

1.介绍下关于Spring Cloud Data Flow最重要的一个方面。 Spring Cloud Data Flow大牛Eric Bottard首先介绍了Spring Cloud Data Flow Shell本身。

https://spring.io/blog/2017/08 ... shell

2.Spring Cloud贡献者Ryan Baxter刚刚宣布了Spring Cloud Edgeware M1 的发布。新版本包括对各种项目的更新以及新的Spring Cloud Gateway项目。此版本基于Spring Boot 1.5.x。

https://spring.io/blog/2017/08 ... lable

3.Spring Cloud联合创始人Spencer Gibb刚刚宣布了Spring Cloud Finchely M2的发布。这个新版本基于Spring Boot 2.0.x,所以是基于Java 8。它还支持Spring Cloud Gateway中更多更酷的功能。在Spring Cloud Gateway和其他版本中,一个常见主题是反应性支持。

https://spring.io/blog/2017/08 ... lable

4.Spring Cloud Flow大牛Thomas Risberg在本文中介绍了针对Kubernetes的Spring Cloud Data Flow的Helm安装程序。 Helm是Kubernetes的包管理者。

https://spring.io/blog/2017/08 ... -helm

5.这个应用现代化的Pivotal和Apigee白皮书是一个很好的阅读题材。

https://content.pivotal.io/api ... 73ac4

6.Matt McCandless称Spring Boot老而弥新,这是个很好的起步。

https://dzone.com/articles/spr ... you-1

7.这篇关于使用Spring Boot的Quartz Scheduler的文章是曾经看过的一篇文章。我喜欢。

http://www.opencodez.com/java/ ... t.htm

8.看起来要想Java 9发布,这个新的Java版本将会有6个月的时间。我个人欢迎新版本。

https://jcp.org/aboutJava/comm ... .html

9.我真的很喜欢这个俄语的帖子,介绍Spring Boot的应用程序开发。

https://alexkosarev.name/2017/ ... rt-1/

10.上周Pivotal与Google和VMWare合作,宣布了Pivotal Cloud Foundry生态系统 - Pivotal Container Service - 由BOSH管理的Kubernetes服务的下一步工作。我们从TechCrunch片中得到一些细节。

https://www.blog.google/topics ... -gcp/

https://techcrunch.com/2017/08 ... /amp/

11.我喜欢这个Auth0在集成Spring Boot与SQL数据库(PostgreSQL),JPA和Liquibase。

https://auth0.com/blog/integra ... base/

12.来自JHipster的这篇关于“API优先开发”的文章很有趣。

http://www.jhipster.tech/doing ... ment/

13.Spring Boot 2.0具有反应弹性框架5和Java 8基准线,肯定会令人印象深刻。新版本中我最喜欢的功能之一就是支持Spring Boot Actuator端点的REST-stack(响应式Spring WebFlux,Spring MVC或JAX-RS)。

https://www.infoq.com/news/201 ... oints

14.看看这个优秀的网络研讨会,了解为什么IntelliJ IDEA是Grails应用程序的首选。我喜欢这个,因为Grails 3是基于Spring Boot的,因为Jeff Scott Brown是一个伟大的演讲者,因为IntelliJ做了一个漂亮的好IDE。

https://blog.jetbrains.com/ide ... ls-3/

15.严格来说,这与Spring不是很相关,但它是值得一读的。丹尼尔·布莱恩特(Daniel Bryant)在这篇文章中描述了测试微服务(以及您可能使用的所有组件)的全方位方法。

https://specto.io/blog/2016/8/ ... ices/

16.我喜欢这个来自Spring Data的StackOverflow响应Oliver Gierke:如何在Spring Boot中选择性升级依赖项?

https://stackoverflow.com/ques ... g-dat

17.如果您想快速了解一下Cloud Foundry服务代理商,请阅读本文。

http://www.automate-it.today/a ... kers/

18.这篇也不是严格来说与Spring有关,但对于构建云原生应用程序的一些Spring用户来说,这对于Java云主机服务的成本降低策略来说非常有用。

https://www.infoq.com/articles/java-cloud-cost-reduction
 
备注:希望大家能够积极参与我们的公益开源翻译项目,对于有些翻译不当的地方由于水平问题望见谅!我们的SpringBoot周报开源翻译项目地址:https://github.com/strongant/ThisWeekInSpringTranslate
欢迎各路大神加入我们的翻译计划,感谢! 查看全部
Spring的粉丝们,欢迎来到本周的另一期官方周报!这个星期我在香港看这个惊人的YOW!然后是新加坡的YOW。这些会议都是来自澳大利亚顶尖的会议,而且会议很新颖。如果你在这两个地方,我希望你会来。

http://www.yowconference.hk/

http://www.yowconference.sg/

1.介绍下关于Spring Cloud Data Flow最重要的一个方面。 Spring Cloud Data Flow大牛Eric Bottard首先介绍了Spring Cloud Data Flow Shell本身。

https://spring.io/blog/2017/08 ... shell

2.Spring Cloud贡献者Ryan Baxter刚刚宣布了Spring Cloud Edgeware M1 的发布。新版本包括对各种项目的更新以及新的Spring Cloud Gateway项目。此版本基于Spring Boot 1.5.x。

https://spring.io/blog/2017/08 ... lable

3.Spring Cloud联合创始人Spencer Gibb刚刚宣布了Spring Cloud Finchely M2的发布。这个新版本基于Spring Boot 2.0.x,所以是基于Java 8。它还支持Spring Cloud Gateway中更多更酷的功能。在Spring Cloud Gateway和其他版本中,一个常见主题是反应性支持。

https://spring.io/blog/2017/08 ... lable

4.Spring Cloud Flow大牛Thomas Risberg在本文中介绍了针对Kubernetes的Spring Cloud Data Flow的Helm安装程序。 Helm是Kubernetes的包管理者。

https://spring.io/blog/2017/08 ... -helm

5.这个应用现代化的Pivotal和Apigee白皮书是一个很好的阅读题材。

https://content.pivotal.io/api ... 73ac4

6.Matt McCandless称Spring Boot老而弥新,这是个很好的起步。

https://dzone.com/articles/spr ... you-1

7.这篇关于使用Spring Boot的Quartz Scheduler的文章是曾经看过的一篇文章。我喜欢。

http://www.opencodez.com/java/ ... t.htm

8.看起来要想Java 9发布,这个新的Java版本将会有6个月的时间。我个人欢迎新版本。

https://jcp.org/aboutJava/comm ... .html

9.我真的很喜欢这个俄语的帖子,介绍Spring Boot的应用程序开发。

https://alexkosarev.name/2017/ ... rt-1/

10.上周Pivotal与Google和VMWare合作,宣布了Pivotal Cloud Foundry生态系统 - Pivotal Container Service - 由BOSH管理的Kubernetes服务的下一步工作。我们从TechCrunch片中得到一些细节。

https://www.blog.google/topics ... -gcp/

https://techcrunch.com/2017/08 ... /amp/

11.我喜欢这个Auth0在集成Spring Boot与SQL数据库(PostgreSQL),JPA和Liquibase。

https://auth0.com/blog/integra ... base/

12.来自JHipster的这篇关于“API优先开发”的文章很有趣。

http://www.jhipster.tech/doing ... ment/

13.Spring Boot 2.0具有反应弹性框架5和Java 8基准线,肯定会令人印象深刻。新版本中我最喜欢的功能之一就是支持Spring Boot Actuator端点的REST-stack(响应式Spring WebFlux,Spring MVC或JAX-RS)。

https://www.infoq.com/news/201 ... oints

14.看看这个优秀的网络研讨会,了解为什么IntelliJ IDEA是Grails应用程序的首选。我喜欢这个,因为Grails 3是基于Spring Boot的,因为Jeff Scott Brown是一个伟大的演讲者,因为IntelliJ做了一个漂亮的好IDE。

https://blog.jetbrains.com/ide ... ls-3/

15.严格来说,这与Spring不是很相关,但它是值得一读的。丹尼尔·布莱恩特(Daniel Bryant)在这篇文章中描述了测试微服务(以及您可能使用的所有组件)的全方位方法。

https://specto.io/blog/2016/8/ ... ices/

16.我喜欢这个来自Spring Data的StackOverflow响应Oliver Gierke:如何在Spring Boot中选择性升级依赖项?

https://stackoverflow.com/ques ... g-dat

17.如果您想快速了解一下Cloud Foundry服务代理商,请阅读本文。

http://www.automate-it.today/a ... kers/

18.这篇也不是严格来说与Spring有关,但对于构建云原生应用程序的一些Spring用户来说,这对于Java云主机服务的成本降低策略来说非常有用。

https://www.infoq.com/articles/java-cloud-cost-reduction
 
备注:希望大家能够积极参与我们的公益开源翻译项目,对于有些翻译不当的地方由于水平问题望见谅!我们的SpringBoot周报开源翻译项目地址:https://github.com/strongant/ThisWeekInSpringTranslate
欢迎各路大神加入我们的翻译计划,感谢!

Spring Cloud实战课程-从0到1实现AG-Admin

回复

Spring Cloudace4spring 发起了问题 • 1 人关注 • 0 个回复 • 147 次浏览 • 2017-09-01 07:26 • 来自相关话题

Spring Cloud实战课程-从0到1实现AG-Admin

回复

Spring Cloudace4spring 发起了问题 • 1 人关注 • 0 个回复 • 122 次浏览 • 2017-09-01 07:26 • 来自相关话题

Spring Boot Web 开发注解篇

Spring Boot泥瓦匠BYSocket 发表了文章 • 0 个评论 • 405 次浏览 • 2017-08-02 13:59 • 来自相关话题

摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!
“人才是公司运作的关键所在”
本文提纲
1. spring-boot-starter-web 依赖概述
1.1 spring-boot-starter-web 职责
1.2 spring-boot-starter-web 依赖关系
2. Spring MVC on Spring Boot
2.1 Spring MVC 体系温故知新
2.2 重要的类
2.3 Spring Boot MVC
2.3.1 控制器
2.3.2 数据绑定
2.3.3 视图和视图解析 

一、spring-boot-starter-web 依赖概述
在 Spring Boot 快速入门中,只要在 pom.xml 加入了 spring-boot-starter-web 依赖,即可快速开发 web 应用。可见,Spring Boot 极大地简化了 Spring 应用从搭建到开发的过程,做到了「开箱即用」的方式。Spring Boot 已经提供很多「开箱即用」的依赖,如上面开发 web 应用使用的 spring-boot-starter-web ,都是以 spring-boot-starter-xx 进行命名的。
 
Spring Boot 「开箱即用」 的设计,对开发者非常便利。简单来说,只要往 Spring Boot 项目加入相应的 spring-boot-starter-xx 依赖,就可以使用对应依赖的功能,比如加入 spring-boot-starter-data-jpa 依赖,就可以使用数据持久层框架 Spring Data JPA 操作数据源。相比 Spring 以前需要大量的XML配置以及复杂的依赖管理,极大的减少了开发工作量和学习成本。

当开发一个特定类型的应用程序时,特定的 Starter 提供所需的依赖关系,并且将对应的 Bean 注册到 Spring 容器中。spring-boot-starter-web 依赖就是提供开发 Web 应用的。

1.1 spring-boot-starter-web 职责
spring-boot-starter-web 是一个用于构建 Web 的 Starter ,包括构建 RESTful 服务应用、Spring MVC 应用等。并且不需要额外配置容器,默认使用 Tomcat 作为嵌入式容器。

1.2 spring-boot-starter-web 依赖关系
spring-boot-starter-web 这么强大,它的组成如下表:
spring-boot-starter  核心包,包括了自动化配置支持、日志、YAML 文件解析的支持等。
spring-boot-starter-json 读写 JSON 包
spring-boot-starter-tomcat Tomcat 嵌入式 Servlet 容器包
hibernate-validator Hibernate 框架提供的验证包
spring-web Spring 框架的 Web 包
spring-webmvc Spring 框架的 Web MVC 包







spring-boot-starter-web 包含了 Tomcat 和 Spring MVC ,那启动流程是这样的。 标识 @SpringBootApplication 的应用,初始化经过 spring-boot-starter  核心包中的自动化配置,构建了 Spring 容器,并通过 Tomcat 启动 Web 应用。很多 Starters 只支持 Spring MVC,一般会将 spring-boot-starter-web 依赖加入到应用的 Classpath。

另外,spring-boot-starter-web 默认使用 Tomcat 作为嵌入式 Servlet 容器,在 pom.xml 配置 spring-boot-starter-jetty 和 spring-boot-starter-undertow 就可以替换默认容器。


二、Spring MVC on Spring Boot
Spring MVC 是 Spring Web 重要的模块。内容包括 MVC 模式的实现和 RESTful 服务的支持。

2.1 Spring MVC 体系温故知新

spring-webmvc 模块里面包:
- org.springframework.web.servlet 提供与应用程序上下文基础结构集成的 Servlet,以及 Spring web MVC 框架的核心接口和类。
- org.springframework.web.servlet.mvc Spring 附带的 Servlet MVC 框架的标准控制器实现。
- org.springframework.web.servlet.mvc.annotation 用于基于注解的 Servlet MVC 控制器的支持包。
- org.springframework.web.servlet.mvc.condition 用于根据条件匹配传入请求的公共 MVC 逻辑。
- org.springframework.web.servlet.mvc.method 用于处理程序方法处理的基于 Servlet 的基础结构,基于在 org.springframework.web.method 包上。
- org.springframework.web.servlet.view 提供标准的 View 和 ViewResolver 实现,包括自定义实现的抽象基类。
- org.springframework.web.servlet.view.freemarker 支持将 FreeMarker 集成为 Spring Web 视图技术的类。
- org.springframework.web.servlet.view.json 支持提供基于 JSON 序列化的 View 实现的类。

上面列出来核心的包。org.springframework.web.servlet.view 包中, View 视图实现有常见的:JSON 、FreeMarker 等。org.springframework.web.servlet.mvc 包中,Controller 控制层实现包括了注解、程序方法处理等封装。自然,看源码先从 org.springframework.web.servlet 包看其核心的接口和类。

2.2 重要的类
DispatcherServlet 类:调度 HTTP 请求控制器(或者处理器 Handler)。  
View 视图层 ModelAndView 类:模型和视图的持有者。
View 接口:MVC WEB 交互。该接口的实现负责呈现视图或者暴露模型。  
Controller 控制层 HandlerMapping 接口: 请求从 DispacherServlet 过来,该接口定义请求和处理程序对象之间的映射。
HandlerInterceptor 接口:处理程序的执行链接口。

Spring MVC 框架模型






2.3 Spring Boot MVC
以前 Spring MVC 开发模式是这样的:
1. 在 web.xml 配置 DispatcherServlet,用于截获并处理所有请求
2. 在 Spring MVC 配置文件中,声明预定义的控制器和视图解析器等
3. 编写预定义的处理请求控制器
4. 编写预定义的视图对象,比如 JSP、Freemarker 等

在 Spring Boot MVC 中,Web 自动化配置会帮你减少上面的两个步骤。默认使用的视图是 ThymeLeaf,在下面小节会具体讲
1. 编写预定义的处理请求控制器
2. 编写默认 ThymeLeaf 视图对象

例如下面会展示用户列表案例:
第一步:处理用户请求控制器
UserController.java/**
* 用户控制层
*
* Created by bysocket on 24/07/2017.
*/
@Controller
@RequestMapping(value = "/users") // 通过这里配置使下面的映射都在 /users
public class UserController {
@Autowired
UserService userService; // 用户服务层
/**
* 获取用户列表
* 处理 "/users" 的GET请求,用来获取用户列表
* 通过 @RequestParam 传递参数,进一步实现条件查询或者分页查询
*/
@RequestMapping(method = RequestMethod.GET)
public String getUserList(ModelMap map) {
map.addAttribute("userList", userService.findAll());
return "userList";
}
}

 
@Controller 注解在 UserController 类上,标识其为一个可接收 HTTP 请求的控制器
@RequestMapping(value = "/users") 注解 ,标识 UserController 类下所有接收的请求路由都是 /users 开头的。注意:类上的 @RequestMapping 注解是不必需的

@RequestMapping(method = RequestMethod.GET) 注解,标识该 getUserList(ModelMap map) 方法会接收并处理 /users 请求,且请求方法是 GET
getUserList(ModelMap map) 方法返回的字符串 userList ,代表着是视图,会有视图解析器解析成为一个具体的视图对象,然后经过视图渲染展示到浏览器

第二步:用户列表 ThymeLeaf 视图对象<!DOCTYPE html>
<html lang="zh-CN">
<head>
<script type="text/javascript" th:src="@{https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js}"></script>
<link th:href="@{https://cdn.bootcss.com/bootst ... n.css}" rel="stylesheet"/>
<link th:href="@{/css/default.css}" rel="stylesheet"/>
<link rel="icon" th:href="@{/images/favicon.ico}" type="image/x-icon"/>
<meta charset="UTF-8"/>
<title>用户列表</title>
</head>
<body>
<div class="contentDiv">
<h5> 《 Spring Boot 2.x 核心技术实战》第二章快速入门案例</h5>
<table class="table table-hover table-condensed">
<legend>
<strong>用户列表</strong>
</legend>
<thead>
<tr>
<th>用户编号</th>
<th>名称</th>
<th>年龄</th>
<th>出生时间</th>
<th>管理</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${userList}">
<th scope="row" th:text="${user.id}"></th>
<td><a th:href="@{/users/update/{userId}(userId=${user.id})}" th:text="${user.name}"></a></td>
<td th:text="${user.age}"></td>
<td th:text="${user.birthday}"></td>
<td><a class="btn btn-danger" th:href="@{/users/delete/{userId}(userId=${user.id})}">删除</a></td>
</tr>
</tbody>
</table>
<div><a class="btn btn-primary" href="/users/create" role="button">创建用户</a></div>
</div>
</body>
</html>
一个 table 展示用户列表,引入了 jquery.min.js 和 bootstrap.min.css ,更好的展示页面效果。具体 ThymeLeaf 语法下面会讲到。

代码共享在:https://github.com/JeffLi1993/spring-boot-core-book-demo  

2.3.1 控制器
什么是控制器?控制器就是控制请求接收和负责响应到视图的角色。
@Controller 注解标识一个类作为控制器。DispatcherServlet 会扫描所有控制器类,并检测 @RequestMapping 注解配置的方法。Web 自动化配置已经处理完这一步骤。

@RequestMapping 注解标识请求 URL 信息,可以映射到整个类或某个特定的方法上。该注解可以表明请求需要的。
使用 value 指定特定的 URL ,比如 @RequestMapping(value = "/users”) 和 @RequestMapping(value = "/users/create”) 等
使用 method 指定 HTTP 请求方法,比如 RequestMethod.GET 等
还有使用其他特定的参数条件,可以设置 consumes 指定请求时的请求头需要包含的 Content-Type 值、设置 produces 可确保响应的内容类型

MVC on REST ful 场景
在 HTTP over JSON (自然 JSON、XML或其他自定义的媒体类型内容等均可)场景,配合上前后端分离的开发模式,我们经常会用 @ResponseBody 或 @RestController 两种方式实现 RESTful HTTP API 。

老方式:
@ResponseBody 注解标识该方法的返回值。这样被标注的方法返回值,会直接写入 HTTP 响应体(而不会被视图解析器认为是一个视图对象)。

新方式:
@RestController 注解,和 @Controller 用法一致,整合了 @Controller 和 @ResponseBody 功能。这样不需要每个 @RequestMapping 方法上都加上 @ResponseBody 注解,这样代码更简明。

使代码更简明,还有常用便捷注解 @GetMapping、@PostMapping 和 @PutMapping 等
HTTP 协议相关知识回顾,可以看看我以前的博文《图解 HTTP 协议》http://www.bysocket.com/?p=282 

2.3.2 数据绑定
数据绑定,简单的说就是 Spring MVC 从请求中获取请求入参,赋予给处理方法相应的入参。主要流程如下:
1. DataBinder 接受带有请求入参的 ServletRequest 对象
2. 调用 ConversionService 组件,进行数据类型转换、数据格式化等工作
3. 然后调用 Validator 组件,进行数据校验等工作
4. 绑定结果到 BindingResult 对象
5. 最后赋予给处理方法相应的入参

@ModelAttribute 注解添加一个或多个属性(类对象)到 model 上。例如@RequestMapping(value = "/create", method = RequestMethod.POST)
public String postUser(@ModelAttribute User user)
@PathVariable 注解通过变量名匹配到 URI 模板中相对应的变量。例如@RequestMapping(value = "/update/{id}", method = RequestMethod.GET)
public String getUser(@PathVariable("id") Long id, ModelMap map)   
@RequestParam 注解将请求参数绑定到方法参数。
@RequestHeader 注解将请求头属性绑定到方法参数。

2.3.3 视图和视图解析
视图的职责就是渲染模型数据,将模型里面的数据展示给用户。

请求到经过处理方法处理后,最终返回的是 ModeAndView 。可以从 Spring MVC 框架模型 看出,最终经过 ViewResolver 视频解析器得到视图对象 View。可能是我们常见的 JSP ,也可能是基于 ThymLeaf 、FreeMarker 或 Velocity 模板引擎视图,当然还有可能是 JSON 、XML 或者 PDF 等各种形式。
业界流行的模板引擎有如下的 Starters 支持:
spring-boot-starter-thymeleaf Thymeleaf 模板视图依赖,官方推荐
spring-boot-starter-freemarker Freemarker 模板视图依赖
spring-boot-starter-groovy-templates Groovy 模板视图依赖
spring-boot-starter-mustache Mustache 模板视图依赖

具体,spring-boot-starter-thymeleaf 使用案例在 GitHub :https://github.com/JeffLi1993/spring-boot-core-book-demo   的 chapter-2-spring-boot-quick-start 工程。

三、小结
本文主要介绍了 Spring Boot 在 Web 开发中涉及到的 HTTP 协议,还有一些 Spring MVC 相关的知识。

推荐:
开源项目 springboot-learning-example https://github.com/JeffLi1993/ ... ample 
开源项目 spring-boot-core-book-demo https://github.com/JeffLi1993/ ... -demo 

资料:
- 官方文档 http://docs.spring.io/spring-b ... ingle 
-《精通Spring 4.x 企业应用开发实战》

欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦!
— http://www.bysocket.com/ —
— https://github.com/JeffLi1993 — 





  查看全部
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!
“人才是公司运作的关键所在”
本文提纲
1. spring-boot-starter-web 依赖概述
1.1 spring-boot-starter-web 职责
1.2 spring-boot-starter-web 依赖关系
2. Spring MVC on Spring Boot
2.1 Spring MVC 体系温故知新
2.2 重要的类
2.3 Spring Boot MVC
2.3.1 控制器
2.3.2 数据绑定
2.3.3 视图和视图解析 

一、spring-boot-starter-web 依赖概述
在 Spring Boot 快速入门中,只要在 pom.xml 加入了 spring-boot-starter-web 依赖,即可快速开发 web 应用。可见,Spring Boot 极大地简化了 Spring 应用从搭建到开发的过程,做到了「开箱即用」的方式。Spring Boot 已经提供很多「开箱即用」的依赖,如上面开发 web 应用使用的 spring-boot-starter-web ,都是以 spring-boot-starter-xx 进行命名的。
 
Spring Boot 「开箱即用」 的设计,对开发者非常便利。简单来说,只要往 Spring Boot 项目加入相应的 spring-boot-starter-xx 依赖,就可以使用对应依赖的功能,比如加入 spring-boot-starter-data-jpa 依赖,就可以使用数据持久层框架 Spring Data JPA 操作数据源。相比 Spring 以前需要大量的XML配置以及复杂的依赖管理,极大的减少了开发工作量和学习成本。

当开发一个特定类型的应用程序时,特定的 Starter 提供所需的依赖关系,并且将对应的 Bean 注册到 Spring 容器中。spring-boot-starter-web 依赖就是提供开发 Web 应用的。

1.1 spring-boot-starter-web 职责
spring-boot-starter-web 是一个用于构建 Web 的 Starter ,包括构建 RESTful 服务应用、Spring MVC 应用等。并且不需要额外配置容器,默认使用 Tomcat 作为嵌入式容器。

1.2 spring-boot-starter-web 依赖关系
spring-boot-starter-web 这么强大,它的组成如下表:
spring-boot-starter  核心包,包括了自动化配置支持、日志、YAML 文件解析的支持等。
spring-boot-starter-json 读写 JSON 包
spring-boot-starter-tomcat Tomcat 嵌入式 Servlet 容器包
hibernate-validator Hibernate 框架提供的验证包
spring-web Spring 框架的 Web 包
spring-webmvc Spring 框架的 Web MVC 包

WX20170727-181441.png



spring-boot-starter-web 包含了 Tomcat 和 Spring MVC ,那启动流程是这样的。 标识 @SpringBootApplication 的应用,初始化经过 spring-boot-starter  核心包中的自动化配置,构建了 Spring 容器,并通过 Tomcat 启动 Web 应用。很多 Starters 只支持 Spring MVC,一般会将 spring-boot-starter-web 依赖加入到应用的 Classpath。

另外,spring-boot-starter-web 默认使用 Tomcat 作为嵌入式 Servlet 容器,在 pom.xml 配置 spring-boot-starter-jetty 和 spring-boot-starter-undertow 就可以替换默认容器。


二、Spring MVC on Spring Boot
Spring MVC 是 Spring Web 重要的模块。内容包括 MVC 模式的实现和 RESTful 服务的支持。

2.1 Spring MVC 体系温故知新

spring-webmvc 模块里面包:
- org.springframework.web.servlet 提供与应用程序上下文基础结构集成的 Servlet,以及 Spring web MVC 框架的核心接口和类。
- org.springframework.web.servlet.mvc Spring 附带的 Servlet MVC 框架的标准控制器实现。
- org.springframework.web.servlet.mvc.annotation 用于基于注解的 Servlet MVC 控制器的支持包。
- org.springframework.web.servlet.mvc.condition 用于根据条件匹配传入请求的公共 MVC 逻辑。
- org.springframework.web.servlet.mvc.method 用于处理程序方法处理的基于 Servlet 的基础结构,基于在 org.springframework.web.method 包上。
- org.springframework.web.servlet.view 提供标准的 View 和 ViewResolver 实现,包括自定义实现的抽象基类。
- org.springframework.web.servlet.view.freemarker 支持将 FreeMarker 集成为 Spring Web 视图技术的类。
- org.springframework.web.servlet.view.json 支持提供基于 JSON 序列化的 View 实现的类。

上面列出来核心的包。org.springframework.web.servlet.view 包中, View 视图实现有常见的:JSON 、FreeMarker 等。org.springframework.web.servlet.mvc 包中,Controller 控制层实现包括了注解、程序方法处理等封装。自然,看源码先从 org.springframework.web.servlet 包看其核心的接口和类。

2.2 重要的类
DispatcherServlet 类:调度 HTTP 请求控制器(或者处理器 Handler)。  
View 视图层 ModelAndView 类:模型和视图的持有者。
View 接口:MVC WEB 交互。该接口的实现负责呈现视图或者暴露模型。  
Controller 控制层 HandlerMapping 接口: 请求从 DispacherServlet 过来,该接口定义请求和处理程序对象之间的映射。
HandlerInterceptor 接口:处理程序的执行链接口。

Spring MVC 框架模型
WechatIMG485.jpeg



2.3 Spring Boot MVC
以前 Spring MVC 开发模式是这样的:
1. 在 web.xml 配置 DispatcherServlet,用于截获并处理所有请求
2. 在 Spring MVC 配置文件中,声明预定义的控制器和视图解析器等
3. 编写预定义的处理请求控制器
4. 编写预定义的视图对象,比如 JSP、Freemarker 等

在 Spring Boot MVC 中,Web 自动化配置会帮你减少上面的两个步骤。默认使用的视图是 ThymeLeaf,在下面小节会具体讲
1. 编写预定义的处理请求控制器
2. 编写默认 ThymeLeaf 视图对象

例如下面会展示用户列表案例:
第一步:处理用户请求控制器
UserController.java
/**
* 用户控制层
*
* Created by bysocket on 24/07/2017.
*/
@Controller
@RequestMapping(value = "/users") // 通过这里配置使下面的映射都在 /users
public class UserController {
@Autowired
UserService userService; // 用户服务层
/**
* 获取用户列表
* 处理 "/users" 的GET请求,用来获取用户列表
* 通过 @RequestParam 传递参数,进一步实现条件查询或者分页查询
*/
@RequestMapping(method = RequestMethod.GET)
public String getUserList(ModelMap map) {
map.addAttribute("userList", userService.findAll());
return "userList";
}
}

 
@Controller 注解在 UserController 类上,标识其为一个可接收 HTTP 请求的控制器
@RequestMapping(value = "/users") 注解 ,标识 UserController 类下所有接收的请求路由都是 /users 开头的。注意:类上的 @RequestMapping 注解是不必需的

@RequestMapping(method = RequestMethod.GET) 注解,标识该 getUserList(ModelMap map) 方法会接收并处理 /users 请求,且请求方法是 GET
getUserList(ModelMap map) 方法返回的字符串 userList ,代表着是视图,会有视图解析器解析成为一个具体的视图对象,然后经过视图渲染展示到浏览器

第二步:用户列表 ThymeLeaf 视图对象
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<script type="text/javascript" th:src="@{https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js}"></script>
<link th:href="@{https://cdn.bootcss.com/bootst ... n.css}" rel="stylesheet"/>
<link th:href="@{/css/default.css}" rel="stylesheet"/>
<link rel="icon" th:href="@{/images/favicon.ico}" type="image/x-icon"/>
<meta charset="UTF-8"/>
<title>用户列表</title>
</head>
<body>
<div class="contentDiv">
<h5> 《 Spring Boot 2.x 核心技术实战》第二章快速入门案例</h5>
<table class="table table-hover table-condensed">
<legend>
<strong>用户列表</strong>
</legend>
<thead>
<tr>
<th>用户编号</th>
<th>名称</th>
<th>年龄</th>
<th>出生时间</th>
<th>管理</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${userList}">
<th scope="row" th:text="${user.id}"></th>
<td><a th:href="@{/users/update/{userId}(userId=${user.id})}" th:text="${user.name}"></a></td>
<td th:text="${user.age}"></td>
<td th:text="${user.birthday}"></td>
<td><a class="btn btn-danger" th:href="@{/users/delete/{userId}(userId=${user.id})}">删除</a></td>
</tr>
</tbody>
</table>
<div><a class="btn btn-primary" href="/users/create" role="button">创建用户</a></div>
</div>
</body>
</html>

一个 table 展示用户列表,引入了 jquery.min.js 和 bootstrap.min.css ,更好的展示页面效果。具体 ThymeLeaf 语法下面会讲到。

代码共享在:https://github.com/JeffLi1993/spring-boot-core-book-demo  

2.3.1 控制器
什么是控制器?控制器就是控制请求接收和负责响应到视图的角色。
@Controller 注解标识一个类作为控制器。DispatcherServlet 会扫描所有控制器类,并检测 @RequestMapping 注解配置的方法。Web 自动化配置已经处理完这一步骤。

@RequestMapping 注解标识请求 URL 信息,可以映射到整个类或某个特定的方法上。该注解可以表明请求需要的。
使用 value 指定特定的 URL ,比如 @RequestMapping(value = "/users”) 和 @RequestMapping(value = "/users/create”) 等
使用 method 指定 HTTP 请求方法,比如 RequestMethod.GET 等
还有使用其他特定的参数条件,可以设置 consumes 指定请求时的请求头需要包含的 Content-Type 值、设置 produces 可确保响应的内容类型

MVC on REST ful 场景
在 HTTP over JSON (自然 JSON、XML或其他自定义的媒体类型内容等均可)场景,配合上前后端分离的开发模式,我们经常会用 @ResponseBody 或 @RestController 两种方式实现 RESTful HTTP API 。

老方式:
@ResponseBody 注解标识该方法的返回值。这样被标注的方法返回值,会直接写入 HTTP 响应体(而不会被视图解析器认为是一个视图对象)。

新方式:
@RestController 注解,和 @Controller 用法一致,整合了 @Controller 和 @ResponseBody 功能。这样不需要每个 @RequestMapping 方法上都加上 @ResponseBody 注解,这样代码更简明。

使代码更简明,还有常用便捷注解 @GetMapping、@PostMapping 和 @PutMapping 等
HTTP 协议相关知识回顾,可以看看我以前的博文《图解 HTTP 协议》http://www.bysocket.com/?p=282 

2.3.2 数据绑定
数据绑定,简单的说就是 Spring MVC 从请求中获取请求入参,赋予给处理方法相应的入参。主要流程如下:
1. DataBinder 接受带有请求入参的 ServletRequest 对象
2. 调用 ConversionService 组件,进行数据类型转换、数据格式化等工作
3. 然后调用 Validator 组件,进行数据校验等工作
4. 绑定结果到 BindingResult 对象
5. 最后赋予给处理方法相应的入参

@ModelAttribute 注解添加一个或多个属性(类对象)到 model 上。例如
@RequestMapping(value = "/create", method = RequestMethod.POST) 
public String postUser(@ModelAttribute User user)

@PathVariable 注解通过变量名匹配到 URI 模板中相对应的变量。例如
@RequestMapping(value = "/update/{id}", method = RequestMethod.GET) 
public String getUser(@PathVariable("id") Long id, ModelMap map)
   
@RequestParam 注解将请求参数绑定到方法参数。
@RequestHeader 注解将请求头属性绑定到方法参数。

2.3.3 视图和视图解析
视图的职责就是渲染模型数据,将模型里面的数据展示给用户。

请求到经过处理方法处理后,最终返回的是 ModeAndView 。可以从 Spring MVC 框架模型 看出,最终经过 ViewResolver 视频解析器得到视图对象 View。可能是我们常见的 JSP ,也可能是基于 ThymLeaf 、FreeMarker 或 Velocity 模板引擎视图,当然还有可能是 JSON 、XML 或者 PDF 等各种形式。
业界流行的模板引擎有如下的 Starters 支持:
spring-boot-starter-thymeleaf Thymeleaf 模板视图依赖,官方推荐
spring-boot-starter-freemarker Freemarker 模板视图依赖
spring-boot-starter-groovy-templates Groovy 模板视图依赖
spring-boot-starter-mustache Mustache 模板视图依赖

具体,spring-boot-starter-thymeleaf 使用案例在 GitHub :https://github.com/JeffLi1993/spring-boot-core-book-demo   的 chapter-2-spring-boot-quick-start 工程。

三、小结
本文主要介绍了 Spring Boot 在 Web 开发中涉及到的 HTTP 协议,还有一些 Spring MVC 相关的知识。

推荐:
开源项目 springboot-learning-example https://github.com/JeffLi1993/ ... ample 
开源项目 spring-boot-core-book-demo https://github.com/JeffLi1993/ ... -demo 

资料:
- 官方文档 http://docs.spring.io/spring-b ... ingle 
-《精通Spring 4.x 企业应用开发实战》

欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦!
http://www.bysocket.com/
https://github.com/JeffLi1993 — 

qrcode_for_gh_cd421e7eb7d6_430.jpg

 

Spring思维导图,让spring不再难懂(一)

Spring Framework泥瓦匠BYSocket 发表了文章 • 1 个评论 • 843 次浏览 • 2017-07-21 12:03 • 来自相关话题

摘要: Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架写在前面

很多人在微信公众号中给我留言说想看spring的思维导图,正好也打算写。与其他框架相比,spring项目拥有更多的模块,我们常用的ioc,mvc,aop等,这些是spring的主要板块。一篇文章也不可能全部都讲,所以,我打算先把spring简介说一下,后续再写ioc,mvc和aop。

关于Spring

Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。

它是一个全面的、企业应用开发一站式的解决方案,贯穿表现层、业务层、持久层。但是Spring仍然可以和其他的框架无缝整合。






Sping架构

Spring框架是分模块存在,除了最核心的Spring Core Container(即Spring容器)是必要模块之外,其他模块都是可选,视需要而定。大约有20多个模块。

Spring3与Spring4是有区别的,4.0主要是对Java 8的新函数式语法进行支持,还有加强了对网络各种新技术比如http-streaming, websocket的更好的支持。










一般来说,Spring主要分为7个模块:






Spring的主要jar包











常用注解

bean注入与装配的的方式有很多种,可以通过xml,getset方式,构造函数或者注解等。简单易用的方式就是使用Spring的注解了,Spring提供了大量的注解方式,让项目阅读和开发起来更加方便。











第三方框架集成

Spring框架的开发不是为了替代现有的优秀第三方框架,而是通过集成的方式把它们都连接起来。下面总结了一些常集成的优秀框架。






最后

这一节简单介绍了Spring,没涉及到原理的东西。Spring如此博大精深,希望大家好好学习哈。 
 
原文地址:https://my.oschina.net/u/3080373 查看全部

摘要: Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架写在前面

很多人在微信公众号中给我留言说想看spring的思维导图,正好也打算写。与其他框架相比,spring项目拥有更多的模块,我们常用的ioc,mvc,aop等,这些是spring的主要板块。一篇文章也不可能全部都讲,所以,我打算先把spring简介说一下,后续再写ioc,mvc和aop。

关于Spring

Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。

它是一个全面的、企业应用开发一站式的解决方案,贯穿表现层、业务层、持久层。但是Spring仍然可以和其他的框架无缝整合。
1.png



Sping架构

Spring框架是分模块存在,除了最核心的Spring Core Container(即Spring容器)是必要模块之外,其他模块都是可选,视需要而定。大约有20多个模块。

Spring3与Spring4是有区别的,4.0主要是对Java 8的新函数式语法进行支持,还有加强了对网络各种新技术比如http-streaming, websocket的更好的支持。

2.png


3.png

一般来说,Spring主要分为7个模块:

4.png


Spring的主要jar包

5.png


6.png


常用注解

bean注入与装配的的方式有很多种,可以通过xml,getset方式,构造函数或者注解等。简单易用的方式就是使用Spring的注解了,Spring提供了大量的注解方式,让项目阅读和开发起来更加方便。

7.png


8.png


第三方框架集成

Spring框架的开发不是为了替代现有的优秀第三方框架,而是通过集成的方式把它们都连接起来。下面总结了一些常集成的优秀框架。

9.png


最后

这一节简单介绍了Spring,没涉及到原理的东西。Spring如此博大精深,希望大家好好学习哈。 
 
原文地址:https://my.oschina.net/u/3080373

单元项目分享

Spring Bootqq514840279 发表了文章 • 0 个评论 • 214 次浏览 • 2017-07-21 10:29 • 来自相关话题

 
本项目github:
https://github.com/514840279/danyuan-application系统采用技术或开源框架:
前端框架:adminLTE
前端技术:bootstrap,bootstrap-table,js,juqery,ajax,ztree,
后端技术:spring-boot,security
数据库: mysql
其他工具:maven,项目启动配置:
系统安装jdk1.8,mysql5.7,maven3.3
新建数据库:application
导入数据库脚本 sql/_mysql_menu_data.sql
修改项目配置:src/main/resources/application.properties
修改数据库链接地址数据库用户名密码
修改 server.port=9999 # 项目端口号
修改security.user.name=admin # 项目验证登录用户 security.user.password=admin # 项目验证登录密码
执行mvn clean
执行mvn install
执行mvn spring-boot:run(java -jar target/danyuan.jar)
访问http://localhost:9999 # 端口号
输入用户名、密码 admin/admin
查看全部
 
本项目github:
https://github.com/514840279/danyuan-application系统采用技术或开源框架:
前端框架:adminLTE
前端技术:bootstrap,bootstrap-table,js,juqery,ajax,ztree,
后端技术:spring-boot,security
数据库: mysql
其他工具:maven,项目启动配置:
系统安装jdk1.8,mysql5.7,maven3.3
新建数据库:application
导入数据库脚本 sql/_mysql_menu_data.sql
修改项目配置:src/main/resources/application.properties
修改数据库链接地址数据库用户名密码
修改 server.port=9999 # 项目端口号
修改security.user.name=admin # 项目验证登录用户 security.user.password=admin # 项目验证登录密码
执行mvn clean
执行mvn install
执行mvn spring-boot:run(java -jar target/danyuan.jar)
访问http://localhost:9999 # 端口号
输入用户名、密码 admin/admin

SpringBoot配置应用自定义启动端口出现405问题

Spring Bootitmuch.com 回复了问题 • 4 人关注 • 2 个回复 • 543 次浏览 • 2017-07-19 18:32 • 来自相关话题

SpringBoot 整合 Dubbo/ZooKeeper 详解 SOA 案例

Spring Boot泥瓦匠BYSocket 发表了文章 • 3 个评论 • 1472 次浏览 • 2017-06-03 17:22 • 来自相关话题

“看看星空,会觉得自己很渺小,可能我们在宇宙中从来就是一个偶然。所以,无论什么事情,仔细想一想,都没有什么大不了的。这能帮助自己在遇到挫折时稳定心态,想得更开。”  – 《腾讯传》

本文提纲
一、为啥整合 Dubbo 实现 SOA
二、运行 springboot-dubbo-server 和 springboot-dubbo-client 工程
三、springboot-dubbo-server 和 springboot-dubbo-client 工程配置详解 

一、为啥整合 Dubbo 实现 SOA

Dubbo 不单单只是高性能的 RPC 调用框架,更是 SOA 服务治理的一种方案。
核心:
1. 远程通信,向本地调用一样调用远程方法。
2. 集群容错
3. 服务自动发现和注册,可平滑添加或者删除服务提供者。
我们常常使用 Springboot 暴露 HTTP 服务,并走 JSON 模式。但慢慢量大了,一种 SOA 的治理方案。这样可以暴露出 Dubbo 服务接口,提供给 Dubbo 消费者进行 RPC 调用。下面我们详解下如何集成 Dubbo。
 

二、运行 springboot-dubbo-server 和 springboot-dubbo-client 工程

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+、Dubbo 2.5+、ZooKeeper 3.3+
 

1.ZooKeeper 服务注册中心
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
下载 ZooKeeper ,地址 http://www.apache.org/dyn/closer.cgi/zookeeper
解压 ZooKeepertar zxvf zookeeper-3.4.8.tar.gz在 conf 目录新建 zoo.cfg ,照着该目录的 zoo_sample.cfg 配置如下。cd zookeeper-3.3.6/conf
vim zoo.cfgzoo.cfg 代码如下(自己指定 log 文件目录):tickTime=2000
dataDir=/javaee/zookeeper/data
dataLogDir=/javaee/zookeeper/log
clientPort=2181
在 bin 目录下,启动 ZooKeeper:cd zookeeper-3.3.6/bin
./zkServer.sh start
2. git clone 下载工程 springboot-learning-example
项目地址见 GitHub – https://github.com/JeffLi1993/ ... ample:git clone git@github.com:JeffLi1993/springboot-learning-example.git
然后,Maven 编译安装这个工程:cd springboot-learning-example
mvn clean install
 

3.运行 springboot-dubbo-server Dubbo 服务提供者工程
右键运行 springboot-dubbo-server 工程 ServerApplication 应用启动类的 main 函数。Console 中出现如下表示项目启动成功:






这里表示 Dubbo 服务已经启动成功,并注册到 ZK (ZooKeeper)中。
 

4.运行 springboot-dubbo-client Dubbo 服务消费者工程
右键运行 springboot-dubbo-client 工程 ClientApplication 应用启动类的 main 函数。Console 中出现如下:...
2017-03-01 16:31:38.473 INFO 9896 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-03-01 16:31:38.538 INFO 9896 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8081 (http)
2017-03-01 16:31:38.547 INFO 9896 --- [ main] org.spring.springboot.ClientApplication : Started ClientApplication in 6.055 seconds (JVM running for 7.026)
City{id=1, provinceId=2, cityName='温岭', description='是我的故乡'}最后打印的城市信息,就是通过 Dubbo 服务接口调用获取的。顺利运行成功,下面详解下各个代码及配置。
 

三、springboot-dubbo-server 和 springboot-dubbo-client 工程配置详解
代码都在 GitHub 上, https://github.com/JeffLi1993/ ... ample。
1.详解 springboot-dubbo-server Dubbo 服务提供者工程
springboot-dubbo-server 工程目录结构├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ └── springboot
│ ├── ServerApplication.java
│ ├── domain
│ │ └── City.java
│ └── dubbo
│ ├── CityDubboService.java
│ └── impl
│ └── CityDubboServiceImpl.java
└── resources
└── application.properties

a.pom.xml 配置
pom.xml 中依赖了 spring-boot-starter-dubbo 工程,该项目地址是 https://github.com/teaey/spring-boot-starter-dubbo。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/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>

<groupId>springboot</groupId>
<artifactId>springboot-dubbo-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-dubbo 服务端:: 整合 Dubbo/ZooKeeper 详解 SOA 案例</name>

<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>

<properties>
<dubbo-spring-boot>1.0.0</dubbo-spring-boot>
</properties>

<dependencies>

<!-- Spring Boot Dubbo 依赖 -->
<dependency>
<groupId>io.dubbo.springboot</groupId>
<artifactId>spring-boot-starter-dubbo</artifactId>
<version>${dubbo-spring-boot}</version>
</dependency>

<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
b.application.properties 配置## Dubbo 服务提供者配置
spring.dubbo.application.name=provider
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880
spring.dubbo.scan=org.spring.springboot.dubbo
这里 ZK 配置的地址和端口,就是上面本机搭建的 ZK 。如果有自己的 ZK 可以修改下面的配置。配置解释如下:spring.dubbo.application.name 应用名称
spring.dubbo.registry.address 注册中心地址
spring.dubbo.protocol.name 协议名称
spring.dubbo.protocol.port 协议端口
spring.dubbo.scan dubbo 服务类包目录

c.CityDubboServiceImpl.java 城市业务 Dubbo 服务层实现层类// 注册为 Dubbo 服务
@Service(version = "1.0.0")
public class CityDubboServiceImpl implements CityDubboService {

public City findCityByName(String cityName) {
return new City(1L,2L,"温岭","是我的故乡");
}
}@Service 注解标识为 Dubbo 服务,并通过 version 指定了版本号。
 
d.City.java 城市实体类
实体类通过 Dubbo 服务之间 RPC 调用,则需要实现序列化接口。最好指定下 serialVersionUID 值。
 

2.详解 springboot-dubbo-client Dubbo 服务消费者工程
springboot-dubbo-client 工程目录结构├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ └── springboot
│ ├── ClientApplication.java
│ ├── domain
│ │ └── City.java
│ └── dubbo
│ ├── CityDubboConsumerService.java
│ └── CityDubboService.java
└── resources
└── application.propertiespom.xml 、 CityDubboService.java、City.java 没有改动。Dubbo 消费者通过引入接口实现 Dubbo 接口的调用。
 

a.application.properties 配置## 避免和 server 工程端口冲突
server.port=8081

## Dubbo 服务消费者配置
spring.dubbo.application.name=consumer
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.scan=org.spring.springboot.dubbo因为 springboot-dubbo-server 工程启动占用了 8080 端口,所以这边设置端口为 8081。
 

b.CityDubboConsumerService.java 城市 Dubbo 服务消费者@Component
public class CityDubboConsumerService {

@Reference(version = "1.0.0")
CityDubboService cityDubboService;

public void printCity() {
String cityName="温岭";
City city = cityDubboService.findCityByName(cityName);
System.out.println(city.toString());
}
}@Reference(version = “1.0.0”) 通过该注解,订阅该接口版本为 1.0.0 的 Dubbo 服务。
这里将 CityDubboConsumerService 注入 Spring 容器,是为了更方便的获取该 Bean,然后验证这个 Dubbo 调用是否成功。
 

c.ClientApplication.java 客户端启动类@SpringBootApplication
public class ClientApplication {

public static void main(String args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
ConfigurableApplicationContext run = SpringApplication.run(ClientApplication.class, args);
CityDubboConsumerService cityService = run.getBean(CityDubboConsumerService.class);
cityService.printCity();
}
}解释下这段逻辑,就是启动后从 Bean 容器中获取城市 Dubbo 服务消费者 Bean。然后调用该 Bean 方法去验证 Dubbo 调用是否成功。
 

四、小结

还有涉及到服务的监控,治理。这本质上和 SpringBoot 无关,所以这边不做一一介绍。感谢阿里 teaey 提供的 starter-dubbo 项目。
 
本文作者: 泥瓦匠
原文链接: http://www.bysocket.com/?p=1681
版权归作者所有,转载请注明出处 查看全部
“看看星空,会觉得自己很渺小,可能我们在宇宙中从来就是一个偶然。所以,无论什么事情,仔细想一想,都没有什么大不了的。这能帮助自己在遇到挫折时稳定心态,想得更开。”  – 《腾讯传》

本文提纲
一、为啥整合 Dubbo 实现 SOA
二、运行 springboot-dubbo-server 和 springboot-dubbo-client 工程
三、springboot-dubbo-server 和 springboot-dubbo-client 工程配置详解 

一、为啥整合 Dubbo 实现 SOA

Dubbo 不单单只是高性能的 RPC 调用框架,更是 SOA 服务治理的一种方案。
核心
1. 远程通信,向本地调用一样调用远程方法。
2. 集群容错
3. 服务自动发现和注册,可平滑添加或者删除服务提供者。
我们常常使用 Springboot 暴露 HTTP 服务,并走 JSON 模式。但慢慢量大了,一种 SOA 的治理方案。这样可以暴露出 Dubbo 服务接口,提供给 Dubbo 消费者进行 RPC 调用。下面我们详解下如何集成 Dubbo。
 

二、运行 springboot-dubbo-server 和 springboot-dubbo-client 工程

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+、Dubbo 2.5+、ZooKeeper 3.3+
 

1.ZooKeeper 服务注册中心
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
下载 ZooKeeper ,地址 http://www.apache.org/dyn/closer.cgi/zookeeper
解压 ZooKeeper
tar zxvf zookeeper-3.4.8.tar.gz
在 conf 目录新建 zoo.cfg ,照着该目录的 zoo_sample.cfg 配置如下。
cd zookeeper-3.3.6/conf
vim zoo.cfg
zoo.cfg 代码如下(自己指定 log 文件目录):
tickTime=2000
dataDir=/javaee/zookeeper/data
dataLogDir=/javaee/zookeeper/log
clientPort=2181

在 bin 目录下,启动 ZooKeeper:
cd zookeeper-3.3.6/bin
./zkServer.sh start

2. git clone 下载工程 springboot-learning-example
项目地址见 GitHub – https://github.com/JeffLi1993/ ... ample
git clone git@github.com:JeffLi1993/springboot-learning-example.git

然后,Maven 编译安装这个工程:
cd springboot-learning-example
mvn clean install

 

3.运行 springboot-dubbo-server Dubbo 服务提供者工程
右键运行 springboot-dubbo-server 工程 ServerApplication 应用启动类的 main 函数。Console 中出现如下表示项目启动成功:

sdubbo.png


这里表示 Dubbo 服务已经启动成功,并注册到 ZK (ZooKeeper)中。
 

4.运行 springboot-dubbo-client Dubbo 服务消费者工程
右键运行 springboot-dubbo-client 工程 ClientApplication 应用启动类的 main 函数。Console 中出现如下:
...
2017-03-01 16:31:38.473 INFO 9896 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-03-01 16:31:38.538 INFO 9896 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8081 (http)
2017-03-01 16:31:38.547 INFO 9896 --- [ main] org.spring.springboot.ClientApplication : Started ClientApplication in 6.055 seconds (JVM running for 7.026)
City{id=1, provinceId=2, cityName='温岭', description='是我的故乡'}
最后打印的城市信息,就是通过 Dubbo 服务接口调用获取的。顺利运行成功,下面详解下各个代码及配置。
 

三、springboot-dubbo-server 和 springboot-dubbo-client 工程配置详解
代码都在 GitHub 上, https://github.com/JeffLi1993/ ... ample
1.详解 springboot-dubbo-server Dubbo 服务提供者工程
springboot-dubbo-server 工程目录结构
├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ └── springboot
│ ├── ServerApplication.java
│ ├── domain
│ │ └── City.java
│ └── dubbo
│ ├── CityDubboService.java
│ └── impl
│ └── CityDubboServiceImpl.java
└── resources
└── application.properties


a.pom.xml 配置
pom.xml 中依赖了 spring-boot-starter-dubbo 工程,该项目地址是 https://github.com/teaey/spring-boot-starter-dubbo。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/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>

<groupId>springboot</groupId>
<artifactId>springboot-dubbo-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-dubbo 服务端:: 整合 Dubbo/ZooKeeper 详解 SOA 案例</name>

<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>

<properties>
<dubbo-spring-boot>1.0.0</dubbo-spring-boot>
</properties>

<dependencies>

<!-- Spring Boot Dubbo 依赖 -->
<dependency>
<groupId>io.dubbo.springboot</groupId>
<artifactId>spring-boot-starter-dubbo</artifactId>
<version>${dubbo-spring-boot}</version>
</dependency>

<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>

b.application.properties 配置
## Dubbo 服务提供者配置
spring.dubbo.application.name=provider
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880
spring.dubbo.scan=org.spring.springboot.dubbo

这里 ZK 配置的地址和端口,就是上面本机搭建的 ZK 。如果有自己的 ZK 可以修改下面的配置。配置解释如下:
spring.dubbo.application.name 应用名称
spring.dubbo.registry.address 注册中心地址
spring.dubbo.protocol.name 协议名称
spring.dubbo.protocol.port 协议端口
spring.dubbo.scan dubbo 服务类包目录


c.CityDubboServiceImpl.java 城市业务 Dubbo 服务层实现层类
// 注册为 Dubbo 服务
@Service(version = "1.0.0")
public class CityDubboServiceImpl implements CityDubboService {

public City findCityByName(String cityName) {
return new City(1L,2L,"温岭","是我的故乡");
}
}
@Service 注解标识为 Dubbo 服务,并通过 version 指定了版本号。
 
d.City.java 城市实体类
实体类通过 Dubbo 服务之间 RPC 调用,则需要实现序列化接口。最好指定下 serialVersionUID 值。
 

2.详解 springboot-dubbo-client Dubbo 服务消费者工程
springboot-dubbo-client 工程目录结构
├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ └── springboot
│ ├── ClientApplication.java
│ ├── domain
│ │ └── City.java
│ └── dubbo
│ ├── CityDubboConsumerService.java
│ └── CityDubboService.java
└── resources
└── application.properties
pom.xml 、 CityDubboService.java、City.java 没有改动。Dubbo 消费者通过引入接口实现 Dubbo 接口的调用。
 

a.application.properties 配置
## 避免和 server 工程端口冲突
server.port=8081

## Dubbo 服务消费者配置
spring.dubbo.application.name=consumer
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.scan=org.spring.springboot.dubbo
因为 springboot-dubbo-server 工程启动占用了 8080 端口,所以这边设置端口为 8081。
 

b.CityDubboConsumerService.java 城市 Dubbo 服务消费者
@Component
public class CityDubboConsumerService {

@Reference(version = "1.0.0")
CityDubboService cityDubboService;

public void printCity() {
String cityName="温岭";
City city = cityDubboService.findCityByName(cityName);
System.out.println(city.toString());
}
}
@Reference(version = “1.0.0”) 通过该注解,订阅该接口版本为 1.0.0 的 Dubbo 服务。
这里将 CityDubboConsumerService 注入 Spring 容器,是为了更方便的获取该 Bean,然后验证这个 Dubbo 调用是否成功。
 

c.ClientApplication.java 客户端启动类
@SpringBootApplication
public class ClientApplication {

public static void main(String args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
ConfigurableApplicationContext run = SpringApplication.run(ClientApplication.class, args);
CityDubboConsumerService cityService = run.getBean(CityDubboConsumerService.class);
cityService.printCity();
}
}
解释下这段逻辑,就是启动后从 Bean 容器中获取城市 Dubbo 服务消费者 Bean。然后调用该 Bean 方法去验证 Dubbo 调用是否成功。
 

四、小结

还有涉及到服务的监控,治理。这本质上和 SpringBoot 无关,所以这边不做一一介绍。感谢阿里 teaey 提供的 starter-dubbo 项目。
 
本文作者: 泥瓦匠
原文链接: http://www.bysocket.com/?p=1681
版权归作者所有,转载请注明出处

SpringData ES中一些底层原理的分析

Spring Dataformat_coder 发表了文章 • 7 个评论 • 974 次浏览 • 2017-06-02 18:31 • 来自相关话题

之前写过一篇SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题,顺便深入学习下Spring Data Elasticsearch。
 
Spring Data Elasticsearch是Spring Data针对Elasticsearch的实现。

它跟Spring Data一样,提供了Repository接口,我们只需要定义一个新的接口并继承这个Repository接口,然后就可以注入这个新的接口使用了。
 
定义接口:
 
@Repository
public interface TaskRepository extends ElasticsearchRepository<Task, String> { }
注入接口进行使用:
 
@Autowired
private TaskRepository taskRepository;

....
taskRepository.save(task);
Repository接口的代理生成
 
上面的例子中TaskRepository是个接口,而我们却直接注入了这个接口并调用方法;很明显,这是错误的。

其实SpringData ES内部基于这个TaskRepository接口构造一个SimpleElasticsearchRepository,真正被注入的是这个SimpleElasticsearchRepository。

这个过程是如何实现的呢?  来分析一下。

ElasticsearchRepositoriesAutoConfiguration自动化配置类会导入ElasticsearchRepositoriesRegistrar这个ImportBeanDefinitionRegistrar。

ElasticsearchRepositoriesRegistrar继承自AbstractRepositoryConfigurationSourceSupport,是个ImportBeanDefinitionRegistrar接口的实现类,会被Spring容器调用registerBeanDefinitions进行自定义bean的注册。

ElasticsearchRepositoriesRegistrar委托给RepositoryConfigurationDelegate完成bean的解析。

整个解析过程可以分3个步骤:
 
找出模块中的org.springframework.data.repository.Repository接口的实现类或者org.springframework.data.repository.RepositoryDefinition注解的修饰类,并会过滤掉org.springframework.data.repository.NoRepositoryBean注解的修饰类。找出后封装到RepositoryConfiguration中遍历这些RepositoryConfiguration,然后构造成BeanDefinition并注册到Spring容器中。需要注意的是这些RepositoryConfiguration会以beanClass为ElasticsearchRepositoryFactoryBean这个类的方式被注册,并把对应的Repository接口当做构造参数传递给ElasticsearchRepositoryFactoryBean,还会设置相应的属性比如elasticsearchOperations、evaluationContextProvider、namedQueries、repositoryBaseClass、lazyInitqueryLookupStrategyKeyElasticsearchRepositoryFactoryBean被实例化的时候设置对应的构造参数和属性。设置完毕以后调用afterPropertiesSet方法(实现了InitializingBean接口)。在afterPropertiesSet方法内部会去创建RepositoryFactorySupport类,并进行一些初始化,比如namedQueries、repositoryBaseClass等。然后通过这个RepositoryFactorySupport的getRepository方法基于Repository接口创建出代理类,并使用AOP添加了几个MethodInterceptor
 
// 遍历基于第1步条件得到的RepositoryConfiguration集合
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : extension
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode)) {
// 构造出BeanDefinitionBuilder
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);

extension.postProcess(definitionBuilder, configurationSource);

if (isXml) {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource) configurationSource);
} else {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource);
}

// 使用命名策略生成bean的名字
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);

if (LOGGER.isDebugEnabled()) {
LOGGER.debug(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName,
configuration.getRepositoryInterface(), extension.getRepositoryFactoryClassName());
}

beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
// 注册到Spring容器中
registry.registerBeanDefinition(beanName, beanDefinition);
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
}

// build方法
public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) {

Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
// 得到factoryBeanName,这里会使用extension.getRepositoryFactoryClassName()去获得
// extension.getRepositoryFactoryClassName()返回的正是ElasticsearchRepositoryFactoryBean
String factoryBeanName = configuration.getRepositoryFactoryBeanName();
factoryBeanName = StringUtils.hasText(factoryBeanName) ? factoryBeanName
: extension.getRepositoryFactoryClassName();
// 基于factoryBeanName构造BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(factoryBeanName);

builder.getRawBeanDefinition().setSource(configuration.getSource());
// 设置ElasticsearchRepositoryFactoryBean的构造参数,这里是对应的Repository接口
// 设置一些的属性值
builder.addConstructorArgValue(configuration.getRepositoryInterface());
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
builder.addPropertyValue("repositoryBaseClass", configuration.getRepositoryBaseClassName());

NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder(
extension.getDefaultNamedQueryLocation());

if (StringUtils.hasText(configuration.getNamedQueriesLocation())) {
definitionBuilder.setLocations(configuration.getNamedQueriesLocation());
}

builder.addPropertyValue("namedQueries", definitionBuilder.build(configuration.getSource()));
// 查找是否有对应Repository接口的自定义实现类
String customImplementationBeanName = registerCustomImplementation(configuration);
// 存在自定义实现类的话,设置到属性中
if (customImplementationBeanName != null) {
builder.addPropertyReference("customImplementation", customImplementationBeanName);
builder.addDependsOn(customImplementationBeanName);
}

RootBeanDefinition evaluationContextProviderDefinition = new RootBeanDefinition(
ExtensionAwareEvaluationContextProvider.class);
evaluationContextProviderDefinition.setSource(configuration.getSource());
// 设置一些的属性值
builder.addPropertyValue("evaluationContextProvider", evaluationContextProviderDefinition);

return builder;
}

// RepositoryFactorySupport的getRepository方法,获得Repository接口的代理类
public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {

// 获取Repository的元数据
RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
// 获取Repository的自定义实现类
Class<?> customImplementationClass = null == customImplementation ? null : customImplementation.getClass();
// 根据元数据和自定义实现类得到Repository的RepositoryInformation信息类
// 获取信息类的时候如果发现repositoryBaseClass是空的话会根据meta中的信息去自动匹配
// 具体匹配过程在下面的getRepositoryBaseClass方法中说明
RepositoryInformation information = getRepositoryInformation(metadata, customImplementationClass);
// 验证
validate(information, customImplementation);
// 得到最终的目标类实例,会通过repositoryBaseClass去查找
Object target = getTargetRepository(information);

// 创建代理工厂
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(new Class[] { repositoryInterface, Repository.class });
// 进行aop相关的设置
result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);

if (TRANSACTION_PROXY_TYPE != null) {
result.addInterface(TRANSACTION_PROXY_TYPE);
}
// 使用RepositoryProxyPostProcessor处理
for (RepositoryProxyPostProcessor processor : postProcessors) {
processor.postProcess(result, information);
}

if (IS_JAVA_8) {
// 如果是JDK8的话,添加DefaultMethodInvokingMethodInterceptor
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
}

// 添加QueryExecutorMethodInterceptor
result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target));
// 使用代理工厂创建出代理类,这里是使用jdk内置的代理模式
return (T) result.getProxy(classLoader);
}

// 目标类的获取
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
// 如果Repository接口属于QueryDsl,抛出异常。目前还不支持
if (isQueryDslRepository(metadata.getRepositoryInterface())) {
throw new IllegalArgumentException("QueryDsl Support has not been implemented yet.");
}
// 如果主键是数值类型的话,repositoryBaseClass为NumberKeyedRepository
if (Integer.class.isAssignableFrom(metadata.getIdType())
|| Long.class.isAssignableFrom(metadata.getIdType())
|| Double.class.isAssignableFrom(metadata.getIdType())) {
return NumberKeyedRepository.class;
} else if (metadata.getIdType() == String.class) {
// 如果主键是String类型的话,repositoryBaseClass为SimpleElasticsearchRepository
return SimpleElasticsearchRepository.class;
} else if (metadata.getIdType() == UUID.class) {
// 如果主键是UUID类型的话,repositoryBaseClass为UUIDElasticsearchRepository
return UUIDElasticsearchRepository.class;
} else {
// 否则报错
throw new IllegalArgumentException("Unsupported ID type " + metadata.getIdType());
}
}
ElasticsearchRepositoryFactoryBean是一个FactoryBean接口的实现类,getObject方法返回的上面提到的getRepository方法返回的代理对象;getObjectType方法返回的是对应Repository接口类型。

我们文章一开始提到的注入TaskRepository的时候,实际上这个对象是ElasticsearchRepositoryFactoryBean类型的实例,只不过ElasticsearchRepositoryFactoryBean实现了FactoryBean接口,所以注入的时候会得到一个代理对象,这个代理对象是由jdk内置的代理生成的,并且它的target对象是SimpleElasticsearchRepository(主键是String类型)。
 
 
SpringData ES中ElasticsearchOperations的介绍
 
ElasticsearchTemplate实现了ElasticsearchOperations接口。

ElasticsearchOperations接口是SpringData对Elasticsearch操作的一层封装,比如有创建索引createIndex方法、获取索引的设置信息getSetting方法、查询对象queryForObject方法、分页查询方法queryForPage、删除文档delete方法、更新文档update方法等等。

ElasticsearchTemplate是具体的实现类,它有这些属性:
 
// elasticsearch提供的基于java的客户端连接接口。java对es集群的操作使用这个接口完成
private Client client;
// 一个转换器接口,定义了2个方法,分别可以获得MappingContext和ConversionService
// MappingContext接口用于获取所有的持久化实体和这些实体的属性
// ConversionService目前在SpringData ES中没有被使用
private ElasticsearchConverter elasticsearchConverter;
// 内部使用EntityMapper完成对象到json字符串和json字符串到对象的映射。默认使用jackson完成映射,可自定义
private ResultsMapper resultsMapper;
// 查询超时时间
private String searchTimeout;
Client接口在ElasticsearchAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public Client elasticsearchClient() {
try {
return createClient();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
ElasticsearchTemplate、ElasticsearchConverter以及SimpleElasticsearchMappingContext在ElasticsearchDataAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public ElasticsearchTemplate elasticsearchTemplate(Client client,
ElasticsearchConverter converter) {
try {
return new ElasticsearchTemplate(client, converter);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

@Bean
@ConditionalOnMissingBean
public ElasticsearchConverter elasticsearchConverter(
SimpleElasticsearchMappingContext mappingContext) {
return new MappingElasticsearchConverter(mappingContext);
}

@Bean
@ConditionalOnMissingBean
public SimpleElasticsearchMappingContext mappingContext() {
return new SimpleElasticsearchMappingContext();
}
 需要注意的是这个bean被自动化配置类构造的前提是它们在Spring容器中并不存在。
 
Repository的调用过程
 
以自定义的TaskRepository的save方法为例,大致的执行流程如下所示:



SimpleElasticsearchRepository的save方法具体的分析在SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题中分析过。

像自定义的Repository查询方法,或者Repository接口的自定义实现类的操作这些底层,可以去QueryExecutorMethodInterceptor中查看,大家有兴趣的可以自行查看源码。
  查看全部
之前写过一篇SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题,顺便深入学习下Spring Data Elasticsearch。
 
Spring Data ElasticsearchSpring Data针对Elasticsearch的实现。

它跟Spring Data一样,提供了Repository接口,我们只需要定义一个新的接口并继承这个Repository接口,然后就可以注入这个新的接口使用了。
 
定义接口:
 
@Repository
public interface TaskRepository extends ElasticsearchRepository<Task, String> { }

注入接口进行使用:
 
@Autowired
private TaskRepository taskRepository;

....
taskRepository.save(task);

Repository接口的代理生成
 
上面的例子中TaskRepository是个接口,而我们却直接注入了这个接口并调用方法;很明显,这是错误的。

其实SpringData ES内部基于这个TaskRepository接口构造一个SimpleElasticsearchRepository,真正被注入的是这个SimpleElasticsearchRepository。

这个过程是如何实现的呢?  来分析一下。

ElasticsearchRepositoriesAutoConfiguration自动化配置类会导入ElasticsearchRepositoriesRegistrar这个ImportBeanDefinitionRegistrar。

ElasticsearchRepositoriesRegistrar继承自AbstractRepositoryConfigurationSourceSupport,是个ImportBeanDefinitionRegistrar接口的实现类,会被Spring容器调用registerBeanDefinitions进行自定义bean的注册。

ElasticsearchRepositoriesRegistrar委托给RepositoryConfigurationDelegate完成bean的解析。

整个解析过程可以分3个步骤:
 
  1. 找出模块中的org.springframework.data.repository.Repository接口的实现类或者org.springframework.data.repository.RepositoryDefinition注解的修饰类,并会过滤掉org.springframework.data.repository.NoRepositoryBean注解的修饰类。找出后封装到RepositoryConfiguration中
  2. 遍历这些RepositoryConfiguration,然后构造成BeanDefinition并注册到Spring容器中。需要注意的是这些RepositoryConfiguration会以beanClass为ElasticsearchRepositoryFactoryBean这个类的方式被注册,并把对应的Repository接口当做构造参数传递给ElasticsearchRepositoryFactoryBean,还会设置相应的属性比如elasticsearchOperations、evaluationContextProvider、namedQueries、repositoryBaseClass、lazyInitqueryLookupStrategyKey
  3. ElasticsearchRepositoryFactoryBean被实例化的时候设置对应的构造参数和属性。设置完毕以后调用afterPropertiesSet方法(实现了InitializingBean接口)。在afterPropertiesSet方法内部会去创建RepositoryFactorySupport类,并进行一些初始化,比如namedQueries、repositoryBaseClass等。然后通过这个RepositoryFactorySupport的getRepository方法基于Repository接口创建出代理类,并使用AOP添加了几个MethodInterceptor

 
// 遍历基于第1步条件得到的RepositoryConfiguration集合
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : extension
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode)) {
// 构造出BeanDefinitionBuilder
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);

extension.postProcess(definitionBuilder, configurationSource);

if (isXml) {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource) configurationSource);
} else {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource);
}

// 使用命名策略生成bean的名字
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);

if (LOGGER.isDebugEnabled()) {
LOGGER.debug(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName,
configuration.getRepositoryInterface(), extension.getRepositoryFactoryClassName());
}

beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
// 注册到Spring容器中
registry.registerBeanDefinition(beanName, beanDefinition);
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
}

// build方法
public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) {

Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
// 得到factoryBeanName,这里会使用extension.getRepositoryFactoryClassName()去获得
// extension.getRepositoryFactoryClassName()返回的正是ElasticsearchRepositoryFactoryBean
String factoryBeanName = configuration.getRepositoryFactoryBeanName();
factoryBeanName = StringUtils.hasText(factoryBeanName) ? factoryBeanName
: extension.getRepositoryFactoryClassName();
// 基于factoryBeanName构造BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(factoryBeanName);

builder.getRawBeanDefinition().setSource(configuration.getSource());
// 设置ElasticsearchRepositoryFactoryBean的构造参数,这里是对应的Repository接口
// 设置一些的属性值
builder.addConstructorArgValue(configuration.getRepositoryInterface());
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
builder.addPropertyValue("repositoryBaseClass", configuration.getRepositoryBaseClassName());

NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder(
extension.getDefaultNamedQueryLocation());

if (StringUtils.hasText(configuration.getNamedQueriesLocation())) {
definitionBuilder.setLocations(configuration.getNamedQueriesLocation());
}

builder.addPropertyValue("namedQueries", definitionBuilder.build(configuration.getSource()));
// 查找是否有对应Repository接口的自定义实现类
String customImplementationBeanName = registerCustomImplementation(configuration);
// 存在自定义实现类的话,设置到属性中
if (customImplementationBeanName != null) {
builder.addPropertyReference("customImplementation", customImplementationBeanName);
builder.addDependsOn(customImplementationBeanName);
}

RootBeanDefinition evaluationContextProviderDefinition = new RootBeanDefinition(
ExtensionAwareEvaluationContextProvider.class);
evaluationContextProviderDefinition.setSource(configuration.getSource());
// 设置一些的属性值
builder.addPropertyValue("evaluationContextProvider", evaluationContextProviderDefinition);

return builder;
}

// RepositoryFactorySupport的getRepository方法,获得Repository接口的代理类
public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {

// 获取Repository的元数据
RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
// 获取Repository的自定义实现类
Class<?> customImplementationClass = null == customImplementation ? null : customImplementation.getClass();
// 根据元数据和自定义实现类得到Repository的RepositoryInformation信息类
// 获取信息类的时候如果发现repositoryBaseClass是空的话会根据meta中的信息去自动匹配
// 具体匹配过程在下面的getRepositoryBaseClass方法中说明
RepositoryInformation information = getRepositoryInformation(metadata, customImplementationClass);
// 验证
validate(information, customImplementation);
// 得到最终的目标类实例,会通过repositoryBaseClass去查找
Object target = getTargetRepository(information);

// 创建代理工厂
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(new Class[] { repositoryInterface, Repository.class });
// 进行aop相关的设置
result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);

if (TRANSACTION_PROXY_TYPE != null) {
result.addInterface(TRANSACTION_PROXY_TYPE);
}
// 使用RepositoryProxyPostProcessor处理
for (RepositoryProxyPostProcessor processor : postProcessors) {
processor.postProcess(result, information);
}

if (IS_JAVA_8) {
// 如果是JDK8的话,添加DefaultMethodInvokingMethodInterceptor
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
}

// 添加QueryExecutorMethodInterceptor
result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target));
// 使用代理工厂创建出代理类,这里是使用jdk内置的代理模式
return (T) result.getProxy(classLoader);
}

// 目标类的获取
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
// 如果Repository接口属于QueryDsl,抛出异常。目前还不支持
if (isQueryDslRepository(metadata.getRepositoryInterface())) {
throw new IllegalArgumentException("QueryDsl Support has not been implemented yet.");
}
// 如果主键是数值类型的话,repositoryBaseClass为NumberKeyedRepository
if (Integer.class.isAssignableFrom(metadata.getIdType())
|| Long.class.isAssignableFrom(metadata.getIdType())
|| Double.class.isAssignableFrom(metadata.getIdType())) {
return NumberKeyedRepository.class;
} else if (metadata.getIdType() == String.class) {
// 如果主键是String类型的话,repositoryBaseClass为SimpleElasticsearchRepository
return SimpleElasticsearchRepository.class;
} else if (metadata.getIdType() == UUID.class) {
// 如果主键是UUID类型的话,repositoryBaseClass为UUIDElasticsearchRepository
return UUIDElasticsearchRepository.class;
} else {
// 否则报错
throw new IllegalArgumentException("Unsupported ID type " + metadata.getIdType());
}
}

ElasticsearchRepositoryFactoryBean是一个FactoryBean接口的实现类,getObject方法返回的上面提到的getRepository方法返回的代理对象;getObjectType方法返回的是对应Repository接口类型。

我们文章一开始提到的注入TaskRepository的时候,实际上这个对象是ElasticsearchRepositoryFactoryBean类型的实例,只不过ElasticsearchRepositoryFactoryBean实现了FactoryBean接口,所以注入的时候会得到一个代理对象,这个代理对象是由jdk内置的代理生成的,并且它的target对象是SimpleElasticsearchRepository(主键是String类型)。
 
 
SpringData ES中ElasticsearchOperations的介绍
 
ElasticsearchTemplate实现了ElasticsearchOperations接口。

ElasticsearchOperations接口是SpringData对Elasticsearch操作的一层封装,比如有创建索引createIndex方法、获取索引的设置信息getSetting方法、查询对象queryForObject方法、分页查询方法queryForPage、删除文档delete方法、更新文档update方法等等。

ElasticsearchTemplate是具体的实现类,它有这些属性:
 
// elasticsearch提供的基于java的客户端连接接口。java对es集群的操作使用这个接口完成
private Client client;
// 一个转换器接口,定义了2个方法,分别可以获得MappingContext和ConversionService
// MappingContext接口用于获取所有的持久化实体和这些实体的属性
// ConversionService目前在SpringData ES中没有被使用
private ElasticsearchConverter elasticsearchConverter;
// 内部使用EntityMapper完成对象到json字符串和json字符串到对象的映射。默认使用jackson完成映射,可自定义
private ResultsMapper resultsMapper;
// 查询超时时间
private String searchTimeout;

Client接口在ElasticsearchAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public Client elasticsearchClient() {
try {
return createClient();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

ElasticsearchTemplate、ElasticsearchConverter以及SimpleElasticsearchMappingContext在ElasticsearchDataAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public ElasticsearchTemplate elasticsearchTemplate(Client client,
ElasticsearchConverter converter) {
try {
return new ElasticsearchTemplate(client, converter);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

@Bean
@ConditionalOnMissingBean
public ElasticsearchConverter elasticsearchConverter(
SimpleElasticsearchMappingContext mappingContext) {
return new MappingElasticsearchConverter(mappingContext);
}

@Bean
@ConditionalOnMissingBean
public SimpleElasticsearchMappingContext mappingContext() {
return new SimpleElasticsearchMappingContext();
}

 需要注意的是这个bean被自动化配置类构造的前提是它们在Spring容器中并不存在。
 
Repository的调用过程
 
以自定义的TaskRepository的save方法为例,大致的执行流程如下所示:



SimpleElasticsearchRepository的save方法具体的分析在SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题中分析过。

像自定义的Repository查询方法,或者Repository接口的自定义实现类的操作这些底层,可以去QueryExecutorMethodInterceptor中查看,大家有兴趣的可以自行查看源码。
 

《Spring 5 官方文档》1. Spring入门指南

Spring Framework泥瓦匠BYSocket 发表了文章 • 2 个评论 • 2105 次浏览 • 2017-05-31 16:04 • 来自相关话题

1.Spring入门指南

本参考指南提供了有关Spring Framework的详细信息。它全面的介绍了Spring的所有的功能,以及Spring涉及的基础概念(如“依赖注入” “Dependency Injection”)。

如果你是刚开始使用Spring,你可能需要首先创建一个Spring Boot应用程序来开始Spring框架之旅。Spring Boot提供了一个快速(和自治的)的方式来创建一个基于Spring的生产环境。它是基于Spring框架,支持约定优于配置,并且被设计成尽可能快地让你启动和运行程序。

您可以使用start.spring.io生成一个基本项目或按照新手入门指南里的任意一个指南构建项目,例如构建一个RESTful Web服务入门指南 。为了更容易帮助你理解,这些指南都是面向任务的,其中大部分都是基于Spring Boot的。 他们还涵盖了很多Spring原型工程,在您需要解决特定问题时可以考虑使用他们。

2.介绍Spring框架

Spring 框架是一个Java平台,它为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此您可以专注于应用程序的开发。

Spring可以让您从“plain old Java objects”(POJO)中构建应用程序和通过非侵入性的POJO实现企业应用服务。此功能适用于Java SE的编程模型,全部的或部分的适应Java EE模型。

这些例子告诉你,作为一个应用程序开发人员,如何从Spring平台中受益:

写一个Java方法执行数据库事务,而无需处理具体事务的APIs。
写一个本地Java方法去远程调用,而不必处理远程调用的APIs。
写一个本地Java方法实现管理操作,而不必处理JMX APIs。
写一个本地Java方法实现消息处理,而不必处理JMS APIs。

2.1依赖注入和控制反转

Java应用程序-这是一个宽松的术语,它包括的范围从受限的嵌入式应用程序到n层的服务器端企业应用程序-通常组成程序的对象互相协作而构成正确的应用程序。因此,在一个应用程序中的对象彼此具有依赖关系(dependencies)。

虽然Java平台提供了丰富的应用程序开发功能,但它缺乏将基本的模块组织成一个整体的方法,而将该任务留给了架构师和开发人员。虽然你可以使用如工厂,抽象工厂,Builder,装饰器和Service Locator等 设计模式来构建各种类和对象实例,使他们组合成应用程序,但这些模式无非只是:最佳实践赋予的一个名字,以及这是什么样的模式,应用于哪里,它能解决的问题等等。 模式是您必须在应用程序中自己实现的形式化的最佳实践。

Spring框架控制反转(IOC)组件通过提供一系列的标准化的方法把完全不同的组件组合成一个能够使用的应用程序来解决这个问题。Spring框架把形式化的设计模式编写为优秀的对象,你可以容易的集成到自己的应用程序中。许多组织和机构使用Spring框架,以这种方式(使用Spring的模式对象)来设计健壮的,可维护的应用程序。

背景

“ 现在的问题是,什么方面的控制被(他们)反转了? ”马丁·福勒2004年在他的网站提出了这个有关控制反转(IOC)的问题 ,福勒建议重命名,使之能够自我描述,并提出了依赖注入( Dependency Injection)。

2.2模块

Spring框架的功能被有组织的分散到约20个模块中。这些模块分布在核心容器,数据访问/集成,Web,AOP(面向切面​​的编程),植入(Instrumentation),消息传输和测试,如下面的图所示。

图2.1 Spring框架概述








以下部分列出了每个可用模块,以及它们的工件名称和它们支持的主要功能。工件的名字对应的是工件标识符,使用在依赖管理工具中。

2.2.1核心容器

核心容器由以下模块组成,spring-core, spring-beans,spring-context,spring-context-support,和spring-expression (Spring表达式语言)。

spring-core和spring-beans模块提供了框架的基础功能,包括IOC和依赖注入功能。 BeanFactory是一个成熟的工厂模式的实现。你不再需要编程去实现单例模式,允许你把依赖关系的配置和描述从程序逻辑中解耦。

上下文(spring-context)模块建立在由Core和Beans模块提供的坚实的基础上:它提供一个框架式的对象访问方式,类似于一个JNDI注册表。上下文模块从Beans模块继承其功能,并添加支持国际化(使用,例如,资源集合),事件传播,资源负载,并且透明创建上下文,例如,Servlet容器。Context模块还支持Java EE的功能,如EJB,JMX和基本的远程处理。ApplicationContext接口是Context模块的焦点。 spring-context-support支持整合普通第三方库到Spring应用程序上下文,特别是用于高速缓存(ehcache,JCache)和调度(CommonJ,Quartz)的支持。

spring-expression模块提供了强大的表达式语言去支持查询和操作运行时对象图。这是对JSP 2.1规范中规定的统一表达式语言(unified EL)的扩展。该语言支持设置和获取属性值,属性分配,方法调用,访问数组,集合和索引器的内容,逻辑和算术运算,变量命名以及从Spring的IoC容器中以名称检索对象。 它还支持列表投影和选择以及常见的列表聚合。

2.2.2 AOP和Instrumentation

spring-aop模块提供了一个符合AOP联盟(要求)的面向方面的编程实现,例如,允许您定义方法拦截器和切入点(pointcuts),以便干净地解耦应该被分离的功能实现。 使用源级元数据(source-level metadata)功能,您还可以以类似于.NET属性的方式将行为信息合并到代码中。

单独的spring-aspects模块,提供了与AspectJ的集成。

spring-instrument模块提供了类植入(instrumentation)支持和类加载器的实现,可以应用在特定的应用服务器中。该spring-instrument-tomcat 模块包含了支持Tomcat的植入代理。

2.2.3消息

Spring框架4包括spring-messaging(消息传递模块),其中包含来自Spring Integration的项目,例如,Message,MessageChannel,MessageHandler,和其他用来传输消息的基础应用。该模块还包括一组用于将消息映射到方法的注释(annotations),类似于基于Spring MVC注释的编程模型。

2.2.4数据访问/集成

数据访问/集成层由JDBC,ORM,OXM,JMS和事务模块组成。

spring-jdbc模块提供了一个JDBC –抽象层,消除了需要的繁琐的JDBC编码和数据库厂商特有的错误代码解析。

spring-tx模块支持用于实现特殊接口和所有POJO(普通Java对象)的类的编程和声明式事务 管理。

spring-orm模块为流行的对象关系映射(object-relational mapping )API提供集成层,包括JPA和Hibernate。使用spring-orm模块,您可以将这些O / R映射框架与Spring提供的所有其他功能结合使用,例如前面提到的简单声明性事务管理功能。

spring-oxm模块提供了一个支持对象/ XML映射实现的抽象层,如JAXB,Castor,JiBX和XStream。

spring-jms模块(Java Messaging Service) 包含用于生产和消费消息的功能。自Spring Framework 4.1以来,它提供了与 spring-messaging模块的集成。

2.2.5 Web

Web层由spring-web,spring-webmvc和spring-websocket 模块组成。

spring-web模块提供基本的面向Web的集成功能,例如多部分文件上传功能,以及初始化一个使用了Servlet侦听器和面向Web的应用程序上下文的IoC容器。它还包含一个HTTP客户端和Spring的远程支持的Web相关部分。

spring-webmvc模块(也称为Web-Servlet模块)包含用于Web应用程序的Spring的模型-视图-控制器(MVC)和REST Web Services实现。 Spring的MVC框架提供了领域模型代码和Web表单之间的清晰分离,并与Spring Framework的所有其他功能集成。

2.2.6测试

spring-test模块支持使用JUnit或TestNG对Spring组件进行单元测试和 集成测试。它提供了Spring ApplicationContexts的一致加载和这些上下文的缓存。它还提供可用于独立测试代码的模仿(mock)对象。

2.3使用场景

之前描述的构建模块使Spring成为许多应用场景的理性选择,从在资源受限设备上运行的嵌入式应用程序到使用Spring的事务管理功能和Web框架集成的全面的企业应用程序。

图2.2 典型的成熟完整的Spring Web应用程序







Spring的声明式事务管理功能使Web应用程序完全事务性,就像使用EJB容器管理的事务一样。所有您的定制业务逻辑都可以使用简单的POJO实现,并由Spring的IoC容器进行管理。附加服务包括支持发送电子邮件和独立于Web层的验证,可让您选择执行验证规则的位置。 Spring的ORM支持与JPA和Hibernate集成;例如,当使用Hibernate时,可以继续使用现有的映射文件和标准的Hibernate SessionFactory配置。表单控制器将Web层与域模型无缝集成,从而无需ActionForms或将HTTP参数转换为域模型的值的其他类。

图2.3使用第三方web框架的Spring中间层








有时情况不允许你完全切换到不同的框架。 Spring框架并不强制您使用其中的一切;这不是一个全有或全无的解决方案。使用Struts,Tapestry,JSF或其他UI框架构建的现有前端可以与基于Spring的中间层集成,从而允许您使用Spring事务功能。您只需要使用ApplicationContext连接您的业务逻辑,并使用WebApplicationContext来集成您的Web层。

图2.4 远程使用场景








当您需要通过Web服务访问现有代码时,你可以使用Spring的 Hessian-,Rmi-或HttpInvokerProxyFactoryBean类。启用对现有应用程序的远程访问并不困难。

图2.5  EJBs – 包装现有的POJOs








Spring Framework还为Enterprise JavaBeans提供了一个访问和抽象层,使您能够重用现有的POJO,并将其包装在无状态会话bean中,以便在可能需要声明式安全性的, 可扩展的,故障安全的Web应用程序中使用。

2.3.1依赖管理和命名约定

依赖关系管理和依赖注入是不同的。为了将Spring的这些不错的功能(如依赖注入)集成到应用程序中,您需要组装所有需要的库(jar文件),并在运行时导入到类路径(classpath)中,也有可能在编译时就需要加入类路径。这些依赖关系不是注入的虚拟组件,而是文件系统中的物理资源(通常是这样)。依赖关系管理的过程包括定位这些资源,存储它们并将其添加到类路径中。依赖关系可以是直接的(例如,我的应用程序在运行时依赖于Spring)或间接的(例如我的应用程序依赖于commons-dbcp ,而commons-dbcp 又依赖于commons-pool)。间接依赖关系具有“传递性”,它们是最难识别和管理的依赖关系。

如果你要使用Spring,你需要获得一个包含你所需要的Spring模块的jar库的副本。为了使这更容易,Spring被打包为一组尽可能分离依赖关系的模块,例如,如果您不想编写Web应用程序,则不需要spring-web模块。要引用本指南中的Spring库模块,我们使用一个简写命名约定spring- *或spring – *.jar,其中*表示模块的简称(例如spring-core,spring-webmvc,spring-jms等) )。您实际使用的jar文件名通常是与版本号连接的模块名称(例如spring-core-5.0.0.M5.jar)。

Spring框架的每个版本都会发布到以下几个地方:

Maven Central,它是Maven查询的默认存储库,不需要任何特殊配置。 Spring的许多常见的库也可以从Maven Central获得,Spring社区的大部分使用Maven进行依赖关系管理,所以这对他们来说很方便。这里的jar的名字是spring – * – <version> .jar,Maven groupId是org.springframework。
在专门用于Spring的公共Maven存储库中。除了最终的GA版本,该存储库还承载开发快照和里程碑版本。 jar文件名与Maven Central格式相同,因此这是一个有用的地方,可以将开发中的版本的Spring与在Maven Central中部署的其他库配合使用。该存储库还包含集中分发的zip文件,其中包含所有Spring jar,捆绑在一起以便于下载。

所以你需要决定的第一件事是如何管理你的依赖关系:我们通常建议使用像Maven,Gradle或Ivy这样的自动化系统,但你也可以通过自己下载所有的jar来手动执行。

您将在下面找到Spring artifacts列表。有关每个模块的更完整的描述,第2.2节“模块”.

表2.1  Spring框架的Artifacts

GroupId                   ArtifactId                                Description(描述)
org.springframework spring-aop                             Proxy-based AOP support
org.springframework spring-aspects                       AspectJ based aspects
org.springframework spring-beans                          Beans support, including Groovy
org.springframework spring-context                        Application context runtime, including scheduling and remoting abstractions
org.springframework spring-context-support           Support classes for integrating common third-party libraries into a Spring application context
org.springframework spring-core                            Core utilities, used by many other Spring modules
org.springframework spring-expression                    Spring Expression Language (SpEL)
org.springframework spring-instrument                     Instrumentation agent for JVM bootstrapping
org.springframework spring-instrument-tomcat         Instrumentation agent for Tomcat
org.springframework spring-jdbc                             JDBC support package, including DataSource setup and JDBC access support
org.springframework spring-jms                              JMS support package, including helper classes to send and receive JMS messages
org.springframework spring-messaging                    Support for messaging architectures and protocols
org.springframework spring-orm                             Object/Relational Mapping, including JPA and Hibernate support
org.springframework spring-oxm                             Object/XML Mapping
org.springframework spring-test                             Support for unit testing and integration testing Spring components
org.springframework spring-tx                                Transaction infrastructure, including DAO support and JCA integration
org.springframework spring-web                             Web support packages, including client and web remoting
org.springframework spring-webmvc                       REST Web Services and model-view-controller implementation for web applications
org.springframework spring-websocket                   WebSocket and SockJS implementations, including STOMP support

Spring的依赖和依赖于Spring

虽然Spring为大量企业和其他外部工具提供集成和支持,但它有意将其强制性的依赖保持在最低限度:在使用Spring用于简单的用例时,您不必定位和下载(甚至自动的去做)大量的jar库。对于基本依赖注入功能,只有一个强制性的外部依赖关系,也就是用于日志记录的依赖(有关日志记录选项的更详细描述,请参阅下文)。

接下来,我们概述了配置依赖于Spring的应用程序所需的基本步骤,首先是使用Maven,然后使用Gradle,最后使用Ivy。在任何情况下,如果不清楚,请参阅依赖关系管理系统的文档,或查看一些示例代码 – Spring本身在构建时使用Gradle来管理依赖关系,我们的示例主要使用Gradle或Maven。

Maven的依赖管理

如果您使用Maven进行依赖关系管理,则甚至不需要显式提供依赖关系。 例如,要创建应用程序上下文并使用依赖注入来配置应用程序,您的Maven依赖配置如下所示:<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.0.M5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
(依赖配置)就是这样。注意,如果您不需要针对Spring API进行编译,那么范围(scope)可以被声明为运行时,通常情况下这是基本的依赖注入用例。

以上示例适用于Maven Central存储库。要使用Spring Maven仓库(例如,使用里程碑或开发中的快照版本),您需要在Maven配置中指定仓库位置。完整版本:<repositories>
<repository>
<id>io.spring.repo.maven.release</id>
<url>http://repo.spring.io/release/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
对于里程碑(milestones):<repositories>
<repository>
<id>io.spring.repo.maven.milestone</id>
<url>http://repo.spring.io/milestone/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
而对于快照(snapshots):<repositories>
<repository>
<id>io.spring.repo.maven.snapshot</id>
<url>http://repo.spring.io/snapshot/</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
Maven的“材料清单”依赖

使用Maven时,可能会意外混合不同版本的Spring JAR。例如,您可能会发现第三方库或另一个Spring项目会传递依赖于旧版本的Spring JARs。如果您忘记自己明确声明直接依赖,可能会出现各种意外问题。

为了克服这些问题,Maven支持“材料清单(bill of materials)”(BOM)依赖的概念。您可以在dependencyManagement部分中导入spring-framework-bom,以确保所有Spring依赖(直接和传递)都是相同的版本<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.0.0.M5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
使用BOM的另外一个好处是,您不再需要在依赖于Spring Framework artifacts时指定<version>属性:<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependencies>
 
Gradle 依赖管理

要使用Gradle 构建系统的Spring仓库,请在仓库部分中包含适当的URL:repositories {
mavenCentral()
// and optionally...
maven { url "http://repo.spring.io/release" }
}
 
您可以根据需要将repositoriesURL从/release更改为/milestone或/snapshot。一旦repositories被配置,你可以通常使用Gradle方式来声明依赖:dependencies {
compile("org.springframework:spring-context:5.0.0.M5")
testCompile("org.springframework:spring-test:5.0.0.M5")
}
Ivy依赖管理

如果您喜欢使用 Ivy 来管理依赖关系,那么还有类似的配置选项。 要配置Ivy指向Spring仓库,请将以下解析器添加到您的ivysettings.xml中:<resolvers>
<ibiblio name="io.spring.repo.maven.release"
m2compatible="true"
root="http://repo.spring.io/release/"/>
</resolvers>
您可以更改root从URL /release/到/milestone/或/snapshot/适当。

配置完成后,您可以在通常的方式添加依赖。例如(在ivy.xml):

您可以根据需要将rootURL从 /release/更改为/milestone/或/snapshot/。 配置完成后,您可以按通常的方式添加依赖项。例如(在ivy.xml中):<dependency org="org.springframework"
name="spring-core" rev="5.0.0.M5" conf="compile->runtime"/>
 
Zip文件发行

虽然使用依赖关系管理的构建系统是推荐的获取Spring框架的方法,但仍然可以下载发布的zip文件。

Zip文件发布到Spring Maven存储库(这仅仅是为了我们的方便,您不需要使用Maven或任何其他构建系统才能下载它们)。

要下载发布的zip文件,打开Web浏览器到http://repo.spring.io/release/ ... pring,并为所需的版本选择相应的子文件夹。zip文件以-dist.zip结尾,例如spring-framework- {spring-version} -RELEASE-dist.zip。里程碑和 快照也会发布在这里。

2.3.2 日志

日志是Spring非常重要的依赖,因为a)它是唯一的强制性外部依赖关系,b)每个人都喜欢看到他们使用的工具的一些输出,以及c)Spring集成了许多其他工具,都会具有日志依赖关系。应用程序开发人员的目标之一通常是将统一的日志配置放在整个应用程序的中央位置,包括所有外部组件。这比以前有更多的困难,因为有这么多的日志框架可以选择。

Spring中的强制性日志依赖关系是Jakarta Commons Logging API(JCL)。我们针对JCL进行编译,我们还使JCL Log 对象对于扩展了Spring Framework的类可见。对于用户来说,所有版本的Spring都使用相同的日志库很重要:迁移很简单,因为即使扩展了Spring的应用程序,但仍然保留了向后兼容性。我们这样做的方式是使Spring中的一个模块显式地依赖于commons-logging(遵循JCL规范的实现),然后在编译时使所有其他模块依赖于它。例如,如果您使用Maven,并且想知道在哪里可以获取对commons-logging的依赖,那么它来自Spring,特别是来自名为spring-core的中央模块。

commons-logging 的好处在于,您不需要任何其他操作来使您的应用程序正常工作。它具有运行时发现算法,可以在类路径中查找其他日志框架,并使用它认为合适的日志框架(或者您可以告诉它需要哪一个)。如果没有其他可用的,您可以从JDK(简称java.util.logging或JUL)获得好用的日志框架。您应该发现,在大多数情况下,您的Spring应用程序可以快乐地把日志输出到控制台,这很重要。

不使用Commons Logging

不幸的是,commons-logging中的运行时发现算法虽然对最终用户很方便,但是也有很多问题。如果我们可以把时空倒回,现在开始使用Spring去开始一个新的项目,我们会使用不同的日志依赖关系。第一选择可能是Simple Logging Facade for Java(SLF4J),它也被许多其他使用Spring的应用工具所使用。

基本上有两种方法来关闭commons-logging:

排除spring-core模块的依赖关系(因为它是明确依赖于commons-logging的唯一模块)
依赖于一个特殊的commons-logging依赖关系,用空的jar代替库(更多的细节可以在SLF4J FAQ中找到)

要排除commons-logging,请将以下内容添加到dependencyManagement部分:<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.M5</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
 
现在这个应用程序可能已经被破坏了,因为在类路径中没有实现JCL API,所以要修复它,必须提供一个新的JCL API。在下一节中,我们向您展示如何使用SLF4J提供JCL的替代实现。

使用SLF4J

SLF4J是一个更清洁的依赖关系,在运行时比commons-logging更有效率,因为它使用编译时绑定,而不是其集成的其他日志框架的运行时发现。这也意味着你必须更加明确地说明你在运行时想要发生什么,并声明它或相应地进行配置。 SLF4J提供对许多常见的日志框架的绑定,因此通常可以选择一个已经使用的日志框架,并绑定到该框架进行配置和管理。

SLF4J提供了绑定到许多常见的日志框架的方法,包括JCL,它也是可以反转的:是其他日志框架和自身(Spring)之间的桥梁。所以要使用SLF4J与Spring,您需要使用SLF4J-JCL bridge替换commons-logging依赖关系。一旦完成,那么在Spring中日志调用将被转换为对SLF4J API的日志调用,因此如果应用程序中的其他库使用该API,那么您有一个统一的地方来配置和管理日志记录。

常见的选择可能是将Spring链接到SLF4J,然后提供从SLF4J到Log4j的显式绑定。您需要提供多个依赖关系(并排除现有的commons-logging):the bridge,Log4j的SLF4J实现和Log4j实现本身。在Maven你会这样做:<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.M5</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.22</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
</dependencies>
这可能看起来像很多依赖只是为了获得一些日志。是的,但它是可选的,它应该比具有类加载器问题的commons-logging更好,特别是如果你在一个要求严格的容器,如OSGi平台。而且还有一个性能优势,因为绑定在编译时不是运行时。

使用较少步骤并生成较少依赖关系的SLF4J用户更为常见的选择是直接绑定到Logback。这将删除额外的绑定步骤,因为Logback直接实现SLF4J,因此您只需要依赖于两个库(jcl-over-slf4j和logback)而不是四个。如果这样做,您可能还需要从其他外部依赖关系(不是Spring)中排除slf4j-api依赖关系,因为您只需要在类路径中使用该API的一个版本。

使用的Log4j

  [[Note]]
Log4j的1.x版本已经寿终正寝,以下的内容特指Log4j 2

许多人使用Log4j 作为配置和管理日志的日志框架。它是高效和成熟的,当我们构建和测试Spring,实际上它是在运行时使用的。 Spring还提供了一些用于配置和初始化Log4j的实用功能,因此它在某些模块中对Log4j具有可选的编译时依赖性。

要使用JCL和Log4j,所有你需要做的就是把Log4j加到类路径,并为其提供一个配置文件(log4j2.xml,log4j2.properties或其他 支持的配置格式)。对于Maven用户,所需的最少依赖关系是:<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.7</version>
</dependency>
</dependencies>
 
如果你也想使用SLF4J,还需要以下依赖关系:<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version>
</dependency>
</dependencies>
 
下面是一个例子log4j2.xml用来把日志输出到控制台:<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="org.springframework.beans.factory" level="DEBUG"/>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
运行时容器和原生JCL

许多人在那些本身提供JCL实现的容器中运行他们的Spring应用程序。IBM WebSphere应用服务器(WAS)为例。这往往会导致问题,遗憾的是没有一劳永逸的解决方案; 在大多数情况下,简单地从您的应用程序排除commons-logging是不够的。

要清楚这一点:报告的问题通常不是JCL本身,甚至commons-logging:而是将commons-logging绑定到另一个框架(通常是Log4j)。这可能会失败,因为commons-logging更改了在一些容器中发现的旧版本(1.0)和大多数人现在使用的版本(1.1)之间执行运行时发现的方式。 Spring不使用JCL API的任何不寻常的部分,所以没有什么破坏,但是一旦Spring或您的应用程序尝试输出日志,您可以发现与Log4j的绑定不起作用

在这种情况下,使用WAS最简单的方法是反转类加载器层次结构(IBM将其称为”parent last”),以便应用程序控制JCL依赖关系,而不是容器。该选项并不总是开放的,但是在公共领域还有许多其他建议可供选择,您的里程(集成程度)可能因容器的确切版本和功能集而异。
 
 本文作者: 胡永
原文链接: http://ifeve.com/overview-gett ... ring/
版权归作者所有,转载请注明出处 查看全部
1.Spring入门指南

本参考指南提供了有关Spring Framework的详细信息。它全面的介绍了Spring的所有的功能,以及Spring涉及的基础概念(如“依赖注入” “Dependency Injection”)。

如果你是刚开始使用Spring,你可能需要首先创建一个Spring Boot应用程序来开始Spring框架之旅。Spring Boot提供了一个快速(和自治的)的方式来创建一个基于Spring的生产环境。它是基于Spring框架,支持约定优于配置,并且被设计成尽可能快地让你启动和运行程序。

您可以使用start.spring.io生成一个基本项目或按照新手入门指南里的任意一个指南构建项目,例如构建一个RESTful Web服务入门指南 。为了更容易帮助你理解,这些指南都是面向任务的,其中大部分都是基于Spring Boot的。 他们还涵盖了很多Spring原型工程,在您需要解决特定问题时可以考虑使用他们。

2.介绍Spring框架

Spring 框架是一个Java平台,它为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此您可以专注于应用程序的开发。

Spring可以让您从“plain old Java objects”(POJO)中构建应用程序和通过非侵入性的POJO实现企业应用服务。此功能适用于Java SE的编程模型,全部的或部分的适应Java EE模型。

这些例子告诉你,作为一个应用程序开发人员,如何从Spring平台中受益:

写一个Java方法执行数据库事务,而无需处理具体事务的APIs。
写一个本地Java方法去远程调用,而不必处理远程调用的APIs。
写一个本地Java方法实现管理操作,而不必处理JMX APIs。
写一个本地Java方法实现消息处理,而不必处理JMS APIs。

2.1依赖注入和控制反转

Java应用程序-这是一个宽松的术语,它包括的范围从受限的嵌入式应用程序到n层的服务器端企业应用程序-通常组成程序的对象互相协作而构成正确的应用程序。因此,在一个应用程序中的对象彼此具有依赖关系(dependencies)。

虽然Java平台提供了丰富的应用程序开发功能,但它缺乏将基本的模块组织成一个整体的方法,而将该任务留给了架构师和开发人员。虽然你可以使用如工厂,抽象工厂,Builder,装饰器和Service Locator等 设计模式来构建各种类和对象实例,使他们组合成应用程序,但这些模式无非只是:最佳实践赋予的一个名字,以及这是什么样的模式,应用于哪里,它能解决的问题等等。 模式是您必须在应用程序中自己实现的形式化的最佳实践。

Spring框架控制反转(IOC)组件通过提供一系列的标准化的方法把完全不同的组件组合成一个能够使用的应用程序来解决这个问题。Spring框架把形式化的设计模式编写为优秀的对象,你可以容易的集成到自己的应用程序中。许多组织和机构使用Spring框架,以这种方式(使用Spring的模式对象)来设计健壮的,可维护的应用程序。

背景

“ 现在的问题是,什么方面的控制被(他们)反转了? ”马丁·福勒2004年在他的网站提出了这个有关控制反转(IOC)的问题 ,福勒建议重命名,使之能够自我描述,并提出了依赖注入( Dependency Injection)。

2.2模块

Spring框架的功能被有组织的分散到约20个模块中。这些模块分布在核心容器,数据访问/集成,Web,AOP(面向切面​​的编程),植入(Instrumentation),消息传输和测试,如下面的图所示。

图2.1 Spring框架概述

s1.png




以下部分列出了每个可用模块,以及它们的工件名称和它们支持的主要功能。工件的名字对应的是工件标识符,使用在依赖管理工具中。

2.2.1核心容器

核心容器由以下模块组成,spring-core, spring-beans,spring-context,spring-context-support,和spring-expression (Spring表达式语言)。

spring-core和spring-beans模块提供了框架的基础功能,包括IOC和依赖注入功能。 BeanFactory是一个成熟的工厂模式的实现。你不再需要编程去实现单例模式,允许你把依赖关系的配置和描述从程序逻辑中解耦。

上下文(spring-context)模块建立在由Core和Beans模块提供的坚实的基础上:它提供一个框架式的对象访问方式,类似于一个JNDI注册表。上下文模块从Beans模块继承其功能,并添加支持国际化(使用,例如,资源集合),事件传播,资源负载,并且透明创建上下文,例如,Servlet容器。Context模块还支持Java EE的功能,如EJB,JMX和基本的远程处理。ApplicationContext接口是Context模块的焦点。 spring-context-support支持整合普通第三方库到Spring应用程序上下文,特别是用于高速缓存(ehcache,JCache)和调度(CommonJ,Quartz)的支持。

spring-expression模块提供了强大的表达式语言去支持查询和操作运行时对象图。这是对JSP 2.1规范中规定的统一表达式语言(unified EL)的扩展。该语言支持设置和获取属性值,属性分配,方法调用,访问数组,集合和索引器的内容,逻辑和算术运算,变量命名以及从Spring的IoC容器中以名称检索对象。 它还支持列表投影和选择以及常见的列表聚合。

2.2.2 AOP和Instrumentation

spring-aop模块提供了一个符合AOP联盟(要求)的面向方面的编程实现,例如,允许您定义方法拦截器和切入点(pointcuts),以便干净地解耦应该被分离的功能实现。 使用源级元数据(source-level metadata)功能,您还可以以类似于.NET属性的方式将行为信息合并到代码中。

单独的spring-aspects模块,提供了与AspectJ的集成。

spring-instrument模块提供了类植入(instrumentation)支持和类加载器的实现,可以应用在特定的应用服务器中。该spring-instrument-tomcat 模块包含了支持Tomcat的植入代理。

2.2.3消息

Spring框架4包括spring-messaging(消息传递模块),其中包含来自Spring Integration的项目,例如,Message,MessageChannel,MessageHandler,和其他用来传输消息的基础应用。该模块还包括一组用于将消息映射到方法的注释(annotations),类似于基于Spring MVC注释的编程模型。

2.2.4数据访问/集成

数据访问/集成层由JDBC,ORM,OXM,JMS和事务模块组成。

spring-jdbc模块提供了一个JDBC –抽象层,消除了需要的繁琐的JDBC编码和数据库厂商特有的错误代码解析。

spring-tx模块支持用于实现特殊接口和所有POJO(普通Java对象)的类的编程和声明式事务 管理。

spring-orm模块为流行的对象关系映射(object-relational mapping )API提供集成层,包括JPA和Hibernate。使用spring-orm模块,您可以将这些O / R映射框架与Spring提供的所有其他功能结合使用,例如前面提到的简单声明性事务管理功能。

spring-oxm模块提供了一个支持对象/ XML映射实现的抽象层,如JAXB,Castor,JiBX和XStream。

spring-jms模块(Java Messaging Service) 包含用于生产和消费消息的功能。自Spring Framework 4.1以来,它提供了与 spring-messaging模块的集成。

2.2.5 Web

Web层由spring-web,spring-webmvc和spring-websocket 模块组成。

spring-web模块提供基本的面向Web的集成功能,例如多部分文件上传功能,以及初始化一个使用了Servlet侦听器和面向Web的应用程序上下文的IoC容器。它还包含一个HTTP客户端和Spring的远程支持的Web相关部分。

spring-webmvc模块(也称为Web-Servlet模块)包含用于Web应用程序的Spring的模型-视图-控制器(MVC)和REST Web Services实现。 Spring的MVC框架提供了领域模型代码和Web表单之间的清晰分离,并与Spring Framework的所有其他功能集成。

2.2.6测试

spring-test模块支持使用JUnit或TestNG对Spring组件进行单元测试和 集成测试。它提供了Spring ApplicationContexts的一致加载和这些上下文的缓存。它还提供可用于独立测试代码的模仿(mock)对象。

2.3使用场景

之前描述的构建模块使Spring成为许多应用场景的理性选择,从在资源受限设备上运行的嵌入式应用程序到使用Spring的事务管理功能和Web框架集成的全面的企业应用程序。

图2.2 典型的成熟完整的Spring Web应用程序

s3.png



Spring的声明式事务管理功能使Web应用程序完全事务性,就像使用EJB容器管理的事务一样。所有您的定制业务逻辑都可以使用简单的POJO实现,并由Spring的IoC容器进行管理。附加服务包括支持发送电子邮件和独立于Web层的验证,可让您选择执行验证规则的位置。 Spring的ORM支持与JPA和Hibernate集成;例如,当使用Hibernate时,可以继续使用现有的映射文件和标准的Hibernate SessionFactory配置。表单控制器将Web层与域模型无缝集成,从而无需ActionForms或将HTTP参数转换为域模型的值的其他类。

图2.3使用第三方web框架的Spring中间层

2.3_.png




有时情况不允许你完全切换到不同的框架。 Spring框架并不强制您使用其中的一切;这不是一个全有或全无的解决方案。使用Struts,Tapestry,JSF或其他UI框架构建的现有前端可以与基于Spring的中间层集成,从而允许您使用Spring事务功能。您只需要使用ApplicationContext连接您的业务逻辑,并使用WebApplicationContext来集成您的Web层。

图2.4 远程使用场景


2.4_.png



当您需要通过Web服务访问现有代码时,你可以使用Spring的 Hessian-,Rmi-或HttpInvokerProxyFactoryBean类。启用对现有应用程序的远程访问并不困难。

图2.5  EJBs – 包装现有的POJOs

2.5_.png




Spring Framework还为Enterprise JavaBeans提供了一个访问和抽象层,使您能够重用现有的POJO,并将其包装在无状态会话bean中,以便在可能需要声明式安全性的, 可扩展的,故障安全的Web应用程序中使用。

2.3.1依赖管理和命名约定

依赖关系管理和依赖注入是不同的。为了将Spring的这些不错的功能(如依赖注入)集成到应用程序中,您需要组装所有需要的库(jar文件),并在运行时导入到类路径(classpath)中,也有可能在编译时就需要加入类路径。这些依赖关系不是注入的虚拟组件,而是文件系统中的物理资源(通常是这样)。依赖关系管理的过程包括定位这些资源,存储它们并将其添加到类路径中。依赖关系可以是直接的(例如,我的应用程序在运行时依赖于Spring)或间接的(例如我的应用程序依赖于commons-dbcp ,而commons-dbcp 又依赖于commons-pool)。间接依赖关系具有“传递性”,它们是最难识别和管理的依赖关系。

如果你要使用Spring,你需要获得一个包含你所需要的Spring模块的jar库的副本。为了使这更容易,Spring被打包为一组尽可能分离依赖关系的模块,例如,如果您不想编写Web应用程序,则不需要spring-web模块。要引用本指南中的Spring库模块,我们使用一个简写命名约定spring- *或spring – *.jar,其中*表示模块的简称(例如spring-core,spring-webmvc,spring-jms等) )。您实际使用的jar文件名通常是与版本号连接的模块名称(例如spring-core-5.0.0.M5.jar)。

Spring框架的每个版本都会发布到以下几个地方:

Maven Central,它是Maven查询的默认存储库,不需要任何特殊配置。 Spring的许多常见的库也可以从Maven Central获得,Spring社区的大部分使用Maven进行依赖关系管理,所以这对他们来说很方便。这里的jar的名字是spring – * – <version> .jar,Maven groupId是org.springframework。
在专门用于Spring的公共Maven存储库中。除了最终的GA版本,该存储库还承载开发快照和里程碑版本。 jar文件名与Maven Central格式相同,因此这是一个有用的地方,可以将开发中的版本的Spring与在Maven Central中部署的其他库配合使用。该存储库还包含集中分发的zip文件,其中包含所有Spring jar,捆绑在一起以便于下载。

所以你需要决定的第一件事是如何管理你的依赖关系:我们通常建议使用像Maven,Gradle或Ivy这样的自动化系统,但你也可以通过自己下载所有的jar来手动执行。

您将在下面找到Spring artifacts列表。有关每个模块的更完整的描述,第2.2节“模块”.

表2.1  Spring框架的Artifacts

GroupId                   ArtifactId                                Description(描述)
org.springframework spring-aop                             Proxy-based AOP support
org.springframework spring-aspects                       AspectJ based aspects
org.springframework spring-beans                          Beans support, including Groovy
org.springframework spring-context                        Application context runtime, including scheduling and remoting abstractions
org.springframework spring-context-support           Support classes for integrating common third-party libraries into a Spring application context
org.springframework spring-core                            Core utilities, used by many other Spring modules
org.springframework spring-expression                    Spring Expression Language (SpEL)
org.springframework spring-instrument                     Instrumentation agent for JVM bootstrapping
org.springframework spring-instrument-tomcat         Instrumentation agent for Tomcat
org.springframework spring-jdbc                             JDBC support package, including DataSource setup and JDBC access support
org.springframework spring-jms                              JMS support package, including helper classes to send and receive JMS messages
org.springframework spring-messaging                    Support for messaging architectures and protocols
org.springframework spring-orm                             Object/Relational Mapping, including JPA and Hibernate support
org.springframework spring-oxm                             Object/XML Mapping
org.springframework spring-test                             Support for unit testing and integration testing Spring components
org.springframework spring-tx                                Transaction infrastructure, including DAO support and JCA integration
org.springframework spring-web                             Web support packages, including client and web remoting
org.springframework spring-webmvc                       REST Web Services and model-view-controller implementation for web applications
org.springframework spring-websocket                   WebSocket and SockJS implementations, including STOMP support

Spring的依赖和依赖于Spring

虽然Spring为大量企业和其他外部工具提供集成和支持,但它有意将其强制性的依赖保持在最低限度:在使用Spring用于简单的用例时,您不必定位和下载(甚至自动的去做)大量的jar库。对于基本依赖注入功能,只有一个强制性的外部依赖关系,也就是用于日志记录的依赖(有关日志记录选项的更详细描述,请参阅下文)。

接下来,我们概述了配置依赖于Spring的应用程序所需的基本步骤,首先是使用Maven,然后使用Gradle,最后使用Ivy。在任何情况下,如果不清楚,请参阅依赖关系管理系统的文档,或查看一些示例代码 – Spring本身在构建时使用Gradle来管理依赖关系,我们的示例主要使用Gradle或Maven。

Maven的依赖管理

如果您使用Maven进行依赖关系管理,则甚至不需要显式提供依赖关系。 例如,要创建应用程序上下文并使用依赖注入来配置应用程序,您的Maven依赖配置如下所示:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.0.M5</version>
<scope>runtime</scope>
</dependency>
</dependencies>

(依赖配置)就是这样。注意,如果您不需要针对Spring API进行编译,那么范围(scope)可以被声明为运行时,通常情况下这是基本的依赖注入用例。

以上示例适用于Maven Central存储库。要使用Spring Maven仓库(例如,使用里程碑或开发中的快照版本),您需要在Maven配置中指定仓库位置。完整版本:
<repositories>
<repository>
<id>io.spring.repo.maven.release</id>
<url>http://repo.spring.io/release/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>

对于里程碑(milestones):
<repositories>
<repository>
<id>io.spring.repo.maven.milestone</id>
<url>http://repo.spring.io/milestone/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>

而对于快照(snapshots):
<repositories>
<repository>
<id>io.spring.repo.maven.snapshot</id>
<url>http://repo.spring.io/snapshot/</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>

Maven的“材料清单”依赖

使用Maven时,可能会意外混合不同版本的Spring JAR。例如,您可能会发现第三方库或另一个Spring项目会传递依赖于旧版本的Spring JARs。如果您忘记自己明确声明直接依赖,可能会出现各种意外问题。

为了克服这些问题,Maven支持“材料清单(bill of materials)”(BOM)依赖的概念。您可以在dependencyManagement部分中导入spring-framework-bom,以确保所有Spring依赖(直接和传递)都是相同的版本
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.0.0.M5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

使用BOM的另外一个好处是,您不再需要在依赖于Spring Framework artifacts时指定<version>属性:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependencies>

 
Gradle 依赖管理

要使用Gradle 构建系统的Spring仓库,请在仓库部分中包含适当的URL:
repositories {
mavenCentral()
// and optionally...
maven { url "http://repo.spring.io/release" }
}

 
您可以根据需要将repositoriesURL从/release更改为/milestone或/snapshot。一旦repositories被配置,你可以通常使用Gradle方式来声明依赖:
dependencies {
compile("org.springframework:spring-context:5.0.0.M5")
testCompile("org.springframework:spring-test:5.0.0.M5")
}

Ivy依赖管理

如果您喜欢使用 Ivy 来管理依赖关系,那么还有类似的配置选项。 要配置Ivy指向Spring仓库,请将以下解析器添加到您的ivysettings.xml中:
<resolvers>
<ibiblio name="io.spring.repo.maven.release"
m2compatible="true"
root="http://repo.spring.io/release/"/>
</resolvers>

您可以更改root从URL /release/到/milestone/或/snapshot/适当。

配置完成后,您可以在通常的方式添加依赖。例如(在ivy.xml):

您可以根据需要将rootURL从 /release/更改为/milestone/或/snapshot/。 配置完成后,您可以按通常的方式添加依赖项。例如(在ivy.xml中):
<dependency org="org.springframework"
name="spring-core" rev="5.0.0.M5" conf="compile->runtime"/>

 
Zip文件发行

虽然使用依赖关系管理的构建系统是推荐的获取Spring框架的方法,但仍然可以下载发布的zip文件。

Zip文件发布到Spring Maven存储库(这仅仅是为了我们的方便,您不需要使用Maven或任何其他构建系统才能下载它们)。

要下载发布的zip文件,打开Web浏览器到http://repo.spring.io/release/ ... pring,并为所需的版本选择相应的子文件夹。zip文件以-dist.zip结尾,例如spring-framework- {spring-version} -RELEASE-dist.zip。里程碑和 快照也会发布在这里。

2.3.2 日志

日志是Spring非常重要的依赖,因为a)它是唯一的强制性外部依赖关系,b)每个人都喜欢看到他们使用的工具的一些输出,以及c)Spring集成了许多其他工具,都会具有日志依赖关系。应用程序开发人员的目标之一通常是将统一的日志配置放在整个应用程序的中央位置,包括所有外部组件。这比以前有更多的困难,因为有这么多的日志框架可以选择。

Spring中的强制性日志依赖关系是Jakarta Commons Logging API(JCL)。我们针对JCL进行编译,我们还使JCL Log 对象对于扩展了Spring Framework的类可见。对于用户来说,所有版本的Spring都使用相同的日志库很重要:迁移很简单,因为即使扩展了Spring的应用程序,但仍然保留了向后兼容性。我们这样做的方式是使Spring中的一个模块显式地依赖于commons-logging(遵循JCL规范的实现),然后在编译时使所有其他模块依赖于它。例如,如果您使用Maven,并且想知道在哪里可以获取对commons-logging的依赖,那么它来自Spring,特别是来自名为spring-core的中央模块。

commons-logging 的好处在于,您不需要任何其他操作来使您的应用程序正常工作。它具有运行时发现算法,可以在类路径中查找其他日志框架,并使用它认为合适的日志框架(或者您可以告诉它需要哪一个)。如果没有其他可用的,您可以从JDK(简称java.util.logging或JUL)获得好用的日志框架。您应该发现,在大多数情况下,您的Spring应用程序可以快乐地把日志输出到控制台,这很重要。

不使用Commons Logging

不幸的是,commons-logging中的运行时发现算法虽然对最终用户很方便,但是也有很多问题。如果我们可以把时空倒回,现在开始使用Spring去开始一个新的项目,我们会使用不同的日志依赖关系。第一选择可能是Simple Logging Facade for Java(SLF4J),它也被许多其他使用Spring的应用工具所使用。

基本上有两种方法来关闭commons-logging:

排除spring-core模块的依赖关系(因为它是明确依赖于commons-logging的唯一模块)
依赖于一个特殊的commons-logging依赖关系,用空的jar代替库(更多的细节可以在SLF4J FAQ中找到)

要排除commons-logging,请将以下内容添加到dependencyManagement部分:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.M5</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

 
现在这个应用程序可能已经被破坏了,因为在类路径中没有实现JCL API,所以要修复它,必须提供一个新的JCL API。在下一节中,我们向您展示如何使用SLF4J提供JCL的替代实现。

使用SLF4J

SLF4J是一个更清洁的依赖关系,在运行时比commons-logging更有效率,因为它使用编译时绑定,而不是其集成的其他日志框架的运行时发现。这也意味着你必须更加明确地说明你在运行时想要发生什么,并声明它或相应地进行配置。 SLF4J提供对许多常见的日志框架的绑定,因此通常可以选择一个已经使用的日志框架,并绑定到该框架进行配置和管理。

SLF4J提供了绑定到许多常见的日志框架的方法,包括JCL,它也是可以反转的:是其他日志框架和自身(Spring)之间的桥梁。所以要使用SLF4J与Spring,您需要使用SLF4J-JCL bridge替换commons-logging依赖关系。一旦完成,那么在Spring中日志调用将被转换为对SLF4J API的日志调用,因此如果应用程序中的其他库使用该API,那么您有一个统一的地方来配置和管理日志记录。

常见的选择可能是将Spring链接到SLF4J,然后提供从SLF4J到Log4j的显式绑定。您需要提供多个依赖关系(并排除现有的commons-logging):the bridge,Log4j的SLF4J实现和Log4j实现本身。在Maven你会这样做:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.M5</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.22</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
</dependencies>

这可能看起来像很多依赖只是为了获得一些日志。是的,但它是可选的,它应该比具有类加载器问题的commons-logging更好,特别是如果你在一个要求严格的容器,如OSGi平台。而且还有一个性能优势,因为绑定在编译时不是运行时。

使用较少步骤并生成较少依赖关系的SLF4J用户更为常见的选择是直接绑定到Logback。这将删除额外的绑定步骤,因为Logback直接实现SLF4J,因此您只需要依赖于两个库(jcl-over-slf4j和logback)而不是四个。如果这样做,您可能还需要从其他外部依赖关系(不是Spring)中排除slf4j-api依赖关系,因为您只需要在类路径中使用该API的一个版本。

使用的Log4j

  [[Note]]
Log4j的1.x版本已经寿终正寝,以下的内容特指Log4j 2

许多人使用Log4j 作为配置和管理日志的日志框架。它是高效和成熟的,当我们构建和测试Spring,实际上它是在运行时使用的。 Spring还提供了一些用于配置和初始化Log4j的实用功能,因此它在某些模块中对Log4j具有可选的编译时依赖性。

要使用JCL和Log4j,所有你需要做的就是把Log4j加到类路径,并为其提供一个配置文件(log4j2.xml,log4j2.properties或其他 支持的配置格式)。对于Maven用户,所需的最少依赖关系是:
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.7</version>
</dependency>
</dependencies>

 
如果你也想使用SLF4J,还需要以下依赖关系:
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version>
</dependency>
</dependencies>

 
下面是一个例子log4j2.xml用来把日志输出到控制台:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="org.springframework.beans.factory" level="DEBUG"/>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

运行时容器和原生JCL

许多人在那些本身提供JCL实现的容器中运行他们的Spring应用程序。IBM WebSphere应用服务器(WAS)为例。这往往会导致问题,遗憾的是没有一劳永逸的解决方案; 在大多数情况下,简单地从您的应用程序排除commons-logging是不够的。

要清楚这一点:报告的问题通常不是JCL本身,甚至commons-logging:而是将commons-logging绑定到另一个框架(通常是Log4j)。这可能会失败,因为commons-logging更改了在一些容器中发现的旧版本(1.0)和大多数人现在使用的版本(1.1)之间执行运行时发现的方式。 Spring不使用JCL API的任何不寻常的部分,所以没有什么破坏,但是一旦Spring或您的应用程序尝试输出日志,您可以发现与Log4j的绑定不起作用

在这种情况下,使用WAS最简单的方法是反转类加载器层次结构(IBM将其称为”parent last”),以便应用程序控制JCL依赖关系,而不是容器。该选项并不总是开放的,但是在公共领域还有许多其他建议可供选择,您的里程(集成程度)可能因容器的确切版本和功能集而异。
 
 本文作者: 胡永
原文链接: http://ifeve.com/overview-gett ... ring/
版权归作者所有,转载请注明出处

Spring Cloud实战课程-从0到1实现AG-Admin

回复

Spring Cloudace4spring 发起了问题 • 1 人关注 • 0 个回复 • 122 次浏览 • 2017-09-01 07:26 • 来自相关话题

Spring Cloud实战课程-从0到1实现AG-Admin

回复

Spring Cloudace4spring 发起了问题 • 1 人关注 • 0 个回复 • 147 次浏览 • 2017-09-01 07:26 • 来自相关话题

SpringBoot配置应用自定义启动端口出现405问题

回复

Spring Bootitmuch.com 回复了问题 • 4 人关注 • 2 个回复 • 543 次浏览 • 2017-07-19 18:32 • 来自相关话题

springboot如何配置多种缓存cache

回复

Spring Boot泥瓦匠BYSocket 回复了问题 • 2 人关注 • 1 个回复 • 405 次浏览 • 2017-07-14 11:28 • 来自相关话题

Failed to invoke @ExceptionHandler method

回复

Spring Frameworkzhongxunking 回复了问题 • 4 人关注 • 6 个回复 • 352 次浏览 • 2017-07-12 17:27 • 来自相关话题

跨网关调用有什么好的解决方案?

回复

Spring Cloudspringcp 回复了问题 • 3 人关注 • 2 个回复 • 333 次浏览 • 2017-07-08 10:57 • 来自相关话题

Spring-Cloud与Dubbo,Motan等对比,帮忙看下表格有没有参考性

回复

Spring Cloudxiaobaxi 回复了问题 • 2 人关注 • 1 个回复 • 378 次浏览 • 2017-06-24 22:21 • 来自相关话题

如何获取 spring bean 中的注解

回复

Spring IO Platformxiaobaxi 回复了问题 • 2 人关注 • 1 个回复 • 300 次浏览 • 2017-06-23 13:42 • 来自相关话题

AOP 如何动态切换数据源

回复

Spring Bootxiaobaxi 回复了问题 • 2 人关注 • 2 个回复 • 404 次浏览 • 2017-06-08 22:32 • 来自相关话题

「转」Spring Boot 2.0 - WebFlux framework

Spring Framework泥瓦匠BYSocket 发表了文章 • 0 个评论 • 238 次浏览 • 4 天前 • 来自相关话题

1、介绍
1.1 什么是响应式编程(Reactive Programming)?

简单来说,响应式编程是针对异步和事件驱动的非阻塞应用程序,并且需要少量线程来垂直缩放(即在 JVM 内)而不是水平(即通过集群)。

响应式应用的一个关键方面是“背压(backpressure)”的概念,这是确保生产者不会压倒消费者的机制。例如,当HTTP连接太慢时,从数据库延伸到HTTP响应的反应组件的流水线、数据存储库也可以减慢或停止,直到网络容量释放。

响应式编程也导致从命令式到声明异步组合逻辑的重大转变。与使用Java 8的 CompletableFuture 编写封锁代码相比,可以通过 lambda
表达式编写后续操作。

1.2 响应式 API(Reactive API)和 构建块(Building Blocks)

Spring Framework 5 将 Reactive Streams 作为通过异步组件和库进行背压通信的合同。Reactive Streams 是通过行业协作创建的规范,也已在Java 9中被采用为 java.util.concurrent.Flow。

Spring Framework 在内部使用 Reactor 自己的响应支持。Reactor 是一个 Reactive Streams 实现,进一步扩展基本的 Reactive Streams Publisher 、Flux 和 Mono 可组合的API类型,以提供对 0..N 和 0..1 的数据序列的声明性操作。

Spring Framework 在许多自己的 Reactive API 中暴露了 Flux 和
Mono。然而,在应用级别,一如既往,Spring 提供了选择,并完全支持使用RxJava。有关的更多信息,请查看 Sebastien Deleuze 发表的 "Understanding Reactive Types" 。

2、Spring WebFlux 模块

Spring Framework 5 包括一个新的 spring-webflux 模块。该模块包含对响应式 HTTP 和 WebSocket 客户端的支持,以及对REST,HTML浏览器和 WebSocket风格交互的响应式服务器Web应用程序的支持。

2.1、服务器端

在服务器端 WebFlux 支持2种不同的编程模型:

基于注解的 @Controller 和其他注解也支持 Spring MVC
Functional 、Java 8 lambda 风格的路由和处理





 

WebFlux 可以在支持 Servlet 3.1 非阻塞 IO API 以及其他异步运行时(如 Netty 和 Undertow )的 Servlet 容器上运行。每个运行时都适用于响应型 ServerHttpRequest 和 ServerHttpResponse,将请求和响应的正文暴露为 Flux<DataBuffer>,而不是具有响应背压的InputStream 和 OutputStream 。顶部作为 Flux<Object> 支持REST风格的 JSON 和 XML 序列化和反序列化,HTML视图呈现和服务器发送事件也是如此。

基于注解的编程模式

WebFlux中也支持相同的 @Controller 编程模型和 Spring MVC 中使用的相同注解。主要区别在于底层核心框架契约(即 HandlerMapping HandlerAdapter )是非阻塞的,并且在响应型 ServerHttpRequest
和 ServerHttpResponse 上运行,而不是在 HttpServletRequest 和HttpServletResponse 上运行。以下是一个响应式 Controller 的例子:
@RestController
public class PersonController {

private final PersonRepository repository;

public PersonController(PersonRepository repository) {
this.repository = repository;
}

@PostMapping("/person")
Mono<Void> create(@RequestBody Publisher<Person> personStream) {
return this.repository.save(personStream).then();
}

@GetMapping("/person")
Flux<Person> list() {
return this.repository.findAll();
}

@GetMapping("/person/{id}")
Mono<Person> findById(@PathVariable String id) {
return this.repository.findOne(id);
}
}


 
函数式编程模式

HandlerFunctions

传入的HTTP请求由 HandlerFunction 处理,HandlerFunction 本质上是一个接收 ServerRequest 并返回 Mono<ServerResponse> 的函数。处理函数的注解对应方法将是一个 @RequestMapping 的方法。

ServerRequest 和 ServerResponse 是提供JDK-8友好访问底层HTTP消息的不可变接口。两者都通过在反应堆顶部建立完全反应:请求将身体暴露为 Flux 或 Mono; 响应接受任何 Reactive Streams Publisher 作为主体。

ServerRequest 可以访问各种HTTP请求元素:方法,URI,查询参数,以及通过单独的ServerRequest.Headers 接口 - 头。通过 body方法 提供对 body 的访问。例如,这是如何将请求体提取为 Mono <String>:
Mono<String> string = request.bodyToMono(String.class);
 
这里是如何将身体提取为 Flux,其中 Person 是可以从body内容反序列化的类(即如果body包含JSON,则由Jackson支持,或者如果是XML,则为JAXB)。
Flux<Person> people = request.bodyToFlux(Person.class);
 
上面的两个方法(bodyToMono 和 bodyToFlux)实际上是使用通用ServerRequest.body(BodyExtractor)函数的便利方法。
BodyExtractor 是一个功能策略界面,允许您编写自己的提取逻辑,但在 BodyExtractors 实用程序类中可以找到常见的 BodyExtractor 实例。所以,上面的例子可以替换为:
Mono<String> string = request.body(BodyExtractors.toMono(String.class);
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class);
 
类似地,ServerResponse 提供对HTTP响应的访问。由于它是不可变的,您可以使用构建器创建一个 ServerResponse 。构建器允许您设置响应状态,添加响应标题并提供正文。例如,这是如何使用200 OK状态创建响应,JSON内容类型和正文:Mono<Person> person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);这里是如何使用201创建的状态,位置标题和空白体来构建响应:
URI location = ...
ServerResponse.created(location).build();
将这些组合在一起可以创建一个 HandlerFunction。例如,这里是一个简单的“Hello World”处理程序 lambda 的示例,它返回一个200状态的响应和一个基于 String 的主体:
HandlerFunction<ServerResponse> helloWorld = request -> ServerResponse.ok().body(fromObject("Hello World"));
 
使用 lambda 写处理函数,就像我们上面所说的那样很方便,但是在处理多个函数时可能缺乏可读性,变得不那么容易维护。因此,建议将相关处理函数分组到一个处理程序或控制器类中。例如,这是一个暴露了一个响应式的 Person 存储库的类:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;

public class PersonHandler {

private final PersonRepository repository;

public PersonHandler(PersonRepository repository) {
this.repository = repository;
}

// 1
public Mono<ServerResponse> listPeople(ServerRequest request) {
Flux<Person> people = repository.allPeople();
return ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Person.class);
}

// 2
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ServerResponse.ok().build(repository.savePerson(person));
}

// 3
public Mono<ServerResponse> getPerson(ServerRequest request) {
int personId = Integer.valueOf(request.pathVariable("id"));
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
Mono<Person> personMono = this.repository.getPerson(personId);
return personMono
.flatMap(person -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person)))
.switchIfEmpty(notFound);
}
}
1/ listPeople 是一个处理函数,它将数据库中发现的所有 Person
对象返回为JSON。

2/ createPerson 是一个处理函数,用于存储请求正文中包含的新Person。请注意,PersonRepository.savePerson(Person) 返回Mono<Void>:发出完成信号的空 Mono,当人从请求中读取并存储时,发出完成信号。因此,当接收到完成信号时,即当 Person 已被保存时,我们使用 build(Publisher<Void>) 方法来发送响应。

3/ getPerson 是一个处理函数,它通过路径变量id来标识一个人。我们通过数据库检索该Person,并创建一个JSON响应(如果找到)。如果没有找到,我们使用 switchIfEmpty(Mono<T>) 来返回 404 Not Found 响应。

RouterFunctions

传入请求将路由到处理函数,并使用一个 RouterFunction,它是一个服务器 ServerRequest 的函数,并返回一个 Mono<HandlerFunction>。如果请求与特定路由匹配,则返回处理函数; 否则返回一个空的 Mono。RouterFunction 与 @Controller 类中的 @RequestMapping 注解类似。

通常,您不要自己编写路由器功能,而是使用
RouterFunctions.route(RequestPredicate, HandlerFunction), 使用请求谓词和处理函数创建一个。如果谓词适用,请求将路由到给定的处理函数; 否则不执行路由,导致 404 Not Found 响应。虽然您可以编写自己的 RequestPredicate ,但是您不需要:RequestPredicates 实用程序类提供常用的谓词,基于路径,HTTP方法,内容类型等进行匹配。使用路由,我们可以路由到我们的 “Hello World” 处理函数:
RouterFunction<ServerResponse> helloWorldRoute =
RouterFunctions.route(RequestPredicates.path("/hello-world"),
request -> Response.ok().body(fromObject("Hello World")));

 
两个路由功能可以组成一个新的路由功能,路由到任一处理函数:如果第一个路由的谓词不匹配,则第二个被评估。组合的路由器功能按顺序进行评估,因此在通用功能之前放置特定功能是有意义的。您可以通过调用 RouterFunction.and(RouterFunction) 或通过调用
RouterFunction.andRoute(RequestPredicate, HandlerFunction) 来组成两个路由功能,这是 RouterFunction.and() 与 RouterFunctions.route() 的一种方便组合。

给定我们上面显示的 PersonH​​andler,我们现在可以定义路由功能,路由到相应的处理函数。我们使用 方法引用(method-references) 来引用处理函数:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> personRoute =
route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson)
.andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
.andRoute(POST("/person").and(contentType(APPLICATION_JSON)), handler::createPerson);

 
除路由功能之外,您还可以通过调用
RequestPredicate.and(RequestPredicate) 或
RequestPredicate.or(RequestPredicate) 来构成请求谓词。这些工作正如预期的那样:如果给定的谓词匹配,则生成的谓词匹配; 或者如果任一谓词都匹配。RequestPredicates 中发现的大多数谓词是组合的。例如,RequestPredicates.GET(String) 是
RequestPredicates.method(HttpMethod) 和RequestPredicates.path(String) 的组合。

启动服务器

现在只有一个难题遗留:在HTTP服务器中运行路由功能。您可以使用
RouterFunction<ServerResponse> route = ...
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create(HOST, PORT);
server.newHandler(adapter).block();
 
对于 Tomcat ,它看起来像这样:
RouterFunction<ServerResponse> route = ...
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler);
Tomcat server = new Tomcat();
Context rootContext = server.addContext("", System.getProperty("java.io.tmpdir"));
Tomcat.addServlet(rootContext, "servlet", servlet);
rootContext.addServletMapping("/", "servlet");
tomcatServer.start();
 
待完成:DispatcherHandler
HandlerFilterFunction
路由功能映射的路由可以通过调用
RouterFunction.filter(HandlerFilterFunction) 进行过滤,其中
HandlerFilterFunction 本质上是一个接收 ServerRequest 和
HandlerFunction 的函数,并返回一个 ServerResponse 。处理函数参数表示链中的下一个元素:通常是路由到的 HandlerFunction ,但是如果应用了多个过滤器,也可以是另一个 FilterFunction 。使用注解,可以使用 @ControllerAdvice 和 / 或 ServletFilter 来实现类似的功能。让我们在我们的路由中添加一个简单的安全过滤器,假设我们有一个 SecurityManager 可以确定是否允许特定的路径:
import static org.springframework.http.HttpStatus.UNAUTHORIZED;

SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = ...

RouterFunction<ServerResponse> filteredRoute =
route.filter(request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
});
 
在这个例子中可以看到,调用 next.handle(ServerRequest) 是可选的:我们只允许在允许访问时执行处理函数。

2.2 客户端(Client Side)

WebFlux 包括一个 functional, reactive WebClient,它为 RestTemplate 提供了一种完全无阻塞和响应式的替代方案。
它将网络输入和输出公开为客户端 HttpRequest 和ClientHttpResponse ,其中请求和响应的主体是 Flux <DataBuffer>而不是 InputStream 和 OutputStream。此外,它还支持与服务器端相同的响应式 JSON,XML和SSE 序列化机制,因此您可以使用类型化对象。以下是使用需要 ClientHttpConnector 实现的 WebClient 插入特定HTTP客户端(如 Reactor Netty)的示例:
WebClient client = WebClient.create("http://example.com");

Mono<Account> account = client.get()
.url("/accounts/{id}", 1L)
.accept(APPLICATION_JSON)
.exchange(request)
.flatMap(response -> response.bodyToMono(Account.class));
 
AsyncRestTemplate 还支持非阻塞交互。主要区别在于它不支持非阻塞流,例如 Twitter one ,因为它基本上仍然依赖于 InputStream 和
OutputStream。

2.4 请求体和响应体的转换(Request and Response Body Conversion)

spring-core 模块提供了响应式 Encoder (编码器) 和 Decoder (解码器),使得能够串行化字符串与类型对象的转换。spring-web 模块添加了 JSON(Jackson)和 XML(JAXB)实现,用于Web应用程序以及其他用于SSE流和零拷贝文件传输。

支持以下 Reactive API:

Reactor 3.x 支持开箱即用
io.reactivex.rxjava2:rxjava 依赖项在类路径上时支持 RxJava 2.x
当 ·io.reactivex:rxjava和io.reactivex:rxjava-reactive-streams`(RxJava 和 Reactive Streams 之间的适配器)依赖关系在类路径上时,支持 RxJava 1.x

例如,请求体可以是以下方式之一,它将在注解和功能编程模型中自动解码:

Account account - 在调用控制器之前,account 将无阻塞地被反序列化。
Mono<Account> account - controller 可以使用 Mono 来声明在反序列化 account 后执行的逻辑。
Single<Account> account - 和 Mono 类似,但是用的是 RxJava
Flux<Account> accounts - 输入流场景
Observable<Account> accounts - RxJava 的 输入流场景

响应体(response body)可以是以下之一:

Mono<Account> - 当 Mono 完成时,序列化而不阻塞给定的Account。
Single<Account> - 与上类似,但是使用的 RxJava
Flux<Account> - 流式场景,可能是SSE,具体取决于所请求的内容类型。
Observable<Account> - 与上类似, 但是使用的 RxJava Observable 类型
Flowable<Account> - 与上类似, 但是使用的 RxJava 2 Flowable 类型。
Publisher<Account> 或 Flow.Publisher<Account> - 支持任何实现Reactive Streams Publisher 的类型。
Flux<ServerSentEvent> - SSE 流。
Mono<Void> - 当 Mono 完成时,请求处理完成。
Account - 序列化而不阻塞给定的Account; 意味着同步、非阻塞的
Controller 方法。
Void - 特定于基于注解的编程模型,方法返回时,请求处理完成;
意味着同步、非阻塞的 Controller 方法。

当使用像 Flux 或 Observable 这样的流类型时,请求/响应或映射/路由级别中指定的媒体类型用于确定数据应如何序列化和刷新。例如,返回 Flux<Account> 的REST端点将默认序列化如下:

application/json : Flux<Account> 作为异步集合处理,并在完成事件发布时将其序列化为具有显式刷新的JSON数组。
application/stream+json : 一个 Flux<Account> 将作为一系列的Account 元素处理,作为以新行分隔的单个JSON对象,并在每个元素之后显式刷新。WebClient 支持JSON流解码,因此这对于服务器到服务器的用例来说是一个很好的用例。
text/event-stream : 一个 Flux<Account> 或 Flux<ServerSentEvent<Account >> 将作为一个 Stream 或ServerSentEvent 元素的流处理,作为单独的 SSE 元素,使用默认的JSON进行数据编码和每个元素之间的显式刷新。这非常适合将流暴露给浏览器客户端。WebClient 也支持读取SSE流。

2.4 响应式 Websocket 支持

WebFlux 包括响应式 WebSocket 客户端和服务器支持。Java WebSocket API(JSR-356),Jetty,Undertow和Reactor Netty都支持客户端和服务器。

在服务器端,声明一个 WebSocketHandlerAdapter,然后简单地添加映射到基于 WebSocketHandler 的端点:
@Bean
public HandlerMapping webSocketMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/foo", new FooWebSocketHandler());
map.put("/bar", new BarWebSocketHandler());

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

@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
在客户端,为上面列出的支持的库之一创建一个 WebSocketClient:
WebSocketClient client = new ReactorNettyWebSocketClient();
client.execute("ws://localhost:8080/echo"), session -> {... }).blockMillis(5000);
 
2.5 测试

spring-test 模块包括一个 WebTestClient,可用于测试具有或不具有正在运行的服务器的 WebFlux 服务器端点。

没有运行服务器的测试与来自Spring MVC的 MockMvc 相当,其中使用模拟请求和响应,而不是使用套接字通过网络连接。然而,WebTestClient 也可以针对正在运行的服务器执行测试。

更多请查看 sample tests

3、开始入门
3.1 Spring Boot Starter

通过 http://start.spring.io 提供的 Spring Boot WebFlux 启动器是最快的入门方式。它做所有必要的,所以你开始像Spring MVC一样编写@Controller类。只需转到 http://start.spring.io ,选择版本
2.0.0.BUILD-SNAPSHOT,并在依赖关系框中键入 respond。
默认情况下,启动器使用 Reactor Netty 运行,但依赖关系可以像往常一样通过 Spring Boot 更改为不同的运行时。有关更多详细信息和说明,请参阅 Spring Boo t参考文档页面。

3.2 手动引导(Manual Bootstrapping)

对于依赖关系,从 spring-webflux 和 spring-context 开始。
然后添加jackson-databind 和 io.netty:netty-buffer(暂时见SPR-14528)以获得JSON支持。最后添加一个支持的运行时的依赖项:

Tomcat — org.apache.tomcat.embed:tomcat-embed-core
Jetty — org.eclipse.jetty:jetty-server 和 org.eclipse.jetty:jetty-servlet
Reactor Netty — io.projectreactor.ipc:reactor-netty
Undertow — io.undertow:undertow-core

基于注解编程模式的引导:
ApplicationContext context = new AnnotationConfigApplicationContext(DelegatingWebFluxConfiguration.class); // (1)
HttpHandler handler = DispatcherHandler.toHttpHandler(context); // (2)
 
以上加载默认的 Spring Web 框架配置(1),然后创建一个DispatcherHandler,主类驱动请求处理(2),并适应 HttpHandler - 响应式HTTP请求处理的最低级别的Spring抽象。

函数编程模式的引导:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // (1)
context.registerBean(FooBean.class, () -> new FooBeanImpl()); // (2)
context.registerBean(BarBean.class); // (3)
context.refresh();

HttpHandler handler = WebHttpHandlerBuilder
.webHandler(RouterFunctions.toHttpHandler(...))
.applicationContext(context)
.build(); // (4)
以上创建了一个 AnnotationConfigApplicationContext 实例(1),可以利用新的功能 bean 注册API(2)使用 Java 8 供应商注册 bean,或者只需通过指定其类(3)即可。HttpHandler 是使用WebHttpHandlerBuilder(4)创建的。

然后可以将 HttpHandler 安装在支持的运行服务器之一中:
// Tomcat and Jetty (also see notes below)
HttpServlet servlet = new ServletHttpHandlerAdapter(handler);
...

// Reactor Netty
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create(host, port).newHandler(adapter).block();

// Undertow
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();
对于特别是使用 WAR 部署的 Servlet 容器,可以使用作为WebApplicationInitializer 的
AbstractAnnotationConfigDispatcherHandlerInitializer,并由 Servlet容器自动检测。它负责注册 ServletHttpHandlerAdapter ,如上所示。您将需要实现一个抽象方法来指向您的 Spring 配置。

3.3 Examples

您将在以下项目中找到有助于构建反应式 Web 应用程序的代码示例:

Functional programming model sample
Spring Reactive Playground: playground for most Spring Web reactive features
Reactor website: the spring-functional
branch is a Spring 5 functional, Java 8 lambda-style application
Spring Reactive University session: live-coded project from this Devoxx BE 2106 university talk
Reactive Thymeleaf Sandbox
Mix-it 2017 website: Kotlin + Reactive + Functional web and bean registration API application
Reactor by example: code snippets coming from this InfoQ article
Spring integration tests: various features tested with Reactor StepVerifier


Webflux 实战项目

demo地址: https://anyim.cfapps.io/

作者:Anoyi
链接:http://www.jianshu.com/p/f4ff6d74ad4a

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  查看全部
1、介绍
1.1 什么是响应式编程(Reactive Programming)?

简单来说,响应式编程是针对异步和事件驱动的非阻塞应用程序,并且需要少量线程来垂直缩放(即在 JVM 内)而不是水平(即通过集群)。

响应式应用的一个关键方面是“背压(backpressure)”的概念,这是确保生产者不会压倒消费者的机制。例如,当HTTP连接太慢时,从数据库延伸到HTTP响应的反应组件的流水线、数据存储库也可以减慢或停止,直到网络容量释放。

响应式编程也导致从命令式到声明异步组合逻辑的重大转变。与使用Java 8的 CompletableFuture 编写封锁代码相比,可以通过 lambda
表达式编写后续操作。

1.2 响应式 API(Reactive API)和 构建块(Building Blocks)

Spring Framework 5 将 Reactive Streams 作为通过异步组件和库进行背压通信的合同。Reactive Streams 是通过行业协作创建的规范,也已在Java 9中被采用为 java.util.concurrent.Flow。

Spring Framework 在内部使用 Reactor 自己的响应支持。Reactor 是一个 Reactive Streams 实现,进一步扩展基本的 Reactive Streams Publisher 、Flux 和 Mono 可组合的API类型,以提供对 0..N 和 0..1 的数据序列的声明性操作。

Spring Framework 在许多自己的 Reactive API 中暴露了 Flux 和
Mono。然而,在应用级别,一如既往,Spring 提供了选择,并完全支持使用RxJava。有关的更多信息,请查看 Sebastien Deleuze 发表的 "Understanding Reactive Types" 。

2、Spring WebFlux 模块

Spring Framework 5 包括一个新的 spring-webflux 模块。该模块包含对响应式 HTTP 和 WebSocket 客户端的支持,以及对REST,HTML浏览器和 WebSocket风格交互的响应式服务器Web应用程序的支持。

2.1、服务器端

在服务器端 WebFlux 支持2种不同的编程模型:

基于注解的 @Controller 和其他注解也支持 Spring MVC
Functional 、Java 8 lambda 风格的路由和处理

3424642-7922d13b6c20ee6e.png

 

WebFlux 可以在支持 Servlet 3.1 非阻塞 IO API 以及其他异步运行时(如 Netty 和 Undertow )的 Servlet 容器上运行。每个运行时都适用于响应型 ServerHttpRequest 和 ServerHttpResponse,将请求和响应的正文暴露为 Flux<DataBuffer>,而不是具有响应背压的InputStream 和 OutputStream 。顶部作为 Flux<Object> 支持REST风格的 JSON 和 XML 序列化和反序列化,HTML视图呈现和服务器发送事件也是如此。

基于注解的编程模式

WebFlux中也支持相同的 @Controller 编程模型和 Spring MVC 中使用的相同注解。主要区别在于底层核心框架契约(即 HandlerMapping HandlerAdapter )是非阻塞的,并且在响应型 ServerHttpRequest
和 ServerHttpResponse 上运行,而不是在 HttpServletRequest 和HttpServletResponse 上运行。以下是一个响应式 Controller 的例子:
@RestController
public class PersonController {

private final PersonRepository repository;

public PersonController(PersonRepository repository) {
this.repository = repository;
}

@PostMapping("/person")
Mono<Void> create(@RequestBody Publisher<Person> personStream) {
return this.repository.save(personStream).then();
}

@GetMapping("/person")
Flux<Person> list() {
return this.repository.findAll();
}

@GetMapping("/person/{id}")
Mono<Person> findById(@PathVariable String id) {
return this.repository.findOne(id);
}
}


 
函数式编程模式

HandlerFunctions

传入的HTTP请求由 HandlerFunction 处理,HandlerFunction 本质上是一个接收 ServerRequest 并返回 Mono<ServerResponse> 的函数。处理函数的注解对应方法将是一个 @RequestMapping 的方法。

ServerRequest 和 ServerResponse 是提供JDK-8友好访问底层HTTP消息的不可变接口。两者都通过在反应堆顶部建立完全反应:请求将身体暴露为 Flux 或 Mono; 响应接受任何 Reactive Streams Publisher 作为主体。

ServerRequest 可以访问各种HTTP请求元素:方法,URI,查询参数,以及通过单独的ServerRequest.Headers 接口 - 头。通过 body方法 提供对 body 的访问。例如,这是如何将请求体提取为 Mono <String>:
Mono<String> string = request.bodyToMono(String.class);

 
这里是如何将身体提取为 Flux,其中 Person 是可以从body内容反序列化的类(即如果body包含JSON,则由Jackson支持,或者如果是XML,则为JAXB)。
Flux<Person> people = request.bodyToFlux(Person.class);

 
上面的两个方法(bodyToMono 和 bodyToFlux)实际上是使用通用ServerRequest.body(BodyExtractor)函数的便利方法。
BodyExtractor 是一个功能策略界面,允许您编写自己的提取逻辑,但在 BodyExtractors 实用程序类中可以找到常见的 BodyExtractor 实例。所以,上面的例子可以替换为:
Mono<String> string = request.body(BodyExtractors.toMono(String.class); 
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class);

 
类似地,ServerResponse 提供对HTTP响应的访问。由于它是不可变的,您可以使用构建器创建一个 ServerResponse 。构建器允许您设置响应状态,添加响应标题并提供正文。例如,这是如何使用200 OK状态创建响应,JSON内容类型和正文:Mono<Person> person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);这里是如何使用201创建的状态,位置标题和空白体来构建响应:
URI location = ... 
ServerResponse.created(location).build();

将这些组合在一起可以创建一个 HandlerFunction。例如,这里是一个简单的“Hello World”处理程序 lambda 的示例,它返回一个200状态的响应和一个基于 String 的主体:
HandlerFunction<ServerResponse> helloWorld = request -> ServerResponse.ok().body(fromObject("Hello World"));

 
使用 lambda 写处理函数,就像我们上面所说的那样很方便,但是在处理多个函数时可能缺乏可读性,变得不那么容易维护。因此,建议将相关处理函数分组到一个处理程序或控制器类中。例如,这是一个暴露了一个响应式的 Person 存储库的类:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;

public class PersonHandler {

private final PersonRepository repository;

public PersonHandler(PersonRepository repository) {
this.repository = repository;
}

// 1
public Mono<ServerResponse> listPeople(ServerRequest request) {
Flux<Person> people = repository.allPeople();
return ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Person.class);
}

// 2
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ServerResponse.ok().build(repository.savePerson(person));
}

// 3
public Mono<ServerResponse> getPerson(ServerRequest request) {
int personId = Integer.valueOf(request.pathVariable("id"));
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
Mono<Person> personMono = this.repository.getPerson(personId);
return personMono
.flatMap(person -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person)))
.switchIfEmpty(notFound);
}
}

1/ listPeople 是一个处理函数,它将数据库中发现的所有 Person
对象返回为JSON。

2/ createPerson 是一个处理函数,用于存储请求正文中包含的新Person。请注意,PersonRepository.savePerson(Person) 返回Mono<Void>:发出完成信号的空 Mono,当人从请求中读取并存储时,发出完成信号。因此,当接收到完成信号时,即当 Person 已被保存时,我们使用 build(Publisher<Void>) 方法来发送响应。

3/ getPerson 是一个处理函数,它通过路径变量id来标识一个人。我们通过数据库检索该Person,并创建一个JSON响应(如果找到)。如果没有找到,我们使用 switchIfEmpty(Mono<T>) 来返回 404 Not Found 响应。

RouterFunctions

传入请求将路由到处理函数,并使用一个 RouterFunction,它是一个服务器 ServerRequest 的函数,并返回一个 Mono<HandlerFunction>。如果请求与特定路由匹配,则返回处理函数; 否则返回一个空的 Mono。RouterFunction 与 @Controller 类中的 @RequestMapping 注解类似。

通常,您不要自己编写路由器功能,而是使用
RouterFunctions.route(RequestPredicate, HandlerFunction), 使用请求谓词和处理函数创建一个。如果谓词适用,请求将路由到给定的处理函数; 否则不执行路由,导致 404 Not Found 响应。虽然您可以编写自己的 RequestPredicate ,但是您不需要:RequestPredicates 实用程序类提供常用的谓词,基于路径,HTTP方法,内容类型等进行匹配。使用路由,我们可以路由到我们的 “Hello World” 处理函数:
RouterFunction<ServerResponse> helloWorldRoute =
RouterFunctions.route(RequestPredicates.path("/hello-world"),
request -> Response.ok().body(fromObject("Hello World")));

 
两个路由功能可以组成一个新的路由功能,路由到任一处理函数:如果第一个路由的谓词不匹配,则第二个被评估。组合的路由器功能按顺序进行评估,因此在通用功能之前放置特定功能是有意义的。您可以通过调用 RouterFunction.and(RouterFunction) 或通过调用
RouterFunction.andRoute(RequestPredicate, HandlerFunction) 来组成两个路由功能,这是 RouterFunction.and() 与 RouterFunctions.route() 的一种方便组合。

给定我们上面显示的 PersonH​​andler,我们现在可以定义路由功能,路由到相应的处理函数。我们使用 方法引用(method-references) 来引用处理函数:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> personRoute =
route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson)
.andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
.andRoute(POST("/person").and(contentType(APPLICATION_JSON)), handler::createPerson);

 
除路由功能之外,您还可以通过调用
RequestPredicate.and(RequestPredicate) 或
RequestPredicate.or(RequestPredicate) 来构成请求谓词。这些工作正如预期的那样:如果给定的谓词匹配,则生成的谓词匹配; 或者如果任一谓词都匹配。RequestPredicates 中发现的大多数谓词是组合的。例如,RequestPredicates.GET(String) 是
RequestPredicates.method(HttpMethod) 和RequestPredicates.path(String) 的组合。

启动服务器

现在只有一个难题遗留:在HTTP服务器中运行路由功能。您可以使用
RouterFunction<ServerResponse> route = ...
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create(HOST, PORT);
server.newHandler(adapter).block();

 
对于 Tomcat ,它看起来像这样:
RouterFunction<ServerResponse> route = ...
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler);
Tomcat server = new Tomcat();
Context rootContext = server.addContext("", System.getProperty("java.io.tmpdir"));
Tomcat.addServlet(rootContext, "servlet", servlet);
rootContext.addServletMapping("/", "servlet");
tomcatServer.start();

 
待完成:DispatcherHandler
HandlerFilterFunction
路由功能映射的路由可以通过调用
RouterFunction.filter(HandlerFilterFunction) 进行过滤,其中
HandlerFilterFunction 本质上是一个接收 ServerRequest 和
HandlerFunction 的函数,并返回一个 ServerResponse 。处理函数参数表示链中的下一个元素:通常是路由到的 HandlerFunction ,但是如果应用了多个过滤器,也可以是另一个 FilterFunction 。使用注解,可以使用 @ControllerAdvice 和 / 或 ServletFilter 来实现类似的功能。让我们在我们的路由中添加一个简单的安全过滤器,假设我们有一个 SecurityManager 可以确定是否允许特定的路径:
import static org.springframework.http.HttpStatus.UNAUTHORIZED;

SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = ...

RouterFunction<ServerResponse> filteredRoute =
route.filter(request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
});

 
在这个例子中可以看到,调用 next.handle(ServerRequest) 是可选的:我们只允许在允许访问时执行处理函数。

2.2 客户端(Client Side)

WebFlux 包括一个 functional, reactive WebClient,它为 RestTemplate 提供了一种完全无阻塞和响应式的替代方案。
它将网络输入和输出公开为客户端 HttpRequest 和ClientHttpResponse ,其中请求和响应的主体是 Flux <DataBuffer>而不是 InputStream 和 OutputStream。此外,它还支持与服务器端相同的响应式 JSON,XML和SSE 序列化机制,因此您可以使用类型化对象。以下是使用需要 ClientHttpConnector 实现的 WebClient 插入特定HTTP客户端(如 Reactor Netty)的示例:
WebClient client = WebClient.create("http://example.com";);

Mono<Account> account = client.get()
.url("/accounts/{id}", 1L)
.accept(APPLICATION_JSON)
.exchange(request)
.flatMap(response -> response.bodyToMono(Account.class));

 
AsyncRestTemplate 还支持非阻塞交互。主要区别在于它不支持非阻塞流,例如 Twitter one ,因为它基本上仍然依赖于 InputStream 和
OutputStream。

2.4 请求体和响应体的转换(Request and Response Body Conversion)

spring-core 模块提供了响应式 Encoder (编码器) 和 Decoder (解码器),使得能够串行化字符串与类型对象的转换。spring-web 模块添加了 JSON(Jackson)和 XML(JAXB)实现,用于Web应用程序以及其他用于SSE流和零拷贝文件传输。

支持以下 Reactive API:

Reactor 3.x 支持开箱即用
io.reactivex.rxjava2:rxjava 依赖项在类路径上时支持 RxJava 2.x
当 ·io.reactivex:rxjava和io.reactivex:rxjava-reactive-streams`(RxJava 和 Reactive Streams 之间的适配器)依赖关系在类路径上时,支持 RxJava 1.x

例如,请求体可以是以下方式之一,它将在注解和功能编程模型中自动解码:

Account account - 在调用控制器之前,account 将无阻塞地被反序列化。
Mono<Account> account - controller 可以使用 Mono 来声明在反序列化 account 后执行的逻辑。
Single<Account> account - 和 Mono 类似,但是用的是 RxJava
Flux<Account> accounts - 输入流场景
Observable<Account> accounts - RxJava 的 输入流场景

响应体(response body)可以是以下之一:

Mono<Account> - 当 Mono 完成时,序列化而不阻塞给定的Account。
Single<Account> - 与上类似,但是使用的 RxJava
Flux<Account> - 流式场景,可能是SSE,具体取决于所请求的内容类型。
Observable<Account> - 与上类似, 但是使用的 RxJava Observable 类型
Flowable<Account> - 与上类似, 但是使用的 RxJava 2 Flowable 类型。
Publisher<Account> 或 Flow.Publisher<Account> - 支持任何实现Reactive Streams Publisher 的类型。
Flux<ServerSentEvent> - SSE 流。
Mono<Void> - 当 Mono 完成时,请求处理完成。
Account - 序列化而不阻塞给定的Account; 意味着同步、非阻塞的
Controller 方法。
Void - 特定于基于注解的编程模型,方法返回时,请求处理完成;
意味着同步、非阻塞的 Controller 方法。

当使用像 Flux 或 Observable 这样的流类型时,请求/响应或映射/路由级别中指定的媒体类型用于确定数据应如何序列化和刷新。例如,返回 Flux<Account> 的REST端点将默认序列化如下:

application/json : Flux<Account> 作为异步集合处理,并在完成事件发布时将其序列化为具有显式刷新的JSON数组。
application/stream+json : 一个 Flux<Account> 将作为一系列的Account 元素处理,作为以新行分隔的单个JSON对象,并在每个元素之后显式刷新。WebClient 支持JSON流解码,因此这对于服务器到服务器的用例来说是一个很好的用例。
text/event-stream : 一个 Flux<Account> 或 Flux<ServerSentEvent<Account >> 将作为一个 Stream 或ServerSentEvent 元素的流处理,作为单独的 SSE 元素,使用默认的JSON进行数据编码和每个元素之间的显式刷新。这非常适合将流暴露给浏览器客户端。WebClient 也支持读取SSE流。

2.4 响应式 Websocket 支持

WebFlux 包括响应式 WebSocket 客户端和服务器支持。Java WebSocket API(JSR-356),Jetty,Undertow和Reactor Netty都支持客户端和服务器。

在服务器端,声明一个 WebSocketHandlerAdapter,然后简单地添加映射到基于 WebSocketHandler 的端点:
@Bean
public HandlerMapping webSocketMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/foo", new FooWebSocketHandler());
map.put("/bar", new BarWebSocketHandler());

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

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

在客户端,为上面列出的支持的库之一创建一个 WebSocketClient:
WebSocketClient client = new ReactorNettyWebSocketClient();
client.execute("ws://localhost:8080/echo"), session -> {... }).blockMillis(5000);

 
2.5 测试

spring-test 模块包括一个 WebTestClient,可用于测试具有或不具有正在运行的服务器的 WebFlux 服务器端点。

没有运行服务器的测试与来自Spring MVC的 MockMvc 相当,其中使用模拟请求和响应,而不是使用套接字通过网络连接。然而,WebTestClient 也可以针对正在运行的服务器执行测试。

更多请查看 sample tests

3、开始入门
3.1 Spring Boot Starter

通过 http://start.spring.io 提供的 Spring Boot WebFlux 启动器是最快的入门方式。它做所有必要的,所以你开始像Spring MVC一样编写@Controller类。只需转到 http://start.spring.io ,选择版本
2.0.0.BUILD-SNAPSHOT,并在依赖关系框中键入 respond。
默认情况下,启动器使用 Reactor Netty 运行,但依赖关系可以像往常一样通过 Spring Boot 更改为不同的运行时。有关更多详细信息和说明,请参阅 Spring Boo t参考文档页面。

3.2 手动引导(Manual Bootstrapping)

对于依赖关系,从 spring-webflux 和 spring-context 开始。
然后添加jackson-databind 和 io.netty:netty-buffer(暂时见SPR-14528)以获得JSON支持。最后添加一个支持的运行时的依赖项:

Tomcat — org.apache.tomcat.embed:tomcat-embed-core
Jetty — org.eclipse.jetty:jetty-server 和 org.eclipse.jetty:jetty-servlet
Reactor Netty — io.projectreactor.ipc:reactor-netty
Undertow — io.undertow:undertow-core

基于注解编程模式的引导:
ApplicationContext context = new AnnotationConfigApplicationContext(DelegatingWebFluxConfiguration.class);  // (1)
HttpHandler handler = DispatcherHandler.toHttpHandler(context); // (2)

 
以上加载默认的 Spring Web 框架配置(1),然后创建一个DispatcherHandler,主类驱动请求处理(2),并适应 HttpHandler - 响应式HTTP请求处理的最低级别的Spring抽象。

函数编程模式的引导:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // (1)
context.registerBean(FooBean.class, () -> new FooBeanImpl()); // (2)
context.registerBean(BarBean.class); // (3)
context.refresh();

HttpHandler handler = WebHttpHandlerBuilder
.webHandler(RouterFunctions.toHttpHandler(...))
.applicationContext(context)
.build(); // (4)

以上创建了一个 AnnotationConfigApplicationContext 实例(1),可以利用新的功能 bean 注册API(2)使用 Java 8 供应商注册 bean,或者只需通过指定其类(3)即可。HttpHandler 是使用WebHttpHandlerBuilder(4)创建的。

然后可以将 HttpHandler 安装在支持的运行服务器之一中:
// Tomcat and Jetty (also see notes below)
HttpServlet servlet = new ServletHttpHandlerAdapter(handler);
...

// Reactor Netty
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create(host, port).newHandler(adapter).block();

// Undertow
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();

对于特别是使用 WAR 部署的 Servlet 容器,可以使用作为WebApplicationInitializer 的
AbstractAnnotationConfigDispatcherHandlerInitializer,并由 Servlet容器自动检测。它负责注册 ServletHttpHandlerAdapter ,如上所示。您将需要实现一个抽象方法来指向您的 Spring 配置。

3.3 Examples

您将在以下项目中找到有助于构建反应式 Web 应用程序的代码示例:

Functional programming model sample
Spring Reactive Playground: playground for most Spring Web reactive features
Reactor website: the spring-functional
branch is a Spring 5 functional, Java 8 lambda-style application
Spring Reactive University session: live-coded project from this Devoxx BE 2106 university talk
Reactive Thymeleaf Sandbox
Mix-it 2017 website: Kotlin + Reactive + Functional web and bean registration API application
Reactor by example: code snippets coming from this InfoQ article
Spring integration tests: various features tested with Reactor StepVerifier


Webflux 实战项目

demo地址: https://anyim.cfapps.io/

作者:Anoyi
链接:http://www.jianshu.com/p/f4ff6d74ad4a

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

自制Mybatis插件--doge-mapper,欢迎使用和吐槽

开源项目throwable 发表了文章 • 4 个评论 • 122 次浏览 • 5 天前 • 来自相关话题

项目简介
   doge-mapper致力于打造一个轻量级、可扩展、灵活的Mybatis插件,配套一个实体、DAO代码生成器doge-mapper-generator(基于JavaFx编写)。目前此插件只支持Mysql和单表,需要Jdk1.8。
 
项目地址
Github:https://github.com/zjcscut/doge-mapper
 
项目文档
doge-mapper文档:https://github.com/zjcscut/doge-mapper/blob/master/readme/readme_zh_cn.md doge-mapper-generator文档:https://github.com/zjcscut/dog ... cn.md

项目使用快照:
    实体:




    Mapper:
 




 
    使用:




 
写在最后
   欢迎吐槽和Star,如果使用过程出现任何问题,直接发送邮件给我,第一时间回答和解决,邮箱见https://github.com/zjcscut/doge-mapper/的Contact部分。

 
  查看全部
项目简介
   doge-mapper致力于打造一个轻量级、可扩展、灵活的Mybatis插件,配套一个实体、DAO代码生成器doge-mapper-generator(基于JavaFx编写)。目前此插件只支持Mysql和单表,需要Jdk1.8。
 
项目地址

 
项目文档


项目使用快照:
    实体:
entity.png

    Mapper:
 
mapper.png

 
    使用:
usage.png

 
写在最后
   欢迎吐槽和Star,如果使用过程出现任何问题,直接发送邮件给我,第一时间回答和解决,邮箱见https://github.com/zjcscut/doge-mapper/的Contact部分。

 
 

Spring学习(一): 使用AbstractRoutingDataSource类进行多数据源配置

Spring Datazane 发表了文章 • 0 个评论 • 159 次浏览 • 2017-09-12 12:33 • 来自相关话题

多数据源在项目中很常见,举几个需求,如页面展示的数据需要从不同的数据库中去查询,修改等,例如数据库的读写分离等

配置多数据源要考虑到如下的情况
单个数据源是如下这种情况:

如果是多个数据源的话,将会变成下面这种情况



这样,每增加一个数据源就要去配置一个sessionFactory,这样比较麻烦,于是spring的
AbstractRoutingDataSource类产生了


它的实现原理是扩展Spring的`AbstractRoutingDataSource`抽象类(该类充当了`DataSource`的路由中介, 能在运行时, 根据某种key值来动态切换到真正的`DataSource`上。)
 从AbstractRoutingDataSource的源码中:public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}

public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}

获取连接的方法中,重点是determineTargetDataSource()方法,看源码:protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。
看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:

接下来,将展示一个多数据源配置demo 

1.配置一个config类,配置3个数据源(下面是JAVA配置写法)/**
* Created by Youjie on 2017/7/23.
*/
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

@Bean
@ConfigurationProperties(prefix = "datasource.db_a")
@Qualifier("dataSourceDefault")
public DataSource dataSourceDefault(){
return new DruidDataSource();
}

@Bean
@ConfigurationProperties(prefix = "datasource.db_b")
@Qualifier("dataSourceB")
public DataSource dataSourceB(){
return new DruidDataSource();
}

@Bean
@ConfigurationProperties(prefix = "datasource.db_c")
@Qualifier("dataSourceC")
public DataSource dataSourceC(){
return new DruidDataSource();
}


@Bean
@Primary
public DataSource multipleDataSource(){
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSourceDefault());

Map<Object,Object> dataSourcesType = new HashMap();
dataSourcesType.put(DataSources.DATASOURCE_A,dataSourceDefault());
dataSourcesType.put(DataSources.DATASOURCE_B,dataSourceB());
dataSourcesType.put(DataSources.DATASOURCE_C,dataSourceC());

dynamicDataSource.setTargetDataSources(dataSourcesType);

return dynamicDataSource;
}



@Bean
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(multipleDataSource());
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
sqlSessionFactoryBean.setTypeAliasesPackage("com.may.model");
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("configuration.xml"));
return sqlSessionFactoryBean.getObject();
}

@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(multipleDataSource());
}



}对应的 application.properties如下server.port=9002
server.context-path=/


#A user
datasource.db_a.driver-class-name=com.mysql.jdbc.Driver
datasource.db_a.url=jdbc:mysql://127.0.0.1:3306/test_A?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_a.username=root
datasource.db_a.password=123456

#B city
datasource.db_b.driver-class-name=com.mysql.jdbc.Driver
datasource.db_b.url=jdbc:mysql://127.0.0.1:3306/test_B?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_b.username=root
datasource.db_b.password=123456

#C role
datasource.db_c.driver-class-name=com.mysql.jdbc.Driver
datasource.db_c.url=jdbc:mysql://127.0.0.1:3306/test_C?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_c.username=root
datasource.db_c.password=123456

2、写一个ThreadLocalDynamicDataSource类继承AbstractRoutingDataSource,并实现determineCurrentLookupKey方法public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceTypeManager.get();
}
}3、编写DataSourceTypeManager执行类,并利用ThreadLocal以空间换时间的方式解决线程安全问题public class DataSourceTypeManager {
private static final ThreadLocal<DataSources> dataSourceTypes = new ThreadLocal<DataSources>() {
@Override
protected DataSources initialValue() {
return DataSources.DATASOURCE_DEFAULT;
}
};

public static DataSources get() {
return dataSourceTypes.get();
}

public static void set(DataSources dataSourceType) {
dataSourceTypes.set(dataSourceType);
}

public static void reset() {
dataSourceTypes.set(DataSources.DATASOURCE_DEFAULT);
}

public static void clearDataSources () {
dataSourceTypes.remove();
}
}




上面代码利用initialValue方法,调用初始化时指定的DataSources 类型,这里DataSources 为枚举类型,里面定义三种数据源的名称,代码如下:public enum DataSources {
DATASOURCE,DATASOURCE_DEFAULT,DATASOURCE_ORACLE
}
4、multipleDataSource多数据源配置的xml配置<bean id="multipleDataSource"
class="com.hxqc.basic.dependency.datasource.ThreadLocalRountingDataSource">
<property name="defaultTargetDataSource" ref="ncDataSource"/>
<property name="targetDataSources">
<map key-type="com.hxqc.basic.dependency.datasource.DataSources">
<entry key="DATASOURCE" value-ref="erpDataSource"/>
<entry key="DATASOURCE_DEFAULT" value-ref="ncDataSource"/>
<entry key="DATASOURCE_ORACLE" value-ref="oracleDataSource"/>
</map>
</property>
</bean>



multipleDataSource多数据源配置的Java配置(上文已经贴出该配置,这里再强调一下)@Bean
@Primary
public DataSource multipleDataSource(){
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSourceDefault());

Map<Object,Object> dataSourcesType = new HashMap();
dataSourcesType.put(DataSources.DATASOURCE_A,dataSourceDefault());
dataSourcesType.put(DataSources.DATASOURCE_B,dataSourceB());
dataSourcesType.put(DataSources.DATASOURCE_C,dataSourceC());

dynamicDataSource.setTargetDataSources(dataSourcesType);

return dynamicDataSource;
}
5.在DAOImpl中切换数据源 或者配置aop切面根据不同的包下面的数据,采用不同的数据源



1,在Dao层直接切换数据源,如切换到默认数据源DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT)
2.根据aop进行动态切换
aop xml配置<bean id="dataSourceInterceptor" class="com.hxqc.basic.dependency.interceptor.DataSourceInterceptor"/>
<aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
<aop:pointcut id="datasource" expression="execution(* com.hxqc.data.gather.core.erp.*.mapper..*.*(..))"/>
<aop:pointcut id="datasource_oracle" expression="execution(* com.hxqc.data.gather.core.oracle.*.mapper..*.*(..))"/>
<aop:pointcut id="datasource_default" expression="execution(* com.hxqc.data.gather.core.nc.*.mapper..*.*(..))"/>
<aop:before method="setDataSource" pointcut-ref="datasource"/>
<aop:before method="setDataSourceDefault" pointcut-ref="datasource_default"/>
<aop:before method="setDataSourceOracle" pointcut-ref="datasource_oracle"/>
</aop:aspect>
</aop:config>aop java 配置@Aspect
@Component
public class DataSourceAspect {

private Logger log = LoggerFactory.getLogger(getClass());

@Pointcut("execution(* com.may.dataA.mapper.*.*(..))")
public void pointCutA(){}

@Pointcut("execution(* com.may.dataB.mapper.*.*(..))")
public void pointCutB(){}

@Pointcut("execution(* com.may.dataC.mapper.*.*(..))")
public void pointCutC(){}
@Pointcut("execution(* com.may..*.*(..))")
public void annotation(){}



public void setDataSourceDefault(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_A);
log.info("切换数据源","切换到默认数据源A");

}
public void setDataSourceB(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_B);
log.info("切换数据源","切换到数据源B");
}

public void setDataSourceC(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_C);
log.info("切换数据源","切换到数据源C");
}

@Before("@annotation(ds)")
public void setAnnotationDataSource(JoinPoint jp,DataSource ds){
// 获取当前的指定的数据源;
String dsId = ds.value();
// 如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
/*if (!DataSourceManager.containsDataSource(dsId)) {

log.error("数据源[{}]不存在,使用默认数据源 > {}", ds.value(), jp.getSignature());

} else {

log.debug("Use DataSource : {} > {}", ds.value(), jp.getSignature());

// 找到的话,那么设置到动态数据源上下文中。
DynamicDataSourceContextHolder.setDataSourceType(ds.name());
}*/

//将string值转换为enum
DataSourceManager.set(Enum.valueOf(DataSources.class,ds.value()));

log.info("切换数据源"+ds.value(),"切换到默认数据源"+ds.value());
}



}
解释,这里定义三个切面,分别对应三个数据源,如在oracle包下的,动态切换到oracle数据源,上面配置的最后部分是用到了自定义注解DataSource注解,该注解在方法上,通过切面来进行数据源的切换,具体看源码

然后配置切面类/**
*
*
* @author maple
*
*/
public class DataSourceInterceptor {

public void setDataSourceDefault(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT);
LogU.i("切换数据源","切换到NC数据源");
System.out.println(jp.getTarget().getClass().getSimpleName());
}

public void setDataSource(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE);
LogU.i("切换数据源","切换到ERP数据源");
}

public void setDataSourceOracle(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE_ORACLE);
LogU.i("切换数据源","切换到Oracle数据源");
}
}





`问题:`多数据源切换是成功了,但牵涉到事务呢?单数据源事务是ok的,但如果多数据源需要同时使用一个事务呢?这个问题有点头大,网络上有人提出用atomikos开源项目实现JTA分布式事务处理。你怎么看?

注意:

本文源码放在码云上,注意,源码将XML配置的内容都改成了JAVA配置,项目是基于Spring-boot,也提供了自定义注解来改变动态数据源!

本文源码:http://git.oschina.net/youjie1/may-dynamic-datasource
原文

  查看全部


多数据源在项目中很常见,举几个需求,如页面展示的数据需要从不同的数据库中去查询,修改等,例如数据库的读写分离等


配置多数据源要考虑到如下的情况
单个数据源是如下这种情况:

如果是多个数据源的话,将会变成下面这种情况



这样,每增加一个数据源就要去配置一个sessionFactory,这样比较麻烦,于是spring的
AbstractRoutingDataSource类产生了


它的实现原理是扩展Spring的`AbstractRoutingDataSource`抽象类(该类充当了`DataSource`的路由中介, 能在运行时, 根据某种key值来动态切换到真正的`DataSource`上。)
 从AbstractRoutingDataSource的源码中:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean


我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:
public Connection getConnection() throws SQLException {  
return determineTargetDataSource().getConnection();
}

public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}


获取连接的方法中,重点是determineTargetDataSource()方法,看源码:
protected DataSource determineTargetDataSource() {  
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。
看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:

接下来,将展示一个多数据源配置demo 

1.配置一个config类,配置3个数据源(下面是JAVA配置写法)
/**
* Created by Youjie on 2017/7/23.
*/
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

@Bean
@ConfigurationProperties(prefix = "datasource.db_a")
@Qualifier("dataSourceDefault")
public DataSource dataSourceDefault(){
return new DruidDataSource();
}

@Bean
@ConfigurationProperties(prefix = "datasource.db_b")
@Qualifier("dataSourceB")
public DataSource dataSourceB(){
return new DruidDataSource();
}

@Bean
@ConfigurationProperties(prefix = "datasource.db_c")
@Qualifier("dataSourceC")
public DataSource dataSourceC(){
return new DruidDataSource();
}


@Bean
@Primary
public DataSource multipleDataSource(){
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSourceDefault());

Map<Object,Object> dataSourcesType = new HashMap();
dataSourcesType.put(DataSources.DATASOURCE_A,dataSourceDefault());
dataSourcesType.put(DataSources.DATASOURCE_B,dataSourceB());
dataSourcesType.put(DataSources.DATASOURCE_C,dataSourceC());

dynamicDataSource.setTargetDataSources(dataSourcesType);

return dynamicDataSource;
}



@Bean
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(multipleDataSource());
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
sqlSessionFactoryBean.setTypeAliasesPackage("com.may.model");
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("configuration.xml"));
return sqlSessionFactoryBean.getObject();
}

@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(multipleDataSource());
}



}
对应的 application.properties如下
server.port=9002
server.context-path=/


#A user
datasource.db_a.driver-class-name=com.mysql.jdbc.Driver
datasource.db_a.url=jdbc:mysql://127.0.0.1:3306/test_A?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_a.username=root
datasource.db_a.password=123456

#B city
datasource.db_b.driver-class-name=com.mysql.jdbc.Driver
datasource.db_b.url=jdbc:mysql://127.0.0.1:3306/test_B?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_b.username=root
datasource.db_b.password=123456

#C role
datasource.db_c.driver-class-name=com.mysql.jdbc.Driver
datasource.db_c.url=jdbc:mysql://127.0.0.1:3306/test_C?autoReconnect=true&useUnicode=true&characterEncoding=utf8&logSlowQueries=false&slowQueryThresholdMillis=0&allowMultiQueries=true
datasource.db_c.username=root
datasource.db_c.password=123456


2、写一个ThreadLocalDynamicDataSource类继承AbstractRoutingDataSource,并实现determineCurrentLookupKey方法
public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceTypeManager.get();
}
}
3、编写DataSourceTypeManager执行类,并利用ThreadLocal以空间换时间的方式解决线程安全问题
public class DataSourceTypeManager {
private static final ThreadLocal<DataSources> dataSourceTypes = new ThreadLocal<DataSources>() {
@Override
protected DataSources initialValue() {
return DataSources.DATASOURCE_DEFAULT;
}
};

public static DataSources get() {
return dataSourceTypes.get();
}

public static void set(DataSources dataSourceType) {
dataSourceTypes.set(dataSourceType);
}

public static void reset() {
dataSourceTypes.set(DataSources.DATASOURCE_DEFAULT);
}

public static void clearDataSources () {
dataSourceTypes.remove();
}
}




上面代码利用initialValue方法,调用初始化时指定的DataSources 类型,这里DataSources 为枚举类型,里面定义三种数据源的名称,代码如下:
public enum DataSources {
DATASOURCE,DATASOURCE_DEFAULT,DATASOURCE_ORACLE
}

4、multipleDataSource多数据源配置的xml配置
<bean id="multipleDataSource"
class="com.hxqc.basic.dependency.datasource.ThreadLocalRountingDataSource">
<property name="defaultTargetDataSource" ref="ncDataSource"/>
<property name="targetDataSources">
<map key-type="com.hxqc.basic.dependency.datasource.DataSources">
<entry key="DATASOURCE" value-ref="erpDataSource"/>
<entry key="DATASOURCE_DEFAULT" value-ref="ncDataSource"/>
<entry key="DATASOURCE_ORACLE" value-ref="oracleDataSource"/>
</map>
</property>
</bean>



multipleDataSource多数据源配置的Java配置(上文已经贴出该配置,这里再强调一下)
@Bean
@Primary
public DataSource multipleDataSource(){
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSourceDefault());

Map<Object,Object> dataSourcesType = new HashMap();
dataSourcesType.put(DataSources.DATASOURCE_A,dataSourceDefault());
dataSourcesType.put(DataSources.DATASOURCE_B,dataSourceB());
dataSourcesType.put(DataSources.DATASOURCE_C,dataSourceC());

dynamicDataSource.setTargetDataSources(dataSourcesType);

return dynamicDataSource;
}

5.在DAOImpl中切换数据源 或者配置aop切面根据不同的包下面的数据,采用不同的数据源



1,在Dao层直接切换数据源,如切换到默认数据源
DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT)

2.根据aop进行动态切换
aop xml配置
<bean id="dataSourceInterceptor" class="com.hxqc.basic.dependency.interceptor.DataSourceInterceptor"/>
<aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
<aop:pointcut id="datasource" expression="execution(* com.hxqc.data.gather.core.erp.*.mapper..*.*(..))"/>
<aop:pointcut id="datasource_oracle" expression="execution(* com.hxqc.data.gather.core.oracle.*.mapper..*.*(..))"/>
<aop:pointcut id="datasource_default" expression="execution(* com.hxqc.data.gather.core.nc.*.mapper..*.*(..))"/>
<aop:before method="setDataSource" pointcut-ref="datasource"/>
<aop:before method="setDataSourceDefault" pointcut-ref="datasource_default"/>
<aop:before method="setDataSourceOracle" pointcut-ref="datasource_oracle"/>
</aop:aspect>
</aop:config>
aop java 配置
@Aspect
@Component
public class DataSourceAspect {

private Logger log = LoggerFactory.getLogger(getClass());

@Pointcut("execution(* com.may.dataA.mapper.*.*(..))")
public void pointCutA(){}

@Pointcut("execution(* com.may.dataB.mapper.*.*(..))")
public void pointCutB(){}

@Pointcut("execution(* com.may.dataC.mapper.*.*(..))")
public void pointCutC(){}
@Pointcut("execution(* com.may..*.*(..))")
public void annotation(){}



public void setDataSourceDefault(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_A);
log.info("切换数据源","切换到默认数据源A");

}
public void setDataSourceB(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_B);
log.info("切换数据源","切换到数据源B");
}

public void setDataSourceC(JoinPoint jp){
DataSourceManager.set(DataSources.DATASOURCE_C);
log.info("切换数据源","切换到数据源C");
}

@Before("@annotation(ds)")
public void setAnnotationDataSource(JoinPoint jp,DataSource ds){
// 获取当前的指定的数据源;
String dsId = ds.value();
// 如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
/*if (!DataSourceManager.containsDataSource(dsId)) {

log.error("数据源[{}]不存在,使用默认数据源 > {}", ds.value(), jp.getSignature());

} else {

log.debug("Use DataSource : {} > {}", ds.value(), jp.getSignature());

// 找到的话,那么设置到动态数据源上下文中。
DynamicDataSourceContextHolder.setDataSourceType(ds.name());
}*/

//将string值转换为enum
DataSourceManager.set(Enum.valueOf(DataSources.class,ds.value()));

log.info("切换数据源"+ds.value(),"切换到默认数据源"+ds.value());
}



}

解释,这里定义三个切面,分别对应三个数据源,如在oracle包下的,动态切换到oracle数据源,上面配置的最后部分是用到了自定义注解DataSource注解,该注解在方法上,通过切面来进行数据源的切换,具体看源码

然后配置切面类
/**
*
*
* @author maple
*
*/
public class DataSourceInterceptor {

public void setDataSourceDefault(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT);
LogU.i("切换数据源","切换到NC数据源");
System.out.println(jp.getTarget().getClass().getSimpleName());
}

public void setDataSource(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE);
LogU.i("切换数据源","切换到ERP数据源");
}

public void setDataSourceOracle(JoinPoint jp){
DataSourceTypeManager.set(DataSources.DATASOURCE_ORACLE);
LogU.i("切换数据源","切换到Oracle数据源");
}
}





`问题:`多数据源切换是成功了,但牵涉到事务呢?单数据源事务是ok的,但如果多数据源需要同时使用一个事务呢?这个问题有点头大,网络上有人提出用atomikos开源项目实现JTA分布式事务处理。你怎么看?

注意:

本文源码放在码云上,注意,源码将XML配置的内容都改成了JAVA配置,项目是基于Spring-boot,也提供了自定义注解来改变动态数据源!

本文源码:http://git.oschina.net/youjie1/may-dynamic-datasource
原文

 

官方Spring周报-20170905

最新动态strongant 发表了文章 • 0 个评论 • 829 次浏览 • 2017-09-07 21:58 • 来自相关话题

Spring的粉丝们,欢迎来到本周的另一期官方周报!这个星期我在香港看这个惊人的YOW!然后是新加坡的YOW。这些会议都是来自澳大利亚顶尖的会议,而且会议很新颖。如果你在这两个地方,我希望你会来。

http://www.yowconference.hk/

http://www.yowconference.sg/

1.介绍下关于Spring Cloud Data Flow最重要的一个方面。 Spring Cloud Data Flow大牛Eric Bottard首先介绍了Spring Cloud Data Flow Shell本身。

https://spring.io/blog/2017/08 ... shell

2.Spring Cloud贡献者Ryan Baxter刚刚宣布了Spring Cloud Edgeware M1 的发布。新版本包括对各种项目的更新以及新的Spring Cloud Gateway项目。此版本基于Spring Boot 1.5.x。

https://spring.io/blog/2017/08 ... lable

3.Spring Cloud联合创始人Spencer Gibb刚刚宣布了Spring Cloud Finchely M2的发布。这个新版本基于Spring Boot 2.0.x,所以是基于Java 8。它还支持Spring Cloud Gateway中更多更酷的功能。在Spring Cloud Gateway和其他版本中,一个常见主题是反应性支持。

https://spring.io/blog/2017/08 ... lable

4.Spring Cloud Flow大牛Thomas Risberg在本文中介绍了针对Kubernetes的Spring Cloud Data Flow的Helm安装程序。 Helm是Kubernetes的包管理者。

https://spring.io/blog/2017/08 ... -helm

5.这个应用现代化的Pivotal和Apigee白皮书是一个很好的阅读题材。

https://content.pivotal.io/api ... 73ac4

6.Matt McCandless称Spring Boot老而弥新,这是个很好的起步。

https://dzone.com/articles/spr ... you-1

7.这篇关于使用Spring Boot的Quartz Scheduler的文章是曾经看过的一篇文章。我喜欢。

http://www.opencodez.com/java/ ... t.htm

8.看起来要想Java 9发布,这个新的Java版本将会有6个月的时间。我个人欢迎新版本。

https://jcp.org/aboutJava/comm ... .html

9.我真的很喜欢这个俄语的帖子,介绍Spring Boot的应用程序开发。

https://alexkosarev.name/2017/ ... rt-1/

10.上周Pivotal与Google和VMWare合作,宣布了Pivotal Cloud Foundry生态系统 - Pivotal Container Service - 由BOSH管理的Kubernetes服务的下一步工作。我们从TechCrunch片中得到一些细节。

https://www.blog.google/topics ... -gcp/

https://techcrunch.com/2017/08 ... /amp/

11.我喜欢这个Auth0在集成Spring Boot与SQL数据库(PostgreSQL),JPA和Liquibase。

https://auth0.com/blog/integra ... base/

12.来自JHipster的这篇关于“API优先开发”的文章很有趣。

http://www.jhipster.tech/doing ... ment/

13.Spring Boot 2.0具有反应弹性框架5和Java 8基准线,肯定会令人印象深刻。新版本中我最喜欢的功能之一就是支持Spring Boot Actuator端点的REST-stack(响应式Spring WebFlux,Spring MVC或JAX-RS)。

https://www.infoq.com/news/201 ... oints

14.看看这个优秀的网络研讨会,了解为什么IntelliJ IDEA是Grails应用程序的首选。我喜欢这个,因为Grails 3是基于Spring Boot的,因为Jeff Scott Brown是一个伟大的演讲者,因为IntelliJ做了一个漂亮的好IDE。

https://blog.jetbrains.com/ide ... ls-3/

15.严格来说,这与Spring不是很相关,但它是值得一读的。丹尼尔·布莱恩特(Daniel Bryant)在这篇文章中描述了测试微服务(以及您可能使用的所有组件)的全方位方法。

https://specto.io/blog/2016/8/ ... ices/

16.我喜欢这个来自Spring Data的StackOverflow响应Oliver Gierke:如何在Spring Boot中选择性升级依赖项?

https://stackoverflow.com/ques ... g-dat

17.如果您想快速了解一下Cloud Foundry服务代理商,请阅读本文。

http://www.automate-it.today/a ... kers/

18.这篇也不是严格来说与Spring有关,但对于构建云原生应用程序的一些Spring用户来说,这对于Java云主机服务的成本降低策略来说非常有用。

https://www.infoq.com/articles/java-cloud-cost-reduction
 
备注:希望大家能够积极参与我们的公益开源翻译项目,对于有些翻译不当的地方由于水平问题望见谅!我们的SpringBoot周报开源翻译项目地址:https://github.com/strongant/ThisWeekInSpringTranslate
欢迎各路大神加入我们的翻译计划,感谢! 查看全部
Spring的粉丝们,欢迎来到本周的另一期官方周报!这个星期我在香港看这个惊人的YOW!然后是新加坡的YOW。这些会议都是来自澳大利亚顶尖的会议,而且会议很新颖。如果你在这两个地方,我希望你会来。

http://www.yowconference.hk/

http://www.yowconference.sg/

1.介绍下关于Spring Cloud Data Flow最重要的一个方面。 Spring Cloud Data Flow大牛Eric Bottard首先介绍了Spring Cloud Data Flow Shell本身。

https://spring.io/blog/2017/08 ... shell

2.Spring Cloud贡献者Ryan Baxter刚刚宣布了Spring Cloud Edgeware M1 的发布。新版本包括对各种项目的更新以及新的Spring Cloud Gateway项目。此版本基于Spring Boot 1.5.x。

https://spring.io/blog/2017/08 ... lable

3.Spring Cloud联合创始人Spencer Gibb刚刚宣布了Spring Cloud Finchely M2的发布。这个新版本基于Spring Boot 2.0.x,所以是基于Java 8。它还支持Spring Cloud Gateway中更多更酷的功能。在Spring Cloud Gateway和其他版本中,一个常见主题是反应性支持。

https://spring.io/blog/2017/08 ... lable

4.Spring Cloud Flow大牛Thomas Risberg在本文中介绍了针对Kubernetes的Spring Cloud Data Flow的Helm安装程序。 Helm是Kubernetes的包管理者。

https://spring.io/blog/2017/08 ... -helm

5.这个应用现代化的Pivotal和Apigee白皮书是一个很好的阅读题材。

https://content.pivotal.io/api ... 73ac4

6.Matt McCandless称Spring Boot老而弥新,这是个很好的起步。

https://dzone.com/articles/spr ... you-1

7.这篇关于使用Spring Boot的Quartz Scheduler的文章是曾经看过的一篇文章。我喜欢。

http://www.opencodez.com/java/ ... t.htm

8.看起来要想Java 9发布,这个新的Java版本将会有6个月的时间。我个人欢迎新版本。

https://jcp.org/aboutJava/comm ... .html

9.我真的很喜欢这个俄语的帖子,介绍Spring Boot的应用程序开发。

https://alexkosarev.name/2017/ ... rt-1/

10.上周Pivotal与Google和VMWare合作,宣布了Pivotal Cloud Foundry生态系统 - Pivotal Container Service - 由BOSH管理的Kubernetes服务的下一步工作。我们从TechCrunch片中得到一些细节。

https://www.blog.google/topics ... -gcp/

https://techcrunch.com/2017/08 ... /amp/

11.我喜欢这个Auth0在集成Spring Boot与SQL数据库(PostgreSQL),JPA和Liquibase。

https://auth0.com/blog/integra ... base/

12.来自JHipster的这篇关于“API优先开发”的文章很有趣。

http://www.jhipster.tech/doing ... ment/

13.Spring Boot 2.0具有反应弹性框架5和Java 8基准线,肯定会令人印象深刻。新版本中我最喜欢的功能之一就是支持Spring Boot Actuator端点的REST-stack(响应式Spring WebFlux,Spring MVC或JAX-RS)。

https://www.infoq.com/news/201 ... oints

14.看看这个优秀的网络研讨会,了解为什么IntelliJ IDEA是Grails应用程序的首选。我喜欢这个,因为Grails 3是基于Spring Boot的,因为Jeff Scott Brown是一个伟大的演讲者,因为IntelliJ做了一个漂亮的好IDE。

https://blog.jetbrains.com/ide ... ls-3/

15.严格来说,这与Spring不是很相关,但它是值得一读的。丹尼尔·布莱恩特(Daniel Bryant)在这篇文章中描述了测试微服务(以及您可能使用的所有组件)的全方位方法。

https://specto.io/blog/2016/8/ ... ices/

16.我喜欢这个来自Spring Data的StackOverflow响应Oliver Gierke:如何在Spring Boot中选择性升级依赖项?

https://stackoverflow.com/ques ... g-dat

17.如果您想快速了解一下Cloud Foundry服务代理商,请阅读本文。

http://www.automate-it.today/a ... kers/

18.这篇也不是严格来说与Spring有关,但对于构建云原生应用程序的一些Spring用户来说,这对于Java云主机服务的成本降低策略来说非常有用。

https://www.infoq.com/articles/java-cloud-cost-reduction
 
备注:希望大家能够积极参与我们的公益开源翻译项目,对于有些翻译不当的地方由于水平问题望见谅!我们的SpringBoot周报开源翻译项目地址:https://github.com/strongant/ThisWeekInSpringTranslate
欢迎各路大神加入我们的翻译计划,感谢!

Spring Boot Web 开发注解篇

Spring Boot泥瓦匠BYSocket 发表了文章 • 0 个评论 • 405 次浏览 • 2017-08-02 13:59 • 来自相关话题

摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!
“人才是公司运作的关键所在”
本文提纲
1. spring-boot-starter-web 依赖概述
1.1 spring-boot-starter-web 职责
1.2 spring-boot-starter-web 依赖关系
2. Spring MVC on Spring Boot
2.1 Spring MVC 体系温故知新
2.2 重要的类
2.3 Spring Boot MVC
2.3.1 控制器
2.3.2 数据绑定
2.3.3 视图和视图解析 

一、spring-boot-starter-web 依赖概述
在 Spring Boot 快速入门中,只要在 pom.xml 加入了 spring-boot-starter-web 依赖,即可快速开发 web 应用。可见,Spring Boot 极大地简化了 Spring 应用从搭建到开发的过程,做到了「开箱即用」的方式。Spring Boot 已经提供很多「开箱即用」的依赖,如上面开发 web 应用使用的 spring-boot-starter-web ,都是以 spring-boot-starter-xx 进行命名的。
 
Spring Boot 「开箱即用」 的设计,对开发者非常便利。简单来说,只要往 Spring Boot 项目加入相应的 spring-boot-starter-xx 依赖,就可以使用对应依赖的功能,比如加入 spring-boot-starter-data-jpa 依赖,就可以使用数据持久层框架 Spring Data JPA 操作数据源。相比 Spring 以前需要大量的XML配置以及复杂的依赖管理,极大的减少了开发工作量和学习成本。

当开发一个特定类型的应用程序时,特定的 Starter 提供所需的依赖关系,并且将对应的 Bean 注册到 Spring 容器中。spring-boot-starter-web 依赖就是提供开发 Web 应用的。

1.1 spring-boot-starter-web 职责
spring-boot-starter-web 是一个用于构建 Web 的 Starter ,包括构建 RESTful 服务应用、Spring MVC 应用等。并且不需要额外配置容器,默认使用 Tomcat 作为嵌入式容器。

1.2 spring-boot-starter-web 依赖关系
spring-boot-starter-web 这么强大,它的组成如下表:
spring-boot-starter  核心包,包括了自动化配置支持、日志、YAML 文件解析的支持等。
spring-boot-starter-json 读写 JSON 包
spring-boot-starter-tomcat Tomcat 嵌入式 Servlet 容器包
hibernate-validator Hibernate 框架提供的验证包
spring-web Spring 框架的 Web 包
spring-webmvc Spring 框架的 Web MVC 包







spring-boot-starter-web 包含了 Tomcat 和 Spring MVC ,那启动流程是这样的。 标识 @SpringBootApplication 的应用,初始化经过 spring-boot-starter  核心包中的自动化配置,构建了 Spring 容器,并通过 Tomcat 启动 Web 应用。很多 Starters 只支持 Spring MVC,一般会将 spring-boot-starter-web 依赖加入到应用的 Classpath。

另外,spring-boot-starter-web 默认使用 Tomcat 作为嵌入式 Servlet 容器,在 pom.xml 配置 spring-boot-starter-jetty 和 spring-boot-starter-undertow 就可以替换默认容器。


二、Spring MVC on Spring Boot
Spring MVC 是 Spring Web 重要的模块。内容包括 MVC 模式的实现和 RESTful 服务的支持。

2.1 Spring MVC 体系温故知新

spring-webmvc 模块里面包:
- org.springframework.web.servlet 提供与应用程序上下文基础结构集成的 Servlet,以及 Spring web MVC 框架的核心接口和类。
- org.springframework.web.servlet.mvc Spring 附带的 Servlet MVC 框架的标准控制器实现。
- org.springframework.web.servlet.mvc.annotation 用于基于注解的 Servlet MVC 控制器的支持包。
- org.springframework.web.servlet.mvc.condition 用于根据条件匹配传入请求的公共 MVC 逻辑。
- org.springframework.web.servlet.mvc.method 用于处理程序方法处理的基于 Servlet 的基础结构,基于在 org.springframework.web.method 包上。
- org.springframework.web.servlet.view 提供标准的 View 和 ViewResolver 实现,包括自定义实现的抽象基类。
- org.springframework.web.servlet.view.freemarker 支持将 FreeMarker 集成为 Spring Web 视图技术的类。
- org.springframework.web.servlet.view.json 支持提供基于 JSON 序列化的 View 实现的类。

上面列出来核心的包。org.springframework.web.servlet.view 包中, View 视图实现有常见的:JSON 、FreeMarker 等。org.springframework.web.servlet.mvc 包中,Controller 控制层实现包括了注解、程序方法处理等封装。自然,看源码先从 org.springframework.web.servlet 包看其核心的接口和类。

2.2 重要的类
DispatcherServlet 类:调度 HTTP 请求控制器(或者处理器 Handler)。  
View 视图层 ModelAndView 类:模型和视图的持有者。
View 接口:MVC WEB 交互。该接口的实现负责呈现视图或者暴露模型。  
Controller 控制层 HandlerMapping 接口: 请求从 DispacherServlet 过来,该接口定义请求和处理程序对象之间的映射。
HandlerInterceptor 接口:处理程序的执行链接口。

Spring MVC 框架模型






2.3 Spring Boot MVC
以前 Spring MVC 开发模式是这样的:
1. 在 web.xml 配置 DispatcherServlet,用于截获并处理所有请求
2. 在 Spring MVC 配置文件中,声明预定义的控制器和视图解析器等
3. 编写预定义的处理请求控制器
4. 编写预定义的视图对象,比如 JSP、Freemarker 等

在 Spring Boot MVC 中,Web 自动化配置会帮你减少上面的两个步骤。默认使用的视图是 ThymeLeaf,在下面小节会具体讲
1. 编写预定义的处理请求控制器
2. 编写默认 ThymeLeaf 视图对象

例如下面会展示用户列表案例:
第一步:处理用户请求控制器
UserController.java/**
* 用户控制层
*
* Created by bysocket on 24/07/2017.
*/
@Controller
@RequestMapping(value = "/users") // 通过这里配置使下面的映射都在 /users
public class UserController {
@Autowired
UserService userService; // 用户服务层
/**
* 获取用户列表
* 处理 "/users" 的GET请求,用来获取用户列表
* 通过 @RequestParam 传递参数,进一步实现条件查询或者分页查询
*/
@RequestMapping(method = RequestMethod.GET)
public String getUserList(ModelMap map) {
map.addAttribute("userList", userService.findAll());
return "userList";
}
}

 
@Controller 注解在 UserController 类上,标识其为一个可接收 HTTP 请求的控制器
@RequestMapping(value = "/users") 注解 ,标识 UserController 类下所有接收的请求路由都是 /users 开头的。注意:类上的 @RequestMapping 注解是不必需的

@RequestMapping(method = RequestMethod.GET) 注解,标识该 getUserList(ModelMap map) 方法会接收并处理 /users 请求,且请求方法是 GET
getUserList(ModelMap map) 方法返回的字符串 userList ,代表着是视图,会有视图解析器解析成为一个具体的视图对象,然后经过视图渲染展示到浏览器

第二步:用户列表 ThymeLeaf 视图对象<!DOCTYPE html>
<html lang="zh-CN">
<head>
<script type="text/javascript" th:src="@{https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js}"></script>
<link th:href="@{https://cdn.bootcss.com/bootst ... n.css}" rel="stylesheet"/>
<link th:href="@{/css/default.css}" rel="stylesheet"/>
<link rel="icon" th:href="@{/images/favicon.ico}" type="image/x-icon"/>
<meta charset="UTF-8"/>
<title>用户列表</title>
</head>
<body>
<div class="contentDiv">
<h5> 《 Spring Boot 2.x 核心技术实战》第二章快速入门案例</h5>
<table class="table table-hover table-condensed">
<legend>
<strong>用户列表</strong>
</legend>
<thead>
<tr>
<th>用户编号</th>
<th>名称</th>
<th>年龄</th>
<th>出生时间</th>
<th>管理</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${userList}">
<th scope="row" th:text="${user.id}"></th>
<td><a th:href="@{/users/update/{userId}(userId=${user.id})}" th:text="${user.name}"></a></td>
<td th:text="${user.age}"></td>
<td th:text="${user.birthday}"></td>
<td><a class="btn btn-danger" th:href="@{/users/delete/{userId}(userId=${user.id})}">删除</a></td>
</tr>
</tbody>
</table>
<div><a class="btn btn-primary" href="/users/create" role="button">创建用户</a></div>
</div>
</body>
</html>
一个 table 展示用户列表,引入了 jquery.min.js 和 bootstrap.min.css ,更好的展示页面效果。具体 ThymeLeaf 语法下面会讲到。

代码共享在:https://github.com/JeffLi1993/spring-boot-core-book-demo  

2.3.1 控制器
什么是控制器?控制器就是控制请求接收和负责响应到视图的角色。
@Controller 注解标识一个类作为控制器。DispatcherServlet 会扫描所有控制器类,并检测 @RequestMapping 注解配置的方法。Web 自动化配置已经处理完这一步骤。

@RequestMapping 注解标识请求 URL 信息,可以映射到整个类或某个特定的方法上。该注解可以表明请求需要的。
使用 value 指定特定的 URL ,比如 @RequestMapping(value = "/users”) 和 @RequestMapping(value = "/users/create”) 等
使用 method 指定 HTTP 请求方法,比如 RequestMethod.GET 等
还有使用其他特定的参数条件,可以设置 consumes 指定请求时的请求头需要包含的 Content-Type 值、设置 produces 可确保响应的内容类型

MVC on REST ful 场景
在 HTTP over JSON (自然 JSON、XML或其他自定义的媒体类型内容等均可)场景,配合上前后端分离的开发模式,我们经常会用 @ResponseBody 或 @RestController 两种方式实现 RESTful HTTP API 。

老方式:
@ResponseBody 注解标识该方法的返回值。这样被标注的方法返回值,会直接写入 HTTP 响应体(而不会被视图解析器认为是一个视图对象)。

新方式:
@RestController 注解,和 @Controller 用法一致,整合了 @Controller 和 @ResponseBody 功能。这样不需要每个 @RequestMapping 方法上都加上 @ResponseBody 注解,这样代码更简明。

使代码更简明,还有常用便捷注解 @GetMapping、@PostMapping 和 @PutMapping 等
HTTP 协议相关知识回顾,可以看看我以前的博文《图解 HTTP 协议》http://www.bysocket.com/?p=282 

2.3.2 数据绑定
数据绑定,简单的说就是 Spring MVC 从请求中获取请求入参,赋予给处理方法相应的入参。主要流程如下:
1. DataBinder 接受带有请求入参的 ServletRequest 对象
2. 调用 ConversionService 组件,进行数据类型转换、数据格式化等工作
3. 然后调用 Validator 组件,进行数据校验等工作
4. 绑定结果到 BindingResult 对象
5. 最后赋予给处理方法相应的入参

@ModelAttribute 注解添加一个或多个属性(类对象)到 model 上。例如@RequestMapping(value = "/create", method = RequestMethod.POST)
public String postUser(@ModelAttribute User user)
@PathVariable 注解通过变量名匹配到 URI 模板中相对应的变量。例如@RequestMapping(value = "/update/{id}", method = RequestMethod.GET)
public String getUser(@PathVariable("id") Long id, ModelMap map)   
@RequestParam 注解将请求参数绑定到方法参数。
@RequestHeader 注解将请求头属性绑定到方法参数。

2.3.3 视图和视图解析
视图的职责就是渲染模型数据,将模型里面的数据展示给用户。

请求到经过处理方法处理后,最终返回的是 ModeAndView 。可以从 Spring MVC 框架模型 看出,最终经过 ViewResolver 视频解析器得到视图对象 View。可能是我们常见的 JSP ,也可能是基于 ThymLeaf 、FreeMarker 或 Velocity 模板引擎视图,当然还有可能是 JSON 、XML 或者 PDF 等各种形式。
业界流行的模板引擎有如下的 Starters 支持:
spring-boot-starter-thymeleaf Thymeleaf 模板视图依赖,官方推荐
spring-boot-starter-freemarker Freemarker 模板视图依赖
spring-boot-starter-groovy-templates Groovy 模板视图依赖
spring-boot-starter-mustache Mustache 模板视图依赖

具体,spring-boot-starter-thymeleaf 使用案例在 GitHub :https://github.com/JeffLi1993/spring-boot-core-book-demo   的 chapter-2-spring-boot-quick-start 工程。

三、小结
本文主要介绍了 Spring Boot 在 Web 开发中涉及到的 HTTP 协议,还有一些 Spring MVC 相关的知识。

推荐:
开源项目 springboot-learning-example https://github.com/JeffLi1993/ ... ample 
开源项目 spring-boot-core-book-demo https://github.com/JeffLi1993/ ... -demo 

资料:
- 官方文档 http://docs.spring.io/spring-b ... ingle 
-《精通Spring 4.x 企业应用开发实战》

欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦!
— http://www.bysocket.com/ —
— https://github.com/JeffLi1993 — 





  查看全部
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!
“人才是公司运作的关键所在”
本文提纲
1. spring-boot-starter-web 依赖概述
1.1 spring-boot-starter-web 职责
1.2 spring-boot-starter-web 依赖关系
2. Spring MVC on Spring Boot
2.1 Spring MVC 体系温故知新
2.2 重要的类
2.3 Spring Boot MVC
2.3.1 控制器
2.3.2 数据绑定
2.3.3 视图和视图解析 

一、spring-boot-starter-web 依赖概述
在 Spring Boot 快速入门中,只要在 pom.xml 加入了 spring-boot-starter-web 依赖,即可快速开发 web 应用。可见,Spring Boot 极大地简化了 Spring 应用从搭建到开发的过程,做到了「开箱即用」的方式。Spring Boot 已经提供很多「开箱即用」的依赖,如上面开发 web 应用使用的 spring-boot-starter-web ,都是以 spring-boot-starter-xx 进行命名的。
 
Spring Boot 「开箱即用」 的设计,对开发者非常便利。简单来说,只要往 Spring Boot 项目加入相应的 spring-boot-starter-xx 依赖,就可以使用对应依赖的功能,比如加入 spring-boot-starter-data-jpa 依赖,就可以使用数据持久层框架 Spring Data JPA 操作数据源。相比 Spring 以前需要大量的XML配置以及复杂的依赖管理,极大的减少了开发工作量和学习成本。

当开发一个特定类型的应用程序时,特定的 Starter 提供所需的依赖关系,并且将对应的 Bean 注册到 Spring 容器中。spring-boot-starter-web 依赖就是提供开发 Web 应用的。

1.1 spring-boot-starter-web 职责
spring-boot-starter-web 是一个用于构建 Web 的 Starter ,包括构建 RESTful 服务应用、Spring MVC 应用等。并且不需要额外配置容器,默认使用 Tomcat 作为嵌入式容器。

1.2 spring-boot-starter-web 依赖关系
spring-boot-starter-web 这么强大,它的组成如下表:
spring-boot-starter  核心包,包括了自动化配置支持、日志、YAML 文件解析的支持等。
spring-boot-starter-json 读写 JSON 包
spring-boot-starter-tomcat Tomcat 嵌入式 Servlet 容器包
hibernate-validator Hibernate 框架提供的验证包
spring-web Spring 框架的 Web 包
spring-webmvc Spring 框架的 Web MVC 包

WX20170727-181441.png



spring-boot-starter-web 包含了 Tomcat 和 Spring MVC ,那启动流程是这样的。 标识 @SpringBootApplication 的应用,初始化经过 spring-boot-starter  核心包中的自动化配置,构建了 Spring 容器,并通过 Tomcat 启动 Web 应用。很多 Starters 只支持 Spring MVC,一般会将 spring-boot-starter-web 依赖加入到应用的 Classpath。

另外,spring-boot-starter-web 默认使用 Tomcat 作为嵌入式 Servlet 容器,在 pom.xml 配置 spring-boot-starter-jetty 和 spring-boot-starter-undertow 就可以替换默认容器。


二、Spring MVC on Spring Boot
Spring MVC 是 Spring Web 重要的模块。内容包括 MVC 模式的实现和 RESTful 服务的支持。

2.1 Spring MVC 体系温故知新

spring-webmvc 模块里面包:
- org.springframework.web.servlet 提供与应用程序上下文基础结构集成的 Servlet,以及 Spring web MVC 框架的核心接口和类。
- org.springframework.web.servlet.mvc Spring 附带的 Servlet MVC 框架的标准控制器实现。
- org.springframework.web.servlet.mvc.annotation 用于基于注解的 Servlet MVC 控制器的支持包。
- org.springframework.web.servlet.mvc.condition 用于根据条件匹配传入请求的公共 MVC 逻辑。
- org.springframework.web.servlet.mvc.method 用于处理程序方法处理的基于 Servlet 的基础结构,基于在 org.springframework.web.method 包上。
- org.springframework.web.servlet.view 提供标准的 View 和 ViewResolver 实现,包括自定义实现的抽象基类。
- org.springframework.web.servlet.view.freemarker 支持将 FreeMarker 集成为 Spring Web 视图技术的类。
- org.springframework.web.servlet.view.json 支持提供基于 JSON 序列化的 View 实现的类。

上面列出来核心的包。org.springframework.web.servlet.view 包中, View 视图实现有常见的:JSON 、FreeMarker 等。org.springframework.web.servlet.mvc 包中,Controller 控制层实现包括了注解、程序方法处理等封装。自然,看源码先从 org.springframework.web.servlet 包看其核心的接口和类。

2.2 重要的类
DispatcherServlet 类:调度 HTTP 请求控制器(或者处理器 Handler)。  
View 视图层 ModelAndView 类:模型和视图的持有者。
View 接口:MVC WEB 交互。该接口的实现负责呈现视图或者暴露模型。  
Controller 控制层 HandlerMapping 接口: 请求从 DispacherServlet 过来,该接口定义请求和处理程序对象之间的映射。
HandlerInterceptor 接口:处理程序的执行链接口。

Spring MVC 框架模型
WechatIMG485.jpeg



2.3 Spring Boot MVC
以前 Spring MVC 开发模式是这样的:
1. 在 web.xml 配置 DispatcherServlet,用于截获并处理所有请求
2. 在 Spring MVC 配置文件中,声明预定义的控制器和视图解析器等
3. 编写预定义的处理请求控制器
4. 编写预定义的视图对象,比如 JSP、Freemarker 等

在 Spring Boot MVC 中,Web 自动化配置会帮你减少上面的两个步骤。默认使用的视图是 ThymeLeaf,在下面小节会具体讲
1. 编写预定义的处理请求控制器
2. 编写默认 ThymeLeaf 视图对象

例如下面会展示用户列表案例:
第一步:处理用户请求控制器
UserController.java
/**
* 用户控制层
*
* Created by bysocket on 24/07/2017.
*/
@Controller
@RequestMapping(value = "/users") // 通过这里配置使下面的映射都在 /users
public class UserController {
@Autowired
UserService userService; // 用户服务层
/**
* 获取用户列表
* 处理 "/users" 的GET请求,用来获取用户列表
* 通过 @RequestParam 传递参数,进一步实现条件查询或者分页查询
*/
@RequestMapping(method = RequestMethod.GET)
public String getUserList(ModelMap map) {
map.addAttribute("userList", userService.findAll());
return "userList";
}
}

 
@Controller 注解在 UserController 类上,标识其为一个可接收 HTTP 请求的控制器
@RequestMapping(value = "/users") 注解 ,标识 UserController 类下所有接收的请求路由都是 /users 开头的。注意:类上的 @RequestMapping 注解是不必需的

@RequestMapping(method = RequestMethod.GET) 注解,标识该 getUserList(ModelMap map) 方法会接收并处理 /users 请求,且请求方法是 GET
getUserList(ModelMap map) 方法返回的字符串 userList ,代表着是视图,会有视图解析器解析成为一个具体的视图对象,然后经过视图渲染展示到浏览器

第二步:用户列表 ThymeLeaf 视图对象
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<script type="text/javascript" th:src="@{https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js}"></script>
<link th:href="@{https://cdn.bootcss.com/bootst ... n.css}" rel="stylesheet"/>
<link th:href="@{/css/default.css}" rel="stylesheet"/>
<link rel="icon" th:href="@{/images/favicon.ico}" type="image/x-icon"/>
<meta charset="UTF-8"/>
<title>用户列表</title>
</head>
<body>
<div class="contentDiv">
<h5> 《 Spring Boot 2.x 核心技术实战》第二章快速入门案例</h5>
<table class="table table-hover table-condensed">
<legend>
<strong>用户列表</strong>
</legend>
<thead>
<tr>
<th>用户编号</th>
<th>名称</th>
<th>年龄</th>
<th>出生时间</th>
<th>管理</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${userList}">
<th scope="row" th:text="${user.id}"></th>
<td><a th:href="@{/users/update/{userId}(userId=${user.id})}" th:text="${user.name}"></a></td>
<td th:text="${user.age}"></td>
<td th:text="${user.birthday}"></td>
<td><a class="btn btn-danger" th:href="@{/users/delete/{userId}(userId=${user.id})}">删除</a></td>
</tr>
</tbody>
</table>
<div><a class="btn btn-primary" href="/users/create" role="button">创建用户</a></div>
</div>
</body>
</html>

一个 table 展示用户列表,引入了 jquery.min.js 和 bootstrap.min.css ,更好的展示页面效果。具体 ThymeLeaf 语法下面会讲到。

代码共享在:https://github.com/JeffLi1993/spring-boot-core-book-demo  

2.3.1 控制器
什么是控制器?控制器就是控制请求接收和负责响应到视图的角色。
@Controller 注解标识一个类作为控制器。DispatcherServlet 会扫描所有控制器类,并检测 @RequestMapping 注解配置的方法。Web 自动化配置已经处理完这一步骤。

@RequestMapping 注解标识请求 URL 信息,可以映射到整个类或某个特定的方法上。该注解可以表明请求需要的。
使用 value 指定特定的 URL ,比如 @RequestMapping(value = "/users”) 和 @RequestMapping(value = "/users/create”) 等
使用 method 指定 HTTP 请求方法,比如 RequestMethod.GET 等
还有使用其他特定的参数条件,可以设置 consumes 指定请求时的请求头需要包含的 Content-Type 值、设置 produces 可确保响应的内容类型

MVC on REST ful 场景
在 HTTP over JSON (自然 JSON、XML或其他自定义的媒体类型内容等均可)场景,配合上前后端分离的开发模式,我们经常会用 @ResponseBody 或 @RestController 两种方式实现 RESTful HTTP API 。

老方式:
@ResponseBody 注解标识该方法的返回值。这样被标注的方法返回值,会直接写入 HTTP 响应体(而不会被视图解析器认为是一个视图对象)。

新方式:
@RestController 注解,和 @Controller 用法一致,整合了 @Controller 和 @ResponseBody 功能。这样不需要每个 @RequestMapping 方法上都加上 @ResponseBody 注解,这样代码更简明。

使代码更简明,还有常用便捷注解 @GetMapping、@PostMapping 和 @PutMapping 等
HTTP 协议相关知识回顾,可以看看我以前的博文《图解 HTTP 协议》http://www.bysocket.com/?p=282 

2.3.2 数据绑定
数据绑定,简单的说就是 Spring MVC 从请求中获取请求入参,赋予给处理方法相应的入参。主要流程如下:
1. DataBinder 接受带有请求入参的 ServletRequest 对象
2. 调用 ConversionService 组件,进行数据类型转换、数据格式化等工作
3. 然后调用 Validator 组件,进行数据校验等工作
4. 绑定结果到 BindingResult 对象
5. 最后赋予给处理方法相应的入参

@ModelAttribute 注解添加一个或多个属性(类对象)到 model 上。例如
@RequestMapping(value = "/create", method = RequestMethod.POST) 
public String postUser(@ModelAttribute User user)

@PathVariable 注解通过变量名匹配到 URI 模板中相对应的变量。例如
@RequestMapping(value = "/update/{id}", method = RequestMethod.GET) 
public String getUser(@PathVariable("id") Long id, ModelMap map)
   
@RequestParam 注解将请求参数绑定到方法参数。
@RequestHeader 注解将请求头属性绑定到方法参数。

2.3.3 视图和视图解析
视图的职责就是渲染模型数据,将模型里面的数据展示给用户。

请求到经过处理方法处理后,最终返回的是 ModeAndView 。可以从 Spring MVC 框架模型 看出,最终经过 ViewResolver 视频解析器得到视图对象 View。可能是我们常见的 JSP ,也可能是基于 ThymLeaf 、FreeMarker 或 Velocity 模板引擎视图,当然还有可能是 JSON 、XML 或者 PDF 等各种形式。
业界流行的模板引擎有如下的 Starters 支持:
spring-boot-starter-thymeleaf Thymeleaf 模板视图依赖,官方推荐
spring-boot-starter-freemarker Freemarker 模板视图依赖
spring-boot-starter-groovy-templates Groovy 模板视图依赖
spring-boot-starter-mustache Mustache 模板视图依赖

具体,spring-boot-starter-thymeleaf 使用案例在 GitHub :https://github.com/JeffLi1993/spring-boot-core-book-demo   的 chapter-2-spring-boot-quick-start 工程。

三、小结
本文主要介绍了 Spring Boot 在 Web 开发中涉及到的 HTTP 协议,还有一些 Spring MVC 相关的知识。

推荐:
开源项目 springboot-learning-example https://github.com/JeffLi1993/ ... ample 
开源项目 spring-boot-core-book-demo https://github.com/JeffLi1993/ ... -demo 

资料:
- 官方文档 http://docs.spring.io/spring-b ... ingle 
-《精通Spring 4.x 企业应用开发实战》

欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦!
http://www.bysocket.com/
https://github.com/JeffLi1993 — 

qrcode_for_gh_cd421e7eb7d6_430.jpg

 

Spring思维导图,让spring不再难懂(一)

Spring Framework泥瓦匠BYSocket 发表了文章 • 1 个评论 • 843 次浏览 • 2017-07-21 12:03 • 来自相关话题

摘要: Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架写在前面

很多人在微信公众号中给我留言说想看spring的思维导图,正好也打算写。与其他框架相比,spring项目拥有更多的模块,我们常用的ioc,mvc,aop等,这些是spring的主要板块。一篇文章也不可能全部都讲,所以,我打算先把spring简介说一下,后续再写ioc,mvc和aop。

关于Spring

Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。

它是一个全面的、企业应用开发一站式的解决方案,贯穿表现层、业务层、持久层。但是Spring仍然可以和其他的框架无缝整合。






Sping架构

Spring框架是分模块存在,除了最核心的Spring Core Container(即Spring容器)是必要模块之外,其他模块都是可选,视需要而定。大约有20多个模块。

Spring3与Spring4是有区别的,4.0主要是对Java 8的新函数式语法进行支持,还有加强了对网络各种新技术比如http-streaming, websocket的更好的支持。










一般来说,Spring主要分为7个模块:






Spring的主要jar包











常用注解

bean注入与装配的的方式有很多种,可以通过xml,getset方式,构造函数或者注解等。简单易用的方式就是使用Spring的注解了,Spring提供了大量的注解方式,让项目阅读和开发起来更加方便。











第三方框架集成

Spring框架的开发不是为了替代现有的优秀第三方框架,而是通过集成的方式把它们都连接起来。下面总结了一些常集成的优秀框架。






最后

这一节简单介绍了Spring,没涉及到原理的东西。Spring如此博大精深,希望大家好好学习哈。 
 
原文地址:https://my.oschina.net/u/3080373 查看全部

摘要: Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架写在前面

很多人在微信公众号中给我留言说想看spring的思维导图,正好也打算写。与其他框架相比,spring项目拥有更多的模块,我们常用的ioc,mvc,aop等,这些是spring的主要板块。一篇文章也不可能全部都讲,所以,我打算先把spring简介说一下,后续再写ioc,mvc和aop。

关于Spring

Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。

它是一个全面的、企业应用开发一站式的解决方案,贯穿表现层、业务层、持久层。但是Spring仍然可以和其他的框架无缝整合。
1.png



Sping架构

Spring框架是分模块存在,除了最核心的Spring Core Container(即Spring容器)是必要模块之外,其他模块都是可选,视需要而定。大约有20多个模块。

Spring3与Spring4是有区别的,4.0主要是对Java 8的新函数式语法进行支持,还有加强了对网络各种新技术比如http-streaming, websocket的更好的支持。

2.png


3.png

一般来说,Spring主要分为7个模块:

4.png


Spring的主要jar包

5.png


6.png


常用注解

bean注入与装配的的方式有很多种,可以通过xml,getset方式,构造函数或者注解等。简单易用的方式就是使用Spring的注解了,Spring提供了大量的注解方式,让项目阅读和开发起来更加方便。

7.png


8.png


第三方框架集成

Spring框架的开发不是为了替代现有的优秀第三方框架,而是通过集成的方式把它们都连接起来。下面总结了一些常集成的优秀框架。

9.png


最后

这一节简单介绍了Spring,没涉及到原理的东西。Spring如此博大精深,希望大家好好学习哈。 
 
原文地址:https://my.oschina.net/u/3080373

单元项目分享

Spring Bootqq514840279 发表了文章 • 0 个评论 • 214 次浏览 • 2017-07-21 10:29 • 来自相关话题

 
本项目github:
https://github.com/514840279/danyuan-application系统采用技术或开源框架:
前端框架:adminLTE
前端技术:bootstrap,bootstrap-table,js,juqery,ajax,ztree,
后端技术:spring-boot,security
数据库: mysql
其他工具:maven,项目启动配置:
系统安装jdk1.8,mysql5.7,maven3.3
新建数据库:application
导入数据库脚本 sql/_mysql_menu_data.sql
修改项目配置:src/main/resources/application.properties
修改数据库链接地址数据库用户名密码
修改 server.port=9999 # 项目端口号
修改security.user.name=admin # 项目验证登录用户 security.user.password=admin # 项目验证登录密码
执行mvn clean
执行mvn install
执行mvn spring-boot:run(java -jar target/danyuan.jar)
访问http://localhost:9999 # 端口号
输入用户名、密码 admin/admin
查看全部
 
本项目github:
https://github.com/514840279/danyuan-application系统采用技术或开源框架:
前端框架:adminLTE
前端技术:bootstrap,bootstrap-table,js,juqery,ajax,ztree,
后端技术:spring-boot,security
数据库: mysql
其他工具:maven,项目启动配置:
系统安装jdk1.8,mysql5.7,maven3.3
新建数据库:application
导入数据库脚本 sql/_mysql_menu_data.sql
修改项目配置:src/main/resources/application.properties
修改数据库链接地址数据库用户名密码
修改 server.port=9999 # 项目端口号
修改security.user.name=admin # 项目验证登录用户 security.user.password=admin # 项目验证登录密码
执行mvn clean
执行mvn install
执行mvn spring-boot:run(java -jar target/danyuan.jar)
访问http://localhost:9999 # 端口号
输入用户名、密码 admin/admin

Spring Cloud Eureka 入门 (二)服务提供者详解

Spring Cloud泥瓦匠BYSocket 发表了文章 • 0 个评论 • 1990 次浏览 • 2017-07-10 16:52 • 来自相关话题

 
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!

“优秀不是过去是一种心态”

 
「Spring Cloud Eureka 入门系列」
Spring Cloud Eureka 入门 (一)服务注册中心详解Spring Cloud Eureka 入门 (二)服务提供者详解Spring Cloud Eureka 入门 (三)服务消费者详解

本文提纲
1. springcloud-eureka-sample 工程结构
2. 运行 springcloud-eureka-client-provider 服务提供者工程
3. 详解 springcloud-eureka-client-provider 服务提供者工程
 
一、springcloud-eureka-sample 工程结构
接着上一小节《Spring Cloud Eureka 入门 (一)服务注册中心详解》,我们成功运行了 Spring Cloud Eureka Server 工程作为服务注册中心工程。这小节,我们写一个作为客户端的服务提供者工程,服务提供者向服务中心注册或者下线服务实例。即图中的右侧 1 ,2 流程:






springcloud-eureka-sample 工程结构├── springcloud-eureka-client-customer
├── springcloud-eureka-client-provider
└── springcloud-eureka-server
上面依次是 服务消费者工程、服务提供者工程和服务注册中心工程。
 
二、运行 springcloud-eureka-client-provider 服务提供者工程
运行环境:JDK 7 或 8,Maven 3.0+
技术栈:Spring Cloud Dalston.SR1、 spring-cloud-netflix 1.3.1、Spring Boot 1.5.4

自然,我们先得去上一小节《Spring Cloud Eureka 入门 (一)服务注册中心详解》 ,把注册中心工程启动完毕。

1. git clone 下载工程 springcloud-learning-example
项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample:git clone https://github.com/JeffLi1993/ ... e.git
2. Maven 编译安装这个工程:cd springcloud-learning-example
mvn clean install
3. 运行 Eureka 工程 springcloud-eureka-client-provider
启动 springcloud-eureka-client-provider 工程启动类 ProviderApplication,启动服务注册中心工程。
EurekaServerApplication 类路径:/springcloud-learning-example/springcloud-eureka-sample/springcloud-eureka-client-provider/src/main/java/org/spring/springcloud/ProviderApplication.java

控制台 Console 看到这类信息,代表启动成功:2017-07-10 16:03:15.075 INFO 11020 --- [ main] o.s.c.n.e.s.EurekaServiceRegistry : Registering application provider-service with eureka with status UP
2017-07-10 16:03:15.075 INFO 11020 --- [ main] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1499673795075, current=UP, previous=STARTING]
2017-07-10 16:03:15.079 INFO 11020 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_PROVIDER-SERVICE/10.18.29.31:provider-service:8080: registering service...
2017-07-10 16:03:15.126 INFO 11020 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_PROVIDER-SERVICE/10.18.29.31:provider-service:8080 - registration status: 204
2017-07-10 16:03:15.183 INFO 11020 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-07-10 16:03:15.185 INFO 11020 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8080
2017-07-10 16:03:15.191 INFO 11020 --- [ main] o.s.springcloud.ProviderApplication : Started ProviderApplication in 9.809 seconds (JVM running for 10.981)
可以看出,注册了应用名为 provider-service 的应用,该服务提供者的工程端口为 8080

4. 访问 Eureka 注册中心可视化界面
打开浏览器,访问 http://localhost:8888/ ,如图所示:





可以看到,服务提供者向服务注册中心注册自己的实例,展示了应用名和端口信息等。

三、详解 springcloud-eureka-client-provider 服务提供者工程
1.springcloud-eureka-server  工程目录结构├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ ├── springcloud
│ │ └── ProviderApplication.java
│ └── web
│ └── ProviderController.java
└── resources
└── application.yml
ProviderApplication.java Eureka Client 启动类,启动服务提供者工程
ProviderApplication.java Provider HelloWorld 案例
application.yml 配置文件

2. 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/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springcloud</groupId>
<artifactId>springcloud-eureka-client-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springcloud-eureka-client-provider :: 服务提供者</name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Cloud Netflix Eureka Client 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud Netflix 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.3.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
使用的依赖是
- spring-cloud-netflix 1.3.1 是 Spring Cloud Dalston.SR1 版本。
- spring-cloud-starter-eureka Eureka Client 模块依赖,包含了客户端 client 的依赖,还有 Ribbon 的依赖,如:org.springframework.cloud:spring-cloud-netflix-eureka-client:1.3.1.RELEASE
org.springframework.cloud:spring-cloud-starter-ribbon:1.3.1.RELEASE
 

3. application.yml 配置server:
port: 8080 # 服务端口
eureka:
client:
service-url:
defaultZone: http://localhost:8888/eureka/ # 服务注册中心地址
spring:
application:
name: provider-service # 服务名称- server.port 设置工程服务端口
- eureka.client.service-url.defaultZone 设置服务注册中心地址
 
4.注册中心应用启动类/**
* Spring Boot Eureka Server 应用启动类
*
* Created by bysocket on 21/06/17.
*/
@EnableEurekaClient // Eureka Client 标识
@SpringBootApplication // Spring Boot 应用标识
public class ProviderApplication {
public static void main(String args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
SpringApplication.run(ProviderApplication.class,args);
}
}@EnableEurekaClient 标志该应用作为 Eureka Client ,并会自动化读取 Eureka 相关配置。

6.服务提供者 Hello World 案例/**
* Provider HelloWorld 案例
* <p>
* Created by bysocket on 06/22/17.
*/
@RestController
public class ProviderController {
private static final Logger LOGGER = LoggerFactory.getLogger(ProviderController.class);
@Autowired
private Registration registration; // 服务注册
@Autowired
private DiscoveryClient discoveryClient; // 服务发现客户端
@RequestMapping("/provider")
public String provider() {
ServiceInstance instance = serviceInstance();
LOGGER.info("provider service, host = " + instance.getHost()
+ ", service_id = " + instance.getServiceId());
return "Hello,Provider!";
}
/**
* 获取当前服务的服务实例
*
* @return ServiceInstance
*/
public ServiceInstance serviceInstance() {
List<ServiceInstance> list = discoveryClient.getInstances(registration.getServiceId());
if (list != null && list.size() > 0) {
return list.get(0);
}
return null;
}
}可以看到注入了 Registration 和 DiscoveryClient 两个对象:
- Registration 服务注册接口,包含了获取服务 ID 的方法。
- DiscoveryClient 服务发现客户端,具有以下方法:
     - String description(); 获取描述
     - ServiceInstance getLocalServiceInstance(); @Deprecated 方法被删除,推荐不要使用。获取本地服务实例
     - List<ServiceInstance> getInstances(String serviceId);  通过服务 ID,获取当前服务的服务实例
     - List<String> getServices(); 获取所有服务 ID 列表

四、小结
此小章节介绍了如何 Eureka 作为服务提供者,并向服务注册中心注册自己实例。 下一小结讲下 服务消费者详解 具体是如何向服务注册中心注册自己,发现其他服务,并调用其他服务的。系列目录如下:
Spring Cloud Eureka 入门 (一)服务注册中心详解Spring Cloud Eureka 入门 (二)服务提供者详解Spring Cloud Eureka 入门 (三)服务消费者详解

资料:
1.《Spring Cloud微服务实战》






2. 官方文档
http://cloud.spring.io/spring- ... EASE/

by 泥瓦匠博客
— http://www.bysocket.com/
— https://github.com/JeffLi1993 查看全部
 
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!


“优秀不是过去是一种心态”


 
「Spring Cloud Eureka 入门系列」


本文提纲
1. springcloud-eureka-sample 工程结构
2. 运行 springcloud-eureka-client-provider 服务提供者工程
3. 详解 springcloud-eureka-client-provider 服务提供者工程
 
一、springcloud-eureka-sample 工程结构
接着上一小节《Spring Cloud Eureka 入门 (一)服务注册中心详解》,我们成功运行了 Spring Cloud Eureka Server 工程作为服务注册中心工程。这小节,我们写一个作为客户端的服务提供者工程,服务提供者向服务中心注册或者下线服务实例。即图中的右侧 1 ,2 流程:

Eureka集群.jpeg


springcloud-eureka-sample 工程结构
├── springcloud-eureka-client-customer
├── springcloud-eureka-client-provider
└── springcloud-eureka-server

上面依次是 服务消费者工程、服务提供者工程和服务注册中心工程。
 
二、运行 springcloud-eureka-client-provider 服务提供者工程
运行环境:JDK 7 或 8,Maven 3.0+
技术栈:Spring Cloud Dalston.SR1、 spring-cloud-netflix 1.3.1、Spring Boot 1.5.4

自然,我们先得去上一小节《Spring Cloud Eureka 入门 (一)服务注册中心详解》 ,把注册中心工程启动完毕。

1. git clone 下载工程 springcloud-learning-example
项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample
git clone https://github.com/JeffLi1993/ ... e.git

2. Maven 编译安装这个工程:
cd springcloud-learning-example
mvn clean install

3. 运行 Eureka 工程 springcloud-eureka-client-provider
启动 springcloud-eureka-client-provider 工程启动类 ProviderApplication,启动服务注册中心工程。
EurekaServerApplication 类路径:/springcloud-learning-example/springcloud-eureka-sample/springcloud-eureka-client-provider/src/main/java/org/spring/springcloud/ProviderApplication.java

控制台 Console 看到这类信息,代表启动成功:
2017-07-10 16:03:15.075  INFO 11020 --- [           main] o.s.c.n.e.s.EurekaServiceRegistry        : Registering application provider-service with eureka with status UP
2017-07-10 16:03:15.075 INFO 11020 --- [ main] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1499673795075, current=UP, previous=STARTING]
2017-07-10 16:03:15.079 INFO 11020 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_PROVIDER-SERVICE/10.18.29.31:provider-service:8080: registering service...
2017-07-10 16:03:15.126 INFO 11020 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_PROVIDER-SERVICE/10.18.29.31:provider-service:8080 - registration status: 204
2017-07-10 16:03:15.183 INFO 11020 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-07-10 16:03:15.185 INFO 11020 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8080
2017-07-10 16:03:15.191 INFO 11020 --- [ main] o.s.springcloud.ProviderApplication : Started ProviderApplication in 9.809 seconds (JVM running for 10.981)

可以看出,注册了应用名为 provider-service 的应用,该服务提供者的工程端口为 8080

4. 访问 Eureka 注册中心可视化界面
打开浏览器,访问 http://localhost:8888/ ,如图所示:

WechatIMG442.jpeg

可以看到,服务提供者向服务注册中心注册自己的实例,展示了应用名和端口信息等。

三、详解 springcloud-eureka-client-provider 服务提供者工程
1.springcloud-eureka-server  工程目录结构
├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ ├── springcloud
│ │ └── ProviderApplication.java
│ └── web
│ └── ProviderController.java
└── resources
└── application.yml

ProviderApplication.java Eureka Client 启动类,启动服务提供者工程
ProviderApplication.java Provider HelloWorld 案例
application.yml 配置文件

2. 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/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springcloud</groupId>
<artifactId>springcloud-eureka-client-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springcloud-eureka-client-provider :: 服务提供者</name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Cloud Netflix Eureka Client 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud Netflix 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.3.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

使用的依赖是
- spring-cloud-netflix 1.3.1 是 Spring Cloud Dalston.SR1 版本。
- spring-cloud-starter-eureka Eureka Client 模块依赖,包含了客户端 client 的依赖,还有 Ribbon 的依赖,如:org.springframework.cloud:spring-cloud-netflix-eureka-client:1.3.1.RELEASE
org.springframework.cloud:spring-cloud-starter-ribbon:1.3.1.RELEASE
 

3. application.yml 配置
server:
port: 8080 # 服务端口
eureka:
client:
service-url:
defaultZone: http://localhost:8888/eureka/ # 服务注册中心地址
spring:
application:
name: provider-service # 服务名称
- server.port 设置工程服务端口
- eureka.client.service-url.defaultZone 设置服务注册中心地址
 
4.注册中心应用启动类
/**
* Spring Boot Eureka Server 应用启动类
*
* Created by bysocket on 21/06/17.
*/
@EnableEurekaClient // Eureka Client 标识
@SpringBootApplication // Spring Boot 应用标识
public class ProviderApplication {
public static void main(String args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
SpringApplication.run(ProviderApplication.class,args);
}
}
@EnableEurekaClient 标志该应用作为 Eureka Client ,并会自动化读取 Eureka 相关配置。

6.服务提供者 Hello World 案例
/**
* Provider HelloWorld 案例
* <p>
* Created by bysocket on 06/22/17.
*/
@RestController
public class ProviderController {
private static final Logger LOGGER = LoggerFactory.getLogger(ProviderController.class);
@Autowired
private Registration registration; // 服务注册
@Autowired
private DiscoveryClient discoveryClient; // 服务发现客户端
@RequestMapping("/provider")
public String provider() {
ServiceInstance instance = serviceInstance();
LOGGER.info("provider service, host = " + instance.getHost()
+ ", service_id = " + instance.getServiceId());
return "Hello,Provider!";
}
/**
* 获取当前服务的服务实例
*
* @return ServiceInstance
*/
public ServiceInstance serviceInstance() {
List<ServiceInstance> list = discoveryClient.getInstances(registration.getServiceId());
if (list != null && list.size() > 0) {
return list.get(0);
}
return null;
}
}
可以看到注入了 Registration 和 DiscoveryClient 两个对象:
- Registration 服务注册接口,包含了获取服务 ID 的方法。
- DiscoveryClient 服务发现客户端,具有以下方法:
     - String description(); 获取描述
     - ServiceInstance getLocalServiceInstance(); @Deprecated 方法被删除,推荐不要使用。获取本地服务实例
     - List<ServiceInstance> getInstances(String serviceId);  通过服务 ID,获取当前服务的服务实例
     - List<String> getServices(); 获取所有服务 ID 列表

四、小结
此小章节介绍了如何 Eureka 作为服务提供者,并向服务注册中心注册自己实例。 下一小结讲下 服务消费者详解 具体是如何向服务注册中心注册自己,发现其他服务,并调用其他服务的。系列目录如下:


资料:
1.《Spring Cloud微服务实战》

481AEC9F-3FE5-44C3-A61C-28D518CB9217.png


2. 官方文档
http://cloud.spring.io/spring- ... EASE/


by 泥瓦匠博客
http://www.bysocket.com/
https://github.com/JeffLi1993

qrcode_for_gh_cd421e7eb7d6_430.jpg


一个基于Spring Boot的API、RESTful API项目种子(骨架)

Spring Boot简单的土豆 发表了文章 • 32 个评论 • 2056 次浏览 • 2017-07-04 17:48 • 来自相关话题

最近使用Spring Boot 配合 MyBatis 、通用Mapper插件、PageHelper分页插件 连做了几个中小型API项目,做下来觉得这套框架、工具搭配起来开发这种项目确实非常舒服,团队的反响也不错。在项目搭建和开发的过程中也总结了一些小经验,与大家分享一下。

在开发一个API项目之前,搭建项目、引入依赖、配置框架这些基础活自然不用多说,通常为了加快项目的开发进度(早点回家)还需要封装一些常用的类和工具,比如统一的响应结果封装、统一的异常处理、接口签名认证、基础的增删改差方法封装、基础代码生成工具等等,有了这些项目才能开工。

然而,下次再做类似的项目上述那些步骤可能还要搞一遍,虽然通常是拿过来改改,但是还是比较浪费时间。所以,可以利用面向对象抽象、封装的思想,抽取这类项目的共同之处封装成了一个种子项目(估计大部分公司都会有很多类似的种子项目),这样的话下次再开发类似的项目直接在该种子项目上迭代就可以了,减少无意义的重复工作。

在相关项目上线之后,我花了点时间对该种子项目做了一些精简,并且已经把该项目分享到GitHub上面了,如果你正准备做类似项目的话,可以去克隆下来试试,项目地址&使用文档:https://github.com/lihengming/ ... -seed  。如果在使用中发现问题或者有什么好建议的话欢迎提issue或pr一起来完善它。
特征&提供
- 最佳实践的项目结构、配置文件、精简的POM

                           (新标签打开图片,查看大图)
注:使用代码生成器生成代码后会创建model、dao、service、web等包。

- 统一响应结果封装及生成工具/**
* 统一API响应结果封装
*/
public class Result {
private int code;
private String message;
private Object data;
public Result setCode(ResultCode resultCode) {
this.code = resultCode.code;
return this;
}
//省略getter、setter方法
}/**
* 响应码枚举,参考HTTP状态码的语义
*/
public enum ResultCode {
SUCCESS(200),//成功
FAIL(400),//失败
UNAUTHORIZED(401),//未认证(签名错误)
NOT_FOUND(404),//接口不存在
INTERNAL_SERVER_ERROR(500);//服务器内部错误

public int code;

ResultCode(int code) {
this.code = code;
}
}/**
* 响应结果生成工具
*/
public class ResultGenerator {
private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";

public static Result genSuccessResult() {
return new Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE);
}

public static Result genSuccessResult(Object data) {
return new Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE)
.setData(data);
}

public static Result genFailResult(String message) {
return new Result()
.setCode(ResultCode.FAIL)
.setMessage(message);
}
}

- 统一异常处理public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
    exceptionResolvers.add(new HandlerExceptionResolver() {
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
            Result result = new Result();
            if (e instanceof ServiceException) {//业务失败的异常,如“账号或密码错误”
                result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
                logger.info(e.getMessage());
            } else if (e instanceof NoHandlerFoundException) {
                result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在");
            } else if (e instanceof ServletException) {
                result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
            } else {
                result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
                String message;
                if (handler instanceof HandlerMethod) {
                    HandlerMethod handlerMethod = (HandlerMethod) handler;
                    message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
                            request.getRequestURI(),
                            handlerMethod.getBean().getClass().getName(),
                            handlerMethod.getMethod().getName(),
                            e.getMessage());
                } else {
                    message = e.getMessage();
                }
                logger.error(message, e);
            }
            responseResult(response, result);
            return new ModelAndView();
        }

    });
}


- 常用基础方法抽象封装public interface Service<T> {
void save(T model);//持久化
void save(List<T> models);//批量持久化
void deleteById(Integer id);//通过主鍵刪除
void deleteByIds(String ids);//批量刪除 eg:ids -> “1,2,3,4”
void update(T model);//更新
T findById(Integer id);//通过ID查找
T findBy(String fieldName, Object value) throws TooManyResultsException; //通过Model中某个成员变量名称(非数据表中column的名称)查找,value需符合unique约束
List<T> findByIds(String ids);//通过多个ID查找//eg:ids -> “1,2,3,4”
List<T> findByCondition(Condition condition);//根据条件查找
List<T> findAll();//获取所有
}

- 提供代码生成器来生成基础代码public abstract class CodeGenerator {
...
public static void main(String args) {
genCode("输入表名");
}
public static void genCode(String... tableNames) {
for (String tableName : tableNames) {
//根据需求生成,不需要的注掉,模板有问题的话可以自己修改。
genModelAndMapper(tableName);
genService(tableName);
genController(tableName);
}
}
...
}CodeGenerator 可根据表名生成对应的Model、Mapper、MapperXML、Service、ServiceImpl、Controller(默认提供POST和RESTful两套Controller模板,根据需要在 genController(tableName) 方法中自己选择,默认是纯POST的),代码模板可根据实际项目的需求来定制,以便渐少重复劳动。由于每个公司业务都不太一样,所以只提供了一些简单的通用方法模板,主要是提供一个思路来减少重复代码的编写。在我们公司的实际使用中,其实根据业务的抽象编写了大量的代码模板。


- 提供简单的接口签名认证public void addInterceptors(InterceptorRegistry registry) {
//接口签名认证拦截器,该签名认证比较简单,实际项目中可以使用Json Web Token或其他更好的方式替代。
if (!"dev".equals(env)) { //开发环境忽略签名认证
registry.addInterceptor(new HandlerInterceptorAdapter() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//验证签名
boolean pass = validateSign(request);
if (pass) {
return true;
} else {
logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}",
request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));

Result result = new Result();
result.setCode(ResultCode.UNAUTHORIZED).setMessage("签名认证失败");
responseResult(response, result);
return false;
}
}
});
}
}/**
* 一个简单的签名认证,规则:
* 1. 将请求参数按ascii码排序
* 2. 拼接为a=value&b=value...这样的字符串(不包含sign)
* 3. 混合密钥(secret)进行md5获得签名,与请求的签名进行比较
*/
private boolean validateSign(HttpServletRequest request) {
String requestSign = request.getParameter("sign");//获得请求签名,如sign=19e907700db7ad91318424a97c54ed57
if (StringUtils.isEmpty(requestSign)) {
return false;
}
List<String> keys = new ArrayList<String>(request.getParameterMap().keySet());
keys.remove("sign");//排除sign参数
Collections.sort(keys);//排序

StringBuilder sb = new StringBuilder();
for (String key : keys) {
sb.append(key).append("=").append(request.getParameter(key)).append("&");//拼接字符串
}
String linkString = sb.toString();
linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);//去除最后一个'&'

String secret = "Potato";//密钥,自己修改
String sign = DigestUtils.md5Hex(linkString + secret);//混合密钥md5

return StringUtils.equals(sign, requestSign);//比较
}
- 集成MyBatis、通用Mapper插件、PageHelper分页插件,实现单表业务零SQL

- 使用Druid Spring Boot Starter 集成Druid数据库连接池与监控

- 使用FastJsonHttpMessageConverter,提高JSON序列化速度

技术选型&文档
- Spring Boot(查看Spring Boot学习&使用指南)
- MyBatis(查看官方中文文档)
- MyBatisb通用Mapper插件(查看官方中文文档)
- MyBatis PageHelper分页插件(查看官方中文文档)
- Druid Spring Boot Starter(查看官方中文文档)
- Fastjson(查看官方中文文档)

注:代码贴上去有点乱,可以查看我的简书原文 查看全部

最近使用Spring Boot 配合 MyBatis 、通用Mapper插件、PageHelper分页插件 连做了几个中小型API项目,做下来觉得这套框架、工具搭配起来开发这种项目确实非常舒服,团队的反响也不错。在项目搭建和开发的过程中也总结了一些小经验,与大家分享一下。

在开发一个API项目之前,搭建项目、引入依赖、配置框架这些基础活自然不用多说,通常为了加快项目的开发进度(早点回家)还需要封装一些常用的类和工具,比如统一的响应结果封装、统一的异常处理、接口签名认证、基础的增删改差方法封装、基础代码生成工具等等,有了这些项目才能开工。

然而,下次再做类似的项目上述那些步骤可能还要搞一遍,虽然通常是拿过来改改,但是还是比较浪费时间。所以,可以利用面向对象抽象、封装的思想,抽取这类项目的共同之处封装成了一个种子项目(估计大部分公司都会有很多类似的种子项目),这样的话下次再开发类似的项目直接在该种子项目上迭代就可以了,减少无意义的重复工作。

在相关项目上线之后,我花了点时间对该种子项目做了一些精简,并且已经把该项目分享到GitHub上面了,如果你正准备做类似项目的话,可以去克隆下来试试,项目地址&使用文档:https://github.com/lihengming/ ... -seed  。如果在使用中发现问题或者有什么好建议的话欢迎提issue或pr一起来完善它。
特征&提供
- 最佳实践的项目结构、配置文件、精简的POM

                           (新标签打开图片,查看大图)
注:使用代码生成器生成代码后会创建model、dao、service、web等包。

- 统一响应结果封装及生成工具
/**
* 统一API响应结果封装
*/
public class Result {
private int code;
private String message;
private Object data;
public Result setCode(ResultCode resultCode) {
this.code = resultCode.code;
return this;
}
//省略getter、setter方法
}
/**
* 响应码枚举,参考HTTP状态码的语义
*/
public enum ResultCode {
SUCCESS(200),//成功
FAIL(400),//失败
UNAUTHORIZED(401),//未认证(签名错误)
NOT_FOUND(404),//接口不存在
INTERNAL_SERVER_ERROR(500);//服务器内部错误

public int code;

ResultCode(int code) {
this.code = code;
}
}
/**
* 响应结果生成工具
*/
public class ResultGenerator {
private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";

public static Result genSuccessResult() {
return new Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE);
}

public static Result genSuccessResult(Object data) {
return new Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE)
.setData(data);
}

public static Result genFailResult(String message) {
return new Result()
.setCode(ResultCode.FAIL)
.setMessage(message);
}
}


- 统一异常处理
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
    exceptionResolvers.add(new HandlerExceptionResolver() {
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
            Result result = new Result();
            if (e instanceof ServiceException) {//业务失败的异常,如“账号或密码错误”
                result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
                logger.info(e.getMessage());
            } else if (e instanceof NoHandlerFoundException) {
                result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在");
            } else if (e instanceof ServletException) {
                result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
            } else {
                result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
                String message;
                if (handler instanceof HandlerMethod) {
                    HandlerMethod handlerMethod = (HandlerMethod) handler;
                    message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
                            request.getRequestURI(),
                            handlerMethod.getBean().getClass().getName(),
                            handlerMethod.getMethod().getName(),
                            e.getMessage());
                } else {
                    message = e.getMessage();
                }
                logger.error(message, e);
            }
            responseResult(response, result);
            return new ModelAndView();
        }

    });
}


- 常用基础方法抽象封装
public interface Service<T> {
void save(T model);//持久化
void save(List<T> models);//批量持久化
void deleteById(Integer id);//通过主鍵刪除
void deleteByIds(String ids);//批量刪除 eg:ids -> “1,2,3,4”
void update(T model);//更新
T findById(Integer id);//通过ID查找
T findBy(String fieldName, Object value) throws TooManyResultsException; //通过Model中某个成员变量名称(非数据表中column的名称)查找,value需符合unique约束
List<T> findByIds(String ids);//通过多个ID查找//eg:ids -> “1,2,3,4”
List<T> findByCondition(Condition condition);//根据条件查找
List<T> findAll();//获取所有
}


- 提供代码生成器来生成基础代码
public abstract class CodeGenerator {
...
public static void main(String args) {
genCode("输入表名");
}
public static void genCode(String... tableNames) {
for (String tableName : tableNames) {
//根据需求生成,不需要的注掉,模板有问题的话可以自己修改。
genModelAndMapper(tableName);
genService(tableName);
genController(tableName);
}
}
...
}
CodeGenerator 可根据表名生成对应的Model、Mapper、MapperXML、Service、ServiceImpl、Controller(默认提供POST和RESTful两套Controller模板,根据需要在 genController(tableName) 方法中自己选择,默认是纯POST的),代码模板可根据实际项目的需求来定制,以便渐少重复劳动。由于每个公司业务都不太一样,所以只提供了一些简单的通用方法模板,主要是提供一个思路来减少重复代码的编写。在我们公司的实际使用中,其实根据业务的抽象编写了大量的代码模板。


- 提供简单的接口签名认证
public void addInterceptors(InterceptorRegistry registry) {
//接口签名认证拦截器,该签名认证比较简单,实际项目中可以使用Json Web Token或其他更好的方式替代。
if (!"dev".equals(env)) { //开发环境忽略签名认证
registry.addInterceptor(new HandlerInterceptorAdapter() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//验证签名
boolean pass = validateSign(request);
if (pass) {
return true;
} else {
logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}",
request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));

Result result = new Result();
result.setCode(ResultCode.UNAUTHORIZED).setMessage("签名认证失败");
responseResult(response, result);
return false;
}
}
});
}
}
/**
* 一个简单的签名认证,规则:
* 1. 将请求参数按ascii码排序
* 2. 拼接为a=value&b=value...这样的字符串(不包含sign)
* 3. 混合密钥(secret)进行md5获得签名,与请求的签名进行比较
*/
private boolean validateSign(HttpServletRequest request) {
String requestSign = request.getParameter("sign");//获得请求签名,如sign=19e907700db7ad91318424a97c54ed57
if (StringUtils.isEmpty(requestSign)) {
return false;
}
List<String> keys = new ArrayList<String>(request.getParameterMap().keySet());
keys.remove("sign");//排除sign参数
Collections.sort(keys);//排序

StringBuilder sb = new StringBuilder();
for (String key : keys) {
sb.append(key).append("=").append(request.getParameter(key)).append("&");//拼接字符串
}
String linkString = sb.toString();
linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);//去除最后一个'&'

String secret = "Potato";//密钥,自己修改
String sign = DigestUtils.md5Hex(linkString + secret);//混合密钥md5

return StringUtils.equals(sign, requestSign);//比较
}

- 集成MyBatis、通用Mapper插件、PageHelper分页插件,实现单表业务零SQL

- 使用Druid Spring Boot Starter 集成Druid数据库连接池与监控

- 使用FastJsonHttpMessageConverter,提高JSON序列化速度

技术选型&文档
- Spring Boot(查看Spring Boot学习&使用指南
- MyBatis(查看官方中文文档
- MyBatisb通用Mapper插件(查看官方中文文档
- MyBatis PageHelper分页插件(查看官方中文文档
- Druid Spring Boot Starter(查看官方中文文档
- Fastjson(查看官方中文文档

注:代码贴上去有点乱,可以查看我的简书原文

Spring Cloud Eureka 入门 (一)服务注册中心详解

Spring Cloud泥瓦匠BYSocket 发表了文章 • 3 个评论 • 3294 次浏览 • 2017-06-30 15:00 • 来自相关话题

「Spring Cloud Eureka 入门系列」
Spring Cloud Eureka 入门 (一)服务注册中心详解Spring Cloud Eureka 入门 (二)服务提供者详解Spring Cloud Eureka 入门 (三)服务消费者详解

本文提纲
1. Eureka 服务治理
1.1 什么是 Eureka
1.2 Eureka 集群架构
2. 运行 Eureka 工程 springcloud-eureka-server
3. 详解 Eureka 工程 springcloud-eureka-server
 

一、Eureka 服务治理
1.1 什么是 Eureka
Eureka,这里是 Spring Cloud Eureka 的简称,是 Spring Cloud Netflix 组件之一。Spring Cloud Netflix 中核心的组件包括了服务治理(Eureka),服务容断(Hystrix),路由(Zuul)和客户端负载均衡(Ribbon)。在系列第三篇,服务消费者讲解会涉及到 Ribbon 的使用。
回到 Spring Cloud Eureka,是基于 Netflix Eureka (Netflix 是 Java 实现的开源软件)。服务治理(Eureka)包括服务注册、服务发现和服务检测监控等,自然本文介绍下 Eureka 作为服务注册中心。
 
1.2 Eureka 架构

Eureka 作为服务治理,必然满足下面几点:
- 服务本身不存在单点故障,
- 支持集群,即高可用性
- 服务与服务之间通过服务注册中心找到彼此实例
 
作为服务端(即服务注册中心),包括
- 管理服务实例
- 提供服务注册或下线
- 提供服务发现
- 提供服务注册表至两类客户端(即服务提供者和消费者)
 
作为客户端(即服务提供者和消费者),包括
- 连接服务注册中心
- 向服务注册中心注册或者下线服务实例
- 向服务注册中心或服务注册缓存列表查询服务
 
Eureka 集群架构如图所示:







二、运行 Eureka 工程 springcloud-eureka-server 

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:Spring Cloud Dalston.SR1、 spring-cloud-netflix 1.3.1、Spring Boot 1.5.4
 
1. git clone 下载工程 springcloud-learning-example
项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample:git clone https://github.com/JeffLi1993/ ... e.git
 
2. Maven 编译安装这个工程:cd springcloud-learning-example
mvn clean install





3. 运行 springcloud-eureka-server Eureka 工程
右键 Main 函数 Run Eureka Server 启动类 EurekaServerApplication,启动服务注册中心工程。
EurekaServerApplication 类地址:/springcloud-learning-example/springcloud-eureka-sample/springcloud-eureka-server/src/main/java/org/spring/springboot/EurekaServerApplication.java
控制台 Console 看到这类信息,代表启动成功:2017-06-30 10:32:47.549 INFO 2977 --- [ Thread-11] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
2017-06-30 10:32:47.625 INFO 2977 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8888 (http)
2017-06-30 10:32:47.626 INFO 2977 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8888
2017-06-30 10:32:47.632 INFO 2977 --- [ main] o.s.springboot.EurekaServerApplication : Started EurekaServerApplication in 23.168 seconds

4. 访问 Eureka 注册中心可视化界面
打开浏览器,访问 http://localhost:8888/ ,如图所示






可以看到主体信息包括:
- 系统状态:环境、运行时间、更新时间等
- 注册信息:服务名、服务地址、服务状态
- 基本信息:环境、内存、副本信息
- 实例信息:IP、端口
 

三、详解 Eureka 工程 springcloud-eureka-server 
1.springcloud-eureka-server  工程目录结构├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ └── springcloud
│ ├── EurekaServerApplication.java
└── resources
└── application.ymlEurekaServerApplication.java Eureka Server 启动类
application.yml 配置文件

2. 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/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springcloud</groupId>
<artifactId>springcloud-eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springcloud-eureka-server :: Spring Cloud Eureka 服务注册中心</name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Cloud Netflix Eureka Server 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud Netflix 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.3.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>使用的依赖是
- spring-cloud-netflix 1.3.1 是 Spring Cloud Dalston.SR1 版本。
- spring-cloud-starter-eureka-server Eureka Server 模块依赖
上面提到的客户端负载均衡 Ribbon ,可以依赖树中看出 spring-cloud-starter-eureka-server 依赖了 Ribbon 相关的库。因为一般 eureka 本身作为服务自注册实现高可用,也可以作为客户端调用其他服务。
 
3. application.yml 配置server:
port: 8888 # 服务端口
eureka:
instance:
hostname: localhost # 设置主机名
client:
registerWithEureka: false # 是否向 Eureka 注册服务。该应用为服务注册中心,不需要自注册,设置为 false
fetchRegistry: false # 是否检索服务。该应用为服务注册中心,职责为注册和发现服务,无需检索服务,设置为 false
server:
waitTimeInMsWhenSyncEmpty: 0 # 设置同步为空时的等待时间。默认 5 * MINUTESapplication.property,可以看下面的配置解释:
- server.port 设置工程服务端口
- eureka.instance.hostname Eureka 实例主机名
- eureka.client.registerWithEureka 是否向 Eureka 注册服务。服务注册中心服务,没有作为集群,所以不需要自注册,设置为 false
- eureka.client.fetchRegistry 是否检索服务。该应用为服务注册中心,职责为注册和发现服务,无需检索服务,设置为 false
- eureka.server.waitTimeInMsWhenSyncEmpty 设置同步为空时的等待时间。默认 5 * MINUTES
 
4.注册中心应用启动类/**
* Spring Boot Eureka Server 应用启动类
*
* Created by bysocket on 21/06/17.
*/
@EnableEurekaServer // Eureka Server 标识
@SpringBootApplication // Spring Boot 应用标识
public class EurekaServerApplication {
public static void main(String args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
SpringApplication.run(EurekaServerApplication.class,args);
}
}@EnableEurekaServer 标志该应用作为 Eureka Server ,并会自动化读取相关配置。
 
 
四、小结
此小章节介绍了如何 Eureka 作为服务注册中心 Server,下一小结讲下 服务提供者详解 具体是如何向服务注册中心注册自己的。系列目录如下:
Spring Cloud Eureka 入门 (一)服务注册中心详解Spring Cloud Eureka 入门 (二)服务提供者详解Spring Cloud Eureka 入门 (三)服务消费者详解
 
资料:
《Spring Cloud微服务实战》https://re.jd.com/cps/item/12172344.html
官方文档
http://cloud.spring.io/spring- ... EASE/

欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦!
— http://www.bysocket.com/ —
— https://github.com/JeffLi1993 — 查看全部
「Spring Cloud Eureka 入门系列」


本文提纲
1. Eureka 服务治理
1.1 什么是 Eureka
1.2 Eureka 集群架构
2. 运行 Eureka 工程 springcloud-eureka-server
3. 详解 Eureka 工程 springcloud-eureka-server
 

一、Eureka 服务治理
1.1 什么是 Eureka
Eureka,这里是 Spring Cloud Eureka 的简称,是 Spring Cloud Netflix 组件之一。Spring Cloud Netflix 中核心的组件包括了服务治理(Eureka),服务容断(Hystrix),路由(Zuul)和客户端负载均衡(Ribbon)。在系列第三篇,服务消费者讲解会涉及到 Ribbon 的使用。
回到 Spring Cloud Eureka,是基于 Netflix Eureka (Netflix 是 Java 实现的开源软件)。服务治理(Eureka)包括服务注册、服务发现和服务检测监控等,自然本文介绍下 Eureka 作为服务注册中心。
 
1.2 Eureka 架构

Eureka 作为服务治理,必然满足下面几点:
- 服务本身不存在单点故障,
- 支持集群,即高可用性
- 服务与服务之间通过服务注册中心找到彼此实例
 
作为服务端(即服务注册中心),包括
- 管理服务实例
- 提供服务注册或下线
- 提供服务发现
- 提供服务注册表至两类客户端(即服务提供者和消费者)
 
作为客户端(即服务提供者和消费者),包括
- 连接服务注册中心
- 向服务注册中心注册或者下线服务实例
- 向服务注册中心或服务注册缓存列表查询服务
 
Eureka 集群架构如图所示:
Eureka集群.jpeg




二、运行 Eureka 工程 springcloud-eureka-server 

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:Spring Cloud Dalston.SR1、 spring-cloud-netflix 1.3.1、Spring Boot 1.5.4
 
1. git clone 下载工程 springcloud-learning-example
项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample
git clone https://github.com/JeffLi1993/ ... e.git

 
2. Maven 编译安装这个工程:
cd springcloud-learning-example
mvn clean install





3. 运行 springcloud-eureka-server Eureka 工程
右键 Main 函数 Run Eureka Server 启动类 EurekaServerApplication,启动服务注册中心工程。
EurekaServerApplication 类地址:/springcloud-learning-example/springcloud-eureka-sample/springcloud-eureka-server/src/main/java/org/spring/springboot/EurekaServerApplication.java
控制台 Console 看到这类信息,代表启动成功:
2017-06-30 10:32:47.549  INFO 2977 --- [      Thread-11] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
2017-06-30 10:32:47.625 INFO 2977 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8888 (http)
2017-06-30 10:32:47.626 INFO 2977 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8888
2017-06-30 10:32:47.632 INFO 2977 --- [ main] o.s.springboot.EurekaServerApplication : Started EurekaServerApplication in 23.168 seconds


4. 访问 Eureka 注册中心可视化界面
打开浏览器,访问 http://localhost:8888/ ,如图所示

WechatIMG406.jpeg


可以看到主体信息包括:
- 系统状态:环境、运行时间、更新时间等
- 注册信息:服务名、服务地址、服务状态
- 基本信息:环境、内存、副本信息
- 实例信息:IP、端口
 

三、详解 Eureka 工程 springcloud-eureka-server 
1.springcloud-eureka-server  工程目录结构
├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ └── springcloud
│ ├── EurekaServerApplication.java
└── resources
└── application.yml
EurekaServerApplication.java Eureka Server 启动类
application.yml 配置文件

2. 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/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springcloud</groupId>
<artifactId>springcloud-eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springcloud-eureka-server :: Spring Cloud Eureka 服务注册中心</name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Cloud Netflix Eureka Server 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud Netflix 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.3.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
使用的依赖是
- spring-cloud-netflix 1.3.1 是 Spring Cloud Dalston.SR1 版本。
- spring-cloud-starter-eureka-server Eureka Server 模块依赖
上面提到的客户端负载均衡 Ribbon ,可以依赖树中看出 spring-cloud-starter-eureka-server 依赖了 Ribbon 相关的库。因为一般 eureka 本身作为服务自注册实现高可用,也可以作为客户端调用其他服务。
 
3. application.yml 配置
server:
port: 8888 # 服务端口
eureka:
instance:
hostname: localhost # 设置主机名
client:
registerWithEureka: false # 是否向 Eureka 注册服务。该应用为服务注册中心,不需要自注册,设置为 false
fetchRegistry: false # 是否检索服务。该应用为服务注册中心,职责为注册和发现服务,无需检索服务,设置为 false
server:
waitTimeInMsWhenSyncEmpty: 0 # 设置同步为空时的等待时间。默认 5 * MINUTES
application.property,可以看下面的配置解释:
- server.port 设置工程服务端口
- eureka.instance.hostname Eureka 实例主机名
- eureka.client.registerWithEureka 是否向 Eureka 注册服务。服务注册中心服务,没有作为集群,所以不需要自注册,设置为 false
- eureka.client.fetchRegistry 是否检索服务。该应用为服务注册中心,职责为注册和发现服务,无需检索服务,设置为 false
- eureka.server.waitTimeInMsWhenSyncEmpty 设置同步为空时的等待时间。默认 5 * MINUTES
 
4.注册中心应用启动类
/**
* Spring Boot Eureka Server 应用启动类
*
* Created by bysocket on 21/06/17.
*/
@EnableEurekaServer // Eureka Server 标识
@SpringBootApplication // Spring Boot 应用标识
public class EurekaServerApplication {
public static void main(String args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
SpringApplication.run(EurekaServerApplication.class,args);
}
}
@EnableEurekaServer 标志该应用作为 Eureka Server ,并会自动化读取相关配置。
 
 
四、小结
此小章节介绍了如何 Eureka 作为服务注册中心 Server,下一小结讲下 服务提供者详解 具体是如何向服务注册中心注册自己的。系列目录如下:

 
资料:
《Spring Cloud微服务实战》https://re.jd.com/cps/item/12172344.html
官方文档
http://cloud.spring.io/spring- ... EASE/


欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦!
http://www.bysocket.com/
https://github.com/JeffLi1993

687474703a2f2f7777772e6279736f636b65742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031372f30312f7172636f64655f666f725f67685f6364343231653765623764365f3433302e6a7067.jpeg