Spring注解版--spring给容器注册组件的四种方法

Spring注解版–spring给容器注册组件的四种方法:

这是我看网上视频教程之后,自己整理的,加强记忆。

文章目录

一. @Configuration&@Bean-自动扫描组件、扫描规则组件

1、创建一个webquick项目,内部添加pom依赖

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

给maven工程添加spring-context的依赖。

2、比较xml注册bean与注解注册bean

(1)xml配置注册bean

1544768247996

IOC在项目中添加一个resources文件夹,内部存放所有的配置文件以及资源。现在我们在内部创建一个bean.xml这个IOC容器的xml配置文件。

bean.xml配置类代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--<bean></bean>-->
    <bean id="user" class="com.xiaojian.bean.User">
        <property name="username" value="xiaojian"></property>
        <property name="userage" value="11"></property>
    </bean>
</beans>

如图,我们给容器添加了一个bean,指向我们的一个User的实体类。此处的User的实体类,我们存放在java目录下的bean文件夹中。
User实体类如下:

package com.xiaojian.bean;

public class User {

    private String username;

    private Integer userage;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getUserage() {
        return userage;
    }

    public void setUserage(Integer userage) {
        this.userage = userage;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", userage='" + userage + '\'' +
                '}';
    }

    public User(String username, Integer userage) {
        this.username = username;
        this.userage = userage;
    }

    public User() {
    }
}

我们在test目录下面创建MainTest01测试类,测试获取IOC容器中的bean。

测试类MainTest01:

package com.xiaojian.test;

import com.xiaojian.bean.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainTest01 {

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

测试结果:

1544768940929

(2)注解注册bean

在config目录下创建MainConfig类,此类为创建bean的配置类。

package com.xiaojian.config;

import com.xiaojian.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

    //给容器注册一个bean;类型为返回值的类型;id默认是用方法名作为id(不过我们也可以自己指定id,就是value值)
    @Bean(value = "user01")
    public User user01(){
        return new User("小贱",13);
    }
}

此class类文件中,@Configuration注解告诉spring这是一个配置类,配置类的作用就是xml配置文件的作用。

@Bean 注解是给容器注册一个bean,类型为返回值的类型,id默认是用方法名作为id,不过我们可以自己定义id,也就是@Bean注解下value属性值。

@Bean注解接口(系统内部的注解,我们直接使用):

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
    @AliasFor("name")
    String[] value() default {};

    @AliasFor("value")
    String[] name() default {};

    /** @deprecated */
    @Deprecated
    Autowire autowire() default Autowire.NO;

    boolean autowireCandidate() default true;

    String initMethod() default "";

    String destroyMethod() default "(inferred)";
}

此注解接口中定义了value这个属性,就是bean的id。当使用value这个属性的时候,所有通过id想要获取此bean,必须通过value定义的id,而不是方法名。

我们在test目录下再定义一个MainTest02这个类文件,测试注解方式注册bean。

测试类MainTest02:

package com.xiaojian.test;

import com.xiaojian.bean.User;
import com.xiaojian.config.MainConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest02 {

    public static void main(String[] args) {

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        User user = (User) applicationContext.getBean("user01");
        System.out.println(user);

        //此处的方法是通过类型来获取所有的bean的集合。
        Map<String, User> beansOfType = applicationContext.getBeansOfType(User.class);
        System.out.println(beansOfType);
        
        
        //此处的方法是通过类型来获取Bean的名称,此处是通过User这个类,来获取Bean的名称。
        String[] beanNamesForType = applicationContext.getBeanNamesForType(User.class);
        for (String name : beanNamesForType) {
            System.out.println(name);
        }
        
        
        //applicationContext下面还有好多的方法,可以有时间可以自己看看。
        
    }
}

运行结果:

1544770122075

3.@ComponentScan包扫描

(1)创建相关的类

首先在xiaojian这个文件夹下面分别创建controller、service、dao这三个文件夹,里面在分别创建对应的BookController、BookService、BookDao这三个类文件,同时添加对应的注解。

BookController:

package com.xiaojian.controller;

import org.springframework.stereotype.Controller;

@Controller   //此注解说明这是一个控制器
public class BookController {
}

BookService:

package com.xiaojian.service;

