【Spring】高級裝配


前言

前面講解了bean的核心裝配技術,其可應付很多中裝配情況,但Spring提供了高級裝配技術,以此實現更為高級的bean裝配功能。

高級裝配

配置profile bean

將所有不同bean定義放置在一個或多個profile中,在將應用部署到每個環境時,要確保對應的profile處於激活狀態。如配置了如下數據源,並使用profile注解定義。

JavaConfig配置profile

  • 開發環境中的數據源配置

package com.hust.grid.leesf.ch3;

import javax.activation.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

@Configuratoin
@Profile("dev")
public class DevelopmentProfileConcifg {
  	@Bean(destroyMethod = "shutdown")
  	public DataSource embeddedDataSource() {
    	return new EmbeddedDatabaseBuilder()
    	    .setType(EmbeddedDatabaseType.H2)
    	    .addScript("classpath:schema.sql")
    	    .addScript("classpath:test-data.sql")
    	    .build();
  	}	
}


  • 生產環境下的數據源配置

package com.hust.grid.leesf.ch3;

import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jndi.JndiObjectFactoryBean;

@Configuration
public class ProductionProfileConfig {
 
  @Bean
  @Profile("prod")
  public DataSource jndiDataSource() {
    JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
    jndiObjectFactoryBean.setJndiName("jdbc/myDS");
    jndiObjectFactoryBean.setResourceRef(true);
    jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
    return (DataSource) jndiObjectFactoryBean.getObject();
  }

}


只有在prod profile激活時,才會創建對應的bean。在Spring 3.1之前只能在類級別上使用@Profile注解,從Spring 3.2之后,可以從方法級別上使用@Profile注解,與@Bean注解一起使用,上述放在兩個不同配置類可以轉化為兩個方法放在同一個配置類中。


package com.hust.grid.leesf.ch3;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;

@Configuration
public class DataSourceConfig {
  
  @Bean(destroyMethod = "shutdown")
  @Profile("dev")
  public DataSource embeddedDataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.H2)
        .addScript("classpath:schema.sql")
        .addScript("classpath:test-data.sql")
        .build();
  }

  @Bean
  @Profile("prod")
  public DataSource jndiDataSource() {
    JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
    jndiObjectFactoryBean.setJndiName("jdbc/myDS");
    jndiObjectFactoryBean.setResourceRef(true);
    jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
    return (DataSource) jndiObjectFactoryBean.getObject();
  }

}


注意:盡管配置類中配置了不同的Profile,但只有規定的profile激活時,對應的bean才會被激活。

XML配置profile

<?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:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="
    http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee.xsd
    http://www.springframework.org/schema/jdbc
    http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd"
  profile="dev">

  <jdbc:embedded-database id="dataSource">
    <jdbc:script location="classpath:schema.sql" />
    <jdbc:script location="classpath:test-data.sql" />
    </jdbc:embedded-database>

</beans>

或者使用beans元素定義多個profile


<?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:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="
    http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee.xsd
    http://www.springframework.org/schema/jdbc
    http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

  <beans profile="dev">
    <jdbc:embedded-database id="dataSource">
      <jdbc:script location="classpath:schema.sql" />
      <jdbc:script location="classpath:test-data.sql" />
    </jdbc:embedded-database>
  </beans>

  <beans profile="qa">
	<bean id="dataSource"
		  class="org.apache.commons.dbcp.BasicDataSource"
		  destory-method="close"
		  p:url="jdbc:h2:tcp://dbserver/~/test"
		  p:driverClassName="org.h2.Driver"
		  p:username="sa"
		  p:password="password"
		  p:initialSize="20"
		  p:maxActive="30" />
  </beans>
  
  <beans profile="prod">
    <jee:jndi-lookup id="dataSource"
      jndi-name="jdbc/myDatabase"
      resource-ref="true"
      proxy-interface="javax.sql.DataSource" />
  </beans>
</beans>


三個beanID都是dataSource,在運行時會動態創建一個bean,這取決激活的哪個profile

激活profile

Spring依賴spring.profiles.activespring.profiles.default兩個屬性確定哪個profile處於激活狀態,如果設置了spring.profiles.active,那么其值用於確定哪個profile是激活狀態,如果未設置,則查找spring.profiles.defaults的值;如果均未設置,則沒有激活的profile,只會創建那些沒有定義在profile中的bean。如下是在web.xml中設置spring.profiles.default


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
  ...>

<context-param>
	<param-name>spring.profiles.default</param-name>
	<param-value>dev</param-value>
</context-param>


<servlet>
	<init-param>
		<param-name>spring.profiles.default</param-name>
		<param-value>dev</param-value>
	</init-param>
</servlet>


Spring提供了@ActiveProfiles注解啟用profile


@Runwith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {
	...
}

條件化的bean

使用@Conditional注解,如果給定條件計算結果為true,那么創建bean,否則不創建。

  • MagicBean

@Bean
@Condition(MagicExistsCondition.class)
public MagicBean magicBean() {
	return new MagicBean();
}

  • MagicExistsCondition

package com.hust.grid.leesf.ch3;

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

public class MagicExistsCondition implements Condition {
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		Environment env = context.getEnvironment();
		return env.containsProperty("magic");
	}
}


