Spring中 注入Bean的7中方式

最近在学习Spring中的容器相关的知识和源码,特此记录下来。

准备工作:创建一个maven项目

next

finish. ok项目创建完成。

导入Spring的maven依赖:

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

新建bean类Person,代码如下

package com.herman.bean;

public class Person {
	
	private String name;
	private Integer age;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	public Person(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}
	public Person() {
		super();
	}

}

好了,准备工作到此就ok了。

Spring中加入Bean的7中方式如下:

第一种:xml中配置

很古老的一种方式,刚开始学习spring的时候应该就是这种吧。

首先,在类路径下新建beans.xml,结构如下:

beans.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 id="person" class="com.herman.bean.Person">
  		<property name="name" value="herman"></property>
  		<property name="age" value="18"></property>
  	</bean>
</beans>

测试类如下:

package com.herman;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.herman.bean.Person;

public class MainTest {
	public static void main(String[] args) {
		/**
		 * ClassPathXmlApplicationContext
		 * 顾名思义:ClassPath下的一个Xml,传入我们的beans.xml即可。
		 */
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
		Person person = (Person) applicationContext.getBean("person");
		System.out.println(person);
	}
}

输出结果:

第二种:利用@Bean注解

新建一个MainConfig,如下:

package com.herman.config;

import java.util.Arrays;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.herman.bean.Person;

/**
 * @author Herman
 *
 *	添加了@Configuration注解的意思就是告诉Spring这个类是一个配置类
 *	配置类的方式等同于配置文件(配置类==配置文件)
 */
@Configuration
public class MainConfig {

	/**
	 * 这里可以对比一下配置文件的写法
	 * 
	 * 		<bean id="person" class="com.herman.bean.Person">
  	 *			<property name="name" value="herman"></property>
  	 *			<property name="age" value="18"></property>
  	 *		</bean>
	 * 
	 */
	/**
	 * @Bean就是给容器中注册一个Bean;类型为方法的返回类型(Person),id默认是方法名person
	 * 
	 * 此处@Bean里面可以指定返回的id名 
	 * 例如  @Bean("herman001") 那么 加入到ioc中的这个Person类型的Bean id叫做 herman001
	 * 所以	String[] names = applicationContext.getBeanNamesForType(Person.class);
	 *		System.out.println(Arrays.asList(names));
	 *		输出结果时候 [herman001]
	 * 也可以修改方法名	public Person person002() { 效果是一样的。
	 * 
	 * 但是如果同时添加了@Bean("herman001") 和 public Person person002() ,
	 * 默认是herman001,也就是@Bean 占主导地位
	 */
	@Bean("herman001")
	public Person person002() {
		return new Person("张三",20);
	}
}

然后测试类继续 如下:

package com.herman;

import java.util.Arrays;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.herman.bean.Person;
import com.herman.config.MainConfig;

public class MainTest {
	public static void main(String[] args) {
		/**
		 * ClassPathXmlApplicationContext
		 * 顾名思义:ClassPath下的一个Xml,传入我们的beans.xml即可。
		 */
//		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//		Person person = (Person) applicationContext.getBean("person");
//		System.out.println(person);
		
		/**
		 * 来来来,再看  AnnotationConfigApplicationContext
		 * Annotation中文意思是 注解 
		 * 顾名思义:通过注解的方式获取ioc容器 (applicationContext 可以看做就是ioc容器)
		 */
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
		Person bean = applicationContext.getBean(Person.class);
		System.out.println(bean);
		String[] names = applicationContext.getBeanNamesForType(Person.class);
		System.out.println(Arrays.asList(names));
	}
}

测试结果:输出 

Person [name=张三, age=20]
[herman001]
-----------------------------------------------------------------------------------------------------------

其实applicationContext里面可以获取到很多东西,挺好玩的,可以自行尝试

比如可以获取到ioc容器中所有的beanName,还有一些环境相关的东西。

第三种:包扫描方式(常用)

以前的开发中用到的是这种,在beans.xml中添加包扫描

首先,beans中加入context的名称空间,然后加入<context:component-scan>标签,如下:

<?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-4.2.xsd">
  
   <!--  只要标注了@Controller @Service @Repository @Componet 注解的类都可以被扫描到Spring容器中-->
  	<context:component-scan base-package="com.herman"></context:component-scan>
  	<bean id="person" class="com.herman.bean.Person">
  		<property name="name" value="herman"></property>
  		<property name="age" value="18"></property>
  	</bean>
</beans>

此处添加了 xmlns:context="http://www.springframework.org/schema/context"等名称空间,利用<<context:component-scan>标签扫描了 com.herman包。

然后方便测试,在maven的pom中加入junit的依赖,  pom现在更新为 如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.herman</groupId>
  <artifactId>spring-annotation</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
  		<!--  springframework  -->
  		<dependency>
  			<groupId>org.springframework</groupId>
  			<artifactId>spring-context</artifactId>
  			<version>4.3.12.RELEASE</version>
  		</dependency>
  		<!-- junit  -->
  		<dependency>
  			<groupId>junit</groupId>
  			<artifactId>junit</artifactId>
  			<version>4.12</version>
  			<scope>test</scope>
  		</dependency>
  </dependencies>
</project>

然后 新建了 controller类,service类,dao类,在测试包下新建 IOCTest 测试类。目前项目结构更新为如下:

IOCTest类代码如下:

package com.herman.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class IOCTest {

	@Test
	public void test01() {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
		String[] names = applicationContext.getBeanDefinitionNames();
		for (String name : names) {
			System.out.println(name);
		}
		
	}
}

来看看执行结果:

mainConfig为什么会被扫描进去呢?因为mainConfig中加入了注解@Configuration,点开源码看看:

发现@Configuration里面有一个@Component注解,所以mainConfig就被加入到 ioc中了。

好了 我们在此不是要探讨配置文件的包扫描,我们来看看注解的包扫描。

我们给 MainConfig加入 @ComponentScan注解,如下 

这里的@ComponentScan等同于配置文件中的<context:component-scan>扫描标签.

修改IOCTest类中加载ioc的方式 从ClassPathXmlApplicationContext换成 AnnotationConfigApplicationContext

package com.herman.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.herman.config.MainConfig;

public class IOCTest {

	@Test
	public void test01() {
//		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
		String[] names = applicationContext.getBeanDefinitionNames();
		for (String name : names) {
			System.out.println(name);
		}
		
	}
}

看看测试结果:

发现结果和配置文件一样。

仔细看看@ComponentScan,点进去发现有个 @Repeatable 注解,也就是说 @ComponentScan可以添加多次。

还有includeFilters和 excludeFilters,包含哪些,排除那些都是可以设置的,还可以自定义规则设置,可以自行研究,这里就不探讨了。

其实还有一个类似的@ComponentScans,源码如下:

发现@ComponentScans里面有一个@ComponentScan的数组。可以用@ComponentScans代替@ComponentScan。

修改MainConfig的扫描规则如下 也是可以的.

好了,第三种就说到这里。

第四种 利用@Conditional注解(Spring boot底层使用的很多呦)

新建配置类MainConfig02 如下:

package com.herman.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.herman.bean.Person;

/**
 * @author Herman
 */
@Configuration
public class MainConfig02 {

	@Bean("bill")
	public Person person01() {
		return new Person("Bill Gates",60);
	}
	@Bean("linux")
	public Person person02() {
		return new Person("linus",50);
	}
}

添加测试方法 如下:

@Test
	public void test02() {
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig02.class);
		String[] names = applicationContext.getBeanNamesForType(Person.class);
		System.out.println(Arrays.asList(names));
		Map<String, Person> persons = applicationContext.getBeansOfType(Person.class);
		System.out.println(persons);
	}

来看看打印结果,看看ioc容器中现在有几个人,很显然是两个人,bill和linux。

好了 准备工作ok了 现在开始看@Conditional注解的作用。

翻译过来就是 条件,源码如下

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

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

}

可以看出来这个注解 可以加在类上,也可以加在 方法上 (@Target({ElementType.TYPE, ElementType.METHOD}))

再看 这个类必须是Condition类的子类(实现类)[  Class<? extends Condition>[] value();  ]

