【Spring注解】01-组件注册

Spring 注解驱动开发【属性赋值】

  1. 组件注册 @Configuration&@Bean给容器中注册组件

    • 自定义配置类 MyConfig
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package indi.zhihuali.config;

    import indi.zhihuali.pojo.Person;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    // 配置类 <=> 配置文件

    @Configuration //声明spring配置类
    public class MyConfig {


    // @Bean 后面也可以加一个参数 作为bean的id 下面的方法名则被覆盖
    @Bean("person")
    // 给容器中注册一个bean 返回类型为bean的类型 方法名为bean的id
    public Person person02(){
    return new Person("zhihuali",20);
    }

    }

    • Test类中测试ioc注入情况
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //        通过注解(自定义配置类 @Configuration) 对ioc容器进行bean注入
    ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
    // 获取ioc容器中的bean对象并实例化
    Person person = context.getBean(Person.class);
    System.out.println(person);
    // 遍历获取类型为Person的组件在ioc容器中的id是什么
    String[] beanNamesForType = context.getBeanNamesForType(Person.class);

    for (String name : beanNamesForType) {
    System.out.println(name);
    }
  2. 组件注册 @ComponentScan 自动扫描组件及指定扫描规则

  • 在自定义配置类中定义@ComponentScan(value=扫描的包)
1
2
3
4
5
@Configuration   //声明spring配置类
@ComponentScan(value = "indi.zhihuali")
public class MyConfig {
}

  • 分别生成组件注解( controller层对应@Controller、mapper层对应@Repository、service层对应@Service 及 @Component)

  • 通过测试类对ioc容器中的组件进行扫描测试

1
2
3
4
5
6
7
8
9
10
@Test
public void testComponent(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
// 查看ioc容器中具有哪些组件
// context.getBeanDefinitionNames() 方法返回容器中所有注册了的bean的名字
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}

输出结果为:

1
2
3
4
5
myConfig 配置类@Configuration本身就是一个@Component
bookController
bookMapper
bookService
person 通过@Bean注册进ioc容器
  • 在@ComponentScan中添加参数 excludeFilters (除掉哪些bean) 或 includeFilters(包含哪些bean)
1
2
3
4
5
6
7
8
9
10
11
@Configuration   //声明spring配置类
// @ComponentScan value:指定要扫描的包
// excludeFilters 返回 Filter[] 是数组
// @ComponentScan.Filter
// (type = FilterType.ANNOTATION 根据注解进行过滤
// classes = {Controller.class, Service.class} 具体要过滤的是哪些类 @Controller对应Controller.class
@ComponentScan(value = "indi.zhihuali",excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
})
public class MyConfig {
}

添加了excludeFilters后的运行结果

1
2
3
myConfig    配置类@Configuration本身就是一个@Component
bookMapper
person 通过@Bean注册进ioc容器
  • 将@ComponentScan内参数修改为includeFilter
1
2
3
4
5
6
7
8
@ComponentScan(value = "indi.zhihuali",includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
}
// 想要设置为只包含什么什么的过滤器 首先要将默认的过滤器关掉
,userDefaultFilters = false


)

组件扫描器可以指定多个 且互补配置

同样可以通过@ComponentScans进行配置 且value为数组 值为@ComponentScan

1
2
3
4
5
6
@ComponentScans(value = {
@ComponentScan(value = "indi.zhihuali",includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
},useDefaultFilters = false
)
})
  1. 组件注册 自定义TypeFilter指定过滤规则
