你只管使用,一切交给IOC。
通过组件注册,IOC就自动帮助我们管理bean。

创建项目,引入依赖:

  1. <dependency>
  2. <groupId>org.projectlombok</groupId>
  3. <artifactId>lombok</artifactId>
  4. <version>1.18.16</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework</groupId>
  8. <artifactId>spring-context</artifactId>
  9. <version>5.3.1</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>javax.annotation</groupId>
  13. <artifactId>javax.annotation-api</artifactId>
  14. <version>1.3.2</version>
  15. </dependency>

@Bean注册组件

创建一个实例类User

  1. @AllArgsConstructor
  2. @NoArgsConstructor
  3. @Data
  4. @ToString
  5. public class User {
  6. private Long id;
  7. private String name;
  8. }

接着创建一个配置类,在配置类中通过@Bean注解注册User类:

  1. @Configuration
  2. public class Config {
  3. @Bean
  4. public User user() {
  5. return new User();
  6. }
  7. }

在SpringBoot入口类中编写如下代码:

  1. @SpringBootApplication
  2. public class KHighnessApplication {
  3. public static void main(String[] args) {
  4. ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(KHighnessApplication.class)
  5. .web(WebApplicationType.SERVLET)
  6. .run(args);
  7. User user = applicationContext.getBean(User.class);
  8. System.out.println(user);
  9. }
  10. }

运行启动类,控制台输出如下:

  1. User(id=1, name=KHighness)

@ComponentSacn扫描包

在这个SpringBoot启动类所在包下创建其他类:UserDao、UserService、UserController,并且标注上响应注解,给User类标注@Component注解。
并修改配置类:

  1. @Configuration
  2. @ComponentScan("top.parak")
  3. public class WebConfig {
  4. }

早在启动类中获取所有的bean:

  1. @SpringBootApplication
  2. public class KHighnessApplication {
  3. public static void main(String[] args) {
  4. ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(KHighnessApplication.class)
  5. .web(WebApplicationType.SERVLET)
  6. .run(args);
  7. String[] names = applicationContext.getBeanDefinitionNames();
  8. Arrays.stream(names).forEach(System.out::println);
  9. }
  10. }

运行启动类,观察控制台,有如下输出:

  1. KHighnessApplication
  2. org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
  3. webConfig
  4. userController
  5. userDao
  6. user
  7. userService

@Scope组件作用域

默认情况下,在Spring的IOC容器中每个组件都是单例的,即无论在任何地方注入多少次,这些对象都是同一个。
将配置类修改回去:

  1. @Configuration
  2. public class WebConfig {
  3. @Bean
  4. public User user() {
  5. return new User(1L, "KHighness");
  6. }
  7. }

修改SpringBoot启动类如下:

  1. @SpringBootApplication
  2. public class KHighnessApplication {
  3. public static void main(String[] args) {
  4. ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(KHighnessApplication.class)
  5. .web(WebApplicationType.SERVLET)
  6. .run(args);
  7. Object user1 = applicationContext.getBean("user");
  8. Object user2 = applicationContext.getBean("user");
  9. System.out.println(user1 == user2);
  10. }
  11. }

运行启动类,控制台输出如下:

  1. true

可以通过@Scope修改作用域,具体如下:

类型 描述
singleton 单实例(默认),在Spring IOC容器启动的时候会调用方法创建对象然后纳入到IOC容器中,以后每次获取都是直接从IOC容器中获取
prototype 多实例,IOC容器启动的时候并不会去创建对象,而是在每次获取的时候才会去调用方法创建对象
request 一个请求对应一个实例
session 同一个session对应一个实例

@Lazy懒加载

懒加载是针对单例模式而言的,容器启动的时候会调用方法创建对象然后纳入到IOC容器中。
在User注册的地方打印一句话以观察:

  1. @Bean
  2. public User user() {
  3. System.out.println("向IOC容器中注册user");
  4. return new User(1L, "KHighness");
  5. }

在IOC容器启动后加入一句话以观察:

  1. ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(KHighnessApplication.class)
  2. .web(WebApplicationType.SERVLET)
  3. .run(args);
  4. System.out.println("IOC容器创建完毕");

运行启动类,观察控制台,有如下输出:

  1. IOC容器中注册user
  2. 2021-06-28 16:43:13.064 INFO 49772 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
  3. 2021-06-28 16:43:13.165 INFO 49772 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
  4. 2021-06-28 16:43:13.168 INFO 49772 --- [ main] top.parak.KHighnessApplication : Started KHighnessApplication in 1.081 seconds (JVM running for 1.667)
  5. IOC容器创建完毕

可以看到,在IOC容器创建完毕之前,组件已经注册到容器中。
将User修改为懒加载注册:

  1. @Bean
  2. @Lazy
  3. public User user() {
  4. System.out.println("向IOC容器中注册user");
  5. return new User(1L, "KHighness");
  6. }

将启动类修改一下,获取user:

  1. @SpringBootApplication
  2. public class KHighnessApplication {
  3. public static void main(String[] args) {
  4. ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(KHighnessApplication.class)
  5. .web(WebApplicationType.SERVLET)
  6. .run(args);
  7. System.out.println("IOC容器创建完毕");
  8. Object user = applicationContext.getBean("user");
  9. System.out.println(user);
  10. }
  11. }

