百味皆苦 java后端开发攻城狮

spring注解驱动开发(组件注册)

2019-05-11
百味皆苦

简介

  • 然而在Springboot和SpringCloud兴起之后,学习Spring的注解驱动及其原理那将会是非常有必要的了;
  • 因为在Springboot和SpringCloud里面会使用到大量的注解来进行配置

@Configuration

  • 给容器中注册组件

依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ldc</groupId>
    <artifactId>spring-annotation</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>
    </dependencies>

</project>

注解方式

  • 首先我们先写一个配置类:等同于xml配置文件
/**
 * @Description 配置类就等同以前的配置文件
 */
@Configuration //告诉Spring这是一个配置类
public class MainConfig {

    //相当于xml配置文件中的<bean>标签,告诉容器注册一个bean
    //之前xml文件中<bean>标签有bean的class类型,那么现在注解方式的类型当然也就是返回值的类型
    //之前xml文件中<bean>标签有bean的id,现在注解的方式默认用的是方法名来作为bean的id
    @Bean
    public Person person() {
        return new Person("lisi",20);
    }

}

public class MainTest {
    public static void main(String[]args){
        /**
         * 这里是new了一个AnnotationConfigApplicationContext对象,以前new的ClassPathXmlApplicationContext对象
         * 的构造函数里面传的是配置文件的位置,而现在AnnotationConfigApplicationContext对象的构造函数里面传的是
         * 配置类的类型
         */
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = applicationContext.getBean(Person.class);
        System.out.println(person);
        
         //我们可以来获取bean的定义信息
        String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
        for (String name : namesForType) { System.out.println(name); }

    }
}
//Person{name=‘lisi’, age=20}
//person
  • Spring注解的方式默认是以配置的方法名来作为这个bean的默认id,如果我们不想要方法名来作为bean的id,我们可以在@Bean这个注解的value属性来进行指定
@Configuration //告诉Spring这是一个配置类
public class MainConfig {

    //相当于xml配置文件中的<bean>标签,告诉容器注册一个bean
    //之前xml文件中<bean>标签有bean的class类型,那么现在注解方式的类型当然也就是返回值的类型
    //之前xml文件中<bean>标签有bean的id,现在注解的方式默认用的是方法名来作为bean的id
    @Bean(value = "person")//通过这个value属性可以指定bean在IOC容器的id
    public Person person01() {
        return new Person("lisi",20);
    }

}

@ComponentScan

  • 自动扫描组件&指定扫描规则

  • xml格式

    <!-- 包扫描、只要标注了@Controller、@Service、@Repository,@Component -->
    <context:component-scan base-package="com.ldc"/>

  • 配置类形式
/**
 * @Description 配置类就等同以前的配置文件
 */
@Configuration //告诉Spring这是一个配置类
@ComponentScan(value = "com.ldc")//相当于是xml配置文件里面的<context:component-scan base-package="com.ldc"/>
public class MainConfig {

    //相当于xml配置文件中的<bean>标签,告诉容器注册一个bean
    //之前xml文件中<bean>标签有bean的class类型,那么现在注解方式的类型当然也就是返回值的类型
    //之前xml文件中<bean>标签有bean的id,现在注解的方式默认用的是方法名来作为bean的id
    @Bean(value = "person")//通过这个value属性可以指定bean在IOC容器的id
    public Person person01() {
        return new Person("lisi",20);
    }

}

  • 我们创建BookController、BookService、BookDao这几个类,分别添加了@Controller@Service@Repository注解
@Controller
public class BookController {
}

@Service
public class BookService {
}

@Repository
public class BookDao {
}

  • 测试
    @Test
    public void test01() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }

除开IOC容器自己要装配的一些组件外,还有是我们自己装配的组件

内部注解配置处理器:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
内部自动装配注解处理器:
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
内部必要注解处理器
org.springframework.context.annotation.internalRequiredAnnotationProcessor
内部公共注解处理器
org.springframework.context.annotation.internalCommonAnnotationProcessor
内部事件监听处理器
org.springframework.context.event.internalEventListenerProcessor
内部事件监听工厂
org.springframework.context.event.internalEventListenerFactory

