从山寨Spring中学习Spring IOC原理-byType自动装配

2024-01-10 10:08

本文主要是介绍从山寨Spring中学习Spring IOC原理-byType自动装配,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

之前的两篇博客【从山寨Spring中学习Spring IOC原理-XML-Setter】和【从山寨Spring中学习Spring IOC原理-XML-Constructor】我们模拟了Spring框架下的手动装配。但是总所周知自动装配才是Spring的精髓,所以既然要山寨就不可能少了这一部份。一般来说Spring的自动装配只需要在字段声明的地方加一个@Autowired就可以了,但是在xml下,需要配置byName或者byType的方式去开启。既不需要构造方法,也不需要Setter方法。我们这一篇就是山寨一个Spring的bytype的自动装配过程,为了博客看起来比较方便,讲解的内容部分会穿插代码片,而完整的代码会贴到最后。更多Spring内容进入【Spring解读系列目录】。

准备内容

还是准备一个事务接口UserDao,一个事务实现类UserDaoImpl,一个事务干扰类UserDaoImpl2,一个业务接口UserService,一个业务实现UserServiceImpl。

public interface UserDao {public void query();
}
public class UserDaoImpl implements UserDao{@Overridepublic void query() {System.out.println("UserDaoImpl query 1");}
}
public class UserDaoImpl2 implements UserDao{@Overridepublic void query() {System.out.println("UserDaoImpl query 2");}
}
public interface UserService {public void find();
}
public class UserServiceImpl implements UserService {private UserDao userDao;@Overridepublic void find() {System.out.println("UserServiceImpl find()");userDao.query();}
}

思路

那么我们现在想一下,如果要完成自动装配有哪些问题。首先就是依赖,因为xml已经无法提供依赖了,所以我们要自动取判断依赖的情况。其次是报错,如果xml如果描述的和真实的情况不符需要针对这种错误抛出异常。最后添加自动装配标签,Spring的default-autowire是配置在<beans>标签里的因此,我们也需要在root标签的位置去判断是否有这个标签。直接再原来的基础上添加代码,后面就要考虑如何取到类中的依赖了。

//检查是不是配了自动装配
Attribute attributeAuto=elementRoot.attribute("default-autowire");
boolean flag=false; //自动装配标志,如果为true说明配置了
if(!Objects.isNull(attributeAuto)){flag=true;
}

如何判断依赖

在我们之前的手动装配的模拟程序中,我们看一个对象有没有依赖要根据两个情况:

  1. 第一看xml中标签没有子标签,因为有子标签就意味着有依赖。
  2. 第二看类中有没有依赖。

但是如果开始了自动装配,我们就可以假设<bean>标签里永远没有子标签。所以当前能够判断一个类有没有依赖的主要依据,就是这个类中有没有属性。如果有属性,就说明有依赖,需要程序进行注入。所以不管是哪个类都需要我们去判断有没有属性。如果发现有依赖就需要去map中找到对应的类型,然后给类中的字段赋值完成注入。

