乐趣区

关于springboot:深入学习-Spring-Web-开发-Bean-的声明

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

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

@Component

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

@Component
public class MyComponent {}

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

@Controller

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

@Controller
public 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 注解:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User findUser(Long userId) {return userRepository.findById(userId);
    }

}

@Repository

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

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

}

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

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

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

@Component
public 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;

@Configuration
public class BeanConfig {

    @Bean
    public MyRestTemplate myRestTemplate() {return new MyRestTemplate();
    }

    @Bean
    public RestTemplate restTemplate() {return new RestTemplate();
    }

}

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

@Configuration
public class BeanConfig2 {

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

}

与 @Component 一起应用

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

@Component
public class BeanConfig3 {

    @Bean
    public RestTemplate restTemplate2() {return new RestTemplate();
    }

}
@Component
public 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: true
restTemplate3 equals restTemplate4: false

@Bean 的属性

name

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

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

}

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

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

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

    @Bean("restTemplateC")
    public RestTemplate restTemplate() {return new RestTemplate();
    }

}

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

@Configuration
public class BeanConfig6 {@Bean("restTemplateD")
    public MyRestTemplate myRestTemplate() {return new MyRestTemplate();
    }

}
@Configuration
public 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 进行实例变量的注入,因而,不倡议再应用该属性。

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

@Configuration
public 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;

}
@Configuration
public class BeanConfig9 {

    @Bean
    public MyRestTemplate2 myRestTemplate2() {return new MyRestTemplate2();
    }

}

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

autowireCandidate

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

@Configuration
public 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()");
    }

}
@Configuration
public 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()");
    }

}
@Configuration
public 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()");
    }

}
@Configuration
public class BeanConfig11 {@Bean(destroyMethod = "destroy")
    public MyRestTemplate6 myRestTemplate6() {return new MyRestTemplate6();
    }

}

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

@Configuration
public 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,示例代码如下:

@Configuration
public 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,示例代码如下:

@Configuration
public 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 的申明,就再也不必范迷糊了。

退出移动版