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

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


  • 首页

  • 归档

  • 搜索

猜数字大小 LeetCode刷题笔记 二、思路分析 三、

发表于 2021-11-07

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


  • 相关文章

LeetCode刷题汇总:LeetCode刷题

一、题目描述


猜数字大小

猜数字游戏的规则如下:

每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。 如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。 你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):

-1:我选出的数字比你猜的数字小 pick < num 1:我选出的数字比你猜的数字大 pick > num 0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num 返回我选出的数字。

二、思路分析


+ 看看题目的示例,我们来理一理这个思路~
+ 示例 1:



1
2
ini复制代码输入:n = 10, pick = 6
输出:6
+ 示例2:
1
2
ini复制代码输入:n = 1, pick = 1
输出:1
+ 示例3: +
1
2
ini复制代码输入:n = 2, pick = 1
输出:1
+ 示例4: +
1
2
ini复制代码输入:n = 2, pick = 2
输出:2
+ 重点是这个,不要被迷惑了! - -1 : 我的数字比较小 - 1 : 我的数字比较大 - 0 : 恭喜!你猜对了! + 这个`-1` 指的是真实的结果比较小,换个说法就是你猜的结果大,不是你猜的小!不要被误导!三、AC 代码

=======


+ 二分法YYDS!



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ini复制代码public class Solution extends GuessGame{
public int guessNumber(int n){
int low=1,high=n;
while(low<=high){
           //防止mid越界爆掉, /2
int mid=low+(high-low)/2;
           //调用guess
int res=guess(mid);
if(res!=0){
if(res==-1){
high=mid-1;
}else {
low=mid+1;
}
}else{
return mid;
}
}
return 0;
}
}
+ 执行结果: + ![image-20211107202431610.png](https://gitee.com/songjianzaina/juejin_p15/raw/master/img/2ddb42e2118d1e6a52395b867747103af9006b8e6bfaf64786075ab5869c6a59) + 做完之后我又去找了找其他大神们的解法,发现基本上都是使用二分法来解决的,不过他们写的更加简洁明了! + 个人感觉这题就是表达很让人误解,其实最终的目的是让我们尽量少调用 `guess`方法来找到分割点。 + 还有就是`mid`的值,不要越界!可以通过 `/2` 或者直接使用`long`来接收!

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

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

本文转载自: 掘金

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

Gateway服务网关之过滤器,一网打尽! 写在前面 过滤器

发表于 2021-11-07

文章已收录到我的Github精选,欢迎Star:github.com/yehongzhi/l…

写在前面

前一篇文章写了Gateway的Predicate(用于路由转发),那么这篇文章就介绍另一个主要的核心,那就是Filter(过滤器)。

过滤器有什么作用呢?工作流程是怎么样的呢?请看下图:

从图中很明显可以看出,在请求后端服务前后都需要经过Filter,于是乎Filter的作用就明确了,在PreFilter(请求前处理)可以做参数校验、流量监控、日志记录、修改请求内容等等,在PostFilter(请求后处理)可以做响应内容修改。

过滤器

Filter分为局部和全局两种:

  • 局部Filter(GatewayFilter的子类)是作用于单个路由。如果需要使用全局路由,需要配置Default Filters。
  • 全局Filter(GlobalFilter的子类),不需要配置路由,系统初始化作用到所有路由上。

局部过滤器

SpringCloud Gateway内置了很多路由过滤器,他们都是由GatewayFilter的工厂类产生。

AddRequestParameter GatewayFilter

该过滤器可以给请求添加参数。

比如我在consumer服务有一个带有userName参数的接口,我想请求网关路由转发的时候给加上一个userName=yehongzhi的参数。

1
2
3
4
less复制代码@RequestMapping(value = "/getOrder",method = RequestMethod.GET)
public String getOrder(@RequestParam(name = "userName") String userName) {
   return "获取到传入的用户名称:" + userName;
}

配置如下:

1
2
3
4
5
6
7
8
9
10
11
yaml复制代码spring:
cloud:
  gateway:
    routes:
      - id: add_request_parameter
        uri: http://localhost:8081/getOrder
        predicates:
          - Method=GET
          - Path=/getOrder
        filters:
          - AddRequestParameter=userName,yehongzhi

那么当我请求网关时,输入http://localhost:9201/getOrder,我们能看到默认加上的userName。

StripPrefix GatewayFilter

该过滤器可以去除指定数量的路径前缀。

比如我想把请求网关的路径前缀的第一级去掉,就可以这样配置实现:

1
2
3
4
5
6
7
8
9
10
yaml复制代码spring:
cloud:
  gateway:
    routes:
      - id: strip_prefix_gateway
        uri: http://localhost:8081
        predicates:
          - Path=/consumer/**
        filters:
          - StripPrefix=1

当请求路径http://localhost:9201/consumer/getDetail/1,能获得结果。

相当于请求http://localhost:8081/getDetail/1,结果是一样的。

PrefixPath GatewayFilter

该过滤器与上一个过滤器相反,是给原有的路径加上指定的前缀。

1
2
3
4
5
6
7
8
9
10
yaml复制代码spring:
cloud:
  gateway:
    routes:
      - id: prefix_path_gateway
        uri: http://localhost:8081
        predicates:
          - Path=/getUserInfo/**
        filters:
          - PrefixPath=/consumer

当请求http://localhost:9201/getUserInfo/1时,跟请求http://localhost:8081/consumer/getUserInfo/1是一样的。

Hystrix GatewayFilter

网关当然有熔断机制,所以该过滤器集成了Hystrix,实现了熔断的功能。怎么使用呢?首先需要引入Hystrix的maven依赖。

1
2
3
4
xml复制代码<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

在gateway服务添加fallback()方法

1
2
3
4
5
6
7
kotlin复制代码@RestController
public class FallBackController {
   @RequestMapping("/fallback")
   public String fallback(){
       return "系统繁忙,请稍后再试!";
  }
}

在网关服务的配置如下,对GET请求方式的请求路由转发出错时,会触发服务降级:

1
2
3
4
5
6
7
8
9
10
11
12
13
yaml复制代码spring:
cloud:
  gateway:
    routes:
      - id: hystrix_filter
        uri: http://localhost:8081
        predicates:
          - Method=GET
        filters:
          - name: Hystrix
            args:
              name: fallbackcmd
              fallbackUri: forward:/fallback

这时我们把8081的服务停止,让网关请求不到对应的服务,从而触发服务降级。

RequestRateLimiter GatewayFilter

该过滤器可以提供限流的功能,使用RateLimiter实现来确定是否允许当前请求继续进行,如果请求太大默认会返回HTTP 429-太多请求状态。怎么用呢?首先还是得引入Maven依赖。

1
2
3
4
xml复制代码<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

接着增加个配置类。

1
2
3
4
5
6
7
8
9
10
11
kotlin复制代码@Configuration
public class LimiterConfig {
​
   /**
    * ip限流器
    */
   @Bean
   public KeyResolver ipKeyResolver() {
       return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
  }
}

然后配置如下,对GET方式的请求增加限流策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
yaml复制代码spring:
cloud:
  gateway:
    routes:
      - id: hystrix_filter
        uri: http://localhost:8081
        predicates:
          - Method=GET
        filters:
          - name: RequestRateLimiter
            args:
              redis-rate-limiter.replenishRate: 1 #每秒允许处理的请求数量
              redis-rate-limiter.burstCapacity: 2 #每秒最大处理的请求数量
              key-resolver: "#{@ipKeyResolver}" #限流策略,对应策略的Bean
