天天看點

Spring實戰-讀書筆記(章節三)-進階裝配

Spring profile

Spring profile是在Spring3.1引入的功能。它的作用是根據環境建立不同的Bean,是條件化建立Bean的一種方式。我們的項目可能在開發環境、測試環境和生産環境上部署,三種環境需要使用不同的DataSource,假設對于三種環境的不同DataSource有三個Bean,我們需要根據環境來切換到合适的Bean。這樣一個部署檔案(如war檔案),修個一個配置值在運作時完成對Bean的選擇。Spring profile類似于MyBatis中根據不同的資料庫廠商使用不同的SQL語句通路資料庫功能。其實仔細一想以前我們都是通過标示加載不同的類,每個程式員都是自己的實作,現在Spring幫你做了,你隻需要學會怎麼用即可,越來越Spring了。

注意:如果bean沒有沒有profile的配置範圍内,此bean總是會被Spring識别。

在javaConfig以及自動裝配方式上使用profile功能

涉及注解

@Profile:标記在類(通常和@Configuratin、@Conponent一同使用)或方法(通常同@Bean一同使用)上表示建立的Bean屬于那個環境,@Profile("dev")其中dev表示環境名。

@ActiveProfiles:标記在類上表示激活那個環境,@ActiveProfiles("dev")其中dev表示環境名,通常和@ContextConfiguration一起使用,用于測試環境。

事例代碼

package com.zhangxy.chapter_3.profiles.main;


import javax.sql.DataSource;

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

@Configuration
public class DataSourceConfig {

	@Bean(name="dataSource")
	@Profile("dev")//指定Bean所屬的環境
	public DataSource getDataSource() {
		System.out.println("dev dataSource");
		return null;
	}
	
	@Bean(name="dataSource")
	@Profile("pro")
	public DataSource getJNDISource() {
		System.out.println("pro dataSource");
		return null;
	}
        //沒有使用@Pprofile指定屬于是那個環境,無論激活那種環境此Bean會一直被建立
	@Bean(name="c3p0DataSource")
	public DataSource getC3P0DataSource() {
		System.out.println("C3P0DataSource");
		return null;
	}
}
           

~~~測試類~~~

package com.zhangxy.chapter_3.profiles.test;

import javax.sql.DataSource;

import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.zhangxy.chapter_3.profiles.main.DataSourceConfig;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=DataSourceConfig.class)
@ActiveProfiles("pro")//激活那個環境下的Bean
public class DataSourceTest {
	public final StandardOutputStreamLog log = new StandardOutputStreamLog();

	@Autowired
	private DataSource dataSource;
	@Autowired
	private DataSource c3p0DataSource;
	
	@Test
	public void test() {
/*		輸出結果:
		C3P0DataSource
		pro dataSource*/
	}
}
           

在XML配置上使用profile功能

在XML使用profile功能配置多個<beans>元素,通過profile屬性指定環境名,<beans>元素下的所有bean都會歸屬這個環境。詳見截圖:

Spring實戰-讀書筆記(章節三)-進階裝配

激活profile

Spring在确定哪個profile處于激活狀态時,需要依賴兩個獨立的屬性:spring.profiles.active和spring.profiles.default。如

果設定了spring.profiles.active屬性的話,那麼它的值就會用來确定哪個profile是激活的。但如果沒有設

置spring.profiles.active屬性的話,那Spring将會查找spring.profiles.default的值。如

果spring.profiles.active和spring.profiles.default均沒有設定的話,那就沒有激活的profile,是以隻會建立那些沒有定義在

profile中的bean。

有多種方式來設定這兩個屬性:

作為DispatcherServlet的初始化參數;

作為Web應用的上下文參數;

作為JNDI條目;

作為環境變量;

作為JVM的系統屬性;

條件化的Bean

條件化的Bean和profile是很類似的,是在Spring4.0引入的功能。我們可能會根據某個Bean是否存在來決定是否建立另一個Bean,根據配置檔案的值來決定是否建立一個Bean。還是拿不同部署環境使用不同DataSource的應用場景,看一下條件化的Bean如何實作。

涉及注解:

@Conditional:同@Bean或@Component一同使用,根據條件結果(true or fasle)決定是否加載Bean,參數是一個實作了Condition接口類的Class類型。

