目录

spring注解驱动之注册组件

演示工程

20201028194639

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

@Configuration

1
2
3
4
5
6
7
8
9
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 本身也是一个组件
public @interface Configuration {
   @AliasFor(annotation = Component.class)
   String value() default "";
   boolean proxyBeanMethods() default true;
}

@Bean

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
   @AliasFor("name")
   String[] value() default {};
   @AliasFor("value")
   String[] name() default {}; // name就是bean的id
   @Deprecated
   Autowire autowire() default Autowire.NO;
   boolean autowireCandidate() default true;
   String initMethod() default "";
   String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.eh.config;

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

// 配置类=配置文件
@Configuration // 告诉Spring这是一个配置类
public class MainConfig {

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

注册的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
package com.eh;


import com.eh.bean.Person;
import com.eh.config.MainConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

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

        /**
         * 因为一个类型可能对应多个不同id的bean,所以使用数组接受
         */
        String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
        for (String name : namesForType) {
            System.out.println(name);
        }
    }
}

@ComponentScan

 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
38
39
40
41
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class) // 可以在一个类上重复注解多次,和@ComponentScans效果一样
public @interface ComponentScan {
   @AliasFor("basePackages")
   String[] value() default {};
  
   String[] basePackages() default {}; // 扫描哪些包下的类
  
   Class<?>[] basePackageClasses() default {};
  
   Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
  
   Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
  
   ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
  
   String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
  
   boolean useDefaultFilters() default true;
  
   Filter[] includeFilters() default {}; // 包括哪些规则的类
  
   Filter[] excludeFilters() default {}; // 排除哪些规则的类
  
   boolean lazyInit() default false;
  
   @Retention(RetentionPolicy.RUNTIME)
   @Target({})
   @interface Filter {
      FilterType type() default FilterType.ANNOTATION;

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

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

使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.eh.config;

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

// 配置类=配置文件
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(basePackages = "com.eh")
public class MainConfig {

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

打印所有的bean id

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.eh;


import com.eh.bean.Person;
import com.eh.config.MainConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest {
    public static void main(String[] args) {

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);

        // 获取所有的bean id
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }
}

可以看到标注了@Component注解的类的bean都注入进来了,包括@Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Person(name=null, age=null)
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
mainConfig
bookController
bookDao
person

同配置文件中一样,@ComponentScan也可以过滤和包括一些符合规则的类

 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.eh.config;

import com.eh.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;

@Configuration
// 指定要扫描的包
@ComponentScan(
        basePackages = "com.eh",
//excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件
//includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件,同配置文件一样一样要禁掉useDefaultFilters,也就是useDefaultFilters=false,默认是true
        excludeFilters = {
                // 过滤标注了Controller注解的类
//                @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)
//FilterType.ANNOTATION:按照注解
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
//FilterType.ASSIGNABLE_TYPE:按照给定的类型;
//FilterType.ASPECTJ:使用ASPECTJ表达式
//FilterType.REGEX:使用正则指定
//FilterType.CUSTOM:使用自定义规则     
        }
)
public class MainConfig {
}

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
26
27
28
29
30
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;

public class MyTypeFilter implements TypeFilter {

