Spring溫故而知新 – bean的裝配

Spring裝配機制

Spring提供了三種主要的裝配機制:

  1. 通過XML進行顯示配置
  2. 通過Java代碼顯示配置
  3. 自動化裝配

自動化裝配

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

自動化裝配自有它的優勢,但是也有它缺陷

  1. 代碼中硬編碼
  2. 無法解決使用第三方組件的問題

所以必要時還是需要進行顯式裝配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實例的作用域,常見作用域有一下幾種:

  1. 單例作用域(Singleton)
  2. 原型作用域(Prototype)
  3. 會話作用域(Session)
  4. 請求作用域(Request)
  5. 全局會話作用域(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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章