开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

详解Java 泛型(1)

发表于 2021-11-01

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」。

Java 泛型是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

泛型的本质是参数化类型

假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?

这种情况可以使用 Java 泛型。

java 中泛型标记符:

  • E - Element (在集合中使用,因为集合中存放的是元素)
  • T - Type(Java 类)
  • K - Key(键)
  • V - Value(值)
  • N - Number(数值类型)
  • ? - 表示不确定的 java 类型
  • 代码测试:*
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
java复制代码package csdncom.tt;

/**
* Created by java李杨勇 on 2021/11/01
*/
public class GenericMethodTest {
// 泛型方法 printArray
public static <E> void printArray(E[] inputArray) {
// 输出数组元素
for (E element : inputArray) {
System.out.printf("%s ", element);
}
System.out.println();
}

public static void main(String args[]) {
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'j', 'a', 'v', 'a', 'l','y','y' };

System.out.println("整型数组元素为:");
printArray(intArray); // 传递一个整型数组

System.out.println("\n双精度型数组元素为:");
printArray(doubleArray); // 传递一个双精度型数组

System.out.println("\n字符型数组元素为:");
printArray(charArray); // 传递一个字符型数组
}
}

编译代码,运行结果如下:

整型数组元素为:
1 2 3 4 5

双精度型数组元素为:
1.1 2.2 3.3 4.4

字符型数组元素为:
j a v a l y y

有界的类型参数:

有时候,你会想限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。

要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。

实例

下面的例子演示了”extends”如何使用在一般意义上的意思”extends”(类)或者”implements”(接口)。该例子中的泛型方法返回三个可比较对象的最大值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java复制代码package csdncom.tt;
/**
* Created by java李杨勇 on 2021/11/01
*/
public class MaximumTest {
// 比较三个值并返回最大值
public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
T max = x; // 假设x是初始最大值
if (y.compareTo(max) > 0) {
max = y; // y 更大
}
if (z.compareTo(max) > 0) {
max = z; // 现在 z 更大
}
return max; // 返回最大对象
}

public static void main(String args[]) {
System.out.printf("%d, %d 和 %d 中最大的数为 %d\n\n", 3, 4, 5, maximum(3, 4, 5));

System.out.printf("%.1f, %.1f 和 %.1f 中最大的数为 %.1f\n\n", 6.6, 8.8, 7.7, maximum(6.6, 8.8, 7.7));

System.out.printf("%s, %s 和 %s 中最大的数为 %s\n", "pear", "apple", "orange", maximum("pear", "apple", "orange"));
}

}

编译代码,运行结果如下:

3, 4 和 5 中最大的数为 5

6.6, 8.8 和 7.7 中最大的数为 8.8

pear, apple 和 orange 中最大的数为 pear

大家点赞、收藏、关注、评论啦 、