事例代碼

事例代碼實作了:方法上标記有@UseDataSource注解就會建立此Bean,否則不建立Bean

package com.zhangxy.chapter_3.conditional.main;

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;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface UseDataSource {

}
           
package com.zhangxy.chapter_3.conditional.main;

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

public class UserDataSourceCondition implements Condition {

	@Override
	public boolean matches(ConditionContext arg0, AnnotatedTypeMetadata arg1) {
		return arg1.isAnnotated(UseDataSource.class.getName());
		//return true;
	}

}
           
package com.zhangxy.chapter_3.conditional.main;

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;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface UseDataSource {

}
           

~~~測試類~~~

package com.zhangxy.chapter_3.conditional.test;

import static org.junit.Assert.assertEquals;

import javax.sql.DataSource;

import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.zhangxy.chapter_3.conditional.main.DataSourceConfig;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=DataSourceConfig.class)
public class DataSourceTest {
	public final StandardOutputStreamLog log = new StandardOutputStreamLog();

	@Autowired
	private DataSource dataSource;
	@Autowired
	private DataSource c3p0DataSource;
	
	@Test
	public void test() {
/*		輸出結果:
		C3P0DataSource
		dev dataSource*/
	}
}
           

處理自動裝配的歧義性

在使用自動裝配時如果依賴的類型找到了多個實作Bean,那Spring将會無法做出選擇,進而抛出異常。我們可以通過兩個注解(@Primary、@qualifier)來解決這種歧義性。

@Primary注解

@Primary注解

标記在使用了@Component注解的類上或使用了@Bean注解的方法上,表示此Bean為首先Bean,當Spring找到多個實作Bean時,會将标記有@Primary注解的Bean注入到依賴類型。注意:如果有多個Bean标記了@Primary注解相當于沒有首選Bean。這個注解在一個特定情況下還是很有用的,比如說:我們注入的類型是AClass,一開始我們隻有一個AClassImpl_1實作,我們使用自動裝配,為AClass類型注入AClassImpl_1 Bean,随着然後得擴充我們有增加了AClassImpl_2和AClassImpl_3,這樣就有了多個實作Bean,我們在之前的代碼上注入AClassImpl_1就需要顯示的指定(要打開每個類去改了),這個時候我們可以把AClassImpl_1 Bean 标記成@Primary。

通過XML配置也可以指定一個bean為首選bean,例如:

<bean primary="true"/>

@qualifier注解

标記在使用了@Component注解的類上或使用了@Bean注解的方法上,表示為bean指定一個限定符。

标記在使用@AutoWired的字段、方法或構造函數上,表示注入bean的限定符。

注意(很重要):Spring會将bean id作為Bean的預設限定符。

什麼是限定符?

我麼可以把限定符了解成給Bean打上的标簽或Bean的某些特點,我們用一個字元來表示這些标簽或特點。也可以把限定符了解成Bean的名稱(類似于bean元素的name屬性)。通過這些标簽或特點我們可以确定唯一一個Bean。就像一個人有叫做馬雲,大衆叫他阿裡CEO,他兒子叫他爸爸,他媽叫他小馬,但是無論他有多少個别名和稱号,他的身份證ID是唯一的(bean id),如果有人找馬雲,可能會這麼說:我找阿裡CEO馬雲(這句化符合了兩個标簽或特點)、我找馬雲他是我爸爸、當然在計算機中你也可以根據他的身份證ID去搜尋馬雲的個人資訊。

事例代碼

package com.zhangxy.chapter_3.qualifier.main;

public interface Dessert {
	public String getName();
}
           
package com.zhangxy.chapter_3.qualifier.main;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component("id-cake")
@Qualifier("q-cake")
public class Cake implements Dessert {

	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return "Cake";
	}

}
           
package com.zhangxy.chapter_3.qualifier.main;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component("id-cookie")
@Qualifier("q-cookie")
public class Cookie implements Dessert {

	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return "Cookie";
	}

}
           
package com.zhangxy.chapter_3.qualifier.main;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component("id-iceCream")
@Qualifier("q-iceCream")
public class IceCream implements Dessert {

	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return "IceCream";
	}

}
           
package com.zhangxy.chapter_3.qualifier.main;

