模拟实现Spring中的注解装配
本文原创,地址为http://www.cnblogs.com/fengzheng/p/5037359.html
在Spring中,XML文件中的bean配置是实现Spring IOC的核心配置文件,在早版本的Spring中,只能基于XML配置文件,配置各个对象之间的依赖关系。在Spring 2.5以后出现了注解,使用注解结合XML的方式,简化了XML配置的复杂度。
老版本中纯XML配置实现IOC
在配置文件中配置如下:
<bean id="userDao" class="com.springapp.mvc.dao.UserDao">
</bean>
<bean id="userService" class="com.springapp.mvc.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
UserServiceImpl的实现如下:
public class UserServiceImpl implements UserService {
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
private UserDao userDao;
public User getUserById(int id){
return userDao.getUserById(id);
}
public int getUserCount(){
return userDao.getUserCount();
}
}
配置的意思是:<property name="userDao" ref="userDao"></property>这行配置是为UserServiceImpl类中的userDao指定userDao这个bean,这样在UserServiceImpl类中调用userDao的方法,其实就是调用com.springapp.mvc.dao.UserDao的方法。
结合注解的实现方式
配置文件简化如下:
<bean id="userDao" class="com.springapp.mvc.dao.UserDao">
</bean>
<bean id="userService" class="com.springapp.mvc.service.impl.UserServiceImpl">
</bean>
UserServiceImpl的实现如下:
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
public User getUserById(int id){
return userDao.getUserById(id);
}
public int getUserCount(){
return userDao.getUserCount();
}
}
利用@Autowired注解,实现在xml中<property name="userDao" ref="userDao"></property>的配置,从而实现了自动注入。@Autowired自动注入的规则为byType,意思就是按照被注解字段的类型和xml中配置的bean的类型相匹配,即在UserServiceImpl 类中的userDao为UserDao类型,匹配的时候会在所有bean中查找类型同样为UserDao的bean。
那么既然是按照类型匹配,如果存在两个相同类型的bean呢,这时候,就会启用第二个匹配规则ByName,即根据字段的名字来匹配相同id的bean。如下XML配置:
<bean id="userDao1" class="com.springapp.mvc.dao.UserDao">
</bean> <bean id="userDao2" class="com.springapp.mvc.dao.UserDao">
</bean> <bean id="userService" class="com.springapp.mvc.service.impl.UserServiceImpl">
</bean>
那么在UserServiceImpl类中应该使用以下这种方式来自动注入:
@Autowired
private UserDao userDao1; @Autowired
private UserDao userDao2;
这样好像不是很灵活的样子,看起来有些不爽,有没有办法可以指定要匹配的bean呢?没错,是有的。可以使用这样的方式,通过@Autowired和@Qualifier相结合的方式,@Qualifier后跟的参数就是bean的名称:
@Autowired
@Qualifier("userDao1")
private UserDao userDao;
还有更常用的方式,@Resource:
@Resource(name = "userDao1")
private UserDao userDao;
关于注解IOC的内容可以参看这篇文章,写的很详细。
注解在Spring中的用法讲完了,下面来自己实习一个简单的类,来模拟Spring利用注解实现IOC的原理。
Spring IOC实现原理
1.首先Spring根据bean配置文件,收集所有bean的实例;
2.Spring根据配置文件中的context:component-scan,扫描需要被注入的包(递归包中的所有待注入类);
3.扫描待注入类时,判断是否有特定的注解(例如@Autowired、@Resource),如果有,则进行第4步,注入;
4.注入:根据注解类型或参数,利用反射,为被注解的字段或属性等设置对应的bean实例。
以上是我个人理解,可能和Spring真正的实现有些出入。
模拟利用注解实现注入
这里要定义一个类似于@Resource的注解,命名为@MyAutowired,定义如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD})
@Documented
public @interface MyAutowired {
public String name() default ""; public String value() default "";
}
定义配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:context="http://www.springframework.org/schema/context">
<context:component-scan id="test" class="fengzheng.Test"/>
<bean id="tomoto" class="fengzheng.Tomoto"></bean>
</beans>
其中bean和Spring中bean的定义是一样的,而context:component-scan在Spring是定义属性base-package,之后根据这个属性,扫描这个包下的所有类,这里为做演示,也定义为一个类,之后会根据这个class属性,对这个类进行注入。
配置文件中的tomoto bean的定义:
package fengzheng;
public class Tomoto {
public void SayHello(){
System.out.println("hello I'm tomoto");
}
}
配置文件中fengzheng.Test类定义,这个即为要被注入的类:
package fengzheng;
import fengzheng.fzAnnotation.MyAutowired;
public class Test {
@MyAutowired(name = "tomoto")
private Tomoto tomoto;
public void Say(){
tomoto.SayHello();
}
}
核心注解分析并实现注入的类:
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map; import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader; import fengzheng.fzAnnotation.MyAutowired; import java.util.stream.*; public class FzClassPathXMLApplication { //xml配置文件中 bean定义的集合
private List<BeanDefine> beanList = new ArrayList<BeanDefine>(); // 存储bean实例和bean的id的对应关系 以便可以根据注解名称找到对应的实例
Map<String, Object> beanInstanceList = new HashMap<String, Object>(); //xml配置文件中 被扫描类的定义的集合 在Spring框架中 直接扫描一个或多个包
List<ScanDefine> scanList = new ArrayList<ScanDefine>(); // 存储被扫描的待注入的实体集合
Map<String, Object> annotationInstanceList = new HashMap<String, Object>(); public FzClassPathXMLApplication(String xmlName) {
ReadXml(xmlName); //实例化所有定义的bean
BeanInstance(); //实例化所有的待注入类
ScanClassInstance(); //开始根据注解实现依赖注入
InjectAnnotation();
} /**
* 读取配置文件 收集bean集合和待注入类的集合
* @param xmlFileName
*/
public void ReadXml(String xmlFileName) {
URL xmlPath = this.getClass().getClassLoader().getResource(xmlFileName);
System.out.println(xmlPath);
SAXReader reader = new SAXReader();
try {
Document dom = reader.read(xmlPath);
Element root = dom.getRootElement();
List<Element> iters = root.elements();
Stream<Element> beans = iters.stream().filter(bean -> bean.getName().equals("bean"));
Iterator<Element> iterBeans = beans.iterator(); while (iterBeans.hasNext()) {
Element bean = iterBeans.next();
String id = bean.attributeValue("id");
String clsName = bean.attributeValue("class");
//System.out.println("id:" + id + "\nclass:" + clsName);
BeanDefine bd = new BeanDefine();
bd.setId(id);
bd.setClsName(clsName);
beanList.add(bd);
} Stream<Element> scanClasses = iters.stream().filter(scan -> scan.getName().equals("component-scan"));
//iters.stream().forEach(scan -> System.out.println(scan.getName()));
Iterator<Element> iterScans = scanClasses.iterator(); while (iterScans.hasNext()) {
Element scan = iterScans.next();
String id = scan.attributeValue("id");
String clsName = scan.attributeValue("class");
ScanDefine sd = new ScanDefine();
sd.setId(id);
sd.setClassName(clsName);
scanList.add(sd);
}
System.out.println("scanList.size():"+scanList.size());
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 收集bean实例
*/
private void BeanInstance() {
for (BeanDefine bd : beanList) {
try {
Object beanInstance = Class.forName(bd.getClsName()).newInstance();
System.out.println(beanInstance.getClass().getName());
beanInstanceList.put(bd.getId(), beanInstance);
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* 收集被扫描的待注入的类的实例
*/
private void ScanClassInstance(){
for(ScanDefine sd:scanList){
try {
Object scanInstance = Class.forName(sd.getClassName()).newInstance();
System.out.println(scanInstance.getClass().getName());
annotationInstanceList.put(sd.getId(), scanInstance);
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* 循环遍历待注入的类
*/
public void InjectAnnotation() {
Iterator<Map.Entry<String, Object>> iters = annotationInstanceList.entrySet().iterator();
while (iters.hasNext()) {
Map.Entry<String, Object> iter = iters.next();
Object scanInstance = iter.getValue();
InjectField(scanInstance);
} } /**
* 注入:把需要注入类中的注解为MyAutowired的字段 注入bean实例
* @param injectClass
*/
private void InjectField(Object injectClass) {
try {
Field[] fields = injectClass.getClass().getDeclaredFields(); for (Field field : fields) { if (field != null && field.isAnnotationPresent(MyAutowired.class)) {
System.out.println(field.getName());
MyAutowired myAutowired = field.getAnnotation(MyAutowired.class);
String beanName = myAutowired.name(); Object value = null;
if (beanName != null && !beanName.equals("")) {
value = beanInstanceList.get(beanName);
} else {
Class<?> fType = field.getType();
for (String key : beanInstanceList.keySet()) {
if (fType.isAssignableFrom(beanInstanceList.get(key).getClass())) {
value = beanInstanceList.get(key);
break;
}
}
}
field.setAccessible(true);
field.set(injectClass, value);
} }
} catch (Exception e) {
e.printStackTrace();
}
} public Object getScan(String scanName){
return this.annotationInstanceList.get(scanName);
}
}
注解处理类中用到的两个实体类:
package fengzheng.simpleSpring;
public class BeanDefine {
private String id;
private String clsName;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClsName() {
return clsName;
}
public void setClsName(String clsName) {
this.clsName = clsName;
}
}
package fengzheng.simpleSpring;
public class ScanDefine {
public String id;
public String className;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
对这段程序逻辑的解释:
1.首先通过ReadXml()方法读取配置文件beans.xml,找到其中的bean节点和context:component-scan节点,然后把bean节点实例化为BeanDefine,并加入beanList集合,把component-scan节点实例化为ScanDefine,并加入scanList集合;
2.通过BeanInstance()方法,把配置文件中的bean都实例化存储到Map集合beanInstanceList中;
3.通过ScanClassInstance()方法,把配置文件中的component-scan节点(即待注入的类)实例化并处处到Map集合annotationInstanceList中;
4.通过InjectAnnotation()方法,遍历annotationInstanceList集合,为其中的被@MyAutowired注解的字段赋值(即对应的bean的实例)
最后调用实现如下:
FzClassPathXMLApplication ctx = new FzClassPathXMLApplication("beans.xml");
Test test =(Test) ctx.getScan("test");
test.Say();
输出结果:

模拟实现Spring中的注解装配的更多相关文章
- 如何在 spring 中启动注解装配?
默认情况下,Spring 容器中未打开注解装配.因此,要使用基于注解装配,我们 必须通过配置 <context:annotation-config/> 元素在 Spring 配置文件 中启 ...
- Spring中@Autowired注解、@Resource注解的区别 (zz)
Spring中@Autowired注解.@Resource注解的区别 Spring不但支持自己定义的@Autowired注解,还支持几个由JSR-250规范定义的注解,它们分别是@Resource.@ ...
- Spring中常用注解的介绍
spring中使用注解时配置文件的写法: <?xml version="1.0" encoding="UTF-8"?> <span style ...
- Spring中Value注解的使用
Spring中Value注解的使用 分类: Spring2014-08-16 17:28 2985人阅读 评论(0) 收藏 举报 有的时候我们定义了Properties文件,并且使用Spring的Pr ...
- 全面解析Spring中@ModelAttribute注解的用法
本文不再更新,可能存在内容过时的情况,实时更新请移步我的新博客:全面解析Spring中@ModelAttribute注解的用法: @ModelAttribute注解用于将方法的参数或方法的返回值绑定到 ...
- EnableAutoConfiguration注解 Spring中@Import注解的作用和使用
EnableAutoConfiguration注解 http://www.51gjie.com/javaweb/1046.html springboot@EnableAutoConfiguration ...
- Spring中使用注解时启用<context:component-scan/>
在spring中使用注解方式时需要在spring配置文件中配置组件扫描器:http://blog.csdn.net/j080624/article/details/56277315 <conte ...
- Spring中异步注解@Async的使用、原理及使用时可能导致的问题
前言 其实最近都在研究事务相关的内容,之所以写这么一篇文章是因为前面写了一篇关于循环依赖的文章: <面试必杀技,讲一讲Spring中的循环依赖> 然后,很多同学碰到了下面这个问题,添加了S ...
- Spring中@Import注解的使用
Spring中@Import注解的使用 @Import注解算是SpringBoot自动配置原理中一个很重要的注解 认识@Import注解 先看一下源码 @Target(ElementType.TYPE ...
随机推荐
- 分布式系列文章——从ACID到CAP/BASE
事务 事务的定义: 事务(Transaction)是由一系列对系统中数据进行访问与更新的操作所组成的一个程序执行逻辑单元(Unit),狭义上的事务特指数据库事务. 事务的作用: 当多个应用程序并发访问 ...
- AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking
AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...
- 【NLP】揭秘马尔可夫模型神秘面纱系列文章(一)
初识马尔可夫和马尔可夫链 作者:白宁超 2016年7月10日20:34:20 摘要:最早接触马尔可夫模型的定义源于吴军先生<数学之美>一书,起初觉得深奥难懂且无什么用场.直到学习自然语言处 ...
- bzoj4724--数论
题目大意: B进制数,每个数字i(i=0,1,...,B-1)有a[i]个.你要用这些数字组成一个最大的B进制数X(不能有前导零,不需要 用完所有数字),使得X是B-1的倍数.q次询问,每次询问X在B ...
- 手把手教你做个人 app
我们都知道,开发一个app很大程度依赖服务端:服务端提供接口数据,然后我们展示:另外,开发一个app,还需要美工协助切图.没了接口,没了美工,app似乎只能做成单机版或工具类app,真的是这样的吗?先 ...
- DockerCon 2016 – 微软带来了什么?
根据Forrester的调查,接近半数的企业CIO在考虑IT架构的时候更乐于接受开源方案,这主要是基于低成本,避免供应商锁定和敏捷的需求:同时另外一家North Bridge的调研机构的调查显示,20 ...
- BZOJ 3504: [Cqoi2014]危桥 [最大流]
3504: [Cqoi2014]危桥 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1407 Solved: 703[Submit][Status] ...
- 二叉树的创建和遍历(C版和java版)
以这颗树为例:#表示空节点前序遍历(根->左->右)为:ABD##E##C#F## 中序遍历(左->根->右)为:#D#B#E#A#C#F# 后序遍历(左->右-> ...
- JavaScript
2015-08-01 16:20 JavaScript使用时需要注意的地方 1.引入JS的位置:最好的做法是把<script>的标签放到HTML文档的最后.</body>标签之 ...
- 一步步开发自己的博客 .NET版(4、文章发布功能)百度编辑器
前言 这次开发的博客主要功能或特点: 第一:可以兼容各终端,特别是手机端. 第二:到时会用到大量html5,炫啊. 第三:导入博客园的精华文章,并做分类.(不要封我) 第四:做个插件,任何网站上的技术 ...