Spring Bean 的注入方式

DI 注入

  • 一. 传统的注入方式
    • 通过bean标签方式注入
    • 通过包扫描的方式注入
  • 二. 注解方式配置注入
    • @Configuration 与 @Bean 注解方式注入
    • @ComponentScan 包扫描方式注入
    • @Import 注入
    • FactoryBean 方式注入
  • 三. 获取容器中的实例
      • scopt 单例与多例取值,与创建实例的时间
      • 针对单例
  • 四. @Conditional() 根据条件向容器中注入实例
  • 五. 总结

一. 传统的注入方式

通过bean标签方式注入

  1. 例如配置数据库连接的DruidDataSource,设置数据库连接,登入数据库的用户名密码,然后注入到Spring容器中,在.xml配置文件中通过bean标签的形式,注入
    Spring Bean 的注入方式_第1张图片
  2. 获取测试(注意点ClassPathXmlApplicationContext获取时默认获取resources下的直接文件,若文件在resources文件夹中的文件夹中需要通过""classpath:resources文件夹下的文件夹"来指定)
	public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext
        		("classpath:spring/applicationContext-mybatis.xml");
        DruidDataSource dataSource = (DruidDataSource) context.getBean("dataSource");
        System.out.println(dataSource.getUrl());
    }

通过包扫描的方式注入

  1. 在使用Spring时都用过@Controller,@Service等,将Controller层,Service层注入到Spring容器中,在使这些注解时,前提是通过xml配置开启包扫描,扫描哪些包下的类使用了这些注解来修饰,如果有则将扫描到的注入到Spring容器中
    Spring Bean 的注入方式_第2张图片
  2. 配置扫描与排除
	<!--扫描位置,扫描com.test包中所有的-->
	<context:component-scan base-package="com.test" >
		<!--排除掉注解, 为Controller类型的-->
        <context:exclude-filter 
        		type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>
  1. 原理: 会向容器中注入一个实现了BeanDefinitionParser接口的注解解析器例如ComponentScanBeanDefinitionParser,通过该类中的parse()方法,解析base-package属性配置的扫描路径,进行扫描,进行创建,初始化,注入等

二. 注解方式配置注入

@Configuration 与 @Bean 注解方式注入

创建配置类,该类使用@Configuration修饰,
配置类中创建方法,方法被@Bean注解修饰,(如果@Bean修饰的方法有形参,形参默认在容器中获取并赋值)
方法有返回值,返回的数据就是注入到Spring容器中的数据

  1. 通过注解的方式配置数据库连接
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfiguration {

    private String url="bbb";

    private String name="ccc";

    private String password = "ccc";

    //@Bean注解,修饰方法,将方法返回的对象注入到Spring容器中
    //默认方法名为对象名,有点像xml方式配置是的id="方法名"
    //也可以通过@Bean("指定")来指定
    @Bean
    public DruidDataSource configMethod(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(url);
        druidDataSource.setName(name);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }
}
  1. 获取测试
	public static void main(String[] args) {
       
        ApplicationContext context = new 
        		AnnotationConfigApplicationContext (MyConfiguration.class);
        DruidDataSource dataSource = 
        		(DruidDataSource) context.getBean(DruidDataSource.class);
        System.out.println(dataSource.getUrl());
        System.out.println(dataSource.getPassword());
    }

@ComponentScan 包扫描方式注入

  1. 创建配置类,配置类使用@Configuration修饰(?是否是必须的)
  2. 配置类还需要使用@ComponentScan(value=“com.扫描的包路径”),修饰,指定扫描哪些包中的
  3. 将扫描的包中被@Service,@Controller,@Component,@Repository等修饰的都会被注入到Spring容器中
  4. @ComponentScan注解可以指定扫描的包中包指定哪些includeFilters,排除哪些excludeFilters
