IoC 是 Spring 框架的最重要个性之一,对于 Spring IoC,咱们可能最直观感触到的可能就是 Bean 的申明与注入,本文,咱们先讲讲与 Bean 的申明相干的内容。

有多种形式能够申明 Spring Bean。

@Component

通常能够在某个类上加上 @Component 注解,以标识主动将该类的实例注册到 Spring IoC 容器中:

@Componentpublic class MyComponent {}

@Controller、@Service、@Repository 是相似的,它们之间概念上的区别大于实际上的区别,通常不同作用的类应用不同的注解。

@Controller

如果这个类是一个 controller 类,那么个别应用 @Controller 注解(或者 @RestController 注解):

@Controllerpublic class UserController {    @Autowired    private UserService userService;    @GetMapping("/api/users/{userId}")    public User findUser(@PathVariable(name = "userId") Long userId) {        return userService.findUser(userId);    }}

@Service

如果这个类是一个 service 类,那么个别应用 @Service 注解:

@Servicepublic class UserService {    @Autowired    private UserRepository userRepository;    public User findUser(Long userId) {        return userRepository.findById(userId);    }}

@Repository

如果这个类是一个 dao 类,那么个别应用 @Repository 注解:

@Repositorypublic class UserRepository {    public User findById(Long userId) {        return new User(userId, "小穆");    }}

这几个注解有一个雷同的属性:value,它是用来定义 Bean 的名字的。默认状况下,value 值为空字符串,此时,Bean 以首字母小写的类名作为名字,如果 value 值不为空字符串,则 Bean 以 value 值作为名字。如上面两个写法,其中 Bean 的名字别离为:myComponentoneComponent

@Componentpublic class MyComponent {}
@Component(value = "oneComponent")public class MyComponent2 {}

我的项目中是不能存在两个名字一样的 Bean 的,假如有上面两个类:

@Componentpublic class MyComponent {}
@Component(value = "myComponent")public class MyComponent2 {}

那么,利用启动的时候将会抛出异样信息:

Annotation-specified bean name 'myComponent' for bean class [org.susamlu.springweb.component.MyComponent2] conflicts with existing, non-compatible bean definition of same name and class [org.susamlu.springweb.component.MyComponent]

@Bean

与 @Configuration 一起应用

@Bean 个别与 @Configuration 一起应用,@Bean 比 @Component 更为灵便(尽管如此,但有时候 @Component 应用起来会更不便),应用它既能够注册本人编写的类,也能够注册第三方类:

import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.client.RestTemplate;import org.susamlu.springweb.component.MyRestTemplate;@Configurationpublic class BeanConfig {    @Bean    public MyRestTemplate myRestTemplate() {        return new MyRestTemplate();    }    @Bean    public RestTemplate restTemplate() {        return new RestTemplate();    }}

另外,通过 @Bean 也能够灵便配置咱们将要创立的 Bean:

@Configurationpublic class BeanConfig2 {    @Bean    public RestTemplate customRestTemplate() {        RestTemplate restTemplate = new RestTemplate();        restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory()));        return restTemplate;    }}

与 @Component 一起应用

@Bean 也能够与 @Component 一起应用,不过与上述形式不同的是,当间接调用该办法获取实例时,上述形式每次获取到的对象是雷同的一个单例对象,而这种形式每次获取到的对象都是新建的对象:

@Componentpublic class BeanConfig3 {    @Bean    public RestTemplate restTemplate2() {        return new RestTemplate();    }}
@Componentpublic class GetBeanTest {    @Autowired    private BeanConfig beanConfig;    @Autowired    private BeanConfig3 beanConfig3;    @EventListener    private void getBeans(ApplicationReadyEvent event) {        RestTemplate restTemplate1 = beanConfig.restTemplate();        RestTemplate restTemplate2 = beanConfig.restTemplate();        RestTemplate restTemplate3 = beanConfig3.restTemplate2();        RestTemplate restTemplate4 = beanConfig3.restTemplate2();        System.out.println("restTemplate1 equals restTemplate2: " + restTemplate1.equals(restTemplate2));        System.out.println("restTemplate3 equals restTemplate4: " + restTemplate3.equals(restTemplate4));    }}

