背景
在使用jwt做权限校验的时候,需要在拦截器里校验前端传来的token信息与redis中的信息是否一致;
刚开始使用StringRedisTemplate获取信息,但是用户服务存储信息的时候是使用RedisTemplate存取信息,导致在我写的微服务内,如果使用StringRedisTemplate获取信息,获取到的token信息会额外多带两个引号,如下所示:
String token = stringRedisTemplate.opsForValue.get(key);
//输出 ""jwt-token信息""
解决思路
- 解决stringRedisTemplate取出来的字符串多了两个
""
的问题,最简单的方法是直接截取掉两头的""
,但是这个方法不够优雅 - 直接修改用户微服务里储存token的代码,改用stringRedisTemplate来存储,但是,为了自己的微服务去修改,没有必要
- 在自己的微服务内,修改stringRedisTemplate的序列器,改为使用Jackson2JsonRedisSerializer修改序列化手段,但是本末倒置,为了取一个token影响了所有的stringRedisTemplate的解析
- 优化redisTemplate,修改默认序列化配置,直接去获取token信息
最终选择第四个,也是最正常的思路去解决这个问题,但是处理的时候发现了别的问题
解决过程
- 配置RedisTemplateConfig
@Configuration
public class RedisTemplateConfig {
@Bean(name = "redisTemplate")
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer( new Jackson2JsonRedisSerializer(Object.class));
redisTemplate.setValueSerializer( new Jackson2JsonRedisSerializer(Object.class));
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
2.运行的时候报错
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.data.redis.core.RedisTemplate' available: expected single matching bean but found 2: redisTemplate,stringRedisTemplate
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1200)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:420)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127)
at com.als.cloud.util.SpringUtil.getBeanByType(SpringUtil.java:35)
at com.als.cloud.interceptor.LoginInterceptor.preHandle(LoginInterceptor.java:89)
at org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(HandlerExecutionChain.java:151)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1035)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.als.cloud.config.OncePerRequest.doFilterInternal(OncePerRequest.java:49)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:94)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
3.发现报错后,刚开始怀疑是bean的那么重复了,然后查看了RedisAutoConfiguration
类,发现并没有影响,当存在redisTemplate
的时候,spring-boot-starter-data-redis
自动装载的RedisTemplate会失效
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
4.然后仔细观察发现还有一行报错com.als.cloud.util.SpringUtil.getBeanByType(SpringUtil.java:35)
,这里主要是我们在拦截器内获取spring上下文里的对象
public class LoginInterceptor implements HandlerInterceptor, ResponseBodyAdvice {
...
private RedisTemplate redisTemplate;
...
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) {
if (redisTemplate == null) {
redisTemplate = SpringUtil.getBeanByType(RedisTemplate.class);
}
...
}
}
5.查看SpringUtil的代码
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext=null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static <T> T getBeanByName(String beanName) {
return (T) getApplicationContext().getBean(beanName);
}
public static <T> T getBeanByType(Class<T> tClass) {
return getApplicationContext().getBean(tClass);
}
public static boolean containsBean(String beanName) {
return getApplicationContext().containsBean(beanName);
}
}
6.代码里可以看出,我们是从ApplicationContext
里获取Bean,我们接着断点进去看看里面的代码
public interface BeanFactory {
...
<T> T getBean(Class<T> var1) throws BeansException;
...
}
7.然后AbstractApplicationContext
继承自BeanFactory
,实现了这个Bean方法
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
...
public <T> T getBean(Class<T> requiredType) throws BeansException {
//检查BeanFactory是否处于active状态
this.assertBeanFactoryActive();
return this.getBeanFactory().getBean(requiredType);
}
...
}
8.在往下走发现DefaultListableBeanFactory
继承自上层抽象类AbstractApplicationContext
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
...
public <T> T getBean(Class<T> requiredType) throws BeansException {
return this.getBean(requiredType, (Object[])null);
}
...
public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException {
Assert.notNull(requiredType, "Required type must not be null");
Object resolved = this.resolveBean(ResolvableType.forRawClass(requiredType), args, false);
if (resolved == null) {
throw new NoSuchBeanDefinitionException(requiredType);
} else {
return resolved;
}
}
}
9.我们可以看到具体获得对象是在this.resolveBean(ResolvableType.forRawClass(requiredType)
这一行,所以我们进去看看,这一行是在干啥
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) {
NamedBeanHolder<T> namedBean = this.resolveNamedBean(requiredType, args, nonUniqueAsNull);
if (namedBean != null) {
return namedBean.getBeanInstance();
} else {
BeanFactory parent = this.getParentBeanFactory();
if (parent instanceof DefaultListableBeanFactory) {
return ((DefaultListableBeanFactory)parent).resolveBean(requiredType, args, nonUniqueAsNull);
} else if (parent != null) {
ObjectProvider<T> parentProvider = parent.getBeanProvider(requiredType);
if (args != null) {
return parentProvider.getObject(args);
} else {
return nonUniqueAsNull ? parentProvider.getIfUnique() : parentProvider.getIfAvailable();
}
} else {
return null;
}
}
}
}
10.关键的一行在this.resolveNamedBean(requiredType, args, nonUniqueAsNull)
@Nullable
private <T> NamedBeanHolder<T> resolveNamedBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) throws BeansException {
Assert.notNull(requiredType, "Required type must not be null");
String[] candidateNames = this.getBeanNamesForType(requiredType);
String[] var6;
int var7;
int var8;
String beanName;
if (candidateNames.length > 1) {
List<String> autowireCandidates = new ArrayList(candidateNames.length);
var6 = candidateNames;
var7 = candidateNames.length;
for(var8 = 0; var8 < var7; ++var8) {
beanName = var6[var8];
if (!this.containsBeanDefinition(beanName) || this.getBeanDefinition(beanName).isAutowireCandidate()) {
autowireCandidates.add(beanName);
}
}
if (!autowireCandidates.isEmpty()) {
candidateNames = StringUtils.toStringArray(autowireCandidates);
}
}
if (candidateNames.length == 1) {
String beanName = candidateNames[0];
return new NamedBeanHolder(beanName, this.getBean(beanName, requiredType.toClass(), args));
} else {
if (candidateNames.length > 1) {
Map<String, Object> candidates = new LinkedHashMap(candidateNames.length);
var6 = candidateNames;
var7 = candidateNames.length;
for(var8 = 0; var8 < var7; ++var8) {
beanName = var6[var8];
if (this.containsSingleton(beanName) && args == null) {
Object beanInstance = this.getBean(beanName);
candidates.put(beanName, beanInstance instanceof NullBean ? null : beanInstance);
} else {
candidates.put(beanName, this.getType(beanName));
}
}
String candidateName = this.determinePrimaryCandidate(candidates, requiredType.toClass());
if (candidateName == null) {
candidateName = this.determineHighestPriorityCandidate(candidates, requiredType.toClass());
}
if (candidateName != null) {
Object beanInstance = candidates.get(candidateName);
if (beanInstance == null || beanInstance instanceof Class) {
beanInstance = this.getBean(candidateName, requiredType.toClass(), args);
}
return new NamedBeanHolder(candidateName, beanInstance);
}
if (!nonUniqueAsNull) {
throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet());
}
}
return null;
}
}
11.我们看到这里的代码,最下面抛出一个NoUniqueBeanDefinitionException
的异常,点进去查看代码
public NoUniqueBeanDefinitionException(ResolvableType type, Collection<String> beanNamesFound) {
super(type, "expected single matching bean but found " + beanNamesFound.size() + ": " + StringUtils.collectionToCommaDelimitedString(beanNamesFound));
this.numberOfBeansFound = beanNamesFound.size();
this.beanNamesFound = beanNamesFound;
}
12.这个方法和我们抛出的异常一模一样,往上看代码.发现candidateNames
这个数组的元素数量大于1导致的,所以我们往前推,看看candidateNames
是怎么生成的?
public String[] getBeanNamesForType(ResolvableType type) {
return this.getBeanNamesForType(type, true, true);
}
public String[] getBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
Class<?> resolved = type.resolve();
return resolved != null && !type.hasGenerics() ? this.getBeanNamesForType(resolved, includeNonSingletons, allowEagerInit) : this.doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit);
}
13.因为resolved
不为null
,type.hasGenerics()
为false,所以最终走的是getBeanNamesForType()
方法
public String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
if (this.isConfigurationFrozen() && type != null && allowEagerInit) {
Map<Class<?>, String[]> cache = includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType;
String[] resolvedBeanNames = (String[])cache.get(type);
if (resolvedBeanNames != null) {
return resolvedBeanNames;
} else {
resolvedBeanNames = this.doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true);
if (ClassUtils.isCacheSafe(type, this.getBeanClassLoader())) {
cache.put(type, resolvedBeanNames);
}
return resolvedBeanNames;
}
} else {
return this.doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit);
}
}
14.发现他先从Map<Class<?>, String[]> cache = includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType;
这里获取bean的缓存,map是类型,value是存的对象的名称,最终通过org.springframework.data.redis.core.RedisTemplate
类型去获取beanName的时候,获取到了两个对象redisTemplate
,stringRedisTemplate
两个类,最终导致这个问题的发生
原因分析
因为调用了ApplicationContext getBean
去获取对象,如果提供的是这个类的class信息,那么如果存在多个这个类型的对象的时候,就会爆出上述的NoUniqueBeanDefinitionException
,原因就是,容器也不知道你要获取的到底是哪个对象.
因为redisTemplate是StringRedisTemplate的父类,所以出现了上述的问题.当使用StringRedisTemplate去获取的时候,因为只有一个对象,所以不会出现.因为java的多态,父类引用指向子类对象,就会出现了,一个类型可以获取到底下的子类型
解决方案
改用Object getBean(String beanName) throws BeansException;
获取即可
`