@ComponentScan 注解概述

1.扫描时排除注解标注的类

例如,我们现在排除@Controller、@Service和@Repository注解,我们可以在PersonConfig类上通过@ComponentScan注解的excludeFilters()实现。例如,我们在PersonConfig类上添加了如下的注解。

1
2
3
@ComponentScan(value = "io.mykit.spring", excludeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class, Repository.class})
})

2.扫描时只包含注解标注的类

例如,我们需要Spring在扫描时,只包含@Controller注解标注的类,可以在PersonConfig类上添加@ComponentScan注解,设置只包含@Controller注解标注的类,并禁用默认的过滤规则,如下所示。

1
2
3
4
@ComponentScan(value = "io.mykit.spring", includeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)

3.重复注解

不知道小伙伴们有没有注意到ComponentScan注解类上有一个如下所示的注解。

1
@Repeatable(ComponentScans.class)

4.FilterType中常用的规则

(1)ANNOTATION

ANNOTATION:按照注解进行过滤

例如,使用@ComponentScan注解进行包扫描时,按照注解只包含标注了@Controller注解的组件,如下所示。

1
2
3
@ComponentScan(value = "io.mykit.spring", includeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)

(2)ASSIGNABLE_TYPE

ASSIGNABLE_TYPE:按照给定的类型进行过滤

例如,使用@ComponentScan注解进行包扫描时,按照给定的类型只包含PersonService类(接口)或其子类(实现类或子接口)的组件,如下所示。

1
2
3
@ComponentScan(value = "io.mykit.spring", includeFilters = {
@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {PersonService.class})
}, useDefaultFilters = false)

此时,只要是PersonService类型的组件,都会被加载到容器中。也就是说:当PersonService是一个Java类时,Person类及其子类都会被加载到Spring容器中;当

PersonService是一个接口时,其子接口或实现类都会被加载到Spring容器中。

(3)ASPECTJ

ASPECTJ:按照ASPECTJ表达式进行过滤**

例如,使用@ComponentScan注解进行包扫描时,按照ASPECTJ表达式进行过滤,如下所示。

1
2
3
@ComponentScan(value = "io.mykit.spring", includeFilters = {
@Filter(type = FilterType.ASPECTJ, classes = {AspectJTypeFilter.class})
}, useDefaultFilters = false)

(4)REGEX

REGEX:按照正则表达式进行过滤

例如,使用@ComponentScan注解进行包扫描时,按照正则表达式进行过滤,如下所示。

1
2
3
@ComponentScan(value = "io.mykit.spring", includeFilters = {
@Filter(type = FilterType.REGEX, classes = {RegexPatternTypeFilter.class})
}, useDefaultFilters = false)

(5)CUSTOM

CUSTOM:按照自定义规则进行过滤

如果实现自定义规则进行过滤时,自定义规则的类必须是org.springframework.core.type.filter.TypeFilter接口的实现类。

例如,按照自定义规则进行过滤,首先,我们需要创建一个org.springframework.core.type.filter.TypeFilter接口的实现类MyTypeFilter,如下所示。

1
2
3
4
5
6
public class MyTypeFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return false;
}
}

当我们实现TypeFilter接口时,需要实现TypeFilter接口中的match()方法,match()方法的返回值为boolean类型。当返回true时,表示符合规则,会包含在Spring容器中;当返回false时,表示不符合规则,不会包含在Spring容器中。另外,在match()方法中存在两个参数,分别为MetadataReader类型的参数和MetadataReaderFactory类型的参数,含义分别如下所示。

  • metadataReader:读取到的当前正在扫描的类的信息。
  • metadataReaderFactory:可以获取到其他任务类的信息。

接下来,使用@ComponentScan注解进行如下配置。

1
2
3
4
@ComponentScan(value = "io.mykit.spring", includeFilters = {
@Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
}, useDefaultFilters = false)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;