redis:
  port: 6379
  host: 192.168.1.5 #redis地址

然后启动服务,连续请求地址http://localhost:9201/getDetail/1,就会触发限流,报429错误。

因为内置的过滤器实在是太多了,这里就不一一列举了,有兴趣的同学可以到官网自行学习。

自定义局部过滤器

如果内置的局部过滤器不能满足需求,那么我们就得使用自定义过滤器,怎么用呢?下面用一个例子,我们自定义一个白名单的过滤器,userName在白名单内的才可以访问,不在白名单内的就返回401错误码(Unauthorized)。

局部过滤器需要实现GatewayFilter和Ordered接口,代码如下:

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
typescript复制代码public class WhiteListGatewayFilter implements GatewayFilter, Ordered {
//白名单集合
   private List<String> whiteList;
//通过构造器初始化白名单
   WhiteListGatewayFilter(List<String> whiteList) {
       this.whiteList = whiteList;
  }
​
   @Override
   public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
       String userName = exchange.getRequest().getQueryParams().getFirst("userName");
       //白名单不为空,并且userName包含在白名单内,才可以访问
       if (!CollectionUtils.isEmpty(whiteList) && whiteList.contains(userName)) {
           return chain.filter(exchange);
      }
       //如果白名单为空或者userName不在白名单内,则返回401
       exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
       return exchange.getResponse().setComplete();
  }
​
   //优先级,值越小优先级越高
   @Override
   public int getOrder() {
       return 0;
  }
}

接着再定义一个过滤器工厂,注入到Spring容器中,代码如下:

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
typescript复制代码@Component
public class WhiteListGatewayFilterFactory extends AbstractConfigurable<WhiteListGatewayFilterFactory.Config> implements GatewayFilterFactory<WhiteListGatewayFilterFactory.Config> {
​
   private static final String VALUE = "value";
​
   protected WhiteListGatewayFilterFactory() {
       super(WhiteListGatewayFilterFactory.Config.class);
  }
​
   @Override
   public List<String> shortcutFieldOrder() {
       return Collections.singletonList(VALUE);
  }
​
   @Override
   public GatewayFilter apply(Config config) {
       //获取配置的白名单
       String whiteString = config.getValue();
       List<String> whiteList = new ArrayList<>(Arrays.asList(whiteString.split(",")));
       //创建WhiteListGatewayFilter实例,返回
       return new WhiteListGatewayFilter(whiteList);
  }
//用于接收配置参数
   public static class Config {
​
       private String value;
​
       public String getValue() {
           return value;
      }
​
       public void setValue(String value) {
           this.value = value;
      }
  }
}

最后,我们可以在application.yaml配置文件中加上配置使用:

1
2
3
4
5
6
7
8
9
10
yaml复制代码spring:
cloud:
  gateway:
    routes:
      - id: white_list_filter
        uri: http://localhost:8081
        predicates:
          - Method=GET
        filters:
          - WhiteList=yehongzhi #等号后面配置的是白名单,用逗号隔开

接着启动项目,先请求localhost:9201/getDetail/1,不带userName,按预期会返回401,不能访问。

请求带有userName=yehongzhi的地址http://localhost:9201/getDetail/1?userName=yehongzhi,是在白名单内的,所以能正常访问。

全局过滤器

全局过滤器在系统初始化时就作用于所有的路由,不需要单独去配置。全局过滤器的接口定义类是GlobalFilter,Gateway本身也有很多内置的过滤器,我们打开类图看看:

我们拿几个比较有代表性的来做介绍,比如负载均衡的全局过滤器LoadBalancerClientFilter。

LoadBalancerClientFilter

该过滤器会解析到以lb://开头的uri,比如这样的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
yaml复制代码spring:
application:
  name: api-gateway