打卡文章更新 92/ 100天

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringCloud微服务实战——搭建企业级开发框架(十一

发表于 2021-11-01

  作为Spring Cloud的子项目之一,Spring Cloud OpenFeign以将OpenFeign集成到Spring Boot应用中的方式,为微服务架构下服务之间的调用提供了解决方案。首先,利用了OpenFeign的声明式方式定义Web服务客户端;其次还更进一步,通过集成Ribbon或Eureka实现负载均衡的HTTP客户端。
  OpenFeign 可以使消费者将提供者提供的服务名伪装为接口进行消费,消费者只需使用“Service 接口+ 注解”的方式。即可直接调用 Service 接口方法,而无需再使用 RestTemplate 了。其实原理还是使用RestTemplate,而通过Feign(伪装)成我们熟悉的习惯。
  GitEgg框架除了新建Fegin服务之外,还定义实现了消费者Fegin-api,在其他微服务调用的时候,只需要引入Fegin-api即可直接调用,不需要在自己重复开发消费者调用接口。

1、在GitEgg-Platform工程的子工程gitegg-platform-cloud中引入spring-cloud-starter-openfeign依赖,重新install GitEgg-Platform工程,然后GitEgg-Cloud项目需要重新在IDEA中执行Reload All Maven Projects。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>GitEgg-Platform</artifactId>
<groupId>com.gitegg.platform</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>gitegg-platform-cloud</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>

<dependencies>
<!-- Nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Nacos 分布式配置-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- OpenFeign 微服务调用解决方案-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>

</project>

  我们从系统架构设计方面考虑,GitEgg-Cloud下的gitegg-service作为业务逻辑处理模块,gitegg-service-api作为微服务统一对外提供接口的模块,这里在测试的时候需要用到两个微服务之间的调用,我们这里在gitegg-service下gitegg-service-base里面新建测试代码,和gitegg-service-system之间相互调用。注意,这里需要说明,gitegg-service-api并不是继承gitegg-service做业务扩展,而是对外提供接口的抽象,比如现在有A、B、C三个系统A、B都需要调用C的同一个方法,如果按照业务逻辑来罗列代码的话,那么就需要在A和B中写相同的调用方法来调用C,这里我们抽出来一个api模块,专门存放调用微服务C的调用方法,在使用时,A和B只需要引入C的jar包即可直接使用调用方法。
2、在gitegg-service-system-api工程中,引入SpringBoot,SpringCloud,Swagger2的依赖,新建ISystemFeign.java和ApiSystemDTO.java,作为OpenFeign调用微服务的公共方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>GitEgg-Cloud</artifactId>
<groupId>com.gitegg.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>gitegg-service-api</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>pom</packaging>
<modules>
<module>gitegg-service-base-api</module>
<module>gitegg-service-bigdata-api</module>
<module>gitegg-service-system-api</module>
</modules>

<dependencies>
<!-- gitegg Spring Boot自定义及扩展 -->
<dependency>
<groupId>com.gitegg.platform</groupId>
<artifactId>gitegg-platform-boot</artifactId>
</dependency>
<!-- gitegg Spring Cloud自定义及扩展 -->
<dependency>
<groupId>com.gitegg.platform</groupId>
<artifactId>gitegg-platform-cloud</artifactId>
</dependency>
<!-- gitegg swagger2-knife4j -->
<dependency>
<groupId>com.gitegg.platform</groupId>
<artifactId>gitegg-platform-swagger</artifactId>
</dependency>
</dependencies>


</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
kotlin复制代码package com.gitegg.service.system.api.feign;

import com.gitegg.platform.boot.common.base.Result;
import com.gitegg.service.system.api.dto.ApiSystemDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "gitegg-service-system")
public interface ISystemFeign {

/**
* OpenFeign测试Get
*
* @param id
* @return
*/
@GetMapping("/system/api/by/id")
Result<Object> querySystemById(@RequestParam("id") Long id);

/**
* OpenFeign测试Post
*
* @param apiSystemDTO
* @return ApiSystemDTO
*/
@PostMapping("/system/api/by/dto")
Result<ApiSystemDTO> querySystemByDto(@RequestBody ApiSystemDTO apiSystemDTO);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
less复制代码package com.gitegg.service.system.api.dto;

import lombok.Data;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Data
public class ApiSystemDTO {

@NotNull
@Min(value = 10, message = "id必须大于10")
@Max(value = 150, message = "id必须小于150")
private Long id;

@NotNull(message = "名称不能为空")
@Size(min = 3, max = 20, message = "名称长度必须在3-20之间")
private String name;

}

2、在gitegg-service-system工程中,修改SystemController.java,添加需要被微服务调用的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
kotlin复制代码package com.gitegg.service.system.controller;

import com.gitegg.platform.boot.common.base.Result;
import com.gitegg.service.system.dto.SystemDTO;
import com.gitegg.service.system.service.ISystemService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@RequestMapping(value = "system")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Api(tags = "gitegg-system")
@RefreshScope
public class SystemController {

private final ISystemService systemService;

@Value("${spring.datasource.maxActive}")
private String nacosMaxActiveType;

@GetMapping(value = "list")
@ApiOperation(value = "system list接口")
public Object list() {
return systemService.list();
}


@GetMapping(value = "page")
@ApiOperation(value = "system page接口")
public Object page() {
return systemService.page();
}

@GetMapping(value = "exception")
@ApiOperation(value = "自定义异常及返回测试接口")
public Result<String> exception() {
return Result.data(systemService.exception());
}

@PostMapping(value = "valid")
@ApiOperation(value = "参数校验测试接口")
public Result<SystemDTO> valid(@Valid @RequestBody SystemDTO systemDTO) {
return Result.data(systemDTO);
}

@PostMapping(value = "nacos")
@ApiOperation(value = "Nacos读取配置文件测试接口")
public Result<String> nacos() {
return Result.data(nacosMaxActiveType);
}

@GetMapping(value = "api/by/id")
@ApiOperation(value = "Fegin Get调用测试接口")
public Result<Object> feginById(@RequestParam("id") String id) {
return Result.data(systemService.list());
}

@PostMapping(value = "api/by/dto")
@ApiOperation(value = "Fegin Post调用测试接口")
public Result<Object> feginByDto(@Valid @RequestBody SystemDTO systemDTO) {
return Result.data(systemDTO);
}
}

3、参照gitegg-service-system工程,在gitegg-service-base工程下,引入gitegg-service-system-api依赖,新建BaseController.java、GitEggBaseApplication.java、bootstrap.yml作为服务调用方:
pom.xml:

1
2
3
4
5
6
7
8
xml复制代码    <dependencies>
<!-- gitegg-service-system 的fegin公共调用方法 -->
<dependency>
<groupId>com.gitegg.cloud</groupId>
<artifactId>gitegg-service-system-api</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>

BaseController.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
kotlin复制代码package com.gitegg.service.base.controller;

import com.gitegg.platform.boot.common.base.Result;
import com.gitegg.service.system.api.dto.ApiSystemDTO;
import com.gitegg.service.system.api.feign.ISystemFeign;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@RequestMapping(value = "base")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Api(tags = "gitegg-base")
@RefreshScope
public class BaseController {

private final ISystemFeign systemFeign;

@GetMapping(value = "api/by/id")
@ApiOperation(value = "Fegin Get调用测试接口")
public Result<Object> feginById(@RequestParam("id") Long id) {
return Result.data(systemFeign.querySystemById(id));
}

@PostMapping(value = "api/by/dto")
@ApiOperation(value = "Fegin Post调用测试接口")
public Result<Object> feginByDto(@Valid @RequestBody ApiSystemDTO systemDTO) {
return Result.data(systemFeign.querySystemByDto(systemDTO));
}

}

GitEggBaseApplication.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
kotlin复制代码package com.gitegg.service.base;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;

/**
* gitegg-base 启动类
*/
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.gitegg")
@ComponentScan(basePackages = "com.gitegg")
@MapperScan("com.gitegg.*.*.mapper")
@SpringBootApplication
public class GitEggBaseApplication {

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

}

bootstrap.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
yaml复制代码server:
port: 8002
spring:
application:
name: gitegg-service-base
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
prefix: gitegg-service-system
group: DEFAULT_GROUP
enabled: true

4、分别启动gitegg-service-base和gitegg-service-system项目,打开浏览器,访问http://127.0.0.1:8002/doc.html (这里gitegg-service-base的端口设置为8002,所以访问gitegg-service-base的服务进行测试),在页面左侧菜单分别点击Fegin Get调用测试接口和Fegin Post调用测试接口,可以查看微服务调用成功

image.png

image.png

本文源码在gitee.com/wmz1930/Git… 的chapter-11分支。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Go - 使用 syncWaitGroup 来实现并发操作

发表于 2021-11-01

前言

如果你有一个任务可以分解成多个子任务进行处理,同时每个子任务没有先后执行顺序的限制,等到全部子任务执行完毕后,再进行下一步处理。这时每个子任务的执行可以并发处理,这种情景下适合使用 sync.WaitGroup。

虽然 sync.WaitGroup 使用起来比较简单,但是一不留神很有可能踩到坑里。

sync.WaitGroup 正确使用

比如,有一个任务需要执行 3 个子任务,那么可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
scss复制代码func main() {
var wg sync.WaitGroup

wg.Add(3)

go handlerTask1(&wg)
go handlerTask2(&wg)
go handlerTask3(&wg)

wg.Wait()

fmt.Println("全部任务执行完毕.")
}

func handlerTask1(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("执行任务 1")
}

func handlerTask2(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("执行任务 2")
}

func handlerTask3(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("执行任务 3")
}

执行输出:

1
2
3
4
erlang复制代码执行任务 3
执行任务 1
执行任务 2
全部任务执行完毕.

sync.WaitGroup 闭坑指南

01

1
2
3
4
5
scss复制代码// 正确
go handlerTask1(&wg)

// 错误
go handlerTask1(wg)

执行子任务时,使用的 sync.WaitGroup 一定要是 wg 的引用类型!

02

注意不要将 wg.Add() 放在 go handlerTask1(&wg) 中!

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
scss复制代码// 错误
var wg sync.WaitGroup

go handlerTask1(&wg)

wg.Wait()

...

func handlerTask1(wg *sync.WaitGroup) {
wg.Add(1)
defer wg.Done()
fmt.Println("执行任务 1")
}

注意 wg.Add() 一定要在 wg.Wait() 执行前执行!

03

注意 wg.Add() 和 wg.Done() 的计数器保持一致!其实 wg.Done() 就是执行的 wg.Add(-1) 。

小结

sync.WaitGroup 使用起来比较简单,一定要注意不要踩到坑里。

其实 sync.WaitGroup 使用场景比较局限,仅适用于等待全部子任务执行完毕后,再进行下一步处理,如果需求是当第一个子任务执行失败时,通知其他子任务停止运行,这时 sync.WaitGroup 是无法满足的,需要使用到通知机制(channel)。

以上,希望对你能够有所帮助。

推荐阅读

  • Go - 使用 sync.Map 解决 map 并发安全问题
  • Go - 基于逃逸分析来提升程序性能
  • Go - 使用 sync.Pool 来减少 GC 压力
  • Go - 使用 options 设计模式
  • Go - 两个在开发中需注意的小点

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

设计模式——单例模式 设计模式系列(三)

发表于 2021-11-01

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」


相关文章

设计模式系列:设计模式


前言