上述代码输入的后果将是:

restTemplate1 equals restTemplate2: truerestTemplate3 equals restTemplate4: false

@Bean 的属性

name

@Bean 也存在 value 属性,同时还存在一个 name 属性,这两个属性的作用是一样的,都是用来定义 Bean 的名字的。value 和 name 属性是一个数组,也就是说一个 Bean 能够有多个名字,例如:

@Configurationpublic class BeanConfig4 {    @Bean(name = {"restTemplateA", "restTemplateB"})    public MyRestTemplate myRestTemplate() {        return new MyRestTemplate();    }}

通过 @Bean 定义的 Bean 同样也遵循 我的项目中不能存在两个名字一样的 Bean 的规定,但在细节上,与通过 @Component 定义 Bean 有一些区别。

例如,在同一个类中,能够有多个 @Bean name 属性雷同的办法,但只会有一个失效,比方上面的代码中,只有第一个办法定义的 Bean 被注册到了 Spring IoC 容器中,第二个办法定义的 Bean 被忽略了。

@Configurationpublic class BeanConfig5 {    @Bean("restTemplateC")    public MyRestTemplate myRestTemplate() {        return new MyRestTemplate();    }    @Bean("restTemplateC")    public RestTemplate restTemplate() {        return new RestTemplate();    }}

但,如果多个 @Bean name 属性雷同的办法定义在不同的类中,那么在利用启动的时候就会抛出异样。如有上面两个类:

@Configurationpublic class BeanConfig6 {    @Bean("restTemplateD")    public MyRestTemplate myRestTemplate() {        return new MyRestTemplate();    }}
@Configurationpublic class BeanConfig7 {    @Bean("restTemplateD")    public RestTemplate restTemplate() {        return new RestTemplate();    }}

利用启动时将抛出如下的异样信息:

The bean 'restTemplateD', defined in class path resource [org/susamlu/springweb/config/BeanConfig7.class], could not be registered. A bean with that name has already been defined in class path resource [org/susamlu/springweb/config/BeanConfig6.class] and overriding is disabled.

autowire

autowire 属性是用来定义实例变量的注入的,在 Spring 5.1 版本已置为弃用(本文应用的 Spring 版本为 5.3.22),所以在此不打算具体解说。至于为何弃用,代码正文的说法是:1. 能够通过办法参数的解析进行实例变量的注入,2. 能够通过 @Autowired 进行实例变量的注入,因而,不倡议再应用该属性。

对于第三方类,咱们能够通过定义方法参数的形式,解决变量的注入问题,如:

@Configurationpublic class BeanConfig8 {    @Bean    public RestTemplate customRestTemplate2(ClientHttpRequestFactory requestFactory) {        RestTemplate restTemplate = new RestTemplate();        restTemplate.setRequestFactory(requestFactory);        return restTemplate;    }    @Bean    public ClientHttpRequestFactory requestFactory() {        return new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());    }}

对于咱们本人编写的类,则间接在须要注入的变量处加上 @Autowired 注解即可:

public class MyRestTemplate2 {    @Autowired    private RestTemplate restTemplate;}
@Configurationpublic class BeanConfig9 {    @Bean    public MyRestTemplate2 myRestTemplate2() {        return new MyRestTemplate2();    }}

如此,在 @Bean 办法中返回的对象,即使是通过 new 的形式创立的,变量也能失常注入。

autowireCandidate

autowireCandidate 的值默认为 true,示意通过 @Bean 办法注册的 Bean 是能够被注入到其余实例变量中的,如果将其改为 false,则示意不可被注入。如上面的代码:

@Configurationpublic class BeanConfig8 {    @Bean    public RestTemplate customRestTemplate2(ClientHttpRequestFactory requestFactory) {        RestTemplate restTemplate = new RestTemplate();        restTemplate.setRequestFactory(requestFactory);        return restTemplate;    }    @Bean(autowireCandidate = false)    public ClientHttpRequestFactory requestFactory() {        return new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());    }}

