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

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


  • 首页

  • 归档

  • 搜索

TypeORM---增删改查

发表于 2021-06-13

-TypeORM入门指南

一.安装

使用官方提供的CLI命令搭建项目

首先全局安装 TypeORM:
npm install typeorm -g

创建项目
typeorm init --name [MyProject] --database [mysql]
其中[MyProject] 为项目的名字,[mysql]为你将使用的数据库,可自行根据选择修改

database可配置的选项有mysql, mariadb, postgres, sqlite, mssql, oracle, mongodb, cordova, react-native, expo, nativescript。执行下面指令创建项目

typeorm init --name ormStudy --database mysql

此时项目结构

1
2
3
4
5
6
7
8
9
10
11
go复制代码ormStudy
├── src // TypeScript 代码
│ ├── entity // 存储实体(数据库模型)的位置
│ │ └── User.ts // 示例 entity
│ ├── migration // 存储迁移的目录
│ └── index.ts // 程序执行主文件
├── .gitignore // gitignore文件
├── ormconfig.json // ORM和数据库连接配置
├── package.json // node module 依赖
├── README.md // 简单的 readme 文件
└── tsconfig.json // TypeScript 编译选项

安装依赖项
cd ormStudy
npm i

使用MySQL创建orm_test的数据库
CREATE SCHEMAorm_test;
编辑ormconfig.json为自己的数据库链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
json复制代码{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "xxxx",
"database": "orm_test",
"synchronize": true,
"logging": false,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
}
}

二.创建一个实体

创建一个实体首先需要创建一个模型,也就是数据库中的表,这里使用官方文档的案例模型Photo

1
2
3
4
5
6
7
javascript复制代码export class Photo {
id: number;
name: string;
description: string;
filename: string;
views: number;
}

在数据库中,把每一类数据对象的个体称为实体。在typeorm中,实体是由@Entitiy装饰器装饰的模型

以Photo模型数据对象作为一个实体
简单介绍下下面使用到的装饰器

  1. @Entity 装饰为一个实体
  2. @Column 装饰为数据库列
  3. @PrimaryGeneratedColumn 可使id列自动生成

在src/entity下创建Photo.ts并写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ts复制代码import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class Photo {
@PrimaryGeneratedColumn()//id列自动生成
id: number;

@Column({
length: 100,//列数据类型
})
name: string;

@Column("text")//列数据类型
description: string;

@Column()
filename: string;

@Column("double")//列数据类型
views: number;

@Column()
isPublished: boolean;
}

此时我们启动项目
npm start
打开MySQL查询此表
在这里插入图片描述
创建成功

三.创建数据库连接

1.增

创建实体后,将src/index.ts的内容更改为下面ts代码后,再次启动此项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ts复制代码import "reflect-metadata";
import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";

createConnection()
.then(async (connection) => {
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.views = 1;
photo.isPublished = true;

await connection.manager.save(photo);
console.log("Photo has been saved");
})
.catch((error) => console.log(error));

启动此项目
npm start

查看photo表,发现上面代码中photo对象的所有属性已经对应关系写入表中
在这里插入图片描述

2.查

将index.ts中被注释的部分更改为以下代码

1
2
3
4
5
6
ts复制代码/*
await connection.manager.save(photo);
console.log("Photo has been saved");
*/
const photos = await connection.manager.find(Photo);
console.log("photos: ", photos);

再次启动观察终端输出,可以得到刚刚输入至数据库的内容
在这里插入图片描述

3.改

现在我们尝试使用Repository而不是EntityManager,并将index.ts中这一部分更改为如下代码。注释中有重构EntityManager的方法

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
ts复制代码//增
/*
//更改前:
await connection.manager.save(photo);
console.log("Photo has been saved");
//更改后:
let photoRepository = connection.getRepository(Photo);
await photoRepository.save(photo);
console.log("Photo has been saved");

*/
//查
/*
//更改前:
const photos = await connection.manager.find(Photo);
console.log("photos: ", photos);
//更改后:
let photoRepository = connection.getRepository(Photo);
let savedPhotos = await photoRepository.find();
console.log("All photos from the db: ", savedPhotos);
*/
//改
let photoRepository = connection.getRepository(Photo);
let photoToUpdate = await photoRepository.findOne(1);
photoToUpdate.name = "Me, my friends and polar bears";
await photoRepository.save(photoToUpdate);

实现将id为1的photo的name更改为”Me, my friends and polar bears”
在这里插入图片描述

4.删

将index.ts中被注释的部分更改为以下代码

1
2
3
4
5
6
7
ts复制代码/*
let photoToUpdate = await photoRepository.findOne(1);
photoToUpdate.name = "Me, my friends and polar bears";
await photoRepository.save(photoToUpdate);
*/
let photoToRemove = await photoRepository.findOne(1);
await photoRepository.remove(photoToRemove);

启动后,这个id=1的photo在数据库中被移除了
在这里插入图片描述

至此,我们通过TypeORM操作MySQL完成了简单的增删改查

本文转载自: 掘金

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

《蹲坑也能进大厂》多线程系列-上下文、死锁、高频面试题

发表于 2021-06-13

这是我参与更文挑战的第 3 天,活动详情查看:更文挑战

作者:JavaGieGie

微信公众号:Java开发零到壹

前言

上一期的蹲坑系列我们介绍了多线程的基础知识,是不是和你平时的了解有些出入呢。

《蹲坑也能进大厂》多线程这几道基础面试题,80%小伙伴第一题就答错

今天继续讲解多线程相对基础的理论知识点,如果你是新手或者对多线程了解不多,千万不要想着上去就肝实战课,没用的,随便出一个bug你都看不出来啥原因,花GIe强烈建议跟着本系列走完(手动狗头护体)。

20160116922699_rEdRNG.gif

我:哟呼,狗剩子今天怎么不在家修养,也不陪你女朋友,又来公司写bug啊

狗剩子:嘿嘿,怎么可能不陪女朋友,我们每天形影不离

我:……

狗剩子:趁着今天没人,走,坑里见,我要和你坦诚相对。

正文

我:狗剩子,请听第一题,守护线程和用户线程有什么区别?

我们应该知道,Java有两种线程:【守护线程Daemon】与【用户线程User】,两者的唯一的区别就是:虚拟机离开时,如果JVM中所有线程都是守护线程时,JVM就会自动退出;但是如果还有一个或以上的非守护线程则不会退出。

我:昨天问过你notify,那你知道Java中notify 和 notifyAll有什么区别?或者说我们怎么选择使用哪一种?

昨天的事居然还记得,你这记性还可以呀,我都忘记了。

是这样的,notify不能指定唤醒某一个具体的线程(这是网上说的,俺就跟着说,至于为啥后面告诉你),可能会导致信号丢失这样的问题,只有在一个线程等待的时候才是它的主场,而notifyAll会唤醒所有等待线程,并允许他们争夺锁,虽然效率不高,但是可以保证至少有一个线程继续执行。如果想要使用notify,必须确保满足以下两种情况。

  • 一次通知仅需要唤醒最多一条线程。
  • 所有等待唤醒的线程,自身处理逻辑相同。举个栗子大家就会明白,比如使用Runnable接口实例创建的不同线程,或者同一个Thread子类new出来的多个实例。

我:不要得意,这都是开胃菜,再问你一个,wait为什么只能在代码块中使用?

啥?(心里捣鼓)wait只能在代码块中使用吗,我咋不知道。那是….可能wait有洁癖,喜欢一个人自嗨吧。

我:你踏马….

哦…我想起来了,我们可以反过来想,如果wait不要求在同步块中,那可能会发生以下的错误。

先看一处用wait、notify实现的线程安全队列的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码class BlockingQueue {
Queue<String> buffer = new LinkedList<String>();

//
public void give(String data) {
buffer.add(data);
notify(); // Since someone may be waiting in take!
}

public String take() throws InterruptedException {
while (buffer.isEmpty()) // don't use "if" due to spurious wakeups.
wait();
return buffer.remove();
}
}
  1. 消费者A调用 take() , 此时buffer.isEmpty()为true;
  2. 消费者A进入while,在调用wait() 方法之前, 生产者B调用了一个完整的 give()( 即buffer.add(data)和notify());
  3. 之后消费者A调用了 wait() ,但是错过了生产者B调用的notify();
  4. 如果之后没有别的生产者调用give()方法,消费者A所在线程则会一直等待。

我:这波解释我都忍不住给你点个赞,你知道Java中锁是什么吗?

锁的概念小伙伴们也可以,在学习完花Gie的volatile之后再看。

锁这个东西说起来很抽象, 你可以就把它想象成现实中的锁, 至于他的防盗锁、金锁、还是指纹锁并不重要,哪怕它就是一根草绳,一个自行车、甚至一坨那啥,都可以当做锁。 锁是什么外在形象并不重要,重要的是它代表的含义: 谁持有它, 谁就有独立访问临界资源的权利。

我:你有了解过什么是线程死锁吗?

死锁就是说两个或两个以上线程在执行过程中,由于竞争资源,它们都在等待某个资源被释放,因此线程被无限期地阻塞,此时的系统处于死锁状态。

如图,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。

image.png

我:那你知道形成死锁的四个必要条件吗?

咱家有啥不知道的,这就给你列举出来。

  1. 互斥条件:线程(进程)对分配到的资源具有排他性,也就是说该资源任意一个时刻只能有一个进程占用。
  2. 请求与保持条件:一个线程(进程)因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程(进程)已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  4. 循环等待条件:当发生死锁时,所等待的线程(进程)必定形成一个环路,死循环造成永久堵塞。

我:既然你知道形成死锁的条件,那你肯定知道如何避免咯?

正所谓对症下药,想要避免死锁,那就需要破坏者四个必要条件的任意一个即可。

  1. 破坏互斥条件:此路不通,因为我们用锁本来就是想让他们互斥的。
  2. 破坏请求与保持条件:一次性申请所有的资源。
  3. 破坏不剥夺条件:占有资源线程可以尝试申请其它资源,如果申请不到,可以主动释放它占有的资源。
  4. 破坏循环等待条件:按照某一顺序申请资源,释放资源时则反序释放。

