Spring注解驱动开发——IOC容器

文章目录

1、使用@Configuration和@Bean给容器中注册组件

首先创建一个Person类:

public class Person {
    private String name;
    private Integer age;
    
    ......

1.1、xml配置文件方法

创建一个beans.xml,在配置文件中指定<bean>标签,其中属性包括id(唯一标识)class(全类名)

<bean id="person" class="bean.Person">
    <property name="age" value="18"></property>
    <property name="name" value="zhangsan"/>
</bean>

测试时,使用ClassPathXmlApplicationContext()方法传入xml配置文件的路径来获取一个ApplicationContext对象:

@Test
public void test1(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    Object person = applicationContext.getBean("person");
    System.out.println(person);
    // Person{name='zhangsan', age=18}
}

1.2、注解配置

创建一个注解类MainConfig,相当于一个配置文件,采用@Configuration注解。

在类中创建一个方法,采用@Bean注解,类型为返回值类型,id默认是用方法名作为id,也可以给@Bean注解传入一个参数,作为id

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

    //给容器中注册一个Bean;类型为返回值类型,id默认是用方法名作为id
    @Bean("person") //可以传入参数,作为id
    public Person person01(){
        return new Person("lisi", 20);
    }
}

获取Bean时,使用AnnotationConfigApplicationContext()方法传入注解类的class:

@Test
public void test2(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
    Person person = applicationContext.getBean(Person.class);
    System.out.println(person);
    // Person{name='lisi', age=20}

    String[] beanNamesForType = applicationContext.getBeanNamesForType(Person.class);
    for (String string :
            beanNamesForType) {
        System.out.println(string);
        // person
    }
}

2、使用@ComponentScan自动扫描组件并指定扫描规则

创建BookService、BookController、BookDao类,并分别使用@Service@Controller@Repository注解:

@Service
public class BookService {
}
@Controller
public class BookController {
}
@Repository
public class BookDao {
}

2.1、xml配置文件方式

<!--包扫描,只要标注了@Controller、@Service、@Repository、@Component-->
<!--<context:component-scan base-package={"bean", "config", "controller", "dao", "service"}></context:component-scan>-->

2.2、注解方式

在注解类中添加@ComponentScan注解,并提供value属性的值:

//配置类==配置文件
@Configuration //告诉spring,这是一个配置类
@ComponentScan(value = {"bean", "config", "controller", "dao", "service"})
public class MainConfig {

    //给容器中注册一个Bean;类型为返回值类型,id默认是用方法名作为id
    @Bean("person") //可以传入参数,作为id
    public Person person01(){
        return new Person("lisi", 20);
    }


}

输出一下所有定义的Bean的名字:

@Test
public void test3(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
    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.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookController
bookDao
bookService
person

2.2.1、扫描规则:excludeFilters

看一下@ComponentScan注解的源码:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
//可重复的
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    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 "**/*.class";

    //默认扫描规则
    boolean useDefaultFilters() default true;

    //包含哪些包
    ComponentScan.Filter[] includeFilters() default {};

    //排除哪些包
    ComponentScan.Filter[] excludeFilters() default {};
    
    ......

可以看到includeFilters()excludeFilters()返回值都是ComponentScan.Filter[],我们查看ComponentScan.Filter[]

@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
    FilterType type() default FilterType.ANNOTATION;

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

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

    String[] pattern() default {};
}

可以看到,需要指定type属性和classes(或value,都是一个数组,可以指定多个):

//配置类==配置文件
@Configuration //告诉spring,这是一个配置类
@ComponentScan(value = {"bean", "config", "controller", "dao", "service"},
               excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})})
//@ComponentScan value:指定要扫描的包
//@ComponentScan excludeFilters = ComponentScan.Filter[]:排除哪些包
public class MainConfig {

    //给容器中注册一个Bean;类型为返回值类型,id默认是用方法名作为id
    @Bean("person") //可以传入参数,作为id
    public Person person01(){
        return new Person("lisi", 20);
    }


}

再次测试:

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

2.2.2、扫描规则:includeFilters

在xml配置文件配置中,使用includeFilters需要先禁用默认扫描规则(因为默认是全部扫描的)

<!--包扫描,只要标注了@Controller、@Service、@Repository、@Component-->
<context:component-scan use-default-filters="false" base-package={"bean", "config", "controller", "dao", "service"} ></context:component-scan>

查看上面的@ComponentScan注解的源码,可以发现其中也有默认的扫描规则,useDefaultFilters默认为true,因为需要先设定其为false,然后按照相同的方法配置includeFilters:

//配置类==配置文件
@Configuration //告诉spring,这是一个配置类
@ComponentScan(
               value = {"bean", "config", "controller", "dao", "service"},
               includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})},
               useDefaultFilters = false
               //excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}
)
//@ComponentScan value:指定要扫描的包
//@ComponentScan excludeFilters = ComponentScan.Filter[]:排除哪些包
//@ComponentScan includeFilters = ComponentScan.Filter[]:只要哪些包
public class MainConfig {

    //给容器中注册一个Bean;类型为返回值类型,id默认是用方法名作为id
    @Bean("person") //可以传入参数,作为id
    public Person person01(){
        return new Person("lisi", 20);
    }


}

再次测试:

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

2.2.3、配置多个ComponentScan

查看上面的@ComponentScan注解的源码,可以发现有@Repeatable注解,说明可以设定多个@ComponentScan

//配置类==配置文件
@Configuration //告诉spring,这是一个配置类
@ComponentScan(
               value = {"bean", "config", "controller", "dao", "service"},
               includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})},
               useDefaultFilters = false
               //excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}
)
//@ComponentScan value:指定要扫描的包
//@ComponentScan excludeFilters = ComponentScan.Filter[]:排除哪些包
//@ComponentScan includeFilters = ComponentScan.Filter[]:只要哪些包
@ComponentScans(
        value = {
                @ComponentScan(
                        value = {"bean", "config", "controller", "dao", "service"},
                        includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})},
                        useDefaultFilters = false
                        //excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}
                )
        }
)
public class MainConfig {

