SpringCloud -3- Eureka通信过程

 

基础架构

[AdSense-A]

服务注册中心:

Eureka提供的服务端,提供服务注册与发现的功能,也就是eureka-server

服务提供者:

提供服务的应用,可以使springboot应用,也可以是其他技术平台且遵循eureka通信机制的应用。它将自己提供的服务注册到Eureka,以供其他应用发现,即Hello-service应用

服务消费者:

从服务注册中心获取服务列表,从而使消费者可以知道去何处调用其所需要的服务,例如我们的实例使用了Ribbon来实现服务消费,后续会介绍Feign的消费方式。
很多时候,客户端即是服务提供者也是服务消费者。

服务治理机制

服务提供者

服务注册:

启动时发送REST请求方式注册到注册中心,注册中心接收到请求后,将元数据信息存储在一个双层结构Map中,第一层Key是服务名,第二层Key是实例名。
就如同我们实现的那样:
SpringCloud 笔记 (三)---- Eureka通信过程

服务同步:

如同之前的高可用注册中心那样,服务可以分别注册到不同的注册中心里。因为此时注册中心之间因互相注册为服务,当服务提供者发送注册请求到一个服务注册中心时,它会将该请求转发给集群中相连的其他注册中心,从而实现注册中心之间的服务同步。这样,同一个服务就可以被多个注册中心所维护。

服务续约:

注册完后,服务提供者会维护一个心跳用来告诉注册中心它还服务着,放置eureka-server的“剔除”将该服务实例从服务列表中排除出去,我们称该操作为服务续约。
可调整相关属性。例如:
eureka.instance.lease-renewal-interval-in-seconds=30
服务续约任务的调用间隔时间,默认30秒
eureka.instance.lease-expiration-duration-in-seconds=90
服务失效时间,默认90秒

服务消费者

获取服务

此时,服务注册中心已经注册了一个服务,并且该服务有两个实例。我们启动消费者的时候,它会发送一个REST请求给服务注册中心,获取上面注册的服务清单。为了性能考虑,Eureka Server会维护一份只读的服务清单返回给客户端,同时该缓存清单会每隔30秒更新一次
获取服务是消费者的基础,所以必须确保eureka.client.fetch-registry=true没有被修改成false,默认为true.
缓存清单的更新时间可以修改:
eureka.client.registry-fetch-interval-seconds=30

服务调用

获取清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。因为有这些服务实例的详细信息,所以客户端可以根据自己的需要决定具体调用哪个实例,在ribbon中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡。

服务下线

当服务实例进行正常的关闭操作时,它会触发一个服务下线的REST请求给EurekaServer,告诉服务注册中心它要下线,服务端接受到请求后,将该服务状态置为下线(DOWN),并把该下线事件传播出去。

服务注册中心

失效剔除

有些时候,服务实例并不一定会正常下线,可能由于内存溢出,网络故障灯原因使得服务不能正常工作,而注册中心并未收到服务下线的请求。为了从服务列表中将这些无法提供服务的实例剔除,EurekaServer在启动时候会创建一个定时任务,默认每隔一段时间默认60秒将当前清单中超时(默认90秒)没有续约的服务剔除出去。

自我保护

当我们在本地调试基于Eureka的程序时,基本上都会碰到这样一个问题,在服务注册中心面板中出现类似下面的红色警告信息:
SpringCloud 笔记 (三)---- Eureka通信过程
该警告就是出发了Eureka Server的自我保护机制。
服务注册到Eureka Server后,会维护一个心跳连接,告诉Eureka Server自己还服务着。在注册中心运行期间,会统计心跳失败的比例再15分钟之内是否低于85%,如果出现低于的情况(单机调试的时候很容易满足,实际在生产环境上通常是由于网络不稳定导致),Eureka
Server会将当前的实例注册信息保护起来,让这些实例不会过期,尽可能保护这些注册信息。但是,在这段保护期间内实例若出现问题,那么客户端很容易拿到实际已经不存在的服务实例,会出现调试失败的情况,所以客户端必须要有容错机制,比如可以使用请求重试,断路器等机制。
由于本地调试很容易触发注册中心的保护机制,会使得注册中心维护的服务实例不那么准确。所以,我们在本地进行开发的时候,可以使用eureka.server.enable-self-preservation=false参数来关闭保护机制,以确保注册中心可以将不可用的实例正确剔除。

SpringCloud 笔记 –1– 简单搭建服务注册中心与服务,实现高可用

此spring cloud笔记系列都来源于翟永超的spring cloud微服务实战一书,可以自行下载。


此文档有关于服务注册中心。

[AdSense-A]

快速构建一个服务注册中心项目

Pom

<parent>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-parent</artifactId>         <version>1.5.6.RELEASE</version>         <relativePath/> <!-- lookup parent from repository -->     </parent>  <!-- spring cloud -->         <dependency>             <groupId>org.springframework.cloud</groupId>             <artifactId>spring-cloud-starter-eureka-server</artifactId>         </dependency>  <dependencyManagement>         <dependencies>             <dependency>                 <groupId>org.springframework.cloud</groupId>                 <artifactId>spring-cloud-dependencies</artifactId>                 <!-- 此版本和springboot版本有关,可查官网,我这里用的springboot1.5,所以用了Dalston -->                 <version>Dalston.SR3</version>                 <type>pom</type>                 <scope>import</scope>             </dependency>         </dependencies>     </dependencyManagement> 

主类

package com.example.eurekaserver;  import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; /*  * 启动一个服务注册中心提供给其他应用进行对话  */ @EnableEurekaServer @SpringBootApplication public class EurekaServerApplication {      public static void main(String[] args) {         SpringApplication.run(EurekaServerApplication.class, args);     } }  

默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己
所以我们需要禁用它的客户端注册行为

Application.properties

server.port = 1111  eureka.instance.hostname = localhost # don't register selt in eureka eureka.client.register-with-eureka = false # don't search service,only Maintain  service instance eureka.client.fetch-registry = false eureka.client.serviceUrl.defaultZone = http://${eureka.instance.hostname}:${server.port}/eureka/

启动并访问http://localhost:1111/
SpringCloud 笔记 (一)---- 简单搭建服务注册中心与服务,实现服务注册中心高可用

可以发现现在注册中心还没有注册任何服务,现在我们来把一个已有的springboot应用加入到eureka服务治理中。

注册服务提供者

如果你本地有一个springboot的普通应用,这就再好不过了,改造即可。当然重新构建一个也没问题。

添加Pom

<dependencies>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>          <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-test</artifactId>             <scope>test</scope>         </dependency>          <!-- eureka -->         <dependency>             <groupId>org.springframework.cloud</groupId>             <artifactId>spring-cloud-starter-eureka</artifactId>         </dependency>     </dependencies>      <dependencyManagement>         <dependencies>             <dependency>                 <groupId>org.springframework.cloud</groupId>                 <artifactId>spring-cloud-dependencies</artifactId>                 <!-- 此版本和springboot版本有关,可查官网,我这里用的springboot1.5,所以用了Dalston -->                 <version>Dalston.SR3</version>                 <type>pom</type>                 <scope>import</scope>             </dependency>         </dependencies>     </dependencyManagement> 

改造Controller

