拦截器里获取redisTemplate报错org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifyin

背景

在使用jwt做权限校验的时候,需要在拦截器里校验前端传来的token信息与redis中的信息是否一致;
刚开始使用StringRedisTemplate获取信息,但是用户服务存储信息的时候是使用RedisTemplate存取信息,导致在我写的微服务内,如果使用StringRedisTemplate获取信息,获取到的token信息会额外多带两个引号,如下所示:

String token = stringRedisTemplate.opsForValue.get(key);
//输出 ""jwt-token信息""

解决思路

  1. 解决stringRedisTemplate取出来的字符串多了两个""的问题,最简单的方法是直接截取掉两头的"",但是这个方法不够优雅
  2. 直接修改用户微服务里储存token的代码,改用stringRedisTemplate来存储,但是,为了自己的微服务去修改,没有必要
  3. 在自己的微服务内,修改stringRedisTemplate的序列器,改为使用Jackson2JsonRedisSerializer修改序列化手段,但是本末倒置,为了取一个token影响了所有的stringRedisTemplate的解析
  4. 优化redisTemplate,修改默认序列化配置,直接去获取token信息

最终选择第四个,也是最正常的思路去解决这个问题,但是处理的时候发现了别的问题

解决过程

  1. 配置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;获取即可
`

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值