/**
* @author binghe
* @version 1.0.0
* @description 自定义过滤规则
*/
public class MyTypeFilter implements TypeFilter {
/**
* metadataReader:读取到的当前正在扫描的类的信息
* 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();
//打印当前正在扫描的类名
System.out.println("-----> " + className);
return false;
}
}

@Scope注解概述

@Scope注解中的取值如下所示

  • singleton:表示组件在Spring容器中是单实例的,这个是Spring的默认值,Spring在启动的时候会将组件进行实例化并加载到Spring容器中,之后,每次从Spring容器中获取组件时,直接将实例对象返回,而不必再次创建实例对象。从Spring容器中获取对象,小伙伴们可以理解为从Map对象中获取对象。
  • prototype:表示组件在Spring容器中是多实例的,Spring在启动的时候并不会对组件进行实例化操作,而是每次从Spring容器中获取组件对象时,都会创建一个新的实例对象并返回。
  • request:每次请求都会创建一个新的实例对象,request作用域用在spring容器的web环境中。
  • session:在同一个session范围内,创建一个新的实例对象,也是用在web环境中。
  • application:全局web应用级别的作用于,也是在web环境中使用的,一个web应用程序对应一个bean实例,通常情况下和singleton效果类似的,不过也有不一样的地方,singleton是每个spring容器中只有一个bean实例,一般我们的程序只有一个spring容器,但是,一个应用程序中可以创建多个spring容器,不同的容器中可以存在同名的bean,但是sope=aplication的时候,不管应用中有多少个spring容器,这个应用中同名的bean只有一个。

其中,request和session作用域是需要Web环境支持的,这两个值基本上使用不到,如果我们使用Web容器来运行Spring应用时,如果需要将组件的实例对象的作用域设置为request和session,我们通常会使用request.setAttribute(“key”,object)和session.setAttribute(“key”, object)的形式来将对象实例设置到request和session中,通常不会使用@Scope注解来进行设置。

Spring在启动时,默认会将单实例bean进行实例化,并加载到Spring容器中。也就是说,单实例bean默认在Spring容器启动的时候创建对象,并将对象加载到Spring容器中。如果我们需要对某个bean进行延迟加载,我们该如何处理呢?此时,就需要使用到@Lazy注解了。

也就是说 @Bean 注入的时候spring 默认的 是单例模式 singleton ,并且在Spring启动的时候会将组件进行实例化并加载到Spring容器中。

加上@Lazy 注解,容器初始化并不会实例化,当用到这个对象的时候才会 加入容器中。以后每次获取bean对象时,直接返回创建好的对象。

总结:懒加载,也称延时加载。仅对单例bean生效。单例bean是在Spring容器启动的时候加载的,添加@Lazy注解后就会延迟加载,在Spring容器启动的时候并不会加载,而是在第一次使用此bean的时候才会加载,但当你多次获取bean的时候不会重复加载,只是在第一次获取的时候会加载,这不是延迟加载的特性,而是单例Bean的特性。

那么可以按照特定的条件才注入bena 么?可以的,请看下面

@Conditional注解概述

Spring支持按照条件向IOC容器中注册bean,满足条件的bean就会被注册到IOC容器中,不满足条件的bean就不会被注册到IOC容器中

@Conditional注解可以按照一定的条件进行判断,满足条件向容器中注册bean,不满足条件就不向容器中注册bean。

实例

比如,如果当前操作系统是Windows操作系统,则向Spring容器中注册binghe001;如果当前操作系统是Linux操作系统,则向Spring容器中注册binghe002。

1
2
3
4
5
6
7
8
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person implements Serializable {
private static final long serialVersionUID = 7387479910468805194L;
private String name;
private Integer age;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class SpringbootBean {
@Conditional({WindowsCondition.class})
@Bean("binghe001")
public Person person01(){
return new Person("binghe001", 18);
}
@Conditional({LinuxCondition.class})
@Bean("binghe002")
public Person person02(){
return new Person("binghe002", 20);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testAnnotationConfig(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringbootBean.class);
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
System.out.println(osName);

String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println);

Map<String, Person> beans = context.getBeansOfType(Person.class);
System.out.println(beans);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class LinuxCondition implements Condition {
/**
* ConditionContext:判断条件使用的上下文环境
* AnnotatedTypeMetadata:注释信息
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//判断是否是Linux系统
//1.获取到IOC容器使用的BeanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2.获取类加载器
ClassLoader classLoader = context.getClassLoader();
//3.获取当前的环境信息
Environment environment = context.getEnvironment();
//4.获取bean定义的注册类,我们可以通过BeanDefinitionRegistry对象查看
//Spring容器中注册了哪些bean,也可以通过BeanDefinitionRegistry对象向
//Spring容器中注册bean,移除bean,查看bean的定义,查看是否包含某个bean的定义
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
return property.contains("linux");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @description Windows条件,判断操作系统是否是Windows
*/
public class WindowsCondition implements Condition {
/**
* ConditionContext:判断条件使用的上下文环境
* AnnotatedTypeMetadata:注释信息
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//判断是否是Linux系统
//1.获取到IOC容器使用的BeanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2.获取类加载器
ClassLoader classLoader = context.getClassLoader();
//3.获取当前的环境信息
Environment environment = context.getEnvironment();
//4.获取bean定义的注册类,我们可以通过BeanDefinitionRegistry对象查看
//Spring容器中注册了哪些bean,也可以通过BeanDefinitionRegistry对象向
//Spring容器中注册bean,移除bean,查看bean的定义,查看是否包含某个bean的定义
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
return property.contains("Windows");
}
}

修改LinuxCondition 直接返回true 的时候。可见单元测试会包含binghe002 bean

说明改配置可以成功

@Conditional注解也可以标注在类上,标注在类上含义为:满足当前条件,这个类中配置的所有bean注册才能生效

@Conditional的扩展注解

@ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。
@ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。

@ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。
@ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。

@ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
@ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。

@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。

@ConditionalOnExpression:当SpEL表达式为true的时候,才会实例化一个Bean。
@ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
@ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
@ConditionalOnResource:当类路径下有指定的资源时触发实例化。
@ConditionalOnJndi:在JNDI存在的条件下触发实例化。

@Import注解概述

我们自己写的类,可以通过包扫描+标注注解(@Controller、@Servcie、@Repository、@Component)的形式将其注册到IOC容器中,如果不是我们自己写的类,比如,我们在项目中引入了一些第三方的类库,此时,我们需要将这些第三方类库中的类注册到Spring容器中,该怎么办呢?此时,我们就可以使用@Bean和@Import注解将这些类快速的导入Spring容器中。

注册bean的方式

向Spring容器中注册bean通常有以下几种方式

  • 包扫描+标注注解(@Controller、@Servcie、@Repository、@Component),通常用于自己写的类。
  • @Bean注解,通常用于导入第三方包中的组件。
  • @Import注解,快速向Spring容器中导入组件。

@Import只允许放到类上面,不能放到方法上

下面使用@Import注解给容器中快速导入一个组件

在扫面类加上,即可把自己项目的类或者第三方的类放入到spring 容器。 默认容器 id 是类名的全路径

@Import注解的使用方式

1.直接填class数组方式

下面的bean id 分别是com.aa.A 和 com.aa.B

@bean 注入容器的是方法名为容器id 这里区分下

1
@Import({com.aa.A.class, com.aa.B.class})

2.ImportSelector方式【重点】

直接填class数组方式 :假设好多需要导入的类,那么在类上写好几十个显然不优雅的, 所以ImportSelector方式可以不就一下

ImportSelector接口 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* 测试@Import注解中使用ImportSelector
* 自定义逻辑,返回需要导入的组件
*/
public class MyImportSelector implements ImportSelector {

/**
* 返回值为需要导入到容器中的bean的全类名数组 ,可以多个类名"com.xxx.Person"
* AnnotationMetadata:当前标注@Import注解的类的所有注解信息
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
Person.class.getName()
};
}
}

加上配置

1
@Import(value = {MyImportSelector.class})

输出可以看到person 已经注入了容器

ImportSelector接口是至spring中导入外部配置的核心接口,其主要作用是收集需要导入的配置类,selectImports()方法的返回值就是我们向Spring容器中导入的类的全类名。如果该接口的实现类同时实现EnvironmentAware, BeanFactoryAware ,BeanClassLoaderAware或者ResourceLoaderAware,那么在调用其selectImports方法之前先调用上述接口中对应的方法,如果需要在所有的@Configuration处理完在导入时可以实现DeferredImportSelector接口。

编写自己得enablexxx

1
2
3
4
5
6
7
8
9
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
public @interface EnableSpringStudy {
}

然后打jar, 其他项目只要加上@EnableSpringStudy 注解 就自动注入了你写好得bean 了,是不是好像写了一个框架一样。手动滑稽。

之后还有种方式自动注入。参考

在resources 文件夹下新建 META-INF\spring.factories

1
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.shuzhuo.core.EncryptedAutoConfigure
1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
* @author shuzhuo
* @date 2020/6/3 11:19
*/
@Configuration
//@ComponentScan(basePackages = {"com.shuzhuo.core"})
@EnableSpringStudy
public class EncryptedAutoConfigure {
}

是不是很nb 得样子,手动滑稽

假设需要动态注入bean 呢?

3.ImportBeanDefinitionRegistrar方式

Spring官方在动态注册bean时,大部分套路其实是使用ImportBeanDefinitionRegistrar接口

使用方法

ImportBeanDefinitionRegistrar需要配合@Configuration和@Import注解,@Configuration定义Java格式的Spring配置文件,@Import注解导入实现了ImportBeanDefinitionRegistrar接口的类。

ImportBeanDefinitionRegistrar 实例

1
2
public class Company {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

/**
* ImportBeanDefinitionRegistrar的实现类
*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

/**
* AnnotationMetadata: 当前类的注解信息
* BeanDefinitionRegistry:BeanDefinition注册类
* 通过调用BeanDefinitionRegistry接口的registerBeanDefinition()方法,可以将所有需要添加到容器中的bean注入到容器中。
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){
/**
* 判断Spring容器中是否同时存在 两个bean,存在 则向Spring容器中注入一个以company命名的bean
*/
boolean personBean = registry.containsBeanDefinition("person");
// 找一个不存在得bean aa
// boolean department = registry.containsBeanDefinition("aaa");
boolean department = true;
if (personBean && department){
BeanDefinition beanDefinition = new RootBeanDefinition(Company.class);
registry.registerBeanDefinition("Company", beanDefinition);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
* @author shuzhuo
* @date 2020/7/8 15:47
*/
@Configuration
//@Import(value = {MyImportBeanDefinitionRegistrar.class,MyImportSelector.class}) //--以 MyImportSelector 方式注入bean 可以
//@Import(value = {MyImportSelector.class,MyImportBeanDefinitionRegistrar.class}) //--以 MyImportSelector 方式注入bean 可以
@Import(value = {MyImportBeanDefinitionRegistrar.class}) //--把bean 以@Bean 注入也可以
public class SpringbootBean {

@Conditional({WindowsCondition.class})
@Bean
public Person person(){
return new Person("binghe001", 18);
}
//
// @Conditional({LinuxCondition.class})
// @Bean("binghe002")
// public Person person02(){
// return new Person("binghe002", 20);
// }
}
1
2
3
4
5
6
7
8
9
10
    @Test
public void testAnnotationConfig(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringbootBean.class);
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
// System.out.println(osName);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
Arrays.stream(beanDefinitionNames).forEach(System.out::println);

}

这里没有连贯性,先学习吧

那么如何使用FactoryBean向Spring容器中注册bean?

FactoryBean概述

T getObject():返回由FactoryBean创建的bean实例,如果isSingleton()返回true,则该实例会放到Spring容器中单实例缓存池中。

boolean isSingleton():返回由FactoryBean创建的bean实例的作用域是singleton还是prototype。

Class getObjectType():返回FactoryBean创建的bean类型。

FactoryBean 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.springframework.beans.factory.FactoryBean;

/**
* 商品的FactoryBean,测试FactoryBean
*/
public class PersonFactoryBean implements FactoryBean<Person> {

//返回一个Person对象,这个对象会被注册到Spring容器中
@Override
public Person getObject() throws Exception {
return new Person();
}

@Override
public Class<?> getObjectType() {
return Person.class;
}

//bean是否为单例;true:是;false:否
@Override
public boolean isSingleton() {
return true;
}
}
1
2
3
4
@Bean
public PersonFactoryBean personFactoryBean(){
return new PersonFactoryBean();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  @Test
public void testAnnotationConfig(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringbootBean.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
Arrays.stream(beanDefinitionNames).forEach(System.out::println);
Object personFactoryBean1 = context.getBean("personFactoryBean");
Object personFactoryBean2 = context.getBean("personFactoryBean");
System.out.println("personFactoryBean1类型:" + personFactoryBean1.getClass());
System.out.println("personFactoryBean2类型:" + personFactoryBean2.getClass());
System.out.println(personFactoryBean1 == personFactoryBean2);
// 只需要在获取bean对象时,在id前面加上&符号即可
Object personFactoryBean3 = context.getBean("&personFactoryBean");
System.out.println("personFactoryBean3类型:" + personFactoryBean3.getClass());
}

虽然我在代码中使用@Bean注解注入的PersonFactoryBean对象,但是,实际上从Spring容器中获取到的bean对象却是调用PersonFactoryBean类中的getObject()获取到的Person对象。

想获取PersonFactoryBean实例,该怎么办呢?

只需要在获取bean对象时,在id前面加上&符号即可