我:这波回答的不错,昨晚是不是偷偷准备了。狗剩子请继续听题,上下文切换晓得吗?

麻烦尊重俺一下,以后请叫狗爷。

说到上下文切换,那我们得先知道什么是上下文,直白说上下文就是某个时间点CPU寄存器和程序计数器的内容。

拓展:每个线程都有一个程序计数器(记录要执行的下一条指令),一组寄存器(保存当前线程的工作变量),堆栈(记录执行历史,其中每一帧保存了一个已经调用但未返回的过程)。

寄存器:寄存器就是CPU内部内存,负责存储已经、正在和将要执行的任务,数量较少但是速度很快,与之相对应的是CPU外部相对较慢的RAM主内存。

程序计数器:程序计数器是一个专用的寄存器,用于表明指令序列CPU当前执行的位置,存储的内容是正在执行指令的位置或下一次将要执行指令的位置。

大致了解了上下文,那上下文切换也就简单了,它是指当前任务执行完,CPU时间片切换到下一个任务之前会先保存自己的状态,以便下次再切换回这个任务时可以继续执行下去,任务从保存到再加载执行就是一次上下文切换。

如果还不是十分清楚,可以分下面三个步骤理解。

  1. 挂起一个进程,将这个进程在CPU中的状态(上下文)存储在内存中;
  2. 在内存中检索下一个进程的上下文,并且将该进程在CPU的寄存器中回复;
  3. 跳转到程序计数器所指向的位置,也就是该进程被中断时,代码当时执行到的位置,从而恢复该进程。

我:讲的好详细,突然好心动,那上下文切换会带来什么问题呢?

看完上面介绍我们应该有一个感觉,那就是如果高并发情况下,频繁切换上下文会导致系统串行执行,运行速率大大降低。

  • 直接消耗:包括CPU寄存器需要保存和加载, 系统调度器的代码需要执行。
  • 间接消耗:CPU为了加快执行速度,会把常用的数据缓存起来,但是当上下文切换后(即CPU执行不同线程的不同代码),那原本所缓存的内容很大程度没有利用价值了,因此CPU就会重新进行缓存,这也导致线程被调度运行后,一开始启动速度会比较慢。

拓展:线程调度器为了避免频繁切换上下文带来的开销,会给每个被调度到的线程设置一个最小执行时间,从而减少上下文切换的次数,从而提高性能,但是缺点也显而易见,就是会降低响应速度。

我:那你跟我讲一下volatile是啥呗?

不了不了,今天快累死咱家了,老衲需要休息休息,明天我们再战。

总结

多线程知识点非常庞大,涉及到很多方面,特别是刚刚接触多线程的小伙伴,对于锁、上下文这种概念理解起来非常困难,想要真正全部掌握需要深究每一个问题所涉及到的知识面,比如怎么用wait/notify实现生产者消费者模式、线程的调度过程、Java代码如何一步步转化被CPU执行,还有非常重要的,就是上面这些知识点的原理是什么,Thread如何启动、中断线程的,线程间进行通信的原理是什么。

这些花GieGie后面都会逐步带大家掌握,有些大的知识点会拿出来进行单篇的讲解,希望大家持续关注,假日不打样,继续肝。

点关注,防走丢

以上就是本期全部内容,如有纰漏之处,请留言指教,非常感谢。我是花GieGie ,有问题大家随时留言讨论 ,我们下期见🦮。

文章持续更新,可以微信搜一搜「 Java开发零到壹 」第一时间阅读,后续会持续更新Java面试和各类知识点,有兴趣的小伙伴欢迎关注,一起学习,一起哈🐮🥃。

原创不易,你怎忍心白嫖,如果你觉得这篇文章对你有点用的话,感谢老铁为本文点个赞、评论或转发一下,因为这将是我输出更多优质文章的动力,感谢!

本文转载自: 掘金

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

一文带你配置使用Iterm2终端神器(自定义主题+高亮+补全

发表于 2021-06-13

一、Iterm的获取

  • Iterm可以用来替换掉默认终端。
  • 官网下载iterm2.com/。

二、将shell设置为zsh

  • 系统提供了很多shell,默认的shell格式为/bin/bash格式。例如:

​# 查看当前的shell格式
echo $SHELL

查看系统支持的所有shell格式

cat /etc/shells

  • 本文需要用到的shell是/bin/zsh,其拥有很多优秀的特性。
  • 配置命令如下:

将当前的shell设置为/bin/zsh格式

chsh -s /bin/zsh

退出系统shell重进

exit

查看当前的shell格式

echo $SHELL

三、安装Oh My Zsh插件

  • Oh My Zsh是一个开源的,社区驱动框架,用来管理zsh。
  • 开源地址:github.com/ohmyzsh/ohm…
  • 下载安装方式1:下载的方式有如下的3种,任选其1即可:

sh -c “(curl−fsSLhttps://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"sh−c"(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
sh -c “(curl−fsSLhttps://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"sh−c"(wget -O- raw.githubusercontent.com/ohmyzsh/ohm…)”
sh -c “$(fetch -o - raw.githubusercontent.com/ohmyzsh/ohm…)”

  • 如果上面的方式下载不了:显示无法访问/或者说是无法建立SSL连接(百度了半天都是无用信息),则使用下面的方式2配置。
  • 下载安装方式2:将ohmyzsh仓库代码下载下来,为tools/install.sh赋予可执行权限,然后进行安装即可。命令如下:

下载

git clone github.com/ohmyzsh/ohm…

加可执行权限

chmod +x ohmyzsh/tools/install.sh

执行脚本安装onmyzsh

ohmyzsh/tools/install.sh

四、配置插件

  • ​在$ZSH/plugins/目录中提供了很多的插件​。

ls $ZSH/plugins/

  • 这些插件主要可以提供快捷键等功能。
  • 每个插件的目录下有一个READMD.md文件,里面详细介绍了插件的使用。下面以git为例:

ls ZSH/plugins/gitcatZSH/plugins/git
cat ZSH/plugins/gitcatZSH/plugins/git/README.md

  • 配置插件:编辑~/.zshrc文件,在plugins选项中加入想要配置的插件,之后使用source生效。例如下面添加git,ruby和python​:

vim ~/.zshrc

编写plugins选项

source ~/.zshr

五、配置主题

  • ohmyzsh提供了很多的主题(theme),完整的样例可以在下面看到:github.com/ohmyzsh/ohm…
  • 从上面可以看到,每种主题都有自己的格式与一个唯一对应的​名称。
  • 配置方法:编辑~/.zshrc文件,设置ZSH_THEME的内容为自己想要配置选项的值。例如:

vim ~/.zshrc

配置ZSH_THEME的值

source ~/.zshr

  • 我常使用的:agnoster,jonathan,wedisagree

六、配置命令高亮

  • 概念:默认情况下,系统命令都是以白色显示。为了与内容区分开来,可以安装下面的插件,安装完成之后pwd​,ls等系统命令都会以高亮显示。​
  • 开源地址如下:​github.com/zsh-users/z…
  • 配置方法:首先将配置目录移动/拷贝到$ZSH_CUSTOM/plugins/目录下,然后编辑~/.zshrc文件,在plugins参数中添加zsh-syntax-highlighting,​最后使用source生效。

cp -r zsh-syntax-highlighting $ZSH_CUSTOM/plugins/
vim ~/.zshrc

在plugins里面加入zsh-syntax-highlighting

source ~/.zshrc

七、配置命令补全

  • 概念:普通的命令补全需要按2下【tab】键,安装了补齐插件之后,系统会自动显示补全命令。
  • ​开源地址如下:github.com/zsh-users/z…
  • ​配置方法:首先将配置目录移动/拷贝到$ZSH_CUSTOM/plugins/目录下,然后编辑~/.zshrc文件,在plugins中添加zsh-autosuggestions,最后使用source生效。

cp -r zsh-autosuggestions $ZSH_CUSTOM/plugins/
vim ~/.zshrc

在plugins里面加入zsh-autosuggestions

source ~/.zshrc

八、Iterm状态栏的配置

  • Iterm的状态栏默认情况下是关闭的,可以通过下面的方式开启:

Perferences->Profiles->Session->底部勾选Status bar enabled

  • 配置状态栏:状态栏提供了很多选项卡,​可以配置在状态栏中。​方法如下:

Status bar enabled右侧点击Configure Status Bar
->将想要的选项卡拖动到Active Components中
->如果想要改变配色,可以设置下面的Auto-Rainbow选项

  • 调整状态栏的位置:例如下面将状态栏设置为在底部显示。

九、Oh My Zsh的卸载

  • 直接执行下面的命令即可,其会删除相关配置文件:

uninstall_oh_my_zsh

  • 这里就不演示了​。

本文转载自: 掘金

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

盘点 Cloud FeignBean 的创建及代理

发表于 2021-06-12

这是我参与更文挑战的第7天,活动详情查看: 更文挑战

总文档 :文章目录

Github : github.com/black-ant

一 . 前言

前面一篇说了Feign 的初始化配置 , 这一篇来说说 FeingBean 的加载 :

接上一篇中 , FeignClient 被扫描处理 , 加入 Bean 工厂中

BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

那么后续是如何使用的呢?

二 . FeignBean 的创建

Bean 的创建主要是基于 FeignClientFactoryBean , 通过工厂实现具体的 FeignBean 个体 ,后续接口代理的时候 , 会反射到该类

2.1 发起的原因

  • Step 1 : Spring 加载一个 Bean ,发现 Bean 中有一个 Autowired 标注的对象
  • Step 2 : 通过 AbstractBeanFactory getBean 获取这对象
  • Step 3 : getSingleton 查询到对应的 工厂方法 (FeignClientFactoryBean)
  • Step 4 : 调用 FeignClientFactoryBean # getObject 获取对象
  • Step 5 : ReflectiveFeign 初始化绑定对象 (newInstance)
    • Step 5.1 : ReflectiveFeign.apply(Target target) 对 Method 进行处理
    • Step 5.1 : ReflectiveFeign.apply(Target target) 对 Method 进行处理

简单点说 ,当出现 Autowired 注入 FeginClient 的时候 , 会通过 FeignClientFactoryBean 返回一个代理类

2.2 调用的流程

前置知识点 : FactoryBean 发起 Object 的获取

PS : IOC 容器中会构建 FactoryBean , 在需要实现 Object 的时候 , 调用 FactoryBean 的 getObject 方法获取具体的Object 对象

1
2
3
4
5
6
7
java复制代码C07- FactoryBean
M- T getObject() throws Exception
M- Class<?> getObjectType()
M- default boolean isSingleton()

// PS : 此处 FeignClientFactoryBean 实现了该方法
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware

扩展 :
了解过 IOC 流程的应该会比较清楚 , 这里简单点说就是提供一个方法 ,让 IOC 获取 FactoryBean 需要创建的类的实例 (即从 FactoryBean 获取它工厂处理出来的对象)

相关逻辑可以从 DefaultSingletonBeanRegistry # getSingleton 方法中看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码com.gang.cloud.template.demo.client.SampleFeignClient 
-> FeignClientFactoryBean{
type=interface com.gang.cloud.template.demo.client.SampleFeignClient,
name='nacos-account-server',
url='http://nacos-account-server',
path='',
decode404=false,
inheritParentContext=true,
applicationContext=
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@4eb1c69,
started on Tue May 18 15:47:20 CST 2021,
parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@4cc61eb1,
fallback=void,
fallbackFactory=void}

Step 1 : FeignClientFactoryBean 调用主流程

主调流程为 :

  • FeignClientFactoryBean.getObject
  • FeignClientFactoryBean.getTarget
  • FeignClientFactoryBean.loadBalance
  • HystrixTargeter.target
  • ReflectiveFeign.newInstance :

这里看到 , 这里的起点是 getObject , 该方法是一个重写方法 , 源头为接口 FactoryBean < Object >

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
java复制代码C09- FeignClientFactoryBean
M09_01- getTarget
1- this.applicationContext.getBean(FeignContext.class) : 获取Feign 核心容器
2- 通过容器配置获取 Feign.Builder 对象
3- 最终调用 loadBalance 进行负载均衡的封装


<T> T getTarget() {
// 1 . 准备了 FeignContext
FeignContext context = this.applicationContext.getBean(FeignContext.class);
// 2 . 准备了 Feign.Builder
Feign.Builder builder = feign(context);

//
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
} else {
this.url = this.name;
}
this.url += cleanPath();
// 通常情况下 , URL 不为空 , 会总结返回 loadBalance 对象
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
// 此处获得连接的 client 对象
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}

[Pro0001] : FeignContext 的作用 ?
创建虚类实例的工厂。它为每个客户端名称创建一个Spring ApplicationContext,并从中提取所需的bean.

[Pro0002] : Feign.Builder ?

这是一个 feign 构造工具 , 包含常用的配置信息和工具对象 , 例如最常见的 client 和 加密解密工具 ,
可以理解为一个集合体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码public static class Builder {

private final List<RequestInterceptor> requestInterceptors =
new ArrayList<RequestInterceptor>();
private Logger.Level logLevel = Logger.Level.NONE;
private Contract contract = new Contract.Default();
private Client client = new Client.Default(null, null);
private Retryer retryer = new Retryer.Default();
private Logger logger = new NoOpLogger();
private Encoder encoder = new Encoder.Default();
private Decoder decoder = new Decoder.Default();
private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private Options options = new Options();
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
private boolean decode404;
private boolean closeAfterDecode = true;
private ExceptionPropagationPolicy propagationPolicy = NONE;
private boolean forceDecoding = false;
private List<Capability> capabilities = new ArrayList<>();

}

Step 2 : loadBalance 构建

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复制代码C09- FeignClientFactoryBean
M09_02- loadBalance
1- feign.Client 对象创建 (LoadBalancerFeignClient)
2- Targeter 创建 (HystrixTargeter)
3- targeter.target 处理 (包括 fallback , fallbackFactory , SetterFactory)

// PS : 在上一个步骤中 , 返回了一个 loadBalance 对象 >>>


protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
// LoadBalancerFeignClient
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
// HystrixTargeter
Targeter targeter = get(context, Targeter.class);

return targeter.target(this, builder, context, target);
}

throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");

}


