Component registration for Spring annotation-driven development

Since SpringBoot and SpringCloud became popular, the use of Spring annotation-driven development must be on the agenda...

First review the use of Spring configuration files:

① Create a maven project and import spring dependencies

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.3.12.RELEASE</version>
</dependency>

② Create bean class (Person)

public class Person {

    private Integer age;

    private String name;

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    public Person(Integer age, String name) {
        this.age = age;
        this.name = name;
    }

    public Person() {
    }
}

③ Write the configuration file beans.xml and store it in the classpath directory

<bean class="com.spring.annotation.bean.Person" id="person">
    <property name="age" value="23"></property>
    <property name="name" value="张三"></property>
</bean>

④ Create a main method for testing

public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    Person bean = (Person) applicationContext.getBean("person");
    System.out.println(bean);
}

⑤ The results are as follows

 The annotation form implements the function of the above configuration file form: the configuration class is equivalent to the configuration file (but the format is different)

@Configuration

① Create configuration class MyConfig

@Configuration//告诉Spring这是一个配置类
public class MyConfig {

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

② Main method test

public static void main(String[] args) {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
    Person bean = applicationContext.getBean(Person.class);
    System.out.println(bean);

    String[] names = applicationContext.getBeanNamesForType(Person.class);
    for (String name: names){
        System.out.println(name);//输出id名称(默认是方法名)
    }
}

③ The results are as follows

 

④ Specify the id name in the configuration class MyConfig

@Configuration//告诉Spring这是一个配置类
public class MyConfig {

    @Bean(name = "person")//给容器中注册一个Bean, 类型是返回值的类型, id默认是用方法名作为id
    public Person person01(){
        return new Person(23, "王五");
    }
}

⑤ The results are as follows

@ComponentScan

Package scanning by configuration file: @Controller @Service @Repository @Component can be scanned

<context:component-scan base-package="com.spring.annotation" use-default-filters="false"/>

Annotated package scanning:

① Create a test class IOCTest, and create a new Controller / Service / Dao class for testing

public class IOCTest {

    @Test
    public void test01(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        String[] names = applicationContext.getBeanDefinitionNames();
        for (String name: names){
            System.out.println(name);
        }
    }
}

② Use the @ComponentScan annotation on the configuration class MyConfig to scan the package path

@Configuration//告诉Spring这是一个配置类
@ComponentScan(value = "com.spring.annotation")
public class MyConfig {

    @Bean(name = "person")//给容器中注册一个Bean, 类型是返回值的类型, id默认是用方法名作为id
    public Person person01(){
        return new Person(23, "王五");
    }
}

③ The results are as follows

④ Exclude some components

@Configuration//告诉Spring这是一个配置类
@ComponentScan(value = "com.spring.annotation", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {
                Controller.class
        })
})
public class MyConfig {

    @Bean(name = "person")//给容器中注册一个Bean, 类型是返回值的类型, id默认是用方法名作为id
    public Person person01(){
        return new Person(23, "王五");
    }
}

⑤ Only inject certain components

@Configuration//告诉Spring这是一个配置类
@ComponentScan(value = "com.spring.annotation", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Service.class})
}, useDefaultFilters = false)
public class MyConfig {

    @Bean(name = "person")//给容器中注册一个Bean, 类型是返回值的类型, id默认是用方法名作为id
    public Person person01(){
        return new Person(23, "王五");
    }
}

⑥ For multi-component scanning usage below jdk8, jdk8 can directly use multiple @ComponentScan

@Configuration//告诉Spring这是一个配置类
@ComponentScans(
        value = {
                @ComponentScan(value = "com.spring.annotation", includeFilters = {
                        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Service.class})
                }, useDefaultFilters = false),
                @ComponentScan(value = "com.spring.annotation", excludeFilters = {
                        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
                })
        }
)
public class MyConfig {

