目录

  • 前言
  • 1. @Configuration和@Bean
  • 1.1 大概介绍
  • 1.2 测试
  • 2. @ComponentScan+@Component
  • 2.1 一般用法
  • 2.2 排除扫描一些类
  • 2.3 只扫描某些类
  • 2.4 @ComponentScans
  • 2.5 自定义扫描规则
  • 2.5.1 ASSIGNABLE_TYPE
  • 2.5.2 CUSTOM,自定义类型扫描
  • 3. Scope注解
  • 3.1 测试单实例bean
  • 3.2 测试多实例bean(懒加载)
  • 4. @Conditional
  • 4.1 放在方法上
  • 4.2 放在类上
  • 5. Import相关
  • 5.1 import直接导入
  • 5.2 importSelector
  • 5.3 ImportBeanDefinitionRegistrar
  • 6. FactoryBean

前言

最近学了一些spring注解的用法,记录一下

1. @Configuration和@Bean

1.1 大概介绍

@Configuration: 告诉spring当前类是一个配置类
@Bean:在配置类中使用添加组件
使用@Bean添加组件的时候,id默认是方法名。我们也可以自己在注解中设置id,比如@Bean(“person01”)

1.2 测试

配置类:

@Configuration
public class AddBeanConfig {

    @Bean
    public People people01(){
        People people = new People();
        people.setAge("18");
        people.setName("aaa");
        return people;
    }

    @Bean("people")
    public People people02(){
        People people = new People();
        people.setAge("19");
        people.setName("bbb");
        return people;
    }
}

测试类:

@Test
    public void test(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AddBeanConfig.class);
        People people01 = (People)applicationContext.getBean("people01");
        People people = (People)applicationContext.getBean("people");
        System.out.println(people01);
        System.out.println(people);
        //Person{name='aaa', age='18'}
        //Person{name='bbb', age='19'}
    }


2. @ComponentScan+@Component

2.1 一般用法

@ComponentScan(“要扫描的包名”),比如@ComponentScan(“com.test.bean”)就是扫面com.test.bean包,被扫描的类要加上@Component

@Component
public class Cat {
}

@Component
public class Dog {
}
@ComponentScan("com.test.bean")
@Configuration
public class AddBeanConfig {

    @Bean
    public People people01(){
        People people = new People();
        people.setAge("18");
        people.setName("aaa");
        return people;
    }

    @Bean("people")
    public People people02(){
        People people = new People();
        people.setAge("19");
        people.setName("bbb");
        return people;
    }
}
@Test
    public void test02(){
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
            //包含了下面两个
            //cat
            //dog
        }
    }


2.2 排除扫描一些类

在@ComponentScan注解内部有下面一段源码,提供了一个FIlter可以给我们用来包含或者排除某些组件,我们可以在Filter中写上自己要排除扫描的注解的类型,比如Controller,Service等等

/**
	 * Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters
	 * include filter} or {@linkplain ComponentScan#excludeFilters exclude filter}.
	 */
	@Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {

		/**
		 * The type of filter to use.
		 * <p>Default is {@link FilterType#ANNOTATION}.
		 * @see #classes
		 * @see #pattern
		 */
		FilterType type() default FilterType.ANNOTATION;
		
		@AliasFor("classes")
		Class<?>[] value() default {};

		@AliasFor("value")
		Class<?>[] classes() default {};

	
		String[] pattern() default {};

	}

//测试:排除扫描Controller和Service
//配置类
@ComponentScan(value = "com.test",excludeFilters =
        {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})}

)
@Configuration
public class AddBeanConfig {

}

//其他类
@Controller
public class UserController {
}

@Service
public class UserService {
}

@Repository
public class UserDao {
}

测试结果:

@Test
    public void test02(){
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
        //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
        //addBeanConfig
        //userDao
    }


2.3 只扫描某些类

includeFilters = Filter[],指定按照某些规则只包含哪些包,注意一定要加上useDefaultFilters = false,不使用默认的过滤规则

//测试只扫描Controller注解
//配置类
@ComponentScan(value = "com.test",includeFilters =
        {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}
        ,useDefaultFilters = false
)
@Configuration
public class AddBeanConfig {

}

//其他类
@Controller
public class UserController {
}

@Service
public class UserService {
}

@Repository
public class UserDao {
}

测试结果:

@Test
    public void test02(){
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
        //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
        //addBeanConfig
        //userController
    }


2.4 @ComponentScans

ComponentScans:可以在里面指定多个ComponentScan的规则
在ComponentScan的源码中,可以看到有一个@Repeatable(ComponentScans.class),也就是说,我们可以使用@ComponentScans注解重复添加ComponentScan

@Repeatable(ComponentScans.class)
public @interface ComponentScan {}

示例:

@ComponentScans(value = {
        @ComponentScan(value = "com.test", includeFilters = {
                @ComponentScan.Filter(type= FilterType.ANNOTATION, classes={Controller.class})
        }, useDefaultFilters = false)
})

2.5 自定义扫描规则

type= FilterType.ANNOTATION只是其中的一个,意思就是根据注解类型进行过滤。同时,spring还为我们提供了其他的一些规则。在这里我们测试ASSIGNABLE_TYPE和CUSTOM。