    //给容器中注册一个Bean;类型为返回值类型,id默认是用方法名作为id
    @Bean("person") //可以传入参数,作为id
    public Person person01(){
        return new Person("lisi", 20);
    }


}

3、自定义TypeFilter指定@ComponentScan注解的过滤规则

先来查看一下@ComponentScan.Filter中的FilterType:

public enum FilterType {
    ANNOTATION,
    ASSIGNABLE_TYPE,
    ASPECTJ,
    REGEX,
    CUSTOM;

    private FilterType() {
    }
}

是一个枚举类型。其中:

  • ANNOTATION:按照注解方式
  • ASSIGNABLE_TYPE:按照类型
  • ASPECTJ:按照ASPECTJ,不常用
  • REGEX:按照正则表达式
  • CUSTOM:按照自定义类型

创建一个类MyTypeFilter继承TypeFilter:

public class MyTypeFilter implements TypeFilter {
    /**
     *
     * @param metadataReader 读取到的当前正在扫描的类的信息
     * @param metadataReaderFactory 可以获取到其他任何类信息的
     * @return
     * @throws IOException
     */
    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);

        //如果类名称包含"er"就扫描
        if (className.contains("er")){
            return true;
        }

        return false;
    }
}
//配置类==配置文件
@Configuration //告诉spring,这是一个配置类
@ComponentScan(
               value = {"bean", "config", "controller", "dao", "service"},
               includeFilters = {
                                /* @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
                                 @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class}),*/
                                 @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
               },
               useDefaultFilters = false
               //excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}
)
//@ComponentScan value:指定要扫描的包
//@ComponentScan excludeFilters = ComponentScan.Filter[]:排除哪些包
//@ComponentScan includeFilters = ComponentScan.Filter[]:只要哪些包
//FilterType.ANNOTATION:按照注解
//FilterType.ASSIGNABLE_TYPE:按照给定类型
//FilterType.ASPECTJ:按照ASPECTJ表达式
//FilterType.REGEX:按照正则表达式
//FilterType.CUSTOM:按照自定义规则
@ComponentScans(
        value = {
                @ComponentScan(
                        value = {"bean", "config", "controller", "dao", "service"},
                        includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})},
                        useDefaultFilters = false
                        //excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}
                )
        }
)
public class MainConfig {

    //给容器中注册一个Bean;类型为返回值类型,id默认是用方法名作为id
    @Bean("person") //可以传入参数,作为id
    public Person person01(){
        return new Person("lisi", 20);
    }


}
--->bean.Person
--->config.MyTypeFilter
--->controller.BookController
--->dao.BookDao
--->service.BookService
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
person
myTypeFilter
bookController
bookService

4、使用@Scope注解设置组件的作用域

创建一个配置类MainConfig2:

@Configuration
public class MainConfig2 {

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

默认情况下是**单实例的,即不管多少次getBean()**,获取到的都是那一个new Person("张三", 5)

@Test
public void test4(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    String[] definitionNames = applicationContext.getBeanDefinitionNames();
    
    //默认是单实例
    Object bean = applicationContext.getBean("person");
    Object bean2 = applicationContext.getBean("person");
    System.out.println(bean == bean2); //true
}

可以使用@Scope注解为其配置实现单实例还是多实例,查看@Scope源码:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {

   @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 "";

   ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

}

发现有四个取值:

  1. ConfigurableBeanFactory.SCOPE_PROTOTYPE, prototype:多实例
  2. ConfigurableBeanFactory.SCOPE_SINGLETON, singleton:单实例(默认值)
  3. org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST, request:同一个请求创建一个实例
  4. org.springframework.web.context.WebApplicationContext.SCOPE_SESSION, session:同一个session创建一个实例
@Configuration
public class MainConfig2 {

    //默认是单实例

    /**
     * ConfigurableBeanFactory.SCOPE_PROTOTYPE,  prototype:多实例
     * ConfigurableBeanFactory.SCOPE_SINGLETON,  singleton:单实例(默认值)
     * org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST,  request:同一个请求创建一个实例
     * org.springframework.web.context.WebApplicationContext.SCOPE_SESSION,  session:同一个session创建一个实例
     * @return
     */
    @Scope("prototype")
    @Bean("person")
    public Person person(){
        return new Person("张三", 5);
    }
}
@Test
public void test4(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    String[] definitionNames = applicationContext.getBeanDefinitionNames();

    //默认是单实例
    Object bean = applicationContext.getBean("person");
    Object bean2 = applicationContext.getBean("person");
    System.out.println(bean);
    System.out.println(bean2);
    
    System.out.println(bean == bean2); //false
}
Person{name='张三', age=5}
Person{name='张三', age=5}
false

在xml配置文件中可以使用<bean>标签中的scope属性实现:

<bean id="person" class="bean.Person" scope="prototype">
    <property name="age" value="18"></property>
    <property name="name" value="zhangsan"/>
</bean>

4.1、单实例和多实例情况下,对象是何时创建的?

先来看单实例情况,在return new Person("张三", 5);调用创建对象的方法之前添加一个System.out.println("给容器中添加Pserson...");语句:

@Configuration
public class MainConfig2 {

    //默认是单实例

    /**
     * ConfigurableBeanFactory.SCOPE_PROTOTYPE,  prototype:多实例
     * ConfigurableBeanFactory.SCOPE_SINGLETON,  singleton:单实例(默认值)
     * org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST,  request:同一个请求创建一个实例
     * org.springframework.web.context.WebApplicationContext.SCOPE_SESSION,  session:同一个session创建一个实例
     *
     * prototype:多实例,IOC容器启动并不会调用方法创建对象放到IOC容器中
     *            以后每次获取时才会调用方法创建对象
     * singleton:单实例(默认值),IOC容器启动时会调用方法创建对象,并放到IOC容器中
     *            以后每次获取就从容器中拿,因此是单实例
     *
     * @return
     */
    @Scope //调整作用域
    @Bean("person")
    public Person person(){
        System.out.println("给容器中添加Pserson...");
        return new Person("张三", 5);
    }
}

在测试代码中,先来创建IOC容器,并没有从容器中拿出对象:

@Test
public void test4(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    //String[] definitionNames = applicationContext.getBeanDefinitionNames();
    //
    //默认是单实例
    System.out.println("IOC容器创建完成");
    //Object bean = applicationContext.getBean("person");
    //Object bean2 = applicationContext.getBean("person");
    //System.out.println(bean);
    //System.out.println(bean2);

    //System.out.println(bean == bean2);
}
给容器中添加Pserson...
IOC容器创建完成

打印出给容器中添加Pserson...。因此,可以推断:单实例(默认值),IOC容器启动时会调用方法创建对象,并放到IOC容器中。以后每次获取就从容器中拿,因此是单实例。

再看多实例情况:

@Configuration
public class MainConfig2 {

