Spring装配机制
Spring提供了三种主要的装配机制:
- 通过XML进行显示配置
- 通过Java代码显示配置
- 自动化装配
自动化装配
Spring中IOC容器分两个步骤来完成自动化装配:
- 组件扫描:Spring会自动发现应用上下文中所创建的 bean(通过定义资源的方式,让 Spring IoC 容器扫描对应的包,从而把 bean 装配进来)
- 自动装配:spring自动满足bean之间的依赖(通过注解定义,使得一些依赖关系可以通过注解完成。)
1 使用@Compoent注解申明bean
这两天看出,冒出最多的一个词语“装配” 什么叫装配?谁能装配谁?引用书上的解释:创建应用对象之间写作关系的行为通常称为装配,这也是依赖注入的本质。定义解释了第一个疑问,那么谁装配谁?显然是spring容器装配bean。
使用@Compoent
注解,表明该类作为组件类,需要Spring IOC容器为这个类创建bean。
package com.sl.ioc;
import org.springframework.stereotype.Component;
@Component
public class Dog
{
private Breed breed;
public Breed getBreed() {
return breed;
}
public void setBreed(Breed breed) {
this.breed = breed;
}
}
仅通过@Component
注解Spring容器并不会主动创建bean,因为Spring的组件扫描默认是不启用的,就是说Spring根本还不认识这个类,所以我们需要启用Spring组件扫描,方式有两种:
- 第一种方式:使用
@ComponentSacn
注解发现bean
package com.sl.ioc;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan //启用组件扫描
public class AnimalConfig {
}
其实这个类并没有什么内容,默认情况下@ComponentScan
会告知Spring扫描AnimalConfig所在的jar包下所有含有@Component
修饰的类,并且通过IOC容器创建bean,到这里,可以创建一个测试类看一下结果了
package com.sl.ioc;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestClass {
@Test
public void TestGetDoInstance() {
//应用上下文
ApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
Dog dog = (Dog) context.getBean("dog");
assertNotNull(dog);
}
}
运行代码可以发现对象dog已经实例化了。
上面的代码context.getBean("dog")
可以发现,从应用上下文中获取bean时传的ID是”dog”,这是因为@Component
注解默认将类名的首字母小写后作为bean的ID,当然也支持显示为bean设置ID,比如上面的代码想设置Bean标识为二哈,
这样既可@Component("huskyDog")
使用传入beanname: context.getBean("huskyDog")
;
同样关于@ComponentScan
默认扫描被修饰类所在的包,如果需要扫描其他包也可以,只需要通过value属性传入包名即可@ComponentScan("com.sl.ioc")
Value其实允许传入一个String[]数组,那么扫描多个包@ComponentScan(value= {"com.sl.ioc","com.sl.aop"})
,验证代码这里略过
- 第二种方式:启用组件扫描还有另外一种方式:使用XML配置
<context:component-scan base-package="com.sl.ioc"></context:component-scan>
2 使用@Autowried注解自动装配bean
通过前面的内容,已经可以让Spring自动发现bean并装载到应用上下文中,那么自动装配就是在这个基础上,将bean自动注入到依赖他的地方。@Autowired
注解由Spring提供,可以对成员变量、方法以及构造函数进行注释
package com.sl.ioc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("animalD")
public class Animal {
@Autowired
private Dog dog;
public void AnimalAction() {
dog.Say();
}
}
package com.sl.ioc;
import org.springframework.stereotype.Component;
@Component("huskyDog")
public class Dog //extends Animal
{
private Breed breed;
private String name;
private Color color;
public Breed getBreed() {
return breed;
}
public void setBreed(Breed breed) {
this.breed = breed;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public void Say() {
System.out.println("dog");
}
}
package com.sl.ioc;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.yi.aop.User;
//@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(classes=AnimalConfig.class)
public class TestClass {
@Test
public void TestGetDoInstance() {
//应用上下文
ApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
Animal animal = (Animal) context.getBean("animalD");
animal.AnimalAction();
}
}
当然用在构造函数或者属性Setter方法上也可以:如下
- 通过构造函数注入
package com.sl.ioc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("animalD")
public class Animal {
private Dog dog;
@Autowired
public void Animal(Dog dog) {
this.dog = dog;
}
public void AnimalAction()
{
dog.Say();
}
}
- 通过Setter方法注入
package com.sl.ioc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("animalD")
public class Animal {
private Dog dog;
public void Animal() {
}
public Dog getDog() {
return dog;
}
@Autowired
public void setDog(Dog dog) {
this.dog = dog;
}
public void AnimalAction() {
dog.Say();
}
}
测试代码同上,略 。
使用@Autowired
注解后,Spring尝试从上下文中寻找对应的Bean,并将其注入到依赖它的位置,需要注意的是如果上下文中找不到对应的bean,则会抛异常,可以通过设置required=false来解决,但是使用bean对象的地方需要做null判断
通过Java代码装配bean
自动化装配自有它的优势,但是也有它缺陷
- 代码中硬编码
- 无法解决使用第三方组件的问题
所以必要时还是需要进行显式装配Java代码或者XML
首先定义一个Config类,并且添加@Configuration
注解, 标志这是一个配置类; 其中@Bean
注解标识这个方法返回的bean对象并且需要装载到Spring应用的上下文中,默认情况下这个bean对象的name为该方法名DogInstance, @Bean
注解支持为
Bean对象起别名,通过name属性设置即可
package com.sl.ioc;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AnimalConfig {
@Bean("dog")
public Dog DogInstance() {
return new Dog();
}
@Bean("animal")
public Animal GetAnimal(Dog dog)
{
return new Animal(dog);
}
}
测试代码:
package com.sl.ioc;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.yi.aop.User;
public class TestClass {
@Test
public void TestGetDoInstance() {
//应用上下文
ApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
Dog dog = (Dog)context.getBean("dog");
dog.Say();
Animal animal = (Animal) context.getBean("animal");
animal.AnimalAction();
assertSame(dog, animal.getDog());
}
}
解释一下:GetAnimal(Dog dog)本身依赖于Dog,Spring在调用次方法创建Animal 对象时会自动从上下文中寻找Dog对应的bean对象并注入到Animal构造函数中
通过XML装配Bean
使用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"
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" >
<bean id="cat" class="com.sl.ioc.Cat" ></bean>
</beans>
通过属性注入初始化bean
具体配置文件如下
<?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" >
<!-- 申明一个bean -->
<bean id="cat" class="com.sl.ioc.Cat" >
<property name="name" value="testCat"></property>
<property name="color" value="YELLOW"></property>
<property name="strs">
<list>
<value>str1</value>
<value>str2</value>
</list>
</property>
</bean>
</beans>
使用<property>
元素进行属性注入,其中name为对象属性名,value为需要注入的值,通过在<property>
元素中内<list>
元素进行集合的注入
package com.sl.ioc;
import java.util.List;
public class Cat {
private String name;
private Color color;
private List<String> strs;
public List<String> getStrs() {
return strs;
}
public void setStrs(List<String> strs) {
this.strs = strs;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
测试代码:
@Test
public void TestGetDoInstance() {
//应用上下文
ApplicationContext context = new ClassPathXmlApplicationContext("xmlbean.xml");
Cat cat = (Cat)context.getBean("cat");
//cat.setName("Persian");
System.out.println(cat.getName());
System.out.println(cat.getColor());
for(String s:cat.getStrs())
{
System.out.println(s);
}
}
通过构造函数注入初始化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"
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" >
<bean id="cat" class="com.sl.ioc.Cat" >
<constructor-arg name="name" value="testCat"></constructor-arg>
<constructor-arg name="color" value="YELLOW"></constructor-arg>
</bean>
<bean id="cat2" class="com.sl.ioc.Cat" >
<constructor-arg name="strs">
<list>
<value>str1</value>
<value>str2</value>
</list>
</constructor-arg>
</bean>
</beans>
用<constructor-arg>
元素进行构造函数参数注入<constructor-arg name="xxx" value="xxx"></constructor-arg>
- name:对应构造函数参数名,
- value:需要注入的值,
- list:对应参数类型是集合,具体值通过多个value属性来设置
- 其他
<set> <map>
等类型略
package com.sl.ioc;
import java.util.List;
public class Cat {
//通过构造函数注入
public Cat(String name,Color color){
this.name = name;
this.color = color;
}
//通过构造函数注入List
public Cat(List<String> strs){
this.strs = strs;
}
private String name;
private Color color;
private List<String> strs;
public List<String> getStrs() {
return strs;
}
public void setStrs(List<String> strs) {
this.strs = strs;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
测试代码:
@Test
public void TestGetDoInstance() {
//应用上下文
ApplicationContext context = new ClassPathXmlApplicationContext("xmlbean.xml");
Cat cat = (Cat)context.getBean("cat");
System.out.println(cat.getName());
System.out.println(cat.getColor());
Cat cat2 = (Cat)context.getBean("cat2");
for(String s:cat2.getStrs())
{
System.out.println(s);
}
}
按条件装配bean
就是当满足特定的条件时Spring容器才创建Bean,Spring中通过@Conditional
注解来实现条件化配置bean
package com.sl.ioc;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AnimalConfig {
@Bean("dog")
@Conditional(DogCondition.class)
public Dog DogInstance() {
return new Dog();
}
@Bean("cat")
@Conditional(CatCondition.class)
public Cat CatInstance() {
return new Cat();
}
}
@Conditional
和 Condition接口的实现
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();
}
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 the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
Conditional注解
通过value传入一个类,实现Condition接口,通过实现Condition接口中matches方法决定是否需要装配Bean,如果满足条件需要创建bean则返回true,否则返回false
自己定义两个继承Condition接口的类:通过ConditionContext查找当前环境中是否存在dog或者cat属性,如果存在,则创建对应的bean对象,具体实现如下:
package com.sl.ioc;
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 DogCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
boolean flag= environment.containsProperty("dog");
return flag;
}
}
package com.sl.ioc;
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 CatCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
boolean flag= environment.containsProperty("cat");
return flag;
}
}
package com.sl.ioc;
import org.springframework.stereotype.Component;
@Component
public interface Animal {
void Say();
}
package com.sl.ioc;import org.springframework.stereotype.Component;
@Component
public class Cat implements Animal {
@Override
public void Say() {
System.out.println("I am a cat");
}
}
package com.sl.ioc;
import org.springframework.stereotype.Component;
@Component
public class Dog implements Animal {
@Override
public void Say() {
System.out.println("I am a dog");
}
}
测试代码:
public class TestClass {
@Test
public void TestGetDoInstance() {
System.setProperty("dog","");
ApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
String[] beanNames = context.getBeanDefinitionNames();
for(String bean : beanNames) {
System.out.println(bean);
}
}
}
运行测试可以看到输出的beanname中会包含dog的bean:
自动装配的歧义处理
Spring自动装配时如果存在多个bean能够匹配的话,那么这种情况会阻碍Spring通过属性、构造函数或方法进行装配。针对这种情况,Spring提供了多种 可选方案来解决这个问题,可以选择一个bean作为首选的bean,或者使用限定符来确定唯一bean
1 使用首选Bean
Spring提供@Primary
注解来设置首选Bean,当初选自动装配歧义时,会选择装配带有@Primary
的bean
沿用上面的示例代码,尝试装载animal
@Component
public class AnimalInstance {
@Autowired
public Animal animal;
}
当Spring尝试注入animal实例时,由于Dog和Cat都继承自Animal,所以此处产生了歧义,下面通过使用@Primary
指定首选bean
@Component
@Primary //指定首选bean
public class Cat implements Animal {
@Override
public void Say() {
System.out.println("I am a cat");
}
}
同样也可以使用XML配置来实现:<bean>
元素提供了primary属性来设置首选bean
<bean id="cat" class="com.sl.ioc.Cat" primary ="true" >
测试代码:
public class TestClass {
@Test
public void TestGetDoInstance() {
//应用上下文
ApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
AnimalInstance animalInstance = context.getBean(AnimalInstance.class);
animalInstance.animal.Say();
}
}
运行结果:I am a cat
首选项只是标识一个优先选择装载的bean,如果配置了多个@Primary
,那么将带来新的歧义,Spring依然无法完成自动装配,可以通过下面限定符
来解决这个问题
2 使用限定符
Spring提供@Qualifier
注解来指定想要注入的具体bean。例如上面的示例,如果指定注入dog:
package com.sl.ioc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class AnimalInstance {
@Autowired
@Qualifier("dog")
public Animal animal;
}
解释一下:@Qualifier("dog")
表示指定的bean具有”dog”限定符,spring中bean如果没有指定限定符,会使用默认限定符,即使用beanID作为限定符。所以上面是恰好使用了dog bean的ID作为了限定符。也可以写成如下方式:
@Component
@Qualifier("specialdog") //为bean指定限定符
public class Dog implements Animal
{
@Override
public void Say() {
System.out.println("I am a dog");
}
}
@Component
public class AnimalInstance {
@Autowired
@Qualifier("specialdog") //使用上面定义的限定符
public Animal animal;
}
Bean的作用域
Spring容器在创建bean实例的同时,还允许指定bean实例的作用域,常见作用域有一下几种:
- 单例作用域(Singleton)
- 原型作用域(Prototype)
- 会话作用域(Session)
- 请求作用域(Request)
- 全局会话作用域(globalSession)
Singleton作用域
在整个应用中,Spring IOC容器为使用singleton模式的bean只创建一个实例,Spring将会缓存Bean实例,任何对该类型bean的请求都会返回该实例。单例也是Spring默认的作用域。具体使用如下,通过XML配置
<bean id="beanid" class="com.sl.ioc.xxx" scope="singleton" ></bean>
<bean>
元素提供了scope属性来设置singleton作用域
对应的注解:
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
Prototype原型作用域
每次注入或者从Spring容器中获取时都创建一个新的bean实例:
<bean id="beanid" class="com.sl.ioc.xxx" scope="prototype" ></bean>
<bean>
元素提供了scope属性来设置singleton作用域
对应的注解:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Session会话作用域
在web应用中,针对每个会话,Spring容器根据bean定义创建的bean实例,只在当前会话Session中有效,XML配置如下:
<bean id="beanid" class="com.sl.ioc.xxx" scope="session" ></bean>
针对某个HTTP Session,Spring容器会根据bean定义创建一个新的bean实例,该bean仅在当前HTTP Session内有效。所以可以根据需要放心的更改bean实例的内部状态,而不影响其他Http Session中bean实例。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被销毁掉。
Request 请求作用域
在web应用中,针对每次请求,Spring容器根据bean定义创建新的bean实例,只在当前请求内有效
<bean id="beanid" class="com.sl.ioc.xxx" scope="request" ></bean>
该bean实例只在当前请求内有效,在请求处理完成之后bean也会被销毁掉
globalSession全局会话作用域
类似于session作用域,只是其用于portlet环境的web应用。如果在非portlet环境将视为session作用域。
<bean id="beanid" class="com.sl.ioc.xxx" scope="globalSession" ></bean>