import org.springframework.stereotype.Service;

@Service   //此注解作用是给容器扫描到,说明此类事服务层
public class BookService {
}

BookDao:

package com.xiaojian.dao;

import org.springframework.stereotype.Repository;

@Repository   //此注解是为了给容器扫描到,说明此类是dao层。
public class BookDao {
}

(2)xml配置中的包扫描方式

xml中的配置方式:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--  包扫描,只要标注了@Controller、@Service、@Repository、@Component中的任何一个,都会被扫描-->
    <context:component-scan base-package="com.xiaojian"></context:component-scan>
    <!--<bean></bean>-->
    <bean id="user" class="com.xiaojian.bean.User">
        <property name="username" value="xiaojian"></property>
        <property name="userage" value="11"></property>
    </bean>
</beans>

xml文件中通过<context:component-scan base-package="" />来扫描配置的bean。

(3)注解实现包扫描方式

我们只需要在@Configuration主配置类中添加@ComponentScan( value="com.xiaojian")就可以自动扫描xiaojian这个包下面的所有的添加了@Controller@Service、@Repository@Component

MainConfig主配置类:

package com.xiaojian.config;

import com.xiaojian.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

//配置类==配置文件
@Configuration  //告诉spring这是一个配置类
@ComponentScan(value = "com.xiaojian")
//包扫描路径,与在xml文件中配置 <context:component-scan base-package="com.xiaojian"></context:component-scan>  作用一样。
public class MainConfig {

    //给容器注册一个bean;类型为返回值的类型;id默认是用方法名作为id(不过我们也可以自己指定id,就是value值)
    @Bean(value = "user01")
    public User user01(){
        return new User("小贱",13);
    }
}

小的测试

在test目录下添加一个测试类——MainTest03。

MainTest03:

package com.xiaojian.test;

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

public class MainTest03 {

    public static void main(String[] args) {

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);

        //此方法可以获取所有的bean定义的名字
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name:beanDefinitionNames){
            System.out.println(name);
        }

    }
}

测试结果:

1544772149159

(4)@ComponentScan属性值

  1. value值:

    value值是说明此扫描的扫描路径,系统只会扫描路径下的所有的添加了@Controller@Service、@Repository@Component注解的类。

  2. excludeFilters属性:

    产生过滤规则,此指定扫描的时候按照什么规则排除哪些组件。此写法是一个Filter数组。

    MainConfig:

    package com.xiaojian.config;
    
    import com.xiaojian.bean.User;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.FilterType;
    import org.springframework.stereotype.Controller;
    import org.springframework.stereotype.Service;
    
    //配置类==配置文件
    @Configuration  //告诉spring这是一个配置类
    @ComponentScan(value = "com.xiaojian",excludeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
    })
    //此处的type中的 FilterType.ANNOTATION表明排除注解方式的配置bean,classes表明排除的配置bean添加的注解的类型(此处主要排除@Controller、@Service两个注解的配置类)
    //包扫描路径,与在xml文件中配置 <context:component-scan base-package="com.xiaojian"></context:component-scan>  作用一样。
    public class MainConfig {
    
        //给容器注册一个bean;类型为返回值的类型;id默认是用方法名作为id(不过我们也可以自己指定id,就是value值)
        @Bean(value = "user01")
        public User user01(){
            return new User("小贱",13);
        }
    }
    
    

    运行MainTest03测试类,测试结果:

    1544773543923

    系统的打印日志中少了BookController、BookService这两个。

  3. includeFilters属性:

    产生过滤规则,此指定扫描的时候按照什么规则只扫描哪些组件。此写法是一个Filter数组。

    (与上面相反)

    不过此处需要注意,因为在@ComponentScan中有个属性useDefaultFilters,默认为true,而我们使用includeFilters属性的时候,需要将useDefaultFilters设置为false。

    MainConfig:

    package com.xiaojian.config;
    
    import com.xiaojian.bean.User;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.FilterType;
    import org.springframework.stereotype.Controller;
    import org.springframework.stereotype.Service;
    
    //配置类==配置文件
    @Configuration  //告诉spring这是一个配置类
    @ComponentScan(value = "com.xiaojian",includeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
    },useDefaultFilters = false)  //必须把useDefaultFilters设置为false才能使用includeFilters
    //包扫描路径,与在xml文件中配置 <context:component-scan base-package="com.xiaojian"></context:component-scan>  作用一样。
    public class MainConfig {
    
        //给容器注册一个bean;类型为返回值的类型;id默认是用方法名作为id(不过我们也可以自己指定id,就是value值)
        @Bean(value = "user01")
        public User user01(){
            return new User("小贱",13);
        }
    }
    
    

    运行MainTest03测试类,测试结果:

    在这里插入图片描述

    此处只有一个bookController,因为includeFilters中只有一个Controller通行。

    @ComponentScan是一个可重复写的注解,我们可以在一个类上多次添加此注解。

    或者我们可以使用@ComponentScans注解,内部添加多个@ComponentScan

