【Java EE】裝備Spring Bean

裝配Spring Bean

依賴注入的三種方式

實現IoC容器的方式分爲兩大類,一類是依賴查找,依賴查找是通過資源定位,把對應的資源查找回來;另一類是依賴注入,而Spring主要使用的是依賴注入。一般而言,依賴注入可以分爲3種方式:

  • 構造器注入
  • setter注入
  • 接口注入
    構造器注入和setter注入是主要的方式,而接口注入是從別的地方注入的方式,比如在Web工程種,配置的數據源往往是通過服務器去配置的,這個時候可以用JNDI的形式通過接口將它注入Spring IoC容器中來。

構造器注入

構造器注入依賴於構造方法實現,而構造方法可以是有參數或者是無參數的。在大部分的情況下,通過類的構造方法來創建類對象,Spring也可以採用反射的方式,通過使用構造方法來完成注入,這就是構造器注入的原理。
爲了讓Spring完成對應的構造注入,有必要去描述具體的類、構造方法並設置對應的參數,這樣Spring就會通過對應的信息用反射的形式去創建對象。例如:

public class Role {
   private Long id;
   private String roleName;
   private String note;
   
   public Role(String roleName, String note) {
      this.roleName = roleName;
      this.note = note;
   }

   public Long getId() {
      return id;
   }

   public void setId(Long id) {
      this.id = id;
   }

   public String getRoleName() {
      return roleName;
   }

   public void setRoleName(String roleName) {
      this.roleName = roleName;
   }

}

這個時候是沒有辦法利用無參數的構造方法去創建對象的,因此,可以使用如下代碼:

<bean id="role1" class="com.ssm.learn.pojo.Role">
	<constructor-arg index="0" value="總經理"/>
	<constructor-arg index="1" value="公司管理者"/>
</bean>

constructor-arg元素用於定義類構造方法的參數,其中index用於定義參數的位置,而value則是設置值,通過這樣的定義Spring便知道使用Role這樣的構造方法去創建對象了。

使用setter注入

setter注入是Spring中最主流的注入方式,它利用Java Bean規範所定義的setter方法來完成注入,靈活且可讀性高。它消除了使用構造器注入時出現多個參數的可能性,首先可以把構造方法聲明爲無參數的,然後使用setter注入爲其設置對應的值。其實也是通過Java反射技術得以實現的。首先假設在Role類中加入了一個沒有參數的構造方法,即:

public Role() {
}

然後修改配置:

<bean id="role_2" class="com.ssm.learn.pojo.Role">
   <property name="roleName" value="高級工程師" />
   <property name="note" value="重要人員" />
</bean>

這樣Spring就會通過反射調用沒有參數的構造方法生成對象,同時通過反射對應的setter注入配置的值了。這在實際工作中使用廣泛。

接口注入

有時候資源並非來自自身系統,而是來自外界,比如數據庫連接資源。這樣數據庫資源屬於開發工程外的資源,可以採用接口注入的形式來獲取它。比如在Tomcat中可以配置數據源,又如在Eclipse中配置了Tomcat後,可以打開服務器的context.xml文件,在這個XML文件元素context中加入自己的一個資源,例如:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
	<!--
	name爲JNDI名稱
	url是數據庫的jdbc連接
	username用戶名
	password數據庫密碼
	-->
	<Resource name="jdbc/ssm" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/ssm?zeroDateTimeBehavior=convertToNUll" username="root" password="123456" />
</Context>

如果已經配置了相應的數據庫連接,那麼Eclipse會把數據庫的驅動包複製到對應的Tomcat的lib文件夾下,否則就需要自己手工將對應的驅動包複製到Tomcat的工作目錄下,它位於{Tomcat_Home}\lib。然後啓動Tomcat,這個時候數據庫資源也會在Tomcat啓動的時候被其加載進來。
如果Tomcat的Web工程使用了Spring,那麼可以通過Spring的機制,用JNDI獲取Tomcat啓動的數據庫連接池。代碼如下:

<!--通過JNDI獲取的數據源,通過Spring的接口注入實現-->
<bean id = "dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
	<property name="jndiName">
		<value>java:comp/env/jdbc/ssm</value>
	</property>
</bean>

裝配Bean概述

將自己開發的Bean裝配到Spring IoC容器中,大部分場景下,使用ApplicationContext的具體實現類,因爲對應的Spring IoC容器功能相對強大,而在Spring中提供了3種方法進行配置:

  • 在XML中顯示配置
  • 在Java的接口和類中實現配置
  • 隱士Bean的發現機制和自動裝配原則
    我們應該怎麼選擇使用哪種方式去把Bean發佈到Spring IoC容器中?
  1. 基於約定優於配置的原則,最優先的應該是通過隱式Bean的發現機制和自動裝配的原則。
  2. 在沒有辦法使用自動裝配原則的情況下應該優先考慮Java接口和類中實現配置,可以避免XML配置的泛濫。
  3. 在上述方法都無法使用的情況下,只能選擇XML去配置Spring IoC容器。
    通俗來講,當配置的類是自身正在開發的工程,那麼應該考慮Java配置爲主,並且優先自動裝配,如果不是自己的工程開發的,建議使用XML的方式。

通過XML配置裝配Bean

使用XML裝配Bean需要定義對應的XML,這裏需要引入對應的XML模式(XSD)文件,這些文件會定義配置Spring 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"
   xsi:schemaLocation="http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
  <!--Spring Bean配置代碼-->
</beans>

裝配簡易值

以一個最簡單的裝配爲例:

<bean id="role_2" class="com.ssm.chapter9.pojo.Role">
   <property name="id" value="1" />
   <property name="roleName" value="高級工程師" />
   <property name="note" value="重要人員" />
</bean>

其中:

  • id屬性是Spring找到這個Bean的編號,但是它不是一個必需的屬性,如果沒有聲明它,那麼Spring將會採用“全限定名#{number}”的格式生成編號。
  • class顯然是一個類全限定名
  • property元素是定義類的屬性,其中name屬性定義的是屬性名稱,而value是其值。
    有時候需要注入一些自定義的類,可以使用ref屬性在另一個Bean的property屬性中引用對應的Bean。