    @Bean(name = "person")//给容器中注册一个Bean, 类型是返回值的类型, id默认是用方法名作为id
    public Person person01(){
        return new Person(23, "王五");
    }
}

 ⑦ Common rules under FilterType, the above is to exclude or inject according to the annotation type (FilterType.ANNOTATION)

FilterType.ASSIGNABLE_TYPE: Filter according to the type of the specified component

@Configuration//告诉Spring这是一个配置类
@ComponentScans(
        value = {
                @ComponentScan(value = "com.spring.annotation", includeFilters = {
                        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Service.class}),
                        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookController.class})
                }, useDefaultFilters = false)
        }
)

The result is as follows

FilterType.CUSTOM: Filter according to custom rules

@Configuration//告诉Spring这是一个配置类
@ComponentScans(
        value = {
                @ComponentScan(value = "com.spring.annotation", includeFilters = {
                        @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
                }, useDefaultFilters = false)
        }
)
public class MyConfig {

    @Bean(name = "person")//给容器中注册一个Bean, 类型是返回值的类型, id默认是用方法名作为id
    public Person person01(){
        return new Person(23, "王五");
    }
}

According to the source code, you need to create a TypeFilter implementation class MyTypeFilter

public class MyTypeFilter 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();
        System.out.println("--->"+className);
        if(className.contains("er")){//如果当前类的类名中包含"er", 则注入
            return true;
        }
        return false;
    }
}

The result is as follows

 @Scope

The scope can be adjusted, the default scope is single instance.

@Configuration
public class MyConfig2 {

    @Bean("person")
    public Person person(){
        return new Person(23, "赵六");
    }
}

Single instance by default, test output

@Test
public void test02(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig2.class);
    Person person1 = (Person) applicationContext.getBean("person");
    Person person2 = (Person) applicationContext.getBean("person");
    System.out.println(person1 == person2);
}

The result is as follows

 Change to multi-instance and test

@Configuration
public class MyConfig2 {

    @Scope("prototype")
    @Bean("person")
    public Person person(){
        return new Person(23, "赵六");
    }
}

 The result is as follows

@Lazy (lazy loading)

Single instance: The object is created when the container starts, and it is stored in the container after the creation is completed, and it is the same instance at any time

Multi-instance: The object is not created when the container starts, and the object is not created until the bean is first obtained (the situation at this time is lazy loading )

Can also be combined with single instance for lazy loading

@Configuration
public class MyConfig2 {

    @Lazy
    @Bean("person")
    public Person person(){
        return new Person(23, "赵六");
    }
}

@Conditional 

Function: Judge according to certain conditions, and register the bean in the container if the condition is met. At this time, when the condition is not met, even if the @Bean annotation is added, the bean will not be injected into the container

Create two judgment classes that implement the Condition interface under the org.springframework.context.annotation package

WindowsCondition

//判断是否为windows系统
public class WindowsCondition implements Condition {

    /**
     * @param conditionContext  判断条件能使用的上下文环境
     * @param annotatedTypeMetadata  注释信息
     * @return
     */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {

        /**
         * todo 判断是否Windows系统
         */
        //获取到ioc当前使用的beanFactory(用于创建对象和装配的)
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        //获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        //获取环境信息
        Environment environment = conditionContext.getEnvironment();
        //获取到bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        String osName = environment.getProperty("os.name");
        if(osName.contains("Windows")){
            return true;
        }
        return false;
    }
}

LinuxCondition

//判断是否为linux系统
public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {

        Environment environment = conditionContext.getEnvironment();
        String osName = environment.getProperty("os.name");
        if(osName.contains("Linux")){
            return true;
        }
        return false;
    }
}

 Test @Conditional annotation

@Conditional({WindowsCondition.class})
@Bean("this is a windows system")
public Person person02(){
    return new Person(23, "田七");
}

@Conditional({LinuxCondition.class})
@Bean("this is a linux system")
public Person person03(){
    return new Person(23, "周八");
}

The result is as follows

 @Import 