public class Popsicle implements Dessert {

	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return "Popsicle";
	}
}
           
<?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:p="http://www.springframework.org/schema/p"
  xmlns:util="http://www.springframework.org/schema/util"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/util 
    http://www.springframework.org/schema/util/spring-util.xsd">
	<!-- 指定兩個名,名稱直接用空格分隔 -->
    <bean id="id-popsicle" name="q-Popsicle-1 q-Popsicle-2" class="com.zhangxy.chapter_3.qualifier.main.Popsicle"></bean>
    
</beans>
           
package com.zhangxy.chapter_3.qualifier.main;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.core.env.Environment;

@Configuration
@ComponentScan(/*basePackages= {"com.zhangxy.chapter_3.qualifier"}*/)
@ImportResource("\\com\\zhangxy\\chapter_3\\qualifier\\main\\config.xml")
public class SystemConfig {

}
           

~~~測試類~~~

package com.zhangxy.chapter_3.qualifier.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.env.Environment;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.zhangxy.chapter_3.qualifier.main.Dessert;
import com.zhangxy.chapter_3.qualifier.main.SystemConfig;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=SystemConfig.class)
public class QualifierTest {
	
	@Autowired
	//@Qualifier("id-cake")//根據bean id限定符注入
	//@Qualifier("q-iceCream")//根據自定義限定符注入
	@Qualifier("q-Popsicle-1")//根據XML中bean的name屬性注入
	private Dessert dessert;
	Environment env;

	@Test
	public void test() {
		System.out.println("dessert->"+dessert.getName());
	}
	
}
           

bean的作用域

Spring定義了多種作用域,可以基于這些作用域建立bean,包括:

單例(Singleton):在整個應用中,隻建立bean的一個執行個體。

原型(Prototype):每次注入或者通過Spring應用上下文擷取的時候,都會建立一個新的bean執行個體。

會話(Session):在Web應用中,為每個會話建立一個bean執行個體。

請求(Rquest):在Web應用中,為每個請求建立一個bean執行個體。

運作時值注入

我們有時會需要從property檔案中讀取值注入到Bean中,我看看Spring是如何實作此功能的。

使用javaConfig配置實作

@PropertySource:标記在使用@Configuration的方法上,表示導入的properties檔案。 一下事例都需要用到的properties檔案。屬性值是中文,為不出現亂碼使用Unicode對中文進行編碼。

disc.title=\u4e00\u4e2a\u4eba\u7684\u884c\u674e
disc.artist=\u6234\u4f69\u59ae
disc.track=MP4,MP3
           

事例代碼

package com.zhangxy.chapter_3.into_Property_bean.javaConfig.main;

public interface CompactDisc {
  void play();
}
           
package com.zhangxy.chapter_3.into_Property_bean.javaConfig.main;

import java.util.List;

public class BlankDisc implements CompactDisc {

  private String title;
  private String artist;
  private List<String> tracks;

  public BlankDisc(String title, String artist, List<String> tracks) {
    this.title = title;
    this.artist = artist;
    this.tracks = tracks;
  }

  public void play() {
    System.out.println("Playing " + title + " by " + artist);
    for (String track : tracks) {
      System.out.println("-Track: " + track);
    }
  }

}
           
package com.zhangxy.chapter_3.into_Property_bean.javaConfig.main;

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

@Configuration
@PropertySource(value="classpath:com\\zhangxy\\chapter_3\\into_Property_bean\\config.properties")
public class SystemConfig {

	@Autowired
	Environment env;

	@Bean
	public CompactDisc blankDisc() {
		String[] arrays = env.getProperty("disc.track").split(",");
		return new BlankDisc(env.getProperty("disc.title"), env.getProperty("disc.artist"), Arrays.asList(arrays));
	}
}
           

~~~測試類~~~

package com.zhangxy.chapter_3.into_Property_bean.javaConfig.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.zhangxy.chapter_3.into_Property_bean.javaConfig.main.CompactDisc;
import com.zhangxy.chapter_3.into_Property_bean.javaConfig.main.SystemConfig;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SystemConfig.class)
public class JavaConfigIntoTest {

	@Autowired
	private CompactDisc compactDisc;
	
	@Test
	public void test() {
		compactDisc.play();
	}
}
           