(5)@Filter注解type属性指定扫描规则

  • FilterType.ANNOTATION:按照注解的方式(最常用)
  • FilterType.ASPECTJ:使用ASPECTJ表达式(几乎不常用)
  • FilterType.REGEX:使用正则表达式
  • FilterType.ASSIGNABLE_TYPE:按照给定的类型
@ComponentScan(value = "com.xiaojian",includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {BookService.class})
},useDefaultFilters = false)

在MainConfig类文件的@ComponentScanincludeFilters属性中添加一个FilterType.ASSIGNABLE_TYPE的Filter,此Filter通过BookService这个指定的类文件。

运行MainTest03测试类,测试结果:

1544775256737

  • FilterType.CUSTOM:使用自定义规则

使用自定义规则的时候,这个值必须是一个TypeFilter规则的实现类。

首先我们在xiaojian这个文件夹下创建一个impls文件夹,在此文件夹下创建一个MyTypeFilter类去实现TypeFilter这个接口,接口中有一个返回boolean类型值的方法。

package com.xiaojian.impls;

import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;

public class MyTypeFilter implements TypeFilter{

    /**
     *
     *
     * @param metadataReader     读取到的当前正在扫描的类的星系
     * @param metadataReaderFactory      可以获取到其他任何类的信息
     * @return
     * @throws IOException
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {

        //获取当前类注释的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

        //获取当前正在扫描的类的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();

        //获取当前类资源(类的路径)
        Resource resource = metadataReader.getResource();

        //获取类名
        String className = classMetadata.getClassName();
        //获取父类名
        String superClassName = classMetadata.getSuperClassName();
        //获取子类名
        String[] memberClassNames = classMetadata.getMemberClassNames();
        //获取接口名
        String[] interfaceNames = classMetadata.getInterfaceNames();

        //上面那些方法我们有时间可以自己去看看。
        System.out.println("---->"+className);

        //判断逻辑,为true,则放行,为false则拦截。
        if (className.contains("vice")){
            return true;
        }
        return false;
    }
}

MainConfig类文件也做出相应的改变

package com.xiaojian.config;

import com.xiaojian.bean.User;
import com.xiaojian.impls.MyTypeFilter;
import com.xiaojian.service.BookService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;

//配置类==配置文件
@Configuration  //告诉spring这是一个配置类
@ComponentScan(value = "com.xiaojian",includeFilters = {
        @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})
},useDefaultFilters = false)
//FilterType.CUSTOM是使用自定义规则,其实现的是MyTypeFilter这个类中放行的内容。

//包扫描路径,与在xml文件中配置 <context:component-scan base-package="com.xiaojian"></context:component-scan>  作用一样。
public class MainConfig {

    //给容器注册一个bean;类型为返回值的类型;id默认是用方法名作为id(不过我们也可以自己指定id,就是value值)
    @Bean(value = "user01")
    public User user01(){
        return new User("小贱",13);
    }
}

运行MainTest03测试类,测试结果:

1544777355713

如图,只有一个bookService被放行,注册到了IOC的容器中,因为判断逻辑只给包含"vice"的放行。而其他的两个,是不在这个过滤器管辖范围内的。

4.@Scope指定作用范围

(1)默认情况(单实例)

  • 创建MainConfig2

我们重新创建一个MainConfig类,类名为MainConfig2,同时标注注解@Configuration

MainConfig2:

package com.xiaojian.config;

import com.xiaojian.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig2 {

   
    //默认的都是单实例的
    @Bean
    public User user(){
        return new User("小贱",333);
    }
}
  • 创建测试类

在test文件夹下面创建一个MainTest04这个测试类。

MainTest04:

package com.xiaojian.test;

import com.xiaojian.bean.User;
import com.xiaojian.config.MainConfig2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest04 {

    public static void main(String[] args) {


        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);

        //此方法可以获取所有的bean定义的名字
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name:beanDefinitionNames){
            System.out.println(name);
        }

        User user = (User) applicationContext.getBean("user");
        User user1 = (User) applicationContext.getBean("user");
        //比较两者是否为同一个对象,观察所有的bean的创建的默认情况下是单例还是多例。
        System.out.println(user==user1);
    }
}

运行MainTest04测试类,测试结果:

1544779115195

说明所有的bean默认情况下都是单例模式,就是所有创建的bean都是同一个。

(2)多实例案例

  • 修改MainConfig2.java类文件,使得User这个bean为多实例bean。

MainConfig2:

package com.xiaojian.config;

import com.xiaojian.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class MainConfig2 {


    /**
     * @Scope的作用范围:
     *   singleton:单实例(系统默认情况下都是单实例的)
     *   prototype:多实例
     *   request:同一次请求创建一个实例
     *   session:同一个session创建一个实例
     *
     * 注意:
     * - 系统默认的是单实例,IOC容器启动会调用方法创建对象放到IOC容器中,以便以后每次获取,就可以直接从容器中获取
     * - 多实例:IOC容器获取的时候才会创建,且获取几次容器会调用方法创建对象几次。IOC容器启动的时候并不会调用该方法创建对象到IOC容器中。
     */
    //默认的都是单实例的
    @Scope("prototype")
    @Bean
    public User user(){
        return new User("小贱",333);
    }
}

  • 测试结果