1
2
3
4
5
6
7
8
9
10
//  @ComponentScan  value:指定要扫描的包
// excludeFilters 返回 Filter[] 是数组 指定扫描时按什么规则排除哪些组件
// includeFilters 指定扫描时只需要包含哪些组件
// (type = FilterType.ANNOTATION 根据注解进行过滤
// FilterType.ASSIGNABLE_TYPE 根据指定类型进行过滤
// FilterType.ASPECTJ 根据ASPECTJ表达式
// FilterType.REGEX 根据正则表达式
// FilterType.CUSTOM 自定义过滤规则
// ,
// classes = {Controller.class, Service.class} 具体要过滤的是哪些类 @Controller对应Controller.class
  • 重点是 采用自定义FilterType.CUSTOM进行过滤
  1. 编写自定义过滤类
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
public class MyTypeFilter implements TypeFilter {
/*

MetadataReader 读取到的当前正在扫描的类的信息
MetadataReaderFactory 可以获取到其他任何类信息

*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// 获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 获取当前类资源(类路径)
Resource resource = metadataReader.getResource();
// 获取当前正在扫描的类的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();

// 对当前类的类信息进行打印
String className = classMetadata.getClassName();
System.out.println(classMetadata);
System.out.println(annotationMetadata);
System.out.println(resource);
System.out.println("--------------------------------");
// 若类名中包含er则放行 不包含则拦截
if(className.contains("er")){
return true;
}
return false;
}
}
  1. 在配置类中添加自定义typefilter的信息
1
2
3
4
5
6
@ComponentScans(value = {
@ComponentScan(value = "indi.zhihuali",includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM,classes = MyTypeFilter.class)
},useDefaultFilters = false
)
})
  1. 编写测试方法对ioc容器中对象进行打印
1
2
3
4
5
6
7
8
9
10
@Test
public void testComponent(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
// 查看ioc容器中具有哪些组件
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}

打印结果为

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
org.springframework.core.type.classreading.SimpleAnnotationMetadata@d282e0
org.springframework.core.type.classreading.SimpleAnnotationMetadata@d282e0
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\config\MyTypeFilter.class]
--------------------------------
org.springframework.core.type.classreading.SimpleAnnotationMetadata@438a68
org.springframework.core.type.classreading.SimpleAnnotationMetadata@438a68
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\controller\bookController.class]
--------------------------------
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1d15f18
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1d15f18
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\mapper\bookMapper.class]
--------------------------------
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1695df3
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1695df3
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\pojo\Person.class]
--------------------------------
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1ea5ab4
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1ea5ab4
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\service\bookService.class]
--------------------------------
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myConfig
myTypeFilter
bookController
bookMapper
person
bookService

注:注解扫描指定包下的所有类 如果满足放行条件则进行放行 否则就拦截

  1. 组件注册 @Scope 设置组件作用域

@Scope可选参数值

1
2
3
4
5
6
7
8
9
10
11
12
13
 /*
* @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
* @see ConfigurableBeanFactory#SCOPE_SINGLETON
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION

* value 的四种取值 :
* prototype 多实例
* singleton 单例 【默认】
* request 同一次请求 创建一次实例 【web环境】
* session 同一个session创建一个实例 【web环境】

*/

在没有显式配置Scope时 默认单例模式

1
2
3
4
5
6
7
8
@Configuration
public class MyConfig2 {
@Bean(value = "person")
public Person person(){
System.out.println("给容器添加Person..."); //看何时进行实例化Person对象
return new Person("wangjingbo",12);
}
}
1
2
3
4
@Test
public void testComponent02(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig2.class);
}

输出:

1
给容器添加Person...

通过上例可证明 默认情况下 (单例bean时),在创建Ioc容器时就自动生成bean对象 即用即取

给bean配置作用域

1
2
3
4
5
6
7
8
@Configuration
public class MyConfig2 {
@Bean(value = "person")
@Scope(value = "prototype")
public Person person(){
return new Person("wangjingbo",12);
}
}
1
2
3
4
@Test
public void testComponent02(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig2.class);
}

打印结果:空

说明多实例情况下 ioc容器启动时并不会去调用构造器创建对象并放在容器中

那么何时调用呢? 其实是在getBean时调用!

1
2
3
4
5
6
 public void testComponent02(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig2.class);
System.out.println("ioc容器初始化完成");
Object person = context.getBean("person");
}
}
1
2
ioc容器初始化完成
给容器添加Person...

若获取多个对象 则调用多次

1
2
Object person = context.getBean("person");
Object person1 = context.getBean("person");
1
2
3
ioc容器初始化完成
给容器添加Person...
给容器添加Person...
  1. 组件注册 @Lazy 懒加载bean 【针对@Scope(“singleton”)】

默认情况下 单例模式下添加bean会在创建Ioc容器时提前自动生成bean对象

通过添加@Lazy 可以达到在创建ioc容器时不再提前自动生成bean对象 而用时生成的效果

1
2
3
4
5
6
@Bean(value = "person")
@Lazy // 懒加载
public Person person(){
System.out.println("给容器添加Person...");
return new Person("wangjingbo",12);
}
  1. 组建注册 @Conditional 按照条件注册bean
1
2
3
@Conditional({condition数组}):按照一定的条件进行判断 满足条件给容器中注册bean
例如:如果系统是 windows 给容器中注册 bill
如果系统是 linux 给容器中注册 linus

将注解标注在方法上 对单一bean进行condition判断