//如果直接走到这里说明没有子标签,但是又加了default-autowire
if(flag){if(attributeAuto.getValue().equals("byType")){//判断是否有依赖,如果有依赖,就byType的注入,因此先要拿到类里的属性Field[] fields=clazz.getDeclaredFields();//如果有依赖就要从map中找到对应类型for (Field field : fields) {//拿到要注入的属性类型Class injectObjClass=field.getType();//拿到属性以后遍历map,找到injectObjClass对应的属性类型for (String key: map.keySet()) {//从map中拿出对应类型的名字,并且和injectObjClass的名字对比if (map.get(key).getClass().getInterfaces()[0].getName().equals(injectObjClass.getName())){injectObject=map.get(key);//拿出对象}}object=clazz.newInstance(); //new出实例对象field.setAccessible(true);field.set(object,injectObject); //注入}}
}

模拟报错

依赖做好了,但是Spring中有一个很常见的报错:如果发现两个类有了同一个依赖就会报错expected single matching bean but found 2。我们这里也可以把这个错误模拟出来,如果发现有两个实例同时对一个类进行注入,我们也报错。所以构建一个我们自己的报错类ConflictException,如果报错就抛出这个异常。然后修改代码。

public class ConflictException extends RuntimeException{public ConflictException(String message) {super(message);}
}
//如果直接走到这里说明没有子标签,但是又加了default-autowire
if(flag){if(attributeAuto.getValue().equals("byType")){//判断是否有依赖,如果有依赖,就byType的注入,因此先要拿到类里的属性Field[] fields=clazz.getDeclaredFields();int count=0;Object injectObject=null;//如果有依赖就要从map中找到对应类型for (Field field : fields) {...}if (count>1){//说明我们找到两个类同时想要注入,Spring中会抛出异常,我们这里也抛一个异常出去throw new ConflictException("我们自己的异常:expected single matching bean but found 2");}else {object=clazz.newInstance(); //new出实例对象field.setAccessible(true);field.set(object,injectObject); //注入}}}
}

测试可用

首先我们看下我们写的异常有没有其作用,配置UserDaoImplUserDaoImpl2冲突,daodao2同时匹配UserServiceImpl里面的UserDao属性,所以一定要报错。运行测试类Test:

<beans default-autowire="byType"><bean id="dao" class="com.demo.dao.UserDaoImpl"></bean><bean id="dao2" class="com.demo.dao.UserDaoImpl2"></bean><bean id="service" class="com.demo.service.UserServiceImpl"></bean>
</beans>
public class Test {public static void main(String[] args) {BeanFactoryAuto beanFactoryAuto=new BeanFactoryAuto("spring-auto.xml");UserService service= (UserService) beanFactoryAuto.getBean("service");service.find();}
}
运行结果:
com.demo.exception.ConflictException: 我们自己的异常:expected single matching bean but found 2at com.demo.beanfactory.BeanFactoryAuto.parseXml(BeanFactoryAuto.java:123)at com.demo.beanfactory.BeanFactoryAuto.<init>(BeanFactoryAuto.java:27)at com.demo.test.Test.main(Test.java:25)
Exception in thread "main" java.lang.NullPointerExceptionat com.demo.test.Test.main(Test.java:27)

然后看下正常情况下,有没有给我们自动注入。打印成功,说明我们的程序已经完成了这个注入,正常执行了逻辑。

<beans default-autowire="byType"><bean id="dao" class="com.demo.dao.UserDaoImpl"></bean><!--<bean id="dao2" class="com.demo.dao.UserDaoImpl2"></bean>--><bean id="service" class="com.demo.service.UserServiceImpl"></bean>
</beans>
运行结果:
UserServiceImpl find()
UserDaoImpl query 1

为了更彻底的验证,我们重新创建一个逻辑SecondDao和SecondDaoImpl,让UserServiceImpl去依赖这个接口,再运行看看。

public interface SecondDao {public void query();
}
public class SecondDaoImpl implements SecondDao{@Overridepublic void query() {System.out.println("SecondDaoImpl query()");}
}
public class UserServiceImpl implements UserService {private SecondDao userDao;@Overridepublic void find() {System.out.println("UserServiceImpl find()");userDao.query();}
}
<beans default-autowire="byType"><bean id="dao3" class="com.demo.dao.SecondDaoImpl"></bean><bean id="service" class="com.demo.service.UserServiceImpl"></bean>
</beans>
运行结果:
UserServiceImpl find()
SecondDaoImpl query()

一样完美的适配了,那么到这里一个简易版的byType自动注入就基本完成了。

注入优先权

自动装配的优先权是低于手动装配的。所以如果在xml中配置了<property>或者<constructor-arg>的参数,自动装配的效果应该是可以被屏蔽掉的,这里要怎么做呢。其实很简单,我们当前已经把前面两篇的程序组合在一起了,所以如果我们通过任意一种手动装配必然会给当前对象赋值,也就是object不会为null,所以只要在if(flag)前面加上判断就可以屏蔽自动装配的效果。修改一下xml运行打印正常。如果这里自动装配起作用了,一定会报我们的自定义异常的。

for (Iterator<Element> itSecond = elementFirstChild.elementIterator(); itSecond.hasNext();) {if (elementSecondChild.getName().equals("property")){object=clazz.newInstance();  //手动装配property赋值}else if (elementSecondChild.getName().equals("constructor-arg")){object=constructor.newInstance(injectObj); //手动装配构造参数赋值}
}
if (Objects.isNull(object)){ //屏蔽自动装配if(flag){...}
}
<beans default-autowire="byType"><bean id="dao" class="com.demo.dao.UserDaoImpl"></bean><bean id="service" class="com.demo.service.UserServiceImpl"><property name="userDao" ref="dao"></property></bean>
</beans>
运行结果,自动装配被屏蔽了,如果起作用报错:single matching bean but found 2
UserServiceImpl find()
UserDaoImpl query 1

总结

本篇博客完成了对Spring IOC自动注入byType模式的模拟。至于byName的模式是根据属性名来的,其实这个已经在【从山寨Spring中学习Spring IOC原理-XML-Setter】这篇中有过涉及(因为笔者偷懒,本来应该解析setter方法名的,直接解析属性名了),大家可以自己模拟或者改改笔者的程序,模拟出来这个例子。其实不止这里,因为笔者的代码只能算一个不完整的框架,如果有兴趣大家可以根据这个小框架修改,比如xml里面配置了<property>但是类里面没有要抛出异常,等等。下一篇【从山寨Spring中学习Spring IOC原理-自动装配注解】我们就去写一个有关Spring的自动注解的类,以了解在注解模式下,Spring是怎么找到那些类并实例化的。

附完整代码

这个BeanFactoryAuto自动装配的类已经整合了手动装配。

public class BeanFactoryAuto {//map存放类的实例Map<String, Object> map=new HashMap<>();/*** 工厂方法* @param xml*/public BeanFactoryAuto(String xml) {parseXml(xml);}/*** 解析* @param xml*/public void parseXml(String xml){//拿到根路径String path=this.getClass().getResource("/").getPath()+xml;File file=new File(path);SAXReader reader = new SAXReader();try {Document document = reader.read(file);Element elementRoot=document.getRootElement();//检查是不是配了自动装配Attribute attributeAuto=elementRoot.attribute("default-autowire");boolean flag=false; //自动装配标志,如果为true说明配置了if(!Objects.isNull(attributeAuto)){flag=true;}for (Iterator<Element> itFirst = elementRoot.elementIterator(); itFirst.hasNext();) {Element elementFirstChild = itFirst.next();Attribute attributeId=elementFirstChild.attribute("id");String beanName=attributeId.getValue();Attribute attributeClass=elementFirstChild.attribute("class");String clazzName=attributeClass.getValue();Class clazz=Class.forName(clazzName);Object object=null;for (Iterator<Element> itSecond = elementFirstChild.elementIterator(); itSecond.hasNext();) {Element elementSecondChild =itSecond.next();if (elementSecondChild.getName().equals("property")){ //手动装配property逻辑object=clazz.newInstance();Object injectObj=map.get(elementSecondChild.attribute("ref").getValue());String nameValue=elementSecondChild.attribute("name").getValue();Field field=clazz.getDeclaredField(nameValue);field.setAccessible(true);field.set(object,injectObj);}else if (elementSecondChild.getName().equals("constructor-arg")){ //手动装配构造方法逻辑String refValue=elementSecondChild.attribute("ref").getValue();Object injectObj=map.get(refValue);Class injectObjClazz=injectObj.getClass();String nameValue=elementSecondChild.attribute("name").getValue();Field field=clazz.getDeclaredField(nameValue);Constructor constructor=clazz.getConstructor(field.getType());object=constructor.newInstance(injectObj);}}if (Objects.isNull(object)){ //屏蔽自动装配//如果直接走到这里说明没有子标签,但是又加了default-autowireif(flag){if(attributeAuto.getValue().equals("byType")){//判断是否有依赖,如果有依赖,就byType的注入,因此先要拿到类里的属性Field[] fields=clazz.getDeclaredFields();int count=0;Object injectObject=null;//如果有依赖就要从map中找到对应类型for (Field field : fields) {//拿到要注入的属性类型Class injectObjClass=field.getType();//拿到属性以后遍历map,找到injectObjClass对应的属性类型for (String key: map.keySet()) {//从map中拿出对应类型的名字,并且和injectObjClass的名字对比if (map.get(key).getClass().getInterfaces()[0].getName().equals(injectObjClass.getName())){//每找到一个就记录一次,这里模仿的就是Spring注入冲突count++;injectObject=map.get(key);}}if (count>1){//说明我们找到两个类同时想要注入,Spring中会抛出异常,我们这里也抛一个异常出去throw new ConflictException("我们自己的异常:expected single matching bean but found 2");}else {object=clazz.newInstance(); //new出实例对象field.setAccessible(true);field.set(object,injectObject);}}}}}if(object==null){//没有子标签,意味着没有依赖所以new出来object=clazz.newInstance();}map.put(beanName,object);}System.out.println(map.toString());} catch (Exception e) {e.printStackTrace();}}/*** 获取对应的bean* @param bean* @return*/public Object getBean(String bean){return map.get(bean);}
}

这篇关于从山寨Spring中学习Spring IOC原理-byType自动装配的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/590429

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06