解析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. a标签的download属性

    a标签加上downlaod属性后,就可完成对href属性链接文件的下载,但仅仅是限于同源文件,如果是非同源,download属性会失效. 无download属性的时候,a标签的默认行为是链接跳转进行预 ...

  2. 日志:slf4j+logback 的配置与使用

    1. 常用日志组件和选择 java开发日志处理是发现和调试bug所 必不可少的,那么现在企业中常用的日志组件有哪些呢,JCL . JUL.  SLF4j.Log4j.  Log4j2 . Logbac ...

  3. golang time json mongodb 时间处理

    golang 中解决前端time 输出,后端mongodb中时间存储. package mask import ( "fmt" "time" "go. ...

  4. Mysql补充部分:SQL逻辑查询语句执行顺序

    一 SELECT语句关键字的定义顺序 SELECT DISTINCT <select_list> FROM <left_table> <join_type> JOI ...

  5. On Java 8

    On Java 8本书原作者为 [美] Bruce Eckel,即<Java 编程思想>的作者.本书是事实上的 <Java 编程思想>第五版.<Java 编程思想> ...

  6. 动画学习之Music图形绘制

    今天来实现一个类似于网易云音乐类似的动态效果,在用网易云音乐听歌时会有一个类似这样的效果,如下: 而咱们这次要实现的效果如下: music图形的绘制: 在实现动画之前先来将静态的图形绘制出来, 如下: ...

  7. sql 创建数据库并对数据库更改排序规则

    use master -- 设置当前数据库为master,以便访问sysdatabases表 go if exists(select * from sysdatabases where name='t ...

  8. JavaScript Array -->map()、filter()、reduce()、forEach()函数的使用

    题目: 1.得到 3000 到 3500 之内工资的人. 2.增加一个年龄的字段,并且计算其年龄. 3.打印出每个人的所在城市 4.计算所有人的工资的总和. 测试数据: function getDat ...

  9. iOS设置UITableViewCell的选中时的颜色

    1.系统默认的颜色设置 //无色   cell.selectionStyle = UITableViewCellSelectionStyleNone;     //蓝色   cell.selectio ...

  10. Python入门提示

    Python入门提示——行与缩进,常量定义 在初学Python时,除了学习一些标准的语法外,会遇到一些其他困难,列举如下. 一.python http://www.xuanhe.net/的行与缩进 P ...