将注解标注在类上 满足当前条件 类中的任何bean注册才会生效 类中组件统一设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class MyConfig2 {
/*
* @Conditional({condition数组}):按照一定的条件进行判断 满足条件给容器中注册bean
* 例如:如果系统是 windows 给容器中注册 bill
* 如果系统是 linux 给容器中注册 linus
* */
@Conditional({LinuxCondition.class})
@Bean("linus")
public Person person01(){
return new Person("Linus",22);
}
@Conditional({WindowsCondition.class})
@Bean("bill")
public Person person02(){
return new Person("Bill",27);
}
}

编写:LinuxCondition.class实现Condition接口

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
public class LinuxCondition implements Condition {
/*
* ConditionContext 判断条件能使用的上下文环境
* AnnotatedTypeMetadata 注释信息
* */
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 registry = context.getRegistry();

// Condition可以判断的东西有很多:
// 可以通过下面的方法判断容器中是否包含名为person的bean
// 也可以通过一些方法注册bean等等
boolean person = registry.containsBeanDefinition("person");

// 通过environment.getProperty("os.name")方法可以查看当前操作系统信息
String property = environment.getProperty("os.name");
if(property.contains("Linux")){
return true;
}
return false;
}
}

WindowsCondition.class 实现Condition接口

1
2
3
4
5
6
7
8
9
10
public class WindowsCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if(property.contains("Windows")){
return true;
}
return false;
}
}

默认情况下操作系统为Windows10 所以会满足WindowsCondition的条件 即注入bill

进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 @Test
public void testComponent03(){
// 动态获取环境变量的值 操作系统值:windows 10
ConfigurableEnvironment environment = (ConfigurableEnvironment) context.getEnvironment();
String property = environment.getProperty("os.name");
System.out.println(property);

// 1、获取Person类bean对象的名字并遍历输出
String[] beanNamesForType = context.getBeanNamesForType(Person.class);
for (String name : beanNamesForType) {
System.out.println(name);
}
// 2、获取Person类bean对象并输出
Map<String, Person> persons = context.getBeansOfType(Person.class);
System.out.println(persons);
}

输出:

1
2
3
Windows 10
bill
{bill=Person(name=Bill, age=27)}

可以通过editConfigurations

image-20200916233646993

给虚拟机设置参数 达到修改os.name的效果从而使LinuxCondition生效而注入linus bean对象 测试同上

结果:

1
2
linux
{}
  1. 组件注册 @Import 给容器中快速导入一个组件
1
2
3
4
5
6
7
8
9
10
11
12
13
给容器中添加组件的几种方法:
1.针对于自己写的类:包扫描+组件定义(@Component @Repository @Service @Controller)
2.针对于不是自己写的类 如第三方导入时:@Configuration & @Bean
3.针对于不是自己写的类 如第三方导入时 若通过Bean导入 还需要给实体类添加无参构造器 较为复杂
同样可以快速为容器中导入一个组件:@Import({})
1.@Import(要导入的组件) 容器中就会自动注册这个组件 id默认为全类名 见7.3.1
2.采用ImportSelector:返回需要导入的组件的全类名数组 见7.3.2
3.采用ImportBeanDefinitionRegistrar:手动注册bean到ioc容器中
4.使用Spring提供的FactoryBean(工厂bean)注册组件:
1.默认获取到的是工厂bean调用getObject创建的对象
即通过context.getBean("colorFatoryBean") 获取到的是Color类的对象
2.要获取工厂bean本身 需要给id前加一个& 即context.getBean("&colorFatoryBean")

7.3.1 在配置类上方添加@Import

1
2
3
4
5
@Configuration
@Import({Color.class})
public class MyConfig2 {
....
}

7.3.2 自定义ImportSelector类

  • 编写两个pojo实体类
1
public class Red{}
1
public class Yellow{}
  • 编写MyImportSelector类实现ImportSelector接口
1
2
3
4
5
6
7
8
9
10
11
12
13
//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {

// 返回值 就是导入到容器中的组件全类名 会自动添加到ioc容器中 不可以为null 但可以是空数组
// AnnotationMetadata:当前标注@Import注解的类的全部注解信息
public String[] selectImports(AnnotationMetadata importingClassMetadata) {

// 可以通过importingClassMetadata.getXXX() 可以通过importingClassMetadata获取多个属性

// 将 Red 和 Yellow 添加到ioc容器中(要写全类名)
return new String[]{"indi.zhihuali.pojo.Red","indi.zhihuali.pojo.Yellow"};
}
}
  • 利用@Import添加MyImportSelector类
1
2
3
@Configuration
@Import({Color.class, MyImportSelector.class})
public class MyConfig2 {}

Tips:全类名的快速获取:

可以右键单击要复制全类名的类->copy->copy reference后 输入双引号并在其中ctrl+v 即可快速复制全类名

测试获取ioc容器中的所有bean 结果为:

