手寫源碼(二):自己實現SpringIOC

NO IMAGE

手寫IOC

IOC控制反轉就是通過反射機制幫我們託管了所有的類。
我想要自己實現的就是使用XML注入Bean和使用註解(@Service之類的)注入Bean

Spring的Xml版本IOC原理

SpringIOC的XML版本使用Dom4j和反射技術解析XML和注入類
所有的Bean在ApplicationContext創建的時候就會初始化

XML版本注入

自行解析XML

一個自己解析XML的小Demo,使用Dom4j解析XML,如下

public class XmlUtils {
public static void main(String[] args) throws DocumentException {
XmlUtils xmlUtils = new XmlUtils();
xmlUtils.readXml("student.xml");
}
public void readXml(String xmlPath) throws DocumentException {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(getResourceAsSteam(xmlPath));
Element rootElement = document.getRootElement();
getNodes(rootElement);
}
private static void getNodes(Element rootElement) {
//獲取節點名稱
System.out.print("節點名稱:" + rootElement.getName()+"\t\t");
//獲取節點屬性
List<Attribute> attributes = rootElement.attributes();
for (Attribute attribute : attributes) {
System.out.print("屬性:"+attribute.getName()+"---"+attribute.getText()+"\t\t");
}
//獲取屬性值
String value = rootElement.getTextTrim();
if (!StringUtils.isEmpty(value)) {
System.out.print("節點值:" + value+"\t\t");
}
System.out.println();
//遍歷子節點
Iterator<Element> elementIterator = rootElement.elementIterator();
while (elementIterator.hasNext()) {
Element next = elementIterator.next();
getNodes(next);
}
}
private InputStream getResourceAsSteam(String xmlPath) {
return this.getClass().getClassLoader().getResourceAsStream(xmlPath);
}
}

自己實現XML獲取Bean的ApplicationContext

  • 實現步驟
    • 讀取配置XML
    • 查看傳入的BeanId和Xml中的BeanId是否一致
    • 使用反射創建對象並且返回
  • 按照上面的步驟實現自己的ApplicationContext

    核心方法代碼如下(這個getBean方法就是按照上面的步驟實現的,我把步驟的具體實現都抽取出去了)
    /** 用於GetBean的方法*/
public Object getBean(String beanId) throws DocumentException, IllegalAccessException, InstantiationException, ClassNotFoundException {
if (StringUtils.isEmpty(beanId)) {
throw new RuntimeException("BeanId為空");
}
//解析Xml,獲取所有節點
List<Element> elements = readXml();
if (elements == null||elements.isEmpty()) {
throw new RuntimeException("沒有任何Bean信息");
}
//查找對應的ClassName
String className = getClassName(beanId, elements);
if (StringUtils.isEmpty(className)) {
throw new RuntimeException("沒有配置類信息");
}
//利用反射機制創建Bean
return newInstance(className);
}

全文如下

public class ExtClassPathXmlApplicationContext {
private String xmlPath;
public ExtClassPathXmlApplicationContext(String xmlPath) {
this.xmlPath = xmlPath;
}
/** 用於GetBean的方法*/
public Object getBean(String beanId) throws DocumentException, IllegalAccessException, InstantiationException, ClassNotFoundException {
if (StringUtils.isEmpty(beanId)) {
throw new RuntimeException("BeanId為空");
}
//解析Xml,獲取所有節點
List<Element> elements = readXml();
if (elements == null||elements.isEmpty()) {
throw new RuntimeException("沒有任何Bean信息");
}
//查找對應的ClassName
String className = getClassName(beanId, elements);
if (StringUtils.isEmpty(className)) {
throw new RuntimeException("沒有配置類信息");
}
//利用反射機制創建Bean
return newInstance(className);
}
/**解析Xml文件,獲取所有節點*/
private List<Element> readXml() throws DocumentException {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(getResourceAsSteam());
Element rootElement = document.getRootElement();
List<Element> elements = rootElement.elements();
return elements;
}
}

然後在主方法裡創建上面的Context,使用getBean方法,就可以拿到想要的Bean了(和Spring的ClassPathApplicationContext一樣)

使用註解注入Bean