@Configuration
/*value 指定扫描那个包
* excludeFilters 指定排除哪些,后面是个{}括号,数组存在的,如果想排除多个,逗号分隔即可
* type 是排除类型,可以根据注解,类,按照正则表达式排除,自定义排除等
* 此处的示例是扫描com.ssm.service包,排除该包下的注解,为Controller类型的
* 如果把excludeFilters 换成 includeFilters 则就是扫描 com.ssm.service 中
* 注解是Controller类型的注入到Spring容器中, 但是还需要添加设置@ComponentScan
* 中的一个 useDefaultFilters = false 默认规则是扫描所有,禁用默认规则,才能生效*/
@ComponentScan(value = "com.ssm.service",
        excludeFilters = {
            @ComponentScan.Filter
                    (type = FilterType.ANNOTATION,
                            classes = {Controller.class})
})
public class MyConfiguration {

}

@Import 注入

  1. 可以使用@Import注解向容器中注入bean
//向容器中注入String,与Persion,多个使用逗号隔开
@Import({String.class , Persion.class})
public class MyConfiguration {

}
  1. 使用@Import注入,也可以向该注解传递一个实现了ImportSelector接口的类,该类重写ImportSelector中的selectImports()方法,将该方法返回的数组中包含的所有组件注入到容器中,数组中包含的是需要注入的全类名,做到批量导入

创建返回批量需要注入的的组件的类,此时就可以向@Import({MyImportSelector .class})注解中传递,在注入时自动调用MyImportSelector 中的selectImports()方法,将方法返回的数组中的类注入到容器中,注意数组可以为0,但是不能为null,会报空指针

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

//实现ImportSelector 接口,重写selectImports()方法
public class MyImportSelector implements ImportSelector {

    /**
     * 该方法的返回值就是要导入到容器中的组件,全类名
     * @param annotationMetadata 当前使用@Import注解修饰的类的所有信息
     * @return
     */
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.ssm.configaution.Persion"};
    }
}
  1. 使用@Import 注入可以向该注解中传递一个实现了 ImportBeanDefinitionRegistrar 接口,重写接口中registerBeanDefinitions()方法的类,该方法可以拿到 BeanDefinitionRegistry,通过这个类对象可以实现手动向容器中注册ben,查询容器中是否存在指定bean, 删除容器中的某个bean等,在容器启动时会自动调用该方法,然后实现bean的手动注册,删除,等…设置

创建 实现ImportBeanDefinitionRegistrar 接口,并重写接口中的抽象方法类

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 MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * @param annotationMetadata  通过这个变量可以拿到使用@Import修饰的的类的所有信息
     * @param beanDefinitionRegistry 通过该变量可以在容器启动时,手动的想容器中注册,删除注册,判断容器中是否存在已注册的某个bean
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {

        //通过beanDefinitionRegistry查询容器中是存在已注册的"persion",也可以传递全类名
        boolean b = beanDefinitionRegistry.containsBeanDefinition("persion");
        //如果已注册,手动向容器中注册一个persion2,注意注册的bean需要使用RootBeanDefinition包裹
        //如果没有注册,手动向容器中注册一个"persion3"
        if(b){
            RootBeanDefinition beanDefinition = new RootBeanDefinition(Persion.class);
            //registerBeanDefinition()向容器中注册bea
            beanDefinitionRegistry.registerBeanDefinition("persion2", beanDefinition);
        }else{
            RootBeanDefinition beanDefinition = new RootBeanDefinition(Persion.class);
            beanDefinitionRegistry.registerBeanDefinition("persion3", beanDefinition);
        }
    }
}

此时在使用@Import注解时就可以向注解中传递MyImportBeanDefinitionRegistrar.class实现对容器中的bean的一些手动操作

//向容器中注入String,与Persion,多个使用逗号隔开
@Import({MyImportBeanDefinitionRegistrar.class})
public class MyConfiguration {

}

FactoryBean 方式注入