主配置类 MainConfig 也是IOC容器里面的组件,也被纳入了IOC容器的管理
mainConfig
bookController
bookDao
bookService
person

  • 我们从@Configuration 这个注解点进去就可以发现这个注解上也标注了 @Component 的这个注解,也纳入到IOC容器中作为一个组件:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

	/**
	 * Explicitly specify the name of the Spring bean definition associated
	 * with this Configuration class. If left unspecified (the common case),
	 * a bean name will be automatically generated.
	 * <p>The custom name applies only if the Configuration class is picked up via
	 * component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}.
	 * If the Configuration class is registered as a traditional XML bean definition,
	 * the name/id of the bean element will take precedence.
	 * @return the specified bean name, if any
	 * @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
	 */
	String value() default "";

}

  • 我们在 @ComponentScan 这个注解上,也是可以指定要排除哪些包或者是只包含哪些包来进行管理:里面传是一个Filter[]数组
@Configuration
@ComponentScan(value = "com.ldc",excludeFilters = {
        //这里面是一个@Filter注解数组,FilterType.ANNOTATION表示的排除的规则 :按照注解的方式来进行排除
        //classes = {Controller.class,Service.class}表示的是标有这些注解的类给排除掉
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class,Service.class})
})
public class MainConfig {

    @Bean(value = "person")
    public Person person01() {
        return new Person("lisi",20);
    }

}

这个时候的测试结果如下:这个时候,bookService、bookController这两个组件就已经被排除掉了,不再被IOC容器给管理:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookDao
person

  • 我们也可以来配置includeFilters:指定在扫描的时候,只需要包含哪些组件
  • 在用xml文件配置的方式来进行配置的时候,还要禁用掉默认的配置规则,只包含哪些组件的配置才能生效
<context:component-scan base-package=“com.ldc” use-default-filters=“false”/>
@Configuration
//excludeFilters = Filter[];指定在扫描的时候按照什么规则来排除脑哪些组件
//includeFilters = Filter[];指定在扫描的时候,只需要包含哪些组件
@ComponentScan(value = "com.ldc",includeFilters = {
        //这里面是一个@Filter注解数组,FilterType.ANNOTATION表示的排除的规则 :按照注解的方式来进行排除
        //classes = {Controller.class}表示的是标有这些注解的类给纳入到IOC容器中
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
},useDefaultFilters = false)
public class MainConfig {

    @Bean(value = "person")
    public Person person01() {
        return new Person("lisi",20);
    }

}

这个时候,测试结果如下:这个时候是按照标有注解来进行包含的,现在就只有一个bookController被纳入到IOC容器进行管理

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookController
person

  • @ComponentScan这个注解是可以重复定义的:来指定不同的扫描策略
  • 我们还可以用 @ComponentScans来定义多个扫描规则:里面是@ComponentScan规则的数组
@Configuration
//excludeFilters = Filter[];指定在扫描的时候按照什么规则来排除脑哪些组件
//includeFilters = Filter[];指定在扫描的时候,只需要包含哪些组件
@ComponentScans(value = {
    @ComponentScan(value = "com.ldc",includeFilters = {
            //这里面是一个@Filter注解数组,FilterType.ANNOTATION表示的排除的规则 :按照注解的方式来进行排除
            //classes = {Controller.class}表示的是标有这些注解的类给纳入到IOC容器中
            @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
    },useDefaultFilters = false),
    @ComponentScan(value = "com.ldc")
})
public class MainConfig {

    @Bean(value = "person")
    public Person person01() {
        return new Person("lisi",20);
    }

}

  • 也可以直接这样来配置多个@ComponentScan注解:但是这样写的话,就必须要java8及以上的支持
@Configuration
//excludeFilters = Filter[];指定在扫描的时候按照什么规则来排除脑哪些组件
//includeFilters = Filter[];指定在扫描的时候,只需要包含哪些组件
@ComponentScan(value = "com.ldc",includeFilters = {
        //这里面是一个@Filter注解数组,FilterType.ANNOTATION表示的排除的规则 :按照注解的方式来进行匹配
        //classes = {Controller.class}表示的是标有这些注解的类给纳入到IOC容器中
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
},useDefaultFilters = false)

@ComponentScan(value = "com.ldc")
public class MainConfig {

    @Bean(value = "person")
    public Person person01() {
        return new Person("lisi",20);
    }

}

自定义过滤

  • 组件注册-自定义TypeFilter指定过滤规则

  • 所有支持的过滤规则如下

public enum FilterType {

	/**按照注解来进行匹配
	 * Filter candidates marked with a given annotation.
	 * @see org.springframework.core.type.filter.AnnotationTypeFilter
	 */
	ANNOTATION,

	/**按照给定的类型来进行匹配
	 * Filter candidates assignable to a given type.
	 * @see org.springframework.core.type.filter.AssignableTypeFilter
	 */
	ASSIGNABLE_TYPE,

