本节再介绍一些常用的将Bean装配到IoC容器的方法。
如果使用注解@Bean将Bean一个个地装配到IoC容器中,那将是一件很麻烦的事情。好在Spring允许通过扫描类的方式来创建对象,然后装配到IoC容器中,这种方法使用的注解是@Component和@ComponentScan的结合。
● @Component :标注扫描哪些类,创建Bean并装配到IoC容器中。
● @ComponentScan :配置采用何种策略扫描并装配Bean。
这里我们首先把代码清单3-2中的User.java移到包com.learn.chapter3.config内,然后对其进行修改,如代码清单3-5所示。
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所示。
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所示。
// 使用配置文件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所示。
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所示。
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所示。
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)})
这样就能扫描指定的包并排除对应的类了。
开发现实的Java应用往往需要引入许多第三方包,并且很有可能希望把第三方包的类对象也装配到IoC容器中,这时注解@Bean就可以发挥作用了。
例如,要引入一个MySQL数据源,我们先在pom.xml中添加数据库MySQL驱动程序的依赖,如代码清单3-11所示。
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
这样,我们就引入了MySQL的JDBC驱动。下面我们创建数据源并放入IoC容器中。此时,可以把代码清单3-12中的代码放置到AppConfig.java中。
// 注解@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容器中了。