解析Spring的IoC容器基于注解实现的自动装配(自动注入依赖)的原理

1.本文案例
使用注解和反射机制来模拟Spring中IoC的自动装配功能
定义两个注解:@Component,用来标注组件;@Autowired,用来标记需要被织入的属性。
定义一个@Component注解处理器,用来扫描所有组件。
定义一个bean工厂,用来实例化组件。
测试:有两个组件,一个组件被设置到另一个组件的属性中。

2.定义注解
2.1.定义@Component注解
这个注解表示被标注的就是一个组件,将会被容器自动扫描并创建实例

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
public String id();
}

注解的定义有点类似于接口的定义

注解定义

public @interface Component {
}

接口定义

public interface Component {
}

区别只是在于interface这个标识符前面有没有@符号。

并且注解的定义,还需要使用到几个原生注解:

@Target(ElementType.TYPE)

这个注解表明自定义的注解Component是用来标记谁的,其中ElementType.TYPE表示这个注解使用来标记类型的,也就是可以标记类、接口等。此外还有FIELD、METHOD等,分别表示用来标记字段、方法等。

@Retention(RetentionPolicy.RUNTIME)

表示这个自定义的注解需要保留到什么时候,如只保留到源码中,编译之后就没有了;或者保留到运行时,就是在运行的时候也一直有。这里设置为运行时。

然后这个注解中有这样一行:

public String id();

有点类似于接口中方法的声明,不过在注解中,这个表示注解的一个属性,后面用到的时候可以看看是怎么使用的,就明白了。

2.2.定义 @Autowired注解
这个注解是一个针对成员变量的注解,使用这个注解则表示,这个字段需要由程序来为其赋值的。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowire {
public String id(); }

3.定义 Beanfactory(也就是注解处理器)
自定义注解完之后,实际上并没有什么用处。要想让注解发挥用处,重点在于注解处理器。
首先来明确下这个处理器干了那些事情,首先根据给定的组件的包名,扫描这个包,找出其中所有的被@Component注解标注的类,将类型的信息保存下来。
然后提供一个getBean()方法,允许根据bean的id来获取bean。
接下来看看这个BeanFactory是如何编写的。

3.1.BeanFactory.java

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
 
public class BeanFactory {
 
private HashMap<String, Object> beanPool;
private HashMap<String, String> components; public BeanFactory(String packageName) {
beanPool = new HashMap<>(); scanComponents(packageName);
} private void scanComponents(String packageName) {
components = ComponentScanner
.getComponentClassName(packageName);
} public Object getBean(String id) throws ClassNotFoundException,
InstantiationException, IllegalAccessException,
NoSuchMethodException, SecurityException,
IllegalArgumentException, InvocationTargetException { if (beanPool.containsKey(id)) {
return beanPool.get(id);
} if (components.containsKey(id)) { Object bean = Class.forName(components.get(id))
.newInstance(); bean = assemblyMember(bean); beanPool.put(id, bean); return getBean(id);
} throw new ClassNotFoundException();
} private Object assemblyMember(Object obj) throws
ClassNotFoundException, InstantiationException,
IllegalAccessException, NoSuchMethodException,
SecurityException, IllegalArgumentException,
InvocationTargetException { Class cl = obj.getClass(); for (Field f : cl.getDeclaredFields()) {
Autowire at = f.getAnnotation(Autowire.class); if (at != null) {
 
Method setMethod = cl.getMethod("set"
+ captureName(f.getName()), f.getType()); setMethod.invoke(obj, getBean(at.id()));
}
}
return obj;
} public static String captureName(String name) {
char[] cs=name.toCharArray();
cs[0]-=32;
return String.valueOf(cs);
} }

3.2.ComponentScann.java
这个BeanFactory在构造函数中使用到了一个类,用来扫描出一个包中所有的类的信息。

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
 