// Targeter 是一个接口 , 主要有2个实现类 : HystrixTargeter , DefaultTargeter


}

[Pro] : getOptional 获取 Client

此处会根据我们之前初始化中说的 , 通过配置选择是否调用 OkHttpClient 或者 HttpClient , 配置后就会发现 , 实际上进入了如下类 :

1
2
3
4
5
6
7
8
java复制代码C- OkHttpFeignConfiguration
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
ConnectionPool connectionPool,
FeignHttpClientProperties httpClientProperties) {

//......................

}

Step 3 : HystrixTargeter 的处理

因为之前的 Feign 就行 HystrixFeign ,所以通常这里会直接调用 feign.target(target);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
: factory.getContextId();
SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(name, context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(name, context, target, builder,fallbackFactory);
}

return feign.target(target);
}

PS : 如果不是 HystrixFeign , 后续其实还是创建了一个 HystrixFeign

Step 4 : Invoke 代理类的构建

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
71
72
73
74
75
76
77
java复制代码C30- ReflectiveFeign
M30_01- apply(Target target)
1- 获取 Method 元数据集合 List<MethodMetadata> -> PS:M30_01_01
2- 根据类型不同构建 BuildTemplateByResolvingArgs -> PS:M30_01_02
3- 构建 MethodHandler -> PS:M30_01_03
M30_02- newInstance(Target<T> target) -> PS:M30_02_03
1- 获取所有方法的 Map<String, MethodHandler> 集合 nameToHandler
// 对象准备
2- Map<Method, MethodHandler> methodToHandler -> LV:M30_02_01
3- List<DefaultMethodHandler> defaultMethodHandlers
// For 循环处理
4-FOR- nameToHandler (Map<String, MethodHandler>)
- methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)))
?- 添加到 LV:M30_02_01 methodToHandler 中
// 实体类构建
5- new ReflectiveFeign.FeignInvocationHandler(target, dispatch) : 构建代理对象 InvocationHandler
6- Proxy.newProxyInstance : 构建代理类 核心方法


// M30_01 源代码
public Map<String, MethodHandler> apply(Target target) {
List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate =
new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
}
if (md.isIgnored()) {
result.put(md.configKey(), args -> {
throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
});
} else {
// 构建 MethodHandler
result.put(md.configKey(),
// 此处的 factory 为 SynchronousMethodHandler.Factory (静态内部类)
factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
}
}
return result;
}
}


// M30_02 源代码
public <T> T newInstance(Target<T> target) {
// 注意 , 此处直接调用了 apply
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply , 获得代理类(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
// 核心逻辑 , 代理对象的生成
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);

for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}

PS:M30_01_01 元数据集合

Feign_client_method_meatadata.jpg

PS:M30_01_02 BuildTemplateByResolvingArgs 对象

Feign_client_build_template.jpg

PS:M30_01_03 MethodHandler 对象参数

可以看到 , 相关的参数均在里面了

feign_method_handler.jpg

总结

这一部分也不麻烦 ,总得来说就是生成了一个 Proxy ,对接口进行了代理

本文转载自: 掘金

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

Python 函数的使用

发表于 2021-06-12

这是我参与更文挑战的第12天,活动详情查看: 更文挑战

一、函数的介绍

所谓函数,就是把 具有独立功能的代码块 组织为一个小模块,在需要的时候 调用

函数的使用包含两个步骤:

  1. 定义函数 —— 封装 独立的功能
  2. 调用函数 —— 享受 封装 的成果

函数的作用: 在开发程序时,使用函数可以提高编写的效率以及代码的 重用

二、函数基本使用

2.1 函数的定义

定义函数的格式如下:

1
2
3
4
python复制代码def 函数名():

函数封装的代码
……
  1. def 是英文 define 的缩写
  2. 函数名称 应该能够表达 函数封装代码 的功能,方便后续的调用
  3. 函数名称 的命名应该 符合 标识符的命名规则
    • 可以由 字母、下划线 和 数字 组成
    • 不能以数字开头

2.2 函数调用

调用函数很简单的,通过 函数名() 即可完成对函数的调用

2.3 第一个函数演练

需求

  • 编写一个打招呼 say_hello 的函数,封装三行打招呼的代码
  • 在函数下方调用打招呼的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python复制代码#!/usr/bin/python3
# -*- coding:utf-8 -*-

name = "hui"

# 解释器知道这里定义了一个函数
def say_hello():
print("hello 1")
print("hello 2")
print("hello 3")

print(name)

say_hello()

print(name)

运行结果如下:

1
2
3
4
5
python复制代码hui
hello 1
hello 2
hello 3
hui

只有在调用函数时,之前定义的函数才会被执行

函数执行完成之后,会重新回到之前的程序中,继续执行后续的代码

定义好函数之后,只表示这个函数封装了一段代码而已

如果不主动调用函数,函数是不会主动执行的

Q: 能否将 函数调用 放在 函数定义 的上方?

A:

  • 不能!
  • 因为在 使用函数名 调用函数之前,必须要保证 Python 已经知道函数的存在
  • 否则控制台会提示 NameError: name 'say_hello' is not defined (名称错误:say_hello 这个名字没有被定义)

2.4 PyCharm 的调试工具

  • 右击 Python 文件,在弹出的菜单项选择 Debug 或 点击右上角绿色的 小虫子,就可开启调试。
  • F8 Step Over 可以单步执行代码,会把函数调用看作是一行代码直接执行
  • F7 Step Into 可以单步执行代码,如果是函数,会进入函数内部

断点调试

2.5 函数的文档注释

  • 在开发中,如果希望给函数添加注释,应该在 定义函数 的下方,使用 连续的三对引号
  • 在 连续的三对引号 之间编写对函数的说明文字
  • 在 函数调用 位置,使用快捷键 CTRL + Q 可以查看函数的说明信息

函数注释

注意:因为 函数体相对比较独立,函数定义的上方 应该和其他代码(包括注释)保留 两个空行

三、函数的参数