运行MainTest04测试类,测试结果:

1544840397310

所以当bean被定义为多实例的时候,每次调用都会创建一个bean对象,并且这些bean对象不是同一个。

(3)单实例与多实例的创建时间对比

  • 单实例情况下:

修改MainConfig2.java文件:

MainConfig2:

package com.xiaojian.config;

import com.xiaojian.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class MainConfig2 {


    /**
     * @Scope的作用范围:
     *   singleton:单实例(系统默认情况下都是单实例的)
     *   prototype:多实例
     *   request:同一次请求创建一个实例
     *   session:同一个session创建一个实例
     *
     * 注意:
     * - 系统默认的是单实例,IOC容器启动会调用方法创建对象放到IOC容器中,以便以后每次获取,就可以直接从容器中获取
     * - 多实例:IOC容器获取的时候才会创建,且获取几次容器会调用方法创建对象几次。IOC容器启动的时候并不会调用该方法创建对象到IOC容器中。
     */
    //默认的都是单实例的
//    @Scope("prototype")
    @Bean
    public User user(){
        System.out.println("给容器中添加User。。。");
        return new User("小贱",333);
    }
}

将User这个bean还原为单实例对象。

修改测试类MainTest04.java:

MainTest04:

package com.xiaojian.test;

import com.xiaojian.bean.User;
import com.xiaojian.config.MainConfig2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest04 {

    public static void main(String[] args) {
        
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器启动完毕");
    }
}

此时系统只是简单的运行了,创建了IOC容器,但是容器中并没有做任何的操作。

运行MainTest04测试类,测试结果:

1544840957584

如图,在系统启动IOC容器的时候,系统已经给我们创建了user这个bean,也就是说,系统默认的是单实例,IOC容器启动会调用方法创建对象放到IOC容器中,以便以后每次获取,就可以直接从容器中获取。

  • 多实例情况下:

修改MainConfig2.java文件:

MainConfig2:

package com.xiaojian.config;

import com.xiaojian.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class MainConfig2 {


    /**
     * @Scope的作用范围:
     *   singleton:单实例(系统默认情况下都是单实例的)
     *   prototype:多实例
     *   request:同一次请求创建一个实例
     *   session:同一个session创建一个实例
     *
     * 注意:
     * - 系统默认的是单实例,IOC容器启动会调用方法创建对象放到IOC容器中,以便以后每次获取,就可以直接从容器中获取
     * - 多实例:IOC容器获取的时候才会创建,且获取几次容器会调用方法创建对象几次。IOC容器启动的时候并不会调用该方法创建对象到IOC容器中。
     */
    //默认的都是单实例的
    @Scope("prototype")
    @Bean
    public User user(){
        System.out.println("给容器中添加User。。。");
        return new User("小贱",333);
    }
}

