spring注解开发1——组件注册

本文最后更新于:2022年5月10日 晚上

摘要:使用注解创建bean的四种方式、具体细节及拓展,涉及到的注解主要有:@Configuration、@Bean、@ComponentScan、@Scope、@Lazy、@Conditional、@Import,涉及到的借口主要有:Condition接口、TypeFilter接口、ImportSelector接口、ImportBeanDefinitionRegistrar接口、FactoryBean接口。

前言

Spring注解开发学习笔记。

Spring通过xml配置文件注注册组件

  1. 创建项目,导入spring-context依赖,使用lombok减少代码量,因此lombok依赖也导入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.19</version>
</dependency>

<!-- lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
</dependencies>
  1. 定义一个bean:Person
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.shg.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @author: shg
* @create: 2022-05-10 10:53 上午
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private int age;
private String name;
}

  1. 创建xml配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="person" class="com.shg.bean.Person">
<property name="age" value="18"/>
<property name="name" value="spring"/>
</bean>

</beans>

  1. 从容器中获取bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.shg.test;

import com.shg.bean.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
* @author: shg
* @create: 2022-05-10 11:01 上午
*/
public class XMLTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Person person = (Person) context.getBean("person");
System.out.println("person = " + person); // person = Person(age=18, name=spring)
}
}

@Configuration和@Bean给容器中注册组件

  1. 创建配置类
  • @Configuration 声明本类为配置类
  • @Bean 给容器注册一个Bean;类型为返回值类型,id默认是用方法名作为id
    • Bean注解的value值,指定bean的id
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
package com.shg.config;

import com.shg.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author: shg
* @create: 2022-05-10 11:04 上午
*/
// 配置类 == 配置文件
@Configuration // 声明本类为配置类
public class MyConfig {

// 给容器注册一个Bean;类型为返回值类型,id默认是用方法名作为id
@Bean
public Person person() {
return new Person(20, "annotation");
}

// 通过value指定id
@Bean(value = "p1")
public Person person01() {
return new Person(20, "annotation");
}
}

  1. 从容器中获取bean
  • getBeanNamesForType()方法可以根据类名获取所有bean的id
  • 使用getBean方法获取bean实例
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
package com.shg.test;

import com.shg.bean.Person;
import com.shg.config.MyConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Arrays;

/**
* @author: shg
* @create: 2022-05-10 11:07 上午
*/

public class AnnotationTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);

String[] beanNames = context.getBeanNamesForType(Person.class);
Arrays.stream(beanNames)
.map(name -> name + " = " + context.getBean(name))
.forEach(System.out::println);
// person = Person(age=20, name=annotation)
// p1 = Person(age=18, name=annotation1)
}
}

@ComponentScan自动扫描组件

  1. 创建三个bean,分别使用注解@Repository、@Service、@Controller进行声明
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
35
36
37
package com.shg.dao;

import org.springframework.stereotype.Repository;

/**
* @author: shg
* @create: 2022-05-10 1:38 下午
*/
@Repository
public class UserDao {
}

package com.shg.service;

import org.springframework.stereotype.Service;

/**
* @author: shg
* @create: 2022-05-10 1:38 下午
*/
@Service
public class UserService {
}

package com.shg.controller;

import org.springframework.stereotype.Controller;

/**
* @author: shg
* @create: 2022-05-10 1:38 下午
*/
@Controller
public class UserController {
}


  1. 创建配置类
  • @ComponentScan注解的参数:
    • value:指定要扫描的包
    • excludeFilters = Filter[]:指定扫描的时候按照什么规则排除组件
    • includeFilters = Filter[]:指定扫描的时候只需要哪些组件
  • Filter[]数组中使用@Filter来具体指定按照什么规则
    • type = FilterType.ANNOTATION:按照注解
    • type = FilterType.ASSIGNABLE_TYPE:按照类型
    • type = FilterType.ASPECTJ:使用ASPECTJ表达式
    • type = FilterType.REGEX:使用正则指定
    • type = FilterType.CUSTOM:自定义规则
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
package com.shg.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;

/**
* @author: shg
* @create: 2022-05-10 1:44 下午
*/
@Component

//@ComponentScan(value = {"com.shg"}) // value指定要扫描的包
// excludeFilters = Filter[]:指定扫描的时候按照什么规则排除组件
//@ComponentScan(value = {"com.shg"}, excludeFilters = {
// @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
//})
// includeFilters = Filter[]:指定扫描的时候只需要哪些组件
@ComponentScan(value = {"com.shg"}, includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)
public class MyConfig1 {
}

  1. 输出组件id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.shg.test;

import com.shg.config.MyConfig1;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Arrays;

/**
* @author: shg
* @create: 2022-05-10 1:45 下午
*/
public class AnnotationTest1 {
@Test
public void test01() {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig1.class);
Arrays.stream(context.getBeanDefinitionNames())
.forEach(System.out::println);

}
}