点开 Condition,如下:

public interface Condition {

	/**
	 * Determine if the condition matches.
	 * @param context the condition context
	 * @param metadata 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 registration.
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

发现是个接口,需要实现matches方法即可,返回一个boolean 类型的参数。我们分别创建两个类来实现Condition接口。

目的 : 如果当前环境是 Windows 给系统中添加 person01 即 bill,如果当前环境是 Linux 给系统中添加 person02  即 linux。

WindowsCondition和LinuxCondition 代码如下:

package com.herman.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * 
 * @author Herman
 * 
 * Windows 环境的 Condition实现类
 *
 */
public class WindowsCondition implements Condition{

	/**
	 * ConditionContext 判断条件能使用的上下文环境
	 * AnnotatedTypeMetadata:注释信息
	 * 
	 */
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		//当前环境
		Environment environment = context.getEnvironment();
		//获取操作系统
		String property = environment.getProperty("os.name");
		if(property.contains("Windows")) {
			return true;
		}
		return false;
	}

}
package com.herman.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * 
 * @author Herman
 * 
 * Linux 环境的 Condition实现类
 *
 */
public class LinuxCondition implements Condition {
	/**
	 * ConditionContext 判断条件能使用的上下文环境
	 * AnnotatedTypeMetadata:注释信息
	 * 
	 */
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		// 当前环境
		Environment environment = context.getEnvironment();
		// 获取操作系统
		String property = environment.getProperty("os.name");
		if (property.contains("linux")) {
			return true;
		}
		return false;
	}

}

补充,其实context里面可以get出来很多好玩,有用的东西,可以自行尝试哦。(context.getRegistry() 这个挺实用。)

改造我们的配置类MainConfig02如下:

package com.herman.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

import com.herman.bean.Person;
import com.herman.condition.LinuxCondition;
import com.herman.condition.WindowsCondition;

/**
 * @author Herman
 */
@Configuration
public class MainConfig02 {

	/**
	 * 
	 * 目的: 如果当前环境是 Windows 给系统中添加 person01 即 bill
	 * 	     如果当前环境是 Linux 给系统中添加 person02  即 linux
	 * 
	 * 
	 */
	@Conditional(WindowsCondition.class)
	@Bean("bill")
	public Person person01() {
		return new Person("Bill Gates",60);
	}
	
	@Conditional(LinuxCondition.class)
	@Bean("linux")
	public Person person02() {
		return new Person("linus",50);
	}
}

ok,运行测试方法 test02,结果来看看:

可以看出来当操作系统是 windows的时候,ioc中只有bill这个person了。

更换操作系统试试?
选中方法test02,右键 Run As 选择 Run Configurations,在Arguments中输入   -Dos.name=linux  如下

点击右下角 Run,结果如下:

这是@Conditional加在方法上,也可以加在类上,当满足条件的时候,这个类中配置的所有的Bean才能生效。

第五种 @Import

新建类Red和Color

package com.herman.bean;

public class Color {

}
package com.herman.bean;

public class Red {

}

修改MainConfig02中的注解,加一个@Import

如下:

@Import的value中可以写多个类,原因看一下源码就知道;

写测试方法运行一下看看情况:

@Test
	public void testImport() {
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig02.class);
		printBeanNames(applicationContext);
	}
	/**
	 * 打印 ioc中的beans
	 * @param applicationContext
	 */
	public void printBeanNames(ApplicationContext applicationContext) {
		String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
		for (String name : beanDefinitionNames) {
			System.out.println(name);
		}
	}

输出结果是:

第五种贼简单吧。嘻嘻

第六种 @ImportSelector和@ImportBeanDefinitionRegistrar

来看看 @ImportSelector的源码:

是一个接口,需要实现selectImports方法。可以导入String的数组。我们新建两个类Blue和Yellow

package com.herman.bean;

public class Yellow {

}


package com.herman.bean;

public class Blue {

}

新建类MyImportSelector implements ImportSelector

package com.herman.bean;

import org.springframework.context.annotation.ImportSelector;

import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Component;
@Component
public class MyImportSelector implements ImportSelector {

	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		return new String[] {"com.herman.bean.Blue","com.herman.bean.Yellow"};
	}

}