将User这个bean设置为多实例对象。

修改测试类MainTest04.java:

MainTest04:

package com.xiaojian.test;

import com.xiaojian.bean.User;
import com.xiaojian.config.MainConfig2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest04 {

    public static void main(String[] args) {


        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);

        System.out.println("IOC容器启动完毕");


        User user = (User) applicationContext.getBean("user");
        User user1 = (User) applicationContext.getBean("user");
        System.out.println(user==user1);
    }
}

运行MainTest04测试类,测试结果:

1544841318961

如图,在系统启动IOC容器的时候,系统并没有给我们创建User这个bean,而是我们去调用这个bean的时候,系统会给我创建这个bean。也就是说,多实例的情况下,IOC容器获取的时候才会创建,且获取几次容器会调用方法创建对象几次。IOC容器启动的时候并不会调用方法创建对象到IOC容器中。

(4)@Scope指定作用范围

@Scope的作用范围:

  • singleton:单实例(系统默认情况下都是单实例的)
  • prototype:多实例
  • request:同一次请求创建一个实例
  • session:同一个session创建一个实例

上面的四个作用范围,我们实际上用的比较多的是singleton以及prototype这两种。

注意:

  • 系统默认的是单实例,IOC容器启动会调用方法创建对象放到IOC容器中,以便以后每次获取,就可以直接从容器中获取
  • 多实例:IOC容器获取的时候才会创建,且获取几次容器会调用方法创建对象几次。IOC容器启动的时候并不会调用方法创建对象到IOC容器中。

5.@Lazy懒加载

(1)懒加载含义

单实例bean的情况下,系统会在IOC容器启动的时候创建单实例的bean,而懒加载的含义是让容器在启动的时候,不创建对象,而是等到第一次调用的时候在创建。

(2)案例

  • 修改MainConfig2.java文件:

MainConfig2:

package com.xiaojian.config;

import com.xiaojian.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;

@Configuration
public class MainConfig2 {


    /**
     * @Scope的作用范围:
     *   singleton:单实例(系统默认情况下都是单实例的)
     *   prototype:多实例
     *   request:同一次请求创建一个实例
     *   session:同一个session创建一个实例
     *
     * 注意:
     * - 系统默认的是单实例,IOC容器启动会调用方法创建对象放到IOC容器中,以便以后每次获取,就可以直接从容器中获取
     * - 多实例:IOC容器获取的时候才会创建,且获取几次容器会调用方法创建对象几次。IOC容器启动的时候并不会调用该方法创建对象到IOC容器中。
     */
    //默认的都是单实例的
//    @Scope("prototype")
    @Lazy
    @Bean
    public User user(){
        System.out.println("给容器中添加User。。。");
        return new User("小贱",333);
    }
}

我们在User这个bean上面添加@Lazy这个注解,标明这个User的bean是懒加载,告诉容器其在本身启动的时候不先创建此单实例的bean,等到调用的时候再创建。

  • 修改测试类

修改MainTest04.java测试类:

MainTest04:

package com.xiaojian.test;

import com.xiaojian.bean.User;
import com.xiaojian.config.MainConfig2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest04 {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);

        System.out.println("IOC容器启动完毕");

        User user = (User) applicationContext.getBean("user");
 
    }
}

我们在系统启动IOC容器完成后再调用这个User的bean,看看这个单实例的bean在什么时候被创建的。

  • 测试运行

运行MainTest04测试类,测试结果:

1544842015881

如图,加上@Lazy注解的User这个bean,在容器IOC启动的时候并没有被创建,而是在IOC调用这个bean的时候才被IOC容器创建。且以后再调用这个bean的时候,不会再创建这个bean。

6.@Conditional条件注解

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

(1)创建类

  • 创建主题类

我们在config文件夹下面创建MainConfig3.java类文件,以作新的案例。

MainConfig3:

package com.xiaojian.config;

