从 Spring - 从官方文档中认识 IoC 容器 这篇文章中我们知道 IoC 容器
管理着所有的 Beans
。我们需要把实例的创建交给 Spring
管理,也就是把 Bean
加入到 IoC 容器
中进行管理,才能使用 IoC 容器
的 控制反转
和 依赖注入
功能。
这里我们使用 Java-based 的方式 来配置 IoC 容器:
MyConfig
,并且标注 @Configuration
注解:@Configuration
public class MyConfig {
}
IoC 容器
:AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
这样就简单的创建了一个 IoC 容器
,我们可以输出 IoC 容器
中的 Beans
:
@Test
void initContainer() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
输出:
也可以使用 加载类路径下配置文件的方式 创建 IoC 容器
:
services.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:content="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 https://www.springframework.org/schema/context/spring-context.xsd">
<content:component-scan base-package="com.keke.web"/>
</beans>
ClassPathXmlApplicationContext
创建 IoC 容器
:ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("services.xml");
public class Blue {
}
@Bean
注解注入到容器中@Configuration
public class MyConfig {
// 方法名就是 Bean 的名称,返回值的类型就是 Bean 的类型
@Bean
public Blue blue() {
return new Blue();
}
}
# 输出 IoC 容器中的 Bean 名称
myConfig
blue
自定义 Bean
的名称:
@Bean
中指定名称作用: 扫描指定路径下标注了
@Controller
、@Service
、@Repository
、@Component
注解的类并加入到IoC 容器
中
@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 {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class";
boolean useDefaultFilters() default true;
ComponentScan.Filter[] includeFilters() default {};
ComponentScan.Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
有几种常用写法,目录结构如下,类都是新建的类,里面什么都没有,分别标注了 @Controller
、@Service
、@Repository
、@Component
四个注解:
// 扫描 com.keke.web 下的组件
@ComponentScan(basePackages ="com.keke.web")
// 源码中可以看到:value 同 basePackages 互为别名
@ComponentScan(value ="com.keke.web")
// 不指定属性的话默认为 value 属性的值,value 是一个 string[] 类型
@ComponentScan({"com.keke.web.controller", "com.keke.web.service", "com.keke.web.dao"})
// 扫描指定类以及指定类所在目录及其子目录下的组件
@ComponentScan(basePackageClasses = Application.class)
// 可以指定多个,规则同上
@ComponentScan(basePackageClasses = {Application.class, BookController.class})
上面的几种方式也可以使用 @ComponentScans
组合起来:
@Configuration
@ComponentScan(basePackages ="com.keke.web")
@ComponentScan(value ="com.keke.web")
@ComponentScan({"com.keke.web.controller", "com.keke.web.service", "com.keke.web.dao"})
@ComponentScan(basePackageClasses = Application.class)
@ComponentScan(basePackageClasses = {Application.class, BookController.class})
@ComponentScans({
@ComponentScan("com.keke.web.controller"),
@ComponentScan("com.keke.web.service")
})
public class MyConfig {
@Bean()
public Blue blue() {
return new Blue();
}
}
# 输出如下:
myConfig
application
bookController
bookDao
bookService
blue
指定排除过滤器:排除某些 Bean 不扫描
// 排除 @Controller 注解标注的组件
@ComponentScan(basePackages ="com.keke.web", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class})
})
指定包含过滤器:只扫描包含符合过滤器规则的 Bean。
注意:必须设置 useDefaultFilters 属性为 false
// 只包含 @Controller 注解标注的组件
@ComponentScan(basePackages ="com.keke.web", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class})
}, useDefaultFilters = false)
Filter
是@ComponentScan
注解的内部类形式的注解。对于内部类
,可以参考Java - 内部类
这篇文章
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
FilterType
指定过滤的类型,有以下几种类型:
public enum FilterType {
// 注解
ANNOTATION,
// 指定类
ASSIGNABLE_TYPE,
// ASPECTJ 表达式
ASPECTJ,
// 正则表达式
REGEX,
// 自定义规则,需要实现 TypeFilter 接口
CUSTOM;
private FilterType() {
}
}
前四种类型都比较好理解,这里我们尝试一下 CUSTOM
类型,需要实现 TypeFilter
接口:
@FunctionalInterface
public interface TypeFilter {
boolean match(MetadataReader var1, MetadataReaderFactory var2) throws IOException;
}
具体我们需要扫描 类名中包含 Book
的组件,我们可以这么定义:
public class MyTypeFilter implements TypeFilter {
/**
*
* @param metadataReader 读取当前正在扫描的类的信息
* @param metadataReaderFactory 可以获取到任何类的信息(工厂类)
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// 获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 获取当前正在扫描的类的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 获取当前类资源(类路径等)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
// 如果返回true,则符合匹配,会注入到Ioc容器中
return className.contains("Book");
}
}
并且在 @ComponentScan
注解中指定扫描类型:
@ComponentScan(basePackages = "com.keke.web", includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class)
}, useDefaultFilters = false)
@Import
注解中的value
可以填三种值:1、要导入的类 2、ImportSelector 3、ImportBeanDefinitionRegistrar
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
快速的给容器中导入一个组件,Bean 名称
为 要导入的组件的 全类名
// Bean 名称为:com.keke.bean.Red
@Import(Red.class)
@Import({Red.class, Blue.class})
selectImports
方法的返回值就是要导入到容器中的组件的全类名
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
举例,自定义 MyImportSelector
实现 ImportSelector
,返回值为需要导入的组件的 全类名
:
public class MyImportSelector implements ImportSelector {
/**
*
* @param importingClassMetadata 当前标注@Import注解的类的所有注解信息
* @return 返回值就是要导入到容器中的组件全类名
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.keke.bean.Red", "com.keke.bean.Blue"};
}
}
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
this.registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
举例,使用 registerBeanDefinition
方法注册 Bean
:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
*
* @param importingClassMetadata:当前类的注解信息
* @param registry BeanDefinition注册类
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean definition = registry.containsBeanDefinition("com.keke.bean.Red");
boolean definition1 = registry.containsBeanDefinition("com.keke.bean.Blue");
if (definition && definition1) {
// 定义bean信息
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RainBow.class);
// 注册bean,指定bean名
registry.registerBeanDefinition("rainBow", rootBeanDefinition);
}
}
}
Spring 官方文档 中有介绍,实现了这个接口的 Bean
,作为一个 工厂 Bean
,并不是为了注入他自己到 IoC 容器
中,而是会注入 getObject
方法返回的对象的到 IoC 容器
中。常用于框架中,比如 AOP
中:
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
举例,FactoryBean
中的泛型就是要注入到容器中的 Bean 的类型:
public class ColorFactoryBean implements FactoryBean<Color> {
@Override
public Color getObject() throws Exception {
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
}
加入到 IoC 容器 中:
@Bean
public ColorFactoryBean colorFactoryBean() {
return new ColorFactoryBean();
}
上面注入到 IoC 容器中的 Bean 的名称应该为:colorFactoryBean
,我们获取这个 Bean 的类型看一下:
Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
System.out.println(colorFactoryBean.getClass()); // class com.keke.bean.Color
输出的是 getObject
方法返回的类型。如果我们需要获取 FactoryBean
本身,需要在 Bean 名称
前面加 &
:
Object bean = applicationContext.getBean("&colorFactoryBean");
System.out.println(bean.getClass()); // class com.keke.bean.ColorFactoryBean
对于这一点,我们可以在 BeanFactory
(ApplicationContext
继承 BeanFactory
,并实现了一些高级功能)中看到: