2014-03-10 Spring的學習(2)------依賴注入和自動掃描Bean

1.依賴注入(DI)的概念

  所謂的依賴注入是指在運行期,由外部容器將依賴對象注入到組件中.依賴注入(DI)背後的基本原理是對象之間的依賴關係(即一塊兒工做的其它對象).例如:Service業務層依賴Dao的提供的對象來實現業務邏輯,若是使用依賴注入技術的話,代碼將更加清晰.並且當bean本身再也不擔憂對象之間的依賴關係(甚至不知道依賴的定義指定地方和依賴的實際類)以後,實現更高層次的鬆耦合將易如反掌。DI主要有兩種注入方式,即Setter注入構造器注入.
html

2.手動裝配--->注入依賴對象(所有基於.XML文件)

  2.1 Setter注入

    2.1.1 Setter注入的簡單案例java

  經過調用無參構造器或無參static工廠方法實例化bean以後,調用該bean的setter方法,便可實現基於setter的DI。mysql

PersonDao.java
package cn.itcast.dao;
public interface PersonDao {
	public abstract void add();
}

PersonDaoImpl.java
package cn.itcast.dao.impl;
import cn.itcast.dao.PersonDao;
public class PersonDaoImpl implements PersonDao {
	/* (non-Javadoc)
	 * @see cn.itcast.dao.impl.PersonDao#add()
	 */
	@Override
	public void add(){
	     System.out.println("This is a PersonDaoImpl.add() itcast method");
	}
}

PersonService.java
package cn.itcast.service;
public interface PersonService {
	public abstract void add();
}

PersonServiceImpl.java
package cn.itcast.service.impl;
import cn.itcast.dao.PersonDao;
import cn.itcast.service.PersonService;
public class PersonServiceImpl implements PersonService {
	private PersonDao personDao;
	public void add(){
		System.out.println("This is a add() method");
		personDao.add();
	}
	/**必須有set方法**/
	public void setPersonDao(PersonDao personDao) {
		this.personDao = personDao;
	}
}

 

<?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-2.5.xsd">
       <!-- Dao層對象注入Service層 :依賴注入 inject-->
       <!-- 正如你所看到的,bean類中的setter方法與xml文件中配置的屬性是一一對應的 !-->
       <bean id="personDao" class="cn.itcast.dao.impl.PersonDaoImpl"/>
        <bean id="personService" class="cn.itcast.service.impl.PersonServiceImpl">
            <property name="personDao" ref="personDao"></property>
        </bean>
</beans>

測試結果:This is a add() method
        This is a PersonDaoImpl.add() itcast method

    2.1.1 編碼實現Setter注入功能的代碼spring

  

BeanDefinition.java
package cn.itcast.mycontext;

import java.util.ArrayList;
import java.util.List;

public class BeanDefinition {
	private String id;
	private String className;
        List<PopertyDefinition> popertys = new ArrayList<PopertyDefinition>();
	
	public List<PopertyDefinition> getPopertys() {
		return popertys;
	}
	public void setPopertys(List<PopertyDefinition> popertys) {
		this.popertys = popertys;
	}
	public BeanDefinition(String id, String clazz) {
		this.id=id;
		this.className = clazz;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getClassName() {
		return className;
	}
	public void setClassName(String className) {
		this.className = className;
	}
}

  

PopertyDefinition.java
package cn.itcast.mycontext;
public class PopertyDefinition {
	
	private String name;  
	private String ref;
	
	public PopertyDefinition(String name, String ref) {
		this.name = name;
		this.ref = ref;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getRef() {
		return ref;
	}
	public void setRef(String ref) {
		this.ref = ref;
	}
}

MyClassPathXmlApplicationContext.java
package cn.itcast.mycontext;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;
public class MyClassPathXmlApplicationContext {
	private List<BeanDefinition> BeanDefinitions = new ArrayList<BeanDefinition>();
	/**Key:id   Value:instaceBean **/
	private Map<String,Object> sigletons = new HashMap<String,Object>();
	public MyClassPathXmlApplicationContext(String filename){
		this.readXml(filename);
		this.instaceBeans();
		this.injectBeans();
	}

