装配Bean

Spring 中有多种 Bean 的装配方式,这里不再详细介绍,我们主要介绍基于注解的方式装配 Bean

在上文已经使用过 @Bean 注解,但是这样的方式有些烦琐,我们可以使用更好的方式。扫描注解就是其中一种,它通过 @ComponentScan@Component 配合使用。

@ComponentScan简介

@Component 指定要被扫描的类,而 @ComponentScan 则把标明 @Component 的类作为 Bean 装配到 IOC 容器中。在讲解之前,可还是先看看 @ComponentScan 的代码,如下所示。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
   //定义扫描的包,对应的包扫描路径,可以是单个路径,也可以是扫描的路径数组
   @AliasFor("basePackages")
   String[] value() default {};
   //定义扫描的包
   @AliasFor("value")
   String[] basePackages() default {};
   //定义扫描的类
   Class<?>[] basePackageClasses() default {};
   //bean name生成器
      Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
   //处理检测到的bean的scope范围
      Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
   //作用域代理
   ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
   //资源匹配
   String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
   //是否启用默认的过滤器
   boolean useDefaultFilters() default true;
   //过滤满足条件的
      org.springframework.context.annotation.ComponentScan.Filter[] includeFilters() default {};
   //过滤不满足条件的
      org.springframework.context.annotation.ComponentScan.Filter[] excludeFilters() default {};
   //是否延迟初始化
   boolean lazyInit() default false;
   //定义过滤器
   @Retention(RetentionPolicy.RUNTIME)
   @Target({})
   @interface Filter {
      //过滤器类型
      FilterType type() default FilterType.ANNOTATION;
      //过滤器的类
      @AliasFor("classes")
      Class<?>[] value() default {};
      //过滤器的类
      @AliasFor("value")
      Class<?>[] classes() default {};
      //匹配方式
      String[] pattern() default {};
   }
}

上面的源码中,虽已加过注解,但是有些重要的知识点需重新说明。代码中有一个配置项 basePackages,如果不配置,则默认扫描当前的包以及其子包下被标注的类;还可以使用配置项 value,这个和 basePackages 一样,可以扫描单个包路径,也可以扫描多个包路径;同时,可以使用 basePackageClasses 定义具体要扫描的类。

在上面的源码中,还使用了排除与过滤的方式,includeFilters 定义符合条件的包才会扫描;excludeFilters 则是不会扫描定义中的包的类,但是需要 @interface Filter 配合使用。在注解中还有一个 Type 类型,其中 FilterType 有 5 种类型,如 ANNOTATION 是注解类型,它是默认的类型从上面的源码中可以看出来;ASSIGNABLE_TYPE 指定固定类;ASPECTJASPECTJ 类型;REGEX 是正则表达式;CUSTOM 是自定义类型,使用方式会在实例中展示。

注意:有些别的注解也注入了 @Component,并且在默认情况下会被注入 IOC 容器,常见的几个注解有 @Component@Repository@Service@Controller 等。

@ComponentScan使用实例

在 4.2.1 中已经介绍了 @ComponentScan,但是具体的实例还没有讲解,下面主要讲两个实例,一个是以扫描包的方式装配 Bean,另一个是以排除 @Controller 的方式装配 Bean。下面的实例,是在上文的源码的基础上修改完成的,包名不变。

以扫描包的方式装配bean

首先改造 pojo 中的类,在注解上添加 @Component,然后使用 @Value 注解添加变量值,方便测试时查看效果,代码如下所示。

package com.springBoot.ioc.pojo;
/**
  * @Desciption 一个简单的bean对象
  */
@Component("student")
public class Student {
   //三个基本属性
   @Value("100")
   private Long id;
   @Value("Tom")
   private String username;
   @Value("123456")
   private String password;

   public Long getId() {
      return id;
   }

   /**
   * generate setters and getters
   */
}

在上面的代码中,重点就是添加 @Component 以进行标注。有一个地方需要注意,@Component 中一般需要指定 Beanname,不然默认的是将类名的首字母变为小写的 name

然后修改 config 包下的配置文件 MySpringBootConfig,代码如下所示。

package com.springBoot.ioc.config;

@Configuration
@ComponentScan(value = "com.springBoot.ioc.pojo")
public class MySpringBootConfig{
}

在上面的代码中,添加了 @ComponentScan,就不再需要像 @Bean 方式生成对象,然后注册,而是根据 value 指定的包名扫描 Bean。这样更加简化,不再需要 new 对象。

除了上面代码中的使用方式,还可以有以下几种方式。

@ComponentScan(basePackages = "com.springBoot.ioc.pojo")
@ComponentScan("com.springBoot.ioc.pojo")
@ComponentScan(basePackageClasses = Student.class)

最后,校验是否可以使用,我们还是使用以前的测试类,和上次相比较,会发现代码没有修改过,代码如下所示。