1
2
3
4
5
6
7
8
9
10
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myConfig2
indi.zhihuali.pojo.Color
indi.zhihuali.pojo.Red
indi.zhihuali.pojo.Yellow
bill

可以看到Red和Yellow都被注册进去了

7.3.3 自定义ImportBeanDefinitionRegistrar类实现ImportBeanDefinitionRegistrar接口

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
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
/*
*
* AnnotationMetadata:当前类的注解信息 可以获取类的若干信息
* BeanDefinitionRegistry:BeanDefinition注册类 将所有需要添加的bean
* 通过调用
* registry.registerBeanDefinition() 手工注册到ioc中
*
* */
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {

// 判断容器中是否有名为Red和Yellow的bean 若有则将rainbow也注册进ioc中
boolean red = registry.containsBeanDefinition("indi.zhihuali.pojo.Red");
boolean yellow = registry.containsBeanDefinition("indi.zhihuali.pojo.Yellow");
if(red && yellow){
// 指定bean的定义信息 (类型 Scope等)
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Rainbow.class);

// 注册一个bean 指定bean名
// registerBeanDefinitions(importingClassMetadata, registry);
// importingClassMetadata bean名字
// registry 指定bean的定义信息 (类型 Scope等)

registry.registerBeanDefinition("rainbow", rootBeanDefinition);
}
}
}

测试所得

1
2
3
4
5
6
myConfig2
indi.zhihuali.pojo.Color
indi.zhihuali.pojo.Red
indi.zhihuali.pojo.Yellow
bill
rainbow

**7.4 ** 自定义FactoryBean类

  • 自定义ColorFactoryBean实现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
25
26
27
28
29
30
31
package indi.zhihuali.pojo;

import org.springframework.beans.factory.FactoryBean;

/**
* @author zhihua.li
* @date 2020/9/18 - 8:06
**/

public class ColorFactoryBean implements FactoryBean<Color> {
// 返回一个Color对象 这个对象会添加到容器中
public Color getObject() throws Exception {
// 在对象被创建时打印标志语句
System.out.println("get it");
return new Color();
}

// 返回对象的类型
public Class<?> getObjectType() {
return Color.class;
}

// 设置对象是否单例:
// true:单例 在容器中只保存一份
// false:多例 每次获取都会创建一个新的


public boolean isSingleton() {
return true;
}
}
  • 在配置类中注入定义的类
1
2
3
4
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
  • 测试ioc容器中bean情况
1
2
3
4
5
@Test
public void testComponent04() throws Exception {
// 封装了获取bean的方法
getAllBeans(context);
}
  • 测试结果
1
2
3
4
5
6
7
myConfig2
indi.zhihuali.pojo.Color
indi.zhihuali.pojo.Red
indi.zhihuali.pojo.Yellow
bill
colorFactoryBean
rainbow
  • 可以发现ioc容器中存在一个名为colorFactoryBean的bean 可定义FactoryBean就是为了注入Color类的bean

    对此进行测试 获取名为ColorFactoryBean的bean的对象并通过反射获取其运行时类

    1
    2
    Object bean = context.getBean("colorFactoryBean");
    System.out.println("不明类型的bean为:"+bean.getClass());

    输出结果为:

    1
    不明类型的bean为:class indi.zhihuali.pojo.Color

    可见 虽然其名字为colorFactoryBean 但实际上调用getBean方法时获取到的还是Color对象

  • 是否为单例?

1
2
3
Object bean1 = context.getBean("colorFactoryBean");
Object bean2 = context.getBean("colorFactoryBean");
System.out.println(bean1 == bean2);
1
true
  • 如果获取到colorFactoryBean类本身对象呢?

    通过给getBean参数前加一个&即可获得其本身bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public interface BeanFactory {
    /* * Used to dereference a {@link FactoryBean} instance and distinguish it from
    * beans <i>created</i> by the FactoryBean. For example, if the bean named
    * {@code myJndiObject} is a FactoryBean, getting {@code &myJndiObject}
    * will return the factory, not the instance returned by the factory.
    */
    String FACTORY_BEAN_PREFIX = "&";
    ...
    }
1
2
3
4
Object bean = context.getBean("colorFactoryBean");
System.out.println("不明类型的bean为:"+bean.getClass());
Object bean2 = context.getBean("&colorFactoryBean");
System.out.println("添加了&后获取到的bean为:"+bean2.getClass());

​ 输出结果为:

1
2
不明类型的bean为:class indi.zhihuali.pojo.Color
添加了&后获取到的bean为:class indi.zhihuali.pojo.ColorFactoryBean