基础知识

JSR 250: Common Annotations for the JavaTM Platform

JSR 330: Dependency Injection for Java

JSR 305: Annotations for Software Defect Detection

beans-annotation-config

本文讲解的是 Spring 基于注解的 Bean 装载,XML 形式的配置与 Annotation 形式的配置实现功能都是一样的,且可以混用,但是要注意的是 Annotation 先于 XML 执行,注解的配置可能会被 XML 覆盖。

装配(autowiring ) 约等于 注入(injection )

自动装配的模式

关键字

  • @Autowired
  • -@Primary-
  • -@Required---formally deprecated as of Spring Framework 5.1
  • @Qualifier
  • -@Nullable-

JSR-250

  • @Resource
  • @PostConstruct
  • @PreDestroy
  • @ManagedBean

JSR-330

  • @Inject
  • @Named

对比结论

使用实例一、多数据源配置

public class JPAMongoConfigBizLog {

    @Autowired
    @Qualifier("bizLogMongoProperties")
    private MongoProperties bizLogMongoProperties;

    @Bean(name = "bizLogMongo")
    @Qualifier("bizLogMongo")
    public MongoTemplate bizLogMongoTemplate() {
        return new MongoTemplate(bizLogFactory(this.bizLogMongoProperties));
    }

    @Bean
    public MongoDbFactory bizLogFactory(MongoProperties bizLogMongoProperties) {
        MongoClientURI mongoClientURI = new MongoClientURI(bizLogMongoProperties.getUri());
        return new SimpleMongoDbFactory(new MongoClient(mongoClientURI), bizLogMongoProperties.getDatabase());
    }
}

public class JPAMongoConfigMain {

    @Autowired
    @Qualifier("mainMongoProperties")
    private MongoProperties mainMongoProperties;

    @Bean(name = "mainMongo")
    @Qualifier("mainMongo")
    @Primary
    public MongoTemplate mainMongoTemplate() {
        return new MongoTemplate(mainFactory(this.mainMongoProperties));
    }

    @Primary
    @Bean
    public MongoDbFactory mainFactory(MongoProperties mainMongoProperties) {
        MongoClientURI mongoClientURI = new MongoClientURI(mainMongoProperties.getUri());
        return new SimpleMongoDbFactory(new MongoClient(mongoClientURI), mainMongoProperties.getDatabase());
    }
}
    方式一、
    @Autowired
    @Qualifier("bizLogMongo")--------必须要指定Qualifier,不指定默认获取到的是 mainMongo,因为 mainMongo 使用了 @Primary
    MongoTemplate bizLogMongo;----bizLogMongo这个名字不影响

    方式二、
    @Resource
    MongoTemplate bizLogMongo;

    方式三、
    @Resource(nbame="bizLogMongo")
    MongoTemplate mongo;

使用实例一、多个实现类

package com.example.service;
public interface DemoService {
}

package com.example.service;
import org.springframework.stereotype.Service;
@Service
public class ADemoServiceImpl implements DemoService {
}

package com.example.service;
import org.springframework.stereotype.Service;
@Service
public class BDemoServiceImpl implements DemoService {
}
    下面两种模式OK
    @Resource
    DemoService ADemoServiceImpl;

    @Resource(name="ADemoServiceImpl")
    DemoService demo;

    @Resource
    DemoService demo;
    会报异常:Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.service.DemoService' available: expected single matching bean but found 2: ADemoServiceImpl,BDemoServiceImpl

    原因是 @Resource 默认按 name 找 demo 这个 bean,没找到,然后按类型找发现有两个实现类,报错。

使用实例一、单个实例

    @Bean
    public RedisTemplate<String, String> stringRedisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }
    四种模式都可以
    @Resource
    StringRedisTemplate redis;

    @Autowired
    StringRedisTemplate redis;

    @Resource(name = "stringRedisTemplate")
    RedisTemplate redis;

    @Resource
    RedisTemplate stringRedisTemplate;

到底应该用哪一种?

给出一个很官方的回答,都可以用,至于选择哪一种或者还是混用,根据团队技术栈合理选择即可。

相信大部分还是在使用 @Autowired 和 @Resource ,能完成工作就好。

@Required

在注入的时候发现没有 Bean 可填充的时候将抛出异常。

该注解在 Spring Framework 5.1 版本中已经被正式声明为弃用 Deprecated

Spring 也推荐使用 Autowired 的 required 属性。

@Autowired

使用 Spring 时候,装载 Bean 最常用的注解,JSR-330 的 @Inject 注解可以替代 @Autowired。

@Autowired 可以作用于 属性, setter 方法, 构造方法。

属性
@Autowired
private MovieCatalog movieCatalog;


setter 方法
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}



构造方法
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
    this.customerPreferenceDao = customerPreferenceDao;
}