	/**按照表达式进行匹配,不常用
	 * Filter candidates matching a given AspectJ type pattern expression.
	 * @see org.springframework.core.type.filter.AspectJTypeFilter
	 */
	ASPECTJ,

	/**按照正则表达式匹配,不常用
	 * Filter candidates matching a given regex pattern.
	 * @see org.springframework.core.type.filter.RegexPatternTypeFilter
	 */
	REGEX,

	/** Filter candidates using a given custom自定义过滤规则
	 * {@link org.springframework.core.type.filter.TypeFilter} implementation.
	 */
	CUSTOM

}

  • 前两种是最常用的匹配规则
@Configuration
//excludeFilters = Filter[];指定在扫描的时候按照什么规则来排除脑哪些组件
//includeFilters = Filter[];指定在扫描的时候,只需要包含哪些组件
@ComponentScans(value = {
        @ComponentScan(value = "com.ldc",includeFilters = {
                //这里面是一个@Filter注解数组,FilterType.ANNOTATION表示的排除的规则 :按照注解的方式来进行匹配
                //classes = {Controller.class}表示的是标有这些注解的类给纳入到IOC容器中

                // FilterType.ANNOTATION 按照注解来进行匹配
                // FilterType.ASSIGNABLE_TYPE 按照给定的类型来进行匹配
                @Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
                //按照给定的类型来进行匹配
                @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class})
        },useDefaultFilters = false)
})

public class MainConfig {

    @Bean(value = "person")
    public Person person01() {
        return new Person("lisi",20);
    }

}

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookController
bookService
person
  • 自定义匹配规则FilterType.CUSTOM
  • 自己来写一个匹配规则的类:MyTypeFilter,这个类要实现TypeFilter这个接口
public class MyTypeFilter implements TypeFilter {
    /**
     *
     * @param metadataReader  the metadata reader for the target class 读取到当前正在扫描的类的信息
     * @param metadataReaderFactory a factory for obtaining metadata readers 可以获取到其他任何类的信息
     * @return
     * @throws IOException
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //获取到当前类注解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前类的资源的信息(比如类的路径)
        Resource resource = metadataReader.getResource();

        //获取到当前正在扫描的类的信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        String className = classMetadata.getClassName();
        //获取包扫描路径下的所有类名
        System.out.println("通过自定义的匹配规则--->"+className);
		//让类名里包含er字母的类被注入到容器中
        if (className.contains("er")) {
            return true;
        }
        return false;
    }
}

@Configuration
@ComponentScans(value = {
        @ComponentScan(value = "com.ldc",includeFilters = {
                @Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
        },useDefaultFilters = false)
})

public class MainConfig {

    @Bean(value = "person")
    public Person person01() {
        return new Person("lisi",20);
    }

}

通过自定义的匹配规则—>com.ldc.test.IOCTest
通过自定义的匹配规则—>com.ldc.bean.Person
通过自定义的匹配规则—>com.ldc.config.MyTypeFilter
通过自定义的匹配规则—>com.ldc.controller.BookController
通过自定义的匹配规则—>com.ldc.dao.BookDao
通过自定义的匹配规则—>com.ldc.MainTest
通过自定义的匹配规则—>com.ldc.service.BookService
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
person
myTypeFilter
bookController
bookService

这个时候,包含“er”的组件就给添加到了IOC容器中了;只要在包扫描里面的包里面的每一个类都会进入到这个自定义的匹配规则进行匹配;

组件作用域

  • 我们可以使用@Scope注解设置组件作用域
  • 首先创建一个配置类,默认情况下是单实例的
@Configuration
public class MainConfig2 {

    //默认是单实例的
    @Bean("person")
    public Person person() {
        return new Person();
    }

}

    @Test
    public void test02() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
        //默认是单实例的
        Person person1 = (Person) applicationContext.getBean("person");
        Person person2 = (Person) applicationContext.getBean("person");
        System.out.println(person1==person2);
    }

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig2
person
true
  • 我们可以用@Scope这个注解来指定作用域的范围:这个就相当于在xml文件中配置的<bean>标签里面指定scope=“prototype” 属性;
  • 源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {

	/**
	 * Alias for {@link #scopeName}.
	 * @see #scopeName
	 */
	@AliasFor("scopeName")
	String value() default "";

	/**
	 * Specifies the name of the scope to use for the annotated component/bean.
	 * <p>Defaults to an empty string ({@code ""}) which implies
	 * {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
	 * @since 4.2
	 * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE 多实例
	 * @see ConfigurableBeanFactory#SCOPE_SINGLETON 单实例
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
	 * @see #value
	 */
	@AliasFor("value")
	String scopeName() default "";