创建一个实现了 FactoryBean 接口的类, 泛型T就是想容器中注入的bean的类型,重写接口中的抽象方法,定义向容器中注入的bean的一些信息
在使用@Bean注解修饰方法向容器中注入bean时,设置方法返回的数据是实现了 FactoryBean 接口的类型数据,此时就将实现该接口时重写 getObject() 方法返回的bean注入到了容器中

  1. 创建实现 FactoryBean接口的类,假设需要向容器中注入一个Persion
import org.springframework.beans.factory.FactoryBean;

public class MyFactoryBean implements FactoryBean<Persion> {

    //该方法返回的Bean对象就是我们想要注入到容器中的bean
    @Override
    public Persion getObject() throws Exception {
        return new Persion();
    }

    //设置注入到容器中的bean的类型
    @Override
    public Class<?> getObjectType() {
        return Persion.class;
    }

    //设置注入到容器中的bean是否是单例模式,true 是单例,
    @Override
    public boolean isSingleton() {
        return true;
    }
}

  1. 通过 @Configuration 与 @Bean 使用 MyFactoryBean 向容器中注入bean
@Configuration
public class MyConfiguration {

    @Bean
    public MyFactoryBean configMethod1(){
       return new MyFactoryBean();
    }
}

3.调用测试

public class Test {

    public static void main(String[] args) {

        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);

        //注意点,虽然,在使用@Bean修饰方法,有方法层面看返回注入
        //到容器中的bean是自定义的MyFactoryBean类型,但是实际是通过MyFactoryBean调用
        //里面重写的getObject()方法注入的bean
        Object obj = context.getBean("configMethod1");
        //输出class com.ssm.configaution.Persion
        System.out.println(obj.getClass());

		//如果我们就是想要获取到注入的 MyFactoryBean
        //在通过id获取时在id前面添加"&"
        Object obj2 = context.getBean("&configMethod1");
        System.out.println(obj2.getClass());
    }

}

三. 获取容器中的实例

在Spring中默认注入到容器中的实例都是单例的,通过scopt可以进行修改设置

scopt 单例与多例取值,与创建实例的时间

  • singleton: 默认单例,默认在容器启动时,就会创建实例放入ioc容器的HashMap集合中,以后每次在容器中直接获取
  • prototype: 多例,在获取实例实例时,创建实例对象放入容器中,获取几次创建几次
  • session: 同一个session创建创建一个实例
  • request: 同一次请求创建一个实例
  1. xml配置方式
	<!--singleton: 默认,单例的
	 prototype: 多例
	 session: 同一个session创建创建一个实例
	 request: 同一次请求创建一个实例-->
	<bean id="hh" class="com.ssm.entity.HelpCategory" scope="singleton">
		<property name="name" value="aaaa"/>
		<property name="url" value="bbbb"/>
	</bean>
  1. @Scopt注解设置
@Configuration
public class MyConfiguration {
    
    @Scope(value = "singleton")
    @Bean
    public DruidDataSource configMethod(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl("aaa");
        druidDataSource.setName("bbb");
        druidDataSource.setPassword("ccc");
        return druidDataSource;
    }
}

针对单例

在单例模式时,有懒加载与非懒加载的区别, Spring创建容器,就会创建实例,将实例放入容器中,默认非懒加载,但是有些对象实例可能是重量级的,在程序运行时也不一定会用到,为了节省资源则可以设置为懒加载

@Lazy设置懒加载

@Configuration
public class MyConfiguration {

    private String url="bbb";

    private String name="ccc";

    private String password = "ccc";

   //@Lazy 单例模式,懒加载,容器创建不会创建实例,获取实例时才会
    @Lazy
    @Bean
    public DruidDataSource configMethod(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(url);
        druidDataSource.setName(name);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }
}

四. @Conditional() 根据条件向容器中注入实例

使用 @Conditional()注解, 可以根据条件向ioc中注入实例,对实例进行判断,满足则向容器中添加,不满足则不添加,该注解需要一个,解需要一个 Condition 类型的参数, Condition 是一个接口,需要创建该接口的实现类,重写接口中的matches()抽象方法,在方法中编写条件,该注解可以修饰方法,也可以修饰类