Role: Quickly import a component into the container, and the container will automatically register the component, and the id is the full class name by default

Usage① (directly @Import)

Create a new bean, Color class

public class Color {
}
@Configuration
@Import({Color.class})
@Conditional({WindowsCondition.class})
public class MyConfig2 {...}

test

@Test
public void testImport(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig2.class);
    printBean(applicationContext);
}

private void printBean(ApplicationContext applicationContext){
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    for(String beanName: beanDefinitionNames){
        System.out.println(beanName);
    }
}

The result is as follows. The id of the injected bean defaults to the full class name

 

 Usage ② (most used, especially in the source code)

Create an implementation class MyImportSelector that implements the ImportSelector interface, and returns an array of full class names that need to be imported

//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {

    /**
     * @param annotationMetadata  当前标注@Import注解的类的其他注解的所有信息
     * @return
     */
    //返回值就是要导入到容器中的组件的全类名
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.spring.annotation.bean.Green", "com.spring.annotation.bean.Yellow"};
    }
}

Introduce the class object of MyImportSelector in the @Import annotation

@Configuration
@Import({Color.class, MyImportSelector.class})
@Conditional({WindowsCondition.class})
public class MyConfig2 {...}

 The test results are as follows

Usage ③ 

Create an implementation class MyImportBeanDefinitionRegistrar that implements the ImportBeanDefinitionRegistrar interface to manually register beans to the container

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * @param importingClassMetadata  当前类的注解信息
     * @param registry  bean定义的注册信息
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean green = registry.containsBeanDefinition("com.spring.annotation.bean.Green");
        if(green){
            //指定bean的定义信息(bean的类型, bean的scope...)
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RainBow.class);
            //注册一个bean, 指定bean名为rainBow
            registry.registerBeanDefinition("rainBow", rootBeanDefinition);
        }
    }
}

Introduce the class object of MyImportBeanDefinitionRegistrar in the @Import annotation

@Configuration
@Import({Color.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
@Conditional({WindowsCondition.class})
public class MyConfig2 {...}

 The test results are as follows

Factory bean (FactoryBean) 

Create an implementation class ColorFactoryBean that implements FactoryBean<Color>

public class ColorFactoryBean implements FactoryBean<Color> {

    /**
     * 工厂bean通过调用该方法而获取Color的bean对象
     * @return
     * @throws Exception
     */
    @Override
    public Color getObject() throws Exception {
        System.out.println("获取对象bean的方法被调用...");
        return new Color();
    }

    /**
     * 获取Color的类对象
     * @return
     */
    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }

    /**
     * 判断是否是单实例
     * true: 单实例
     * false: 多实例(默认)
     * @return
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}

Register ColorFactoryBean in the configuration class

@Bean
public ColorFactoryBean colorFactoryBean(){
    return new ColorFactoryBean();
}

 test

@Test
public void testFactoryBean(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig2.class);
    Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
    Object colorFactoryBean1 = applicationContext.getBean("colorFactoryBean");
    System.out.println(colorFactoryBean.getClass());  //此时的类型是Color
    System.out.println(colorFactoryBean == colorFactoryBean1);  //此时是单例模式, 所以输出true
}

The result is as follows

If you change the return value to false

@Override
public boolean isSingleton() {
    return false;
}

 Test Results

The above default is to obtain the object created by the factory bean by calling the getObject() method. If you want to obtain the object of the factory bean itself, you need to add "&" when the container gets the bean according to the id

@Test
    public void testFactoryBean(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig2.class);
        Object colorFactoryBean = applicationContext.getBean("&colorFactoryBean");
        System.out.println(colorFactoryBean.getClass());  //此时输出是工厂bean本身的对象
    }

The result is as follows

 The reason is explained in the source code

After finishing all night, the component registration part has come to an end, and the life cycle part will be written next...

Guess you like

Origin blog.csdn.net/ip_JL/article/details/85399537