IoC的概念是控制反转html
谁控制谁,控制什么:传统Java SE程序设计,咱们直接在对象内部经过new进行建立对象,是程序主动去建立依赖对象;而IoC是有专门一个容器来建立这些对象,即由Ioc容器来控制对象的建立;谁控制谁?固然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不仅是对象包括好比文件等)。java
为什么是反转,哪些方面反转了:有反转就有正转,传统应用程序是由咱们本身在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙建立及注入依赖对象;为什么是反转?由于由容器帮咱们查找及注入依赖对象,对象只是被动的接受依赖对象,因此是反转;哪些方面反转了?依赖对象的获取被反转了。git
传统应用程序示意图 有IoC容器后程序结构示意图spring
该部分来源于 https://www.cnblogs.com/NancyStartOnce/p/6813162.htmlapache
此处就再也不介绍更多知识,能够本身检索相关知识点。app
这里有两种方式实现IoC,基于XML配置文件和注解方式。dom
基本步骤:maven
a.定义xml或者注解描述对象的依赖关系。ide
b.经过程序解析xml或者注解。工具
c.经过反射建立须要实例化的对象,并放到容器中存储。
1.建立maven工程并加入解析xml的依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.tgb.myspring</groupId> <artifactId>myspring</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <!-- 解析xml的工具 --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> </dependencies> <build> <sourceDirectory>src</sourceDirectory> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
1.建立须要注入的测试类BeanA、BeanB。此处先贴一下工程结构,结尾处也有下载地址。
BeanA代码
public class BeanA { private BeanB beanB; public BeanB getBeanB() { return beanB; } public void setBeanB(BeanB beanB) { this.beanB = beanB; } }
BeanB代码
package com.lzy.ioc.test; import com.lzy.ioc.annotation.Bean; public class BeanB { private String name ; public String getName() { return name; } public void setName(String name) { this.name = name; } }
2.建立applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean name="beanA" class="com.lzy.ioc.test.BeanA"> <property name="beanB" ref="beanB"></property> </bean> <bean name="beanB" class="com.lzy.ioc.test.BeanB"> <property name ="name" value="lzy"></property> </bean> </beans>
这个xml文件很简单,只是描述出了BeanA和BeanB的关系。
3.建立Bean和Property两个类用于描述xml文件
package com.lzy.ioc.config; import java.util.ArrayList; import java.util.List; /** * 描述bean的基本信息 * <bean name="B" class="com.lzy.ioc"> <property name ="a" ref="A"></property> * </bean> 主要有name 、 class、 property * * @author admin * */ public class Bean { /** bean名称 */ private String name; /** 类的全路径包名 */ private String className; /** 类的全路径包名 */ private List<Property> properties = new ArrayList<>(); //get/set本身生产 }
package com.lzy.ioc.config; /** * bean 的属性值 <property name ="name" value="zhangsan" ref="bean2"></property> * * @author admin * */ public class Property { /**名称*/ private String name; /**值*/ private String value; /**引用 */ private String ref; //get/set本身生产 }
4.建立ConfigManager类解析这个xml文件
package com.lzy.ioc.config.parse; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import com.lzy.ioc.config.Bean; import com.lzy.ioc.config.Property; /** * 读取配置文件,返回Bean * * @author admin * */ public class ConfigManager { /** * 将配置文件转换成Bean返回 * * @param path * @return */ public static Map<String, Bean> getConfig(String path){ //存储解析出来的bean Map<String, Bean> beanMap = new HashMap<>(); // 1.建立解析器 SAXReader saxReader = new SAXReader(); // 2.读取配置 文件 InputStream is = ConfigManager.class.getResourceAsStream(path); Document document = null; try { document = saxReader.read(is); } catch (DocumentException e) { e.printStackTrace(); throw new RuntimeException("请检查xml配置文件"); } // 3.定义xpath表达式,用于匹配全部bean String xpath = "//bean"; // 4.从document中找出全部的bean元素 List<Element> beanNodes = document.selectNodes(xpath); if(beanNodes != null){ for (Element beanNode : beanNodes) { Bean bean = new Bean(); // 将xml中的bean描述信息封装到自定义的Bean对象中 String name = beanNode.attributeValue("name"); String className = beanNode.attributeValue("class"); bean.setName(name); bean.setClassName(className); // 将xml中的property封装到bean中 List<Element> children = beanNode.elements("property"); if(children != null){ for (Element child : children) { // 获取xml中的Property属性的信息,并封装到Property对象中 Property property = new Property(); String pName = child.attributeValue("name"); String pValue = child.attributeValue("value"); String pRef = child.attributeValue("ref"); property.setName(pName); property.setRef(pRef); property.setValue(pValue); // 将property封装到bean对象中 bean.getProperties().add(property); } // 将bean存储到beanMap中 beanMap.put(name, bean); } } } return beanMap; } }
5.通过以上步骤咱们已经将xml转化成咱们能处理的java对象,如今就能够建立Bean工厂了,这里 咱们须要先定义一个接口和抽象类,并实现xml模式的实现类。
接口AbstraBeanFactory.java
package com.lzy.ioc.main; /** * bean 工厂类 * * 只负责bean的基本管理,不负责bean 的建立 * * @author admin * */ public interface BeanFactory { /** * 根据bean的名称获取bean * * @param beanName * @return */ Object getBean(String beanName); }
抽象类AbstraBeanFactory
package com.lzy.ioc.main; import java.util.HashMap; import java.util.Map; public abstract class AbstraBeanFactory implements BeanFactory{ /** bean 容器,用于存储建立的bean */ protected Map<String,Object> context = new HashMap<>(); @Override public Object getBean(String beanName) { return context.get(beanName); } }
咱们的xml Bean工厂实现类
package com.lzy.ioc.main; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import com.lzy.ioc.config.Bean; import com.lzy.ioc.config.Property; import com.lzy.ioc.config.parse.ConfigManager; import com.lzy.ioc.utils.BeanUtils; /** * * 使用xml 方式注入bean * * @author admin * */ public class ClassPathXmlApplicationContext extends AbstraBeanFactory { //配置信息 private Map<String, Bean> config; public ClassPathXmlApplicationContext(String path) { // 1.读取配置信息,将xml转化成com.lzy.ioc.config.Bean对象 config = ConfigManager.getConfig(path); // 2.遍历出全部的com.lzy.ioc.config.Bean,根据信息建立实例 if(config != null){ for(Entry<String, Bean> en :config.entrySet()){ // 获取bean描述信息 String beanName = en.getKey(); Bean bean = en.getValue(); // 判断该bean是否已经存在,存在就再也不建立,不存在就继续建立 Object object = context.get(beanName); if(Objects.isNull(object)){ // 建立须要注入的bean Object creatBean = creatBean(bean); // 放入到容器中 context.put(beanName, creatBean); } } } } /** * 根据Bean对象的描述信息建立 注入到ioc容器的对象实例 * * 实现:获取bean中的className,根据反射实例化该对象 * * @param bean * @return */ private Object creatBean(Bean bean) { // 1.获取class的路径 String className = bean.getClassName(); Class clazz = null; try { clazz = Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new RuntimeException("请检查bean的Class配置" + className); } // 2.根据clazz建立实例对象 Object beanObj = null; try { beanObj = clazz.newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); throw new RuntimeException("bean没有空参构造"+className); } // 3.为建立的对象填充数据 if(Objects.nonNull(bean.getProperties())){ for (Property property : bean.getProperties()) { // 1.基本类型value注入 // 获取注入的属性名称 String name = property.getName(); // 根据属性名称获取对应的set方法 Method setMethod = BeanUtils.getWriteMethod(beanObj, name); Object parm = null; if(Objects.nonNull(property.getValue())){ // 获取注入的属性值 String value = property.getValue(); parm = value; } // 2.若是是复杂对象的注入 if(Objects.nonNull(property.getRef())){ // 先从当前容器中获取,看是否已经被建立 Object exsiBean = context.get(property.getRef()); if(Objects.isNull(exsiBean)){ // 建立bean并放到容器中 exsiBean = creatBean(config.get(property.getRef())); context.put(property.getRef(), exsiBean); } parm = exsiBean; } // 3.调用set方法将值设置到对象中 try { setMethod.invoke(beanObj, parm); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); throw new RuntimeException("bean的属性"+parm+"没有对应的set方法,或者参数不正确"+className); } } } return beanObj; } }
这里用到了BeanUtils工具类,我也先贴一下
package com.lzy.ioc.utils; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * 根据对象和属性获取set方法 * * @author admin * */ public class BeanUtils { /** * 获取set方法 * * @param beanObj set方法所属的类 * @param name set方法字段 * @return */ public static Method getWriteMethod(Object beanObj, String name) { Method method = null; try { // 1.分析bean对象 -->BeanInfo BeanInfo beanInfo = Introspector.getBeanInfo(beanObj.getClass()); // 2.根据beaninfo获取全部属性的描述器 PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); // 3.遍历属性器 if (pds != null) { for (PropertyDescriptor pd : pds) { // 获取当前属性 String pName = pd.getName(); if (pName.equals(name)) { // 获取set方法 method = pd.getWriteMethod(); } } } } catch (IntrospectionException e) { e.printStackTrace(); } if (method == null) { throw new RuntimeException("请检查" + name + "属性的set方法是否建立"); } return method; } /** * 经过对象直接给属性赋值,不经过set方法 * * @param bean set方法所属的类 * @param fieldName 属性字段名 * @param value 注入的值 * @throws IllegalArgumentException * @throws IllegalAccessException * @throws SecurityException * @throws NoSuchFieldException */ public static void setValue(Object bean, String fieldName, Object value) throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException { Field privateVar = bean.getClass().getDeclaredField(fieldName); privateVar.setAccessible(true); privateVar.set(bean, value); } /** * 不经过get方法获取属性值 * * * @param bean get方法所属的类 * @param fieldName 属性字段名 * @return * @throws IllegalArgumentException * @throws IllegalAccessException * @throws SecurityException * @throws NoSuchFieldException */ public static Object getValue(Object bean, String fieldName) throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException { Field privateVar = bean.getClass().getDeclaredField(fieldName); privateVar.setAccessible(true); return privateVar.get(bean); } }
通过以上步骤咱们就完成了一个很是简单的基于xml配置文件的ioc容器也就是Bean工厂,如今来测试一下
package com.lzy.ioc.test; import com.lzy.ioc.main.AnnotationApplicationContext; import com.lzy.ioc.main.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 测试注解模式 //AnnotationApplicationContextTest(); // 测试配置文件模式 ClassPathXmlApplicationContextTest(); } /** * Annotation 方式注入 * */ /*public static void AnnotationApplicationContextTest(){ AnnotationApplicationContext ioc = new AnnotationApplicationContext("com.lzy.ioc"); BeanB b = (BeanB) ioc.getBean("beanB"); System.out.println(b.toString()); }*/ /** * XML 方式注入 * */ public static void ClassPathXmlApplicationContextTest(){ String path = "applicationContext.xml"; ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext(path); BeanA a = (BeanA) ioc.getBean("beanA"); System.out.println(a.getBeanB().getName()); } }
这里能够输出name的值就说明注入成功了,更详细的测试可使用debug模式查看ClassPathXmlApplicationContext ioc的Map<String,Object> context容器,这里面存储了全部的bean。
注解模式相比xml模式更加简洁,开发过程当中就能完成依赖的配置,不用切换到xml配置文件。
1.定义两个注解Bean、Autowired,被Bean注解的类表示要添加到IoC容器中,被Autowired注解的表示须要自动注入值。
package com.lzy.ioc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 标记为须要扫描实例化的类 * * @author admin * */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Bean { }
package com.lzy.ioc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 标记 为须要自动注入 * * @author admin * */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { }
2.将注解添加到测试的Bean上,既是BeanA、BeanB上。
package com.lzy.ioc.test; import com.lzy.ioc.annotation.Bean; @Bean public class BeanB { private String name ; public String getName() { return name; } public void setName(String name) { this.name = name; } }
package com.lzy.ioc.test; import com.lzy.ioc.annotation.Autowired; import com.lzy.ioc.annotation.Bean; @Bean public class BeanA { @Autowired private BeanB beanB; public BeanB getBeanB() { return beanB; } public void setBeanB(BeanB beanB) { this.beanB = beanB; } }
这样添加注解的意思是容器须要把BeanA和BeanB都实例化到容器,而且把BeanB注入到BeanA的属性上。
3.建立注解模式的IOC容器 AnnotationApplicationContext
package com.lzy.ioc.main; import java.io.File; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.Set; import com.lzy.ioc.annotation.Autowired; import com.lzy.ioc.annotation.Bean; import com.lzy.ioc.utils.BeanUtils; /** * bean 工厂,使用注解注入bean的bean工厂 * * * @author admin * */ public class AnnotationApplicationContext extends AbstraBeanFactory { /** * 初始化bean工厂 * * @param packageName */ public AnnotationApplicationContext(String packageName) { // 1.获取扫描根包的实际路径 scanPackage(packageName); // 为容器内的bean注入属性 injectionField(); } /** * 扫描给的包下的类,并实例化到容器中,此处只实例化,不作属性注入 * * @param packageName */ public void scanPackage(String packageName){ String currentpath = ClassLoader.getSystemResource("").getPath(); String filePath = currentpath + (packageName.replace(".", "\\")); List<String> annotationClasses = new ArrayList<>(); getAnnotationClassName(filePath, annotationClasses); for (String path : annotationClasses) { Class<?> clazz = null; try { clazz = Class.forName(path); } catch (ClassNotFoundException e) { e.printStackTrace(); } // 获取类上面的全部注解 Annotation[] annotations = clazz.getAnnotations(); for (Annotation annotation : annotations) { if (annotation instanceof Bean) { Object newInstance = null; try { // 实例化Bean,并将类的首字母小写的名称做为实例化后的bean名称 newInstance = clazz.newInstance(); String beanName = path.substring(path.lastIndexOf(".") + 1); char firstChar = Character.toLowerCase(beanName.charAt(0)); String replaceFirst = beanName.replaceFirst(beanName.substring(0, 1), Character.toString(firstChar)); context.put(replaceFirst, newInstance); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } } } } } /** * 向容器中注入属性 * 使用的set方法 注入的,全部必须包含set方法 * */ public void injectionField(){ Set<Entry<String, Object>> beans = context.entrySet(); for (Entry<String, Object> beanEntry : beans) { Object bean = beanEntry.getValue(); String beanName = beanEntry.getKey(); // 获取须要注入的属性 Field[] declaredFields = bean.getClass().getDeclaredFields(); for (Field field : declaredFields) { Annotation[] fieldAnnotions = field.getAnnotations(); // 处理字段上的注入注解 for (Annotation fieldAnnotation : fieldAnnotions) { String fieldName = field.getName(); if(fieldAnnotation instanceof Autowired){ // 判断容器中是否有该bean if(context.containsKey(fieldName)){ // 将容器中的值经过set方法注入到bean中 /* //这里使用的是 set方法注入 Method getMethod = BeanUtils.getWriteMethod(bean, fieldName); try { getMethod.invoke(bean, context.get(fieldName)); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); }*/ // 不在依赖属性的set方法为属性注入值 try { BeanUtils.setValue(bean, fieldName, context.get(fieldName)); } catch (IllegalArgumentException | IllegalAccessException | SecurityException | NoSuchFieldException e) { e.printStackTrace(); } } }else{ // 容器中没有该值,须要建立该bean // TODO throw new RuntimeException("请检查"+ beanName + "类中的" + fieldName + "字段是否已经注入到容器中"); } } break; } } } /** * 获取路径下的全部全类名 * * @param filePat * @param annotationClasses */ public void getAnnotationClassName(String filePath, List<String> annotationClasses) { String currentpath = ClassLoader.getSystemResource("").getPath(); File file = new File(filePath); File[] childFiles = file.listFiles(); for (File childFile : childFiles) { if (childFile.isDirectory()) { // 目录 getAnnotationClassName(childFile.getPath(), annotationClasses); } else { // 文件 String childPath = childFile.getPath(); if (childPath.endsWith(".class")) { // 是class文件 String packageNameOfClass = childPath.substring(currentpath.length() - 1, childPath.length() - 6) .replace("\\", "."); annotationClasses.add(packageNameOfClass); } } } } }
4.建立测试代码,咱们能够经过debug模式查看容器内部是否已经建立了被注解加持的对象。
package com.lzy.ioc.test; import com.lzy.ioc.main.AnnotationApplicationContext; import com.lzy.ioc.main.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 测试注解模式 AnnotationApplicationContextTest(); // 测试配置文件模式 //ClassPathXmlApplicationContextTest(); } /** * Annotation 方式注入 * */ public static void AnnotationApplicationContextTest(){ AnnotationApplicationContext ioc = new AnnotationApplicationContext("com.lzy.ioc"); BeanB b = (BeanB) ioc.getBean("beanB"); System.out.println(b.toString()); } /** * XML 方式注入 * */ public static void ClassPathXmlApplicationContextTest(){ String path = "applicationContext.xml"; ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext(path); BeanA a = (BeanA) ioc.getBean("beanA"); //System.out.println(a.getBeanB().getName()); } }
至此咱们的两种简单IOC就实现完成了,最后贴一下码云的代码地址:
https://gitee.com/liubluesnow/MyIOC