案例
有一个persion对象,当项目运行环境是windows系统时,将这个persion对象注入到Spring容器中

  1. Persion 类
public class Persion {
    private String name;

    public Persion(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
  1. 创建判断是否允许注入到容器中的 Condition 实现类,重写matches()判断方法
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionReader;
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;

public class MyCondition implements Condition {

    /**
     *
     * @param conditionContext 上下文环境
     * @param annotatedTypeMetadata 注释信息
     * @return
     */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {

        //==========查看ConditionContext中可以获取什么数据 =============
        //1.使用上下文环境对象可以获取到当前ioc使用的创建实例,进行装配的bean工厂
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();

        //2.可以获取到类加载器
        ClassLoader loader = conditionContext.getClassLoader();

        //3.可以获取到当前环境信息,运行时的一下信息,虚拟机信息,环境变量等等
       Environment environment = (Environment) conditionContext.getEnvironment();

        //4.可以获取到,Spring运行时的注册类,
        //运行时所有bean通过这个类注册,可以通过这个类注册一个bean
        //移除一个bea,查看bean是否存在等
        BeanDefinitionRegistry reader = (BeanDefinitionRegistry) conditionContext.getRegistry();
        //============查看ConditionContext中可以获取什么数据 =============



        //此处通过第三步拿到的运行上下文环境,去获取数据
        //获取运行项目的系统名称
        String property = environment.getProperty("os.name");

		//判读如果运行环境系统名称包含"Windows",则将使用该类的bean注入到容器中
        if(property.contains("Windows")){
            return true;
        }else {
            return false;
        }
    }
}
  1. 创建向容器中注入 bean 的配置类, 使用@Conditional(),修饰注入bean的方法,注解中传递第二步创建的判断条件类MyCondition ,在向容器中创建被@Conditional()修饰的需要注入的bean时,会自动调用MyCondition中的matches()方法,如果方法返回true,才允许注入,否则不允许
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

//如果使用该注解修饰类,则这个配置类中所有向容器中注册bean的方法都会被监管
//@Conditional({MyCondition.class}) 

@Configuration
public class MyConfiguration {

    private String url="bbb";

    private String name="ccc";

    private String password = "ccc";

    /*@Conditional()设置向容器中注入实例的条件,
    该注解需要一个 Condition 类型的参数, Condition 是一个接口
    创建该接口的实现类,重写接口中的matches()抽象方法*/
    @Conditional({MyCondition.class})
    @Bean("persion")
    public Persion configMethod1(){
       return new Persion("联想电脑");
    }
    
}
  1. 运行测试
 	public static void main(String[] args) {

        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        
        Map<String,Persion> persionMap = context.getBeansOfType(Persion.class);
        for(Map.Entry<String,Persion> entry: persionMap.entrySet()){
            System.out.println(entry.getValue().getName());
        }

    }
  1. 另外,使用@Import 与 FactoryBean的方式好像也可以实现例如条件判断样式的向容器中注入bean

五. 总结

通过了解bean的几种不同注入方式我们了解到

  • Spring 中注入的 bean 默认为单例,的,也可以设置为多例,单例与多例的不同会影响到创建的时间,具体查看上面 scopt 单例与多例取值,与创建实例的时间
  • 针对单例模式,可以设置懒加载与非懒加载(默认)
  • Spring 在启动时底层通过 BeanDefinitionRegistry 实现向容器中注册, 删除等操作容器中的bean
  • 在使用@Controller, @Service等相关类型的注解向 Spring 中进行注入时,并不是使用该注解修饰就可以注入了,而是在其他地方定义了包扫描,被扫描的包中有这些注解修饰的才会注入
  • 通过条件输入了解到 ApplicationContext , ConditionContext 等上下文环境对象,是一个比较强大的类,可以获取到运行环境数据,类加载器,Spring 的 bean 工厂, 对容器中的bean进行管理的 BeanDefinitionRegistry 等相关数据

你可能感兴趣的:(Spring,#,Spring注解驱动开发,spring)