自定義異常
/** * 自定義異常 */ public class SpringException extends RuntimeException {
public SpringException(String msg) {
super(msg);
}
}
|
自定義註解
// 要設置註解的生命週期是運行時,如果不設置運行時,那麼代碼在運行時註解就被去掉了,spring也就掃描不到了 @Retention(RetentionPolicy.RUNTIME) public @interface Service {
public String value();
}
|
一、使用XML向容器中裝配Bean
public class BeanFactory {
// 這裏我們簡單使用map來存儲在factory中管理的bean,在真實的spring源碼中是通過一個定義的數據結構來存儲的,因爲一個bean有很多數據需要存儲,只靠一個map是遠遠不夠的 Map<String, Object> map = new HashMap<String, Object>();
public BeanFactory(String xml) {
parseXml(xml);
}
/** * 解析XML配置文件 * * @param xml */
public void parseXml(String xml) throws SpringException {
// 生成xml文件對象
// 獲取xml文件路徑 String path = this.getClass().getResource("/").getPath() + "//" + xml;
try {
// 解決路徑中文亂碼問題 path = URLDecoder.decode(path, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
File file = new File(path);
SAXReader reader = new SAXReader();
try {
// 生成xml的DOM樹 Document document = reader.read(file);
// 獲得DOM樹上的根節點 Element elementRoot = document.getRootElement();
// 獲取根標籤中的default-autowire屬性 Attribute attribute = elementRoot.attribute("default-autowire");
// 標記是否開啓了自動裝配
boolean flag = false;
// 判斷是否有default-autowire,即是否開啓自動裝配
if (attribute != null) {
flag = true;
}
// 從根節點開始向下遍歷樹上的所有一級節點
for (Iterator<Element> it = elementRoot.elementIterator(); it.hasNext(); ) {
/** * 1、實例化對象 */
// 獲取一個標籤 Element elementFirstChil = it.next();
// 獲取這個標籤的id屬性對象 Attribute attributeId = elementFirstChil.attribute("id");
// 獲取id屬性的值 String beanName = attributeId.getValue();
// 獲取這個標籤的class屬性對象 Attribute attributeClass = elementFirstChil.attribute("class");
// 獲取class屬性的值 String clazzName = attributeClass.getValue();
// 獲取bean的class對象
Class clazz = Class.forName(clazzName);
// 生成對應bean的對象引用 Object object = null;
/** * 2、維護依賴關係 * 看這個對象有沒有依賴(判斷xml的bean標籤中是否有property這個子標籤/或者判斷類是否有屬性) * 如果有則注入 */
for (Iterator<Element> itSecond = elementFirstChil.elementIterator(); itSecond.hasNext(); ) {
/** * 得到ref的value,通過value得到要注入的對象(從map中取得) * 得到name的值,根據這個值回去一個Filed對象(Filed對象用來取得一個類中的屬性) 這裏爲了簡單默認使用set方法來進行注入,所以要取name值 * 通過filed的set方法來注入對象 */
// 取得二級子標籤很替 Element elementSecondChil = itSecond.next();
// 查看<bean>標籤的子標籤中是否有property標籤,有的話說明這個bean有依賴對象,需要用setter方法注入
if (elementSecondChil != null && "property".equals(elementSecondChil.getName())) {
// 生成對應bean的對象實例 object = clazz.newInstance();
// 獲取依賴對象的id String refValue = elementSecondChil.attribute("ref").getValue();
// 從map中獲取要注入的bean Object injectObject = map.get(refValue);
// 獲取<property>標籤的name屬性,表示的是要匹配的set方法名,但是這裏爲了簡單起見,就默認service對象dao的set方法名與service對象中UserDao屬性名一致,都是dao String nameValue = elementSecondChil.attribute("name").getValue();
// 獲取該類屬性名爲nameValue的屬性的Field對象,來進行注入 Field field = clazz.getDeclaredField(nameValue);
// 因爲這個屬性是私有的,所以要開啓暴力反射,將Accessible設置爲true field.setAccessible(true);
// 將要注入的對象注入到object中,這裏object表示的是被注入的對象,也就是service,injectObject表示的是注入的對象,也就是dao field.set(object, injectObject);
// 使用構造方法注入 } else if (elementSecondChil != null && "constructor-arg".equals(elementSecondChil.getName())){
// 獲取依賴對象的id String refValue = elementSecondChil.attribute("ref").getValue();
// 從map中獲取要注入的bean Object injectObject = map.get(refValue);
// 獲取被注入對象的屬性名 Attribute nameAttribute = elementSecondChil.attribute("name");
String nameValue = null;
if (nameAttribute != null) {
nameValue = nameAttribute.getValue();
}
// 如果XML標籤傳入了name屬性就直接通過注入對象的Field類獲取注入對象的Class
if (nameValue != null) {
// 獲取該類屬性名爲nameValue的屬性的Field對象 Field field = clazz.getDeclaredField(nameValue);
// 直接通過Field對象獲取注入對象的Class Class injectObjectClazz = field.getType();
// 通過被注入對象的Class獲取構造方法對象,並將注入對象的Class傳入,這樣就可以用這個參數是注入對象的構造方法對象來創建注入dao的service對象 Constructor constructor = clazz.getConstructor(injectObjectClazz);
// 將注入對象dao傳入構造方法中並且生成service的實例對象 object = constructor.newInstance(injectObject);
// 如果XML標籤沒有傳入name屬性就通過ref屬性指定的在spring容器中的bean獲取注入對象的Class } else {
// 通過spring容器中的bean獲取注入對象的Class Class injectObjectClazz = injectObject.getClass();
// 由於在spring容器中的bean的類型是UserDaoImpl,而被注入對象中屬性的類型是接口UserDao,所以會出現兩者類型不一致,無法獲取正確的構造方法的情況(構造方法參數類型是UserDao) Constructor constructor = clazz.getConstructor(injectObjectClazz.getInterfaces()[0]);
// 將注入對象dao傳入構造方法中並且生成service的實例對象 object = constructor.newInstance(injectObject);
}
}
}
/** * 使用自動裝配 * 以前不使用自動裝配,spring通過掃描XML來獲取bean之間的依賴關係,讀取完XML後會去代碼中掃描屬性,進一步確認依賴關係,如果XML定義了依賴關係,但是代碼中並沒有這種依賴關係,就會拋出異常,上面我們模擬的過程沒有寫拋出異常的部分,讀者可以自行添加 * * byType:直接通過代碼中定義的依賴關係來進行自動裝配,如果被注入對象中有注入對象這個屬性,那麼就說明他們之間有依賴關係,通過Fidld來獲得注入對象的Class對象,再通過反射實現注入 */
// 需要在掃描完上面的所有子標籤之後再判斷是不是開啓了自動裝配,因爲spring手動裝配的優先級高於自動裝配,如果上面發現bean標籤裏有子標籤已經完成了手動裝配,我們就不需要再執行後面的自動裝配邏輯了,這裏的判斷標準就是被注入對象object是否已經被實例化
if (object == null && flag) {
// 使用byType方式自動裝配
if ("byType".equals(attribute.getValue())) {
// 判斷是否有依賴
// 獲取bean中的所有屬性的Field對象 Field fields[] = clazz.getDeclaredFields();
// 遍歷所有的屬性,查找有沒有和spring容器中的bean類型一樣的
for (Field field : fields) {
// 得到屬性類型 Class injectObjectClazz = field.getType();
/** * 由於是byType 所以需要遍歷map中的所有對象 * 判斷對象的類型是不是和這個injectObjectClazz一致 */
// 記錄spring容器中和注入對象類型匹配的個數
int count = 0;
Object injectObject = null;
for (String key : map.keySet()) {
// 獲取spring容器中bean的類型,這裏爲了我們樣例代碼演示方便就是獲取的接口 Class temp = map.get(key).getClass().getInterfaces()[0];
if (temp.getName().equals(injectObjectClazz.getName())) {
injectObject = map.get(key);
// 記錄類型一直個數 count++;
}
}
if (count > 1) {
throw new SpringException("需要一個bean,但是在容器中存在" + count + "個符合條件的bean");
} else {
// 創建被注入對象 object = clazz.newInstance();
// 因爲是私有屬性,所以使用field.set注入需要開啓暴力注入 field.setAccessible(true);
// 實現注入 field.set(object, injectObject);
}
}
}
}
// 沒有子標籤的情況需要單獨給bean實例化
if (object == null) {
object = clazz.newInstance();
}
// 將生成的bean實例對象存入到map中 map.put(beanName, object);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(map);
}
public Object getBean(String beanName) {
return map.get(beanName);
}
}
|
測試使用的三套XML配置文件
<?xml version="1.0" encoding="UTF-8"?> <!-- 1、哪些類需要我來管理 2、怎麼告訴我這些類(bean標籤) 3、怎麼維護依賴關係(setter方法或構造方法) 4、怎麼體現setter或者構造方法(使用property或constructor內嵌標籤)
spring就是通過bean標籤來完成上面兩個操作的 --> <beans>
<!--
下面面的XML配置文件的含義就是我們將UserDaoImpl和UserServiceImpl裝配到spring容器中,將他們的id分別設置成dao和service service的依賴dao,並且通過相應的setter方法/構造方法注入 -->
<!--告訴sping容器UserDaoImpl需要它來管理-->
<bean id="dao" class="priv.priv.cy.dao.UserDaoImpl"></bean>
<!--告訴sping容器UserServiceImpl需要它來管理-->
<bean id="service" class="priv.priv.cy.service.UserServiceImpl">
<!--注入dao對象的前提就是dao對象已經裝配給spring容器去管理,所以要先寫一個dao對象的bean-->
<!--注入方法在setter和constructor之中選一個就行,不過要在代碼中把需要的構造方法或者setter方法編寫出來-->
<!--
表示使用setter方法完成注入 property標籤表示使用setter方法完成注意,裏面有兩個屬性,name和ref name:表示的是setter方法的名字,name屬性的值會去匹配將setter方法中的前綴set去掉之後,再將剩下的首字母小寫得到的方法名,如果找到名字一致的注入操作就是由這個setter方法
因爲我們一般就是用默認生成的stter方法名,所以這個name標籤也可以不加,只是爲了防止有需要修改setter方法名時,就需要特別指定一下setter方法了 ref:這個指定的是要將哪個bean注入給service,後面寫的是spring容器中bean的id
所以下面這句話的意思就是我們要將spring容器中id爲dao的bean,通過service對象中一個名爲dao的stter方法將其注入給service對象 -->
<property name="dao" ref="dao"></property>
</bean> </beans>
|
<?xml version="1.0" encoding="UTF-8"?> <!-- 1、哪些類需要我來管理 2、怎麼告訴我這些類(bean標籤) 3、怎麼維護依賴關係(setter方法或構造方法) 4、怎麼體現setter或者構造方法(使用property或constructor內嵌標籤)
spring就是通過bean標籤來完成上面兩個操作的 --> <beans>
<!--
下面面的XML配置文件的含義就是我們將UserDaoImpl和UserServiceImpl裝配到spring容器中,將他們的id分別設置成dao和service service的依賴dao,並且通過相應的setter方法/構造方法注入 -->
<!--告訴sping容器UserDaoImpl需要它來管理-->
<bean id="dao" class="priv.priv.cy.dao.UserDaoImpl"></bean>
<!--告訴sping容器UserServiceImpl需要它來管理-->
<bean id="service" class="priv.priv.cy.service.UserServiceImpl">
<!--注入dao對象的前提就是dao對象已經裝配給spring容器去管理,所以要先寫一個dao對象的bean-->
<!--注入方法在setter和constructor之中選一個就行,不過要在代碼中把需要的構造方法或者setter方法編寫出來-->
<!--
使用構造方法注入 name:表示的是被注入的對象中所依賴的這個對象的屬性名,不是構造方法傳入參數的屬性名。也就是service對象中的UserDao類型的屬性名爲dao。這個name也可以不寫,spring會自動找到構造方法去進行注入 ref:表示的是bean的id,也就是將spring容器中id爲dao的這個bean注入到service中
下面這段代碼的意思就是將spring容器中id爲dao的這個bean通過構造方法注入到service中屬性名爲dao的這個屬性 -->
<constructor-arg name="dao" ref="dao"></constructor-arg>
</bean> </beans>
|
<?xml version="1.0" encoding="UTF-8"?> <!-- 1、哪些類需要我來管理 2、怎麼告訴我這些類(bean標籤) 3、怎麼維護依賴關係(setter方法或構造方法) 4、怎麼體現setter或者構造方法(使用property或constructor內嵌標籤)
spring就是通過bean標籤來完成上面兩個操作的 --> <beans default-autowire="byType">
<!--
下法注面面的XML配置文件的含義就是我們將UserDaoImpl和UserServiceImpl裝配到spring容器中,將他們的id分別設置成dao和service
並且實現自動裝配(byName/byType) -->
<!--
通過byType實現自動裝配
這裏需要注意的就是byType自動裝配是通過Field類的set方法反射注入的,不依賴setter方法和構造方法,即使沒有setter方法和構造方法依舊可以成功注入 -->
<!--告訴sping容器UserDaoImpl需要它來管理-->
<bean id="dao" class="priv.priv.cy.dao.UserDaoImpl"></bean>
<!--告訴sping容器TestDaoImpl需要它來管理-->
<bean id="test" class="priv.priv.cy.dao.TestDaoImpl"></bean>
<!--告訴sping容器UserDaoImpl1需要它來管理--> <!-- <bean id="dao1" class="priv.priv.cy.dao.UserDaoImpl1"></bean>-->
<!--告訴sping容器UserServiceImpl需要它來管理-->
<bean id="service" class="priv.priv.cy.service.UserServiceImpl">
<!--手動裝配優先於自動裝配--> <!-- <property name="dao" ref="dao"></property>-->
</bean> </beans>
|
二、使用註解向容器中裝配Bean
package org.spring.util;
import org.spring.annotation.Service;
import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLDecoder;
/** * 實現對註解的掃描,使用註解將bean裝配到spring容器 */ public class AnnotationConfigApplicationContext {
/** * 真正的AnnotationConfigApplicationContext也有scan這個方法,方法原型和作用我們下面模擬的這個方法作用是一致的。 * @param basePackage */
public void scan(String basePackage) {
// 獲得classpath路徑,通過這個路徑獲取類的全限定名,這樣纔可以將bean實例化
// 下面這個是動態獲取classpath路徑,因爲項目部署到不同的服務器,它的classpath路徑是會變化的 String rootPath = this.getClass().getResource("/").getPath();
// 將包路徑轉換成文件路徑
// 將一個.換成\ .和\是一個轉義字符 String basePackagePath = basePackage.replaceAll("\\.", "\\\\");
// 將classpath路徑和傳入的包路徑拼接起來就得到了要掃描的class文件所在路徑 String path = rootPath + "//" + basePackagePath;
try {
// 解決路徑中文亂碼問題 path = URLDecoder.decode(path, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
File file = new File(path);
// 獲取當前路徑的所有文件名 這個是通過遞歸的方法,將這個路徑下面所有的文件名都取出來,不取文件夾名 String[] names = file.list();
for (String name : names) {
name = name.replaceAll(".class", "");
try {
// 獲取要交給容器的bean的Class對象,用來實例化 Class clazz = Class.forName(basePackage + "." + name);
// 如果是模擬@Autowired使用byType的話,就利用反射通過clazz將他的所有的屬性field對象取出,然後通過field獲取屬性的Type,查看spring容器中有沒有符合條件的bean,有的話就取出注入,基本過程和模擬xml的一樣
// 判斷該類上的註解類型,這裏只演示@Service
if (clazz.isAnnotationPresent(Service.class)) {
// 獲取註解的對象 Service service = (Service) clazz.getAnnotation(Service.class);
// 取得註解中的value,用來給bean設置name System.out.println(service.value());
// 後面就是將bean實例化放入factory中去,前面對xml方式的模擬已經有了,這裏就不再寫了,直接實例化輸出驗證 System.out.println(clazz.newInstance());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
|
其他文章:【Spring】史上最全的spring IoC使用講解
【Spring】面試官,請別再問Spring Bean的生命週期了!