@RestController @RequestMapping(value="/user") public class UserController {     private final Logger logger = Logger.getLogger(getClass());      @Autowired     private DiscoveryClient client;      @RequestMapping("/hello")     public String greet() {         ServiceInstance instance = client.getLocalServiceInstance(); //打印服务相关内容         logger.info("/hello,host:"+instance.getHost()+", service_id:"+instance.getServiceId());         return "Hello";     } }  

主类

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

主类中加上此注解@EnableDiscoveryClient,激活Eureka中的DiscoveryClient实现(自动化配置,创建DiscoveryClient接口针对Eureka客户端的EnableDiscoveryClient实例),才能实现Controller中对服务信息的输出。

Application.properties:

# name the service spring.application.name = hello-service # define the register url eureka.client.serviceUrl.defaultZone = http://localhost:1111/eureka/

启动此项目(此时注册中心eruka-server应该启动着),在此访问注册中心
http://localhost:1111/
SpringCloud 笔记 (一)---- 简单搭建服务注册中心与服务,实现服务注册中心高可用
标明服务注册到其中。
访问刚才的controller:http://localhost:8080/user/hello
控制台打印了:
SpringCloud 笔记 (一)---- 简单搭建服务注册中心与服务,实现服务注册中心高可用

高可用注册中心

Eureka Server的高可用实际上就是将自己作为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步,达到高可用的效果。下面我们来尝试搭建高可用的服务注册中心集群。

在eureka-server服务注册中心的基础之上进行扩展,构建一个双节点的服务注册中心集群。

Application.properties:

# don't register selt in eureka eureka.client.register-with-eureka = false # don't search service,only Maintain  service instance eureka.client.fetch-registry = false

创建application-peer1.properties与application-peer2.properties
SpringCloud 笔记 (一)---- 简单搭建服务注册中心与服务,实现服务注册中心高可用

Application-peer1.properties:

spring.application.name=eureka-server server.port=1111  # double nodes : first--peer1,that directs peer2 eureka.instance.hostname=peer1  eureka.client.serviceUrl.defaultZone=http://peer2:1112/eureka/

Application-peer2.properties:

server.port = 1112 spring.application.name = eureka-server  # double nodes : second--peer2,that directs peer1 eureka.instance.hostname = peer2  eureka.client.serviceUrl.defaultZone = http://peer1:1111/eureka/

clean项目然后maven clean-maven install
target下此时jar包最新

hosts文件

需要在本机系统中配置peer1,peer2使系统能通过他们找到ip
windows文件位置:
SpringCloud 笔记 (一)---- 简单搭建服务注册中心与服务,实现服务注册中心高可用
打开此文件配置如下:
127.0.0.1 peer1
127.0.0.1 peer2

我们通过jar命令与profile配置分别启动peer1与peer2,实际中也可以两个注册中心项目,application.properties中分别指向彼此,启动。
SpringCloud 笔记 (一)---- 简单搭建服务注册中心与服务,实现服务注册中心高可用
SpringCloud 笔记 (一)---- 简单搭建服务注册中心与服务,实现服务注册中心高可用
访问如下即配置完成:
SpringCloud 笔记 (一)---- 简单搭建服务注册中心与服务,实现服务注册中心高可用
SpringCloud 笔记 (一)---- 简单搭建服务注册中心与服务,实现服务注册中心高可用

现在我们来实验一下注册服务,启动之前需要把注册地址都填上:
之前的client-service的application.properties中修改注册地址如下:

# define the register url eureka.client.serviceUrl.defaultZone = http://peer1:1111/eureka/,http://peer2:1112/eureka/ 

启动此服务,重新访问两个注册节点:
SpringCloud 笔记 (一)---- 简单搭建服务注册中心与服务,实现服务注册中心高可用
SpringCloud 笔记 (一)---- 简单搭建服务注册中心与服务,实现服务注册中心高可用

这样我们两个注册中心彼此都有注册,其中一个dang,另一个仍然可以工作。服务依然可访问。

如果我们不想使用主机名来定义注册中心的地址,也可以使用IP地址的形式,但是需要配置文件中增加配置参数eureka.instance.prefer-ip-address=true,该默认为false。然后当应用程序向eureka注册时,它将使用IP地址而不是主机名

SpringCloud –2–新建服务消费者,ribbon实现负载均衡

[AdSense-A]

在实现之前