    //默认是单实例

    /**
     * ConfigurableBeanFactory.SCOPE_PROTOTYPE,  prototype:多实例
     * ConfigurableBeanFactory.SCOPE_SINGLETON,  singleton:单实例(默认值)
     * org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST,  request:同一个请求创建一个实例
     * org.springframework.web.context.WebApplicationContext.SCOPE_SESSION,  session:同一个session创建一个实例
     *
     * prototype:多实例,IOC容器启动并不会调用方法创建对象放到IOC容器中
     *            以后每次获取时才会调用方法创建对象
     * singleton:单实例(默认值),IOC容器启动时会调用方法创建对象,并放到IOC容器中
     *            以后每次获取就从容器中拿,因此是单实例
     *
     * @return
     */
    @Scope("prototype") //调整作用域
    @Bean("person")
    public Person person(){
        System.out.println("给容器中添加Pserson...");
        return new Person("张三", 5);
    }
}

依然只创建IOC容器:

@Test
public void test4(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    //String[] definitionNames = applicationContext.getBeanDefinitionNames();
    //
    //默认是单实例
    //System.out.println("IOC容器创建完成");
    //Object bean = applicationContext.getBean("person");
    //Object bean2 = applicationContext.getBean("person");
    //System.out.println(bean);
    //System.out.println(bean2);

    //System.out.println(bean == bean2);
}

并没有任何输出。因此可以推断:多实例,创建IOC 容器时,并不会创建对象

开启获取对象,在获取对象前添加System.out.println("IOC容器创建完成");

@Test
public void test4(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    //String[] definitionNames = applicationContext.getBeanDefinitionNames();
    //
    //默认是单实例
    System.out.println("IOC容器创建完成");
    Object bean = applicationContext.getBean("person");
    Object bean2 = applicationContext.getBean("person");
    //System.out.println(bean);
    //System.out.println(bean2);

    //System.out.println(bean == bean2);
}
IOC容器创建完成
给容器中添加Pserson...
给容器中添加Pserson...

获取了两次对象,输出了两次给容器中添加Pserson...。因此,可以推断:多实例,IOC容器启动并不会调用方法创建对象放到IOC容器中,以后每次获取时才会调用方法创建对象


5、@Lazy-bean如何实现懒加载?

懒加载是针对单实例bean:

  • 单实例bean默认在容器启动时创建对象;
  • 懒加载:容器启动时不创建对象,第一次使用(获取)bean时创建对象,并初始化。
@Scope//("prototype") //调整作用域
@Lazy //懒加载
@Bean("person")
public Person person(){
    System.out.println("给容器中添加Pserson...");
    return new Person("张三", 5);
}

测试时,先不获取对象:

@Test
public void test4(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    //String[] definitionNames = applicationContext.getBeanDefinitionNames();
    //
    //默认是单实例
    System.out.println("IOC容器创建完成");
    //Object bean = applicationContext.getBean("person");
    //Object bean2 = applicationContext.getBean("person");
    //System.out.println(bean);
    //System.out.println(bean2);

    //System.out.println(bean == bean2);
}
IOC容器创建完成

只打印出IOC容器创建完成。并没创建对象。

获取对象,并判断对象是否相等:

@Test
public void test4(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    //String[] definitionNames = applicationContext.getBeanDefinitionNames();
    //
    //默认是单实例
    System.out.println("IOC容器创建完成");
    Object bean = applicationContext.getBean("person");
    Object bean2 = applicationContext.getBean("person");
    System.out.println(bean);
    System.out.println(bean2);

    System.out.println(bean == bean2);
}
IOC容器创建完成
给容器中添加Pserson...
Person{name='张三', age=5}
Person{name='张三', age=5}
true

可以看到,虽然获取了两次对象,但只打印了一次给容器中添加Pserson...,说明只调用了一次创建对象的方法,因此,两个对象相等。


6、如何按照条件向Spring容器中注册bean?

@Conditional():按照一定的条件进行判断,满足条件给容器中注册bean

先查看@Conditional()源码:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

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

}

可以看到@Target({ElementType.TYPE, ElementType.METHOD}),表明:@Conditional()可以用在类上也可以用在方法上

Class<? extends Condition>[] value();也表明:@Conditional()的value值需要一个继承extends Condition的类(.class)的列表。

查看Condition接口:

@FunctionalInterface
public interface Condition {

   /**
    * Determine if the condition matches.
    * @param context the condition context
    * @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
    * or {@link org.springframework.core.type.MethodMetadata method} being checked
    * @return {@code true} if the condition matches and the component can be registered,
    * or {@code false} to veto the annotated component's registration
    */
   boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

可知,需要重写matches()方法。

我们的需求是,注册两个Person类,

  • 如果系统是Windows,给容器中注册(“bill”);

  • 如果系统是Linux,给容器中注册(“linux”)

创建两个类WindowsCondition和LinuxCondition,实现Condition接口,并重写matches()方法:

//判断操作系统是否是Windows
public class WindowsCondition implements Condition {
    /**
     *
     * @param context:判断条件能使用的上下文(环境)
     * @param metadata:注释信息
     * @return
     */
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //是否Windows系统
        //1、能获取到IOC使用的beanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //2、获取类加载器
        ClassLoader classLoader = context.getClassLoader();
        //3、获取当前环境信息
        Environment environment = context.getEnvironment();
        //4、获取到bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();

        String property = environment.getProperty("os.name");
        if (property.contains("Windows")){
            return true;
        }

        return false;
    }
}
//判断操作系统是否是Linux
public class LinuxCondition implements Condition {
    /**
     *
     * @param context:判断条件能使用的上下文(环境)
     * @param metadata:注释信息
     * @return
     */
    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();

