一 介绍
所谓IOC,说句大白话就是,不用我们自己new对象了,DI呢?就是对象里的属性能自动注入了,网上东扯西扯其实就这点事…
二 理解IOC
前后端交互的过程大致如下:
可以看到Service和Dao层,以及Dao层和queryRunner是相互依赖的,说白了Service这个类里面一定有一个属性是Dao层实现类的对象,而Dao层实现类里也一定有一个一个属性是queryRunner的对象,而queryRunner里面还会有dataSource,dataSource构造方法还需要四个基本的参数(Driver,url,user,password)
想象一下如果我们自己一次一次new 各种对象 比如Service里面的dao实现类有一天我们需要换一种技术更换一下实现类…再或者有一天我们需要更换一下数据库…再或者压根不用queryRunner了改Mybatis了等等等头疼的情况…我们怎么做?
而且有时候项目已经跑在服务器上了,而服务器上可能压根都没有编译环境,那我们怎么办,所以每一次我们不得不从服务器把代码拿回来,修改源码,编译,打成jar包,再上线回去…这个过程是很麻烦的.那么如果频繁这么操作呢?
显然我们必须通过一种不修改源码就能顺利改变依赖的方式,而这种思想就被成为IOC.而Spring的两大思想其中之一就是IOC思想
今天我们手写一下Spring freamwork中beanFactory看看IOC思想下的代码是怎样的
三 技术支撑
- xml解析 --DOM4J
- java反射
四 项目目录
我们主要研究beans.xml和BeanFactory,用这两个东西来得到各种实体类的对象,先来一个最简单的例子,我们就是向单纯的调用一下Student空参的构造方法在创建一个对象,那么.xml👇
<?xml version="1.0" encoding="GBK" ?>
<beans>
<bean id="stu" class="com.etoak.student.entity.Student">
</bean>
</beans>
beanFactory
public class BeanFactory {
public static Object getBean(String id){
Object target = null;
try {
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
//这里用到的是DOM4J解析xml,网上一搜一堆,下个jar包导进来就行
SAXReader reader = new SAXReader();
Document doc = reader.read(is);
//根元素
Element root = doc.getRootElement();
//获得根元素下的所有一级子元素
List<Element> firsts = root.elements("bean");//拿到所有的一级元素了
for(Element first:firsts){
//id属性值
String current_id = first.attributeValue("id");
if(current_id.equals(id)){
String className = first.attributeValue("class");
Class clazz = Class.forName(className);//反射
target = clazz.newInstance();//构造对象
}
}
}catch(Exception e){
e.printStackTrace();
}
return target;
}
}
测试代码👇
Student stu = (Student)BeanFactory.getBean("stu");//执行查看stu是否有地址
说明我们的工厂初步是可以使用的,我们继续添加功能,倘若有个别情况我需要单例模式开发呢?这个学生对象可以重复被拿过来使用吗?
我们改改xml代码 加上一个scope
<?xml version="1.0" encoding="GBK" ?>
<beans>
<bean id="stu" class="com.etoak.student.entity.Student" scope="singleton">
</bean>
</beans>
哎,看看工厂,首先需要一个Map集合存放已有的对象
public class BeanFactory {
private static Map<String,Object> objectMap = new ConcurrentHashMap<>();
public static Object getBean(String id){
Object target = null;
try {
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
//这里用到的是DOM4J解析xml,网上一搜一堆,下个jar包导进来就行
SAXReader reader = new SAXReader();
Document doc = reader.read(is);
//根元素
Element root = doc.getRootElement();
//获得根元素下的所有一级子元素
List<Element> firsts = root.elements("bean");//拿到所有的一级元素了
for(Element first:firsts){
//id属性值
String current_id = first.attributeValue("id");
if(current_id.equals(id)){
String className = first.attributeValue("class");
//获得scope属性值
String scope = first.attributeValue("scope");
if("singleton".equals(scope)){
//需要一个对象
target = objectMap.get(className);
}else{
Class clazz =class.forName(className);
target=clazz.newInstance()
objectMap.put(className, target);
}
}
}
}catch(Exception e){
e.printStackTrace();
}
return target;
}
}
测试代码
Student stu = (Student)BeanFactory.getBean("stu");
Student stu1 = (Student)BeanFactory.getBean("stu");
System.out.println(stu==stu1);//true
总而言之单例模式加个scope开关就解决了,我们再继续,那么一个类里面有其他实体类对象怎么办呢?哎这就引入了,Service和dao层的关系了,能否解耦合呢?我们看一下,这次我们直接上Service来说事儿
xml👇
<?xml version="1.0" encoding="GBK" ?>
<beans>
<bean id="stu" class="com.etoak.student.entity.Student" scope="prototype">
<property name="name" value="etoak"></property>
</bean>
<bean id="service" class="com.etoak.student.service.StudentService">
<property name="dao" >
<ref beanid="dao"></ref>
</property>
</bean>
<bean id="dao" class="com.etoak.student.dao.StudentDaoImpl">
<property name="qr" >
<ref beanid="qr"></ref>
</property>
</bean>
<bean id="qr" class="org.apache.commons.dbutils.QueryRunner">
<!--不加该标签使用无参构造方法,加上该标签 需要调用带参构造方法-->
<constructor-args type="javax.sql.DataSource" ref="ds"></constructor-args>
</bean>
<bean id="ds" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/et2009"></property>
<property name="username" value="root"></property>
<property name="password" value="etoak"></property>
</bean>
</beans>
这里的意思是
beanFactory
//BeanFactory 专门负责对外提供对象
public class BeanFactory {
//存放对象的
private static Map<String,Object> objectMap = new ConcurrentHashMap<>();
public static Object getBean(String id){
Object target = null;
try {
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
//DOM4J
SAXReader reader = new SAXReader();
Document doc = reader.read(is);
//根元素
Element root = doc.getRootElement();
//获得根元素下的所有一级子元素
List<Element> firsts = root.elements("bean");
for(Element first:firsts){
//id属性值
String current_id = first.attributeValue("id");
//当前遍历到的id的值 匹配传入的id的值,说明就是该类的对象
if(current_id.equals(id)){
//获得类名字
String className = first.attributeValue("class");
//获得scope属性值
String scope = first.attributeValue("scope");
if("singleton".equals(scope)){
//需要一个对象
target = objectMap.get(className);
}else {
if (null == className) {
return null;
}
Class clazz = Class.forName(className);
//试图寻找构造方法参数如果有 则使用带参数的构造方法来构造对象
List<Element> cons_args= first.elements("constructor-args");
if(cons_args!=null && cons_args.size()>0){
//这俩数组为下面的Coustructor服务需要参数类型和参数
Class[] parameterTypes = new Class[cons_args.size()];
Object[] parameters = new Object[cons_args.size()];
//遍历<constructor-arg>标签
int k=0;
for(Element ca:cons_args){
//构造方法参数类型
String typeName = ca.attributeValue("type");
String ref = ca.attributeValue("ref");
Object obj = getBean(ref);
parameterTypes[k] = Class.forName(typeName);
parameters[k] = obj;
k++;
}
Constructor c = clazz.getDeclaredConstructor(parameterTypes);
target = c.newInstance(parameters);
}else {
//构造对象
target = clazz.newInstance();
//获得当前匹配到的<bean>元素下的<property>元素
List<Element> props = first.elements("property");
for(Element pro:props){
//属性名字
String proName = pro.attributeValue("name");
//属性类型
Class fieldType = clazz.getDeclaredField(proName).getType();
//属性对应的setter方法的名字
String setterName = "set"+proName.substring(0,1)
.toUpperCase()+proName.substring(1);
//setter方法
Method setter = clazz.getDeclaredMethod(setterName,fieldType);
//判断<property>元素是否有子元素
if(pro.isTextOnly()) { //没有子元素
//属性值
String value = pro.attributeValue("value");
//执行
setter.invoke(target, value);
}else{
//有子元素 <ref>
Element third = (Element)pro.elements().get(0);
//引用其他属性 1.首先获得其他id对应的对象 2.给当前对象中的属性赋值
if("ref".equals(third.getName())){
String beanid = third.attributeValue("beanid");
Object obj = getBean(beanid);
setter.invoke(target,obj);
}
}
}
}
objectMap.put(className, target);
}
}
}
}catch(Exception e){
e.printStackTrace();
}
return target;
}
}
我们beanFactory这个工厂就基本可以对标Spring里的哪个IOC的意思,那么刚上来我们哪个图就变成这个模式👇
而倘若我们需要改里面注入的依赖,我们直接修改xml配置文件就可以了,源码完全不需要修改,这也就免了重新编译运行的麻烦,这也就是IOC思想下的开发模式
结论
Spring之所以能十几年不衰,是有它自己的独到之处的,我们需要看看人家好的思想来提高自己的水平,多思考,原创不易,喜欢系的点赞+关注哦~后续我会持续说一些Spring底层的东西