1什么是缓存雪崩?何如避免?
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
何如避免
-
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
-
做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
-
不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
2 Redis集群方案应该怎么做?都有哪些方案?
- twemproxy,它类似于一个代理方式,使用方法和普通redis无任何区别,设置好它下属的多个redis实例后,使用时在本需要连接redis的地方改为连接twemproxy,它会以一个代理的身份接收请求并使用一致性hash算法,将请求转接到具体redis,将结果再返回twemproxy。使用方式简便(相对redis只需修改连接端口),是对旧项目扩展的首选。 问题:twemproxy自身单端口实例的压力,使用一致性hash后,对redis节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。
- codis,目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在节点数量改变情况下,旧节点数据可恢复到新hash节点。
- redis cluster3.0自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。
- 在业务代码层实现,起几个毫无关联的redis实例,在代码层,对key 进行hash计算,然后去对应的redis实例操作数据。 这种方式对hash层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。
3 Redis集群方案什么情况下会导致整个集群不可用?
有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用。
4 MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?
redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
5 Redis有哪几种数据淘汰策略?
- noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
- allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
- volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
- allkeys-random: 回收随机的键使得新添加的数据有空间存放。
- volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
- volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
篇幅限制下面就只能给大家展示小册部分内容了。这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
6 Redis有哪些适合的场景?
- 会话缓存(Session Cache):最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。
- 全页缓存(FPC):除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
- 队列:Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。
- 排行榜/计数器:Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”;当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:ZRANGE user_scores 0 10 WITHSCORESAgora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的。
- 发布/订阅:发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!
7 Redis哈希槽的概念
Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
8 Redis集群之间是如何复制的?
异步复制
9 Redis集群会有写操作丢失吗?为什么?
Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。
10 Redis集群最大节点个数是多少?
16384个。
11 Redis集群如何选择数据库?
Redis集群目前无法做数据库选择,默认在0数据库。
12 怎么测试Redis的连通性?
ping
13 Redis中的管道有什么用?
一次请求/响应服务器能实现处理新的请求,即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多POP3协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。
14 怎么理解Redis事务?
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
15 Redis事务相关的命令有哪几个?
MULTI、EXEC、DISCARD、WATCH ##28、Redis key的过期时间和永久有效分别怎么设置? EXPIRE和PERSIST命令。
16 Redis如何做内存优化?
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面.
17 Redis回收进程如何工作的?
一个客户端运行了新的命令,添加了新的数据。Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。一个新的命令被执行,等等。所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。
18 Redis回收使用的是什么算法?
LRU算法
19 Redis如何做大量数据插入?
Redis2.6开始redis-cli支持一种新的被称之为pipe mode的新模式用于执行大量数据插入工作。
2.27 为什么要做Redis分区?
分区可以让Redis管理更大的内存,Redis将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使Redis的计算能力通过简单地增加计算机得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。
2.28 你知道有哪些Redis分区实现方案?
客户端分区就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取。大多数客户端已经实现了客户端分区。代理分区 意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。
2.29 Redis分区有什么缺点?
涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用交集指令)。同时操作多个key,则不能使用Redis事务.分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set).当使用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件。分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题。
2.30 Redis持久化数据和缓存怎么做扩容?
如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。
2.31 分布式Redis是前期做还是后期规模上来了再做好?为什么?
既然Redis是如此的轻量(单实例只使用1M内存),为防止以后的扩容,最好的办法就是一开始就启动较多实例。即便你只有一台服务器,你也可以一开始就让Redis以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。一开始就多设置几个Redis实例,例如32或者64个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。这样的话,当你的数据不断增长,需要更多的Redis服务器时,你需要做的就是仅仅将Redis实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦你添加了另一台服务器,你需要将你一半的Redis实例从第一台机器迁移到第二台机器。
2.32 Twemproxy是什么?
Twemproxy是Twitter维护的(缓存)代理系统,代理Memcached的ASCII协议和Redis协议。它是单线程程序,使用c语言编写,运行起来非常快。它是采用Apache 2.0 license的开源软件。 Twemproxy支持自动分区,如果其代理的其中一个Redis节点不可用时,会自动将该节点排除(这将改变原来的keys-instances的映射关系,所以你应该仅在把Redis当缓存时使用Twemproxy)。 Twemproxy本身不存在单点问题,因为你可以启动多个Twemproxy实例,然后让你的客户端去连接任意一个Twemproxy实例。 Twemproxy是Redis客户端和服务器端的一个中间层,由它来处理分区功能应该不算复杂,并且应该算比较可靠的。
2.33 支持一致性哈希的客户端有哪些?
Redis-rb、Predis等。
2.34 Redis与其他key-value存储有什么不同?
Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,应为数据量不能大于硬件内存。在内存数据库方面的另一个优点是, 相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。 同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
2.35 Redis的内存占用情况怎么样?
给你举个例子: 100万个键值对(键是0到999999值是字符串“hello world”)在我的32位的Mac笔记本上 用了100MB。同样的数据放到一个key里只需要16MB, 这是因为键值有一个很大的开销。 在Memcached上执行也是类似的结果,但是相对Redis的开销要小一点点,因为Redis会记录类型信息引用计数等等。当然,大键值对时两者的比例要好很多。64位的系统比32位的需要更多的内存开销,尤其是键值对都较小时,这是因为64位的系统里指针占用了8个字节。 但是,当然,64位系统支持更大的内存,所以为了运行大型的Redis服务器或多或少的需要使用64位的系统。
2.36 都有哪些办法可以降低Redis的内存使用情况呢?
如果你使用的是32位的Redis实例,可以好好利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。
2.37 查看Redis使用情况及状态信息用什么命令?
info
2.38 Redis的内存用完了会发生什么?
如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将Redis当缓存来使用配置淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。
2.39 Redis是单线程的,如何提高多核CPU的利用率?
可以在同一个服务器部署多个Redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果你想使用多个CPU,你可以考虑一下分片(shard)。
2.40 一个Redis实例最多能存放多少的keys?
List、Set、Sorted Set他们最多能存放多少元素?理论上Redis可以处理多达232的keys,并且在实际中进行了测试,每个实例至少存放了2亿5千万的keys。我们正在测试一些较大的值。任何list、set、和sorted set都可以放232个元素。换句话说,Redis的存储极限是系统中的可用内存值。
2.41 Redis常见性能问题和解决方案?
(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
2.42 Redis提供了哪几种持久化方式?
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始。
2.43 如何选择合适的持久化方式?
一般来说, 如果想达到足以媲美PostgreSQL的数据安全性, 你应该同时使用两种持久化功能。如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用RDB持久化。有很多用户都只使用AOF持久化,但并不推荐这种方式:因为定时生成RDB快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快,除此之外, 使用RDB还可以避免之前提到的AOF程序的bug。
2.44 修改配置不重启Redis会实时生效吗?
针对运行实例,有许多配置选项可以通过 CONFIG SET 命令进行修改,而无需执行任何形式的重启。 从 Redis 2.2 开始,可以从 AOF 切换到 RDB 的快照持久性或其他方式而不需要重启 Redis。检索 ‘CONFIG GET *’ 命令获取更多信息。但偶尔重新启动是必须的,如为升级 Redis 程序到新的版本,或者当你需要修改某些目前 CONFIG 命令还不支持的配置参数的时候。
三、Shiro
3.1 什么是shiro
Shiro是一个强大易用的java安全框架,提供了认证、授权、加密、会话管理、与web集成、缓存等功能,对于任何一个应用程序,都可以提供全面的安全服务,相比其他安全框架,shiro要简单的多。
3.2 Shiro的核心概念Subject、SecurityManager、Realm
Subject
主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如爬虫、机器人等,即Subject是一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者。
SecurityManager
安全管理器,即所有与安全有关的操作都会与SecurityManager交互,且它管理着所有Subject;可以看出它是shiro的核心, SecurityManager相当于SpringMVC中的DispatcherServlet前端控制器。
Realm
域,shiro从Realm获取安全数据(如用户、角色、权限),Realm是与持久层交互的桥梁,就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
3.3 Authentication 身份验证
principals
身份,即主体的标识属性,可以是任何东西,如用手机号、户名、邮箱等,唯一即可。
credentials
证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
身份认证流程
- 首先调用Subject.login(token)进行登录,其会自动委托给SecurityManager,调用之前必须通过SecurityUtils.setSecurityManager()设置;
- SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;
- Authenticator才是真正的身份验证者,是Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现;
- Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;
- Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。
Authenticator及AuthenticationStrategy
- Authenticator的职责是验证用户账号,是Shiro API中身份验证核心的入口点。
- AuthenticationStrategy 认证策略 ModularRealmAuthenticator默认使用AtLeastOneSuccessfulStrategy策略
- FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略;
- AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,返回所有Realm身份验证成功的认证信息;
- AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
自定义实现认证时一般继承AbstractAuthenticationStrategy即可
3.4 Authorization 授权
授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角(Role)
授权方式:
- 编程式:通过写if/else授权代码完成
Subject subject = SecurityUtils.getSubject();
If(subject.hasRole(“admin”){
// 有权限
}else{
// 无权限
} - 注解
@RequiresRoles(“admin”)
public void helloWord(){
// 有权限
} - Jsp/gsp标签
<shiro:hasRole name = “admin”>
<!—有权限
</shiro:hasRole>
基于资源的访问控制
1. 隐式角色:硬编码的方式(if/else);粗粒度造成的问题:如果有一天不需要了那么就需要修改相应代码把所有相关的地方进行删除; - 显示角色:规则:资源标识符:操作(user:create,user:update)这种方式叫资源级别的粒度;好处:如果需要修改都是一个资源级别的修改,不会对其他模块代码产生影响,粒度小;但实现起来可能稍微复杂点,需要维护“用户—角色,角色—权限(资源:操作)”之间的关系
Permission
字符串通配符权限
规则:资源标识符 : 操作 : 对象实例ID
“:”表示资源/操作/实例的分割
“,”表示操作的分割
“*”表示任意资源/操作/实例 - 单个资源多个权限
Role=system:user:update,system:user:delete
等价于role=system:user:update,delete,但是反过来是规则不成立
代码判断
subject().checkPermissions(“system:user:update,delete”) - 单个资源全部权限:role=sys:user:*/sys:user
- 所有资源全部权限:role=*:view;subject.checkPermissions(“user:view”);
- 实例级别的权限
单实多限:role=”user:update,delete:1”;
subject().checkPermissions(”user:update,delete:1”);
all实单限:role=”user:auth:”;
subject().checkPermissions(“user:auth:1”, “user:auth:2”);
all实all限:role=”user:?”;
subject().checkPermissions(“user:view:1”, “user:auth:2”);
授权流程:
- 首先调用Subject.isPermitted
hasRole
接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer; - Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”), 其首先会通过PermissionResolver把字符串转换成相应的Permission实例;在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
- Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给 ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted
hasRole
会返回true, 否则返回false表示授权失败。
3.5 加密
编码/解码
Shiro提供了base64和16进制字符串编码/解码的API支持,方便一些编码解码操作
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-java"><span style="color:#708090">//编码</span>
Base64<span style="color:#999999">.</span><span style="color:#dd4a68">encodeToString</span><span style="color:#999999">(</span>str<span style="color:#999999">.</span><span style="color:#dd4a68">getBytes</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">)</span>
<span style="color:#708090">//解码</span>
Base64<span style="color:#999999">.</span><span style="color:#dd4a68">decodeToString</span><span style="color:#999999">(</span>base64Encoded<span style="color:#999999">)</span>
</code></span></span>
- 1
- 2
- 3
- 4
散列算法
常见散列算法如MD5,SHA等
- 首先创建一个DfaultHashService,默认使用SHA-512算法;
- 可以通过hashAlgorithmName属性修改算法;
- 可以通过privateSalt设置一个私盐,其在散列时自动与用户传入的公盐混合产生一个新盐;
- 可以通过generatePublicSalt属性在用户没有传入公盐的情况下设置是否生成公盐;
- 可以设置randomNumberGenerator用于生成公盐;
- 可以设置hashIterations属性来修改默认加密迭代次数;
- 需要构建一个HashRequest,传入算法、数据、公盐、迭代次数。
生成随机数
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-java">SecureRandomNumberGenerator randomNumberGenerator <span style="color:#a67f59">=</span> <span style="color:#0077aa">new</span> SecureRandomNumberGenerator<span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
randomNumberGenerator<span style="color:#999999">.</span><span style="color:#dd4a68">setSeed</span><span style="color:#999999">(</span>“<span style="color:#986801">159</span>”<span style="color:#999999">.</span><span style="color:#dd4a68">getBytes</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
String hex <span style="color:#a67f59">=</span> randomNumberGenerator<span style="color:#999999">.</span><span style="color:#dd4a68">nextBytes</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">.</span><span style="color:#dd4a68">toHex</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
</code></span></span>
- 1
- 2
- 3
加密/解密
提供对称式加密/解密算法的支持,如AES、Blowfish等
PasswordService/CredentialsMatcher用于提供加密密码及验证密码服务
Shiro默认提供了PasswordService实现DefaultPasswordService;CredentialsMatcher实现PasswordMatcher及HashedCredentialsMatcher(更强大)
HashedCredentialsMatcher实现密码验证服务
Shiro提供了CredentialsMatcher的散列实现HashedCredentialsMatcher,和PasswordMatcher不同的是,它只是用于密码验证,且可以提供自己的盐,而不是随机生成盐,且生成密码散列值的算法需要自己写,因为能提供自己的盐;
3.5 Realm 域
定义Realm(自定义Realm继承AuthorizingRealm即可)
- UserRealm父类AuthorizingRealm将获取Subject相关信息分成两步:获取身份验证信息(doGetAuthenticationInfo)及授权信息(doGetAuthorizationInfo)
- doGetAuthenticationInfo获取身份验证相关信息:首先根据传入的用户名获取User信息;如果user为空,那么抛出没找到账号异常UnknownAccountExecption;如果user找到但却被锁定了抛出锁定异常LockedAccountException;最后生成AuthenticationInfo信息,交给间接父类AuthenticatingRealm使用CredentialsMatcher进行判断密码是否匹配,如果不匹配将抛出密码错误异常信息IncorrectCredentialsException;如果密码重试次数太多将抛出超出重试次数异常ExcessiveAttemptsException;在组装SimpleAuthenticationInfo信息时,需要传入:身份信息(用户名)、凭据(密文密码)、盐(username+salt),CredentialsMatcher使用盐加密传入的明文密码和此处的密文密码进行匹配。
- doGetAuthorizationInfo获取授权信息:PrincipalCollection是一个身份集合,因为只用到了一个Realm,所以直接调用getPrimaryPrincipal得到之前传入的用户名即可;然后根据用户名调用UserService接口获取角色及权限信息。
AuthenticationInfo的两个作用
- 如果Realm是AuthenticatingRealm子类,则提供给AuthenticatingRealm内部使用的CredentialsMatcher进行凭据验证;(如果没有继承它需要在自己的Realm中实现验证);
- 提供给SecurityManager来创建Subject(提供身份信息);
3.6 拦截器
基于表单登录拦截器
onPreHandle主要流程:
- 首先判断是否已经登录过了,如果已经登录过了继续拦截器链即可;
- 如果没有登录,看看是否是登录请求,如果是get方法的登录页面请求,则继续拦截器链(到请求页面),否则如果是get方法的其他页面请求则保存当前请求并重定向到登录页面;
- 如果是post方法的登录页面表单提交请求,则收集用户名/密码登录即可,如果失败了保存错误消息到“shiroLoginFailure”并返回到登录页面;
- 如果登录成功了,且之前有保存的请求,则重定向到之前的这个请求,否则到默认的成功页面。
任意角色授权拦截器
流程:
- 首先判断用户有没有任意角色,如果没有返回false,将到onAccessDenied进行处理;
- 如果用户没有角色,接着判断用户有没有登录,如果没有登录先重定向到登录;
- 如果用户没有角色且设置了未授权页面(unauthorizedUrl),那么重定向到未授权页面;否则直接返回401未授权错误码。
默认拦截器
身份验证相关的
authc 基于表单的拦截器,即验证成功之后才能访问 /=authc
authcBasic Basic HTTP身份验证拦截器,主要属性:applicationName
logout 退出 /logout=logout
user 用户拦截器 /=user
anon 匿名拦截器,一般用于静态资源过滤 /static/=anon
授权相关的
roles 角色授权拦截器,主要属性:loginUrl,unauthorizedUrl /admin/=roles[admin]
perms 权限授权拦截器 /user/**=perms[“user:create”]
port 端口拦截器,主要属性: port(80) /test=port[80]
rest rest风格拦截器 /users=rest[user],会自动拼接出“user:read,user:create,user:update,user:delete”
ssl ssl拦截器,只有请求协议是https才能通过
3.7 Session Manager 会话管理
Session
所谓session,即用户访问应用时保持的连接关系,在多次交互中应用能够识别出当前访问的用户是谁,且可以在多次交互中保存一些数据。
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-java">Subject subject <span style="color:#a67f59">=</span> SecurityUtils<span style="color:#999999">.</span><span style="color:#dd4a68">getSubject</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
Session session <span style="color:#a67f59">=</span> subject<span style="color:#999999">.</span><span style="color:#dd4a68">getSession</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
session<span style="color:#999999">.</span><span style="color:#dd4a68">getId</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span> <span style="color:#708090">// 获取当前session的唯一标识</span>
session<span style="color:#999999">.</span><span style="color:#dd4a68">getHost</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span> <span style="color:#708090">// 获取当前Subject的主机地址,该地址是通过HostAuthenticationToken.getHost()提供的</span>
session<span style="color:#999999">.</span><span style="color:#dd4a68">getTimeOut</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span> <span style="color:#708090">// 获取超时时间</span>
session<span style="color:#999999">.</span><span style="color:#dd4a68">setTimeOut</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span> <span style="color:#708090">// 设置超时时间(不设置默认是全局过期时间)</span>
session<span style="color:#999999">.</span><span style="color:#dd4a68">touch</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span> <span style="color:#708090">// 更新最后访问时间</span>
session<span style="color:#999999">.</span><span style="color:#dd4a68">stop</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span> <span style="color:#708090">// 销毁session,当Subject.logout()时会自动调用stop方法来销毁会话。如果在web中,调用javax.servlet.http.HttpSession.invalidate()也会自动调用shiro session.top方法进行销毁shiro的会话</span>
session<span style="color:#999999">.</span><span style="color:#dd4a68">setAttribute</span><span style="color:#999999">(</span>“key”<span style="color:#999999">,</span>”<span style="color:#986801">123</span>”<span style="color:#999999">)</span><span style="color:#999999">;</span> <span style="color:#708090">// 设置session属性</span>
session<span style="color:#999999">.</span><span style="color:#dd4a68">getAttribute</span><span style="color:#999999">(</span>“key”<span style="color:#999999">)</span><span style="color:#999999">;</span> <span style="color:#708090">// 获取session属性</span>
session<span style="color:#999999">.</span><span style="color:#dd4a68">removeAttribute</span><span style="color:#999999">(</span>“key”<span style="color:#999999">)</span><span style="color:#999999">;</span> <span style="color:#708090">// 删除属性</span>
</code></span></span>
注:Shiro提供的会话可以用于javaSE/javaEE环境,不依赖于任何底层容器,可以独立使用,是完整的会话模块。
Session manager 会话管理器
会话管理器管理着应用中所有Subject的会话的创建、维护、删除、失效、验证等工作。是Shiro的核心组件,顶层组件SecurityManager直接继承了SessionManager,且提供了SessionSecurityManager实现直接把会话管理委托给相应的SessionManager、DefaultSecurityManager及DefaultWebSecurityManager 默认SecurityManager都继承了SessionSecurityManager。
Shiro提供了三个默认实现
- DefaultSessionManager:DefaultSecurityManager使用的默认实现,用于JavaSE环境;
- ServletContainerSessionManager: DefaultWebSecurityManager使用的默认实现,用于Web环境,其直接使用Servlet容器的会话;
- DefaultWebSessionManager:用于Web环境的实现,可以替代ServletContainerSessionManager,自己维护着会话,直接废弃了Servlet容器的会话管理。
3.8 Shiro注解
- @RequiresAuthentication : 表示当前Subject已经通过login进行了身份验证;即 Subject.isAuthenticated() 返回 true
- @RequiresUser : 表示当前Subject 已经身份验证或者通过
记住我
登录的 - @RequiresGuest : 表示当前Subject没有身份验证或通过
记住我
登陆过,即是游客身份 - @RequiresRoles(value = { “admin”, “user” }, logical = Logical.AND) : 表示当前 Subject 需要角色 admin和user
- @RequiresPermissions(value = { “user:a”, “user:b” }, logical = Logical.OR) : 表示当前 Subject 需要权限 user:a 或 user:b
3.9 shiro的优点
- 简单的身份验证,支持多种数据源
- 对角色的简单授权,支持细粒度的授权(方法)
- 支持一级缓存,以提升应用程序的性能
- 内置基于POJO的企业会话管理,适用于web及非web环境
- 非常简单的API加密
- 不跟任何框架绑定,可以独立运行
篇幅限制下面就只能给大家展示小册部分内容了。这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
四 集合
4.1 ArrayList、LinkedList、Vector
都继承Collection接口
ArrayList
有序可重复,底层是数组结构,查询快,增删慢,线程不安全
LinkedList
有序可重复,底层是链表结构,查询慢,增删快,线程不安全
Vector
Vector使用了synchronized方法-线程安全,性能上比ArrayList差一点
4.2 Set
和List一样,继承Collection接口,不同的是Set集合是不可重复的(不一定是无序的),并且最多只能允许一个null值。Set常见的实现类有:HashSet、TreeSet和LinkedHashSet。
HashSet
HashSet是一个没有重复元素的集合。它是由HashMap实现的,不能保证元素的顺序,重要的是HashSet允许使用null元素。
HashSet是非同步的(线程不安全)。如果多个线程同时访问一个hashset,而其中至少一个线程修改了该hashset,那么它必须保持外部同步。
TreeSet
是一个有序的集合,是一个set集合,线程不安全。继承abstractset,实现了navigableset,cloneable,serializable接口。
LinkedHashSet
是一个有序,线程不安全集合
4.3 Map
HashMap、HashTable都继承Map接口
HashMap
线程不安全,使用HashMap要注意避免集合的扩容,它会很耗性能,根据元素的数量给它一个初始大小的值(HashMap是数组和链表组成的,默认大小为16,当hashmap中的元素个数超过数组大小*loadFactor(默认值为0.75)时就会把数组的大小扩展为原来的两倍大小,然后重新计算每个元素在数组中的位置)。
HashMap的键值都可以为NULL,HashTable不行。
HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,采用哈希表来存储的;
HashTable
线程安全,key、value不可以为null;
LinkedHashMap
按照添加顺序存储元素(按自然顺序存储元素则使用TreeMap,按照自定义顺序存储元素也使用TreeMap(Comparetor c))
有没有有顺序的 Map 实现类? 如果有, 他们是怎么保证有序的?
TreeMap和LinkedHashMap是有序的(TreeMap默认升序,LinkedHashMap则记录了插入顺序)。
五 Oracle数据库
5.1 数据库的三大范式
-
第一范式:原子件,要求每一列的值不能再拆分了。
-
第二范式: 一张表只描述一个实体(若列中有冗余数据,则不满足)
-
第三范式: 所有列与主键值直接相关。
5.2 事务的特性(ACID)
-
原子性(Atomic): 事务中的各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事务的失败。
-
一致性(Consistent): 事务前后数据的完整性必须保持一致。
-
隔离性(Isolated):多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
-
持久性(Durable):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
5.3 oracle中 dml、ddl、dcl
Dml 数据操做语言,如select、update、delete,insert
Ddl 数据定义语言,如create table 、drop table、Truncate 等等
Dcl 数据控制语言, 如 commit、 rollback、grant、 invoke等
5.4 索引
建索引的原则
- 索引字段建议建立NOT NULL约束
- 经常与其他表进行连接的表,在连接字段上应该建立索引;
- 经常出现在Where子句中的字段且过滤性很强的,特别是大表的字段,应该建立索引;
- 可选择性高的关键字 ,应该建立索引;
- 不要将那些频繁修改的列作为索引列;
索引缺点
- 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
- 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间
- 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,降低了数据的维护速度
- 索引创建在表上,不能创建在视图上
5.5 oracle中的经常使用到得函数
Length 长度、 lower 小写、upper 大写, to_date 转化日期, to_char转化字符
Ltrim 去左边空格、 rtrim去右边空格,substr取字串,add_month增加或者减掉月份、to_number转变为数字
5.6 Oracle是怎样分页的
Oracle中使用rownum来进行分页, 这个是效率最好的分页方法,hibernate也是使用rownum来进行oralce分页的
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-sql"><span style="color:#0077aa">select</span> <span style="color:#a67f59">*</span> <span style="color:#0077aa">from</span>
<span style="color:#999999">(</span> <span style="color:#0077aa">select</span> rownum r<span style="color:#999999">,</span>a <span style="color:#0077aa">from</span> tabName <span style="color:#0077aa">where</span> rownum <span style="color:#a67f59"><=</span> <span style="color:#986801">20</span> <span style="color:#999999">)</span>
<span style="color:#0077aa">where</span> r <span style="color:#a67f59">></span> <span style="color:#986801">10</span>
</code></span></span>
- 1
- 2
- 3
5.7 truncate和delete命令的区别
- Truncate 和delete都可以将数据实体删掉,truncate 的操作并不记录到 rollback日志,所以操作速度较快,但同时这个数据不能恢复
- Delete操作不腾出表空间的空间
- Truncate 不能对视图等进行删除
- Truncate是数据定义语言(DDL),而delete是数据操纵语言(DML)
5.8 什么是死锁,如何解决Oracle中的死锁
死锁
就是存在加了锁而没有解锁,可能是使用锁没有提交或者没有回滚事务;如果是表级锁则不能操作表,客户端处于等在状态,如果是行级锁则不能操作锁定行;
解决死锁
- 查找出被锁的表
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-sql"><span style="color:#0077aa">select</span> b<span style="color:#999999">.</span>owner<span style="color:#999999">,</span>b<span style="color:#999999">.</span>object_name<span style="color:#999999">,</span>a<span style="color:#999999">.</span>session_id<span style="color:#999999">,</span>a<span style="color:#999999">.</span>locked_mode
<span style="color:#0077aa">from</span> v$locked_object a<span style="color:#999999">,</span>dba_objects b
<span style="color:#0077aa">where</span> b<span style="color:#999999">.</span>object_id <span style="color:#a67f59">=</span> a<span style="color:#999999">.</span>object_id<span style="color:#999999">;</span>
<span style="color:#0077aa">select</span> b<span style="color:#999999">.</span>username<span style="color:#999999">,</span>b<span style="color:#999999">.</span>sid<span style="color:#999999">,</span>b<span style="color:#999999">.</span><span style="color:#0077aa">serial</span><span style="color:#708090">#,logon_time</span>
<span style="color:#0077aa">from</span> v$locked_object a<span style="color:#999999">,</span>v$<span style="color:#0077aa">session</span> b
<span style="color:#0077aa">where</span> a<span style="color:#999999">.</span>session_id <span style="color:#a67f59">=</span> b<span style="color:#999999">.</span>sid <span style="color:#0077aa">order</span> <span style="color:#0077aa">by</span> b<span style="color:#999999">.</span>logon_time<span style="color:#999999">;</span>
</code></span></span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 杀进程中的会话
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-sql"><span style="color:#0077aa">alter</span> system <span style="color:#0077aa">kill</span> <span style="color:#0077aa">session</span> <span style="color:#50a14f">"sid,serial#"</span><span style="color:#999999">;</span>
</code></span></span>
- 1
5.9 四种隔离级别
-
读未提交(Read uncommitted):
这种事务隔离级别下,select语句不加锁。
此时,可能读取到不一致的数据,即“读脏 ”。这是并发最高,一致性最差的隔离级别。 -
读已提交(Read committed):
可避免 脏读 的发生。
在互联网大数据量,高并发量的场景下,几乎 不会使用 上述两种隔离级别。 -
可重复读(Repeatable read):
MySql默认隔离级别。
可避免脏读 、不可避免重复读的发生。 -
串行化(Serializable ):
可避免 脏读、不可重复读、幻读 的发生。
以上四种隔离级别最高的是 Serializable 级别,最低的是 Read uncommitted 级别,当然级别越高,执行效率就越低。像 Serializable 这样的级别,就是以 锁表 的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读) 。
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读) ;而在 Oracle数据库 中,只支持Serializable (串行化) 级别和 Read committed (读已提交) 这两种级别,其中默认的为 Read committed(读已提交) 级别。
六 SpringBoot
6.1 什么是 SpringBoot
SpringBoot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。Spring Boot 与传统项目最大的区别是,传统项目都是打成 WAR 包部署到服务器上面,需要额外的 Servlet 容器, 而 Spring Boot 则可以直接打成 jar包,并内置集成了 Servlet 容器,通过命令 java -jar xx.jar 则可以直接运行,不需要独立的 Servlet 容器。
6.2 SpringBoot优点
一、独立运行
Spring Boot而且内嵌了各种servlet容器,Tomcat、Jetty等,现在不再需要打成war包部署到容器中,Spring Boot只要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内。
二、简化配置
spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。相对于SpringMVC,SpringBoot无需XML配置文件就能完成所有配置工作
6.3 SpringBoot 的核心配置文件有哪几个?它们的区别是什么?
Spring Boot 中有以下两种配置文件
- bootstrap (.yml 或者 .properties)
- application (.yml 或者 .properties)
bootstrap与application 的区别
Spring Cloud 构建于 Spring Boot 之上,在 Spring Boot 中有两种上下文,一种是 bootstrap,,另外一种是 application,,bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于 applicaton。bootstrap 主要用于从额外的资源来加载配置信息,还可以在本地外部配置文件中解密属性。这两个上下文共用一个环境,它是任何Spring应用程序的外部属性的来源。bootstrap 里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。
所以,对比 application 配置文件,bootstrap 配置文件具有以下几个特性:
-
boostrap 由父 ApplicationContext 加载,比 applicaton 优先加载
-
boostrap 里面的属性不能被覆盖
bootstrap与application 的应用场景
application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。
bootstrap 配置文件有以下几个应用场景:
- 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
- 一些固定的不能被覆盖的属性
- 一些加密/解密的场景。
6.4 Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 SpringBoot 的核心注解,其主要组合包含以下 3 个注解:
-
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
-
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
-
@ComponentScan:Spring组件扫描。
6.5 开启 Spring Boot 特性有哪几种方式?
两种方式
方式一
继承spring-boot-starter-parent项目
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-yaml"><parent<span style="color:#999999">></span>
<groupId<span style="color:#999999">></span>org.springframework.boot</groupId<span style="color:#999999">></span>
<artifactId<span style="color:#999999">></span>spring<span style="color:#999999">-</span>boot<span style="color:#999999">-</span>starter<span style="color:#999999">-</span>parent</artifactId<span style="color:#999999">></span>
<version<span style="color:#999999">></span>1.5.6.RELEASE</version<span style="color:#999999">></span>
</parent<span style="color:#999999">></span>
</code></span></span>
- 1
- 2
- 3
- 4
- 5
方式二
导入spring-boot-dependencies项目依赖
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-yaml"><dependencyManagement<span style="color:#999999">></span>
<dependencies<span style="color:#999999">></span>
<dependency<span style="color:#999999">></span>
<groupId<span style="color:#999999">></span>org.springframework.boot</groupId<span style="color:#999999">></span>
<artifactId<span style="color:#999999">></span>spring<span style="color:#999999">-</span>boot<span style="color:#999999">-</span>dependencies</artifactId<span style="color:#999999">></span>
<version<span style="color:#999999">></span>1.5.6.RELEASE</version<span style="color:#999999">></span>
<type<span style="color:#999999">></span>pom</type<span style="color:#999999">></span>
<scope<span style="color:#999999">></span>import</scope<span style="color:#999999">></span>
</dependency<span style="color:#999999">></span>
<dependencies<span style="color:#999999">></span>
</dependencyManagement<span style="color:#999999">></span>
</code></span></span>
6.6 如何在 Spring Boot 启动的时候运行一些特定的代码?
如果你想在Spring Boot启动的时候运行一些特定的代码,你可以实现接口ApplicationRunner或者CommandLineRunner,这两个接口实现方式一样,它们都只提供了一个run方法。
使用方式:
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-java"><span style="color:#0077aa">package</span> com<span style="color:#999999">.</span>qf<span style="color:#999999">.</span>service<span style="color:#999999">.</span>impl<span style="color:#999999">;</span>
<span style="color:#0077aa">import</span> org<span style="color:#999999">.</span>springframework<span style="color:#999999">.</span>boot<span style="color:#999999">.</span>CommandLineRunner<span style="color:#999999">;</span>
<span style="color:#0077aa">import</span> org<span style="color:#999999">.</span>springframework<span style="color:#999999">.</span>stereotype<span style="color:#999999">.</span>Component<span style="color:#999999">;</span>
<span style="color:#708090">/**
* @Author: sgw
* @Date 2020/3/8 16:12
* @Description: TODO
**/</span>
<span style="color:#999999">@Component</span>
<span style="color:#0077aa">public</span> <span style="color:#0077aa">class</span> MyBean <span style="color:#0077aa">implements</span> CommandLineRunner <span style="color:#999999">{</span>
<span style="color:#999999">@Override</span>
<span style="color:#0077aa">public</span> <span style="color:#0077aa">void</span> <span style="color:#dd4a68">run</span><span style="color:#999999">(</span>String<span style="color:#999999">.</span><span style="color:#999999">.</span><span style="color:#999999">.</span> args<span style="color:#999999">)</span> <span style="color:#0077aa">throws</span> Exception <span style="color:#999999">{</span>
System<span style="color:#999999">.</span>out<span style="color:#999999">.</span><span style="color:#dd4a68">println</span><span style="color:#999999">(</span><span style="color:#50a14f">"启动项目的时候执行这里的代码"</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#999999">}</span>
</code></span></span>
启动顺序
如果启动的时候有多个ApplicationRunner和CommandLineRunner,想控制它们的启动顺序,可以实现 org.springframework.core.Ordered接口或者使用 org.springframework.core.annotation.Order注解。
6.7 读取配置文件
读取application文件
在application.yml或者properties文件中添加:
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-java">info<span style="color:#999999">.</span>address<span style="color:#a67f59">=</span>China
</code></span></span>
- 1
方式一、在程序里的属性上加注解读取
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-java"><span style="color:#999999">@Value</span><span style="color:#999999">(</span><span style="color:#50a14f">"${info.address}"</span><span style="color:#999999">)</span>
<span style="color:#0077aa">private</span> String addr<span style="color:#999999">;</span>
</code></span></span>
- 1
- 2
方式二、在类上加注解,添加前缀后,将属性名称对应上即可
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-java"><span style="color:#999999">@Component</span>
<span style="color:#999999">@ConfigurationProperties</span><span style="color:#999999">(</span>prefix<span style="color:#a67f59">=</span><span style="color:#50a14f">"info"</span><span style="color:#999999">)</span>
<span style="color:#0077aa">public</span> <span style="color:#0077aa">class</span> MyTest<span style="color:#999999">{</span>
<span style="color:#0077aa">private</span> String addr<span style="color:#999999">;</span>
<span style="color:#708090">//get/set方法......</span>
<span style="color:#999999">}</span>
</code></span></span>
读取指定文件
资源目录下建立config/db-config.properties:
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-java">db<span style="color:#999999">.</span>username<span style="color:#a67f59">=</span>root
db<span style="color:#999999">.</span>password<span style="color:#a67f59">=</span><span style="color:#986801">123456</span>
</code></span></span>
- 1
- 2
方式一、@PropertySource+@Value注解读取方式:
<span style="color:#000000"><span style="background-color:#fafafa"><code class="language-java"><span style="color:#0077aa">package</span> com<span style="color:#999999">.</span>qf<span style="color:#999999">.</span>service<span style="color:#999999">.</span>impl<span style="color:#999999">;</span>
<span style="color:#0077aa">import</span> org<span style="color:#999999">.</span>springframework<span style="color:#999999">.</span>beans<span style="color:#999999">.</span>factory<span style="color:#999999">.</span>annotation<span style="color:#999999">.</span>Value<span style="color:#999999">;</span>
<span style="color:#0077aa">import</span> org<span style="color:#999999">.</span>springframework<span style="color:#999999">.</span>boot<span style="color:#999999">.</span>CommandLineRunner<span style="color:#999999">;</span>
<span style="color:#0077aa">import</span> org<span style="color:#999999">.</span>springframework<span style="color:#999999">.</span>context<span style="color:#999999">.</span>annotation<span style="color:#999999">.</span>PropertySource<span style="color:#999999">;</span>
<span style="color:#0077aa">import</span> org<span style="color:#999999">.</span>springframework<span style="color:#999999">.</span>stereotype<span style="color:#999999">.</span>Component<span style="color:#999999">;</span>
<span style="color:#708090">/**
* @Author: sgw
* @Date 2020/3/8 16:12
* @Description: TODO
**/</span>
<span style="color:#999999">@Component</span>
<span style="color:#999999">@PropertySource</span><span style="color:#999999">(</span>value <span style="color:#a67f59">=</span> <span style="color:#999999">{</span><span style="color:#50a14f">"config/db-config.properties"</span><span style="color:#999999">}</span><span style="color:#999999">)</span>
<span style="color:#0077aa">public</span> <span style="color:#0077aa">class</span> MyBean <span style="color:#999999">{</span>
<span style="color:#999999">@Value</span><span style="color:#999999">(</span><span style="color:#50a14f">"${db.username}"</span><span style="color:#999999">)</span>
<span style="color:#0077aa">private</span> String userName<span style="color:#999999">;</span>
<span style="color:#999999">@Value</span><span style="color:#999999">(</span><span style="color:#50a14f">"${db.password}"</span><span style="color:#999999">)</span>
<span style="color:#0077aa">private</span> String passWord<span style="color:#999999">;</span>
<span style="color:#0077aa">public</span> String <span style="color:#dd4a68">getUserName</span><span style="color:#999999">(</span><span style="color:#999999">)</span> <span style="color:#999999">{</span>
<span style="color:#0077aa">return</span> userName<span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#0077aa">public</span> <span style="color:#0077aa">void</span> <span style="color:#dd4a68">setUserName</span><span style="color:#999999">(</span>String userName<span style="color:#999999">)</span> <span style="color:#999999">{</span>
<span style="color:#0077aa">this</span><span style="color:#999999">.</span>userName <span style="color:#a67f59">=</span> userName<span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#0077aa">public</span> String <span style="color:#dd4a68">getPassWord</span><span style="color:#999999">(</span><span style="color:#999999">)</span> <span style="color:#999999">{</span>
<span style="color:#0077aa">return</span> passWord<span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#0077aa">public</span> <span style="color:#0077aa">void</span> <span style="color:#dd4a68">setPassWord</span><span style="color:#999999">(</span>String passWord<span style="color:#999999">)</span> <span style="color:#999999">{</span>
<span style="color:#0077aa">this</span><span style="color:#999999">.</span>passWord <span style="color:#a67f59">=</span> passWord<span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#999999">}</span>
</code></span></span>
注意:@PropertySource不支持yml文件读取。
方式二、@PropertySource+@ConfigurationProperties注解读取方式:
6.8 SpringBoot 支持哪些日志框架?推荐和默认的日志框架是哪个?
Spring Boot支持Java Util Logging,Log4j2,Lockback作为日志框架,如果你使用starters启动器,Spring Boot将使用Logback作为默认日志框架。无论使用哪种日志框架,Spring Boot都支持配置将日志输出到控制台或者文件中。
spring-boot-starter启动器包含spring-boot-starter-logging启动器并集成了slf4j日志抽象及Logback日志框架。
自定义日志文件
根据不同的日志框架,默认加载的日志配置文件的文件名,放在资源根目录下,其他的目录及文件名不能被加载
既然默认自带了Logback框架,Logback也是最优秀的日志框架,往资源目录下创建一个logback-spring.xml即可。
强烈推荐使用logback-spring.xml作为文件名,因为logback.xml加载太早。