        String property = environment.getProperty("os.name");
        if (property.contains("Linux")){
            return true;
        }

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


        return false;
    }
}

修改配置类:

/**
 * @Conditional(Class<? extends Condition>[]):按照一定的条件进行判断,满足条件给容器中注册bean
 * 如果系统是Windows,给容器中注册("bill")
 * 如果系统是Linux,给容器中注册("linux")
 */
@Conditional({WindowsCondition.class})
@Bean("bill")
public Person person01(){
    return new Person("Bill Gates", 62);
}

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

测试:

@Test
public void test5(){
    String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
    Environment environment = applicationContext.getEnvironment();

    //动态获取环境变量的值:Windows 10
    String property = environment.getProperty("os.name");
    System.out.println(property);

    for (String name :
           namesForType ) {
        System.out.println(name);
    }

    Map<String, Person> persons = applicationContext.getBeansOfType(Person.class);
    System.out.println(persons);
}
Windows 10
person
bill
给容器中添加Pserson...
{person=Person{name='张三', age=5}, bill=Person{name='Bill Gates', age=62}}

也可以把@Conditional()放在类上:

@Configuration
//类中组件统一设置:满足当前条件,这个类中配置的所有bean注册才能生效
@Conditional({WindowsCondition.class})
public class MainConfig2 {
    ......

类中组件统一设置:满足当前条件,这个类中配置的所有bean注册才能生效。


7、使用@Import注解给容器中快速导入一个组件

7.1、@Import(要导入的组件)

创建两个类Color和Red:

public class Color {
}
public class Red {
}

在配置类上配置注解@Import

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

测试,抽取出打印Bean名称的方法:

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

@Test
public void testImport(){
    printBeans(applicationContext);
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig2
bean.Color
bean.Red
person
bill

7.2、在@Import注解中使用ImportSelector接口导入bean

查看@Import源码:

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

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

}

除了regular component classes常规的类,还可以使用ImportSelectorImportBeanDefinitionRegistrar

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.
    * @return the class names, or an empty array if none
    */
   String[] selectImports(AnnotationMetadata importingClassMetadata);

   /**
    * Return a predicate for excluding classes from the import candidates, to be
    * transitively applied to all classes found through this selector's imports.
    * <p>If this predicate returns {@code true} for a given fully-qualified
    * class name, said class will not be considered as an imported configuration
    * class, bypassing class file loading as well as metadata introspection.
    * @return the filter predicate for fully-qualified candidate class names
    * of transitively imported configuration classes, or {@code null} if none
    * @since 5.2.4
    */
   @Nullable
   default Predicate<String> getExclusionFilter() {
      return null;
   }

}

ImportSelector接口中有一个selectImports()方法,因此需要重写该方法。@return the class names, or an empty array if none表明:返回值是一个class数组或一个空数组。

再创建两个类,Blue和Yellow:

public class Blue {
}
public class Yellow {
}

创建一个MyImportSelector类,实现ImportSelector接口:

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

    //返回值,就是导入到容器中的组件全类名
    //AnnotationMetadata:当前标注@Import注解的类的所有注解信息
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {


        return new String[]{"bean.Blue", "bean.Yellow"};
    }

}

测试:

@Test
public void testImport(){
    printBeans(applicationContext);
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig2
bean.Color
bean.Red
bean.Blue
bean.Yellow
person
bill

7.3、在@Import注解中使用ImportBeanDefinitionRegistrar向容器中注册bean

查看ImportBeanDefinitionRegistrar接口:

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.
    * <p>The default implementation delegates to
    * {@link #registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)}.
    * @param importingClassMetadata annotation metadata of the importing class
    * @param registry current bean definition registry
    * @param importBeanNameGenerator the bean name generator strategy for imported beans:
    * {@link ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR} by default, or a
    * user-provided one if {@link ConfigurationClassPostProcessor#setBeanNameGenerator}
    * has been set. In the latter case, the passed-in strategy will be the same used for
    * component scanning in the containing application context (otherwise, the default
    * component-scan naming strategy is {@link AnnotationBeanNameGenerator#INSTANCE}).
    * @since 5.2
    * @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR
    * @see ConfigurationClassPostProcessor#setBeanNameGenerator
    */
   default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
         BeanNameGenerator importBeanNameGenerator) {

      registerBeanDefinitions(importingClassMetadata, registry);
   }

   /**
    * 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.
    * <p>The default implementation is empty.
    * @param importingClassMetadata annotation metadata of the importing class
    * @param registry current bean definition registry
    */
   default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
   }

}

ImportBeanDefinitionRegistrar接口中有一个方法:registerBeanDefinitions(),其中有两个参数:

  1. AnnotationMetadata importingClassMetadata:当前类的注解信息
  2. BeanDefinitionRegistry registry:BeanDefinition注册类

查看BeanDefinitionRegistry接口:

public interface BeanDefinitionRegistry extends AliasRegistry {
    void registerBeanDefinition(String var1, BeanDefinition var2) throws BeanDefinitionStoreException;

    void removeBeanDefinition(String var1) throws NoSuchBeanDefinitionException;

    BeanDefinition getBeanDefinition(String var1) throws NoSuchBeanDefinitionException;

    boolean containsBeanDefinition(String var1);

    String[] getBeanDefinitionNames();

    int getBeanDefinitionCount();

    boolean isBeanNameInUse(String var1);
}

其中也有一个方法:registerBeanDefinition(),里边有一个BeanDefinition类型。

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    String SCOPE_SINGLETON = "singleton";
    String SCOPE_PROTOTYPE = "prototype";
    int ROLE_APPLICATION = 0;
    int ROLE_SUPPORT = 1;
    int ROLE_INFRASTRUCTURE = 2;
    ......

BeanDefinition是一个接口,我们需要找到其实现类,其中有一个RootBeanDefinition:

public class RootBeanDefinition extends AbstractBeanDefinition {
    @Nullable
    private BeanDefinitionHolder decoratedDefinition;
    ......

创建MyImportBeanDefinitionRegistrar,继承ImportBeanDefinitionRegistrar接口:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     *
     * @param importingClassMetadata:当前类的注解信息
     * @param registry:BeanDefinition注册类
     *                把所有需要添加到容器中的bean;调用
     *                registry.registerBeanDefinition手动注册
     */
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        boolean definition = registry.containsBeanDefinition("bean.Red");
        boolean definition1 = registry.containsBeanDefinition("bean.Blue");
        if (definition && definition1){
            //指定Bean定义信息:Bean的类型,Scope,...
            RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
            //注册一个Bean,指定bean名
            registry.registerBeanDefinition("rainBow", beanDefinition);
        }
    }
}

测试:

@Test
public void testImport(){
    printBeans(applicationContext);
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig2
bean.Color
bean.Red
bean.Blue
bean.Yellow
person
bill
rainBow

总结:

给容器中注册组件:

  1. 包扫描+组件标记注解(@Controller/@Service/@Repository/@Component)【局限于自己写的类】
  2. @Bean【导入的第三方包里边的组件】
  3. @Import【快速给容器中导入组件】
    1. @Import(要导入的组件):容器中就会自动注册这个组件,id默认是组件的全类名
    2. ImportSelect:返回需要导入的组件的全类名数组
    3. ImportBeanDefinitionRegistrar:手动注册Bean到容器中

8、如何使用FactoryBean向Spring容器中注册bean?

查看一下FactoryBean的源码:

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}

是一个接口,里边还要三个方法:getObject()getObjectType()isSingleton()

创建一个ColorFactoryBean类,继承FactoryBean接口:

//创建一个Spring定义的FacoryBean
public class ColorFactoryBean implements FactoryBean<Color> {

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

    public Class<?> getObjectType() {
        return Color.class;
    }

    //控制是单例吗?
    //如果返回true,这个bean是单实例,在容器中保存一份
    //如果返回false,这个bean是多实例,每次获取都会创建一个新的bean,就是调用getObject()方法
    public boolean isSingleton() {
        return false;
    }
}

需要重写三个方法:

  1. getObject():返回一个对象,这个对象会添加到IOC容器中;
  2. getObjectType():返回值是一个Class类型;
  3. isSingleton():是否是单实例?

在配置文件中写上colorFactoryBean()方法:

@Bean
public ColorFactoryBean colorFactoryBean(){
    return new ColorFactoryBean();
}
@Test
public void testImport(){
    printBeans(applicationContext);

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

    System.out.println(colorFactoryBean == colorFactoryBean2);

    Object colorFactoryBean3 = applicationContext.getBean("&colorFactoryBean");
    System.out.println(colorFactoryBean3.getClass());
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig2
bean.Color
bean.Red
bean.Blue
bean.Yellow
person
bill
colorFactoryBean
rainBow
ColorFactoryBean...调用getObject()
ColorFactoryBean...调用getObject()
bean类型:class bean.Color
false
class bean.ColorFactoryBean

可以发现:

  • 工厂Bean获取的是调用getObject创建的对象

    即:applicationContext.getBean("colorFactoryBean");根据id获取的工厂bean,得到的是getObject创建的对象,bean.Color

  • 获取工厂bean本身,我们需要给id前边加一个&

    即:applicationContext.getBean("&colorFactoryBean");获得的才是bean.ColorFactoryBean


9、如何使用@Bean注解指定初始化和销毁的方法?

bean的生命周期:

  • bean初始化 --> 初始化 --> 销毁的过程

容器管理bean的生命周期:

  • 我们可以自动以初始化和销毁方法:

    容器在bean进行到当前生命周期的时候来调用自定义的初始化和销毁方法

xml配置文件的方法:

<bean id="person" class="bean.Person" scope="prototype" init-method="" destroy-method="">
    <property name="age" value="18"></property>
    <property name="name" value="zhangsan"/>
</bean>

创建一个Car类,指定其中构造方法、自定义初始化方法和销毁方法:

public class Car {
    public Car() {
        System.out.println("Car constructor...");
    }

    public void init(){
        System.out.println("Car init...");
    }

    public void destory(){
        System.out.println("Car destory...");
    }
}

创建一个配置类MainConfigOfLifeCycle,使用@Bean的initMethod和destroyMethod指定初始化和销毁方法:

@Configuration
public class MainConfigOfLifeCycle {

    @Bean(initMethod = "init", destroyMethod = "destory")
    public Car car(){
        return new Car();
    }
}
@Test
public void test1(){
    System.out.println("容器创建开始...");
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
    System.out.println("容器创建完成...");

    //关闭IOC
    applicationContext.close();
}
容器创建开始...
Car constructor...
Car init...
容器创建完成...
Car destory...

如果是多实例:

@Configuration
public class MainConfigOfLifeCycle {

    @Scope("prototype")
    @Bean(initMethod = "init", destroyMethod = "destory")
    public Car car(){
        return new Car();
    }
}
@Test
public void test1(){
    System.out.println("容器创建开始...");
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
    System.out.println("容器创建完成...");

    //关闭IOC
    applicationContext.close();
}
容器创建开始...
容器创建完成...

可以发现:多实例时,容器创建时是不会创建对象的。

@Test
public void test1(){
    System.out.println("容器创建开始...");
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
    System.out.println("容器创建完成...");

    Object car = applicationContext.getBean("car");

    //关闭IOC
    applicationContext.close();
}
容器创建开始...
容器创建完成...
Car constructor...
Car init...

可以发现:多实例时,获取对象的时候才创建对象,但是在容器关闭时,并没有销毁对象。


10、使用InitializingBean和DisposableBean来管理bean的生命周期

先来查看一下InitializingBean和DisposableBean的源代码:

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

一个接口,里边有afterPropertiesSet()方法。

public interface DisposableBean {
    void destroy() throws Exception;
}

也是一个接口,里边有destroy()方法。

创建一个Car类,实现这两个接口:

@Component
public class Cat implements InitializingBean, DisposableBean {

    public Cat() {
        System.out.println("Cat constructor...");
    }

    public void destroy() throws Exception {
        System.out.println("Cat destroy...");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("Cat afterPropertiesSet...");
    }
}

并在其上加上@Component组件,准备使用扫描的方式:

在配置类上加上扫描的配置:

@ComponentScan("bean")
@Configuration
public class MainConfigOfLifeCycle {
    ......

测试:

@Test
public void test2(){
    System.out.println("容器创建开始...");
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
    System.out.println("容器创建完成...");

    //关闭IOC
    applicationContext.close();
}
容器创建开始...
Cat constructor...
Cat afterPropertiesSet...
容器创建完成...
Cat destroy...

11、@PostConstruct注解和@PreDestroy注解

@PostConstruct源码:

@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {
}

可以看到这个注解是标注在方法(METHOD)上。@PostConstruct:在bean创建完成并且属性赋值完成,来执行初始化方法。

@PreDestroy源码:

@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PreDestroy {
}

可以看到这个注解也是标注在方法(METHOD)上。@PreDestroy:在容器销毁bean之前通知我们进行清理工作。

创建一个Dog类,添加上面两个注解:

@Component
public class Dog {
    public Dog(){
        System.out.println("Dog constructor...");
    }

    //对象创建并赋值之后调用
    @PostConstruct
    public void init(){
        System.out.println("Dog PostConstruct...");
    }

    //容器移除之前
    @PreDestroy
    public void destory(){
        System.out.println("Dog PreDestroy...");
    }

}

12、BeanPostProcessor后置处理器

BeanPostProcessor源码:

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

有两个方法:

  1. postProcessBeforeInitialization():在初始化之前
  2. postProcessAfterInitialization():在初始化之后

创建MyBeanPostProcessor类继承BeanPostProcessor接口:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization" + beanName + "==>" + bean);
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization" + beanName + "==>" + bean);
        return bean;
    }
}
@Test
public void test2(){
    System.out.println("容器创建开始...");
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
    System.out.println("容器创建完成...");

    //关闭IOC
    applicationContext.close();
}
容器创建开始...
postProcessBeforeInitializationmainConfigOfLifeCycle==>config.MainConfigOfLifeCycle$$EnhancerBySpringCGLIB$$ba2fd071@4149c063
postProcessAfterInitializationmainConfigOfLifeCycle==>config.MainConfigOfLifeCycle$$EnhancerBySpringCGLIB$$ba2fd071@4149c063
Cat constructor...
postProcessBeforeInitializationcat==>bean.Cat@376a0d86
Cat afterPropertiesSet...
postProcessAfterInitializationcat==>bean.Cat@376a0d86
容器创建完成...
Cat destroy...

13、BeanPostProcessor的执行流程


14、BeanPostProcessor在Spring底层是如何使用的?


15、使用@Value注解为bean的属性赋值

创建一个MyConfigOfPropertyValues配置类:

@Configuration
public class MyConfigOfPropertyValues {

    @Bean
    public Person person(){
        return new Person();
    }
}

在Person类中使用@Value为属性赋值:

public class Person {

    //使用@Value赋值:
    //1、基本数值
    //2、可以写SqEL:#{}
    //3、可以写${}:取出配置文件中的值(在运行环境变量里边的值)
    @Value("王五")
    private String name;
    private Integer age;
    ......

测试:

public class IOCTest_PropertyValue {

    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfPropertyValues.class);

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

    @Test
    public void test1(){

        printBeans(applicationContext);
        System.out.println("=======================");

        Object person = applicationContext.getBean("person");
        System.out.println(person);

        //关闭IOC
        applicationContext.close();
    }
}
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
myConfigOfPropertyValues
person
=======================
Person{name='王五', age=null}

后两种使用情况:

首先创建一个外部配置文件person.properties:

person.nickName = jack

在Person中添加一个属性nickName:

public class Person {

    //使用@Value赋值:
    //1、基本数值
    //2、可以写SqEL:#{}
    //3、可以写${}:取出配置文件[properties]中的值(在运行环境变量里边的值)
    @Value("王五")
    private String name;
    @Value("#{20-2}")
    private Integer age;
    @Value("${person.nickName}")
    private String nickName;
    ......

在其上使用@Value("${person.nickName}")获取外部的值。

同时还需要在配置类中获取配置文件,使用@PropertySource注解:

@PropertySource(value = {"classpath:/person.properties"}, encoding = "GBK")
@Configuration
public class MyConfigOfPropertyValues {

    @Bean
    public Person person(){
        return new Person();
    }
}

测试:

@Test
public void test1(){

    printBeans(applicationContext);
    System.out.println("=======================");

    Object person = applicationContext.getBean("person");
    System.out.println(person);

    //关闭IOC
    applicationContext.close();
}
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
myConfigOfPropertyValues
person
=======================
Person{name='王五', age=18, nickName='jack'}

一旦读取到了配置文件,就会加载到环境中,因此可以用environment.getProperty()方法获取到:

@Test
public void test1(){

    printBeans(applicationContext);
    System.out.println("=======================");

    Object person = applicationContext.getBean("person");
    System.out.println(person);

    ConfigurableEnvironment environment = applicationContext.getEnvironment();
    String property = environment.getProperty("person.nickName");
    System.out.println(property);

    //关闭IOC
    applicationContext.close();
}

16、使用@Autowired、@Qualifier、@Primary这三大注解自动装配组件

自动装配:

Spring利用依赖注入(DI),完成IOC容器的各个组件的依赖关系赋值

16.1、@Autowired:自动注入

简而言之:一个组件要用到另一个组件

就比如BookService组件需要使用到BookDao组件:

@Service
public class BookService {

    @Autowired
    private BookDao bookDao;

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao=" + bookDao +
                '}';
    }
}

BookController组件需要用到BookService组件:

@Controller
public class BookController {

    @Autowired
    private BookService bookService;
}
@Test
public void test1(){

    printBeans(applicationContext);
    System.out.println("=======================");

    BookService bookService = applicationContext.getBean(BookService.class);
    System.out.println(bookService);

    Object bookDao = applicationContext.getBean("bookDao");
    System.out.println(bookDao);


    //关闭IOC
    applicationContext.close();
}
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
mainConfigOfAutowired
bookService
bookDao
bookController
bookDao2
=======================
BookService{bookDao=BookDao@4de5031f}
BookDao@4de5031f

@Autowired默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookService.class);找到就赋值