	/**
	 * Specifies whether a component should be configured as a scoped proxy
	 * and if so, whether the proxy should be interface-based or subclass-based.
	 * <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
	 * that no scoped proxy should be created unless a different default
	 * has been configured at the component-scan instruction level.
	 * <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
	 * @see ScopedProxyMode
	 */
	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

}

多实例

@Configuration
public class MainConfig2 {
    //singleton:单实例的
    //prototype:多实例的
    //request:同一次请求创建一个实例
    //session:同一个session创建的一个实例
    @Scope("prototype")
    //默认是单实例的
    @Bean("person")
    public Person person() {
        return new Person();
    }

}

    @Test
    public void test02() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
        //默认是单实例的
        Person person1 = (Person) applicationContext.getBean("person");
        Person person2 = (Person) applicationContext.getBean("person");
        System.out.println(person1==person2);
    }

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig2
person
false
这个时候,bean的实例就多实例的,每调用一次getBean()方法就会创建一个实例;
  • 我们来看看当bean的作用域为多例的时候,它在IOC容器中是何时创建的:
@Configuration
public class MainConfig2 {

    @Scope("prototype")
    @Bean("person")
    public Person person() {
        System.out.println("给IOC容器中添加Person...");
        return new Person();
    }

}

    @Test
    public void test02() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    }

我们可以发现,控制台没有任何的输出结果;在IOC容器创建的时候,没有去创建这个作用域为多实例的bean;
    @Test
    public void test02() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器创建完成...");
        Person person = (Person) applicationContext.getBean("person");
        Person person1 = (Person) applicationContext.getBean("person");
    }

IOC容器创建完成…
给IOC容器中添加Person…
给IOC容器中添加Person…

我们可以发现,我们用getBean方法获取几次,就创建几次bean的实例;
也就是说当bean是作用域为多例的时候,IOC容器启动的时候,就不会去创建bean的实例的,而是当我们调用getBean()获取的时候去创建bean的实例;而且每次调用的时候,都会创建bean的实例;

单例

  • 我们来看看当bean的作用域为单例的时候,它在IOC容器中是何时创建的:
@Configuration
public class MainConfig2 {
    
    @Scope
    @Bean("person")
    public Person person() {
        System.out.println("给IOC容器中添加Person...");
        return new Person();
    }

}

    @Test
    public void test02() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    }

给IOC容器中添加Person…

这个时候,我们就可以发现,当作用域为单例的时候,IOC容器在启动的时候,就会将容器中所有作用域为单例的bean的实例给创建出来;以后的每次获取,就直接从IOC容器中来获取,相当于是从map.get()的一个过程;

懒加载

  • @Lazy-bean懒加载
  • 懒加载:是专门针对于单实例的bean的
  • 单实例的bean:默认是在容器启动的时候创建对象;
  • 懒加载:容器启动的时候,不创建对象,而是在第一次使用(获取)Bean的时候来创建对象,并进行初始化
@Configuration
public class MainConfig2 {

    /**
     * 懒加载:是专门针对于单实例的bean的
     *       1.单实例的bean:默认是在容器启动的时候创建对象;
     *       2.懒加载:容器启动的时候,不创建对象,而是在第一次使用(获取)Bean的时候来创建对象,并进行初始化
     * @return
     */
    @Lazy
    @Bean("person")
    public Person person() {
        System.out.println("给IOC容器中添加Person...");
        return new Person();
    }

}

    @Test
    public void test02() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器创建完成...");
    }

IOC容器创建完成…

我们可以看到,这个时候,只有IOC容器启动了,而作用域为单例的bean并没有被创建;
  • 而当我们第一次获取的时候,我们再来看看运行结果:
    @Test
    public void test02() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器创建完成...");
        Person person = (Person) applicationContext.getBean("person");
    }

IOC容器创建完成…
给IOC容器中添加Person…

从运行结果,我们可以看到,当我们调用了getBean()方法获取的时候,bean的实例对象才会被创建;而且只会被创建一次;作用域还是单实例的!

条件注册

  • @Conditional:按照条件注册bean
  • 现在有两个bean: bill 和 linus ,现在我们想按照操作系统的不同来选择是否在IOC容器里面注册bean:
现在下面的两个bean注册到IOC容器是要条件的:
1.如果系统是windows,给容器注册(“bill”)
1.如果系统是linux,给容器注册(“linus”)
@Configuration
public class MainConfig2 {

    /**
     * @Conditional:是SpringBoot底层大量使用的注解,按照一定的条件来进行判断,满足条件 给容器注册bean
     */

