解析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. pytorch中的torch.repeat()函数与numpy.tile()

    repeat(*sizes) → Tensor Repeats this tensor along the specified dimensions. Unlike expand(), this fu ...

  2. JavaScript里面9种数组遍历!

    ​大家好,我在这里总结分享了JavaScript中的闹腾的数组循环家族. 1.大家最常用的for循环,我就不解释了: for(let i = 0; i < 5 ; i++){ console.l ...

  3. postgres使用pg_ctl 命令

    想要用pg_ctl等一系列的命令,需要配置环境变量: PATH=$PATH:$HOME/.local/bin:$HOME/bin:/usr/local/pgsql/binexport PGDATA=/ ...

  4. docker常用命令与容器创建

    ################docker安装##################### Docker从1.13版本之后采用时间线的方式作为版本号,分为社区版CE和企业版EE. 社区版是免费提供给个 ...

  5. Dubbo 04 服务化最佳实现流程

    Dubbo 04 服务化最佳实践 分包 建议将服务接口.服务模型.服务异常等均放在 API 包中,因为服务模型和异常也是 API 的一部分,这样做也符合分包原则:重用发布等价原则(REP),共同重用原 ...

  6. 洛谷P3370 && 字符串哈希讲解

    字符串哈希 寻找长度为n的主串s中的的匹配串T(长度为m)出现的位置或者次数问题属于字符串匹配问题. 朴素(一般)的想法就是从一个字符串的头开始for循环查找,当查找的一个字符与匹配串首字符相同时,往 ...

  7. Kendo UI for jQuery使用教程:初始化jQuery插件

    [Kendo UI for jQuery最新试用版下载] Kendo UI目前最新提供Kendo UI for jQuery.Kendo UI for Angular.Kendo UI Support ...

  8. 使用Vue做个简单的评论 + localstorage存储

     1.引入Vue.js 2.编写代码 代码 <!DOCTYPE html> <html lang="zh"> <head> <meta c ...

  9. pandas的dataframe与spark的dataframe

  10. 【Winform-右下角弹窗】实现右下角弹窗,提示信息

    网页是否经常在电脑右下角弹窗显示消息?其实Winform也是可以实现的.下面介绍两种方法. 第一步:设计窗体 第二步:实现代码 第一种方法 引用user32 声明常量 窗体Load事件 窗体FormC ...