一些需要注意的性質

  • 需要把已知帶有註解的類裝入一個集合裡,便於隨時取用
  • 加載時才初始化上面的集合,還需要注意線程安全問題
  • 使用懶加載模式加載Bean
  • 只實現了單例,所有getBean都是創建了一個實例
  • 只實現了使用默認的beanId進行注入(類名第一個字母小寫),不能自定義ID

實現註解裝配Bean並且通過getBean方法獲取Bean

  • 實現步驟
    • 使用反射機制,掃包,獲取所有的類(使用了一個開源的掃包的工具類。。沒有自己實現)
    • 判斷每個類上是否有注入bean的註解
    • 使用反射機制進行初始化類
  • 按照上面的步驟實現自己的ApplicationContext

    核心代碼如下
    /**初始化Bean容器*/
private void initBeans() throws IllegalAccessException, InstantiationException {
beans = new ConcurrentHashMap<String, Object>();
//使用掃包工具獲得包下所有的類
List<Class<?>> classes = ClassUtils.getClasses(packageName);
//判斷所有的類上面是否有註解,有的話就會加入到Bean容器裡面去
findClassExistAnnotation(classes);
if (beans == null || beans.isEmpty()) {
throw new RuntimeException("沒有類加上了註解");
}
}

全文如下

public class ExtAnnotationApplicationContext {
private String packageName;
/**保存有Service註解的類*/
private ConcurrentHashMap<String, Object> beans = null;
public ExtAnnotationApplicationContext(String packageName) throws InstantiationException, IllegalAccessException {
this.packageName = packageName;
initBeans();
}
/**初始化Bean容器*/
private void initBeans() throws IllegalAccessException, InstantiationException {
beans = new ConcurrentHashMap<String, Object>();
//使用掃包工具獲得包下所有的類
List<Class<?>> classes = ClassUtils.getClasses(packageName);
//判斷所有的類上面是否有註解,有的話就會加入到Bean容器裡面去
findClassExistAnnotation(classes);
if (beans == null || beans.isEmpty()) {
throw new RuntimeException("沒有類加上了註解");
}
}
/**掃包,把有註解的類加入到bean容器裡*/
private void findClassExistAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException {
for (Class classInfo : classes) {
//判斷是否有註解
Annotation annotation = classInfo.getAnnotation(ExtService.class);
if (annotation != null) {
//到這裡表示有這個註解
String className = classInfo.getName();
//默認Id是首字母小寫
beans.put(toLowerCaseFirestOne(classInfo.getSimpleName()), newInstance(classInfo));
}
}
}
/**類名的首字母小寫*/
private String toLowerCaseFirestOne(String className) {
return new StringBuilder().append(Character.toLowerCase(className.charAt(0))).append(className.substring(1)).toString();
}
/**獲取Bean的方法*/
public Object getBean(String beanId) throws IllegalAccessException, InstantiationException {
if (StringUtils.isEmpty(beanId)) {
throw new RuntimeException("BeanID為空");
}
return beans.get(beanId);
}
/**利用反射機制創建Bean*/
private Object newInstance(Class classInfo) throws IllegalAccessException, InstantiationException {
if (classInfo == null) {
throw new RuntimeException("沒有這個ID的bean");
}
return classInfo.newInstance();
}
/**依賴注入傳入類的屬性*/
private void attrAssign(Class<?> classInfo) {
//獲取這個類所有的屬性
Field[] fields = classInfo.getFields();
//判斷當前屬性是否有註解
for (Field field : fields) {
ExtService extService = field.getAnnotation(ExtService.class);
if (extService != null) {
//到這裡說明這個屬性裡有這個註解
String fieldName = field.getName();
}
}
}
}

現在獲取到Bean只用在類上加上自己的Service註解然後使用getBean方法傳入類名的首字母小寫就可以了

實現自動裝配(依賴注入)

  • 自動裝配/依賴注入原理(實現步驟)
    • 使用反射機制獲取當前類的所有屬性
    • 判斷當前類是否存在註解
    • 使用默認名稱在Bean容器裡查找對象,然後賦值
  • 核心代碼
    /**自動注入注入這個對象的屬性*/