修改 配置类MainConfig02,修改原@Import的值为@Import(value= {Red.class, Color.class, MyImportSelector.class})

继续执行测试方法testImport,输出结果:

接下来看@ImportBeanDefinitionRegistrar

源码如下:

也是一个接口,需要实现registerBeanDefinitions方法。

新建类MyImportBeanDefinitionRegistrar.如下:

(我们这里搞个需求,假如容器中有Red和Blue类,就给容器中注册一个RainRow类。)

package com.herman.bean;

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;
import org.springframework.stereotype.Component;

@Component
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		/**
		 *  	这里搞个需求,假如容器中有Red和Blue类,就给容器中注册一个RainRow类。
		 *  	目的:熟悉BeanDefinitionRegistry里面的方法,可以判断有没有某个类,
		 *  	或者往ioc里面加入某个类,还可以移除某个类,挺好玩的
		 */
		boolean blue = registry.containsBeanDefinition("com.herman.bean.Blue");
		boolean red = registry.containsBeanDefinition("com.herman.bean.Red");
		if(blue && red) {
			//指定bean的定义信息
			RootBeanDefinition beanDefinition = new RootBeanDefinition(RainRow.class);
			//注册一个bean
			registry.registerBeanDefinition("rainRow", beanDefinition);
		}
	}

}

新建RainRow类。

package com.herman.bean;

public class RainRow {

}

修改配置类MainConfig02的@Import为

@Import(value= {Red.class, Color.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})

执行测试方法testImport(),执行结果如下:

第七种:使用Spring提供的FactorBean(工厂Bean)

可以看一下源码:


package org.springframework.beans.factory;

public interface FactoryBean<T> {

	
	T getObject() throws Exception;

	
	Class<?> getObjectType();

	
	boolean isSingleton();

}

也是一个接口,三个方法:

getObject() 返回一个T类型的对象,放在容器中。

getObjectType() 返回对象的类型

isSingleton() 是否单例。

新建类ColorFactoryBean implements FactoryBean<Color>

package com.herman.bean;

import org.springframework.beans.factory.FactoryBean;

public class ColorFactoryBean implements FactoryBean<Color> {
	//返回一个Color对象,添加在容器中
	public Color getObject() throws Exception {
		return new Color();
	}
	public Class<?> getObjectType() {
		return Color.class;
	}
	//true 代表 bean是单实例 。反之 多实例。
	public boolean isSingleton() {
		return false;
	}

}

修改配置类MainConfig02,把ColorFactoryBean加入到ioc中:

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

执行测试方法testImport(),结果如下:

虽然看着是注册了colorFactoryBean,但是实际上是个Color对象,因为调用的是getObject()方法。

那么我就要获取一个colorFactoryBean对象,要怎么做?,继续往下看

只需要给

Object bean = applicationContext.getBean("&colorFactoryBean");

bean的id前面 添加一个 & 符号即可。

继续测试结果如下:

为何不直接使用@Bean,而使用FactoryBean呢?
Spring 中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean 即 FactoryBean。FactoryBean跟普通Bean不同,其返回的对象不是指定类的一个实例,而是该FactoryBean的getObject方法所返回的对象。创建出来的对象是否属于单例由isSingleton中的返回决定。

官方解释:
FactoryBean 通常是用来创建比较复杂的bean,一般的bean 直接用xml配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,用xml配置比较困难,这时可以考虑用FactoryBean。

这里套用一下别人的解释:

简单的说:它是用来处理复杂的Bean,在初始化过程中需要做很多事情(比如MyBatis的SqlSessionFactoryBean等等),从而屏蔽内部实现,对调用者/使用者友好的一种解决方案。
如果这种Bean用xml去配置,几乎是不可能的。当用注解驱动@Bean去做后,虽然也是可以的,但是很显然对调用很不友好的(因为我们使用MyBatis可不想去知道它初始化到底要做些啥事之类的)

至此,完结,知识摘自 尚硅谷课堂,感谢。

猜你喜欢

转载自blog.csdn.net/cyberHerman/article/details/100171388
今日推荐