裝配集合

有些時候需要做一些複雜的裝配工作,比如Set、Map、List、Array和Properties等,可以採用如下形式裝配這些屬性:

<bean id="complexAssembly" class="com.ssm.chapter10.pojo.ComplexAssembly">
   <property name="id" value="1" />
   <property name="list">
      <list>
         <value>value-list-1</value>
         <value>value-list-2</value>
         <value>value-list-3</value>
      </list>
   </property>
   <property name="map">
      <map>
         <entry key="key1" value="value-key-1" />
         <entry key="key2" value="value-key-2" />
         <entry key="key3" value="value-key-3" />
      </map>
   </property>
   <property name="props">
      <props>
         <prop key="prop1">value-prop-1</prop>
         <prop key="prop2">value-prop-2</prop>
         <prop key="prop3">value-prop-3</prop>
      </props>
   </property>
   <property name="set">
      <set>
         <value>value-set-1</value>
         <value>value-set-2</value>
         <value>value-set-3</value>
      </set>
   </property>
   <property name="array">
      <array>
         <value>value-array-1</value>
         <value>value-array-2</value>
         <value>value-array-3</value>
      </array>
   </property>
</bean>

有時候可能需要更爲複雜的裝載,示例如下:
先建立兩個POJO:

public class Role {
   private Long id;
   private String roleName;
   private String note;

   public Role() {
   }
   
   public Role(Long id, String roleName, String note) {
   	this.id = id;
      	this.roleName = roleName;
      	this.note = note;
   }
}
public class User {
    private Long id;
    private String userName;
    private String note;
    /*setter and getter*/
}

然後建立一個複雜的POJO:

import java.util.List;
import java.util.Map;
import java.util.Set;

public class UserRoleAssembly {
    private Long id;
    private List<Role> list;
    private Map<Role, User> map;
    private Set<Role> set;
    /**setter and getter*/
}

這裏可以看到對於List、Map和Set等集合類使用的是類對象,Spring IoC容器提供了對應的配置方法,代碼如下:

<bean id="role1" class="com.ssm.learn.pojo.Role">
   <property name="id" value="1" />
   <property name="roleName" value="role_name_1" />
   <property name="note" value="role_note_1" />
</bean>

<bean id="role2" class="com.ssm.learn.pojo.Role">
   <property name="id" value="2" />
   <property name="roleName" value="role_name_2" />
   <property name="note" value="role_note_2" />
</bean>

<bean id="user1" class="com.ssm.learn.pojo.User">
   <property name="id" value="1" />
   <property name="userName" value="user_name_1" />
   <property name="note" value="role_note_1" />
</bean>

<bean id="user2" class="com.ssm.learn.pojo.User">
   <property name="id" value="2" />
   <property name="userName" value="user_name_2" />
   <property name="note" value="role_note_1" />
</bean>

<bean id="userRoleAssembly" class="com.ssm.learn.pojo.UserRoleAssembly">
   <property name="id" value="1" />
   <property name="list">
      <list>
         <ref bean="role1" />
         <ref bean="role2" />
      </list>
   </property>
   <property name="map">
      <map>
         <entry key-ref="role1" value-ref="user1" />
         <entry key-ref="role2" value-ref="user2" />
      </map>
   </property>
   <property name="set">
      <set>
         <ref bean="role1" />
         <ref bean="role2" />
      </set>
   </property>
</bean>

其中:

  • List屬性使用<List>元素注入,使用多個<ref>元素的Bean屬性去引用之前定義好的Bean。
  • Map屬性使用<map>元素定義注入,使用多個<entry>元素的key-ref屬性去引用之前定義好的Bean作爲鍵,用value-ref屬性去引用之前定義好的Bean作爲值。
  • Set屬性使用<set>元素定義注入,使用多個<ref>元素的Bean去引用之前定義好的Bean。

命名空間裝配

Spring還提供了對應的命名空間的定義。只是在使用命名空間的時候要先引入對應的命名空間和XML模式(XSD)文件。

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:c="http://www.springframework.org/schema/c" 
   xmlns:p="http://www.springframework.org/schema/p"
   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="role1" class="com.ssm.chapter10.pojo.Role" c:_0="1" c:_1="role_name_1" c:_2="role_note_1" />
   <bean id="role2" class="com.ssm.chapter10.pojo.Role" p:id="2" p:roleName="role_name_2" p:note="role_note_2" />
</beans>

其中:

  • 第二行和第三行定義了XML的命名空間,這樣才能在內容裏面使用p和c這樣的前綴定義。
  • id爲role1的角色定義,c:_0代表構造方法的第1個參數,c:_1代表的是第2個,c:_2代表的是第3個,以此類推。
  • id爲role2的角色定義,p代表引用屬性,其中p:id="2"以2爲值,使用setId方法設置,roleName、note屬性也是一樣的道理。
    類似的,藉助引入XML文檔(XSD)文件的方法,把UserRoleAssembly類實例註冊給Spring IoC容器:
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:c="http://www.springframework.org/schema/c" xmlns:p="http://www.springframework.org/schema/p"
   xmlns:util="http://www.springframework.org/schema/util" 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
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
   <bean id="role1" class="com.ssm.chapter10.pojo.Role" 
   c:_0="1" c:_1="role_name_1" c:_2="role_note_1" />
   <bean id="role2" class="com.ssm.chapter10.pojo.Role" 
       p:id="2" p:roleName="role_name_2" p:note="role_note_2" />
   <bean id="user1" class="com.ssm.chapter10.pojo.User" 
       p:id="1" p:userName="role_name_1" p:note="user_note_1" />
   <bean id="user2" class="com.ssm.chapter10.pojo.User" 
       p:id="2" p:userName="role_name_2" p:note="user_note_2" />

   <util:list id="list">
      <ref bean="role1" />
      <ref bean="role2" />
   </util:list>

   <util:map id="map">
      <entry key-ref="role1" value-ref="user1" />
      <entry key-ref="role2" value-ref="user2" />
   </util:map>

   <util:set id="set">
      <ref bean="role1" />
      <ref bean="role2" />
   </util:set>

   <bean id="userRoleAssembly" class="com.ssm.chapter10.pojo. UserRoleAssembly"
      p:id="1" p:list-ref="list" p:map-ref="map" p:set-ref="set" />