public enum FilterType {

    //注解类型
	ANNOTATION,

	//按照给定的类型
	ASSIGNABLE_TYPE,

	//使用ASPECTJ表达式
	ASPECTJ,

	//使用正则表达式
	REGEX,

	//自定义规则
	CUSTOM

}

2.5.1 ASSIGNABLE_TYPE

按照给定的类的类型进行过滤,比如我要单独扫描某一个类或者单独排除某一个类,就可以用这种方法

//这次把注解换成type = FilterType.ASSIGNABLE_TYPE,class就填我们要包含的类的类型,比如我们要只想扫描UserDao.class
@ComponentScan(value = "com.test",includeFilters =
        {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {UserDao.class})}
        ,useDefaultFilters = false
)

测试结果:

@Test
    public void test02(){
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
        //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
        //addBeanConfig
        //userDao
    }

2.5.2 CUSTOM,自定义类型扫描

使用@ComponentScan.Filter(type = FilterType.CUSTOM),我们可以写一个类实现TypeFilter 接口,在里面编写我们的自定义扫描代码逻辑。

//这里写逻辑,默认过滤dao的组件
public class MyFilter implements TypeFilter {

    /**
     * @param metadataReader 读取当前正在扫描的类的信息
     * @param metadataReaderFactory 可以获取到其他任何类的信息
     * @return
     * @throws IOException
     */
    @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();
        //如果包含dao,就返回true
        if(className.contains("Dao")){
            return true;
        }
        return false;
    }
}
//这里根据我们的需求,只扫描MyFilter中的逻辑指定的组件类型,就是只扫描类名含有Dao的
@ComponentScan(value = "com.test",includeFilters =
        {@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyFilter.class})}
        ,useDefaultFilters = false
)
@Configuration
public class AddBeanConfig {

}
@Test
    public void test02(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AddBeanConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
        //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
        //addBeanConfig
        //userDao
    }


3. Scope注解

这其实不算是一种添加bean的方法,而是指定bean的类型的方法,比如指定bean是单实例还是多实例…
Scope的取值默认有四种,默认是单实例

/**
		 --- prototype(多实例,IOC容器并不会在启动就创建,而是等getBean后才创建)
         --- singleton(单实例,默认这个,在ioc容器启动的时候会调用方法创建对象在IOC容器中,以后每次获取就回到IOC容器中拿)
         --- request(同一个请求创建一个实例,同一次请求只有一个实例bean)
         --- session(同一个session创建一个实例,同一个session只有一个实例bean)
	*/
	@AliasFor("value")
	String scopeName() default "";

我们当前默认是单实例bean的,在IOC容器启动的时候就会调用方法创建bean,以后每次获取都是直接从IOC容器中拿。

3.1 测试单实例bean

@ComponentScan
public class Tree {

    public Tree() {
        System.out.println("Tree 被创建了");
    }
}
@Configuration
public class AddBeanConfig {
	//此时没有加Scope,默认是单实例的
    @Bean
    public Tree tree(){
        return new Tree();
    }
}

测试类:(测试bean的创建和销毁)

@Test
    public void test03(){
        //创建启动IOC容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AddBeanConfig.class);
        System.out.println("容器启动完成");
        Tree tree1 = (Tree)applicationContext.getBean("tree");
        Tree tree2 = (Tree)applicationContext.getBean("tree");
        System.out.println(tree1 == tree2);
        //true
        applicationContext.close();
    }

结果:

springboot服务注册skywalking spring 服务注册_java

结论: 可以看到,我们在容器创建完成之前,就已经完成bean的创建了,在多次getBean的时候,获取到的是同一个bean。

3.2 测试多实例bean(懒加载)

@ComponentScan
public class Tree {

    public Tree() {
        System.out.println("Tree 被创建了");
    }
}
@Configuration
public class AddBeanConfig {
	//加了prototype,表示多实例
    @Bean
    @Scope("prototype")
    public Tree tree(){
        return new Tree();
    }
}

测试类:(测试bean的创建和销毁)

@Test
    public void test03(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AddBeanConfig.class);
        System.out.println("容器创建完成");
        Tree tree1 = (Tree)applicationContext.getBean("tree");
        Tree tree2 = (Tree)applicationContext.getBean("tree");
        System.out.println(tree1 == tree2);
        applicationContext.close();
   
    }

springboot服务注册skywalking spring 服务注册_spring_02

结论:Tree被创建了2次,而且每次都是在容器创建完成后才创建tree的,最终也能看出来这两个tree不是同一个tree。

4. @Conditional

按照条件给容器添加bean,按照一定条件给容器中添加条件,满足需求才添加。

  1. 放在类上表示满足当前条件类中的bean才会加载
  2. 放在方法上表示满足当前条件方法才会生效
  3. 用法:实现Condition接口,重写matches方法

4.1 放在方法上

测试:根据不同的系统注册不同的bean

Condition 接口:

