博客须知
-
本文来源于尚硅谷雷神的32.Web开发-【代码式】-WebMvcConfigurer使用_哔哩哔哩_bilibili以及他的语雀笔记2、SpringBoot3-Web开发 (yuque.com),我对视频内容进行了整合,由于记笔记时图片使用的是本地路径,所以导致博客的图片无法正常显示,如果有图片需求可以下载上方的pdf
-
本篇博客内容相较于spring boot 3核心特性就没那么重要,核心特性我之前发过相关博客,传送门:Spring Boot 3核心特性【优化排版】-CSDN博客
-
这个板块【对应视频p67-》p94的内容】的知识点感觉就是跟着过了一遍,了解为主
- 如果是想要入门springboot的话直接去看核心特性就够了
- 如果对这个板块感兴趣的内容建议自己去B站搜索对应课程,因为雷神这一块讲的确实太少了,完全就是过一轮
- 最后AOT实践没有跟着做,感觉这一块对于后端开发来说学习的性价比不高【所以我看到第88集后面就没有继续了】
环境准备
云服务器
-
我这边是阿里云免费试用三个月,我之前好像用过oss产品,然后现在密码忘了,可以按以下步骤重置密码
-
创建好服务器后在控制台处点击远程连接测试登录
-
此时就可以修改密码了
.
-
-
注意:每次使用完云服务器需要将其停机【下次启动时公网IP可能会改变,需要重新配置,但是磁盘信息是不会清空的】
-
下载windterm用于管理云服务器:https://github.com/kingToolbox/WindTerm/releases/download/2.5.0/WindTerm_2.5.0_Windows_Portable_x86_64.zip ,填写云服务器相关信息,然后登录用户即可
云服务器安装组件
Docker安装
还不会docker的同学,参考【云原生实战(10~25集)】快速入门
https://www.bilibili.com/video/BV13Q4y1C7hS?p=10
以上课程有需要就去了解,我是没有去学习过的
-
安装docker的命令
sudo yum install -y yum-utils sudo yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl enable docker --now #开机时启动docker,并且现在也启动
- 一路输入y就行了
-
测试安装是否成功
docker ps
.
- 如果没有显示上图的镜像信息,可能是cv的时候控制台漏了
sudo systemctl enable docker --now
,导致docker没有启动,所以重新输入一遍这条命令就行了
- 如果没有显示上图的镜像信息,可能是cv的时候控制台漏了
-
测试批量安装软件功能是否生效
docker compose --help
.
批量安装所有软件
创建 /prod 文件夹,用于存放配置文件
.
prometheus.yml
- 在安装prometheus组件前需要提前准备好它的配置文件
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'redis'
static_configs:
- targets: ['redis:6379']
- job_name: 'kafka'
static_configs:
- targets: ['kafka:9092']
docker-compose.yml
- 编写批量安装的yaml文件,交给gpt生成
.
version: '3.9'
services:
redis:
image: redis:latest
container_name: redis
restart: always
ports:
- "6379:6379"
networks:
- backend
zookeeper:
image: bitnami/zookeeper:latest
container_name: zookeeper
restart: always
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
networks:
- backend
kafka:
image: bitnami/kafka:3.4.0
container_name: kafka
restart: always
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
ALLOW_PLAINTEXT_LISTENER: yes
KAFKA_CFG_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
networks:
- backend
kafka-ui:
image: provectuslabs/kafka-ui:latest
container_name: kafka-ui
restart: always
depends_on:
- kafka
ports:
- "8080:8080"
environment:
KAFKA_CLUSTERS_0_NAME: dev
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
networks:
- backend
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: always
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
networks:
- backend
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: always
depends_on:
- prometheus
ports:
- "3000:3000"
networks:
- backend
networks:
backend:
name: backend
批量安装
- 命令
docker compose -f docker-compose.yml up -d
- 指定批量安装的配置文件
- up -d表示以后台的方式启动
- 测试各个容器的启动情况
-
验证kafka【其它组件同理】是否启动成功:通过公网ip+kafka-ui暴露的端口来访问成功kafka的ui界面
- 如果访问不了,可能是因为没有放行端口导致的,解决步骤如下
- 控制台点击服务器的示例
- 找到安全组的标签,然后管理规则
- 手动添加规则【这里源填的是我个人的IP地址】
- 访问成功
.
- 如果访问不了,可能是因为没有放行端口导致的,解决步骤如下
-
访问redis可以下载https://redis.com/redis-enterprise/redis-insight/#insight-form,不过我之前都是用redisManager的,但是这个软件看上去整体更高级【收费】
Redis整合
环境搭建
- 勾选redis的场景
.
- 配置redis的连接主机【默认端口号为6379】
spring.data.redis.host=你的redis主机所在ip地址【即云服务器的公网IP】
- 测试
@RestController
public class RedisTestController {
@Autowired
StringRedisTemplate redisTemplate;
@GetMapping("/count")
public String count(){
Long hello = redisTemplate.opsForValue().increment("hello");
return "访问次数:"+hello;
}
}
- 注意
- redis相关内容可以去看周阳老师的尚硅谷Redis零基础到进阶,最强redis7教程,阳哥亲自带练(附redis面试题)_哔哩哔哩_bilibili
- 我也整理过对应知识点,传送门:Redis核心知识点总结-CSDN博客
自动配置原理
-
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
中导入了RedisAutoConfiguration
、RedisReactiveAutoConfiguration
和RedisRepositoriesAutoConfiguration
- 所有属性绑定在
RedisProperties
中 RedisReactiveAutoConfiguration
属于响应式编程,不用管RedisRepositoriesAutoConfiguration
属于 JPA 操作,也不用管
- 所有属性绑定在
-
RedisAutoConfiguration 配置了以下组件
-
LettuceConnectionConfiguration
: 给容器中注入了连接工厂LettuceConnectionFactory
,和操作redis的客户端DefaultClientResources
-
RedisTemplate<Object, Object>
: 给redis中存储任意对象,会使用jdk默认序列化方式,存储的对象必须实现序列化接口 -
StringRedisTemplate
: 给redis中存储字符串【key-value都是字符串】,如果要存对象,需要开发人员自己进行序列化
-
定制化
序列化机制
- 配置Json的序列化机制
@Configuration
public class AppRedisConfiguration {
/**
* 允许Object类型的key-value,都可以被转为json进行存储
* @param redisConnectionFactory 自动配置好了连接工厂
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//把对象转为json字符串的序列化工具
template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
redis客户端
-
RedisTemplate、StringRedisTemplate: 操作redis的工具类
-
要从redis的连接工厂获取链接才能操作redis
-
常见的操作Redis的客户端
-
- Lettuce: 默认客户端
- Jedis:可以使用以下方式进行切换
-
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 切换 jedis 作为操作redis的底层客户端-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
配置参考
spring.data.redis.host=8.130.74.183
spring.data.redis.port=6379
#spring.data.redis.client-type=lettuce
#设置lettuce的底层参数
#spring.data.redis.lettuce.pool.enabled=true
#spring.data.redis.lettuce.pool.max-active=8
#设置客户端类型,并且配置连接池信息
spring.data.redis.client-type=jedis
spring.data.redis.jedis.pool.enabled=true
spring.data.redis.jedis.pool.max-active=8
接口文档【了解】
OpenAPI 3 与 Swagger
- Swagger 可以快速生成实时接口文档,方便前后开发人员进行协调沟通,遵循 OpenAPI 规范,文档:https://springdoc.org/v2/
- OpenAPI 3支持原生方式和响应式编程的Web开发
.
整合
- 导入场景
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
- 配置
# /api-docs endpoint custom path 默认 /v3/api-docs
springdoc.api-docs.path=/api-docs
# swagger 相关配置在 springdoc.swagger-ui
# swagger-ui custom path
springdoc.swagger-ui.path=/swagger-ui.html
springdoc.show-actuator=true
使用
常用注解
注解 | 标注位置 | 作用 |
---|---|---|
@Tag | controller 类 | 标识 controller 作用 |
@Parameter | 参数 | 标识参数作用 |
@Parameters | 参数 | 参数多重说明 |
@Schema | model 层的 JavaBean | 描述模型作用及每个属性 【可以标识在类或者类中的字段上】 |
@Operation | 方法 | 描述方法作用 |
@ApiResponse | 方法 | 描述响应状态码等 |
Docket配置
- Docket相当于分组方式的API
- 如果有多个Docket,配置如下【配置某个路径下属于哪个分组】
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("springshop-public")
.pathsToMatch("/public/**")
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("springshop-admin")
.pathsToMatch("/admin/**")
.addMethodFilter(method -> method.isAnnotationPresent(Admin.class))
.build();
}
- 如果只有一个Docket,可以配置如下
springdoc.packagesToScan=package1, package2
springdoc.pathsToMatch=/v1, /api/balance/**
OpenAPI配置
@Bean
public OpenAPI springShopOpenAPI() {
return new OpenAPI()
.info(new Info().title("SpringShop API")
.description("Spring shop sample application")
.version("v0.0.1")
.license(new License().name("Apache 2.0").url("http://springdoc.org")))
.externalDocs(new ExternalDocumentation()
.description("SpringShop Wiki Documentation")
.url("https://springshop.wiki.github.org/docs"));
}
Springfox 迁移
注解变化
原注解 | 现注解 | 作用 |
---|---|---|
@Api | @Tag | 描述Controller |
@ApiIgnore | @Parameter(hidden = true) @Operation(hidden = true) @Hidden | 描述忽略操作 |
@ApiImplicitParam | @Parameter | 描述参数 |
@ApiImplicitParams | @Parameters | 描述参数 |
@ApiModel | @Schema | 描述对象 |
@ApiModelProperty(hidden = true) | @Schema(accessMode = READ_ONLY) | 描述对象属性 |
@ApiModelProperty | @Schema | 描述对象属性 |
@ApiOperation(value = “foo”, notes = “bar”) | @Operation(summary = “foo”, description = “bar”) | 描述方法 |
@ApiParam | @Parameter | 描述参数 |
@ApiResponse(code = 404, message = “foo”) | @ApiResponse(responseCode = “404”, description = “foo”) | 描述响应 |
Docket配置
以前写法
@Bean
public Docket publicApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("org.github.springshop.web.public"))
.paths(PathSelectors.regex("/public.*"))
.build()
.groupName("springshop-public")
.apiInfo(apiInfo());
}
@Bean
public Docket adminApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("org.github.springshop.web.admin"))
.paths(PathSelectors.regex("/admin.*"))
.apis(RequestHandlerSelectors.withMethodAnnotation(Admin.class))
.build()
.groupName("springshop-admin")
.apiInfo(apiInfo());
}
新写法
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("springshop-public")
.pathsToMatch("/public/**")
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("springshop-admin")
.pathsToMatch("/admin/**")
.addOpenApiMethodFilter(method -> method.isAnnotationPresent(Admin.class))
.build();
}
添加OpenAPI组件
@Bean
public OpenAPI springShopOpenAPI() {
return new OpenAPI()
.info(new Info().title("SpringShop API")
.description("Spring shop sample application")
.version("v0.0.1")
.license(new License().name("Apache 2.0").url("http://springdoc.org")))
.externalDocs(new ExternalDocumentation()
.description("SpringShop Wiki Documentation")
.url("https://springshop.wiki.github.org/docs"));
}
远程调用
RPC(Remote Procedure Call):远程过程调用
.
本地调用和远程调用的区别
-
本地过程调用: 不同方法都在同一个JVM运行
-
远程过程调用
- 会产生两种角色:服务提供者、服务消费者【调用提高者提供的功能】
- 通过连接对方服务器进行请求\响应交互,来实现调用效果
-
!!!面试题:API/SDK的区别是什么?
- api:接口(Application Programming Interface),可以远程提供功能
- sdk:工具包(Software Development Kit),导入jar包,直接调用功能即可,一般局限于本地调用
远程调用场景
-
开发过程中,经常需要调用别人写的功能
- 如果是内部微服务,可以通过依赖cloud、注册中心、openfeign等进行调用
- 如果是外部暴露的,可以发送 http 请求、或遵循外部协议进行调用
-
SpringBoot 整合提供了很多方式进行远程调用
-
轻量级客户端方式
-
RestTemplate: 普通开发
-
WebClient: 响应式编程开发
-
Http Interface: 声明式编程
-
-
Spring Cloud分布式解决方案方式
- Spring Cloud OpenFeign
-
第三方框架
- Dubbo
- gRPC
-
WebClient
非阻塞、响应式HTTP客户端
环境搭建
- WebClient属于响应式编程的web开发,所以要导入响应式编程相关场景
.
- 阿里云试用天气查询的api,postman测试接口调用
创建与配置
- 发请求需要关注请求方式、请求路径、请求参数、请求头、请求体
- 创建WebClient
- WebClient.create()
- WebClient.create(String baseUrl)
- 还可以使用WebClient.builder() 配置更多参数项【了解】
- uriBuilderFactory:自定义UriBuilderFactory ,定义 baseurl
- defaultUriVariables:默认 uri 变量
- defaultHeader:每个请求默认头
- defaultCookie:每个请求默认 cookie
- defaultRequest:Consumer 自定义每个请求
- filter:过滤 client 发送的每个请求
- exchangeStrategies:HTTP 消息 reader/writer 自定义
- clientConnector:HTTP client 库设置
- 远程调用阿里云的api【我这一块用的是万维易源的天气查询】
public Mono<String> weather(String city){
//1.创建webClient【获取到一个调用天气查询功能的客户端】
WebClient webClient = WebClient.create();
//2.准备查询参数
Map<String,String> params=new HashMap<>();
params.put("area",city);
//3.定义请求的相关属性
return webClient.get()
.uri("https://ali-weather.showapi.com/area-to-weather-date?area={area}",params)
.accept(MediaType.APPLICATION_JSON)//接收json数据
.header("Authorization", "APPCODE 你自己的appcode")//这一块具体得看你使用的产品
.retrieve()
.bodyToMono(String.class);//把响应体中的数据转成字符串类型
}
- 测试结果
.
HTTP Interface
基础功能
- Spring允许通过定义接口的方式,给任意位置发送 http 请求,实现远程调用,可以用来简化 HTTP 远程访问【需要webflux场景】
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
- 定义接口【其中@RequestParam声明了发送出去的参数名】
public interface WeatherInterface {
@GetExchange(url = "/area-to-weather-date",accept = "application/json")
String getWeather(@RequestParam("area")String city//将传入的city参数以area的参数名发送出去
,@RequestHeader("Authorization") String header);
}
- 注入接口组件
@Bean
WeatherInterface weatherInterface(){
//1.创建webClient
WebClient webClient = WebClient.builder().
baseUrl("https://ali-weather.showapi.com")
.codecs(clientCodecConfigurer -> {
clientCodecConfigurer
.defaultCodecs()
.maxInMemorySize(256*1024*1024);
}).build();
//2.创建工厂
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builder(WebClientAdapter.forClient(webClient)).build();
//3、获取代理对象
return factory.createClient(WeatherInterface.class);
}
- 自动注入该接口,然后调用方法
@Autowired
WeatherInterface weatherInterface;
@GetMapping("/weather")
public Mono<String> weather(@RequestParam("city") String city){
return weatherInterface.getWeather(city,"APPCODE 6ca80ab7736c4666be72d368dcd8879c");
}
深层抽取
- 将创建代理对象的工厂类单独抽取出来,之后就可以用这个工厂创建不同的代理对象【工厂组件就不指定具体的路径了】
//注入创建代理对象的工厂
@Bean
HttpServiceProxyFactory factory(){
//1.创建webClient
WebClient webClient = WebClient.builder()
.codecs(clientCodecConfigurer -> {
clientCodecConfigurer
.defaultCodecs()
.maxInMemorySize(256*1024*1024);
}).build();
//2.创建工厂
return HttpServiceProxyFactory
.builder(WebClientAdapter.forClient(webClient)).build();
}
//通过工厂创建代理对象
@Bean
WeatherInterface weatherInterface(HttpServiceProxyFactory factory){
return factory.createClient(WeatherInterface.class);
}
-
新增物流查询功能【云市场大部分请求参数和请求头都是大差不差的】
- postman测试api
.
- 创建api调用接口
public interface ExpressApi { @GetExchange(url = "https://wuliu.market.alicloudapi.com/kdi",accept = "application/json") Mono<String> getExpress(@RequestParam("no") String number); }
- 使用工厂创建接口代理
@Bean ExpressApi expressApi(HttpServiceProxyFactory factory){ return factory.createClient(ExpressApi.class); }
- 测试
@GetMapping("/express") public Mono<String> express(@RequestParam("no") String no){ return expressApi.getExpress(no,"APPCODE 你自己的appcode"); }
.
- 阿里云市场的用户认证头都是统一的,所以可以在工厂类配置默认的请求头
WebClient webClient = WebClient.builder() .defaultHeader("Authorization","你自己的appcode") .codecs(clientCodecConfigurer -> { clientCodecConfigurer .defaultCodecs() .maxInMemorySize(256*1024*1024); }).build();
- 也可以抽取成可以配置的属性
消息服务
消息队列-场景
异步
.
解耦
.
削峰
.
缓冲
- 其实缓存和削峰差不多呀
.
消息队列-Kafka
消息模式
.
Kafka工作原理
- 副本就是本分区的数据在其它分区都有备份,本分区作为存储该数据的主分区,其它机器也是同理
.
SpringBoot整合
- 参照:https://docs.spring.io/spring-kafka/docs/current/reference/html/#preface
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
- 配置kafka集群的所有节点:
spring.kafka.bootstrap-servers=云服务器的公网ip:9092
- 如果有多个服务器可以用
,
分割,或者使用数组的形式
- 如果有多个服务器可以用
- KafkaAutoConfiguration提供如下功能
- KafkaProperties:kafka的所有配置,以 spring.kafka开始
- bootstrapServers:kafka集群的所有服务器地址
- properties:参数设置
- consumer:消费者1
- producer:生产者
- @EnabLeKafka:开启Kafka的注解驱动功能
- KafkaTemplate:收发消息
- KafkaAdmin:维护主题等
- KafkaProperties:kafka的所有配置,以 spring.kafka开始
- 代码方式创建主题
@EnableKafka
@Configuration
public class KafkaConfiguration {
@Bean
public NewTopic topic() {
return TopicBuilder.name("thing")
.partitions(1)
.compact()
.build();
}
}
发送消息
发送string类型的消息
@SpringBootTest
class Boot07KafkaApplicationTests {
@Autowired
KafkaTemplate kafkaTemplate;
@Test
void contextLoads() throws ExecutionException, InterruptedException {
StopWatch watch = new StopWatch();
watch.start();
CompletableFuture[] futures = new CompletableFuture[10000];
for (int i = 0; i < 10000; i++) {
CompletableFuture send = kafkaTemplate.send("order", "order.create."+i, "订单创建了:"+i);
futures[i]=send;
}
CompletableFuture.allOf(futures).join();//阻塞等待所有的异步任务完成
watch.stop();
System.out.println("总耗时:"+watch.getTotalTimeMillis());
}
}
-
如果抛出主机不存在的信息
-
先去kafka-ui找到自己的kafka服务器的主机名
-
然后修改hosts文件中的域名映射
-
-
发送成功
发送自定义对象
-
kafka默认使用的是string类型的序列化机制,所以发送对象类型配置自定义序列化规则
spring.kafka.producer.value-serializer
-
先ctrl点击配置文件的value-serializer,查看默认的序列化器
private Class<?> valueSerializer = StringSerializer.class;
-
然后进入StringSerializer,发现其实现了序列化接口
public class StringSerializer implements Serializer<String>
-
进入序列化接口,选择需要用到的序列化器【ctrl+h可以查看接口的实现类】
.
-
最后配置需要的序列化规则
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
-
-
测试发送对象
kafkaTemplate.send("news", "person",new Person(18,"里奥"));
.
监听消息
默认监听
- 默认是监听项目启动之后的新的消息
- 必须指定分组id,统一组内消息读取是竞争关系,不同组就是独立的
@Component
public class MyListener {
@KafkaListener(topics ="order",groupId = "testKafka")
public void newsListener(ConsumerRecord record){
//ConsumerRecord用于获取消息的详细消息【属于哪个主题等等】
Object key = record.key();
Object value = record.value();
System.out.println(key+"========"+value);
}
}
监听所有消息
- 如果要监听之前完整的消息需要设置偏移量
@KafkaListener(groupId = "test2",topicPartitions = {
@TopicPartition(topic = "order",partitionOffsets = {
@PartitionOffset(partition = "0",initialOffset = "0")
})
})
public void orderListener(ConsumerRecord record){
Object key = record.key();
Object value = record.value();
System.out.println(key+"========"+value);
}
小结
-
@EnableKafka+@KafkaListener
用于监听消息- 监听者需要指定group-id
- 收消息使用
@KafkaListener+ConsumerRecord
- spring.kafka绑定了kafka相关配置属性
-
核心概念
- 分区:分散存储,大量数据分散到不同节点
- 副本:备份机制,每个小分区都有备份
- 主题:消息是发送给某个主题的
Web安全
安全架构
关注的方面
-
认证:Authentication
- who are you?
- 场景:登录系统,用户系统
-
授权:Authorization
-
what are you allowed to do?
-
场景:权限管理,用户授权
-
-
攻击防护
- XSS(Cross-site scripting)、CSRF(Cross-site request forgery)
- CORS(Cross-Origin Resource Sharing)、SQL注入
拓展:权限模型【了解】
RBAC(Role Based Access Controll)
-
用户(t_user)
- id,username,password,xxx
- 1,zhangsan
- 2,lisi
-
角色(t_role)
- id,role_name
- admin
- hr
- common_user
-
用户_角色(t_user_role)【N对N关系需要中间表】
- zhangsan,admin
- zhangsan,common_user
- lisi,hr
- lisi,common_user
-
角色_权限(t_role_perm)
- admin,文件r
- admin,文件w
- admin,文件执行
- admin,订单query、create、xxx
- hr, 文件r
-
权限(t_permission)
- id,perm_id
- 文件 r,w,x
- 订单 query,create,xxx
ACL(Access Controll List)
直接用户和权限挂钩
-
用户(t_user)
- zhangsan
- lisi
-
权限(t_permission)
- id,perm_id
- 文件 r,w,x
- 订单 query,create,xxx
-
用户_权限(t_user_perm)
- zhangsan,文件 r
- zhangsan,文件 x
- zhangsan,订单 query
@Secured("文件 r")
public void readFile(){
//读文件
}
Spring Security 原理
过滤器链架构
- Spring Security利用 FilterChainProxy 封装一系列拦截器链,实现各种安全拦截功能
- Servlet三大组件:Servlet、Filter、Listener
.
FilterChainProxy
- 可以对不同的路径定义不同的过滤规则
.
SecurityFilterChain
- 所有的权限过滤器被spring security放在FilterChainProxy里面,这个代理里面每一个都是Security Filter
- 所有的Security Filter合起来就叫SecurityFilterChain
.
使用
HttpSecurity
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/match1/**")
.authorizeRequests()
.antMatchers("/match1/user").hasRole("USER")
.antMatchers("/match1/spam").hasRole("SPAM")
.anyRequest().isAuthenticated();
}
}
核心
-
WebSecurityConfigurerAdapter
-
@EnableGlobalMethodSecurity: 开启全局方法安全配置
-
- @Secured
- @PreAuthorize
- @PostAuthorize
-
UserDetailService: 去数据库查询用户详细信息的service(用户基本信息、用户角色、用户权限)
-
自动配置类:SecurityAutoConfiguration、SpringBootW/ebSecurityConfiguration、 SecurityFilterAutoConfiguration
-
security的所有配置在 SecurityProperties:以spring.security开头
-
默认SecurityFitterChain组件
-
所有请求都需要认证 (登录)
-
开启表单登录:spring security提供一个默认登录页,未经登录的所有请求都需要登录
.
-
httpbasic方式登录
-
-
@EnableWebSecurity开启功能
- WebSecurityConfiguration生效:web安全配置
- HttpSecurityConfiquration生效:http安全规则
- @EnableGlobalAuthentication生效:全局认证生效【AuthenticationConfiquration:认证配置】
-
实战
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.配置安全规则
@Configuration
public class SecurityConfiguration {
@Bean
@Order(2147483642)
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(registry->{
registry.requestMatchers("/").permitAll()//首页所有人都可以访问
.anyRequest().authenticated();//剩下的所有请求都需要认证(登录)
});
http.formLogin();//开启默认的表单登录功能
return http.build();
}
}
- 配置自定义的登录页
//开启默认的表单登录功能
http.formLogin(formLogin -> {
formLogin.loginPage("/login").permitAll();//自定义登录页的请求位置,并且允许所有人访问
});
.
3.配置登录信息
- 使用UserDetailsService可以自定义登录信息
- 自定义登录信息需要配置密码加密器
@Bean
UserDetailsService userDetailsService(){
UserDetails zhangSan = User.withUsername("zhangsan")
.password(passwordEncoder().encode("123456"))//密文存储
.roles("hr", "admin")
.build();
//模拟在内存中保存用户信息
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(zhangSan);
return manager;
}
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
4.开启方法级别的权限控制
- @EnableMethodSecurity:开启方法级别的精确权限控制
- 配合权限控制注解使用,@PreAuthorize提前检验权限
@PreAuthorize("hasAuthority('hello')")
@GetMapping("/hello")
@ResponseBody
public String word(){
return "Hello";
}
.
可观测性
基础知识
- 可观测性 Observability:对线上应用进行观测、监控、预警
- 健康状况【组件状态、存活状态】Health
- 运行指标【cpu、内存、垃圾回收、吞吐量、响应成功率…】Metrics
- 链路追踪
SpringBoot Actuator
实战
- 导入场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
-
暴露指标
#暴露所有端点信息 management.endpoints.web.exposure.include=*
-
访问数据
- 访问 http://localhost:8080/actuator:展示出所有可以用的监控端点
- http://localhost:8080/actuator/beans
- http://localhost:8080/actuator/configprops
- http://localhost:8080/actuator/metrics:打印所有指标
- http://localhost:8080/actuator/metrics/jvm.gc.pause
- http://localhost:8080/actuator/endpointName/detailPath
.
Endpoint
- 重要端点
threaddump
:下载线程信息heapdump
:下载堆内存信息metrics
:监控指标
- 常用端点一览表
ID | 描述 |
---|---|
auditevents | 暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件 。 |
beans | 显示应用程序中所有Spring Bean的完整列表。 |
caches | 暴露可用的缓存。 |
conditions | 显示自动配置的所有条件信息,包括匹配或不匹配的原因。 |
configprops | 显示所有@ConfigurationProperties 。 |
env | 暴露Spring的属性ConfigurableEnvironment |
flyway | 显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway 组件。 |
health | 显示应用程序运行状况信息。 |
httptrace | 显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository 组件。 |
info | 显示应用程序信息。 |
integrationgraph | 显示Spring integrationgraph 。需要依赖spring-integration-core 。 |
loggers | 显示和修改应用程序中日志的配置。 |
liquibase | 显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase 组件。 |
metrics | 显示当前应用程序的“指标”信息。 |
mappings | 显示所有@RequestMapping 路径列表。 |
scheduledtasks | 显示应用程序中的计划任务。 |
sessions | 允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。 |
shutdown | 使应用程序正常关闭。默认禁用。 |
startup | 显示由ApplicationStartup 收集的启动步骤数据。需要使用SpringApplication 进行配置BufferingApplicationStartup 。 |
threaddump | 执行线程转储。 |
heapdump | 返回hprof 堆转储文件。 |
jolokia | 通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core 。 |
logfile | 返回日志文件的内容(如果已设置logging.file.name 或logging.file.path 属性)。支持使用HTTPRange 标头来检索部分日志文件的内容。 |
prometheus | 以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus 。 |
定制端点
前置知识
- 健康监控:返回存活、死亡
- 指标监控:次数、率
- 如下配置可以展示详细的健康信息
management.endpoint.health.enabled=true
management.endpoint.health.show-details=always
HealthEndpoint
- 实现HealthEndpoint接口来定制组件的健康状态对象(Health)
- 也可以通过继承
AbstractHealthIndicator
并重写doHealthCheck()
就可以定制健康检查的逻辑
@Component
public class MyComponentIndicator extends AbstractHealthIndicator {
@Autowired
MyComponent component;
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
//自定义检查方法
if (component.check() == 1) {
builder.up()//存活
.withDetail("code", "200")
.withDetail("msg", "活的很健康")
.build();
} else {
builder.down()
.withDetail("msg", "死的很痛苦")
.build();//下线
}
}
}
@Component
public class MyComponent {
public int check(){
return 1;
}
}
- 测试结果
.
MetricsEndpoint
- 自定义指标可以注入MeterRegistry来实现定制化
@Component
public class MyComponent {
Counter counter=null;//定义计数器
/**
* 如果只有一个有参构造器,那定义的形参就会从容器中拿
* @param meterRegistry 注入该组件来保存和统计所有指标
*/
public MyComponent(MeterRegistry meterRegistry) {
//得到一个名为helloCounter的计数器
counter=meterRegistry.counter("helloCounter");
}
public int check(){
return 1;
}
public void hello(){
System.out.println("hello");
counter.increment();//计数器加一
}
}
.
整合Prometheus + Grafana
环境搭建
- 安装【之前在云服务器中批量创建过了】
#安装prometheus:时序数据库
docker run -p 9090:9090 -d \
-v pc:/etc/prometheus \
prom/prometheus
#安装grafana;默认账号密码 admin:admin
docker run -d --name=grafana -p 3000:3000 grafana/grafana
- 导入依赖,产生prometheus需要的时序数据
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
- 导入prometheus会多一个监控端点
项目上线
-
打包当前项目【clean+package】
.
-
部署到linux服务器
-
需要先安装上传工具
yum install lrzsz
-
然后运行
rz
命令就可以选择本地上传的文件
.
-
-
安装openjdk【以下命令来源于gpt】
# 下载openjdk wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz # 创建文件夹,之后将openjdk安装到该位置 mkdir -p /opt/java tar -xzf jdk-17_linux-x64_bin.tar.gz -C /opt/java/ sudo vi /etc/profile #======在配置文件结尾追加以下内容======== export JAVA_HOME=/opt/java/安装的jdk名称 export PATH=$PATH:$JAVA_HOME/bin #==================================== #让环境变量生效 source /etc/profile
-
java -version
检查环境是否生效.
-
项目启动
-
后台启动java应用,就不会阻塞linux服务器
nohup java -jar boot3-actuator-0.0.1-SNAPSHOT.jar > output.log 2>&1 &
-
在同一台机器可以通过访问私网地址的actuator/prometheus获取指标信息
curl 私网ip:9999/actuator/prometheus
.
配置Prometheus拉取数据
- 修改Prometheus.yaml,拉取某个应用的指标数据
- job_name: '任务名【随便取】'
metrics_path: '/actuator/prometheus' #指定抓取的路径
static_configs:
- targets: ['自己的私网地址【公网也可以,但是会慢一点】:9999']
labels:
nodename: 'app-demo'
- 重启Prometheus,刷新配置
.
- 拉取数据【在status的targets中】
.
配置Grafana监控面板
-
添加数据源(Prometheus)
- 在Grafana的Connections下的data sources目录中配置Prometheus的访问地址
- 因为都是在docker安装的,所以写容器的名称就能访问到Prometheus
-
添加面板:可以去dashboard市场找一个自己喜欢的面板,也可以自己开发面板Dashboards | Grafana Labs
-
复制面板id
-
在Grafana主页导入该面板
-
好像12900的面板即支持springboot应用又支持Prometheus【如果无法导入数据源或者监控面板没信息就选择12900】
.
-
.
AOT
AOT与JIT
基础概念
- AOT(Ahead-of-Time提前编译)::程序执行前,全部被编译成机器码
- JIT(Just in Time即时编译): 程序边编译,边运行;
- 编译: 源代码(.c、.cpp、.go、.java) =编译=》 机器码
- 语言
- 编译型语言:编译器
- 解释型语言:解释器
Complier与Interpreter
.
对比项 | 编译器 | 解释器 |
---|---|---|
机器执行速度 | 快,因为源代码只需被转换一次 | 慢,因为每行代码都需要被解释执行 |
开发效率 | 慢,因为需要耗费大量时间编译 | 快,无需花费时间生成目标代码,更快的开发和测试 |
调试 | 难以调试编译器生成的目标代码 | 容易调试源代码,因为解释器一行一行地执行 |
可移植性(跨平台) | 不同平台需要重新编译目标平台代码 | 同一份源码可以跨平台执行,因为每个平台会开发对应的解释器 |
学习难度 | 相对较高,需要了解源代码、编译器以及目标机器的知识 | 相对较低,无需了解机器的细节 |
错误检查 | 编译器可以在编译代码时检查错误 | 解释器只能在执行代码时检查错误 |
运行时增强 | 无 | 可以动态增强 |
AOT与JIT对比
JIT | AOT | |
---|---|---|
优点 | 1.具备实时调整能力 2.生成最优机器指令 3.根据代码运行情况优化内存占用 | 1.速度快,优化了运行时编译时间和内存消耗 2.程序初期就能达最高性能 3.加快程序启动速度 |
缺点 | 1.运行期边编译速度慢 2.初始编译不能达到最高性能 | 1.程序第一次编译占用时间长 2.牺牲高级语言一些特性 |
- 在 OpenJDK 的官方 Wiki 上,介绍了HotSpot 虚拟机一个相对比较全面的、即时编译器(JIT)中采用的优化技术列表【了解】
- 可使用:-XX:+PrintCompilation 打印JIT编译信息
JVM
架构
- JVM:既有解释器,又有编译器(JIT:即时编译)
.
Java的执行过程
- 建议阅读
- 美团技术:https://tech.meituan.com/2020/10/22/java-jit-practice-in-meituan.html
- openjdk官网:https://wiki.openjdk.org/display/HotSpot/Compiler
- jvm里面会判断一段代码是解释执行还是编译执行
- 如果是编译执行,就编译成机器码并且放到缓存里面,下次用到的时候,直接取
- 如果是解释执行,就直接解释执行,并且统计解释执行的次数,当次数达到某个阈值的时候,就触发编译,将机器码放在缓存中
- 热点代码:调用次数非常多的代码
JVM编译器
- JVM中集成了两种编译器,Client Compiler 和 Server Compiler
- Client Compiler注重启动速度和局部的优化
- Server Compiler更加关注全局优化,性能更好,但由于会进行更多的全局分析,所以启动速度会慢
- Client Compiler
- HotSpot VM带有一个Client Compiler C1编译器
- 这种编译器启动速度快,但是性能比较Server Compiler来说会差一些
- 编译后的机器码执行效率没有C2的高
- Server Compiler
- Hotspot虚拟机中使用的Server Compiler有两种:C2 和 Graal
- 在Hotspot VM中,默认的Server Compiler是C2编译器
分层编译
基础概念
- Java 7开始引入了分层编译(Tiered Compiler)的概念,它结合了C1和C2的优势,追求启动速度和峰值性能的一个平衡
- 分层编译将JVM的执行状态分为了五个层次
- 解释执行
- 执行不带profiling的C1代码
- 执行仅带方法调用次数以及循环回边执行次数profiling的C1代码
- 执行带所有profiling的C1代码
- 执行C2代码
- profiling就是收集能够反映程序执行状态的数据。其中最基本的统计数据就是方法的调用次数,以及循环回边的执行次数
不同编译路径分析
.
-
图中第①条路径,代表编译的一般情况,热点方法从解释执行到被3层的C1编译,最后被4层的C2编译
-
如果方法比较小(比如Java服务中常见的getter/setter方法),3层的profiling没有收集到有价值的数据,JVM就会断定该方法对于C1代码和C2代码的执行效率相同,就会执行图中第②条路径。在这种情况下,JVM会在3层编译之后,放弃进入C2编译,直接选择用1层的C1编译运行
-
在C1忙碌的情况下,执行图中第③条路径,在解释执行过程中对程序进行profiling ,根据信息直接由第4层的C2编译
-
前文提到C1中的执行效率是1层>2层>3层,第3层一般要比第2层慢35%以上,所以在C2忙碌的情况下,执行图中第④条路径。这时方法会被2层的C1编译,然后再被3层的C1编译,以减少方法在3层的执行时间
-
如果编译器做了一些比较激进的优化,比如分支预测,在实际运行时发现预测出错,这时就会进行反优化,重新进入解释执行,图中第⑤条执行路径代表的就是反优化
-
总的来说,C1的编译速度更快,C2的编译质量更高,分层编译的不同编译路径,也就是JVM根据当前服务的运行情况来寻找当前服务的最佳平衡点的一个过程。从JDK 8开始,JVM默认开启分层编译
云原生:Cloud Native
-
原生应用:任意编程语言写的应用
-
存在的问题
- java应用的jar包是解释执行的过程,只有热点代码才编译成机器码,初始启动速度慢,初始处理请求数量少
- 大型云平台,要求每一种应用都必须秒级启动。每个应用都要求效率高
-
希望的效果
-
java应用也能提前被编译成机器码,随时急速启动,一启动就急速运行,最高性能
-
编译成机器码的好处
-
另外的服务器还需要安装Java环境
-
编译成机器码可以在当前平台直接运行
-
-
-
原生镜像:native-image(机器码、本地镜像),把应用打包成能适配本机平台的可执行文件(机器码、本地镜像)
GraalVM
基础概念
- 官方网址
- GraalVM是一个高性能的JDK,旨在加速用Java和其他JVM语言编写的应用程序的执行,同时还提供JavaScript、Python和许多其他流行语言的运行时。
- GraalVM提供了两种运行Java应用程序的方式
- 在HotSpot JVM上使用Graal即时编译器(JIT)
- 作为预先编译(AOT)的本机可执行文件运行(本地镜像)
- GraalVM的多语言能力使得在单个应用程序中混合多种编程语言成为可能,同时消除了外部语言调用的成本
架构
.
安装
- 这一块去看雷神的笔记7、AOT (yuque.com),要安装杂七杂八的,感觉对java学习没什么太大必要
.