import com.xiaojian.bean.User;
import com.xiaojian.impls.LinuxCondition;
import com.xiaojian.impls.WindowsCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig3 {

    /**
     * @Conditional:按照一定的条件进行判断,满足条件给容器中注册bean
     *
     * @Conditional注解:
     *    可以标注在类上,也可以标注在方法上
     *
     * 要求:
     *    如果系统是windows,给容器中注册xiaojian
     *    如果系统是linux,给容器中注册dashu
     */

    @Conditional({WindowsCondition.class})
    @Bean("xiaojian")
    public User user01(){
        return new User("xiaojian",22);
    }

    @Conditional({LinuxCondition.class})
    @Bean("dashu")
    public User user02(){
        return new User("dashu",23);
    }
}

  • 创建WindowsCondition.java

我们在impls这个文件包下面创建WindowsCondition.java这个类文件。

WindowsCondition:

package com.xiaojian.impls;

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

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

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




        //1.能获取到IOC使用的beanfactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();

        //2.获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();

        //3.获取当前环境信息
        Environment environment = conditionContext.getEnvironment();

        //4.获取到bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        //我们可以通过registry这个对象,判断容器中对的bean的注册情况,同时也可以更新一些信息

        
        //判断操作系统
        String property = environment.getProperty("os.name");
        boolean windows = property.contains("Windows");

        if (windows){
            System.out.println("此电脑是windows系统");
            return true;
        }
        return false;
    }
}

  • 创建LinuxCondition.java

我们在impls这个文件包下面创建LinuxCondition.java这个类文件。

LinuxCondition:

package com.xiaojian.impls;

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

//判断系统是否为linux
public class LinuxCondition implements Condition{

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



        //1.能获取到IOC使用的beanfactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();

        //2.获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();

        //3.获取当前环境信息
        Environment environment = conditionContext.getEnvironment();

        //4.获取到bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        //我们可以通过registry这个对象,判断容器中对的bean的注册情况,同时也可以更新一些信息


        //判断操作系统
        String property = environment.getProperty("os.name");
        boolean linux = property.contains("Linux");
        if (linux){
            System.out.println("此电脑是linux系统");
            return true;
        }
        return false;
    }
}

  • 测试类

我们在test的文件夹下创建MainTest05。

MainTest05:

package com.xiaojian.test;

import com.xiaojian.bean.User;
import com.xiaojian.config.MainConfig3;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.Environment;

import java.util.Map;

public class MainTest05 {

    public static void main(String[] args) {

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
        System.out.println("IOC容器启动完成");

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


        //获取User类型的所有的bean
        Map<String, User> beansOfType = applicationContext.getBeansOfType(User.class);
        System.out.println(beansOfType);
    }
}

(2)测试结果

运行测试类MainTest05.java类文件中的主函数。

测试结果:

1544854351948

如图所示,在IOC容器启动过程中,系统会默认的去创建单实例的bean,而我们两个bean上面都标注了条件注解,所以在创建bean的时候,它们都会先去启动条件判断执行的类,所以第一条是“这是windows系统”,之后才是“IOC容器启动完成”,而后我们可以看出系统打印出现在系统中已经存在的bean的名称,因为此系统是windows系统,所以只有"xiaojian"这个bean被注册。

二. @Bean[导入的第三方包里面的组件]

此部分省略。

三. @Import[快速给容器中导入一个组件]

1.导入单个组件

(1)创建Color.java类文件

我们在bean目录下创建一个Color.java类文件。

Color:

package com.xiaojian.bean;

public class Color {
}

此类文件将作为本次的bean对象。

(2)创建MainConfig4.java类文件

我们创建MainConfig4.java类文件,此文件作为此案例的配置类。

MainConfig4:

package com.xiaojian.config;

import com.xiaojian.bean.Color;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(Color.class)   //@Import导入组件,id默认是组件的全类名
public class MainConfig4 {
}

此处我们使用@Import注解来给IOC容器注册bean,组件的id默认是组件的全类名。

(3)创建测试类

我们在test包下面创建一个MainTest06.java测试类。
MainTest06:

package com.xiaojian.test;

import com.xiaojian.config.MainConfig4;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest06 {

    public static void main(String[] args) {

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4.class);

        //获取系统中的所有定义的bean名称
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name:beanDefinitionNames){
            System.out.println(name);
        }

    }
}