public class ComponentScanner {
 
public static HashMap<String, String> getComponentClassName(
String packageName) {
List<String> classes = getClassName(packageName);
HashMap<String, String> components = new HashMap<String, String>(); try { for (String cl : classes) {
cl = cl.replace("workspace_java.LearningJava.bin.", ""); Component comp = Class.forName(cl).getAnnotation(Component.class); if (comp != null) {
components.put(comp.id(), cl);
}
} } catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} return components;
} public static List<String> getClassName(String packageName) {
String filePath = ClassLoader.getSystemResource("").getPath()
+ packageName.replace(".", "\\");
List<String> fileNames = getClassName(filePath, null);
return fileNames;
} private static List<String> getClassName(String filePath
, List<String> className) {
List<String> myClassName = new ArrayList<String>();
File file = new File(filePath);
File[] childFiles = file.listFiles();
for (File childFile : childFiles) {
if (childFile.isDirectory()) {
myClassName.addAll(getClassName(childFile.getPath()
, myClassName));
} else {
String childFilePath = childFile.getPath();
childFilePath = childFilePath.substring(childFilePath
.indexOf("\\classes") + 9, childFilePath.lastIndexOf("."));
childFilePath = childFilePath.replace("\\", ".");
myClassName.add(childFilePath);
}
} return myClassName;
} public static void main(String[] args) {
getComponentClassName("com.oolong.javase.annotation");
} }

4.测试

定义一个模拟的数据库访问接口

@Component(id = "dataAccessInterface")
public class DataAccessInterface {
 
public String queryFromTableA() {
return "query result";
}
}

这个类使用了Component这个注解,并且注意,这里使用了这个注解的id属性。

定义一个模拟的业务接口

@Component(id="businessObject")
public class BusinessObject {
 
@Autowire(id="dataAccessInterface")
private DataAccessInterface dai; public void print() {
System.out.println(dai.queryFromTableA());
} public void setDai(DataAccessInterface dai) {
this.dai = dai;
}
}

这个接口除了使用@Component这个注解标注之外,还有个成员变量,使用了Autowire这个注解标注。使用这个注解标注,表示这个成员变量的初始化将会交给BeanFactory来进行。

测试

public class BeanFactoryTester {
 
public static void main(String[] args) {
BeanFactory beanFactory = new BeanFactory("com.oolong.javase.annotation"); BusinessObject obj = (BusinessObject) beanFactory.getBean("businessObject");
obj.print(); } }

这里使用BeanFactory创建了一个BusinessObject的对象之后,调用这个对象的print方法,最终打印出来一个结果。

而回到这个类的定义中,可以看到:

public void print() {
System.out.println(dai.queryFromTableA());
}

这个方法调用的是成员变量dai的queryFromTableA方法。而在这个类中,只有这个成员变量的声明,而没有赋值。

这个赋值又是在哪里进行的呢?

这个就是有我们编写的这个BeanFactory执行的。通过注解和反射机制,自动为类注入依赖。