    /**
     *  现在下面的两个bean注册到IOC容器是要条件的:
     *  1.如果系统是windows,给容器注册("bill")
     *  1.如果系统是linux,给容器注册("linus")
     * @return
     */
    @Bean("bill")
    public Person person01() {
        return new Person("Bill Gates",62);
    }

    @Bean("linus")
    public Person person02() {
        return new Person("linus",48);
    }

}

  • 我们可以看到这个注解:value值传的是实现了Condition这个接口的类的数组
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition}s that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();

}

条件

/**
 * 判断操作系统是否为Linux系统
 */
public class LinuxCondition implements Condition {
    /**
     *  ConditionContext : 判断条件能使用的上下文(环境)
     *  AnnotatedTypeMetadata : 注释信息
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //判断是否为Linux系统
        //1.能获取到IOC容器里面的BeanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

        //2.获取类加载器
        ClassLoader classLoader = context.getClassLoader();
        //3.能获取当前环境的信息
        Environment environment = context.getEnvironment();
        //4.获取到bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();
		 //可以判断容器中bean的注册情况,也可以给容器中注册bean
        boolean definition = registry.containsBeanDefinition("person");
        //获取操作系统
        String property = environment.getProperty("os.name");
        if (property.contains("linux")) {
            return true;
        }
        return false;
    }
}

/**
 * 判断操作系统是否为Windows系统
 */
public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //3.能获取当前环境的信息
        Environment environment = context.getEnvironment();
        //获取操作系统
        String property = environment.getProperty("os.name");
        if (property.contains("Windows")) {
            return true;
        }
        return false;
    }
}

  • 当两个条件写好了之后,我们给容器注册bean就可以写了:
@Configuration
public class MainConfig2 {

    /**
     * @Conditional:是SpringBoot底层大量使用的注解,按照一定的条件来进行判断,满足条件 给容器注册bean
     */

    /**
     *  现在下面的两个bean注册到IOC容器是要条件的:
     *  1.如果系统是windows,给容器注册("bill")
     *  1.如果系统是linux,给容器注册("linus")
     * @return
     */
    @Conditional({WindowsCondition.class})
    @Bean("bill")
    public Person person01() {
        return new Person("Bill Gates",62);
    }

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

}

@Test
    public void test03() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);

        //我们可以获取当前的操作系统是什么:
        Environment environment = applicationContext.getEnvironment();
        //动态的获取环境变量的值:Windows 7
        String property = environment.getProperty("os.name");
        System.out.println(property);

        //获取IOC容器类型为Person类型的bean的名字一共有哪些
        String[] definitionNames = applicationContext.getBeanNamesForType(Person.class);
        for (String name : definitionNames) {
            System.out.println(name);
        }
        //我们也可以来获取类型为Person类型的对象
        Map<String, Person> persons = applicationContext.getBeansOfType(Person.class);
        persons.values().forEach(System.out::println);
    }

Windows 7
bill
Person{name=‘Bill Gates’, age=62}

标注类上

  • @Conditional 的这个注解还可以标注在类上,对容器中的组件进行统一设置:满足当前条件,这个类中配置的所有bean注册才能生效
@Configuration
//满足当前条件,这个类中配置的所有bean注册才能生效
@Conditional({WindowsCondition.class})
public class MainConfig2 {

    /**
     * @Conditional:是SpringBoot底层大量使用的注解,按照一定的条件来进行判断,满足条件 给容器注册bean
     */

    /**
     *  现在下面的两个bean注册到IOC容器是要条件的:
     *  1.如果系统是windows,给容器注册("bill")
     *  1.如果系统是linux,给容器注册("linus")
     * @return
     */

    @Bean("bill")
    public Person person01() {
        return new Person("Bill Gates",62);
    }

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

}

@import

  • 可以使用@import注解给容器中快速导入一个组件

方法1

  • 使用@Import(Color.class)注解
@Configuration
//满足当前条件,这个类中配置的所有bean注册才能生效
@Conditional({WindowsCondition.class})
//快速导入组件,id默认是组件的全类名
@Import(Color.class)
public class MainConfig2 {

    /**
     * @Conditional:是SpringBoot底层大量使用的注解,按照一定的条件来进行判断,满足条件 给容器注册bean
     */

    /**
     *  现在下面的两个bean注册到IOC容器是要条件的:
     *  1.如果系统是windows,给容器注册("bill")
     *  1.如果系统是linux,给容器注册("linus")
     * @return
     */

    @Bean("bill")
    public Person person01() {
        return new Person("Bill Gates",62);
    }

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

