spring5.0 之@Primary注解的应用

Posted Dreamer who

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring5.0 之@Primary注解的应用相关的知识,希望对你有一定的参考价值。

spring5.0 之@Primary注解的应用

在spring容器中,如果同一个类型有多个实例,但我们需要注入一个的时候,我们必须采取措施,不然spring容器 会报错:....required a single bean, but 2 were found:......... 有时候我们能保证同一个类型在spring容器中只有一个实例,有时候我们保证不了,此时不讨论by name注入。这 个时候@Primary注解就非常重要了。
 
org.springframework.context.annotation
Annotation Type Primary

 
@Target(value=TYPE,METHOD)
 @Retention(value=RUNTIME)
 @Inherited
 @Documented
public @interface Primary
Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value.
This annotation is semantically equivalent to the <bean> element's primary attribute in Spring XML.
May be used on any class directly or indirectly annotated with @Component or on methods annotated with @Bean.
Example
 @Component
 public class FooService 

     private FooRepository fooRepository;

     @Autowired
     public FooService(FooRepository fooRepository) 
         this.fooRepository = fooRepository;
     
 

 @Component
 public class JdbcFooRepository 

     public JdbcFooService(DataSource dataSource) 
         // ...
     
 

 @Primary
 @Component
 public class HibernateFooRepository 

     public HibernateFooService(SessionFactory sessionFactory) 
         // ...
     
 
 
Because HibernateFooRepository is marked with @Primary, it will be injected preferentially over the jdbc-based variant assuming both are present as beans within the same Spring application context, which is often the case when component-scanning is applied liberally.
Note that using @Primary at the class level has no effect unless component-scanning is being used. If a @Primary-annotated class is declared via XML, @Primary annotation metadata is ignored, and <bean primary="true|false"/> is respected instead.
Since:
3.0
Author:
Chris Beams
See Also:
Lazy, Bean, ComponentScan, Component

其实@Primary注解的实例优先于其他实例被注入。
下面看一个不得不使用@Primary注解的例子。
在spring security oauth2 资源服务器配置的时候,我们需要实例化一个ResourceServerTokenServices,该类主要从checkTokenEndpointUrl 地址校验用户登录的token。
spring boot 按照默认配置,会自动创建一个RemoteTokenServices,代码在ResourceServerTokenServicesConfiguration中的114行左右:
@Configuration
	@Conditional(RemoteTokenCondition.class)
	protected static class RemoteTokenServicesConfiguration 

		@Configuration
		@Conditional(TokenInfoCondition.class)
		protected static class TokenInfoServicesConfiguration 

			private final ResourceServerProperties resource;

			protected TokenInfoServicesConfiguration(ResourceServerProperties resource) 
				this.resource = resource;
			

			@Bean
			public RemoteTokenServices remoteTokenServices() 
				RemoteTokenServices services = new RemoteTokenServices();
				services.setCheckTokenEndpointUrl(this.resource.getTokenInfoUri());
				services.setClientId(this.resource.getClientId());
				services.setClientSecret(this.resource.getClientSecret());
				return services;
			

		

我想自己创建一个自己的UserInfoTokenServices,如:
package com.sdcuike.spring.security;

import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;

/**
 * Created by beaver on 2017/6/12.
 * <p>
 * 获取用户信息-> oid
 *
 * @see https://stackoverflow.com/questions/35056169/how-to-get-custom-user-info-from-oauth2-authorization-server-user-endpoint/35092561
 */
public class RichUserInfoTokenServices extends UserInfoTokenServices 
    public RichUserInfoTokenServices(String userInfoEndpointUrl, String clientId) 
        super(userInfoEndpointUrl, clientId);
    
    
    public RichUserInfoTokenServices(String userInfoEndpointUrl, String clientId, RichUserPrincipalExtractor richUserPrincipalExtractor) 
        super(userInfoEndpointUrl, clientId);
        setPrincipalExtractor(richUserPrincipalExtractor);
    
    


按照spring boot java config方式创建给bean:
package com.sdcuike.practice.config.security;

import com.sdcuike.spring.security.RichUserDetails;
import com.sdcuike.spring.security.RichUserInfoTokenServices;
import com.sdcuike.spring.security.RichUserPrincipalExtractor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;

import java.util.Map;

/**
 * Created by beaver on 2017/6/12.
 * <p>
 * 获取用户信息-> oid
 *
 * @see https://stackoverflow.com/questions/35056169/how-to-get-custom-user-info-from-oauth2-authorization-server-user-endpoint/35092561
 */
@Configuration
public class UserInfoTokenServicesConfig 
    
    @Value("$security.oauth2.resource.clientId")
    private String clientId;
    
    @Autowired
    private ResourceServerProperties sso;
    
    @Bean
//    @Primary
    public ResourceServerTokenServices richUserInfoTokenServices() 
        return new RichUserInfoTokenServices(sso.getUserInfoUri(), clientId, new RichUserPrincipalExtractor());
    
    
    


如果把@Primary注解注释掉,启动会报错:
***************************
APPLICATION FAILED TO START
***************************

Description:

Method springSecurityFilterChain in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a single bean, but 2 were found:
	- richUserInfoTokenServices: defined by method 'richUserInfoTokenServices' in class path resource [com/sdcuike/practice/config/security/UserInfoTokenServicesConfig.class]
	- remoteTokenServices: defined by method 'remoteTokenServices' in class path resource [org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration$RemoteTokenServicesConfiguration$TokenInfoServicesConfiguration.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

-

但实例化的remoteTokenServices我们无法控制(如果有,请通知俺),我们必须是使用@Primary注解,让我们控制哪个实例优先被注入。
博文相关代码见:https://github.com/sdcuike/spring-boot-oauth2-demo/blob/blog-2017-07-18/spring-boot-oauth-resource-server/src/main/java/com/sdcuike/practice/config/security/UserInfoTokenServicesConfig.java

以上是关于spring5.0 之@Primary注解的应用的主要内容,如果未能解决你的问题,请参考以下文章

spring5.0.10注解版-一些核心组件作用初始化时机

JAVAEE框架整合技术之Spring02-AOP面向切面编程技术

JAVAEE框架整合技术之Spring02-AOP面向切面编程技术

Spring IOC容器注解 @Autowired@Resource@Primary@Value的用法

Spring之IOC-注解方式

Spring注解@Primary的意思