spring5.0 之@Primary注解的应用

来源:互联网 发布:什么是模块化编程 编辑:程序博客网 时间:2024/06/05 04:15

spring5.0 之@Primary注解的应用

在spring容器中,如果同一个类型有多个实例,但我们需要注入一个的时候,我们必须采取措施,不然spring容器
会报错:....required a single bean, but 2 were found:.........
有时候我们能保证同一个类型在spring容器中只有一个实例,有时候我们保证不了,此时不讨论by name注入。这
个时候@Primary注解就非常重要了。
 org.springframework.context.annotationAnnotation Type Primary @Target(value={TYPE,METHOD}) @Retention(value=RUNTIME) @Inherited @Documentedpublic @interface PrimaryIndicates 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.0Author:Chris BeamsSee 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;}@Beanpublic 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 */@Configurationpublic 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

原创粉丝点击