  • 单例模式(Singleton) ,也叫单子模式,是一种常用的设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候,整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,显然,这种方式简化了在复杂环境下的配置管理。
  • 单例模式就是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法。
  • 平时我们使用的Spring中的Bean默认就是单例的!尤其是javaConfig!

一、饿汉单例

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    java复制代码//饿汉单例模式
    @Data
    @Accessors(chain = true)
    public class ehanSingleton {
       //其他的属性,举例用的
       private Integer readings = 0;//阅读量
       private Integer collects = 0;//收藏量
       private Integer likes = 0;//点赞量
    ​
       //将自身的实例化对象设置成一个属性,并且加上final,static关键字修饰
       private static final ehanSingleton singleton = new ehanSingleton();
    ​
       //构造方法私有化---外部无法靠new来创建该对象
       private ehanSingleton(){};
    ​
       //公共的静态方法返回该实例,方便外部调用
       public static ehanSingleton getInstance(){
           return singleton;
      }
    }
  • TestMain

+ 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
csharp复制代码public class SingletonMain {
   public static void main(String[] args) {
       ehanSingleton singleton = ehanSingleton.getInstance();
       singleton.setReadings(singleton.getReadings()+1);
       read(singleton);
       singleton.setCollects(singleton.getCollects()+1);
       singleton.setLikes(singleton.getLikes()+1);
       System.out.println("阅读量为:"+singleton.getReadings());
       System.out.println("收藏量为:"+singleton.getCollects());
       System.out.println("点赞量为:"+singleton.getLikes());
​
  }
   public static void read(ehanSingleton singleton){
       for (int i=0;i<100;i++){
           singleton.setReadings(singleton.getReadings()+1);
      }
  }
}
  • 执行结果
+ ![image-20211101195643307.png](https://gitee.com/songjianzaina/juejin_p13/raw/master/img/ba81f852029ec1b88a8bd7dcd92eb48ec4b028cbbbc3e88bb6f13ad912ed94d3)
  • 一上来就把单例对象创建出来了,要用的时候直接返回即可,这种可以说是单例模式中最简单的一种实现方式。
  • 但是问题也比较明显。单例在还没有使用到的时候,初始化就已经完成了。
  • 也就是说,如果程序从头到位都没用使用这个单例的话,单例的对象还是会创建。这就造成了不必要的资源浪费。所以不推荐这种实现方式。

二、懒汉单例

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    java复制代码//懒汉单例模式
    @Data
    public class lanhanSingleton {
       //其他的属性,举例用的
       private Integer readings = 0;//阅读量
       private Integer collects = 0;//收藏量
       private Integer likes = 0;//点赞量
    ​
       //将自身的实例化对象设置成一个属性,并且加上final,static关键字修饰
       private static lanhanSingleton singleton = null;
    ​
       //构造方法私有化---外部无法靠new来创建该对象
       private lanhanSingleton(){};
    ​
       //公共的静态方法返回该实例,方便外部调用,线程安全的1:同步代码块,效率低
       public static synchronized lanhanSingleton getInstance(){
           if (singleton == null){
               singleton = new lanhanSingleton();
          }
           return singleton;
      }
    ​
       //公共的静态方法返回该实例,方便外部调用,线程安全的2:DCL 双检查锁机制
       public static lanhanSingleton getInstance1(){
           synchronized(lanhanSingleton.class){
               //第一次检查instance是否被实例化出来,如果没有进入if块
               if (singleton == null){
                   // 某个线程取得了类锁,实例化对象前第二次检查instance是否已经被实例化出来,如果没有,才最终实例出对象
                   if (singleton == null){
                       singleton = new lanhanSingleton();
                  }
              }
          }
           return singleton;
      }
    }
  • TestMain

+ 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
csharp复制代码public class SingletonMain {
   public static void main(String[] args) {
       lanhanSingleton instance = lanhanSingleton.getInstance();
       instance.setReadings(instance.getReadings()+1);
       read(instance);
       instance.setCollects(instance.getCollects()+1);
       instance.setLikes(instance.getLikes()+1);
       System.out.println("阅读量为:"+instance.getReadings());
       System.out.println("收藏量为:"+instance.getCollects());
       System.out.println("点赞量为:"+instance.getLikes());
  }
   public static void read(lanhanSingleton singleton){
       for (int i=0;i<100;i++){
           singleton.setReadings(singleton.getReadings()+1);
      }
  }
}
  • 执行结果
+ ![image-20211101200319853.png](https://gitee.com/songjianzaina/juejin_p13/raw/master/img/ecbbad863e8963e6766b132c09cdf775be8be489cdbf70efc1613261670add49)
  • 线程安全,调用效率不高,但是能延时加载。
  • 那么饿汉和懒汉在我们实际使用中该如何选择呢?
+ 单例对象 占用资源少,不需要延时加载,枚举 好于 饿汉
+ 单例对象 占用资源多,需要延时加载,静态内部类 好于 懒汉式

路漫漫其修远兮,吾必将上下求索~

如果你认为i博主写的不错!写作不易,请点赞、关注、评论给博主一个鼓励吧~hahah

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

AlibabaCloud 和 SpringCloud的区别+

发表于 2021-11-01

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战


AlibabaCloud和SpringCloud的区别?


1、地址

spring.io/projects/sp…

2、先说下两个的区别和对应的技术栈

背景:

1、2018年之前的开发的项目的Cloud微服务技术栈

【SpringCloud全家桶】

  • 服务注册发现 Eureka (不维护)
  • 远程调用 Open-Feign-SpringCloud
  • 业务网关 Zuul (不维护)
  • 限流降级 Hystrix (不维护)
  • 分布式配置中心 Config
  • 链路追踪 Sleuth+Zipkin

刚开始SpringCloud还不成熟,所以拿了业界很多第三方组件来使用(大杂烩),但是受限于他人,就出现了多个技术框架闭源或者不维护了。

2、2019年到现在开发的项目的Cloud微服务技术栈

【AlibabaCloud全家桶(也称为SpringCloud Alibaba)】

  • 服务注册发现 Naocs
  • 远程调用 Open-Feign-SpringCloud
  • 业务网关 SpringCloudGateway
  • 限流降级 Sentinel
  • 分布式配置中心 Nacos
  • 链路追踪 Sleuth+zipkin

随着微服务社区不断强大,SpringCloud推出了自己的全家桶,比如SpringCloudGateway等,多个业界顶级公司也推出了微服务,并加入了Spring官方 比如Spring Cloud Azure(微软)、Alibaba Cloud(阿里),因此SpringCloud生态就很强大了。

国内互联网公司当下是怎样的技术栈?


主流的都是SpringCloud+AlibabaCloud组合起来使用,即部分采用SpringCloud自研,部分采用AlibabaCloud自研,也就是课程使用的这套技术栈,现在都合并了,所以也称为 AlibabaCloud 或者 SpringCloud Alibaba。

架构师会关注公司技术栈升级吗?

目前多数公司都是进行了架构升级,从以前的多个老技术栈改用新的技术栈,把旧SpringCloud 升级为SpringCloud Alibaba。但是也存在少数公司没进行架构升级(ROI不划算,非致命问题,有其他更紧急的项目)。


多机房部署-数据库复制延迟-解决方案


目的:

  • 实时灾备,用于故障切换
  • 读写分离,提供查询服务
  • 访问量暴涨引入读写分离,主机负责读写操作,从机只负责读操作

问题点:

复制存在延迟-跨机房-跨城市-跨国业务,主库写入了新数据但是从库来不急更新,导致业务读取从库出问题。

图片

问题场景:

用户注册登录,用户注册信息写入了主库,登录的时候从库查询不到,提示未注册。

解决方案一:

最简洁办法一,只能降低延迟,不能避免(花钱解决):

  • 分库架构,mysql服务水平扩展
  • 不同业务的mysql物理上放在不同物理机
  • 采用更好的硬件,比如固态比硬盘好
  • 扩国多机房部署-数据一致性一般是拉专线

解决方案二:

区分实时业务和非实时业务,由使用方进行控制,决定调用要查询主库还是从库

注册登录是核心,因此都是访问主库,查询用户信息走从库。

  • 优点:避免复制延迟导致数据不一致的问题
  • 缺点:代码增加了耦合性,需要增加参数访问主库还是从库

解决方案三:

引入缓存,写DB之后,再同步到分布式缓存里面,再返回给用户响应。

结论:

没用通用的方案,要根据业务结合,数据同步成本高,许多企业都是拉专线,成本高,因此不是全部业务都是要做到数据实时一致,基本都是最终一致。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

改善既有代码的设计 重构:改善既有代码的设计(第2版)

发表于 2021-11-01

重构:改善既有代码的设计(第2版)

何为重构

1
2
3
复制代码   重构就是对软件内部结构的一种调整,目的是不改变软件可观察行为的前提下,提高其可理解性,降低其可修改成本
重构是优化代码结构,使其阅读性更好,扩展性更强的一种高级技术
程序是首先写给人看的,其次才是写给机器看

第一章 重构的第一个例子

1.重构函数

  • 重复代码:编程中不要有大量的重复代码,解决办法就是去提炼到一个单独的函数中
  • 内联临时变量:如果对一个变量只引用了一次,那就不妨对他进行一次重构
  • 尽量去掉临时变量:临时变量多了会难以维护,所以尽量去掉所使用的临时变量
  • 引入解释性变量:跟上面那个相反,如果使用函数变得很复杂,可以考虑使用解释型变量了
  • 移除对参数的赋值:用临时变量接收复制结果,另外函数中声明的临时变量最好只被赋值一次,如果超过一次就考虑再声明变量对其进行分解了

2.重构类

  • 搬移方法:每一个方法应该放在她最适合的位置,不能随便乱放,所以很多时候你需要考虑,一个方法在这里是不是最适合的。
  • 搬移字段:每一个字段,变量都应该放到其自己属于的类中,不能随便放,不属于这个类中的字段也需要移走。
  • 提炼一个新类:将不属于这个类中的字段和方法提取到一个新的类中
  • 内容移动:有时候每一个子类都有声明一个字段或方法,但是父类里面却没有这个字段或方法,这时候就考虑把这个字段或方法移动到父类里面,去除子类的这个字段和方法
  • 提炼接口:接口也就是协议,现在比较推崇的是面向接口编程

3.重新组织数据

  • 自封装字段:把字段封装起来的好处就是:如果子类复写这个字段的getter函数,那么可以在里面改变这个字段的获取结果,这样子扩展性可能会更好一点
  • 以对象取代数值:随着开发的进行,有时候一个数据项表示不再简单了,比如刚开始只需要知道一个人的名字就行了,可是后来的需求变成了不但要知道这个人的名字还要知道这个人的电话号码,还有住址等。这个时候就需要考虑将数据变成一个对象了
  • 常量取代数字:有时候使用一个固定的数值并不是太好,最好使用建立一个常量,取一个有意思的名字来替换这个常量

4.简化条件表达式

  • 分解条件表达式:有时候看着一个if else语句很复杂,我们就试着把他分解一下
  • 合并条件表达式:有时我们写的多个if语句是可以合并到一起的
  • 合并重复的条件片段:有时候可能会在if else 语句中写重复的语句,这时候需要将重复的语句抽出来
  • 以卫语句取代嵌套表达式
  • 以多态取代switch语句

第二章 重构的原则

  1. 重构的定义
* (名词形式)对软件内部结构的一种调整,目的是在不改变软件可察行为的前提下,提高可理解性,降低修改成本。
* (动词形式)使用一些列重构手法,在不改变软件可观察行为的前提下,调整其结构。
  1. 软件开发的两顶帽子
* 添加新功能时,不应该修改既有代码,只管添加新功能并通过测试。(做到这个太难了)
* 重构时不再添加新功能,只管改进程序结构,并通过已有测试。
  1. 为何重构
* 重构改进软件设计(Design)消除重复代码,我就可以确定所有事物和行为在代码中只表述一次。
* 重构使软件更容易理解(Maintain)好让以后接手的人看得懂
* 重构帮助找到BUG(Debug)顺着计算机的稍微走一遍,大部分bug就能解决
* 重构提高编程速度(Efficiency)添加新的功能时候顾虑少一点,思路清晰
  1. 何时重构
* 事不过三,三则重构
* 预备性重构:添加新的功能时更加容易,让修改多处的代码变成修改一处
* 帮助理解的重构:使代码更容易懂,让代码做到一目了然
* 捡垃圾式重构:复审代码时感觉不好 如果有时间就改了
* 有计划的重构:一般都是有了重大问题
* 长期重构:先把要重构的地方放着,如果有人遇到要重构的地方就改,因为小修改后系统功能不变
  1. 何时不该重构
* 既有代码太混乱,且不能正常工作,需要重写而不是重构。
* 如果不需要修改那些代码就不要重构。
* 项目接近最后期限时,应该避免重构。
  1. 重构的目标
为什么程序如此难以相与? 设计与重构的目标
难以阅读的程序,难以修改 容易阅读
逻辑重复的程序,难以修改 所有逻辑都只在唯一地点指定
添加新行为时需要修改已有代码的程序,难以修改 新的改动不会危及现有行为
带复杂条件逻辑的程序,难以修改 尽可能简单表达条件逻辑
  1. 代码应该有一套完整的测试套件,并且运行速度要快。
  2. 先写出可调优的软件,然后调优它以求得足够的速度。

第三章 代码的坏味道

我觉得这一章的内容非常重要,识别出代码的坏味道,是开始正确重构的前提。

  1. 神秘命名
  • 命名这东西刚开始学编程的时候就很是个问题,abcd,xxx1234,拼音什么的妖魔鬼怪都有,看别人的代码看到这些东西真的会头大。我自己一开始也用a1、a2什么的,过了几天就不知道我在写什么了。一个好名字能清晰表明自己的功能和用法。
  1. 重复代码
  • 如果要修改重复代码,必须找出所有相关的副本来修改,想想就很累,需设法提炼成函数。
  1. 过长函数
  • 函数越长,越难理解。每当感觉方法的某个地方需要注释来加以说明,可以把这部分代码放入一个独立的方法中,并以用途(而不是实现手法)来命名方法。
  • 条件表达式和循环常常也是提炼的信号。
  1. 过长参数列表
  • 不用参数就只能选择全局数据,这肯定是不可取的。
  • 改善的几点方法:
    • 如果可以向某个参数发起查询获得另一个参数的值,就用以查询取代参数。
    • 如果正在从现有的数据结构中抽取很多数据项,就保持对象完整。
    • 如果几个参数总是同时出现,就用引入参数对象。
    • 如果某个参数被用作区分函数行为的标记,可以使用移除标记参数。
  1. 全局数据
  • 全局数据的问题在于:从代码库任何一个角落都可以修改它。
  • 把全局数量用一个函数包装起来,并控制对其的访问,最好搬移到一个类或者模块中,控制其作用域。
  1. 可变数据
  • 在一处更新数据,却没有意识到软件中另一处期望着完全不同的数据,于是一个功能失效了。
  • 函数式编程–建立在“数据永不改变”的概念基础上:如果要更改一个数据结构,就返回一份新的数据副本,旧的数据仍保持不变。
  1. 发散式变化
  • 因为不同的原因,在不同的方向上,修改同一个模块。
  1. 霰弹式修改
  • 每遇到某种变化,需要在多个类内做出许多小修改,容易遗漏。应该把需要修改的部分放到一处。
  1. 依恋情结
  • 函数和另一个模块中的函数或者数据交流频繁,远多于自己所处模块内部交流。最好将此函数移动到那个模块中。
  1. 数据泥团
  • 在很多地方看到相同的三四项数据,如果删掉其中一项,其他数据也没有意义,那就应该为它们产生一个新的对象。
  1. 基本类型偏执
  • 创建和自己的问题域有用的基本类型,不要简单用字符串等替代。
  1. 重复switch
  • 每当想要增加一个选择分支,必须找到所有的switch,并逐一更新。可以使用多态来解决。
  1. 循环语句
  • 用管道代替循环可以帮助我们更快看清被处理的元素以及处理它们的动作。
  1. 冗赘的元素
  • 如果一个类不值得存在,那么它就应该消失。
  1. 夸夸其谈通用性
  • 如果函数和类的唯一用户是测试案例,那就先删掉测试,然后移除死代码。
  1. 临时字段
  • 类中某个字段只为某些特殊情况而设置。
  1. 过长的消息链
  • 一个对象请求另一个对象,然后再请求另一个对象。。。代码与查找过程中的导航结构紧密耦合,一旦对象之间的关系发生任何变化,代码就不得不发生改变。
  1. 中间人
  • 某个类的接口有一半的函数都委托给其他类,就应该移除这个中间人。
  1. 内幕交易
  • 模块之间的数据交换很难完全避免,应该都放到明面上来。
  1. 过大的类
  • 类的设计应当遵循单一职责原则。
  1. 异曲同工的类
  • 类的替换要保持接口一致。
  1. 纯数据类
  • 把数据处理搬移到纯数据类中,除非被用作const返回值。
  1. 被拒绝的遗赠
  • 子类继承父类的所有函数和数据,子类只挑选几样来使用。为子类新建一个兄弟类,再运用下移方法和下移字段把用不到的函数下推个兄弟类。
  • 子类只复用了父类的行为,却不想支持父类的接口。运用委托替代继承来达到目的。
  1. 注释
  • 注释不是用来补救劣质代码的,事实上如果我们去除了代码中的所有坏味道,当劣质代码都被移除的时候,注释已经变得多余,因为代码已经讲清楚了一切。

第四章 构筑测试体系

  • 要正确地进行重构,前提是有一套稳固的测试集合,以帮助我发现难以避免的疏漏。
  • 编写优良的测试程序,可以极大提高编程速度。
  • 我们一开始写一些代码喜欢把结果输出到屏幕上 然后逐一检测,这些完全可以让计算机来做,我们要做的就是把期望的输出放到测试代码中,然后做一个对比就行了。
  • 编写测试代码其实就是在自己:为了添加功能我需要实现些什么?还能帮我把注意力剧种到接口而非实现。
  • 测试驱动开发–先编写一个失败的测试,编写代码使测试通过,然后进行重构以保证代码整洁

五、重构列表

Java开发,由于IDE(Intellij Idea)能够很好的支持大多数情况下的重构,有各种的自动提示,所以感觉暂时不需要用到重构列表。

六、重新组织函数

1 . Extract Method 提炼函数

将一段代码放进一个独立函数中,并让函数名称解释该函数的用途。

增加可读性,函数粒度小更容易被复用和覆写。

2 . Inline Method(内联函数)

在函数调用点插入函数本体,然后移除该函数。

函数的本体与名称同样清楚易懂,间接层太多反而不易理解。

3 . Inline Temp(内联临时变量)

将所有对该变量的引用动作,替换为对它赋值的那个表达式自身。

4 . Replace Temp with Query(以查询取代临时变量)

将一个表达式提炼到一个独立函数中,并将临时变量的引用点替换为对函数的调用。

临时变量扩展为查询函数,就可以将使用范围扩展到整个类。

减少临时变量,使函数更短更易维护。

5 . Introduce Explaining Variable(引入解释性变量)

将该复杂表达式的结果放进一个临时变量,以变量名来解释其用途。

6 . Split Temporary Variable(分解临时变量)

针对每次赋值,创造一个独立、对应的临时变量。

临时变量会被多次赋值,容易产生理解歧义。

如果变量被多次赋值(除了“循环变量”和“结果收集变量”),说明承担了多个职责,应该分解。

7 . Remove Assignments to Parameters(移除对参数的赋值)

以一个临时变量取代该参数的位置。

对参数赋值容易降低代码的清晰度;

容易混淆按值传递和按引用传递的方式 ;

8 . Replace Method with Method object 函数对象取代函数

一个大型函数如果包含了很多临时变量,用Extract Method很难拆解,

可以把函数放到一个新创建的类中,把临时变量变成类的实体变量,再用Extract Method拆解。

9 . Substitute Algorithm 替换算法

复杂的算法会增加维护的成本,替换成较简单的算法实现,往往能明显提高代码的可读性和可维护性。

七、在对象之间搬移特性

在面向对象的设计过程中,“决定把责任放在哪儿”是最重要的事之一。

最常见的烦恼是:你不可能一开始就保证把事情做对。

在这种情况下,就可以大胆使用重构,改变自己原先的设计。

1 . Move Method 移动函数

类的行为做到单一职责,不要越俎代庖。

如果一个类有太多行为,或一个类与另一个类有太多合作而形成高度耦合,就需要搬移函数。

观察调用它的那一端、它调用的那一端,已经继承体系中它的任何一个重定义函数。

根据“这个函数不哪个对象的交流比较多”,决定其移动路径。

2 . Move Field(搬移字段)

如果一个类的字段在另一个类中使用更频繁,就考虑搬移它。

3 . Extract Class提炼类

一个类应该是一个清楚地抽象,处理一些明确的责仸。

4 . Inline Class 将类内联化

Inline Class (将类内联化)正好于Extract Class (提炼类)相反。如果一个类丌再承担足够责仸、丌再有单独存在的理由。将这个类的所有特性搬移到另一个类中,然后移除原类。

5 . Hide Delegate 隐藏委托关系

在服务类上建立客户所需的所有函数,用以隐藏委托关系
6 . Remove Middle Man(移除中间人)

封装委托对象也是要付出代价的:每当客户要使用受托类的新特性时,就必须在服务端添加一个委托函数。

随着委托类的特性(功能)越来越多,服务类完全变成了“中间人”,此时就应该让客户直接调用受托类。

很难说什么程度的隐藏才是合适的,随着系统不断变化,使用Hide Delegate和Remove Middle Man不断调整。

7 . Introduce Foreign Method 引入外加函数

你需要为提供服务的类增加一个函数,但你无法修改这个类。

在客户类中建立一个函数,并以第一参数形式传入一个服务类实例。

8 . Introduce Local Extension 引入本地扩展

你需要为服务类提供一些额外函数,但你无法修改这个类。

建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类戒包装类。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring 的那点事 (一) —— Spring 全流程概

发表于 2021-11-01

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

本系列文章主要来讲解 Spring 框架 的实现原理以及如何进行扩展使用。

Spring 的加载方式

Spring 的加载方式有两种: ClassPathXmlApplicationContext 和 AnnotationApplicationContext 两种方式。两种方式的区别,只在 refresh() 之前存在不同。

ClassPathXmlApplicationContext 的加载方式

  1. 创建一个实体类
1
2
3
4
5
6
7
java复制代码package com.recluse

public class Hello {
private String str;

// 省略 get / set 方法
}
  1. 创建 applicationContext.xml 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<!--使用Spring来创建对象,在Spring这些都称为Bean

类型 变量名 = new 类型();
Hello hello = new Hello();

id = 变量名
class = new 的对象;
property 相当于给对象中的属性设置一个值!
-->
<bean id="hello" class="com.recluse.Hello">
<property name="str" value="mySpring"/>
</bean>
</beans>
  1. 创建 Spring 应用程序的上下文
1
2
3
4
5
java复制代码public static void main (String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml")

Hello hello = context.getBean("str");
}

Spring 的加载过程

  1. 调用父类的构造方法,创建PathMathingResourcePatternResolver并解析配置文件
  2. 设置配置文件路径到当前应用程序中
  3. 开始进入容器的创建和刷新环节refresh(),这一步 ClassPathXmlApplicationContext 和 AnnotationApplicationContext 大体是相同的。

refresh 的处理过程

refresh 方法是由 12 个方法组成的,下面先对 12 个方法做一个简单的阐述,在之后的文章中会进行详细的说明

  1. prepareRefresh(): 容器刷新前的准备工作,其中包括记录容器的启动状态、终止状态、启动时间等等
  2. obtainFreshBeanFactory(): 创建容器,并且完成配置文件的加载,创建 DefaultListableBeanFactory 以及完成 BeanDefination 的加载和解析工作
  3. prepareBeanFactory(beanFactory): 通过 add,set,ignore,register 给容器对象完成属性的赋值操作
  4. postProcessBeanFactory(beanFactory): 默认没有实现,留给子类进行实现操作
  5. invokeBeanFactoryPostProcessors(beanFactory): 执行 BFPP 接口中的方法
  6. registerBeanPostProcessors(beanFactroy): 注册 BeanPostProcessor, 完成 Spring 自带或者用户自定义的BeanPostProcessor的解析
  7. initMessageSource(): 初始化信息资源, 进行国际化相关的操作。MessageSource、Locale
  8. initApplicationEventMulticaster(): 初始化事件广播器
  9. onRefresh() 在 Spring 中默认没有任何实现, Springboot 启动了 web 容器
  10. registerListeners(): 注册监听器,接收广播的事件, 与 8 呼应
  11. finishBeanFactoryInitialization(beanFactory): 完成所有非懒加载的单例对象的实例化操作, 从此方法开始进行对象的创建,包含了实例化,初始化,循环依赖,AOP等核心逻辑的处理过程,此步骤是最最核心且关键的点,要对其中的细节最够清楚
  12. finishRefresh(): 完成刷新

Spring 全流程图

大家可以先根据图对整个流程中各个方法的工作内容有个大概的了解,之后会根据该流程图一点点的梳理整个 Spring 框架。

spring 加载过程.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Docker(上) Docker简介 Docker安装与启动

发表于 2021-11-01

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」

Docker简介

虚拟化技术

虚拟化技术是一种计算机资源管理技术,是将计算机的各种实体资源,如服务器、网络、内存及存储等,予以抽象、转换后呈现出来。虚拟化技术打破了计算机实体结构间的,不可切割的障碍。使用户可以比原本的组态更好的方式,来应用这些资源。

虚拟化技术主要作用:

  • 高性能的物理硬件产能过剩和老的旧的硬件产能过低的重组重用,透明化底层物理硬件
  • 软件跨环境迁移问题(代码的水土不服)

在一台主机上实现多个操作系统,关键技术就是硬件的虚拟化。

image.png

什么是Docker

首先,我们先来看几个问题:

1.合作开发的时候,在本机可以跑,别人的电脑跑不起来

这里我们拿Java Web应用程序举例,我们一个Java Web应用程序涉及很多东西,比如JDK、tomcat、spring等等。当这些其中某一项版本不一致的时候,可能就会导致应用程序跑不起来这种情况。Docker则将程序直接打包成镜像,直接运行在容器中即可。

2.服务器自己的程序挂了,结果发现是别人程序出了问题把内存吃完了,自己程序因为内存不够就挂了

这种也是一种比较常见的情况,如果你的程序重要性不是特别高的话,公司基本上不可能让你的程序独享一台服务器的,这时候你的服务器就会跟公司其他人的程序共享一台服务器,所以不可避免地就会受到其他程序的干扰,导致自己的程序出现问题。Docker就很好解决了环境隔离的问题,别人程序不会影响到自己的程序。

3.公司要弄一个活动,可能会有大量的流量进来,公司需要再多部署几十台服务器

在没有Docker的情况下,要在几天内部署几十台服务器,这对运维来说是一件非常折磨人的事,而且每台服务器的环境还不一定一样,就会出现各种问题,最后部署地头皮发麻。用Docker的话,我只需要将程序打包到镜像,你要多少台服务,我就给你跑多少容器,极大地提高了部署效率。

官网地址:www.Docker.com

image.png

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器或Windows 机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。

特点:

  • 标准化交付:Docker将应用打包成标准化单元,用于交付、部署;
  • 轻量级:容器及包含了软件运行所需的所有环境,而且非常轻量级
  • 高可靠:容器化的应用程序,可以在任何Linux环境中始终如一的运行
  • 隔离性:容器化的应用程序,具备隔离性,这样多团队可以共享同一Linux系统资源

容器与虚拟机比较

特性 容器 虚拟机
启动 秒级 分钟级
硬盘使用 一般为MB 一般为GB
性能 接近原生硬件 弱鸡
系统支持量 单机可跑几十个容器 单机几个虚拟OS
运行环境 主要在Linux 主要在window

相同:

  • 容器和虚拟机都是虚拟化技术,具备资源隔离和分配优势

不同:

  • Docker虚拟化的是操作系统,虚拟机虚拟化的是硬件
  • 传统虚拟机可以运行不同的操作系统,Docker主要运行同一类操作系统(Linux)

Docker 基本概念

宿主机:

安装Docker守护进程的Linux服务器,称之为宿主机;

镜像(Image):

Docker 镜像,就相当于是一个 root 文件系统。除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数。

容器(Container):

镜像运行之后的实体,镜像和容器的关系,就像是面向对象程序设计中的类和对象一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

仓库(Repository):

仓库可看成一个镜像控制中心,用来保存镜像。

Docker安装与启动

安装

Docker官方建议在Ubuntu中安装Docker软件。因为Docker基于Ubuntu发布,而且Docker出现问题时,Ubuntu系统版本的一般是先打补丁。很多版本在CentOS中,是不支持更新最新补丁包的。没有好的解决方案。

但是,由于我学习的环境都使用CentOS。因此,这里我将Docker安装到CentOS上。注意,一定安装在CentOS 7.x及以上版本,CentOS6.x的版本中有Bug!

查看电脑上已经已经安装Docker

1
perl复制代码yum list installed | grep docker

安装docker

1
复制代码yum -y install docker

安装后查看docker版本

1
复制代码docker -v

Docker守护进程相关命令

启动docker:

1
sql复制代码systemctl start docker

停止docker:

1
arduino复制代码systemctl stop docker

重启docker:

1
复制代码systemctl restart docker

查看docker状态:

1
lua复制代码systemctl status docker

开机启动:

1
bash复制代码systemctl enable docker

查看docker概要信息

1
复制代码docker info

查看docker帮助文档

1
bash复制代码docker --help

镜像加速的2个方案

默认情况,将从docker hub(hub.docker.com/) 下载docker镜像太慢,一般都会配置镜像加速器

方案一:中科大

中国科学技术大学(ustc)是老牌的linux镜像服务提供者了,还在遥远的ubuntu 5.04版本的时候就在用。ustc的docker镜像加速器速度很快。ustc docker mirror的优势之一就是不需要注册,是真正的公共服务。

lug.ustc.edu.cn/wiki/mirror…

编辑该文件:

1
bash复制代码vim /etc/docker/daemon.json

在该文件中输入如下内容:

1
2
3
json复制代码{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}

方案二:阿里云

如果中科大镜像加载速度很慢,建议配置阿里云镜像加速,这个镜像仓库如果不好使,可以自己从阿里云上申请!速度杠杠的~

1
2
3
json复制代码{
"registry-mirrors": ["https://3ad96kxd.mirror.aliyuncs.com"]
}

必须要注册,每个人分配一个免费的docker镜像加速地址,速度极快

配置完成记得刷新配置

1
2
复制代码sudo systemctl daemon-reload
sudo systemctl restart docker

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

系统学习Java新特性-行为参数化

发表于 2021-11-01

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

阅读《Java实战》一书,系统学习梳理下Java8之后新增的特性,lambda表达式、方法引用、流、默认方法、Optional、CompletableFuture等等,本文为前两章的总结。

1、基础概念

1.1 流处理

  • Java8提供的Stream API,支持多个数据处理的并行操作,思路是从高层角度描述需求,而由“实现”(Stream库)来选择底层最佳执行机制。
  • 将集合的制造和处理构成抽象封装,如循环迭代直接由库内部进行,使用时不用纠结循环流程。
  • 提供封装的并行处理能力,基于分治+无共享可变状态。

1.2 方法引用&Lambda-匿名函数

  • 方法引用:类比对象引用,将方法作为一等值(执行时可做参数)来传递。
  • Lambda:将函数作为一等值来传递。

1.3 接口的默认方法

让接口支持声明默认的接口方法实现,只有类中没有实现具有默认方法的接口,才会去调用接口中的默认方法。

1.4 Java模块化

  • 解决问题:
    • 基于jar的java包没有声明的结构,不适合组件化构建。
    • 迭代演进中,一个接口的改变,实现该接口的所有类多要改变
  • 处理方式:
    • java9支持通过语法定义由一系列包组合成的模块,更好控制命名空间和包可见性。
    • 引入接口默认方法,支持接口持续演进,而不影响实现该接口的所有类。

1.5 其他内容

  • Optional处理null问题
  • 模式匹配

2、行为参数化演进

定义:

​ 一个方法可以接收多个不同的行为作为参数,并在内部使用他们,完成不同的行为能力。

演进场景:

​ 根据苹果的颜色或者重量属性遍历过滤出来苹果。

版本1:传统实现
1
2
3
4
5
6
7
8
9
10
java复制代码    // 版本1: 直接迭代筛选
public static List<Apple> filterApples(List<Apple> inventory,Color color){
List<Apple> filterApples = new ArrayList<>();
for(Apple apple:inventory){
if(color.equals(apple.getColor())){
filterApples.add(apple);
}
}
return filterApples;
}
版本2:基于行为接口+具体实现类,实现行为参数化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
java复制代码    // 版本2:基于接口+实现类的策略模式,用对象封装方法策略
public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate predicate){
List<Apple> filterApples = new ArrayList<>();
for(Apple apple:inventory){
if(predicate.test(apple)){
filterApples.add(apple);
}
}
return filterApples;
}
// 行为接口
interface ApplePredicate {
public boolean test(Apple a);
}
// 行为参数化对象:红色苹果判断
class AppleRedPredicate implements ApplePredicate{
@Override
public boolean test(Apple a) {
return Color.RED.equals(a.getColor());
}
}
// 行为参数化对象:红色苹果判断
class AppleHeavyWeightPredicate implements ApplePredicate{
@Override
public boolean test(Apple a) {
return a.getWeight()>150;
}
}
版本3:基于匿名类,简化类数量
1
2
3
4
5
6
7
java复制代码// 版本3:基于匿名类
List<Apple> redApples3 = filterApples(inventory, new ApplePredicate() {
@Override
public boolean test(Apple a) {
return Color.RED.equals(a.getColor());
}
});
版本4:用lambda替换匿名类
1
2
java复制代码// 版本4:用lambda简化匿名类
List<Apple> result = filterApples(inventory,(Apple a) -> Color.RED.equals(a.getColor()));
版本5:将List类型抽象化(将过滤行为复用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码    // 版本5:类型抽象化(引入类型参数)
interface Predicate<T>{
boolean test(T t);
}
// 泛化的过滤函数
static <T> List<T> filter(List<T> list,Predicate<T> p){
List<T> result = new ArrayList<T>();
for(T e:list){
if(p.test(e)){
result.add(e);
}
}
return result;
}
// 使用示例
List<Apple> result2 = filter(inventory,(Apple a) -> a.getWeight()>500);
System.out.println(result2);
// 过滤偶数
Integer[] arrInt = {1,2,3,4,5,6};
System.out.println(filter(Arrays.asList(arrInt),(Integer i) -> i%2==0));

完整示例代码

总结:

  • 核心:一个方法接受多个不同行为作为参数,并在内部使用它,完成不同能力。
  • 目的场景:适应不断变化的要求,不同行为参数化,减少硬编码,适合条件多变的排序&过滤的规则、线程等。
  • java8之前用匿名类来处理创建大量类实现问题,java8之后基于lambda实现

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

go gRPC 、gateway 简单使用 gRPC 简单使

发表于 2021-11-01

gRPC 简单使用

Server端代码 github.com/Charlotte32…

Client端代码 github.com/Charlotte32…

  1. 安装 protobuf

1
bash复制代码brew install protobuf
  1. 创建一个go 程序

1
bash复制代码go mod init moudleName
  1. 安装grpc

1
bash复制代码go get -u google.golang.org/grpc
  1. 安装 protobuf 插件(protoc-gen-go),用来生成 pb.go pb_grpc.go

1
2
bash复制代码go get -u google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

安装后可以到 go env GOPATH里面的bin目录去查看 是否生成了protoc-gen-go

  1. 编写.proto文件

语法文档: developers.google.com/protocol-bu…

1
2
bash复制代码touch Hello.proto
vi Hello.proto
1
2
3
4
5
6
7
8
9
10
11
12
protobuf复制代码syntax = "proto3"; //版本号
package services; // 生成的文件放到哪个包

option go_package = "nsqk.com/rpc";

message SayHelloRequest{
string name = 1;
}

message HelloReplay{
string message = 1;
}
  1. 生成对应的pb.go 文件

1
2
3
4
5
bash复制代码cd ..
# 创建一个services 文件夹,因为proto文件中的package写的是services
mkdir services
cd pb/
protoc --go_out=../services Hello.proto
  1. 编写对应的rpc service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protobuf复制代码syntax = "proto3"; //版本号
package services; // 生成的文件放到哪个包

option go_package = "nsqk.com/rpc";

message SayHelloRequest{
string name = 1;
}

message HelloReplay{
string message = 1;
}

// 下面编写rpc 接口
service HelloWorld{
rpc HelloRPC (SayHelloRequest) returns (HelloReplay) {}
}
  1. 生成.pb_grpc 文件

1
2
3
4
5
bash复制代码cd pb

protoc --go_out=../services --go_opt=paths=source_relative \
--go-grpc_out=../services --go-grpc_opt=paths=source_relative \
Hello.proto

gRPC-getway 同时提供RESTful 和 grpc接口

证书相关的操作 juejin.cn/post/702550…

  1. 安装

  1. 新建一个tool 文件夹,下面创建一个tool.go
  2. 引入需要的包
1
2
3
4
5
6
7
go复制代码package tool
import (
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
_ "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
_ "google.golang.org/protobuf/cmd/protoc-gen-go"
)
  1. go mod tidy
  2. 安装插件到go bin目录中‘
1
2
3
4
5
bash复制代码go install \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \
google.golang.org/protobuf/cmd/protoc-gen-go \
google.golang.org/grpc/cmd/protoc-gen-go-grpc
  1. 把google 这个文件夹拷贝到pb文件夹下方
1
bash复制代码cp /Users/jr/go/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.16.0/third_party/googleapis/google ./pb/

googleapis
2. 新建一个Prod.proto


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
protobuf复制代码syntax = "proto3"; //版本号
package services; // 生成的文件的包名

option go_package = ".;services"; //用来覆盖上面的package名称 ;前面是路径

import "google/api/annotations.proto";

message ProdRequest{
int32 prod_id = 1; // 商品id
}

message ProdResponse{
int32 prod_stock = 1; // 库存
}

message QuerySize{
int32 size = 1; //页尺寸
}

message ProdResponseList{ // 返回商品库存列表
repeated ProdResponse prodres = 1;
}


service ProdService{
rpc GetProdStock(QuerySize) returns (ProdResponseList){
option (google.api.http) = {
get: "/v1/prod/{size}"
};
}

}
  1. 重新生成 pb.go _grpc.pb.go,和生成新的gateway需要的pb.gw.go

生成Prod.pb.go 、 Prod_grpc.pb.go

1
2
3
bash复制代码cd pb && protoc --go_out=../services --go_opt=paths=source_relative \
--go-grpc_out=../services --go-grpc_opt=paths=source_relative \
Hello.proto

生成 Prod.pb.gw.go

1
2
3
4
5
bash复制代码protoc -I . \
--grpc-gateway_out ../services \
--grpc-gateway_opt logtostderr=true \
--grpc-gateway_opt paths=source_relative \
Prod.proto
  1. 编写ProdService 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
go复制代码package services

import (
"context"
"log"
)

type ProdService struct {

}

func (p *ProdService)GetProdStock(ctx context.Context, req *QuerySize) (*ProdResponseList, error){
log.Println(req.Size)
return &ProdResponseList{Prodres: []*ProdResponse{&ProdResponse{ProdStock: 2333}}},nil
}
func (p *ProdService)mustEmbedUnimplementedProdServiceServer(){
log.Fatalln("含有未实现的service")
}
  1. 编写http 服务文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
go复制代码package main

import (
"context"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"log"
"net/http"
"nsqk.com/rpc/helper"
"nsqk.com/rpc/services"
)

func main() {
ctx := context.Background()
ctx,cancel := context.WithCancel(ctx)
defer cancel()

// 创建一个gateway mux
gwmux := runtime.NewServeMux()
// 创建grpc client dial 配置项
// 这里把RESTful api 转成 gRPC client 去请求,所以传入客户端的证书
opt := []grpc.DialOption{grpc.WithTransportCredentials(helper.GetClientCreds())}
// 注册并且设置http的handler,这里传入的endpoint 是gRPC server的地址
err := services.RegisterHelloWorldHandlerFromEndpoint(ctx,gwmux,"localhost:2333",opt)
if err != nil{
log.Fatalln("注册hello grpc转发失败:",err)
}
err = services.RegisterProdServiceHandlerFromEndpoint(ctx,gwmux,"localhost:2333",opt)
if err != nil{
log.Fatalln("注册prod grpc转发失败:",err)
}
httpServer := &http.Server{
Addr: ":23333",
Handler: gwmux,
}
err = httpServer.ListenAndServe()
if err != nil{
log.Fatalln("http 启动失败:",err)
}


}
  1. 启动

  1. 先启动grpcServer
  2. 再启动httpServer

这里grpc 端口是2333 http端口是23333

结尾

到此,http 和 grpc 都可以进行访问了

image.png

demo代码地址 :

​ client :github.com/Charlotte32…

​ server: github.com/Charlotte32…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…441442443…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%