處理自動裝配的歧義性

如下代碼


@Autowired
public void setDessert(Dessert dessert) {
	this.dessert = dessert;
}

其中Dessert為一個接口,其有多個子類。


@Component
public class Cake implements Dessert {}

@Component
public class Cookies implements Dessert {}

@Component
public class IceCream implements Dessert {}

此時,會發現不止一個bean可以匹配,Spring會拋出異常,可以將某個bean設置為首選的bean或使用限定符。

標識首選bean

使用Primary注解標識首選bean。


@Component
@Primary
public class IceCream implements Dessert {}

或者使用xml配置首選bean


<bean id="iceCream"
	  class="com.dessertteater.IceCream"
      primary="true" />

如果配置多個首選bean,那么也將無法工作。

限定自動裝配的bean

使用@Qualifier注解進行限定。


@Autowired
@Qualifier("iceCream")
pulbic void setDessert(Dessert dessert) {
	this.dessert = dessert;
}

  • 創建自定義限定符

可以為bean設置自己的限定符,而不依賴將bean ID作為限定符,在bean的聲明上使用@Qualifier注解,其可以與@Component組合使用。


@Component
@Qualifier("cold")
public class IceCream implements Dessert {}

這樣,使用如下。


@Autowired
@Qualifier("cold")
pulbic void setDessert(Dessert dessert) {
	this.dessert = dessert;
}

  • 使用自定義的限定符注解

如果多個bean都具備相同特性的話,那么也會出現問題,無法確定唯一bean,如定義@Cold注解


@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
			ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
#Qualifier
public @interface Cold {}

這樣就可以使用如下注解進行定義

@Component
@Cold
@Creamy
public class IceCream implements Dessert {} 

通過自定義注解后,然后可以通過多個注解的組合確定唯一一個符合條件的bean



@Autowired
@Cold
@Creamy
pulbic void setDessert(Dessert dessert) {
	this.dessert = dessert;
}

bean的作用域

默認情況下,Spring上下文中所有bean都是作為以單例形式創建的。但有時候需要多個不同的bean實例,Spring定義了多種作用域,包括:

  • 單例,整個應用中,只創建一個bean實例。
  • 原型,每次注入或者通過Spring應用上下文獲取時,都會創建一個新的bean實例。
  • 會話,在Web應用中,為每個會話創建一個bean實例。
  • 請求,在Web應用中,為每個請求創建一個bean實例。

使用@Scope注解確定bean的作用域,如將如下bean聲明為原型。


@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class NotePad {}

當使用xml文件配置時如下


<bean id="notepad"
	  class="com.hust.grid.leesf.Notepad"
	  scope="prototype" />

使用會話和請求作用域

Web應用中,可能需要實例化在會話和請求范圍內共享的bean,如電商網站,需要會話作用域。


@Component
@Scope(
	value=WebApplicationContext.SCOPE_SESSION,
	proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() {}

需要將ShoppingCart bean注入到單例StoreService bean中。


@Component
public class StoreService {
	@Autowired
	public void setShoppingCart(ShoppingCart shoppingCart) {
		this.shoppingCart = shoppingCart;
	}
}

此時,由於ShoppingCart是會話作用域,直到某個用戶創建了會話后,才會出現ShoppingCart實例,並且Spring會注入一個代理至StoreService中,這個代理與ShoppingCart有相同的方法,當處理時需要將調用委托給會話作用域內真正的ShoppingCart

在XML中聲明作用域代理

需要使用Spring aop命名空間的新元素


<bean id="cart"
	  class="com.hust.grid.leesf.ShoppingCart"
	  scope="session">
  <aop:scoped-proxy />
</bean>

上述情況會使用CGLib創建目標類的代理,但也可將proxy-target-class屬性設置為false,進而要求它生成基於接口的代理。


<bean id="cart"
	  class="com.hust.grid.leesf.ShoppingCart"
	  scope="session">
  <aop:scoped-proxy proxy-target-class="false" />
</bean>

為使用<aop:scoped-proxy>元素,需要在XML中聲明spring-aop.xsd命名空間。

運行時值注入

不使用硬編碼注入,想讓值在運行時確定,Spring提供了如下兩種方式。

  • 屬性占位符
  • Spring表達式語言

注入外部的值

聲明屬性源並通過SpringEnvironment來檢索屬性。


...
@Configuration
@PropertySource("classpath:/com/hust/gird/leesf/app.properties")
public class ExpressiveConfig {
	@Autowired
	Environment env;
	
	@Bean
	public BlankDisc disc() {
		return new BlankDisc(
			env.getProperty("disc.title"),
			env.getProperty("disc.artist"));
	}
}

通過在app.properties中配置對應的屬性完成注入。還可使用占位符完成注入。


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

為使用占位符,需要配置PropertySourcesPlaceholderConfigurer


@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
	return new PropertySourcesPlaceholderConfigurer();
}

或者在XML配置文件中使用<context:property-placeholder />,這樣會生成一個PropertySourcesPlaceholderConfigurerbean

總結

本篇學習了更為高級的裝配技巧,如Spring profile,還有條件化裝配bean,以及bean的作用域等等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM