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  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  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.
 * 

* 获取用户信息-> 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.
 * 

* 获取用户信息-> 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

你可能感兴趣的:(Spring,Boot,Spring,Boot,实战)