所謂的依賴注入是指在運行期,由外部容器將依賴對象注入到組件中.依賴注入(DI)背後的基本原理是對象之間的依賴關係(即一塊兒工做的其它對象).例如:Service業務層依賴Dao的提供的對象來實現業務邏輯,若是使用依賴注入技術的話,代碼將更加清晰.並且當bean本身再也不擔憂對象之間的依賴關係(甚至不知道依賴的定義指定地方和依賴的實際類)以後,實現更高層次的鬆耦合將易如反掌。DI主要有兩種注入方式,即Setter注入和構造器注入.
html
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); } }
基於構造器的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 //~
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
在<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>
所謂的內部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
標記及id
或name
屬性將被忽略。內部bean老是匿名的且它們老是prototype模式的。同時將內部bean注入到包含該內部bean以外的bean是不可能的。
經過<list/>
、<set/>
、<map/>
及<props/>
元素能夠定義和設置與Java Collection
類型對應List
、Set
、Map
及Properties
的值。
<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
基於註解的配置方式,使用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:還可以解決配置文件中的字段過多的問題.減小配置文件的壓力.
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() { } // ...}
參考開發文檔基於註解(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(); } } } }
<?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{ }