运行启动类,观察控制台,有如下输出:

  1. IOC容器创建完毕
  2. IOC容器中注册user
  3. User(id=1, name=KHighness)

从输出看到,创建时并没有注册user,获取时才去注册,可以证明懒加载成功。

条件注册

@Conditional

使用@Conditional注解我们指定组件注册的条件,即满足特定条件才将组件纳入到IOC容器中。
在使用该注解之前,先创建一个类,实现Condition接口:

  1. public class MyCondition implements Condition {
  2. @Override
  3. public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
  4. return false;
  5. }
  6. }

该接口包含一个matches方法,包含两个入参:

  1. ConditionContext:上下文信息;
  2. AnnotatedTypeMetadata:注解信息。

简单完善一下实现类:

  1. public class MyCondition implements Condition {
  2. @Override
  3. public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
  4. String osName = conditionContext.getEnvironment().getProperty("os.name");
  5. return osName != null && osName.contains("windows");
  6. }
  7. }

将这个条件添加到注册User的地方:

  1. @Bean
  2. @Conditional(MyCondition.class)
  3. public User user() {
  4. System.out.println("向IOC容器中注册user");
  5. return new User(1L, "KHighness");
  6. }

在Windows系统下,User会被成功注册,其他操作系统下,这个组件则不会被注册到IOC容器中。

@Profile

@Profile可以根据不同的环境变量来注册不同的组件。
新建一个接口CalculateService,用于求和:

  1. public interface CalculateService {
  2. Integer sum(Integer... value);
  3. }

接着添加两个实现类Java7CalculateServiceImplJava8CalculateServiceImpl

  1. @Service
  2. @Profile("java7")
  3. public class Java7CalculateServiceImpl implements CalculateService {
  4. @Override
  5. public Integer sum(Integer... value) {
  6. System.out.println("Java7环境下执行");
  7. int res = 0;
  8. for (int val : value)
  9. res += val;
  10. return res;
  11. }
  12. }
  1. @Service
  2. @Profile("java8")
  3. public class Java8CalculateServiceImpl implements CalculateService {
  4. @Override
  5. public Integer sum(Integer... value) {
  6. System.out.println("Java8环境下执行");
  7. return Arrays.stream(value).reduce(0, Integer::sum);
  8. }
  9. }

通过@Profile注解我们实现了:当环境变量包含java7的时候,Java7CalculateServiceImpl将会被注册到IOC容器中;当环境变量包含java8的时候,Java8CalculateServiceImpl将会被注册到IOC容器中。
修改启动类如下:

  1. @SpringBootApplication
  2. public class KHighnessApplication {
  3. public static void main(String[] args) {
  4. ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(KHighnessApplication.class)
  5. .web(WebApplicationType.SERVLET)
  6. .profiles("java8")
  7. .run(args);
  8. CalculateService service = applicationContext.getBean(CalculateService.class);
  9. System.out.println("求和结果:" + service.sum(1, 2, 3, 4, 5));
  10. }
  11. }

运行启动类,观察控制台,有如下输出:

  1. Java8环境下执行
  2. 求和结果:15

同理,如果设置了环境变量java7的时候,将会输出Java7环境下执行

导入组件

@Import

上面学习了使用@Bean和@@ComponentScan扫描实现组件注册,除此之外,我们还可以使用@Import来快速地往IOC容器中添加组件。
创建一个新的类Hello

  1. public class Hello {
  2. }