演练需求

  1. 开发一个 sum_2_num 的函数
  2. 函数能够实现 两个数字的求和 功能

演练代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
python复制代码#!/usr/bin/python3
# -*- coding:utf-8 -*-

def sum_2_num():

num1 = 10
num2 = 20
result = num1 + num2

print("%d + %d = %d" % (num1, num2, result))

sum_2_num() # 10 + 20 = 30

思考一下存在什么问题

函数只能处理 固定数值 的相加

如何解决?

  • 如果能够把需要计算的数字,在调用函数时,传递到函数内部就好了!

3.1 函数参数的使用

  • 在函数名的后面的小括号内部填写 参数
  • 多个参数之间使用 , 分隔
1
2
3
4
5
6
7
8
9
10
python复制代码#!/usr/bin/python3
# -*- coding:utf-8 -*-

def sum_2_num(num1, num2):

result = num1 + num2

print("%d + %d = %d" % (num1, num2, result))

sum_2_num(50, 20) # 50 + 20 = 70

3.2 参数的作用

函数,把 具有独立功能的代码块 组织为一个小模块,在需要的时候 调用

函数的参数,增加函数的 通用性,针对 相同的数据处理逻辑,能够 适应更多的数据

  1. 在函数 内部,把参数当做 变量 使用,进行需要的数据处理
  2. 函数调用时,按照函数定义的参数顺序,把 希望在函数内部处理的数据,通过参数 传递

3.3 形参和实参

形参:定义 函数时,小括号中的参数,是用来接收参数用的,在函数内部 作为变量使用

实参:调用 函数时,小括号中的参数,是用来把数据传递到 函数内部 用的

函数形参、实参

四、 函数的返回值

  • 在程序开发中,有时候,会希望 一个函数执行结束后,告诉调用者一个结果,以便调用者针对具体的结果做后续的处理
  • 返回值 是函数 完成工作后,最后 给调用者的 一个结果
  • 在函数中使用 return 关键字可以返回结果
  • 调用函数一方,可以 使用变量 来 接收 函数的返回结果

注意:return 表示返回,后续的代码都不会被执行

1
2
3
4
5
6
7
8
9
10
11
12
python复制代码#!/usr/bin/python3
# -*- coding:utf-8 -*-

def sum_2_num(num1, num2):
"""对两个数字的求和"""

return num1 + num2

# 调用函数,并使用 result 变量接收计算结果
result = sum_2_num(10, 20)

print("计算结果是 %d" % result) # 30

五、函数的嵌套调用

  • 一个函数里面 又调用 了 另外一个函数,这就是 函数嵌套调用
  • 如果函数 test2 中,调用了另外一个函数 test1
    • 那么执行到调用 test1 函数时,会先把函数 test1 中的任务都执行完
    • 才会回到 test2 中调用函数 test1 的位置,继续执行后续的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
python复制代码#!/usr/bin/python3
# -*- coding:utf-8 -*-

def test1():

print("*" * 50)
print("test 1")
print("*" * 50)


def test2():

print("-" * 50)
print("test 2")

test1()

print("-" * 50)

test2()

运行结果如下:

1
2
3
4
5
6
python复制代码--------------------------------------------------
test 2
**************************************************
test 1
**************************************************
--------------------------------------------------

打印分隔线

体会一下工作中 需求是多变 的

需求 1

  • 定义一个 print_line 函数能够打印 * 组成的 一条分隔线
1
2
3
python复制代码def print_line(char):

print("*" * 50)

需求 2

  • 定义一个函数能够打印 由任意字符组成 的分隔线
1
2
3
python复制代码def print_line(char):

print(char * 50)

需求 3

  • 定义一个函数能够打印 任意重复次数 的分隔线
1
2
3
4
5
6
7
python复制代码def print_line(char, times):
"""
打印分割线
:param char: 分割线组成的字符
:param times: 字符的数量
"""
print(char * times)

需求 4

  • 定义一个函数能够打印 n 行 的分隔线,分隔线要求符合需求 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
29
python复制代码def print_line(char, times):
"""
打印分割线
:param char: 分割线组成的字符
:param times: 字符的数量
"""
print(char * times)


def print_lines(char, times, row):
"""
打印指定行数的分割线
:param char: 分割线组成的字符
:param times: 字符的数量
:param row: 指定的行数
"""

line = 0

while line < row:
print_line(char, times)

line += 1


# 函数调用
print_lines(char='-*-', times=20, row=2)
print('测试分割线')
print_lines(char='-*-', times=20, row=2)

运行结果如下:

1
2
3
4
5
python复制代码-*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-
-*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-
测试分割线
-*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-
-*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-

尾语

✍ 用 Code 谱写世界,让生活更有趣。❤️

✍ 万水千山总是情,点赞再走行不行。❤️

✍ 码字不易,还望各位大侠多多支持。❤️

014.png

本文转载自: 掘金

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

快速上手java8四大函数式接口!

发表于 2021-06-12

这是我参与更文挑战的第12天,活动详情查看: 更文挑战

今天 4ye 来和小伙伴们分享下 java8四大函数式接口 (Consumer,Supplier,Function,Predicate)的简单使用

当然我们不得不聊下它的这个函数式编程先,毕竟这个是一个很重要的改进 😁

函数式接口

函数式接口指的是接口中只有一个抽象方法的接口,用注解@FunctionalInterface标记,表示该接口是函数式接口

注解FunctionalInterface

如图,可以看到这里什么都没有,表面它只是一个标记接口.

20201014223650

四大接口

Consumer

源码如下:

20201014225926

作用: 内部消化传进来的参数, 没有返回值

示例:

1
2
3
java复制代码Consumer<String> consumer=System.out::println;
consumer.accept("Consumer");
// 结果: "Consumer"

Supplier

源码如下:

20201014231056

作用: 没有传参 , 直接返回一个结果

示例:

1
2
3
java复制代码Supplier<String> supplier= () -> "Supplier";
System.out.println(supplier.get());
// 结果: "Supplier"

Function

额这个源码太长了,截取部分源码如下:

20201014231327
可以看到它是综合了这个consumer和Supplier🐖

作用: 根据传的参数,返回一个结果,这里是既有传参,又有返回值😝

示例:

1
2
3
java复制代码Function<String,String> func= String::toUpperCase;
System.out.println(func.apply("Function"));
// 结果: "FUNCTION"

Predicate

这个源码也比较长,截取部分源码如下:

20201014232954

作用: 判断给的参数是否符合条件,返回 false 或者 true

示例:

1
2
3
java复制代码Predicate<String> predicate= "predicate"::equals;
System.out.println(predicate.test("predicate"));
// 结果: true

可以看到还是特别简单滴~

小例子

再举个小例子 (大家都熟悉的 hashmap)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码    @Test
public void mergeTest(){
String key="java4ye";
HashMap<String, Integer> map=new HashMap<>();
map.put(key,1);

// 写法1
map.merge(key,2,Integer::sum);

System.out.println(map.get(key));

// 写法2
Integer integer = map.get(key);
if (integer!=null){
integer+=2;
map.put(key,integer);
}

System.out.println(map.get(key));
}
// 3
// 5

这里就用到这个 Function 函数式接口啦~ 对比这两种写法,是不是发现第一种简洁多啦!

而且我们又 get 到另外一个概念,lambda表达式,通过第一种写法,我们也可以很快认识到,lambda表达式其实是实现函数式接口的一个快捷方式~


那么本期就简单介绍这些先啦~😄

最后

欢迎小伙伴们来一起探讨问题~

如果你觉得本篇文章还不错的话,那拜托再点点赞支持一下呀😝

让我们开始这一场意外的相遇吧!~

欢迎留言!谢谢支持!ヾ(≧▽≦*)o 冲冲冲!!

我是4ye 咱们下期很快再见!!

本文转载自: 掘金

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

王者并发课-黄金3:雨露均沾-不要让你的线程在竞争中被“饿死

发表于 2021-06-12

欢迎来到《王者并发课》,本文是该系列文章中的第13篇。

在上篇文章中,我们介绍了避免死锁的几种策略。虽然死锁臭名昭著,然而在并发编程中,除了死锁之外,还有一些同样重要的线程活跃性问题值得关注。它们的知名度不高,但破坏性极强,本文将介绍的正是其中的线程饥饿和活锁问题。

一、饥饿的产生

所谓线程 饥饿(Starvation) 指的是在多线程的资源竞争中,存在贪婪的线程一直锁定资源不释放,其他的线程则始终处于等待状态,然而这个等待是没有结果的,它们会被活活地饿死。

独占者的贪婪是饥饿产生的原因之一,概括来说,饥饿一般由下面三种原因导致:

(1)线程被无限阻塞

当获得锁的线程需要执行无限时间长的操作时(比如IO或者无限循环),那么后面的线程将会被无限阻塞,导致被饿死。

(2) 线程优先级降低没有获得CPU时间

当多个竞争的线程被设置优先级之后,优先级越高,线程被给予的CPU时间越多。在某些极端情况下,低优先级的线程可能永远无法被授予充足的CPU时间,从而导致被饿死。

(3) 线程永远在等待资源

在青铜系列文章中,我们说过notify在发送通知时,是无法唤醒指定线程的。当多个线程都处于wait时,那么部分线程可能始终无法被通知到,以至于挨饿。

二、饥饿与公平

为了直观体验线程的饥饿,我们创建了下面的代码。

