-
-
个人笔记,如有描述不当,欢迎留言指出~
spring 日记
Bean
@Scope(作用域注解)
关键属性:
scopeName
:作用域名,”prototype”、”singleton”、”request”、”session”proxyMode
:代理模式,ScopedProxyMode. DEFAULT(默认为NO)、ScopedProxyMode.NO(不代理)、ScopedProxyMode.INTERFACES(基于jdk接口代理)、ScopedProxyMode.TARGET_CLASS(基于cglib代理)
用法:
1 |
|
或者
1 |
|
NOTE:
若scopeName=”prototype”,proxyMode=ScopedProxyMode.NO,那每次都会得到一个新的bean;
若scopeName=”prototype”,proxyMode=ScopedProxyMode.INTERFACES或TARGET_CLASS, 那么会被注入一个代理类(它是单例并非原型),代理类里根据scopeName来返回具体的bean。
缓存
cacheManager
cacheManager(缓存管理器)可以注册多个,但是id必须不同,另外必须指定其中一个cacheManager上加上@Primary
,否则CacheAspectSupport
(cache切面类)中获取cacheManager bean时返回多个会报错。
也可以不指定@Primary
,但要注册一个cache配置类继承CachingConfigurerSupport
,并覆盖其中cacheManager()方法,返回一个已注册的cacheManager
@Cacheable
中可以指定cacheManager, 故可以实现不同的缓存方式
EhcacheCacheManage
、JcacheCacheManager
、RedisCacheManager
三者都继承了 AbstractTransactionSupportingCacheManager
, 通过设置setTransactionAware(boolean transactionAware)方法,可以实现事务提交后再进行缓存操作(注意,不管事务最后成功与否,缓存都会执行,慎用!!)
redisTemplate
redisTemplate
(redis缓存模板)中 setEnableTransactionSupport(boolean enableTransactionSupport)设置开启事务支持,结合@Transactional
可以实现缓存回滚
other
若一个方法上同时存在@Cacheable
和@Transaction
,spring默认cache代理优先级高于transaction,所以会出现先进行cache操作再进行transaction操作的情况。可以通过设置@EnableCaching(order=xxx)
、@EnableTransactionManagement(order=xxx)
中order值来调节代理顺序,order越小优先级越高。
事务
@TransactionalEventListener
事务事件监听器,一般用于事务提交成功时处理一些业务逻辑
关键属性phase
:
- TransactionPhase.BEFORE_COMMIT 事务提交前触发
- TransactionPhase.AFTER_COMMIT 事务提交成功时触发
- TransactionPhase.AFTER_ROLLBACK 事务回滚时触发
- TransactionPhase.AFTER_COMPLETION 事务完成(事务提交成功后或回滚后触发)
用法:
1 | public class MyAfterTransactionEvent extends ApplicationEvent { |
NOTE: spring事件机制默认是同步的,使用 @TransactionalEventListener不过是异步调用,本质上监听方法的执行和事务是在同一线程中。而上面例子中我们的监听方法在事务提交成功时执行,千万不要在监听方法进行insert/update/delete操作,因为spring的事务是绑定线程的,事务虽然提交了,但仍和当前线程绑定,此时进行增删改操作都是无效的!!详见这篇外文
怎么解决这个问题?目前能想到2种
- 1:在监听方法里调用异步方法来避免
- 2:在监听方法上添加@Transactional(propagation = Propagation.REQUIRES_NEW),这样监听方法会创建新的事务
有的人可能会想在监听方法上加@Async,这样监听方法在子线程种执行,子线程不和主线程共享事务,从而解决上述问题。我只能说想法很美好,现实很骨感。前面说了spring的事务是绑定线程的, @TransactionalEventListener是监听当前线程的事务,而子线程中丢失了主线程的任务,结果就是你的监听器不起效
security
我们知道SecurityContextHolder 持有security的contextSecurityContextHolder
部分源码:
1 | public class SecurityContextHolder { |
从源码里可以看出,SecurityContextHolder 默认为线程副本策略,这就会导致异步线程中获取不到security的context,解决方法有4种:
- 1:配置文件中设置
spring.security.strategy=MODE_INHERITABLETHREADLOCAL
- 2:使用
DelegatingSecurityContextRunnable.create(Runnable delegate, SecurityContext securityContext)
装饰任务,或者使用DelegatingSecurityContextExecutor
创建excutor(线程执行器) 3:和2的方法很像 ,excutor中设置任务装饰器
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/**
* security 异步任务装饰器
*/
static class ContextCopyingDecorator implements TaskDecorator {
public Runnable decorate(@NonNull Runnable runnable) {
RequestAttributes context = RequestContextHolder.currentRequestAttributes();
SecurityContext securityContext = SecurityContextHolder.getContext();
return () -> {
try {
RequestContextHolder.setRequestAttributes(context);
SecurityContextHolder.setContext(securityContext);
runnable.run();
} finally {
SecurityContextHolder.clearContext();
RequestContextHolder.resetRequestAttributes();
}
};
}
}
public Executor createExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new ContextCopyingDecorator());
...
}4:使用spring提供的
MethodInvokingFactoryBean
修改SecurityContextHolder
策略1
2
3
4
5
6
7
8
public MethodInvokingFactoryBean setSecurityStrategy() {
MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();
factoryBean.setTargetClass(SecurityContextHolder.class);
factoryBean.setStaticMethod("setStrategyName");
factoryBean.setArguments(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
return factoryBean;
}
扩展: 从SecurityContextHolder部分源码中不难看出,SecurityContextHolder在初始化时匹配3种策略名从而生成对应策略,若都没有匹配上,则加载自定义策略,此时strategyName
(策略名)为自定义策略的类路径,自定义策略需实现SecurityContextHolderStrategy
接口
自定义用户认证
代码实现:
1 | //从spring容器中获取UserDetailsService(这个从数据库根据用户名查询用户信息,及加载权限的service) |
v1.5.2