(4)测试运行

运行MainTest06.java测试类,测试结果如下:

1544856125324

如图,系统中已经注册了Color这个bean,只不过其id是Color.java这个类文件的全路径。

2.导入多个组件

(1)创建Animal.java类文件

我们在bean目录下创建一个Animal.java类文件,内部为空,只是简单的作为一个bean创建对象。

package com.xiaojian.bean;

public class Animal {
}

(2)修改MainConfig4.java类文件

导入多个组件,其实就是修改@Import的导入的值,而这个注解本身是可以导入一个数组的,所以我们将上面的那个之后改成一个数组就行。

MainConfig4:

package com.xiaojian.config;

import com.xiaojian.bean.Animal;
import com.xiaojian.bean.Color;
import com.xiaojian.bean.User;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({Color.class, Animal.class, User.class})   //@Import导入组件,id默认是组件的全类名
public class MainConfig4 {
}

(3)测试运行

运行MainTest06.java测试类,测试结果如下:

1544857191082

如图,配置类中导入了三个组件,此处显示三个组件的全类名。

3.ImportSelector接口实现(第二种导入方法)

此方法是导入一个类,该类去实现ImportSelector接口,这个接口内部有一个selectImports方法需要方法重载,其会返回一个带有系统需要导入的组件的全类名的数组,告诉IOC容器去创建哪些bean对象。

(1)修改MainConfig4.java类文件

MainConfig4:

package com.xiaojian.config;

import com.xiaojian.bean.Animal;
import com.xiaojian.bean.Color;
import com.xiaojian.bean.User;
import com.xiaojian.impls.MyImportSelector;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
//@Import({Color.class, Animal.class, User.class})   //@Import导入组件,id默认是组件的全类名
@Import({MyImportSelector.class})
//导入一个自定义的组件选择控制类,其实现了ImportSelector这个接口,接口返回一个需要导入的组件的全类名的数组
public class MainConfig4 {
}

(2)创建MyImportSelector.java类文件

我们在impls这个包下面创建一个MyImportSelector.java类文件,去实现ImportSelector这个接口。

MyImportSelector:

package com.xiaojian.impls;

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

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

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

        //方法不能返回一个null,否则会出现空指针异常。
        return new String[]{"com.xiaojian.bean.Color","com.xiaojian.bean.User"};
    }
}

此处的selectImports方法返回一个数组,其内部包含Color、User这两个实例对象,目的就是告诉IOC容器在启动的时候去创建这两个bean对象。

(3)测试运行

运行MainTest06.java测试类,测试结果如下:

1544858445957

如图,控制台打印出了Color以及User这两个实体类的bean对象。

4.ImportBeanDefinitionRegistrar接口实现(第三种导入方法)

此方法是导入一个类,该类去实现ImportBeanDefinitionRegistrar接口,这个接口内部有一个registerBeanDefinitions方法需要方法重载,我们可以在这个方法的内部注册我们所需要的bean,同时我们也可以在这个方法中做一些其他的事情。

(1)创建MyImportBeanDefinitionRegistrar.java类文件

我们在impls这个包下面创建一个MyImportBeanDefinitionRegistrar.java类文件,去实现registerBeanDefinitions这个接口。

MyImportBeanDefinitionRegistrar:

package com.xiaojian.impls;

import com.xiaojian.bean.Animal;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportBeanDefinitonRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     *
     * @param annotationMetadata   当前类的注解信息以及其他的信息
     * @param beanDefinitionRegistry   BeanDefinition注册类
     *
     * 把所有的需要添加到容器中的bean:调用
     *   BeanDefinitionRegistry.registerBeanDefinition手动注册进来
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {

        //判断内部是否有User这个bean的信息
        boolean user = beanDefinitionRegistry.containsBeanDefinition("User");

        //判断内部是否有Color这个bean的信息
        boolean color = beanDefinitionRegistry.containsBeanDefinition("Color");


        if (!user && !color){


            //registerBeanDefinition这个方法用于注册bean。
            //    第一个参数是这个bean的名称
            //    第二个参数是一个BeanDefinition,我们用RootBeanDefinition这个实现

            //指定bean定义信息:(bean的类型,bean的作用域。。。)
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Animal.class);
            //注册一个bean,指定bean的名称
            beanDefinitionRegistry.registerBeanDefinition("Animal", rootBeanDefinition);
        }
    }
}