那么如果容器中有两个组件相同类型的组件,@Autowired会选择哪一个呢?

除了自动注入的,我们在配置类中注册一个组件:

@Configuration
@ComponentScan({"service","dao","controller"})
public class MainConfigOfAutowired {

    @Bean("bookDao2")
    public BookDao bookDao(){
        BookDao bookDao = new BookDao();
        bookDao.setLabel("2");
        return bookDao;
    }
}

这个bookDao的id我们取为bookDao2。而自动注入的是首字母小写,即bookDao。

同时为了更明显的区分,我们在BookDao类中添加一个label属性,默认为1:

@Repository
public class BookDao {
    private String label = "1";

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public BookDao() {
    }

    @Override
    public String toString() {
        return "BookDao{" +
                "label='" + label + '\'' +
                '}';
    }
}

测试:

@Test
public void test1(){

    printBeans(applicationContext);
    System.out.println("=======================");

    BookService bookService = applicationContext.getBean(BookService.class);
    System.out.println(bookService);

    Object bookDao = applicationContext.getBean("bookDao");
    System.out.println(bookDao);


    //关闭IOC
    applicationContext.close();
}
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
mainConfigOfAutowired
bookService
bookDao
bookController
bookDao2
=======================
BookService{bookDao=BookDao{label='1'}}
BookDao{label='1'}