然后在配置类中导入这个组件:

  1. @Configuration
  2. @Import({Hello.class})
  3. public class WebConfig {...

修改启动类,查看所有组件的名称:

  1. @SpringBootApplication
  2. public class KHighnessApplication {
  3. public static void main(String[] args) {
  4. ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(KHighnessApplication.class)
  5. .web(WebApplicationType.SERVLET)
  6. .run(args);
  7. String[] names = applicationContext.getBeanDefinitionNames();
  8. Arrays.stream(names).forEach(System.out::println);
  9. }
  10. }

运行启动类,观察控制台,有如下输出:

  1. top.parak.entity.Hello

可以看到,通过@Import我们可以快速向容器中添加组件,默认名称为全类名。

ImportSelector

通过@Import可以实现组件导入,如果需要一次性导入较多组件,我们可以使用@ImportSelector来实现。
查看ImportSelector源码:

  1. public interface ImportSelector {
  2. /**
  3. * Select and return the names of which class(es) should be imported based on
  4. * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
  5. */
  6. String[] selectImports(AnnotationMetadata importingClassMetadata);
  7. }

@ImportSelector是一个接口,包含一个selectImports方法,方法返回的类的全类名数组(即需要导入到IOC容器中组件的全类名数组),包含一个@AnnotationMetadata类型入参,通过这个参数我们可以获取到使用@ImportSelector的类的全部注解信息。
新建三个类,分别为OrangeBananaPineapple
新建一个ImportSelector实现类MyImportSelector

  1. public class MyImportSelector implements ImportSelector {
  2. @Override
  3. public String[] selectImports(AnnotationMetadata importingClassMetadata) {
  4. return new String[] {
  5. "top.parak.entity.Orange",
  6. "top.parak.entity.Banana",
  7. "top.parak.entity.Pineapple"
  8. };
  9. }
  10. }

接着通过@Import注解将MyImportSelector把丧组件快速导入到容器中:

  1. @Import(MyImportSelector.class)
  2. public class WebConfig {...

运行启动类,观察控制台,发现有如下输出:

  1. top.parak.entity.Orange
  2. top.parak.entity.Banana
  3. top.parak.entity.Pineapple

说明三个组件已经批量导入。

ImportBeanDefinitionRegistor

除了上述两种向IOC容器中导入组件的方法外,我们还可以使用ImportBeanDefinitionRegistrar来手动往IOC容器中导入组件。
查看ImportBeanDefinitionRegistrar源码:

  1. public interface ImportBeanDefinitionRegistrar {
  2. default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  3. }
  4. }

ImportBeanDefinitionRegistrar是一个接口,包含一个registerBeanDefinitions方法,该方法包含两个入参:

  1. AnnotationMetadata:可以通过它获取类的注解信息;
  2. BeanDefinitionRegistry:Bean定义注册器,包含了一些和Bean有关的方法: ```java public interface BeanDefinitionRegistry extends AliasRegistry {

    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

    1. throws BeanDefinitionStoreException;

    void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    boolean containsBeanDefinition(String beanName);

    String[] getBeanDefinitionNames();

    int getBeanDefinitionCount();

    boolean isBeanNameInUse(String beanName);

}

  1. 这里借助`BeanDefinitionRegistry``registerBeanDefinition`方法往IOC容器中注册Bean。该方法包含两个入参,第一个参数为需要注册的Bean名称,第二个参数为Bean的定义信息,它是一个接口,我们可以用其实现类`RootBeanDefinition`来完成:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/493248/1624932571373-b470a8b7-6d81-43db-ab8d-3519fc966015.png#clientId=u3270af3a-3b6d-4&from=paste&height=262&id=u9e2755e6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=262&originWidth=952&originalType=binary&ratio=1&size=70490&status=done&style=none&taskId=u2fba5581-d818-4570-a8a6-69c5226dd34&width=952)<br />为了演示`ImportBeanDefinitionRegistrar`的使用,新增一个类`Blueberry`。<br />创建一个`ImportBeanDefinitionRegistrar`实现类`MyImportBeanDefinitionRegistrar`:
  2. ```java
  3. public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  4. @Override
  5. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  6. final String beanName = "blueberry";
  7. boolean contain = registry.containsBeanDefinition(beanName);
  8. if (!contain) {
  9. RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Blueberry.class);
  10. registry.registerBeanDefinition(beanName, rootBeanDefinition);
  11. }
  12. }
  13. }

然后在配置类中导入:

  1. @Configuration
  2. @Import({MyImportBeanDefinitionRegistrar.class})
  3. public class WebConfig {...

运行启动类,观察控制台,有如下输出:

  1. blueberry

说明组件注册成功。

FactoryBean

Spring提供了FactoryBean接口,我们可以通过该接口来注册组件,该接口包含两个抽象方法和一个默认方法:
image.png
为了演示FactoryBean的作用,新建一个类Cherry
然后创建FactoryBean的实现类CherryFactoryBean

  1. public class CherryFactoryBean implements FactoryBean<Cherry> {
  2. @Override
  3. public Cherry getObject() throws Exception {
  4. return new Cherry();
  5. }
  6. @Override
  7. public Class<?> getObjectType() {
  8. return Cherry.class;
  9. }
  10. @Override
  11. public boolean isSingleton() {
  12. return false;
  13. }
  14. }

最后在配置类中注册即可:

  1. @Bean
  2. public CherryFactoryBean cherryFactoryBean() {
  3. return new CherryFactoryBean();
  4. }

修改启动类,获取cherryFactoryBean

  1. Object bean = applicationContext.getBean("cherryFactoryBean");
  2. System.out.println(bean.getClass());

运行启动类,观察控制台,有如下输出:

  1. class top.parak.entity.Cherry

可以看到,虽然获取的是ID为cherryFactoryBean的组件,但是实际上获取到的是getObject里返回的对象。
如果要获取cherryFactoryBean本身,则可以这样做:

  1. Object bean = applicationContext.getBean("&cherryFactoryBean");
  2. System.out.println(bean.getClass());

重新运行,输出如下:

  1. class top.parak.entity.CherryFactoryBean

为什么这里加上&前缀就可以获取到相应的工厂类? 查看BeanFactory源码就可以看到原因:

  1. public interface BeanFactory {
  2. /**
  3. * Used to dereference a {@link FactoryBean} instance and distinguish it from
  4. * beans <i>created</i> by the FactoryBean. For example, if the bean named
  5. * {@code myJndiObject} is a FactoryBean, getting {@code &myJndiObject}
  6. * will return the factory, not the instance returned by the factory.
  7. */
  8. String FACTORY_BEAN_PREFIX = "&";