    /**
     * 给容器中注册组件:
     * 1)、扫描+组件标注注解(@Controller/@Service/@Repository/@Component)
     * 【局限于要求是自己写的类,如果导入的第三方没有添加这些注解,那么就注册不上了】
     *
     * 2)、@Bean[导入的第三方包里面的组件]
     * 3)、@Import[快速的给容器中导入一个组件](1)、 @Import(要导入容器中的组件);容器中就会自动的注			册这个组件,id默认是全类名
     */
}

public class Color {
}

  • @Import这个注解也是可以导入多个组件的:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
	 * or regular component classes to import.
	 */
	Class<?>[] value();

}

@Configuration
//满足当前条件,这个类中配置的所有bean注册才能生效
@Conditional({WindowsCondition.class})
//快速导入组件,id默认是组件的全类名
@Import({Color.class,Red.class})
public class MainConfig2 {

    /**
     * @Conditional:是SpringBoot底层大量使用的注解,按照一定的条件来进行判断,满足条件 给容器注册bean
     */

    /**
     *  现在下面的两个bean注册到IOC容器是要条件的:
     *  1.如果系统是windows,给容器注册("bill")
     *  1.如果系统是linux,给容器注册("linus")
     * @return
     */


    @Bean("bill")
    public Person person01() {
        return new Person("Bill Gates",62);
    }

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

    /**
     * 给容器中注册组件:
     * 1)、扫描+组件标注注解(@Controller/@Service/@Repository/@Component)
     * 【局限于要求是自己写的类,如果导入的第三方没有添加这些注解,那么就注册不上了】
     *
     * 2)、@Bean[导入的第三方包里面的组件]
     * 3)、@Import[快速的给容器中导入一个组件] (1)、 @Import(要导入容器中的组件);容器中就会自动的			注册这个组件,id默认是全类名
     */
}

方法2

  • 使用importSelector接口
  • ImportSelector是一个接口:返回需要的组件的全类名的数组;
public interface ImportSelector {

	/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);

}

  • 自定义逻辑返回需要导入的组件,需要实现 ImportSelector 这个接口:
//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
    //返回值就是要导入到容器中的组件的全类名
    //AnnotationMetadata :当前标注@Import注解的类的所有注解信息
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //方法不要返回null值
        return new String[]{"com.ldc.bean.Blue","com.ldc.bean.Yellow"};
    }
}

@Configuration
//满足当前条件,这个类中配置的所有bean注册才能生效
@Conditional({WindowsCondition.class})
//快速导入组件,id默认是组件的全类名
@Import({Color.class,Red.class,MyImportSelector.class})
public class MainConfig2 {

    /**
     * @Conditional:是SpringBoot底层大量使用的注解,按照一定的条件来进行判断,满足条件 给容器注册bean
     */

    /**
     *  现在下面的两个bean注册到IOC容器是要条件的:
     *  1.如果系统是windows,给容器注册("bill")
     *  1.如果系统是linux,给容器注册("linus")
     * @return
     */


    @Bean("bill")
    public Person person01() {
        return new Person("Bill Gates",62);
    }

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

    /**
     * 给容器中注册组件:
     * 1)、扫描+组件标注注解(@Controller/@Service/@Repository/@Component)
     * 【局限于要求是自己写的类,如果导入的第三方没有添加这些注解,那么就注册不上了】
     *
     * 2)、@Bean[导入的第三方包里面的组件]
     * 3)、@Import[快速的给容器中导入一个组件]
     *      (1)、 @Import(要导入容器中的组件);容器中就会自动的注册这个组件,id默认是全类名
     *      (2)、 ImportSelector :返回需要的组件的全类名的数组;
     */
}

方法3

  • 使用ImportBeanDefinitionRegistrar接口手动添加bean
public interface ImportBeanDefinitionRegistrar {

	/**
	 * Register bean definitions as necessary based on the given annotation metadata of
	 * the importing {@code @Configuration} class.
	 * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
	 * registered here, due to lifecycle constraints related to {@code @Configuration}
	 * class processing.
	 * @param importingClassMetadata annotation metadata of the importing class
	 * @param registry current bean definition registry
	 */
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

}

  • 定义一个MyImportBeanDefinitionRegistrar类并且实现ImportBeanDefinitionRegistrar这个接口
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     *
     * AnnotationMetadata 当前类的注解信息
     * BeanDefinitionRegistry BeanDefinition注册类
     *
     * 我们把所有需要添加到容器中的bean通过BeanDefinitionRegistry里面的registerBeanDefinition方法来手动的进行注册
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //判断IOC容器里面是否含有这两个组件
        boolean definition = registry.containsBeanDefinition("com.ldc.bean.Red");
        boolean definition2 = registry.containsBeanDefinition("com.ldc.bean.Blue");
        //如果有的话,我就把RainBow的bean的实例给注册到IOC容器中
        if (definition && definition2) {
            //指定bean的定义信息,参数里面指定要注册的bean的类型:RainBow.class
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RainBow.class);
            //注册一个bean,并且指定bean名
            registry.registerBeanDefinition("rainBow", rootBeanDefinition );
        }
    }
}