可以发现自动注入的label为1的bookDao,即自动扫描注入的bookDao。

那么如何装配bookDao2呢?

@Service
public class BookService {

    @Autowired
    private BookDao bookDao2;

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao=" + bookDao2 +
                '}';
    }
}

把BookService中的BookDao属性改为bookDao2。

测试:

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
mainConfigOfAutowired
bookService
bookDao
bookController
bookDao2
=======================
BookService{bookDao=BookDao{label='2'}}
BookDao{label='1'}

16.2、@Qualifier:指定id装配

@Service
public class BookService {

    @Qualifier("bookDao")
    @Autowired
    private BookDao bookDao2;

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao=" + bookDao2 +
                '}';
    }
}

测试:

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
mainConfigOfAutowired
bookService
bookDao
bookController
bookDao2
=======================
BookService{bookDao=BookDao{label='1'}}
BookDao{label='1'}

那如果自动装配的对象没有注册到容器中呢?

我们把BookDao从容器中删去:

//@Repository
public class BookDao {
    ......
@Configuration
@ComponentScan({"service","dao","controller"})
public class MainConfigOfAutowired {

    //@Bean("bookDao2")
    public BookDao bookDao(){
        ......

再次测试:

Error creating bean with name 'bookService': Unsatisfied dependency expressed through field 'bookDao2'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'dao.BookDao' available:

会报错!

所以,自动装配默认是一定要将属性赋值好,没有就会报错!

可以使用@Autowired中的required属性改变自动装配策略:如果容器中存在组件则自动装配,没有则不装配。

@Service
public class BookService {

    @Qualifier("bookDao")
    @Autowired(required = false)
    private BookDao bookDao2;

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao=" + bookDao2 +
                '}';
    }
}

测试:

@Test
public void test1(){

    printBeans(applicationContext);
    System.out.println("=======================");

    BookService bookService = applicationContext.getBean(BookService.class);
    System.out.println(bookService);

    //关闭IOC
    applicationContext.close();
}
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
mainConfigOfAutowired
bookService
bookController
=======================
BookService{bookDao=null}

可以看到,bookDao为null。

16.3、@Primary:默认首选的bean

让Spring进行自动装配的时候,默认首选的bean

@Primary
@Bean("bookDao2")
public BookDao bookDao(){
    BookDao bookDao = new BookDao();
    bookDao.setLabel("2");
    return bookDao;
}

比如说,在bookDao2上添加注解@Primary:就是在装配的时候,首选是bookDao2。

这样的话,@Qualifier注解就不能使用了,这个是明确指定id装配。与属性名也无关:

//@Qualifier("bookDao")
@Autowired(required = false)
private BookDao bookDao;

测试:

BookService{bookDao=BookDao{label='2'}}
BookDao{label='1'}

装配的是bookDao2。当然,还可以继续使用@Qualifier明确指定装配哪个:

@Qualifier("bookDao")
@Autowired(required = false)
private BookDao bookDao;

测试:

BookService{bookDao=BookDao{label='1'}}
BookDao{label='1'}

可以看到虽然bookDao2是默认首先,但是我使用@Qualifier明确指定要装配bookDao。


17、@Resource注解和@Inject注解

Spring还支持使用@Resource(JSR250)注解和@Inject注解(JSR330),这两个都是java规范的注解。

@Resource

  • 可以和@Autowired一样实现自动装配功能,默认是按照组件名称进行装配的。

    没有能支持@Primary功能和@Autowired(required = false)的功能。

@Service
public class BookService {

    //@Qualifier("bookDao")
    //@Autowired(required = false)
    @Resource
    private BookDao bookDao;

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao=" + bookDao +
                '}';
    }
}

在BookService中把@Autowired(required = false)@Qualifier("bookDao")注解注释掉,添加@Resource注解。

BookService{bookDao=BookDao{label='1'}}
BookDao{label='1'}

@Primary注解的是bookDao2,但是并没有注入。

使用@Inject还需要导入一个依赖:

<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>
@Service
public class BookService {

    //@Qualifier("bookDao")
    //@Autowired(required = false)
    //@Resource
    @Inject
    private BookDao bookDao;
    ......
BookService{bookDao=BookDao{label='2'}}
BookDao{label='1'}

@Inject

  • 需要导入javax.inject的包,和@Autowired的功能一样。
  • 没有required = false功能。

18、实现方法、构造器位置的自动装配

@Autowired源码:

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

@Autowired可以标注的位置:

  • CONSTRUCTOR:构造器
  • METHOD:方法
  • PARAMETER:参数
  • FIELD:属性

18.1、方法位置

创建一个Boss类:

@Component
public class Boss {

    private Car car;

    public Car getCar() {
        return car;
    }

    //标注在方法上,Spring容器创建当前对象,就会调用方法,完成赋值
    //方法使用的参数,自定义类型的值从ioc容器上获取
    @Autowired
    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Boss{" +
                "car=" + car +
                '}';
    }
}

setCar()方法上添加@Autowired