cloud:
  nacos:
    discovery:
      server-addr: 127.0.0.1:8848
      service: ${spring.application.name}
  gateway:
    routes:
      - id: consumer
        uri: lb://consumer #使用lb协议,consumer是服务名,不再使用IP地址配置
        order: 1
        predicates:
          - Path=/consumer/**

这个全局过滤器就会取到consumer这个服务名,然后通过LoadBalancerClient获取到ServiceInstance服务实例。根据获取到的服务实例,重新组装请求的url。

这就是一个全局过滤器应用的例子,它是作用于全局,而且并不需要配置。下面我们探索一下自定义全局过滤器,假设需要统计用户的IP地址访问网关的总次数,怎么做呢?

自定义全局过滤器

自定义全局过滤器需要实现GlobalFilter接口和Ordered接口。

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复制代码@Component
public class IPAddressStatisticsFilter implements GlobalFilter, Ordered {
​
   @Override
   public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
       InetSocketAddress host = exchange.getRequest().getHeaders().getHost();
       if (host == null || host.getHostName() == null) {
           exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
           return exchange.getResponse().setComplete();
      }
       String hostName = host.getHostName();
       AtomicInteger count = IpCache.CACHE.getOrDefault(hostName, new AtomicInteger(0));
       count.incrementAndGet();
       IpCache.CACHE.put(hostName, count);
       System.out.println("IP地址:" + hostName + ",访问次数:" + count.intValue());
       return chain.filter(exchange);
  }
​
   @Override
   public int getOrder() {
       return 10101;
  }
}
​
//用于保存次数的缓存
public class IpCache {
   public static final Map<String, AtomicInteger> CACHE = new ConcurrentHashMap<>();
}

启动项目,然后请求服务,可以看到控制台打印结果。

1
2
3
4
5
6
7
复制代码IP地址:192.168.1.4,访问次数:1
IP地址:192.168.1.4,访问次数:2
IP地址:192.168.1.4,访问次数:3
IP地址:localhost,访问次数:1
IP地址:localhost,访问次数:2
IP地址:localhost,访问次数:3
IP地址:192.168.1.4,访问次数:4

总结

通过上一篇的Predicates和这篇的Filters基本上把服务网关的功能都实现了,包括路由转发、权限拦截、流量统计、流量控制、服务熔断、日志记录等等。所以网关对于微服务架构来说,网关服务是一个非常重要的部分,有很多一线的互联网公司还会自研服务网关。因此掌握服务网关对于后端开发可以说是必备技能,感谢大家的阅读。

觉得有用就点个赞吧,你的点赞是我创作的最大动力~

我是一个努力让大家记住的程序员。我们下期再见!!!

能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!

本文转载自: 掘金

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

Springboot源码系列之SpringBoot启动流程(

发表于 2021-11-07

作者 : 玉龙小怪兽

1
复制代码成长的路总是伴随着艰辛,坚持,沉淀 , 分享 , 让自己和他人都有所收获

目录

  • 一、前言
  • 二、构建SpringApplication的过程
  • 三、SpringBoot核心流程之run()方法
  • 四、总结

一、前言

每一天都在使用Springboot , 都在 @Controller, @Service , @Configuration ,@Autowired等一系列注解的照顾下写出了一行一行代码。是的 , 我们是完成了功能的开发,我们是熟练的使用了Springboot提供给我们的注解,但却总感觉缺少了些什么。抛开这些常用的注解 , 大家想想你对Springboot还了解多少 ?

[所以我们需要去探究Springboot的设计思想 ,去看SpringBoot的代码风格 ,去基于Springboot提供给我们的接口做二次开发,从而提高对SpringBoot的整体把握,而不是局限于那些常用的注解]

二、构建SpringApplication的过程

Springboot的启动入口

1
2
3
4
5
6
java复制代码@SpringBootApplication
public class StartApp {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(StartApp.class, args);
}
}

通过main方法调用SpringApplication的静态方法run方法, 最后返回一个容器 , 完成Springboot的整个启动过程

1
2
3
java复制代码public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args){
return new SpringApplication(primarySources).run(args);
}

run方法它去构建了一个SpringApplication的实例,然后调用run方法去完成Springboot的启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
scss复制代码public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//获取资源加载器 (null)
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//SpringApplication实例记录主配置启动类,StartApp。class
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//选择应用程序的类型(servlet)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//从类路径下的META-INF/spring.factories下读取所有ApplicationContextInitializer的实现类并实例化存储到initializers属性里
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//从类路径下的META-INF/spring.factories下读取所有ApplicationListener的实现类并实例化存储到listeners属性里
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//存储主启动类到mainApplicationClass属性里
this.mainApplicationClass = deduceMainApplicationClass();
}

ApplicationContextInitializer(系统初始化器) 主要是用于准备容器时applyInitializers()方法执行里面的方法给容器中做一些初始化的处理

1
2
3
4
5
6
7
8
9
java复制代码public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);

}

加载META-INF/spring.factories下key值为org.springframework.context.ApplicationContextInitializer的实现类,Springboot默认的有如下:

1
2
3
4
5
6
7
properties复制代码# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

这里我们也可以按照Springboot它的规范 , 自定义我们自己的初始化类,做我们自己想做的事情。

1
2
properties复制代码org.springframework.context.ApplicationContextInitializer=\
com.captain.demo.RegisterBeanApplicationContextInitializer

ApplicationListener,Sping的监听器,可以对你感兴趣的事件进行监听和处理。

1
2
3
4
5
6
7
8
9
java复制代码public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);

}

加载META-INF/spring.factories默认的key = org.springframework.context.ApplicationListener的实现类,同样可以自定义属于我们自己的监听器,Springboot默认的有如下:

1
2
3
4
5
6
7
8
9
10
11
properties复制代码org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

关于Spring的事件机制 ,后面再用一篇文章详细的说明。Spring的事件机制需要三个角色: 1.事件的发布器 2.事件的载体 3.事件的监听者

这里在介绍一下SpringFactoriesLoader这个类它的作用:

​ 1.从classpath下的多个jar包特定的位置读取文件并初始化类(一般为 spring.factories下的)

​ 2.文件的内容以key为全限定名(抽象类/接口),value是实现类的全类名用,分割

1
2
3
4
5
6
7
8
java复制代码private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

三、SpringBoot核心流程之run()方法

run方法的核心做的事情如下

1.构建系统需要的环境

2.创建并配置容器

3.加载,解析class为BeanDefinitions,注册到容器中

4.实例化Bean

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
scss复制代码public ConfigurableApplicationContext run(String... args) {
//构建任务观察器
StopWatch stopWatch = new StopWatch();
//开始记录时间
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//设置程序在headless模式下工作
configureHeadlessProperty();
//默认情况只有一个SpringApplicationRunListener工作
SpringApplicationRunListeners listeners = getRunListeners(args);
//发布 ApplicationStartingEvent 事件,监听者监听到该事件后, 执行相应的逻辑代码
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//准备运行环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
//打印 Banner
Banner printedBanner = printBanner(environment);
//创建AnnotationConfigServletWebServerApplicationContext容器
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//准备上下文,详细说明看下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新容器,详细说明看下文
refreshContext(context);
//刷新容器后的处理(空处理)
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//发布ApplicationStartedEvent(应用程序启动事件)
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

try {
//发布ApplicationReadyEvent(应用程序就绪事件)
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
//返回上下文
return context;
}

prepareEnvironment() :准备环境的过程中, 主要做了以下几件事情

​ 1.创建系统的运行环境,并初始化一些属性源

​ 2.给环境设置转化器,重新排列PropertySource

​ 3.选择激活的配置文件 dev-properties,prod-properties

​ 4.将创建的环境与SpringApplication关联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
scss复制代码private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
//创建运行环境(StandardEnvironment),分别初始化MapPropertySource / ConfigurationPropertySourcesPropertySource/PropertiesPropertySource/SystemEnvironmentPropertySource
ConfigurableEnvironment environment = getOrCreateEnvironment();
//设置转化器,配置属性源,配置 激活的配置文件
configureEnvironment(environment, applicationArguments.getSourceArgs());
//将ConfigurationPropertySourcesPropertySource属性源放在最前面
ConfigurationPropertySources.attach(environment);
//发布ApplicationEnvironmentPreparedEvent(环境准备事件)
listeners.environmentPrepared(environment);
//将环境与SpringApplication进行绑定
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
//将ConfigurationPropertySourcesPropertySource属性源放在最前面
ConfigurationPropertySources.attach(environment);
return environment;
}

prepareContext() :准备上下文主要做了以下几件事情:

​ 1.给容器设置环境,给factoty设置转化器

​ 2.执行所有initializers属性的初始化方法,对容器进行初始化处理

​ 3.发布ApplicationContextInitializedEvent(容器初始化事件)

​ 4.注册主启动类(StartApp)到BeanDefinitionMap中

​ 5.发布 ApplicationPreparedEvent(应用程序准备事件)

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
scss复制代码private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//给容器设置环境
context.setEnvironment(environment);
//给factory工厂设置转换服务
postProcessApplicationContext(context);
//执行之前加载进来的initializers属性的初始化方法,对容器进行初始化处理
applyInitializers(context);
//发布ApplicationContextInitializedEvent(容器初始化事件)
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//下面几步操作给beanFactory设置属性
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//注册主启动类(StartApp)到BeanDefinitionMap中
load(context, sources.toArray(new Object[0]));
//发布 ApplicationPreparedEvent(应用程序准备事件)
listeners.contextLoaded(context);
}

refreshContext(context) :刷新容器的这个方法是最为核心的,要做了以下几件事情

​ 1.准备刷新容器,给容器设置同步标识,具体方法

​ 2.给容器中注入一些容器需要的系统bean,beanfactoryPostProcessor等

​ 3.执行BeanFactoryPostProcessor的postProcessBeanDefinitionRegistry方法,将扫描到的注解中的所有的

​ BeanDefinition注册到容器中

​ 4.注册Bean创建前后的BeanPostProcessor

​ 5.国际化处理

6.初始化事件的发布者

7.给事件的发布者注册监听器

​ 8.实例化非懒加载的所有的类

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
scss复制代码@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//1.设置容器的激活状态,验证必要的属性(可以设置环境中必要的属性),
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
//Beanfactoty设置序列化id
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
//给beanFactoty添加BeanPostProcessor,设置beanFactoty的属性
prepareBeanFactory(beanFactory);

try {
//添加WebApplicationContextServletContextAwareProcessor,
//设置 WebApplicationScope 为 request /session
postProcessBeanFactory(beanFactory);
//执行BeanFactoryPostProcessor的postProcessBeanDefinitionRegistry方法
//对BeanDefinition进行增删改
//加载所有的自动配置类并注册到beanDefinitionMap中
invokeBeanFactoryPostProcessors(beanFactory);
//beanFactory添加BeanPostProcessors,对Bean初始化前后进行操作
registerBeanPostProcessors(beanFactory);
//国际化处理
initMessageSource();
//初始化 ApplicationEventMulticaster(应用程序事件多播器)
initApplicationEventMulticaster();
//构建web服务器 (tomcat服务器)
onRefresh();
//给事件发布器注册监听者
registerListeners();
//实例化所有的非懒加载的单例bean
finishBeanFactoryInitialization(beanFactory);
//清除资源缓存,给上下文初始化生命周期处理器
//发布容器刷新完成的事件
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

四、总结

1
erlang复制代码坚持,沉淀 , 分享 , 让自己和他人都有所收获.
  • 有身边的朋友说,我读了源码,好像就是在背这个源码的执行流程,过几天就忘了,活生生的变成了八股文,流程只是让我们知道他到底干了点撒事情,更加需要我们去理解的是:他的类与类之间是如何面向对象设计的,框架中如何运用设计模式的,我想这样的收获肯定是大于你去记住那些流程来得更加的有意义。
  • 通过上面对Spingboot的整体脉络的理解,我想大家已经可以摸索出一点心得了。 Springboot在启动时,到底都干了哪些事情,如何利用ApplicationContextInitializer,ApplicationListener去做自己的扩展,自动化配置是在哪里被加载成为BeanDefinition的等。
  • 当然本篇文章只是把Springboot启动的整体脉络数理了,并没有做更深入的探究,下一篇文章将会把ApplicationContextInitializer和ApplicationListener进行实战,基于他们做一些功能的二次开发,会去构建一个属于自己的framework
  • 文章基于个人的理解以及参考优秀的书籍,文章,如有错误,敬请指出!

本文转载自: 掘金

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

设计模式连更(7)-原型模式 原型模式

发表于 2021-11-07

原型模式

概述

原型模式是一种创建型设计模式,使你能够复制已有的对象,而又无需使代码依赖他们所属的类

prototype.png

问题

如果你有一个对象,并且你希望生成与其完全相同的一个复制品,你该如何实现呢?

你可以新建一个相同类的对象,然后遍历原始对象的所有成员变量,并将成员变量复制到新对象中

但是有些对象可能拥有私有对象,它们在对象本身以外是不可见的

解决方案

  • 原型模式将克隆过程委派给被克隆的实际对象。模式为所有支持克隆的对象声明了一个通用接口,该接口让你能够克隆对象,同时又无需将代码和对象所属类耦合
  • 所有的类对克隆方法的实现都非常相似。该方法会创建一个当前类的对象,然后将原始对象所有的成员变量值复制到新建的类中,甚至可以复制私有成员变量

原型模式适合应用场景

  1. 如果你需要复制一些对象,同时又希望代码独立于这些对象所属的具体类,可以使用原型模式

这一点考量通常出现在代码需要处理第三方代码通过接口传递过来的对象时。即使不考虑代码耦合的情况,你的代码也不能依赖这些对象所属的具体类,因为你不知道他们的具体信息

原型模式为客户端代码提供一个通用接口,客户端代码可通过这一接口与所有实现了克隆的对象进行交互,它也使得客户端代码与其所克隆的对象具体类独立开来
2. 如果子类的区别仅在于其对象的初始化方式,那么你可以使用该模式来减少子类的数量。别人创建这些子类的目的可能是为了创建特定类型的对象

在原型模式中,你可以使用一系列预生成的,各种类型的对象作为原型

实现方式

  1. 创建原型接口,并在其中声明克隆方法
  2. 原型类必须另行定义一个以该类对象为参数的构造函数,构造函数必须复制参数对象中的所有成员变量值到新建实体中
  3. 克隆方法通常只有一行代码:使用new运算符调用原型版本的构造函数
  4. 你还可以创建一个中心化原型注册表,用于存储常用原型

原型模式的优缺点

优点

  1. 可以克隆对象,而无需与它们所属的具体类相耦合
  2. 可以克隆预生成原型,避免反复运行初始化代码
  3. 可以更方便地生成复杂对象
  4. 可以用继承以外的方式来处理复杂对象的不同配置

缺点

  1. 克隆包含循环引用的复杂对象可能会非常麻烦

本文转载自: 掘金

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

系统学习Java新特性-用流收集数据

发表于 2021-11-07

阅读《Java实战》,本文为第6章总结,主要介绍收集器(Collectors)的使用,如收集器简介、归约与汇总处理、分组分区、收集器接口机制、自定义一个高效的收集器。

1、收集器(Collectors)

collect(Collector<? super T, A, R> collector)是一个终端操作,接受的参数是定义流中元素累积到汇总结果的各种方式,该参数即为收集器。收集器包含的功能主要有两个:

  • 用作高级归约:对流中的元素触发以一个归约操作,如Collectors.toList()将结构收集到一个List中返回。
  • 预定义收集器:利用Collectors类提供的工厂方法(如groupingBy)创建收集器,扩展相关功能,主要三大功能如下:
    • 将流元素归约和汇总为一个值
    • 元素分组
    • 元素分区

2、归约和汇总

2.1 查询流中的最大值和最小值

  • Collectors.maxBy(Comparator<? super T> comparator):最大值收集器
  • Collectors.minBy(Comparator<? super T> comparator):最小值收集器
1
2
3
4
5
6
7
8
9
java复制代码    //求最值收集器使用示例
//1.定义比较器Comparator
Comparator<Dish> dishCaloriesComparable =
Comparator.comparingInt(Dish::getCalories);
//2.使用maxBy求出最大热量的菜肴
Optional<Dish> mostCaloriesDish =
Dish.menu.stream()
.collect(Collectors.maxBy(dishCaloriesComparable));
mostCaloriesDish.ifPresent(System.out::println);// pork

2.2 汇总

主要对结果做汇总统计,比如求和、平均值以及统计,具体使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码    //# 汇总收集器示例
//1、求和
int totalCalories =
Dish.menu.stream()
.collect(Collectors.summingInt(Dish::getCalories));
System.out.println(totalCalories);//4300
//2、求平均
double avgCalories =
Dish.menu.stream()
.collect(Collectors.averagingInt(Dish::getCalories));
System.out.println(avgCalories);//477.77777777
//3、统计
IntSummaryStatistics menuStatistics =
Dish.menu.stream()
.collect(Collectors.summarizingInt(Dish::getCalories));
//IntSummaryStatistics{count=9, sum=4300, min=120, average=477.777778, max=800}
System.out.println(menuStatistics);

2.3 连接字符串

joining工厂方法返回的收集器会把流中每一个对象引用toString()方法得到的所有字符串连接成一个字符串。

1
2
3
4
5
6
7
java复制代码    //# 连接收集器
String shortNames =
Dish.menu.stream()
.map(Dish::getName)
.collect(Collectors.joining(", "));
// pork, beef, chicken, french fries,...
System.out.println(shortNames);

2.4 广义的归约汇总

前三种收集器,本质是reducing的常见特殊情况处理。比如,求和,本质内容是如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码    //#广义的归约汇总
//1、求和
int totalCalories1 =
Dish.menu.stream()
.collect(Collectors.reducing(0,Dish::getCalories,(i,j) -> i+j));
System.out.println(totalCalories1);
//2、求最值
Optional<Dish> mostCaloriesDish1 =
Dish.menu.stream()
.collect(Collectors.reducing((d1,d2) -> d1.getCalories()>d2.getCalories()?d1:d2));

//3、joining字符串拼接
String shortNames1 =
Dish.menu.stream()
.collect(Collectors.reducing("",Dish::getName,(s1,s2) -> s1+","+s2));
System.out.println(shortNames1);

3、分组

与数据库的常见操作,根据一个或者多个属性对集合中的项目进行分组类似。Collectors.groupingBy工厂方法返回的收集器可以实现该功能。

待完成:

2021年11月7日 新工作岗位入职,因为岗位需要nestjs相关技术栈+源码阅读活动,暂时暂缓javaCore后面内容阅读梳理~

本文转载自: 掘金

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

Springboot 集成 Mybatis

发表于 2021-11-07

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

首先添加 Mybatis 和 Mysql 依赖

1
2
3
4
5
6
7
8
9
xml复制代码<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

创建 dao 层文件

1
2
3
4
5
6
java复制代码@Mapper
@Repository
public interface LearnMapper {
Long insert(Learn learn);
List<Learn> queryList(Learn learn);
}

然后在 resources/mapper/ 下创建 xml 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
xml复制代码<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.younote.learn.Mapper.LearnMapper">
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into
learn_mybatis (user_name,nick_name,age)
values (#{userName},#{nickName},#{age})
</insert>
<select id="queryList" parameterType="top.younote.learn.pojo.Learn" resultType="top.younote.learn.pojo.Learn">
select
id,
user_name,
nick_name,
age
from
learn_mybatis
where
user_name = #{userName}
</select>
</mapper>

在 application.yml 下新增

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
yml复制代码server:
port: 8089
spring:
#数据库连接配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&useSSL=false
username: root
password: dly3230

#mybatis的相关配置
mybatis:
#mapper配置文件
mapper-locations: classpath:mapper/*.xml
type-aliases-package: top.younote.learn.pojo
#开启驼峰命名
configuration:
map-underscore-to-camel-case: true
# mybatis 显示 sql 语句,便于调试,top.younote.learn.Mapper 是 dao 层包名,修改成自己的路径
logging:
level:
top.younote.learn.Mapper: debug

Mybatis 插入数据返回插入的那条数据的 id

只需要增加 useGeneratedKeys 、keyProperty 属性即可,其中 keyProperty 对应自增序列对应的字段,这里是 id。
这个操作实际上是把插入到数据库的 id 属性赋值到入参里面的,所以这个需要从入参里面获取,而不是 insert 返回值(插入成功结果始终是 1 ),这里是通过 learn.getId() 获取 id

1
2
3
4
5
xml复制代码<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into
learn_mybatis (user_name,nick_name,age)
values (#{userName},#{nickName},#{age})
</insert>

日志打印 Sql 语句

1
2
3
yml复制代码logging:
level:
top.younote.learn.Mapper: debug

本文转载自: 掘金

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

ORACLE 12C采坑之 ORA-12541 TNS 无监

发表于 2021-11-07

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

目录

1.首先查看oracle12c监听服务是否启动

2. ping 本地id是否能ping通

3.检测配置监听是否有错误:

4.重新配置监听器Net Configuration Assistant

5.之后重启OracleServiceORCL服务

1.首先查看oracle12c监听服务是否启动

)​

  1. ping 本地id是否能ping通

)​

3.检测配置监听是否有错误:

找到 Oracle 监听配置文件(listener.ora)和TNS配置文件(tnsnames.ora)

我的安装路径是E:\oracle\product\11.2.0\dbhome_1\NETWORK\ADMIN

LISTENER =

(DESCRIPTION_LIST =

(DESCRIPTION =

(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1521))

)

(DESCRIPTION =

(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.139)(PORT = 1521))

)

)

4.重新配置监听器Net Configuration Assistant

具体配置参考【oracle常见错误】oracle监听程序配置/“ORA-12541: TNS: 无监听程序” - yxtic - 博客园

5.之后重启OracleServiceORCL服务

)​基本情况就以上几种 我也踩过一次坑 所以就记录下来了


本文转载自: 掘金

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

Istio 整体架构

发表于 2021-11-07

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

Istio 从逻辑上分为数据平面(Data Plane)和控制平面(Control Plane)。

  • 数据平面:由整个网格内的 Sidecar 代理组成,这些代理以 Sidecar 的形式和应用服务一起部署。这些代理负责协调和控制应用服务之间的所有网络通信。每一个 Sidecar会接管进入和离开服务的流量,并配合控制平面完成流量控制等方面的功能。
  • 控制平面:控制和管理数据平面中的 Sidecar 代理,完成配置分发、服务发现、流量路由、授权鉴权等功能,以达到对数据平面的统一管理。在 Istio 1.5 版本中,控制平面由原来分散的、独立部署的几个组件整合为一个独立的 istiod,变成了一个单进程、多模块的组织形态。同时,在 Istio 1.5 版本中,基于性能、部署方便考虑,废弃了Mixer,转而将这些功能放到了 Sidecar 中。

下图展示了组成每个平面的不同组件:

Istio架构图

图 4.1.1:Istio架构图

1、核心组件

下面将简单的介绍一下 Istio 架构中几个核心组件的主要功能。

1.1 Proxy

Proxy 位于数据平面,即:常说的 Sidecar 代理,与应用服务以 Sidecar 方式部署在同一个 Pod 中。Proxy 实际上包括 istio-proxy 和 pilot-agent 两部分,它们以两个不同的进程部署在同一个容器 istio-proxy 中。

istio-proxy

Istio 的数据平面默认使用 Envoy 的扩展版本作为 Sidecar 代理(即:istio-proxy),istio-proxy 是基于 Envoy 新增了一些扩展,其代码仓库位于 istio/proxy。

注:理论上,Istio 是支持多种 Sidecar 代理,其中 Envoy 作为默认提供的数据平面,如无特殊说明在 Istio 中通常所说的 Envoy 就是 istio-proxy。

Envoy 是用 C++ 开发的高性能代理,用于协调服务网格中所有服务的入站和出站流量,是唯一与数据平面流量交互的组件。主要包括三部分能力:

  • 动态服务发现、负载均衡、路由、流量转移。
  • 弹性能力:如超时重试、熔断等。
  • 调试功能:如故障注入、流量镜像等。

polit-agent

pilot-agent,负责管理 istio-proxy 的整个生命周期,具体包括 istio-proxy 准备启动参数和配置文件,负责管理 istio-proxy 的启动过程、运行状态监控以及重启等。其代码仓库位于 istio/istio/pilot/cmd/pilot-agent。

部署上,isito-proxy 不是单独构建镜像,而是和 polit-agent 一起打包构建成一个镜像 istio/proxyv2,poilt-agent 将会以子进程的方式启动 istio-proxy,并监控 istio-proxy 的运行状态。

1.2 Istiod

自 Istio 1.5 版本开始,控制平面由原来分散、独立部署的三个组件(Pilot、Citadel、Galley)整合为一个独立的 istiod,变成了一个单进程、多模块的组织形态,极大的降低了原来部署的复杂度。

Pilot

负责 Istio 数据平面的 xDS 配置管理,具体包括:

  • 服务发现、配置规则发现:为 Sidecar 提供服务发现、用于智能路由的流量管理功能(例如,A/B 测试、金丝雀发布等)以及弹性功能(超时、重试、熔断器等)。通过提供通用的流量管理模型和服务发现适配器(Service Discovery Adapter),来对接不同平台的适配层。
  • xDS 配置下发:提供统一的 xDS API,供 Sidecar 调用。将路由规则等配置信息转换为 Sidecar 可以识别的信息,并下发给数据平面。

注:这里实际上是指 pilot-discovery,代码仓库位于 istio/istio/pilot/cmd/pilot-discovery

Citadel

负责安全证书的管理和发放,可以实现授权和认证等操作。

Citadel 并不是唯一的证书管理方式,Istio 当前支持 Citadel、Vault 和 Google 等多种证书管理方式,Citadel 是当前默认的证书管理方式。

Galley

Galley 是 Istio 1.1 版本中新引入的配置管理组件,主要负责配置的验证、提取和处理等功能。其目的是将 Istio 和底层平台(如 Kubernetes)进行解耦。

在引入 Galley 之前,Istio 控制平面的各个组件需要分别对 Kubernetes 资源进行管理,包括资源的配置验证,监控资源配置变化,并针对配置变更采取相应的处理等。

2、设计目标

几个关键的设计目标形成了 Istio 的架构,这些目标对于使系统能够大规模和高性能地处理服务是至关重要的。

  • 对应用透明性:从本质上说,对应用透明是 Service Mesh 的特性,一个合格的 Service Mesh 产品都应该具有这一特性,否则也就失去了网格产品的核心竞争力。为此,Istio 自动将自己注入到服务之间的所有网络路径中,做到对应用的透明性。Istio 使用 Sidecar 代理来捕获流量,并在不更改已部署应用程序代码的情况下,自动对网络层进行配置,以实现通过这些代理来路由流量。
  • 可扩展性:Istio 认为,运维和开发人员随着深入使用 Istio 提供的功能,会逐渐涌现更多的需求,主要集中在策略方面。因此,为策略系统提供足够的扩展性,成为了 Istio 的一个主要的设计目标。
  • 可移植性:考虑到现有云生态的多样性,Istio 被设计为可以支持不同的底层平台,也支持本地、虚拟机、云平台等不同的部署环境。不过从目前的情况来看,Istio 和 Kubernetes 还是有着较为紧密的依赖关系,平台无关性、可移植性将是 Istio 最终实现目标。
  • 策略一致性:Istio 使用自己的 API 将策略系统独立出来,而不是集成到 Sidecar 中,从而允许服务根据需要直接与之集成。同时,Istio 在配置方面也注重统一和用户体验的一致性。一个典型的例子是路由规则都统一由虚拟服务来配置,可在网格内、外以及边界的流量控制中复用。

3、Istio 架构演进

从 2017 年 5 月发布以来,Istio 经历了四个重要的版本和由此划分的三个发展阶段。在不到三年的产品迭代过程中,出现了两次重大的架构变动。

  • 0.1 版本:2017 年 5 月发布。作为第二代 Service Mesh 的开创者,宣告了 Istio 的诞生,也燃起了网格市场的硝烟与战火。
  • 1.0 版本:发布于 2018 年 7 月,对外宣传生产环境可用。从 0.1 到 1.0 版本,开发时间经历了一年多,但持续的发布了多个 0.x 版本,这一阶段处于快速迭代期。
  • 1.1 版本:发布于 2019 年 3 月,号称企业级可用的版本。一个小的版本号变化居然耗费了半年之久,其主要原因是出现了第一次架构重构,这一阶段算是调整期。
  • 1.5 版本:发布于 2020 年 3 月,再次进行架构的重建,将多组件整合为单体形态的 istiod。从 1.1 到 1.5 版本的一年中,Istio 开始遵循季节性发布,进入了产品的稳定发展期。

Istio架构演进

图 4.1.2:Istio架构演进

接下来,我们将会针对 Istio 数据平面和控制平面展开具体说明。

本文转载自: 掘金

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

即时编译器的代码优化技术 方法内联 逃逸分析 公共子表达式消

发表于 2021-11-07

前边讲到了即时编译器将字节码翻译为本地机器码,本文介绍的优化技术是即时编译器在生成代码时采用的代码优化技术

方法内联

消除方法调用的成本,为其他优化手段建立良好的基础,示例如下,只是举例方法内联的含义,实际优化后肯定不是这样的,还会有其他的优化手段继续优化。

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
java复制代码//未优化过的代码
public class MethodInlining {
public static void foo(Object obj){
if(obj == null){
System.out.println("do....");
}
}
public static void testInline(String[] args){
Object object = null;
foo(object);
}
}

//优化之后的代码
public class MethodInlining {
public static void foo(Object obj){
if(obj == 1){
System.out.println("do....");
}
}
public static void testInline(String[] args){
Object object = 1;
if(obj == null){
System.out.println("do....");
}
}
}

逃逸分析

  • 方法逃逸: 分析对象动态作用域,当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中
  • 线程逃逸: 当一个对象可能被外部线程访问到,譬如赋值给可以在其他线程中访问的实例变量,这种称为线程逃逸

如果能证明一个对象不会逃逸到方法或线程之外与,或者逃逸程度比较低(只逃逸出方法而不会逃逸出线程),则可能为这个对象实例采取不同程度的优化,比如:

  • 栈上分配(HotSpot还没有做这项优化): Java在堆上分配对象的内存空间是大家都知道的尝试,Java堆中的对象对于各个线程都是共享和可见的,只要持有这个对象的引用,就可以访问到堆中存储的对象数据。垃圾收集系统会回收堆中无用对象,但是整个回收的过程需要耗费大量资源。如果确定一个对象不会逃逸出线程之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁,这样垃圾收集系统的压力就会下降很多
  • 标量替换:
    • 如果一个数据已经无法再分解成更小的数据来表示了,原始数据类型(int、long等数值类型及reference类型等)都不能再进一步分解了,那么这些数据就可以被称为标量。
    • 如果一个数据可以继续分解,那它就被称为聚合量(Aggregate),Java中的对象就是典型的聚合量。
    • 如果把一个Java对象拆散,根据程序的访问情况,将其用到的成员变量恢复为原始类型访问,这个过程就称为标量替换。假如逃逸分析能够证明一个对象不会被方法外部访问,并且这个对象可以被拆散,那么程序真正执行的时候可能不会去创建这个对象,而改为直接创建它的若干个被这个方法使用的成员变量来代替。将对象拆分后,除了可以让对象的成员变量在栈上(栈上存储的数据,很大机会被虚拟机分配至物理机器的高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件。
  • 同步消除:线程同步本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写肯定就不会有竞争,对这个变量实施的同步措施也就可以安全地消除掉

-XX:+DoEscapeAnalysis:开启逃逸分析
-XX:+EliminateAllocations:开启标量替换
-XX:+EliminateLocks :开启同步消除

从JDK6 Update23开始,访问到编译器中开始才默认开启逃逸分析,但目前逃逸分析技术仍在发展之中,未完全成熟。

公共子表达式消除

公共子表达式: 如果一个表达式E之前已经被计算过了,并且从先前的计算到现在E中所有变量的值都没有发生变化,那么E的这次出现就称为公共子表达式。
​

对于这种表达式,没有必要花时间再对它进行重新计算,只需要直接用前面计算过的表达式结果代替E。
举例:

1
java复制代码int d = (c * b) * 12 + a + (a + c * b)

上面这段代码中的公共子表达式为b * c,Javac编译器不会对这段代码进行任何优化,生成的字节码是完全遵照Java源码的写法直译而成的。
但是当这段代码进入虚拟机的即时编译器后,编译器检测到c * b与b * c是一样的表达式,并且在计算期间b与c的值是不变的,因此这条表达式就可能被视为

1
java复制代码int d = E * 12 + a + (a + E)

有的编译器还可能进行另外一种优化—-代数化简,在E本来就有乘法运算的前提下,把表达式变为

1
java复制代码int d= E * 12 + a + a

数组边界检查消除

我们在访问数组元素的时候肯定遇到过IndexOutOfBoundsException,如果有一个数组foo[],在Java语言中访问数组元素foo[i]的时候徐彤将会自动进行上下界的范围检查,即i必须满足“i>=0 && i<foo.length”的访问条件,否则就会抛出运行时异常IndexOutOfBoundsException。
这样对于开发者的好处就是即时我们没有专门编写防御代码,也能够避免大多数的溢出攻击;对于虚拟机的执行子系统来说,每次数组元素的读写都带有一次隐含的条件判定操作,对于大量数组访问的程序代码,这必定是一种性能负担。
​

但是为了安全这种检查肯定是要做的,但数组边界检查不一定必须在运行期间一次不漏的进行,因此Java将一部分数组边界检查操作放到了编译器,例如:

  1. 当访问foo[3]时,只要在编译器确定foo.length的值,并判定下标“3”没有越界,执行的时候就无须判定了。
  2. 更常见的就是在循环体中使用循环变量来访问数组了,编译器通过数据流分析就可以判定变量的取值范围永远在[0,foo.length)之内,那么在循环中就可以把整个数组的上下界检查消除掉,着这样可以节省很多次的条件判定操作

还有其他消除操作,比如自动装箱消除,安全点消除,消除反射等。

本文转载自: 掘金

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

Spring Security 认证与授权

发表于 2021-11-07

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

一、基于配置的认证与授权

  • 新建controller包
  • 在该包下新建三个控制器,AdminController,AppController,UserController
    image-20201015094116252.png
  • 分别创建测试API
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码/**
* 模拟后台相关Api接口
*/
@RequestMapping("/admin/api")
@RestController
public class AdminController {

@RequestMapping(value = "/hi",method = RequestMethod.GET)
public String hi(){
return "hi,admin.";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码/**
* 模拟对外公开的Api接口
*/
@RequestMapping("/app/api")
@RestController
public class AppController {

@RequestMapping(value = "/hi", method = RequestMethod.GET)
public String hi() {
return "hi,app.";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码/**
* 模拟用户相关Api接口
*/
@RequestMapping("/user/api")
@RestController
public class UserController {

@RequestMapping(value = "/hi", method = RequestMethod.GET)
public String hi() {
return "hi,user.";
}
}
  • 配置资源授权
  • 配置configure
  • 修改之前的配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码 @Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/api/**").hasRole("ADMIN")
.antMatchers("/user/api/**").hasRole("USER")
.antMatchers("/app/api/**").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/myLogin.html")
// 指定处理登录请求的路径,修改请求的路径,默认为/login
.loginProcessingUrl("/mylogin")
// 使登录页面不设限访问
.permitAll()
.and()
.csrf().disable();

}
  • antMatchers()一个采用ANT模式的URL匹配器
  • ?表示匹配任意单个字符
  • * 表示匹配0或任意数量字符
  • ** 表示匹配0或更多的目录
  • 重启服务
  • 访问api http://localhost:8080/app/api/hi
  • 访问成功 页面显示 hi,app.
  • 访问api http://localhost:8080/user/api/hi
  • 跳转到登录页面
  • 输入自定义的用户名密码
  • 登录成功,页面却报403错误,表示授权失败
  • 认证已经通过,授权失败

因为我们配置的.antMatchers("/user/api/**").hasRole("USER"),需要用户具有USER角色权限

  • 修改配置文件application.yml
1
2
3
4
5
6
yml复制代码spring:
security:
user:
name: caoshenyang
password: 123456
roles: USER
  • 给用户添加USER权限
  • 重启项目
  • 访问api http://localhost:8080/user/api/hi
  • 登录成功后,页面显示hi,user.

访问api http://localhost:8080/admin/api/hi

出现同样情况

修改配置文件application.yml

给用户添加上ADMIN权限

重启项目

访问正常,页面显示hi,admin.

二、基于内存的多用户设置

1. 实现自定义的UserDetailsService

1
2
3
4
5
6
7
8
9
10
java复制代码@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//MD5 加密 明文 111 加密后 698d51a19d8a121ce581499d7b701668
//noop 明文
manager.createUser(User.withUsername("aa").password("{MD5}698d51a19d8a121ce581499d7b701668").roles("USER").build());
manager.createUser(User.withUsername("bb").password("{noop}222").roles("USER").build());

return manager;
}

注意: SpringSecurity5.x 以上版本需要配置加密否则会出现以下异常

1
java复制代码There is no PasswordEncoder mapped for the id "null"

SpringSecurity5.x 加密方式采用{Id}password的格式配置

我们可以看一下PasswordEncoderFactories自带的加密方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码public class PasswordEncoderFactories {
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new LdapShaPasswordEncoder());
encoders.put("MD4", new Md4PasswordEncoder());
encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new StandardPasswordEncoder());
encoders.put("argon2", new Argon2PasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}

private PasswordEncoderFactories() {
}
}
  • 重新启动
  • 输入账号密码
  • 登录成功
  • 此配置会覆盖原先application.yml中的配置

2. 通过congfigure

1
2
3
4
5
6
7
8
java复制代码@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("tom").password("111").roles("ADMIN","USER")
.and()
.withUser("lisi").password("222").roles("USER");
}

同实现自定义UserDetailsService大同小异,此配置会覆盖原先application.yml中的配置和自定义UserDetailsService中配置,选其中之一就可以

三、 基于默认数据库模型的授权与认证

  • 查看InMemoryUserDetailsManager源码
  • 实现了UserDetailsManager接口

image-20201016101659111.png

  • 选中UserDetailsManager接口,Ctrl+H

发现实现该接口的还有另一个实现类JdbcUserDetailsManager
image-20201016101511396.png

从命名应该能猜到该实现类通过JDBC方式连接数据库

  • 为工程引入JDBC和MYSQL依赖
1
2
3
4
5
6
7
8
9
xml复制代码<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
  • application.yml配置数据连接参数
1
2
3
4
5
6
yml复制代码spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/springSecurityDemo?useUnicode=true&&characterEncoding=utf8&&useSSL=false&&serverTimezone=Asia/Shanghai
  • 创建数据库springSecurityDemo

SpringSecurity提供了默认的数据库模型

1
2
3
4
5
java复制代码public JdbcUserDetailsManagerConfigurer<B> withDefaultSchema() {
this.initScripts.add(new ClassPathResource(
"org/springframework/security/core/userdetails/jdbc/users.ddl"));
return this;
}

地址在org/springframework/security/core/userdetails/jdbc/users.ddl下

image-20201016105051344.png

1
2
3
mysql复制代码create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null);
create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);

注意: MySql不支持varchar_ignorecase这种类型,将其改为varchar

1
2
3
mysql复制代码create table users(username VARCHAR(50) not null primary key,password VARCHAR(500) not null,enabled boolean not null);
create table authorities (username VARCHAR(50) not null,authority VARCHAR(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);
  • 执行建表语句
  • 创建两张表

image-20201016110104571.png

authorities表

image-20201016110547743.png

users表

image-20201016110743875.png

  • 构建JdbcUserDetailsManager实例,让SpringSecurity使用数据库来管理用户,和基于内存类似,只是用户信息来源于数据库
  • 引入DataSource
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
java复制代码@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private DataSource dataSource;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/api/**").hasRole("ADMIN")
.antMatchers("/user/api/**").hasRole("USER")
.antMatchers("/app/api/**").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/myLogin.html")
// 指定处理登录请求的路径,修改请求的路径,默认为/login
.loginProcessingUrl("/mylogin")
.permitAll()
.and()
.csrf().disable();
}

/**
* 基于默认数据库数据模型用户设置
*/
@Bean
public UserDetailsService userDetailsService(){
JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
manager.setDataSource(dataSource);

//MD5 加密 名文 111 加密后 698d51a19d8a121ce581499d7b701668
manager.createUser(User.withUsername("aa").password("{MD5}698d51a19d8a121ce581499d7b701668").roles("USER").build());
manager.createUser(User.withUsername("bb").password("{noop}222").roles("USER").build());
return manager;
}
}
  • 重启项目
  • 访问api http://localhost:8080/user/api/hi
  • 输入用户名aa 密码111
  • 访问成功

发现数据库存储了这些信息

image-20201016114243117.png

image-20201016114311093.png

并且注意到在我们设置的权限前加了ROLE_前缀

  • 查看JdbcUserDetailsManager源码

发现定义了大量的sql执行语句

createUser()其实就相当与执行下面SQL语句

1
java复制代码insert into users (username, password, enabled) values (?,?,?)

上述代码中存在一个问题,每当我们重启项目时都会去创建用户,但是username是主键,会出现主键冲突异常

1
java复制代码nested exception is java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'aa' for key 'PRIMARY'
  • 稍作修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码/**
* 基于默认数据库数据模型用户设置
*/
@Bean
public UserDetailsService userDetailsService() {
JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
manager.setDataSource(dataSource);
if (!manager.userExists("aa")) {
//MD5 加密 名文 111 加密后 698d51a19d8a121ce581499d7b701668
manager.createUser(User.withUsername("aa").password("{MD5}698d51a19d8a121ce581499d7b701668").roles("USER").build());

}
if (!manager.userExists("bb")) {
manager.createUser(User.withUsername("bb").password("{noop}222").roles("USER").build());
}
return manager;
}
  • 重启项目
  • 正常运行
  • 通过修改数据库数据添加管理员用户

image-20201016142700235.png

image-20201016142756146.png

  • 访问api http://localhost:8080/admin/api/hi

输入自己定义的管理员用户名密码,访问成功

四、 基于自定义数据库模型的授权与认证

在项目开发中,默认的数据库模型太过于简单,往往不能满足我们业务的需求,SpringSecurity同样支持,自定义数据库模型的授权与认证。

  • 下面接入自定义的数据库模型
  • 持久层框架使用MyBatis-Plus
  • 使用lombok插件简化代码
  • 为工程引入相关依赖
1
2
3
4
5
6
7
8
9
10
xml复制代码<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>

1. 实现UserDetails

之前的案例中通过实现UserDetailsService,并加上注解注入spring容器,Spring Security会自动发现并使用, UserDetailsService也仅仅实现了一个loadUserByUsername()方法,用于获取UserDetails对象 ,UserDetails包含验证所需的一系列信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();

String getPassword();

String getUsername();

boolean isAccountNonExpired();

boolean isAccountNonLocked();

boolean isCredentialsNonExpired();

boolean isEnabled();
}

所以无论数据来源是什么,或者数据库结构如何变化,我们只需要构造一个UserDetails即可。

1.1 实现自己的用户表

1
2
3
4
5
6
7
8
mysql复制代码CREATE TABLE `t_user` (
`id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT,
`username` VARCHAR ( 60 ) NOT NULL,
`password` VARCHAR ( 60 ) NOT NULL,
`enable` TINYINT ( 4 ) NOT NULL DEFAULT '1' COMMENT '用户是否可用',
`roles` text CHARACTER SET utf8mb4 COMMENT '用户角色,多个角色之间用逗号隔开',
PRIMARY KEY ( `id` ), KEY ( `username` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
  • 在username字段上添加索引,提高搜索速度
  • 手动插入两条数据

image-20201016153806726.png

1.2 编写我们的User实体

  • 创建entity包存放实体
  • 新建User实体类
1
2
3
4
5
6
7
8
java复制代码@Data
public class User {
private Long id;
private String username;
private String password;
private String roles;
private boolean enable;
}
  • 实现UserDetails
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
java复制代码@Data
public class User implements UserDetails {
private Long id;
private String username;
private String password;
private String roles;
private boolean enable;

private List<GrantedAuthority> authorities;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return this.enable;
}
}

重写方法

  • isAccountNonExpired()、isAccountNonLocked()、isCredentialsNonExpired()暂时用不到全部返回true
  • isEnabled()对应enable字段
  • getAuthorities()原本对应的是roles字段,但是自己定义的结构变化,所以我们先新建一个authorities,后期进行填充。

1.3 持久层准备

  • 创建mapper包
  • 创建UserMapper
1
2
3
4
5
6
java复制代码@Component
public interface UserMapper extends BaseMapper<User> {

@Select("SELECT * FROM t_user WHERE username = #{username}")
User findByUserName(@Param("username") String username);
}
  • 启动类添加包扫描注解
1
2
3
4
5
6
7
8
java复制代码@SpringBootApplication
@MapperScan("com.yang.springsecurity.mapper")
public class SpringSecurityApplication {

public static void main(String[] args) {
SpringApplication.run(SpringSecurityApplication.class, args);
}
}
  • 编写业务代码
  • 创建service包
  • 创建MyUserDetailsService实现UserDetailsService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码@Service
public class MyUserDetailsService implements UserDetailsService {

@Autowired
private UserMapper userMapper;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询用户信息
User user = userMapper.selectByUsername(username);
if (user==null){
throw new UsernameNotFoundException(username+"用户不存在");
}
//重新填充roles
user.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles()));
return user;
}
}

注意: SpringSecurity5.x 以上版本需要配置加密否则会出现以下异常

1
java复制代码There is no PasswordEncoder mapped for the id "null"
  • 配置默认加密方式
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
java复制代码@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private DataSource dataSource;
@Autowired
private MyUserDetailsService myUserDetailsService;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/api/**").hasRole("ADMIN")
.antMatchers("/user/api/**").hasRole("USER")
.antMatchers("/app/api/**").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/myLogin.html")
// 指定处理登录请求的路径,修改请求的路径,默认为/login
.loginProcessingUrl("/mylogin")
.permitAll()
.and()
.csrf().disable();
}

@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(NoOpPasswordEncoder.getInstance());
}

}
  • 重启项目
  • 访问api http://localhost:8080/admin/api/hi
  • 输入用户名密码
  • 访问成功

到此我们已经实现了自定义的数据库模型的授权与认证,后期可以根据项目需要丰富验证逻辑,加强安全性

这里一直有个问题,为什么我们的数据库里权限需要加上ROLE_前缀?

查看 hasRole() 方法源码就很容易理解了

1
2
3
4
5
6
7
8
9
java复制代码private static String hasRole(String role) {
Assert.notNull(role, "role cannot be null");
if (role.startsWith("ROLE_")) {
throw new IllegalArgumentException(
"role should not start with 'ROLE_' since it is automatically inserted. Got '"
+ role + "'");
}
return "hasRole('ROLE_" + role + "')";
}

如果不想要匹配这个前缀可换成**hasAuthority()**方法

本文转载自: 掘金

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

1…402403404…956

开发者博客

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