@Configuration
//满足当前条件,这个类中配置的所有bean注册才能生效
@Conditional({WindowsCondition.class})
//快速导入组件,id默认是组件的全类名
@Import({Color.class,Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
public class MainConfig2 {

    /**
     * @Conditional:是SpringBoot底层大量使用的注解,按照一定的条件来进行判断,满足条件 给容器注册bean
     */

    /**
     *  现在下面的两个bean注册到IOC容器是要条件的:
     *  1.如果系统是windows,给容器注册("bill")
     *  1.如果系统是linux,给容器注册("linus")
     * @return
     */


    @Bean("bill")
    public Person person01() {
        return new Person("Bill Gates",62);
    }

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

    /**
     * 给容器中注册组件:
     * 1)、扫描+组件标注注解(@Controller/@Service/@Repository/@Component)
     * 【局限于要求是自己写的类,如果导入的第三方没有添加这些注解,那么就注册不上了】
     *
     * 2)、@Bean[导入的第三方包里面的组件]
     * 3)、@Import[快速的给容器中导入一个组件]
     *      (1)、 @Import(要导入容器中的组件);容器中就会自动的注册这个组件,id默认是全类名
     *      (2)、 ImportSelector :返回需要的组件的全类名的数组;在springboot中用的较多
     *      (3)、 ImportBeanDefinitionRegistrar : 手动注册bean到容器中
     */
}

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig2
com.ldc.bean.Color
com.ldc.bean.Red
com.ldc.bean.Blue
com.ldc.bean.Yellow
bill
rainBow

FactoryBean

  • 使用FactoryBean工厂进行组件注册
public interface FactoryBean<T> {

	/**
	 * Return an instance (possibly shared or independent) of the object
	 * managed by this factory.
	 * <p>As with a {@link BeanFactory}, this allows support for both the
	 * Singleton and Prototype design pattern.
	 * <p>If this FactoryBean is not fully initialized yet at the time of
	 * the call (for example because it is involved in a circular reference),
	 * throw a corresponding {@link FactoryBeanNotInitializedException}.
	 * <p>As of Spring 2.0, FactoryBeans are allowed to return {@code null}
	 * objects. The factory will consider this as normal value to be used; it
	 * will not throw a FactoryBeanNotInitializedException in this case anymore.
	 * FactoryBean implementations are encouraged to throw
	 * FactoryBeanNotInitializedException themselves now, as appropriate.
	 * @return an instance of the bean (can be {@code null})
	 * @throws Exception in case of creation errors
	 * @see FactoryBeanNotInitializedException
	 */
	T getObject() throws Exception;

	/**
	 * Return the type of object that this FactoryBean creates,
	 * or {@code null} if not known in advance.
	 * <p>This allows one to check for specific types of beans without
	 * instantiating objects, for example on autowiring.
	 * <p>In the case of implementations that are creating a singleton object,
	 * this method should try to avoid singleton creation as far as possible;
	 * it should rather estimate the type in advance.
	 * For prototypes, returning a meaningful type here is advisable too.
	 * <p>This method can be called <i>before</i> this FactoryBean has
	 * been fully initialized. It must not rely on state created during
	 * initialization; of course, it can still use such state if available.
	 * <p><b>NOTE:</b> Autowiring will simply ignore FactoryBeans that return
	 * {@code null} here. Therefore it is highly recommended to implement
	 * this method properly, using the current state of the FactoryBean.
	 * @return the type of object that this FactoryBean creates,
	 * or {@code null} if not known at the time of the call
	 * @see ListableBeanFactory#getBeansOfType
	 */
	Class<?> getObjectType();

	/**
	 * Is the object managed by this factory a singleton? That is,
	 * will {@link #getObject()} always return the same object
	 * (a reference that can be cached)?
	 * <p><b>NOTE:</b> If a FactoryBean indicates to hold a singleton object,
	 * the object returned from {@code getObject()} might get cached
	 * by the owning BeanFactory. Hence, do not return {@code true}
	 * unless the FactoryBean always exposes the same reference.
	 * <p>The singleton status of the FactoryBean itself will generally
	 * be provided by the owning BeanFactory; usually, it has to be
	 * defined as singleton there.
	 * <p><b>NOTE:</b> This method returning {@code false} does not
	 * necessarily indicate that returned objects are independent instances.
	 * An implementation of the extended {@link SmartFactoryBean} interface
	 * may explicitly indicate independent instances through its
	 * {@link SmartFactoryBean#isPrototype()} method. Plain {@link FactoryBean}
	 * implementations which do not implement this extended interface are
	 * simply assumed to always return independent instances if the
	 * {@code isSingleton()} implementation returns {@code false}.
	 * @return whether the exposed object is a singleton
	 * @see #getObject()
	 * @see SmartFactoryBean#isPrototype()
	 */
	boolean isSingleton();

}

  • 创建一个类,并且实现 FactoryBean 接口:
//创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean<Color> {

    //返回一个Color对象,这个对象会添加到容器中
    @Override
    public Color getObject() throws Exception {
        System.out.println("ColorFactoryBean...getBean...");
        return new Color();
    }

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

    //控制是否为单例
    // true:表示的就是一个单实例,在容器中保存一份
    // false:多实例,每次获取都会创建一个新的bean
    @Override
    public boolean isSingleton() {
        return true;
    }
}

  • 在配置类里面进行配置:可以看到表面上我们装配的是ColorFactoryBean这个类型,但是实际上我们装配的是Color这个bean的实例
@Configuration
//满足当前条件,这个类中配置的所有bean注册才能生效
@Conditional({WindowsCondition.class})
//快速导入组件,id默认是组件的全类名
@Import({Color.class,Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
public class MainConfig2 {

    /**
     * @Conditional:是SpringBoot底层大量使用的注解,按照一定的条件来进行判断,满足条件 给容器注册bean
     */

    /**
     *  现在下面的两个bean注册到IOC容器是要条件的:
     *  1.如果系统是windows,给容器注册("bill")
     *  1.如果系统是linux,给容器注册("linus")
     * @return
     */


    @Bean("bill")
    public Person person01() {
        return new Person("Bill Gates",62);
    }

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

    /**
     * 给容器中注册组件:
     * 1)、扫描+组件标注注解(@Controller/@Service/@Repository/@Component)
     * 【局限于要求是自己写的类,如果导入的第三方没有添加这些注解,那么就注册不上了】
     *
     * 2)、@Bean[导入的第三方包里面的组件]
     * 3)、@Import[快速的给容器中导入一个组件]
     *      (1)、 @Import(要导入容器中的组件);容器中就会自动的注册这个组件,id默认是全类名
     *      (2)、 ImportSelector :返回需要的组件的全类名的数组;
     *      (3)、 ImportBeanDefinitionRegistrar : 手动注册bean到容器中
     *
     * 4)、使用Spring提供的FactoryBean(工厂bean)
     *      (1)、默认获取到的是工厂bean调用getObject创建的对象
     *      (2)、要获取工厂bean本身,我们需要给id前面加上一个“&”符号:&colorFactoryBean
     */

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

    @Test
    public void test4() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        printBeans(applicationContext);
        Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
        System.out.println("bean的类型:"+colorFactoryBean.getClass());
    }

    private void printBeans(ApplicationContext applicationContext) {
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig2
com.ldc.bean.Color
com.ldc.bean.Red
com.ldc.bean.Blue
com.ldc.bean.Yellow
bill
colorFactoryBean
rainBow
ColorFactoryBean…getBean…
bean的类型:class com.ldc.bean.Color

注册工厂本身

  • 如果我们就想要获取这个工厂bean,我们就可以在id的前面加上一个”&”符号
    @Test
    public void test4() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        printBeans(applicationContext);

        //工厂bean获取的是调用getObject方法创建的对象
        Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
        System.out.println("bean的类型:"+colorFactoryBean.getClass());

        //如果我们就想要获取这个工厂bean,我们就可以在id的前面加上一个"&"符号
        Object colorFactoryBean2 = applicationContext.getBean("&colorFactoryBean");
        System.out.println("bean的类型:"+colorFactoryBean2.getClass());
    }

    private void printBeans(ApplicationContext applicationContext) {
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig2
com.ldc.bean.Color
com.ldc.bean.Red
com.ldc.bean.Blue
com.ldc.bean.Yellow
bill
colorFactoryBean
rainBow
ColorFactoryBean…getBean…
colorFactoryBean的类型:class com.ldc.bean.Color
colorFactoryBean2的类型:class com.ldc.bean.ColorFactoryBean

Similar Posts

Comments