自定义Filter指定规则包含或排除组件

  1. 自定义一个Filter,实现TypeFilter接口,指定类名中包含User的类注册为bean
  • metadataReader:读取到当前正在扫描的类的信息
    • metadataReader.getAnnotationMetadata():获取当前类注解的信息
    • metadataReader.getClassMetadata():获取当前正在扫描的类信息
    • metadataReader.getResource():获取当前类的自愿信息(类路径)
  • metadataReaderFactory:可以获取到其他任何类信息的
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
package com.shg.filter;

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: shg
* @create: 2022-05-10 2:52 下午
*/
public class MyFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// metadataReader:读取到当前正在扫描的类的信息
// metadataReaderFactory:可以获取到其他任何类信息的

// 获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 获取当前正在扫描的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 获取当前类的自愿信息(类路径)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
System.out.println("className = " + className);
return className.contains("User");
}
}

  1. 在配置类中使用自定义的Filter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.shg.config;

import com.shg.filter.MyFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Component;

/**
* @author: shg
* @create: 2022-05-10 1:44 下午
*/
@Component
// 自定义规则:指定名中包含 User 的类
@ComponentScan(value = {"com.shg"}, includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyFilter.class})
}, useDefaultFilters = false)
public class MyConfig1 {
}

  1. 输出组件的id(略)

@Scope注解设置组件的作用域、@Lazy懒加载

  1. 创建配置类
  • @Scope通过参数value设置组件的作用域,默认为singleton
  • value的取值有:”singleton”、”prototype”、”request”、”session”
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
package com.shg.config;

import com.shg.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

/**
* @author: shg
* @create: 2022-05-10 3:15 下午
*/
@Configuration
public class MyConfig2 {
// 通过@Scope的value设置bean的作用域,value的取值有:"singleton"、"prototype"、"request"、"session"
// @Lazy设置单实例bean懒加载
@Scope(value = "singleton")
@Lazy
@Bean(value = "p")
public Person person() {
return new Person(18, "scope");
}

@Scope(value = "prototype")
@Bean(value = "p1")
public Person person01() {
return new Person(18, "scope");
}
}

  1. 查看输出结果
  • 根据id为p获取的bean是单实例的,单实例bean在Ioc容器初始化的时候创建
  • 根据id为p1获取的bean是多实例的
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
package com.shg.test;

import com.shg.bean.Person;
import com.shg.config.MyConfig1;
import com.shg.config.MyConfig2;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Arrays;

/**
* @author: shg
* @create: 2022-05-10 1:45 下午
*/
public class AnnotationTest1 {

@Test
public void testScope() {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig2.class);
Person p = (Person) context.getBean("p");
Person p1 = (Person) context.getBean("p");
System.out.println("(p == p1) = " + (p == p1)); // (p == p1) = true
Person p2 = (Person) context.getBean("p1");
Person p3 = (Person) context.getBean("p1");
System.out.println("(p2 == p3) = " + (p2 == p3)); // (p2 == p3) = false
}
}

@Conditional按照条件注册bean

  1. 定义三个实现Condition接口的条件类,表示虚拟机运行的系统条件
  • 判断是否是Windows系统
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.shg.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
* @author: shg
* @create: 2022-05-10 4:02 下午
*/
public class WinCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断是否是Windows系统
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
return osName != null && osName.contains("Windows");
}
}

  • 判断是否是Linux系统
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.shg.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
* @author: shg
* @create: 2022-05-10 3:56 下午
*/
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断是否是Linux系统
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
return osName != null && osName.contains("Linux");
}
}

  • 判断是否是Mac系统
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
package com.shg.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
* @author: shg
* @create: 2022-05-10 3:57 下午
*/
public class MacCondition implements Condition {
/**
* @param context 判断条件能使用的上下文环境
* @param metadata 注释信息
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断是否是Mac系统
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
return osName != null && osName.contains("Mac");
}
}

  1. 创建配置类
  • 使用@Conditional注解指定bean注册条件
  • @Conditional可以放在类上也可以放在方法上
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
35
package com.shg.config;

import com.shg.bean.Person;
import com.shg.condition.LinuxCondition;
import com.shg.condition.MacCondition;
import com.shg.condition.WinCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

/**
* @author: shg
* @create: 2022-05-10 3:51 下午
*/
@Configuration
public class MyConfig3 {
@Bean(value = "bill")
@Conditional(value = WinCondition.class)
public Person person() {
return new Person(60, "bill");
}

@Bean(value = "linus")
@Conditional(value = LinuxCondition.class)
public Person person1() {
return new Person(48, "linus");
}

@Bean(value = "jobs")
@Conditional(value = MacCondition.class)
public Person person2() {
return new Person(48, "Jobs");
}
}

  1. 输出结果
1
2
3
4
5
6
7
8
9
10
11
@Test
public void testConditional() {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig3.class);
Environment environment = context.getEnvironment();
System.out.println("environment.getProperty(\"os.name\") = " + environment.getProperty("os.name"));
// environment.getProperty("os.name") = Mac OS X

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

@Import给容器注册组件