  • 标注在方法上,Spring容器创建当前对象,就会调用方法,完成赋值
  • 方法使用的参数,自定义类型的值从ioc容器上获取

测试:

@Test
public void test2(){

    Object boss = applicationContext.getBean("boss");
    System.out.println(boss);

    //关闭IOC
    applicationContext.close();
}
Boss{car=bean.Car@130161f7}
Cat destroy...

那么,这个Car是从容器中拿到的吗?

@Test
public void test2(){

    System.out.println("=======================");
    
    Object boss = applicationContext.getBean("boss");
    System.out.println(boss);

    Car car = applicationContext.getBean(Car.class);
    System.out.println(car);

    //关闭IOC
    applicationContext.close();
}
Boss{car=bean.Car@130161f7}
bean.Car@130161f7
Cat destroy...

发现没错,地址相同。

18.2、构造器位置

@Component
public class Boss {
    ......

默认加在ioc容器组件,容器自动会调用无参构造器,创建对象,再进行初始化赋值操作。

我们给Boss添加一个有参构造器:

@Component
public class Boss {

    private Car car;

    public Boss(Car car) {
        this.car = car;
    }
    ......

给有参构造器添加@Autowired组件:

@Autowired
public Boss(Car car) {
    this.car = car;
    System.out.println("Boss...有参构造器");
}

那么有参构造器要使用的参数也是从容器中获取的吗?下面做验证:

Boss{car=bean.Car@50b472aa}
bean.Car@50b472aa

没错!就是从容器中获取。

如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略。参数位置的组件还是从容器中获取。

18.3、参数

public Boss(@Autowired Car car) {
    this.car = car;
    System.out.println("Boss...有参构造器");
}

也是从容器中获取参数的值。

18.4、方法位置

假如Color也需要Car:

public class Color {
    private Car car;

    public Color() {
    }
    ......

我们在配置类中使用方法为容器中注入组件Color:

@Bean
public Color color(Car car){
    return new Color();
}

@Bean标注的方法创建对象的时候,方法参数的值从容器中获取

默认不写@Autowired


19、自定义组件中如何注入Spring底层的组件?


20、使用@Profile注解实现开发、测试和生产环境的配置和切换

@Profile

  • Spring为我们提供的可以根据当前环境,动态激活和切换一系列组件的功能;

比如:

  • 开发环境,想要连接A数据源
  • 测试环境,想要连接B数据源
  • 生产环境,想要连接C数据源

以数据源为例:

首先导入需要的包:

<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.5</version>
</dependency>

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>

然后创建配置类,创建三个不同的数据源:

@Configuration
public class MainConfigOfProfile {

    @Bean
    public DataSource dataSourceTest() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("Opfordream@0518");
        dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/mysql");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }

    @Bean
    public DataSource dataSourceDev() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("Opfordream@0518");
        dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/ssm_crud");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }

    @Bean
    public DataSource dataSourceProd() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("Opfordream@0518");
        dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/db1");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }
}

但一般不写死,创建一个properties文件:

db.user=root
db.password=Opfordream@0518
db.driverClass=com.mysql.cj.jdbc.Driver
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile {

    @Value("${db.user}")
    private String user;

    @Value("${db.driverClass}")
    private String driverClass;

    @Bean("testDataSource")
    public DataSource dataSourceTest(@Value("${db.password}") String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/mysql");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Bean("devDataSource")
    public DataSource dataSourceDev(@Value("${db.password}") String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/ssm_crud");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Bean("prodDataSource")
    public DataSource dataSourceProd(@Value("${db.password}") String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/db1");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }
}

测试容器中有哪些数据源:

@Test
public void test1(){

    AnnotationConfigApplicationContext applicationContext =
            new AnnotationConfigApplicationContext(MainConfigOfProfile.class);

    String[] beanNamesForType =
            applicationContext.getBeanNamesForType(DataSource.class);
    for (String name :
            beanNamesForType) {
        System.out.println(name);
    }

    applicationContext.close();

}
testDataSource
devDataSource
prodDataSource

有三个数据源。

@Profile

  • 指定组件在哪个环境下才能被注册到容器中。
@Profile("test")
@Bean("testDataSource")
public DataSource dataSourceTest(@Value("${db.password}") String pwd) throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser(user);
    dataSource.setPassword(pwd);
    dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/mysql");
    dataSource.setDriverClass(driverClass);
    return dataSource;
}

@Profile("dev")
@Bean("devDataSource")
public DataSource dataSourceDev(@Value("${db.password}") String pwd) throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser(user);
    dataSource.setPassword(pwd);
    dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/ssm_crud");
    dataSource.setDriverClass(driverClass);
    return dataSource;
}

@Profile("prod")
@Bean("prodDataSource")
public DataSource dataSourceProd(@Value("${db.password}") String pwd) throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser(user);
    dataSource.setPassword(pwd);
    dataSource.setJdbcUrl("jdbc:mysql://192.168.196.128:3306/db1");
    dataSource.setDriverClass(driverClass);
    return dataSource;
}

再次测试,发现一个组件都没有了。

加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境。

如果在测试环境下,还有一个组件Yellow:

@Profile("test")
@Bean
public Yellow yellow(){
    return new Yellow();
}

那么如何切换环境呢?

  1. 使用命令行动态参数

  2. 代码方式

    @Test
    public void test1(){
    
        //1、创建一个applicationContext
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext();
    
        //2、设置需要激活的环境
        applicationContext.getEnvironment().setActiveProfiles("test", "dev");
    
        //3、注册主配置类
        applicationContext.register(MainConfigOfProfile.class);
    
        //4、启动刷新容器
        applicationContext.refresh();
    
        String[] beanNamesForType =
                applicationContext.getBeanNamesForType(DataSource.class);
        for (String name :
                beanNamesForType) {
            System.out.println(name);
        }
    
    
        applicationContext.close();
    
    }
    
    testDataSource
    devDataSource
    
  3. @Profile还可以写在配置类上

    只有当前环境是指定环境是,该类中的组件才能注册。

  4. 没有标注环境标识的bean,在任何环境下都是加载的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值