    /**
     * metadataReader:读取到的当前正在扫描的类的信息
     * metadataReaderFactory:可以获取到其他任何类信息的
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) {
        //获取当前类注解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前正在扫描的类的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取当前类资源(类的路径)
        Resource resource = metadataReader.getResource();

        String className = classMetadata.getClassName();
        System.out.println("--->" + className);
        if (className.contains("er")) {
            return true;
        }
        return false;
    }
}

使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Configuration
@ComponentScans(
        value = {
                @ComponentScan(value = "com.eh", includeFilters = {
                        @Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
                }, useDefaultFilters = false)
        }
)
public class MainConfig {
}

@Scope

调整作用域

 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
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
   @AliasFor("scopeName")
   String value() default "";
  
  /**
	 * ConfigurableBeanFactory#SCOPE_PROTOTYPE    
	 * @see ConfigurableBeanFactory#SCOPE_SINGLETON  
	 下面这两个在web项目下菜生效
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST  request
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION	 sesssion
	 * @return\
	 * @Scope:调整作用域
	 * prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。
	 * 					每次获取的时候才会调用方法创建对象;
	 * singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。
	 * 			以后每次获取就是直接从容器(map.get())中拿,
	 * request:同一次请求创建一个实例
	 * session:同一个session创建一个实例
	 */
   @AliasFor("value")
   String scopeName() default "";
  
   ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}

@Lazy

懒加载

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {

   /**
   懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化;
   单实例bean:默认在容器启动的时候创建对象;可以加上这个注解在第一次使用的时候创建对象并初始化
   */
   boolean value() default true;

}

@Conditional

按照一定的条件进行判断,满足条件则在容器中注册Bean,这个注解在Sringboot中大量使用

1
2
3
4
5
6
7
8
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
  
   Class<? extends Condition>[] value();
  
}

ok, 我们写两个Condition的实现类,一个表示Windows环境,一个表示Mac OS X 环境

MacCondition.java

 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
package com.eh.condition;


import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * 判断是否是Mac系统
 */
public class MacCondition implements Condition {

    /**
     * ConditionContext:判断条件能使用的上下文(环境)
     * AnnotatedTypeMetadata:注解信息
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //1、能获取到ioc使用的beanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //2、获取类加载器
        ClassLoader classLoader = context.getClassLoader();
        //3、获取当前环境信息
        Environment environment = context.getEnvironment();
        //4、获取到bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();

        // 我们使用环境信息
        return environment.getProperty("os.name").contains("Mac");
    }
}

WindowsCondition.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package com.eh.condition;

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

public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("os.name").contains("Windows");
    }
}

注册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
package com.eh.config;

import com.eh.bean.Person;
import com.eh.condition.MacCondition;
import com.eh.condition.WindowsCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig {

    @Conditional(WindowsCondition.class)
    @Bean("Windows")
    public Person person1() {
        return new Person("gates", 50);
    }

    @Conditional(MacCondition.class)
    @Bean("Mac")
    public Person person2() {
        return new Person("jobs", 52);
    }

}

通过切换系统环境来测试,可以使用虚拟机变量-Dos.name=Windows切换到windows环境

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.eh;


import com.eh.bean.Person;
import com.eh.config.MainConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Map;

public class MainTest {
    public static void main(String[] args) {

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);

        Map<String, Person> map = applicationContext.getBeansOfType(Person.class);
        System.out.println(map);
    }

    // 输出 {Mac=Person(name=jobs, age=52)}
}

还可以使用registry判断容器中是否已经存在某个Bean

1
2
//可以判断容器中的bean注册情况,也可以给容器中注册bean
boolean exists = registry.containsBeanDefinition("person");

@Conditional还可以放在类上,对类中所有组件统一按照条件判断进行设置。满足当前条件,这个类中配置的所有bean注册才能生效。

@Import

快速给容器中导入组件,容器中会自动注册注解里要求的组件,id默认是全类名

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

   /**
   	以下分别对应
   	1. 使用了Configuration注解的类,将该类下的Bean注册进容器,无需使用容器启动时指定配置类的方式
   	2. 实现了ImportSelector的类,返回需要导入的组件的全类名数组
   	3. 实现了ImportBeanDefinitionRegistrar的类,手动注册bean到容器中
   	4. 普通的java类
    * {@link Configuration @Configuration}, {@link ImportSelector},
    * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
    */
   Class<?>[] value();

}

演示:

ImportSelector

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.eh.condition;

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

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

    //返回值,就是到导入到容器中的组件全类名
    //AnnotationMetadata:当前标注@Import注解的类的所有注解信息
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //方法不要返回null值
        return new String[]{"com.eh.bean.Red", "com.eh.bean.Yellow"};
    }
}

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
32
package com.eh.condition;

import com.eh.bean.RainBow;
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;