</beans>

引入的命名空間和XSD文件,然後定義了兩個角色類對象(role1和role2)和兩個用戶類對象(user1和user2)。通過命名空間util去定義Map、Set和List對象,跟着就是定義id爲userRoleAssembly的Bean,這裏的list-ref代表採用list屬性,顯然就是util命名空間定義的List,同理Map和Set也是如此。

通過註解裝配Bean

更多的時候考慮使用註解(annotation)的方式去裝配Bean。在Spring中,它提供了兩種方式來讓Spring IoC容器發現Bean。

  • 組件掃描:通過定義資源的方式,讓Spring IoC容器掃描對應的包,從而把Bean裝配進來。
  • 自動裝配:通過註解定義,使得一些依賴關係可以通過註解完成。
    通過掃描和自動裝配,大部分的工程都可以用Java配置完成,而不是XML。目前企業所流行的方式是,以註解爲主,以XML爲輔。

使用@Component裝配Bean

首先定義一個POJO:

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

@Component(value = "role")
public class Role {
   @Value("1")
   private Long id;
   @Value("role_name_1")
   private String roleName;
   @Value("role_note_1")
   private String note;
   /* setter and getter */
}

其中:

  • 註解@Component代表Spring IoC會把這個類掃描生成Bean實例,而其中的value屬性代表這個類在Spring中的id,這就相當於XML方式定義的Bean的id,也可以簡寫成@Component(“role”),甚至直接攜程@Component,對於不寫的,Spring IoC容器就默認類名,但是是以首字母小寫的形式作爲id,爲其生成對象,配置到容器中。
  • 註解@Value代表的是值的注入,這裏只是簡單注入一些值,其中id是一個long型,注入的時候Spring會爲其轉化類型。
    然後Spring IoC需要使用一個JavaConfig來告訴容器去哪裏掃描對象,代碼如下:
package com.ssm.chapter10.annotation.pojo;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class PojoConfig {

}

注意兩點:

  1. 包名和POJO保持一致
  2. @ComponentScan代表掃描,默認是掃描當前包的路徑,POJO的包名和它保持一致才能掃描,否則是沒有的。
    然後就可以通過Spring定義好的Spring IoC容器的實現類——AnnotationConfigApplicationContext去生成IoC容器。代碼如下:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.ssm.chapter10.annotation.pojo.PojoConfig;
import com.ssm.chapter10.annotation.pojo.Role;
public class AnnotationMain {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class);
		Role role = context.getBean(Role.class);
		System.err.println(role.getId());
    	}
}

這裏使用了AnnotationConfigApplicationContext類去初始化Spring IoC容器,這裏有兩個弊端:

  • 對於@ComponentScan註解,它只是掃描所在包的Java類,但是更多的時候真正需要的是可以掃描所指定的類;
  • 上面只注入了一些簡單的值,而沒有注入對象,同樣在現實的開發中可以注入對象是十分重要的。

@ComponentScan存在着兩個配置項:第1個是basePackages,它是由base和package兩個單詞組成,而且package還使用了複數,意味着它可以配置一個Java包的數組,Spring會根據它的配置掃描對應的包和子包,將配置好的Bean裝配起來;第2個是basePackageClasses,它由base、package和class三個單詞組成的,採用複數,意味着它可以配置多個類,Spring會根據配置的類所在的包,爲包和子包進行掃描裝配對應配置的Bean。
首先定義一個接口RoleService,如下:

package com.ssm.chapter10.annotation.service;
import com.ssm.chapter10.annotation.pojo.Role;

public interface RoleService {
   public void printRoleInfo(Role role);
}

使用接口來編寫一些操作類是Spring所推薦的,它可以將定義和實現相分離。然後開發一個實現類去實現接口:

package com.ssm.chapter10.annotation.service.impl;

import org.springframework.stereotype.Component;

import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.RoleService;

@Component
public class RoleServiceImpl implements RoleService {
   @Override
   public void printRoleInfo(Role role) {
      System.out.println("id =" + role.getId());
      System.out.println("roleName =" + role.getRoleName());
      System.out.println("note =" + role.getNote());
   }
}

這裏的@Component表明它是一個Spring所需要的Bean,而且也實現了對應的RoleService接口所定義的printRoleInfo方法。爲了裝配RoleServiceImpl和Role的兩個Bean,需要給@ComponentScan註解加上對應的配置。代碼如下:

package com.ssm.chapter10.annotation.config;
import org.springframework.context.annotation.ComponentScan;
import com.ssm.chapter10.annotation.service.impl.RoleServiceImpl;
@ComponentScan(basePackageClasses = { Role.class, RoleServiceImpl.class }, 
// @ComponentScan(basePackages = {"com.ssm.chapter10.annotation.pojo",
// "com.ssm.chapter10.annotation.service"})
// @ComponentScan(basePackages = {"com.ssm.chapter10.annotation.pojo",
// "com.ssm.chapter10.annotation.service"},
// basePackageClasses = {Role.class, RoleServiceImpl.class})
public class ApplicationConfig {
}

注意:

  • 這是對掃描包的定義,可以採用任意一個@ComponentScan去定義,也可以取消代碼中的註釋
  • 如果採用多個@ComponentScan去定義對應的包,但是每定義一個@ComponentScan,Spring就會爲所定義的類生成一個新的對象,也就是所配置的Bean將會生成多個實例,這往往不是我們的需要
  • 對於已定義了basePackages和basePackageClasses的@ComponentScan,Spring會進行專門的區分,也就是說在同一個@ComponentScan中即使重複定義相同的包或者存在其子包定義,也不會造成因同一個Bean的多次掃描,而導致一次配置生成多個對象。
    因此,建議不要採用多個@ComponentScan註解進行配置。對於basePackages和basePackageClasses的選擇問題,basePackages的可讀性會更好一點,但是在大量重構的工程中,儘量不要使用basePackages定義,因爲很多時候重構修改包名需要反覆地配置,而IDE不會給任何提示。而採用basePackageClasses,當對包移動的時候,IDE會報錯,並且可以輕鬆處理這些錯誤。
    測試代碼如下:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
Role role = context.getBean(Role.class);
RoleService roleService = context.getBean(RoleService.class);
roleService.printRoleInfo(role);
context.close();

自動裝配——@Autowired

通過學習Spring IoC容器,我們知道Spring是先完成Bean的定義和生成,然後尋找需要注入的資源。也就是當Spring生成所有的Bean後,如果發現這個註解,它就會在Bean中查找,然後找到對應的類型,將其注入進來,這樣就完成依賴注入了。所謂自動裝配技術是一種由Spring自己發現對應的Bean,自動完成裝配工作的方式,它會應用到一個十分常用的註解@Autowired上,這個時候Spring會根據類型去尋找定義的Bean然後將其注入。示例如下
首先定義一個接口:

public interface RoleService2{
	public void printRoleInfo();
}

實現接口方法:

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

import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.RoleService2;

@Component("RoleService2")
public class RoleServiceImpl2 implements RoleService2 {

	@Autowired
	private Role role = null;

	public Role getRole() {
		return role;
	}

//	@Autowired
	public void setRole(Role role) {
		this.role = role;
	}

	@Override
	public void printRoleInfo() {
		System.out.println("id =" + role.getId());
		System.out.println("roleName =" + role.getRoleName());
		System.out.println("note =" + role.getNote());
	}
}

這裏的@Autowired註解,表示在Spring IoC定位所有的Bean後,這個字段需要按類型注入,這樣IoC容器就會尋找資源,然後將其注入。
IoC容器有時候會尋找失敗,在默認的情況下尋找失敗它就會拋出異常,也就是說默認情況下,Spring IoC容器會認爲一定要找到對應的Bean來注入這個字段,有時候這並不是一個真實的需求,這個時候可以通過@Autowired的配置項required來改變它,比如@Autowired(required=false)。
@Autowired除了可以配置在屬性之外,還允許方法配置,常見的Bean的setter方法也可以使用它來完成注入,例如:

public class RoleServiceImpl2 implements RoleService2{
	private Role role = null;
	...
	@Autowired
	public void setRole(Role role){
		This.role =role;
	}
}

在大部分的配置中都推薦使用@Autowired註解,這是Spring IoC自動裝配完成的,使得配置大幅度減少,滿足約定優於配置的原則,增強程序的健壯性。

自動裝配的歧義性(@Primary和@Qualifier)

按照Spring的建議,在大部分情況下使用接口編程,但是定義一個接口,並不一定只有與之對應的一個實現類。換句話說,一個接口可以有多個實現類,這樣就會出現有時候自動裝配不能使用。
原因在於@Autowired採用的是按類型注入對象,而在Java中接口可以有多個實現類,同樣的抽象類也可以有多個實例化的類,這樣就會造成通過類型(by type)獲取Bean的不唯一,從而導致Spring IoC類似於按類型的方法無法獲得唯一的實例化類。這就是自動裝配的歧義性。BeanFactory的定義如下,它存在一個通過類型獲取Bean的方法:

<T> T getBean(Class<T> requiredType) throws BeansException;

爲了消除歧義性,Spring提供了兩個註解(@Primary和@Qualifer),這是兩個不同的註解:
1. 註解@Primary
註解@Primary代表首要,當Spring IoC通過一個接口或者抽象類注入對象的時候,由於存在多個實現類或者具體類,就會犯糊塗,不知道採用哪個類注入爲好。註解@Primary則是告訴Spring IoC容器,請優先使用該類注入。示例如下:

...
import org.springframework.context.annotation.Primary;
@Component("roleService3")
@Primary
public class RoleServiceImpl3 implements RoleService{
......
}

但是還存在這樣一個問題,可以將@Primary註解加入到多個實現類中,這樣在Spring中也是允許的,只是在注入的時候將拋出異常。但是無論如何,@Primary只能解決首要性的問題,不能解決選擇性的問題。簡而言之,它不能選擇使用接口具體的實現類去注入。
2. 註解@Qualifier
歧義性的一個重要原因是Spring在尋找依賴注入的時候採用按類型注入引起的。除了按類型查找Bean,Spring IoC容器最底層的接口BeanFactory,也定義了按名稱查找的方法,如果採用名稱查找的方法,而不是採用按類型查找的方法,那麼就可以消除歧義性了。註解@Qualifier正是這樣的一個註解。代碼修改如下所示:

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

import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.RoleService;

@Component
public class RoleController {
	
	@Autowired
	@Qualifier("roleService3")
	private RoleService roleService = null;
	
	public void printRole(Role role) {
		roleService.printRoleInfo(role);
	}
}

這個時候IoC容器不會再按照類型的方式注入了,而是按照名稱的方式注入,IoC容器的底層接口——BeanFactory,它所定義的方法如下:

Object getBean(String name) throws BeansException;

使用@Qualifier註解後就可以使用這個方法通過名稱從IoC容器中獲取對象進行注入。

裝載帶有參數的構造方法類

角色類的構造方法都是沒帶參數的,而事實上在某些時候構造方法是帶參數的,對於一些帶有參數的構造方法,也允許我們通過註解進行注入。示例如下:

@Component
public class RoleController2{
private RoleService roleService = null;
public RoleController2(RoleService roleService){
	this.roleService = roleService;
}
......
}

以及如下代碼:

public RoleController2(@Autowired RoleService roleService){
	this.roleService = roleService;
}

使用@Bean裝配Bean

@Component只能註解在類上,不能註解到方法上。這個時候Spring給予一個註解@Bean,它可以註解到方法之上,並且將方法返回的對象作爲Spring的Bean,存放在IoC容器中。示例如下:

@Bean(name="dataSource")
public DataSource getDataSource(){
	Properties props = new Properties();
	props.setProperty("driver","com.mysql.jdbc.Driver");
	props.setProperty("url","jdbc:mysql://localhost:3306/chapter12");
	props.setProperty("username","root");
	props.setProperty("password","123456");
	DataSource dataSource = null;
	try{
		dataSource = BasicDataSourceFactory.createDataSource(props);
	}catch(Exception e){
		e.printStackTrace();
	}
	return dataSource;
}

這樣就能夠裝配一個Bean,當Spring IoC容器掃描它的時候,就會爲其生成對應的Bean。而且這裏還配置了@Bean的name選項爲dataSource,這意味着Spring生成該Bean的時候就會使用dataSource作爲其BeanName。另外,它也可以通過@Autowired或者@Qualifier等註解注入別的Bean中。

註解自定義Bean的初始化和銷燬方法

Bean的初始化可以通過實現Spring所定義的一些關於生命週期的接口來實現,這樣BeanFactory或者其他高級容器ApplicationContext就可以調用這些接口所定義的方法了,這和使用XML是一樣的。另外使用註解@Bean的配置項就可以實現自定義的初始化方法和銷燬方法。
註解@Bean不能使用在類的標註上,它主要使用在方法上,@Bean的配置項中包含4個配置項:

  • name:是一個字符串數組,允許配置多個BeanName
  • autowire:標誌是否是一個引用的Bean對象,默認值是Autowire.NO。
  • initMethod:自定義初始化方法
  • destroyMethod:自定義銷燬方法
    示例代碼如下,其中指明瞭它的初始化方法和銷燬方法:
@Bean(name="juiceMaker2", initMethod="init", destroyMethod="myDestroy")
public JuiceMaker2 initJuiceMaker2(){
	JuiceMaker2 juiceMaker2 = new JuiceMaker2();
	juiceMaker2.setBeverageShop("貢茶");
	Source source = new Source();
	source.setFruit("橙子");
	source.setSize("大杯");
	source.setSugar("少糖");
	juiceMaker2.setSource(source);
	return juiceMaker2;
}

裝配的混合使用

在現實中,使用XML或者註解各有道理,建議在自己的工程中所開發的類儘量使用註解方式,因爲使用它並不困難,更爲簡單,而對於引入第三方包或者服務的類,儘量使用XML方式,這樣的好處是可以儘量對第三方包或者服務的細節減少理解,也更加清晰和明朗。
Spring同時支持這兩種形式的裝配,所以可以自由選擇,只是無論採用XML還是註解方式的裝配都是將Bean裝配到Spring IoC容器中,這樣就可以通過Spring IoC容器去管理各類資源了。
以數據庫池的配置爲例,首先DBCP數據庫連接池是通過第三方去定義的,沒有辦法給第三方加入註解,因此可以選擇通過XML給出。假設它配置XML文件——spring-data.xml,需要通過引入它達到註解的體現當中,而註解的體系則需要完成對角色編號(id)爲1的查詢功能。
首先,使用註解@ImportResource,引入spring-data.xml所定義的內容:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
@ComponentScan(basePackages={"com.ssm.learn.annotation"})
@ImportResource({"classpath:spring-dataSource.xml"})
public class ApplicationConfig {
}

@ImportResource中配置的內容是一個數組,也就是可以配置多個XML配置文件,這樣就可以引入多個XML所定義的Bean了。
然後通過@Autowired注入去實現對數據庫連接池的注入:

import com.ssm.chapter10.annotation.pojo.Role;

public interface RoleDataSourceService {
   public Role getRole(Long id);
}

這是一個簡單的接口,需要一個實現類,這個實現類要用到數據庫連接池,這時就可以使用@Autowired進行注入,代碼如下:

@Component
public class RoleDataSourceServiceImpl implements RoleDataSourceService {

   @Autowired
   DataSource dataSource = null;
   
   @Override
   public Role getRole(Long id) {
      Connection conn = null;
      ResultSet rs = null;
      PreparedStatement ps = null;
      Role role = null;
      try {
          conn = dataSource.getConnection();
          ps = conn.prepareStatement("select id, role_name, note from t_role where id = ?");
          ps.setLong(1, id);
          rs = ps.executeQuery();
          while(rs.next()) {
             role = new Role();
             role.setId(rs.getLong("id"));
             role.setRoleName(rs.getString("role_name"));
             role.setNote(rs.getString("note"));
          }
      } catch (SQLException e) {
         e.printStackTrace();
      } finally {
         /**********close database resources************/
      }
      return role;
   }

}

通過這樣的形式就能夠把XML所配置的dataSource注入RoleDataSourceServiceImpl中了,同樣也可以注入其他的資源。
有時候所有的配置放在一個ApplicationConfig類裏面會造成配置複雜,因此會有多個類似於ApplicationConfig配置類。Spring也提供了註解@Import的方式注入這些配置類。

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
@ComponentScan(basePackages={"com.ssm.learn.annotation"})
@Import({ApplicationConfig2.class,ApplicationConfig3.class})
public class ApplicationConfig {
}

通過這樣的形式加載了多個配置文件。
如果有多個XML文件,而希望通過其中的一個XML文件去引入其他的XML文件,假設目前有了spring-bean.xml,需要引入spring-datasource.xml,那麼可以在spring-bean.xml使用import元素來加載它:

<import resource="spring-datasource.xml" />

如果希望使用XML加載java配置類,但是目前spring是不能支持的,不過spring可以支持通過XML的配置掃描註解的包,只需要通過<context:component-scan>定義掃描的包就可以了:

@ComponentScan(basePackages={"com.ssm.learn.annotation"})的功能
<context:component-scan base-package="com.ssm.learn.annotation" />

無論是使用XML方式,還是使用註解方式,各有利弊,推薦把第三方包、系統外的接口服務和通用的配置使用XML配置,對於系統內部的開發則以註解方式爲主。

使用Profile

存在需求:在不同的環境中切換以執行不同的任務,比如測試和開發。

使用註解@Profile配置

假設存在兩個數據庫連接池,一個用於開發(dev),一個用於測試(test),代碼如下:

/*****************imports********************/
@Component
public class ProfileDataSource{
	@Bean(name="devDataSource")
	@Profile("dev")
	public DataSource getDevDataSource(){
		Properties props = new Properties();
		props.setProperty("driver", "com.mysql.jdbc.Driver");
		props.setProperty("url", "jdbc:mysql://localhost:3306/learn");
		props.setProperty("username", "root");
		props.setProperty("password", "123456");
		DataSource dataSource = null;
		try {
			dataSource = BasicDataSourceFactory.createDataSource(props);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return dataSource;
	}
	@Bean(name="testDataSource")
	@Profile("test")
	public DataSource getTestDataSource(){
		Properties props = new Properties();
		props.setProperty("driver", "com.mysql.jdbc.Driver");
		props.setProperty("url", "jdbc:mysql://localhost:3306/learndemo");
		props.setProperty("username", "root");
		props.setProperty("password", "123456");
		DataSource dataSource = null;
		try {
			dataSource = BasicDataSourceFactory.createDataSource(props);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return dataSource;
	}
}

這裏定義了兩個Bean,分別定義了@Profile,一個是dev,一個是test,同樣也可以使用XML進行定義。

使用XML定義Profile

使用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-4.0.xsd"
	profile="dev">
	<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/chapter10" />
		<property name="username" value="root" />
		<property name="password" value="123456" />
	</bean>
</beans>

在一個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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
	<beans profile="test">
	<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/ssm1" />
		<property name="username" value="root" />
		<property name="password" value="123456" />
	</bean>
	</beans>
	<beans profile="dev">
	<bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/ssm2" />
		<property name="username" value="root" />
		<property name="password" value="123456" />
	</bean>
	</beans>
</beans>

啓動Profile

當啓動Java配置或者XML配置Profile時,可以發現這兩個Bean並不會被加載到Spring IoC容器中,需要自行激活Profile。激活Profile的方法有5種:

  • 在使用Spring MVC的情況下可以配置Web上下文參數,或者DispatchServlet參數。
  • 作爲JNDI條目
  • 配置環境變量
  • 配置JVM啓動參數
  • 在集成測試環境中使用@ActiveProfiles。

首先,在測試代碼中激活Profile,可以使用註解@ActiveProfiles進行定義。代碼如下:

import javax.sql.DataSource;
/**************imports**************/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ProfileConfig.class)
@ActiveProfiles("dev")
public class ProfileTest{
	@Autowired
	private DataSource dataSource;
	@Test
	public void test(){
		System.out.println(dataSource.getClass().getName());
	}
}

在測試代碼中加入@ActiveProfiles來指定加載哪個Profile,這樣程序就會自己去加載對應的Profile了。
有時候要在一些服務器上運行,這個時候可以配置Java虛擬機的啓動項。比如在Tomcat服務器上或者在main方法上,那麼可以啓用Java虛擬機的參數來實現它,關於制定Profile的參數存在兩個:

  • spring.profiles.active:啓動的Profile,如果配置了它,那麼spring.profiles.default配置項將失效
  • spring.profiles.default:默認啓動的Profile,如果系統沒有配置關於Profile參數的時候,那麼它將啓動。
    這個時候可以配置JVM的參數來啓用對應的Profile,比如這裏需要啓動test,那麼就可以配置爲:
JAVA_OPTS="-Dspring.profiles.active=test"

有時候也可以給運行的類加入虛擬機參數,這樣在IDE運行代碼的時候,Spring就知道採用哪個Profile進行操作了。
在大部分情況下需要啓動Web服務器,如果使用的是Spring MVC,那麼也可以設置Web環境參數或者DispatcherServlet參數來選擇對應的Profile,比如在web.xml中進行配置:

<!-- 使用Web環境參數-->
<context-param>
	<param-name>spring.profiles.active</param-name>
	<param-value>test</param-value>
</context-param>
<!--使用SpringMVC的DispatcherServlet環境參數-->
<servlet>
	<servlet-name>dispatcher</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<load-on-startup>2</load-on-startup>
	<init-param>
		<param-name>spring.profiles.active</param-name>
		<param-name>test</param-name>
	</init-param>
</servlet>

這樣也可以在Web工程啓動的時候來啓用對應的Profile。通過編碼去實現也是可以的。

加載屬性(properties)文件

在開發的過程中,配置文件往往就是那些屬性(properties)文件,比如使用properties文件配置數據庫文件,例如:

jdbc.database.driver=com.mysql.jdbc.Driver
jdbc.database.url=jdbc:mysql://localhost:3306/chapter10
jdbc.database.username=root
jdbc.database.password=123456

在Spring中可以使用註解或者XML的方式進行加載屬性文件。

使用註解方式加載屬性文件

首先Spring提供了註解@PropertySource來加載屬性文件,它的配置項有:

  • name:字符串,配置這次屬性配置的名稱。
  • value:字符串數組,可以配置多個屬性的文件。
  • ignoreResourceNotFound:boolean值,默認爲false,其含義爲如果找不到對應的屬性文件是否進行忽略處理,所以默認會拋出異常。
  • encoding:編碼,默認爲“”.
    注意:如果只有@PropertySource的加載,Spring只會把對應文件加載進來。
    示例如下,首先定義Java配置類:
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.PropertySourcePlaceholderConfigurer;
@Configuration
@PropertySource(value={"classpath:database-config.properties"},ignoreResourceNotFound=true)
public class ApplicationConfig {
}

@PropertySource的配置,首先加載了database-config.properties文件,然後定義選項爲ignoreResourceNotFound=true,找不到該文件就會忽略掉它。
然後對其進行測試:

Application context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
String url = context.getEnvironment().getProperty("jdbc.database.url");
System.out.println(url);

Spring推薦使用一個屬性文件解析類進行處理,它就是PropertySourcesPlaceholderConfigurer,使用它就意味着允許Spring解析對應的屬性文件,並通過佔位符去引用對應的配置。示例如下:

/*****************imports****************/
@Configuration
@ComponentScan(basePackages={"com.ssm.chapter10.annotation"})
@PropertySource(value={"classpath:database-config.properties"},ignoreResourceNotFound=true)
public class ApplicationConfig{
	@Bean
	public PropertySourcePlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
		return new PropertySourcePlaceholderConfigurer();
	}
}

這裏定義了一個PropertySourcePlaceholderConfigurer類的Bean,作用是爲了讓Spring能夠解析屬性佔位符。另外既然屬性文件已經定義了關於數據庫連接所需要的配置,那麼還需要知道如何去引用已經定義好的配置,這裏可以使用註解@Value和佔位符,示例如下:

/*************imports****************/
@Component
public class DataSourceBean {

	@Value("${jdbc.database.driver}")
	private String driver = null;
	
	@Value("${jdbc.database.url}")
	private String url = null;
	
	@Value("${jdbc.database.username}")
	private String username = null;
	
	@Value("${jdbc.database.password}")
	private String password = null;
	
	
	public String getDriver() {
		return driver;
	}


	public void setDriver(String driver) {
		this.driver = driver;
	}


	public String getUrl() {
		return url;
	}


	public void setUrl(String url) {
		this.url = url;
	}


	public String getUsername() {
		return username;
	}


	public void setUsername(String username) {
		this.username = username;
	}


	public String getPassword() {
		return password;
	}


	public void setPassword(String password) {
		this.password = password;
	}


	@Bean(name = "dataSource1")
	public DataSource getDataSource() {
		Properties props = new Properties();
		props.setProperty("driver", driver);
		props.setProperty("url", url);
		props.setProperty("username", username);
		props.setProperty("password", password);
		DataSource dataSource = null;
		try {
			dataSource = BasicDataSourceFactory.createDataSource(props);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return dataSource;
	}
}

使用XML方式加載屬性文件

使用XML文件進行加載屬性文件,只需要使用<context:property-placeholder>元素加載一些配置項即可,例如:

<?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:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    
	<context:component-scan base-package="com.ssm.chapter10.annotation" />
	<context:property-placeholder
		ignore-resource-not-found="true" location="classpath:database-config.properties" />
</beans>

其中屬性location是一個配置屬性文件路徑的選項,它可以配置單個文件或者多個文件,多個文件之間要使用逗號分隔。如果系統中存在很多文件,那麼屬性location就要配置常常的字符串了,不過還有其他XML的方式也可以進行配置,示例如下:

<!--字符串數組,可配置多個屬性文件 --> 
	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<array>
				<value>classpath:database-config.properties</value>
				<value>classpath:log4j.properties</value>
			</array>
		</property>
		<property name="ignoreResourceNotFound" value="true" />
	</bean>

條件化裝配Bean

在某些條件下不需要去裝配Bean。這個時候就需要通過條件化去判斷。Spring提供了註解@Conditional去配置,通過它可以配置一個或者多個類,只是這些類都需要實現接口Condition(org.springframework.context.annotation.Condition)。示例如下:
先修改關於DBCP數據源的Bean:

@Bean(name="dataSource")
@Conditional({DataSourceCondition.class})
public DataSource getDataSource(
	@Value("${jdbc.database.driver}") String driver,
	@Value("${jdbc.database.url}") String url,
	@Value("${jdbc.database.username}") String username,
	@Value("${jdbc.database.password}") String password){
	Properties props = new Properties();
  	props.setProperty("driver", driver);
  	props.setProperty("url", url);
  	props.setProperty("username", username);
  	props.setProperty("password", password);
  	DataSource dataSource = null;
  	try {
   		dataSource = BasicDataSourceFactory.createDataSource(props);
  	} catch (Exception e) {
   		e.printStackTrace();
  	}
  	return dataSource;
}

這裏通過@Value往參數裏注入了對應屬性文件的配置,但是沒有辦法確定這些數據源連接池的屬性是否在屬性文件中已經配置完整,如果是不充足的屬性配置,則會引起創建失敗,爲此要判斷屬性文件的配置是否充足才能繼續創建Bean。通過@Conditional去引入了一個類——DataSourceCondition,由它來進行判斷。代碼如下:

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 DataSourceCondition implements Condition {
 @Override
 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  //獲取上下文環境
  Environment env = context.getEnvironment();
  //判斷是否存在關於數據源的基礎配置
  return env.containsProperty("jdbc.database.driver") 
    && env.containsProperty("jdbc.database.url")
    && env.containsProperty("jdbc.database.username")
    && env.containsProperty("jdbc.database.password");
 }
}

這裏要求DataSourceCondition實現接口Condition的matches方法,該方法有兩個參數,一個是ConditionContext,通過它可以獲得Spring的運行環境,一個是AnnotatedTypeMetadata,通過它可以獲得關於該Bean的註解信息。
代碼中先獲取了運行上下文環境,然後判斷在環境中屬性文件是否配置了數據庫的相關參數,如果配置了,則返回爲true,那麼Spring會去創建對應的Bean,否則是不會創建的。

Bean的作用域

在默認的情況下,Spring IoC容器只會對一個Bean創建一個實例,比如:

ApplicationContext ctx = new ClassPathXmlApplicationContext("Spring-props.xml");
RoleDataSourceService RoleService = ctx.getBean(RoleDataSourceService.class);
RoleDataSourceService RoleService2 = ctx.getBean(RoleDataSourceService.class);
System.out.println(RoleService==RoleService2);

通過類型兩次從Spring IoC容器中取出Bean,然後通過==比較,是否爲同一個對象,經測試它們是同一個對象。
有時候希望能夠通過Spring IoC容器中獲取多個實例,而這些可以通過Spring的作用域來實現。Spring提供了4種作用域,它會根據情況來決定是否生成新的對象:

  • 單例(singleton):它是默認的選項,在整個應用種,Spring只爲其生成一個Bean的實例。
  • 原型(prototype):當每次注入,或者通過Spring IoC容器獲取Bean時,Spring都會爲它創建一個新的實例。
  • 會話(session):在web應用中使用,就是在會話過程中Spring只創建一個實例
  • 請求(request):在Web應用中使用,就是在一次請求中Spring會創建一個實例,但是不同的請求會創建不同的實例。
    示例如下:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class RoleDataSourceImpl implements RoleDataSourceService{
......
}

這裏使用了註解@Scope,並且聲明爲原型。每當我們從Spring IoC容器中獲取對象,它就會生成一個新的實例。

使用Spring表達式(Spring EL)

Spring還提供了更靈活的注入方式,那就是Spring表達式。
Spring EL擁有很多功能:

  • 使用Bean的id來引用Bean
  • 調用指定對象的方法和訪問對象的屬性
  • 進行運算
  • 提供正則表達式進行匹配
  • 集合配置

Spring EL相關的類

Spring EL的相關類首先是ExpressionParser接口,它是一個表達式的解析接口,Spring會提供很多的實現類。例如:

  //表達式解析器
  ExpressionParser parser = new SpelExpressionParser();
  //設置表達式
  Expression exp = parser.parseExpression("'hello world'");
  String str = (String) exp.getValue();
  System.out.println(str);
  //通過EL訪問普通方法
  exp = parser.parseExpression("'hello world'.charAt(0)");
  char ch = (Character) exp.getValue();
  System.out.println(ch);
  //通過EL訪問的getter方法
  exp = parser.parseExpression("'hello world'.bytes");
  byte[] bytes = (byte[]) exp.getValue();
  System.out.println(bytes);
  //通過EL訪問屬性,相當於"hello world".getBytes().length
  exp = parser.parseExpression("'hello world'.bytes.length");
  int length = (Integer)exp.getValue();
  System.out.println(length);
  exp = parser.parseExpression("new String('abc')");
  String abc = (String)exp.getValue();
  System.out.println(abc);

通過表達式可以創建對象,調用對象的方法獲取屬性。Spring還支持變量的解析,只是使用變量解析的時候常常用到一個接口——EvaluationContext,它可以有效解析表達式中的變量,它也有一個實現類——StandardEvaluationContext:

  //創建角色對象
  Role role = new Role(1L, "role_name", "note");
  exp = parser.parseExpression("note");
  //相當於從role中獲取備註信息
  String note = (String) exp.getValue(role);
  System.out.println(note);
  //變量環境類,並且將角色對象role作爲其根節點
  EvaluationContext ctx = new StandardEvaluationContext(role);
  //變量環境類操作根節點
  parser.parseExpression("note").setValue(ctx, "new_note");
  //獲取備註,這裏的String.class指明,我們希望返回的是一個字符串
  note = parser.parseExpression("note").getValue(ctx, String.class);
  System.out.println(note);
  //調用getRoleName方法
  String roleName = parser.parseExpression("getRoleName()").getValue(ctx, String.class);
  System.out.println(roleName);
  //新增環境變量
  List<String> list = new ArrayList<String>();
  list.add("value1");
  list.add("value2");
  //給變量環境增加變量
  ctx.setVariable("list", list);
  //通過表達式去讀寫環境變量的值
  parser.parseExpression("#list[1]").setValue(ctx, "update_value2");
  System.out.println(parser.parseExpression("#list[1]").getValue(ctx));

EvaluationContext使用了他的實現類StandardEvaluationContext,進行了實例化,在構造方法中將角色傳遞給它了,那麼估值內容就會基於這個類進行解析。
Spring EL最重要的功能就是對Bean屬性進行注入。

Bean的屬性和方法

使用註解的方式需要用到註解@Value,在屬性文件的讀取中使用的是“$”,而在Spring EL中則使用“#”。示例代碼如下:

/*************imports*************/
@Component("role")
public class Role {
 // 賦值long型
 @Value("#{1}")
 private Long id;
 // 字符串賦值
 @Value("#{'role_name_1'}")
 private String roleName;
 // 字符串賦值
 @Value("#{'note_1'}")
 private String note;
/*********** setter and getter *********/

這樣就可以定義一個BeanName爲role的角色類了,同時給予它所有的屬性賦值,這個時候可以通過另外一個Bean去引用它的屬性或者調用它的方法。比如新建一個類:

@Component("elBean")
public class ElBean {
 
//通過beanName獲取bean,然後注入
 @Value("#{role}")
 private Role role;
 
//獲取bean的屬性id
 @Value("#{role.id}")
 private Long id;
 
//調用bean的getNote方法,獲取角色名稱
 @Value("#{role.getNote()?.toString()}")
 private String note;
 /**********setter and getter**********/

通過BeanName進行注入,也可以通過OGNL獲取其屬性或者調用其方法來注入其他的Bean中。這裏的問號(?)的含義是先判斷是否返回非null,如果不是則不再調用toString方法。

使用類的靜態變量和方法

有時候希望使用一些靜態方法和常量,比如圓周率Π,可以按照如下方式注入:

@Value("#{T(Math).PI}")
private double pi;

這裏的Math代表的是java.lang.*包下的Math類,在java代碼中使用該包是不需要使用import關鍵字引入的。如果在Spring中使用一個非該包的內容,那麼要給出該類的全限定名。如下所示:

@Value("#{T(java.lang.Math).PI}")
private double pi;

Spring EL運算

Spring EL運算,比如在EIBean上增加一個數子num,其值默認爲要求是角色編號(id)+1,可以寫成:

@Value("#{role.id+1}")
private int num;

有時候“+"號也可以用在字符串的連接上,比如:

@Value("#{role.roleName+role.note}")
private String str;

比較兩個值是否相等,數字和字符串都可以使用“eq"或者“=="進行相等比較,除此之外,還有大於、小於等數學運算。另外,還有三目運算符

@Value("#{role.id==1}")
private boolean equalNum;
@Value("#{role.note.eq 'note_1'}")
private boolean equalString;

@Value("#{role.id >2}")
private boolean greater;

@Value("#{role.id} <2")
private boolean less;

@Value("#{role.id>1?5:1}")
private int max;
@Value("#{role.note?:'hello'}")
private String defaultString;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章