创建哪吒、兰陵王等四个英雄玩家,他们以竞争的方式打野,杀死野怪可以获得经济收益。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码public class StarvationExample {

public static void main(String[] args) {
final WildMonster wildMonster = new WildMonster();

String[] players = {
"哪吒",
"兰陵王",
"铠",
"典韦"
};
for (String player: players) {
Thread playerThread = new Thread(new Runnable() {
public void run() {
wildMonster.killWildMonster();
}
});
playerThread.setName(player);
playerThread.start();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码 public class WildMonster {
public synchronized void killWildMonster() {
while (true) {
String playerName = Thread.currentThread().getName();
System.out.println(playerName + "斩获野怪!");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("打野中断");
}
}
}
}

运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
shell复制代码哪吒斩获野怪!
哪吒斩获野怪!
哪吒斩获野怪!
哪吒斩获野怪!
哪吒斩获野怪!
哪吒斩获野怪!
哪吒斩获野怪!
哪吒斩获野怪!
哪吒斩获野怪!
哪吒斩获野怪!
哪吒斩获野怪!

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

从结果中可以看到,在几个线程的运行中,始终只有哪吒可以斩获野怪,其他英雄束手无策等着被饿死。为什么会发生这样的事?

仔细看WildMonster类中的代码,问题出在killWildMonster同步方法中。一旦某个英雄进入该方法后,将一直持有对象锁,其他线程被阻塞而无法再进入。

当然,解决的方法也很简单,只要打破独占即可。比如,我们在下面的代码中把Thread.sleep改成wait,那么问题将迎刃而解。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码 public static class WildMonster {
public synchronized void killWildMonster() {
while (true) {
String playerName = Thread.currentThread().getName();
System.out.println(playerName + "斩获野怪!");
try {
wait(500);
} catch (InterruptedException e) {
System.out.println("打野中断");
}
}
}
}

运行结果如下:

1
2
3
4
5
6
7
8
shell复制代码哪吒斩获野怪!
铠斩获野怪!
兰陵王斩获野怪!
典韦斩获野怪!
兰陵王斩获野怪!
典韦斩获野怪!

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

从结果中可以看到,四个英雄都获得了打野的机会,在一定程度上实现了公平。(备注:wait会释放锁,但sleep不会,对此不理解的可以查看青铜系列文章。)

如何让线程之间公平竞争,是线程问题中的重要话题。虽然我们无法保证百分百的公平,但我们仍然要通过设计一定的数据结构和使用相应的工具类来增加线程之间的公平性。

关于线程之间的公平性,在本文中重要的是理解它的存在和重要性,关于如何优雅地解决,我们会在后续的文章中介绍相关的并发工具类。

三、活锁的麻烦

相对于死锁,你可能对活锁没有那么熟悉。然而,活锁所造成的负面影响并不亚于死锁。在结果上,活锁和死锁都是灾难性的,都将会造成应用程序无法提供正常的服务能力。

所谓活锁(LiveLock),指的是两个线程都忙于响应对方的请求,但却不干自己的事。它们不断地重复特定的代码,却一事无成。

不同于死锁,活锁并不会造成线程进入阻塞状态,但它们会原地打转,所以在影响上和死锁相似,程序会进入无线死循环,无法继续进行。

如果你无法直观理解活锁是什么,相信你在走路时一定遇到过下面这种情况。两人相向而行,出于礼貌两人互相让行,让来让去,结果两人仍然无法通行。活锁,也是这个意思。

小结

以上就是关于线程饥饿与活锁的全部内容。在本文中,我们介绍了线程产生饥饿的原因。对待线程饥饿,没有百分百的方案,但可以尽可能地实现公平竞争。我们没有在本文列举线程公平性的一些工具类,因为我认为对问题的理解要比解决方案更重要。如果没有对问题的理解,方案在落地时也会出现知其然而不知其所以然的情况。另外,虽然活锁并不像死锁那样知名度,但是对活锁的恰当理解仍然非常必要,它是并发知识体系中的一部分。

正文到此结束,恭喜你又上了一颗星✨

夫子的试炼

  • 编写代码设置不同线程的优先级,体验线程饥饿并给出解决方案。

延伸阅读与参考资料

  • 动态图片引用
  • 《王者并发课》大纲与更新进度总览

关于作者

从业近十年,先后从事敏捷与DevOps咨询、Tech Leader和管理等工作,对分布式高并发架构有丰富的实战经验。热衷于技术分享和特定领域书籍翻译,掘金小册《高并发秒杀的设计精要与实现》作者。


关注公众号【MetaThoughts】,及时获取文章更新和文稿。

如果本文对你有帮助,欢迎点赞、关注、监督,我们一起从青铜到王者。

本文转载自: 掘金

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

自定义Metrics|让Prometheus监控你的应用程序

发表于 2021-06-12

这是我参与更文挑战的第12天,活动详情查看: 更文挑战

前言:

本文将以Spring Boot/Spring Cloud为例,介绍如果使用Prometheus SDK实现自定义监控指标的定义以及暴露,并且会介绍Prometheus中四种不同指标类型(Counter, Gauge, Histogram, Summary)的实际使用场景;

需求背景:

最近在做的一个新的项目,上线后要实现Prometheus监控,以及对项目里自己比较在意的指标进行自定义监控,用的是公司已有的Grafana,并没有自己搭建Grafana+Prometheus的仪表盘,下一篇文章会更新一些如何使用Grafana+Prometheus搭建项目仪表盘,这里就不多做赘述啦!下面看正文👇👇👇;

对于后端的一些应用以及环境架构。一般而言,我们通常会从几个层面进行监控指标的采集:

  • 入口网关:这里可以是Nginx/HaProxy这一类的负载均衡器,也可以是注入Spring Cloud Zuul这一类框架提供的微服务入口。一般来说我们需要对所有Http Request相关的指标数据进行采集。如请求地址,Http Method,返回状态码,响应时长等。从而可以通过这些指标历史数据去分析业务压力,服务状态等信息。
  • 应用服务:对于应用服务而言,基本的如应用本身的资源使用率,比如如果是Java类程序可以直接通过JVM信息来进行统计,如果是部署到容器中,则可以通过Container的资源使用情况来统计。除了资源用量外,某些特殊情况下,我们可能还会对应用中的某些业务指标进行采集。
  • 基础设施:虚拟机或者物理机的资源使用情况等。
  • 其它:集群环境中所使用到的数据库,缓存,消息队列等中间件状态等。

对于以上的集中场景中,除了直接使用Prometheus社区提供的Exporter外,不同的项目可能还需要实现一些自定义的Exporter用于实现对于特定目的的指标的采集和监控需求。

对于监控系统主要由以下几个结构组成:

  • 目标服务:该服务即为各个组上线的服务,通过接入架构组提供的公共监控服务包后,会在服务启动之后暴露出一个数据监控接口 /actuator/prometheus,我们的一些监控数据指标也暴露在这里,直接请求http://localhost:8080/actuator/prometheus 即可
  • 数据搜集服务:即prometheus服务,该服务主要负责将定时各个目标服务接口暴露的数据搜集起来,并将接口中的数据根据【Metric】数据格式整合起来,供监控展示服务使用
  • 数据展示服务:即grafana,该服务为单纯的数据展示服务,可支持包括prometheus等多种数据搜集服务的数据展示,同时提供一些特性函数以应对多种复合的展示场景

接入方法

pom依赖:

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
xml复制代码<!--监控点开始-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${starter-actuator.version}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient</artifactId>
<version>${prometheus-simpleclient.version}</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_spring_boot</artifactId>
<version>${prometheus-simpleclient.version}</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_hotspot</artifactId>
<version>${prometheus-simpleclient.version}</version>
</dependency>
<!--监控点结束-->

application.properties配置文件:

1
2
3
4
5
6
7
ini复制代码management.metrics.export.prometheus.enabled=true
management.metrics.export.prometheus.step=1m
management.metrics.export.prometheus.descriptions=true
management.web.server.auto-time-requests=true
management.endpoints.prometheus.id=produce-server
management.endpoints.web.exposure.include=health,info,env,prometheus,metrics,httptrace,threaddump,heapdump,springmetrics,git
management.health.rabbit.enabled=false

不同项目对于暴露出去的接口要求不同,可针对各值环境对上述management.endpoints.web.exposure.include 参数进行相应调整

项目内新增prometheus数据收集组件:

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
java复制代码import io.micrometer.core.instrument.ImmutableTag;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

/**
* levelDb自定义监控指标
*
* @author taoze
* @version 1.0
* @date 4/27/21 10:42 AM
*/
@Component
@Slf4j
public class LevelDbMetrics {

/**
* levelDB监控项
*/
private AtomicLong levelDBHead = Metrics.gauge("a.levelDb.head", init(), new AtomicLong(0));
private AtomicLong levelDBTail = Metrics.gauge("a.levelDb.tail", init(), new AtomicLong(0));

/**
* 初始化tag
*
* @return
*/
List<Tag> init() {
List<Tag> list = new ArrayList<>();
String ip = null;
try {
ip = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
list.add(new ImmutableTag("host", StringUtils.isBlank(ip) ? "127.0.0.1" : ip));
return list;
}

public void setLevelDBHead(Long head) {
levelDBHead.set(head);
log.info("levelDB head = [{}]", levelDBHead.get());
}

public void setLevelDBTail(Long tail) {
levelDBTail.set(tail);
log.info("levelDB tail = [{}]", levelDBTail.get());
}
}
  • 想要将自定义数据暴露在/actuator/prometheus 中,就需要在项目中建立对一个Metrics对象,然后对这个对象进行数据更新,最终在调用接口时就会从这个Metrics对象存储的数据中获取到指定的数据
  • 更新值的时候调用 setLevelDBHead或setLevelDBTail方法即可或使用定时任务定时修改值

Metrics指标类型以及使用场景:

  • Counter,只增不减的计数器
  • Gauge,可增可减的仪表盘
  • Histogram,自带buckets区间用于统计分布统计图
  • Summary, 客户端定义的数据分布统计图
    除了上述方法我们也可以通过拦截器/过滤器:用于统计所有应用请求的情况等

WebMvcConfigurerAdapter方式手机监控指标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
scala复制代码@SpringBootApplication
@EnablePrometheusEndpoint
public class SpringApplication extends WebMvcConfigurerAdapter implements CommandLineRunner {

@Autowired
private CustomExporter customExporter;

...省略的代码

@Override
public void run(String... args) throws Exception {
...省略的代码
customExporter.register();
}
}

ok!今日分享到此结束,搜集指标代码还是比较简单的,有类似需求的小伙伴可以试试哦,希望可以对大家有帮助,有不对的地方希望大家可以提出来的,共同成长;

整洁成就卓越代码,细节之中只有天地

本文转载自: 掘金

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

这个极简前后端分离应用不能错过 这个极简前后端分离应用不能错

发表于 2021-06-12

这个极简前后端分离应用不能错过

这是一个极简的代码展示,使用koa web服务渲染网站静态页面。

系统组件组成

在这里插入图片描述

前端服务的文件列表:
在这里插入图片描述
读者可自行准备package.json, 本文使用以下版本:

1
2
3
json复制代码"koa": "^2.13.1",
"koa-router": "^10.0.0",
"koa-static": "^5.0.0"

直接懒人复制 package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
js复制代码{
"name": "uiapp",
"version": "1.0.0",
"description": "uiapp by levin",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"keywords": [],
"author": "levin",
"dependencies": {
"koa": "^2.13.1",
"koa-router": "^10.0.0",
"koa-static": "^5.0.0"
}
}

app.js 应用入口代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
javascript复制代码const koa = require('koa');  
const serve = require('koa-static');
const path=require('path');

// 启动koa web服务
const app = new koa()

// 加载静态页面
var staticPath = path.join(__dirname + '/static');
console.log('static-path:', staticPath)
app.use(serve(staticPath))

console.log(new Date())
const PORT = 8080
console.log('start app at port %s', PORT)
app.listen(PORT);

思路:启动一个koa 服务,并使用koa-static中间件渲染当前应用下的static文件夹的静态文件,会默认加载index.html首页。

好,接下来。开发我们的UI站点。

UI站点

先写index.html

下面代码主要使用了axios和一个getProduct.js。
当首页加载的时候,getProduct.js 会获取后台产品服务展示产品。

1
2
3
4
5
6
7
8
9
10
11
12
html复制代码<html>
<head>
<title>雷学委-UI FrontendSerivce</title>
</head>
<body>
<h1>这是一个前后段分离的应用!</h1>
<h2>后端数据展示:"http://localhost:8081"</h2>
<div id="result" ></div>
</body>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="/getProducts.js" ></script>
</html>

接下来是getProducts.js

这个内部使用了axios调用了产品服务(第三方服务)的接口(http://localhost:8081/products) ,然后分别针对请求成功和失败把状态写到id为“result” 的div上。

调用第三方服务成功显示绿色背景,失败显示红色背景。

重点来了:
复杂的web 应用中会有很多getProducts这样的JS更后端多个接口进行交互,然后再把交互完的数据更新反馈到UI层,这个不管是Angular/React/Vue都是如此。

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
html复制代码function $(id){
return document.getElementById(id)
}

function handleOnData(data){
$('result').innerHTML = '<div style="background:green">'
+ JSON.stringify(data)
+ '</div>'
}

function handleOnError(msg){
$('result').innerHTML = '<div style="background:red">'
+ JSON.stringify(msg)
+ '</div>'
}

// 第三方接口(产品服务接口)
// 代码实现:https://blog.csdn.net/geeklevin/article/details/109403172
var api = 'http://localhost:8081/products'
console.log('will get products from api:' , api);
axios.get(api)
.then(function (response) {
handleOnData(response.data);
})
.catch(function (error) {
console.log(error);
handleOnError('后端服务已下线!');
});

后端的产品服务实现代码

请复制: NodeJS 后端开发 03 使用Restify开发API 一个完整的CRUD , 改一下端口为8081,并参考该文章启动。

效果演示

UI启动命令如下,下面两个图左边为UI服务主页,右边为产品服务接口浏览器打开的状态。

1
bash复制代码node app.js #启动UI服务。

启动后台服务(右边已不可访问),左边的UI站点显示产品的JSON数据!

UI读者可以自行绘制更漂亮的,请尽情发挥想象。

在这里插入图片描述
停止后台服务(右边已不可访问),左边的UI站点显示产品后台服务已下线!
在这里插入图片描述

思想总结

前后端分离更多是解耦合大原则指导下在web应用上的呈现。

像过去有Swing, Qt或者C#UI,也能应用这个思想。
把界面/页面跟后端代码通过使用API(web API)来实现无缝整合。
这样还能实现技术上异构(web服务NodeJS,后端用Springboot/PythonDjango等),弹性很大又不会导致架构过度离散。

前后端分离是行业的标准做法!

看完本文,您学会了吗?
不妨试试使用熟悉的技术自己做一次,或者找一两个朋友按着本文分别开发一个服务并整合起来,互相讨论,这样技术进步很明显。

希望读者们都完全掌握理解,把握重点。

需要学习更多NodeJS知识可以关注: 雷学委的NodeJS专栏

本文转载自: 掘金

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

仅用CSS几步实现赛博朋克2077风格视觉效果

发表于 2021-06-12

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

背景

文章开始之前先简单了解下什么是 赛博朋克,以及什么是 赛博朋克2077。

  • 赛博朋克(Cyberpunk)是“控制论、神经机械学”与“朋克”的结合词,背景大都建立于“低端生活与高等科技的结合”,拥有先进的科学技术,再以一定程度崩坏的社会结构做对比;拥有五花八门的视觉冲击效果,如街头的霓虹灯、街排标志性广告以及高楼建筑等,通常搭配色彩是以黑、紫、绿、蓝、红为主。其中菲利普·狄克所著作的《仿生人会梦到电子羊吗?》最受注目,小说亦被改编成电影《银翼杀手》。总的来讲,赛博朋克的风格主线,就是反应出科技高度发展的人类文明,与脆弱渺小的人类个体之间的强烈反差,同时外界与内在,钢铁与肉体,过去与未来,现实与虚幻等矛盾在其中交织。

《赛博朋克2077》 是一款动作角色类游戏,于 2020年12月10日 登陆各大游戏平台。故事发生在夜之城,权力更迭和身体改造是这里不变的主题。玩家将扮演一名野心勃勃的雇佣兵:V,追寻一种独一无二的植入体——获得永生的关键。它以自由的探索性,较高的操控度以及惊艳的视觉特效收获了一大批玩家。我非常喜欢 2077 官网的设计风格,因此本篇文章主要以 2077 官网为例,通过几个例子,依次实现赛博朋克风格元素效果。

实现

高对比度

首先我们来看一下 2077 中文官网首页,页面主要以醒目的 明黄色 为主色调,并小面积使用它的对比色 淡蓝色、玫红色 的色块作为点缀,文本和线条边框使用 纯黑色。这一步实现非常简单,我们在实现赛博朋克风格的页面时,可以使用上面提到的 黑、紫、绿、蓝、红 为主色调,再以它们的对比色作为按钮、文本提示框,可以实现强烈的视觉冲击。

constract.png

故障风格按钮

故障效果是一种显示设备崩坏效果,在 2077 官网中应用很多,我们先来实现 button 在 hover 时产生故障效果。

button.gif

1
html复制代码<button>立即加入</button>

故障效果主要通过 clip-path: inset 和动画实现。利用 button 的伪元素 ::after,给它定义多个分片 --slice 变量,并通过动画切换切片的位置,来实现晃动效果。其中clip-path 属性使用裁剪方式创建元素的可显示区域。区域内的部分显示,区域外的隐藏。 inset() 方法用于定义一个矩形,可以传入 5 个参数,分别对应 top,right,bottom,left的裁剪位置及 round 和 radius(可选,圆角),它的基本语法如下:

1
2
css复制代码inset( <length-percentage>{1,4} [ round <border-radius> ]? )
clip-path: inset(2em 3em 2em 1em round 2em);

完整实现:

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
css复制代码button, button::after {
width: 300px;
height: 86px;
font-size: 40px;
background: linear-gradient(45deg, transparent 5%, #FF013C 5%);
border: 0;
color: #fff;
letter-spacing: 3px;
line-height: 88px;
box-shadow: 6px 0px 0px #00E6F6;
outline: transparent;
position: relative;
}
button::after {
--slice-0: inset(50% 50% 50% 50%);
--slice-1: inset(80% -6px 0 0);
--slice-2: inset(50% -6px 30% 0);
--slice-3: inset(10% -6px 85% 0);
--slice-4: inset(40% -6px 43% 0);
--slice-5: inset(80% -6px 5% 0);
content: '立即加入';
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, transparent 3%, #00E6F6 3%, #00E6F6 5%, #FF013C 5%);
text-shadow: -3px -3px 0px #F8F005, 3px 3px 0px #00E6F6;
clip-path: var(--slice-0);
}
button:hover::after {
animation: 1s glitch;
animation-timing-function: steps(2, end);
}
@keyframes glitch {
0% { clip-path: var(--slice-1); transform: translate(-20px, -10px); }
10% { clip-path: var(--slice-3); transform: translate(10px, 10px); }
20% { clip-path: var(--slice-1); transform: translate(-10px, 10px); }
30% { clip-path: var(--slice-3); transform: translate(0px, 5px); }
40% { clip-path: var(--slice-2); transform: translate(-5px, 0px); }
50% { clip-path: var(--slice-3); transform: translate(5px, 0px); }
60% { clip-path: var(--slice-4); transform: translate(5px, 10px); }
70% { clip-path: var(--slice-2); transform: translate(-10px, 10px); }
80% { clip-path: var(--slice-5); transform: translate(20px, -10px); }
90% { clip-path: var(--slice-1); transform: translate(-10px, 0px); }
100% { clip-path: var(--slice-1); transform: translate(0); }
}

故障风格图片

故障效果同样可以应用在文本、图片、视频等媒体展示上,营造满满的科技氛围。这部分内容来看看如何实现故障风格的图片展示效果。

glitch_picture.gif

.glitch 是为图片展示容器主体,它的子元素 glitch__item 用来表示故障条,与上例中 ::after 伪元素作用类似。

1
2
3
4
5
html复制代码<div class="glitch">
<div class="glitch__item"></div>
<!-- ... -->
<div class="glitch__item"></div>
</div>

故障风格图片和故障风格按钮实现思路基本类似,不过这次用到了 clip-path: polygon 实现,polygon 用于裁切多边形的方法,它的每对值表示裁切元素的坐标。 background-blend-mode 属性定义了背景层的混合模式。由于文章篇幅有限,以下代码只展示了一个故障条的动画,完整例子可查看文章末尾对应链接 🔗:

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
css复制代码:root {
--gap-horizontal: 10px;
--gap-vertical: 5px;
--time-anim: 4s;
--delay-anim: 2s;
--blend-mode-1: none;
--blend-color-1: transparent;
}
.glitch {
position: relative;
}
.glitch .glitch__item {
background: url("banner.png") no-repeat 50% 50%/cover;
height: 100%;
width: 100%;
top: 0;
left: 0;
position: absolute;
}
.glitch .glitch__item:nth-child(1) {
background-color: var(--blend-color-1);
background-blend-mode: var(--blend-mode-1);
animation-duration: var(--time-anim);
animation-delay: var(--delay-anim);
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-name: glitch-anim-1;
}
@keyframes glitch-anim-1 {
0% {
opacity: 1;
transform: translate3d(var(--gap-horizontal), 0, 0);
clip-path: polygon(0 2%, 100% 2%, 100% 5%, 0 5%);
}
2% { clip-path: polygon(0 15%, 100% 15%, 100% 15%, 0 15%); }
4% { clip-path: polygon(0 10%, 100% 10%, 100% 20%, 0 20%); }
6% { clip-path: polygon(0 1%, 100% 1%, 100% 2%, 0 2%); }
8% { clip-path: polygon(0 33%, 100% 33%, 100% 33%, 0 33%); }
10% { clip-path: polygon(0 44%, 100% 44%, 100% 44%, 0 44%); }
12% { clip-path: polygon(0 50%, 100% 50%, 100% 20%, 0 20%); }
14% { clip-path: polygon(0 70%, 100% 70%, 100% 70%, 0 70%); }
16% { clip-path: polygon(0 80%, 100% 80%, 100% 80%, 0 80%); }
18% { clip-path: polygon(0 50%, 100% 50%, 100% 55%, 0 55%); }
20% { clip-path: polygon(0 70%, 100% 70%, 100% 80%, 0 80%); }
21.9% {
opacity: 1;
transform: translate3d(var(--gap-horizontal), 0, 0);
}
22%, 100% {
opacity: 0;
transform: translate3d(0, 0, 0);
clip-path: polygon(0 0, 0 0, 0 0, 0 0);
}
}

霓虹元素

在赛博朋克场景中,如电影《银翼杀手》《机壳特工队》、游戏《看门狗》《赛博朋克2077》中无论是在废弃的建筑物 🏠、还是繁华的歌舞町 ⛩️ ,都存在大量的霓虹 neon 元素。我们同样可以使用大量霓虹元素来装饰页面,比如页面标题、按钮、表单边框等都可以使用霓虹效果,下面是霓虹文字实现的简要示例:

neon.gif

.neon 和 .flux 两个元素是两个文本载体,将被应用不同的霓虹效果样式和动画。

1
2
html复制代码<div class="neon">CYBER</div>
<div class="flux">PUNK</div>

文字的霓虹效果主要通过 text-shadow 属性实现,闪烁效果也是通过添加与文字颜色相近的 text-shadow 动画来实现,其中 .neon 元素被应用了 ease-in-out 运动曲线, .flux 元素被应用了 linear 运动曲线,可以看出两者之间的差别吗。😂

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
css复制代码.neon {
text-shadow: 0 0 3vw #F4BD0A;
animation: neon 2s ease-in-out infinite;
}
.flux {
text-shadow: 0 0 3vw #179E05;
animation: flux 2s linear infinite;
}
@keyframes neon {
0%, 100% {
text-shadow: 0 0 1vw #FA1C16, 0 0 3vw #FA1C16, 0 0 10vw #FA1C16, 0 0 10vw #FA1C16, 0 0 .4vw #FED128, .5vw .5vw .1vw #806914;
color: #FFFC00;
}
50% {
text-shadow: 0 0 .5vw #800E0B, 0 0 1.5vw #800E0B, 0 0 5vw #800E0B, 0 0 5vw #800E0B, 0 0 .2vw #800E0B, .5vw .5vw .1vw #40340A;
color: #806914;
}
}
@keyframes flux {
0%, 100% {
text-shadow: 0 0 1vw #10ff4c, 0 0 3vw #1041FF, 0 0 10vw #1041FF, 0 0 10vw #1041FF, 0 0 .4vw #8BFDFE, .5vw .5vw .1vw #147280;
color: #03C03C;
}
50% {
text-shadow: 0 0 .5vw #024218, 0 0 1.5vw #024713, 0 0 5vw #023812, 0 0 5vw #012707, 0 0 .2vw #022201, .5vw .5vw .1vw #011a06;
color: #179E05;
}
}

为了使文字看起来更有霓虹效果,以上示例使用了 neon 字体:s3-us-west-2.amazonaws.com/s.cdpn.io/7…

不规则文本框

赛博朋克2077中可以看到很多文本展示框都是这种不规则图形的,是不是很酷呢,这部分内容将介绍如何实现 2077 风格的文本框。

textbox.png

上面 3 个文本框分别由3 个 p 标签构成,.inverse 类表示反色背景,.dotted 将实现点状背景。

1
2
3
html复制代码<p class="cyberpunk">经典的赛博朋克角色是边缘且性格疏远的独行者。他们生活在社会群体的边缘,一个弥漫反乌托邦氛围的未来:日常生活受到急剧改变的科技影响,普及的计算机化信息笼罩全球,以及侵入性的人体改造。</p>
<p class="cyberpunk inverse">经典的赛博朋克角色是边缘且性格疏远的独行者。他们生活在社会群体的边缘,一个弥漫反乌托邦氛围的未来:日常生活受到急剧改变的科技影响,普及的计算机化信息笼罩全球,以及侵入性的人体改造。</p>
<p class="cyberpunk inverse dotted">经典的赛博朋克角色是边缘且性格疏远的独行者。他们生活在社会群体的边缘,一个弥漫反乌托邦氛围的未来:日常生活受到急剧改变的科技影响,普及的计算机化信息笼罩全球,以及侵入性的人体改造。</p>

文本框不规则的形状主要由 clip-path: polygon 实现,通过以下几个坐标的裁切,就可以实现 2077 风格的多边形了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
css复制代码clip-path: polygon(
0px 25px,
26px 0px,
calc(60% - 25px) 0px,
60% 25px,
100% 25px,
100% calc(100% - 10px),
calc(100% - 15px) calc(100% - 10px),
calc(80% - 10px) calc(100% - 10px),
calc(80% - 15px) 100%,
80px calc(100% - 0px),
65px calc(100% - 15px),
0% calc(100% - 15px)
);

完整代码:

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
css复制代码:root {
--yellow-color: #f9f002;
--border-color: #8ae66e;
}
.cyberpunk {
padding: 5px;
position: relative;
font-size: 1.2rem;
color: var(--yellow-color);
border: 30px solid var(--yellow-color);
border-right: 5px solid var(--yellow-color);
border-left: 5px solid var(--yellow-color);
border-bottom: 24px solid var(--yellow-color);
background-color: #000;
clip-path: polygon(0px 25px, 26px 0px, calc(60% - 25px) 0px, 60% 25px, 100% 25px, 100% calc(100% - 10px), calc(100% - 15px) calc(100% - 10px), calc(80% - 10px) calc(100% - 10px), calc(80% - 15px) 100%, 80px calc(100% - 0px), 65px calc(100% - 15px), 0% calc(100% - 15px));
}
.cyberpunk.inverse {
border: none;
padding: 40px 15px 30px;
color: #000;
background-color: var(--yellow-color);
border-right: 2px solid var(--border-color);
}
.dotted, .dotted:before, .dotted:after {
background: var(--yellow-color);
background-image: radial-gradient(#00000021 1px, transparent 0);
background-size: 5px 5px;
background-position: -13px -3px;
}
/* 文本框右侧小编号样式 */
.cyberpunk:before {
content: "P-14";
display: block;
position: absolute;
bottom: -12px;
right: 25px;
padding: 2px 2px 0px 2px;
font-size: 0.6rem;
line-height: 0.6rem;
color: #000;
background-color: var(--yellow-color);
border-left: 2px solid var(--border-color);
}
.cyberpunk.inverse:before {
content: "T-71";
right: 90px;
bottom: 9px;
}
.cyberpunk.inverse:before, .cyberpunk:before {
background-color: #000;
color: var(--yellow-color);
}

炫酷的表单元素

2077 的表单也很有特色,输入框元素 input 和 textarea 同样可以使用 clip-path: polygon 实现不规则形状,单选框和多选框则可以利用伪元素:before、:after 进行装饰。

input.png

1
2
3
4
5
html复制代码<input class="cyberpunk" type="text" placeholder="input 输入框" />
<textarea class="cyberpunk" placeholder="textarea 文本框"></textarea>
<label class="cyberpunk"><input class="cyberpunk" name="test" type="radio" />单选框1</label>
<label class="cyberpunk"><input class="cyberpunk" name="test" type="radio" />单选框2</label><br />
<label class="cyberpunk"><input class="cyberpunk" type="checkbox" />多选框</label><br />

完整实现如下:

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
71
72
73
74
75
76
77
78
79
80
css复制代码input[type="text"].cyberpunk, textarea.cyberpunk {
width: calc(100% - 30px);
border: 30px solid #000;
border-left: 5px solid #000;
border-right: 5px solid #000;
border-bottom: 15px solid #000;
clip-path: polygon(0px 25px, 26px 0px, calc(60% - 25px) 0px, 60% 25px, 100% 25px, 100% calc(100% - 10px), calc(100% - 15px) calc(100% - 10px), calc(80% - 10px) calc(100% - 10px), calc(80% - 15px) calc(100% - 0px), 10px calc(100% - 0px), 0% calc(100% - 10px));
padding: 12px;
}
input[type="radio"].cyberpunk {
border-radius: 15%;
z-index: 100;
height: 14px;
width: 20px;
appearance: none;
outline: none;
background-color: #000;
cursor: pointer;
position: relative;
margin: 0px;
display: inline-block;
}
input[type="radio"].cyberpunk:after {
content: "";
display: block;
width: 8px;
height: 6px;
background-color: var(--yellow-color);
position: absolute;
top: 2px;
left: 2px;
transition: background 0.3s, left 0.3s;
}
input[type="radio"].cyberpunk:checked:after {
background-color: var(--border-color);
left: 10px;
}
input[type="checkbox"].cyberpunk {
border-radius: 15%;
z-index: 100;
height: 20px;
width: 20px;
appearance: none;
outline: none;
background-color: #000;
cursor: pointer;
position: relative;
margin: 0px;
margin-bottom: -3px;
display: inline-block;
}
input[type="checkbox"].cyberpunk:before {
content: "";
display: block;
width: 8px;
height: 8px;
border: 2px solid var(--yellow-color);
border-top: 2px solid transparent;
border-radius: 50%;
position: absolute;
top: 5px;
left: 4px;
}
input[type="checkbox"].cyberpunk:after {
content: "";
display: block;
width: 2px;
height: 7px;
background-color: var(--yellow-color);
position: absolute;
top: 3px;
left: 9px;
}
input[type="checkbox"].cyberpunk:checked:before {
border-color: var(--border-color);
border-top-color: transparent;
}
input[type="checkbox"].cyberpunk:checked:after {
background-color: var(--border-color);
}

标题和文本

赛博朋克风格网页在文本展示中,常常用到如下图所示的 输入光标闪烁 样式及屏幕 故障风格 的样式,我们可以统一为 h1 - h5 标题,hr 等元素增加下划线装饰和故障动画效果,下面来看看如何实现这样的文字效果的。

text.gif

1
2
html复制代码<h1 class="cyberpunk">H1 title</h1>
<h1 class="cyberpunk glitched">H1 title glitched</h1>
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
css复制代码h1.cyberpunk {
position: relative;
}
h1.cyberpunk:before {
content: "";
display: block;
position: absolute;
bottom: 0px;
left: 2px;
width: 100%;
height: 10px;
background-color: #000;
clip-path: polygon(0px 0px, 85px 0px, 90px 5px, 100% 5px, 100% 6px, 85px 6px, 80px 10px, 0px 10px);
}
h1.cyberpunk.glitched {
animation-name: glitched;
animation-duration: calc(.9s * 1.4);
animation-iteration-count: infinite;
animation-timing-function: linear;
}
@keyframes glitched {
0% { left: -4px; transform: skew(-20deg); }
11% { left: 2px; transform: skew(0deg); }
50% { transform: skew(0deg); }
51% { transform: skew(10deg); }
60% { transform: skew(0deg); }
100% { transform: skew(0deg); }
}
h1.cyberpunk.glitched:before {
animation-name: beforeglitched;
animation-duration: calc(.9s * 2);
animation-iteration-count: infinite;
animation-timing-function: linear;
}
@keyframes beforeglitched {
0% {
left: -4px;
transform: skew(-20deg);
clip-path: polygon(0px 0px, 85px 0px, 90px 5px, 100% 5px, 100% 6px, 85px 6px, 80px 10px, 0px 10px);
}
11% {
left: 2px;
transform: skew(0deg);
clip-path: polygon(0px 0px, 85px 0px, 90px 5px, 100% 5px, 100% 6px, 85px 6px, 80px 10px, 0px 10px);
}
50% {
transform: skew(0deg);
clip-path: polygon(0px 0px, 85px 0px, 90px 5px, 100% 5px, 100% 6px, 85px 6px, 80px 10px, 0px 10px);
}
51% {
transform: skew(0deg);
clip-path: polygon(0px 0px, 85px 0px, 90px 5px, 100% 5px, 40% 5px, calc(40% - 30px) 0px, calc(40% + 30px) 0px, calc(45% - 15px) 5px, 100% 5px, 100% 6px, calc(45% - 14px) 6px, calc(40% + 29px) 1px, calc(40% - 29px) 1px, calc(40% + 1px) 6px, 85px 6px, 80px 10px, 0px 10px);
}
60% {
transform: skew(0deg);
clip-path: polygon(0px 0px, 85px 0px, 90px 5px, 100% 5px, 100% 6px, 85px 6px, 80px 10px, 0px 10px);
}
100% {
transform: skew(0deg);
clip-path: polygon(0px 0px, 85px 0px, 90px 5px, 100% 5px, 100% 6px, 85px 6px, 80px 10px, 0px 10px);
}
}

金属质感

在 赛博朋克2077 的网页里,为了突显科技感,很多页面元素都具有金属质感,如模态弹窗的背景、文本展示块的边框等。这部分内容看看如何实现简单的金属材质背景。

metal_1.png

4个 button 元素,将被分别添加 金、银、铜、钛 的金属背景色效果。

1
2
3
4
html复制代码<button class="gold">gold 金</button>
<button class="silver">silver 银</button>
<button class="bronze">bronze 铜</button>
<button class="titanium">titanium 钛</button>

实现金属光泽效果,主要以下几个个css 属性:

  • box-shadow:增加阴影,突出立体质感。
  • background: radial-gradient:径向渐变,添加底部阴影。
  • background: linear-gradient:线性渐变,主色调背景。
  • background: conic-gradient:圆锥渐变,最终反光金属效果。

依次添加以上三种渐变如下图所示:

metal.png

示例完整实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
css复制代码button {
padding: 2px;
width: 250px;
height: 250px;
border-radius: 12px;
border: groove 1px transparent;
}
.gold {
box-shadow: inset 0 0 0 1px #eedc00, inset 0 1px 2px rgba(255, 255, 255, 0.5), inset 0 -1px 2px rgba(0, 0, 0, 0.5);
background: conic-gradient(#edc800, #e3b600, #f3cf00, #ffe800, #ffe900, #ffeb00, #ffe000, #ebc500, #e0b100, #f1cc00, #fcdc00, #d4c005fb, #fad900, #eec200, #e7b900, #f7d300, #ffe800, #ffe300, #f5d100, #e6b900, #e3b600, #f4d000, #ffe400, #ebc600, #e3b600, #f6d500, #ffe900, #ffe90a, #edc800) content-box, linear-gradient(#f6d600, #f6d600) padding-box, radial-gradient(rgba(120, 120, 120, 0.9), rgba(120, 120, 120, 0) 70%) 50% bottom/80% 0.46875em no-repeat border-box;
}
.silver {
box-shadow: inset 0 0 0 1px #c9c9c9, inset 0 1px 2px rgba(255, 255, 255, 0.5), inset 0 -1px 2px rgba(0, 0, 0, 0.5);
background: conic-gradient(#d7d7d7, #c3c3c3, #cccccc, #c6c6c6, #d3d3d3, #d8d8d8, #d5d5d5, #d8d8d8, #d3d3d3, #c5c5c5, #c0c0c0, #bfbfbf, #d0d0d0, #d9d9d9, #d1d1d1, #c5c5c5, #c8c8c8, #d7d7d7, #d5d5d5, #cdcdcd, #c4c4c4, #d9d9d9, #cecece, #c5c5c5, #c5c5c5, #cdcdcd, #d8d8d8, #d9d9d9, #d7d7d7) content-box, linear-gradient(#d4d4d4, #d4d4d4) padding-box, radial-gradient(rgba(120, 120, 120, 0.9), rgba(120, 120, 120, 0) 70%) 50% bottom/80% 0.46875em no-repeat border-box;
}
.bronze {
box-shadow: inset 0 0 0 1px #bc7e6b, inset 0 1px 2px rgba(255, 255, 255, 0.5), inset 0 -1px 2px rgba(0, 0, 0, 0.5);
background: conic-gradient(#d95641, #b14439, #b2453a, #d25645, #d56847, #d05441, #b85137, #b2453a, #c34f40, #df4647, #a94338, #c94943, #c85442, #a4413c, #d9543a, #d1564e, #ab4338, #bb4a3c, #dc5843, #b94839, #aa4237, #c24e42, #ce523f, #ab4338, #dd5944, #ca4d33, #ab4338, #cb503e, #d95641) content-box, linear-gradient(#ad3b36, #ad3b36) padding-box, radial-gradient(rgba(120, 120, 120, 0.9), rgba(120, 120, 120, 0) 70%) 50% bottom/80% 0.46875em no-repeat border-box;
}
.titanium {
box-shadow: inset 0 0 0 1px #c7aca0, inset 0 1px 2px rgba(255, 255, 255, 0.5), inset 0 -1px 2px rgba(0, 0, 0, 0.5);
background: conic-gradient(#e6c9bf, #d2b5aa, #cbaea3, #d4b5ab, #e5c3bd, #d9c0b4, #d9bcb1, #c5a399, #e3c6bc, #e7cac0, #dec0b5, #d3b6ab, #cfada1, #d4b6ac, #e2c6c0, #e2c6c0, #d2b1a6, #d2b1a6, #d1b4a9, #e1c4ba, #e5c9be, #dec1b6, #d3b6ab, #ceb0a6, #cfada3, #d2b5aa, #dabdb2, #e5c9be, #e6c9bf) content-box, linear-gradient(#e5c9be, #e5c9be) padding-box, radial-gradient(rgba(120, 120, 120, 0.9), rgba(120, 120, 120, 0) 70%) 50% bottom/80% 0.46875em no-repeat border-box;
}

结合 3 种渐变,还能创造出更多复杂好看的金属材质效果,完整代码可预览文章尾部的对应链接 🔗。

metal_2.png

其他

除了上述几个方面,还有哪些赛博朋克风格的元素是值得我们学习的呢?通过以下几点,也可以提升网页的科技艺术感和用户体验,你有没有更好的想法呢?😊

  • 使用扁平、像素化字体;
  • 科技感满满的页面加载动画、滚动动画、滚动条;
  • 中/日/英混杂的文案突出未来世界的文化融合;
  • 根据鼠标移动增加透视效果,可以看我另一篇文章 《如何在CSS中映射的鼠标位置,并实现通过鼠标移动控制页面元素效果》。
  • …

阅读更多

  • 故障图片效果示例完整版 codepen.io/dragonir/fu…
  • 复杂的金属效果 codepen.io/dragonir/fu…
  • 标题文本 codepen.io/dragonir/fu…
  • 赛博朋克404页面 codepen.io/ltrademark/…

本文转载自: 掘金

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

1…644645646…956

开发者博客

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