  1. 定义四个类Color、Blue、Yellow、RainBow
1
2
3
4
5
6
7
8
9
package com.shg.bean;

/**
* @author: shg
* @create: 2022-05-10 4:18 下午
*/
public class Color {
}

1
2
3
4
5
6
7
8
9
package com.shg.bean;

/**
* @author: shg
* @create: 2022-05-10 4:31 下午
*/
public class Blue {
}

1
2
3
4
5
6
7
8
9
package com.shg.bean;

/**
* @author: shg
* @create: 2022-05-10 4:32 下午
*/
public class Yellow {
}

  1. 自定义一个ImportSelector接口的实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.shg.condition;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

/**
* @author: shg
* @create: 2022-05-10 4:29 下午
*/
public class MyImportSelector implements ImportSelector {
/**
*
* @param importingClassMetadata 当前标注@Import注解的类的所有信息
* @return 需要导入到容器中到组件的全类名
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.shg.bean.Yellow", "com.shg.bean.Blue"};
}
}

  1. 自定义一个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
29
30
31
package com.shg.condition;

import com.shg.bean.RainBow;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

/**
* @author: shg
* @create: 2022-05-10 4:40 下午
*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
*
* @param importingClassMetadata 当前类的注册信息
* @param registry BeanDefinition注册类,把所有需要添加到容器中的bean:调用registry.registerBeanDefinition()手工注册进来
* @param importBeanNameGenerator
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
boolean hasBlue = registry.containsBeanDefinition("com.shg.bean.Yellow");
boolean hasYellow = registry.containsBeanDefinition("com.shg.bean.Blue");
if (hasBlue && hasYellow) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
registry.registerBeanDefinition("rainBow", beanDefinition);
}
}
}

  1. 创建配置类
  • @Import导入组件,value是一个数组,数组中的元素可以是:
    • 一个普通类,id默认是组件的全类名
    • ImportSelector接口的实现类,id默认是组件的全类名
    • ImportBeanDefinitionRegistrar接口的实现类,id可以在实现类中指定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.shg.config;

import com.shg.bean.Color;
import com.shg.condition.MyImportBeanDefinitionRegistrar;
import com.shg.condition.MyImportSelector;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;

/**
* @author: shg
* @create: 2022-05-10 4:18 下午
*/
@Component
@Import(value = {Color.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class}) // @Import导入组件,id默认是组件的全类名
public class ImportConfig {
}

  1. 输出组件的id
1
2
3
4
5
6
7
8
@Test
public void testImport() {
ApplicationContext context = new AnnotationConfigApplicationContext(ImportConfig.class);
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
1
2
3
4
5
6
7
8
9
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
com.shg.bean.Color
com.shg.bean.Yellow
com.shg.bean.Blue
rainBow

使用FactoryBean注册组件

  1. 创建一个FactoryBean接口的实现类,范型是要创建的bean的类型
  • isSingleton()的返回值:true:这个bean是单实例,在容器中只保存一份; 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
package com.shg.bean;

import org.springframework.beans.factory.FactoryBean;

/**
* @author: shg
* @create: 2022-05-10 4:54 下午
*/
public class ColorFactoryBean implements FactoryBean<Color> {
@Override
public Color getObject() throws Exception {
return new Color();
}

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

/**
*
* @return true:这个bean是单实例,在容器中只保存一份; false:多实例
*/
@Override
public boolean isSingleton() {
return true;
}
}

  1. 创建配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.shg.config;

import com.shg.bean.ColorFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author: shg
* @create: 2022-05-10 6:20 下午
*/
@Configuration
public class MyConfig4 {
@Bean
public ColorFactoryBean colorFactoryBean() {
return new ColorFactoryBean();
}
}

  1. 获取bean
  • 根据id获取bean实例,默认是获取的是调用getObject创建的对象
  • 如果要获取工厂bean实例,只需要在id前加上 & 前缀
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testFactoryBean() {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig4.class);
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
// 工厂Bean获取的是调用getObject创建的对象
Object colorFactoryBean = context.getBean("colorFactoryBean");
System.out.println("colorFactoryBean = " + colorFactoryBean); // colorFactoryBean = com.shg.bean.Color@20b2475a

// 如果要获取ColorFactorBean实例,只需要在id前加上 & 前缀
Object factoryBean = context.getBean("&colorFactoryBean");
System.out.println("factoryBean = " + factoryBean);

}

总结

使用注解注册Bean的方式

  1. @Configuration + @Bean

  2. @ComponentScan + @Component/@Controller/@Service/@Repository

    • 注册bean的排除规则
    • 使用@Scope注解设置bean的作用域
    • 使用@Lazy注解懒加载单例bean
    • 使用@Conditional注解设置bean的注册条件
  3. @Import

    • 导入普通类

    • 导入ImportSelector接口的实现类

    • 导入ImportBeanDefinitionRegistrar接口的实现类

  4. @Configuration + @Bean + 实现FactoryBean接口

    • isSingleton() 方法来设置bean是单例还是多例

spring注解开发1——组件注册
https://shgang97.github.io/posts/spring-annotation-1-c594551a5c15/
作者
shgang
发布于
2022年5月10日
更新于
2022年5月10日
许可协议