  1. 启动服务注册中心eureka-server
  2. 为了试验负载均衡,我们通过java –jar命令行的方式启动两个不同的端口hello-service
Java –jar hello-service-0.0.1-SNAPSHOT.jar –server.port=8082 Java –jar hello-service-0.0.1-SNAPSHOT.jar –server.port=8083

此时可发现注册中心中hello-service有了两个实例单元,如下:
SpringCloud 笔记 (二)---- 简单搭建一个服务消费者,实现简单的ribbon负载均衡

创建消费者工程ribbon-consumer

Pom依赖

省去了基本的父依赖,web依赖与maven打包插件等

<dependencies>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>          <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-test</artifactId>             <scope>test</scope>         </dependency>          <!-- spring cloud -->         <dependency>             <groupId>org.springframework.cloud</groupId>             <artifactId>spring-cloud-starter-eureka</artifactId>         </dependency>          <!-- ribbon -->         <dependency>             <groupId>org.springframework.cloud</groupId>             <artifactId>spring-cloud-starter-ribbon</artifactId>         </dependency>     </dependencies>      <dependencyManagement>         <dependencies>             <dependency>                 <groupId>org.springframework.cloud</groupId>                 <artifactId>spring-cloud-dependencies</artifactId>                 <!-- 此版本和springboot版本有关,可查官网,我这里用的springboot1.5,所以用了Dalston -->                 <version>Dalston.SR3</version>                 <type>pom</type>                 <scope>import</scope>             </dependency>         </dependencies>     </dependencyManagement>

主类

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; /*  * 注册为eureka客户端应用,获得服务发现的能力  */ @EnableDiscoveryClient @SpringBootApplication public class RibbonConsumerApplication {      @Bean//spring bean实例     @LoadBalanced//开启客户端负载均衡     RestTemplate restTemplate() {         return new RestTemplate();     }       public static void main(String[] args) {         SpringApplication.run(RibbonConsumerApplication.class, args);     } }

请求

@RestController public class ConsumerController {     @Autowired     RestTemplate restTemplate;      @RequestMapping(value="/ribbon-consumer",method=RequestMethod.GET)     public String helloConsumer() {         return restTemplate.getForEntity("http://HELLO-SERVICE/user/hello", String.class).getBody();     } }

Application.properties:

#name spring.application.name=ribbon-consumer  #port ,can't be same with other server.port=9001  # register,the same as the service,other you'll not find the service eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

此时服务提供者的地址应是http://localhost:1111/eureka/,必须一致否则发现不了
启动此消费者,注册中心中发现消费者
SpringCloud 笔记 (二)---- 简单搭建一个服务消费者,实现简单的ribbon负载均衡
再通过http://localhost:9001/ribbon-consumer请求:
SpringCloud 笔记 (二)---- 简单搭建一个服务消费者,实现简单的ribbon负载均衡
成功返回。
而且我们可以在ribbon-consumer应用的控制台中看到如下信息:
Ribbon输出了当前客户端维护的HELLO-SERVICE的服务列表情况,其中包含了各个实例的位置,其就是按照此信息进行轮询访问,实现基于客户端的负载均衡。另外还输出了一些其他非常有用的信息,如对各个实例的请求总数量,第一次连接信息,上一次连接信息,总得请求失败数量等。
DynamicServerListLoadBalancer for client HELLO-SERVICE initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=HELLO-SERVICE,current list of Servers=[CC-PC:8083, CC-PC:8082]

此次请求8083服务控制台打印:
2017-09-04 10:56:47.573 INFO 8104 — [nio-8083-exec-6] c.e.clientservice.web.UserController : /hello,host:CC-PC, service_id:hello-service
再次请求,8082服务控制台打印:
2017-09-04 10:56:02.498 INFO 7560 — [io-8082-exec-10] c.e.clientservice.web.UserController : /hello,host:CC-PC, service_id:hello-service

证明ribbon实现了负载均衡,轮询访问。

SpringCloud 笔记 –6– 客户端负载均衡Ribbon

Spring cloud ribbon是一个基于HTTP和TCP的客户端负载均衡工具,基于Netflix Ribbon实现。通过spring cloud的封装,可以让我们轻松地将面向服务的REST模板请求自动转换成客户端负载均衡的服务调用。
微服务的调用,API网关的请求转发等内容,实际上都是通过Ribbon实现的,Feign也是基于Ribbon实现的工具,所以对于spring cloud ribbon的理解和使用,对于使用spring cloud来构建微服务非常重要。

[AdSense-A]

客户端负载均衡

负载均衡是对系统高可用、网络压力的缓解和处理能力扩容的重要手段之一。我们通常说的负载均衡指的是服务端负载均衡,软件是通过在服务器上安装一些具有均衡负载功能或模块的软件来完成请求分发工作。会维护一个下挂可用的服务端清单,通过心跳检测来剔除故障的服务端节点保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备以某种算法(比如线性轮询,按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务端地址,进行转发。

[AdSense-A]
客户端负载均衡和服务端负载均衡最大不同点在于服务清单所存储的位置。客户端负载均衡中,所有客户端节点维护者自己要访问的服务端清单,这些服务端清单来自于服务注册中心。同服务端负载均衡架构类似,在客户端负载均衡中也需要心跳取维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成。在spring cloud实现的服务治理框架中,默认会创建针对各个服务治理框架的Ribbon自动化整合配置。

通过spring cloud ribbon的封装,在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步:
1. 服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的注册中心
2. 服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用
这样就可以将服务提供者的高可用以及服务消费者的负载均衡一起实现了。
(此实例可以看之前的ribbon-consumer)

RestTemplate

该对象会使用Ribbon的自动化配置,同时通过配置@LoadBalanced还能够开启客户端负载均衡。以下演示针对几种不同请求类型和参数类型的服务调用实现。

GET请求

RestTemplate中,对GET请求可以通过如下两个方法进行调用实现(源码中的返回用了HttpMethod.GET)

1.getForEntity函数

返回ResponseEntity,该对象是spring 对HTTP请求相应的封装,主要存储了HTTP的几个重要元素,比如HTTP请求状态码的枚举对象HttpStatus,它的父类HttpEntity中还存储着HTTP请求的头信息对象HttpHeaders以及泛型类型的请求体对象

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)throws RestClientException;

在服务提供者中加一个请求:

@RequestMapping("/findUser")  public User findUser(){     User user = userService.findOneUser(1);     return user; }

启动注册中心eureka-server,启动服务提供者client-service
在服务消费者ribbon-consumer中增加请求(主类参考之前的服务消费者实例中,这里不重复):

@RestController public class ConsumerController {     @Autowired     RestTemplate restTemplate;      //当然你需要吧服务提供者的User类考到消费者项目下     @RequestMapping(value="/ribbon-consumer1",method=RequestMethod.GET)     public User findUser() {         ResponseEntity<User> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/user/findUser", User.class);         return responseEntity.getBody();     } }

启动服务消费者,访问http://cc-pc:9001/ribbon-consumer1
{“id”:1,”userName”:”cc”,”password”:”123456”,”mobilePhone”:”18800000000”,”address”:”北京”,”role”:1,”note”:”我天,我们有了第一个用户”}

可以传递参数
我们改造一下服务提供者中请求:

@RequestMapping("/findUser")  public User findUser(Integer id){     User user = userService.findOneUser(id);     return user; }

再改造一下服务消费者请求:

@RequestMapping(value="/ribbon-consumer1",method=RequestMethod.GET)     public User findUser(Integer id) {     ResponseEntity<User> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/user/findUser?id={1}", User.class, id);     return responseEntity.getBody(); }

重新编译启动服务提供者,消费者,访问http://cc-pc:9001/ribbon-consumer1?id=1
{“id”:1,”userName”:”cc”,”password”:”123456”,”mobilePhone”:”18800000000”,”address”:”北京”,”role”:1,”note”:”我天,我们有了第一个用户”}

这个函数还提供了重载的方法1

@Override     public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)             throws RestClientException;

ribbon-consumer添加请求:

@RequestMapping(value="/ribbon-consumer2",method=RequestMethod.GET)     public User findUser2(Integer id) {         Map<String,Integer> params = new HashMap<String,Integer>();         params.put("id", 1);         ResponseEntity<User> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/user/findUser?id={id}", User.class, params);         return responseEntity.getBody();     }

访问http://cc-pc:9001/ribbon-consumer2?id=1
{“id”:1,”userName”:”cc”,”password”:”123456”,”mobilePhone”:”18800000000”,”address”:”北京”,”role”:1,”note”:”我天,我们有了第一个用户”}

这个函数还提供了重载的方法2

@Override     public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException;

ribbon-consumer添加请求:

@RequestMapping(value="/ribbon-consumer3",method=RequestMethod.GET)     public User findUser3(Integer id) {         UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://HELLO-SERVICE/user/findUser?id={id}").build()                 .expand(1).encode();         URI uri = uriComponents.toUri();         ResponseEntity<User> responseEntity = restTemplate.getForEntity(uri, User.class);         return responseEntity.getBody();     }

http://cc-pc:9001/ribbon-consumer3?id=1
{“id”:1,”userName”:”cc”,”password”:”123456”,”mobilePhone”:”18800000000”,”address”:”北京”,”role”:1,”note”:”我天,我们有了第一个用户”}

2.getForObject函数

这个函数就不用再找body来找对象了,直接就返回了。
和上面一样有三个重载的方法:

@RequestMapping(value="/ribbon-consumer11",method=RequestMethod.GET)     public User findUser11(Integer id) {         User user = restTemplate.getForObject("http://HELLO-SERVICE/user/findUser?id={1}", User.class, id);         return user;     }
@RequestMapping(value="/ribbon-consumer12",method=RequestMethod.GET)     public User findUser12(Integer id) {         Map<String,Integer> params = new HashMap<String,Integer>();         params.put("id", 1);         User user = restTemplate.getForObject("http://HELLO-SERVICE/user/findUser?id={id}", User.class, params);         return user;     }
@RequestMapping(value="/ribbon-consumer13",method=RequestMethod.GET)     public User findUser13(Integer id) {         UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://HELLO-SERVICE/user/findUser?id={id}").build()                 .expand(1).encode();         URI uri = uriComponents.toUri();         User user= restTemplate.getForObject(uri, User.class);         return user;     }

访问http://cc-pc:9001/ribbon-consumer11?id=1等,均返回了。

POST请求

1.postForEntity函数

与GET请求的getForEntity类似,下面这个例子提交User对象,返回String
服务提供者增加:

@RequestMapping("/findUserName") public String findUserName(User user) {     return "cc -->"+user.toString();//为了看参数 }

服务消费者增加:

@RequestMapping(value="/ribbon-consumer21",method=RequestMethod.POST)     public String findUserName21(String name) {         User user = new User();         user.setId(1);         user.setUserName("cc");         user.setPassword("123456");         ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://HELLO-SERVICE/user/findUserName?id={1}&userName={2}&password={3}",                 null,String.class, user.getId(),user.getUserName(),user.getPassword());         return responseEntity.getBody();     }

因为这里是post方式请求,浏览器url写路径是行不通了,我们这里简单写一个静态页面,就放在static下面了,如果写动态页面参照之前的springboot模板引擎。
Static文件夹下创建cc.html:

<html> <head>     <title>cc练习页面</title> </head> <body>     <form action="/ribbon-consumer21" method="post">         <input type="submit" value="Post请求练习21"/>     </form> </body> </html>

分别启动,访问http://cc-pc:9001/cc.html
SpringCloud 笔记 (六)---- 客户端负载均衡Ribbon
点击后显示:
SpringCloud 笔记 (六)---- 客户端负载均衡Ribbon
类似GET方法,有类似的重载方法:

@Override     public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)             throws RestClientException ;     @Override     public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException ; 

不再一一列举。

2.postForObject函数

与getForObject类似,也是直接返回对象使用不用再处理,也有重载的方法类似。

public <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)             throws RestClientException;
@RequestMapping(value="/ribbon-consumer31",method=RequestMethod.POST)     public String findUserName31() {         User user = new User();         user.setId(1);         user.setUserName("cc");         user.setPassword("123456");         String str = restTemplate.postForObject("http://HELLO-SERVICE/user/findUserName?id={1}&userName={2}&password={3}",                 null,String.class, user.getId(),user.getUserName(),user.getPassword());         return str;     }

cc–>User [id=1, userName=cc, password=123456]

3.postForLocation函数

post请求提交资源,返回资源的URI,因为该URI就相当于制定了返回类型,所以此方法实现的POST请求不需要再指定responseType,也有类似的重载方法

@Override     public URI postForLocation(String url, Object request, Object... uriVariables) throws RestClientException;

这个方法我老是执行不通。。。可以用下别的方法= =

PUT请求和DELETE请求省略,可自行去观看。。。
源码中还可以看到exchange和execute,他们是普通写法,可以使用,方法类似,具体可参考spring官网。

Ribbon的配置

全局配置

ribbon.<key>=<value>

格式进行配置,例如

ribbon.ConnnectTimeout=250

全局配置可以作为默认值理解,当客户端配置了相应key值,将覆盖全局配置

指定客户端配置

<client>.ribbon.<key>=<value>

例如我们之前的restTemplate.getForObject(“http://HELLO-SERVICE/user/findUser?id={id}”, User.class, params)方法中的实例名称即第一个client

HELLO-SERVICE.ribbon.listOfServers=localhost:8001,localhost:8002

Key和value可以通过查看com.netflix.client.config.CommonClientConfigKey来获得
(可参考以下网址http://javadox.com/com.netflix.ribbon/ribbon-core/2.0-RC4/com/netflix/client/config/CommonClientConfigKey.html

与Eureka结合

此时,spring cloud会触发Eurek中实现的对Ribbon的自动化配置。我们的配置将会变得非常简单,Eureka会为我们维护所有服务的实例清单,不用去通过指定ribbon的参数来指定具体的服务实例清单。当然如果你想使用ribbon的配置来维护清单,可以配置禁用eureka的清单维护:ribbon.eureka.enabled=false.
对于ribbon的参数配置,依然可采用之前介绍的两种配置方式来实现。
Spring cloud ribbon默认实现了区域亲和策略,所以,我们可以通过Eureka实例的元数据配置来实现区域化的实例配置方案。比如,可以将处于不同机房的实例配置成不同的区域值,作为跨区域的容错机制实现。只需在服务实例的元数据中增加zone参数来指定自己所在的区域
Eureka.instance.metadataMap.zone=shanghai

重试机制

通过简单的配置,原来那些通过RestTemplate实现的服务访问就会自动根据配置来实现重试策略。

# open the retry function spring.cloud.loadbalancer.retry.enabled=true # hystrix'time must be more larger than the ribbon's,or the retry is not useful hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000 # connect timeout hello-service.ribbon.ConnectTimeout=250  # handle timeout hello-service.ribbon.ReadTimeout=1000 # is it for all the operations to retry hello-service.ribbon.OkToRetryOnAllOperation=true # change time for alter service hello-service.ribbon.MaxAutoRetriesNextServer=2 # the time for now service to change time hello-service.ribbon.MaxAutoRetries=1

以上配置后,当访问故障请求的时候,会再次尝试访问一次当前实例,如果不行,就换一个实例进行访问,如果不行,再换一个,还不行,返回失败信息。

Spring Boot 2.0(二):Spring Boot 2.0 动态Banner

Spring Boot 2.0 提供了很多新特性,动态 Banner。

[AdSense-A]
配置依赖

使用 Spring Boot 2.0 首先需要将项目依赖包替换为刚刚发布的 2.0 RELEASE,现在网站https://start.spring.io/也将 Spring Boot2.0 设置为默认版本。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>

设置完毕后,dependencies中没有指明版本的依赖包,将自动使用2.0.0.RELEASE依赖的版本。

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

Spring Boot 2.0 刚刚发布,一些 Maven 仓库还没更新,可以手动添加 Spring Boot 官方 Maven 仓库。

<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

第一次使用 Spring Boot 2.0 ,完整依赖包需要下载半小时左右。

Spring Boot 更换 Banner

在 Spring Boot 1.0 中如何更换启动 Banner,其实都很简单,只需要在src/main/resources路径下新建一个banner.txt文件,banner.txt中填写好需要打印的字符串内容即可。

一般情况下,我们会借助第三方工具帮忙转化内容,如网站http://www.network-science.de/ascii/将文字转化成字符串,

网站:http://www.degraeve.com/img2txt.php可以将图片转化成字符串。

在 Spring Boot 2.0 项目src/main/resources路径下新建一个banner.txt文件,粘贴上述字符串,启动 Spring Boot 项目观察启动日志,发现 Spring Boot 2.0 已经将默认的 Spring 字符串替换为 hello world。说明 Spring Boot 2.0 也支持1.0更换 Banner 的方法。

接下来在Spring Boot 2.0 项目中测试打印动态Banner 。

同样我们将 banner.gif 文件放到项目的src/main/resources路径下,启动项目进行测试,输出栏打印信息如下:

通过上述输出我们发现 Spring Boot 在启动的时候,会将 gif 图片的每一个画面,按照顺序打印在日志中,所有的画面打印完毕后,才会启动 Spring Boot 项目。

如果目录src/main/resources下同时存在banner.txt和banner.gif,项目会先将banner.gif每一个画面打印完毕之后,再打印banner.txt中的内容。

项目的启动 Banner 有什么用呢,在一些大的组织或者公司中,可以利用这个特性定制自己专属的启动画面,增加团队对品牌的认同感。

参考源码

Spring Boot 2.0(一):Spring Boot 2.0发布

就在上个月Spring Boot2.0.0 release正式发布,在发布Spring Boot2.0的时候还出现一个小插曲,将Spring Boot2.0同步到Maven仓库的时候出现了错误,然后Spring Boot官方又赶紧把 GitHub 上发布的 v2.0.0.RELEASE 版本进行了撤回。到了下午将问题修复后,又重新进行了上传,至此Spring Boot2.0正式推出!

要知道这是Spring Boot1.0发布4年之后第一次重大修订,因此有多的新功能和特性值得大家期待!在Spring Boot官方博客中我们了解到:Spring Boot2.0版本经历了 17 个月的开发,有 215 个不同的使用者提供了超过 6800 次的提交,并表示非常感谢提供贡献的每一位用户,和所有对这些里程碑版本提供重要反馈的早期采用者。

熟悉Spring Boot/Cloud的技术者们都知道,Spring Boot依赖于Spring,而Spring Cloud又依赖于Spring Boot,因此Spring Boot2.0的发布正式整合了Spring5.0的很多特性,同样后面Spring Cloud最新版本的发布也需要整合最新的Spring Boot2.0内容。

新版本特性

新版本值得关注的亮点有哪些:

基于 Java 8,支持 Java 9

也就是说Spring Boot2.0的最低版本要求为JDK8,据了解国内大部分的互联网公司系统都还跑在JDK1.6/7上,因此想要升级到Spring Boot2.0的同学们注意啦,同时支持了Java9,也仅仅是支持而已。

响应式编程

使用 Spring WebFlux/WebFlux.fn提供响应式 Web 编程支持, Webflux 是一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好,此功能来源于Spring5.0。

Spring Boot2.0也提供对响应式编程的自动化配置,如:Reactive Spring Data、Reactive Spring Security 等

HTTP/2支持

在Tomcat, Undertow 和 Jetty 中均已支持 HTTP/2

对Kotlin支持

引入对 Kotlin 1.2.x 的支持,并提供了一个 runApplication 函数,让你通过惯用的 Kotlin 来运行 Spring Boot 应用程序。

全新的执行器架构

全新的执行器架构,支持 Spring MVC, WebFlux 和 Jersey

支持 Quartz

Spring Boot1.0并没有提供对 Quartz 的支持,之前出现了各种集成方案,Spring Boot2.0给出了最简单的集成方式。

Security

大大的简化了安全自动配置

Metrics

Metrics方面,Spring Boot 2引入了Micrometer,来统一metrics的规范,使得开发人员更好的理解和使用metrics的模块,而不需要关心对接的具体存储是什么。

监控方面

Spring Boot 2 增强了对 Micrometer 的集成。RabbitMQ、JVM 线程和垃圾收集指标会自动进行 instrument 监控,异步控制器(controller)也会自动添加到监控里。通过集成,还可以对 InfluxDB 服务器进行监控。

数据方面

  • db方面,默认引入了HikariCP,替代了之前的tomcat-pool作为底层的数据库连接池, 对比于tomcat-pool, HikariCP拥有更好的性能,总而言之就是提高了db的访问速度
  • JOOQ的支持
  • Redis方面, 默认引入了Lettuce, 替代了之前的jedis作为底层的redis链接方式
  • MongoDB\Hibernate优化

Thymeleaf 3

Spring Boot 2支持了Thymeleaf 3,Thymeleaf 3相对于Thymeleaf 2性能提升可不是一点点,因为2.0的性能确实不咋地,同时也使用了新的页面解析系统。

OAuth 2.0

同时也加入了 对于OAuth 2.0的支持, 使得开发人员更加友好的使用spring-security来完成权限模块的开发

依赖组件的更新

  • Jetty 9.4
  • Tomcat 8.5
  • Flyway 5
  • Hibernate 5.2
  • Gradle 3.4
  • Thymeleaf 3。0

最后还有一个小彩蛋,Spring Boot2.0支持了动态gif的启动logo打印.

技术名词解释

Spring 现在作为Java开源界的老大,它的一举一动都影响着行业的技术方向,在这次发布的 Release Notes中发现有很多的技术都还没有了解过,也分享出来:

WebFlux 是什么?

WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 Flux。Spring webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好。

非阻塞的关键预期好处是能够以小的固定数量的线程和较少的内存进行扩展。在服务器端 WebFlux 支持2种不同的编程模型:

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

默认情况下,Spring Boot 2使用Netty WebFlux,因为Netty在异步非阻塞空间中被广泛使用,异步非阻塞连接可以节省更多的资源,提供更高的响应度。通过比较Servlet 3.1非阻塞I / O没有太多的使用,因为使用它的成本比较高,Spring WebFlux打开了一条实用的通路。

值得注意的是:支持reactive编程的数据库只有MongoDB, redis, Cassandra, Couchbase

HTTP/2

相比 HTTP/1.x,HTTP/2 在底层传输做了很大的改动和优化:

  • HTTP/2 采用二进制格式传输数据,而非 HTTP/1.x 的文本格式。二进制格式在协议的解析和优化扩展上带来更多的优势和可能。
  • HTTP/2 对消息头采用 HPACK 进行压缩传输,能够节省消息头占用的网络的流量。而 HTTP/1.x 每次请求,都会携带大量冗余头信息,浪费了很多带宽资源。头压缩能够很好的解决该问题。
  • 多路复用,直白的说就是所有的请求都是通过一个 TCP 连接并发完成。HTTP/1.x 虽然通过 pipeline 也能并发请求,但是多个请求之间的响应会被阻塞的,所以 pipeline 至今也没有被普及应用,而 HTTP/2 做到了真正的并发请求。同时,流还支持优先级和流量控制。
  • Server Push:服务端能够更快的把资源推送给客户端。例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求。当客户端需要的时候,它已经在客户端了。

JOOQ

JOOQ 是基于Java访问关系型数据库的工具包。JOOQ 既吸取了传统ORM操作数据的简单性和安全性,又保留了原生sql的灵活性,它更像是介于 ORMS和JDBC的中间层。对于喜欢写sql的码农来说,JOOQ可以完全满足你控制欲,可以是用Java代码写出sql的感觉来。

Lettuce

Lettuce是一个可伸缩的线程安全的Redis客户端,用于同步,异步和反应使用。 多个线程可以共享同一个RedisConnection。它利用优秀netty NIO框架来高效地管理多个连接。 支持先进的Redis功能,如Sentinel,集群,流水线,自动重新连接和Redis数据模型。

国内使用Jedis的居多,看来以后要多研究研究Lettuce了。

HikariCP

HikariCP是一个高性能的JDBC连接池。Hikari是日语“光”的意思。可能是目前java业界最快的数据库连接池。

Flyway

Flyway是独立于数据库的应用、管理并跟踪数据库变更的数据库版本管理工具。用通俗的话讲,Flyway可以像SVN管理不同人的代码那样,管理不同人的sql脚本,从而做到数据库同步。

Gson

Gson 是google解析Json的一个开源框架,同类的框架fastJson,JackJson等等

是否选择升级

通过以上内容可以看出Spring Boot2.0相对于1.0增加了很多新特性,并且最重要的是Spring Boot2.0依赖的JDK最低版本是1.8,估计国内大多互联网公司还么这么激进。另外一个新的重大版本更新之后,难免会有一些小Bug什么的,往往需要再发布几个小版本之后,才会慢慢稳定下来。

因此我的建议是,如果不是特别想使用Spring Boot2.0上面提到的新特性,就尽量不要着急进行升级,等Spring Boot2.0彻底稳定下来后再使用。如果想要升级也请先从早期的版本升级到Spring Boot1.5X系列之后,再升级到Spring Boot2.0版本,Spring Boot2.0的很多配置内容和Spring Boot1.0不一致需要注意。

Spring Boot1.0发布之后给我们带来了全新的开发模式,Spring Boot2.0发布标志着Spring Boot已经走向成熟,对Java界带来的变革已经开启!

SpringData 基于SpringBoot快速入门

什么是Spring data?以下是官方的解释。
Spring Data的任务是为数据访问提供一个熟悉的、一致的、基于Spring的编程模型,同时仍然保留底层数据存储的特殊性。
它使得使用数据访问技术、关系和非关系数据库、map-reduce框架和基于云的数据服务变得更加容易。这是一个伞式项目,它包含许多特定于给定数据库的子项目。
本章通过学习SpringData 和SpringBoot 相关知识将面向服务架构(SOA)的单点登录系统(SSO)需要的代码实现。从本课程可以学到Spring两个框架,并实现单点登录。
本章你将掌握 SpringBoot项目的搭建,Starter pom的使用,配置全局文件,核心注解SpringBootApplication 介绍以及单元测试 SpringBootTest注解的使用。
SpringData 的入门使用,Repository接口的使用,查询方法关键字的规则定义,@Query,@Modifying 注解的使用,最后是开发中的建议和遇到的问题。

SpringBoot 知识

SpringBoot 是一个用于简化Spring应用搭建开发的框架。开发过程中,我们经常通过配置xml文件来整合第三方技术。而这些重复整合的工作交给了SpringBoot完成。SpringBoot使用”习惯优于配置”的理念帮我们快速搭建并运行项目。对主流的开发框架能无配置集成。笔者用的开发工具是sts(Spring Tool Suite),其操作和eclipse几乎一致。若没有这个工具,创建Maven项目是一样的。具体操作请参考:如何通过intellij idea构建springboot应用

Starter pom

Starter pom

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.itdragon</groupId> <artifactId>springbootStudy</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>springbootStudy</name> <description>Demo project for Spring Boot</description> <!-- 添加 spring boot的父级依赖,它是SpringBoot项目的标志 spring-boot-starter-parent 它是一个特殊的starter,提供了很多相关的Maven依赖,不用再为version而头疼了,大大简化了开发 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency><!-- 添加web依赖 ,包含spring和springmvc等--> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency><!-- 添加对jpa的支持,包含spring-data和Hibernate --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency><!-- mysql连接的jar包 --> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency><!-- 因为SpringBoot内嵌的tomcat不支持jsp页面,同时SpringBoot也不推荐用jsp --> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <dependency><!-- jsp标签库 --> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> </dependencies> <build> <plugins> <plugin><!-- SpringBoot 编译插件 --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

xml文件中大量的spring-boot-starter-* 的语句。SpringBoot之所以能简化开发的秘密就在这里—— Starter pom
spring-boot-starter-parent :父级依赖,SpringBoot项目的标志。里面封装了很多jar的版本
spring-boot-starter-web :对web项目的支持,其中包含了SpringMVC和tomcat
spring-boot-starter-data-jpa :对JPA的支持,其中包含了常用的SpringData和Hibernate,没有Mybatis哦
spring-boot-starter-tomcat :使用tomcat作为Servlet容器
spring-boot-starter-test :对常用测试框架的支持,如JUnit

配置全局文件

再看看SpringBoot项目全局配置文件 application.properties

# 配置tomcat端口号 server.port=8081 # 配置SpringMVC视图解析器 spring.mvc.view.prefix=/WEB-INF/views/ spring.mvc.view.suffix=.jsp # 配置连接池,默认使用的是tomcat的连接池,但实际很少用tomcat的连接池 spring.datasource.url=jdbc:mysql://localhost:3306/jpa?useUnicode=true&characterEncoding=UTF8 spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver # 配置方言 否则提示:Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect # 自动更新数据库表结构,也可以是 validate | update | create | create-drop spring.jpa.properties.hibernate.hbm2ddl.auto=update # 显示sql语句 spring.jpa.show-sql=true

全局配置文件可以是application.properties 也可以是 application.yml,建议放在resources目录下。

核心注解

最后是SpringBoot HelloWorld项目的入口类,只需要下面一个java文件,执行main方法,即可实现页面的跳转和数据返回的功能。

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @SpringBootApplication public class SpringbootStudyApplication { @RequestMapping("/") public String index() { return "index"; } @RequestMapping("hello") @ResponseBody public String helloWorld() { return "Hello SpringBoot !"; } public static void main(String[] args) { SpringApplication.run(SpringbootStudyApplication.class, args); } }

@SpringBootApplication:是 SpringBoot 的核心注解,一般用在入口类上。它是一个组合注解,其中主要内容有一下三个
@SpringBootConfiguration:是一个类级注释,指示对象是一个bean定义的源,可以理解为xml中的beans,一般和 @Bean 注解一起使用。
@EnableAutoConfiguration:启用 Spring 应用程序上下文的自动配置,试图猜测和配置您可能需要的bean。自动配置类通常采用基于你的 classpath 和已经定义的 beans 对象进行应用。
@ComponentScan:该注解会自动扫描指定包下的全部标有 @Component、@Service、@Repository、@Controller注解 的类,并注册成bean

SpringData入口类

package com.itdragon; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class StartApplication { public static void main(String[] args) { SpringApplication.run(StartApplication.class, args); } }

SpringDataJPA 知识

SpringData 是一个用于简化数据库访问,并支持云服务的开源框架。支持非关系型数据库(NoSQL) 和 关系型数据库。其主要目的是使数据库的访问变得方便快捷。
SpringData JPA 是由Spring提供的简化JPA开发的框架,致力于减少数据访问层的开发量。

POJO层

创建实体类User 表,对应数据库表名是 itdragon_user,id作为自增长的主键,plainPassword是不保存到数据库的明文密码。

import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Transient; /** * 用户实体类 * @author itdragon * */ @Table(name="itdragon_user") @Entity public class User { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; // 自增长主键 private String account; // 登录的账号 private String userName; // 注册的昵称 @Transient private String plainPassword; // 登录时的密码,不持久化到数据库 private String password; // 加密后的密码 private String salt; // 用于加密的盐 private String iphone; // 手机号 private String email; // 邮箱 private String platform; // 用户来自的平台 private String createdDate; // 用户注册时间 private String updatedDate; // 用户最后一次登录时间 // 省略get/set/toString 方法 }

Repository接口层

创建UserRepository,这是SpringData 的核心知识点,我们先看代码

import java.util.List; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import com.itdragon.pojo.User; /** * 核心知识:SpringData Repository 接口 * * CrudRepository 接口提供了最基本的对实体类的添删改查操作  * - T save(T entity); //保存单个实体  * - T findOne(ID id); // 根据id查找实体         * - void delete(ID/T/Iterable); // 根据Id删除实体,删除实体,批量删除 * PagingAndSortingRepository 提供了分页与排序功能 * - <T, ID extends Serializable> // 第一个参数传实体类,第二个参数传注解数据类型 * - Iterable<T> findAll(Sort sort); // 排序 * - Page<T> findAll(Pageable pageable); // 分页查询(含排序功能) * JpaSpecificationExecutor 提供了Specification(封装 JPA Criteria查询条件)的查询功能 * - List<T> findAll(Specification<T> spec); * - Page<T> findAll(Specification<T> spec, Pageable pageable); * - List<T> findAll(Specification<T> spec, Sort sort); * * 开发建议 * 1. 这里值列出的是常用方法 * 2. CrudRepository 中的findAll() 方法要慎用。当数据库中数据量大,多线程脚本调用findAll方法,系统可能会宕机。 * 3. CrudRepository 中的deletAll()方法要慎用。这是物理删除,现在企业一般采用逻辑删除。 * 4. PagingAndSortingRepository 和 JpaSpecificationExecutor 能满足大部分业务需求。 */ public interface UserRepository extends PagingAndSortingRepository<User, Long>, JpaSpecificationExecutor<User>{ /** * 重点知识:SpringData 查询方法定义规范 * * 1. 查询方法名一般以 find | read | get 开头,建议用find * findByAccount : 通过account查询User * account是User的属性,拼接时首字母需大写 * 2. 支持的关键词有很多比如 Or,Between,isNull,Like,In等 * findByEmailEndingWithAndCreatedDateLessThan : 查询在指定时间前注册,并以xx邮箱结尾的用户 * And : 并且 * EndingWith : 以某某结尾 * LessThan : 小于 * * 注意 * 若有User(用户表) Platform(用户平台表) 存在一对一的关系,且User表中有platformId字段 * SpringData 为了区分: * findByPlatFormId 表示通过platformId字段查询 * findByPlatForm_Id 表示通过platform实体类中id字段查询 * * 开发建议 * 表的设计,尽量做单表查询,以确保高并发场景减轻数据库的压力。 */ // 1 通过账号查用户信息 User findByAccount(String account); // 2 获取指定时间内以xx邮箱结尾的用户信息 List<User> findByEmailEndingWithAndCreatedDateLessThan(String email, String createdDate); /** * 重点知识:使用 @Query 注解 * * 上面的方法虽然简单(不用写sql语句),但它有最为致命的问题-----不支持复杂查询,其次是命名太长 * 1. 使用@Query 注解实现复杂查询,设置 nativeQuery=true 使查询支持原生sql * 2. 配合@Modifying 注解实现创建,修改,删除操作 * 3. SpringData 默认查询事件为只读事务,若要修改数据则需手动添加事务注解 * * 注意 * 若@Query 中有多个参数,SpringData 提供两种方法: * 第一种 ?1 ... ?2 要求参数顺序一致 * 第二种 :xxx ... :yyy xxx 和 yyy 必须是实体类对应的属性值,不要求参数顺序但参数前要加上@Param("xxx") * 模糊查询可使用 %xxx% * * 开发建议 * 1. 参数填写的顺序要保持一致,不要给自己添加麻烦 * 2. 建议使用@Query,可读性较高 */ // 3 获取某平台活跃用户数量 @Query(value="SELECT count(u.id) FROM User u WHERE u.platform = :platform AND u.updatedDate <= :updatedDate") long getActiveUserCount(@Param("platform")String platform, @Param("updatedDate")String updatedDate); // 4 通过邮箱或者手机号模糊查询用户信息 @Query(value="SELECT u FROM User u WHERE u.email LIKE %?1% OR u.iphone LIKE %?2%") List<User> findByEmailAndIhpneLike(String email, String iphone); // 5 修改用户邮箱 @Modifying @Query("UPDATE User u SET u.email = :email WHERE u.id = :id") void updateUserEmail(@Param("id") Long id, @Param("email") String email); }

代码中共有五个方法,每个方法都包含了很多的知识点。
方法一和方法二主要介绍的是SpringData关键字的用法。
关键字的解析
这里用findByPlatFormId() 方法来介绍SpringData 解析查询方法的流程。
第一步:去除关键字findBy
第二步:剩下的PlatFormId 首字母小写并在User对象中找是否有该属性,若有则查询并结束。若没有则第三步
第三步:platFormId,从右到左截取到第一个大写字母,再判断剩下的platForm是否为User对象,如此循环直到结束为止。
级联属性区分
若查询的属性是实体类,为了避免误会和冲突,用”_”表示属性中的属性
查询分页排序
若findByPlatFormId() 方法想要排序或者分页,是可以在后面加Pageable,Sort参数。
findByPlatFormId(String platFormId, Pageable pageable)
findByPlatFormId(String platFormId, Sort sort)
其他的关键字

方法三到方法五主要介绍的是 @Query 注解的使用。
传参方式
索引参数:?n ,n从1开始,表示第一个参数。方法传入的参数的照顺序和个数要和 n 保持一致。
命名参数::key ,传参必须有 @Param(“key”) 注解修饰
原生的sql
@Query 注解支持本地查询,即用原生的sql语句。如:@Query(value=”xxxx”, nativeQuery=true)
Modifying
若直接执行修改操作,SpringDataJPA 会提示错误信息 Executing an update/delete query 。是因为Spring Data 默认所有的查询均声明为只读事务。
所以我们要在Service层添加 @Transactional 注解。

SpringDataJPA 核心知识Repository接口
Repository: 空接口,标识作用,表明任何继承它的均为Repository接口类
CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法
PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法
JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法
JpaSpecificationExecutor: 不属于Repository体系,实现一组 JPA Criteria 查询相关的方法
PagingAndSortingRepository 和 JpaSpecificationExecutor 基本满足企业中大部分的需求。也可以自定义Repository,只需继承 JpaRepository 即可具备了通用的数据访问控制层的能力。
进入各自接口类中,使用快捷键 Ctrl + o 即可查看当前类的所有方法,所以这里就不贴出来了。

Service层

创建UserService 并加上注解 @Transactional

import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.itdragon.common.ItdragonResult; import com.itdragon.pojo.User; import com.itdragon.repository.UserRepository; @Service @Transactional public class UserService { @Autowired private UserRepository userRepository; public ItdragonResult registerUser(User user) { // 检查用户名是否注册,一般在前端验证的时候处理,因为注册不存在高并发的情况,这里再加一层查询是不影响性能的 if (null != userRepository.findByAccount(user.getAccount())) { return ItdragonResult.build(400, ""); } userRepository.save(user); // 注册成功后选择发送邮件激活。现在一般都是短信验证码 return ItdragonResult.build(200, ""); } public ItdragonResult editUserEmail(String email) { // 通过Session 获取用户信息, 这里假装从Session中获取了用户的id,后面讲解SOA面向服务架构中的单点登录系统时,修改此处代码 FIXME long id = 3L; // 添加一些验证,比如短信验证 userRepository.updateUserEmail(id, email); return ItdragonResult.ok(); } }

单元测试

SpringBoot 的单元测试,需要用到 @RunWith 和 @SpringBootTest注解,代码注释中有详细介绍。

import java.util.List; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Order; import org.springframework.data.jpa.domain.Specification; import org.springframework.test.context.junit4.SpringRunner; import com.itdragon.StartApplication; import com.itdragon.common.ItdragonUtils; import com.itdragon.pojo.User; import com.itdragon.repository.UserRepository; import com.itdragon.service.UserService; /** * @RunWith 它是一个运行器 * @RunWith(SpringRunner.class) 表示让测试运行于Spring测试环境,不用启动spring容器即可使用Spring环境 * @SpringBootTest(classes=StartApplication.class) 表示将StartApplication.class纳入到测试环境中,若不加这个则提示bean找不到。 * * @author itdragon * */ @RunWith(SpringRunner.class) @SpringBootTest(classes=StartApplication.class) public class SpringbootStudyApplicationTests { @Autowired private UserService userService; @Autowired private UserRepository userRepository; @Test public void contextLoads() { } @Test // 测试注册,新增数据 public void registerUser() { User user = new User(); user.setAccount("gitLiu"); user.setUserName("ITDragonGit"); user.setEmail("itdragon@git.com"); user.setIphone("12349857999"); user.setPlainPassword("adminroot"); user.setPlatform("github"); user.setCreatedDate(ItdragonUtils.getCurrentDateTime()); user.setUpdatedDate(ItdragonUtils.getCurrentDateTime()); ItdragonUtils.entryptPassword(user); userService.registerUser(user); } @Test // 测试SpringData 关键字 public void findByEmailEndingWithAndCreatedDateLessThan() { List<User> users = userRepository.findByEmailEndingWithAndCreatedDateLessThan("qq.com", ItdragonUtils.getCurrentDateTime()); System.out.println(users.toString()); } @Test // 测试SpringData @Query 注解和传多个参数 public void getActiveUserCount() { long activeUserCount = userRepository.getActiveUserCount("weixin", ItdragonUtils.getCurrentDateTime()); System.out.println(activeUserCount); } @Test // 测试SpringData @Query 注解,传多个参数 和 like 查询 public void findByEmailAndIhpneLike() { List<User> users = userRepository.findByEmailAndIhpneLike("163.com", "6666"); System.out.println(users.toString()); } @Test // 测试SpringData @Query 注解 和 @Modifying 注解 public void updateUserEmail() { /** * org.springframework.dao.InvalidDataAccessApiUsageException:Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query * userRepository.updateUserEmail(3L, "update@email.com"); */ userService.editUserEmail("update@email.com"); } @Test // 测试SpringData PagingAndSortingRepository 接口 public void testPagingAndSortingRepository() { int page = 1; // 从0开始,第二页 int size = 3; // 每页三天数据 PageRequest pageable = new PageRequest(page, size, new Sort(new Order(Direction.ASC, "id"))); Page<User> users = userRepository.findAll(pageable); System.out.println(users.getContent().toString()); // 当前数据库中有5条数据,正常情况可以打印两条数据,id分别为4,5 (先排序,后分页) } @Test // 测试SpringData JpaSpecificationExecutor 接口 public void testJpaSpecificationExecutor(){ int pageNo = 1; int pageSize = 3; PageRequest pageable = new PageRequest(pageNo, pageSize); Specification<User> specification = new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { Predicate predicate = cb.gt(root.get("id"), 1); // 查询id 大于 1的数据 return predicate; } }; Page<User> users = userRepository.findAll(specification, pageable); System.out.println(users.getContent().toString()); // 当前数据库中有5条数据,正常情况可以打印一条数据,id为5 } }

可能存在的问题
项目启动时提示 Unknown character set: ‘utf8mb4’

导致的原因可能是mysql服务器版本安装不正确,解决方法有两种。
第一种:换mysql-connector-java jar包版本为 5.1.6 (不推荐); 当前jar版本为 5.1.44。
第二种:重装mysql版本,当前最新版本是5.7。教程都准备好了。
https://www.cnblogs.com/sshoub/p/4321640.html (mysql安装)
http://blog.csdn.net/y694721975/article/details/52981377 (mysql卸载)

SpringBoot 连接池配置疑惑

我们只是在全局配置文件中设置了相关值,就完成了连接池的配置,想必大家都有所疑惑。其实当我们在pom.xml文件中加入spring-boot-starter-data-jpa 依赖时,SpringBoot就会自动使用tomcat-jdbc连接池。
当然我们也可以使用其他的连接池。
https://www.cnblogs.com/gslblog/p/7169481.html (springBoot数据库连接池常用配置)
https://www.cnblogs.com/xiaosiyuan/p/6255292.html (SpringBoot使用c3p0)

STS工具 ctrl + shift + o 重新导包快捷键失效

解决方法:preference -> general -> keys ,找到 Organize Imports ,然后 在 When 里面选择 Editing Java Source

总结

1 SpringDataJPA 是简化JPA开发的框架,SpringBoot是简化项目开发的框架。
2 spring-boot-starter-parent 是SpringBoot项目的标志
3 SpringBootApplication 注解是SpringBoot项目的入口
4 SpringData 通过查询关键字和 @Query注解实现对数据库的访问
5 SpringData 通过PagingAndSortingRepository 实现分页,排序和常用的crud操作

到这里 SpringData 基于SpringBoot快速入门就结束了,如果有什么问题请指教。

如何通过intellij idea构建一个springboot应用

本教程将介绍使用IntelliJ idea启动和创建SpringBoot应用程序。这篇文章的主要目的是帮助任何新手编写SpringBoot应用程序在IntelliJ idea引导。为进一步阅读,一定要签出Spring的指导部分其他教程编写SpringBoot启动应用程序。

创建一个新的SpringBoot项目

Next enter your Maven project properties including name, project type, packaging, Java version, group ID, artifact ID and version for your application.  Complete this step by entering a project description and root package.  Then click Next

.

Next select your Spring Boot version and any Spring Framework dependency your project will require.  All of Spring’s projects are available from web, security, cloud, database and so on.  Click Next once you’ve selected all your dependencies.

The final step is entering your IntelliJ IDEA project settings.  Click Finish when complete.

At this point, IntelliJ may prompt you to add your pom.xml as a managed Maven project.  Click Add as Maven Project to dismiss this prompt and have your Maven dependencies automatically synced with your IntelliJ build path.  You’ll see that using Spring Boot, there’s many dependencies you don’t have to add yourself thanks to Spring Boot’s starter POMs.

EXPLORING THE PROJECT

Open up your project’s pom.xml and you’ll see what we just built.

<?xml version=”1.0″ encoding=”UTF-8″?>
<project xmlns=”http://maven.apache.org/POM/4.0.0″ xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd”>
<modelVersion>4.0.0</modelVersion>

<groupId>com.patrickgrimard</groupId>
<artifactId>spring-boot-hello-world</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>spring-boot-hello-world</name>
<description>Sample Spring Boot Project</description>

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

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

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

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

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

[AdSense-B]

THE GENERATED APPLICATION CLASS

@SpringBootApplication

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

}

This class although very simple looking has a lot going on.  First, it’s been annotated with the @SpringBootApplicationannotation. This declares the class as a @Configuration class for your application.  You can define additional @Bean definitions in it if you like.  It adds @ComponentScan to locate any other beans you define in your project.  Finally it adds @EnableAutoConfiguration in order to enable automatic configuration of your Spring application context by determining what you have available on your classpath.

CREATE A REST CONTROLLER

The next thing you may want to do at this point is add a controller to your application to handle servlet requests.

@RequestMapping(“/api”)

@RestController

public class WidgetController {

@RequestMapping(method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})

public Widget index() {

return new Widget(“green”, 10, 7); } }

The first annotation on this controller is @RequestMapping("/api") and it tells us that this controller will handle all requests beginning with /api in our application.  The second annotation @RestController tells Spring two things.  First this class is a controller bean that should be managed by Spring and will be picked up by component scanning.  Secondly, all methods inherently use @ResponseBody semantics which means the return value will become the body of our servlet response.

I’ve only defined a single method in this controller responding to the GET HTTP method, and it produces JSON output.  My method merely returns a new instance of the Widget class.  Your controller will return something else that’s related to your business case.

RUNNING YOUR APPLICATION

At this point, you can run your application simply by right clicking inside your application class and selecting Run SpringBootHelloWorldApplication.main().  Then browse to http://localhost:8080/api and you’ll see the response from the controller and request mapping we just created.

WRAPPING UP!

By now, you should know how to create a new Spring Boot application from scratch using IntelliJ, it’s really quite simple with the introduction of Spring Initializr built into the IDE.  I’ve also provided a very brief introduction to writing a Spring Boot app to get you started.

If you’d like to view the source code of this application, it’s available on Github https://github.com/pgrimard/spring-boot-hello-world.