package com.springBoot.ioc.test;
/**
  * @Desciption IOC实例测试类
  */
public class IocTestDemo {

   private static final Logger log=LoggerFactory.getLogger(IocTestDemo.class);

   public static void main(String[] args) {
      ApplicationContext context=new AnnotationConfigApplicationContext(MySpringBootConfig.class);
      Student student=context.getBean(Student.class);
      log.info("id= "+(student.getId()+""));
      log.info("username= "+student.getUsername());
      log.info("password= "+student.getPassword());
   }
}

效果如下所示。

16:12:32.350 [main] INFO com.springBoot.ioc.test.IocTestDemo - id=100
16:12:32.350 [main] INFO com.springBoot.ioc.test.IocTestDemo username= Tom
16:12:32.350 [main] INFO com.springBoot.ioc.test.IocTestDemo password= 123456

控制默认加载的Bean

在下面的代码中,我们验证 @Controller 时对应的类会被默认加载,因此会使用属性控制,使得 @Controller 对应的类不被扫描加载。通过下面的实例讲解,可了解属性的使用。

在这里新建包,为了清晰地展示代码,这里展示一下代码结构,如图4.4所示。

image 2024 03 31 15 33 56 142
Figure 1. 图4.4 程序结构

首先,新建 controller 包,在此包下新建 StudentController 类,代码如下所示。

package com.springBoot.ioc.controller;

/**
  * @Description 用于演示的控制类
  */
@Controller("studentController")
public class StudentController {
}

在上面这段代码中,我们可以看到,代码中加了 @Controller 注解。然后,我们去配置项中添加包路径,如下所示。

package com.springBoot.ioc.config;

@Configuration
@ComponentScan("com.springBoot.ioc.pojo,com.springBoot.ioc. controller")
public class MySpringBootConfig{
}

在上面的这段代码中,编写方式有点费事,只是想表示已经将 controller 包添加过来了。在实际的代码中,建议用正则表达式写包路径。最后,修改测试类代码如下所示。

package com.springBoot.ioc.test;
/**
  * @Desciption IOC实例测试类
  */
public class IocTestDemo {
   private static final Logger log=LoggerFactory.getLogger(IocTestDemo. class);
   public static void main(String[] args) {
      ApplicationContext context=new AnnotationConfigApplicationContext(MySpringBootConfig.class);
      //student
      Student student=context.getBean(Student.class);
      log.info("id= "+(student.getId()+""));
      log.info("username= "+student.getUsername());
      log.info("password= "+student.getPassword());
      //studentController
      StudentController studentController=context. getBean(StudentController.class);
      log.info("studentController name: "+studentController. getClass().getName());
   }
}

展示控制台效果如下所示。

16:27:11.610[main]DEBUGorg.springframework.beans.factory.support. DefaultListableBeanFactory-Returning cached instance of singleton bean 'student'
16:27:11.611 [main] INFO com.springBoot.ioc.test.IocTestDemo - id=100
16:27:11.611 [main] INFO com.springBoot.ioc.test.IocTestDemo username= Tom
16:27:11.611 [main] INFO com.springBoot.ioc.test.IocTestDemo password= 123456
16:27:11.611 [main] DEBUG org.springframework.beans.factory.support. DefaultListableBeanFactory - Returning cached instance of singleton bean 'studentController'
16:27:11.611 [main] INFO com.springBoot.ioc.test.IocTestDemo studentController name: com.springBoot.ioc.controller.StudentController

通过日志,可以看到 studentController 装配到 IOC 中了。如果不想让 StudentController 的 Bean 被默认加载,要如何做?其实在上文讲解知识点时已经提及,就是使用 excludesFilters,具体的做法依旧通过实例来展示。因为代码修改的部分有 config 下的配置文件,代码如下所示。

package com.springBoot.ioc.config;

/**
  * @Desciption 配置文件
  */
@Configuration
@ComponentScan(basePackages="com.springBoot.ioc.*",
        excludeFilters = {@ComponentScan.Filter(type = FilterType. ANNOTATION,classes = Controller.class)})
public class MySpringBootConfig{
}

在这段代码中,要注意一点,classes=Controller.class,而不是写成具体的类文件,不然会有如下报错信息。

Caused by: java.lang.IllegalArgumentException: @ComponentScan ANNOTATION type filter requires an annotation type: class com.springBoot. ioc.controller.StudentController

同时要修改测试类代码,因为这时已经没有 StudentController 这个 Bean,但是为了明显看到获取不到 StudentController 这个 Bean,代码就不再修改。执行测试代码,效果如下所示。

Exception in thread "main" org.springframework.beans.factory. NoSuchBeanDefinitionException: No qualifying bean of type ' com. springBoot.ioc.controller.StudentController' available

所以我们可以通过属性控制类的 Bean 默认加载。