-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.json
More file actions
1 lines (1 loc) · 101 KB
/
search.json
File metadata and controls
1 lines (1 loc) · 101 KB
1
[{"title":"《SpringBoot常用注解》","url":"/2023/02/15/SpringCloud笔记——遇到的问题/","content":"\n# SpringCloud项目本身\n\n## iml文件引发的启动报错\n\n### 起因\n\n在学习SpringCloud Ribbon的时候,创建了多个微服务项目,并且有一个项目专门给其他项目提供依赖、常量类,但是某一个项目一直启动不起来,一直报`没有为模块指定输出路径`(常量类模块)的问题。\n\n### 报错原因\n\n比对了报错项目和其他项目的不同之处,发现了创建的iml文件不太一样\n\n正常项目:\n\n```iml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module version=\"4\">\n <component name=\"FacetManager\">\n <facet type=\"Spring\" name=\"Spring\">\n <configuration />\n </facet>\n </component>\n</module>\n```\n\n异常项目:\n\n```iml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module version=\"4\">\n <component name=\"NewModuleRootManager\">\n <content url=\"file://$MODULE_DIR$\">\n <sourceFolder url=\"file://$MODULE_DIR$/src/main/java\" isTestSource=\"false\" />\n <sourceFolder url=\"file://$MODULE_DIR$/src/main/resources\" isTestSource=\"true\" />\n </content>\n </component>\n</module>\n```\n\n去了解了一下,iml文件是IDEA自动创建的文件,`information of module`,就是IDEA的项目标识文件,没有这个文件IDEA会无法识别项目(可以去把这个文件删了然后刷新一下maven试试),一般情况下我们不用去理会。\n\n### 解决办法\n\n~~二般情况下~~,解决办法也很简单\n\n```iml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module version=\"4\">\n <component name=\"NewModuleRootManager\">\n <facet type=\"Spring\" name=\"Spring\">\n <configuration />\n </facet>\n </component>\n</module>\n```\n\n把这些内容替换掉就行了\n\n如果这个方法解决不了,并且你的异常项目没有启动类,尝试重新创建项目,启动一次,再把启动类删掉,初始化一下输出目录\n","tags":["SpringCloud"]},{"title":"《Java后端面试大全》","url":"/2023/02/12/Java开发面试题整理/","content":"\nJDK、JRE、JVM之间的区别和联系\n\n+ JDK是Java语言的软件开发工具包,它提供了Java编译、运行所需的各种工具和资源,包括Java编译器,运行时环境以及常用的Java类库等\n+ JRE是Java的运行环境,包含了JVM的标准实现和Java的核心类库,并不是开发环境\n+ JVM是Java运行时的环境,即Java虚拟机,它是Java实现跨平台最核心的部分,负责运行字节码文件\n\n--------------------------------------------------------------------------------------------------------\n\nhashcode()与equals()的联系\n\n+ hashcode和equals都可以作为判断两个对象是否相同的判断标准,:\n + 如果两个对象的hashcode不同,那两个对象一定不同\n + 如果hashcode相同,两个对象也不一定相同\n + 如果两个对象相同,那么它们的hashcode也一定相同\n\n在Java一些集合类或者工具包的视线中,有判断对象是否相等的一些工具函数,里面通常会先去判断hashcode,如果hashcode相同,再去使用equals进行比较\n\n--------------------------------------------------------------------------------------------------------\n\nString、StringBuffer、StringBuilder区别\n\n+ String、StringBuffer、StringBuilder三个类都提供了\n\n+ 在Java中,String作为典型的引用型变量,是不可变的,如果改变变量的值,只会改变堆中指针的指向,并不会改变常量池中的值\n+ 因此,Java提供了两个可变字符串类StringBuffer和StringBuilder\n\n","tags":["Java"]},{"title":"《SpringCloud》——详解Ribbon","url":"/2022/11/24/SpringCloud笔记——Ribbon/","content":"\n~~Ribbon来喽~~\n\n# 相关文档\n\n[Netflix Ribbon]: https://github.com/Netflix/ribbon\n\n# 什么是Ribbon\n\nRibbon是一个在Cloud中经过实战测试的IPC库,提供以下功能:\n\n+ 客户端负载均衡\n+ 容错性\n+ 异步和反应模型中的多协议支持(HTTP、TCP、UDP)\n+ 缓存和批处理\n\nSpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具,能够自动基于某种规则去调用这些服务,也可以使用我们自己的负载均衡算法。\n\nRibbon虽然是一个组件,但是并不需要像注册中心那样独立部署,它几乎存在于每一个SpringCloud中,因为IPC调用基本都是通过它来实现。\n\n# 负载均衡\n\n负载均衡在微服务架构中是一个很重要的组件,是对系统的高可用、环节网络压力和服务器扩容的重要手段之一。\n\n负载均衡又分为客户端负载均衡和服务端负载均衡,而我们说的一般是指**服务端负载均衡**,架构如下:\n\n\n\n## 区别\n\n客户端和服务端的负载均衡,最大的不同点就是服务清单存储的位置:\n\n+ 在客户端负载均衡中,每一个客户端都维护者一份自己要访问的服务清单\n+ 而在服务端中,这些服务器的清单来自于注册中心,比如*Eureka*\n\n和服务端一样,客户端也需要心跳去维护服务清单的健康性\n\n<hr>\n\n在SpringCloud实现的服务治理框架中,默认会对各个服务治理框架的Ribbon自动化整合配置,比如Eureka的`org.springframework.cloud.netflix.ribbon.eureka.RibbinEurekaAutoConfiguration`\n\n## 使用\n\n通过Spring Cloud Ribbon的封装,我们使用负载均衡会变得很简单:\n\n1. 服务提供者只需要启动多个服务实例并且注册到一个注册中心或是多个相关联的注册中心\n2. 服务消费者直接通过调用被`@LoadBalanced`注解修饰过的`RestTemplate`来实现服务接口的调用\n\n## 示例\n\n首先启动多个其他实例,并且注册到注册中心\n\n\n\n将RestTemplate交给Spring管理,并且加上`@LoadBalanced`注解用于开启负载均衡\n\n```java\n@Bean\n@LoadBalanced\npublic RestTemplate restTemplate(){\n return new RestTemplate();\n}\n```\n\n使用RestTemplate请求服务\n\n```java\npublic String getMethod(){\n ResponseEntity<String> responseEntity = restTemplate.getForEntity(\n \"http://userInfoApplication/v1/user/userInfo/getMethod/我是参数\", String.class\n );\n return responseEntity.getBody();\n }\n```\n\n**注**:由于Ribbon已经被集成在spring-cloud-starter-netflix-eureka-client中了,不需要单独引入spring-cloud-starter-netflix-ribbon,否则容易造成依赖冲突\n\n------\n\n# 源码剖析\n\n在使用`RestTemplate`的时候,我们会发现,这是Spring自己提供的包,和Ribbon并没有什么太大关系,负载均衡是通过`@LoadBalanced`注解来实现的。\n\n查看LoadBalanced的源码,里面有一段注释\n\n```txt\n/**\n * Annotation to mark a RestTemplate or WebClient bean to be configured to use a\n * LoadBalancerClient.\n * @author Spencer Gibb\n */\n```\n\n从注释中可以了解到这个注解是用来给RestTemplate或者WebClient做标记,以使用LoadBalancerClient的。\n\n再去查看LoadBalancerClient的源码,里面有两个方法:\n\n+ T excute(String serviceId, LoadBalancerRequest request):从负载均衡器中挑选出服务实例来执行\n+ URL reconstructURL:为系统构建合适的URL用来请求\n\n在老版本的Cloud中,还有一个方法,`ServiceInstance choose(String serviceId)`,这个方法会根据传入的`ServiceId`,从服务列表中挑选一个服务实例,但是这个方法在后面被独立到了`ServiceInstanceChooser`类中\n\n顺着`LoadBalancerClient`的包下,可以找到一个`LoadBalancerInterceptor`类,这里面注入了`LoadBalancerClient`方法,而`LoadBalancerAutoConfiguration`类有新建了`LoadBalancerInterceptor`类,从这个类的注释可以看到,这是用来自动配置客户端负载均衡的\n\n这个类上面有几个注解:\n\n```java\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnClass(RestTemplate.class)\n@ConditionalOnBean(LoadBalancerClient.class)\n@EnableConfigurationProperties(LoadBalancerClientsProperties.class)\n```\n\n从注解里我们可以知道,实现负载均衡需要满足以下两个条件:\n\n+ @ConditionalOnClass(RestTemplate.class):RestTemplate必须在当前的工程环境中\n+ @ConditionalOnBean(LoadBalancerClient.class):在当前工程中必须有实现了`@LoadBalancerClient`的Bean\n\n往下翻可以看到这样一段代码\n\n```java\n\t@LoadBalanced\n\t@Autowired(required = false)\n\tprivate List<RestTemplate> restTemplates = Collections.emptyList();\n\n\t@Bean\n\tpublic SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(\n\t\t\tfinal ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {\n\t\treturn () -> restTemplateCustomizers.ifAvailable(customizers -> {\n\t\t\tfor (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {\n\t\t\t\tfor (RestTemplateCustomizer customizer : customizers) {\n\t\t\t\t\tcustomizer.customize(restTemplate);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {\n\t\treturn restTemplate -> {\n\t\t\tList<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());\n\t\t\tlist.add(loadBalancerInterceptor);\n\t\t\trestTemplate.setInterceptors(list);\n\t\t};\n\t}\n\n\t@Configuration(proxyBeanMethods = false)\n\t@Conditional(RetryMissingOrDisabledCondition.class)\n\tstatic class LoadBalancerInterceptorConfig {\n\n\t\t@Bean\n\t\tpublic LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,\n\t\t\t\tLoadBalancerRequestFactory requestFactory) {\n\t\t\treturn new LoadBalancerInterceptor(loadBalancerClient, requestFactory);\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\tpublic RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {\n\t\t\treturn restTemplate -> {\n\t\t\t\tList<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());\n\t\t\t\tlist.add(loadBalancerInterceptor);\n\t\t\t\trestTemplate.setInterceptors(list);\n\t\t\t};\n\t\t}\n\n\t}\n```\n\n从这里可以看到在该自动化配置中,主要做了三件事:\n\n+ 创建了一个`LoadBalancerInterceptor`的Bean,用于实现客户端发起请求时进行拦截,从而实现客户端负载均衡\n+ 创建了一个`RestTemplateCustomizer`的Bean用于给`RestTemplate`添加`LoadBalancerInterceptor`拦截器\n+ 维护了一个被`@LoadBalanced`注解修饰过的RestTemplate对象列表,并在这里初始化,通过调用`RestTemplateCustomizer`的实例来给需要客户端负载均衡的`RestTemplate`添加负载均衡拦截器\n\n------\n\n# 初次使用可能存在的问题\n\n## java.net.UnknownHostException\n\n初次使用Ribbon做负载均衡,利用RestTemplate请求接口可能会出现这个报错,这可能是因为RestTemplate没有用`@LoadBalanced`注解进行修饰,这个注解可以开启客户端的负载均衡\n\n### 解决方案\n\n```java\n @Bean\n @LoadBalanced\n public RestTemplate restTemplate(){\n return new RestTemplate();\n }\n```\n\n将RestTemplate封装成一个bean交给Spring进行管理,使用`@LoadBalanced`注解进行修饰\n\n## No instances available for userInfoApplication\n\n这里可能有几个原因\n\n### 原因一\n\n服务提供方没有注册到注册中心,可以打开注册中心的管理界面看一下\n\n\n\n比如这里`FILEAPPLICATION`是服务调用方,`USERINFOAPPLICATION`是服务提供方,双方都需要注册到注册中心\n\n### 原因二\n\n如果注册中心上有双方的数据了,看一下服务调用方的`properties`配置文件,有没有把获取服务的配置禁用\n\n```yaml\neureka:\n client:\n fetch-registry: true # 从注册中心获取注册信息,默认true\n```\n\n这里默认是true,不要改为false\n\n### 原因三\n\n重复引入ribbon造成的依赖冲突,在springcloud中,ribbon已经被集成在`spring-cloud-starter-netflix-eureka-client`中了,不需要单独引入`spring-cloud-starter-netflix-ribbon`,springcloud在2020.0.0之后,将依赖改成了`spring-cloud-starter-loadbalancer`\n\n\n\n","tags":["SpringCloud"]},{"title":"《SpringCloud》——详解Eureka","url":"/2022/11/17/SpringCloud笔记——Eureka/","content":"\n火速滚回来填坑\n\n# 相关资源\n\nSpring官方对于Eureka的介绍:\n\n[SpringCloud Netflix]: https://docs.spring.io/spring-cloud-netflix/docs/3.1.4/reference/html/\n\n项目源码地址:\n\n[Eureka]: https://github.com/Netflix/eureka\n\n# 什么是Eureka\n\nEureka是一种基于RESTful的服务,用于定位服务,可以说是微服务架构中最核心也是最为基础的模块,主要用于实现各个微服务的自动注册和服务发现,它基于REST服务,采用了CS架构,也就是说,Eureka由两个组件组成:**服务端**(Server)和**客户端**(Client)组成。\n\nEureka由Netflix公司开发,SpringCloud对其进行了二次封装用于实现SpringBoot的服务发现和服务注册。\n\n# 为什么要用Eureka\n\n在最开始构建微服务时,我们的服务可能并不多,可以直接通过一些静态配置来完成服务的调用。\n\n比如,一开始我们有A B两个服务,A服务需要调用B来完成一系列的业务操作,为了实现B服务的高可用,我们对B服务进行了横向拓展,无论是采用服务器负载均衡还是客户端负载均衡,我们都需要维护B服务的具体实例,但是随着业务的发展,我们的服务会越来越多,系统功能也会越来越复杂,静态配置就会变得难以维护,为了解决这种问题,服务治理应运而生。\n\n# 服务端与客户端\n\nEureka分为服务端和客户端\n\n## 服务端\n\n我们也称为服务的注册中心,如同其他注册中心一样,Eureka也支持高可用配置,依托于一致性提供良好的服务实例可用性,额可以用谷底多种不同的故障场景。\n\n## 客户端\n\n主要用于服务的注册与发现,客户端服务通过注解和参数的方式配置,嵌入在客户端应用程序的代码中,在服务运行时,客户端会向注册中心注册自身提供的服务,并且周期性发送心跳来更新它的服务续约,同时也能够从服务端查询当前注册的服务信息并把他们缓存到本地并周期性的刷新服务状态。\n\n# 代码实现\n\n## 搭建服务注册中心\n\n### pom依赖\n\n```xml\n<dependency>\n <groupId>org.springframework.cloud</groupId>\n <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>\n</dependency>\n```\n\n### 启动类\n\n通过`@EnableEurekaServer`注解启动一个服务注册中心并提供给其他应用进行对话\n\n```java\n@SpringBootApplication\n@EnableEurekaServer\npublic class JiangEurekaApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(JiangEurekaApplication.class, args);\n }\n\n}\n```\n\n### 配置文件\n\n在默认配置下,注册中心会将自己作为客户端尝试注册它自己,所以我们需要禁用他的客户端注册行为,增加如下配置\n\n```yaml\nserver:\n port: 8080\n\neureka:\n instance:\n hostname: localhost\n client:\n service-url:\n defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka\n register-with-eureka: false\n fetch-registry: false\n enabled: true\n healthcheck:\n enabled: true\n```\n\n#### 配置详解\n\n+ eureka\n + instance\n + hostname:主机实例名\n + client\n + server-url\n + defaultZone:eureka服务器地址,如果注册中心为集群形式,多个注册中心以逗号分隔\n + register-with-eureka:是否需要向注册中心注册自己\n + fetch-register:是否需要向注册中心检索服务\n\n### 运行\n\n运行好后,可以直接去`localhost:8080`查看,但是上面还没注册任何服务,如图\n\n\n\n## 注册服务提供者\n\n### pom依赖\n\n```xml\n<dependency>\n <groupId>org.springframework.cloud</groupId>\n <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>\n</dependency>\n```\n\n### 启动类\n\n同样,Eureka客户端也需要加上一个注解`@EnableEurekaClient`\n\n```java\n@SpringBootApplication\n@EnableEurekaClient\npublic class UserInfoApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(UserInfoApplication.class, args);\n }\n\n}\n```\n\n### 配置文件\n\n```yaml\n# eureka客户端配置\neureka:\n instance:\n hostname: ${spring.application.name}\n instance-id: localhost:${spring.application.name}:${server.port}\n client:\n enabled: true #启用eureka\n service-url:\n defaultZone: http://localhost:8080/eureka\n```\n\n#### 配置详解\n\n部分在注册中心的配置中有,这里不再讲述\n\n+ eureka\n + instance\n + instance-id:注册到eureka的实例ID\n\n### 运行\n\n然后到`http://localhost:8080`上,可以看到`userInfoApplication`服务已经注册上去了\n\n\n\n\n\n<center>2022-11-21</center>\n\n------\n\n# 高可用注册中心\n\n在微服务架构中,我们经常会考虑到一个问题:这个服务挂了怎么办?\n\n常用的解决办法就是进行高可用部署,就是把这个服务横向拓展,一台不够部署两台,这样的情况下,一台服务挂了,还可以用另一台暂时撑一下\n\n注册中心既然是微服务架构的核心组件,那我们肯定也需要对其进行高可用部署\n\n## 高可用实现\n\n在Eureka的设计中,每个服务既是服务的提供方,也是服务的消费方,注册中心也不例外,想要对注册中心进行高可用部署,那就要让Eureka也注册到自身\n\n还记得前面作为单节点的服务注册中心,通过设置下面这两个参数,让服务注册中心不注册自身\n\n```properties\neureka.client.register-with-eureka=false\neureka.client.fetch-register=false\n```\n\n将Eureka高可用部署,实际上就是将Eureka作为一个服务向集群中的所有实例注册,这样几个Eureka服务就可以形成一个集群,相互同步,相互注册,达到高可用的效果,所以这两个配置当然要改成true\n\n具体实现:\n\n首先肯定是要修改配置文件\n\n别问为什么不新开一个服务,懒\n\n```yaml\nspring:\n application:\n name: JiangEurekaApplication\nserver:\n# port: 8761\n port: 8762\n# port: 8763\n\neureka:\n instance:\n# hostname: Eureka8761\n hostname: Eureka8762\n# hostname: Eureka8763\n client:\n register-with-eureka: true # 是否注册\n fetch-registry: true # 查询获取注册中心服务信息\n service-url:\n # 注册中心地址,如果有多个Eureka,可以相互注册\n# defaultZone: http://Eureka8762:8762/eureka/,http://Eureka8763:8763/eureka/\n defaultZone: http://Eureka8761:8761/eureka/,http://Eureka8763:8763/eureka/\n# defaultZone: http://Eureka8761:8761/eureka/,http://Eureka8762:8762/eureka/\n```\n\n三台都启动起来,进入`localhost:8761`,你就能发现诡异的一幕\n\n\n\n虽然集群是注册上了,但是示例一个都没有,注意我们这里`service-url`属性,用的并不是`localhost`或者`127.0.0.1`,所以要改一下host文件做一下映射\n\n```txt\n127.0.0.1 Eureka8761\n127.0.0.1 Eureka8762\n127.0.0.1 Eureka8763\n```\n\nhost文件加上这三行\n\n在进入到`localhost:8761`或者`Eureka8761:8761`\n\n当然,8762和8763端口也都是一样\n\n\n\n高可用Eureka注册成功\n\n# Eureka详解\n\n这里是一些理论的东西,不感兴趣的可以直接跳过\n\n## 基础架构\n\nEureka的三大核心要素:\n\n+ 服务注册中心\n+ 服务提供者\n+ 服务消费者\n\n服务消费者需要配合Ribbon一起使用,下一章再讲\n\n大多数情况下,服务提供者同时也是服务消费者\n\n## 服务通信机制\n\n来看一下Eureka各个节点之间是如何通信的\n\n\n\n如图,有两个Eureka服务注册中心,他们相互注册形成了一个高可用集群\n\n服务提供者拥有两个实例,分别注册在 1 和 2 两台注册中心上,还有两个服务消费者,也分别指向了一个注册中心\n\n### 服务注册\n\n服务提供者在服务启动的时候,会向Eureka发送REST请求,将自己注册到`Eureka Server`上,同时也会附带一些自身服务的元数据,Eureka接收到这个REST请求后,会将元数据信息存储在一个双层结构Map中,第一层的key是服务名,第二层的key是服务实例名。\n\n> 注意:配置中eureka.client.register-with-eureka一定要开启,否则服务启动不会进行注册操作\n\n### 服务同步\n\n上图中,两个不同的服务提供者分别注册到了两个不同的注册中心,这时,由于两台Eureka服务器之间相互注册,当服务提供者将请求发送到其中一台服务器时,注册请求也会被转发给另一台服务器,从而实现两台Eureka之间的同步\n\n### 服务续约\n\n在服务正常停止时,服务会向Eureka发送一次REST请求,告诉`Eureka Service`,“我走了,不要想我”,Eureka就会将该服务踢出注册中心,但是为了防止某些极端情况发生,比如服务器非正常关闭,`Eureka Server`会一直维持着该服务的实例,Eureka提供了服务续约\n\n服务提供者会维护一个心跳,用来告诉Eureka:“我还活着”,防止Eureka将服务提供者踢出服务列表,这个操作我们成为服务续约,为了达成这个操作,我们需要设置两个重要属性\n\n```yaml\neureka:\n\tinstance:\n\t\tlease-renewal-interval-in-seconds: 30\n\t\tlease-expiration-duration-in-seconds: 90\n```\n\n+ lease-renewal-interval-in-seconds:服务续约任务的调用时间间隔,也就是客户端向注册中心发送心跳的时间间隔\n+ lease-expiration-duration-in-seconds:服务失效时间,注册中心在收到客户端心跳策略后,等待下一次心跳的超时时间,如果超过了这个时间,则移除该客户端\n\n### 获取服务\n\n我们知道在服务启动的时候,服务提供者会发送一个REST请求给注册中心,来注册当前服务,服务消费者也是一样,在启动的时候,会发送一个REST请求,用来获取注册的服务清单。\n\n同时为了性能考虑,Eureka会维护一份只读的服务清单返给客户端,该清单30s更新一次\n\n> 注意:获取服务列表是服务消费的前提,确保eureka.client.fetch-register=true\n\n可以通过`eureka.client.register-fetch-interval-seconds=30`修改缓存清单的更新时间\n\n### 服务下线\n\n当服务提供者停止服务时,会向Eureka发送一个REST请求告诉它,“我要下线了”,而`Eureka Server`收到请求后会将该客户端踢出服务提供列表。\n\n### 自我保护\n\n我们之前调试Eureka的时候,经常会看到一句红色的\n\n> <font color=\"#dd0000\">EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT.RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE</font>\n\n这就是触发了Eureka Server的自我保护机制\n\n之前我们介绍过客户端会向服务端维护一个心跳连接,在Eureka运行期间,如果15分钟内的心跳连接失败概率低于85%,就会将当前实例保护起来,不让它过期,这种情况在生产上很常见,大多都因为网络连接问题,但是在保护期间内,如果服务真的出问题了,那就会出现调用失败的情况,这时候断路器就很重要了。\n\n我们可以通过下面这个配置来关闭自我保护\n\n```yaml\neureka:\n\tserver:\n\t\tenableself-preservation: false\n```\n\n# 配置详解\n\n## 服务注册类配置\n\n关于服务注册类的信息,可以在`org.springframework.cloud.netflix,eureka.EurekaClientConfigBean`里查看详细的内容\n\n```yaml\neureka:\n\tclient:\n\t\tenable: #是否启用Eureka客户端,默认为true\n\t\tregistry-fetch-interval-second: #从服务端获取注册信息的间隔时间,单位为秒,默认30\n\t\tinstance-info-replication-interval-seconds: #更新实例信息变化到Eureka服务端的间隔时间,单位为秒,默认30\n\t\tinitial-instance-info-replication-interval-seconds: #实例初始化信息到服务端的间隔时间,单位为秒,默认40\n\t\teureka-service-url-poll-interval-seconds: #轮询Eureka服务端地址更改的时间间隔,单位为秒,默认300\n\t\teureka-server-read-timeout-seconds: #读取服务端信息的超时时间,单位为秒,默认为8\n\t\teureka-server-connect-timeout-seconds: #连接服务端的超时时间,单位为秒,默认为5\n\t\teureka-server-total-connections: #从客户端到所有服务端的连接总数,默认200\n\t\teureka-server-total-connections-per-host: #从客户端连接到每个Eureka服务端主机的连接总数,默认50\n\t\teureka-connection-idle-timeout-seconds: #服务端连接的空闲关闭时间,单位为秒,默认30\n\t\theartbeat-executor-thread-pool-size: #心跳连接池的初始化线程数,默认为5\n\t\theartbeat-executor-exponential-back-off-bound: #心跳超时重试延迟时间的最大乘数值,默认为10\n\t\tcache-refresh-executor-thread-pool-size: #缓存刷新线程池的初始化线程数,默认为2\n\t\tcache-refresh-executor-exponential-back-off-bound: #缓存刷新重试延迟时间的最大乘数,默认为10\n\t\tuse-dns-for-fetching-service-urls: #使用DNS来获取服务端的serviceUrl,默认false\n\t\tregister-with-eureka: #是否将自身注册到服务端,默认true\n\t\tprefer-same-zone-eureka: #是否偏好使用处于相同Zone的服务端,默认true\n\t\tfilter-only-up-instances: #获取实例是否过滤,仅保留UP状态的实例,默认true\n\t\tfetch-registry: #是否从服务端获取注册信息,默认true\n```\n\n## 服务实例类配置\n\n关于服务实例类的配置,可以在`org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean`中查看,所有配置都会在通过这个类进行加载\n\n在这个Bean中,大部分的配置都是服务元数据的配置\n\n> 元数据:客户端向注册中心发送注册请求时,用于描述自身服务信息的对象,例如:实例名、实例IP、服务名称、实例端口等\n\n使用Eureka时,配置信息通过`EurekaInstanceConfigBean`进行加载,但是发送给服务端的时候,还是会将服务信息包装成一个类,在`ocom.netflix.eureka.appinfo.InstanceInfo`这里。\n\n我们可以通过`eureka.instance.metadataMap.<key>=<value>`进行配置服务的元数据信息,例如:\n\n`eureka.instance.metadataMap.zone=zhejiang`\n\n### 常用元数据配置\n\n#### 实例名\n\n即InstanceInfo中的`InstanceId`项\n\n`eureka.instance.instance-id: xxxx`通过这个规则进行配置实例名。\n\n#### 端点配置\n\n在`InstanceInfo`中,有一些Url信息,比如:`homePageUrl`、`statusPageUrl`、`healthCheckUrl`,分别代表了应用主页url、状态页url、健康检查url\n\n```yaml\neureka:\n\tinstance:\n\t\tstatus-page-url-path: /xxxxx\n\t\thealth-check-url: /xxx\n```\n\n也可以改成绝对路径\n\n#### 其他配置\n\n```yaml\neureka:\n instance:\n prefer-ip-address: #是否有限使用IP作为主机名的标识,默认false\n lease-renewal-interval-in-seconds: #客户端向服务端发送心跳的时间间隔,单位秒,默认30\n lease-expiration-duration-in-seconds: #服务端在收到最后一次心跳之后等待的上限时间,单位秒,默认90\n non-secure-port: #非安全的通信端口号,默认80\n secure-port: #安全通信端口号,默认443\n non-secure-port-enabled: #是否启用非安全通信端口,默认true\n secure-port-enabled: #是否启用安全的通信端口\n appname: #服务名,默认取spring.application.name的配置项,没有则为unknown\n hostname: #主机名,不配置默认操作系统的主机名\n```\n","tags":["SpringCloud"]},{"title":"《SpringCloud》——Cloud五大组件","url":"/2022/11/14/SpringCloud笔记——cloud五大组件/","content":"\n# SpringCloud\n\nSpringCloud是微服务系统架构的一站式解决方案,基于SpringBoot。\n\nSpringCloud并不是一个框架,而是一系列框架的集合,包含了世面上较好的微服务框架\n\n## 优缺点\n\n优点:\n\n+ 耦合度低,各个服务之间不会直接调用,一定程度上减轻了雪崩问题\n+ 降低了开发成本,各个服务之间独立开发\n+ 跨平台,可以用任何一种语言进行开发\n+ 各个服务之间可以使用不同的数据库,也可以使用公用数据库\n\n缺点:\n\n+ 由于服务粒度小,维护成本相对比较高\n+ 数据管理比较麻烦\n+ 性能监控比较麻烦\n\n## 五大组件\n\nSpringCloud有五大常用组件:\n\n+ 注册中心组件,常用:Eureka、nacos\n+ 客户端负载均衡,常用:Ribbon\n+ 断路器组件,常用:Hystrix\n+ 服务网关,常用:Zuul\n+ 分布式配置,常用:SpringCloudConfig、Apollo\n\n## 各大组件的作用\n\n### 注册中心\n\n注册中心,用于服务发现与服务注册,为了解耦服务提供者与服务消费者\n\n#### 透明化路由\n\n在分布式微服务架构中,各个服务之间需要相互调用,但是不需要通过硬编码的方式去调用服务提供者的服务,而是通过注册中心主动查询和被动通知的方式获取服务提供者的地址信息,消费者只需要知道哪个系统发布了什么服务,不需要清楚服务存在于什么位置,这就叫**透明化路由**。\n\n为了实现透明化路由,注册中心需要存储服务提供者的信息、服务发布相关的属性信息,消费者通过主动查询和被动通知的方式获取服务提供者的地址信息\n\n------\n\n如果需要了解为什么需要注册中心,可以去看一下微服务的发展历史\n\n### 客户端负载均衡\n\n负载均衡:指单台服务器达到性能瓶颈时,通过横向拓展服务器来增加系统的吞吐量\n\n客户端负载均衡出现在cloud之后,最常见的就是ribbon,通常和Eureka一起使用,发送请求前,ribbon会去Eureka获取服务端列表,然后通过负载均衡算法选择一个服务器进行访问,即在客户端完成负载均衡分配。\n\n<center>2022-11-17</center>\n\n------\n\n### 断路器组件\n\n#### 应用场景\n\n在微服务架构中,我们会将业务拆分成一个个服务,服务与服务之间通过RPC相互调用,这里就存在着一个隐患,假设现有A B C三个服务,三个服务的相互调用关系如下\n\n\n\nA服务需要调用B服务的某个接口,B服务又要调用C服务的某个接口\n\n某一天,C服务宕机了\n\n\n\n刚好这时B服务有大量的请求进来了,这些请求都需要去请求C服务,会造成B服务的IO阻塞,于是B服务也宕机了,最终A服务也会宕机,这就是Cloud的雪崩问题\n\n#### 实现原理\n\n引入断路器组件就可以很好的解决这个问题,最常用的断路器就是Hystrix,那么Hystrix如何做到这一点:\n\n+ 对所有的外部系统调用包装在一个`HystrixCommand`或`HystrixObservableCommand`中,该对象通常在单独的线程中执行\n+ 在配置中我们可以指定一个调用阈值,对于超过阈值的调用,会返回失败\n+ 为每个依赖项维护一个小的线程池;如果该线程池已满,发往该依赖的请求将立即被拒绝,而不会排队\n+ 测量成功、失败、超时和线程拒绝\n+ 如果服务的失败百分比超过阈值,则手动或自动触发断路器以停止对这个服务的请求一段时间\n+ 当请求失败、被拒绝、超时或短路时执行回退逻辑\n+ 近乎实时监控指标和配置变化\n\n### 服务网关\n\n网关是微服务架构中的一个关键角色,用于保护、增强和控制对于微服务的访问,网关是一个处于应用程序和微服务之前的系统,用于管理授权、访问控制和流量限制,这样微服务就会被网关保护起来,对于调用者不透明\n\n作用:\n\n+ 身份验证和安全性——识别每个资源的身份验证要求,并拒绝不满足的请求\n+ 洞察力和监控——在边缘跟踪有意义的数据和统计数据,以便我们准确了解生产情况\n+ 动态路由——根据需要动态地将请求路由到不同的后端集群\n+ 压力测试——逐渐增加集群的流量以衡量性能\n+ 减轻负载——为每种类型的请求分配容量并丢弃超过限制的请求\n+ 静态响应处理——直接在边缘构建一些响应,而不是将它们转发到内部集群\n+ 多区域弹性——跨AWS区域路由请求,以分散我们的ELB使用\n\n### 分布式配置\n\n相比于其他组件,分布式配置中心很好理解,就是把原来服务中的properties文件分理出自身的系统,并且让这些信息能够被实时获取\n\n#### 为什么需要分布式配置中心\n\n使用分布式配置中心,可以在不同节点上设置不同的额皮质,并且对各种配置进行相应的操作。\n\n------\n\n当然,SpringCloud并不止这些组件,还有智能路由、微代理、控制总线、全局锁等等\n\n\n\n以后会把这些组件进行详细介绍(先画个饼,能不能填上以后再说)\n","tags":["SpringCloud"]},{"title":"《Feign远程调用返回对象变成了LinkedHashMap》","url":"/2022/09/24/feign调用返回对象变成了LinkedHashMap/","content":"\n近期在工作中遇到了一个类型转换问题,排查下来发现是由于OpenFeign进行远程调用时,将返回类型变成了LinkedHashMap\n\n# 问题复现\n\n不要纠结于这些名字,只是临时复现用的\n\n服务提供方\n\n```java\n/**\n * @author JiangLiu\n * @Date 2022/9/24 10:05:45\n * @description\n */\n@RestController\n@RequestMapping(\"/api\")\npublic class TestApi {\n\n @RequestMapping(\"/test\")\n public ResponseDTO test(){\n User<String> a = new User<>();\n a.setAge(18);\n a.setName(\"黑牛\");\n a.setTest(new ArrayList<>());\n return new ResponseDTO().success(a);\n }\n}\n```\n\n```java\n/**\n * @author JiangLiu\n * @Date 2022/9/24 11:37:12\n * @description\n */\n@Data\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\npublic class ResponseDTO<T> {\n private String code;\n private String msg;\n private T data;\n\n public ResponseDTO failed(String msg){\n this.code = \"400\";\n this.msg = msg;\n return this;\n }\n\n public ResponseDTO success(T data){\n this.code = \"200\";\n this.data = data;\n return this;\n }\n}\n```\n\n# 服务消费方\n\n(由于懒的搞负载均衡,这里直接用url调用)\n\n```java\n/**\n * @author JiangLiu\n * @Date 2022/9/24 10:10:55\n * @description\n */\n@FeignClient(\n name = \"DEMO1\",\n url = \"http://localhost:8081\",\n path = \"/api\",\n fallback = TestClientFallBack.class\n)\npublic interface TestClient {\n\n @RequestMapping(value = \"/test\", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)\n ResponseDTO test();\n}\n```\n\n```java\n/**\n * @author JiangLiu\n * @Date 2022/9/24 10:13:30\n * @description\n */\n@RestController\n@Slf4j\npublic class TestApi {\n\n @Resource\n TestClient testClient;\n\n @RequestMapping(\"/get\")\n public void get(){\n log.info(\"开始发送请求\");\n ResponseDTO test = testClient.test();\n log.info(\"请求返回参数:{}\", test);\n log.info(\"返回结果类型:{}\", test.getData().getClass());\n }\n}\n```\n\n# 请求结果\n\n```tex\n2022-09-24 11:45:27.122 INFO 15656 --- [nio-8082-exec-1] com.example.demo2.Api.TestApi : 请求返回参数:ResponseDTO(code=200, msg=null, data={name=黑牛, age=18, test=[]})\n2022-09-24 11:45:27.122 INFO 15656 --- [nio-8082-exec-1] com.example.demo2.Api.TestApi : 返回结果类型:class java.util.LinkedHashMap\n2022-09-24 11:46:38.278 INFO 15656 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration\n2022-09-24 11:51:38.287 INFO 15656 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration\n```\n\n可以看到,这时候ResponseDTO中的`data`类型变成了`LinkedHashMap`\n\n# 原因剖析\n\n由于Data的数据类型是一个泛型,而我们使用的时候又没有指定泛型,所以被直接转换成了LinkedHashMap。\n\nFeign在服务相互调用时,会出现传递一个复杂对象的情况,这种时候,返回值无法解析,就会被Java默认转换成了`LinkedHashMap`,如果我们想传递原对象,最好的方法就是带上泛型。\n\n至于为什么是`LinkedHashMap`,继续往下看\n\nSpring中有一个类叫做`ModelMap`,在源码中也有相应的介绍\n\n```tex\nThis class serves as generic model holder for Servlet MVC but is not tied to it. Check out the Model interface for an interface variant.\n```\n\n它是MVC的通用模型持有者,换句话说,任何一个接口返回对象都能够转换成ModelMap,而它继承了`LinkedHashMap`\n\n**注意**:LinkedHashMap是没有泛型的,不管返回的是一个多复杂的对象,什么类型的Map,都能够直接用`ModelMap`来接收他的结果。\n\n","tags":["Java"]},{"title":"《正则表达式》","url":"/2022/08/28/正则表达式/","content":"\n# 正则表达式\n\n今天来聊一聊让程序员又爱又恨的正则表达式\n\n## 简介\n\n正则表达式是一种进行模式匹配和文本操作的复杂而又强大的工具,虽然比固定字符匹配要慢,但是胜在灵活,本文中以`Go`做介绍\n\n## 语法规则\n\n### 字符\n\n| 语法 | 说明 | 表达式示例 | 匹配结果 |\n| -------- | ------------------------------------------------------------ | ---------- | ----------------- |\n| 一般字符 | 匹配自身 | abc | abc |\n| . | 匹配任意除换行符\"\\n\"外的字符, 在 DOTALL 模式中也能匹配换行符 | a.c | abc |\n| \\ | 转义字符,使后一个字符改变原来的意思; 如果字符串中有字符 * 需要匹配,可以使用 \\* 或者字符集[*]。 | a\\.c a\\\\c | a.c a\\c |\n| [...] | 字符集(字符类),对应的位置可以是字符集中任意字符。 字符集中的字符可以逐个列出,也可以给出范围,如 [abc] 或 [a-c], 第一个字符如果是 ^ 则表示取反,如 [^abc] 表示除了abc之外的其他字符。 | a[bcd]e | abe 或 ace 或 ade |\n| \\d | 数字:[0-9] | a\\dc | a1c |\n| \\D | 非数字:[^\\d] | a\\Dc | abc |\n| \\s | 空白字符:[<空格>\\t\\r\\n\\f\\v] | a\\sc | a c |\n| \\S | 非空白字符:[^\\s] | a\\Sc | abc |\n| \\w | 单词字符:[A-Za-z0-9] | a\\wc | abc |\n| \\W | 非单词字符:[^\\w] | a\\Wc | a c |\n\n### 数量词\n\n| 语法 | 说明 | 表达式示例 | 匹配结果 |\n| ----- | ------------------------------------------------------------ | ---------- | ------------ |\n| * | 匹配前一个字符 0 或无限次 | abc* | ab 或 abccc |\n| + | 匹配前一个字符 1 次或无限次 | abc+ | abc 或 abccc |\n| ? | 匹配前一个字符 0 次或 1 次 | abc? | ab 或 abc |\n| {m} | 匹配前一个字符 m 次 | ab{2}c | abbc |\n| {m,n} | 匹配前一个字符 m 至 n 次,m 和 n 可以省略,若省略 m,则匹配 0 至 n 次; 若省略 n,则匹配 m 至无限次 | ab{1,2}c | abc 或 abbc |\n\n### 边界匹配\n\n| 语法 | 说明 | 表达式示例 | 匹配结果 |\n| ---- | -------------------------------------------- | ---------- | -------- |\n| ^ | 匹配字符串开头,在多行模式中匹配每一行的开头 | ^abc | abc |\n| $ | 匹配字符串末尾,在多行模式中匹配每一行的末尾 | abc$ | abc |\n| \\A | 仅匹配字符串开头 | \\Aabc | abc |\n| \\Z | 仅匹配字符串末尾 | abc\\Z | abc |\n| \\b | 匹配 \\w 和 \\W 之间 | a\\b!bc | a!bc |\n| \\B | [^\\b] | a\\Bbc | abc |\n\n### 特殊构造\n\n| 语法 | 说明 | 表达式示例 | 匹配结果 |\n| --------- | ------------------------------------------------------------ | ----------------- | ---------------- |\n| (?:...) | (…) 的不分组版本,用于使用 \"\\|\" 或后接数量词 | (?:abc){2} | abcabc |\n| (?iLmsux) | iLmsux 中的每个字符代表一种匹配模式,只能用在正则表达式的开头,可选多个 | (?i)abc | AbC |\n| (?#...) | # 后的内容将作为注释被忽略。 | abc(?#comment)123 | abc123 |\n| (?=...) | 之后的字符串内容需要匹配表达式才能成功匹配 | a(?=\\d) | 后面是数字的 a |\n| (?!...) | 之后的字符串内容需要不匹配表达式才能成功匹配 | a(?!\\d) | 后面不是数字的 a |\n| (?<=...) | 之前的字符串内容需要匹配表达式才能成功匹配 | (?<=\\d)a | 前面是数字的a |\n| (?<!...) | 之前的字符串内容需要不匹配表达式才能成功匹配 | (?<!\\d)a | 前面不是数字的a |\n\n### Go演示\n\n```go\nimport (\n\t\"fmt\"\n\t\"regexp\"\n)\n\nfunc main() {\n\tbuf := \"abc azc a7c aac 888 a9c tac\"\n\tregex := regexp.MustCompile(`a.c`)\n\tresutl := regex.FindAllStringSubmatch(buf, -1)\n\tfmt.Println(resutl)\n}\n```\n\n运行结果\n\n```go\n[[abc] [azc] [a7c] [aac] [a9c]]\n```\n","tags":["每天写点啥"]},{"title":"《Java流操作》——读书笔记","url":"/2022/08/10/Java流操作/","content":"\n# 从迭代到流的操作\n\n处理集合时,我们常常会遍历他们的元素,然后对其中的元素做一些操作,例如我们如果想从一个文件中读取一个字符串,然后用非字母对他们进行分割,我们一般会这么干\n\n```java\n String contents = Files.readString(Paths.get(\"D:/001_program/JavaProgram/JavaBase/src/main/java/核心技术卷II/JavaSe8的流库/从迭代到流的操作/alice.txt\"));\n // 非字母分隔符\n List<String> words = Arrays.asList(contents.split(\"\\\\PL+\"));\n System.out.println(words);\n for (String word : words) {\n System.out.println(word);\n }\n```\n\n现在,我们可以尝试用流的操作替代循环\n\n```java\n String contents = Files.readString(Paths.get(\"D:/001_program/JavaProgram/JavaBase/src/main/java/核心技术卷II/JavaSe8的流库/从迭代到流的操作/alice.txt\"));\n // 非字母分隔符\n List<String> words = Arrays.asList(contents.split(\"\\\\PL+\"));\n System.out.println(words);\n// for (String word : words) {\n// System.out.println(word);\n// }\n long count1 = words.stream().filter(Objects::nonNull).count();\n System.out.println(count1);\n```\n\n就像这样,这比循环更加简洁\n\n## stream和parallelStream\n\n在Java中,这两种都是流操作,但是它们两个有一些区别\n\n+ stream是串行流,也就是说stream流中的元素是一个一个顺序执行的\n+ 而parallelStream是并行流,可以以并行方式来进行过滤和计数\n\n至于并行流,在后面会详细介绍\n\n流操作表面上和集合是非常相似的,但是它们有着非常显著的差异:\n\n+ 流操作并不存储元素。这些元素储存在底层的集合中按需生成。\n+ 流操作并不会修改数据源,而是产生一个新的流\n+ 流的操作是尽可能惰性执行的。比如我们想要查找前五个长单词而不是所有长单词,那么流就会在匹配到第五个长单词时停止过滤,因此按理说我们可以操作无限流。\n\n## 章节总结\n\n流操作API:\n\n+ filter(Predicate<? super T> P):产生一个流,其中包含所有满足P的元素\n+ count():计算当前流中元素的数量\n\n流的种类:\n\n+ stream:串行流\n+ parallelStream:并行流\n\n# 流的创建\n\n我们已经知道用Collection类的stream方法可以将一个集合转化为流,当然,不止集合,数组也可以\n\n对于一个数组,我们可以使用Stream.of方法将数组转变为流\n\n```java\nint[] a = new int[]{1, 3, 5, 9, 1, 5, 6, 8};\nStream<int[]> a1 = Stream.of(a);\n```\n\nof方法用的是可变参数,所以我们可以传入任意长度的数组\n\n但是更加推荐Array.stream(array, from, to)方法,可以从数组的from到to创建一个流对象\n\n如果想产生一个空的流,可以使用`Stream.empty()`方法\n\n## 为什么推荐Array.stream\n\n对于对象类型的数组,Array.stream和Stream.of虽然有同样的返回,但是对于基本类型的数组,Stream.of.count返回的数组永远是1\n\n对于这点可以自行验证。\n\n## 无限流\n\n在Java8的流库中,有两个创建无限流的方法,generate和iterator\n\n详细可以从另一篇博客《Java流中的generate与iterator》了解\n\n### iterator\n\niterator需要我们传入两个参数,`seed`和`initial element seed`,一个是初始值,一个是产生无限流的依据\n\n```java\nStream<BigInteger> stream = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.TEN)).limit(100);\nSystem.out.println(Arrays.toString(stream.filter(n -> n.compareTo(new BigInteger(String.valueOf(170L))) < 0).toArray()));\n```\n\n### generate\n\ngenerate则需要我们传入一个Supplier对象,里面可以定义规则,比iterator更加灵活\n\n```java\nStream<Integer> stream1 = Stream.generate(new Supplier<Integer>() {\n static int a = 0;\n\n @Override\n public Integer get() {\n return a++;\n }\n }).limit(20);\n System.out.println(Arrays.toString(stream1.toArray()));\n```\n\n或者说,generate可以根据多个元素制定规则,而iterator只能根据一个元素\n\n在Java中,产生流的方式还有很多,比如Pattern.splitStream()、Files.lines()\n\nPattern.splitStream可以根据正则表达式来分割字符串形成一个流\n\n```java\nPattern.complie(\"\\\\PL+\").splitAsStream(content)\n```\n\n而Files.lines(path)方法则可以返回一个包含文件中所有行的流\n\n```java\ntry(Stream<String> lines = Files.lines(path)){\n // Process line\n}\n```\n\n\n\n## 章节总结\n\n操作流API:\n\n+ of(T... values):根据给定数组产生一个流\n+ empty():产生一个空的流\n+ generate():产生一个无限流\n+ iterator():产生一个无限流\n\njava.util.Arrays:\n\n+ stream(T[] Arrays, int start, int end):根据数组创建一个流\n\njava.util.regex.Pattern:\n\n+ splitAsStream(CharSequence input):根据input产生一个流\n\njava.nio.file.Files:\n\n+ stream(Path path, [Charset c]):将指定文件中的行转化为流,并且可以设置指定字符集\n\njava.util.function.Supplier:\n\n+ get():提供一个值,用于产生无限流\n\n# filter、map和flatMap\n\n## filter\n\nfilter可以从一个流中转换出一个流,其中的元素遵循某种规则,可以在`filter()`括号中定义这个规则,比如\n\n```java\nList<String> words = ...;\nStream<String> longWords = wordList.stream().filter(w -> w.length > 12)\n```\n\n这样这个流中就会只包含长度大于12的单词\n\nfilter更像是从一个流中筛选元素,组成另一个流\n\n## map\n\n相比于filter,map虽然也是产生一个新的流,但是map是将原来的流中的元素进行转换,比如将words中的单词全部小写\n\n```java\nStream<String> words = wordList.stream().map(String::toLowerCase);\n```\n\n或者你可以自定义一个函数,如下\n\n```java\npackage 核心技术卷II.JavaSe8的流库.流方法;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * @author JiangLiu\n * @Date 2022/8/10 21:24:52\n * @description\n */\npublic class map {\n public static void main(String[] args) {\n List<String> words = new ArrayList<>();\n words.add(\"Abc\");\n words.add(\"Bcd\");\n words.add(\"Java\");\n words.add(\"GoLang\");\n words.add(\"Rust\");\n List<String> collect = words.stream().map(String::toLowerCase).collect(Collectors.toList());\n System.out.println(collect);\n\n List<List<String>> collect1 = words.stream().map(w -> myMap(w).collect(Collectors.toList())).collect(Collectors.toList());\n System.out.println(collect1);\n\n }\n\n public static Stream<String> myMap(String s){\n List<String> list = new ArrayList<>();\n for (int i = 0; i < s.length(); i++){\n list.add(s.substring(i, i + 1));\n }\n return list.stream();\n }\n}\n[abc, bcd, java, golang, rust]\n[[A, b, c], [B, c, d], [J, a, v, a], [G, o, L, a, n, g], [R, u, s, t]]\n```\n\n但是这样每个单词都是一个List,我们可能并不想出现这种情况,这时候flatMap方法就派上用场了\n\n## flatMap\n\nflatMap可以将当前流中的所有元素拼接到一起返回\n\n```java\nList<String> collect2 = words.stream().flatMap(w -> myMap(w)).collect(Collectors.toList());\n System.out.println(collect2);\n[A, b, c, B, c, d, J, a, v, a, G, o, L, a, n, g, R, u, s, t]\n```\n\n返回一个流,其中包含将**该流的每个元素**替换为将提供的映射函数应用到每个元素所产生的映射流的内容的结果。每个被映射的流在其内容被放置到这个流之后被关闭。\n\n而map是单个元素\n\n## 章节总结\n\njava.util.Stream:\n\n+ filter:产生一个流,其中包含当前流中所有满足条件的元素\n+ map(Function<? super T> mapper):产生一个流,其中包含将mapper应用于流中的每一个元素所产生的结果\n+ flatMap(Function mapper):产生一个流,其中包含将mapper应用于流中每一个元素所产生的结果的组合\n\n# 抽取子流和连接流\n\n## 抽取流\n\n在介绍无限流的时候,有一个API叫`limit()`,这个API会产生一个新的流,并且在流运行到第n个元素时结束,对裁剪无限流非常好用\n\n```java\nList<Double> collect = Stream.generate(Math::random).limit(5).collect(Collectors.toList());\nSystem.out.println(collect);\n```\n\n这个流就只包含五个随机数\n\n而`stream.skip(long n)`API正好相反,这个API会在跳过前n个元素,对后面的元素进行截取。\n\n```java\nList<String> words = new ArrayList<>();\n words.add(\"Abc\");\n words.add(\"Bcd\");\n words.add(\"Java\");\n words.add(\"GoLang\");\n words.add(\"Rust\");\n List<String> collect1 = words.stream().skip(2).collect(Collectors.toList());\n System.out.println(collect1);\n\n[Java, GoLang, Rust]\n```\n\n返回的流跳过了前两个元素\n\n## 连接流\n\n如果想要将两个Luis连接起来,可以使用`stream.contact(Stream a, Stream b)`将两个流进行连接\n\n```java\nList<Object> collect2 = Stream.concat(collect.stream(), collect1.stream()).collect(Collectors.toList());\n System.out.println(collect2);\n\n[0.19283510014488758, 0.986608060762175, 0.07406377892420113, 0.6737128935974602, 0.5210312651857187, Java, GoLang, Rust]\n```\n\n## 章节总结\n\n本章一共介绍了三个API,两个抽取子流,一个拼接流\n\njava.util.Stream:\n\n+ limit(long maxSize):抽取流中最初的maxSize个元素,并返回一个新的流\n+ skip(long n):抽取流中除了前n个元素之前的元素,并返回一个新的流\n+ contact(Stream a, Stream b):拼接两个流,并返回一个新的流\n\n# 其他流的转换\n\n`distinct()`函数能够帮助我们从一个流中返回一个新的流,并且没有重复元素\n\n```java\n/**\n * @author JiangLiu\n * @Date 2022/8/29 21:05:21\n * @description\n */\npublic class StreamTrans {\n public static void main(String[] args) {\n Stream<String> a = Stream.of(\"merrily\", \"merrily\", \"merrily\", \"gently\");\n // 流只能使用一次!!!!\n// System.out.println(Arrays.toString(a.toArray()));\n System.out.println(Arrays.toString(a.distinct().toArray()));\n }\n}\n```\n\n与数组一样,我们可以使用`sorted()`对流进行排序\n\n```java\nSystem.out.println(\"=========流排序========\");\n String[] str = new String[]{\"Go\", \"Java\", \"C/C++\"};\n Stream<String> str1 = Stream.of(str);\n Object[] objects = str1.sorted(Comparator.comparing(String::length)).toArray();\n System.out.println(Arrays.toString(objects));\n```\n\n当然,我们也可以使用数组进行排序,但是当排序方法是流管道的一部分时,sorted函数就显得非常有用\n\n最后还有一个`peek`函数,这个函数和`map`有一些类似,都是产生一个新的流,并且对元素进行一些处理,但是`map`是处理元素值,`peek`是执行函数\n\n```java\n String[] str = new String[]{\"Go\", \"Java\", \"C/C++\"};\n\t\tSystem.out.println(\"=========peek========\");\n Stream<String> str2 = Stream.of(str);\n Stream<String> peek = str2.peek(System.out::println);\n System.out.println(Arrays.toString(peek.toArray()));\n```\n\n这样就会将数组逐个输出。\n\n## 章节总结\n\n### java.util.stream.Stream\n\n+ Stream<T> distinct():产生一个新的流,剔除当前流中的重复元素\n+ Stream<T> sorted()\n+ Stream<T> sorted(Comparator<? super T> comparator):产生一个新的流并且进行排序\n+ Stream<T> peek(Consumer<? super T> action):产生一个新的流,与当前元素相同,并且在获取其中每个元素时都会传递给action\n\n# 简单约简\n\n我们已经看到了如何创建和转换流,终于可以来一点有意思的东西了:从数据流中获取答案,这种方法被称为约简。这是一种`终结操作`,他们**会将流约简成可以在程序中使用的非流值**。\n\n之前我们已经使用过`count`,这就是一种简单约简,同样的还有`max`和`min`,这些方法的返回值是一个`Optional<T>`,它会在其中包装答案,或者什么都不做(流值为空),在以前这种操作容易造成空指针异常,在下一节中我们会详细讨论。\n\n```java\npublic static void main(String[] args) {\n String[] words = new String[]{\"Go\", \"Java\", \"C/C++\", \"Rust\"};\n Stream<String> words1 = Stream.of(words);\n Optional<String> max = words1.max(String::compareToIgnoreCase);\n System.out.println(max);\n }\n```\n\n这个方法展示了如何获取流的最大值,但是结果和我们想象的不太一样\n\n`Optional[Rust]`\n\n还有找到第一个以G开头的单词\n\n```java\nSystem.out.println(\"=====findFirst====\");\nOptional<String> g = Stream.of(words).filter(e -> e.startsWith(\"G\")).findFirst();\nSystem.out.println(g);\n```\n\n如果不强制匹配第一个,那么可以使用`findAny`,这个方法在处理并行流时很有效\n\n或者只是想知道有没有存在某个值符合,可以使用`anyMatch`来进行匹配,这个函数会返回一个boolean,而不是具体的值,同时,这个方法需要接受一个断言引元,而不是filter,就像这样\n\n```java\nboolean g2 = Stream.of(words).anyMatch(e -> e.startsWith(\"G\"));\nSystem.out.println(g2);\n```\n\n\n\n还有`allMatch`和`noneMatch`,分别会在全部符合或者没有符合时返回`true`\n\n## 章节总结\n\n### java.util.stream.Stream\n\n+ Optional<T> max(Comparator<? super T> comparator)\n+ Optional<T> min(Comparator<? super T> comparator):这两个方法分别会返回最大元素和最小远古三\n+ Optional<T> findFirst()\n+ Optional<T> findAny():分别产生这个流的第一个元素和任意一个元素\n+ Optional<T> anyMatch(Predicate<? super T> predicate)\n+ Optional<T> allMatch(Predicate<? super T> predicate)\n+ Optional<T> noneMatch(Predicate<? super T> predicate):分别在这个流中任意元素、所有元素和没有任何元素匹配时返回`true`\n\n# Optional类型\n\n上一节中我们使用到了这个对象,这节来自习介绍一下\n\nOptional类型是一种包装器对象,要么包装了类型T的对象,要么没有包装任何对象,用于解决空指针异常问题。\n\n## 如何使用\n\nOptional是一个很好用的东西,如果值不存在的状态下,它会自动产生一个替代的值\n\n第一种情况:\n\n如果某个值不存在,我们想要用一个空字符串去代替他\n\n```java\nString s = a.orElse(\"\");\nSystem.out.println(s);\n```\n\n或者调用一个方法去计算\n\n```java\ns = a.orElseGet(() -> {\n return String.valueOf(System.currentTimeMillis());\n});\nSystem.out.println(s);\n```\n\n又或者是抛出一个异常\n\n```java\nString s1 = a.orElseThrow(IllegalAccessError::new);\n```\n\n这些方法在不存在任何值时会产生一个相应的替代物;还有一个函数可以选择只有在值存在的情况下才进行消费。\n\n`ifPresent`只会在值存在的情况下才会接受一个函数,否则不会发生任何事情。\n\n```java\nOptional<String> b = Optional.of(\"123\");\nb.ifPresent(System.out::println);\n```\n\n当我们调用`isPresent`时,这个函数不会返回任何值,如果想要获取处理的结果,可以使用`map`\n\n```java\nList<String> result = new ArrayList<>();\nOptional<Boolean> aBoolean = b.map(result::add);\nSystem.out.println(aBoolean);\n```\n\n**注意**:这里的aBoolean有三种值,`true`和`false`所对应的Option,和空值所对应的Option\n\n### 总结\n\n#### java.util.Optional\n\n+ T orElsr(T other):产生这个Optional的值,在值为空时,返回other\n+ T orElseGet(Supplier<? extends T> other):产生这个Optional的值,在值为空时,返回other这个**方法**所返回的结果\n+ <X extends Throwable> T orElseThrow(Supplier<? extends X> action):返回Optional的值,值为空时,抛出action返回的结果\n+ void isPresent(Consumer<? super T> consumer):如果值不为空,就将值传递给consumer这个**方法** \n+ <U> Optional<U> map(Function<? super T, ?extends U> mapper):将值传递给mapper并产生返回结果,如果值为空,返回也是一个空Optional\n\n## 不适合使用Optional值的方式\n\n如果没有正确使用Optional的值,那么相比较其他获得`null`的方式,并没有任何区别。\n\n`get`方法会在Optional值存在IDE情况下获得其中包装的元素,或者在不存在的情况下抛出一个`NoSuchElementException`对象,所以\n\n```java\nOptional<T> optional = ...;\noptional.get().method();\n```\n\n```java\nT a = new T();\na.method();\n```\n\n以上两种用法并没有太大区别。\n\n```java\nOptional<T> optional = ...;\nif (optional.isPresent()){\n optional.get().method()\n}\n```\n\n```java\nT value = ...;\nif (value != null) value.method();\n```\n\n这两种方法也没有什么区别\n\n### 总结\n\n#### java.util.Optional\n\n+ T get():产生这个Optional的值,或者在值为空时,抛出一个`NoSuchElementException`异常\n+ boolean isPresent():如果值不为空,返回true\n\n## 创建Optional值\n\n现在我们已经知道了如何使用Optional值,但是对于如何创建我们一无所知\n\n有多个方法可以用于创建Optional,例如:\n\n+ Optional.of(T value)\n+ Optional.empty()\n\n或者如果不确定有没有这个value,也可以使用`Optional.ofNullable(T value)`,这个函数会根据value是否存在去调用`Optional.empty`或是`Optional.of()`\n\n### 总结\n\n#### java.util.Optional\n\n+ static <T> Optional<T> of(T value)\n+ static <T> Optional<T> empty():产生一格具有给定值的Optional,如果值为空,of会抛出一个NullPointerException异常,empty会返回一个空Optional\n+ static <T> Optional<T> ofNullable(T value):如果value存在调用of,不存在调用empty\n\n## 用flatMap构建Optional值的函数\n\n考虑这样一种场景,你有一个能够生成`Optional<T>`对象的方法`f`,`T`又有一个能够返回`Optional<U>`的方法`g`,如果想要通过f方法创建`Optional<U>`对象,可以使用\n\n```java\nOptional<U> result = s.f().flatMap(T::g);\n```\n\n如果`s.f()`的值存在,那么`g`就可以应用到它上面,否则会产生一个空Optional\n\n通过这种方式我们可以疯狂使用`flatMap`构建Optional,从而构建由这些步骤组成的管道,当所有步骤成功时,该管道才会成功\n\n在之前我们见过`Stream.flatMap()`,但是这里的flatMap和stream的不太一样。\n\n### 总结\n\n#### java.util.Optional\n\n+ <U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper):产生将mapper应用于当前的Optional值所产生的结果,或者在当前Optional为空时,返回一个空Optional\n\n# 收集结果\n\n当处理完流之后,如果想要查看其中的元素,我们可以使用`iterator`迭代器,或者`foreach`将某个函数应用到流中的每一个元素上去。\n\n当然,在并行流上`foreach`方法会导致访问顺序不一致,如果想要按照流原来的顺序进行访问,需要使用`forEachOrderd`方法,同时这个方法也会丧失并行流的部分甚至全部优势。\n\n或者我们可以使用`stream.toArray()`将流转化为Array类型。\n\n或者`stream`提供了一个`collect`方法,它接受一个`Collector`实例,可以直接将流收集到列表或集合中\n\n```java\nStream<String> a = Stream.of(\"123\", \"456\");\nList<String> collect = a.collect(Collectors.toList());\nMap<String, String> collect1 = a.collect(Collectors.toMap(e -> e, e -> e));\nSet<String> collect2 = a.collect(Collectors.toSet());\n```\n\n比如这三个方法可以将stream转换成集合形式。\n\n## 总结\n\n### java.util.stream.BaseStream\n\n+ Iterator<T> iterator():产生一个用于获取当前流中各个元素的迭代器。这是一种终结操作。\n\n# 收集到映射表中\n\n假设有一个对象的流`Stream<People>`,如果想要根据ID获取对应信息,可以使用`toMap`,但是toMap接受两个参数,键和值,值一般情况下是元素本身,可以使用`Function.identity()`,当然还要遵循Map的规则,键不能相同,会抛出一个`IllegalStateException`异常。\n\n如果担心出现这种问题,我们可以对值进行选择\n\n```java\na.collect(Collectors.toMap(e -> e, e -> e, (old, newValue) -> newValue));\n```\n\n这样就代表,如果出现了主键重复的情况,选择新的value进行覆盖\n\n或者如果你想指定产生的`Map`为`TreeMap`,可以使用如下方式:\n\n```java\na.collect(Collectors.toMap(e -> e, e -> e, (old, newValue) -> newValue, TreeMap::new));\n```\n\n# 群组和分区\n\n```java\nStream<Locale> locales = Stream.of(Locale.getAvailableLocales());\nMap<String, Set<String>> collect = locales.collect(Collectors.toMap(\n Locale::getDisplayCountry,\n l -> Collections.singleton(l.getDisplayLanguage()),\n (old, newValue) -> {\n Set<String> union = new HashSet<>(old);\n union.addAll(newValue);\n return union;\n }));\nSystem.out.println(collect);\n```\n\n这段代码可以手机给定国家的所有语言,但是有点过于冗长了。\n\n在`Java`中,将具有相同特性的值群聚成组是很常见的,我们可以使用`groupingBy`进行这个操作\n\n```java\nMap<String, List<Locale>> collect1 = locales.collect(Collectors.groupingBy(Locale::getDisplayCountry));\nSystem.out.println(collect1);\n```\n\n现在我们可以对这个Map进行操作,比如查找中国所用的语言\n\n```java\nList<Locale> Ch = collect1.get(\"中国\");\nSystem.out.println(Ch);\n```\n\n如果`groupingBy`中的函数返回值是一个boolean类型,那我们可以使用`partitioningBy`进行分组,在这种情况下,他要比`groupingBy`更加高效\n\n```java\nlocales = Stream.of(Locale.getAvailableLocales());\nMap<Boolean, List<Locale>> map = locales.collect(Collectors.partitioningBy(\n l -> l.getLanguage().equals(\"en\")\n));\n```\n\n比如这段代码能够筛选是否使用英语的国家\n\n## 总结\n\n### java.util.stream.Collector\n\n+ static<T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<> super T, ? extends K> classifier)\n+ groupingCurrentMap:产生一个收集器,它会产生一个映射表或并发映射表,键是`classifier`应用于所有元素上所产生的结果,而值是具有相同键的元素构成的一个个列表\n+ partitioningBy:产生一个收集器,它会产生一个映射表,键是`true`或`false`,而值是由断言的段素构成的列表\n\n# 下游收集器\n\n如果想要返回的值不是一个`List`而是一个`Set`,可以使用`Collectors.groupingBy(Function, toSet())`\n\n不仅如此,Java还提供了很多将群组元素约简的方法:\n\n+ countings():对元素个数进行计数\n\n+ summing(int | long | double):将函数应用到下游元素中,并计算他们的和\n\n+ maxBy和minBy:产生一个比较器,并产生最大或最小元素\n\n+ mapping:产生将函数应用到下游结果上的收集器,并传递给另一个收集器\n\n+ ```java\n Map<String, Optional<String>> stateToLongestCityName = \n cities.collect(\n \t\tgroupingBy(City::getStat),\n \tmapping(City::getName,\n maxBy(Comparator.comparing(String::length))));\n ```\n\n# 约简操作\n\n在前面介绍过,约简操作是一种终结操作,能够将流转换成非流值,这节中来介绍更多的约简操作:\n\nreduce是一种从流中计算某个值的通用操作。比如下面这种操作能够计算`List<Integer>`集合中的总和\n\n```JAVA\nInteger[] a = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9};\nList<Integer> list = Arrays.asList(a);\nOptional<Integer> reduce = list.stream().reduce(Integer::sum);\nSystem.out.println(reduce);\n```\n\n在reduce中,还有一项操作非常重要,就是`op`,这个约简操作会产生`v0 op v1 op v2...`,其中我们将函数`op(v1, v2)`写作`v1 op v2`。这项操作是可结合的,比如将切换顺序,他们的结果应当相同,也就是`(v1 op v2) op v3`应当与`v1 op (v2 op v3)`相同。\n\n在reduce操作中,我们经常会使用一个`幺元值`,这个幺元值能够帮助我们在列表为空时不需要使用`Optional`进行判空,如果列表为空,那么就会返回这个幺元值。\n\n```java\nList<Integer> b = new ArrayList<>();\nInteger reduce1 = b.stream().reduce(1, Integer::sum);\nSystem.out.println(reduce1);\n```\n\n就像这样,`reduce1`的值应该是`1`\n\n现在我们需要对一个字符串数组属性进行求和,求出每个字符串长度之和,就不能使用简单的`reduce`,而是要使用`(T1 + T2 ) -> T`形式\n\n在实践中,我们更习惯去调用`words.mapToInt(String::length).sum()`\n\n```java\nstr = new String[]{\"GoLang\", \"Java\", \"Rust\", \"C/C++\"};\nint sum = Arrays.asList(str).stream().mapToInt(String::length).sum();\nSystem.out.println(sum);\n```\n\n在我们使用并行流时,如果使用reduce可能会产生多个类型的计算,我们需要将结果进行合并,可以在reduce中加入第三个方法,表示结果进行合并\n\n```java\na = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9};\n Integer reduce3 = Arrays.asList(a).parallelStream().reduce(0, (x, y) -> x + y, (sum1, sum2) -> sum1 + sum2);\n System.out.println(reduce3);\n```\n\n## 注意\n\n有时候`reduce`对我们来说并不够用,比如我们想要搜集`BitSet`中的结果。如果收集操作是并行的,那么我们就不能将元素放入单个`BitSet`中,因为`BitSet`对象并非线程安全的。因此我们不能使用`reduce`,因为每个部分都需要以自己的空集开始,并且`reduce`只能提供一个幺元值。在这时候我们更应该使用`collect`,它能够接受单个引元:\n\n+ 一个提供者,它会创建目标类型的新实例,例如散列集的构造器\n+ 一个累积器,它会将一个元素添加到一个实例上,例如`add`方法\n+ 一个组合器,能够将两个实例合并成一个,例如`addAll`\n\n下面会展示`collect`如何操作位集\n\n```java\nStream<Integer> stream = Stream.of();\nBitSet collect = stream.collect(BitSet::new, BitSet::set, BitSet::or);\n```\n\n# 基本流类型\n\n到目前为止,我们都是将对象收集到`Stream<T>`中,对于整数类型,我们一般使用`Stream<Integer>`,但是这样效率是比较低的,在流库中,有专门的类:\n\n+ IntStream:用于存储short、char、byte和boolean\n+ LongStream\n+ DoubleStream:存储float类型\n\n用于存储基本类型值,无需使用包装器。\n\n为了创建IntStream,我们可以使用`of`或`Arrays.stream()`\n\n```java\nint[] numbers = new int[]{1,2,3,4,5,6,7,8,9};\nIntStream intStream = IntStream.of(numbers);\nintStream = Arrays.stream(numbers);\n```\n\n和对象流一样,IntStream和LongStream也可以通过`generate`或者`iterator`创建无限流;此外,还有两个静态方法`range`和`rangeClose`,可以用于生成步长为1的整数范围\n\n```java\nIntStream.rangeClosed(0, 100); \nIntStream.range(0, 100);\n```\n\n`CharSequence`接口有`codePoints()`和`chars`方法,可以生成由字符的`Unicode`码或`UTF-16`编码所组成的IntStream\n\n假如我们现在已经有了一个对象流,想将这个对象流转换成基本类型流,可以通过`mapToInt/Long/Double`方法。\n\n```java\nStream<String> stream = Stream.of(\"Java\", \"GoLang\");\nIntStream intStream2 = stream.mapToInt(String::length);\nSystem.out.println(Arrays.toString(intStream2.toArray()));\n```\n\n比如这串代码能够将字符串的长度作为元素放入`IntStream`\n\n如果想将基本类型流转换成对象流,可以使用`boxed`方法\n\n```java\nIntStream intStream3 = IntStream.of(1, 2, 3, 45, 6);\nStream<Integer> boxed = intStream3.boxed();\n```\n\n基本类型流基本上的方法和对象流类似,最主要的差异有:\n\n+ `toArray`方法会返回基本类型数组\n+ 产生可选结果的方法会返回`OptionalInt/Long/Double`,这些方法与`Optional`类似,但是有`getAsInt/Long/Double`方法,而不是get\n+ 具有返回总和、平均值、最大值、最小值的`sum`、`average`、`max`、`min`方法,而对象流没有这些\n+ `summaryStatistics`方法会产生一个类型为`Int/Long/DoubleSummaryStatistics`对象,他们可以同时报告流的总和、平均值、最大值和最小值。\n\n```java\nIntSummaryStatistics intSummaryStatistics = intStream3.summaryStatistics();\nSystem.out.println(intSummaryStatistics);\n\n//IntSummaryStatistics{count=5, sum=57, min=1, average=11.400000, max=45}\n```\n\n\n\n## 总结\n\n这章所涉及的API比较多\n\n### java.util.stream.IntStream\n\n+ range()\n+ rangeClosed():产生一个由给定范围内的整数所构成的`IntStream`,区别在于`rangeClosed`包含最后的结束节点,range不包含\n+ of(int... values):产生由给定元素组成的流\n+ toArray():将当前流转换成数组\n+ sum()\n+ max()\n+ min()\n+ average()\n+ summaryStatistics():产生当前流中元素的总和、最大值、最小值、平均值,或是包含着四种值的对象\n+ boxed():将基本类型流转换成对象流\n\n### java.util.stream.LongStream\n\n### java.util.stream.DoubleStream\n\n用法和IntStream一致\n\n### java.lang.CharSequence\n\nString类型实现了`CharSequence`接口\n\n+ codePoints():将当前字符串中所有的`Unicode`码点构成流\n\n### java.util.Random\n\n+ ints()\n+ ints(int begin, int end)\n+ ints(long size)\n+ ints(long size, int begin, int end)\n+ longs().....\n+ doubles()......\n+ 产生随机数流,如果提供了size,这个刘就是具有给定元素数量的有限流,当提供了边界时,其元素位于`begin`和`end`之间\n\n### java.util.Optional(Int/Long/Double)\n\n+ of(T value):用提供的基本数据类型产生一个可选对象\n+ getAsT():产生当前可选对象的值,如果不存在抛出`NoSuchElementException`异常\n+ orElse(value):返回可选对象值,如果不存在返回value\n+ orElseGet(Supplier other):产生当前可选对象的值,如果不存在,产生可替换的值\n+ isPresent(Consumer c):如果当前可选对象不为空,将值传递给c\n\n### java.util.(Int/Long/Double) SummaryStatistics\n\n+ getCount()\n+ getSum()\n+ getAverage()\n+ getMax()\n+ getMin()\n+ 产生收集到的元素个数、总和、平均值、最大值、最小值\n\n# 并行流\n\n流使得并行处理块操作变得很容易,但是需要遵循一些规则,首先是产生一个并行流,有两种方式可以产生并行流:\n\n+ parallelStream():可以从任何集合中获取一个并行流\n+ parallel():将流转换成并行流\n\n只要在终结方法执行时,流处于并行模式,那么所有的中间流操作都将被并行化\n\n当流操作并行运行时,其目标时要让其返回结果与顺序执行时返回的结果相同。重要的是,这些操作可以以任意顺序执行。\n\n来看这样一段代码:\n\n```java\nStream<String> stream = Stream.of(\"Java\", \"Golang\", \"C/C++\", \"Rust\");\nint[] shrtWords = new int[12];\nstream.parallel().forEach(e -> {\n if (e.length() < 12) {\n shrtWords[e.length()]++;\n }\n});\nSystem.out.println(shrtWords);\n```\n\n**注意**:这是一种很糟糕的代码,传递给forEach的函数会在多个并发线程中运行,每个都会更新共享的数组。当数据量稍微大那么一点点时,运行出来的数据就会产生不同的结果。\n\n我们并不需要错误的结果,就需要保证传递给并行流的操作安全的并行执行,而达到这个目的的最佳方式是原理易变状态。\n\n在本例中,我们可以用长度将字符串群组,然后分别对他们进行计数,就可以安全的并行化这项计算。\n\n```java\nMap<Integer, Long> collect =\n stream.parallel().filter(s -> s.length() < 12)\n .collect(Collectors.groupingBy(String::length, Collectors.counting()));\nSystem.out.println(collect);\n```\n\n默认情况下,从有序集合、范围、生成器和迭代产生的流,或者通过调用`Stream.sorted`产生的流,都是有序的。\n\n排序并不排斥高效的并行处理,例如,当计算`stream.map(fun)`时,流可以被划分为n个部分,他们会被并行处理,然后按照顺序重新组装起来。\n\n当我们放弃排序需求时,有些操作可以被更加高效的并行化。通过在流上游调用`unordered`方法,可以标明我们对排序不感兴趣。`distinct`就是从这种方式中获益的一种操作,在有序流中,`distinct`会保留所有元素中的第一个,这是对并行化的一种阻碍。在所有线程都处理完之前,我们不知道应该丢弃哪些元素。如果可以接受保留唯一元素中任意一个的做法,那么所有部分就可以并行处理。\n\n还有放弃排序来提高`limit`速度。\n\n我们需要知道,合并映射表的代价是非常高的,正是这个原因,`Collectors.groupingByConcurrent`方法使用了共享的并发映射表。为了从并行化中受益,映射表中值的顺序不会与流中的顺序相同。\n\n```java\nConcurrentMap<Integer, List<String>> collect1 = stream.parallel().collect(Collectors.groupingByConcurrent(String::length));\n```\n\n## 警告\n\n不要试图将一个集合生成流之后,再修改这个集合,此时流不会收集他们的元素,准确来说,流操作都是惰性的,直到终结操作时才对集合进行修改才是可行的。\n\n为了让并行流正常工作,需要满足以下条件:\n\n+ 数据应该存在内存中,必须等数据到达是非常低效的\n+ 流可以被高效分为若干个子部分,由数组或平衡二叉树支撑的流都可以工作的很好,但是`iterate`返回的结果不行\n+ 流操作的工作量应该具有较大的规模,如果总工作负载并不是很大,那么搭建并行计算时所付出的代价就没有什么意义。\n+ 流操作不应该被阻塞。\n\n换句话说,不要讲所有的流操作都转化成并行流,只有在对已经位于内存中,并且数据执行大量计算操作时,才应该使用并行流。\n\n## 总结\n\n### java.util.stream.BaseStream\n\n+ parallel():产生一个与当前流中元素相同的并行流\n+ unordered():产生一个与当前流中元素相同的无序流\n\n### java.util.Collection\n\n+ parallelStream():使用当前集合中的元素产生一个并行流\n\n\n\n\n\n\n\n","tags":["Java","Java流"]},{"title":"《Go随机数》——学习笔记","url":"/2022/08/09/go随机数/","content":"\n在GO语言中,提供了随机数的核心方法`rand`,但是go的随机数其实并不随机,是一个伪随机数,简单来说就是Go的随机数生成需要依赖种子值,对于相同的种子值产生的随机数顺序是相同的\n\n为了产生一个随机数,我们需要给rand设置一个不重复种子,最好的选择当然就是时间\n\n```java\nrand.Seed(time.Now().UnixNano())\n```\n\n**注意**:Seed接收的是一个int64类型的数字,需要用Unix将time转换成int64类型\n\n然后就可以使用rand.Intn(max int64)来进行取随机数了\n\n```java\nrand.Seed(time.Now().UnixNano())\nrandomInt := int64(rand.Intn(100))\nfmt.Println(randomInt)\n```\n\n","tags":["GoLang"]},{"title":"《Java序列化与反序列化》","url":"/2022/08/04/序列化与反序列化/","content":"\n这是一个新的系列,每天写一点自己的想法。\n\n\n\n突然想起来之前在面试的时候,面试官问过我这么一个问题:Java中创建类实例有哪几种方法\n\n+ new\n+ Java反射的newInstance\n+ clone\n+ 反序列化\n\n然后面试官又问我:反序列化你用过吗\n\n我就答不上来了,只知道有这个概念,具体怎么实现还真没去看过,今天来试一试\n\n## 编写实体类\n\n这里随便来写一个,但是记得实现序列化接口`Serializable`,没啥技术含量\n\n```java\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * @author JiangLiu\n * @Date 2022/8/4 15:38:24\n * @description 需要序列化的对象\n */\n@Data\n@AllArgsConstructor\npublic class People implements Serializable {\n private String name;\n private int age;\n}\n```\n\n## 编写序列化类\n\n这里主要用到的就是IO流,反序列化的处理可能不是那么妥当,但是又一直报警告,明天再处理一下\n\n```java\nimport java.io.*;\n\n/**\n * @author JiangLiu\n * @Date 2022/8/4 15:39:56\n * @description\n */\npublic class SerializableUtil<T> {\n\n /**\n * 序列化\n */\n public void serialize(T obj, String fileName) throws IOException {\n OutputStream out = new FileOutputStream(fileName);\n ObjectOutputStream outputStream = new ObjectOutputStream(out);\n outputStream.writeObject(obj);\n // 用完记得关闭\n outputStream.close();\n }\n\n /**\n * 反序列化\n */\n public T deSerialize(String fileName) throws IOException, ClassNotFoundException {\n InputStream in = new FileInputStream(fileName);\n ObjectInputStream inputStream = new ObjectInputStream(in);\n Object a = null;\n a = inputStream.readObject();\n return (T) a;\n }\n}\n```\n\n## 编写main方法\n\n```java\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author JiangLiu\n * @Date 2022/8/4 15:38:06\n * @description\n */\npublic class main {\n public static void main(String[] args) {\n // 造几个People\n List<People> people = new ArrayList<>();\n people.add(new People(\"张三\", 15));\n people.add(new People(\"李四\", 18));\n String fileName = \"test.txt\";\n SerializableUtil<List<People>> s = new SerializableUtil<>();\n // 对象序列化\n try {\n s.serialize(people, fileName);\n } catch (IOException e) {\n throw new RuntimeException(e);\n }\n // 反序列化\n List<People> list = null;\n try {\n list = s.deSerialize(fileName);\n } catch (Exception e) {\n throw new RuntimeException(e);\n }\n System.out.println(list);\n }\n}\n```\n\n## 运行结果\n\n```tex\n[People(name=张三, age=15), People(name=李四, age=18)]\n```\n\n完美","tags":["每天写点啥"]},{"title":"《RabbitMQ学习笔记》——读书笔记","url":"/2022/07/27/RabbitMQ学习笔记/","content":"\n# 什么是MQ\n\nMQ是`Message Queue`的简称,就是一个消息队列,队列嘛,FIFO先进先出,与普通队列的区别就是,MQ中存放的是消息,并且它是一种跨进程的通信机制,用于上下游传递消息,能够实现上下游之间的解耦\n\n# MQ在SpringBoot中的配置\n\n```yaml\nspring:\n\trabbitmq:\n\t\thost: // rabbitmq的地址\n\t\tport: // mq的端口\n\t\tusername: // mq的用户名\n\t\tpassword: // mq的密码\n\t\tvirtual-host: // 虚拟消息服务器\n\t\tpublisher-confirms: // 是否开启发送确认\n\t\tpublisher-returns: // 是否开启发送失败退回\n\t\ttemplate:\n\t\t\tmandatory: // 生产者是否启用强制消息\n\t\t\tretry:\n\t\t\t\tenable: // 生产者是否开启重启\n\t\tlistener:\n\t\t\tacknowledge-mode: // 消费者ack模式\n\t\t\tretry:\n\t\t\t\tenable: // 消费者是否重试\n\t\t\t\tmax-attempts: // 消费者重试次数\n```\n\n## 配置详解\n\n### virtual-host\n\n`virtualHost`虚拟消息服务器,每个virtualHost相当于一个独立的MQ服务器,每个VirtualHost之间消息是隔离的,exchange、queue、message不能互通\n\n### publisher-confirms\n\n这个配置是为了在MQ和生产者之间的消息能够可靠传输,是MQ的扩展\n\n生产者推送消息到消息队列后,会触发两个回调函数`ConfirmCallback`和`ReturnCallback`,从消息推送的结果来看,一共有四种组合:\n\n+ 消息推送到server,但是在server里找不到交换机\n+ 消息推送到server,找到了交换机但是找不到队列\n+ 消息推送到server了,交换机和队列都没找到\n+ 消息推送成功\n\n生产者和消费者确认详见后文\n\n\n\n\n\n\n\n# 生产者和消费者确认\n\n由于MQ的传输协议方法无法确认生产者和消费者是否成功发布或者消费信息,所以生产者和消费者都需要一种传递和处理确认的机制\n\n## 消费者确认\n\n### 自动ACK\n\n在MQ中有一种自动确认模式机制,消息发送成功后立即被视为传递成功,这种模式以更高的吞吐量来降低交付和消费者处理的安全性为代价,如果消费者的TCP连接或通道在消息发送成功之前关闭,那么消息就会丢失,所以这种方法被视为是不安全的。\n\n在这个模式中,当方法没有异常执行完毕后,会对MQ发出ACK,若方法出现异常,会对MQ发出nack,消息重回队列。\n\n### 手动ACK\n\n常用API:\n\n+ channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false/true)\n + 消息确认,第一个参数是队列名称,第二个参数是multiple\n + multiple:是否一次性ack所有deleveryTag的消息\n+ channel.basicReject(deleveryTag, requeue)\n + 拒绝消息\n + requeue:bool类型,false表示将这条消息丢弃,true表示消息重回队列\n+ channel.basicNack(deliveryTag, multiple, requeue)\n + 拒绝消息\n + deliveryTag:队列名称\n + multiple:是否拒绝deliveryTag的所有消息\n + requeue:是否返回队列\n","tags":["读书笔记","SpringBoot","RabbitMQ"]},{"title":"《Java流中的generate与iterator》——读书笔记","url":"/2022/07/26/stream中的generate与iterator/","content":"\n# Java流中的generate与iterator\n\n在Java流中,有两个创建无限流的方法:\n\n+ stream().generate()\n+ stream().iterator()\n\n## iterator\n\n从源码中给的解释来看\n\n```tex\nReturns an infinite sequential ordered Stream produced by iterative application of a function f to an initial element seed, producing a Stream consisting of seed, f(seed), f(f(seed)), etc.\nThe first element (position 0) in the Stream will be the provided seed. For n > 0, the element at position n, will be the result of applying the function f to the element at position n - 1.\nThe action of applying f for one element happens-before the action of applying f for subsequent elements. For any given element the action may be performed in whatever thread the library chooses.\n形参:\nseed – the initial element f – a function to be applied to the previous element to produce a new element\n返回值:\na new sequential Stream\n```\n\niterator创建的无限流是根据`seed`与`initial element seed`来创建的,简单来说就是一个起始元素seed,一个创建的规则\n\n```java\nStream<BigInteger> stream = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE)).limit(10);\n System.out.println(Arrays.toString(stream.filter(n -> n.compareTo(new BigInteger(String.valueOf(1794952398L))) < 0).toArray()));\n```\n\n这里用limit来限制一下产生的无限流,否则无法正常输出\n\n## generate\n\n同样还是来看源码中给的解释\n\n```tex\nReturns an infinite sequential unordered stream where each element is generated by the provided Supplier. This is suitable for generating constant streams, streams of random elements, etc.\n形参:\ns – the Supplier of generated elements\n返回值:\na new infinite sequential unordered Stream\n```\n\n对于generate来说,只提供给我们一个参数`Supplier`,翻译过来叫 供应商,里面存放着产生供应流的规则\n\n```java\nStream<Integer> stream1 = Stream.generate(new Supplier<Integer>() {\n static int a = 0;\n\n @Override\n public Integer get() {\n return a++;\n }\n }).limit(20);\n System.out.println(Arrays.toString(stream1.toArray()));\n```\n\n这里同样用limit限制一下\n\n至于具体的使用场景,后续会继续更新\n","tags":["Java","Java流"]},{"title":"《MyBatis 和 MyBatis Plus冲突问题》——解决模块","url":"/2022/07/25/mybatisplus和mybatis冲突问题/","content":"\n# 问题\n\n在工作的时候,遇到了一个奇怪的问题,使用 MyBatis Plus 的IService模板中的list对数据库进行操作时,报了个`Invalid bound statement (not found)`\n\n# 解决\n\n搜了半天,主要是以下几个问题:\n\n+ xml 的 namespace 不正确\n+ Mapper.java 中的方法在 Mapper.xml 中不存在\n+ xml 返回类型配置错误\n+ 没有构建成功\n\n但是这些问题都检查了,没问题,用 Maven Helper 查看了一下依赖冲突,看到项目中同时引入了mybatis和mybatisplus,具体冲突的包有三个:\n\n+ mapper-spring-boot-starter\n+ mybatis-spring-boot-starter\n+ mybatis-plus-extension\n\n首先,`mybatis-spring-boot-starter`包是用来连接mybatis和springboot的中间件,这个 mybatis-plus-boot-starter能够代替,冲突了,去掉\n\n然后是 `mapper-spring-boot-starter`包,这个包是用来导入公共mapper模板的,具体作用暂时不知道,但是不去掉也不能运行\n\n最后是`mybatis-plus-extension`,这个东西具体作用没查到,只知道他是mybatisplus的扩展插件,但是去掉之后service层的函数全都无法调用了\n\n","tags":["MyBatis Plus","SpringBoot","问题解决"]},{"title":"《Java常用注解》","url":"/2022/07/15/java常用注解/","content":"\n# @PostConstruct\n\n从Java EE 5 之后,Servlet增加了两个影响Servlet生命周期的注解:\n\n+ @PostConstruct\n+ @PreConstruct\n\n## @PostConstruct\n\n被这个注解修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,被PostConstruct修饰的方法会在构造函数之后,init之前运行\n\n## @PreConstruct\n\n被这个注解修饰的方法会在服务器卸载Servlet的时候运行,并且只会调用一次,类似于destroy","tags":["Java","注解"]},{"title":"《lambda表达式的语法》——读书笔记","url":"/2022/07/15/lambda表达式/","content":"\n# lambda表达式的语法\n\n了解过javax.swing.Timer函数和Comparator比较器的可以发现,这两个例子有一些共同点,都是将某一段代码块传到某个对象,如果可以直接传入一段代码块,那代码会变得非常简洁,但是Java并不支持这种方法,因为这会让Java语言变得一团糟\n\n在 java 8 后,加入了lambda表达式,这是一个可传递的代码块,可以让某个接口不写实现类而直接使用\n\nnew Timer中需要传入一个ActionListener接口,实际上只是调用这个接口中的actionPerformed函数,Comparator也是同理\n\n案例:\n\n```java\npublic class lambdaTest {\n public static void main(String[] args) {\n String[] a = new String[]{ \"Mercury\" , \"Venus\" , \"Earth\" , \"Mars\" ,\n \"Jupiter\" , \"Saturn\" , \"Uranus\" , \"Neptune\"};\n Arrays.sort(a, (left, right) -> {\n return left.length() - right.length();\n });\n System.out.println(a);\n\n Timer t = new Timer(100, event ->{\n System.out.println(new Date());\n });\n t.start();\n JOptionPane.showMessageDialog(null, \"Quit\");\n System.exit(0);\n }\n}\n```\n\n# 函数式接口\n\nJava中已经有了很多封装代码块地接口,如AactionListener、Comparator,lambda与这些接口是兼容的\n\n对于只有一个抽象方法的接口,需要用到这种接口的对象时,可以使用lambda表达式,这种接口成为函数式接口\n\n+ 为什么Comparator接口也能成为函数式接口,明明有compare、equals两个抽象函数\n + 对于接口重写Object的公共方法是不算入函数式接口中的,也就是说Comparator只有compare一个非公共抽象函数\n\n以Arrays.sort为例,在底层,sort方法会接收Comparator的某个类的对象,在这个对象上再调用compare方法执行lambda表达式的方法体。\n\nlambda表达式可以转换成接口\n\n```java\nTimer t = new Timer(100, event ->{\n System.out.println(new Date());\n});\n```\n\n与原来的写法相比,这个可读性要高得多\n\n实际上在Java中 lambda 表达式的作用非常有限,也只能转换为函数式接口,在其他语言中,可以声明函数类型、声明这些类型的变量,还可以使用变量保存函数表达式。\n\n# 方法引用\n\n有时候我们希望可已经有现成的方法可以完成你想要传递到其他代码的某个动作,比如希望定时器事件打印这个事件对象\n\n```java\nTimer t = new Timer(1000, event -> System.out.println(event)):\n```\n\n但是入股哟能直接把print方法传递到Timer构造器就更简洁了,lambda\n\n表达式也能够做到\n\n```java\nTimer t = new Timer(100, System.out::println);\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n","tags":["Java","读书笔记"]},{"title":"《MyBatisPlus 常用注解》","url":"/2022/07/15/mybatisplus常用注解/","content":"\n\n\n# @TableName\n\n用法\n\n```java\n@TableName(\"sys_user\")\n```\n\n描述:表名注解,标识实体类对应表\n\n使用位置:实体类类注解\n\n属性:\n\n+ value:表名\n+ schema:用来指定模式名称,如果使用的是mysql,则指定数据库名称,如果使用oracle,则为schema\n+ keepGlobalPrefix:是否保持使用全局的tablePrefix的值\n+ resultMap:xml中resultMap的id\n+ autoResultMap:是否自动构建ResultMap\n+ excludeProperty:需要排除的属性名\n\n# @TableId\n\n用法\n\n```java\n@TableId(value=\"user_id\", type=IdType.ASSIGN_UUID)\n```\n\n 描述:主键属性\n\n使用位置:实体类主键字段\n\n属性:\n\n+ value:主键字段名\n+ type:指定主键类型\n\n## Type属性值\n\n+ AUTO:数据库ID自增\n+ NONE:无状态,未设置主键类型(跟随全局,全局默认为INPUT)\n+ INPUT:insert前自行设置\n+ ASSIGN_ID:分配ID,使用接口`IdentifierGenerator`的`nextId`,实现类默认为雪花算法\n+ ASSIGN_UUID:分配UUID\n\n## @TableField\n\n用法\n\n```java\n@TableName(\"sys_user\")\npublic class User {\n @TableId\n private Long id;\n @TableField(\"nickname\")\n private String name;\n private Integer age;\n private String email;\n}\n```\n\n描述:字段注解(非主键)\n\n属性:\n\n+ value:数据库字段名\n+ exist:是否为数据库字段\n\n","tags":["注解","MyBatis Plus"]},{"title":"《事务传播行为》","url":"/2022/07/15/事务传播行为/","content":"\n# 什么是事务传播行为\n\n我们在Spring中使用事务时,经常会在一个事务中调用另外一个事务,这种事务嵌套的控制方式就是事务传播行为\n\n# 事务传播行为的七种方式\n\n+ propagation_required\n + 事务传播的默认形式,如果当前没有事务,就新建一个事务,如果已经存在事务,就加入到这个事务中\n+ propagation_supports\n + 支持当前事务,如果当前没有事务,就以非事务方式执行\n+ propagation_mandatory\n + 使用当前事务,如果当前没有事务,就抛出异常\n+ propagation_requires_new\n + 新建事务,如果当前存在事务,就把当前事务挂起\n+ propagation_not_supported\n + 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起\n+ propagation_never\n + 以非事务方式执行,如果当前存在事务,就抛出异常\n+ propagation_nested\n + 如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行propagation_required类似的操作\n\n\n\n总结\n\n如果事务嵌套,子事务回滚,如果子事务没有将父事务挂起,父事务也会回滚,不管父事务中是否有对子事务进行异常捕获","tags":["SpringBoot","事务","MySQL"]},{"title":"《事务失效》","url":"/2022/07/15/事务失效的几种情况与原因/","content":"\n# 事务失效的几种情况与原因\n\n+ service没有托管给spring\n+ 抛出受检异常\n+ 业务自己捕获了异常\n+ 切面顺序导致\n+ 非public方法\n+ 父子容器\n+ 方法被final修饰\n+ 方法被static修饰\n+ 调用本类方法\n+ 多线程调用\n+ 错误的传播行为\n+ 使用了不支持事务的存储引擎\n+ 数据源没有配置事务管理器\n+ 被代理的类过早实例化\n\n## service没有托管给spring\n\n事务的前提是service必须是一个bean对象\n\n## 抛出受检异常\n\nspring默认回滚的是runtimeException,如果要触发其他异常的回滚,可以通过rollbackFor进行配置\n\n## 业务自己捕获了异常\n\nspring只有捕捉到了业务抛出的异常时,才会进行后续处理,如果业务自己捕获了异常并进行处理,事务无法感知\n\n## 切面顺序导致\n\n\n\n因为spring事务本质上也是一个切面,自定义切面捕捉到了异常但是没有往外抛出,事务切面捕获不到异常\n\n## 非public方法\n\nspring事务默认生效的方法权限都必须为public\n\n解决办法:\n\n+ 修改方法为public\n+ 修改TransactionAttributeSource,将publicMethodsOnly修改为false\n+ 开启AspectJ代理\n\n## 父子容器\n\n原因:子容器扫描范围过大,将未加事务配置的service扫描进来\n\n这个一般用于spring整合springmvc中,springboot没有父子容器\n\n## 方法用final修饰\n\nspring事务是用动态代理实现的,如果方法使用了final修饰,代理类无法对目标类进行重写,就无法实现事务\n\n## 方法用static修饰\n\n原因和final一样\n\n## 调用本类方法\n\n调用本类方法不经过代理,就无法进行增强\n\n## 多线程调用\n\n原因:spring的事务是通过数据库连接来实现的,而数据库连接spring是放在threadLocal里面的,同一个事务只能用同一个数据库连接。而多线程场景下,拿到的数据库连接不同,即属于不同事务\n\n## 错误的传播行为\n\n详情看 事务传播行为\n\n## 使用了不支持事务的存储引擎\n\n比如mysql中的MyISAM就不支持事务\n\n## 数据源没有配置事务管理器\n\nspringboot中默认开启事务管理器\n\n## 被代理的类被过早实例化\n\n具体应该要看源码","tags":["SpringBoot","事务","MySQL"]},{"title":"《常用类与接口》","url":"/2022/07/15/常用类与接口/","content":"\n# Comparator接口\n\n遇到的问题:在开发中需要对一个含有实体类的泛型数组进行排序\n\ncomparator接口可以实现这个功能\n\n简单用法:\n\n```java\npublic class Collections_ {\n public static void main(String[] args) {\n List list = new ArrayList();\n list.add(\"tom\");\n list.add(\"smith\");\n list.add(\"king\");\n list.add(\"king\");\n list.add(\"king\");\n list.add(\"milan\");\n\n Collections.sort(list, new Comparator() {\n @Override\n public int compare(Object o1, Object o2) {\n if (o1 instanceof String && o2 instanceof String){\n return (((String) o1).length() - ((String) o2).length());\n }\n return 0;\n }\n });\n System.out.println(list);\n }\n}\n```\n\n# Cloneable接口\n\n克隆接口,这个接口中提供了一个安全的clone方法\n\n# DefaultIdentifierGenerator类\n\n位于`com.baomidou.mybatisplus.core`包中,用于生成雪花算法ID\n\n```java\nlong id = new DefaultIdentifierFenerator().nextId(new Objec);\n```\n\n\n\n","tags":["Java"]},{"title":"《SpringBoot常用注解》","url":"/2022/07/15/SpringBoot常用注解/","content":"\n# Springboot常用注解\n\n+ Value:属性赋值\n+ Component:与业务层、dao层、控制层不相关的类需要在spring容器中创建使用\n+ Mapper:注解当前类为mapper类\n+ MapperScan:如果想要每个接口都变成实现类,那么需要在每个接口上添加Mapper注解,比较麻烦,可以使用MapperScan进行扫描\n+ Service:表示当前层为Service层\n+ Controller:控制层对象的创建\n+ RestController:Controller与ResponseBody的结合,让当前类下web请求返回数据而不是视图\n+ Autowired:根据类型自动注入\n+ Resouce:根据名称自动注入\n\n## @SpringBootApplication注解\n\n这个注解包含了三个注解,分别是:\n\n@SpringBootConfiguration:自动扫描添加了@Configuration注解的类,读取其中的配置信息\n\n@EnableAutoConfiguration:开启自动配置告诉Springboot基于所添加的依赖去猜测你想要如何配置spring,比如说我们引入了spring-boot-starter-web,而这个启动器中帮我们添加了tomcat、SpringMVC的依赖,此时自动配置就只要你是要开发一个web应用,就会帮我们去完成web以及springMVC的默认配置。\n\n@ComponentScan:配置组件扫描\n\n## @Transaction\n\n事务注解\n\n### 失效场景\n\n具体看我的另一篇博客——《事务失效》\n\n## @EnableScheduling\n\n开启定时任务,配合@Schedule注解使用,使这个注解功能可用\n\n## @Bean\n\n告诉方法产生一个bean对象,然后将这个bean交给spring进行管理,在产生bean的时候这个方法会调用一次,然后将产生的bean对象放入spring容器中\n\n## @PostConstruct\n\n在spring中,有一个接口叫`InitializationBean`,这个接口允许bean在合适的时机通过设置注解的初始化属性从而调用初始化方法,并且在这个接口中有一个定义好的初始化方法`afterPropertiesSet`\n\n**但是**,spring并不推荐使用这种方法来调用初始化,它会将不必要的代码耦合到spring\n\n相比于`InitializationBean`,spring更推荐我们使用`@PostConstruct`注解\n\n至于为什么推荐使用`@PostConstruct`:\n\n+ InitializationBean是直接执行方法来进行初始化的,会耦合进Spring项目\n+ @PostConstruct注解是通过反射机制来初始化的\n\n## @ConfigurationProperties\n\n在SpringBoot中,如果我们想要获取到配置文件中某个属性的值,有两个方法:\n\n+ @Value\n+ @ConfigurationProperties\n\n这里我们只介绍第二个\n\n```yaml\nconfig:\n\tusername: JiangLiu\n\tpassword: 123\n```\n\n如果我们想要获取username,只需要这样\n\n```java\n\n@Component\n@ConfigurationProperties(prefix = \"config\")\npublic class TestBean{\n \n private String username;\n \n private String password;\n}\n```\n\n## @Qualifier\n\n`@Autowired`注解可以帮助我们进行spring依赖注入,但是很多场景下只用这个注解,spring并不知道我们需要注入哪个bean,比如B、C两个类同时继承A接口,这时我们注入A,spring就会抛出`NoUniqueBeanDefinitionException`异常,这时就需要使用@Qualifier注解\n","tags":["注解","SpringBoot"]}]