Spring——原理解析-利用反射和注解模拟IoC的自动装配的更多相关文章

  1. Spring原理解析-利用反射和注解模拟IoC的自动装配

  2. Java反射学习总结终(使用反射和注解模拟JUnit单元测试框架)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 本文是Java反射学习总结系列的最后一篇了,这里贴出之前文章的链接,有兴趣的可以打开看看. ...

  3. IDEA02 利用Maven创建Web项目、为Web应用添加Spring框架支持、bean的创建于获取、利用注解配置Bean、自动装配Bean、MVC配置

    1 环境版本说明 Jdk : 1.8 Maven : 3.5 IDEA : 专业版 2017.2 2 环境准备 2.1 Maven安装及其配置 2.2 Tomcat安装及其配置 3 详细步骤 3.1 ...

  4. Spring注解驱动开发(三)-----自动装配

    自动装配 概念 Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值. @Autowired-----自动注入 1.默认优先按照类型去容器中找对应的组件 application ...

  5. Java——利用反射机制将表单数据自动填充到JavaBean中

    以一个案例介绍反射机制的一种常见的使用场景,以及具体实现. 1.本文案例 在编写Java Web应用程序时,使用表单提交数据是一个必不可少的环节,后台对于前台使用表单提交的数据需要能够从请求中解析,并 ...

  6. Spring详解(四)------注解配置IOC、DI

    Annotation(注解)是JDK1.5及以后版本引入的.它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查.注解是以‘@注解名’在代码中存在的. 前面讲解 IOC 和 DI 都是通过 ...

  7. 不懂Ribbon原理的可以进来看看哦,分析SpringBoot自动装配完成了Ribbon哪些核心操作

      前面详细的给大家介绍了SpringBoot的核心内容,有了这部分的基础支持的话,我们再来分析SpringCloud中的相关组件就很容器了,本文我们来给大家开始介绍Ribbon的相关内容,首先来介绍 ...

  8. Spring学习笔记 5. 尚硅谷_佟刚_Spring_自动装配

    1,回顾以前的做法 一个人有姓名,有住址,有一辆车.其中住址和车也是一个类,这种情况下不用自动装配是十分容易实现的 (1)Person类 package com.zsq; public class P ...

  9. 第四章 Spring.Net 如何管理您的类___对象的自动装配

    由于这几天都比较忙,所以对笔记暂时没有更新. Spring.NET具有自动装配的能力,也就是说,Spring.NET可以通过对象的定义自动分辨某个对象的协作对象.自动装配是针对单个对象(按:针对每个协 ...

随机推荐

  1. 中文转拼音,pinyin4j实用示例

    Pinyin4j是一个流行的Java库,支持中文字符和拼音之间的转换.拼音输出格式可以定制. Support Chinese character (both Simplified and Trandi ...

  2. Delphi 数组与记录类型

  3. 2019-2020-1 20199319《Linux内核原理与分析》第五周作业

    系统调用的三层机制(上) 基础知识 1.通过库函数的方式进行系统调用,库函数用来把系统调用给封装起来. 2.CPU有四种不同的执行级别:0.1.2.3,数字越小,特权越高.Linux操作系统中采用了0 ...

  4. deployment控制pod进行滚动更新以及回滚

    更新pod镜像两种方式: 方式一:kubectl set image deployment/${deployment name} ${container name}=${image} 例: kubec ...

  5. zabbix 添加 host item

    Zabbix常用术语 host(主机):监控的网络设备,可由IP或DNS名称指定. host Group(主机组):Host的逻辑容器,可以包含主机和模板. Item(监控项):一个特定监控指标的相关 ...

  6. python中import cv2遇到的错误及安装方法

    参考链接:https://blog.csdn.net/yuanlulu/article/details/79017116 从x86_64 + ubuntu18.04 + python3.5中impor ...

  7. Efficient Estimation of Word Representations in Vector Space (2013)论文要点

    论文链接:https://arxiv.org/pdf/1301.3781.pdf 参考: A Neural Probabilistic Language Model (2003)论文要点  https ...

  8. Java 实现《编译原理》简单词法分析功能 - 程序解析

    Java 实现<编译原理>简单词法分析功能 - 程序解析 简易词法分析功能 要求及功能 (1)读取一个 txt 程序文件(最后的 # 作为结束标志,不可省去) { int a, b; a ...

  9. git中working tree, index, commit

    这三个名字可以简单理解为文件在本地仓库存在的三种不同的位置. 如下,是做commit提交两段提交过程,工作区(working tree),暂存区(index)和 branch(commit). wor ...

  10. Floyed(floyd)算法详解

    是真懂还是假懂? Floyed算法:是最短路径算法可以说是最慢的一个. 原理:O(n^3)的for循环,对每一个中间节点k做松弛(寻找更短路径): 但它适合算多源最短路径,即任意两点间的距离. 但sp ...