private void attrAssign(Object object) throws IllegalAccessException {
//獲取這個類所有的屬性
Field[] fields = object.getClass().getDeclaredFields();
//判斷當前屬性是否有註解
for (Field field : fields) {
ExtService extService = field.getAnnotation(ExtService.class);
if (extService != null) {
//到這裡說明這個屬性裡有這個註解,在從容器裡獲取對象然後給這個屬性賦值
String fieldName = field.getName();
Object target = beans.get(fieldName);
if (target == null) {
throw new RuntimeException("注入\"" + fieldName + "\"屬性失敗,bean容器裡沒有這個對象");
}
//允許訪問私有屬性
field.setAccessible(true);
//第一個參數是這個屬性所在的對象
field.set(object,target);
}
}
}

這個方法我們需要在Bean容器初始化完成之後,把所有的bean容器的Object裡做一遍,達到依賴注入的效果,如下(如果給所有的類都實現注入Bean容器裡的bean的話,就是依賴注入@Autowired了)

    public ExtAnnotationApplicationContext(String packageName) throws InstantiationException, IllegalAccessException {
this.packageName = packageName;
initBeans();
//在所有Bean容器裡所有bean自動注入所有的Bean
for (Map.Entry<String, Object> entry : beans.entrySet()) {
System.out.println("beanId:"+entry.getKey());
Object bean = entry.getValue();
attrAssign(bean);
}
}

增加了依賴注入的Context全文如下

public class ExtAnnotationApplicationContext {
private String packageName;
/**保存有Service註解的類*/
private ConcurrentHashMap<String, Object> beans = null;
public ExtAnnotationApplicationContext(String packageName) throws InstantiationException, IllegalAccessException {
this.packageName = packageName;
initBeans();
//在所有Bean容器裡所有bean自動注入所有的Bean
for (Map.Entry<String, Object> entry : beans.entrySet()) {
System.out.println("beanId:"+entry.getKey());
Object bean = entry.getValue();
attrAssign(bean);
}
}
/**初始化Bean容器*/
private void initBeans() throws IllegalAccessException, InstantiationException {
beans = new ConcurrentHashMap<String, Object>();
//使用掃包工具獲得包下所有的類
List<Class<?>> classes = ClassUtils.getClasses(packageName);
//判斷所有的類上面是否有註解,有的話就會加入到Bean容器裡面去
findClassExistAnnotation(classes);
if (beans == null || beans.isEmpty()) {
throw new RuntimeException("沒有類加上了註解");
}
}
/**掃包,把有註解的類加入到bean容器裡*/
private void findClassExistAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException {
for (Class classInfo : classes) {
//判斷是否有註解
Annotation annotation = classInfo.getAnnotation(ExtService.class);
if (annotation != null) {
//到這裡表示有這個註解
String className = classInfo.getName();
//默認Id是首字母小寫
beans.put(toLowerCaseFirestOne(classInfo.getSimpleName()), newInstance(classInfo));
}
}
}
/**類名的首字母小寫*/
private String toLowerCaseFirestOne(String className) {
return new StringBuilder().append(Character.toLowerCase(className.charAt(0))).append(className.substring(1)).toString();
}
/**獲取Bean的方法*/
public Object getBean(String beanId) throws IllegalAccessException, InstantiationException {
if (StringUtils.isEmpty(beanId)) {
throw new RuntimeException("BeanID為空");
}
return beans.get(beanId);
}
/**利用反射機制創建Bean*/
private Object newInstance(Class classInfo) throws IllegalAccessException, InstantiationException {
if (classInfo == null) {
throw new RuntimeException("沒有這個ID的bean");
}
return classInfo.newInstance();
}
/**自動注入注入這個對象的屬性*/
private void attrAssign(Object object) throws IllegalAccessException {
//獲取這個類所有的屬性
Field[] fields = object.getClass().getDeclaredFields();
//判斷當前屬性是否有註解
for (Field field : fields) {
ExtResource extResource = field.getAnnotation(ExtResource.class);
if (extResource != null) {
//允許訪問私有屬性
field.setAccessible(true);
//到這裡說明這個屬性裡有這個註解,在從容器裡獲取對象然後給這個屬性賦值
String fieldName = field.getName();
Object target = beans.get(fieldName);
if (target == null) {
throw new RuntimeException("注入\"" + fieldName + "\"屬性失敗,bean容器裡沒有這個對象");
}
//第一個參數是這個屬性所在的對象
field.set(object,target);
}
}
}
}

相關文章

團長大人的學習筆記——Hibernate

團長大人的學習筆記——自定義Java註解

手寫源碼(四):自己實現Mybatis

手寫源碼(三):自己實現SpringMVC