因为通过 requestFactory() 办法注册的 Bean 被禁止了被注入,因而,利用启动的时候会抛出上面的异样信息:

Parameter 0 of method customRestTemplate2 in org.susamlu.springweb.config.BeanConfig8 required a bean of type 'org.springframework.http.client.ClientHttpRequestFactory' that could not be found.

initMethod

initMethod 能够指定在 Bean 初始化实现前须要执行的办法,该办法必须定义在 Bean 的类中,且该办法必须是无参的。上面的代码展现了 initMethod 的应用形式:

public class MyRestTemplate3 {    private void init() {        System.out.println("call MyRestTemplate3#init()");    }}
@Configurationpublic class BeanConfig10 {    @Bean(initMethod = "init")    public MyRestTemplate3 myRestTemplate3() {        return new MyRestTemplate3();    }}

destroyMethod

destroyMethod 能够指定在 Bean 销毁前须要执行的办法,该办法必须定义在 Bean 的类中,且该办法必须是公共且无参的。

默认状况下,destroyMethod 的值为 (inferred),示意会主动推断 Bean 销毁前须要执行的办法,即如果 Bean 中存在公共无参办法 close() 或 shutdown() 时,Bean 销毁前将会主动执行它。如果 Bean 中同时存在 close() 和 shutdown() 办法,则只会执行 close() 办法。

public class MyRestTemplate4 {    public void close() {        System.out.println("call MyRestTemplate4#close()");    }}
public class MyRestTemplate5 {    public void shutdown() {        System.out.println("call MyRestTemplate5#shutdown()");    }}
@Configurationpublic class BeanConfig11 {    @Bean    public MyRestTemplate4 myRestTemplate4() {        return new MyRestTemplate4();    }    @Bean    public MyRestTemplate5 myRestTemplate5() {        return new MyRestTemplate5();    }}

也能够显式指定该办法:

public class MyRestTemplate6 {    public void destroy() {        System.out.println("call MyRestTemplate6#destroy()");    }}
@Configurationpublic class BeanConfig11 {    @Bean(destroyMethod = "destroy")    public MyRestTemplate6 myRestTemplate6() {        return new MyRestTemplate6();    }}

如果须要勾销利用的默认推断行为,须要将 destroyMethod 的值设置为空字符串:

@Configurationpublic class BeanConfig11 {    @Bean(destroyMethod = "")    public MyRestTemplate7 myRestTemplate7() {        return new MyRestTemplate7();    }}

这种形式不会影响 DisposableBean 的 destroy() 办法的执行。

@Import

@Import 能够引入配置类、ImportSelector 和 ImportBeanDefinitionRegistrar 的实现类,也能够引入惯例的其余组件类。通过它既能够引入本人编写的类,也能够引入第三方类。通过 @Import 引入的类的实例会被主动注册到 Spring IoC 容器中。

@Import 有一个数组类型的 value 属性,咱们能够向这个属性传递一个或多个 class:

@Configuration@Import({JdbcProperties.class, MyRestTemplate7.class})public class BeanConfig12 {}

@Import 能够反复屡次引入同一个类(当然这是没有必要的),这个类对应的实例最终只会被注册一次。

ImportSelector 和 ImportBeanDefinitionRegistrar 是用来做什么的?它们都是接口,ImportSelector 有一个 selectImports() 办法,ImportBeanDefinitionRegistrar 有一个 registerBeanDefinitions() 办法,这两个办法一个是用来筛选类,一个是用来注册类的,咱们能够通过实现这两个接口,从而自定义类的筛选和注册逻辑。

@ImportResource

@ImportResource 与 @Import 相似,都是为了引入 Spring Bean。@Import 能够间接引入待引入的类,而 @ImportResource 可能引入的是内部文件,如 xml 文件或 groovy 文件。@ImportResource 有三个属性,其中两个是 value 和 locations,这两个属性的作用是一样的,都是用来指定引入资源的地位的。另外一个属性是 reader,如果咱们心愿 @ImportResource 能解析 xml 和 groovy 以外的文件,那么只有自定义 BeanDefinitionReader,并将自定义的类传值给 reader 属性即可,具体如何自定义,在此不做具体解说。上面是一个 @ImportResource 的应用例子:

public class MyBean {    private String field;    public void setField(String field) {        this.field = field;    }}
<!-- bean.xml --><?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:context="http://www.springframework.org/schema/context"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">    <bean class="org.susamlu.springweb.component.MyBean">        <property name="field" value="sample-value"></property>    </bean></beans>
@Configuration@ImportResource("classpath:bean.xml")public class BeanConfig13 {}

手动注册

手动注册 BeanDefinition,个别能够应用 DefaultListableBeanFactory 的 registerBeanDefinition() 办法进行注册,注册的时候通常能够借助 GenericBeanDefinition 或 BeanDefinitionBuilder 来实现。

GenericBeanDefinition

借助 GenericBeanDefinition 实现 BeanDefinition 的注册,示例代码如下:

public class MyBean2 {    private String field;    public void setField(String field) {        this.field = field;    }    public void doSomething() {        System.out.println("from my bean, field: " + field);    }}
public class GenericBeanDefinitionExample {    public static void main(String[] args) {        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();        beanDefinition.setBeanClass(MyBean2.class);        beanDefinition.getPropertyValues().addPropertyValue("field", "sample-value");        beanFactory.registerBeanDefinition("myBean", beanDefinition);        MyBean2 bean = beanFactory.getBean(MyBean2.class);        bean.doSomething();    }}

BeanDefinitionBuilder

借助 BeanDefinitionBuilder 实现 BeanDefinition 的注册,示例代码如下:

public class BeanDefinitionBuilderExample {    public static void main(String[] args) {        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(MyBean2.class)                .addPropertyValue("field", "sample-value");        beanFactory.registerBeanDefinition("myBean", builder.getBeanDefinition());        MyBean2 bean = beanFactory.getBean(MyBean2.class);        bean.doSomething();    }}

为了在 Spring Boot 利用启动的过程中注册 BeanDefinition,能够通过实现 BeanFactoryPostProcessor 或 BeanDefinitionRegistryPostProcessor 接口来实现。

BeanFactoryPostProcessor

通过实现 BeanFactoryPostProcessor 接口注册 BeanDefinition,示例代码如下:

@Configurationpublic class BeanConfig14 implements BeanFactoryPostProcessor {    @Override    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(MyBean2.class)                .addPropertyValue("field", "sample-value");        ((DefaultListableBeanFactory) beanFactory)                .registerBeanDefinition("myBean2", builder.getBeanDefinition());    }}

BeanDefinitionRegistryPostProcessor

通过实现 BeanDefinitionRegistryPostProcessor 接口注册 BeanDefinition,示例代码如下:

@Configurationpublic class BeanConfig15 implements BeanDefinitionRegistryPostProcessor {    @Override    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(MyBean2.class)                .addPropertyValue("field", "sample-value");        registry.registerBeanDefinition("myBean3", builder.getBeanDefinition());    }    @Override    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {        // do nothing    }}

所有引入了 @Component 的注解

除了下面的形式能够注册 Spring Bean,还有其余形式吗?

在后面的文章中,咱们剖析过 Spring Boot 利用在启动的时候,会将含有 @Component 注解的全副类注册到 Spring IoC 容器中。而 Spring 框架的个性是,只有某个注解 注解B 引入了另一个注解 注解A,那么某个类在应用了这个注解 注解B 的时候其实也引入了另一个注解 注解A,因而,只有一个类应用了 所有引入了 @Component 的注解,它就会被注册到 Spring IoC 容器中。

而这些注解就包含了:@ControllerAdvice、@RestControllerAdvice、@JsonComponent 等。

小结

本文零碎介绍了申明 Spring Bean 的各种形式,置信读者在理解完这些内容后,对于在什么场景下应用什么形式以及如何进行 Bean 的申明,就再也不必范迷糊了。