public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //获取到当前的环境
        Environment environment = context.getEnvironment();
        //获取操作系统的名字
        String property = environment.getProperty("os.name");
        if(property.contains("linux")){
            return true;
        }
        return false;
    }
}
public class WindowsCondition implements Condition {
    @Override
    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;
    }
}

Config:

@Configuration
public class AddBeanConfig {

    @Conditional({LinuxCondition.class})
    @Bean
    public People linux(){
        return new People("linux", "18");
    }

    @Conditional({WindowsCondition.class})
    @Bean
    public People window(){
        return new People("window", "18");
    }
}

test
```java
@Test
    public void test05(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AddBeanConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
        //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
        //addBeanConfig
        //window
    }

当前系统是window,所以注册了window组件,没有注册linux组件

4.2 放在类上

@Configuration
@ComponentScan("com.test")
public class AddBeanConfig {

    @Conditional({LinuxCondition.class})
    @Bean
    public People linux(){
        return new People("linux", "18");
    }

    @Conditional({WindowsCondition.class})
    @Bean
    public People window(){
        return new People("window", "18");
    }
}
//选择放在UserService 类上
@Conditional({LinuxCondition.class})
@Service
public class UserService {
}

测试结果:可以看到,由于我们不是linux系统,所以不会出现UserService的组件

@Test
    public void test05(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AddBeanConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
        //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
        //addBeanConfig
        //userController
        //userDao
        //window
    }


5. Import相关

源码:其实就是要导入哪一个类就写哪一个

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	Class<?>[] value();

}

5.1 import直接导入

直接在config类上面加上Import哪一个类就行,注意一点,import导入的bean的id是全类名

@Configuration
@Import({People.class})
public class AddBeanConfig {

}

测试:

//People被成功导入
@Test
    public void test06(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AddBeanConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
        //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
        //addBeanConfig
        //com.test.bean.People
    }

5.2 importSelector

除了第一种直接导入,import还为我们提供了其他的导入方法

springboot服务注册skywalking spring 服务注册_单实例_03

用法就是:继承ImportSelector 接口,在selectImports里面返回全类名数组,注意要返回全类名

public class MyImportSeletor implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.test.bean.Cat", "com.test.bean.Dog"};
    }
}

接着在config类上面加上自己的importSelector类就可以了

@Configuration
@Import({People.class, MyImportSeletor.class})
public class AddBeanConfig {

}

测试结果:

@Test
    public void test06(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AddBeanConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
        //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
        //addBeanConfig
        //com.test.bean.People
        //com.test.bean.Cat
        //com.test.bean.Dog
    }

5.3 ImportBeanDefinitionRegistrar

在这个方法中,我们可以直接注册bean,比如满足一定条件就注册哪个bean等等用途。使用的方法是继承ImportBeanDefinitionRegistrar 接口,在registerBeanDefinitions里面注册bean

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * @param importingClassMetadata 当前类的信息
     * @param registry 注册类
     *  把所有需要添加到容器中的bean,调用registry.registerBeanDefinition来手动注册
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //指定bean信息
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Tree.class);
        //注册bean
        //参数1:bean名字    参数2:bean信息
        registry.registerBeanDefinition("myTree", rootBeanDefinition);
    }
}

加入到import中

@Configuration
@Import({People.class, MyImportSeletor.class, MyImportBeanDefinitionRegistrar.class})
public class AddBeanConfig {

}

测试结果:

springboot服务注册skywalking spring 服务注册_System_04


6. FactoryBean

  • 使用spring提供的FactoryBean(工厂Bean)

1、继承FactoryBean,在getObject添加组件,getObjectType指定组件类型

public class MyFactory {

    private String call;

    public MyFactory() {
    }

    public MyFactory(String call) {
        this.call = call;
    }
}

//创建一个spring定义的工厂bean
public class MyFactoryBean implements FactoryBean {

    //返回一个对象,该对象会添加到容器中
    @Override
    public Object getObject() throws Exception {
        return new MyFactory("MyFactory被创建了");
    }

    //根据类型去容器中找bean
    @Override
    public Class<?> getObjectType() {
        return MyFactory.class;
    }

    //是不是单例
    //true:单例,在容器中保存一份
    // false:不是,每次调用都获取
    @Override
    public boolean isSingleton() {
        return true;
    }
}

2、配置类注册FactoryBean

@Configuration
@Import({People.class, MyImportSeletor.class, MyImportBeanDefinitionRegistrar.class})
public class AddBeanConfig {
    
    @Bean
    public MyFactoryBean myFactoryBean(){
        return new MyFactoryBean();
    }
}

3、测试,可以看到虽然注册的是MyFactoryBean,但是得到的是MyFactory

@Test
    public void test07(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AddBeanConfig.class);
        //提供FactoryBean获取组件
        MyFactory myFactory = (MyFactory)applicationContext.getBean("myFactoryBean");
        System.out.println(myFactory);
        //Tree 被创建了
        //com.jianglianghao.bean.MyFactory@1817d444
    }

4、获取MyFactoryBean本身,在BeanName前面加一个&就可以了

MyFactoryBean myFactoryBean = (MyFactoryBean)applicationContext.getBean("&myFactoryBean");
        //com.jianglianghao.config.MyFactoryBean@1817d444