共计 11771 个字符,预计需要花费 30 分钟才能阅读完成。
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 的名字别离为:myComponent
和 oneComponent
。
@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 的申明,就再也不必范迷糊了。