// 调用BeanDefinitionRegistry.registerBeanDefinition将所有需要添加到容器中的bean手工注册进来
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * AnnotationMetadata:当前类的注解信息
     * BeanDefinitionRegistry:BeanDefinition注册类;
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        boolean definition = registry.containsBeanDefinition("com.eh.bean.Red");
        boolean definition2 = registry.containsBeanDefinition("com.eh.bean.Yellow");
        boolean definition3 = registry.containsBeanDefinition("com.eh.bean.Green");
        boolean definition4 = registry.containsBeanDefinition("com.eh.bean.Pink");
        // 如果容器中有红色和黄色就注册彩虹
        if (definition && definition2 & definition3 & definition4) {
            //指定Bean定义信息;(Bean的类型)
            RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
            //注册一个Bean,指定bean名
            registry.registerBeanDefinition("rainBow", beanDefinition);
        }
    }

}

使用@Import注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.eh.config;

import com.eh.bean.Green;
import com.eh.bean.Pink;
import com.eh.condition.MyImportBeanDefinitionRegistrar;
import com.eh.condition.MyImportSelector;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({MyImportSelector.class, MyImportBeanDefinitionRegistrar.class, Green.class, Pink.class})
public class MainConfig {
}

注意:MyImportBeanDefinitionRegistrar会在其他类注册后进行注册

测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.eh;


import com.eh.config.MainConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Arrays;

public class MainTest {
    public static void main(String[] args) {

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        printAllBeanNames(applicationContext);
    }

    public static void printAllBeanNames(ApplicationContext applicationContext) {
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        Arrays.asList(beanDefinitionNames).forEach(System.out::println);
    }
}

运行结果:

1
2
3
4
5
6
mainConfig
com.eh.bean.Red
com.eh.bean.Yellow
com.eh.bean.Green
com.eh.bean.Pink
rainBow

使用FactoryBean接口创建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
package com.eh.bean;

import org.springframework.beans.factory.FactoryBean;

// 工厂Bean
public class PersonFactoryBean implements FactoryBean<Person> {
    // 返回的对象
    @Override
    public Person getObject() throws Exception {
        return new Person();
    }

    // 返回的Bean类型
    @Override
    public Class<?> getObjectType() {
        return Person.class;
    }

    // 是否单例
    @Override
    public boolean isSingleton() {
        return true;
    }
}

使用工厂Bean

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package com.eh.config;

import com.eh.bean.PersonFactoryBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(PersonFactoryBean.class)
public class MainConfig {
}

测试,注意注释部分,默认使用getBean获取的是工厂Bean创建的对象,要获取工厂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
30
package com.eh;


import com.eh.config.MainConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Arrays;

public class MainTest {
    public static void main(String[] args) {

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        Object personFactoryBean = applicationContext.getBean("com.eh.bean.PersonFactoryBean");
        // 输出 class com.eh.bean.Person,所以getBean获取的是工厂Bean创建的对象
        System.out.println(personFactoryBean.getClass());

        /**
         * 我们也可以使用 & {@link org.springframework.beans.factory.BeanFactory} 获取工厂Bean本身
         */
        Object personFactoryBean1 = applicationContext.getBean("&com.eh.bean.PersonFactoryBean");
        // 输出 class com.eh.bean.PersonFactoryBean
        System.out.println(personFactoryBean1.getClass());
    }

    public static void printAllBeanNames(ApplicationContext applicationContext) {
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        Arrays.asList(beanDefinitionNames).forEach(System.out::println);
    }
}

总结

四种注册组件的方式

  1. 包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)

    一般自己写的类这样使用

  2. @Bean

    一般导入的第三方包里面的组件这样使用

  3. @Import

    可以快速给容器中导入一个组件,id默认是全类名

    • ImportSelector:返回需要导入的组件的全类名数组;
    • ImportBeanDefinitionRegistrar:手动注册bean到容器中
  4. 使用Spring提供的 FactoryBean(工厂Bean),整合其他框架的时候用的特别多

    • 默认获取到的是工厂bean调用getObject创建的对象
    • 要获取工厂Bean本身,我们需要给id前面加一个&