	/**
	 * 注入Beans
	 */
	private void injectBeans() {
		for(BeanDefinition beanDefinition:BeanDefinitions){
			Object bean = sigletons.get(beanDefinition.getId());
			if(bean!=null){
				PropertyDescriptor[] pds;
				try {
					pds = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
					for(PopertyDefinition popertyDefinition:beanDefinition.getPopertys()){
						for(PropertyDescriptor pd:pds){
							if(popertyDefinition.getName().equals(pd.getName())){
								Method setter = pd.getWriteMethod();
								if(setter!=null){
									Object value = sigletons.get(popertyDefinition.getRef());
									setter.setAccessible(true);
									setter.invoke(bean, value);
								}
								break;
							}
						}
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 實例化Bean
	 */
	private void instaceBeans() {
		for(BeanDefinition beanDefinition:BeanDefinitions){
			if(beanDefinition.getClassName()!=null && beanDefinition.getClassName().trim()!=""){
			  try {
				sigletons.put(beanDefinition.getId(), Class.forName(beanDefinition.getClassName()).newInstance());
			} catch (InstantiationException | IllegalAccessException
					| ClassNotFoundException e) {
				e.printStackTrace();
			}
			}
		}
	}

	/**
	 * 讀取配置Bean的XML文件
	 * dom4j-1.6.1.jar,jaxen-1.1-beta-6.jar
	 * @param filename
	 */
	private void readXml(String filename) {
		SAXReader reader = new SAXReader();
		Document document = null;
		try{
			URL url = this.getClass().getClassLoader().getResource(filename);
			document = reader.read(url);
			Map<String,String> nsMap = new HashMap<String,String>();
			nsMap.put("ns","http://www.springframework.org/schema/beans");
			XPath xsub =  document.createXPath("//ns:beans/ns:bean");
			xsub.setNamespaceURIs(nsMap);
			@SuppressWarnings("unchecked")
			List<Element> elements = xsub.selectNodes(document);
			for(Element element : elements){
				String id = element.attributeValue("id");
				String clazz = element.attributeValue("class");
				BeanDefinition beanDfine = new BeanDefinition(id,clazz);
				xsub =  document.createXPath("ns:property");
				xsub.setNamespaceURIs(nsMap);
				@SuppressWarnings("unchecked")
				List<Element> propertys = xsub.selectNodes(element);
				for(Element property:propertys){
					String propertyName =  property.attributeValue("name");
					String propertyRef = property.attributeValue("ref");
					PopertyDefinition propertyDefinition = new PopertyDefinition(propertyName, propertyRef);
					beanDfine.getPopertys().add(propertyDefinition);
				}
				BeanDefinitions.add(beanDfine);
			}
		}catch (Exception e) {
			e.printStackTrace();
		}
	}
	/**
	 * 獲取Bean實例
	 * @param beanName
	 * @return
	 */
	public Object getBean(String beanName){
		return this.sigletons.get(beanName);
	}
}

  2.2 構造器注入

     基於構造器的DI經過調用帶參數的構造器來實現,每一個參數表明着一個依賴。sql

修改PersonServiceImpl.java
package cn.itcast.service.impl;
import cn.itcast.dao.PersonDao;
import cn.itcast.service.PersonService;
public class PersonServiceImpl implements PersonService {
	private PersonDao personDao;
	public PersonServiceImpl(PersonDao personDao){
		this.personDao = personDao;
	}
	public void add(){
		System.out.println("This is a add() method");
		personDao.add();
	}
}

<?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-2.5.xsd">
       <!-- 第一種方式 
       <bean id="personDao" class="cn.itcast.dao.impl.PersonDaoImpl"/>
       <bean id="personService" class="cn.itcast.service.impl.PersonServiceImpl">
            <constructor-arg>
              <bean class="cn.itcast.dao.impl.PersonDaoImpl"></bean>
            </constructor-arg>
        </bean>
         ---->
        <!-- 第二種方式  ---->
        <bean id="personDao" class="cn.itcast.dao.impl.PersonDaoImpl"/>
       <bean id="personService" class="cn.itcast.service.impl.PersonServiceImpl">
            <constructor-arg index="0" type="cn.itcast.dao.PersonDao" ref="personDao"/>
        </bean>
</beans>

//output:
This is a add() method
This is a PersonDaoImpl.add() itcast method
//~

  2.3 Setter和構造器注入其餘類型配置詳解

         

2.1.1 直接變量(基本類型、Strings類型等。)

<value/>元素經過人能夠理解的字符串來指定屬性或構造器參數的值。正如前面所提到的,JavaBean PropertyEditor將用於把字符串從java.lang.String類型轉化爲實際的屬性或參數類型。apache

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">  
  <!-- results in a setDriverClassName(String) call -->
  <property name="driverClassName">
    <value>com.mysql.jdbc.Driver</value>
  </property>
  <property name="url">
    <value>jdbc:mysql://localhost:3306/mydb</value>
  </property>
  <property name="username">
    <value>root</value>
  </property>
  <property name="password">
    <value>masterkaoli</value>
  </property>
</bean>

<property/><constructor-arg/> 元素中也可使用'value' 屬性,這樣會使咱們的配置更簡潔,好比下面的配置:api

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">  
  <!-- results in a setDriverClassName(String) call -->
  <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
  <property name="username" value="root"/>
  <property name="password" value="masterkaoli"/>
</bean>

Spring團隊更傾向採用屬性方式(使用<value/>元素)來定義value值。固然咱們也能夠按照下面這種方式配置一個java.util.Properties實例:app

<bean id="mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">            
   <!-- typed as a java.util.Properties -->
   <property name="properties">
      <value>
         jdbc.driver.className=com.mysql.jdbc.Driver
         jdbc.url=jdbc:mysql://localhost:3306/mydb
      </value>
   </property>
</bean>

看到什麼了嗎?若是採用上面的配置,Spring容器將使用JavaBean PropertyEditor<value/>元素中的文本轉換爲一個java.util.Properties實例。因爲這種作法的簡單,所以Spring團隊在不少地方也會採用內嵌的<value/>元素來代替value屬性。dom

 

2.1.2 引用其它的bean(協做者)

<constructor-arg/><property/>元素內部還可使用ref元素。該元素用來將bean中指定屬性的值設置爲對容器中的另一個bean的引用。如前所述,該引用bean將被做爲依賴注入,並且在注入以前會被初始化(若是是singleton  bean則已被容器初始化)。儘管都是對另一個對象的引用,可是經過id/name指向另一個對象卻有三種不一樣的形式,不一樣的形式將決定如何處理做用域及驗證。ide

第一種形式也是最多見的形式是經過使用<ref/>標記指定bean屬性的目標bean,經過該標籤能夠引用同一容器或父容器內的任何bean(不管是否在同一XML文件中)。XML  'bean'元素的值既能夠是指定bean的id值也能夠是其name值。

<ref bean="someBean"/>

第二種形式是使用ref的local屬性指定目標bean,它能夠利用XML解析器來驗證所引用的bean是否存在同一文件中。local屬性值必須是目標bean的id屬性值。若是在同一配置文件中沒有找到引用的bean,XML解析器將拋出一個例外。若是目標bean是在同一文件內,使用local方式就是最好的選擇(爲了儘早地發現錯誤)。

<ref local="someBean"/>

第三種方式是經過使用ref的parent屬性來引用當前容器的父容器中的bean。parent屬性值既能夠是目標bean的id值,也能夠是name屬性值。並且目標bean必須在當前容器的父容器中。使用parent屬性的主要用途是爲了用某個與父容器中的bean同名的代理來包裝父容器中的一個bean(例如,子上下文中的一個bean定義覆蓋了他的父bean)。

<!-- in the parent context --> 
<bean id="accountService" class="com.foo.SimpleAccountService">     
<!-- insert dependencies as required as here -->
 </bean>

<!-- in the child (descendant) context -->
<bean id="accountService"  <-- notice that the name of this bean is the same as the name of the'parent' bean     
 class="org.springframework.aop.framework.ProxyFactoryBean">      
 <property name="target">          
    <ref parent="accountService"/>  <-- notice how we refer to the parent bean      
 </property>   
 <!-- insert other configuration and dependencies as required as here -->
 </bean>

   

2.1.3. 內部bean

所謂的內部bean(inner bean)是指在一個bean的<property/>或  <constructor-arg/>元素中使用<bean/>元素定義的bean。內部bean定義不須要有id或name屬性,即便指定id 或  name屬性值也將會被容器忽略。

<bean id="outer" class="...">  
  <!-- instead of using a reference to a target bean, simply define the target bean inline -->      <property name="target">     
         <bean class="com.example.Person"> 
             <!-- this is the inner bean -->       
              <property name="name" value="Fiona Apple"/>       
              <property name="age" value="25"/>     
          </bean>   
      </property> 
 </bean>

注意:內部bean中的scope標記及idname屬性將被忽略。內部bean老是匿名的且它們老是prototype模式的。同時將內部bean注入到包含該內部bean以外的bean是可能的。

2.1.4. 集合

經過<list/><set/><map/><props/>元素能夠定義和設置與Java Collection類型對應ListSetMapProperties的值。

<bean id="moreComplexObject" class="example.ComplexObject">  
   <!-- results in a setAdminEmails(java.util.Properties) call -->
  <property name="adminEmails">
    <props>
        <prop key="administrator">administrator@example.org</prop>
        <prop key="support">support@example.org</prop>
        <prop key="development">development@example.org</prop>
    </props>
  </property>
  <!-- results in a setSomeList(java.util.List) call -->
  <property name="someList">
    <list>
        <value>a list element followed by a reference</value>
        <ref bean="myDataSource" />
    </list>
  </property>  <!-- results in a setSomeMap(java.util.Map) call -->
  <property name="someMap">
    <map>
        <entry>
            <key>
                <value>an entry</value>
            </key>
            <value>just some string</value>
        </entry>
        <entry>
            <key>
                <value>a ref</value>
            </key>
            <ref bean="myDataSource" />
        </entry>
    </map>
  </property>  <!-- results in a setSomeSet(java.util.Set) call -->
  <property name="someSet">
    <set>
        <value>just some string</value>
        <ref bean="myDataSource" />
    </set>
  </property>
</bean>

注意:map的key或value值,或set的value值還能夠是如下元素:

bean | ref | idref | list | set | map | props | value | null

 

3.手動裝配--->注入依賴對象(基於.XML文件和註解)

    基於註解的配置方式,使用BeanPostProcessor與註解是 Spring IoC 容器的一個普通擴展方法。例如,Spring 2.0 對必須的屬性引入了@Required註解。在 Spring 2.5中已經能夠用註解的方式去驅動 Spring 的依賴注射了。更重要的是,@Autowired註解提供功能,而且提供了更細緻的控制與更好的適應性。Spring 2.5 也支持 JSR-250 中的一些註解,例如@Resource @PostConstruct,以及@PreDestroy。固然,要使註解可用,您必須使用 Java 5 (Tiger)或更新的版本,以使得能夠訪問源代碼層次的註解。這些註解能夠被註冊爲獨立 bean 的定義,但它們也能夠被隱式地註冊,經過基於 XML 的配置方式,以下例(請注意包含 'context' 命名空間):

<?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-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd">              
     <context:annotation-config/>
</beans>

  PS:還可以解決配置文件中的字段過多的問題.減小配置文件的壓力.

   3.1 @Resource  (common-annotations.jar)

Spring 也提供了使用 JSR-250 bean 屬性支持的注射方式。這是一種在 Java EE 5 與 Java 6 中廣泛使用的方式(例如,在 JSF 1.2 中映射 beans 或者 JAX-WS 2.0 端點),對於Spring 託管的對象 Spring 能夠以這種方式支持映射。

@Resource 有一個‘name’屬性,缺省時,Spring 將這個值解釋爲要注射的 bean 的名字。換句話說,若是遵循by-name的語法,以下例:

public class SimpleMovieLister {

    private MovieFinder movieFinder;    
    @Resource(name="myMovieFinder")
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

若是沒有顯式地給出名字,缺省的名字將繼承於字段名或者 setter 方法名:若是是字段名,它將簡化或者等價於字段名;若是是 setter 方法名,它將等價於 bean 屬性名。下面這個例子使用名字 "movieFinder" 注射到它的 setter 方法:

public class SimpleMovieLister {

    private MovieFinder movieFinder;    
    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

<?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-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd">              
     <context:annotation-config/>
     <bean id="movieFinder" class="MovieFinder"/>
     <bean id="simpleMovieLister " class="SimpleMovieLister "/>
</beans>

注意:註解提供的名字將被BeanFactory解析爲 bean 名。請注意,這些名字也可能經過 JNDI 被解析(須要配置 Spring 的SimpleJndiBeanFactory)。不過,建議您依靠缺省行爲與 Spring 的 JNDI 查找功能。

@Autowired相似,@Resource 能夠回退爲與標準 bean 類型匹配(例如,使用原始類型匹配取代特殊命名 bean)來解決著名的"resolvable dependencies":BeanFactory 接口,ApplicationContext 接口,ResourceLoader 接口,ApplicationEventPublisher 接口以及 MessageSource 接口。請注意:這隻有適用於未指定命名的@Resource

下面的例子有一個customerPreferenceDao字段,首先要查找一個名叫 「customerPreferenceDao」 的 bean,而後回退爲一個原始類型以匹配類型CustomerPreferenceDao。"context" 字段將基於已知解決的依賴類型ApplicationContext而被注入。

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;

    public MovieRecommender() {
    }    // ...}

   3.2 @Autowired

          參考開發文檔基於註解(Annotation-based)的配置--->@Autowired

     3.3 本身編碼實現Spring註解功能注入對象

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.FIELD, ElementType.METHOD})
public @interface ItcastResource {
	public String name() default "";
}

修改MyClassPathXMLApplicationContext.java
public MyClassPathXMLApplicationContext(String filename){
		this.readXML(filename);
		this.instanceBeans();
		this.annotationInject();
		this.injectObject();
	}
	
	private void annotationInject() {
		for(String beanName : sigletons.keySet()){
			Object bean = sigletons.get(beanName);
			if(bean!=null){
				try {
					PropertyDescriptor[] ps = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
					for(PropertyDescriptor properdesc : ps){
						Method setter = properdesc.getWriteMethod(); 
						if(setter!=null && setter.isAnnotationPresent(ItcastResource.class)){
							ItcastResource resource = setter.getAnnotation(ItcastResource.class);
							Object value = null;
							if(resource.name()!=null && !"".equals(resource.name())){
								value = sigletons.get(resource.name());
							}else{
								value = sigletons.get(properdesc.getName());
								if(value==null){
									for(String key : sigletons.keySet()){
										if(properdesc.getPropertyType().isAssignableFrom(sigletons.get(key).getClass())){
											value = sigletons.get(key);
											break;
										}
									}
								}								
							}
							setter.setAccessible(true);
							setter.invoke(bean, value); 
						}
					}
					Field[] fields = bean.getClass().getDeclaredFields();
					for(Field field : fields){
						if(field.isAnnotationPresent(ItcastResource.class)){
							ItcastResource resource = field.getAnnotation(ItcastResource.class);
							Object value = null;
							if(resource.name()!=null && !"".equals(resource.name())){
								value = sigletons.get(resource.name());
							}else{
								value = sigletons.get(field.getName());
								if(value==null){
									for(String key : sigletons.keySet()){
										if(field.getType().isAssignableFrom(sigletons.get(key).getClass())){
											value = sigletons.get(key);
											break;
										}
									}
								}								
							}
							field.setAccessible(true); 
							field.set(bean, value);
						}
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

   

4.自動裝配

5.對受管組件的Classpath自動掃描

<?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-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd">
     <context:component-scan base-package="cn.itcast"/>  
</beans>

 PS:Spring 2.5引入了更多典型化註解(stereotype annotations): @Component@Service @Controller@Component是全部受Spring管理組件的通用形式; 而@Repository@Service @Controller則是@Component的細化, 用來表示更具體的用例(例如,分別對應了持久化層、服務層和表現層)。也就是說, 你能用@Component來註解你的組件類, 但若是用@Repository@Service @Controller來註解它們,你的類也許能更好地被工具處理,或與切面進行關聯。 例如,這些典型化註解能夠成爲理想的切入點目標。固然,在Spring Framework之後的版本中, @Repository@Service @Controller也許還能攜帶更多語義。如此一來,若是你正在考慮服務層中是該用 @Component仍是@Service , 那@Service 顯然是更好的選擇。一樣的,就像前面說的那樣, @Repository已經能在持久化層中進行異常轉換時被做爲標記使用了。

 

 @Component
 public class Componet{
 }
 @Service
 public class Service{
 }
 @Controller
 public class Action{
 }
 @Repository
 public class Dao{
 }
相關文章
相關標籤/搜索