造一個(gè)方形輪子文章目錄:造一個(gè)方形的輪子
01澄干、先把車正過(guò)來(lái)
在上一篇《造一個(gè)方形的輪子3--控制反轉(zhuǎn)》的最后提出了一個(gè)問(wèn)題,如果同一個(gè)接口有一個(gè)以上的實(shí)現(xiàn)類,那么在初始化的時(shí)候肯夏,實(shí)現(xiàn)相同接口的BeanObject對(duì)象,后一個(gè)放入Map容器中時(shí)會(huì)把前邊的覆蓋掉桥状,這樣肯定有問(wèn)題,簡(jiǎn)單處理一下硝清,在BeanObject類中添加一個(gè)next指針引用辅斟,把他改造成一個(gè)可以支持鏈表的形式。
BeanObject.java 添加:
//......上略
/**
* 使用鏈表處理同一接口多個(gè)實(shí)現(xiàn)類的情況
*/
private BeanObject next;
public BeanObject getNext() {
return next;
}
public void setNext(BeanObject next) {
this.next = next;
}
//......下略
在原來(lái)的BeansInitUtil.loadClass()方法里修改以下代碼:
//......上略
beanObject.setPackages(clzz.getPackage().toString());
Object obj = clzz.newInstance();
beanObject.setObject(obj);
// 按接口設(shè)置bean
for (Class aClass : beanObject.getInterfacs()) {
BeanObject tmp = map.get(aClass.getName());
if(tmp != null){
beanObject.setNext(tmp);
}
map.put(aClass.getName(), beanObject);
}
// 按類設(shè)置bean
map.put(beanObject.getClassName(), beanObject);
//......下略
可以看到在按接口全限定名保存容器時(shí)芦拿,由原來(lái)的直接保存改為先查詢士飒,如果原來(lái)有值(tmp不為空)說(shuō)明已有其它實(shí)現(xiàn)類,被初始化蔗崎,那么將當(dāng)然beanObject的next指針指向原來(lái)保存的值就可以了(為什么要將當(dāng)前beanObject的next指針指向已存在的tmp對(duì)象酵幕,而不是將tmp的next指針指向beanObject? )。
02缓苛、依賴注入準(zhǔn)備
到目前為止bean的實(shí)例化已經(jīng)完成了芳撒,現(xiàn)在把依賴注入的過(guò)程大概理一下:
1、找到每個(gè)bean中添加的注入注解的字段
2、按類型笔刹、接口芥备、名稱去容器中查找Bean實(shí)例
3、如果找到了且為一舌菜,則使用反射設(shè)值
4萌壳、如果沒(méi)有找到或者找到多個(gè)沒(méi)法確定使用哪一個(gè),則拋出異常(啟動(dòng)失斎赵隆)
好袱瓮,先來(lái)實(shí)現(xiàn)第一步,改造一下BeanObject 把字段也保存起來(lái)爱咬,好在DI階段使用尺借,注解的這里就不自己實(shí)現(xiàn)了直接使用Java提供的@Resource
, @Resource
注解有一個(gè)name的字段,我們把他定義為指定的bean名字台颠。
BeanObject.java 添加字段屬性:
//.....
/**
* 字段數(shù)組
*/
private Field[] fields;
public Field[] getFields() {
return fields;
}
public void setFields(Field[] fields) {
this.fields = fields;
}
03、處理依賴注入
在BeansInitUtil.java中添加initDI()方法負(fù)責(zé)處理依賴注入:
//....上略
/**
* 處理關(guān)系依賴
* @param map Bean容器
*/
private static void initDI(Map<String, BeanObject> map){
// 循環(huán)所有Bean處理依賴
for(Map.Entry entry : map.entrySet()){
BeanObject beanObject = (BeanObject)entry.getValue();
// 先判斷是否有Resource注解
for (Field field : beanObject.getFields()) {
if(filterFieldAnnotation(field.getAnnotations())){
String name = getResourceName(field.getAnnotations());
BeanObject bean = null;
// 有指定bean名字按指定去取
if(name != null && !name.equals("")){
bean = map.get(firstToLowerCase(name));
} else {
// 沒(méi)有指定按接口(如果有的話)或類型去取
Class fieldClass = field.getType();
bean = map.get(fieldClass.getName());
// 如果有next說(shuō)明是有多個(gè)實(shí)現(xiàn)的接口,則要判斷名字
if(bean != null && bean.getNext() != null){
String fieldName = field.getName();
while(bean != null){
if(firstToLowerCase(bean.getSimpleName()).equals(fieldName)){
break;
}
bean = bean.getNext();
}
if(bean == null){
// 多于兩個(gè)匹配的bean異常
log.error("無(wú)法確定的Bean依賴谨读,field:{}, 存在多個(gè)依賴劳殖!", beanObject.getClassName()+"."+fieldName);
throw new SquareBeanInitException("無(wú)法確定的Bean依賴哆姻,存在多個(gè)依賴矛缨!");
}
}
}
if(bean == null){
// 找不到依賴bean異常
log.error("無(wú)法找到Bean依賴帖旨,field:{}", beanObject.getClassName()+"."+field.getName());
throw new SquareBeanInitException("無(wú)法找到Bean依賴");
}
// 注入依賴
try {
field.setAccessible(true);
field.set(beanObject.getObject(), bean.getObject());
} catch (IllegalAccessException e) {
log.error("Bean注入失敗解阅,field:{}", beanObject.getClassName()+"."+field.getName(), e);
throw new SquareBeanInitException("Bean注入失敗");
}
}
}
}
}
/** * 判斷字段上加的注解是否需要做注入 */
private static boolean filterFieldAnnotation(Annotation[] annotations){
boolean b = false;
for (Annotation annotation : annotations) {
b = annotation instanceof Resource;
}
return b;
}
/** 獲取注入注解上指定的Bean名字 */
private static String getResourceName(Annotation[] annotations){
String name = null;
for (Annotation annotation : annotations) {
name = ((Resource)annotation).name();
}
return name;
}
/** 首字段轉(zhuǎn)大寫(xiě)*/
public static String firstToUpperCase(String str){
if(str == null || str.equals("")){
return str;
}
char f = str.charAt(0);
str = str.substring(1);
if(f>'Z'){
f = (char)(f-32);
}
return f+str;
}
/** 首字段轉(zhuǎn)大寫(xiě)*/
public static String firstToLowerCase(String str){
if(str == null || str.equals("")){
return str;
}
char f = str.charAt(0);
str = str.substring(1);
if(f<'a'){
f = (char)(f+32);
}
return f+str;
}
//....下略
看注釋就可以了述召,基本就是按照上邊說(shuō)的邏輯一步一步做的桨武,這里主要的關(guān)注點(diǎn)在呀酸,同一個(gè)接口有多個(gè)實(shí)現(xiàn)的時(shí)候琼梆,怎么去確認(rèn)要注入的bean茎杂,上邊代碼里使用的是如果有多個(gè)實(shí)例則按需要注入字段的名字去匹配煌往。
在BeansInitUtil.java的init()方法中添加調(diào)用initDI()刽脖。
//...上略
public static Map<String, BeanObject> init(Class clazz){
Map<String, BeanObject> beansMap = new HashMap<>();
String path = clazz.getResource("").getPath();
log.info("===bean init path:{}", path);
File root = new File(path);
// 處理控制反轉(zhuǎn)
initFile(root, beansMap);
// 處理依賴注入
initDI(beansMap);
return beansMap;
}
//...下略
04曲管、驗(yàn)證依賴注入
為了驗(yàn)證效果我們先在com.jisuye.service包下添加幾個(gè)類
ClassDI.java
package com.jisuye.service;
import com.jisuye.annotations.Service;
@Service
public class ClassDi {
public String exe(String name){
return "Class DI "+name;
}
}
Def.java
package com.jisuye.service;
public interface Def {
String exe(String name);
}
添加兩個(gè)Def接口實(shí)現(xiàn)類
DefImpl.java
package com.jisuye.service.impl;
import com.jisuye.annotations.Service;
import com.jisuye.service.Def;
@Service
public class DefImpl implements Def {
@Override
public String exe(String name) {
return "Interface DI... "+name;
}
}
Def2Impl.java
package com.jisuye.service.impl;
import com.jisuye.annotations.Service;
import com.jisuye.service.Def;
@Service
public class Def2Impl implements Def {
@Override
public String exe(String name) {
return "def2 "+name;
}
}
修改AbcImpl.java(上一篇?jiǎng)?chuàng)建的類) 添加需要注入的引入
package com.jisuye.service.impl;
//import ...
@Service
public class AbcImpl implements Abc {
// 名字對(duì)不上會(huì)報(bào)異常
@Resource
private Def defImpl;
// 名字對(duì)不上可以使用注解中指定bean名字的方式
@Resource(name = "def2Impl")
private Def defByName;
// 注入Class類實(shí)例
@Resource
private ClassDi classDi;
@Override
public int test(String name) {
System.out.println(defImpl.exe(name));
System.out.println(defByName.exe(name));
System.out.println(classDi.exe(name));
return 0;
}
}
在SquareApplication.run()方法中添加調(diào)用查看注入是否成功
public static void run(Class clzz, String[] args) {
try {
long startTime = System.currentTimeMillis();
classesPathUtil = new ClassesPathUtil(clzz);
// 加載配置
loadYaml(classesPathUtil.getProjectPath());
// 初始化參數(shù)
setArgs(args);
// 輸出banner
printBanner(classesPathUtil.getProjectPath());
Map<String, BeanObject> map = BeansInitUtil.init(clzz);
log.info("beans size is:{}", map.size());
//查看bean是否注入成功
Abc abc = (Abc)(map.get("com.jisuye.service.Abc").getObject());
abc.test("ixx");
tomcat = new Tomcat();
//....下略
查看控制臺(tái)輸出:
20:42:07.749 [main] INFO com.jisuye.core.SquareApplication - beans size is:10
Interface DI... ixx
def2 ixx
Class DI ixx
....
ok 可以看到對(duì)應(yīng)的屬性都已經(jīng)注入成功
05、翻車時(shí)間
重新思考一下撬腾,上一篇只考慮了同一接口的不同實(shí)現(xiàn)類民傻,會(huì)造成BeanObject覆蓋問(wèn)題饰潜,但其實(shí)按類型簡(jiǎn)稱(不帶包名)以及按注解上設(shè)置的Bean名字去初始化Bean的時(shí)候都會(huì)有覆蓋問(wèn)題彭雾,比如不同包下的相同的類锁保,或者在注解上設(shè)置了相同Bean名字的類,想一下要怎么處理呢者填?
本篇代碼地址: https://github.com/iuv/square/tree/square4
本文作者: ixx
本文鏈接: http://jianpage.com/2019/06/29/square4
版權(quán)聲明: 本作品采用 知識(shí)共享署名-非商業(yè)性使用-相同方式共享 4.0 國(guó)際許可協(xié)議 進(jìn)行許可做葵。轉(zhuǎn)載請(qǐng)注明出處酿矢!