用于对个参数装配也是没有问题的
@Autowired
public void prepare(MovieCatalog movieCatalog,
        CustomerPreferenceDao customerPreferenceDao) {
    this.movieCatalog = movieCatalog;
    this.customerPreferenceDao = customerPreferenceDao;
}


属性与构造方法混用也是没问题的
    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }


当然也支持数组和集合形式的注入
    @Autowired
    private MovieCatalog[] movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }



可以使用 @Order 或者 @Priority 实现排序,如果没有指定,就按照 Bean 注册的顺序或者是容器中定义的顺序排序。

默认情况下,@Autowired 注解在装配时候没有找到对应的 Bean 会失败。如果要允许启动时候不直接装载,可以将 required 属性设置为 false

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }


另一种变通的方式是使用 Java 8 的 java.util.Optional

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }


Spring Framework 5.0 以上的版本中,还可以使用 @Nullable (javax.annotation.Nullable 来自于 JSR-305)

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }


@Autowired 甚至还可以装配 Spring 的一些核心组件,且不需要任何额外的设置。

  • BeanFactory

  • ApplicationContext

  • Environment

  • ResourceLoader

  • ApplicationEventPublisher

  • MessageSource

  • ConfigurableApplicationContext

  • ResourcePatternResolver

核心常用组件
    @Autowired
    private ApplicationContext context;


由于 Spring 的 Autowired 使用的是基于类型的组装,那么我们就会遇到同时存在两个 Bean 的情况。Spring 为我们了提供了几种方案来处理。

@Primary

当存在多个候选 Bean 且只要装配一个的时候,使用 @Primary 的 Bean 成为优先装配的 Bean。

下面定义了两个 MovieCatalog 对象,firstMovieCatalog 被标志为 @Primary
@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

下面的使用实例中 movieCatalog 就是 firstMovieCatalog
public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}


上面的例子还会引发另一个问题,就是当我们希望 movieCatalog 是 secondMovieCatalog 的时候,我们应该怎么处理呢?Spring 提供了另一个注解 Qualifiers

@Qualifiers

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;


    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }



如果我们没有指定 Bean 的 Qualifier,Spring 默认使用对象的名字为 Qualifier。Autowired 从根本上来说是按 type 装配的,虽然我们可以指定 Qualifier 或者用默认的 Bean 的名字来区分,但是从语义上来说并不能保证 Bean 的唯一性。所以在起名字的时候,最好是能明确标志 Bean 的实际意义的名字,而不是仅仅是一个 id 这样的简单名字。

Qualifier 也可以作用于集合类型,这意味着限定符不是惟一的。

你可以用 “action” 定义多个 MovieCatalog,所有的 action 都被注入到

@Qualifier("action")
Set<MovieCatalog>


注意: 说到这里可能大家也看到问题。虽然使用 Autowired 和 Qualifier 这个组合也能在一定程度上解决多个 Bean 的识别问题,但是 Qualifier 并不保证唯一。 所以在有多个同 type 的 Bean 的时候,我们尽量不要使用 Autowired,我们可以使用 JSR-250 @Resource

@Resource

@Resource (JSR-250 javax.annotation.Resource)可以用他的 name 属性来唯一标识一个目标 Bean。而声明的类型判断反而是其次的。所以说 @Resource 是 byName 来注入的。

    @Resource(name="myMovieFinder")
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }


如果没有指定 name 属性,@Resource 使用属性或者是参数名作为默认名称查找 Bean。

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }


@Resource 在没有明确指定 name 的情况下,跟 @Autowired 类似,也会在没有找到默认名字 bean 的情况下,根据类型去查找。

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;


customerPreferenceDao 会按 name 查找。

context 会按 ApplicationContext 类型查找。

@PostConstruct 和 @PreDestroy

@PostConstruct (javax.annotation.PostConstruct)和 @PreDestroy(javax.annotation.PreDestroy)是 JSR-250 关于生命周期的另外两个注解。

从 Spring 3.0 开始,Spring 支持 JSR-330 standard annotations (Dependency Injection) 。但是需要自己加入到 jar 包依赖。

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>


你可以使用 @javax.inject.Inject 替代 @Autowired。

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }


@Inject 跟 @Autowired 一样,也可以作用于属性, setter函数,构造函数,还可以配合 Provider 使用。

    private Provider<MovieFinder> movieFinder;

    @Inject
    public void setMovieFinder(Provider<MovieFinder> movieFinder) {
        this.movieFinder = movieFinder;
    }


当然,你可以配合 @Named 使用。

@Inject
    public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }


@Inject 还可以配合 java.util.Optional 或者 @Nullable 使用,但是 @Inject 不再支持 required 属性。

    @Inject
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }

    @Inject
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }


@Named 和 @ManagedBean 完全等同于 @Component

import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}


要使用 @Named 或者 @ManagedBean,也需要加入自动扫描。

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    ...
}


JSR-330 注解的局限性

Spring组件模型元素 VS JSR-330 变体

04-13 01:15