购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.2 装配你的Bean

本节再介绍一些常用的将Bean装配到IoC容器的方法。

3.2.1 通过扫描装配你的Bean

如果使用注解@Bean将Bean一个个地装配到IoC容器中,那将是一件很麻烦的事情。好在Spring允许通过扫描类的方式来创建对象,然后装配到IoC容器中,这种方法使用的注解是@Component和@ComponentScan的结合。

@Component :标注扫描哪些类,创建Bean并装配到IoC容器中。

@ComponentScan :配置采用何种策略扫描并装配Bean。

这里我们首先把代码清单3-2中的User.java移到包com.learn.chapter3.config内,然后对其进行修改,如代码清单3-5所示。

代码清单3-5 加入注解@Component
package com.learn.chapter3.config;
 
/**** imports ****/
@Component("user")
public class User {
 
   @Value("1")
   private Long id;
   @Value("user_name_1")
   private String userName;
   @Value("note_1")
   private String note;
   
   /** setters and getters **/
}

上述代码中的注解@Component标注这个类将被IoC容器扫描、装配,其中配置的“user”则作为Bean的名称,当然也可以不配置这个字符串,那么IoC容器就会把类名的第一个字母改为小写,其他不变,作为Bean的名称放入IoC容器中;注解@Value的作用是指定类属性的值,使得IoC容器给对应的Bean属性设置对应的值。为了让IoC容器装配这个类,需要改造类AppConfig,如代码清单3-6所示。

代码清单3-6 加入注解@ComponentScan
package com.learn.chapter3.config;
/**** imports ****/
 
// 标注为Java配置类
@Configuration
// 配置扫描策略
@ComponentScan
public class AppConfig {
}

上述代码中加入了注解@ComponentScan,这意味着IoC容器会根据它的配置进行扫描,但是只会扫描类AppConfig所在的当前包和其子包,之前把User.java移到包com.learn.chapter3.config内就是这个原因。这样就可以删掉代码清单3-3中使用@Bean标注的创建对象方法,然后进行测试,测试代码如代码清单3-7所示。

代码清单3-7 测试扫描
// 使用配置文件AppConfig.java创建IoC容器
var ctx = new AnnotationConfigApplicationContext(AppConfig.class);
try {
   // 通过getBean()方法获取Bean
   var user = ctx.getBean(User.class);
   System.out.println(user.getUserName());
} finally {
   // 关闭IoC容器
   ctx.close();
}

这样就能够进行测试了。然而,为了使User类能够被扫描,代码清单3-5把它迁移到了本不该放置它的配置包,这样显然就不太合理了。为了使配置更加合理,@ComponentScan还允许自定义扫描的包,下面探讨它的配置项。@ComponentScan的源码如代码清单3-8所示。

代码清单3-8 @ComponentScan源码
package org.springframework.context.annotation;
 
/**imports**/
@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名称生成器
   Class<? extends BeanNameGenerator> nameGenerator() 
      default BeanNameGenerator.class;
 
   // 作用域解析器
   Class<? extends ScopeMetadataResolver> scopeResolver() 
      default AnnotationScopeMetadataResolver.class;
 
   // 作用域代理模式
   ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
 
   // 资源匹配模式
   String resourcePattern() default 
      ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
 
   // 是否启用默认的过滤器
   boolean useDefaultFilters() default true;
 
   // 当满足过滤器的条件时扫描
   Filter[] includeFilters() default {};
 
   // 当不满足过滤器的条件时扫描
   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 :指定需要扫描的包名,如果不配置它或者包名为空,则只扫描当前包和其子包下的路径。

basePackageClasses :指定被扫描的类;

includeFilters :指定满足 过滤器 (Filter)条件的类将会被IoC容器扫描、装配;

excludeFilters :指定满足过滤器条件的类将不会被IoC容器扫描、装配。

lazyInit :延迟初始化,这个配置项有点复杂,3.4节介绍Bean的生命周期时会再讨论它。

includeFilters和excludeFilters这两个配置项都需要通过一个注解@Filter定义,这个注解有以下配置项。

type: 通过它可以选择通过注解或者正则式等进行过滤;

classes: 通过它可以指定通过什么注解进行过滤,只有被标注了指定注解的类才会被过滤;

pattern: 通过它可以定义过滤的正则式。

此时,我们再把User类放到包com.learn.chapter3.pojo中,这样User和AppConfig就不再同包,然后我们把AppConfig中的注解修改为

@ComponentScan("com.learn.chapter3.*")

@ComponentScan(basePackages = {"com.learn.chapter3.pojo"})

