疑问
# SpringBoot自启动
实现ApplicationRunner接口,并@Component。
原理,Springboot启动时,先创建context,然后会执行callRunners方法,该方法是找到所有类型为ApplicationRunner的对象,然后执行该对象的run方法。
# SpringSecurity认证
疑问,为什么我实现了userDetailsService
,框架会自动调用我的loadUserByUsername()
方法。
Spring Security框架中有默认的UserDetailsService
实现类InMemoryUserDetailsManager
,为什么会优先实现我的呢?
发现的自动配置类
@Configuration
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean({
AuthenticationManager.class,
AuthenticationProvider.class,
UserDetailsService.class })
public class UserDetailsServiceAutoConfiguration {
private static final String NOOP_PASSWORD_PREFIX = "{noop}";
private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern
.compile("^\\{.+}.*$");
private static final Log logger = LogFactory
.getLog(UserDetailsServiceAutoConfiguration.class);
@Bean
@ConditionalOnMissingBean(type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
@Lazy
public InMemoryUserDetailsManager inMemoryUserDetailsManager(
SecurityProperties properties,
ObjectProvider<PasswordEncoder> passwordEncoder) {
SecurityProperties.User user = properties.getUser();
List<String> roles = user.getRoles();
return new InMemoryUserDetailsManager(User.withUsername(user.getName())
.password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
.roles(StringUtils.toStringArray(roles)).build());
}
private String getOrDeducePassword(SecurityProperties.User user,
PasswordEncoder encoder) {
String password = user.getPassword();
if (user.isPasswordGenerated()) {
logger.info(String.format("%n%nUsing generated security password: %s%n",
user.getPassword()));
}
if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
return password;
}
return NOOP_PASSWORD_PREFIX + password;
}
}
阅读源码可以发现,声明InMemoryUserDetailsManager
Bean是在UserDetailsServiceAutoConfiguration
配置类中,有条件注解
@ConditionalOnMissingBean({
AuthenticationManager.class,
AuthenticationProvider.class,
UserDetailsService.class })
也就是说,若没有类型为UserDetailsService
的Bean,则会创建InMemoryUserDetailsManager
。
加载顺序
先加载@Component
注解的Bean,而后加载@Configuraion
中声明的Bean。
# Jwt、OAuth2、加密解密
OAuth2是一个认证协议,可以获得授权码,通过授权码可以获得令牌并颁发令牌,这里是指普通的UUID令牌。但是可以选择将令牌转换成Jwt令牌,Jwt令牌由header.payload.sign三部分组成,客户端只能解析前面两部分,header包含加密方式,payload载荷数据。使用Jwt令牌好处就是,资源服务可以通过秘钥
自行校验Jwt令牌的合法性,如果校验通过,并任务该请求是合法请求,而无需再进行远程调用认证服务进行认证。
# 非对称加密
所有人都拥有自己的一对公钥和私钥,若A想发消息给B,则A的消息通过B的公钥进行加密,这样这条消息只能通过B的私钥进行解密。
验证签名
如果想判断这条消息是否是A发送,A发送消息时将消息的摘要使用私钥进行加密,得到签名,将签名和消息一并发送给B。B得到后,对签名使用A的公钥进行解密,得到摘要D,然后B对消息生成摘要d,对比D和d是否相同,若不相同,则说明消息被篡改,或者这不是A的签名。
发送者:用自己的私钥进行签名,用对方的公钥进行加密;
接受者:用自己的私钥进行解密,用对方的公钥进行验签。
# MyBatis执行过程
有一个配置连接的xml,一个写SQL语句的xml,和一个接口。
首先,肯定是要通过配置xml文件,会有一个类来解析xml文件,作为inputstream传递给构建工厂,最终创建SqlSession
对象,是MyBatis的顶层API接口,会话访问,执行增删改查。
然后sql,和接口怎么对应起来呢?一个xml文件的id,和接口名相同,这个会绑定起来。然后xml中的有很多标签,那肯定也是有响应的解析类,来解析这些标签。重要的是<select>
标签,他会根据namespace和这个标签的id,生成一个MapperStatement对象,放到一个map中,key为namespace和id,value为MapperStatement对象。
对于调用session.selectOne()
方法,需要传入的mapper接口的方法的权限名加方法名,以及对应的参数。然后底层会拿到那个MapperStatement对象,交给Executor执行,它会生成SQL语句,进行查询缓存,如何查不到,那就得执行JDBC那一套查询流程,然后过程中肯定也有处理Java类型和jdbc类型转换的操作。
对于整合了Springboot的话,那就不需要写生成SqlSessionFactory的代码了,这些Session交给容器来管理。对于session.selectOne()
方法的调用,通过动态代理来调用。
# MyBatis
1 总的来说就是解析主配置文件把主配置文件里的所有信息封装到Configuration这个对象中。
2 稍微细一点就是 通过XmlConfigBuilder解析主配置文件,然后通过XmlMapperBuild解析mappers下映射的所有xml文件(循环解析)。把每个xml中的各个sql解析成一个个MapperStatement对象装在Configuration维护的一个Map集合中,key值是id,value是mapperstatement对象-----然后把解析过的xml的名字和名称空间装在set集合中,通过名称空间反射生成的mapper的class对象以及class对象的代理对象装在Configuration对象维护的mapperRegistry中的Map中。
3简化一点:主要就是把每个sql标签解析成mapperstatement对象装进集合,然后把mapper接口的class对象以及代理对象装进集合,方便后来使用。
4一级缓存是sqlsession级别的,二级缓存是全局的。
# HashMap与ConcurrentHashMap
HashMap中为什么不线程安全
当某个桶是空时,在两个线程同时插入元素到这个桶,可能会覆盖元素
当插入到不同的桶,成员变量的size也会脏读。
JDK1.7中头插法的危害,会造成循环链表,当两个线程同时进行扩容时,一个线程刚开始赋值了e, 和next指针,但另一个线程以及扩容完成,如果e和next指针rehash后仍然在一个桶中,就会导致链表反转,此时当前线程再次进行扩容,就会造成循环链表。
理解上面后,ConcurrentHashMap中保证线程安全的操作就简单了。
JDK1.7中,采用分段锁,插入元素过程中,先从Segments(HashMap)找到具体的Segment,在从Segment中找到具体的Entry,进而进行插入元素。
JDK1.8中,采用CAS + Synchronized,锁粒度更细,实现原理就对应HashMap上面线程不安全。
- 当桶是空时,采用CAS判断,真正插入时判断这个桶是不是被占了,是的话就重试。
- 保证不能让多个线程,各自扩容各自的。
- 在正常增加链表元素或者红黑树节点时,使用Synchronized同步。