(2)修改MainConfig4.java类文件

MainConfig4:

package com.xiaojian.config;

import com.xiaojian.bean.Animal;
import com.xiaojian.bean.Color;
import com.xiaojian.bean.User;
import com.xiaojian.impls.MyImportBeanDefinitonRegistrar;
import com.xiaojian.impls.MyImportSelector;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
//@Import({Color.class, Animal.class, User.class})   //@Import导入组件,id默认是组件的全类名
//@Import({MyImportSelector.class})
//导入一个自定义的组件选择控制类,其实现了ImportSelector这个接口,接口返回一个需要导入的组件的全类名的数组

@Import(MyImportBeanDefinitonRegistrar.class)
//导入一个实现ImportBeanDefinitionRegistrar接口的类,该接口把所有需要注册的bean通过内部的方法手动注册进来。
public class MainConfig4 {
}

(3)测试运行

运行MainTest06.java测试类,测试结果如下:

1544860814962

如图,控制台只打印了一个Animal这个bean,因为我们在方法中只注册了一个这个bean。

四. 使用Spring提供的FactoryBean工厂(bean)

1.创建ColorFactoryBean.java类文件

我们在impls这个包下面创建一个ColorFactoryBean.java类文件,此类实现FactoryBean这个接口。

ColorFactoryBean:

package com.xiaojian.impls;

import com.xiaojian.bean.Color;
import org.springframework.beans.factory.FactoryBean;

//创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean {

    //返回一个Color对象,这个对象会添加到容器中
    @Override
    public Object getObject() throws Exception {

        //此处返回一个Color对象,意思就是告诉IOC容器,我们注册一个Color这个bean。
        return new Color();
    }

    @Override
    public Class<?> getObjectType() {

        //此处是返回上面getObject()创建的bean对象的类型
        return Color.class;
    }


    @Override
    public boolean isSingleton() {

        //此处是返回上面getObject()方法创建的bean对象是否为单例模式
        //true则为单实例,容器中只保存一份;false,多实例,每次获取都会返回一份。
        return false;
    }
}

此类实现FactoryBean,我们需要重写三个方法:

  • getObject():此方法返回的是一个bean对象,就是将会在IOC容器中创建的对象
  • getObjectType():此方法返回的是getObject()方法返回的对象的类型
  • isSingleton():此方法返回的是getObject()方法返回的bean对象作用域范围,就是是单例模式还是多例模式

2.创建MainConfig5.java类文件

我们在config包下面创建MainConfig5.java类文件,在此类文件中创建ColorFactoryBean这个bean对象的声明。

MainConfig5:

package com.xiaojian.config;

import com.xiaojian.impls.ColorFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig5 {

    //此处声明了ColorFactoryBean这个bean的对象,告诉IOC容器
    @Bean
    public ColorFactoryBean colorFactoryBean(){
        return new ColorFactoryBean();
    }
}

3.创建测试类

在test包下面创建一个MainTest07.java类文件,进行相干的测试。

MainTest07:

package com.xiaojian.test;

import com.xiaojian.config.MainConfig5;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

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

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig5.class);

        //此处查看我们所有的bean对象的名称
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name:beanDefinitionNames){
            System.out.println("此方法为:"+name);
        }

        //工厂Bean获取的时调用getObject方法创建的对象
        
        //此处是为了查看我们获取的bean实际上的名称
        Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
        System.out.println("bean的名称"+colorFactoryBean.toString());
        System.out.println("bean的类型:"+colorFactoryBean.getClass());

    }
}

4.运行测试

运行MainTest07.java测试类,测试结果如下:

1544863586414

如图,为测试结果。图中的1,是IOC容器创建的bean对象,而2是colorFactoryBean这个对象的实际的指向。也就是说,我们在MainConfig5.java类文件中创建的那个bean,我们在IOC容器中获取的却是Color,因为我们在ColorFactoryBean的方法中返回了Color这个bean。

猜你喜欢

转载自blog.csdn.net/dh12313012/article/details/85015924