@ComponentScan(basePackageClasses = {User.class})

无论采用何种方式,都能够使IoC容器扫描User类,而包名可以采用正则式进行匹配。有时候我们的需求是扫描一些包,将一些Bean装配到IoC容器中,但是要求不能装配某些指定的Bean。例如,现在我们有一个UserService类,为了标注它为服务类,将注解@Service(这个注解上也标注了@Component,所以在默认的情况下UserService类也会被Spring扫描、装配到IoC容器中)标注在类上,这里再假设我们采用了以下策略:

@ComponentScan("com.learn.chapter3.*")

这样com.learn.chapter3.service和com.learn.chapter3.pojo两个包都会被扫描。此时定义UserService类,如代码清单3-9所示。

代码清单3-9 UserService类
package com.learn.chapter3.service;
/**** imports ****/
@Service
public class UserService {
   
   public void printUser(User user) {
      System.out.println("编号:" + user.getId());
      System.out.println("用户名:" + user.getUserName());
      System.out.println("备注:" + user.getNote());
   }
}

按照上述装配策略,UserService类将被扫描、装配到IoC容器中。为了不被装配,需要把扫描的策略修改为

@ComponentScan(basePackages = "com.learn.chapter3.*", 
   // type配置通过注解的方式进行过滤,classes指定通过什么注解进行过滤
   excludeFilters = @Filter(type=FilterType.ANNOTATION, classes = Service.class))

由于加入了excludeFilters的配置,标注了@Service的类将不被IoC容器扫描注入,因此就不会把UserService类装配到IoC容器中了。事实上,在代码清单2-3所示的Spring Boot启动类中看到的注解@SpringBootApplication也注入了@ComponentScan,这里不妨探索其源码,如代码清单3-10所示。

代码清单3-10 @SpringBootApplication源码
package org.springframework.boot.autoconfigure;
/**imports**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
// 通过excludeFilters 来自定义不扫描哪些类
@ComponentScan(excludeFilters = {
       @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
       @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
 
   // 通过类型排除自动配置类
   @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
   Class<?>[] exclude() default {};
 
   // 通过名称排除自动配置类
   @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
   String[] excludeName() default {};
 
   // 定义扫描包
   @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
   String[] scanBasePackages() default {};
 
   // 定义被扫描的类
   @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
   Class<?>[] scanBasePackageClasses() default {};
 
}

显然,通过@SpringBootApplication就能够定义扫描哪些包。但是这里需要特别注意的是,它提供的exclude()和excludeName()两个方法对于其内部的自动配置类才会生效。为了能够排除其他类,还可以再加入@ComponentScan以达到我们的目的。例如,要扫描User类而不扫描标注@Service的类,就可以把启动配置文件写成

@SpringBootApplication
@ComponentScan(basePackages = {"com.learn.chapter3"}, 
   excludeFilters = {@Filter(classes = Service.class)})

这样就能扫描指定的包并排除对应的类了。

3.2.2 自定义第三方Bean

开发现实的Java应用往往需要引入许多第三方包,并且很有可能希望把第三方包的类对象也装配到IoC容器中,这时注解@Bean就可以发挥作用了。

例如,要引入一个MySQL数据源,我们先在pom.xml中添加数据库MySQL驱动程序的依赖,如代码清单3-11所示。

代码清单3-11 引入MySQL的JDBC驱动
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
</dependency>

这样,我们就引入了MySQL的JDBC驱动。下面我们创建数据源并放入IoC容器中。此时,可以把代码清单3-12中的代码放置到AppConfig.java中。

代码清单3-12 创建MySQL数据源
// 注解@Bean表示需要将方法返回的对象装配到IoC容器中,name配置Bean名称
@Bean(name = "dataSource")
public DataSource getDataSource() {
   var dataSource = new MysqlDataSource();
   try {
      dataSource.setUrl("jdbc:mysql://localhost:3306/chapter3");
      dataSource.setUser("root");
      dataSource.setPassword("123456");
   } catch (Exception e) {
      e.printStackTrace();
   }
   return dataSource;
}

上述代码通过@Bean定义了其配置项name为"dataSource",那么Spring就会用名称“dataSource”把方法返回的对象装配到IoC容器中。当然,你也可以不填写这个名称,那么Spring就会用你的方法名称作为Bean名称装配到IoC容器中。这样就可以将第三方包的类装配到IoC容器中了。 Tv49FyX8aZSgSxFOy1DaZHssEgkcn4jq8LWacGNEmDyYLD1h4xpCfpnx03MSMK8K

点击中间区域
呼出菜单
上一章
目录
下一章
×