Spring注解驱动开发之组件注册

自SpringBoot和SpringCloud火起来后, 使用Spring注解驱动开发就必须提上日程了...

首先回顾一下Spring配置文件方式的使用:

① 创建一个maven项目, 导入spring的依赖

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

② 创建bean类(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() {
    }
}

③ 编写配置文件beans.xml, 存放于classpath目录下

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

④ 创建一个主方法进行测试

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

⑤ 结果如下

 注解形式实现上面配置文件形式的功能: 配置类就等同于配置文件(只不过是格式不同)

@Configuration

① 创建配置类MyConfig

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

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

② 主方法测试

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名称(默认是方法名)
    }
}

③ 结果如下

 

④ 在配置类MyConfig中指定id名称

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

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

⑤ 结果如下

@ComponentScan

配置文件方式的包扫描: 可以扫描@Controller @Service @Repository @Component

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

注解方式的包扫描:

① 创建一个测试类IOCTest, 并且新建Controller / Service / Dao类用于测试

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);
        }
    }
}

② 在配置类MyConfig上使用@ComponentScan注解扫描包路径

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

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

③ 结果如下

④ 排除某些组件

@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, "王五");
    }
}

⑤ 只注入某些组件

@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, "王五");
    }
}

⑥ jdk8以下的多组件扫描用法, jdk8可以直接使用多个@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, "王五");
    }
}

 ⑦ FilterType下的常用规则, 上面是根据注解类型来做排除或注入的(FilterType.ANNOTATION)

FilterType.ASSIGNABLE_TYPE: 根据指定组件的类型过滤

@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)
        }
)

结果如下

FilterType.CUSTOM: 根据自定义规则来过滤

@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, "王五");
    }
}

根据源码来看, 需要先创建一个TypeFilter的实现类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;
    }
}

结果如下

 @Scope

可以调整作用域, 默认的作用域是单实例的.

@Configuration
public class MyConfig2 {

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

默认情况下是单实例的, 测试类输出

@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);
}

结果如下

 改为多实例并进行测试

@Configuration
public class MyConfig2 {

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

 结果如下

@Lazy(懒加载)

单实例: 在容器启动时创建对象, 创建完成后就存放于容器中, 随时取都是同一个实例

多实例: 在容器启动时不创建对象, 直到第一次获取bean的时候才创建对象(此时的情况就是懒加载)

也可以结合单实例进行懒加载

@Configuration
public class MyConfig2 {

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

@Conditional 

作用: 按照一定的条件进行判断, 满足条件则给容器中注册bean, 此时当不满足条件时, 即使加上了@Bean注解也不会向容器中注入bean

创建两个实现org.springframework.context.annotation包下的Condition接口的判断类

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;
    }
}

 测试@Conditional注解

@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, "周八");
}

结果如下

 @Import 

作用: 快速给容器中导入一个组件, 容器中就会自动注册这个组件, id默认是全类名

用法①(直接就是@Import)

新建一个bean, Color类

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

测试

@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);
    }
}

结果如下 注入的bean的id默认是全类名

 

 用法②(最多使用, 特别是源码里面)

创建一个实现ImportSelector接口的实现类MyImportSelector, 返回的是需要导入组件的全类名数组

//自定义逻辑返回需要导入的组件
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"};
    }
}

@Import注解中引入MyImportSelector的类对象

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

 测试结果如下

用法③ 

创建一个实现ImportBeanDefinitionRegistrar接口的实现类MyImportBeanDefinitionRegistrar, 用于手动注册bean到容器中

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);
        }
    }
}

@Import注解中引入MyImportBeanDefinitionRegistrar 的类对象

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

 测试结果如下

工厂bean(FactoryBean) 

创建一个实现FactoryBean<Color>的实现类ColorFactoryBean

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;
    }
}

把ColorFactoryBean在配置类中进行注册

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

 测试

@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
}

结果如下

如果将返回值改为false

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

 测试结果

以上默认是获取工厂bean调用getObject()方法创建的对象, 如果要获取工厂bean本身的对象, 则需要容器根据id获取bean时加上"&"

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

结果如下

 原因在源码中已有说明

整理了一晚上, 组件注册部分暂告一段落, 接下来会写生命周期部分...

猜你喜欢

转载自blog.csdn.net/ip_JL/article/details/85399537