使用XML配置實作

使用bean标簽建立 org.springframework.beans.factory.config.PropertyPlaceholderConfigurer 類型bean,傳入properties檔案即可。

事例代碼

package com.zhangxy.chapter_3.into_Property_bean.xmlConfig.main;

public interface CompactDisc {
  void play();
}
           
package com.zhangxy.chapter_3.into_Property_bean.xmlConfig.main;

import java.util.Arrays;
import java.util.List;

public class BlankDisc implements CompactDisc {

  private String title;
  private String artist;
  private List<String> tracks;

  public BlankDisc(String title, String artist, String tracks) {
    this.title = title;
    this.artist = artist;
    this.tracks = Arrays.asList(tracks.split(","));
  }

  public void play() {
    System.out.println("Playing " + title + " by " + artist);
    for (String track : tracks) {
      System.out.println("-Track: " + track);
    }
  }

}
           
<?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:p="http://www.springframework.org/schema/p"
  xmlns:util="http://www.springframework.org/schema/util"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/util 
    http://www.springframework.org/schema/util/spring-util.xsd">
    
	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>com\zhangxy\chapter_3\into_Property_bean\config.properties</value>
			</list>
		</property>
	</bean>
	
	<bean id="blankDisc"
		class="com.zhangxy.chapter_3.into_Property_bean.xmlConfig.main.BlankDisc">
		<constructor-arg value="${disc.title}" />
		<constructor-arg value="${disc.artist}"></constructor-arg>
		<constructor-arg value="${disc.track}"></constructor-arg>
	</bean>

</beans>
           

~~~測試類~~

package com.zhangxy.chapter_3.into_Property_bean.xmlConfig.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.zhangxy.chapter_3.into_Property_bean.xmlConfig.main.CompactDisc;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:com\\zhangxy\\chapter_3\\into_Property_bean\\xmlConfig\\main\\blankDisc-config.xml")
public class XmlConfigIntoTest {

	@Autowired
	private CompactDisc compactDisc;
	
	@Test
	public void test() {
		compactDisc.play();
	}
}
           

自動裝配實作

@Value:需要注入屬性值,用${}表達式的形式擷取值,例如:@Valuer("jdbc.username")

事例代碼

package com.zhangxy.chapter_3.into_Property_bean.autowired.main;

public interface CompactDisc {
  void play();
}
           
package com.zhangxy.chapter_3.into_Property_bean.autowired.main;

import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class BlankDisc implements CompactDisc {

	@Value("${disc.title}")
  private String title;
	@Value("${disc.artist}")
  private String artist;
	@Value("${disc.track}")
  private String trackStr;
	
 private List<String> tracks;
	
  public BlankDisc() {

  }
  
/*  public BlankDisc(@Value("${disc.title}")String title, @Value("${disc.artist}")String artist, @Value("${disc.track}")String trackStr) {
    this.title = title;
    this.artist = artist;
    this.trackStr = trackStr;
  }*/

  public void play() {
	  this.tracks = Arrays.asList(this.trackStr.split(","));
    System.out.println("Playing " + title + " by " + artist);
    for (String track : tracks) {
      System.out.println("-Track: " + track);
    }
  }

}
           
package com.zhangxy.chapter_3.into_Property_bean.autowired.main;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

@Configuration
@ComponentScan
@PropertySource(value="classpath:com\\zhangxy\\chapter_3\\into_Property_bean\\config.properties",name="UTF-8")
public class SystemConfig {
	//###此處必須建立 placeholderConfigurer bean,否則${}表達式無法解析,當作字元串"${}"注入
	//###或在XML配置檔案中添加 <context:property-placeholder/> 配置
	@Bean
	public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
		return new PropertySourcesPlaceholderConfigurer();
	}
}
           

~~~測試類~~~

package com.zhangxy.chapter_3.into_Property_bean.autowired.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.PropertySource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.zhangxy.chapter_3.into_Property_bean.autowired.main.CompactDisc;
import com.zhangxy.chapter_3.into_Property_bean.autowired.main.SystemConfig;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SystemConfig.class)
public class AutoWiredTest {
	
	@Autowired
	private CompactDisc compactDisc;
	
	@Test
	public void test1() {
		compactDisc.play();
	}
}
           

繼續閱讀